Repository navigation
Commit f19fbb8
fix(core): an undeclared hook abort is HOOK_ABORTED on the import row too (#29996)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBCyXvgM1LSLj8HhcsPZvUkP)_
Fixes #29978
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:钩子用自由文本中止写入且未声明错误码时,`POST /api/v1/data/:object/import`
与异步导入作业的行错误码现在是 `HOOK_ABORTED`,不再是 `IMPORT_ROW_FAILED`,与
`/data`、`/createMany`、`/batch` 和每个 `/data/:object/batch` 行一致。
- **为什么改**:#27597 给这种中止定了唯一线上码 `HOOK_ABORTED`,PR #29977 补上了批量行;导入行仍答
`IMPORT_ROW_FAILED`,导入客户端无法把业务规则拒绝与其它失败行区分开。
- **风险与回滚**:只换这一种中止的行码(行文案本就是钩子原话,不变);钩子声明了码的、钩子崩溃的、声明 5xx
的、驱动故障与畸形行都照旧。回滚即 revert 本 PR。
- **先看**:`packages/core/src/utils/import-runner.ts` 的 `toFailedResult`
那一行 `code` 与新函数 `isUndeclaredHookAbort`;新 pin 在
`packages/qa/dogfood/test/batch-row-hook-aborted.dogfood.test.ts` 的 §4。
- **两分钟验证**:`pnpm --filter @objectstack/dogfood exec vitest run
test/batch-row-hook-aborted.dogfood.test.ts`,预期 23 passed。
- **需要你做的**:无,席位落地。
## What changes
- **`@objectstack/core` (patch).** `toFailedResult`, the core import
runner's row mapper, now answers `UNDECLARED_HOOK_REFUSAL_CODE`
(`HOOK_ABORTED`, from `@objectstack/types`) where it used to fall back
to `IMPORT_ROW_FAILED`, when the failure is a sandboxed hook body's
refusal that declared no code. The precedence is unchanged above it: a
field finding's code, then the code the thrown error declared
(registered or not), and only then the new arm, before
`IMPORT_ROW_FAILED`. The row's `error` was already the hook's sentence
(`sandboxBusinessMessage`, #22694) and does not move.
- **The judge.** The new private `isUndeclaredHookAbort(err)` asks
`resolveThrownHttpError(err, 400).code ===
UNDECLARED_HOOK_REFUSAL_CODE`. That resolver is where the constant is
declared and the one place that answers it for exactly this throw: a
sandbox business refusal, no `code` spelled, served at a 4xx. The `400`
fallback is the status the data doors serve an undeclared sandbox
refusal at.
- **Why not import `isUndeclaredHookAbort` from metadata-protocol** (the
dispatch's mechanism assumption, measured and falsified): it is a
module-private function in `@objectstack/metadata-protocol`, which
depends on `@objectstack/core`
(`packages/metadata-protocol/package.json`), so core importing it is a
cycle. Exporting it from `@objectstack/types` would add a public export
(`Clause-②: yes (widening)`) and move a second package. Its own doc
comment says it is "the resolver's own test, restated over the row's
inputs", so the resolver is the shared judge both rows reach, and no
second reading of `innerMessage`, `code` or the status is written here.
- **Why not adopt `mapDataError`'s verdict** (the other candidate,
measured): its wrapper fallback answers `HOOK_ABORTED` for a plain
`Error` that merely carries the `hook 'x' threw:` text with no
`innerMessage`. The row deliberately relays that error's own text (§4
control in `import-runner-sandbox-refusal-row.test.ts`), and the batch
row's `isUndeclaredHookAbort` does not count it as an abort either.
Adopting the door's verdict would have moved that population too.
- **Import row-code vocabulary, measured before the change.**
`ImportRowResultSchema.code` (`packages/spec/src/api/export.zod.ts`) is
`z.string().optional()`, an open string: the row already carries
field-level codes (`required`, `min_value`), a hook's declared code
verbatim, `UNIQUE_VIOLATION`, `INVALID_FIELD`, `DUPLICATE_MATCH_KEY` and
`IMPORT_ROW_FAILED`. No published enum moves, and `HOOK_ABORTED` is
already a ledger row (`@objectstack/types`, `error-code-ledger.zod.ts`).
Hand-written docs (`content/docs` outside `references/`, `skills/`) name
`IMPORT_ROW_FAILED` nowhere. So `Metadata-protocol: none`, `Clause-②:
no`, `patch`, as the claim declared.
## Tests
- `packages/core/src/utils/import-runner-sandbox-refusal-row.test.ts`:
§1's four write paths (inline `createData`, batched `createManyData`
degraded to per row, partial-success `insertManyData`, the update half
of an upsert) now expect `UNDECLARED_HOOK_REFUSAL_CODE`. New §6 pins
door-to-row agreement (the row's `code` IS
`mapDataError(err).body.code`, with an anti-vacuity check that the door
says `HOOK_ABORTED`), a declared 4xx with no code, and six controls that
keep their own code: a declared registered code, a declared unregistered
code, a crashed body, a declared 5xx with no code, a plain error
carrying only the wrapper text, and a driver fault.
- `packages/qa/dogfood/test/batch-row-hook-aborted.dogfood.test.ts` §4,
beside PR #29977's batch-row pin and on the same boot: the card's
fixture (`beforeInsert` throwing free text) through `POST
/data/:object/import` and `POST /data/:object/import/jobs`. Each door:
the abort is `HOOK_ABORTED` with the hook's sentence; a declared
`RECORD_LOCKED` wins; a crashed body keeps `IMPORT_ROW_FAILED`; a
malformed row (blank required `name`) keeps its cell code. Then nothing
was written.
## Acceptance notes
- **The malformed-row control pins `required`, not
`IMPORT_ROW_FAILED`.** The dispatch expected a malformed row to keep
`IMPORT_ROW_FAILED`. Measured live on both doors, a blank required cell
answers `{ code: 'required', field: 'name', error: 'Name is required' }`
before and after this change, so that is what is pinned.
`IMPORT_ROW_FAILED` is the fallback for a write failure that names no
code at all; the crashed-body control holds that population.
- The §4 pin lives inside the file's one top-level `describe` so it
shares the boot (one `bootStack` per file) instead of booting a second
stack.
- `pnpm lint` (repo-wide eslint) was not run locally:
`.claude/agents/os-dev.md` puts repo scans on CI and wins over the
dispatch where they conflict. eslint with `--no-inline-config` on the
three touched source files exited 0, and CI's `Lint & Repo Gates` runs
the full scan.
## Verification
All readings below are on head `91ca524d` (objectstack-ai/objectstack),
taken in this branch's own worktree.
<details><summary>Build, suites, typecheck, eslint</summary>
- `pnpm --filter '@objectstack/core...' build` under `os-verify-lock`:
`VERDICT command-exit 0`.
- `pnpm --filter @objectstack/core test`: exit 0, `Test Files 133 passed
(133)`, `Tests 2952 passed (2952)`.
- `pnpm --filter @objectstack/core typecheck`: exit 0
(`check:test-typecheck: OK`, 4/4 pinned debt signatures held).
- `pnpm --filter '@objectstack/dogfood^...' build && pnpm --filter
@objectstack/dogfood exec vitest run
test/batch-row-hook-aborted.dogfood.test.ts`: `VERDICT command-exit 0`,
`Tests 23 passed (23)`.
- `pnpm --filter @objectstack/dogfood typecheck` (8 GB heap): exit 0.
- `pnpm --filter '@objectstack/rest^...' build` then the rest import and
hook-refusal files (`rest-write-route-hook-refusal-sentence.ledger`,
`import-undo-controls`, `rest-hook-refusal-code-parity`,
`import-runner-unique-violation-row`, `import-row-report-field-20701`,
`import-row-schema-drift-20701`): `Tests 657 passed (657)`. No rest pin
asserted the import row's `IMPORT_ROW_FAILED` for an abort, so none
flips there.
- `eslint --no-inline-config` on the three touched files: exit 0.
</details>
<details><summary>Ablation (once, the mapping reverted)</summary>
Mutation by `scripts/ablation-replace.mjs`: the anchor
`(isUndeclaredHookAbort(err) ? UNDECLARED_HOOK_REFUSAL_CODE :
'IMPORT_ROW_FAILED')` (x1 → x0) replaced by a string-marked
`'IMPORT_ROW_FAILED'` (x0 → x1), blob `6780021e` → `e8a5bddf`. `pnpm
--filter @objectstack/core build` exit 0; `ablation-dist-preflight.mjs
@objectstack/core 'ablated-29978'`: marker present in `dist/index.js`
and `dist/index.cjs`.
- dogfood file: exit 1, `Tests 2 failed | 21 passed (23)`. The two reds
are `sync` and `jobs`: "an undeclared abort is HOOK_ABORTED with the
hook's sentence", `expected 'IMPORT_ROW_FAILED' to be 'HOOK_ABORTED'`,
the row reading `{"row":1,"ok":false,"action":"failed","error":"A QA
budget line needs an approver before it can be
recorded.","code":"IMPORT_ROW_FAILED"}`. Every control stayed green.
- core unit file: exit 1, 6 failed: §1's four write paths and §6's two
abort cases, each `"code": "IMPORT_ROW_FAILED"` received. §6's six
controls stayed green.
Restore: `git checkout HEAD --
packages/core/src/utils/import-runner.ts`, `git diff HEAD` empty, `git
hash-object` = HEAD blob `6780021ee723`, core rebuilt,
`ablation-dist-preflight … --absent`: marker absent from all 14 built
files, working tree clean.
</details>
<details><summary>Derived gates (dispatch-gates --commands, 64
commands)</summary>
`node scripts/pm/dispatch-gates.mjs --ran` on `91ca524d`: `64 derived
famil(ies) accounted for — 64 run, 0 NOT-MEASURED`. Two exited 3
(prerequisite) on the first pass and were rerun after the prerequisite
was met: `check-plugin-teardown-shape.mjs --self-test` after `git fetch
--unshallow origin main`, and `check:dual-build-cjs-loads` after
building the seven packages it named (studio, client-react,
embedder-openai, knowledge-memory, knowledge-ragflow,
service-cluster-redis, service-knowledge). Both then exited 0. Every
recorded exit code:
```text
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: 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-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dev-prereqs.mjs --self-test :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:empty-state :: exit 0
pnpm --filter @objectstack/spec run check:liveness :: exit 0
pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0
pnpm --filter @objectstack/spec run check:variant-docs :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-code-casing :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:kernel-hook-pairs :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
```
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WGufzizyYnG1BNnFxDRaQb)_
<!-- signed: domain:server#6 · os-support-ai ·
session_01WGufzizyYnG1BNnFxDRaQb -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f7aab89 commit f19fbb8
4 files changed
Lines changed: 143 additions & 8 deletions
File tree
- .changeset
- packages
- core/src/utils
- qa/dogfood/test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
35 | 41 | | |
36 | 42 | | |
37 | 43 | | |
38 | | - | |
| 44 | + | |
39 | 45 | | |
40 | 46 | | |
41 | 47 | | |
| |||
98 | 104 | | |
99 | 105 | | |
100 | 106 | | |
101 | | - | |
| 107 | + | |
102 | 108 | | |
103 | 109 | | |
104 | 110 | | |
| |||
110 | 116 | | |
111 | 117 | | |
112 | 118 | | |
113 | | - | |
| 119 | + | |
114 | 120 | | |
115 | 121 | | |
116 | 122 | | |
| |||
126 | 132 | | |
127 | 133 | | |
128 | 134 | | |
129 | | - | |
| 135 | + | |
130 | 136 | | |
131 | 137 | | |
132 | 138 | | |
| |||
140 | 146 | | |
141 | 147 | | |
142 | 148 | | |
143 | | - | |
| 149 | + | |
144 | 150 | | |
145 | 151 | | |
146 | 152 | | |
| |||
223 | 229 | | |
224 | 230 | | |
225 | 231 | | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
527 | 527 | | |
528 | 528 | | |
529 | 529 | | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
530 | 541 | | |
531 | 542 | | |
532 | 543 | | |
| |||
537 | 548 | | |
538 | 549 | | |
539 | 550 | | |
540 | | - | |
| 551 | + | |
541 | 552 | | |
542 | 553 | | |
543 | 554 | | |
| |||
548 | 559 | | |
549 | 560 | | |
550 | 561 | | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
551 | 580 | | |
552 | 581 | | |
553 | 582 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
14 | 20 | | |
15 | 21 | | |
16 | 22 | | |
| |||
39 | 45 | | |
40 | 46 | | |
41 | 47 | | |
42 | | - | |
| 48 | + | |
43 | 49 | | |
44 | 50 | | |
45 | 51 | | |
| |||
50 | 56 | | |
51 | 57 | | |
52 | 58 | | |
| 59 | + | |
| 60 | + | |
53 | 61 | | |
54 | 62 | | |
55 | 63 | | |
| |||
150 | 158 | | |
151 | 159 | | |
152 | 160 | | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
153 | 212 | | |
0 commit comments