fix(rest): converge every declared-4xx exit on the declared-code prefix strip (#13095) - #14120
Conversation
… declared-code prefix (#13095) Spread the #12975 ruling's declared-code-anchored strip (withoutDeclaredCodePrefix) to resolveErrorResponse's passThroughStatus 4xx arm, converging every /data exit (batch/createMany/updateMany/deleteMany/ clone via handleRouteError) and, because the record-share classified arm re-dresses the same classification, the share family with it. Converge the approvals door's blanket /^[A-Z_]+:\s*/ strip onto the code the row answers (the regex shape #12975 rejected). Correct the #8111 comment's false mechanism claim in both homes (rest-server.ts and the sharing-envelope.test docblock) without over-claiming the measured consequence. Move the MEASURED-NOT-REPAIRED pin to the CONVERGENCE pin it announced, and add the moved arm's own anchoring controls. Per the 2026-08-31 maintainer ruling (option 1) on #13095. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UngCYXF98BVpYA9hfz6NYk
…set (minor + migration note) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UngCYXF98BVpYA9hfz6NYk
…solve-error-response-code-prefix
…#13095) The header's ablation record spelled the approvals door's old blanket strip as a slash-delimited regex literal inside a block comment; its closing */ ended the comment 40 lines early. check:engine-double-contract's ts-parse refusal caught it. Spelled without delimiters. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UngCYXF98BVpYA9hfz6NYk
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. 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 602727d5d4dcbbe3c8f4e0d487e4c98433bedb44 && git checkout 602727d5d4dcbbe3c8f4e0d487e4c98433bedb44
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 556ebc1509b1a6dcc842425ec58e74d095b7c966 209965648d4871bf2d20df5ccf4fc9357deae682 && git checkout -B drift-repro 556ebc1509b1a6dcc842425ec58e74d095b7c966 && git merge --no-ff 209965648d4871bf2d20df5ccf4fc9357deae682
node scripts/docs-audit/affected-docs.mjs --json 556ebc1509b1a6dcc842425ec58e74d095b7c966 |
|
REQUEST CHANGES — one required edit, everything else verified as ruled: the changeset's migration note must carry the cloud NOT-MEASURED bound that every other artifact in this PR carries. This is an at-tier contract review under The census premise (the question that matters most)What I measured myself, each zero with a control that returned non-zero:
What I could not reach: Is cloud materially exposed? From what is reachable: cloud consumes this framework at a pinned SHA ( My judgement, stated separately as asked: the unmeasured cloud census is an acceptable residue to be recorded, not a merge blocker — conditional on the changeset edit above, which is what makes the residue "recorded" where a cloud engineer would look. Reasons: the director seat issued the ruling knowing cloud was unreachable from its own seat (comment 5478591418: 「cloud 本席不可达」) and made only a non-zero census a fork trigger, not unreachability a gate; the pinned-SHA seam bounds the blast radius to a diagnosable pin-bump failure; and the residue is one grep (the ready-made command in comment 5475900981) that an authorized cloud-lane seat can run in minutes. I recommend the PM file a cloud-lane card (the #6026 lane) to run that census before cloud's next Clause ② — both limbs, measured
The four ruled items — delivered as ruled
Ablation — reproduced independently, controls verifiedIn a detached review worktree at
The deliberate non-convergence — right callThe empty-string TYPE-keyed degrade is pinned at Verdict mechanics
Generated by Claude Code |
…hipping artifact (#13095) At-tier contract review finding on PR #14120: the changeset said a cross-repo census found zero prefix-branching consumers without naming objectstack-ai/cloud as unmeasured. The PR body and both #8111 comment homes carried the bound; the changeset -- the artifact that feeds release notes and reaches operators -- did not, so its zero read as 'checked everywhere'. The census statement now names the three repos it covered (each with a positive control) and states that cloud was NOT MEASURED and is deliberately not reported as clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UngCYXF98BVpYA9hfz6NYk
Fixes #13095
Implements the 2026-08-31 director-seat ruling (option 1, maintainer verbatim 「同意」, comment 5478591418) — adopted verbatim, not re-derived.
Clause-②: yes
This PR changes what a published REST door puts in
error/messageon the wire, and parks as a draft for at-tier contract review by design.Measured wire movement (real registered route handlers, one producer per row — a thrown
Errorcarryingcode: 'FORBIDDEN',status: 403, messageFORBIDDEN:+ the localized sentence, the exact shapeplugin-sharing/src/sharing-plugin.ts's by-id write gate throws):PATCH /data/:object/:id(by-id, ruled by the #12975 ruling)POST /data/:object/batchand every bulk/clone exit throughhandleRouteErrorFORBIDDEN:+ sentenceGET/POST/DELETE /data/:object/:id/shares...classified arm (nested envelopeerror.message)FORBIDDEN:+ sentencehandleApprovalError) — a message opening with a DIFFERENT SCREAMING_SNAKE token than the answered codecodeis ever strippedAll movement is subtractive on message text only; statuses,
code,declaredCode,userMessageuntouched. Everything else is pinned unchanged: no-code4xx keeps its prefix (the token is nowhere else on the wire), a prefix not naming the declared code stays (driver prose), declared-5xx prose withholding, the share family's bare-Error prefix-idiom arm, and the passthrough's empty-string TYPE-keyed degrade (a standing pin this ruling did not authorize moving — deliberately preserved, see below).First-verify premise: the prefix-consumer census
Does any consumer branch on the ADR-0111
CODE:prefix in the wireerrortext?cc837dbfezero; incrementcc837dbfe..836a29c27are-scanned = zero new, control: 7 genericstartsWith(added lines read)a5a799dzero; incrementa5a799d..6c1d95d= zero new, control: 5)includeshit reads its own test's stdout marker, not an error body; control: 79startsWith(hits)#8111comment. The ruling itself was made with cloud unmeasured — it records 「cloud 本席不可达」 (comment 5478591418) — and its only stop condition is a NON-ZERO census. With every reachable scope at zero and cloud carried as NOT MEASURED in every artifact of this change (this body, both#8111comment homes, the changeset), implementation proceeds and the bound ships with it.No non-zero anywhere reachable ⇒ no fork.
The four mandatory items
resolveErrorResponse'spassThroughStatus4xx arm now applies the existingwithoutDeclaredCodePrefix— declared-code anchored, never a regex anchor. Strip runs before the rest-server 的 4xx 直通把 ≥500 字符的 message 整条换成 "Request failed" —— #5368 刚写好的过滤器拒收措辞,客户端一个字也收不到(实测) #5423 truncation bound; a nothing-but-prefix message degrades toRequest failed(the sibling arm's rule travelling with the strip). The record-share classified arm converges automatically becauseclassifiedRefusalAnswerre-dresses this same answer.#8111comment corrected in two places, one write (commit4633fd31a2):rest-server.ts(located by symbol, aboverespondSharingError) and thesharing-envelope.test.tsdocblock. The false mechanism claim ("stripped below and never reaches the wire") is corrected to name the classified limb that shipped the prefix until this PR; the consequence stays exactly as measured — no consumer branches on the prefix in the two reachable repos, cloud NOT measured — not restated as a guarantee.handleApprovalErrorinrest-server.ts): the blanket SCREAMING_SNAKE-colon regex — the shape the The /data door ships the ADR-0111CODE:prefix inside the user-facingerrorstring, so a localized refusal renders asFORBIDDEN: …in a toast #12975 ruling rejected — is converged onto the code the matched row answers, the same anchored semantics asrespondSharingError's prefix arm andwithoutDeclaredCodePrefix.CONVERGENCE [#13095]pin asserting the new truth, with a docblock recording what moved and why; new §6 (bulk door) and §7 (approvals door) pin the moved arms' own anchoring controls.Everything located by symbol; the card's and ruling's line numbers had rotted as predicted (
#8111comment and third strip point both moved again since the ruling was written).Verification
pnpm --filter @objectstack/rest test: 164 files / 2771 passed (pre-merge head; targeted 12-file re-run at final head below).typecheckgreen; both edited test files confirmed present intsc --listFiles(coverage measured, not assumed); the only raw tsc errors are the pre-existing pinned debt entries intest-typecheck-debt.json.error-response.tsat pre-fix bytes): predicted exactly 2 red — §5 CONVERGENCE pin + §6 nothing-but-prefix; measured 2 red / 50 green, withrest-hook-refusal-message-parity.test.tsall green both sides — the preserved empty-string pin measurably did not move. §6's no-code and non-matching-prefix controls stayed green on both sides (they red under a pattern-anchored strip, not under the missing fix — controls that can fail for the right reason, wired to a different failure than the fix's absence).rest-server.tsat pre-fix bytes): predicted exactly 1 red — §7 longer-token; measured 1 red / 25 green,rest-approvals-wire-codes.test.tsall green both sides (the anchored strip answers the well-formed idiom byte-identically).4ed81ba8b3(derived bydispatch-gates.mjs --repo objectstack-ai/objectstackon the merged head; origin/main merged first, all repo-level readings taken after the merge): named 37, ran 37, unreconciled 0 (exact-stringcommboth directions). All green exceptcheck-test-completeness.mjs, which exits 3 on its own documented no-log-named local branch: NOT MEASURED locally by its own verdict text; CI measures it with the teed test log.check:type-check-debtverdict line: "check-type-check-coverage --re-measure: OK — 28 ledger entr(ies) re-measured in 660.9s, 1468 raw tsc error(s) total, none above its recorded number."check:system-context-censusgreen with no regeneration needed.pnpm lint(never named by the derivation): full-repo eslint, exit 0.209965648d(the at-tier review's changeset edit): the only file changed vs4ed81ba8b3is.changeset/rest-passthrough-strips-declared-code-prefix.md(git diff --stat, one file). Every changeset-reading gate re-ran green on the new head (nul-bytes, adr-0087-registration, changeset-no-major, empty-changeset, changeset-gate-self-tests, objectui-changeset, doc-authoring, both docs-audit gates); every other gate's input set is byte-identical to4ed81ba8b3where the full union ran, and the full-repo lint reading stands by eslint's own config answer for this file: "File ignored because no matching configuration was supplied".Grading
minor+ migration note in the changeset, per the ruling and the #13347 precedent (an additive envelope change was ruled minor; a subtractive change to wire text is at least as strong a case). Not regraded.Out of scope, untouched here: #13753 and #13906 wait behind this PR on
rest-server.ts; #12975 and #13910 are context only — none of those cards is addressed here and all remain as they are.Generated by Claude Code