Uh oh!
There was an error while loading. Please reload this page.
fix(rest): the package door carries the demoted producer code on declaredCode (#12405) - #12508
Conversation
…aredCode
`sendThrownError` in `packages/rest/src/package-routes.ts` resolved every throw
through the shared ADR-0112 rule and then forwarded `{ details }` and nothing
else, so the demoted spelling it was holding in the same local was dropped one
line later. It now reads `demotedDeclaredCode(thrown)` and spreads it into the
envelope writer's `extra`, which is the idiom the dispatcher twin serving this
same path (`errorFromThrown`, #9106) and the flat `/data` door
(`thrownCodeFields`, #9232) already apply.
Additive: a registered code still carries no `declaredCode`, an uncoded throw
still carries none, `details` is untouched, and `code`/`status`/`message` are
unchanged on every existing path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 13 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 492330ba0e735b904a50a311f9f1507dc2e3e0dd && git checkout 492330ba0e735b904a50a311f9f1507dc2e3e0dd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8425c17cccc604ab3305362a4bda6b6c42c29c0e 424f28503fece2111d6aef434e7372b89bc611b4 && git checkout -B drift-repro 8425c17cccc604ab3305362a4bda6b6c42c29c0e && git merge --no-ff 424f28503fece2111d6aef434e7372b89bc611b4
node scripts/docs-audit/affected-docs.mjs --json 8425c17cccc604ab3305362a4bda6b6c42c29c0e |
… rests on Docblock only, in `package-door-declared-code.test.ts` section 4. The block argued the CONSISTENCY half (prose-scoped withhold, the twin applies no status condition) and left unstated the fact the decision actually rests on: no producer reaching these four seams can put a driver errno in `declaredCode`, because `PackageService` discriminates on the STATUS channel and never on `.code`. That is what makes not-withholding safe rather than merely consistent, and it is a premise that can rot, so it is now named alongside what would falsify it. Proved comment-only: `ts.transpileModule` with `removeComments` emits byte-identical output before and after (13515 bytes, sha256 8849a4b2c9f4c197), and the same check reports DIFFERENT when one code token is perturbed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
os-litant
commented
Aug 26, 2026
Addendum pushed as Section 4's block (
The ruling itself is recorded with its date and its ground, with B named and refused and C flagged as the separate cross-door card. No new gate union was run, and none is owed — this is a comment-only addition to a file whose union already ran green at Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#12405
sendThrownErrorinpackages/rest/src/package-routes.tsresolved every throw through the shared ADR-0112 rule and then forwarded{ details }and nothing else, so the demoted producer spelling it was holding in the same local was dropped one line later. It now readsdemotedDeclaredCode(thrown)and spreads it into the shared envelope writer'sextra.Premise re-verified on current
origin/mainTriage verified this at
4045b95; re-measured here ate0bdbc30b(branch point) and again on the merged tree at904c0afe4:sendThrownErrorstill forwarded only{ details }— unrepaired, nothing fixed it in passing.sendError'sextraadmitsdeclaredCode(packages/types/src/response-envelope.ts, types:sendError'sextracannot carrydeclaredCode, so every nested-envelope route silently drops an unregistered producer's own code spelling #11719 /db8c288), so the repair compiles.Direct resolver measurement, the shape the fix depends on:
statuscodedemotedDeclaredCode{ status: 409, code: 'PACKAGE_IS_HAUNTED' }RESOURCE_CONFLICTPACKAGE_IS_HAUNTED{ status: 409, code: 'DESTRUCTIVE_CHANGE' }DESTRUCTIVE_CHANGEundefined{ code: 'SQLITE_ERROR' }(no status)INTERNAL_ERRORSQLITE_ERROR{ status: 500, code: 1234 }INTERNAL_ERRORundefinednew Error('kaboom')INTERNAL_ERRORundefinedAdopting an existing rule, not inventing one
demotedDeclaredCodeis read, neverthrown.declaredCoderaw: presence MEANS demotion, and the resolver sets the raw field for a REGISTERED spelling too. The two sibling doors already do exactly this — the flat/datadoor'sthrownCodeFields(error-response.ts, #9232), and, more directly, the runtime dispatcher'serrorFromThrown(packages/runtime/src/http-dispatcher.ts, #9106), which is the TWIN transport for/api/v1/packages.That twin is why this reads as a disagreement rather than an omission: the dispatcher domain has emitted this channel all along, while this direct-mount registrar — which registers first and is therefore the transport production serves for the three routes both declare — dropped it. One path, two doors, disagreeing on a declared channel.
Reachability — the card's own unmeasured half
Two different claims, measured separately.
The channel is live at this door. Every producer reaching
sendThrownErroris injected: thePackageServiceis resolved from the service registry per request (#7563), theprotocolslice is duck-typed intoPackageRoutesOptions, andresolveExecutionContextis handed in bydirect-mount-composition.ts. The door forwards whatever they throw, so the demote fires on any stringcodeoutside@objectstack/spec's ledger. All four seams are driven in the new suite.The framework's own producers do not populate it — by gate, not by accident. Scanned every
.code = '…'andObject.assign(new Error(…), { code })spelling in non-test source underpackages/(107 sites, 47 distinct spellings) against the realApiErrorSchema: exactly one is unregistered, and it is a docblock example (dispatcher-error-vocabulary.ts:64). That ispnpm check:dispatcher-error-vocabularydoing its job — it fails on an unswept platform producer precisely so a platform code cannot silently demote off the wire.Tracing the four seams confirms it end to end:
PackageService.publish/deletere-throw only viadeclaresHttpAnswer(the STATUS channel, never.code),get/listre-throw only the branded seam refusal (SERVICE_UNAVAILABLE), andprotocol.deletePackageescapes only withTENANT_SCOPE_REQUIREDormetadataStoreUnavailableError— every one a ledger member. Its per-item deletes and its plugin cleanups are each caught and reported asfailed[]/cleanups[].So the live population is the limb no ledger enumerates, which is what
declaredCodewas declared for: a metadata app's own thrown.codeacross the QuickJS boundary (#7867), and a downstream repo's codes — kept out of this ledger BY the federation ruling (2026-08-03, re-confirmed 2026-08-09) whileresolveThrownHttpErrorstill judges membership against the framework ledger alone. Severity, not validity: an unregistered platform-authored spelling would be demoted and dropped identically.The instrument can say no
Proved in three places, on terms that are not substrings of the ones under test:
trueforRESOURCE_NOT_FOUND/DESTRUCTIVE_CHANGEandfalseforZQX_NEVER_REGISTERED/PACKAGE_IS_HAUNTED;sendError, non-zero in all seven modules) and a negative one (quixoticNonexistentSymbol, zero in all seven);ObjectQL, a realObjectStackProtocolImplementationand a failing driver, driven throughDELETE /api/v1/packages/:id, answer a REGISTERED503 SERVICE_UNAVAILABLEand must carry nodeclaredCode.The #11719 census, re-run rather than inherited
It holds. Across the seven modules on the shared
sendOk/sendErrorpair, onlypackage-routes.tsconsults the shared resolver (4 hits);storage-routes,settings-routes,admin-routes,external-datasource-routes,i18n-service-pluginandshare-link-routesreturn 0 forresolveThrownHttpError/demotedDeclaredCode/classifiedRefusalAnswer. One addition worth recording:rest-server.tsis an eighth importer of the shared writer (assendEnvelopeError) and does hold a resolved answer — but it is the fenced file (PR #12421 / #11926) and is untouched here.The 5xx withhold is scoped to the prose, deliberately
The demote is NOT suppressed on the sanitised 5xx arm. This door's withhold (#8086) replaces a leaky message and its own docblock scopes it: "Only the PROSE is withheld:
status,codeanddetailsare untouched."declaredCodeis a code channel, and the twin applies no status condition to it either — a condition added here would be a new rule at one door and would re-create the divergence this closes. Pinned as a live case so a reader sees it was chosen. There is a reading under which a driver-authored spelling on a sanitised 5xx is itself disclosure; that fork is reported to the PM rather than decided here.Reverse verification, and the half the prediction got wrong
Predicted before running: reverting
sendThrownErrorto its pre-repair forward turns sections 1, 3 and 4 RED and leaves sections 2 and 5 GREEN.Measured: 30 failed / 125 passed of 155. Sections 1, 3 and 4 went red as predicted — and so did the convergence block of section 5, which the prediction had lumped in with its real-producer sibling and should not have: that block compares the wire against
demotedDeclaredCodeover six shapes, four of which demote, so red is correct there. What stayed green is what actually asserts ABSENCE.package-routes-coded-error-mapping.test.tscontributed 4 of the 30 (its new line at the four seams). The corrected account is recorded in the suite header rather than smoothed over.The mutation was proved on disk before the run — blob
81c113c…to1ac288d…, injected marker count 0, removed marker count 1 — and the restore proved after it:git diff HEADclean and the blob back to theHEADhash. The script carried atrap … EXIT INT TERMwith absolute paths.Verification
Everything below ran at
5f1d5235c, the final commit, and each verdict is quoted from the gate's own line.pnpm --filter @objectstack/rest test— 148 files, 2403 tests passedpnpm --filter @objectstack/rest typecheck—tsc --noEmit, exit 0pnpm lint(whole repo,eslint . --no-inline-config) — exit 0, no narrowing claimedpnpm check:route-envelope— OK (this file's gate; the two#9559ratchet lines are pre-existing and unmoved)pnpm check:dispatcher-error-vocabulary— "OK — 21 unregistered code-stamping site(s), all classified"pnpm check:type-check-debt— "OK — 32 ledger entr(ies) re-measured … none above its recorded number"pnpm check:engine-double-contract— "OK — 416 pinned, 133 in the DEBT ledger, 2 exempt"pnpm check:where-matcher— "303 matcher(s) … none new"pnpm check:query-options-erasure— "ratchet holds … none new"pnpm check:cross-package-test-inputs— "OK: 20 package(s) read outside themselves, all declared"pnpm check:test-source-alias,check:nul-bytes,check:type-check-coverage,check:type-source-resolution, and the changeset gate family — all exit 0Gate family derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackon the merged tree (20 matched + the test-file conventions), not recalled.One ratchet went genuinely red and was repaired at the source, not at the ledger.
check:type-check-debtreported@objectstack/restTEST_DEBT 155 to 158 (+3). Reproduced the gate's program locally at 158 and attributed all three to the new suite:Object.hasOwnisTS2550against this package's pre-es2022lib— the same ceiling its ledger note already records asTS2550 x16forArray.prototype.at. Rewritten as'declaredCode' in error; the layer re-measures at exactly 155 with 0 errors in either edited file, and the gate is green. ⛔ The ledger entry was not raised.Scope
declaredCodeonly. ⛔userMessageis not threaded — it is a separate card with a different caller obligation, and observations gathered for it are reported to the PM rather than acted on here.packages/rest/src/rest-server.ts(hard-serial behind PR #12421) andpackages/types/**are untouched; what was observed in the fenced file is reported, not changed.Generated by Claude Code