test(rest): pin the six approvals wire-code rows with no live emission - #15354
Merged
Conversation
`handleApprovalError` maps nine prefixes to (status, code). Ablation — each row's regex corrupted so it can never match, the whole 176-file `packages/rest` suite run per row — measured which rows anything actually observes: THROTTLED 1 red, FORBIDDEN 3, READ_BACK_FAILED 1, and zero for VALIDATION_FAILED, DUPLICATE_REQUEST, INVALID_STATE, REQUEST_NOT_FOUND, RESUME_TARGET_LOST and RESUME_FAILED. Adds a live-emission pin for each of those six, driven through a real route rather than asserted off the mapping table, each asserting the ADR-0112 minimum (`code` AND `status`) plus the #13095 strip and closed-union membership. The six use six different routes, so they also cover all four catch arms that call the door. `rest-server.ts` is untouched: this pins rows that already exist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
…proval-error-wire-code-pins
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-litant
marked this pull request as ready for review
September 4, 2026 13:17
os-litant
enabled auto-merge
September 4, 2026 13:18
os-litant
deleted the
claude/issue-14849-approval-error-wire-code-pins
branch
September 4, 2026 13:47
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 free
to 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.
Fixes #14849
handleApprovalError(packages/rest/src/rest-server.ts, a closure insideregisterApprovalsEndpoints) maps nine message prefixes to (status, code). This adds a live-emission pin for the six rows that had none — driven through a real route, never asserted off the mapping table.rest-server.tsis untouched. The diff is one test file. The door was mutated only transiently for the ablation, under a restore trap, and restored byte-for-byte (hash compared against the HEAD blob) before anything was committed.Method — every row confirmed by ABLATION, not by grep
This card exists because a file-scoped grep got exactly this wrong once already (#14573 read
FORBIDDENas unpinned; the pin was inrest-data-door-code-prefix.test.ts§7, a file whose declared subject is the #13095 strip). So no row here was judged by grep.The instrument: corrupt the row's regex prefix (
/^CODE/becomes/^ZZABLATED_CODE/) so the row can never match while its literal text stays in the file. A red under that mutation is therefore an observation of live emission, never of literal presence — which is the property the card is about. Then run the whole 176-filepackages/restsuite, one row at a time.Every leg proved the mutation reached disk before anything was measured (injected token counted at 1, original counted at 0), and every restore was proved by
git diff HEADempty plus ahash-objectmatch against the HEAD blob.Per-row result (measured 2026-09-04)
THROTTLEDrest-approvals-wire-codes.test.tsFORBIDDENrest-data-door-code-prefix.test.tsREAD_BACK_FAILEDrest-approvals-wire-codes.test.tsVALIDATION_FAILEDDUPLICATE_REQUESTINVALID_STATEREQUEST_NOT_FOUNDRESUME_TARGET_LOSTRESUME_FAILEDThe
FORBIDDENleg is the instrument's positive control. It reproduces the three reds the #14573 correction measured — two of them in a different file, on a different declared contract — so the same instrument, returning zero for the other six, is returning zero with demonstrated resolving power. This is the check the card asked for, and it passes.⭐ Correction to the card: the prediction did not hold
The card and its triage both predicted that at least one of the six would turn out already covered somewhere unobvious, and said finding that would be a result. Measured: none of them was. All six were genuinely bare, so the card's table was right as a worklist even though it was only a hypothesis when written. That prediction is now answered by measurement instead of being carried forward as a caveat — reported here because "the expected surprise did not happen" is as much a result as the surprise would have been.
Two supporting screens, so "repo-wide" is measured rather than argued:
dist. All nine rows ablated,@objectstack/restrebuilt (dist marker preflight: the ablation token present 9 times indist/index.jsanddist/index.cjs), then the dependent packages' tests run:@objectstack/client214 passed,@objectstack/runtime303 passed,@objectstack/plugin-auth11 passed. No consumer-side pin exists. Three further files were NOT MEASURED there (@objectstack/runtimehad no built entry point at that moment):client/src/analytics-automation-json-erasure.test.tsdrives onlyPOST /analytics/dataset/query,client/src/meta-delete-item-carriers.test.tsdrives meta routes, andverify/src/automation-trigger-paused-run.test.tsdrives no route at all — none can address the approvals door.registerApprovalsEndpoints, reachable only throughPOST .../approvals/requests/:id/ACTION. Only six packages depend on@objectstack/rest(cli, client, plugin-auth, plugin-dev, runtime, verify) and no test in any of them addresses that path.packages/plugins/plugin-approvalsmentionshandleApprovalErroronly in prose — it does not depend on@objectstack/restand cannot observe it; itsRESUME_FAILED/INVALID_STATE/DUPLICATE_REQUESThits assert the throw-site prefix, a different fact.What was added
Six cases in
packages/rest/src/rest-approvals-wire-codes.test.ts, each asserting the ADR-0112 minimum —codeandstatus— plus the #13095 strip and closed-union membership againstApiErrorSchema.Each drives a different route, so the six also cover all four catch arms that call the door (
decisionRoute, therecallroute,flowMoveRoute,threadRoute):VALIDATION_FAILED400reassignapproval-service.tsreassign()DUPLICATE_REQUEST409resubmitapproval-service.tsresubmit()INVALID_STATE409recallapproval-service.tsrecall()REQUEST_NOT_FOUND404revisesendBack()thenloadPendingRow()RESUME_TARGET_LOST409approveassertRunResumable()viadecide()RESUME_FAILED500rejectresumeRecordedOutcome()viadecide()Reverse verification, run after the implementation was committed so the restore leg had the implementation to restore to: ablating each of the six reds exactly its own new case and nothing else. Five red on status (
expected 500 to be 400 / 409 / 409 / 404 / 409).RESUME_FAILEDreds on the code instead —expected 'APPROVAL_REJECT_FAILED' to be 'RESUME_FAILED'— because the degraded shape is a 500 too. That is exactly why ADR-0112 asks for both halves, and the case says so in place.Verification at
f334fcfepnpm --filter @objectstack/rest exec vitest run— Test Files 176 passed (176), Tests 2993 passed (2993).pnpm --filter @objectstack/rest run typecheck— exit 0;check:test-typecheck: OK — @objectstack/rest's test layer compiles under packages/rest/tsconfig.test.json. Coverage is real, not phantom:tsc -p packages/rest/tsconfig.test.json --listFileslists the edited file.pnpm lint— repo-wide (eslint . --no-inline-config), exit 0. No narrowing was needed.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(35 commands), plus the four artifact-roster gates it flags as keeping their roster underpackages/. 37 of 39 exit 0. The two exceptions are NOT MEASURED, not red — both printPREREQUISITE NOT METand exit 3 because they read built output the worktree does not have:pnpm check:dual-build-cjs-loadsandpnpm check:type-check-debt(both wantturbo run buildover the whole workspace). Neither can be moved by this diff in any case — the only changed file is a*.test.ts, and@objectstack/restpublishesdistalone.$RUNNER_TEMP,$MERGE_BASE) and have no local invocation; they run in CI.skip-changeset: the diff adds one test file and publishes nothing from any package.Generated by Claude Code