Repository navigation
ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788
Copy link
Copy link
Open
Labels
area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:engineenhancementNew feature or requestNew feature or requestpm:blockedpriority:p2Medium: important, M3Medium: important, M3
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: #22861
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). 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 ispm:blockedon 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 (domain:spec). - E3, the SQL family (D3):
driver-sqlcompiles the leaf inapplyTenantScope; wasm and local Turso inherit; the remote transport compiles it. After E2. - E4, memory and mongodb (D3): memory implements it; mongodb refuses with the envelope. With E3.
- 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. - E6,
changed_fields(D4): the column, the writer stamp, the backfill migration, and the rule as a predicate. After E5. - E7, comments, reactions and attachments (D4): the three gates switched. After E5.
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.
Generated by Claude Code
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:engineenhancementNew feature or requestNew feature or requestpm:blockedpriority:p2Medium: important, M3Medium: important, M3
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