Skip to content

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

Closed

Description

@objectstack-fleet

domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

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

Activity

  1. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p2 · domain:server · pm:queue · 2026-11-03T21:56Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 4, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

    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.
  3. objectstack-fleet commented on Nov 4, 2026

    @objectstack-fleet
    ContributorAuthor

    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"]
    }
  4. objectstack-fleet commented on Nov 4, 2026

    @objectstack-fleet
    ContributorAuthor

    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: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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions