Repository navigation
Commit 8bc7836
Fixes #22868
Clause-②: no
## What was wrong
The QuickJS runner raises some `SandboxError`s itself, with no
`innerMessage`: a budget, a denied capability, an unavailable `ctx.api`,
a missing `ctx.api` method, a failed install or marshal. Two of those
texts name the object the hook body addressed:
- the `ctx.api.object(...)` capability gate (`quickjs-runner.ts:1173`,
`throwSandboxFault`): `capability 'api.read' not granted to hook 'H'
(called ctx.api.object('OBJ').find)`;
- the missing-method arm (`quickjs-runner.ts:1199`):
`ctx.api.object('OBJ').find not implemented`.
`classifyDataError`'s unknown-object limb answers `404 OBJECT_NOT_FOUND`
("Object 'OBJ' is not registered") for a message holding the quoted
requested object and `not`. 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.ts`
`classifyDataError` asks `isSandboxOwnFault` (PR #22867) at its top,
beside the `isSandboxCrash` terminal, and answers
`UNCLASSIFIED_FAULT()`: `500`, `code: INTERNAL_ERROR`, `error: Internal
server error`. No second rule: the predicate reads the producer's
identity (`name === 'SandboxError'` and no `innerMessage`), never the
text. The `isSandboxOwnFault` docblock's "mapDataError asks nothing
here" paragraph now says that it does.
### Where it is asked, measured
`mapDataError(SandboxError(text), 'mz_locked')` on the source through
`tsx`, with the fix line ablated (`scripts/ablation-replace.mjs`,
restored, blob == HEAD) and with the fix:
| runner text | fix ablated | with the fix |
|---|---|---|
| capability gate, requested object | 404 OBJECT_NOT_FOUND | 500
INTERNAL_ERROR |
| missing method, requested object | 404 OBJECT_NOT_FOUND | 500
INTERNAL_ERROR |
| capability gate, another object | 500 INTERNAL_ERROR | 500
INTERNAL_ERROR |
| capability gate, body-chosen object name `no such table` | 500
DATABASE_ERROR | 500 INTERNAL_ERROR |
| capability gate, body-chosen object name `unknown object` | 404
OBJECT_NOT_FOUND | 500 INTERNAL_ERROR |
| capability gate, body-chosen object name `has no column named x` | 400
INVALID_FIELD | 500 INTERNAL_ERROR |
| `ctx.title` gate, body-chosen field `unique constraint` | 500
DATABASE_ERROR | 500 INTERNAL_ERROR |
| `capability 'log' not granted to hook 'g'`, request object named `log`
| 404 OBJECT_NOT_FOUND | 500 INTERNAL_ERROR |
| CPU budget | 500 INTERNAL_ERROR | 500 INTERNAL_ERROR |
| nested crash relayed as a fault (`hook 'g' threw: TypeError: boom`) |
500 INTERNAL_ERROR | 500 INTERNAL_ERROR |
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 no `code` or `status`, 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:
- I added the two texts to its §6 `SANDBOX_FAULTS` on 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)"`. Their `sandboxFaultAnswer`
(`packages/rest/src/error-response.ts`) answers only for a sandbox
ORIGIN (`innerMessage` set), so an own fault falls to each family's own
terminal. That is a separate defect in `packages/rest`, outside this
claim's file surface. It is reported for filing, not fixed here, and the
probe was reverted.
- This member does fit in
`packages/core/src/utils/import-runner-sandbox-fault-row.test.ts` (PR
#22867). That file parses `quickjs-runner.ts` with the TypeScript
compiler and renders every own-fault site. It holds each site to
`mapDataError`'s `500 INTERNAL_ERROR` body 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 at `500` from the producer. It also adds:
- a census floor: at least one capability-denial site and one
missing-method site name the requested object (no wording pinned);
- CONTROL: a real SQLite or Postgres unknown-object text on the
requested object still answers `404 OBJECT_NOT_FOUND`;
- CONTROL: the same object-naming texts on a plain `Error` (not the
sandbox) keep the heuristic's `404`, which proves the door asks the
producer, not the words.
The `@objectstack/core` suite aliases `@objectstack/types` to its source
(`packages/core/vitest.config.ts`), so this pin reads the fixed
classification directly, with no `dist/` in between.
## Evidence, at HEAD `31b25500d1`
- Reverse verification, with the fix committed first (`d457a70fbd`):
`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**,
exactly `quickjs-runner.ts:1173 throwSandboxFault(…)` and
`quickjs-runner.ts:1199 new SandboxError(…)`, both `expected 404 to be
500`. Every control and every other site stayed green. After the
restore, blob == HEAD and `git diff HEAD` is 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 in `tsconfig.test.json`'s program (`--listFiles` count 1) and
has no errors.
- `@objectstack/rest`, against the rebuilt `@objectstack/types` dist: 21
files that exercise the sandbox and unknown-object answers, 938 passed.
They include `rest-unknown-object-heuristic`,
`rest-hook-script-fault-envelope`, the unchanged write-route ledger and
`rest.test.ts`.
- Gates: `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-loads` exited 3, PREREQUISITE NOT MET: it needs a
whole-workspace build, and 44 packages had no `dist/`. Declared narrow
substitute: `@objectstack/types`' CJS and ESM `dist` both load and
answer the fixed `500`. `--ran` reconciliation: 65 accounted, 0 UNRUN.
- Lint, as a proven narrowing: `eslint --no-inline-config --format json`
over 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's
`packages/**/*.{ts,…}` population. The config never enables type-aware
linting (no `parserOptions.project`), so this diff cannot move a verdict
in an untouched file. The `.md` changeset 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 in
`packages/types/src` is the new `if`. The runtime answer of
`mapDataError` moves for an error named `SandboxError` with no
`innerMessage`. Changeset: `@objectstack/types` `patch`.
## Acceptance notes
- The REST hand-built write routes that relay the runner's own-fault
text (above) are reported to the dispatching seat for filing, with the
probe. They are not addressed here.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent b1d701b commit 8bc7836
3 files changed
Lines changed: 84 additions & 14 deletions
File tree
- .changeset
- packages
- core/src/utils
- types/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
56 | 58 | | |
57 | 59 | | |
58 | 60 | | |
| |||
68 | 70 | | |
69 | 71 | | |
70 | 72 | | |
| 73 | + | |
| 74 | + | |
71 | 75 | | |
72 | 76 | | |
73 | 77 | | |
74 | | - | |
| 78 | + | |
75 | 79 | | |
76 | 80 | | |
77 | 81 | | |
| |||
82 | 86 | | |
83 | 87 | | |
84 | 88 | | |
85 | | - | |
| 89 | + | |
86 | 90 | | |
87 | 91 | | |
88 | 92 | | |
| |||
219 | 223 | | |
220 | 224 | | |
221 | 225 | | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
222 | 231 | | |
223 | 232 | | |
224 | 233 | | |
| |||
228 | 237 | | |
229 | 238 | | |
230 | 239 | | |
231 | | - | |
232 | | - | |
233 | 240 | | |
234 | 241 | | |
235 | 242 | | |
| |||
280 | 287 | | |
281 | 288 | | |
282 | 289 | | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
283 | 295 | | |
284 | 296 | | |
285 | 297 | | |
| |||
331 | 343 | | |
332 | 344 | | |
333 | 345 | | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
334 | 368 | | |
335 | 369 | | |
336 | 370 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
795 | 795 | | |
796 | 796 | | |
797 | 797 | | |
798 | | - | |
799 | | - | |
800 | | - | |
801 | | - | |
802 | | - | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
803 | 804 | | |
804 | 805 | | |
805 | 806 | | |
| |||
1336 | 1337 | | |
1337 | 1338 | | |
1338 | 1339 | | |
| 1340 | + | |
| 1341 | + | |
| 1342 | + | |
| 1343 | + | |
| 1344 | + | |
| 1345 | + | |
| 1346 | + | |
| 1347 | + | |
| 1348 | + | |
| 1349 | + | |
| 1350 | + | |
| 1351 | + | |
| 1352 | + | |
| 1353 | + | |
| 1354 | + | |
| 1355 | + | |
| 1356 | + | |
| 1357 | + | |
| 1358 | + | |
| 1359 | + | |
| 1360 | + | |
| 1361 | + | |
1339 | 1362 | | |
1340 | 1363 | | |
1341 | 1364 | | |
| |||
0 commit comments