Filed unassigned by the #13552 dev (PR #13570) while measuring that card's PM mechanism assumption 1 ("does fixing only the guard leave the analytics lowering path independently reachable?"). Recording only — severity and routing are triage's. #13552 is not resolved by this card and this card does not wait on it.
What was measured, on f7347eb
packages/services/service-analytics/src/read-scope-sql.ts compileOperator:
Why it did NOT block #13552, and what keeps it from biting today
In-repo, the scope input to this compiler is StrategyContext.getReadScope, wired through security.getReadFilter — i.e. the RLS compiler's output. After #13552 (PR #13570) the RLS path drops any policy whose compiled filter leans on an emptied membership at inverted polarity, so neither shape reaches this lowering through RLS. The CEL pushdown compiler also never emits $nin.
Why it is still worth a look
StrategyContext.getReadScope is a spec contract (packages/spec/src/contracts/analytics-service.ts), fillable by any provider; its own doc shows a hand-written example. A non-RLS provider handing this compiler { $not: { f: { $in: [] } } } or { f: { $nin: [] } } gets a scope clause that is TRUE for every row — on the READ-SCOPE lowering, where a wrong answer is ADR-0021 scope over-reach, not a loose chart filter.
- The module's stated posture is fail-closed ("it throws rather than drop a predicate"). Folding an emptied membership to constant TRUE is the one spot where the emptied case widens instead of throwing or narrowing.
- Possible dispositions for triage to weigh: treat an emptied membership at widening polarity as a compile refusal (matches the module's fail-closed posture); or fold it null-safely and document the invariant that only fail-closed producers may feed this compiler; or explicitly declare the producer contract (RLS-guard-filtered input only) and pin it.
Related
#13552 (the guard-side repair; PR #13570) · #5297 (closed — $not null-safety and $not: {} at this same site) · #5298 (no-value semantics) · ADR-0021 (read-scope contract) · ADR-0055 / ADR-0058 (pushdown contract)
Filed unassigned by the #13552 dev (PR #13570) while measuring that card's PM mechanism assumption 1 ("does fixing only the guard leave the analytics lowering path independently reachable?"). Recording only — severity and routing are triage's. #13552 is not resolved by this card and this card does not wait on it.
What was measured, on
f7347ebpackages/services/service-analytics/src/read-scope-sql.tscompileOperator:$inwith an empty array folds toFALSE_CLAUSE(1 = 0) with the comment "IN () matches nothing — safe" — a polarity-DEPENDENT inference sitting one arm away from the$notlowering that inverts it (NOT (1 = 0)is TRUE for every row). This is the exact inference The RLSisEmptyMembershipFilterdeny guard is polarity-blind: an emptied membership under a supportednot incompiles to allow-all instead of the deny sentinel #13552 repaired at the RLS guard, still standing verbatim at the lowering site.$ninwith an empty array folds directly to1 = 1("NOT IN () excludes nothing") — constant TRUE with no negation needed at all.Why it did NOT block #13552, and what keeps it from biting today
In-repo, the scope input to this compiler is
StrategyContext.getReadScope, wired throughsecurity.getReadFilter— i.e. the RLS compiler's output. After #13552 (PR #13570) the RLS path drops any policy whose compiled filter leans on an emptied membership at inverted polarity, so neither shape reaches this lowering through RLS. The CEL pushdown compiler also never emits$nin.Why it is still worth a look
StrategyContext.getReadScopeis a spec contract (packages/spec/src/contracts/analytics-service.ts), fillable by any provider; its own doc shows a hand-written example. A non-RLS provider handing this compiler{ $not: { f: { $in: [] } } }or{ f: { $nin: [] } }gets a scope clause that is TRUE for every row — on the READ-SCOPE lowering, where a wrong answer is ADR-0021 scope over-reach, not a loose chart filter.Related
#13552 (the guard-side repair; PR #13570) · #5297 (closed —
$notnull-safety and$not: {}at this same site) · #5298 (no-value semantics) · ADR-0021 (read-scope contract) · ADR-0055 / ADR-0058 (pushdown contract)