Repository navigation
fix(metadata-protocol): an undeclared hook abort is HOOK_ABORTED on the per-object batch row too - #29977
Conversation
…he batch row too The per-object batch row (POST /data/:object/batch, every verb and mode, and updateManyData/deleteManyData rows) derived a sandboxed free-text refusal's code from its 400 (VALIDATION_ERROR), while /data, /createMany and /batch answer HOOK_ABORTED since #27597. toRowApiError now picks UNDECLARED_HOOK_REFUSAL_CODE for that case; a declared code still wins. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzeV9ZbECNr1Z2G2Puu
|
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5 Contract reviewServed-tier: Face: declaration ( ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5 席位核对Verdict: ACCEPT at head
|
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 10 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 6c107ae076c675caeea186ce6850245b01602bdb && git checkout 6c107ae076c675caeea186ce6850245b01602bdb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c92b88728cb533338759c3d700a683f29db1ee60 73cf31de6e575b40c8b8ea21082672194d02a342 && git checkout -B drift-repro c92b88728cb533338759c3d700a683f29db1ee60 && git merge --no-ff 73cf31de6e575b40c8b8ea21082672194d02a342
node scripts/docs-audit/affected-docs.mjs --json c92b88728cb533338759c3d700a683f29db1ee60
|
|
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5 Head moved · 2026-11-03T22:30Z ·
|
Requested by Yibo Wang · project thread
Fixes #29894
Clause-②: no
Metadata-protocol: none
维护者速读
POST /api/v1/data/:object/batch的行错误码现在是HOOK_ABORTED,不再是VALIDATION_ERROR,与/data、/createMany、/batch一致。onErrorabort answers the data door with a code-less 400; give it a ledgered ADR-0112 code (#27508 code half) #27597 给这种中止定了唯一线上码HOOK_ABORTED,但行映射toRowApiError仍按 400 推导出VALIDATION_ERROR,批量客户端会把业务拒绝当成输入格式错误。httpStatus不变);钩子声明了码的照旧。回滚即 revert 本 PR。packages/metadata-protocol/src/protocol-client-failures.ts的toRowApiError/isUndeclaredHookAbort;新 pinpackages/qa/dogfood/test/batch-row-hook-aborted.dogfood.test.ts。pnpm --filter @objectstack/dogfood exec vitest run test/batch-row-hook-aborted.dogfood.test.ts,预期 14 passed。What changes
@objectstack/metadata-protocol(patch).toRowApiErrornow answersUNDECLARED_HOOK_REFUSAL_CODE(HOOK_ABORTED, from@objectstack/types) for a sandboxed hook body's refusal that spelled nocodeand is served at a 4xx. That is the same testresolveThrownHttpErrorapplies on the doors rest/runtime: an undeclared hook abort still answers VALIDATION_ERROR on the doors that resolve through resolveThrownHttpError (package publish, /actions) after HOOK_ABORTED lands #27672 moved, restated over the row's inputs (isUndeclaredHookAbort). A registered code the body declared still wins. An unregistered one still gets the code its status derives, as on the resolver's doors. The row'smessage(the hook's sentence) and the absenthttpStatusdon't change.VALIDATION_ERROR→HOOK_ABORTEDfor an undeclared hook abort, on every row that shares the mapper:POST /api/v1/data/:object/batchwithoperationcreate/update/upsert/delete, in every mode (plain,atomic,continueOnError), and the per-row results ofupdateManyData/deleteManyData.VALIDATION_ERROR→HOOK_ABORTEDmove on its doors:patch,Clause-②: no,Metadata-protocol: none. No input that was accepted before is refused, no key is added, and no code is minted (HOOK_ABORTEDis error codes: an undeclared hookonErrorabort answers the data door with a code-less 400; give it a ledgered ADR-0112 code (#27508 code half) #27597's ledgered code).Same-pattern sweep (what I checked)
protocol-batch-data.ts: the create/upsert loop (:586), the update loop (:887) and the delete loop (:1079) all calltoRowApiError, so one fix covers them. The atomic siblings stayNOT_ATTEMPTED/ROLLED_BACK, and the causal row now carriesHOOK_ABORTED(pinned live).clientFacingFailureCode(same file): it builds the metadatafailed[]code limb. No hook abort reaches it, so it is left as is.POST /data/:object/import,core/src/utils/import-runner.tstoFailedResult) do not share the mapper. Measured on the same boot, the row answers{ error: <sentence>, code: "IMPORT_ROW_FAILED" }, which is the import report's own generic code and notVALIDATION_ERROR. This PR leaves it alone, because the order scopes the sweep to doors that share the mapper. Whether the import row should also sayHOOK_ABORTEDis reported to the seat as a possible follow-up.Pins
packages/qa/dogfood/test/batch-row-hook-aborted.dogfood.test.ts. It boots once (14 cases). §1 covers the card's three whole-request doors (/data,/createMany,/batch):400, the sentence, andHOOK_ABORTED. §2 covers the per-object batch row:create,create atomic,create continueOnError,update,upsert(existing id),delete, each with the sentence andHOOK_ABORTED, and nothing written. §3 is the control: a hook that declaresRECORD_LOCKEDkeeps it on all four doors.packages/metadata-protocol/src/protocol.batch-row-sandbox-refusal.test.ts§1/§2 (8 cases) now expectHOOK_ABORTED, and §1 compares the row's code to the/datadoor's ownbody.codeinstead of the code its status derives. §3's unregistered-code case still pinsVALIDATION_ERRORand is unchanged.Ablation, once. I restored
protocol-client-failures.tsfromorigin/mainand rebuilt metadata-protocol. The dogfood file then went 6 failed / 8 passed: exactly the six §2 rows, eachexpected 'VALIDATION_ERROR' to be 'HOOK_ABORTED', while §1 and the §3 controls stayed green. The unit file went 8 failed / 12 passed. After restoring the fix and rebuilding: 14/14 and 20/20.Verification
pnpm --filter @objectstack/metadata-protocol test: exit 0 (312 files, 22339 passed).typecheck: exit 0.pnpm --filter @objectstack/dogfood typecheck: exit 0. The new dogfood file: 14 passed.pnpm lint: exit 0.turbo run build --filter=@objectstack/dogfood^...exit 0, plus the seven distscheck:dual-build-cjs-loadsreads.node scripts/pm/dispatch-gates.mjs --commandsonf286c443: 63 derived, 63 run, all exit 0.--ranreports "63 run, 0 NOT-MEASURED".check:dual-build-cjs-loadsexited 3 until those dists were built, then 0.🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzeV9ZbECNr1Z2G2Puu