Skip to content

[finding] data door: a sandboxed hook's capability denial or missing ctx.api method naming the requested object answers 404 OBJECT_NOT_FOUND ("Object 'X' is not registered") through the unknown-object heuristic #22868

Closed

Description

@objectstack-fleet

Filing gate: ① a product defect, class (a), reach by a named producer. Raised by #22741's dev (os-dev-report on #22741, out-of-scope finding 1; PR #22867) and filed by domain:engine seat 2 (#20966) · session_01ADzJtzYTLUfgrRZHxkagkX. ⛔ Not a claim. ⛔ Not graded here: triage grades and routes.

Reader who acts: triage, then the lane that owns classifyDataError in packages/types/src/data-error-classification.ts (domain:cli by the lane table). #22741 does not reach it: its import row copies the door's answer only for a 5xx, so this 404 is outside its contract.

The gap

  • The QuickJS runner raises its own SandboxErrors with no innerMessage (the runner's declared contract for its internal faults). Two of them name the object the body addressed:
    • the capability gate, through throwSandboxFault in packages/runtime/src/sandbox/quickjs-runner.ts: "capability api.read not granted to hook H (called ctx.api.object(OBJ).find)";
    • the missing-method arm: "ctx.api.object(OBJ).find not implemented".
  • classifyDataError's unknown-object limb answers 404 OBJECT_NOT_FOUND, "Object 'OBJ' is not registered", for any message that holds the quoted requested object name AND the substring not (looksLikeUnknownObject, read on origin/main eed637c09f).
  • So when the hook's body addressed the same object the request did, the data door answers a sandbox capability denial or a missing method as "this object does not exist". Every other runner fault site answers 500 INTERNAL_ERROR, which is what the producer's contract says such an error is.
  • Measured by the dev through the real QuickJSScriptRunner at eed637c09f into the real mapDataError(err, OBJ), the classification POST /api/v1/data/:object serves. Not measured at a booted door.

Direction (not a ruling)

  • The heuristic should not judge an error the sandbox raised itself. isSandboxOwnFault (PR fix(core): an import row for the sandbox's own fault reads the door's fault sentence (#22741) #22867, in the same file) recognises that error off the producer, and the classification could answer it as the fault it is before the substring limb runs. ⛔ No second rule, and no new wording test.
  • Pin: each runner fault text that names the requested object answers 500 INTERNAL_ERROR at mapDataError, and a real unknown-object driver text still answers 404 (control).

Duplicate check

Activity

  1. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p3 · domain:cli · area:api, into pm:queue

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-11T16:52Z. ⛔ Not a claim, ⛔ not a dispatch.

  2. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-11T18:18Z
    Session: session_01VoSxBQujKLZKPwK2u5ehQ6
    Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22868-sandbox-fault-not-unknown-object
    Worktree: objectstack-issue-22868
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    File surface (read on origin/main f9ebb7dd31; stop on a breach and explain it in the report):


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 22868,
      "status": "done",
      "branch": "claude/issue-22868-sandbox-fault-not-unknown-object",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/22882",
      "session": "session_01VoSxBQujKLZKPwK2u5ehQ6 (mode:subagent, the dispatching PM's session id)",
      "premise_still_valid": true,
      "summary": "classifyDataError (packages/types/src/data-error-classification.ts) now asks isSandboxOwnFault at its top, beside the isSandboxCrash terminal, and answers UNCLASSIFIED_FAULT (500 INTERNAL_ERROR, 'Internal server error'): one statement, no second rule, keyed on the producer's identity, never the text. Premise held on origin/main 51f36e7750: isSandboxOwnFault at :804, the substring limb at :1978; the two runner texts are quickjs-runner.ts:1173 (throwSandboxFault, ctx.api.object capability gate) and :1199 (missing method), and they were the only producer-rendered own-fault sites the door misread (ablation: exactly those 2 of 28 go red, 404 instead of 500). Placement measured: with body-chosen names interpolated by the runner, the missing-relation, 'unknown object', missing-column and leak limbs also misread own faults (404/400/500 DATABASE_ERROR), so the gate sits at the top, not beside the substring limb; only misread answers move. The pin lives in the producer enumeration packages/core/src/utils/import-runner-sandbox-fault-row.test.ts (its objectName sample is now the requested object, plus a census floor and two CONTROL pins), not in the REST write-route ledger: adding the two texts there turned 46 cases red on 23 hand-built rows that relay the runner text verbatim, a separate packages/rest defect outside the claim's surface (see out_of_scope_findings); that probe was reverted.",
      "tests": "HEAD 31b25500d1. Reverse verification (fix committed first at 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 -> 'ok mutation landed: anchor 1 -> 0, blob 2456ae803e5d -> 6b384309a658', 'Tests 2 failed | 26 passed (28)', failing exactly 'quickjs-runner.ts:1173 throwSandboxFault(...)' and 'quickjs-runner.ts:1199 new SandboxError(...)' with 'expected 404 to be 500'; 'ok restored: blob == HEAD (2456ae803e5d) and git diff HEAD is empty'. Resolution path: packages/core/vitest.config.ts aliases ^@objectstack/types$ to ../types/src/index.ts, so no dist rebuild or dist preflight applies to this leg. With the fix: 28/28. Limb measurement (tsx on source, same ablation tool, restored): 10 runner texts, fix ablated vs fixed - object-naming capability gate 404->500, missing method 404->500, body-chosen 'no such table' 500 DATABASE_ERROR->500 INTERNAL_ERROR, 'unknown object' 404->500, 'has no column named x' 400 INVALID_FIELD->500, ctx.title 'unique constraint' 500 DATABASE_ERROR->500 INTERNAL_ERROR, capability 'log' with object 'log' 404->500; CPU budget, other-object capability gate and relayed nested crash 500 INTERNAL_ERROR both ways. pnpm --filter @objectstack/types test: 'Test Files 27 passed (27) Tests 754 passed (754)' exit 0. 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: 'Test Files 2 passed (2) Tests 43 passed (43)' exit 0. pnpm --filter @objectstack/core typecheck: exit 0 (check:test-typecheck OK, 4 pinned debt errors in 4 other files); tsc --noEmit -p tsconfig.test.json --listFiles counts the touched test file 1 time, no error in it. pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 (21 files touching sandbox / unknown-object answers, incl. rest-unknown-object-heuristic, rest-hook-script-fault-envelope, error-response-structured-arm-door-parity, the unchanged write-route ledger, rest.test.ts) against types dist rebuilt from d457a70fbd (dist grep: fix line present 1x in index.mjs and index.js): 'Test Files 21 passed (21) Tests 938 passed (938)' exit 0. Ledger probe (not committed): the two own-fault shapes added to rest-write-route-hook-refusal-sentence.ledger.test.ts §6 -> 'Tests 46 failed | 229 passed (275)', all 46 on 23 hand-built rows. Lint, proven narrowing: eslint --no-inline-config --format json over the 2 touched TS files -> 2 files, 0 errors, 0 warnings (no file-ignored warning, so both are in the config's packages/**/*.{ts,...} population); eslint.config.mjs enables no type-aware linting (no parserOptions.project), so untouched files' verdicts cannot move; the changeset .md is outside eslint's population. Public surface: the only non-comment changed line in packages/types/src is the new if inside the non-exported classifyDataError; no declaration moved. dual-build substitute: packages/types dist CJS require and ESM import both load and answer {status:500, code:INTERNAL_ERROR}.",
      "gates_head": "31b25500d1",
      "gates": [
        "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).",
        "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 :: ✓ check-adr-0087-registration --self-test: 463 assertions over real temp git repos (real scan()/assertInputs() path)",
        "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 :: ✓ Protocol lockstep: PROTOCOL_VERSION major 18, @objectstack/spec@17.7.0 — the one exception, evidenced: pre mode (tag `next`) and a pen",
        "node scripts/check-changeset-no-major.mjs --self-test :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption s",
        "node scripts/check-ci-filter-parity.mjs :: exit 0 :: OK: all 21 build input(s) turbo.json declares outside the packages (itself, 2 globalDependencies, and every `$TURBO_ROOT$` input of a build ",
        "node scripts/check-closing-keyword-parity.mjs :: exit 0 :: check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar",
        "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 :: ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.",
        "node scripts/check-comment-mask-adoption.mjs :: exit 0 :: OK  check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/**, all 14 recorded and every recorded row s",
        "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 :: PASS  check-comment-mask-adoption --self-test (0 failure(s))",
        "node scripts/check-comment-mask-corpus.mjs :: exit 0 :: ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 8797 files, 0 disagree, 0 unparseable, 107.6s (comparator self-test: 26 cases p",
        "node scripts/check-dev-prereqs.mjs --self-test :: exit 0 :: ✓ check:dev-prereqs --self-test — every verdict reachable, exclusions and freshness coverage pinned (19 batteries, 76 cases), plus the s",
        "node scripts/check-dts-emitted.mjs --self-test :: exit 0 :: check-dts-emitted self-test: all assertions passed.",
        "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 :: ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
        "node scripts/check-empty-changeset.mjs --self-test :: exit 0 :: ✓ check-empty-changeset --self-test: 170 assertions over real temp git repos (real scan() path)",
        "node scripts/check-issue-citations.mjs :: exit 0 :: ✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference).",
        "node scripts/check-keyed-text-bounds.mjs :: exit 0 :: ✓ check:keyed-text-bounds: 112 *.object.ts files under packages/** + apps/** + examples/** (walk is repo-wide; 0 outside), 117 object decl",
        "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0 :: PASS  check-keyed-text-bounds --self-test (0 failure(s))",
        "node scripts/check-platform-object-tenancy-census.mjs :: exit 0 :: ✓ platform-object tenancy census matches the tree: 84 platform-namespace objects, 49 in the machinery's reach, 35 outside it, every exclus",
        "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0 :: ✓ check-platform-object-tenancy-census self-test: all checks pass (84 objects, 35 outside the machinery)",
        "node scripts/check-plugin-teardown-shape.mjs :: exit 0 :: ✓ check:plugin-teardown-shape: 74 Plugin implementation(s) across 8216 source(s) under packages/**; every teardown-shaped method (stop / s",
        "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 :: ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and both delegating-alias directio",
        "node scripts/check-registry-log-declared.mjs :: exit 0 :: OK: 73 vitest-running package(s) walked, 10 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/",
        "node scripts/check-registry-log-declared.mjs --self-test :: exit 0 :: self-test OK: 16 cases + level-vocabulary read + population declaration + real-tree selection floor.",
        "node scripts/check-rest-log-spy-declared.mjs :: exit 0 :: OK: 30 of 279 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declares its own OS_REST_LOG level.",
        "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0 :: ✓ the real tree yields a NON-EMPTY observer population that is a strict subset of its test files",
        "node scripts/check-system-context-census.mjs :: exit 0 :: check-system-context-census: OK — 123 elevation read sites in 20 packages across 57 files, living in 105 symbol(s); the page cites 118 sym",
        "node scripts/check-system-context-census.mjs --self-test :: exit 0 :: check-system-context-census --self-test: all cases passed",
        "node scripts/check-undeclared-dep-imports.mjs :: exit 0 :: ✓ check:undeclared-dep-imports: 81 workspace packages under packages/** + apps/** + examples/**, 3026 non-test src files, 2510 @objectstac",
        "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0 :: PASS  check-undeclared-dep-imports --self-test (0 failure(s))",
        "node scripts/docs-audit/check-affected-docs.mjs :: exit 0 :: ✓ affected-docs self-test: 605 cases pass.",
        "node scripts/docs-audit/check-drift-comment.mjs :: exit 0 :: ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).",
        "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 :: ✓ self-test passed",
        "node scripts/release-pending-publish.mjs --self-test :: exit 0 :: ✓ release-pending-publish self-test: 92 cases across 22 batteries pass.",
        "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 :: ✓ check:duration-unit-keys — 197 unit-declaring numeric key(s) across 3030 source file(s) all carry their unit in the key name (or in a ",
        "pnpm check:changeset-gate-self-tests :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption s",
        "pnpm check:cross-package-test-inputs :: exit 0 :: OK: 30 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split \"test:repo\" task); ",
        "pnpm check:dispatcher-error-vocabulary :: exit 0 :: check-dispatcher-error-vocabulary: OK — 54 unregistered code-stamping site(s), all classified; 2 awaiting a ledger entry (#8846).",
        "pnpm check:doc-authoring :: exit 0 :: ✓ doc authoring guard: sibling-package prose ids hold the baseline — 0 pinned site(s) across 0 file(s), 91824 string(s) read in 1314 par",
        "pnpm check:driver-memory-census :: exit 0 :: check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states \"#6664 census: 2 r",
        "pnpm check:dts-closure :: exit 0 :: check-dts-closure self-test: all assertions passed.",
        "pnpm check:dual-build-cjs-loads :: exit 3 :: PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/.",
        "pnpm check:engine-double-contract :: exit 0 :: check-engine-double-contract: OK — 993 pinned, 125 in the DEBT ledger, 3 exempt.",
        "pnpm check:error-status-conformance :: exit 0 :: ✓ every derivable runtime status is documented, and every documented status is reachable.",
        "pnpm check:gitlink-declared :: exit 0 :: check-gitlink-declared: OK (10931 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing",
        "pnpm check:issue-citations :: exit 0 :: ✅ check-issue-citations --self-test: grammar narrowed, every spelling enumerated, qualifier a closed set of repositories, four 404 causes ",
        "pnpm check:kernel-hook-pairs :: exit 0 :: ✓ kernel hook pin pairing: 4 dispatched kernel:* hook(s), each pinned in both kernel.test.ts and lite-kernel.test.ts",
        "pnpm check:lean-entry-closure :: exit 0 :: ✓ check-lean-entry-closure: 2 published condition(s) measured from a real load.",
        "pnpm check:logger-receiver-detach :: exit 0 :: OK  every log channel keeps its receiver: 3316 non-test TS file(s) walked, 0 detach(es) on the 5 declared receiver-sensitive sink spelling(s",
        "pnpm check:nul-bytes :: exit 0 :: check-nul-bytes: OK (scanned 10922 text file(s) -- 10922 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).",
        "pnpm check:objectql-double-limit :: exit 0 :: OK  ObjectQL double `limit` conformance holds: 475 double(s) graded, 281 apply the caller's bound or refuse it loudly.",
        "pnpm check:objectui-changeset :: exit 0 :: ✓ objectui-range --self-test: all checks passed",
        "pnpm check:org-identifier :: exit 0 :: check-org-identifier: OK (3338 author-facing source file(s), 18 session binding(s) resolved, no removed session.tenantId alias).",
        "pnpm check:page-declaration-shape :: exit 0 :: check-page-declaration-shape: OK — 36 page entries across 3339 sources under packages/**, examples/**, apps/** all reach the kernel throug",
        "pnpm check:pm-changeset-deadline-census :: exit 0 :: ✓ changeset-deadline-census --self-test: all cases passed across 5 batteries (what counts as a named target, the controls that make a zero",
        "pnpm check:published-files :: exit 0 :: ✓ check:published-files — 70 publishable package(s) of 81 workspace member(s) declare a `files` whitelist that covers every entry point ",
        "pnpm check:query-options-erasure :: exit 0 :: ✓ query-options-erasure ratchet holds: 65 unswept non-test site(s) in 17 file(s), none new, and every file measured parsed. Every other no",
        "pnpm check:refd-timer-probe :: exit 0 :: OK  check-refd-timer-probe: 8791 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts",
        "pnpm check:slot-lookup :: exit 0 :: ✓ slot-lookup ratchet holds: 104 unswept site(s) in 25 file(s), none new, and every file in the population parsed. Every other file under ",
        "pnpm check:sourcemap-no-sources-content :: exit 0 :: check-sourcemap-no-sources-content self-test: all assertions passed.",
        "pnpm check:test-source-alias :: exit 0 :: check-test-source-alias OK — 73 packages with tests scanned; 60 registered as still resolving a workspace dep through `dist/`; 53 publishe",
        "pnpm check:tier-file-adoption :: exit 0 :: OK: 81 workspace package(s) walked, 82 nightly-tier test file(s) on disk (82 e2e, 0 live), owned by 1 package(s); every one reads OS_TEST_TI",
        "pnpm check:type-check-coverage :: exit 0 :: check-type-check-coverage: OK — 79/81 workspace packages type-checked (plus the root), 1 in the DEBT ledger (26 frozen raw errors, https:/",
        "pnpm check:type-check-debt :: exit 0 :: check-type-check-coverage: OK — 79/81 workspace packages type-checked (plus the root), 1 in the DEBT ledger (26 frozen raw errors, https:/",
        "pnpm check:watch-hint-literal :: exit 0 :: ✓ check-watch-hint-literal: 72 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 48, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_H",
        "pnpm check:where-matcher :: exit 0 :: ✓ where-matcher conformance holds: 498 matcher(s) discovered, 498 answer the combinator battery correctly or refuse it loudly (323 refuse)"
      ],
      "gates_reconciliation": "node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack -> 'Run reconciliation — 65 derived, 64 run, 1 NOT-MEASURED, 0 UNRUN.' exit 0. Derivation without paths at 31b25500d1 added 2 families to the dispatch list: node scripts/check-dev-prereqs.mjs --self-test and pnpm check:kernel-hook-pairs (both run, exit 0). NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: exit 3 PREREQUISITE NOT MET, it reads every package's built output and 44 packages had no dist/ (a whole-workspace build was not run on the shared box); declared narrow substitute in tests.",
      "line_budget": "PR additions 84 + deletions 14 = 98 (3 files), under the 3000 human-merge line",
      "files_changed": [
        ".changeset/22868-sandbox-fault-not-unknown-object.md (+13/-0)",
        "packages/core/src/utils/import-runner-sandbox-fault-row.test.ts (+43/-9)",
        "packages/types/src/data-error-classification.ts (+28/-5)"
      ],
      "labels": "0 label writes: the dispatch named no label and skip-changeset does not apply (a @objectstack/types patch changeset ships)",
      "deviations": [
        "Enumeration file: the decision named 'the ledger #22764 rides'; the PM's assumption 3 read it as packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts. Measured: adding the two texts to its §6 SANDBOX_FAULTS reds 23 hand-built rows that relay the runner text (a separate packages/rest defect, outside the claim's surface), so the texts were pinned in the producer enumeration packages/core/src/utils/import-runner-sandbox-fault-row.test.ts, which renders every own-fault site from quickjs-runner.ts and asserts mapDataError and the import row. The claim's file surface allows 'the family file the dev measures as the real enumeration'. The ledger file is unchanged.",
        "No new test file beside data-error-classification.ts in packages/types: the decision forbids a separate list, and the core enumeration reads the types source through its vitest alias.",
        "pnpm check:dual-build-cjs-loads NOT MEASURED (exit 3, prerequisite), narrow substitute declared.",
        "Locked build/test runs and the gate battery ran under nohup and were waited on with a foreground tail --pid (the 10-minute foreground cap), each exit code read from its own log before any pipe.",
        "Stray file: a shell-variable slip wrote /build1.pid (6 bytes, a pid number) at the filesystem root, outside every repo; the safety check refused its removal, so it remains for a human to delete with rm /build1.pid.",
        "Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude) rather than the harness reminder's model-named Co-Authored-By; the PR footer uses AGENTS.md's session-URL form rather than the reminder's."
      ],
      "mcp_calls": "0",
      "api_writes": "2 relay strokes via scripts/pm (fleet-write, as objectstack-fleet[bot]): (1) pr_create: POST /repos/objectstack-ai/objectstack/dispatches -> executed POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/22882/assignees ['os-steve'], read back 8539 bytes identical, assignee os-steve; (2) this os-dev-report comment: post-stamped -> POST /repos/objectstack-ai/objectstack/issues/22868/comments. Plus 3 git pushes (not REST). 0 label writes.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · reach: public door + wrong answer, measured through the real handlers (RestServer.registerRoutes + mountAndRecordDirectRoutes, the write-route ledger harness) at 31b25500d1, not at a booted door; named producer: the QuickJS runner's own faults (quickjs-runner.ts single-argument new SandboxError and throwSandboxFault sites) · evidence: 23 hand-built REST write routes answer the sandbox's own fault with the runner's diagnostic text verbatim where POST /api/v1/data/:object answers 500 INTERNAL_ERROR 'Internal server error': POST /analytics/dataset/query, POST /security/explain, POST and DELETE /data/:object/:id/shares, POST /sharing/rules, DELETE /sharing/rules/:idOrName, POST /sharing/rules/:idOrName/evaluate, POST /security/suggested-bindings/:id/confirm and /dismiss, the nine POST /approvals/requests/:id/* writes, POST /packages/publish, and the four POST /datasources/:name/external/* routes. Probe: add two own-fault shapes (name SandboxError, no innerMessage, message capability 'api.read' not granted to hook 'mz_guard' (called ctx.api.object('mz_locked').find) / ctx.api.object('mz_locked').find not implemented) to §6 SANDBOX_FAULTS of packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts -> 46 failed / 229 passed, e.g. Expected 'Internal server error', Received the capability text. Status and code of those rows not read (the sentence assertion fails first). Mechanism by reading: sandboxFaultAnswer (packages/rest/src/error-response.ts) returns undefined unless isSandboxOrigin (innerMessage set), so an own fault falls to each family's own terminal that relays .message; the record-share rows go through their own fault terminal the same way. By construction this covers every own-fault kind (budget, capability, missing method, install/marshal), not only the object-naming texts; only the two object-naming texts were measured. Family: sandbox fault text (#22588, #22694, #22719, #22764, #22741), the REST write-route side whose card #22764 is closed · dedupe words: sandboxFaultAnswer, isSandboxOwnFault, own fault, capability not granted, hand-built write routes"
      ]
    }
  4. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #22882 at 31b25500d1. It lands when every check on the head is green

    domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-11T19:05Z · holder of claim 6112170533. Report: os-dev-report 6112555965. Thread-read: 6112555965.

    Checklist, read against GitHub:

    • PR shape: draft, base main, line 1 Fixes #22868, line 2 Clause-②: no, assignee os-steve. The body carries no other closing keyword (1 match in a full-body scan).
    • Scope: 3 files, +84 / −14 (98 lines), merge base 51f36e7750: data-error-classification.ts, packages/core/src/utils/import-runner-sandbox-fault-row.test.ts and the changeset.
      • The core test file is a domain:engine test-only file. The dev measured it as the family's real enumeration for mapDataError. The claim's surface allowed "the family file the dev measures as the real enumeration", and the claim declares the file by edit (6112170533).
      • No open PR other than this one touches it. No packages/runtime, no quickjs-runner.ts, no packages/spec, no governed path.
    • Diff read: one statement, if (isSandboxOwnFault(error)) return UNCLASSIFIED_FAULT();, at the top of classifyDataError beside the isSandboxCrash terminal, plus its comment and isSandboxOwnFault's docblock.
      • The predicate keys on the producer's identity, never the text.
      • The dev measured that the missing-relation, unknown-object, missing-column and leak limbs all misread own faults whose text the runner interpolates from the body. So the gate sits at the top, not beside the one substring limb, and only misread answers move.
    • Changeset .changeset/22868-sandbox-fault-not-unknown-object.md: @objectstack/types patch, Clause-②: no. Sentences checked against the diff:
      • the two runner texts;
      • 404 OBJECT_NOT_FOUND → 500 INTERNAL_ERROR with Internal server error;
      • "the check reads the error's producer, not its words";
      • a driver's unknown-object text still answers 404.
    • Pins:
      • the producer enumeration now samples the requested object, with a census floor and two CONTROL pins;
      • reverse verification (the gate removed) goes red on exactly the two object-naming sites, expected 404 to be 500;
      • the restore is proven against HEAD;
      • per the report, 21 packages/rest files (938 tests) are green against a rebuilt types dist.
    • Gates: 65 derived families. 64 exit 0, and check:dual-build-cjs-loads is NOT MEASURED (exit 3, prerequisite: 44 packages had no dist). That gate is declared to CI with a narrow substitute: the types dist loads through both require and import and answers 500. Lint is narrowed with the three-part proof over 2 files. The public surface does not move: the only code line is inside the non-exported classifyDataError.
    • No contract-review face: Clause-②: no, no packages/spec, no governed path.
    • CI on 31b25500d1 at this stamp: 13 success, 3 skipped, 15 in progress. It is not green yet.
    • Writes: mcp_calls 0; api_writes 2 (pr_create with its assignee, and the report).

    Out of scope:


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22882 → 8bc78368ac (Fixes #22868)

    domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-11T19:55Z · holder of claim 6112170533, released by this landing.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:clipriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions