Uh oh!
There was an error while loading. Please reload this page.
fix(runtime): demote non-enum author-thrown codes to declaredCode — error.code closed at every door (#9106) - #9234
Merged
Conversation
…patcher door (#9106) The dispatcher door's error.code had a limb authored by tenants at runtime: SandboxError carries a metadata app's own .code across the QuickJS boundary (#7867) and domains/actions.ts served it into error.code verbatim. Ruled 2026-08-16: error.code stays a closed vocabulary at every door; an author-thrown code that is not an ErrorCode member is demoted to the wire's declaredCode, exactly as the REST mapper resolveThrownHttpError already does. - ApiErrorSchema declares optional declaredCode — the open, author-authored channel; presence means demotion (spec docs + authorable-surface regen) - HttpDispatcher.errorFromThrown, dispatcher-plugin errorResponseBase and endpoint-executor endpointErrorAnswer all take the resolver's narrowed code; the demoted spelling rides extra.declaredCode via the one builder - @objectstack/types adds demotedDeclaredCode(); resolver behavior unchanged - DUPLICATE re-homed as the demote witness (NOT registered; fenced off #8846) - stale closed-vocabulary prose swept: thrown-http-error, error-code-ledger, dispatcher-error-vocabulary, check-dispatcher-error-vocabulary header Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
…ging origin/main (#9106) Discharges the os-regen deferral recorded by the merge commit: gen:docs for content/docs/references/** and the check:authorable-surface rewrite of authorable-surface/api.json (ApiError:declaredCode). gen:openapi rerun after the schema chain, per the sister trap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
…efinition site (#9106) Prime Directive #13: leave the ADR id in the code and anchor the load-bearing spot. thrown-http-error.ts is where 'code is closed / declaredCode is open' is decided for every door, and the two spellings read like a redundancy to anyone opening the file alone — the anchor says why collapsing them re-opens the tenant-authored limb #9106 closed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
…9232) #7035, which #9098's prose named as the open envelope-position finding, is closed (PR #7293). The flat sendThrownError door's remaining vocabulary question is filed as #9232 — a comment-only correction so the next reader is not sent to a closed card. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 110 hand-written doc(s) reference the affected code — list omitted above 15 rows. Re-derive: ⛔ 7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.
|
This was referenced Aug 17, 2026
os-project-manager
marked this pull request as ready for review
August 17, 2026 04:56
Uh oh!
There was an error while loading. Please reload this page.
os-project-manager pushed a commit
that referenced
this pull request
Aug 17, 2026
Discharges the os-regen deferral recorded by the merge commit. Carries main's #9234 vocabulary change (`error.code` closed at every door) through the generator together with this branch's unquantified in-shape marker. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
akarma-synetal pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 17, 2026
…ardinality — one new error code touches 2 pages, not 11 (objectstack-ai#9239) * fix(spec-docs): the in-shape enum marker stops restating vocabulary cardinality `+N more` is a function of a vocabulary's cardinality, so every reference page carrying the marker was rewritten whenever that vocabulary grew by one. Measured on `ApiError.code` (288 members, StandardErrorCode union the error-code ledger) by registering a single code and regenerating: 11 pages, 69 lines, of which 66 are this marker — and 9 of the 11 pages contain nothing else at all, 100% of their changed lines being `+285 more` -> `+286 more`. The ledger is a per-PR append, so any two PRs registering a code were mutually exclusive by construction, and the generated pages carry no conflict markers when a merge drops one side. The count is kept wherever the page prints members to check it against: the top-level vocabulary row (its `### Allowed Values` list is directly below, and that page is rewritten by a vocabulary change regardless) and the union variant cap (arity does not grow with a ledger). Only the in-shape summary copy loses it — the position this module already documents as the cheap one, "the full list is elsewhere, or the JSON Schema is the authority". Not a second omission style: the bare `…` is the token the same cell already uses for its key elision, which has never quantified what it withheld. The refusal threshold is still measured against the quantified marker, so this changes the notation and never which bodies elide. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza * chore(spec-docs): regenerate references from the merged tree Discharges the os-regen deferral recorded by the merge commit. Carries main's objectstack-ai#9234 vocabulary change (`error.code` closed at every door) through the generator together with this branch's unquantified in-shape marker. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #9106
Part of, notFixes, by PM ruling (superseding the dispatch prompt, which had specifiedFixes #9106). The ruling on #9106 names the ADR-0112 prose as the deliverable that matters most, and that prose is in #9233, behind a human-approval gate. If this PR carried the closing keyword, the card would auto-close the moment the code landed while half its ruling sat unmerged — the exact silent half-delivered state #9106 exists to prevent. #9233 carries the closing keyword instead, so #9106 closes only when both halves are in.Implements the maintainer ruling of 2026-08-16:
error.codeis a closed vocabulary at every door. An author-thrown code that is not anErrorCodemember is demoted to the wire'sdeclaredCodeat the dispatcher exits, mirroring whatresolveThrownHttpErroralready computed for the REST package door — and #7867's capability is preserved: the author's code still crosses the sandbox and still reaches the wire, in the open channel instead of the closed one.The two binding items from the ruling
1. The binding precondition — measured, empty, search proven. No existing consumer of the actions door branches on author-authored strings in
error.code. Method and full classification are on the issue; the load-bearing parts, re-run independently at this head:check:dispatcher-error-vocabularyreports), not from a hand list.packages/client,client-react,examples,packages/qa, the objectui checkout and the hand-written docs — 480 distinct literals, 409 outside the union. The widening is the point: objectui branches through a helper (errorCodeIs), which anx.code === 'LIT'scan cannot see.ANALYTICS_NOT_INSTALLED,DEV_ENV_PLAN_LOCKED) and it sees the helper-mediatedATTACHMENT_DOWNLOAD_DENIEDbranch that a comparison-shape scan misses.errorCodeIs/errorCodeIsAnyOf; 11 are closed-union members. The twelfth,INVALID_PAYLOAD, has zero producers anywhere in this repo and rides an OR with the registeredINVALID_METADATAplus astatus === 422test — nothing demotes, and the affordance cannot be lost.objectui/packages/core/src/actions/actionResponse.tsis, by its own docblock, "the ONE place aPOST /api/v1/actions/...response is interpreted", and it resolves failures throughactionErrorDetail, which readserror/error.message/messageand nevercode.DUPLICATEanywhere.objectstack-ai/cloudis not reachable from this session (I attempted to attach it; access denied). Evidence it is unaffected is unchanged: cloud's consumer-branched entitlement codes ride its own control-plane sender, not this repo's dispatcher exits.2. ADR-0112 text — drafted, in its own PR, awaiting maintainer merge. Prime Directive #14 keeps
docs/adr/**off this PR: the amendment is #9233 onclaude/issue-9106-adr-0112-closed-everywhere. It states the closure holds everywhere, namesdeclaredCodeas the open author-authored channel, pins "presence means demotion" as its semantics, records that #7867 is preserved rather than retired, names where the rule is pinned, and fencesDUPLICATEoff from the ledger. ⛔ This PR can land without it — but the card stays open until it lands, which is what thePart ofabove buys.Review notes on the inherited commit
The implementation commit was written by a previous session that died mid-verification, so nothing in it had passing evidence behind it. Reviewed as a stranger's PR; it held up, with these corrections:
sendErroroverload does not narrow, so an unregisterederror.codereaches the wire there too #9098 resolved semantically, both intents stacked.The REST door's ownsendErroroverload does not narrow, so an unregisterederror.codereaches the wire there too #9098 landed a correction sayingpackages/resthas a second,error: anyresponder and that thrown errors there are "NOT narrowed, deliberately and symmetrically with this door". The branch's rewrite had dropped that paragraph and re-asserted the claim The REST door's ownsendErroroverload does not narrow, so an unregisterederror.codereaches the wire there too #9098 had just falsified. The merged text keeps The REST door's ownsendErroroverload does not narrow, so an unregisterederror.codereaches the wire there too #9098's correction, scopes "either door" to the two this resolver actually serves, and states the flat-dialect path per path.rest-server.ts里三个相邻/metahandler 的错误信封是三种不同形状,其中两种不符合 ADR-0112 #7035 is no longer open (landed via PR fix(rest): put the/meta501 refusals inside the ADR-0112 error envelope (#7035) #7293, three/meta501 handlers) — The REST door's ownsendErroroverload does not narrow, so an unregisterederror.codereaches the wire there too #9098's prose cites it as the live envelope card. Corrected here and in the ADR; the real remaining question is filed as finding:packages/rest's flatsendThrownErrorstill puts a thrown error'scodeon the wire un-narrowed — ADR-0112's closure does not reach that door #9232.packages/types/src/thrown-http-error.ts, the definition site of both spellings.for (const code of PENDING_AT_DISPATCHER_DOOR)loop inerror-envelope.conformance.test.tscurrently generate zero tests — that list is empty post-spec: register the dispatcher conformance gate's reported error codes in ERROR_CODE_LEDGER (spec half of the #8087 ruling) #8846. The demote's real coverage is the three cases named below, each independently falsified.Verification
Everything below at
c6255040e, aftergit merge origin/main(which had moved substantially) plus the deferred regeneration.pnpm --filter @objectstack/runtime testpnpm --filter @objectstack/types testpnpm --filter @objectstack/spec testpnpm --filter @objectstack/rest testtypecheck(types, runtime)pnpm --filter @objectstack/spec check:generatedcheck:dispatcher-error-vocabularycheck:type-check-debt --re-measureReverse verification — direction predicted before running, and observed: red. With the demote ablated in
errorFromThrown(the pre-rulingthrown.declaredCodestraight intoerror.code), exactly three cases fail: the actions-doorDUPLICATEwitness, the sandbox-limb conformance case, and the package-door demote pin. Restored from the committed branch state, tree verified clean, and the gate union re-run afterwards.Gate union derived from the real changed paths (
node scripts/pm/dispatch-gates.mjs), not from memory — the derivation addedcheck:query-options-erasure,check:where-matcher,check:engine-double-contract,check:type-check-debt(convention-triggered by the touched test files) and the changeset family. All run, all green.check:type-check-debtis the gate that refuses on an unbuilt closure — the workspace was built exactly aslint.ymldoes (turbo run build --filter=./packages/* --filter=./packages/*/*) before it.What honours the ruling's fences
DUPLICATEis re-homed, NOT registered. It stays absent fromERROR_CODE_LEDGERand from thePENDING_LEDGER_REGISTRATIONhand-off (asserted), fenced off from spec: register the dispatcher conformance gate's reported error codes in ERROR_CODE_LEDGER (spec half of the #8087 ruling) #8846. Registering one tenant spelling closes nothing — the next app picks a different string.ctx.api.object().update()against a nonexistent id answers 400 (or worse) instead of 404, while the protocol and callData paths both gate correctly #7867's capability is preserved. The author's code crosses the sandbox and reaches the wire; only its slot changed. The actions-door test pins exactly that end to end.declaredCodeis emitted only when the demote actually happened — never as a second copy of a registeredcode.demotedDeclaredCode()is the single definition, and the conformance suite asserts the property for every body the door emits.Model-tier substitution — recorded, not silent
The card and its triage comment mandate
model: claude-fable-5for this work under the standing tiering clause (wire-contract behaviour change). That tier's quota is exhausted, and the maintainer explicitly approved finishing on opus instead. The tiering rule was waived by an explicit maintainer decision for this card — it was not overlooked, and this note exists so a future reader can see which of the two it was.Out of scope, filed not fixed
packages/rest's flatsendThrownErrorstill puts a thrown error'scodeon the wire un-narrowed — ADR-0112's closure does not reach that door #9232 —packages/rest's flatsendThrownErrorstill passes a throwncodethrough un-narrowed. Left alone deliberately: that file is The REST door's ownsendErroroverload does not narrow, so an unregisterederror.codereaches the wire there too #9098's surface, the field is the body's top-levelcoderather thanerror.code, and The REST door's ownsendErroroverload does not narrow, so an unregisterederror.codereaches the wire there too #9098's own note calls narrowing it a separate public-contract decision. Going to the maintainer, not carved into the invariant here.Generated by Claude Code