Uh oh!
There was an error while loading. Please reload this page.
feat(security): REST 401 anonymous-deny body carries code UNAUTHENTICATED alongside error/message (#9487) - #9824
Conversation
… alongside error/message (#9487) Maintainer-ruled additive change: every other REST error family answers { error, code } with the machine code in code; the 401 family was the one outlier. ANONYMOUS_DENY_BODY gains code: ANONYMOUS_DENY_CODE — no key removed or moved, so no existing reader breaks. The two strict pins that asserted the old two-key shape (core anonymous-deny.test.ts, dogfood showcase-anonymous-deny-surfaces) are updated to the new exact shape, not loosened. The dogfood two-family classifier is untouched: isRestFlatDeny judges family on discriminating keys and tolerates the additive code key (verified against a real booted showcase, 25/25 green). Does not settle ADR-0112 D5 (flat vs nested convergence, #9559): both declared envelope families are unchanged in kind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WeN7F6jQFpcqW2BN56RdPa
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅ What this run could not seeCoarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin bd1544abb973fb6e2254be4a81e2748702de7089 && git checkout bd1544abb973fb6e2254be4a81e2748702de7089
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9ff11921a26f44a62ca834651adaa54105468536 b0d5633c5a6be8874e4fbfe37201f2368f0b37a0 && git checkout -B drift-repro 9ff11921a26f44a62ca834651adaa54105468536 && git merge --no-ff b0d5633c5a6be8874e4fbfe37201f2368f0b37a0
node scripts/docs-audit/affected-docs.mjs --json 9ff11921a26f44a62ca834651adaa54105468536 |
Uh oh!
There was an error while loading. Please reload this page.
Part of #9487 — delivers the maintainer-ruled additive fix for the dispatched surface. One additional producer was found outside that surface and is filed as sub-issue #9823, so this PR deliberately uses no closing keyword: the PM decides whether the parent closes on this landing or on #9823.
What changed (ruling: additive only — no key removed, no key moved)
packages/core/src/security/anonymous-deny.ts—ANONYMOUS_DENY_BODYgainscode: ANONYMOUS_DENY_CODE. The wire effect surfaces through@objectstack/rest'senforceAuth, which writes this constant verbatim on every/data,/metaand/reports401 (all 69 call sites).errorkeeps carrying the same code value it always has; docstring updated to match, including an explicit note that this does NOT settle ADR-0112 D5 ([tracking] Envelope-position convergence line for packages/rest's flat dialect — the live ratchet owner #9559 stays open — both envelope families unchanged in kind).packages/core/src/security/anonymous-deny.test.ts— the strict pin updated to the new exact three-key shape (stricttoEqual, not loosened).packages/qa/dogfood/test/showcase-anonymous-deny-surfaces.dogfood.test.ts— the strict wire pin updated to the exact three-key shape. The two-family classifier (isRestFlatDeny/isDispatcherWrapperDeny) is byte-untouched: it judges family on the discriminating keys and tolerates the additive key — verified on the wire, not assumed (below). ThetoEqual(ANONYMOUS_DENY_BODY)verbatim pin follows the constant by construction and needed no edit..changeset/anonymous-deny-401-code-key.md—@objectstack/coreminor. Not breaking; no ADR-0087 marker required (check:adr-0087-registrationgreen).The ledger half of the ruling — measured already satisfied, no spec edit needed
The ruling asks that the 401 family be declared like every other family. Measured on
main:UNAUTHENTICATEDis already a tier-1StandardErrorCodemember (packages/spec/src/api/errors.zod.ts:69) withHttpStatusErrorCodeMap401 mapped to it (:174) — the same tier asPERMISSION_DENIED/OBJECT_NOT_FOUND/RECORD_NOT_FOUND; the card's other named codes (VALIDATION_FAILED,BATCH_NOT_ATOMIC,UNSUPPORTED_QUERY_PARAM) are tier-2 ledger rows only because they are not standard members. A tier-2 ledger row forUNAUTHENTICATEDwould be refused by the ledger's own admission gate ("no registered code shadows the standard catalog",packages/spec/src/api/error-code-ledger.test.ts:38-42). Sopackages/spec/**is untouched and thedomain:specboundary was never breached.Measured benefit (the card's cost, removed)
packages/client/src/index.ts:4958-4961readsasSemanticCode(errorBody?.code) ?? asSemanticCode(errorBody?.error?.code). Harness against built dist (stub server answering the built constant; client resolved via packageexports): before —err.code === undefined; after —err.code === 'UNAUTHENTICATED',err.httpStatus === 401, with no SDK change. objectui's fourUNAUTHENTICATEDreaders (error-message.ts:212,MarketplacePackagePage.tsx:338, tests) all key oncodeand start receiving it; none asserts absence.Verification (all at
b0d5633c5, the head this PR ships)@objectstack/coretests: 35 files / 857 passed.@objectstack/restfull suite: 129 files / 2105 passed.@objectstack/dogfoodfull suite: 118 files passed / 1 skipped, 866 tests passed / 3 skipped; the anonymous-deny file alone re-run: 1 file / 25 passed against a real booted showcase — the additive body classifiedrest-flat, no third dialect, wire-verbatim pin green.@objectstack/dogfoodtypecheck green.scripts/ablation-dist-preflight.mjs, value-unique markers,--absenton the ablation leg): reverting the constant turns the core pin RED (1 failed / 7 passed, missingcode) and the SDK harness back toerr.code === undefined; restore leg returns 8/8 and PASS. First marker attempt was rejected by the preflight itself (the marker also matched a pre-existing docstring surviving in.d.ts) — remeasured with value-unique markers.node scripts/pm/dispatch-gates.mjsfrom the real changeset (15 path-matched + 5 convention-triggered), all green, plus the tool's known gaps run explicitly:pnpm lintandpnpm check:slot-lookupgreen,check:nul-bytesgreen,check:type-check-debtre-measure green on the built closure (turbo 70/70).Scope discipline
body.errorreaders (rest-meta-auth.test.ts:49,security-routes.test.ts:99) stay green by construction; all nestederror.codereaders are unaffected.packages/runtime/src/dispatcher-plugin.ts:213-221(service-declared endpoint routes) is Endpoint-route 401 in dispatcher-pluginmountRouteOnServerwrites an inline flat body with nocodekey — the one producer #9487's constant change does not reach #9823 — out of the dispatched surface, reported rather than silently expanded into.packages/rest,packages/client,packages/spec: zero edits needed, zero made.Generated by Claude Code