Repository navigation
fix(types): the data door answers the sandbox's own fault as a fault, not as an unknown object (#22868) - #22882
Conversation
…t it is, before the unknown-object heuristic Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6 Co-authored-by: Claude <noreply@anthropic.com>
…and holds its controls Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b56bbffe0dfb7428b286c012b781f8e0a9dff3db && git checkout b56bbffe0dfb7428b286c012b781f8e0a9dff3db
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 75b6d6a0483645a62d753557e82701ea53d99dfd 31b25500d1d89d8548c5535296db713485247399 && git checkout -B drift-repro 75b6d6a0483645a62d753557e82701ea53d99dfd && git merge --no-ff 31b25500d1d89d8548c5535296db713485247399
node scripts/docs-audit/affected-docs.mjs --json 75b6d6a0483645a62d753557e82701ea53d99dfd
|
Fixes #22868
Clause-②: no
What was wrong
The QuickJS runner raises some
SandboxErrors itself, with noinnerMessage: a budget, a denied capability, an unavailablectx.api, a missingctx.apimethod, a failed install or marshal. Two of those texts name the object the hook body addressed:ctx.api.object(...)capability gate (quickjs-runner.ts:1173,throwSandboxFault):capability 'api.read' not granted to hook 'H' (called ctx.api.object('OBJ').find);quickjs-runner.ts:1199):ctx.api.object('OBJ').find not implemented.classifyDataError's unknown-object limb answers404 OBJECT_NOT_FOUND("Object 'OBJ' is not registered") for a message holding the quoted requested object andnot. So when the body addressed the request's own object,mapDataError, and every door that reaches it (POST /api/v1/data/:object,resolveErrorResponse's fall-through, the import rows), answered a sandbox fault as "this object does not exist".The fix: one statement in
packages/types/src/data-error-classification.tsclassifyDataErrorasksisSandboxOwnFault(PR #22867) at its top, beside theisSandboxCrashterminal, and answersUNCLASSIFIED_FAULT():500,code: INTERNAL_ERROR,error: Internal server error. No second rule: the predicate reads the producer's identity (name === 'SandboxError'and noinnerMessage), never the text. TheisSandboxOwnFaultdocblock's "mapDataError asks nothing here" paragraph now says that it does.Where it is asked, measured
mapDataError(SandboxError(text), 'mz_locked')on the source throughtsx, with the fix line ablated (scripts/ablation-replace.mjs, restored, blob == HEAD) and with the fix:no such tableunknown objecthas no column named xctx.titlegate, body-chosen fieldunique constraintcapability 'log' not granted to hook 'g', request object namedloghook 'g' threw: TypeError: boom)The runner interpolates the body's own arguments (
ctx.api.object(name),ctx.title(field)) into its texts, so the missing-relation,unknown object, missing-column and leak limbs can each misread one too. An exclusion beside the substring limb alone would leave them. Asked at the top, the answer is the terminal the other runner faults already reached, so only misread answers move. Own faults carry nocodeorstatus, so the code-gated arms between the top and the message limbs never matched them.The pin: the producer enumeration, not the REST write-route ledger
The triage named the ledger #22764 rides (
packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts) as the family's enumeration. Measured, these two texts cannot live there inside this card:SANDBOX_FAULTSon the fixed tree: 46 failed, 229 passed. The 21 rows that answer through the classification passed. 23 hand-built rows relay the runner's text verbatim: analytics dataset query, security explain, record shares (2), sharing rules (3), suggested bindings (2), approvals (9), package publish, external datasources (4). For example:Received: "capability 'api.read' not granted to hook 'mz_guard' (called ctx.api.object('mz_locked').find)". TheirsandboxFaultAnswer(packages/rest/src/error-response.ts) answers only for a sandbox ORIGIN (innerMessageset), so an own fault falls to each family's own terminal. That is a separate defect inpackages/rest, outside this claim's file surface. It is reported for filing, not fixed here, and the probe was reverted.packages/core/src/utils/import-runner-sandbox-fault-row.test.ts(PR fix(core): an import row for the sandbox's own fault reads the door's fault sentence (#22741) #22867). That file parsesquickjs-runner.tswith the TypeScript compiler and renders every own-fault site. It holds each site tomapDataError's500 INTERNAL_ERRORbody and holds the import row to that sentence. Its sample object was deliberately NOT the import's object, and its header called this door defect out of scope. This PR makes the sample the requested object, so the two sites that name it are pinned at500from the producer. It also adds:404 OBJECT_NOT_FOUND;Error(not the sandbox) keep the heuristic's404, which proves the door asks the producer, not the words.The
@objectstack/coresuite aliases@objectstack/typesto its source (packages/core/vitest.config.ts), so this pin reads the fixed classification directly, with nodist/in between.Evidence, at HEAD
31b25500d1d457a70fbd):node scripts/ablation-replace.mjs --file packages/types/src/data-error-classification.ts --anchor " if (isSandboxOwnFault(error)) return UNCLASSIFIED_FAULT();" --replacement " /* ABLATED-22868 */" -- pnpm --filter @objectstack/core exec vitest run --maxWorkers=2 src/utils/import-runner-sandbox-fault-row.test.ts. The mutation landed (anchor 1 to 0). Result: 2 failed, 26 passed, exactlyquickjs-runner.ts:1173 throwSandboxFault(…)andquickjs-runner.ts:1199 new SandboxError(…), bothexpected 404 to be 500. Every control and every other site stayed green. After the restore, blob == HEAD andgit diff HEADis empty. With the fix: 28 of 28.pnpm --filter @objectstack/types test: 27 files, 754 passed.pnpm --filter @objectstack/types typecheck: exit 0.pnpm --filter @objectstack/core exec vitest run --maxWorkers=2 src/utils/import-runner-sandbox-fault-row.test.ts src/utils/import-runner-sandbox-refusal-row.test.ts: 2 files, 43 passed.pnpm --filter @objectstack/core typecheck: exit 0. The test file is intsconfig.test.json's program (--listFilescount 1) and has no errors.@objectstack/rest, against the rebuilt@objectstack/typesdist: 21 files that exercise the sandbox and unknown-object answers, 938 passed. They includerest-unknown-object-heuristic,rest-hook-script-fault-envelope, the unchanged write-route ledger andrest.test.ts.node scripts/pm/dispatch-gates.mjs --commands(no paths) derived 65. All 65 ran: 64 exit 0, and 1 NOT MEASURED.pnpm check:dual-build-cjs-loadsexited 3, PREREQUISITE NOT MET: it needs a whole-workspace build, and 44 packages had nodist/. Declared narrow substitute:@objectstack/types' CJS and ESMdistboth load and answer the fixed500.--ranreconciliation: 65 accounted, 0 UNRUN.eslint --no-inline-config --format jsonover the two touched TS files reports 2 files, 0 errors, 0 warnings. There is no "file ignored" warning, so both files are in the config'spackages/**/*.{ts,…}population. The config never enables type-aware linting (noparserOptions.project), so this diff cannot move a verdict in an untouched file. The.mdchangeset is outside eslint's population.Public surface
The one code line is a statement inside the non-exported
classifyDataError. No declaration moved, so@objectstack/types' type surface is unchanged: the only non-comment changed line inpackages/types/srcis the newif. The runtime answer ofmapDataErrormoves for an error namedSandboxErrorwith noinnerMessage. Changeset:@objectstack/typespatch.Acceptance notes
Generated by Claude Code