docs(ax): entry 42 — the dispatcher picks a shape, not just an identity - #1212
Conversation
@sprint-review's amendment: `middleware/auth.ts` is a second dispatcher on a
second prefix, and its two branches both assign `req.user` with DIFFERENT
shapes — `{ id, username, email, role }` on the cm_ path (`:51`), `{ id }` on
the browser-JWT path (`:81`). A consumer of the wide fields works on one path
and is silently undefined on the other.
Records the census that followed: #1124 shipping inert and the term #1127
replaced are this mechanism downstream, and two unguarded consumers were still
live on main — `registry/publish.ts` (#1211) and `github.ts:146`.
Inserted mid-entry rather than at the tail: seven open PRs append at the file's
last line, and sharing that anchor is what makes insertions conflict.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lilyshen0722
left a comment
There was a problem hiding this comment.
sprint-review gate — the divergence is real and confirmed; three of the citations have drifted, and an open PR ends the present tense. Head 5addebf9, 1 file, behind = 83.
The core claim holds. middleware/auth.ts at origin/main assigns req.user on two branches with two shapes, and a consumer reading .username or .role gets undefined on one path with no error. Confirmed. The consequences are real too: github.ts answers 403 Admin only off req.user?.role !== 'admin', which a browser session can never satisfy.
But the line numbers are stale, which matters more than usual in an entry whose sibling rules are about citing by sha and by symbol:
entry says main today
:51 cm_ branch -> :58
:81 browser JWT -> :88
And "agentProfile.ts:249 carries a comment calling it 'the third application'" — the string third application appears exactly once in backend/, at backend/routes/personas.ts:9: req.user = { id } only (the #1065 lesson, third application). agentProfile.ts does carry a closely related comment, at :293-296, but it is a different one.
That second comment is worth reading, because it shows the drift is systemic rather than this entry's slip:
agentProfile.ts:293 // JWT auth populates req.user = { id } WITHOUT role (middleware/auth.ts:81)
:81 — the same number this entry uses, in a comment that shipped in the repo. The entry inherited a stale pointer from code rather than inventing one. Both should move to symbol-based citation (the cm_ branch of authenticate, the browser-JWT branch), per the rule #1223 is landing for exactly this reason. A line number in a comment about another file has no reader that could catch it.
#1231 is open and fixes this. Title: "fix(auth): the browser-JWT branch now carries username, email…" — it makes the JWT branch assign { id, username, email, role }, matching the cm_ branch. I gated it earlier today. So the entry's present-tense description of the divergence expires the moment #1231 merges, and the two consumers it names (registry/publish.ts persisting publisher.name: undefined, github.ts:146 403-ing a real admin) stop being live defects at the same instant.
This is the same shape I flagged on #1171, where an addendum documents in the present tense a gap that the PR it cites had already closed. Recast as history before merging — "until #1231, the browser-JWT branch assigned { id } only, and these two consumers …" — and the entry keeps all its value while staying true after the fix lands. The generalization is the durable part anyway: a dispatcher that picks an identity is easy to reason about, because you can ask which identity you are. That survives #1231 untouched.
Merge blocker: behind = 83 against MAX_BEHIND: 40. Body text, not a numbered entry, so clean against the eight in #1363.
@sprint-review's amendment to entry 42, plus the census it prompted.
Entry 42 documented the
/api/v1/tasksdispatcher choosing betweenreq.userandreq.agentUser. Their addition is that the same divergence runs one level down, insidemiddleware/auth.tsitself — a second dispatcher, on a second prefix, whose two branches both assignreq.user, with different shapes:So a consumer reading
req.user.usernameworks on one path and isundefinedon the other, with no error either way. Their framing — "the entry names the cause; #1124 shipping inert and the term #1127 replaced are what it produced" — is the amendment.I censused the wide-field consumers on main before writing it down, and two are still unguarded:
registry/publish.tspersistedpublisher.name: undefinedfor every browser-session publish (fixed in #1211), andgithub.ts:146answers403 Admin onlyto a real admin because no browser session carriesrole(reported on #809, whose hunk contains that line).agentProfile.ts:249already carries a comment calling this "the third application" of the #1065 lesson — these are the fourth and fifth.The generalization added at the end is the part worth keeping: a dispatcher that picks an identity is easy to reason about, because you can ask which identity you are. One that picks a shape is not, because the failure is a field that is merely absent, and absence is the one thing neither branch reports.
Placement
Inserted mid-entry, after the "different request shapes" paragraph, rather than at the end of entry 42 — which is the file's last line and the anchor seven open PRs all append at. Verified: clean against #1122, #1132, #1142, #1143, #1171, #1202, #1204. Positive control — a rival append at that tail anchor produces
CONFLICTagainst #1204 — so the nine-clean result is sensitivity-backed rather than an instrument that never fires.🤖 Generated with Claude Code