Skip to content

fix(core): an import row for the sandbox's own fault reads the door's fault sentence (#22741) - #22867

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-22741-sandbox-fault-import-row
Oct 11, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-22741-sandbox-fault-import-row

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #22741
Clause-②: yes (widening)

What

An import row for a fault the QuickJS sandbox raises itself now reads the create door's fault sentence instead of the runner's diagnostic text. These faults are the CPU budget, the wall-clock ceiling, a denied capability, an unavailable ctx.api, a failed install or marshal, and a nested body's crash relayed across a VM hop. POST /api/v1/data/:object and /createMany answer them 500 INTERNAL_ERROR Internal server error. The sync /import row and the /import/jobs stored row relayed .message instead, for example hook 'mz_lock_insert' exceeded CPU budget of 50ms (after 0 pump iterations).

Landing point, and why no runner marker

  • Predicate: isSandboxOwnFault(error) in packages/types/src/data-error-classification.ts, beside isSandboxOrigin. It answers true when name === 'SandboxError' and isSandboxOrigin is false (no non-empty innerMessage). Both halves are read off the producer. quickjs-runner.ts sets this.name = 'SandboxError', and its innerMessage contract says the field is undefined for "the sandbox's own internal errors (capability denials, timeouts, marshalling failures)". The producer already marks its own faults, so the runner needs no new marker and packages/runtime is untouched.
  • Why the class name and not instanceof: @objectstack/runtime depends on @objectstack/core (packages/runtime/package.json), so core cannot import the class. isEngineDuplicateRecordEnvelope in the same file recognises a class by its declared contract for the same reason, and objectql's hook-withheld-readonly-fault.ts already reads 'SandboxError' by name.
  • Row: toFailedResult (packages/core/src/utils/import-runner.ts) now computes sandboxed = isSandboxOrigin(err) || isSandboxOwnFault(err), and takes the door's sentence when sandboxed && door.status >= 500. That is the rule PR fix(core): an import row for a sandboxed body the door answers as a fault reads as that fault #22740 applies to a crash. ⛔ There is no second withholding rule: mapDataError still decides which errors are faults and what their sentence is. The row's code stays IMPORT_ROW_FAILED.
  • ⛔ Still not "every door 5xx": a driver error is neither a sandbox origin nor a sandbox fault, and its row keeps sanitizeRowError text.

Measured before the fix

This ran through the real QuickJSScriptRunner at eed637c09f, feeding the real mapDataError and runImport (a scratch test, not committed). Import object mz_locked:

fault (real runner) name / innerMessage create door row before
CPU budget (for(;;){}, 50ms) SandboxError / undefined 500 Internal server error hook 'mz_lock_insert' exceeded CPU budget of 50ms (after 0 pump iterations)
wall-clock ceiling (never-settling host call) SandboxError / undefined 500 Internal server error hook 'mz_lock_insert' exceeded wall-clock ceiling of 300ms while awaiting host calls (after 40 pump iterations)
api.read denied on another object SandboxError / undefined 500 Internal server error capability 'api.read' not granted to hook 'mz_lock_insert' (called ctx.api.object('mz_other').find)
log denied SandboxError / undefined 500 Internal server error capability 'log' not granted to hook 'mz_lock_insert'
missing ctx.api method SandboxError / undefined 500 Internal server error ctx.api.object('mz_other').find not implemented
api.read denied on the import's own object SandboxError / undefined 404 OBJECT_NOT_FOUND Object 'mz_locked' is not registered the capability text

The last row is a separate door defect (see Acceptance notes). The row does not copy a 4xx sentence, so it keeps the capability text there.

The enumeration pin

packages/core/src/utils/import-runner-sandbox-fault-row.test.ts sits in core's repo vitest project and is listed in vitest.repo-tests.json, because it reads packages/runtime/src/sandbox/quickjs-runner.ts. Core's declared radius packages/**/*.ts already covers that read. The test parses the runner with the TypeScript compiler, so the two throw new SandboxError( examples inside doc comments are not sites.

  • §1, 15 fault sites: every new SandboxError(…) with no innerMessage (:235, :240, :660, :891, :917, :1025, :1104, :1199, :1553) and every throwSandboxFault(…) call (:634, :730, :756, :803, :1173, :1180). Each message is rendered from its own template literal with a fixed sample per interpolation. Each case first asserts the door's verdict (500, { error: 'Internal server error', code: 'INTERNAL_ERROR' }, the text withheld) as anti-vacuity. It then asserts isSandboxOwnFault and that the row equals { row: 1, ok: false, action: 'failed', error: DOOR_SENTENCE, code: 'IMPORT_ROW_FAILED' }. The batched insertManyData path is driven once for the budget fault.
  • The census: the two non-literal single-argument constructions (message in throwSandboxFault, and withoutSandboxErrorPrefix(String(errStr)) in the pump loop) must be exactly the declared relays. A site the pin cannot render, such as a new interpolation with no sample, is red and the failure names it. Each named kind keeps its measured floor: CPU budget 1, wall-clock 1, capability denial 5, ctx.api unavailable 1, install 4, marshal 1, nested transaction 1, missing method 1. The class name is read from the producer's constructor, so a rename there reaches every case.
  • §2, the relay half: the four constructions that do set innerMessage (:280, :349, :403, :448) are rendered after a nested body crashed (TypeError: boom). That is the .message the outer pump rebuilds as a fault with no innerMessage. The door withholds it, and the row takes the door's sentence.
  • §3, controls: the same budget text on a plain Error keeps sanitizeRowError's reading (the predicate reads class identity, never the message). A code-defined hook's native TypeError keeps its text. Driver text keeps sanitizeRowError's reading although its door answers 5xx. A body's refusal or crash, and non-error values, answer false. import-runner-sandbox-refusal-row.test.ts §4, the driver-text control PR fix(core): an import row for a sandboxed body the door answers as a fault reads as that fault #22740's ablation B pinned, is not edited.

Ablations

All were run from committed HEAD 2180c575bf through node scripts/ablation-replace.mjs. In each, the anchor hit once and moved from 1 to 0, the blob changed, and the restore was proven with the blob equal to HEAD and git diff HEAD empty. The command was pnpm --filter @objectstack/core exec vitest run --project repo --maxWorkers=2 src/utils/import-runner-sandbox-fault-row.test.ts.

leg mutation result
A toFailedResult: isSandboxOrigin(err) || isSandboxOwnFault(err) becomes isSandboxOrigin(err) 20 failed / 5 passed: every §1 site, the batched path and all four §2 relays went red; the census and the 4 controls stayed green
B isSandboxOwnFault body becomes return false && … 20 failed / 5 passed
C producer this.name = 'SandboxError' becomes 'SandboxFault' in quickjs-runner.ts 20 failed / 5 passed: the cross-package name contract is pinned
D a new fault site the pin cannot render: :660 becomes a template with ${txState.depth} 1 failed / 23 passed: the census, naming quickjs-runner.ts:660: no sample for the interpolation txState.depth

The test imports ./import-runner relatively, and core's vitest aliases @objectstack/types to its source, so no dist/ sits on these paths. The runner is read from disk.

Tests

All on HEAD bb09729a0a (the branch merged with origin/main 55382dc02a). The dependency closure was built first with pnpm --filter '@objectstack/core...' build (exit 0).

  • pnpm --filter @objectstack/core exec vitest run --project local --project repo --maxWorkers=2: 100 files / 2451 tests passed (all 100 test files in the package, both projects).
  • pnpm --filter @objectstack/types exec vitest run --project local --project repo --maxWorkers=2: 28 files / 765 tests passed.
  • pnpm --filter @objectstack/types typecheck: exit 0. pnpm --filter @objectstack/core typecheck: exit 0. The test layer stays at its 4 pinned signatures, and the new test adds none.
  • The new file alone: 25/25 passed (the census, 15 §1 sites, the batched path, 4 §2 relays, 4 controls).
  • Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 66 families from this diff, and all 66 exit 0. Two of them, check:dual-build-cjs-loads and check:lean-entry-closure, first answered PREREQUISITE NOT MET (exit 3, not measured). Both were re-run green on the same HEAD after check:type-check-debt's full build. --ran reconciliation: 66 derived, 66 run, 0 NOT-MEASURED (a derived zero, since every line carries its exit code). Re-run on 23a4a8a83f (the changeset commit): the same 66 families, all exit 0, none exit 3; --ran 66 derived, 66 run, 0 NOT-MEASURED.
  • Lint, a declared narrowing: pnpm exec eslint --no-inline-config --format json on the three changed .ts files gives 3 files, 0 errors and 0 warnings (the count is read from the JSON). The population was read from ESLint itself: isPathIgnored is false and calculateConfigForFile yields rules for all three. This repo never enables type-aware linting (parserOptions.project is null for all three, and eslint.config.mjs says so), so the diff cannot move the verdict on any untouched file. The repo-wide pnpm lint is left to CI.
  • CI: in_progress when this PR opened, and not waited on.

Acceptance notes

  • A door defect, outside this card: mapDataError's unknown-object heuristic, lower.includes("'OBJECT'") && lower.includes('not'), answers 404 OBJECT_NOT_FOUND Object 'OBJECT' is not registered for a sandbox fault whose text names the requested object. A capability denial on that object, such as capability 'api.read' not granted to hook '…' (called ctx.api.object('OBJECT').find), does. It was measured through the real runner as shown above. The row does not copy that 4xx, so it keeps the capability text there. If the door is fixed to 500, the row follows with no change here. This is reported to the seat for filing.
  • Population versus triage's count: triage named 8 no-innerMessage sites (:235, :240, :660, :891, :917, :1025, :1104, :1199). The enumeration also finds the marshal failure (:1553), the six throwSandboxFault capability and ctx.api sites, and the two relays (:432, :1524). The PM's 17 new SandboxError lines are 15 constructions plus 2 inside doc comments.
  • Mechanism note: isSandboxOrigin is not module-private. It is in the file's trailing export { … } block, so @objectstack/types already exports it, and core imported it before this PR.
  • Public surface: @objectstack/types' . entry gains one export, isSandboxOwnFault(error: unknown): boolean. It was measured on the built dist/index.d.ts (the declaration and the export list) and on dist/index.d.mts. No other name changes, and nothing re-exports @objectstack/types wholesale (git grep), so it cannot collide. The seat settled the line as Clause-②: yes (widening). The changeset declares it too and takes minor for @objectstack/types; @objectstack/core stays patch.
  • Test wiring: packages/core/vitest.repo-tests.json gains the new file, which the core repo project requires for a test that reads outside the package.

Generated by Claude Code

… fault sentence

The QuickJS runner raises SandboxErrors of its own (CPU budget, wall-clock
ceiling, capability denial, install and marshal failures) with no
innerMessage. The row keyed its door-sentence rule on a sandbox origin, so
it relayed the runner's diagnostic text where the create door answers
Internal server error. isSandboxOwnFault (types) recognises them by the
producer's declared class name and empty innerMessage, and the row applies
the same rule to them.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-authored-by: Claude <noreply@anthropic.com>
…ort row to the door's sentence

Parses packages/runtime/src/sandbox/quickjs-runner.ts with the TypeScript
compiler: every SandboxError construction with no innerMessage and every
throwSandboxFault call is a site, rendered from its own template. Each
asserts the door's withheld verdict first, then that the row reads that
sentence. Controls: driver text, the same text on a non-sandbox error, a
code hook's native TypeError.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 11, 2026
@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/core/vitest.repo-tests.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/core/vitest.repo-tests.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 30 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8532783f651a4a48b68e7d89579856e45501885e → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 04bd8cb781bee5ae9035389a5c814c10b2e46144 — the merge of head 23a4a8a83f12634e900b9de2a2d8f1c51ddd88d6 into base 8532783f651a4a48b68e7d89579856e45501885e, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 04bd8cb781bee5ae9035389a5c814c10b2e46144 && git checkout 04bd8cb781bee5ae9035389a5c814c10b2e46144
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8532783f651a4a48b68e7d89579856e45501885e 23a4a8a83f12634e900b9de2a2d8f1c51ddd88d6 && git checkout -B drift-repro 8532783f651a4a48b68e7d89579856e45501885e && git merge --no-ff 23a4a8a83f12634e900b9de2a2d8f1c51ddd88d6

node scripts/docs-audit/affected-docs.mjs --json 8532783f651a4a48b68e7d89579856e45501885e

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

…for types

@objectstack/types gains the isSandboxOwnFault export, a public-surface
widening, so the changeset carries the Clause-② yes (widening) line and
types moves to minor. core stays patch.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 23a4a8a83f12634e900b9de2a2d8f1c51ddd88d6
Local-runs: none

PR #22867 for card #22741, read as the net diff from merge-base 55382dc02a (5 files, +419 / -3: .changeset/22741-sandbox-fault-import-row.md, packages/types/src/data-error-classification.ts, packages/core/src/utils/import-runner.ts, packages/core/src/utils/import-runner-sandbox-fault-row.test.ts, packages/core/vitest.repo-tests.json). Head repo is the base repo; no governed path in the file list; the review is owed for the Clause-②: yes declaration. Check-runs on the head at read time: 48 runs, 41 success, 7 skipped (path-filtered or re-fired duplicates: Build Docs, Console Pin Gate, Packed-tarball smoke opt-in, the second and third Auto Label and Check PR Size), 0 failed, 0 pending; all seven required contexts success.

① Derived judgments

  1. Public surface, @objectstack/types . entry: +1 export, isSandboxOwnFault(error: unknown): boolean. data-error-classification.ts is re-exported wholesale by src/index.ts (export * from './data-error-classification.js') and the new function is declared export function, so it reaches dist/index.d.ts / index.mjs / index.js under the . exports map. The ./node entry is untouched. @objectstack/types is published (17.7.0, not private). Nothing re-exports @objectstack/types wholesale (zero hits on origin/main), so no other package's surface moves. RIGHT, and declared (②).
  2. isSandboxOrigin was already public. It sits in the file's trailing export { … } block (origin/main line 1989) and core imported it before this PR; the diff adds no second export of it. The dev's correction of its own mechanism assumption is RIGHT.
  3. Predicate accept set: name === 'SandboxError' and no non-empty innerMessage. Read off the producer: packages/runtime/src/sandbox/quickjs-runner.ts is the only non-test site on origin/main that names an error 'SandboxError' (this.name = 'SandboxError', line 1735), and its innerMessage contract says the field is undefined for the sandbox's own internal errors. A body's refusal or crash crosses the VM wrapped WITH innerMessage, so it stays on isSandboxOrigin's side; the two predicates partition the class. Recognition by declared name rather than instanceof is forced: @objectstack/runtime depends on @objectstack/core (workspace:*) and core does not depend on runtime, so core cannot see the class; isEngineDuplicateRecordEnvelope in the same file is the precedent. RIGHT. Accepted residue: an error hand-built with name = 'SandboxError' and no innerMessage by non-sandbox code would read as a sandbox fault; no such producer exists on origin/main, and the pin reads the producer's name from source so a rename there is caught (ablation C).
  4. Row accept set, toFailedResult in @objectstack/core: for an error that is a sandbox origin OR a sandbox own fault, and whose door answer is a 5xx, the row's error becomes the door's sentence (Internal server error); code stays IMPORT_ROW_FAILED; the row is still ok: false. No row flips between ok and failed; only the error text of that class moves, on both the sync /import row and the /import/jobs stored row (one function). sandboxBusinessMessage returns undefined for an own fault (no innerMessage), so the new branch is the only one that changes. This is PR fix(core): an import row for a sandboxed body the door answers as a fault reads as that fault #22740's rule extended to the second half of the class the card names. RIGHT.
  5. No second withholding rule. mapDataError / classifyDataError are not edited; the door's verdict is still computed by the door and the row copies it. The card's ⛔ holds. RIGHT.
  6. Not "every door 5xx". A driver error is neither predicate's; its row keeps sanitizeRowError text although its door answers 5xx. Pinned in §3 of the new file, and PR fix(core): an import row for a sandboxed body the door answers as a fault reads as that fault #22740's §4 control (import-runner-sandbox-refusal-row.test.ts) is not in the diff. A code-defined hook's native TypeError keeps its text (card: "Not in this card"). RIGHT.
  7. A sandbox own fault the door answers 4xx (the unknown-object heuristic answering 404 OBJECT_NOT_FOUND when the fault text names the requested object) keeps the row's own text. The rule copies only a 5xx, so the row follows the door whichever way that door defect is later fixed; the defect is outside the card (door classification, not toFailedResult) and is carried in ③. RIGHT.
  8. The enumeration pin is the triage's pin. import-runner-sandbox-fault-row.test.ts parses the runner with the TypeScript compiler: every single-argument new SandboxError(…) that is not a declared relay and every throwSandboxFault(…) call is a site (15 at this head, with per-kind floors: CPU budget 1, wall-clock 1, capability denial 5, ctx.api unavailable 1, install 4, marshal 1, nested transaction 1, missing method 1); the two non-literal constructions must be exactly the declared relays; an unrenderable site reds the census by name. Each site asserts the door's 500 INTERNAL_ERROR verdict first (anti-vacuity), then isSandboxOwnFault true, then the row equal to the door's sentence; the batched insertManyData path is driven once; §2 covers the four innerMessage-setting constructions relayed as a fault one VM hop up. Doc-comment examples are not sites because the parser never sees them, which is the PM's 17 against the dev's 15. The sample object is deliberately not the import's object, keeping the pin clear of the 4xx heuristic in item 7. RIGHT.
  9. Test wiring. The file reads packages/runtime/src/sandbox/quickjs-runner.ts, spelled resolve(HERE, '../../../runtime/…') from HERE = dirname(fileURLToPath(import.meta.url)), one of the recognised escape spellings; it is listed in packages/core/vitest.repo-tests.json, which is what puts it in core's repo vitest project (check:cross-package-test-inputs runs inside the green Lint & Repo Gates). It imports ./import-runner with no .js, the same spelling as the sibling pin from PR fix(core): an import row for a sandboxed body the door answers as a fault reads as that fault #22740; core's tsconfig.test.json compiles the test layer with vitest's module semantics and package.json's typecheck names it through check:test-typecheck, so the file is compiled, not a phantom (the dev reports the ledger held at 4 pinned signatures with none added; TypeScript Type Check and all four Type Check · runs are green). RIGHT.
  10. Ablation readings carry to the head. The four ablations ran at 2180c575bf; the diff from that commit to the head over import-runner.ts, data-error-classification.ts, the new test, vitest.repo-tests.json and quickjs-runner.ts is empty, and quickjs-runner.ts is unchanged from the merge-base, so the merge of origin/main moved nothing the ablations or the census read. A (drop the predicate from the row), B (predicate returns false) and C (rename the producer) each 20 failed / 5 passed with the census and controls green; D (an unrenderable new site) 1 failed, naming the line. RIGHT.
  11. packages/runtime untouched. The claim allowed a runner marker only if the predicate needed one; it does not (item 3). RIGHT.
  12. Nothing else published moves. @objectstack/core exports no new name; ImportRowResult's shape is unchanged; no spec, schema, REST route, env var or ADR surface is in the diff. RIGHT.

② Semver level

  • Changeset .changeset/22741-sandbox-fault-import-row.md: '@objectstack/core': patch, '@objectstack/types': minor, body line Clause-②: yes (widening). No skip-changeset label (labels: documentation, size/m, tests, tooling).
  • @objectstack/types minor: an added export on the published . entry is a widening and takes at least minor. MATCHES ①1.
  • @objectstack/core patch: a bug fix in a released package (17.7.0, not private) with no exported-surface change. MATCHES ①4 and ①12.
  • Nothing removed or renamed, no accept-set narrowing, so no breaking changeset and no ADR-0087 disposition is owed; the dev reports check-adr-0087-registration and check-changeset-no-major exit 0 on this head, and all three Check Changeset runs on the head are success.
  • Clause-②: line, three carriers: PR body line 2 Clause-②: yes (widening); changeset Clause-②: yes (widening); claim 6110232592 (edited by the seat) Clause-②: yes, with the widening named in the prose beneath it. yes and yes (widening) are one reading under scripts/pm/clause2-line.mjs (an absent arm declares no direction; both take at least minor), so the three carriers agree, and the changeset's prose describes exactly what the diff does (the predicate's two halves, the row's rule, code unchanged, driver and code-hook text unchanged). CONSISTENT.

③ Boundary flags

Two os-dev-report comments on the card: 6111286788 (round 1) and 6111682179 (patch round 1, supersedes). open_questions is empty in both; nothing to answer there.

Round 1 deviations:

  1. packages/core/vitest.repo-tests.json gained one line, outside the literal three-file surface. ANSWERED, accepted: it is the membership list a cross-package-reading test must join (①9); the claim's surface already reads "tests under packages/core/src/", and this is that test's registration, not a new surface.
  2. Mechanism assumption 1 falsified in part (isSandboxOrigin already exported) and assumption 4 measured true (runtime depends on core). ANSWERED: both verified on origin/main (①2, ①3); the only consequence is name-based recognition, which is right.
  3. Assumption 3 re-enumerated: the no-innerMessage population is wider than triage's 8 sites (adds :1553 marshal, six throwSandboxFault sites, two relays). ANSWERED, accepted: the triage asked for an enumeration pin precisely so the population is read from the producer, and the pin's per-kind floors record the wider count (①8).
  4. Attribution conflict, harness reminder against AGENTS.md. ANSWERED, correct as resolved: AGENTS.md wins; the four authored commits carry the model-free Claude-Session: + Co-authored-by: Claude pair and the PR body carries the session-URL footer. No action.
  5. A scratch test placed under packages/runtime/src/sandbox for the premise measurement, deleted before the first commit. ANSWERED: not in the diff (5 files); nothing to clean.
  6. check:type-check-debt ran a full unlocked turbo build (about 17 minutes of shared-box time). ANSWERED: a process-cost note with no diff consequence; CI measured the same gates fresh.
  7. Ablations ran at 2180c575bf, before the origin/main merge. ANSWERED, verified: no pinned or read file differs between that commit and the head (①10).
  8. Public surface: types gains one export; the PR body then carried Clause-②: no and the changeset patch for both. ANSWERED, superseded: round 2 moved types to minor with Clause-②: yes (widening) in the changeset, the seat rewrote PR body line 2, and the claim line was edited to yes (②).

Round 1 out_of_scope_findings:

Round 2 deviations:

  1. dispatch-gates printed STALE TREE: origin/main was 2 commits past the merge-base and scripts/cross-package-test-inputs.mjs changed in that range, so the local derivation read the branch's copy; not merged. ANSWERED: the head's PR checks ran on the merge ref against the then-current base (8532783f65), and Lint & Repo Gates is success, so the current gate judged the merged tree; the queue re-runs it on the rebuilt generation. Not a blocker.
  2. The four changeset gates and the offline level-axis runs were made before the push, the 66-gate union after it, both on 23a4a8a83f. ANSWERED: same commit, same tree; the head's three Check Changeset runs are success.
  3. Carried from round 1: vitest.repo-tests.json as test wiring; trailers and footer per AGENTS.md. ANSWERED above (round 1 items 1 and 4).

Round 2 out_of_scope_findings:

Round 2 pr_body_sentences_now_false (two entries): both replacements are present in the PR body at this read (the Public-surface bullet now reads "The seat settled the line as Clause-②: yes (widening) …", and the Gates bullet carries the 23a4a8a83f re-run sentence). DISCHARGED.

Implemented-by: claude/issue-22741-sandbox-fault-import-row
Reviewed-by: session_01ADzJtzYTLUfgrRZHxkagkX

VERDICT: PASS

Adopted by domain:engine#2 (session_01ADzJtzYTLUfgrRZHxkagkX) at 2026-10-11T17:37Z from an isolated at-tier reviewer whose transcript shows read-only tool use (its one write is its own scratch record).

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants