Audit F2: the PDF placeholder was padded with underscores embedded directly
in the /URI path, so Acrobat fetched /c/<id>____________ and the chi route
extracted the padded id. Lookup returned ErrNotFound, the webbug fallback
served a 1x1 GIF, and event recording skipped because tok was nil. PDF
tokens silently never recorded events.
Part B (durable): pad inside a ?p= query string so the trigger path stays
canonical and chi extracts the real id. Part A (defense in depth): handler
TrimRight on _ so PDFs already in the field also resolve correctly. Added
two regression tests: byte-after-id assertion in the PDF artifact, and the
handler's trim path.
Both audits flagged buildPage as a non-blocking but worth-cleaning nit:
it re-derived next_cursor from len(events) and the last event's ID
rather than using the HasMore + NextCursor flags that
event.Repository.ListByToken already computes via the LIMIT+1 peek
trick. Both calculations agreed today, but the duplication would
mask a divergence if the repo's pagination logic ever evolved
(e.g. switched to opaque token cursors).
Inline buildPage into gatherManageData and use list.HasMore +
list.NextCursor as the authoritative signals. Drops the buildPage
helper. Behavior identical; tests still pass.
Adds the HTTP layer for spec §8.4. The UUID in the URL is the
capability — no other auth required, by design.
- token/dto.go: ManageTokenView (slimmer than Response — no manage_id,
no manage_url, no metadata, since the user is already on the manage
page and those would be redundant), ManagePage{NextCursor, HasMore},
ManageResponse{Token, Events, EventsTotal, EventsSilencedActive, Page}.
Token.ToManageView(triggerURL) builder. Events use the existing
event.Response (already matches spec shape exactly).
- token/handler.go: EventQuery + DedupCounter interfaces declared near
the existing EventRecorder + FingerprintRecorder; same dependency-
injection style. NewHandler signature gains the two new args (5 → 6;
test sites + main.go updated).
- GetManage(w, r): parses ?cursor= (int64, must be >= 0) and ?limit=
(defaults 20, capped at 100). Returns 404 envelope for unknown
manage_id (envelope, not bare http.NotFound, so frontend gets a
parseable error code). 400 BAD_CURSOR for non-numeric or negative.
Calls svc.GetByManageID + eventQuery.ListByToken + CountByToken +
dedupCounter.CountActiveDedup; assembles the manage payload.
- DeleteManage(w, r): 204 on success, 404 on miss. Cascade-delete is
via the existing tokens-events FK ON DELETE CASCADE.
- buildPage helper: NextCursor is the string of the last event's ID
iff len(events) == limit (matches spec example "next_cursor": "41").
Tests cover happy path, 404 on unknown id, 400 on bad/negative cursor,
?limit= respected, ?limit=999 capped at 100, DELETE happy + 404.