Repository navigation
[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
Copy link
Copy link
Closed
Labels
area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingSomething isn't workingdomain:clipriority:p3
Description
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 noinnerMessage(the runner's declared contract for its internal faults). Two of them name the object the body addressed:- the capability gate, through
throwSandboxFaultinpackages/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".
- the capability gate, through
classifyDataError's unknown-object limb answers 404OBJECT_NOT_FOUND, "Object 'OBJ' is not registered", for any message that holds the quoted requested object name AND the substringnot(looksLikeUnknownObject, read onorigin/maineed637c09f).- 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
QuickJSScriptRunnerateed637c09finto the realmapDataError(err, OBJ), the classificationPOST /api/v1/data/:objectserves. 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_ERRORatmapDataError, and a real unknown-object driver text still answers 404 (control).
Duplicate check
- Population: 5,122 issues, closed included (5,043 closed), [finding] Both documented list channels can be down AT ONCE — search_issues zeroed session-wide while the prescribed REST fallback answers 403, contradicting open finding #11577 #11835 to [Decision] 用户页「分配岗位」「授予权限集」两个选择器改读注册表:加规范字段(A/A')、去掉用户页入口(B1),还是给授权的名称字段配注册表选择控件(B2) #22865 (REST list of issues updated since 2026-08-15, newest 99 pages), local grep over titles and bodies; comments are outside this instrument's radius.
- Queries:
looksLikeUnknownObject0;throwSandboxFault0;unknown-object0;capability.*not granted1 (security(plugin-audit): the compliance ledger's create/update rows serve a withheld field's stored value in their before/after snapshots to a reader whose sets grant the ledger read, while the data plane serves that reader without the key #21155, closed, a plugin-audit snapshot leak, different);classifyDataError3, all closed ([finding] A sandboxed crash carrying a declared 4xx still answers with that status and the QuickJS wrapper — the faces outsidemapDataErrorthat #15071 did not converge #17273, Two more 4xx exits still ship the ADR-0111CODE:prefix in the human-readable text —resolveErrorResponse's passthrough, which the #12975 ruling did not reach #13095, The /data door ships the ADR-0111CODE:prefix inside the user-facingerrorstring, so a localized refusal renders asFORBIDDEN: …in a toast #12975, none about this limb);OBJECT_NOT_FOUND10 andis not registered15, all closed, none about a sandbox fault. - Control:
mapDataError10, including the open [finding] import: a sandbox's own fault (CPU budget, wall-clock ceiling) reaches an import row as its debug wrapper, where the data doors answerInternal server error#22741, so the instrument reaches this family.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingSomething isn't workingdomain:clipriority:p3
Activity
objectstack-fleet commentedon Oct 11, 2026
Triage: first grade,
bug·priority:p3·domain:cli·area:api, intopm:queueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T16:52Z. ⛔ Not a claim, ⛔ not a dispatch.classifyDataError(looksLikeUnknownObject) inpackages/types/src/data-error-classification.tsisdomain:cliby the lane table. The area isarea:api, as for [finding] rest: the 20 write routes #22719 repaired still answer a crashed hook body with its native fault text, and a declared-5xx refusal unwithheld, where the data doors answerInternal server error#22764.ctx.apimethod, reads as "Object 'X' is not registered" (404) when the hook addressed the request's own object. Nothing is wrongly granted or lost; the answer misnames the fault. That is the grade of the family's REST side ([finding] rest: the 20 write routes #22719 repaired still answer a crashed hook body with its native fault text, and a declared-5xx refusal unwithheld, where the data doors answerInternal server error#22764).500 INTERNAL_ERROR) before the unknown-object substring limb runs. Recognise it with PR fix(core): an import row for the sandbox's own fault reads the door's fault sentence (#22741) #22867'sisSandboxOwnFault, off the producer. ⛔ No second rule, and ⛔ no new wording test.?top=0answerstotal: 0, andsecurity/explainallows updates the write door refuses #22588, [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, [finding] rest: 20 REST write routes answer a sandboxed hook's refusal as the debug wrapper, because their hand-built error arms relay.message(approvals ×9, sharing rules ×3, security ×3, packages/publish, external datasources ×4) #22719, [finding] rest: the 20 write routes #22719 repaired still answer a crashed hook body with its native fault text, and a declared-5xx refusal unwithheld, where the data doors answerInternal server error#22764, [finding] import: a sandbox's own fault (CPU budget, wall-clock ceiling) reaches an import row as its debug wrapper, where the data doors answerInternal server error#22741). Its enumeration pin is the ledger [finding] rest: the 20 write routes #22719 repaired still answer a crashed hook body with its native fault text, and a declared-5xx refusal unwithheld, where the data doors answerInternal server error#22764 rides. This member is the classifier's substring limb, so add its two runner fault texts to that enumeration rather than a separate list.500atmapDataError;404.Internal server error#22741) merges, which introducesisSandboxOwnFaultin the same file. This is ordering, not a block.Clause-②: no. It corrects a status to what the producer's contract declares.objectstack-fleet commentedon Oct 11, 2026
Claim: PM loop round 1 · 2026-10-11T18:18Z
Session:
session_01VoSxBQujKLZKPwK2u5ehQ6Account:
os-steve(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-22868-sandbox-fault-not-unknown-objectWorktree:
objectstack-issue-22868Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)File surface (read on
origin/mainf9ebb7dd31; stop on a breach and explain it in the report):packages/types/src/data-error-classification.ts:classifyDataErroranswers 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) as the fault it is, before thelooksLikeUnknownObjectsubstring limb runs. Its tests beside it.Internal server error#22764 rides (packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts, or the family file the dev measures as the real enumeration), gains the two runner fault texts.packages/core/src/utils/import-runner-sandbox-fault-row.test.ts(adomain:enginetest file, test-only), the producer enumeration that renders every own-fault site ofquickjs-runner.ts. The dev measured it as the family's real enumeration formapDataError; the REST ledger is unchanged (PR fix(types): the data door answers the sandbox's own fault as a fault, not as an unknown object (#22868) #22882)..changeset/22868-*.md(@objectstack/typespatch).packages/runtime/src/sandbox/quickjs-runner.tschange (the producer's texts are read, not edited), and nopackages/spec.Container & model:
S, judged asM(the classification's limb order must be measured on every runner fault site, not only the two named),mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). No contract-review face:Clause-②: no, nopackages/spec, no governed path.Clause-②: no
Responsibility:
classifyDataError's unknown-object substring limb (this lane, packages/types) reads a quoted requested-object name plus the word "not" as a missing object | PR #22867 (6d5191e697) already recognises the sandbox's own fault off the producer as isSandboxOwnFault, but classifyDataError does not ask it before that limb | a hook author whose sandboxed body is denied a capability, or calls a ctx.api method that is not implemented, on the request's own object: POST /api/v1/data/:object answers 404 OBJECT_NOT_FOUND "Object 'X' is not registered" instead of the 500 the producer's contract declares (measured by #22741's dev at mapDataError)Thread-read: 6111381095
Serial constraints cleared: PR fix(core): an import row for the sandbox's own fault reads the door's fault sentence (#22741) #22867 ([finding] import: a sandbox's own fault (CPU budget, wall-clock ceiling) reaches an import row as its debug wrapper, where the data doors answer
Internal server error#22741), the triage's ordering, merged as6d5191e697. No open PR touchesdata-error-classification.ts, the refusal-sentence ledgers orquickjs-runner.ts(all 8 open PRs' file lists read in this act). None of the openpm:dispatchedclaims declares them. [finding] rest: the 20 write routes #22719 repaired still answer a crashed hook body with its native fault text, and a declared-5xx refusal unwithheld, where the data doors answerInternal server error#22764, the family's REST side, is closed.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
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" ] }objectstack-fleet commentedon Oct 11, 2026
✅ ACCEPT: PR #22882 at
31b25500d1. It lands when every check on the head is greendomain:cliseat 2 (#22648) ·os-steve· sessionsession_01VoSxBQujKLZKPwK2u5ehQ6· 2026-10-11T19:05Z · holder of claim6112170533. Report:os-dev-report6112555965. Thread-read: 6112555965.Checklist, read against GitHub:
main, line 1Fixes #22868, line 2Clause-②: no, assigneeos-steve. The body carries no other closing keyword (1 match in a full-body scan).51f36e7750:data-error-classification.ts,packages/core/src/utils/import-runner-sandbox-fault-row.test.tsand the changeset.domain:enginetest-only file. The dev measured it as the family's real enumeration formapDataError. The claim's surface allowed "the family file the dev measures as the real enumeration", and the claim declares the file by edit (6112170533).packages/runtime, noquickjs-runner.ts, nopackages/spec, no governed path.if (isSandboxOwnFault(error)) return UNCLASSIFIED_FAULT();, at the top ofclassifyDataErrorbeside theisSandboxCrashterminal, plus its comment andisSandboxOwnFault's docblock..changeset/22868-sandbox-fault-not-unknown-object.md:@objectstack/typespatch,Clause-②: no. Sentences checked against the diff:404 OBJECT_NOT_FOUND→500 INTERNAL_ERRORwithInternal server error;404.expected 404 to be 500;packages/restfiles (938 tests) are green against a rebuilttypesdist.check:dual-build-cjs-loadsis NOT MEASURED (exit 3, prerequisite: 44 packages had nodist). That gate is declared to CI with a narrow substitute: thetypesdist loads through bothrequireandimportand answers500. 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-exportedclassifyDataError.Clause-②: no, nopackages/spec, no governed path.31b25500d1at this stamp: 13 success, 3 skipped, 15 in progress. It is not green yet.mcp_calls0;api_writes2 (pr_createwith its assignee, and the report).Out of scope:
Internal server error#22885. 23 hand-built REST write routes relay the sandbox's own-fault text, becausesandboxFaultAnswerasks onlyisSandboxOrigin. It is class (a), with a named producer and a measurement through the real handlers. It meets the may-leak-data exception: the text names the hook, the object and the capability. The dev's probe was reverted, and the ledger is unchanged in this PR./build1.pid(6 bytes, a pid) is left at the container's filesystem root by a shell slip. The harness's safety check refused its removal from the dev and from this seat, so it is left for a person. No repository is touched.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
Landed: PR #22882 →
8bc78368ac(Fixes #22868)domain:cliseat 2 (#22648) ·os-steve· sessionsession_01VoSxBQujKLZKPwK2u5ehQ6· 2026-10-11T19:55Z · holder of claim6112170533, released by this landing.8bc78368ac, a squash with one parent.origin/maincarriesif (isSandboxOwnFault(error)) return UNCLASSIFIED_FAULT();inpackages/types/src/data-error-classification.ts. The card closedcompleted, andpm:dispatchedcomes off in this act.31b25500d1were success or roster skips. No contract-review face applied (Clause-②: no, nopackages/spec, no governed path).packages/types/src/data-error-classification.tsandpackages/core/src/utils/import-runner-sandbox-fault-row.test.ts.Internal server error#22885, filed from this card's report. With this PR landed, the data door now answers every own fault500 INTERNAL_ERROR, so [finding] rest: 23 hand-built write routes answer the sandbox's own fault (a denied capability, a missing ctx.api method, a budget) with the runner's diagnostic text, where the data door answersInternal server error#22885's comparison baseline holds onmain.Generated by Claude Code