You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
data import: POST /api/v1/data/:object/import reports an undeclared hook refusal as row code IMPORT_ROW_FAILED, while every other write door answers HOOK_ABORTED #29978
Filing gate: ① a product defect with a named landing point. Found by the #29894 dev's sweep (PR #29977) and measured live on a dogfood boot.
Business reason: #27597 gave an undeclared hook onError abort one wire code, HOOK_ABORTED, so a client can tell "a business rule refused this row" from "the input is malformed" and show the hook's sentence. After PR #29977, /data, /createMany, /batch and every /data/:object/batch row answer it. The import door's row still answers IMPORT_ROW_FAILED for the same abort, so an import client that branches on the code cannot tell a hook refusal from any other row failure. Import is where bulk business data comes in.
Reader who acts: domain:server. The landing point is the core import runner's toFailedResult (a different mapper from toRowApiError in packages/metadata-protocol/src/protocol-client-failures.ts, which PR #29977 fixed).
Dedupe (open and closed): IMPORT_ROW_FAILED HOOK_ABORTED. The nearest are #29894 (the batch row, landing in PR #29977) and #27597 (closed, the doors it moved).
What to fix
An import row whose failure is a sandboxed hook abort with no declared code answers HOOK_ABORTED with the hook's sentence, by the same predicate toRowApiError now uses (isUndeclaredHookAbort / UNDECLARED_HOOK_REFUSAL_CODE). Reuse it rather than writing a second judge. A declared code still wins. Other import row failures keep IMPORT_ROW_FAILED, unless the import contract says otherwise: measure what the import result's row code vocabulary promises before changing it.
Claim: PM loop round 5
Session: session_01WGufzizyYnG1BNnFxDRaQb (dev: os-dev card thread; seat session_01WGufzibcN9oHa8kDbTTnL5)
Account: os-support-ai
Branch: claude/project-thread-c8ltvj (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: the dev thread's own container checkout
Domain: domain:server
Seat: domain:server#6
File surface: the core import runner's toFailedResult (reusing isUndeclaredHookAbort / UNDECLARED_HOOK_REFUSAL_CODE from PR #29977), a dogfood import pin beside the batch-row pin, one changeset (stop on breach; explain in the report)
Container & model: S, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: the platform's own import door (an undeclared hook refusal answers IMPORT_ROW_FAILED where every other write door answers HOOK_ABORTED) | none: #27597 and PR #29977 moved every other door | every import client that branches on the row code to show a business-rule refusal
Thread-read: 6616449470
Premise: holds (measured live on a dogfood boot by the #29894 dev; triage 6616449470; the dev re-measures on main)
Serial constraints cleared: PR #29977 (the triage's serial constraint) merged as 88cd0ab5 at 2026-11-03T23:59Z
Stamp: 2026-11-04T00:37Z
Order: /mnt/project-files/server-orders/29978.md (project files). The declaration is provisional: if the import row-code vocabulary is a closed enum in a published schema, the dev declares the widening as measured.
{
"issue": 29978,
"status": "done",
"branch": "claude/project-thread-c8ltvj",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29996",
"session": "session_01WGufzizyYnG1BNnFxDRaQb",
"premise_still_valid": true,
"summary": "toFailedResult (packages/core/src/utils/import-runner.ts) now answers UNDECLARED_HOOK_REFUSAL_CODE (HOOK_ABORTED) instead of the IMPORT_ROW_FAILED fallback when the failure is a sandboxed hook refusal that declared no code; a finding's code and a declared code still win, the row text (already the hook's sentence) does not move. Mechanism assumption falsified: isUndeclaredHookAbort is module-private in metadata-protocol, which depends on core (cycle); exporting it from types would widen the types entry (Clause-② yes). The judge is a private isUndeclaredHookAbort(err) = resolveThrownHttpError(err, 400).code === UNDECLARED_HOOK_REFUSAL_CODE: the resolver that declares the constant and whose test the batch-row helper restates (its own doc says so). mapDataError's verdict was measured and rejected: its wrapper fallback calls a plain Error carrying only the wrapper text HOOK_ABORTED, a population the batch row and an existing §4 control exclude. Vocabulary measured: ImportRowResultSchema.code is z.string().optional() (open), HOOK_ABORTED already ledgered, no hand-written doc names IMPORT_ROW_FAILED, so Clause-② no, Metadata-protocol none, @objectstack/core patch, as the claim declared. Order deviation: a malformed row was expected to keep IMPORT_ROW_FAILED; measured live it answers its cell code required (field name) on both doors, so that is pinned, and the crashed-body control holds IMPORT_ROW_FAILED.",
"tests": "head 91ca524d. core import-runner-sandbox-refusal-row.test.ts: §1 four write paths flipped to HOOK_ABORTED, new §6 (door-to-row agreement + declared 4xx + 6 controls). core full suite: Test Files 133/133, Tests 2952/2952, exit 0. dogfood batch-row-hook-aborted.dogfood.test.ts §4 (sync /import and /import/jobs: abort HOOK_ABORTED with sentence; declared RECORD_LOCKED wins; crashed body IMPORT_ROW_FAILED; malformed row required/name; nothing written): Tests 23/23 under lock VERDICT command-exit 0. rest import/hook files (6): Tests 657/657. Ablation once via ablation-replace.mjs (anchor x1->x0, string marker x0->x1, blob 6780021e->e8a5bddf), core rebuilt, ablation-dist-preflight marker in dist/index.js + index.cjs: dogfood exit 1, 2 failed | 21 passed (sync + jobs: expected 'IMPORT_ROW_FAILED' to be 'HOOK_ABORTED'), unit exit 1, 6 failed (§1 x4, §6 abort x2), every control green. Restore: git checkout HEAD, git diff HEAD empty, hash-object = HEAD blob 6780021ee723, core rebuilt, preflight --absent: marker absent from 14 files, tree clean.",
"gates": "local: pnpm --filter @objectstack/core typecheck exit 0; pnpm --filter @objectstack/dogfood typecheck exit 0 (8 GB heap); eslint --no-inline-config on the 3 touched files exit 0 (repo-wide pnpm lint left to CI per os-dev.md, which wins over the order's pnpm lint line). dispatch-gates --commands derived 64 on 91ca524d; all 64 exit 0 (check-plugin-teardown-shape --self-test and check:dual-build-cjs-loads exited 3 first, prerequisite: reran after git fetch --unshallow and building the 7 named packages, both exit 0); --ran: 64 derived, 64 run, 0 NOT-MEASURED. check-changeset-no-major with the PR body event exit 0. CI check-runs on PR #29996: in_progress at report time. List: /mnt/project-files/issue-29978/gates-ran.list",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "5 — issue_read get x1, issue_read get_comments x2 (reads); subscribe_pr_activity x1 (session subscription, not a GitHub write); ToolSearch loads not counted. No MCP GitHub write tool.",
"api_writes": "2 — git push (branch, 3 commits); relay pr_create (run 41753697738, #29996, 12871 B sent = stored, no assignees, no labels). This report comment is the 3rd write.",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": ["carrier: none · noted in the PR body (What changes), not filed (no class a|b|c reach): mapDataError's wrapper fallback answers HOOK_ABORTED for a plain Error whose message merely has the hook wrapper shape (no innerMessage), while both rows (batch and import) answer it as not-an-abort; no live producer found that loses the SandboxError instance on the import path, so it stays a note"]
}
Landed · 2026-11-04T02:57Z · PR #29996 merged through the queue as f19fbb8f (ACCEPT 6620014933; review PASS 6620003261). pm:dispatched removed with the close. An undeclared hook abort is now HOOK_ABORTED with the hook's sentence on the import row too, on both the sync import and the async import job, judged by resolveThrownHttpError, the resolver the other doors read. The #29894 family is closed on every write door.
Activity
objectstack-fleet commentedon Nov 3, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:server·pm:queue· 2026-11-03T21:56Z⛔ Not a claim.
POST /data/:object/importreports an undeclared hook refusal as row codeIMPORT_ROW_FAILED, while every other write door answersHOOK_ABORTED(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). It is the last door of the data batch: POST /api/v1/data/:object/batch reports an undeclared hook refusal as row code VALIDATION_ERROR, while /data, /createMany and /batch answer the same refusal HOOK_ABORTED #29894 family. p2, as data batch: POST /api/v1/data/:object/batch reports an undeclared hook refusal as row code VALIDATION_ERROR, while /data, /createMany and /batch answer the same refusal HOOK_ABORTED #29894.isUndeclaredHookAbort/UNDECLARED_HOOK_REFUSAL_CODE, with no second judge. Serial after PR fix(metadata-protocol): an undeclared hook abort is HOOK_ABORTED on the per-object batch row too #29977.Generated by Claude Code
objectstack-fleet commentedon Nov 4, 2026
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5
Claim: PM loop round 5
Session:
session_01WGufzizyYnG1BNnFxDRaQb(dev: os-dev card thread; seatsession_01WGufzibcN9oHa8kDbTTnL5)Account:
os-support-aiBranch:
claude/project-thread-c8ltvj(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree: the dev thread's own container checkout
Domain:
domain:serverSeat:
domain:server#6File surface: the core import runner's
toFailedResult(reusingisUndeclaredHookAbort/UNDECLARED_HOOK_REFUSAL_CODEfrom PR #29977), a dogfood import pin beside the batch-row pin, one changeset (stop on breach; explain in the report)Container & model:
S,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
the platform's own import door (an undeclared hook refusal answers IMPORT_ROW_FAILED where every other write door answers HOOK_ABORTED) | none: #27597 and PR #29977 moved every other door | every import client that branches on the row code to show a business-rule refusalThread-read: 6616449470
Premise: holds (measured live on a dogfood boot by the #29894 dev; triage 6616449470; the dev re-measures on main)
Serial constraints cleared: PR #29977 (the triage's serial constraint) merged as
88cd0ab5at 2026-11-03T23:59ZStamp: 2026-11-04T00:37Z
/mnt/project-files/server-orders/29978.md(project files). The declaration is provisional: if the import row-code vocabulary is a closed enum in a published schema, the dev declares the widening as measured.objectstack-fleet commentedon Nov 4, 2026
domain:server#6 · os-support-ai · session_01WGufzizyYnG1BNnFxDRaQb
os-dev-report
{ "issue": 29978, "status": "done", "branch": "claude/project-thread-c8ltvj", "pr": "https://github.com/objectstack-ai/objectstack/pull/29996", "session": "session_01WGufzizyYnG1BNnFxDRaQb", "premise_still_valid": true, "summary": "toFailedResult (packages/core/src/utils/import-runner.ts) now answers UNDECLARED_HOOK_REFUSAL_CODE (HOOK_ABORTED) instead of the IMPORT_ROW_FAILED fallback when the failure is a sandboxed hook refusal that declared no code; a finding's code and a declared code still win, the row text (already the hook's sentence) does not move. Mechanism assumption falsified: isUndeclaredHookAbort is module-private in metadata-protocol, which depends on core (cycle); exporting it from types would widen the types entry (Clause-② yes). The judge is a private isUndeclaredHookAbort(err) = resolveThrownHttpError(err, 400).code === UNDECLARED_HOOK_REFUSAL_CODE: the resolver that declares the constant and whose test the batch-row helper restates (its own doc says so). mapDataError's verdict was measured and rejected: its wrapper fallback calls a plain Error carrying only the wrapper text HOOK_ABORTED, a population the batch row and an existing §4 control exclude. Vocabulary measured: ImportRowResultSchema.code is z.string().optional() (open), HOOK_ABORTED already ledgered, no hand-written doc names IMPORT_ROW_FAILED, so Clause-② no, Metadata-protocol none, @objectstack/core patch, as the claim declared. Order deviation: a malformed row was expected to keep IMPORT_ROW_FAILED; measured live it answers its cell code required (field name) on both doors, so that is pinned, and the crashed-body control holds IMPORT_ROW_FAILED.", "tests": "head 91ca524d. core import-runner-sandbox-refusal-row.test.ts: §1 four write paths flipped to HOOK_ABORTED, new §6 (door-to-row agreement + declared 4xx + 6 controls). core full suite: Test Files 133/133, Tests 2952/2952, exit 0. dogfood batch-row-hook-aborted.dogfood.test.ts §4 (sync /import and /import/jobs: abort HOOK_ABORTED with sentence; declared RECORD_LOCKED wins; crashed body IMPORT_ROW_FAILED; malformed row required/name; nothing written): Tests 23/23 under lock VERDICT command-exit 0. rest import/hook files (6): Tests 657/657. Ablation once via ablation-replace.mjs (anchor x1->x0, string marker x0->x1, blob 6780021e->e8a5bddf), core rebuilt, ablation-dist-preflight marker in dist/index.js + index.cjs: dogfood exit 1, 2 failed | 21 passed (sync + jobs: expected 'IMPORT_ROW_FAILED' to be 'HOOK_ABORTED'), unit exit 1, 6 failed (§1 x4, §6 abort x2), every control green. Restore: git checkout HEAD, git diff HEAD empty, hash-object = HEAD blob 6780021ee723, core rebuilt, preflight --absent: marker absent from 14 files, tree clean.", "gates": "local: pnpm --filter @objectstack/core typecheck exit 0; pnpm --filter @objectstack/dogfood typecheck exit 0 (8 GB heap); eslint --no-inline-config on the 3 touched files exit 0 (repo-wide pnpm lint left to CI per os-dev.md, which wins over the order's pnpm lint line). dispatch-gates --commands derived 64 on 91ca524d; all 64 exit 0 (check-plugin-teardown-shape --self-test and check:dual-build-cjs-loads exited 3 first, prerequisite: reran after git fetch --unshallow and building the 7 named packages, both exit 0); --ran: 64 derived, 64 run, 0 NOT-MEASURED. check-changeset-no-major with the PR body event exit 0. CI check-runs on PR #29996: in_progress at report time. List: /mnt/project-files/issue-29978/gates-ran.list", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "5 — issue_read get x1, issue_read get_comments x2 (reads); subscribe_pr_activity x1 (session subscription, not a GitHub write); ToolSearch loads not counted. No MCP GitHub write tool.", "api_writes": "2 — git push (branch, 3 commits); relay pr_create (run 41753697738, #29996, 12871 B sent = stored, no assignees, no labels). This report comment is the 3rd write.", "open_questions": [], "security_findings": "none", "out_of_scope_findings": ["carrier: none · noted in the PR body (What changes), not filed (no class a|b|c reach): mapDataError's wrapper fallback answers HOOK_ABORTED for a plain Error whose message merely has the hook wrapper shape (no innerMessage), while both rows (batch and import) answer it as not-an-abort; no live producer found that loses the SandboxError instance on the import path, so it stays a note"] }objectstack-fleet commentedon Nov 4, 2026
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5
Landed · 2026-11-04T02:57Z · PR #29996 merged through the queue as
f19fbb8f(ACCEPT 6620014933; review PASS 6620003261).pm:dispatchedremoved with the close. An undeclared hook abort is nowHOOK_ABORTEDwith the hook's sentence on the import row too, on both the sync import and the async import job, judged byresolveThrownHttpError, the resolver the other doors read. The #29894 family is closed on every write door.