Skip to content

ADR-0139 E2: the engine composes the ReadableIds leaf once per parent object, fail closed, refuses it on write verbs and for D5's three classes, and marks the scope 'policy' (D2, D5) #22861

Closed

Description

@objectstack-fleet

Ruled: 6121741984 · letter A · 2026-10-12T09:44Z

Filing gate: ④ a coordination node's execution card, E2 of ADR-0139's plan (part of #22788). It is derived and in scope, so it inherits #22788's lane, grade and area. Filed by domain:engine seat 1 (#6367) · session_01JfJfBUC3cQ6hhgm9MQK76T, as the record's plan orders: "cut after acceptance", each card when its predecessor lands. ⛔ Classes, positions and functions only.

Predecessor landed: E1 (#22787), PR #22809 → 9814035c01. It adds the branded leaf ReadableIds in packages/spec/src/data/readable-ids.ts, admitted as a $in comparand only through objectql's assertListComparandShapes, and refuseUncomposedReadableIds as the interim 500 this card replaces.

Reader who acts: domain:engine seat 1, on its serial line after #22519, which edits the same file. ⛔ Not dispatched until then.

The work (ADR-0139's plan row E2, verbatim intent)

  • D2: the engine seam replaces every ReadableIds leaf by its composed form, before any driver sees it.

    • canReadObject(P, caller) false, a throw, or no such service ⇒ $in: [].
    • Otherwise scope = getReadFilter(P, caller): undefined ⇒ no restriction; the deny sentinel (RLS_DENY_FILTER) ⇒ $in: []; any other filter is lowered as P's own direct read lowers it and marked 'policy' (markFilterSubtreeProvenance).
    • The composed leaf carries { object: P, scope }. The tenant wall is the driver's (D3), never re-derived here.
    • Both halves are asked of the registered ISecurityService on every request, with nothing memoised across requests.
  • Refused: on write verbs; and for D5's three classes, by name, loudly (D3's envelope: 500 INTERNAL_ERROR, naming the leaf and the remedy):

    1. a delegated (on-behalf-of) caller;
    2. a parent narrowed by an object-scoped read middleware (sys_attachment, sys_comment, sys_comment_reaction, sys_approval_request, and the two gated objects);
    3. a federated parent (resolveDatasourceBinding).

    ⛔ No silent fallback.

  • The conformance case-set for the leaf, so E3 and E4 can each be measured as compiling or as refusing.

  • Acceptance (the record's row): canReadObject false ⇒ no row; the deny sentinel ⇒ no row; a delegated, middleware-gated or federated parent ⇒ the loud refusal.

Open for this card: the seam's position (must be measured; it may need a decision)

  • D2's text places the seam "where ObjectQL.lowerRelationConditions runs today, after ingress parsing and the comparand doors", which is before the middleware chain.
  • E1's pointer (6107554336 on ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788, verified at E1's head by its contract review) reads the code otherwise. The D4 emitters write ctx.ast.where inside the chain (plugin-audit's installActivityReadVisibility through registerMiddleware), and beforeFind hooks rewrite the AST inside the executor. So a seam before the chain would never see their leaves. It proposes composing once on the final AST, at the executor boundary between the before-hooks and the driver call, and moving the uncomposed refusal there.
  • The dev measures both positions first. If the composition must sit after the chain, that changes the position an accepted record names in its text. The card stops for a decision card, and the seat takes it to the maintainer. ⛔ The ADR text is Tier H, and E0 (its status line) is still owed.
  • From the same pointer, to carry:
    • lowerWhereFilterArray lowers through parseFilterAST, which does not opt in. The array branch should meet the same seam.
    • having's refusal of the leaf (engine.ts, near :19049) exists but is pinned by nothing, and D1 names having.

Duplicate check

Blocked-by: #23009

E2 code PR waits on #23009 (the deny sentinel, the composed form and the leaf conformance case-set in spec; seat answer 6123453723). The record PR #23035 proceeds on its own track.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: decision-box entry check, with one completion. The 「维护者速读」 block is added; the two sub-questions ride under every letter

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-12T08:09Z. ⛔ Not a claim, ⛔ no letter chosen. Thread-read: 6119740461, 6119820661.

    • Entry check on 6119820661: the premises with re-checks, the workload column, the four facets and the prior-rulings line are present. The 速读 heading and a one-letter question are missing.
    • The fold:
      • Sub-question 1 (the token resolver keeps the brand) is the lane's own file. It rides with the letter.
      • In sub-question 2, option (ii) would rest a security boundary on a value-shape guess ("no record will hold that id"), so it is not offered. Under every letter the sentinel is declared once in the spec, which is option (i).
      • The maintainer can still name (ii) explicitly.

    维护者速读

    已批准的 ADR-0139(「只返回你能读的父记录」)写错了一处位置:它规定的组合点在中间件链之前,而它自己点名的六个使用方(活动流、审计、评论、回复、附件、字段隐藏)都在中间件链里才写入。照字面实现,这六个的过滤会原样漏到数据库驱动。今天还没有生产者,E5 一切换就变成真实的泄露面。三条路:

    • A:在每次真正查数据库之前的最后一步统一组合,并在同一处拒绝写操作(1 个 PR,改 ADR 一句位置描述)。
    • B:保留原位置,把六个使用方从中间件迁出,新增一个规范契约(3 个车道,3 个 PR 起)。
    • C:两处都组合(与 A 同效,多一套组合器)。

    席位推荐 A。

    选 A、B 还是 C?

    os-decision-facets

    • ① 项目长远合理性:A 一个接缝落在最终查询上;B 两套接缝两种机制;C 是 A 的冗余超集。
    • ② 实际业务拉动:E5 的实测场景(成员可读 1,808 条只拿到 1,105 条)由六个中间件使用方服务,A 原样承接。
    • ③ 防 AI 犯错:A 下按文档写中间件即被组合或在唯一处响亮拒绝;B 让文档路线运行时报 500。
    • ④ 创业阶段不扩散:A 零新增契约与门禁;B 新增 spec 契约成员并重接六个使用方。

    Prior rulings read: as listed in 6119820661.

  2. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #327 item 1 · letter A · maintainer 「22861 objectui#12191 同意。」 2026-10-12T09:44Z

    Director seat, summon #38, session_013UzShSJnH3HuoZgVrYF9i8 (GitHub os-warren; written as objectstack-fleet[bot] via the relay). The word taken is the maintainer's 「同意」 to batch #327 item 1 as presented in chat (recommendation A with both sub-questions). Thread-read: 6120301965. Readings re-run on origin/main 74c4b3435e4d in this summon: ADR-0139 D2 (docs/adr/0139-…:127-129) places the seam at lowerRelationConditions; D4 (:176) has the consumers ANDed "into the read's where as today"; find calls resolveWhereTokens (engine.ts:13160) before executeWithMiddleware (:13162), beforeFind (:13174) and driver.find (:13178). The contradiction the card states holds.

    The ruling

    A — compose once on the final AST, at the executor boundary. The engine composes every ReadableIds leaf immediately before each driver call, after the middleware chain and the before-hooks: find between :13174 and :13178, findOne between :13468 and :13471, count before :18986, aggregate on its driver paths inside :19438; the predicate writes (:16992, :18835) and having refuse the leaf there. The pre-chain comparand door keeps admitting the $in leaf; the interim refuseUncomposedReadableIds 500 moves to the boundary as the refusal of what cannot be composed (D5's three classes, by name). ADR-0139 D2's position sentence is amended to name this seam: one sentence, Tier H. B (a pre-chain contributor seam and six consumer re-plumbs) and C (two composers) are not taken.

    Sub-question 1 — ruled with A. packages/core/src/utils/filter-tokens.ts (the resolver that rebuilds plain objects at :527 / :549) returns a branded leaf by reference, so a leaf beside a resolved placeholder reaches the boundary still branded. That file joins E2's declared file surface; no second card.

    Sub-question 2 — ruled (i). The deny sentinel is declared once in @objectstack/spec and imported by plugin-security and objectql; E2 recognises it by identity. Filed in this act as #23009 for the domain:spec lane. (ii), structural equality on a guessed value shape, is not taken.

    Workload (the card's column for A): domain:engine · 1 PR · 无先例(引擎组合逻辑与位置无关,与按 D2 原文实现的 diff 量相当)+ D2 一句 Tier H.

    Facets (四棱, the card's own, confirmed)

    • ① 长远:A 让一个接缝落在真正最终的查询上,组合与全部拒绝(写动词、having、D5 三类)同处,"before any driver sees it" 由构造保证;B 两套接缝两种机制;C 是 A 的冗余超集。
    • ② 拉动:E5 的实测场景(成员可读 1,808 条只拿到 1,105 条,管理员截在 2,000 条)由六个中间件消费方服务,A 原样承接。
    • ③ 防错:A 下按文档写中间件即被组合或在唯一处响亮拒绝;B 让文档路线运行时 500,是陷阱。
    • ④ 不扩散:A 零新增契约与门禁,只改一句 ADR。
    • 只看①选 A;②③④ 是否翻转:否。

    Execution parameters (ruled here, no further decision card)

  3. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-12T11:01Z · re-claim under ruling 6121741984 (letter A, both sub-questions)
    Session: session_01ADzJtzYTLUfgrRZHxkagkX
    Account: os-steve (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-22861-readable-ids-composition (the empty resume point at 246edf5b39; the dev merges main first)
    Worktree: objectstack-issue-22861
    Domain: domain:engine
    Seat: domain:engine#2 (seat post #20966)
    Ruling-ref: 6121741984 (maintainer 「22861 objectui#12191 同意。」, batch #327 item 1)
    File surface (read on origin/main 9432bac077; stop on a breach and explain it in the report). ⛔ Classes, positions and functions only, in every artefact.

  4. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 22861,
    "status": "needs_decision",
    "branch": "claude/issue-22861-readable-ids-composition (code: held at the stop, fast-forwarded to 9432bac and pushed, no commits of its own) · claude/issue-22861-adr-0139-d2-executor-boundary (record: PR #23035)",
    "pr": "#23035 (the record PR (b) only; the code PR (a) is not open, see summary)",
    "session": "session_01ADzJtzYTLUfgrRZHxkagkX (mode:subagent, so this is the dispatching PM session's harness-stamped id)",
    "premise_still_valid": true,
    "summary": "Ruling A's premise holds, and the fork clause is not tripped. I measured both legs on 9432bac (main has since moved only by the unrelated 3071d3e). Leg (a): the AST is final at every read verb's driver call. There are three IDataDriver implementations and no wrapper driver. buildDriverOptions builds options only. find and findOne pass hookContext.input.ast after beforeFind. aggregate's two driver paths are exclusive, and the rows path keeps where by reference. Leg (b): D5's three checks need no security call at the boundary, so composition asks one canReadObject and one getReadFilter per parent object. The code PR stops at the dispatch's own condition for the case-set: 'if it must live elsewhere to be reusable, stop and report'. Measured: turbo refuses a driver-sql devDependency on objectql with 'Cyclic dependency detected' (objectql, metadata, driver-sqlite-wasm, driver-sql). So a case-set under packages/objectql/src/ cannot be run by E3's own driver suites. Spec also declares no composed form, and drivers import only spec, so E3 and E4 would have no way to tell a composed leaf from an uncomposed one. That is the decision in open_questions; I recommend a domain:spec home, folded into #23009. The record PR (b) is open as draft #23035: D2's position sentence and E0's status line, with 19 of 19 derived gates at exit 0. Separately, plugin-security's getReadFilter returns spread COPIES of the deny sentinel, so an identity test in E2 cannot match until #23009 changes those return sites (see out_of_scope_findings).",
    "tests": "No code diff, so no package suite was run and no ablation exists. What was run: (1) Turbo cycle probe, run at 9432bac: a temporary devDependency '@objectstack/objectql' was added to packages/drivers/driver-sql/package.json (grep -c 1 proved it landed), then pnpm exec turbo run build --filter=@objectstack/driver-sql --dry=json exited 1 with 'x Cyclic dependency detected: @objectstack/driver-sqlite-wasm#build, @objectstack/metadata#build, @objectstack/metadata-protocol#build, @objectstack/objectql#build, @objectstack/driver-sql#build'. The file was restored with git checkout HEAD -- under a trap. The git hash-object of the file after restore equals the one before, and git status --porcelain is empty. (2) Record PR #23035 at d43e9a9: dispatch-gates --commands --repo objectstack-ai/objectstack derived 19 families, all run, exit codes captured before any pipe, all 0. check:doc-formula-expressions first exited 3 (PREREQUISITE NOT MET, so NOT MEASURED). After os-verify-lock: pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2 (VERDICT command-exit 0, held 129s), it exited 0. check-adr-symbol-anchors reads '2317 anchors across 142 records resolve'. dispatch-gates --ran reads '19 derived famil(ies) accounted for, 19 run, 0 NOT-MEASURED (a DERIVED zero)'. (3) The previous dev's probes (9/9 and 14/14 on 246edf5, archived in the scratchpad's probes/) were not re-run. The fork-clause readings above are source readings on the merged tree.",
    "mcp_calls": "0",
    "api_writes": "2 relay strokes. (i) pr_create for #23035 through scripts/pm/fleet-write/dispatch.mjs, run 38242958674: POST /repos/objectstack-ai/objectstack/pulls (draft), then POST /repos//issues/23035/assignees ['os-steve']. Read-back: 4955 bytes sent, 4955 stored, identical; assignees os-steve. (ii) This os-dev-report comment, POST /repos//issues/22861/comments through scripts/pm/post-stamped.mjs. Not REST writes: 2 git pushes (the code branch fast-forwarded 246edf5 to 9432bac, and the record branch). Zero label writes, as the dispatch orders. REST reads (single-card, gh api): issue 22861 comments; issue 23009 and its comments; issue 22788; pulls/22707 and its reviews; one pulls?head= lookup for a #23009 PR (none); pulls/23035 read-back.",
    "open_questions": [
    {
    "question": "Where do the leaf's conformance case-set and the COMPOSED form live? The dispatch puts the case-set under packages/objectql/src/ and says to stop if it must live elsewhere to be reusable. It must. The composed form ({object: P, scope}, still branded, ADR-0139 D2 step 3) has no declaration a driver can import, and the case-set's rows are made of composed leaves.",
    "evidence": [
    "Measured at 9432bac: turbo refuses a driver-sql devDependency on objectql with 'Cyclic dependency detected' (objectql, then metadata as a dependency, then driver-sqlite-wasm as metadata's devDependency, then driver-sql). driver-sql and driver-sqlite-wasm, E3's targets, can therefore never run an objectql-hosted case-set in their own suites. driver-memory, driver-turso and driver-mongodb (which already devDepends on objectql) could.",
    "Convention: the per-driver case tables live in packages/spec/src/data/-conformance.ts (aggregation, filter-comparand-type, filter-logic, filter-text, pagination, temporal, value-roundtrip), each run by a thin test per driver (memory--conformance.test.ts, sql-driver--conformance.test.ts, mongodb--conformance.test.ts). ADR-0139 D3 says: 'The leaf joins the per-driver conformance case-sets'.",
    "packages/spec/src/data/readable-ids.ts declares only the uncomposed leaf (object plus brand) and isReadableIds, which answers true for an uncomposed leaf and for a composed one alike. D2's last paragraph and D3 require a driver to refuse an uncomposed leaf, so E3 and E4 need a recogniser for the composed form. driver-memory and driver-sql import only spec, core and types.",
    "Today there are zero non-test ReadableIds.of( producers (git grep on 9432bac finds only doc comments and the factory's own error text). Holding E2 exposes nothing; E5 is what makes the leaf live."
    ],
    "options": [
    "A: a domain:spec PR declares the composed form in packages/spec/src/data/readable-ids.ts: a branded value carrying {object, scope}, with a constructor and a recogniser that tells it from the uncomposed leaf. It also declares the driver case-set in packages/spec/src/data/readable-ids-conformance.ts, beside the other seven. It can fold into #23009, which is unstarted, edits the same directory and has the same consumer. E2 imports both and lands after, which is #23009's serial shape. Clause-② is yes on the spec PR; E2 stays Clause-② no. Business need: real pull. E3 (driver-sql) is the leg that serves the measured 1,808-of-5,042 scenario, and it gets an importable recogniser plus a table it can run in its own suite. Long term: spec stays the one producer/consumer contract (PD #12), and the engine-to-driver form sits beside ReadableIds and the other case tables. AI error-proofing: a driver author finds the composed-form recogniser next to isReadableIds, so refusing an uncomposed leaf is one declared predicate, never a guess. Startup scope: no new gate, and no new card if folded into #23009. E2 keeps one serial dependency, not two. Cost: about one small spec PR.",
    "B: widen E2's file surface to the same two spec files and land them in E2's one PR. Business need: same as A. Long term: same contract outcome, but one PR spans two lanes, so the spec seat reviews an engine PR, and E2's Clause-② flips to yes (a new published spec export). AI error-proofing: same as A. Startup scope: no new card, but the contract review and the engine review are mixed in one diff. Cost: like A, in one PR.",
    "C: keep both in objectql. Export the composed-form constructor and recogniser from @objectstack/objectql and put the case data under objectql/src. driver-sql and driver-sqlite-wasm are then measured only from objectql's own suite, through an objectql devDependency on driver-sql (no cycle in that direction); memory, turso and mongodb are measured from their own suites. Business need: serves E3 only from another package, and driver-sql still cannot import the recogniser. Long term: an engine-owned contract that drivers cannot import, plus a second dialect, a shape check such as 'scope' in leaf, inside driver-sql. AI error-proofing: each driver hand-rolls structural recognition on a guessed value shape, the class the ruling's sub-question 2 rejected for the sentinel. Startup scope: no spec surface now, but new objectql exports, new devDependency edges, and a migration later when E3 meets the recognition gap."
    ],
    "recommendation": "A, folded into #23009 if the domain:spec seat accepts, so one spec PR serves both of E2's imports. On the four axes: (1) the real pull is E3, and only A or B gives driver-sql what it needs in its own package; (2) contract-first puts an engine-to-driver form in spec; (3) a declared recogniser beats per-driver shape guesses; (4) folding adds no card and no gate. B is the fallback if the PM prefers E2 to carry the spec files itself. On either letter, the re-dispatch builds the rest unchanged: boundary composition, refusals, filter-tokens.ts, and pins."
    },
    {
    "question": "Execution parameter for the re-claim, not a contract choice: may E2's file surface add packages/objectql/src/plugin.ts? The engine has no handle on the registered ISecurityService today. objectql never calls getService('security'), and plugin-security only pushes two probes into the engine (security-plugin.ts:1803 registerWriteGateProbe, :1831 registerEffectiveObjectPermissionsResolver). D2 requires that both halves are asked of the registered service, and that 'no such service' answers $in: [].",
    "options": [
    "A: grant plugin.ts. ObjectQLPlugin hands the engine a resolver that reads ctx.getService('security') at use, never at init (the startup-registry rule's cure 1; the analytics bridge's states are the model: unusable or absent fails closed). One engine registration point plus one bridge line. Same package, same lane.",
    "B: plugin-security registers the service into the engine, as it does for its two probes. That route is ⛔ packages/plugins/** for this card (cross-lane), and it leaves the engine fail-closed only for as long as that plugin remembers to register."
    ],
    "recommendation": "A. It keeps the wiring inside the engine lane. It resolves at use (the absent case is a defined deny state), and it adds no contract member."
    }
    ],
    "out_of_scope_findings": [
    "carrier: #23009 (domain:spec, pm:queue, unstarted) · noted, not filed. Measured on 9432bac: plugin-security's getReadFilter returns SPREAD COPIES of the deny sentinel at every top-level deny (security-plugin.ts:5851, :5867, :5889, :5900, :5927, each '{ ...RLS_DENY_FILTER }'), and the Layer 0 deny spreads too (tenant-layer.ts:175). Its own recognisers compare by value for exactly that reason (explain-engine.ts:178-187, security-plugin.ts:664-666 and :792). #23009's acceptance names only that 'the compiler emits the spec's object'. Unless those getReadFilter return sites also return the spec object BY REFERENCE, the identity recognition the ruling orders (sub-question 2 (i)) never matches against the real service. The failure stays closed, because the copied sentinel, passed as a scope, matches no row. But D2 step 2's '$in: []' arm would be dead code that only tests can reach. Dedupe words: RLS_DENY_FILTER spread copy getReadFilter identity sentinel.",
    "carrier: E2 re-dispatch (domain:engine seat 2) · noted, not filed. ADR-0139 D2 step 2's parenthetical says the scope is run through '(comparand doors, relation lowering, the shared temporal lowering)'. Measured: P's own direct read runs none of these over its POLICY scope. resolveWhereTokens runs before the chain, and engine.ts:12675-12678 states that injected RLS and sharing filters are not inspected. plugin-security ANDs the scope in its middleware and calls next() (security-plugin.ts:4246-4261). E2 will follow the binding words, 'lowered as P's own direct read lowers it': it passes getReadFilter's scope exactly as the direct read passes its injected scope, marked 'policy'. The record text is unchanged, because the ruling amends only the position sentence. Dedupe words: ADR-0139 D2 lowering policy scope parenthetical.",
    "carrier: E2 re-dispatch · noted, not filed. On update and delete, the pre-image reads hand opCtx.ast to a driver BEFORE the ruling's predicate-write lines: update at engine.ts:16133 before updateMany at :16992, delete at :18750 before deleteMany at :18835. Update and delete hooks receive no ast (their input is id, data and options), so the AST is final once the chain returns. E2's write refusal therefore belongs at the write executor's entry, after the chain and before the first driver contact. That is the same boundary, placed where 'before any driver sees it' holds. Not a fork."
    ],
    "gates": [
    {
    "pr": "record #23035 at d43e9a9",
    "derived": 19,
    "run": 19,
    "results": [
    "node scripts/check-adr-links.mjs :: exit 0",
    "node scripts/check-adr-links.mjs --self-test :: exit 0",
    "node scripts/check-adr-symbol-anchors.mjs :: exit 0",
    "node scripts/check-adr-symbol-anchors.mjs --self-test :: exit 0",
    "node scripts/check-ci-filter-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-corpus.mjs :: exit 0",
    "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
    "pnpm check:adr-anchors :: exit 0",
    "pnpm check:cross-package-test-inputs :: exit 0",
    "pnpm check:doc-authoring :: exit 0",
    "pnpm check:driver-memory-census :: exit 0",
    "pnpm check:gitlink-declared :: exit 0",
    "pnpm check:nul-bytes :: exit 0",
    "pnpm check:pm-governed-merges :: exit 0",
    "pnpm check:pm-prior-rulings :: exit 0",
    "pnpm check:refd-timer-probe :: exit 0",
    "pnpm check:watch-hint-literal :: exit 0"
    ],
    "note": "check:doc-formula-expressions first exited 3 (PREREQUISITE NOT MET); rerun exited 0 after building formula and lint under os-verify-lock (VERDICT command-exit 0). Reconcile: --ran reads 19/19, a DERIVED zero NOT-MEASURED."
    },
    {
    "pr": "code (a)",
    "derived": null,
    "run": 0,
    "results": [],
    "note": "NOT RUN: no code diff (the stop). The lead list dispatch-gates-v2.txt (68 commands) would only measure origin/main. The full @objectstack/objectql and @objectstack/core suites are not owed on an empty diff."
    },
    {
    "probe": "turbo cycle probe",
    "command": "pnpm exec turbo run build --filter=@objectstack/driver-sql --dry=json (with a temporary objectql devDependency)",
    "exit": 1,
    "note": "the measurement: Cyclic dependency detected; restored and hash-verified"
    },
    {
    "command": "os-verify-lock: pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2",
    "exit": 0,
    "note": "VERDICT command-exit 0, held 129s, waited 0s"
    }
    ],
    "line_budget": {
    "record PR #23035": "+14 / -7, 1 file (docs/adr only); under the 3,000-line human-merge threshold",
    "code PR": "0 (not opened)"
    },
    "deviations": [
    "The code PR (a) was not built. The dispatch's case-set clause ('if it must live elsewhere to be reusable, stop and report') and the claim's file surface ('stop on a breach') both stop it, and the measurement says the case-set must live in spec. I read the stop as covering (a) only, because (b) is a separate, fully ruled deliverable that does not depend on where the case-set lives. So the record PR #23035 was delivered. If the PM meant the stop to cover the whole card, #23035 can simply wait as a draft.",
    "Line positions: the dispatch expected them to have moved since 246edf5. Measured on 9432bac, they have not: find 13160/13162/13174/13178; findOne 13450/13452/13468/13471; count 18971/18983/18986; aggregate 19396/19430/19438, with driver paths at 19495 and 19539; updateMany 16992; deleteMany 18835; interim refusal at filter-comparand-shape.ts:120 via engine.ts:1107; the having door at 19305; aggregations filter door at 19168; filter-tokens.ts rebuild at 527 and 549.",
    "The record PR's status paragraph changes two verbs besides the status words ('becomes' to 'became', 'stays' to 'stayed'), because the paragraph described the time before acceptance. The PR body says so.",
    "skip-changeset is not applied on #23035, because the dispatch forbids label writes. The seat applies it, or Check Changeset stays red (it is not a required context).",
    "The code branch was fast-forwarded to 9432bac and pushed, following the dispatch's 'merge origin/main first' and the role file's push-before-edit. The resume point is now 9432bac, with zero commits of its own.",
    "Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. AGENTS.md's model-free trailer pair and its session-URL PR footer were used instead, because AGENTS.md takes precedence and the pre-push hook refuses a model identifier.",
    "The cycle probe edited packages/drivers/driver-sql/package.json in the worktree. It was restored by git checkout HEAD -- under a trap, hash-verified, and never committed.",
    "The worktree ../objectstack-issue-22861 is removed after this report is posted (node_modules first, then git worktree remove without --force). Both branches are on the remote."
    ],
    "files_changed": [
    "/home/user/objectstack-issue-22861/docs/adr/0139-engine-internal-semi-join-leaf-readable-parent-ids.md (record PR #23035 only)"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat answer to the dev's two open questions (6123413708). Both are execution parameters, answered in-seat, not escalated · domain:engine seat 2 · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T11:34Z

    The ruling (6121741984) stands untouched: the fork clause is not tripped. The dev measured that the AST is final at every read driver call, and D5's checks need no extra security call.

    Q1, the home of the composed form and the conformance case-set: A, packages/spec.

    • Governing text: ADR-0139 D3 reads "The leaf joins the per-driver conformance case-sets". Every per-driver case table lives in packages/spec/src/data/*-conformance.ts (aggregation, filter-comparand-type, filter-logic, filter-text, pagination, temporal, value-roundtrip), each run by a thin test per driver.
    • PD Add comprehensive test suite for Zod schema validation #12 makes spec the one producer/consumer contract. The composed form { object: P, scope } (D2 step 3) is the engine-to-driver shape that E3 and E4 must recognise.
    • The dev's cycle probe rules out the objectql home for driver-sql / driver-sqlite-wasm: turbo refuses with "Cyclic dependency detected" (objectql → metadata → driver-sqlite-wasm → driver-sql).
    • So this is spec-lane work, routed to spec: declare the RLS deny sentinel once in @objectstack/spec, imported by plugin-security and objectql (ADR-0139 D2 step 2; #22861 sub-question 2 ruled (i)) #23009 (domain:spec, unstarted, same directory, same consumer). A fold request is posted there. If the spec seat prefers a separate card, it files one and E2 waits on both.
    • E2 stays Clause-②: no and imports the composed form's constructor and recogniser and the case-set from spec.

    Q2, packages/objectql/src/plugin.ts: granted (A). ObjectQLPlugin hands the engine a resolver that reads ctx.getService('security') at use, never at init, with the absent or unusable case failing closed ($in: []). It is the same package and the same lane, and adds no contract member. Option B (plugin-security pushing the service in) is cross-lane and fails open when the plugin forgets.

    The findings:

    1. The deny sentinel is returned as spread copies (security-plugin.ts getReadFilter returns { ...RLS_DENY_FILTER } at five sites, and tenant-layer.ts:175 does the same), so identity recognition would never match the real service. Carrier: spec: declare the RLS deny sentinel once in @objectstack/spec, imported by plugin-security and objectql (ADR-0139 D2 step 2; #22861 sub-question 2 ruled (i)) #23009. The sentinel's spec home must come with those return sites returning the spec object by reference, or D2 step 2's $in: [] arm is dead code. Posted on spec: declare the RLS deny sentinel once in @objectstack/spec, imported by plugin-security and objectql (ADR-0139 D2 step 2; #22861 sub-question 2 ruled (i)) #23009.
    2. D2's lowering parenthetical (comparand doors, relation lowering, temporal lowering) does not describe what P's own direct read does with its policy scope. Carried into the re-dispatch: E2 follows the binding words "lowered as P's own direct read lowers it" and passes the scope as the direct read passes its injected scope, marked 'policy'. The record text is not touched beyond the ruled position sentence.
    3. The write refusal's position: the pre-image reads reach a driver before updateMany / deleteMany, so the refusal sits at the write executor's entry, after the chain and before the first driver contact. Carried into the re-dispatch. Not a fork, as the dev says.

    State:

  6. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT (Tier H, awaiting the maintainer): record PR #23035 at d43e9a99ed

    domain:engine seat 2 (#20966) · os-steve · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T11:47Z. This is the record half of ruling 6121741984 ("The record" paragraph). Report 6123413708. Checked on GitHub rather than taken from the report.

    • Form:
      • draft to main, assignee os-steve;
      • line 1 Part of #22861, with no closing keyword (the code PR completes the card);
      • line 2 Clause-②: no.
      • Labels: documentation, skip-changeset (docs only, nothing publishes).
    • Scope: one file, docs/adr/0139-engine-internal-semi-join-leaf-readable-parent-ids.md, +14 / −7, two hunks:
    • Governed: docs/adr/** is Tier H. Per the path fork: this ACCEPT is the review conclusion; the PR stays visibly with the maintainer (needs-user-decision on the PR, plus a final 维护者速读 comment); and review is requested from os-zhuang and hotlong. ⛔ The seat does not ready, queue or arm it.
    • Contract review: PASS on d43e9a99ed, record on the PR. The sentence matches the ruling element by element and names the real seam on origin/main. The rest of D2 is byte-identical. No sentence still places composition at lowerRelationConditions. The status facts match docs(adr): ADR-0139 — one engine-internal semi-join leaf, "the ids of object X this caller may read" (ruling A on #22590 item 3) #22707's approval (os-zhuang, 2026-10-11) and merge (a2e94c2a05).
    • CI on d43e9a99ed: 43 runs, all concluded. The required contexts are success, or skipped on a docs-only diff.

    Landing: the maintainer's APPROVED review lands it through the queue, by the seat's landing steps (pre-landing check, ready, auto-merge). Until then it waits, visibly.

  7. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Superseded (2026-10-12T13:07Z): the maintainer readied PR #23035 and queued it by hand at 2026-10-12T12:57Z, and the queue merged it as 2db2f8daea at 2026-10-12T13:05Z. Nothing is owed to the maintainer for it. The landing record follows on this card. The text below is kept as history.

    Landing stopped: PR #23035, refused by the seat session's permission classifier · domain:engine seat 2 · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T12:56Z

    • Approval on record: os-zhuang (a GOVERNED_APPROVERS account) APPROVED PR docs(adr): ADR-0139 reads Accepted, and D2 names the executor boundary as the composition seam (record half of the ruling on #22861) #23035 on d43e9a99ed at 2026-10-12T12:54Z (review 5491060949), and readied it by hand at the same minute.

    • Pre-landing checks, all passing:

      • CI on d43e9a99ed: every check is green or an expected skip, and Governed Surface Queue Guard is success;
      • check-governed-merges.mjs --pr 23035: Tier H, docs/adr/** ×1, 21 changed lines, an authorized approval present;
      • git merge-tree against origin/main: clean;
      • the body reads Part of #22861, so no card closes.
    • The seat's landing command:

      • scripts/pm/label-write.mjs --issue 23035 --remove needs-user-decision;
      • then the fleet relay op automerge_enable for pull 23035.

      This session's permission classifier refused it, with the reason "Merge Without Review". Nothing was written: the label is still on the PR, and auto-merge is not armed.

    • Per the landing rule (a classifier refusal: stop, report to the maintainer, record the command and the reason), the seat does not re-spell the command or route around it.

    The maintainer's one action: merge PR #23035 by hand (or click "Merge when ready"). A human merge is the review record. The alternative is to allow this seat's landing command in the session's permissions, and the seat then lands it. When it merges, this seat verifies the squash, posts the landing record here, and keeps #22861 pm:blocked on #23009 for the code half.

  8. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: record half, PR #23035 → 2db2f8daea · domain:engine seat 2 · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T13:06Z

  9. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer from #23009 for E2: what PR #23144 lands, what it does not, and three notes · domain:spec seat 3 (seat post #18883) · session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-13T03:32Z

    Lands in PR #23144 (draft, head 828798a9a). A patch round to the changeset text follows; then the contract-tier review and the queue. Everything below comes from @objectstack/spec/data:

    • RLS_DENY_FILTER. One frozen object, and the tree's only literal. plugin-security re-exports it under the same name and holds no copy. Every deny return that reaches getReadFilter's top level now returns the reference:
      • five returns in getReadFilter itself;
      • the tenant wall's deny;
      • the dropped-policy Layer 1 deny;
      • the unresolvable-master deny.
        14 toBe pins hold this, and two ablations turn them red.
    • ReadableIds.compose(leaf, scope), isComposedReadableIds and ComposedReadableIds. compose refuses RLS_DENY_FILTER, compared by identity. The recognisers are disjoint: isReadableIds answers false for a composed leaf.

    Not in it: the per-driver case-set. The seat ruled on #23009 that it lands with E3/E4's driver suites, not ahead of them. The pointer on #22788 has the parked commit and the gate's verdict. None of E2's plan pins reads a driver case table, so E2 no longer waits on it. Blocked-by: #23009 clears when the card closes, and PR #23144's merge closes it.

    Three notes for E2, measured by the #23009 dev. None is filed.

    1. A deny ANDed with another layer stays a composite. For an org-less caller on a private-sharing tenant object, getReadFilter returns { $and: [RLS_DENY_FILTER, sharingFilter] }. E2's identity arm misses it and composes a subquery that matches nothing. That is fail-safe, but it is not $in: [].
    2. Dual package. @objectstack/spec ships CJS and ESM builds, and esm.RLS_DENY_FILTER === cjs.RLS_DENY_FILTER is false. A process that loads spec through both entries misses the identity check across them. The miss is fail-safe, because the copy composes to a subquery that matches nothing. A process-global slot is not proposed, because any earlier code could pre-seed it.
    3. Sharing's own deny-all. getReadFilter can return plugin-sharing's { id: '__deny_all__' } unchanged. sharing-service.ts buildReadFilter produces it for a context with no userId, on a private object that has an owner field. It is not the sentinel, so E2 composes it as an ordinary scope (zero rows).

    Generated by Claude Code

  10. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    #23009 has landed (domain:spec seat 3, #18883 · session session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-13T05:57Z): PR #23144 → 785611000. This card's Blocked-by: #23009 is satisfied.

    What E2 can import from @objectstack/spec/data on main:

    • RLS_DENY_FILTER. Every deny that comes up through getReadFilter is this object, by reference.
    • ReadableIds.compose / isComposedReadableIds / ComposedReadableIds.
      • compose refuses the sentinel by identity.
      • The two recognisers are strictly disjoint. A hand-built value that carries both brands reads as uncomposed: isComposedReadableIds is false for it.

    The three notes in my earlier pointer on this card (6137591851) still apply: the composite deny, the dual-package identity miss and sharing's __deny_all__. The case-set is not in it; it rides E3/E4 (#22788).


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Release: session_01ADzJtzYTLUfgrRZHxkagkX (domain:engine seat 2) · cause: the maintainer narrowed this seat to the ADR-0131 epic (#15194) only · destination: pm:queue, unassigned, for any domain:engine seat · 2026-10-13T12:28Z
    Resume-from: claude/issue-22861-readable-ids-composition@9432bac077

    Provenance: the maintainer, in this seat's session session_01ADzJtzYTLUfgrRZHxkagkX, at 2026-10-13T12:24Z: 「你现在开始只处理和这个 epic 相关的卡片」. This card is ADR-0139 E2, not part of #15194.

    State handed over:

  12. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-13T12:42Z · re-claim after seat 2's release 6145561964, under ruling 6121741984 (letter A)
    Session: session_01P88ehrZ6hM19hBzdpsQTTo
    Account: os-support-ai
    Branch: claude/issue-22861-readable-ids-composition
    Worktree: objectstack-issue-22861
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface: packages/objectql/src/engine.ts at the executor boundary of each read verb (find, findOne, count, aggregate's driver paths), plus the refusal at the predicate writes and having. Also packages/objectql/src/plugin.ts, for the security resolver read at use (granted, 6123453723), and packages/core/src/utils/filter-tokens.ts, which returns a branded leaf by reference (ruled, sub-question 1). Tests go beside them, with a changeset. The dev stops on any other breach and explains it in the report.
    Container & model: M, mode:subagent, model: default (dispatch-gates --tier at 4f5679c261: no path-derived mandate)
    Clause-②: no
    Responsibility: n/a — not a defect card
    Thread-read: 6145561964
    Serial constraints cleared: no open PR touches engine.ts, plugin.ts or filter-tokens.ts at this read. The last engine.ts change on main is 466380132e. Seat 2's #23224 (ADR-0138 E1) holds security-plugin.ts's resolvePermissionSetsForContextUnmemoized and core's resolveAuthzContext (6145576431). Those are disjoint regions, and the later lander merges main. This seat's #23198 (metadata-protocol) and #23182 row 4 (metadata-fs) are disjoint. No path here is a SINGLE_CLAIM_PATHS entry. Read against origin/main 4f5679c261.

    Correction to the release's handover: the branch above is not a resume point with code on it. Its tip 9432bac077 is an ancestor of main: the earlier dev's report 6123413708 reads "code: held at the stop, fast-forwarded to 9432bac and pushed, no commits of its own". So E2's code half is built from main by this dispatch, using that report's measurements, ⛔ not resumed from a sha. No Resume-from: line is carried for that reason. The branch name is reused: the dev merges main into it first.


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 22861,
    "status": "done",
    "branch": "claude/issue-22861-readable-ids-composition",
    "pr": "#23301",
    "session": "session_01P88ehrZ6hM19hBzdpsQTTo (mode:subagent, so this is the dispatching PM session's harness-stamped id)",
    "premise_still_valid": true,
    "summary": "Built ADR-0139 E2 under ruling A, on the merged head 693b450. The fork clause was not tripped. I re-measured that the AST is final at each read verb's driver call: find and findOne after beforeFind and buildDriverOptions; count immediately before driver.count; aggregate once, for its two exclusive driver paths. Every ReadableIds leaf in a read verb's where is replaced, immediately before the driver call, by ReadableIds.compose(leaf, scope) or by $in: []. The scope is getReadFilter(P)'s answer, by reference and marked 'policy'. The answer is $in: [] when canReadObject (or explain's allowed) is false or throws, when getReadFilter answers RLS_DENY_FILTER by identity or throws, when the security service is absent or unusable, or when the parent is not registered. Each parent costs one canReadObject and one getReadFilter per read, and nothing is memoised. Refused INTERNAL_ERROR / 500 by name, before any driver is asked: D5's three classes (a delegated caller, a parent with an object-scoped read middleware, a federated parent), decided before any security call; update and delete (the caller's leaf at the comparand door, a middleware's at the executor entry, before the prior-row reads); having and an aggregation's filter; any position but a $in comparand (the where array branch now asks the same rule); and a composed leaf the engine did not compose. ObjectQLPlugin.init hands the engine a source that reads ctx.getService('security') at use. core's resolveFilterTokens returns the leaf by reference. The interim refuseUncomposedReadableIds is gone. Clause-② stays 'no' per the ruling. Note for the PM: the engine class gains one public method, ObjectQL.registerSecurityServiceSource, which is not an IObjectQLEngine member. files_changed: 13 (see files_changed). line_budget: +1106 / -116. gates: 79 derived, 78 run at exit 0, 1 NOT MEASURED. deviations: 7 (see deviations).",
    "tests": "All on 693b450 unless stated; each heavy run went through os-verify-lock. (1) Builds: turbo build of @objectstack/objectql and its dependency closure, VERDICT command-exit 0 (14 tasks). (2) typecheck: pnpm --filter @objectstack/objectql typecheck (tsc plus the test layer, 40 files / 234 errors / 65 pinned signatures, unchanged) and pnpm --filter @objectstack/core typecheck, both exit 0. (3) core local suite: 95 files, 2402 tests passed. (4) objectql local suite, in four chunks to fit the foreground cap: 106/2233 + 100/1954 + 107/1621 + 100/2252 = 413 files and 8060 tests passed, plus the repo project (1 file, 5 tests), on 87545e5. packages/objectql is byte-identical at 693b450; that merge moved only core's security and tenancy modules and two spec data schemas. After it, the focused set (10 files, 211 tests) passed and both typechecks passed again. (5) New pins: engine-readable-ids-composition.test.ts (23 cases), plugin-readable-ids-security-source.test.ts (1), engine-readable-ids-door.test.ts rewritten for E2 (7), and one case in core's filter-tokens.test.ts. Every refusal pin asserts code and status, plus the named position or class. (6) Ablations through scripts/ablation-replace.mjs (WRAP mode: the anchor must hit; the restore is proven blob == HEAD with git diff HEAD empty; an outer handler restores from HEAD on any exit or signal; every batch ended with a clean porcelain). 22 src ablations, each with its target pin red: the find/findOne/count/aggregate boundary calls (16/4/4/6 failed), the deny-sentinel arm, the canReadObject arm, the absent-service fail-closed, the 'policy' mark, the update and delete entry refusals, having at the boundary, the three D5 classes, the unregistered parent, the array-branch rule, once-per-parent, the forged composed leaf, the position arm, the door's write-verb arm, the plugin source, and core's by-reference line. Plus 1 dist-resolved ablation: core's by-reference line, rebuilt, with ablation-dist-preflight showing the marker ABSENT from packages/core/dist; the engine-level 'beside a resolved placeholder' pin failed (1 failed); then restored, rebuilt, preflight showed the marker present in dist/index.js, and the pin passed. The first count-boundary ablation was a no-op that the tool refused (its replacement was already present 7 times); it was rerun with a unique replacement and went red. (7) Gates: dispatch-gates --commands --repo objectstack-ai/objectstack derived 79 commands, the same list on 87545e5 and 693b450. All were run on 693b450, with exit codes captured before any pipe: 78 exit 0, the ratchet families included (query-options-erasure, 506 s; slot-lookup, 222 s; type-check-coverage; type-check-debt, re-measured in 607 s). check:dual-build-cjs-loads exited 3, PREREQUISITE NOT MET (it needs every package's dist/), so it is NOT MEASURED and left to CI. dispatch-gates --ran reads: 79 derived, 78 run, 1 NOT-MEASURED (derived from the recorded exit 3), 0 UNRUN. CI on the PR: in_progress at report time, not waited on.",
    "mcp_calls": "0",
    "api_writes": "2 relay strokes. (i) pr_create through scripts/pm/fleet-write/dispatch.mjs, run 38432052663: POST /repos/objectstack-ai/objectstack/pulls (draft), then POST /repos//issues/23301/assignees ['os-support-ai']. Read-back: 13540 bytes sent, 13540 stored, identical. (ii) This os-dev-report comment: POST /repos//issues/22861/comments through scripts/pm/post-stamped.mjs. Zero label writes: the dispatch names no label, and skip-changeset does not apply because the diff publishes and carries a changeset. The size/xl label on the PR is the size labeler's. Not REST writes: 8 git pushes of the branch (a fast-forward to main as the write-route probe, WIP commits, two merges of origin/main). REST reads (gh api, single card): issue 22861 and its comments, issue 22788 and its comments, pulls/23301.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none · noted, not filed. resolveFilterTokens (packages/core/src/utils/filter-tokens.ts) exempts only Date among class instances when it rebuilds a tree that holds a placeholder, although its comment says 'Dates and other class instances are comparands'. A Map or other class-instance comparand beside a placeholder would be rebuilt as a plain object. This is a read-only inference, not reproduced, and no public door carries a class instance. Recorded in the PR's Acceptance notes. Dedupe words: resolveFilterTokens class instance comparand placeholder rebuild."
    ],
    "gates": [
    "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
    "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
    "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
    "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
    "node scripts/check-ci-filter-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-adoption.mjs :: exit 0",
    "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-corpus.mjs :: exit 0",
    "node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
    "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
    "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
    "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
    "node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
    "node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
    "node scripts/check-issue-citations.mjs :: exit 0",
    "node scripts/check-keyed-text-bounds.mjs :: exit 0",
    "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
    "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
    "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
    "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
    "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
    "node scripts/check-registry-log-declared.mjs :: exit 0",
    "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
    "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
    "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
    "node scripts/check-scripts-symbol-anchors.mjs :: exit 0",
    "node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0",
    "node scripts/check-system-context-census.mjs :: exit 0",
    "node scripts/check-system-context-census.mjs --self-test :: exit 0",
    "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
    "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
    "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
    "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
    "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
    "node scripts/release-pending-publish.mjs --self-test :: exit 0",
    "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
    "pnpm check:adr-anchors :: exit 0",
    "pnpm check:agent-test-spelling :: exit 0",
    "pnpm check:bash32-floor :: exit 0",
    "pnpm check:changeset-gate-self-tests :: exit 0",
    "pnpm check:cli-command-ids :: exit 0",
    "pnpm check:cross-package-test-inputs :: exit 0",
    "pnpm check:dispatcher-error-vocabulary :: exit 0",
    "pnpm check:doc-authoring :: exit 0",
    "pnpm check:driver-memory-census :: exit 0",
    "pnpm check:dts-closure :: exit 0",
    "pnpm check:dual-build-cjs-loads :: exit 3",
    "pnpm check:durability-log-level :: exit 0",
    "pnpm check:engine-double-contract :: exit 0",
    "pnpm check:entry-guard :: exit 0",
    "pnpm check:error-status-conformance :: exit 0",
    "pnpm check:gitlink-declared :: exit 0",
    "pnpm check:issue-citations :: exit 0",
    "pnpm check:kernel-hook-pairs :: exit 0",
    "pnpm check:lean-entry-closure :: exit 0",
    "pnpm check:logger-receiver-detach :: exit 0",
    "pnpm check:nul-bytes :: exit 0",
    "pnpm check:objectql-double-limit :: exit 0",
    "pnpm check:objectui-changeset :: exit 0",
    "pnpm check:org-identifier :: exit 0",
    "pnpm check:page-declaration-shape :: exit 0",
    "pnpm check:parse-guard :: exit 0",
    "pnpm check:pm-changeset-deadline-census :: exit 0",
    "pnpm check:pnpm-filter-targets :: exit 0",
    "pnpm check:published-files :: exit 0",
    "pnpm check:query-options-erasure :: exit 0",
    "pnpm check:refd-timer-probe :: exit 0",
    "pnpm check:slot-lookup :: exit 0",
    "pnpm check:sourcemap-no-sources-content :: exit 0",
    "pnpm check:stack-collection-maps :: exit 0",
    "pnpm check:swallow-census-controls :: exit 0",
    "pnpm check:test-source-alias :: exit 0",
    "pnpm check:tier-file-adoption :: exit 0",
    "pnpm check:type-check-coverage :: exit 0",
    "pnpm check:type-check-debt :: exit 0",
    "pnpm check:watch-hint-literal :: exit 0",
    "pnpm check:where-matcher :: exit 0"
    ],
    "line_budget": "+1106 / -116 across 13 files (git diff origin/main...HEAD at 693b450); under the 3,000-line human-merge threshold",
    "deviations": [
    "File surface: beyond the claim's engine.ts, plugin.ts and filter-tokens.ts, the diff touches (a) packages/objectql/src/filter-comparand-shape.ts, which holds the interim refusal the ruling moves to the boundary and is in the original claim's surface that the ruling re-uses; (b) scripts/adr-anchors/packages__objectql__src__filter-comparand-shape.ts.json, whose invariant text described the removed interim refusal (check:adr-anchors exit 0); (c) three test doubles of the engine (plugin-authored-actions, plugin-authored-hooks, plugin-publish-announce), which gain registerSecurityServiceSource because ObjectQLPlugin.init now calls it.",
    "The dispatch asked for a merge of main just before the PR. I merged twice (87545e5, 693b450) and opened the PR on 693b450. main then gained 3 commits (metadata-protocol, rest, plugin-auth tests, the spec api protocol schema). None touches packages/objectql, packages/core or any path of this diff, and git merge-tree against them is clean. I did not merge them, so every reading in this report stays on one head; CI runs on the merge ref.",
    "The first count-boundary ablation was a no-op, refused by ablation-replace (the replacement count could not rise). It was rerun with a unique replacement and went red.",
    "A first check:type-check-debt run, killed by my runner's timeout, left this worktree's packages/spec/dist without .d.ts files. That made core's DTS phase fail during the dist ablation; the JS output it measures still landed, as the preflight on dist/index.js shows. I restored the spec and core dist through a turbo build before any later measurement, and the final type-check-debt run (exit 0) completed.",
    "Attribution: commits carry AGENTS.md's model-free trailer pair, and the PR body ends with AGENTS.md's session-URL footer, in place of the harness reminder's model-named Co-Authored-By and its footer form. AGENTS.md takes precedence, and the pre-push hook refuses a model identifier.",
    "The objectql local suite was run in four chunks, because the whole suite exceeds the foreground cap (500 s timed out). The chunks ran on 87545e5, where packages/objectql is identical to the PR head.",
    "The dispatch says #22861 has 17 comments. The REST read (per_page=100) returned 16, from 6119321139 to 6145796666, and all 16 were read. The newest Claim: comment, 6145796666, names this branch.",
    "The worktree /home/user/objectstack-issue-22861 is removed after this report is posted, node_modules first and then git worktree remove without --force."
    ],
    "files_changed": [
    "/home/user/objectstack-issue-22861/packages/objectql/src/engine.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/filter-comparand-shape.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/plugin.ts",
    "/home/user/objectstack-issue-22861/packages/core/src/utils/filter-tokens.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/engine-readable-ids-composition.test.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/plugin-readable-ids-security-source.test.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/engine-readable-ids-door.test.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/plugin-authored-actions.test.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/plugin-authored-hooks.test.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/plugin-publish-announce.test.ts",
    "/home/user/objectstack-issue-22861/packages/core/src/utils/filter-tokens.test.ts",
    "/home/user/objectstack-issue-22861/.changeset/22861-readable-ids-composition.md",
    "/home/user/objectstack-issue-22861/scripts/adr-anchors/packages__objectql__src__filter-comparand-shape.ts.json"
    ]
    }


    Generated by Claude Code

  14. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    PM direction: patch round on PR #23301 after the contract review's FAIL · domain:engine seat 1 · session_01P88ehrZ6hM19hBzdpsQTTo · 2026-10-13T16:57Z

    The contract review (6150749585, on PR #23301 at 693b450c90) found one defect: Clause-②: no is declared on a diff that adds a public member, ObjectQL.registerSecurityServiceSource, to a class both exports entries publish. That widens the public surface. Every other derived judgment, the semver level and every boundary flag were judged right.

    The remedy this seat takes: arm (ii). The seam leaves the published surface, and Clause-②: no stays, as ruling 6121741984 and the seat answer 6123453723 set it.

    • Why arm (ii), not arm (i):

      • The member exists only so that ObjectQLPlugin.init can hand the engine a source.
      • No consumer outside the package needs it.
      • The package already carries this exact shape: the symbol-keyed engine slot SKIP_SCHEMA_SYNC_ENGINE_KEY in plugin.ts.
      • Publishing a wiring seam nobody else calls would widen the surface for nothing.

      This is an implementation choice inside the ruling, not a reopening of it.

    • What changes:

      • The engine reads its source from a symbol-keyed slot, or from a module export of engine.ts that neither index.ts nor core.ts re-exports. Each existing behaviour stays: read at use, never at init; unregistered fails closed; last registration wins.
      • The three test doubles follow.
      • The changeset drops its "New: ObjectQL.registerSecurityServiceSource" bullet. Its bump stays as the diff then publishes, re-judged by the next record.
      • The PR body's Clause-②: no line stays, and so does the claim (6145796666).
    • Optional: the review noted that the unregistered-parent deny arm is the one arm that logs nothing. Not required. If added, it uses the same one-warn-per-state shape.

    • Then: the derived gates are re-run on the new head, CI goes green, and a fresh contract-review record is posted on the new head before the queue.


    Generated by Claude Code

  15. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 22861,
    "status": "done",
    "branch": "claude/issue-22861-readable-ids-composition",
    "pr": "#23301",
    "session": "session_01P88ehrZ6hM19hBzdpsQTTo (mode:subagent, so this is the dispatching PM session's harness-stamped id)",
    "premise_still_valid": true,
    "summary": "Patch round on PR #23301, after contract review 6150749585 (FAIL, section 2) and seat direction 6150767225 (arm ii). The new head is 586d7b5, which merges origin/main as db671d9; the PR stays a draft. (1) The defect is fixed and Clause-② stays 'no'. ObjectQL.registerSecurityServiceSource is gone. ObjectQLPlugin.init now writes its source, () => ctx.getService('security'), into a symbol-keyed engine slot. The slot key, SECURITY_SERVICE_SOURCE_SLOT = Symbol.for('objectstack.objectql.securityServiceSource'), is a module export of engine.ts that neither index.ts nor core.ts re-exports, and it is not a member of ObjectQL. Symbol.for is used rather than a module-local Symbol because each entry bundles the engine separately: the slot key is present once in each of dist/index.mjs, dist/index.js and dist/core.mjs. Behaviour is unchanged. The engine reads the slot at use, never at init. An empty slot, a source that answers nothing, a source that throws and a half-capable service all fail closed to $in: [], and a slot holding a non-function does too (new case in the existing pin). Assignment means the last registration wins. The three engine test doubles (plugin-authored-actions, plugin-authored-hooks, plugin-publish-announce) go back byte-identical to main, because the slot write needs no method on a double. plugin-readable-ids-security-source.test.ts needed no change: it drives ObjectQLPlugin.init, which now writes the slot. Surface proof after the build: registerSecurityServiceSource, SECURITY_SERVICE_SOURCE_SLOT and securityServiceSource each have 0 hits in dist/index.d.ts, dist/core.d.ts, dist/index.d.mts, dist/core.d.mts and the shared chunk dist/util-BZBGGHtf.d.ts / .d.mts, which both entries import ObjectQL from. Positive controls in that chunk: registerEffectiveObjectPermissionsResolver 1 hit, registerWriteGateProbe 2. The index and core entry files themselves carry 0 hits for either control, because the class is declared in the chunk. The engine's private helpers appear there by name only, untyped, as every private member of ObjectQL does (private readonly readableIdsSecurityReported; private resolveReadableIdsSecurity; private composeReadableIdsAtBoundary). (2) CI red at 693b450, Test Core (3/6), reproduced locally with pnpm --filter @objectstack/core run test:repo: 1 failed / 82 passed. The failing test is src/security/row-serving-door-exposure.pin.test.ts, 'every caller of the read admission is classified, and every classified file still calls it'. Its first error line: 'AssertionError: These sources ask the read admission themselves, which is how a door that serves rows outside the data routes looks, and this pin does not know them: - packages/objectql/src/engine.ts'. Cause: the composition calls svc.canReadObject(parent), and that census holds every non-test caller of canReadObject to a classification. The census has no --write mode; its remedy is a row in CALLERS. Fix: engine.ts is classified 'not-a-door' with its reason (it asks a PARENT's admission only to compose the scope of a $in leaf that narrows the read the caller addressed, whose own door already admitted it; no row or field of the parent is served; a refused parent answers $in: []). No test is skipped, deleted or loosened. After the fix, core test:repo has 6 files and 83 tests passed, and objectql test:repo has 1 file and 5 tests passed. The CI shard Test Core (2/6) was cancelled at 693b450, so nothing about it is known locally. (3) Changeset: the 'New: ObjectQL.registerSecurityServiceSource' bullet is dropped. The no-service condition now says that the engine reads the kernel's security service through ObjectQLPlugin, so an engine used without that plugin has none. Every other sentence was re-checked against the diff and still holds. @objectstack/objectql stays minor on the behaviour alone: find, findOne, count and aggregate now serve a query they refused 500 on main (a ReadableIds $in leaf, composed into a subquery), which is a new engine capability rather than a fix. @objectstack/core stays patch. (4) The optional log on the unregistered-parent arm was not added, because it is optional and not required. PR body edits the seat owes (the dev writes the body once only): in the 'Wired' paragraph, replace registerSecurityServiceSource(() => ctx.getService('security')) with 'writes a source, () => ctx.getService('security'), into the engine's symbol-keyed slot SECURITY_SERVICE_SOURCE_SLOT (a Symbol.for key, not a member of ObjectQL, re-exported by neither entry)'; drop the Acceptance-notes bullet 'One new public method'; add to Tests the core census row (row-serving-door-exposure.pin.test.ts classifies engine.ts) and the slot-read ablation below. files_changed: 11 (see files_changed). line_budget: +1121 / -115. gates: 79 derived, 78 run at exit 0, 1 NOT MEASURED. deviations: 3 (see deviations).",
    "tests": "All on 586d7b5; each heavy run went through os-verify-lock with OS_VERIFY_LOCK_SLOT=dev-22861 and was bounded by a timeout. (1) Build: turbo build of @objectstack/objectql and its closure, VERDICT command-exit 0 (14 tasks). The objectql dist was built at 358ffeb, whose packages/objectql is identical to 586d7b5. (2) typecheck: pnpm --filter @objectstack/objectql typecheck, exit 0 (test layer 40 files / 234 errors / 65 pinned signatures, unchanged); pnpm --filter @objectstack/core typecheck, exit 0 (4 / 4 / 4, unchanged). (3) Repo projects: core test:repo 6 files, 83 tests passed (it was 1 failed / 82 passed before the census row); objectql test:repo 1 file, 5 tests passed. (4) Focused suites: objectql engine-readable-ids-composition, engine-readable-ids-door, plugin-readable-ids-security-source, plugin-authored-actions, plugin-authored-hooks and plugin-publish-announce: 6 files, 60 tests passed. core filter-tokens: 1 file, 52 tests passed. (5) Ablation of the new seam through scripts/ablation-replace.mjs (WRAP mode, plus an outer trap that restores from HEAD). In engine.ts, the slot read was replaced with a read of the old public-method field, _securityServiceSource, which nothing writes. The mutation landed (anchor 1 -> 0; blob 42b13d8193ae -> 64fc927f5d86). The run went red: 11 failed / 13 passed, including the plugin source pin 'a security service registered after init composes the next read', 'the security service is read AT USE', and every composing pin. Restore proven: blob == HEAD (42b13d8193ae), git diff HEAD empty, porcelain clean. (6) Gates: dispatch-gates --commands --repo objectstack-ai/objectstack on 586d7b5 derived 79 commands, the same list as before. All 79 were run on 586d7b5, with exit codes captured before any pipe: 78 exit 0, including the ratchets (query-options-erasure 484 s, slot-lookup 256 s, type-check-coverage, type-check-debt re-measured in 802 s). check:dual-build-cjs-loads exited 3, PREREQUISITE NOT MET (it needs every package's dist/), so it is NOT MEASURED and left to CI's Lint & Repo Gates. dispatch-gates --ran: 79 derived, 78 run, 1 NOT-MEASURED (derived from the recorded exit 3), 0 UNRUN. CI on 586d7b5: not read, not waited on.",
    "mcp_calls": "0",
    "api_writes": "1 relay stroke this round: this os-dev-report comment, POST /repos//issues/22861/comments through scripts/pm/post-stamped.mjs. No PR body edit (see summary for the edits the seat owes), no label write. Not REST writes: 3 git pushes (db671d9 merging main, 358ffeb, 586d7b5), fast-forward only, with no rebase, amend or force. REST reads (gh api): comments 6150749585 and 6150767225, and the check-runs of 693b450.",
    "open_questions": [],
    "out_of_scope_findings": [],
    "gates": [
    "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
    "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
    "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
    "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
    "node scripts/check-ci-filter-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-adoption.mjs :: exit 0",
    "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-corpus.mjs :: exit 0",
    "node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
    "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
    "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
    "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
    "node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
    "node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
    "node scripts/check-issue-citations.mjs :: exit 0",
    "node scripts/check-keyed-text-bounds.mjs :: exit 0",
    "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
    "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
    "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
    "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
    "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
    "node scripts/check-registry-log-declared.mjs :: exit 0",
    "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
    "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
    "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
    "node scripts/check-scripts-symbol-anchors.mjs :: exit 0",
    "node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0",
    "node scripts/check-system-context-census.mjs :: exit 0",
    "node scripts/check-system-context-census.mjs --self-test :: exit 0",
    "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
    "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
    "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
    "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
    "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
    "node scripts/release-pending-publish.mjs --self-test :: exit 0",
    "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
    "pnpm check:adr-anchors :: exit 0",
    "pnpm check:agent-test-spelling :: exit 0",
    "pnpm check:bash32-floor :: exit 0",
    "pnpm check:changeset-gate-self-tests :: exit 0",
    "pnpm check:cli-command-ids :: exit 0",
    "pnpm check:cross-package-test-inputs :: exit 0",
    "pnpm check:dispatcher-error-vocabulary :: exit 0",
    "pnpm check:doc-authoring :: exit 0",
    "pnpm check:driver-memory-census :: exit 0",
    "pnpm check:dts-closure :: exit 0",
    "pnpm check:dual-build-cjs-loads :: exit 3",
    "pnpm check:durability-log-level :: exit 0",
    "pnpm check:engine-double-contract :: exit 0",
    "pnpm check:entry-guard :: exit 0",
    "pnpm check:error-status-conformance :: exit 0",
    "pnpm check:gitlink-declared :: exit 0",
    "pnpm check:issue-citations :: exit 0",
    "pnpm check:kernel-hook-pairs :: exit 0",
    "pnpm check:lean-entry-closure :: exit 0",
    "pnpm check:logger-receiver-detach :: exit 0",
    "pnpm check:nul-bytes :: exit 0",
    "pnpm check:objectql-double-limit :: exit 0",
    "pnpm check:objectui-changeset :: exit 0",
    "pnpm check:org-identifier :: exit 0",
    "pnpm check:page-declaration-shape :: exit 0",
    "pnpm check:parse-guard :: exit 0",
    "pnpm check:pm-changeset-deadline-census :: exit 0",
    "pnpm check:pnpm-filter-targets :: exit 0",
    "pnpm check:published-files :: exit 0",
    "pnpm check:query-options-erasure :: exit 0",
    "pnpm check:refd-timer-probe :: exit 0",
    "pnpm check:slot-lookup :: exit 0",
    "pnpm check:sourcemap-no-sources-content :: exit 0",
    "pnpm check:stack-collection-maps :: exit 0",
    "pnpm check:swallow-census-controls :: exit 0",
    "pnpm check:test-source-alias :: exit 0",
    "pnpm check:tier-file-adoption :: exit 0",
    "pnpm check:type-check-coverage :: exit 0",
    "pnpm check:type-check-debt :: exit 0",
    "pnpm check:watch-hint-literal :: exit 0",
    "pnpm check:where-matcher :: exit 0"
    ],
    "line_budget": "+1121 / -115 across 11 files (git diff origin/main...HEAD at 586d7b5); under the 3,000-line human-merge threshold",
    "deviations": [
    "File surface: this round adds packages/core/src/security/row-serving-door-exposure.pin.test.ts (one CALLERS row), the CI fix the PM's addendum ordered. The three engine test doubles leave the diff and are byte-identical to main again.",
    "main moved 4 commits after the db671d9 merge (cli, plugin-auth, plugin-security, pm scripts). None touches packages/objectql, packages/core or a path of this diff, and git merge-tree is clean, so the branch was not re-merged and every reading here stays on 586d7b5.",
    "The PR body still names registerSecurityServiceSource. The dev writes the body once only, so the edits are listed in the summary for the seat to make."
    ],
    "files_changed": [
    "/home/user/objectstack-issue-22861/packages/objectql/src/engine.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/filter-comparand-shape.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/plugin.ts",
    "/home/user/objectstack-issue-22861/packages/core/src/utils/filter-tokens.ts",
    "/home/user/objectstack-issue-22861/packages/core/src/security/row-serving-door-exposure.pin.test.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/engine-readable-ids-composition.test.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/plugin-readable-ids-security-source.test.ts",
    "/home/user/objectstack-issue-22861/packages/objectql/src/engine-readable-ids-door.test.ts",
    "/home/user/objectstack-issue-22861/packages/core/src/utils/filter-tokens.test.ts",
    "/home/user/objectstack-issue-22861/.changeset/22861-readable-ids-composition.md",
    "/home/user/objectstack-issue-22861/scripts/adr-anchors/packages__objectql__src__filter-comparand-shape.ts.json"
    ]
    }


    Generated by Claude Code

  16. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    PM ACCEPT: PR #23301 at 586d7b57ff (ADR-0139 E2) · domain:engine seat 1 · session_01P88ehrZ6hM19hBzdpsQTTo · 2026-10-13T18:29Z

    I checked against GitHub and the diff, not the reports (6150445600, then the patch round 6152188732).

    • PR form:

      • draft, base main;
      • line 1 is Fixes #22861 and line 2 is Clause-②: no;
      • the assignee is os-support-ai;
      • 11 files, +1121 / −115;
      • check-governed-merges: NOT governed.
    • The contract review, at CONTRACT_REVIEW_TIER, in an isolated reviewer:

      • First record 6150749585 (693b450c90): FAIL, on one defect. ObjectQL.registerSecurityServiceSource was a public member of a class both exports entries publish, so it widened the public surface under a Clause-②: no.
      • The seat chose arm (ii) (6150767225): the seam leaves the published surface, and no stays as ruled.
      • Second record 6152392261 (this head): PASS, with no defect.
        • The seam is now SECURITY_SERVICE_SOURCE_SLOT, a Symbol.for key written through a cast. It is re-exported by neither entry, and its name has 0 hits in every built .d.ts, against two lit controls.
        • Clause-②: no is right on both limbs.
        • The semver levels match: objectql minor, core patch.
        • All 13 boundary flags are answered.
    • The CI red at 693b450c90 (Test Core (3/6), core test:repo) was the row-serving census in row-serving-door-exposure.pin.test.ts. engine.ts now calls canReadObject for a parent.

      • The remedy is one CALLERS row classifying engine.ts not-a-door, with its reason: the engine asks a parent's admission only to scope a $in leaf narrowing a read whose own door admitted it, and it serves no parent row.
      • The classification is true; the record judged it in ① 17. ⛔ No test was skipped or loosened.
    • The changeset, read sentence by sentence against the head:

      • the title;
      • "Composed" with its four $in: [] conditions; the no-service condition now names the ObjectQLPlugin wiring;
      • "Refused" with its five arms;
      • "Before";
      • the @objectstack/core by-reference sentence;
      • "Not yet".

      All hold. The "New: registerSecurityServiceSource" bullet is gone.

    • The PR body: the seat edited it on the dev's list (the Wired paragraph names the slot; the "One new public method" note is gone; the patch-round evidence is under Tests). The live body was read back byte-identical.

    • Docs drift (report-only): its 21 pages are found by generic anchors (ObjectQL, the read verbs). No documented behaviour changes today: no driver evaluates the composed leaf before E3/E4, and nothing in the platform writes a ReadableIds leaf (the changeset's "Not yet"). Not verified page by page.

    • CI on this head: the latest run of all 40 names is success or an expected skip. check-expected-skips --pr 23301 is OK (8, all in the roster), and mergeable_state is clean.

    • Out of scope: resolveFilterTokens exempts only Date among class instances: Acceptance notes, no carrier (a read-only inference, not reproduced).

    Landing: pr_ready + automerge_enable through the relay, now. On merge, this seat verifies by content, and #22788 records E2 as landed. Under the maintainer's p1-only scope order, E3/E4 (p2) are not cut by this seat.


    Generated by Claude Code

  17. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23301 → 87e902dcbb · domain:engine seat 1 · session_01P88ehrZ6hM19hBzdpsQTTo · 2026-10-13T19:03Z

    • Merge: the merge queue merged it at 87e902dcbb, after ACCEPT 6152415222 and contract review PASS 6152392261. Fixes #22861 closed this card completed.
    • Checked on origin/main by content:
      • composeReadableIdsAtBoundary: 6 hits;
      • SECURITY_SERVICE_SOURCE_SLOT: 3 hits;
      • registerSecurityServiceSource: 0 hits, so it is off the published surface as arm (ii) required;
      • the census row classifying objectql/src/engine.ts is in row-serving-door-exposure.pin.test.ts (1).
    • Labels: pm:dispatched is removed in this stroke; the domain, area and type labels stay.
    • The parent, ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788: E2 is recorded there as landed. E3/E4 (p2) are the next rows, and this seat does not cut them under the maintainer's p1-only scope order.
    • Acceptance notes, not filed: resolveFilterTokens exempts only Date among class instances (a read-only inference, not reproduced).

    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:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landenhancementNew feature or requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions