Skip to content

fix(runtime): endpoint-route 401 arm writes the shared anonymous-deny body — code key included - #9848

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-9823-endpoint-route-401-code-key
Aug 19, 2026
Merged

fix(runtime): endpoint-route 401 arm writes the shared anonymous-deny body — code key included#9848
huangyiirene merged 1 commit into
mainfrom
claude/issue-9823-endpoint-route-401-code-key

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes#9823

Part of the #9487 acceptance property ("one documented key identifies the machine code on every error family, 401 included") — no closing keyword for #9487 by design; whether that card can now close is the PM's call. #9559 / ADR-0112 D5 (envelope convergence) is deliberately not touched: this arm stays in the flat family, additive key only, per the #9487 maintainer ruling.

What

mountRouteOnServer in packages/runtime/src/dispatcher-plugin.ts (the if (route.auth !== false && !user) arm) wrote an inline { error, message } copy of the flat anonymous-deny body, so #9487's additive code key (landed in @objectstack/core's ANONYMOUS_DENY_BODY via PR #9824) never reached it. The arm now writes ANONYMOUS_DENY_BODY / ANONYMOUS_DENY_STATUS verbatim. Wire effect: hook-emitted endpoint routes answer anonymous callers 401 { error, code, message } — additive only, no key removed or moved, message byte-identical.

Files: the 401 arm + one import line in dispatcher-plugin.ts; a new pin dispatcher-plugin.route-auth-deny-body.test.ts; a changeset (@objectstack/runtime minor). Sibling card #9813's two discovery bodies in the same file are untouched — that card is held serial behind this one. #9813 is not addressed here; #9559 remains open.

Measurements (the dispatch's Zone-2 assumptions)

  • Swap is exactly additive — verified before swapping: ANONYMOUS_DENY_STATUS is 401 (matches the arm's hardcoded status), ANONYMOUS_DENY_MESSAGE is byte-identical to the inline message, the constant carries no header behaviour (the arm's securityHeaders loop is untouched), and @objectstack/core already exports all of it on this file's existing import path. The only wire delta is the code key.
  • This arm was the last producer. Sweep on the merged ref (152bff8fcd, merge-base of this branch): exact-message grep → 2 non-test producers (the core constant + this inline copy); tolerant prefix grep ("Authentication is required") → the same 2; error: 'UNAUTHENTICATED' literal producers → 1 (this arm; the other hit is a core docstring line, not a producer). Control (typo'd message) → 0, proving the grep shape distinguishes. No third producer.
  • Pin path re-derived, not assumed.dispatcher-plugin.endpoint-fallback.integration.test.ts asserts a nested body.error.code — that is the http-dispatcher wildcard path, a different 401 producer in the wrapped family. No existing test pinned this arm's flat body. The new pin exercises the concrete hook-route mount (__aiRoutes recovery path) and spells the exact body literally.
  • Reachability sharpened while re-deriving:registerAIRoutes mounts /ai/* wildcards for get/post/delete/put only, so a PATCH RouteDefinition under /ai/* (a legal method per the interface) reaches these concrete mounts unshadowed, as does any emitted path outside /ai/*. The arm is live on the wire; the issue's premise holds.

Tests — union run at 7dffa23303 (the final commit; no push since)

  • New pin: 4/4 green (anonymous → 401 + exact literal body + handler never called; body equals the shared constant; session → served; auth: false → open).
  • Reverse verification, from the committed state, predicted direction: red.git restore --source=origin/main on the source file → exactly the 2 body pins fail (AssertionError: expected { error: 'UNAUTHENTICATED', …(1) } to deeply equal { …(2) } — the missing code), the 2 behaviour tests stay green (2 failed | 2 passed). Restored from HEAD, clean porcelain, 4/4 green again. No ablation-rebuild caveat: the pin imports ./dispatcher-plugin.js as a same-package sibling source, compiled by vitest directly — the mutation never had to reach a dist/.
  • Full @objectstack/runtime suite: 176 files / 2621 tests green. pnpm --filter @objectstack/runtime typecheck green.
  • Derived gates (node scripts/pm/dispatch-gates.mjs, no hand-fed paths): all 13 green — changeset family (adr-0087, no-major, empty, gate-self-tests, objectui-changeset), cross-package-test-inputs (both spellings), affected-docs, and the convention-triggered test-file set (query-options-erasure, type-check-coverage, type-check-debt --re-measure after a full packages-closure build — "none above its recorded number", engine-double-contract, where-matcher). Plus pnpm lint (mandated regardless of derivation) and check:nul-bytes — both green.

Out of scope, reported

  • Core's anonymous-deny.ts docstring says "Exactly one consumer writes it" — true today, false once this lands (this arm becomes the second writer). Outside the declared file surface; filed as a finding issue rather than a rider.

Generated by Claude Code

…_BODY — code key included (#9823)
The mountRouteOnServer 401 arm wrote an inline { error, message } copy of
the flat deny body, so #9487's additive code key never reached it. The arm
now writes @objectstack/core's ANONYMOUS_DENY_BODY / ANONYMOUS_DENY_STATUS
verbatim (additive only per the #9487 maintainer ruling), and a pin asserts
this arm's exact body — the existing integration test covers the OTHER
(http-dispatcher, nested-envelope) path, not this one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WeN7F6jQFpcqW2BN56RdPa
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅

What this run could not see

Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e77a9b45e4ea3a0a9960446f52f0e6689ce3a9e4packageMentionDocs.

Which tree this was computed on

This run read content/docs from 61d897354e8789dcbfc097218d3f372ceca20784 — the merge of head 7dffa23303f13d1866ab601ecf004c991b6f28a6 into base e77a9b45e4ea3a0a9960446f52f0e6689ce3a9e4, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 61d897354e8789dcbfc097218d3f372ceca20784 && git checkout 61d897354e8789dcbfc097218d3f372ceca20784
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e77a9b45e4ea3a0a9960446f52f0e6689ce3a9e4 7dffa23303f13d1866ab601ecf004c991b6f28a6 && git checkout -B drift-repro e77a9b45e4ea3a0a9960446f52f0e6689ce3a9e4 && git merge --no-ff 7dffa23303f13d1866ab601ecf004c991b6f28a6
node scripts/docs-audit/affected-docs.mjs --json e77a9b45e4ea3a0a9960446f52f0e6689ce3a9e4

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 19, 2026
@huangyiirene
huangyiirene marked this pull request as ready for review August 19, 2026 08:22
@huangyiirene
huangyiirene added this pull request to the merge queueAug 19, 2026
Merged via the queue into main with commit b2789adAug 19, 2026
26 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-9823-endpoint-route-401-code-key branch August 19, 2026 08:39
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@huangyiirene@claude