You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
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):
a delegated (on-behalf-of) caller;
a parent narrowed by an object-scoped read middleware (sys_attachment, sys_comment, sys_comment_reaction, sys_approval_request, and the two gated objects);
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.
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:engineseat 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 leafReadableIdsinpackages/spec/src/data/readable-ids.ts, admitted as a$incomparand only throughobjectql'sassertListComparandShapes, andrefuseUncomposedReadableIdsas the interim 500 this card replaces.Reader who acts:
domain:engineseat 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
ReadableIdsleaf by its composed form, before any driver sees it.canReadObject(P, caller)false, a throw, or no such service ⇒$in: [].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).{ object: P, scope }. The tenant wall is the driver's (D3), never re-derived here.ISecurityServiceon 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):sys_attachment,sys_comment,sys_comment_reaction,sys_approval_request, and the two gated objects);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):
canReadObjectfalse ⇒ 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)
ObjectQL.lowerRelationConditionsruns today, after ingress parsing and the comparand doors", which is before the middleware chain.ctx.ast.whereinside the chain (plugin-audit'sinstallActivityReadVisibilitythroughregisterMiddleware), andbeforeFindhooks 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.lowerWhereFilterArraylowers throughparseFilterAST, 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 nameshaving.Duplicate check
objectstack buildskips the runtime module ("all N callables are body-only") on a stack that carries a callable at the top level and in apackages[i].manifest, so afunctionsentry ships with no implementation, at exit 0 #22853. A local grep over titles and bodies.ReadableIds: 3 (thedomain:specseat post, ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788, and feat(spec): ADR-0139 E1 — theReadableIdsbranded comparand leaf, declared in spec and unspellable by a request #22787, closed).refuseUncomposedReadableIds: 0.ADR-0139: 5, all seat posts, the parent or E1.ADR-005512.Generated by Claude Code