Skip to content

fix(rest): put the /meta 501 refusals inside the ADR-0112 error envelope (#7035) - #7293

Merged
os-help merged 1 commit into
mainfrom
claude/issue-7035-meta-501-envelopes
Aug 10, 2026
Merged

os-help merged 1 commit into
mainfrom
claude/issue-7035-meta-501-envelopes

Conversation

@os-help

@os-help os-help commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7035

Three adjacent /meta handlers in packages/rest/src/rest-server.ts answered the
same condition — "the protocol implementation lacks this method", a 501 — in three
different shapes. All of them now speak the ADR-0112 nested envelope,
{ error: { code, message } }, which is the shape POST /meta/_migrate-stored
already used.

Per the triage promotion ruling of 2026-08-09 this is the card's option 1;
option 3 (a shared envelope constructor across the whole file) is explicitly not
this card.

Premise: verified, and it moved

Both sites the card named are still non-conforming on origin/main. The card's
line numbers had drifted (:4697 / :4996 matched no revision of main at
filing time), so I relocated by content.

Relocating by content found three, not two:

route before shape
POST /meta/_migrate-stored { error: { code, message } } nested — conformant, the anchor
DELETE /meta/:type/:name { error: '…' } bare string, no code at all
PUT /meta/:type/:name { error: '…', code } code as a sibling of error
PUT /meta/:type/:section/:name { error: '…', code } code as a sibling of error

The card's table lists one PUT. The two PUT refusals were byte-identical,
and the file's own gate comment on the compound route calls the pair "WORD FOR
WORD the same mechanism, because it is word for word the same operation". So
"the sibling-key site" could not be resolved to one of them by content — and
fixing one while leaving its literal twin would have left the wrong template in
the file directly beside the right one, which is precisely the harm the card
describes ("下一个照抄相邻 handler 的人会照抄哪一种,取决于他滚到了哪一行").
I converged all three and am flagging the count deviation rather than burying it.

I did not touch the file's other ~44 bare-string error bodies — that is the
card's option 2, and it is not this card. See the finding below.

The catalog question the card left 未实测

Checked, and the catalog answers it: NOT_IMPLEMENTED is already a member of
the standard catalog (packages/spec/src/api/errors.zod.ts), and it is the code
the catalog maps 501 to — standardErrorCodeForHttpStatus(501) returns it, pinned
in packages/spec/src/api/errors.test.ts. So the code is kept as-is, nested, and
no catalog entry is minted; packages/spec is untouched.

The gate half: measured, and it is not a one-rule extension

The ruling asked to extend the error-code-casing / route-envelope gate family
to cover these sites if it does not already. Measured: neither gate sees this
defect, and closing that is a restructuring job rather than a rule.

  • check:route-envelope does not scan rest-server.ts at all. Its
    discover() collects only files ending -routes.ts, plus the one named
    i18n-service-plugin.ts. Running its own scanSource against rest-server.ts
    by hand reports responses: 208, ok: 2, err: 0, stringError: 44. Bringing the
    file into the scan therefore means a ratchet pinning 208 write sites in a file
    that changes several times a day — every unrelated res.json added or removed
    flips the number — and the gate's model ("route everything through the shared
    sendOk / sendError") is card option 3 for this file.
  • The sibling-code dialect is invisible to the scanner even so. scanSource
    counts stringError and privateOk; it has no counter for "top-level code
    as a sibling of error". Covering the second of this card's two dialects needs
    a new counter plus self-test cases, not a table row.
  • check:error-code-casing does scan rest-server.ts, but only for casing
    lowercase literals in code positions. Position is outside its thesis, and it is
    textual by design, which is exactly what check:route-envelope went AST to avoid.

Per the dispatch's own instruction for this outcome, filed as an out-of-scope
finding instead of built here. Both gates were run and are green on this branch.

Tests

New: packages/rest/src/meta-501-envelope.test.ts (7 cases). Each refusal case
asserts the ADR-0112 pair — status and body.error.code at the nested
position — plus both retired dialects being gone (body has no top-level code;
body.error is an object, not a string). No toThrow-shaped assertion: these
handlers send rather than throw, so "the promise resolved" could not separate
"refused with the wrong envelope" from "did not refuse at all", and the wrong
envelope is the defect.

The 501 branches are reachable only when the protocol lacks the method, so the
in-file stub deliberately omits saveMetaItem, deleteMetaItem and
migrateStoredMetadata, and isSystem clears the capability gate that fires
before the protocol is probed.

Two cases are deliberately not defect detectors and are labelled as such: the
_migrate-stored case pins the anchor shape so the convergence keeps its
reference point, and the twin-equality case pins the two PUTs as one contract.

Reverse verification (direction predicted before running: 5 red, 2 green).
Fix removed with git checkout origin/main -- packages/rest/src/rest-server.ts,
restored from a patch file:

× DELETE /meta/:type/:name — was a bare-string `error` with no code at all
× DELETE /meta/:type/:name?dropStorage=true — the destructive form answers the same envelope
× PUT /meta/:type/:name — was `code` as a sibling of `error`
× PUT /meta/:type/:section/:name — the compound twin, same dialect, same fix
× one code path reads every refusal — `err.error.code` on all four routes
AssertionError: expected undefined to be 'NOT_IMPLEMENTED' // Object.is equality
 Tests  5 failed | 2 passed (7)

The failure text is the card's argument verbatim: body.error.code read
undefined. The 2 green are the anchor and the twin-equality pin, as predicted.

Green with the fix in place:

pnpm --filter @objectstack/rest test
 Test Files  77 passed (77)
      Tests  1226 passed (1226)

pnpm --filter @objectstack/rest typecheck   → clean
pnpm check:route-envelope                   → ✓ 8 modules, 7 conformant, 0 ratcheted, 1 exempt
pnpm check:error-code-casing                → ✓ self-test 17 cases; no lowercase codes in 3378 files
node scripts/check-nul-bytes.mjs            → OK (6640 files)
pnpm check:type-check-debt                  → OK, 33 entries re-measured, none above its recorded number

@objectstack/rest TEST_DEBT: recorded 163, measured 155 on this branch, with
0 errors attributable to the new test file. The new file's first draft did add
one TS2835 (import … from './rest-server'); it is written './rest-server.js'
instead, matching three existing test files in the package. That matters because
#7248 lowers this entry to 155 — a zero-margin ceiling this branch stays clean
against whether or not #7248 is in the merge base.

Changeset

patch on @objectstack/rest. Wire-visible on three 501 branches: a caller running
a kernel without metadata writers sees the code move from err.code into
err.error.code, and the DELETE route's error becomes an object. No in-repo
or objectui consumer reads either retired position — grepped both repos for the
two message strings and for the routes' shapes, zero hits — so no tolerant
dual-read window is needed.

Out of scope, filed separately

  • The rest of rest-server.ts's non-conforming error bodies (44 bare-string
    error sites by the gate's own scanner) — card option 2, untouched here.
  • The dispatcher-side envelope in packages/runtime/src/domains/meta.ts — out per
    the card's own 未实测 fence.

Generated by Claude Code

…elope (#7035)

Three adjacent `/meta` handlers answered "the protocol implementation lacks
this method" in three different shapes. `POST /meta/_migrate-stored` sent the
ADR-0112 nested `{ error: { code, message } }`; `DELETE /meta/:type/:name` sent
a bare-string `error` with no code at all; and the two `PUT` save twins sent the
code as a *sibling* of `error`.

A client reading `err.error.code` — the position ADR-0112 declares — therefore
got `undefined` on three of the four routes, and `undefined` takes the "no code"
branch rather than an error branch. That is Prime Directive #12's
producer-is-the-contract broken in the shape that forces `??` chains on
consumers.

All four now send the nested shape. `NOT_IMPLEMENTED` is unchanged: it is
already the standard catalog's member for 501 (`spec/src/api/errors.zod.ts`,
`standardErrorCodeForHttpStatus(501)`), so no catalog entry is minted and
`packages/spec` is untouched.

The card's table sampled two non-conforming sites; measurement found THREE. The
two `PUT` refusals were byte-identical — the file's own gate comment calls them
"WORD FOR WORD the same mechanism" — so converging one and leaving its literal
twin would have left the wrong template beside the right one, which is the harm
this card is about.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0158ZQo7LiHSxGWpYKuPq1wu
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 10, 2026 5:12am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest.

9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/connect-mcp.mdx (via @objectstack/rest)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest)
  • content/docs/api/index.mdx (via @objectstack/rest)
  • content/docs/permissions/authentication.mdx (via @objectstack/rest)
  • content/docs/permissions/system-context.mdx (via packages/rest)
  • content/docs/plugins/index.mdx (via @objectstack/rest)
  • content/docs/plugins/packages.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest)

