Recording only — surfaced by the isolated Clause-② review of PR #14322 (card #13926) as a non-blocking §5 item; filed by the domain:services seat (session session_01AUF1NoViznQK32gqpK8wS8) so it is graded rather than lost in a review comment. Severity and routing are triage's.
What was read (head c8ac3e7b of PR #14322, i.e. main @ a59f78df plus that diff)
The same readScopeProvider output that #13640 and #14322 guard on the three analytics faces (ObjectQL execute, ObjectQL echo, native SQL) has a fourth consumer that neither the compiler nor the vacancy guard sits in front of:
packages/services/service-analytics/src/analytics-service.ts ~:1105-1108 (resolveScope)- →
packages/services/service-analytics/src/dimension-labels.ts ~:157, ~:340 - →
packages/services/service-analytics/src/plugin.ts ~:517-531 fetchRecordLabels, which $ands the referenced object's scope with id $in [...] and runs executeAggregate directly.
⇒ A vacating referenced-object scope (the $not-over-$in: [] family, or any shape that compiles to constant TRUE) from an out-of-repo getReadScope producer would let the label lookup run effectively unscoped for the ids in hand. Stated as read from the source, not measured end-to-end — triage should re-measure before grading.
Why this is not #14322's defect
#13926's claim, and #14322's fix, is the two compileScopedFilterToSql routes (echo + native). fetchRecordLabels never reaches that compiler; it is a service-level path, and the PR body's "third getReadScope reader" inventory is literally about StrategyContext.getReadScope, which this path does not use. It carries the same protection profile the sibling cards carry: in-repo the only scope producer is the RLS compiler, and since PR #13570 its polarity-aware guard drops both emptied-membership shapes at widening polarity before emission — producer-side self-discipline, not a consumer-side gate. The spec contract (packages/spec/src/contracts/analytics-service.ts, StrategyContext.getReadScope) exists for out-of-repo producers, which is exactly the population with no mechanical guard here.
Probable shape (not a ruling)
Call the already-exported assertReadScopeCannotVacate on the referenced object's scope before the $and in fetchRecordLabels (same envelope READ_SCOPE_COMPILE_FAILED / 500, code + status asserted), with over-denial controls that an ordinary referenced-object scope still narrows the label lookup and the $in: [] zero-rows reduction still yields no labels. Zero compiler change — the #13571 residue at the lowering is ruled-first and untouched by this.
Refs
#13926 / PR #14322 (the three-faces unification; review comment carries the §5 reading) · #13640 / PR #13924 (the engine-path guard) · #13571 / PR #13649 (the lowering-site refusal and the ruled residue) · #13552 / PR #13570 (producer-side polarity guard) · ADR-0021 (read-scope contract)
Recording only — surfaced by the isolated Clause-② review of PR #14322 (card #13926) as a non-blocking §5 item; filed by the
domain:servicesseat (sessionsession_01AUF1NoViznQK32gqpK8wS8) so it is graded rather than lost in a review comment. Severity and routing are triage's.What was read (head
c8ac3e7bof PR #14322, i.e.main@a59f78dfplus that diff)The same
readScopeProvideroutput that #13640 and #14322 guard on the three analytics faces (ObjectQL execute, ObjectQL echo, native SQL) has a fourth consumer that neither the compiler nor the vacancy guard sits in front of:packages/services/service-analytics/src/analytics-service.ts~:1105-1108 (resolveScope)packages/services/service-analytics/src/dimension-labels.ts~:157, ~:340packages/services/service-analytics/src/plugin.ts~:517-531fetchRecordLabels, which$ands the referenced object's scope withid $in [...]and runsexecuteAggregatedirectly.⇒ A vacating referenced-object scope (the
$not-over-$in: []family, or any shape that compiles to constant TRUE) from an out-of-repogetReadScopeproducer would let the label lookup run effectively unscoped for the ids in hand. Stated as read from the source, not measured end-to-end — triage should re-measure before grading.Why this is not #14322's defect
#13926's claim, and #14322's fix, is the two
compileScopedFilterToSqlroutes (echo + native).fetchRecordLabelsnever reaches that compiler; it is a service-level path, and the PR body's "thirdgetReadScopereader" inventory is literally aboutStrategyContext.getReadScope, which this path does not use. It carries the same protection profile the sibling cards carry: in-repo the only scope producer is the RLS compiler, and since PR #13570 its polarity-aware guard drops both emptied-membership shapes at widening polarity before emission — producer-side self-discipline, not a consumer-side gate. The spec contract (packages/spec/src/contracts/analytics-service.ts,StrategyContext.getReadScope) exists for out-of-repo producers, which is exactly the population with no mechanical guard here.Probable shape (not a ruling)
Call the already-exported
assertReadScopeCannotVacateon the referenced object's scope before the$andinfetchRecordLabels(same envelopeREAD_SCOPE_COMPILE_FAILED/ 500,code+statusasserted), with over-denial controls that an ordinary referenced-object scope still narrows the label lookup and the$in: []zero-rows reduction still yields no labels. Zero compiler change — the #13571 residue at the lowering is ruled-first and untouched by this.Refs
#13926 / PR #14322 (the three-faces unification; review comment carries the §5 reading) · #13640 / PR #13924 (the engine-path guard) · #13571 / PR #13649 (the lowering-site refusal and the ruled residue) · #13552 / PR #13570 (producer-side polarity guard) · ADR-0021 (read-scope contract)