Skip to content

fix(runtime): demote non-enum author-thrown codes to declaredCodeerror.code closed at every door (#9106) - #9234

Merged
os-project-manager merged 6 commits into
mainfrom
claude/issue-9106-demote-author-codes
Aug 17, 2026
Merged

fix(runtime): demote non-enum author-thrown codes to declaredCodeerror.code closed at every door (#9106)#9234
os-project-manager merged 6 commits into
mainfrom
claude/issue-9106-demote-author-codes

Conversation

@os-project-manager

@os-project-manageros-project-manager commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Part of #9106

⚠️Part of, not Fixes, by PM ruling (superseding the dispatch prompt, which had specified Fixes #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.code is a closed vocabulary at every door. An author-thrown code that is not an ErrorCode member is demoted to the wire's declaredCode at the dispatcher exits, mirroring what resolveThrownHttpError already 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:

  • The closed union was extracted from the built spec (288 members — the same count check:dispatcher-error-vocabulary reports), not from a hand list.
  • Widened literal scan, not a comparison-shape scan: every SCREAMING_SNAKE string literal across 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 an x.code === 'LIT' scan cannot see.
  • Proof-of-search before believing the empty result: the scan finds the known-present out-of-vocabulary literals (ANALYTICS_NOT_INSTALLED, DEV_ENV_PLAN_LOCKED) and it sees the helper-mediated ATTACHMENT_DOWNLOAD_DENIED branch that a comparison-shape scan misses.
  • Every helper-mediated branch code enumerated: 12 distinct codes reach errorCodeIs / errorCodeIsAnyOf; 11 are closed-union members. The twelfth, INVALID_PAYLOAD, has zero producers anywhere in this repo and rides an OR with the registered INVALID_METADATA plus a status === 422 test — nothing demotes, and the affordance cannot be lost.
  • The actions door specifically:objectui/packages/core/src/actions/actionResponse.ts is, by its own docblock, "the ONE place a POST /api/v1/actions/... response is interpreted", and it resolves failures through actionErrorDetail, which reads error / error.message / message and nevercode.
  • Zero branches on DUPLICATE anywhere.
  • Named residual:objectstack-ai/cloud is 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 on claude/issue-9106-adr-0112-closed-everywhere. It states the closure holds everywhere, names declaredCode as 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 fences DUPLICATE off from the ledger. ⛔ This PR can land without it — but the card stays open until it lands, which is what the Part of above 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:

Verification

Everything below at c6255040e, after git merge origin/main (which had moved substantially) plus the deferred regeneration.

WhatResult
pnpm --filter @objectstack/runtime test165 files, 2463 passed
pnpm --filter @objectstack/types test12 files, 348 passed
pnpm --filter @objectstack/spec test407 files, 10834 passed
pnpm --filter @objectstack/rest test122 files, 2011 passed
typecheck (types, runtime)clean
pnpm --filter @objectstack/spec check:generated13 of 13 artifacts up to date
check:dispatcher-error-vocabularyOK — 7 sites, all classified, 0 awaiting a ledger entry
check:type-check-debt --re-measureOK — 33 entries, none above its recorded number

Reverse verification — direction predicted before running, and observed: red. With the demote ablated in errorFromThrown (the pre-ruling thrown.declaredCode straight into error.code), exactly three cases fail: the actions-door DUPLICATE witness, 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 added check: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-debt is the gate that refuses on an unbuilt closure — the workspace was built exactly as lint.yml does (turbo run build --filter=./packages/* --filter=./packages/*/*) before it.

What honours the ruling's fences

Model-tier substitution — recorded, not silent

The card and its triage comment mandate model: claude-fable-5 for 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


Generated by Claude Code

…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
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/runtime, @objectstack/spec, @objectstack/types.

110 hand-written doc(s) reference the affected code — list omitted above 15 rows. Re-derive: node scripts/docs-audit/affected-docs.mjs --json origin/main.

7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 17, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 17, 2026 04:56
@os-project-manager
os-project-manager added this pull request to the merge queueAug 17, 2026
Merged via the queue into main with commit 2f65b1bAug 17, 2026
29 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-9106-demote-author-codes branch August 17, 2026 05:15
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-project-manager@claude