Skip to content

objectql: a ReadableIds leaf nested inside a composed read scope (a two-level controlled_by_parent chain) is composed, instead of reaching the driver uncomposed #25010

Closed

Description

@objectstack-fleet

Filing gate ③ a task the maintainer set: engine card (b)(ii) of the maintainer's ruling B on #24932 (6303721609, batch #379 item 3), the sibling of the delegated-composition card. Filed by domain:services seat 2 (seat post #21118, os-support-ai, session session_01UmqzD97e14iws6hmVhgZ5y). domain:engine is the ruling's word; priority:p1 and area:access follow #24932. ⛔ Not a claim.

Reader (who acts): the domain:engine seat claims and dispatches it. #24932 is Blocked-by: this card.

The ruling, as it binds this card

The ruling's words: "the engine first composes the leaf for a delegated (on-behalf-of) caller … and for a nested master chain; then plugin-security switches … in one change, and the id-list pre-scan retires" (6303721609).

What this card does

Pins

Under the lane's commitment, with ablation:

  • a two-level chain composes and returns the same rows today's id-list path returns;
  • one statement per find.

Readings it builds on

The #24932 dev report 6298477402 (the 2-level chain measurement: today 8 statements per find+count; the leaf nested uncomposed answers 500). ADR-0139 D6 and O5.

Dedupe

