fix(rest): put the /meta 501 refusals inside the ADR-0112 error envelope (#7035) - #7293
Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also reference the affected code. These are read-only:
|
Fixes #7035
Three adjacent
/metahandlers inpackages/rest/src/rest-server.tsanswered thesame 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 shapePOST /meta/_migrate-storedalready 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'sline numbers had drifted (
:4697/:4996matched no revision ofmainatfiling time), so I relocated by content.
Relocating by content found three, not two:
POST /meta/_migrate-stored{ error: { code, message } }DELETE /meta/:type/:name{ error: '…' }PUT /meta/:type/:name{ error: '…', code }errorPUT /meta/:type/:section/:name{ error: '…', code }errorThe card's table lists one
PUT. The twoPUTrefusals 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
errorbodies — that is thecard'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_IMPLEMENTEDis already a member ofthe standard catalog (
packages/spec/src/api/errors.zod.ts), and it is the codethe catalog maps 501 to —
standardErrorCodeForHttpStatus(501)returns it, pinnedin
packages/spec/src/api/errors.test.ts. So the code is kept as-is, nested, andno catalog entry is minted;
packages/specis untouched.The gate half: measured, and it is not a one-rule extension
The ruling asked to extend the
error-code-casing/route-envelopegate familyto 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-envelopedoes not scanrest-server.tsat all. Itsdiscover()collects only files ending-routes.ts, plus the one namedi18n-service-plugin.ts. Running its ownscanSourceagainstrest-server.tsby hand reports
responses: 208, ok: 2, err: 0, stringError: 44. Bringing thefile into the scan therefore means a ratchet pinning 208 write sites in a file
that changes several times a day — every unrelated
res.jsonadded or removedflips the number — and the gate's model ("route everything through the shared
sendOk/sendError") is card option 3 for this file.codedialect is invisible to the scanner even so.scanSourcecounts
stringErrorandprivateOk; it has no counter for "top-levelcodeas a sibling of
error". Covering the second of this card's two dialects needsa new counter plus self-test cases, not a table row.
check:error-code-casingdoes scanrest-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-envelopewent 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 caseasserts the ADR-0112 pair —
statusandbody.error.codeat the nestedposition — plus both retired dialects being gone (
bodyhas no top-levelcode;body.erroris an object, not a string). NotoThrow-shaped assertion: thesehandlers 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,deleteMetaItemandmigrateStoredMetadata, andisSystemclears the capability gate that firesbefore the protocol is probed.
Two cases are deliberately not defect detectors and are labelled as such: the
_migrate-storedcase pins the anchor shape so the convergence keeps itsreference 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:
The failure text is the card's argument verbatim:
body.error.codereadundefined. The 2 green are the anchor and the twin-equality pin, as predicted.Green with the fix in place:
@objectstack/restTEST_DEBT: recorded 163, measured 155 on this branch, with0 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
patchon@objectstack/rest. Wire-visible on three 501 branches: a caller runninga kernel without metadata writers sees the code move from
err.codeintoerr.error.code, and theDELETEroute'serrorbecomes an object. No in-repoor 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
rest-server.ts's non-conforming error bodies (44 bare-stringerrorsites by the gate's own scanner) — card option 2, untouched here.packages/runtime/src/domains/meta.ts— out perthe card's own 未实测 fence.
Generated by Claude Code