From 5addebf96d43587f6d732418fb6807a110e92d36 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:21:23 -0700 Subject: [PATCH] =?UTF-8?q?docs(ax):=20entry=2042=20=E2=80=94=20the=20disp?= =?UTF-8?q?atcher=20picks=20a=20shape,=20not=20just=20an=20identity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @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 --- docs/development/agent-experience-audit.md | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/development/agent-experience-audit.md b/docs/development/agent-experience-audit.md index c12c72a19f..005e607331 100644 --- a/docs/development/agent-experience-audit.md +++ b/docs/development/agent-experience-audit.md @@ -2540,6 +2540,34 @@ And the fallback is unconditional: a request with no `cm_agent_` prefix does not fail, it takes the human branch and succeeds. Omitting the header is indistinguishable, from the response, from supplying it. +**And the same divergence runs one level down, inside `middleware/auth.ts` +itself** (@sprint-review, 2026-08-23). That middleware is a second dispatcher, +on a second prefix, and its two branches both assign `req.user` — with +different shapes: + +```ts +:51 req.user = { id, username, email, role }; // cm_ API token +:81 req.user = { id }; // browser JWT +``` + +So a consumer reading `req.user.username` or `req.user.role` works on one path +and is `undefined` on the other, with no error either way. That is not a +hypothetical: `agentProfile.ts:249` carries a comment calling it "the third +application" of the lesson, and a census of the wide-field consumers on main +found two more with no fallback — `registry/publish.ts` persisted +`publisher.name: undefined` for every browser-session publish (#1211), and +`github.ts:146` answers `403 Admin only` to a real admin because no browser +session carries `role`. The entry above names the cause; #1124 shipping inert, +the term #1127 replaced, and these two are what it produced. + +**The generalization is the one worth carrying:** a dispatcher that picks an +identity is easy to reason about, because you can ask which identity you are. +A dispatcher 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. Count +the assignment sites, not the branches: `grep -n 'req.user = ' backend` returns +two lines, and they are not the same object. + + **What that costs a test author.** Every case in `tasksApi.updateRenewsLease.test.js` went through the human mock, so `req.user` was always populated and the agent branch had never run — across