Semantic search on objectstack-ai/objectstack, open and closed, for "ReadableIds leaf delegated caller on-behalf-of compose ADR-0139 D5.1 nested chain" (run just after filing): this card, its sibling, and the closed ADR-0139 E1 / E2 / E3+E4 / E5 / E7 cards (#22787, #22861, #23639, #23673, #23732, #23857) that built the leaf. None composes for a delegated caller or a nested chain. New.

Activity

  1. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Serial note · domain:engine seat 2 (seat post #20966) · session_01QK6pdbxKZiHdFApL8RYVh9 · 2026-10-21T00:48Z. ⛔ Not a claim, no label change: the card stays in pm:queue.

    Serial behind #25009 (claimed by this seat, claim 6305174176). Both change the same region of packages/objectql/src/engine.ts, the ReadableIds leaf composition (doc block near :6135, the D5 refusals near :6225 on 62bc12930), and #25009's PR carries the ADR-0139 revision the ruling puts in the first engine PR. This seat takes this card when #25009's PR lands. Whoever dispatches it re-measures the two-level chain on the merged main, and builds on #25009's composition rather than beside it.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-21T03:47Z · p1, domain:engine (ruling B on #24932, 6303721609, card (b)(ii)). A ReadableIds leaf nested inside an already-composed read scope (a two-level controlled_by_parent chain) is composed by the engine instead of reaching the driver uncomposed.
    Session: session_01QK6pdbxKZiHdFApL8RYVh9 (the dispatching seat; the dev is a mode:subagent run of this session, told apart by its branch)
    Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-25010-readable-ids-nested-compose, stacked: cut from claude/issue-25009-readable-ids-delegated-compose at 2a8d0d5b4 (PR #25032, Tier H, awaiting the maintainer's approval), so it builds on #25009's composition in the same region instead of beside it.
    Worktree: objectstack-issue-25010
    Domain: domain:engine
    Seat: domain:engine#2 (seat post #20966)
    Ruling-ref: 6303721609 (read at this claim)
    File surface:

    ⛔ Not in scope: plugin-security's switch and the id-list pre-scan's retirement (#24932, card (c)); the consumers' probe routing (#25034); ADR-0055.
    Landing order: this card's PR lands only after PR #25032. Until then it stays a draft, with the stacked commits named in its body. After PR #25032 merges, the branch merges origin/main, and the diff then shows this card's commits alone.
    Container & model: M, mode:subagent, model: CONTRACT_REVIEW_TIER (ceiling; reason: a security read-scope composition in the engine's leaf, recursive; this run's dispatch-gates.mjs --tier: "no path-derived mandate")
    Clause-②: no (an engine-internal leaf, not a published accept set; the runtime read-scope change is the human floor's, decided by ruling 6303721609)
    Responsibility: n/a — not a defect card
    Thread-read: 6305198641
    Serial constraints cleared: this seat's own #25009 (PR #25032) holds the region, and this card is built on top of it. The open PRs' file lists were read at this claim: PR #24893 (near :91, :9043, :12187) and PR #25016 (near :19596, :19954) hold other engine.ts regions. PR #25039 (this seat, #24964) adds inspectCascadeDelete near the cascade walk, a different region.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25010,
      "status": "done",
      "branch": "claude/issue-25010-readable-ids-nested-compose",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/25072",
      "session": "session_01QK6pdbxKZiHdFApL8RYVh9 — the dispatching seat's session (mode:subagent shares it)",
      "premise_still_valid": true,
      "summary": "The engine composes a ReadableIds leaf that sits inside a composed read scope, recursively: composeReadableIdsAtBoundary resolves the security service once per read and hands the sites to the new composeReadableIdsChain, which composes every leaf a parent's answered scope carries (same five steps, same caller context at every level, delegated included) before the enclosing leaf, so the driver receives one composed leaf whose scope holds composed leaves and compiles one statement with nested subqueries. Each parent is asked once per read (answers shared across levels, reused by reference); refused by name (INTERNAL_ERROR/500) before the service is asked: D5.2/D5.3 at a nested level, a cycle, a chain deeper than 8 scopes (READABLE_IDS_MAX_CHAIN_DEPTH, the same cut as plugin-security's CBP_MAX_CHAIN_DEPTH), a nested leaf outside a $in or a forged composed leaf inside a scope. collectComposableReadableIds gains an optional `at` label so nested sites read where.record_id.$in.scope.grand.$in. H1 reproduced on the base (driver-sql UNCOMPOSED 500 on every leaf-path read of a 2-level chain); H2 holds; H3: one statement per find and per count on the composed path. Branch built on #25009's (2a8d0d5b4), then origin/main merged after PR #25032 landed as f8c5fb396 (merge 70d95b3e2, one engine.ts conflict resolved by keeping this card's hunks); final head 549f0bcc8, own diff vs main = 5 files (1079/13). No plugin-security switch, no pre-scan retirement, no consumer routing change, no docs/adr edit.",
      "tests": "All on the post-merge head 549f0bcc8 unless noted, every heavy run through scripts/pm/os-verify-lock.sh. Closure build `pnpm exec turbo run build --filter='@objectstack/plugin-security...' --concurrency=2`: 19 tasks successful (1 cached after the merge), VERDICT command-exit 0. `pnpm --filter @objectstack/objectql test`: Test Files 454 passed (454), Tests 8545 passed (8545), 692 s, exit 0. `pnpm --filter @objectstack/plugin-security test`: Test Files 201 passed (201), Tests 4133 passed | 45 skipped (4178), 259 s, exit 0. `pnpm --filter @objectstack/objectql typecheck` exit 0 (check:test-typecheck OK, 39 files / 233 errors / 64 pinned signatures held — the pre-existing ledger); `pnpm --filter @objectstack/plugin-security typecheck` exit 0 (0/0/0). The four as one locked command with the ablation: VERDICT command-exit 0 · held the lock 1070s. New pins alone (pre-merge head 9cee19a5d, the only later change is a four-site cast move): packages/objectql/src/engine-readable-ids-nested-composition.test.ts + engine-readable-ids-composition.test.ts Tests 39 passed (39); packages/plugins/plugin-security/src/readable-ids-nested-compose.test.ts Tests 9 passed (9) — the real stack (ObjectQL + better-sqlite3 + real SecurityPlugin/SharingService), the composed statement measured as `select * from qa_rid_nest_child_leaf where parent in (select id from qa_rid_nest_parent where grand in (select id from qa_rid_nest_grand where ((owner_id = ?) or (id in (?)))))`; rows row-for-row equal to today's id-list path for two direct identities and a delegated caller, find and count agree; today's path: one `select id from GRAND`, one `select id from PARENT where grand in (?,…)`, then the child statement binding a list. H1 reproduction on the base 2a8d0d5b4 (engine files restored tree-only, blobs 2879fdae7f2d / b5a9e1aa8d95 verified, restore to HEAD proven): the same real-stack file 5 failed | 4 passed (9), every leaf-path read refused `[driver-sql] where.grand.$in holds a ReadableIds leaf … UNCOMPOSED` INTERNAL_ERROR/500. ABLATION (one behaviour, one leg, scripts/ablation-replace.mjs, on 549f0bcc8): anchor `: { scope: await this.composeReadableIdsChain(chain, answer.scope, nested, [...ancestors, parent]) });` → `: answer); /* ABLATION_25010_NO_RECURSION */` — `ok mutation landed: anchor 1 → 0, blob 51393862e428 → 999f76cdee36` (on disk: recursion call 0, marker 1); under it objectql nested+composition pins 9 failed | 30 passed (39), real-stack nested+delegated pins 5 failed | 14 passed (19) (driver-sql UNCOMPOSED, as on the base); `ok restored: blob == HEAD (51393862e428) and git diff HEAD is empty`, marker 0 after. Both suites resolve objectql from src (objectql imports ./engine.js; plugin-security aliases @objectstack/objectql to src/index.ts), so no dist was on the resolution path. The same leg on 9cee19a5d (blob 455faa95a5fb) read identically. test:repo not run locally (CI's Test Core).",
      "gates": "Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths) on 549f0bcc8: 75 families (on the stacked base before the merge the derivation named 83, covering PR #25032's files too; that sweep on 9cee19a5d read 79 exit 0, 3 exit 3, and 1 red — check:query-options-erasure, test sites 236 → 240 from four `as any` options-object casts in the objectql pins, fixed in 549f0bcc8). Final sweep on 549f0bcc8, exit codes captured per command before any pipe into ran-3.list: 72 exit 0 (check:query-options-erasure holds at 236; check:where-matcher, check:objectql-double-limit, check:engine-double-contract, check:test-source-alias, check:cross-package-test-inputs, check:changeset-gate-self-tests, check:empty-changeset, check:changeset-no-major, check:adr-0087-registration, check:type-check-coverage, check:nul-bytes, check:durability-log-level, check:swallow-census-controls, check:stack-collection-maps, check:undeclared-dep-imports among them); 3 exit 3 NOT MEASURED locally (prerequisite refusals, not reds): pnpm check:dual-build-cjs-loads and pnpm check:i18n (read every package's dist; only the plugin-security closure is built here — CI's Build Core / lint legs), pnpm check:type-check-debt (rebuilds the closure under the verify lock; queue-timeout behind this PR's own locked suite run, and a second attempt after it timed out behind another seat's hold, `--re-measure cannot run: the closure build never ran`, exit 3 both times — CI's `Type Check · debt ledger` carries it). `node scripts/pm/dispatch-gates.mjs --ran ran-3.list --repo objectstack-ai/objectstack`: `✓ 75 derived famil(ies) accounted for — 72 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)`, 0 UNRUN, exit 0. check-platform-checklist not owed (no export renamed or removed). CI check-runs on the PR head 549f0bcc8, read once at this report (not polled): 38 runs, 0 failure; 10 success (Auto Label, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, The card this PR closes must claim this branch, filter); 7 skipped (opt-in and full-battery jobs); 21 in_progress — the honest value — including every required context (Build Core, Test Core 1–6/6, Lint · ESLint, Lint · repo & tooling gates, Lint · docs & code guards, Lint · CI release & engine gates, Lint · PM dispatch-gates, the three scoped lint legs, Type Check · workspace / source gates / consumer gates / debt ledger, Check Changeset, Governed Surface Queue Guard). CI concludes them; I did not wait.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm (Clause-②: no, copied from the claim)",
      "mcp_calls": "0 — no MCP GitHub tool was called; reads were `gh api` REST GETs (issue 25010 + comments, comments 6303721609 / 6298477402 / 6306657655 / 6307019524, issue 25009, pull 25032, issue 24932)",
      "api_writes": "3 REST writes, all through the fleet-write relay as objectstack-fleet[bot] (scripts/pm/, no bare write): POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft forced, relay run 39557516932 → PR #25072, 19062 bytes sent = 19062 stored, read back) · POST /repos/objectstack-ai/objectstack/issues/25072/assignees (the same pr_create action's second request, assignees: os-support-ai, as the pull lists it) · POST /repos/objectstack-ai/objectstack/issues/25010/comments (this report, via scripts/pm/post-stamped.mjs). No PATCH, no label write (the dispatch named no label; skip-changeset not applicable — @objectstack/objectql publishes and carries a changeset; `size/xl` on the PR is the auto-labeler's). git pushes, not REST writes: 5 (the empty-branch probe, then 46905c05d, 9cee19a5d, 70d95b3e2, 549f0bcc8; normal pushes, no force).",
      "line_budget": "n/a — no skills/** or governed file in the diff",
      "deviations": [
        "Stacked base, as the dispatch claim 6307546021 directed: the branch was cut from claude/issue-25009-readable-ids-delegated-compose at 2a8d0d5b4 although AGENTS.md §2 names a stacked series an unsupported form; after PR #25032 landed (f8c5fb396) origin/main was merged (70d95b3e2, true merge, one engine.ts conflict resolved by keeping this card's hunks over the identical #25009 text), so the PR's diff vs main is this card's five files alone.",
        "Three derived families NOT MEASURED locally (exit 3, prerequisite refusals): check:dual-build-cjs-loads, check:i18n, check:type-check-debt — see gates.",
        "The real-stack parity pin emulates the plugin-security switch test-side: the engine's security source is the real service wrapped so getReadFilter(PARENT) answers the real scope with its pre-resolved master id list replaced by ReadableIds.of(GRAND); the real service's own grandparent pre-scan still runs while answering (retires with card (c)), so the one-statement pin is on the composed read's own statement.",
        "A Turso remote-face probe test (packages/drivers/driver-turso/src/zz-probe-25010-nested.test.ts) was written into the worktree, run once under the lock, and deleted before any commit; its readings are in out_of_scope_findings.",
        "The objectql pins were committed once with four `as any` options-object casts that grew the query-options-erasure ratchet (236 → 240) in the stacked-base sweep; fixed by casting the context value (549f0bcc8), the ratchet re-read holds, and the full suites, the ablation and the gate sweep were re-run on that head.",
        "My own queued lock waiters were stopped twice (recorded PIDs only: a D+E leg not yet acquired when the test fix became necessary; a second D leg that had timed out at 99 so D and E could run under one acquisition); my unlocked gate sweep on 70d95b3e2 was stopped at 24/75 for the same reason and re-run whole on 549f0bcc8.",
        "The harness-written commit trailer is `Co-authored-by: Claude` with `Claude-Session`, model-free, as AGENTS.md spells it; the system-reminder's model-named Co-Authored-By line was not used."
      ],
      "files_changed": [
        "packages/objectql/src/engine.ts",
        "packages/objectql/src/filter-comparand-shape.ts",
        "packages/objectql/src/engine-readable-ids-nested-composition.test.ts",
        "packages/plugins/plugin-security/src/readable-ids-nested-compose.test.ts",
        ".changeset/25010-readable-ids-nested-compose.md"
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: #24932 (card (c), the switch that makes a nested leaf live) · noted in the PR's Acceptance notes, not filed · class would be (b) against ADR-0139 D3 (the Turso remote transport 'implements the same compilation') but reach is none today: no shipped producer writes a leaf inside a read scope until the plugin-security switch. Measured on 549f0bcc8 with a probe over the spec's conformance rows and a composed leaf whose scope carries a composed leaf: TursoDriver LOCAL face answers rows c1,c3,c6,c7 (the nested semi-join evaluated); the REMOTE face refuses INTERNAL_ERROR/500 `[driver-turso] where.id.$in holds a ReadableIds leaf … composed, and driver-turso does not compile it: the remote transport compiles it only as TursoDriver resolved it` — TursoDriver.toRemoteWhere resolves top-level composed leaves only (replaceComposedReadableIds does not descend into a composed leaf's scope) and hands the nested one to the transport via toRemoteFilter. driver-sql (applyFilters) and driver-memory (convertToMongoQuery) compile the nested scope by construction. Remedy: one line (resolve the scope through toRemoteWhere with the outer read's options) plus a nested case in packages/spec/src/data/readable-ids-conformance.ts so every driver suite measures the shape. Not done here (a driver verification surface this card does not own; the just-in-place exemption's condition ④ fails). Dedupe words: Turso remote transport nested composed ReadableIds, toRemoteWhere scope, readable-ids-conformance nested, ADR-0139 D3.",
        "carrier: none · noted, not filed · ADR-0139 D2 step 2 says the composed scope 'is run through the same lowering P's own direct read runs'; the engine marks the scope 'policy' and hands it through as the security service answered it (no lowering call in readableIdsScopeOf), at the nested level exactly as at the top level. Source reading; the services answer driver-ready filters. Governed text, so not edited here.",
        "carrier: #24932 (card (c)) · noted, not filed · the real getReadFilter(PARENT) still pre-scans GRAND to build PARENT's id-list scope (ADR-0055 §2 (b)) — the mechanism the ruling keeps until the switch; not a finding, recorded so the one-statement reading is read correctly."
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25072 comment 6310429071 (TIER_DEFAULT, no contract face) on 549f0bcc8 · ready + auto-merge armed 2026-10-21T07:22Z. The merge closes this card via Fixes. The Turso remote transport's nested-leaf gap is recorded on #24932 for card (c) (6309967905).


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:engine seat 2 · session_01QK6pdbxKZiHdFApL8RYVh9 · 2026-10-21T07:52Z

    Probe Reading
    PR #25072 merged: true, merge commit d96f618ed, through the merge queue on its first entry
    origin/main d96f618ed feat(objectql): the ReadableIds leaf inside a composed read scope is composed too — the nested master chain (ADR-0139 D6 as amended) (#25072)
    queue branch gh-readonly-queue/main/pr-25072-* gone
    card closed completed by Fixes #25010; #24932 and #25034 still open (no wrong close)

    State: pm:dispatched removed in this act. Ruling B's two engine cards, (b)(i) #25009 and (b)(ii) this one, are both on main. Card (c) of #24932, the plugin-security switch and the id-list pre-scan's retirement, now has its engine prerequisites. Its pm:blocked is the unlock scan's to read. The Turso remote nested-leaf gap that switch must carry is recorded there (6309967905).


    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:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:enginepriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions