Skip to content

Commit ab085a0

Browse files
docs(qa): re-point error-sentences acceptance[1] to HOOK_ABORTED (#28980)
<!-- claude-projects-attribution: {"github_login":"marchtian"} --> _Requested by **田騏** · [project thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSi9paTx1Lu8ENYwwssthfK3h)_ Fixes #28936 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:只改测试清单 `api-backend.error-sentences-and-unique-violation-wire-code` 一项的 acceptance[1](条款与核验文字)和 `automated.ref` 说明,revision 5 → 6,加一条 history;不动任何产品代码。 - 为什么改:产品早已按裁决给"钩子未声明错误码的拒绝"回 `HOOK_ABORTED`,清单却还写着"没有 code / VALIDATION_ERROR",每次发版 QA 都把一个正确的产品判成 FAIL,假红会让跑测的人学会忽略这一项。 - 风险与回滚:只是文档账本,风险极低;回滚即 revert 本 PR。条款从"断言没有 code"变成"断言 code 等于 HOOK_ABORTED",只更严不更松。 - 先看:`docs/qa/platform-checklist/areas/api-backend.json` 里该项的 acceptance[1] 两段文字,以及末尾新增的 revision 6 history 行。 - 两分钟验证:在本分支跑 `pnpm check:platform-checklist`,预期末行打印 `check-platform-checklist: OK — 16 areas, 352 items`。 - 需要你做的:无,席位落地。 ## What changes One item in one file, `docs/qa/platform-checklist/areas/api-backend.json`, item `api-backend.error-sentences-and-unique-violation-wire-code`: - **acceptance[1].clause** — the /data door's answer to a hook that declared no code is now the ledgered ADR-0112 code `HOOK_ABORTED` (`UNDECLARED_HOOK_REFUSAL_CODE`, #27597), never a code-less 400 and never the status-derived `VALIDATION_ERROR`; the hand-built nested-envelope rows (sharing rules, suggested bindings, approvals, package publish) answer 400 `HOOK_ABORTED` with the hook's sentence (package publish through `resolveThrownHttpError`, #27672). Status (400, not 500) and sentence are unchanged. - **acceptance[1].verify** — the live pin asserts `code` equal to `HOOK_ABORTED` (was: no `code` key); the unit pin asserts `HOOK_ABORTED` and no `declaredCode` through `mapDataError`; the ledger pin's rows are `HOOK_ABORTED_400` in its §2 (the old `REFUSED_400` name no longer exists in that file). - **automated.ref** prose — "400 with no code on the /data doors" becomes "400 with code HOOK_ABORTED", and it says the two rest pins also hold acceptance[1]'s code below HTTP. The five pin paths are unchanged. - `revision` 5 → 6, one `history` row citing QA run #28929 and this card. Main's five history rows are kept as an exact prefix; no other item, and no other field of this item, changed. The clause is narrower, not weaker: it used to accept an absent code, and now it demands one specific code. ## Tests Docs-only ledger edit; no product or test file changed, so no package test is owed (`skip-changeset`: nothing under any package's `files[]` moved). Where each new statement comes from, read at base `135054ebaa` and unchanged at head: - `HOOK_ABORTED` is in the ADR-0112 ledger: `packages/spec/src/api/error-code-ledger.zod.ts`, with its #27597 comment. - `UNDECLARED_HOOK_REFUSAL_CODE = 'HOOK_ABORTED'`: `packages/types/src/thrown-http-error.ts`; stamped on the /data door by `sandboxRefusalCodeFields` in `packages/types/src/data-error-classification.ts`, and by `resolveThrownHttpError` for the doors #27672 converged. - Live pin `packages/qa/dogfood/test/error-sentences-live.dogfood.test.ts`: both /data doors assert 400, `body.error` equal to the sentence, `body.code` equal to `HOOK_ABORTED`, zero rows. - Unit pin `packages/rest/src/rest-hook-script-fault-envelope.test.ts`: `mapDataError` answers `HOOK_ABORTED` and has no `declaredCode`. - Ledger pin `packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts`: `HOOK_ABORTED_400 = { status: 400, code: 'HOOK_ABORTED' }` on every sharing-rule, suggested-binding, approval and package-publish row. - The three pins, the dogfood fixture, and the three source files above (`thrown-http-error.ts`, `data-error-classification.ts`, `packages/rest/src/package-routes.ts`) are byte-identical between the QA run subject `7fa9873f` (where #28929 ran both pins green, twice) and this head: `git diff --stat 7fa9873 HEAD` over the seven files is empty. ## Acceptance notes - The triage notes the item is missing from the `docs/NORTH-STAR.md` ladder rows. It is: no ladder row names `api-backend.error-sentences-and-unique-violation-wire-code`. `docs/NORTH-STAR.md` is a separate, governed file, outside this card's one-item surface, so it is left for the seat. - The ledger pin still has one `VALIDATION_ERROR` row by design: a sandboxed refusal that DID declare a code (`MZ_LOCKED`) rides the nested envelope as `VALIDATION_ERROR` plus `declaredCode`. acceptance[1] covers only a hook that declared none, so the new clause does not say "no VALIDATION_ERROR anywhere". - The pins were not re-run locally. The diff changes no tested behaviour, and the files are byte-identical to the subject where #28929 measured them green. ## Verification <details><summary>Checklist validator</summary> ``` pnpm check:platform-checklist :: exit 0 check-platform-checklist: OK — 16 areas, 352 items (347 active, 1 planned); coverage: 40 kinds mapped, 3 waived; traps: 19 documented, 19 in use; ... ``` </details> <details><summary>Derived gates (dispatch-gates --commands, head e9d9711, 16 families)</summary> ``` node scripts/check-adr-links.mjs :: exit 0 node scripts/check-adr-links.mjs --self-test :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 pnpm check:ci-filter-parity :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:platform-checklist :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:watch-hint-literal :: exit 0 Run reconciliation — 16 derived, 16 run, 0 NOT-MEASURED, 0 UNRUN. ✓ dispatch-gates --ran: 16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED (a DERIVED zero — all 16 recorded an exit code and none of them is 3). ``` On the first pass, before `pnpm install`, six of these exited 3 (PREREQUISITE NOT MET), and `check:doc-formula-expressions` exited 3 again until `@objectstack/formula` and `@objectstack/lint` were built. The readings above are the re-runs after that. The CI-only families (type-check lanes, Plan Test Core shards) are CI's; see this PR's check-runs. </details> Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3a76e40 commit ab085a0

1 file changed

Lines changed: 10 additions & 4 deletions

File tree

‎docs/qa/platform-checklist/areas/api-backend.json‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3277,7 +3277,7 @@
32773277
"title": "Error answers made honest on v18: a hook refusal on a hand-built write route answers 400 with the hook's own sentence, a crash answers a generic 500 that withholds the caught message, and a unique-constraint refusal is 409 UNIQUE_VIOLATION with details at the action door, MCP and sandboxed bodies too",
32783278
"since": "v18",
32793279
"status": "active",
3280-
"revision": 5,
3280+
"revision": 6,
32813281
"priority": "P2",
32823282
"surface": "api",
32833283
"personas": [
@@ -3304,9 +3304,9 @@
33043304
"evidence": "the vitest summary line per pin file, with the package name and the subject sha"
33053305
},
33063306
{
3307-
"clause": "the hook refusal answers 400 carrying the hook's own sentence — not 500. On a /data door (the live leg) the envelope carries no `code` when the hook declared none, by pinned design; the hand-built nested-envelope rows (sharing rules, suggested bindings, approvals, package publish) re-dress the same refusal as 400 VALIDATION_ERROR — pin-only, since no package-authorable hook reaches their objects",
3307+
"clause": "the hook refusal answers 400 carrying the hook's own sentence — not 500. On a /data door (the live leg) a hook that declared no code is answered with the ledgered ADR-0112 code `HOOK_ABORTED` (UNDECLARED_HOOK_REFUSAL_CODE in @objectstack/types, #27597) — never a code-less 400 and never the status-derived VALIDATION_ERROR; the hand-built nested-envelope rows (sharing rules, suggested bindings, approvals, package publish) re-dress the same refusal as 400 HOOK_ABORTED with the hook's sentence (package publish through resolveThrownHttpError, #27672) — pin-only, since no package-authorable hook reaches their objects",
33083308
"oracle": "api",
3309-
"verify": "status + message on the live /data door, and the absence of error.code when the hook declared none, read on the live door's body: error-sentences-live.dogfood.test.ts drives POST /data/OBJECT and /createMany on a scratch app whose sandboxed beforeInsert hook throws a sentence, over HTTP, and asserts 400, the sentence verbatim, no `code` key and no row written (rest-hook-script-fault-envelope.test.ts pins the same design at unit through mapDataError); the VALIDATION_ERROR rows of the hand-built nested-envelope routes from rest-write-route-hook-refusal-sentence.ledger.test.ts (REFUSED_400) — both in automated.ref",
3309+
"verify": "status + message + code on the live /data door, read on the live door's body: error-sentences-live.dogfood.test.ts drives POST /data/OBJECT and /createMany on a scratch app whose sandboxed beforeInsert hook throws a sentence and declares no code, over HTTP, and asserts 400, the sentence verbatim as `error`, `code` equal to HOOK_ABORTED and no row written (rest-hook-script-fault-envelope.test.ts pins the same answer at unit through mapDataError: `code` HOOK_ABORTED and no `declaredCode`); the hand-built nested-envelope rows from rest-write-route-hook-refusal-sentence.ledger.test.ts §2, each answering 400 HOOK_ABORTED (HOOK_ABORTED_400) with the hook's sentence after its named downstream was called — both in automated.ref",
33103310
"evidence": "the body"
33113311
},
33123312
{
@@ -3324,7 +3324,7 @@
33243324
],
33253325
"automated": {
33263326
"kind": "dogfood",
3327-
"ref": "packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts + packages/rest/src/rest-hook-script-fault-envelope.test.ts + packages/runtime/src/dispatcher-unique-violation-wire-code.test.ts + packages/services/service-storage/src/storage-routes.server-fault-withheld.test.ts (acceptance[0], unit and engine) + packages/qa/dogfood/test/error-sentences-live.dogfood.test.ts (acceptance[1]–[3] over HTTP on the scratch app packages/qa/dogfood/test/fixtures/error-sentences-fixture.ts: the hook's sentence at 400 with no code on the /data doors, 409 UNIQUE_VIOLATION with details exactly developerMessage + object at the action door, and a storage route's 500 INTERNAL withholding the caught text)"
3327+
"ref": "packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts + packages/rest/src/rest-hook-script-fault-envelope.test.ts + packages/runtime/src/dispatcher-unique-violation-wire-code.test.ts + packages/services/service-storage/src/storage-routes.server-fault-withheld.test.ts (acceptance[0], unit and engine; the first two also hold acceptance[1]'s HOOK_ABORTED below HTTP — the nested-envelope rows at 400 HOOK_ABORTED, and mapDataError's HOOK_ABORTED with no declaredCode) + packages/qa/dogfood/test/error-sentences-live.dogfood.test.ts (acceptance[1]–[3] over HTTP on the scratch app packages/qa/dogfood/test/fixtures/error-sentences-fixture.ts: the hook's sentence at 400 with code HOOK_ABORTED on the /data doors, 409 UNIQUE_VIOLATION with details exactly developerMessage + object at the action door, and a storage route's 500 INTERNAL withholding the caught text)"
33283328
},
33293329
"source": [
33303330
"packages/types/src/sandbox-business-message.ts#sandboxBusinessMessage",
@@ -3363,6 +3363,12 @@
33633363
"date": "2026-10-27",
33643364
"change": "acceptance[1]–[3] are pinned at the layer their oracle names (the release-18.2.0 Tier 1 sweep #26468 scored a1–a3 partial: mapDataError called directly, HttpDispatcher with no Hono adapter, storage handlers over a fake request). The new error-sentences-live.dogfood.test.ts boots the scratch app the fixtures name (fixtures/error-sentences-fixture.ts: a sandboxed refusing hook, a unique email with a declarative update action, a storage root under a regular file) and drives each clause over HTTP; automated.ref and the three verify texts name it. Measured at base 74db43ef. No clause changed",
33653365
"ref": "#26572"
3366+
},
3367+
{
3368+
"revision": 6,
3369+
"date": "2026-11-02",
3370+
"change": "acceptance[1] (clause, verify) and the automated.ref prose re-pointed to HOOK_ABORTED — stale spec from QA run #28929 (release-18.3.0-w4-c2a, at 7fa9873f), api-backend.error-sentences-and-unique-violation-wire-code acceptance[1], re-point card #28936. r5 asserted the pre-ruling shape: no `code` on the /data doors and 400 VALIDATION_ERROR (REFUSED_400) on the nested-envelope rows. #27597 gave an undeclared hook abort the ledgered ADR-0112 code HOOK_ABORTED (UNDECLARED_HOOK_REFUSAL_CODE in packages/types/src/thrown-http-error.ts, stamped by mapDataError) and #27672 carried it to the doors that resolve through resolveThrownHttpError (package publish). Re-read at base 135054ebaa: error-sentences-live.dogfood.test.ts asserts code HOOK_ABORTED on both /data doors, rest-hook-script-fault-envelope.test.ts asserts HOOK_ABORTED and no declaredCode through mapDataError, and rest-write-route-hook-refusal-sentence.ledger.test.ts answers HOOK_ABORTED_400 on every sharing-rule, suggested-binding, approval and package-publish row (no REFUSED_400 remains). Status and sentence unchanged; the clause now names the code instead of its absence, so it is narrower, not weaker. No other clause changed",
3371+
"ref": "#28936"
33663372
}
33673373
]
33683374
}

0 commit comments

Comments
 (0)