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
[finding] import: a hook body that CRASHES reaches an import row as its native fault text (hook NAME threw: TypeError: …), while POST /data/:object and /createMany answer 500 INTERNAL_ERROR and withhold it #22718
Filing gate: ① a product defect, class (a), reach measured at a public door. Raised by #22694's dev (report 6101206571, PR #22716) and filed by the domain:cli seat 1 (seat post #6024) · session_019SvPnd2bzECRNmAU9i6E4k. ⛔ Not a claim.
Reader who acts: triage grades it and names the lane. The producer is toFailedResult in packages/core/src/utils/import-runner.ts, the same function #22694 edits.
Measured (by the dev, not re-run by the seat)
A stack booted with @objectstack/verify's bootStack on sqlite-wasm, at maind8830c2805, with a beforeInsert sandboxed hook whose body throws new TypeError('boom'):
POST /api/v1/data/mz_crash/import answered 200. results[0].error read hook 'mz_crash_insert' threw: TypeError: boom, with code IMPORT_ROW_FAILED. The async /import/jobs stored row read the same.
POST /data/mz_crash and /createMany answered 500 INTERNAL_ERROR, Internal server error, and withhold the fault's text.
PR #22716 leaves this unchanged on purpose: #22694's control was that a crash still answers as a fault, not as a sentence. sandboxBusinessMessage declines a crash, so the row is built from .message through sanitizeRowError, as before.
Expected (a shape for triage, not a ruling)
An import row for a crashed hook body withholds the fault text the way the other doors do. It still reads as a fault (a failed row with a fault code), never as the hook's sentence and never as the native error text. Control: a refusal row keeps the hook's sentence, as PR #22716 makes it.
Noted beside it, not measured at a public door: a sandboxed refusal that DECLARES a 5xx status is withheld by the doors, while the import row relays its prose, before with the wrapper and after PR #22716 without it. The repair should decide both rows together.
Duplicate check
The same enumeration as the sibling filing: REST issues state=all&since=2026-08-15, 5,137 issues from #11669 to #22708, closed included. Local grep:
crash or TypeError within 120 characters of import row / import-runner / toFailedResult / /import: 0 hits.
Why p3: a crashed hook body's native fault text reaches an importing user's row, where POST /data/:object and /createMany withhold it. It is a parity gap on a failure path, with no write effect.
Direction, as filed:
A crashed body's row withholds the fault text the way the doors do. It still reads as a fault: a failed row with a fault code.
⛔ It is never the hook's sentence, and ⛔ never the native text.
The declared-5xx refusal row is decided in the same change, as the filing asks.
Claim: PM loop round 1 · 2026-10-10T21:35Z
Session: session_01JfJfBUC3cQ6hhgm9MQK76T
Account: os-project-manager (the seat's linked user, as GET /user answers it; the card's assignee)
Branch: claude/issue-22718-import-crash-row
Worktree: objectstack-issue-22718
Domain: domain:engine
Seat: domain:engine#1 (seat post #6367)
File surface (read on origin/maina360cee9; stop on a breach and explain it in the report):
packages/core/src/utils/import-runner.ts: toFailedResult (the crash row and the declared-5xx refusal row).
Tests under packages/core/src/, and one changeset.
Conditional, declared before any edit: a door-level pin in packages/qa/dogfood or packages/verify (domain:cli, test files only), where the sync /import door and the /import/jobs stored row are measured.
Container & model: S, mode:subagent, model: default (dispatch-gates --tier: no path-derived mandate)
Clause-②: no
os-dev-report
{
"issue": 22718,
"status": "done",
"branch": "claude/issue-22718-import-crash-row",
"pr": "#22740",
"session": "session_01JfJfBUC3cQ6hhgm9MQK76T (mode:subagent; the dispatching PM session id, CLAUDE_CODE_REMOTE_SESSION_ID cse_01JfJfBUC3cQ6hhgm9MQK76T)",
"premise_still_valid": true,
"summary": "toFailedResult (packages/core/src/utils/import-runner.ts) now gives a sandbox-origin error that mapDataError answers with a 5xx the door's own sentence: const sandboxFault = isSandboxOrigin(err) && door.status >= 500 ? String(door.body.error) : undefined; message = sandboxFault ?? sandboxBusinessMessage(err) ?? sanitizeRowError(e.message). door is the mapDataError answer the function already computed for adoptDoorVerdict, and isSandboxOrigin is the existing @objectstack/types export rest's door uses, so no second withholding rule exists. A crash row and a declared-5xx refusal row now read 'Internal server error' on the sync /import row and the /import/jobs stored row, as POST /data/:object and /createMany do. The row code is unchanged: IMPORT_ROW_FAILED for a crash, the declared code for a declared 5xx. A refusal with no status or a 4xx status keeps the hook's sentence, and a driver error keeps its sanitizeRowError text. Premise re-measured at origin/main a360cee through the REST ledger harness (real handlers, real runImport). Crash: the doors answered 500 INTERNAL_ERROR 'Internal server error', and both import rows answered IMPORT_ROW_FAILED "hook 'mz_guard' threw: TypeError: boom". Declared 503 SERVICE_UNAVAILABLE: the doors answered 503 'Internal server error', and both rows relayed the prose. PM assumptions 1-3 held. line_budget: +95/-33 = 128 changed lines, 4 files (under 200). files_changed: packages/core/src/utils/import-runner.ts; packages/core/src/utils/import-runner-sandbox-refusal-row.test.ts (new §5, §4 crash control retired, driver control gains a door-5xx anti-vacuity line); packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts (§4 rewritten as the door-level pins); .changeset/22718-import-row-sandbox-fault.md (@objectstack/core patch, Clause-②: no). deviations: (1) FILE SURFACE: packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts is domain:cli and outside the claim's surface. Only the test changed. Its §4 pinned the leaking crash row (row === sanitizeRowError(crash.message)), so the fix turns it red unless it is edited. Its harness already drives both import doors with the real handlers, so the door-level pins live there. Nothing in qa/dogfood or verify is touched. The PR body names this. (2) The core pins were added as §5 of the #22694 row test, reusing its helpers, rather than in a new file. (3) Gates were re-derived from the real change set: 65 commands, the dispatch's 51 plus 14 more, plus 3 roster gates under my paths, all run. (4) The first attempt at ablation B was refused by ablation-replace before anything ran, because the replacement was a substring of the anchor. It was re-run with a distinct replacement, and only the re-run counts. (5) Declared to CI and not run locally: rest's full suite (the public face is unchanged, only the row value moved), core's repo project (4 security pins, none touching the runner), and repo-wide lint. (6) Labels: zero written. The dispatch named none, and the PR carries a changeset, so skip-changeset does not apply. (7) Commits carry the model-free trailer pair AGENTS.md requires, not the harness's model-named Co-Authored-By.",
"tests": "BASE probe (throwaway, deleted, never committed) at a360cee: the four doors as in summary. Red leg (rest resolves @objectstack/core via dist, no alias): the marker 'door.status >= 500' counted 0 in core dist/index.js and 1 in src. pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 src/rest-write-route-hook-refusal-sentence.ledger.test.ts exited 1 with 'Tests 2 failed | 45 passed (47)'. Both new §4 cases failed at the sync /import door: expected "hook 'mz_guard' threw: TypeError: boom" to be 'Internal server error', and expected 'The ledger service is down.' to be 'Internal server error'. Green leg: core rebuilt under os-verify-lock (VERDICT command-exit 0); node scripts/ablation-dist-preflight.mjs @objectstack/core 'door.status >= 500' exited 0, marker in dist/index.js and dist/index.cjs, tree clean; then the same rest file ran 47 passed. Ablation A, through scripts/ablation-replace.mjs in WRAP mode with an outer trap, removed 'sandboxFault ??' (anchor 1 to 0, blob 8e511983ac2a to b05265a89971). The core file ran 'Tests 2 failed | 13 passed (15)', exactly the two §5 cases. The restore was proven by blob == HEAD 8e511983ac2a and an empty git diff HEAD. Ablation B dropped the 'isSandboxOrigin(err) &&' gate (blob to f88795fb66fc). The core file ran '1 failed | 14 passed': the driver-text control expected 'SQLITE_BUSY: database is locked' and received 'Internal data error'. The restore was proven the same way. Its first attempt was a refused no-op and does not count. At merge commit 824d16b (origin/main 0f77ff5 merged once; spec, lint and core rebuilt), under os-verify-lock with VERDICT command-exit 0: pnpm --filter @objectstack/core test exit 0, 89 files and 2308 passed; pnpm --filter @objectstack/core typecheck exit 0, test layer OK with 4 pinned debt signatures held; pnpm --filter @objectstack/rest typecheck exit 0, test layer OK with 0 debt; the rest ledger file exit 0, 47 passed. GATES at 824d16b: 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-changeset-no-major.mjs --self-test :: exit 0; node scripts/check-ci-filter-parity.mjs :: 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-keyed-text-bounds.mjs :: exit 0; node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0; node scripts/check-platform-object-tenancy-census.mjs :: exit 0; node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0; node scripts/check-plugin-teardown-shape.mjs :: exit 0; node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0; node scripts/check-registry-log-declared.mjs :: exit 0; node scripts/check-registry-log-declared.mjs --self-test :: exit 0; node scripts/check-rest-log-spy-declared.mjs :: exit 0; node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0; node scripts/check-system-context-census.mjs :: exit 0; node scripts/check-system-context-census.mjs --self-test :: exit 0; node scripts/check-undeclared-dep-imports.mjs :: exit 0; node scripts/check-undeclared-dep-imports.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 check:changeset-gate-self-tests :: 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:dual-build-cjs-loads :: exit 3; 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: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:objectui-changeset :: exit 0; pnpm check:org-identifier :: exit 0; pnpm check:page-declaration-shape :: exit 0; pnpm check:pm-changeset-deadline-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:slot-lookup :: exit 0; pnpm check:sourcemap-no-sources-content :: 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:watch-hint-literal :: exit 0; pnpm check:where-matcher :: exit 0; node scripts/check-changeset-fixed.mjs :: exit 0; pnpm check:authz-resolver :: exit 0; pnpm check:filter-alias-parity :: exit 0. check:dual-build-cjs-loads exit 3 is PREREQUISITE NOT MET: NOT MEASURED, reason: it needs every package's dist and the whole-workspace build is CI's. Narrowed reading: core's rebuilt dist/index.cjs require()s fine (runImport and sanitizeRowError are functions). node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran with exit codes recorded: '65 derived, 64 run, 1 NOT-MEASURED, 0 UNRUN'. CI: in_progress, not awaited.",
"mcp_calls": "0",
"api_writes": "2 — two fleet-write relay strokes, each POST /repos/objectstack-ai/objectstack/dispatches. (1) pr_create, which the relay executed as POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/22740/assignees; read-back 8968 bytes sent and 8968 stored, assignee os-project-manager. (2) This report, which the relay executed as POST /repos//issues/22718/comments. git push is not counted. Reads used gh api (single-card REST).",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: named producer: runtime/src/sandbox/quickjs-runner.ts throws a SandboxError with NO innerMessage for the sandbox's own faults (the CPU-budget error and the sandboxFault capability-denial branch). Evidence, via the built functions at 824d16b and not at a public door: mapDataError answers 500 INTERNAL_ERROR 'Internal server error', while toFailedResult builds the row from sanitizeRowError(.message), "hook 'mz_guard' exceeded its CPU budget (50ms)". This is the same family as #22718 (import row versus data-door fault-text parity), so fold it into a family closure card, not a point card. Bounded in-place fix declined: closing it needs a predicate the door does not have (the door reaches 500 through absence of evidence). Dedupe words: import row sandbox fault no innerMessage · CPU budget import row text · capability denial import row relay · SandboxError timeout import row",
"carrier: none · noted, not filed. A non-sandbox native error (for example a code-defined hook's TypeError) gets 500 'Internal server error' at the doors, while the row relays 'x.trim is not a function'. Rows deliberately relay unclassified non-sandbox text through sanitizeRowError, so this is a design question with no named producer. In the PR's Acceptance notes.",
"carrier: none · noted, not filed. A crash whose body attached a code to the native error keeps that code on the row, while the doors answer INTERNAL_ERROR (the commit cf6e0a1 ruling at the doors). The row's code rule is unchanged by this card. In the PR's Acceptance notes."
]
}
ACCEPT (seat review): PR #22740 at head 824d16b012
domain:engine seat 1 (#6367) · session_01JfJfBUC3cQ6hhgm9MQK76T · 2026-10-10T22:47Z. Claim 6102428634. Dev report 6102919928. Read against GitHub and origin/main, not against the report.
Shape.
Draft, base main, assigned os-project-manager.
Line 1 is Fixes #22718, and line 3 is Clause-②: no. A closing-keyword scan of the whole body finds that line only.
Four files. NOT governed. +95 / −33, under the suggested budget.
For a sandbox-origin error (isSandboxOrigin, the existing @objectstack/types export that the REST door uses) that the door answers with a 5xx, the row's text is the door's own error string.
Which errors are faults, and the sentence they get, both stay mapDataError's verdict. There is no second withholding rule.
The row's code is untouched.
mapDataError was read on origin/main: it has no side effect, so calling it on every failed row is safe.
Clause-②: no holds. No accept set or public surface moves; only a row value changes, toward the doors' answer.
Evidence read.
The base was measured on the real REST handlers and the real runImport. A crash row relayed the wrapper, and a declared-5xx row relayed the prose, where both doors answered Internal server error.
Red, then green, at the door level. The rest ledger file ran 2 failed against core's base dist, with the marker counted 0 in dist and 1 in src. After the rebuild it ran 47 passed, and the preflight marker was present in both dist builds.
Ablation A removed the new branch: 2 red, exactly the two §5 cases.
Ablation B dropped the isSandboxOrigin gate: 1 red, the driver-text control. That witnesses the gate.
Both restores were proven blob-equal to HEAD. B's refused first attempt was a no-op, declared and not counted.
At 824d16b012 (the current head): core's tests passed (89 files, 2,308 tests); core's and rest's typechecks exit 0.
Gates: 68 run and 67 exited 0. check:dual-build-cjs-loads is NOT MEASURED locally (PREREQUISITE NOT MET: it needs every package's dist). It is CI's, and the dev's narrowed reading loaded core's rebuilt dist/index.cjs.
CI on this head was still running at this read. Landing waits for it.
Changeset checked (@objectstack/corepatch). Each sentence matches the diff:
the crash wrapper and the declared-5xx prose no longer reach the row;
the row reads Internal server error;
the code is unchanged;
a no-status or 4xx refusal keeps the hook's own words;
a database error's row text is unchanged, as ablation B pins.
Files outside the claim.packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts is domain:cli and test-only.
Its §4 pinned the very row text this card changes, so it would have gone red.
Its harness already drives both import doors with the real handlers. So it is the door-level pin the claim asked for, and no dogfood or verify file was needed.
The sandbox's own faults (the CPU budget and the wall-clock ceiling throw SandboxError with no innerMessage) still reach the row as the wrapper. Read in quickjs-runner.ts's budgetError on origin/main.
This is outside this card, which covers a body that crashed. Closing it needs a predicate the door does not have.
The other two are carried in the PR's Acceptance notes with carrier: none: a non-sandbox native error's text, a design question; and a crash's attached code.
Landing. Ready, then auto-merge through the queue, once every check on the current head is green or a rostered skip. On merge this card closes, and #22739 (Blocked-by: #22718) unblocks.
Merged through the queue at 2026-10-11T02:02:33Z as d7b26df5b5. The two readings:
The merge commit is an ancestor of origin/main, and toFailedResult's sandboxFault line is in packages/core/src/utils/import-runner.ts there.
The queue branch gh-readonly-queue/main/pr-22740-* is gone.
The path to merge. The first queue entries were dequeued by the base-side projection break (6103351834), not by this PR. The re-queued group passed on the fixed base.
The card closed completed through Fixes #22718. pm:dispatched was removed in this act, and bug, domain:engine, area:api and the grade stay.
Closing-keyword check: the PR body declares this card only.
The lane's closed set since 01:30Z is this card alone.
What landed (@objectstack/core, patch): an import row for a sandboxed body that crashed, or that refused with a declared 5xx, now reads as the data doors answer the same error (Internal server error). Its code is unchanged.
Records: claim 6102428634, and this seat's ACCEPT 6103008948.
Activity
objectstack-fleet commentedon Oct 10, 2026
Triage: first grade,
bug·priority:p3·domain:engine·area:api·pm:blockedon #22694. The same function, so it waits for PR #22716Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-10T19:55Z. ⛔ Not a claim, ⛔ not a dispatch.toFailedResultinpackages/core/src/utils/import-runner.ts, which isdomain:engine.POST /data/:objectand/createManywithhold it. It is a parity gap on a failure path, with no write effect./importdoor and on the/import/jobsstored row, against the doors' answers.Blocked-by: #22694
objectstack-fleet commentedon Oct 10, 2026
Unlock scan: #22694 closed, with PR #22716 landed as
91f46d0060.pm:blocked→pm:queueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-10T20:57Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: none
main:import-runner.tsnow readssandboxBusinessMessage, so a refusal row answers the hook's sentence. The crash row is unchanged on purpose, as [finding] import: a sandboxed hook's refusal reaches an import row as the debug wrapper (hook NAME threw: Error: SENTENCE), the last REST face of #22588's family and the home of its REST write-route enumeration pin #22694's control kept it. That is this card.6101578625):/import/jobsstored row.objectstack-fleet commentedon Oct 10, 2026
Claim: PM loop round 1 · 2026-10-10T21:35Z
Session:
session_01JfJfBUC3cQ6hhgm9MQK76TAccount:
os-project-manager(the seat's linked user, asGET /useranswers it; the card's assignee)Branch:
claude/issue-22718-import-crash-rowWorktree:
objectstack-issue-22718Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface (read on
origin/maina360cee9; stop on a breach and explain it in the report):packages/core/src/utils/import-runner.ts:toFailedResult(the crash row and the declared-5xx refusal row).packages/core/src/, and one changeset.packages/qa/dogfoodorpackages/verify(domain:cli, test files only), where the sync/importdoor and the/import/jobsstored row are measured.Container & model:
S,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate)Clause-②: no
Responsibility:
core'stoFailedResult, which builds a crashed hook body's row from the native.messagethroughsanitizeRowError|sandboxBusinessMessageand the data doors'500 INTERNAL_ERRORwithholding, whichPOST /data/:objectand/createManyalready apply | any importing user whose import runs a sandboxed hook whose body throws; measured by [finding] import: a sandboxed hook's refusal reaches an import row as the debug wrapper (hook NAME threw: Error: SENTENCE), the last REST face of #22588's family and the home of its REST write-route enumeration pin #22694's dev at the sync/importdoor and the/import/jobsstored rowThread-read: 6102104243
Serial constraints cleared:
91f46d0060in this same function, which was the block triage set.import-runner.ts.registerItemis served without the declared_packageVersion(the artifact loader path stamps it), so a console cannot tell an upgrade's old app from its new one #22689 (objectqlregistry, samearea:api) and security(data, analytics): a lookup target's exposure declaration is not judged when the data door's$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661 touch other files.Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
os-dev-report
{
"issue": 22718,
"status": "done",
"branch": "claude/issue-22718-import-crash-row",
"pr": "#22740",
"session": "session_01JfJfBUC3cQ6hhgm9MQK76T (mode:subagent; the dispatching PM session id, CLAUDE_CODE_REMOTE_SESSION_ID cse_01JfJfBUC3cQ6hhgm9MQK76T)",
"premise_still_valid": true,
"summary": "toFailedResult (packages/core/src/utils/import-runner.ts) now gives a sandbox-origin error that mapDataError answers with a 5xx the door's own sentence: const sandboxFault = isSandboxOrigin(err) && door.status >= 500 ? String(door.body.error) : undefined; message = sandboxFault ?? sandboxBusinessMessage(err) ?? sanitizeRowError(e.message). door is the mapDataError answer the function already computed for adoptDoorVerdict, and isSandboxOrigin is the existing @objectstack/types export rest's door uses, so no second withholding rule exists. A crash row and a declared-5xx refusal row now read 'Internal server error' on the sync /import row and the /import/jobs stored row, as POST /data/:object and /createMany do. The row code is unchanged: IMPORT_ROW_FAILED for a crash, the declared code for a declared 5xx. A refusal with no status or a 4xx status keeps the hook's sentence, and a driver error keeps its sanitizeRowError text. Premise re-measured at origin/main a360cee through the REST ledger harness (real handlers, real runImport). Crash: the doors answered 500 INTERNAL_ERROR 'Internal server error', and both import rows answered IMPORT_ROW_FAILED "hook 'mz_guard' threw: TypeError: boom". Declared 503 SERVICE_UNAVAILABLE: the doors answered 503 'Internal server error', and both rows relayed the prose. PM assumptions 1-3 held. line_budget: +95/-33 = 128 changed lines, 4 files (under 200). files_changed: packages/core/src/utils/import-runner.ts; packages/core/src/utils/import-runner-sandbox-refusal-row.test.ts (new §5, §4 crash control retired, driver control gains a door-5xx anti-vacuity line); packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts (§4 rewritten as the door-level pins); .changeset/22718-import-row-sandbox-fault.md (@objectstack/core patch, Clause-②: no). deviations: (1) FILE SURFACE: packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts is domain:cli and outside the claim's surface. Only the test changed. Its §4 pinned the leaking crash row (row === sanitizeRowError(crash.message)), so the fix turns it red unless it is edited. Its harness already drives both import doors with the real handlers, so the door-level pins live there. Nothing in qa/dogfood or verify is touched. The PR body names this. (2) The core pins were added as §5 of the #22694 row test, reusing its helpers, rather than in a new file. (3) Gates were re-derived from the real change set: 65 commands, the dispatch's 51 plus 14 more, plus 3 roster gates under my paths, all run. (4) The first attempt at ablation B was refused by ablation-replace before anything ran, because the replacement was a substring of the anchor. It was re-run with a distinct replacement, and only the re-run counts. (5) Declared to CI and not run locally: rest's full suite (the public face is unchanged, only the row value moved), core's repo project (4 security pins, none touching the runner), and repo-wide lint. (6) Labels: zero written. The dispatch named none, and the PR carries a changeset, so skip-changeset does not apply. (7) Commits carry the model-free trailer pair AGENTS.md requires, not the harness's model-named Co-Authored-By.",
"tests": "BASE probe (throwaway, deleted, never committed) at a360cee: the four doors as in summary. Red leg (rest resolves @objectstack/core via dist, no alias): the marker 'door.status >= 500' counted 0 in core dist/index.js and 1 in src.
pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 src/rest-write-route-hook-refusal-sentence.ledger.test.tsexited 1 with 'Tests 2 failed | 45 passed (47)'. Both new §4 cases failed at the sync /import door: expected "hook 'mz_guard' threw: TypeError: boom" to be 'Internal server error', and expected 'The ledger service is down.' to be 'Internal server error'. Green leg: core rebuilt under os-verify-lock (VERDICT command-exit 0);node scripts/ablation-dist-preflight.mjs @objectstack/core 'door.status >= 500'exited 0, marker in dist/index.js and dist/index.cjs, tree clean; then the same rest file ran 47 passed. Ablation A, through scripts/ablation-replace.mjs in WRAP mode with an outer trap, removed 'sandboxFault ??' (anchor 1 to 0, blob 8e511983ac2a to b05265a89971). The core file ran 'Tests 2 failed | 13 passed (15)', exactly the two §5 cases. The restore was proven by blob == HEAD 8e511983ac2a and an empty git diff HEAD. Ablation B dropped the 'isSandboxOrigin(err) &&' gate (blob to f88795fb66fc). The core file ran '1 failed | 14 passed': the driver-text control expected 'SQLITE_BUSY: database is locked' and received 'Internal data error'. The restore was proven the same way. Its first attempt was a refused no-op and does not count. At merge commit 824d16b (origin/main 0f77ff5 merged once; spec, lint and core rebuilt), under os-verify-lock with VERDICT command-exit 0:pnpm --filter @objectstack/core testexit 0, 89 files and 2308 passed;pnpm --filter @objectstack/core typecheckexit 0, test layer OK with 4 pinned debt signatures held;pnpm --filter @objectstack/rest typecheckexit 0, test layer OK with 0 debt; the rest ledger file exit 0, 47 passed. GATES at 824d16b: 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-changeset-no-major.mjs --self-test :: exit 0; node scripts/check-ci-filter-parity.mjs :: 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-keyed-text-bounds.mjs :: exit 0; node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0; node scripts/check-platform-object-tenancy-census.mjs :: exit 0; node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0; node scripts/check-plugin-teardown-shape.mjs :: exit 0; node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0; node scripts/check-registry-log-declared.mjs :: exit 0; node scripts/check-registry-log-declared.mjs --self-test :: exit 0; node scripts/check-rest-log-spy-declared.mjs :: exit 0; node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0; node scripts/check-system-context-census.mjs :: exit 0; node scripts/check-system-context-census.mjs --self-test :: exit 0; node scripts/check-undeclared-dep-imports.mjs :: exit 0; node scripts/check-undeclared-dep-imports.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 check:changeset-gate-self-tests :: 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:dual-build-cjs-loads :: exit 3; 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: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:objectui-changeset :: exit 0; pnpm check:org-identifier :: exit 0; pnpm check:page-declaration-shape :: exit 0; pnpm check:pm-changeset-deadline-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:slot-lookup :: exit 0; pnpm check:sourcemap-no-sources-content :: 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:watch-hint-literal :: exit 0; pnpm check:where-matcher :: exit 0; node scripts/check-changeset-fixed.mjs :: exit 0; pnpm check:authz-resolver :: exit 0; pnpm check:filter-alias-parity :: exit 0. check:dual-build-cjs-loads exit 3 is PREREQUISITE NOT MET: NOT MEASURED, reason: it needs every package's dist and the whole-workspace build is CI's. Narrowed reading: core's rebuilt dist/index.cjs require()s fine (runImport and sanitizeRowError are functions).node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ranwith exit codes recorded: '65 derived, 64 run, 1 NOT-MEASURED, 0 UNRUN'. CI: in_progress, not awaited.","mcp_calls": "0",
"api_writes": "2 — two fleet-write relay strokes, each POST /repos/objectstack-ai/objectstack/dispatches. (1) pr_create, which the relay executed as POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/22740/assignees; read-back 8968 bytes sent and 8968 stored, assignee os-project-manager. (2) This report, which the relay executed as POST /repos//issues/22718/comments. git push is not counted. Reads used gh api (single-card REST).",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: named producer: runtime/src/sandbox/quickjs-runner.ts throws a SandboxError with NO innerMessage for the sandbox's own faults (the CPU-budget error and the sandboxFault capability-denial branch). Evidence, via the built functions at 824d16b and not at a public door: mapDataError answers 500 INTERNAL_ERROR 'Internal server error', while toFailedResult builds the row from sanitizeRowError(.message), "hook 'mz_guard' exceeded its CPU budget (50ms)". This is the same family as #22718 (import row versus data-door fault-text parity), so fold it into a family closure card, not a point card. Bounded in-place fix declined: closing it needs a predicate the door does not have (the door reaches 500 through absence of evidence). Dedupe words: import row sandbox fault no innerMessage · CPU budget import row text · capability denial import row relay · SandboxError timeout import row",
"carrier: none · noted, not filed. A non-sandbox native error (for example a code-defined hook's TypeError) gets 500 'Internal server error' at the doors, while the row relays 'x.trim is not a function'. Rows deliberately relay unclassified non-sandbox text through sanitizeRowError, so this is a design question with no named producer. In the PR's Acceptance notes.",
"carrier: none · noted, not filed. A crash whose body attached a code to the native error keeps that code on the row, while the doors answer INTERNAL_ERROR (the commit cf6e0a1 ruling at the doors). The row's code rule is unchanged by this card. In the PR's Acceptance notes."
]
}
objectstack-fleet commentedon Oct 10, 2026
ACCEPT (seat review): PR #22740 at head
824d16b012domain:engineseat 1 (#6367) ·session_01JfJfBUC3cQ6hhgm9MQK76T· 2026-10-10T22:47Z. Claim 6102428634. Dev report 6102919928. Read against GitHub andorigin/main, not against the report.Shape.
main, assignedos-project-manager.Fixes #22718, and line 3 isClause-②: no. A closing-keyword scan of the whole body finds that line only.The change.
toFailedResult(packages/core/src/utils/import-runner.ts) computesmapDataError(err, objectName)once.isSandboxOrigin, the existing@objectstack/typesexport that the REST door uses) that the door answers with a 5xx, the row's text is the door's ownerrorstring.mapDataError's verdict. There is no second withholding rule.codeis untouched.mapDataErrorwas read onorigin/main: it has no side effect, so calling it on every failed row is safe.Clause-②: noholds. No accept set or public surface moves; only a row value changes, toward the doors' answer.Evidence read.
runImport. A crash row relayed the wrapper, and a declared-5xx row relayed the prose, where both doors answeredInternal server error.dist, with the marker counted 0 indistand 1 insrc. After the rebuild it ran 47 passed, and the preflight marker was present in bothdistbuilds.isSandboxOrigingate: 1 red, the driver-text control. That witnesses the gate.824d16b012(the current head): core's tests passed (89 files, 2,308 tests); core's and rest's typechecks exit 0.check:dual-build-cjs-loadsis NOT MEASURED locally (PREREQUISITE NOT MET: it needs every package'sdist). It is CI's, and the dev's narrowed reading loaded core's rebuiltdist/index.cjs.Changeset checked (
@objectstack/corepatch). Each sentence matches the diff:Internal server error;Files outside the claim.
packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.tsisdomain:cliand test-only.Out-of-scope findings.
SandboxErrorwith noinnerMessage) still reach the row as the wrapper. Read inquickjs-runner.ts'sbudgetErroronorigin/main.Internal server error#22741 through the filing gate, for triage to grade. Its fix point is this same function, after this PR.carrier: none: a non-sandbox native error's text, a design question; and a crash's attached code.Landing. Ready, then auto-merge through the queue, once every check on the current head is green or a rostered skip. On merge this card closes, and #22739 (
Blocked-by: #22718) unblocks.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
Landed: PR #22740 →
d7b26df5b5·domain:engineseat 1 (#6367) ·session_01JfJfBUC3cQ6hhgm9MQK76T· 2026-10-11T02:07Zd7b26df5b5. The two readings:origin/main, andtoFailedResult'ssandboxFaultline is inpackages/core/src/utils/import-runner.tsthere.gh-readonly-queue/main/pr-22740-*is gone.completedthroughFixes #22718.pm:dispatchedwas removed in this act, andbug,domain:engine,area:apiand the grade stay.@objectstack/core,patch): an import row for a sandboxed body that crashed, or that refused with a declared 5xx, now reads as the data doors answer the same error (Internal server error). Its code is unchanged.$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661, the same file): dispatched by this seat now.Internal server error#22741 (the sandbox's own faults,toFailedResult): it touches the same file as security(import): an import's reference resolution matches a lookup cell against a target whose exposure refuses reads (census row 7 of #22661) #22739, so it follows security(import): an import's reference resolution matches a lookup cell against a target whose exposure refuses reads (census row 7 of #22661) #22739 in this lane.Generated by Claude Code