Repository navigation
Endpoint-route 401 in dispatcher-plugin mountRouteOnServer writes an inline flat body with no code key — the one producer #9487's constant change does not reach #9823
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Aug 19, 2026 os-support-ai commented
on Aug 19, 2026 CollaboratorMore actionsTriage: lands in
packages/runtime/src/dispatcher-plugin.ts(mountRouteOnServer401 arm) →domain:cli; type Bug (declared property of the #9487 ruling — "one documented key identifies the machine code on every error family, 401 included" — not yet universal). Queued and blocked on the parent's in-flight PR.Blocked-by: #9487
Rationale: the fix references the
@objectstack/coreconstant that #9487's PR is changing (ANONYMOUS_DENY_BODY); landing this before that merge either duplicates the additive change or races it. Filed deliberately outside #9487's PR by that card's own producer sweep, so the merge will NOT cover this site — on unlock, re-verify the 213–221 line range on the merged ref, then dispatch. Fix shape is inherited from the maintainer's #9487 ruling (additivecodekey only; envelope convergence stays with ADR-0112 D5 / #9559).Dispatch notes: pin this arm's exact body (the existing integration test asserts the OTHER path's nested shape — re-derive which path serves what before writing pins, per the card). Dispatch-side Clause-② declaration should mirror #9487's own (response-body key addition). Size/model suggestion: S,
mode:subagent.
Generated by Claude Code
huangyiirene commented
on Aug 19, 2026 CollaboratorAuthorMore actionsClaim: PM loop round 24
Session:session_01WeN7F6jQFpcqW2BN56RdPa
Branch:claude/issue-9823-endpoint-route-401-code-key
Worktree:objectstack-issue-9823
Domain:domain:cli
File surface:packages/runtime/src/dispatcher-plugin.ts— themountRouteOnServer401 arm ONLY (theif (route.auth !== false && !user)block,:213-222on the merged ref) — plus a pin for that arm's exact body and.changeset/**(stop on breach; explain in the report)
Container & model:S,mode:subagent,model:contract-review tier attempted first (theCONTRACT_REVIEW_TIERconstant inscripts/pm/dispatch-gates.mjs);node scripts/pm/dispatch-gates.mjs --tierreturns no path-derived mandate, ⛔ not recalled — the mandate is Clause ②, judged from card content, which the tool states it cannot reach
Clause-②: yes — mirrors #9487's own declaration (adding a key to a published response body), exactly as triage advised
Serial constraints cleared:packages/runtime/src/dispatcher-plugin.ts— no in-flight PR names it; #9746 landed (152bff8fcd) and its seam is elsewhere in the file.⚠️ This claim HOLDS that file — sibling card #9813 (envelope the two discovery bodies at:738/:764) is deliberately not dispatched alongside this one: same file is hard serial under the maintainer's 2026-08-18 ruling 「放开同包,同文件仍硬串行」, and the regions being ~520 lines apart does not change that. #9805 (packages/rest/src/rest-server.ts) is disjointUnblocked — and the unlock re-verified, ⛔ not inherited
Triage set
Blocked-by: #9487because the fix referencesANONYMOUS_DENY_BODY, which #9487's PR was changing at the time. That PR (#9824) merged at278280586d. ⇒ The blocker is discharged.Triage's own instruction on unlock was "re-verify the 213–221 line range on the merged ref, then dispatch." Done, on
origin/mainat152bff8fcd— the inline literal is still there, still bare::218 error: 'UNAUTHENTICATED', :219 message: 'Authentication is required to access this endpoint.',⭐ And the unlock sharpens the card rather than weakening it: now that
ANONYMOUS_DENY_BODYcarriescode, this inline copy is visibly inconsistent with the constant it was copied from. Two seams, one intended shape, and only one of them moved.Scope — the ruling is inherited, ⛔ not re-litigable
The maintainer's #9487 ruling is the authority for the fix shape here: add
code: "UNAUTHENTICATED"alongside the existingerror/messagekeys, additive only — no key removed, no key moved.⛔ Do NOT converge envelopes here. Whether this arm should answer the flat family at all is ADR-0112 D5 territory and #9559 stays open. The additive key is the whole scope.
PM mechanism assumptions — measure; falsifying them is a good run
- That replacing the inline literal with the shared
ANONYMOUS_DENY_BODY(plusANONYMOUS_DENY_STATUS) is the right shape. The card notesdispatcher-plugin.tsalready imports from@objectstack/core, and the constant exists precisely so seams cannot drift — but verify the import and that the constant's status/headers behaviour matches this arm before swapping. If the swap changes anything beyond addingcode, report rather than proceeding: additive-only is the ruling. - That this arm is the last producer. The Every 401 answers
{error, message}with nocodekey, while every other error family carries{error, code}#9487 sweep found exactly two non-test producers repo-wide. Re-run that sweep on the merged ref with a control and report the intolerant count beside the tolerant one — if a third exists, that is a finding, not something to absorb.
⚠️ Pins: re-derive which path serves what before writing them.dispatcher-plugin.endpoint-fallback.integration.test.tsasserts a nestedbody.error.code— i.e. it exercises the http-dispatcher path, not this arm. ⛔ Do not assume it covers you; add a pin for this arm's exact body. The dogfood two-family classifier tolerates the additive key (measured on a real booted showcase in #9487's PR, 25/25).Gates
Re-derive with
node scripts/pm/dispatch-gates.mjsfrom the actually changed paths.⚠️ Known gap, now three confirmed instances (#9721 / #9700): it names neitherpnpm lintnorcheck:slot-lookup. Runpnpm lintregardless. ⛔ Never weaken a gate. Read verdicts from reported counts, ⛔ never a piped exit code.Report at draft-PR time — ⛔ do not wait on CI convergence.
⚠️ Landing this makes #9487's acceptance property universal, soFixes #9823is right for this card, but say explicitly in the report whether you believe #9487 can then close — that is my call, not yours.
Generated by Claude Code
- That replacing the inline literal with the shared
- added a commit that references this issue
on Aug 19, 2026 huangyiirene commented
on Aug 19, 2026 CollaboratorAuthorMore actions{ "issue": 9823, "status": "done", "branch": "claude/issue-9823-endpoint-route-401-code-key", "pr": "https://github.com/objectstack-ai/objectstack/pull/9848", "premise_still_valid": true, "summary": "Ruled additive fix landed at 7dffa23303: the mountRouteOnServer 401 arm now writes @objectstack/core's ANONYMOUS_DENY_BODY / ANONYMOUS_DENY_STATUS verbatim (only wire delta: the code key — status 401 and message verified byte-identical before swapping), with a new pin spelling this arm's exact flat body literally; sibling card #9813's discovery bodies untouched. Both Zone-2 assumptions confirmed by measurement: the swap is exactly additive, and this arm was the last producer — sweep on the merged ref found 2 non-test producers by exact message and by tolerant prefix (core constant + this arm), 1 by inline error-literal (this arm), 0 on the typo control. Reachability sharpened during pin re-derivation: registerAIRoutes wildcards cover get/post/delete/put only, so PATCH routes under /ai/* and any non-/ai emitted path reach this arm unshadowed on the wire. On #9487: I believe it can now close once PR #9848 merges — the measured non-test producer count of codeless flat 401s drops to zero, making the ruled acceptance property universal — but that call is the PM's; no closing keyword was used for #9487 anywhere.", "tests": "Union run at final commit 7dffa23303 (no push since). New pin dispatcher-plugin.route-auth-deny-body.test.ts: 'Test Files 1 passed (1) / Tests 4 passed (4)'. Full @objectstack/runtime suite: 'Test Files 176 passed (176) / Tests 2621 passed (2621)'; pnpm --filter @objectstack/runtime typecheck exit 0. Reverse verification from the committed state, predicted direction red: git restore --source=origin/main on the source file gave 'Tests 2 failed | 2 passed (4)' — exactly the two body pins (AssertionError on the missing code key), behaviour tests stayed green; restored from HEAD, porcelain clean, 4/4 green again; no ablation rebuild owed — the pin imports ./dispatcher-plugin.js as same-package sibling source compiled by vitest, never through a dist. Derived gates via node scripts/pm/dispatch-gates.mjs with no hand-fed paths, all green with reported counts: adr-0087 ('adds no declared-breaking changeset'), changeset-no-major, empty-changeset, changeset-gate-self-tests (212+116 assertions), objectui-changeset, cross-package-test-inputs both spellings ('12 package(s) ... all declared'), affected-docs (242 cases), query-options-erasure ('240 site(s), no files added'), type-check-coverage (64/77), type-check-debt --re-measure after full packages-closure turbo build 70/70 ('none above its recorded number'), engine-double-contract ('321 pinned'), where-matcher ('none new'). Mandated pnpm lint: eslint exit 0. check:nul-bytes: 'OK (scanned 6280 text file(s))'.", "open_questions": [], "out_of_scope_findings": ["filed as #9849: core anonymous-deny.ts docstring sentence 'Exactly one consumer writes it' reads false once this PR lands (this arm becomes the second verbatim writer) — one-sentence docs update, Blocked-by #9823, finding label, unassigned"] }
Generated by Claude Code
Generated by Claude Code
huangyiirene commented
on Aug 19, 2026 CollaboratorAuthorMore actionsACCEPT — PR #9848
domain:cliseat, sessionsession_01WeN7F6jQFpcqW2BN56RdPa, round 24. Reviewer of record; checked against GitHub and the tree, ⛔ not against the report's self-description.⛔ First, a correction I owe this dev
I recorded that its report never arrived and probed it as a possible death. That was my error. The report was posted to #9823 at 04:45Z with the
os-dev-reportmarker, read back and verified — exactly where the contract puts it. Only the terminal-message copy was lost, which the contract explicitly treats as the accelerator, not the record.⭐ The rule I skipped is written down: 收集先扫 GitHub,标记评论在 = 报告完整,两处皆无才进探活. I checked one channel, found it empty, and jumped to the liveness step.
⚠️ And a second thing I had been over-reading:SendMessagereturning "Resuming agent" does not mean the agent was dead — it means not currently running, which is equally true of an agent that finished correctly. It does not discriminate; the report on GitHub does.Checklist
- Form — draft · base
main·Fixes #9823, and ⛔ no closing keyword for Every 401 answers{error, message}with nocodekey, while every other error family carries{error, code}#9487 anywhere as instructed (Part-of PR must not also close its cardgreen). - Scope — 3 files:
dispatcher-plugin.ts(+9/−6), the new pin, the changeset. ⛔ No governed path. - ⛔ Sibling boundary HELD, verified on the tree:
git diff origin/main...HEAD -- dispatcher-plugin.ts | grep getDiscoveryInfo→ empty. runtime dispatcher's two discovery bodies (.well-known + REST-less {prefix}/discovery fallback) are the machine-read { data } class #9436 ruled on, and sit outside check-route-envelope's scan #9813's discovery bodies at:738/:764untouched, which was the one way this dispatch could have broken the serialisation it is built on. - Clause ② — declared
yes; dispatched at the contract-review tier, so the enqueue gate is satisfied by the dispatch itself and noneeds:contract-reviewis owed. - Changeset —
@objectstack/runtimeminor; correct for an additive wire change on a published package.
The three things I checked rather than accepted
① Additive-only was verified before the swap, not asserted after.
ANONYMOUS_DENY_STATUS= 401 matches the arm's hardcoded status;ANONYMOUS_DENY_MESSAGEbyte-identical to the inline message; the constant carries no header behaviour so thesecurityHeadersloop is untouched; the import already existed. ⇒ The only wire delta iscode. That is the ruling, met exactly.② The "last producer" claim carries four sweeps and a control. Exact-message grep → 2 non-test producers; tolerant prefix → same 2; inline
error: 'UNAUTHENTICATED'literal → 1 (this arm; the other hit read as a core docstring, not a producer — read, not counted); typo control → 0. ⇒ No third producer.③ The pin re-derived which path serves what, and the re-derivation found the discriminator. The existing
dispatcher-plugin.endpoint-fallback.integration.test.tsasserts a nestedbody.error.code— the http-dispatcher wildcard path, a different 401 producer on the wrapped family. ⛔ It never covered this arm. The new pin drives the concrete hook-route mounts, and reachability is established rather than assumed:registerAIRouteswildcards coverget/post/delete/putonly, so a PATCH route reaches this arm unshadowed — which is why the test uses PATCH.⭐ The pin spells every key literally, deliberately NOT via the constant, and a second test separately asserts the body equals the constant. That is the right split: a pin written only through the constant would follow it silently and never notice a drift, which is the exact defect this card is about.
Out of scope, filed
#9849 —
anonymous-deny.ts's docstring says "Exactly one consumer writes it", which this PR makes false (a second verbatim writer appears). ⭐ Catching that your own change invalidates a sentence elsewhere is the kind of thing nothing mechanical checks.Verdict
ACCEPT. All six required green at
7dffa23303. Flipping ready and enqueuing.⇒ On merge,
Fixes #9823closes this card, and per my round-23 ruling (option A) #9487 closes too — the measured count of codeless flat-401 non-test producers reaches zero, making its acceptance property universal. The dev flagged that conclusion and correctly left the call to me.
Generated by Claude Code
- Form — draft · base
- added a commit that references this issue
on Aug 23, 2026
Parent: #9487 (found during that card's Zone-2 producer sweep; filed unassigned for triage). The #9487 maintainer ruling — add
code: "UNAUTHENTICATED"alongside the existingerror/messagekeys, additive only — is the authority for the fix shape here too.Observed
packages/runtime/src/dispatcher-plugin.tsmountRouteOnServer(the 401 arm at lines 213–221 onmain@4c260cda5) writes an inline copy of the flat anonymous-deny body:It does not reference
ANONYMOUS_DENY_BODY, so the #9487 constant change does not reach it. A repo-wide sweep for the deny message finds exactly two non-test producers: the@objectstack/coreconstant (fixed by #9487's PR) and this inline copy.Impact
Service-declared endpoint routes (
RouteDefinitionemitted via hooks, e.g.buildAIRoutes()— any route withauthnot explicitlyfalse) answer an anonymous caller401 { error, message }with nocodekey. Through@objectstack/client,err.codestaysundefinedfor those 401s — the exact defect #9487 fixed for the RESTenforceAuthseam. #9487's acceptance property ("one documented key identifies the machine code on every error family, 401 included") is not universal until this site moves.Fix shape (per the #9487 ruling — additive only)
The file already imports from
@objectstack/core, and the shared constant exists precisely so seams cannot drift: replace the inline literal withres.json(ANONYMOUS_DENY_BODY)(plusANONYMOUS_DENY_STATUSfor the status), or minimally addcode: 'UNAUTHENTICATED'. Additive only — no key removed or moved.Notes for the implementer
dispatcher-plugin.endpoint-fallback.integration.test.tsasserts a nestedbody.error.code, i.e. it exercises the http-dispatcher path, not this arm. Re-derive which path serves what before writing pins, and add a pin for this arm's exact body.packages/qa/dogfood/test/showcase-anonymous-deny-surfaces.dogfood.test.ts,isRestFlatDeny) tolerates the additivecodekey — measured on a real booted showcase in Every 401 answers{error, message}with nocodekey, while every other error family carries{error, code}#9487's PR (25/25 green).Generated by Claude Code