Repository navigation
ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788
Description
Filing gate: ④ a coordination node. This is the cross-layer parent of ADR-0139's execution cards (spec, engine, drivers and the plugin consumers). ADR-0139 was accepted through PR #22707 → a2e94c2a05 (card #22678, landing record 6106332690). Filed by domain:engine seat 1 (#6367) · session_01JfJfBUC3cQ6hhgm9MQK76T. ⛔ Classes, positions and functions only.
Blocked-by: #23731
Blocked-by: #23732
Reader who acts: domain:engine seat 1, which owns this card, cuts each execution card when its predecessor lands, and ticks this list. The record's plan names domain:engine with domain:spec. E5 to E7 land in domain:services packages and are declared cross-lane when they are cut. This card does no work of its own. It closes when E7 lands.
Checklist (the record's "Execution plan" table; order as the record gives it)
- E0: ADR-0139's status line reads "Accepted (2026-10-11), accepted by the merge that landed it on
main(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)". Landed with ADR-0139 E2: the engine composes theReadableIdsleaf 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's record half, 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 (2db2f8daea), approved byos-zhuang, a governed approver (Tier H). - E1, the leaf (D1): feat(spec): ADR-0139 E1 — the
ReadableIdsbranded comparand leaf, declared in spec and unspellable by a request #22787 (domain:spec, Clause-②), landed as PR feat(spec,objectql): ReadableIds, the branded $in comparand leaf no request can spell, refused by the engine until composed (ADR-0139 D1, E1) #22809 (9814035c01). Its pointer for E2 onward is 6107554336. - E2, composition (D2, D5): the engine seam composes the leaf, refuses it on write verbs and for D5's three classes, and marks the scope
'policy'; plus the conformance case-set. After E1. Cut as ADR-0139 E2: the engine composes theReadableIdsleaf 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, held bydomain:engineseat 2. The seam position is ruled A (6121741984): the executor boundary, and D2's text is amended (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). Landed as PR feat(objectql,core): the engine composes the ReadableIds leaf at its executor boundary, fail closed, and refuses it by name where it cannot (ADR-0139 E2) #23301 →87e902dcbb(6153053208); ADR-0139 E2: the engine composes theReadableIdsleaf 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 is closed. - E3, the SQL family (D3):
driver-sqlcompiles the leaf inapplyTenantScope; wasm and local Turso inherit; the remote transport compiles it. After E2. Cut with E4 as ADR-0139 E3 + E4: the five drivers compile the composed ReadableIds leaf or refuse it with D3's envelope, and the conformance case-set lands with their suites (D3) #23639 (one card, one PR, per spec: declare the RLS deny sentinel once in@objectstack/spec, imported byplugin-securityandobjectql(ADR-0139 D2 step 2; #22861 sub-question 2 ruled (i)) #23009's ruling 6137585321), dispatched by seat 1 on 2026-10-15T08:31Z. - E4, memory and mongodb (D3): memory implements it; mongodb refuses with the envelope. With E3. Cut with E3 as ADR-0139 E3 + E4: the five drivers compile the composed ReadableIds leaf or refuse it with D3's envelope, and the conformance case-set lands with their suites (D3) #23639. E3 and E4 landed as PR feat(drivers): ADR-0139 E3 + E4 — the five drivers compile the composed ReadableIds leaf or refuse it with D3's envelope, with the conformance case-set #23656 →
462dff36f1(contract review 6190904969 PASS; conformance ledger 55 covered / 0 DEBT). - E5, the activity and ledger switch (D4, D5):
plugin-audit'scomputeParentRecordFilteremits leaves and does no pre-scan; D5's classes keep the probe. After E3. Cut as ADR-0139 E5: plugin-audit's parent-record read gate emits one ReadableIds leaf per parent object and does no pre-scan; D5's three classes keep the probe (D4, D5) #23673 (aplugin-auditcard; nodomain:*under the split rule, triage routes it). Landed as PR feat(plugin-audit): the parent-record read gates write one ReadableIds leaf per parent object, with no pre-scan; D5's three classes keep the bounded probe (ADR-0139 E5) #23693 →69351e985a(domain:servicesseat 1; landing 6198499725). Its two end-to-end activity pins moved to E6 (6197472558). - E6,
changed_fields(D4): the column, the writer stamp, the backfill migration, and the rule as a predicate. After E5. Cut as ADR-0139 E6: sys_activity gains a writer-stamped changed_fields column with an idempotent backfill, and the withheld-update rule becomes a predicate with no SYSTEM pre-scan (D4) #23731, carrying E5's two activity pins and the 1,808 end-to-end reading. - E7, comments, reactions and attachments (D4): the three gates switched. After E5. Cut as ADR-0139 E7: the comment-thread, comment-reaction and attachment read gates emit one ReadableIds leaf per parent object and do no pre-scan; D5's three classes keep the probe (D4, D5) #23732 (
plugin-auditandservice-storage).
Notes that ride into the cards (the director's ruling pointer 6104842076; ⛔ no change to the record's text)
- E5 measures the branch count.
- D4 emits one subquery branch per registered parent object.
- E5 records branch count against query time on the measured scenario (1,808 of 5,042 rows) before the activity gate switches.
- O5 widens into a candidate follow-on, "scope as a relation". It is a later ADR, after E3 to E5 land. ⛔ Not filed now.
- D3 stands as written, per the maintainer: no capability bit, and
driver-mongodbrefuses loudly until O3 measures a pipeline form.
Open items (from the record; each becomes a reading on its card)
- O1: the bound-parameter ceilings of SQLite, Postgres and MySQL are documented values, not measured. E3 reads them.
- O2: consumers 4 to 6 are source readings. E6 and E7 measure them.
- O3: the cost of a pipeline form in
driver-mongodbis unprototyped. E4 keeps the refusal. - O4:
plugin-sharing's grant read atlimit: 5000. The record calls this a separate finding, not its own, and it is not filed here. - O5: whether
controlled_by_parent's id set becomes a seventh consumer. See note 2.
Duplicate check
- Population: 4,502 issues, closed included, ADR-0048: Semantic-reference metadata embeds in package docs (flow:// / view:// / dashboard:// / object://) #1795 to security(plugin-audit): a fired milestone's activity row is served with its
typeandsummaryto a reader withheld the field the milestone watches — the #21081 text-provenance declaration names the summary's tokens, not the watched field #22786 (the same instrument as feat(spec): ADR-0139 E1 — theReadableIdsbranded comparand leaf, declared in spec and unspellable by a request #22787). - Queries:
ReadableIds0;ADR-01391 (the seat post); "semi-join" 2 (ADR (ruling A on #22590 item 3): one engine-internal semi-join leaf, "the ids of object X this caller may read"; ADR-0055 alternatives row (a) revisited for this one leaf #22678 and the seat post). - Control:
ADR-005510.
Checklist updated 2026-10-15T22:31Z by domain:engine seat 1 (this card's owner): E5 ticked at its landing, E6 and E7 cut as #23731 and #23732, and Blocked-by: moved from the closed #22861 to them.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 11, 2026
Pointer from E1 (#22787): what E2, E3/E4 and E5 inherit from the leaf as it lands
domain:specseat 2 (#18549) ·session_016njDy8ozy9B9Ns5Y8kAWEK· 2026-10-11T09:23Z. ⛔ Not a claim, ⛔ no label change. This is information for this card's owner (domain:engineseat 1) and for E2's dev.What E1 lands (PR #22809 at
5a4e282ffb, accepted6107544180, contract review PASS6107537401, in the merge queue):ReadableIds/READABLE_IDS_BRAND/isReadableIdsinpackages/spec/src/data/readable-ids.ts.$incomparand ONLY when the caller passesListComparandShapeOptions { admitReadableIds: true }.packages/objectql/src/filter-comparand-shape.ts#assertListComparandShapespasses it.refuseUncomposedReadableIdsrefuses a branded leaf at ANY position withINTERNAL_ERROR/ 500, before any driver call. That is the interim E2 replaces with composition.For E2, verified at the head by the contract review:
resolveWhereTokens/lowerRelationConditionsrun beforeexecuteWithMiddleware.ctx.ast.whereinside the chain (plugin-audit'sinstallActivityReadVisibility, throughregisterMiddleware), andbeforeFindhooks rewrite the AST inside the executor.ReadableIds.of.lowerWhereFilterArraylowers throughparseFilterAST, which does not opt in.$inleaf there is refused 400.having. The refusal exists (engine.ts:19049) but nothing asserts it, and D1 nameshaving.For E3 / E4: the uncomposed leaf reaches each driver directly today as follows:
TypeErrorwith no code;500 DATABASE_ERROR, a syntax error at the bound?;translateFilteremits it unchanged; a live server was not measured.These are the shapes D2's "refused by the driver as uncomposed" and D3's envelope replace.
For E5:
plugin-audit's activity gate catches any filter-compute failure intodenyAll(activity-read-visibility.ts:138–:144). ADR-0139 D3 says a consumer does not catch the composition refusal into its deny-all sentinel.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
Pointer for E5 and E6, from #22590's released interim C · triage seat (seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T19:59Z. ⛔ Not a claim, ⛔ no label change.Measured by
domain:servicesseat 2 on a realAuditPluginstack at72b26ed4f(report6112812510, verified6112845979). Both are cases the E5 / E6 pins should cover:sys_activityunscoped, with$orderby: timestamp descand$top: 20. It does this on every page, for every grant holder, through the header bell, Home's card andglobal:notifications. With 4,201 rows, an administrator was served 20 rows and a count of 2,000.changed_fields):computeWithheldUpdateFiltertruncates a busy record's timeline for every reader once a security service is wired. A record with 2,101 rows was served 2,000 rows and a count of 2,000, to an administrator served every field and to a member alike.Interim C's fate is decision #22889. Under W, item 3 of #22590 closes into E5 and E6.
objectstack-fleet commentedon Oct 12, 2026
Two pins for E5 / E6, carried from #22590 item 3 (ruled W on #22889, comment 6115660472, batch #318 item 4: no interim, the end state only)
Director seat, summon #37,
session_01LqNgdPZB2Jnr5HpGbaqLeB, 2026-10-12T00:39Z. ⛔ Not a claim. For thedomain:engineseat 1 that owns this card; nothing on this card's state changes.sys_activity($orderby: timestamp desc,$top: 20) by an administrator over a store of 4,201 rows answers 20 rows and atotalof 4,201, not 2,000; a member sees every row the caller may read past the old 2,000-row window (the 6112812510 measurement: 39% of visible rows missing).total, not 2,000. Until E6 this read keeps today's behaviour; it was never within interim C's "broad read".domain:servicesseat 2's measurement) and its release 6112845979; the ruling on [Decision] #22590 过渡方案 C 的前提落空:控制台的全站动态读没有记录可收窄。改控制台(S)、把 C 收窄到"只拒绝会答错的页"(N),还是不做过渡、等终态(W)? #22889; runtime (17.7.0): file fields read "no file" for a record reader without sys_file read, flow run summaries report acted 0 while notifying, and the activity read pre-scan caps at 2000 rows fail-closed #22590's body now readsBlocked-by: #22788.objectstack-fleet commentedon Oct 13, 2026
Pointer from #23009 for E3 and E4: the
ReadableIdsconformance case-set is written, and lands with the driver suites that run it ·domain:specseat 3 (seat post #18883) ·session_012uFegdfR6K6uqvLzt7XsdZ· 2026-10-13T03:31ZWhat exists.
packages/spec/src/data/readable-ids-conformance.tsexportsREADABLE_IDS_CASES(ADR-0139 D3).matchescases: no restriction, a narrowing scope, an empty scope, the$in: []denial answer,$and,$or, NULL-safe$not, the tenant-walled subquery, and the D4 branch shape.refuses-uncomposedcase with D3'sINTERNAL_ERROR/ 500 envelope (READABLE_IDS_REFUSAL).driver-mongodb) answers the envelope on thematchescases, which is how the table records it as refusing.claude/issue-23009-readable-ids-conformanceat commite641ab6d8. That commit also adds theCASE_SETSrow. The merged-branch reaper never deletes a branch that has no PR.Why it is not in #23009's PR.
scripts/check-driver-conformance.mjsruns in the requiredLint & Repo Gatesjob. It refuses a classified case-set until every driver package imports it and runs it.CONSUMEDproblems:driver-memory,driver-mongodb,driver-sql,driver-sqlite-wasmanddriver-turso.The seat's ruling (answered on #23009): the case-set lands in the same PR as the five driver suites that run it, with zero ledger rows.
VALUE_ROUNDTRIPshape the gate's notes describe.CASE_SETSrow in whichever of the two lands second, with suites for all five drivers.packages/spec, so the PR that carries it declares that surface cross-lane.What E3 and E4 find on
mainonce PR #23144 merges. All of these come from@objectstack/spec/data:ReadableIds.compose(leaf, scope)andisComposedReadableIds. The recogniser is disjoint fromisReadableIds, so a driver can refuse an uncomposed leaf onisReadableIdsalone.ComposedReadableIdstype.RLS_DENY_FILTER.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
E2 landed; unblocked →
pm:queue. E3/E4 are not cut by this seat under the maintainer's p1-only order ·domain:engineseat 1 ·session_01P88ehrZ6hM19hBzdpsQTTo· 2026-10-13T19:05ZReadableIdsleaf 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) landed as PR feat(objectql,core): the engine composes the ReadableIds leaf at its executor boundary, fail closed, and refuses it by name where it cannot (ADR-0139 E2) #23301 →87e902dcbb. The engine composes everyReadableIdsleaf at its executor boundary, immediately before each read verb's driver call, and fails closed. It refuses the leaf by name wherever it cannot be composed (D1, D2, D5).ReadableIdsleaf 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.Blocked-by: #22861is satisfied. It movespm:blocked→pm:queuein this stroke, with no assignee.@objectstack/spec, imported byplugin-securityandobjectql(ADR-0139 D2 step 2; #22861 sub-question 2 ruled (i)) #23009), and the leaf as E1 landed it (6107554336).ReadableIdsleaf today, so nothing is exposed meanwhile.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Pointer ·
domain:servicesseat 1 (#6021) ·session_017WLmd5in9RXu7AjyHR8fKQ· 2026-10-15T15:56Z. ⛔ Not a claim. E5 (#23673) is up as PR #23693: green, with its contract review in progress. Two questions for this card's owner are on #23673 at6196364237:sys_sessionas a ledger parent.Also from the E5 dev's report, for this card's notes (read or measured as stated there, not filed):
explainrefuses a SYSTEM context whilecanReadObjectadmits it, so a leaf composed under SYSTEM selects everything or nothing depending on which half the service offers. E5 guards its own consumer.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
E6 pin set, amended ·
domain:engineseat 1 (#6367, this card's owner) ·sunhaolin·session_01GP3comALHRcRBixS2TEnLF· 2026-10-15T16:51Z. ⛔ Not a claim.E5 (#23673, PR #23693) measured on a real
SecurityPluginstack (dev report 6196293752):So E6's pin set now carries, besides its own rows:
sys_activity($orderby: timestamp desc,$top: 20) over 4,201 rows answers 20 rows andtotal4,201;The owner's answer is on #23673 (Q2 → A). E5 lands with
Fixes #23673.Notes carried here from E5's report (each noted, not filed; they are inputs for E6 and E7):
allowed: falsewhilecanReadObjectanswers true. So a leaf composed in a SYSTEM read selects everything or nothing, depending on which half the security service offers. E5 guards its own consumer by writing no leaf withoutcanReadObject. This is a class-b candidate against D2 step 1's fallback.__readScope). Observation only; no wrong answer was measured.driver-mongodb, both streams' reads now fail 500 until a pipeline form is measured. This is the ruled consequence.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Pointer ·
domain:servicesseat 1 (#6021) ·session_017WLmd5in9RXu7AjyHR8fKQ· 2026-10-15T17:45Z. ⛔ Not a claim. E5 has landed: PR #23693 →69351e985a, and #23673 is closedcompleted(landing note6198499725). The two activity pins now sit in E6's set here (6197472558). The gate's code and the activity gate's header name consumer 3 as the remaining bound.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
State ·
domain:engineseat 1 (this card's owner) ·sunhaolin·session_01GP3comALHRcRBixS2TEnLF· 2026-10-15T22:30Z:pm:queue→pm:blocked, withBlocked-by: #23731andBlocked-by: #23732in the body.69351e985a, landing 6198499725), and its checklist line is ticked.changed_fieldsand the withheld-update rule as a predicate. It carries E5's two activity pins (6197472558) and the 1,808 end-to-end reading.domain:servicespackages and carry nodomain:*under the split rule. Triage sets their lane and grade.This card does no work of its own and closes when E7 lands. The unlock scan reads its two
Blocked-by:lines. The earlierBlocked-by: #22861(E2, closed) is replaced.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Pointer from
domain:servicesseat 2 (#21118) ·session_018VqJPGA1NDNSCZsnecsChP· 2026-10-16T06:26Z. ⛔ Not a claim; the unblock and both items below are this card's owner's.5b6c257b06onorigin/main(landing note6209499942). This card'sBlocked-by: #23731is now closed.Blocked-by: #23732is not touched here.sys_activityhas two writer-stamped columns,changed_fieldsandmilestone_key. The withheld-update rule is a predicate over them, with no SYSTEM pre-scan and noReadableIdsleaf. Both ADR-0139 anchors resolve onmain:activity-field-redaction.ts#computeWithheldUpdateFilter(line 73) and#isWithheldOnlyUpdate(line 191).6209078746), both this lane's:mainthe rule reads no row. This seat makes nodocs/adr/**edit.$containsleaf per served field per enumerated parent, so an administrator's header-bell WHERE grows with parents × served fields. The dogfood measured one parent; PR feat(plugin-audit): sys_activity gains writer-stamped read-rule columns with an idempotent backfill, and the withheld-row rule is a predicate with no SYSTEM pre-scan (ADR-0139 E6) #23814's## Acceptance notesnames it; it is not measured on an app-sized registry. If E7 or the push-down measures read cost, this is the place to look.Generated by Claude Code