3 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/rest)
  • content/docs/releases/v12.mdx (via @objectstack/rest)
  • content/docs/releases/v17.mdx (via @objectstack/rest)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 10, 2026
@os-help
os-help marked this pull request as ready for review August 10, 2026 05:43
@os-help
os-help added this pull request to the merge queue Aug 10, 2026
Merged via the queue into main with commit 870f90c Aug 10, 2026
26 checks passed
@os-help
os-help deleted the claude/issue-7035-meta-501-envelopes branch August 10, 2026 05:57
os-project-manager pushed a commit that referenced this pull request Aug 17, 2026
…9232)

#7035 — cited by #9098's prose as the open envelope-position finding — is
closed (PR #7293, three /meta 501 handlers). The flat sendThrownError door's
remaining vocabulary question is filed as #9232; the amendment now names it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
os-project-manager pushed a commit that referenced this pull request Aug 17, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
… `error.code` closed at every door (objectstack-ai#9106) (objectstack-ai#9234)

* fix(runtime): demote non-enum author codes to declaredCode at the dispatcher door (objectstack-ai#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
(objectstack-ai#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 objectstack-ai#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

* chore(spec): regenerate authorable surface + reference docs after merging origin/main (objectstack-ai#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

* docs(adr): anchor the ADR-0112 closed-vocabulary demote rule at its definition site (objectstack-ai#9106)

Prime Directive objectstack-ai#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 objectstack-ai#9106 closed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza

* test(runtime): point the door-parity note at the live envelope card (objectstack-ai#9232)

objectstack-ai#7035, which objectstack-ai#9098's prose named as the open envelope-position finding, is
closed (PR objectstack-ai#7293). The flat sendThrownError door's remaining vocabulary
question is filed as objectstack-ai#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

---------

Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang pushed a commit that referenced this pull request Aug 18, 2026
Both `ratchet:` fields in the route-envelope gate (`rest-server.ts`,
`error-response.ts`) named #7035, closed since 2026-08-10 (PR #7293, three
`/meta` 501 handlers converged — never the owner of "convert this file onto
the shared sendOk/sendError"). Triage executed option 1 at the triage layer:
#9559 is a `tracking` card that stays open while any `ratchet:` field names
it, with both legal exits (a dedicated envelope card, or the ADR-0112-D5
permanently-accepted path) written into it.

Repoints both fields from #7035 to #9559, preserving the option-1 prose
verbatim. Nothing else moves: no counter, threshold, or exempt entry changed
— confirmed by diffing the gate's own before/after output, which is
byte-identical except the citation. The eleven other `#7035` mentions in the
file are historical prose (what #7035 did, past tense) and are left alone.

Fixes #9461

Co-Authored-By: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 23, 2026
… `declaredCode` is the open channel (objectstack-ai#9106) (objectstack-ai#9233)

* docs(adr): ADR-0112 amendment — error.code is closed at every door, declaredCode is the open channel (objectstack-ai#9106)

Records the maintainer ruling of 2026-08-16 on objectstack-ai#9106 as an amendment on D4,
plus a Consequences bullet for the wire change it implies.

The gap it closes is the one the card names as the deciding axis: an agent
reading ADR-0112 today concludes error.code is closed and may write a consumer
that switches exhaustively on it — wrong at exactly one door, in a way no gate
reports. The amendment states the closure holds everywhere, names declaredCode
as the open author-authored channel, fixes 'presence means demotion' as its
semantics, records that objectstack-ai#7867's sandbox passthrough is preserved rather than
retired, names where the rule is pinned, and fences DUPLICATE off from the
ledger.

⛔ Maintainer-merged per Prime Directive objectstack-ai#14 — split into its own PR so the
implementation can land through the normal path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza

* docs(adr): point the objectstack-ai#9106 amendment's scope note at the live card (objectstack-ai#9232)

objectstack-ai#7035 — cited by objectstack-ai#9098's prose as the open envelope-position finding — is
closed (PR objectstack-ai#7293, three /meta 501 handlers). The flat sendThrownError door's
remaining vocabulary question is filed as objectstack-ai#9232; the amendment now names it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 23, 2026
…ctstack-ai#9559 (objectstack-ai#9564)

Both `ratchet:` fields in the route-envelope gate (`rest-server.ts`,
`error-response.ts`) named objectstack-ai#7035, closed since 2026-08-10 (PR objectstack-ai#7293, three
`/meta` 501 handlers converged — never the owner of "convert this file onto
the shared sendOk/sendError"). Triage executed option 1 at the triage layer:
objectstack-ai#9559 is a `tracking` card that stays open while any `ratchet:` field names
it, with both legal exits (a dedicated envelope card, or the ADR-0112-D5
permanently-accepted path) written into it.

Repoints both fields from objectstack-ai#7035 to objectstack-ai#9559, preserving the option-1 prose
verbatim. Nothing else moves: no counter, threshold, or exempt entry changed
— confirmed by diffing the gate's own before/after output, which is
byte-identical except the citation. The eleven other `objectstack-ai#7035` mentions in the
file are historical prose (what objectstack-ai#7035 did, past tense) and are left alone.

Fixes objectstack-ai#9461

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding: rest-server.ts 里三个相邻 /meta handler 的错误信封是三种不同形状,其中两种不符合 ADR-0112

2 participants