Repository navigation
[finding] [security] An in-process engine verb passes an object name the registry does not resolve to the driver as a raw table name, so a sandboxed body reads a protected table by a name the data door refuses #21516
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Oct 3, 2026 objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·security·priority:p1·domain:engine·area:access·pm:queue. An in-process verb refuses a name the registry does not resolve, as the door doesTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T02:08Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes, positions and functions only.Why p1. It defeats every name-keyed in-process guard, including the p1 family seam that #21454's PR #21513 adds. Reach is measured at a public door with a member invoking.
Routing:
packages/objectql/src/engine.ts,resolveObjectName, sodomain:engine.Ruling: one name space for the in-process verbs and the door.
- An in-process engine verb resolves its target only through the registry. A name the registry does not resolve is refused with the data door's own not-found. ⛔ Never treat it as a raw table name.
- A census comes first. The claim enumerates every platform-internal reader that addresses a table by a name the registry does not hold. Each one moves to a declared internal path that a body cannot reach. ⛔ No allow-list keyed by spelling.
- If the census finds a legitimate internal reader that cannot move, the claim stops and reports.
Pins:
- the measured exit, which now answers not-found for a member and an administrator;
- a registered name as the control;
- the [finding] [security] An action/automation body's object API and an action handler's engine handle read the stored-metadata family outside its body projection and keyed serve (reach NOT MEASURED) #21454 seam's pins stay green.
findingcomes off in this act.
Generated by Claude Code
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardspriority:p1High: required for production / M2High: required for production / M2and removed
on Oct 3, 2026 objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsClaim: PM loop round 26 · 2026-10-03T03:09Z
Session:session_01DDZNkDVwPQnevTFcYE47H3
Account:os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21516-unresolved-name-refusal
Worktree:objectstack-issue-21516
Domain:domain:engine
Seat:domain:engine#1(seat post #6367)
File surface: census first, ⛔ no refusal before it exists (triage 5964437602).- The census: every platform-internal reader that hands an in-process engine verb a name the registry does not resolve (the
resolveTableName({ name })fall-through inresolveObjectName,packages/objectql/src/engine.tsabout:9207atorigin/mainad7c351898), read across the repository and measured where a read is ambiguous. The PR records the census. - Then the engine:
resolveObjectNameand its in-process callers inpackages/objectql/src/engine.tsrefuse an unresolved name with the data door's ownOBJECT_NOT_FOUND. ⛔ Never treat it as a raw table name. ⛔ No allow-list keyed by spelling. - Each census reader moves to a declared internal path a body cannot reach. Its call site joins this surface, named in the report. If a reader is in an open PR's file list, or cannot move, stop and report.
- The pins' test files in
packages/objectql, plus the measured public-door exit.
Revision 2 (after the seat's answer 5965797270, Q1 A with three corrections):
- The factory:
objectNotFoundErrorlives in@objectstack/core(src/utils/object-not-found.ts, exported from its entry), and both doors build the refusal from it. - The census readers moved:
metadata-protocol:seed-loader.tsresolveSoleOrganizationId, andsys-metadata-repository.tsnextItemVersion/nextEventSeq;protocol.ts: only the data door's existence gate, which now builds from the factory.
- The spec docblock: the
IObjectQLEnginedocblock inpackages/spec/src/contracts/objectql-engine.ts, comment only. - The test migration: test files only, in
objectql,runtime, and any package whose suite measures fallout (cli,services/**,plugins/**,rest), each named in the PR. - Stop rules:
- ⛔ If a PRODUCTION reader anywhere relies on the fall-through, stop and report.
- ⛔ No ADR text is edited.
- ⛔ No finding: after the #7598 Q1=B ruling, a read scope with a driver-refused field reference answers 400 from the driver — which cuts across #5367's attribution argument on that one path #7929-ruled pin changes what it asserts.
⛔ Not PR #21513's reader seam (#21454, landed at
abe8f289e8): its pins stay green and its code is not edited. Stop on breach; explain in the report.
Container & model:M(a census with a conditional fix),mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate forengine.ts, floor sonnet · default opus · ceiling fable)
Clause-②: yes (narrowing)
Thread-read: 5964437602
Serial constraints cleared: read at 2026-10-03T03:09Z againstorigin/mainad7c351898. No open PR's file list carriespackages/objectql/src/engine.ts. No other card of this lane holdsresolveObjectName's region; #21470 (in flight) is inmetadataandmetadata-protocol.Clause-②: yes (narrowing)(revision 2): in-process verbs refuse names they accepted, and@objectstack/coregains theobjectNotFoundErrorexport. The at-tier contract review is owed before ACCEPT (the spec docblock, andClause-② yes).
Generated by Claude Code
- The census: every platform-internal reader that hands an in-process engine verb a name the registry does not resolve (the
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 21516, "status": "needs_decision", "branch": "claude/issue-21516-unresolved-name-refusal", "pr": "https://github.com/objectstack-ai/objectstack/pull/21545", "session": "session_01DDZNkDVwPQnevTFcYE47H3 (the dispatching domain:engine seat; this run is its subagent)", "premise_still_valid": true, "summary": "Engine fix implemented and demonstrated: an in-process engine verb now refuses a target name the schema registry does not resolve with the data door's own OBJECT_NOT_FOUND (404) via one shared factory in @objectstack/core (objectNotFoundError), instead of handing the name to the driver as a raw table name. The data door's existence gate raises the same factory. One name space for both doors, no spelling allow-list, no per-caller marker. Measured at the engine: a registered name serves (control); an unregistered spelling that resolves to a protected family table is refused OBJECT_NOT_FOUND/404 (was served before). CENSUS RESULT: no production or example reader relies on the raw-table fall-through (4 example apps booted+seeded+driven through the doors: 0 fall-through events). The only in-repo callers that touch it are three constant-name best-effort probes already fail-soft for a missing table (now also recognising the refusal) plus unit/integration test harnesses relying on tolerant stub drivers. NEEDS_DECISION on completion scope: the uniform engine refusal changes the org probe's behaviour for an UNREGISTERED org object from reach-the-driver-fail-soft to refused-before-the-driver, which ripples into a documented multi-file noise discipline and two recorded decisions. #21513's reader-seam pins stay green (23/23).", "census": { "question": "does any legitimate platform reader rely on the raw-table fall-through?", "tree": "origin/main ad7c3518983a1bb63fd4601954ac92d055124e42", "instrument": "temporary probe on resolveObjectName's fall-through, committed then reverted on this branch", "measured_boots": "example apps crm, showcase, todo, multi-package \u2014 booted with seeds and driven through discovery/meta/data/actions doors; 0 fall-through events each", "measured_suites": "objectql (366 files, instrumented), metadata-protocol (208 files), runtime (311 files, both shards)", "answer": "No production or example reader relies on the fall-through. Readers that touch it by function:", "readers": [ "ObjectQL.probeInstallOrganizations (packages/objectql/src/engine.ts) \u2014 reads the org object on a system-context tenant-scoped write; already fail-soft on a missing table; MOVED: registry-presence guard returns empty when the org object is unregistered (a path a body cannot reach)", "SeedLoaderService.resolveSoleOrganizationId (packages/metadata-protocol/src/seed-loader.ts:1615) \u2014 reads the org object to stamp seed rows; already fail-soft; MOVED: recognises the refusal attributed to the org object as the not-provisioned case", "SysMetadataRepository.nextItemVersion/nextEventSeq (packages/metadata-protocol/src/sys-metadata-repository.ts) \u2014 history lineage counters; already fail-soft; MOVED: recognise the refusal attributed to the history object as the not-provisioned case (counter 1)", "ObjectQL.cascadeDeleteRelations / planCascadeAtomicity / referenceExists (engine.ts) \u2014 already wrap resolveObjectName in try/catch; UNCHANGED (already tolerate a throw)", "the data door existence gate assertObjectRegistered (protocol.ts) \u2014 now raises the shared objectNotFoundError (same wire, same code)" ] }, "declared_internal_path": "the driver itself \u2014 ObjectQL.datasource(name) / getDriverForObject(name), held by host code only; a sandboxed body's ctx.api (ScopedContext) exposes object()/sudo()/transaction() but no driver handle, so a body cannot address storage without a registry entry (engine.ts). No new path had to be invented.", "tests": "Builds (all through os-verify-lock, slot issue-21516-dev, VERDICT command-exit 0): objectql closure, cli+examples closures, runtime closure. GREEN with the fix: metadata-protocol full suite (205 files/3151 tests passed, 3 skipped); #21513 reader-seam pins stored-metadata-reader-contexts.pin.test.ts + stored-metadata-reader-seam.test.ts (2 files/23 tests). Engine demonstration (examples/embed-objectql, built dist): CONTROL registered name -> 1 row; BYPASS unregistered spelling resolving to the protected family table -> REFUSED code=OBJECT_NOT_FOUND status=404. Before/after of the bypass primitive is the #3770 pin protocol-unregistered-object.test.ts case B (asserts the engine SERVES the ghost row on origin/main; goes red against this change). nul-bytes gate OK; control-byte self-scan of the diff clean. NAMED-GAP RED (the completion decision): objectql ~28 test files and runtime ~12 noise-pin files assert the old raw-table fall-through and must be migrated (assert the refusal, or register the full stored-metadata family the repository requires); objectql shards after the reader guards: 43+29+27 failures across those files, down from 533+443+463 before the guards. Gates NOT fully reconciled pending the completion decision (the test families would be red); dispatch-gates --commands derived 80 families at 5e876116af; only nul-bytes run to green. No ablation commit-revert run; the forward behaviour change is directly measured by the suite deltas and the #3770 pin flip.", "mcp_calls": "0", "api_writes": "3 relay dispatches via scripts/pm/ (objectstack-fleet[bot]): pr_create (draft #21545), assign (label-write, PR assignee os-elon-musk), and this os-dev-report comment (post-stamped --comment=21516). git push (branch) is not a REST write.", "open_questions": [ { "question": "Completion scope for the ruled engine refusal: how far to carry the implied test migration, which reshapes a documented noise discipline and narrows two recorded decisions. The engine fix and the three reader moves are done and correct; the remaining work is test pins encoding the old fall-through.", "options": [ "A \u2014 land the engine refusal and migrate the full test surface: objectql ~28 files (assert the refusal for deliberate unregistered-object probes; register the full stored-metadata family in metadata-write harnesses) and runtime ~12 files (reshape the #12852/#7929 org-probe noise discipline, which asserts the probe reaches the driver for an unregistered org object). Narrows recorded decision #3770 (the engine deliberately does not reject; internal callers unaffected) and ADR-0053 type-blind lowering reachability (preserved for a registered field-less object; removed for an unregistered name = the bypass). Scope beyond objectql/metadata-protocol/runtime (cli/services/plugins) is unmeasured.", "B \u2014 keep the engine refusal plus the three reader moves (as pushed) and treat the noise-pin reshape + #3770 pin as a separate, seat-reviewed change, since reshaping those recorded disciplines under a p1-security no-widening posture is a governance-adjacent judgement.", "C \u2014 a narrower threat-aligned fix was considered and REJECTED by the ruling: refuse only on the body-reachable path or allow-list the protected spellings. The ruling forbids both (one name space; no allow-list keyed by spelling), and #21513's analysis found the engine cannot tell an elevated body from an internal reader without a new context marker." ], "recommendation": "A, by the four axes. (1) Real business need: measured zero pull on the fall-through \u2014 4 example apps booted produced 0 fall-through events, so no real deployment reads a table by a name the registry does not hold; the capability exists only as a bypass and in test harnesses. (2) Long-term soundness: one name space for the in-process verbs and the door is contract-first and removes a whole class of name-keyed-guard bypass, not just this card's; a per-caller marker (option C) would be a new contract surface the ruling declined. (3) AI-proofing: a loud refusal at the resolver beats a raw-table read an author cannot see is unguarded \u2014 the fix tightens the contract rather than tolerating off-spec names at a consumer. (4) Startup focus: no new capability and no new error code; the refusal is the data door's existing one. The scope caveat is governance, not direction: the migration touches recorded disciplines #3770/ADR-0053/#12852, so the seat should confirm option A (and the unmeasured cli/services/plugins surface) before the full pin migration lands. The engine change itself is ready." } ], "out_of_scope_findings": [ "class: b \u00b7 reach: named producer \u2014 SysMetadataRepository's history append (engine.insert into the history object, packages/metadata-protocol/src/sys-metadata-repository.ts) and other metadata-write paths require the full stored-metadata family to be registered; in production the family is registered together, so this is not a production defect, but partial unit harnesses relied on the raw-table fall-through for the append. Fold into this card's completion (option A), not a single-point card. \u00b7 carrier: this PR's completion \u00b7 noted, not filed", "carrier: this card's PR #21545 completion (domain:engine seat) \u00b7 the noise-discipline reshape (#12852/#7929) and the #3770 pin update \u00b7 noted, not filed" ] }objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsSeat answer to os-dev report 5965771442 (
needs_decision): Q1 A, with three correctionsdomain:engine#1·session_01DDZNkDVwPQnevTFcYE47H3· 2026-10-03T05:05Z. The seat re-read the following:- PR fix(objectql): an in-process engine verb refuses an object name the registry does not resolve (#21516) #21545 at
5e876116af: 7 files, +140/-41; - the case-B pin in
packages/objectql/src/protocol-unregistered-object.test.tsonorigin/main; seed-loader.ts's seed-loaderresolveSoleOrganizationIdswallows everysys_organizationread failure behind a comment naming one benign cause, so a transient outage silently seeds org-less rows #12852 block;- the three packages' declared dependencies;
- the new
@objectstack/coreexport, andrecordNotFoundError's precedent named in its header.
Why the seat answers this rather than escalating:
- Triage 5964437602 already ruled the product semantics: "an in-process engine verb resolves its target only through the registry … ⛔ Never treat it as a raw table name."
- What is left is how far this PR carries the test surface that encodes the old mechanism. That is verification scope, a class the seat answers.
- B cannot land: a PR whose own suites are red is not green, and a p1
securityfix would wait behind a chore. - C is ruled out by the ruling, as the report says.
- The maintainer can overrule; the card stays on this claim.
Governing text:
- triage 5964437602 (the ruling, the census-first clause, the three pins);
references/contract-review.md(which surfaces owe the at-tier review);.claude/skills/pm-dispatch/SKILL.md(four-axis framework, escalation classes).
What the "recorded decisions" are, read on
origin/main:- 曝露 gate 对未知对象放行所依赖的「data path 会 404」并不成立 —— findData 无存在性校验(#3545 同类前提) #3770's "the engine does not reject unregistered names": a description of the mechanism at that time, in a pin's header and in case B's last assertion ("the row really is reachable through the engine"). It is not a ruling. 曝露 gate 对未知对象放行所依赖的「data path 会 404」并不成立 —— findData 无存在性校验(#3545 同类前提) #3770's ruling is the data door's existence gate, which this PR keeps and now builds from the same factory.
- Case B's door assertion stays.
- Its engine assertion flips, by design, to the refusal, and the header's ② gets one sentence naming [finding] [security] An in-process engine verb passes an object name the registry does not resolve to the driver as a raw table name, so a sandboxed body reads a protected table by a name the data door refuses #21516.
- seed-loader
resolveSoleOrganizationIdswallows everysys_organizationread failure behind a comment naming one benign cause, so a transient outage silently seeds org-less rows #12852: the seed-loader rule that only the absence ofsys_organizationis benign, and every other read failure propagates. The PR keeps it.- A refusal attributed to
sys_organizationon the error's ownobjectis that same absence. - A refusal naming another object, and every driver fault, still throw.
- A refusal attributed to
- finding: after the #7598 Q1=B ruling, a read scope with a driver-refused field reference answers 400 from the driver — which cuts across #5367's attribution argument on that one path #7929: the maintainer ruling of 2026-08-12 (option B), that a cross-field refusal withholds its predicate. This PR does not touch what that ruling protects.
Answer: Q1 → A. Complete the migration in this PR.
- Test files that encode the raw-table fall-through: assert the refusal where the probe of an unregistered name is deliberate. Register the stored-metadata family a harness writes through where the fall-through was incidental.
- The runtime org-probe noise pins: reshape them to the new mechanism and keep each pin's subject. The probe stays quiet for an absent organization object, and a real fault still propagates.
- The unmeasured packages: measure
cli,services/**,plugins/**andrestby running their suites on the branch.- Test-only fallout there is fixture triage, in this PR, named in its body.
- ⛔ If a PRODUCTION reader in any package relies on the fall-through, stop and report: that is triage's stop clause.
- Stop rules:
- ⛔ No ADR text is edited. ADR-0053's type-blind lowering for a REGISTERED field-less object stays; if a sentence of any ADR becomes false, stop and report.
- ⛔ No pin under the finding: after the #7598 Q1=B ruling, a read scope with a driver-refused field reference answers 400 from the driver — which cuts across #5367's attribution argument on that one path #7929 ruling changes what it asserts about withholding.
Correction 1 —
Clause-②: the line readsClause-②: yes (narrowing), not the claim'sno (narrowing).-
@objectstack/core's entry gainsobjectNotFoundError, a new export on a publishedexportspath, so the surface widens. -
The accept set of the engine's in-process verbs narrows.
-
recordNotFoundErroris the precedent for the factory's home (ADR-0076 D2's boundary ratchet), so the home is right. -
Changesets:
@objectstack/core: minor,Clause-②: yes;@objectstack/objectql: minor, with the BREAKING banner,Clause-②: no (narrowing), and an ADR-0087 disposition;@objectstack/metadata-protocol: its arm per what its diff publishes;@objectstack/spec, for the contract docblock: as the repository's rules grade a docblock that reachesdist.
Each changeset is true sentence by sentence.
Correction 2 — at-tier contract review: owed before the seat's ACCEPT, on two surfaces:
- the diff touches
packages/spec/src/contracts/objectql-engine.ts, a non-test file; - the PR declares
Clause-② yes.
The seat runs it on the completed head.
Correction 3 — the closing line: when the migration is complete and the suites are green, the PR body's first line becomes
Fixes #21516.Part ofwas right for the head the report left.Pins, unchanged from triage:
- the measured exit answers not-found for a member and an administrator;
- a registered name is the control;
- the [finding] [security] An action/automation body's object API and an action handler's engine handle read the stored-metadata family outside its body projection and keyed serve (reach NOT MEASURED) #21454 seam's pins stay green.
The census table stays in the PR body as reported.
Out of scope: the report's two findings are this PR's completion. They are not filed.
Next: a completion round on this claim and branch (
claude/issue-21516-unresolved-name-refusal, PR #21545). The claim's file surface is revised to match this answer.
Generated by Claude Code
- PR fix(objectql): an in-process engine verb refuses an object name the registry does not resolve (#21516) #21545 at
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsPointer from
domain:cli#1(seat post #6024) ·session_016GiHYRmLSNWTfbX9gVQkpz· 2026-10-03T08:44Z · ⛔ Not a claim, ⛔ not a request for work. ⛔ Classes, doors and roles only.The write half now rides on this card too. PR #21563 (#21520, ruling A) refuses a sandboxed body's WRITE of the stored-metadata tables at the reader-context seam. Like the read seam (PR #21513 and PR #21539) and the generic data door, the refusal is keyed on the object NAME a body passes, judged by the family predicate.
The route this card carries applies to both halves the same way: a name the registry does not resolve reaches the driver as a raw table name. PR #21563's contract review of record (
5967280729, PASS) names it, and agrees ⛔ no name normalisation belongs in the seam, because the engine is the one authority for a name. When this card's fix lands, the seam's write refusal is covered by it, with no change on this lane's side.
Generated by Claude Code
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsACCEPT, pending the at-tier contract review — PR #21545 at head
6752a29827· handed over for review and landingdomain:engine#1·session_01DDZNkDVwPQnevTFcYE47H3· read at 2026-10-03T10:05Z. On the maintainer's order this seat goes off shift once the development is done; the at-tier contract review is the director's. ⛔ This is not the contract review, and the PR is not ready until one PASSes on its head.The seat's own read (the dev's completion-round report is not yet posted on this card; this read is of GitHub):
- Shape: draft, base
main,mergeable_stateclean.- The first body lines are
Fixes #21516andClause-②: yes (narrowing), the seat's answer 5965797270, corrections 1 and 3. The body was patched at 2026-10-03T09:18Z. - No other card number stands beside a closing keyword, and the
Part ofparagraph is gone. check-governed-merges.mjs --pr 21545: NOT governed; 1277 changed lines (+1065/-212) in 80 files.
- The first body lines are
- CI on this head: 36 success. 5 skipped, all roster skips: Build Docs, Console Pin Gate, Packed-tarball smoke, Auto Label, Check PR Size. One stale red: the
Part-of PR must not also close its cardrun from 2026-10-03T08:44Z, which re-ran at 2026-10-03T09:18Z after the body patch and issuccess. - The file surface: the claim's revision 2 (5964938685).
- The engine refusal is in
objectql. - The factory
objectNotFoundErroris incore(src/utils/object-not-found.tsand the entry). metadata-protocolholds the existence gate and the two readers.- The
specchange is theIObjectQLEngine.judgeFilterdocblock, comment only. - The test migration covers
objectql(26 files),runtime(19),rest(14),plugins/*,services/service-settings,triggers/trigger-record-change,qa/*andcli, all test files.
- The engine refusal is in
- The four changesets, checked sentence by sentence against the seat's answer:
core: minor,Clause-②: yes, the new exportobjectNotFoundError. Its envelope iscode,status,objectand the door's message, and nothing that existed changes.objectql: minor, with the BREAKING banner,Clause-②: no (narrowing)and ADR-0087 not-required. It covers the eight verbs, the refusal before any hook, middleware or driver,judgeFilterunchanged, the org probe asking the registry first, and the driver path for host code.metadata-protocol: patch,Clause-②: no. The gate's bytes are unchanged, and the two readers treat a refusal of their ownobjectas absence.spec: patch,Clause-②: no. The docblock ships indist/*.d.ts.
Owed before landing — the at-tier contract review (director):
- Why it is owed: the diff touches
packages/spec/src/contracts/objectql-engine.ts(non-test), and the PR declaresClause-② yes.needs:contract-reviewis hung on PR fix(objectql): an in-process engine verb refuses an object name the registry does not resolve (#21516) #21545 in this act. - What it must judge:
- the new
@objectstack/coreexport, with the precedentrecordNotFoundErrorand ADR-0076 D2; - the accept-set narrowing of the engine's in-process verbs, and the
OBJECT_NOT_FOUNDenvelope's byte identity at the data door; - the readers' absence rule (attributed on the error's own
object; every other failure propagates, seed-loaderresolveSoleOrganizationIdswallows everysys_organizationread failure behind a comment naming one benign cause, so a transient outage silently seeds org-less rows #12852); - the test migration's reach: the 曝露 gate 对未知对象放行所依赖的「data path 会 404」并不成立 —— findData 无存在性校验(#3545 同类前提) #3770 case-B pin's engine assertion flips, and no finding: after the #7598 Q1=B ruling, a read scope with a driver-refused field reference answers 400 from the driver — which cuts across #5367's attribution argument on that one path #7929-ruled pin changes what it asserts;
- each changeset's level and
Clause-②arm; - triage 5964437602's three pins (member and administrator refused at the measured exit, a registered name as the control, [finding] [security] An action/automation body's object API and an action handler's engine handle read the stored-metadata family outside its body projection and keyed serve (reach NOT MEASURED) #21454's seam pins green).
- the new
- Landing, after a PASS on the current head: relay
pr_ready, thenautomerge_enable, then confirmadded_to_merge_queue. After the merge, verify the squash onorigin/main, confirmFixes #21516closed the card, and removepm:dispatched. - Noted for the reviewer:
domain:cli's pointer 5967297021. PR fix(runtime)!: an app-authored body may not bind a hook to, or write, the stored-metadata tables (#21520) #21563's write-half refusal at the reader-context seam rides on this card's engine refusal, with no change on that lane's side.
Caretaker: the director seat (#12708), for the contract review and the landing. This seat's dev run on this branch,
mode:subagentundersession_01DDZNkDVwPQnevTFcYE47H3, may still post its completion-roundos-dev-reporthere. Its readings are the dev's; the head above is what is reviewed.
Generated by Claude Code
- Shape: draft, base
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsClaim: director landing round (merge-queue failure on PR #21545)
Session:session_016tKoy8NJa35Yih1FdzrVmn
Account:hotlong(the session's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21516-unresolved-name-refusal(PR #21545, head6752a29827; continued, not re-cut)
Worktree:objectstack-issue-21516
Domain:domain:engine
Seat: the director seat, summon #32. It is the caretaker named in the handover5968082941("the director seat (#12708), for the contract review and the landing"), and it acts on the maintainer's order in the live director chat, verbatim 「21545 契约复审,并跟进到合并」. The contract review is PASS (5969221098on PR #21545).
File surface: the merge-queue failure only.- Merge
origin/maininto the branch, as a merge commit: fix(cli): the rest of the read-only data doors answer a project with no database yet with empty work (#21552) #21570 (fixing [finding] The rest of the read-only data-command family exits 1 on a project with no database yet: migrate account-issuer, audit-metadata-bodies, meta --stored, secret orphans and rewrap, storage orphans read tables their boot deferred #21552) and fix(cli,runtime): os migrate resume, recorded-by and value-shapes answer a project with no database yet with empty work #21550 landed after the PR's base1ca1eb0972. - Reproduce
packages/cli/src/commands/migrate/data-commands.absent-database.integration.test.ts› "[[finding] The rest of the read-only data-command family exits 1 on a project with no database yet: migrate account-issuer, audit-metadata-bodies, meta --stored, secret orphans and rewrap, storage orphans read tables their boot deferred #21552] the rest of the family: a project with no database yet is empty work, exit 0". In merge-queue run 37123511365 it exits 1 where 0 is pinned. - Fix it at the root, in the read-only data-command family's absent-database handling, or wherever the engine's new
OBJECT_NOT_FOUNDmeets it. Keep fix(objectql): an in-process engine verb refuses an object name the registry does not resolve (#21516) #21545's absence rule: a refusal counts as absence only when it is attributed on the error's ownobject, and every other failure propagates (seed-loaderresolveSoleOrganizationIdswallows everysys_organizationread failure behind a comment naming one benign cause, so a transient outage silently seeds org-less rows #12852). - Pins, plus a changeset sentence if a published package's behaviour moves.
⛔ No weakened or skipped test. ⛔ No widening of the engine refusal.
Container & model:M,mode:subagent,model: opus
Clause-②: yes (narrowing), carried from the PR.
Thread-read: 5968082941
Serial constraints cleared: read 2026-10-03T13:06Z.domain:engine#1(session_01DDZNkDVwPQnevTFcYE47H3) handed the landing over in5968082941and is off shift.domain:cli's pointer5967297021(PR #21563) rides on this engine refusal and is unchanged.- Merge
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21516,
"status": "done",
"branch": "claude/issue-21516-unresolved-name-refusal",
"pr": "#21545",
"session": "session_016tKoy8NJa35Yih1FdzrVmn (subagent run under the director seat's session)",
"premise_still_valid": true,
"summary": "Merged origin/main (901e7cf, which carries #21570 for #21552) into the PR branch as merge commit b5cec3d. The merge was clean and left no pending regeneration. The three queue failures reproduced locally, and all three wereos migrate account-issuer: the absent-database--jsoncase, the human case, and the booted control. Each exited 1 with the document {"error":"Cannot enumerate sys_account: Object 'sys_account' not found. Refusing rather than reporting an unread table as clean.","code":"RESOURCE_CONFLICT"}. That is the engine's new OBJECT_NOT_FOUND forsys_account, which this command's read-only boot does not register because it composes no auth plugin, wrapped by the probe's own refusal. The fix is in packages/cli/src/commands/migrate/account-issuer.ts only (commits ffc0e5b and 2df18ea, head 2df18ea). The pre-flight now readssys_accountthrough the driver the engine routes that name to (getDriverForObject, the host-code path the objectql changeset prescribes) instead of the engine'sfind. #21570's mechanism for this table is kept unchanged: the driver's missing-table refusal, recognised with the sharedisMissingTableErrorforsys_accountonly, reads as no rows, and every other failure still becomes the probe's refusal. A missing driver is also a refusal, never a clean report. CHOICE, per the ruling zone: neither of the two named options fits this door. (1) 'Not asked' cannot apply: the boot cannot measure an unregistered table, sotableAbsent('sys_account')is false on every database. (2) Treating the attributed OBJECT_NOT_FOUND as absence would be wrong: in this composition the refusal says only that the boot registers nosys_account, and nothing about the database. Read as absence, it answers the booted control (2 rows) as 0 and reports a table full of accounts as 'Pre-flight clean', which authorises the destructiveissuerdrop. The command also inventories the physical table in its legacy shape:issueris the very column the registeredsys_accountschema no longer declares. So the read belongs on the driver, not on the engine's registry. The engine refusal and the readers' absence rule are untouched, and no test was edited, skipped or weakened. No changeset sentence was added: the cli's observable answers are byte-for-byte the ones main gives, as the #21552 pins (written against main) measure, and no other published package moved. PR body is not edited (dev writes it once). Suggested sentence for the seat to add: 'Merge-queue fix:os migrate account-issuerreadssys_accountthrough the driver the engine routes that name to, because its read-only boot registers nosys_accountand the engine now refuses an unregistered name; the refusal is not read as absence, which would report a table full of accounts as a clean pre-flight.' The worktree has been removed.",
"tests": "All runs are at head 2df18ea unless noted, and all heavy runs went through os-verify-lock. (1) REPRODUCTION at merge b5cec3d:pnpm exec vitest run --project integration --maxWorkers=2 src/commands/migrate/data-commands.absent-database.integration.test.tsin packages/cli -> 'Tests 3 failed | 33 passed (36)'. The failures were x 'migrate account-issuer --json: empty work, exit 0, no refused read of its own tables', x 'migrate account-issuer (human): exit 0 on the empty-work sentence' and x 'migrate account-issuer: the control, a booted database, is READ and its row reported'. A direct run,node bin/run-dev.js migrate account-issuer --json --database-url file:ABSENT, gave EXIT=1 with the OBJECT_NOT_FOUND-wrapped document quoted in summary. (2) AFTER: the same file gives 'Test Files 1 passed (1) / Tests 36 passed (36)', VERDICT command-exit 0. The direct run gives EXIT=0 with {scanned:0, keys:0, collisions:[], ok:true} and the stderr line 'sys_account has no table in this database yet ...'. (3) ABLATION, throughnode scripts/ablation-replace.mjsWRAP under the lock. Anchor 'return await driver.find(object, query);' x1 -> x0, replaced by a read through the engine'sfind(the pre-fix reader). Blob bfc270d279f1 -> def23a1578cf, and on-disk grep counts mutation 1 / anchor 0. Result: the same 3 account-issuer cases red, 'Tests 3 failed | 33 passed (36)', direction red as predicted. Restore: blob after restore bfc270d279f1 == HEAD blob,git diff HEADempty, independently re-read withgit status --porcelain(empty) andgit hash-object(bfc270d279f1...). The subject runs from src through tsx (bin/run-dev.js), so no dist leg applies. (4) cli unit tier:vitest run --project unitgave 251 passed plus 2 PREREQUISITE NOT MET (published-subpath-console and -hook-body pins need cli dist). Afterpnpm --filter @objectstack/cli buildthose 2 gave 'Test Files 2 passed / Tests 29 passed'. Unit total: 253/253 files, 3689 tests passed, 29 pre-existing skips. (5) cli typecheck:pnpm typecheck(tsc --noEmit plus check:test-typecheck OK), exit 0. (6) cli integration siblings: resume.recorded-by, preview-read-only, plan.deferred-reads, schema-migrate.one-shot-family (drives account-issuer), schema-migrate.readonly-probe, duplicates, meta.stored-flow-resolution gave 'Test Files 7 passed / Tests 101 passed | 1 skipped'. The skip is the pre-existing env-gated live-dialect cell. Main's new artifact-flag-precedence and package-install-local-uninstall-cleanups, plus schema-migrate.host-composition and test/exit-signal.pin (drives account-issuer with a fake engine), gave 'Test Files 4 passed / Tests 149 passed'. The nightly-tier e2e files json-stdout-purity (account-issuer member) and migrate-apply-refuses-before-ddl, run with OS_TEST_TIERS=nightly, gave 'Test Files 2 passed / Tests 57 passed'. (7) packages/objectql full: 'Test Files 367 passed (367) / Tests 7384 passed'. packages/metadata-protocol full: 'Test Files 207 passed | 3 skipped (210) / Tests 3192 passed | 19 skipped'. (8) Overlap with main's merged tests: cloud-connection install-local-uninstall cleanups and withdrawal (2 files / 14 tests); rest public-form intake, routes and withdrawal (4 / 62); runtime stored-metadata-body boundary, pin and writes (3 / 25); metadata-core anonymous-form-intake (1 / 13). All passed. (9) GATES:node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 98 commands over the PR's 81 paths, and all 98 were run sequentially with exit codes recorded.--ranreconciliation: '98 derived, 96 run, 2 NOT-MEASURED (DERIVED from a recorded exit 3)'. 95 exited 0. NOT MEASURED: check:dual-build-cjs-loads, reason PREREQUISITE NOT MET (dist absent for studio, client-react and 7 other packages outside the cli closure). NOT MEASURED: check:i18n-coverage, reason partial round, exit 3. NOT MEASURED: check-engine-split-ratio --days 90, exit 2, reason the shallow clone's oldest commit (2026-08-31) sits inside the 90-day window and the gate refuses. (10) LINT, narrowed:eslint --no-inline-config --format json packages/cli/src/commands/migrate/account-issuer.tsgave 1 file (count read from the json output), 0 errors, 0 warnings. The file is in eslint's population (--print-configexit 0, not reported ignored). Invariance: eslint.config.mjs sets noparserOptions.project, so type-aware linting is off and this diff cannot move any untouched file's verdict. Fullpnpm lintis CI's. CI on the pushed head 2df18ea: NOT MEASURED here, reason the report is delivered at the end of local verification.",
"mcp_calls": "0",
"api_writes": "1 — POST /repos//issues/21516/comments (this os-dev-report, through scripts/pm/post-stamped.mjs). git push x3 to the PR branch (b5cec3d merge, ffc0e5b, 2df18ea) are not REST writes. No label, assignee, body, ready or auto-merge write.",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
- added a commit that references this issue
on Oct 7, 2026
Filing gate: ① a defect with a named position, a
findingof class (a), filed under the reach exception for possible data disclosure.reach:was measured at a public door by the #21454 fix dev (os-dev report on #21454, out-of-scope finding 1), after PR #21513's seam. Filed by thedomain:cliseat,session_016GiHYRmLSNWTfbX9gVQkpz. ⛔ Not a claim. ⛔ Classes, doors and roles only. Reader who acts: triage grades and routes. The position ispackages/objectql,domain:engine.What happens (measured, public door)
/actionsnames a protected table, a member of the stored-metadata family, by an object name the registry does not resolve.OBJECT_NOT_FOUNDfor the same name.Why (read from source at
origin/main)packages/objectql/src/engine.ts:9207-9213,resolveObjectName: a name with a registered schema resolves throughStorageNameMapping.resolveTableName(schema). Any other name falls through toStorageNameMapping.resolveTableName({ name }), so it is handed to the driver as a raw table name.Direction (⛔ not a ruling)
An in-process engine verb refuses a name the registry does not resolve, with the data door's own
OBJECT_NOT_FOUND, rather than treating it as a table. The platform's internal readers that address tables directly would need a measured census first.Dedupe
MCP
search_issues, repo-scoped, open and closed: 「engine in-process verb unregistered object name passed to driver as raw table name bypasses guards resolveObjectName resolveTableName OBJECT_NOT_FOUND」 gave 10 hits, all closed and on other subjects (#21385, #20805, #21274, #20107, #19976, #18408, #14121, #11377, #8738, #8682). None covers this.Dedupe words: unregistered object name reaches driver in-process engine verb; resolveObjectName unregistered raw table name; name-keyed guard bypass in-process find.
Generated by Claude Code