Repository navigation
[finding] service-analytics read scope: compileScopedFilterToSql applies no whole-day upper bound and binds a temporal comparand as written, so an RLS $lte on a bare day drops the rest of that day in NativeSQL analytics #20733
Description
Activity
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p2·domain:services·area:reports·pm:on-hold. Held on the maintainer's ruling on #5930: the convergence's first cut closes this face structurally, and a per-face fix now would be one more copy that cut deletesTriage: lands in
packages/services/service-analytics/src/read-scope-sql.ts⇒domain:services.Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T00:03Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2. It takes #20661's grade (p2): valid input answering the wrong rows, measured at a published export under the same reach standard.
- An RLS
usingbound on a bare day hides that day's rows in NativeSQL analytics, while CRUD reads and the policy's owncheckadmit them. - It fails closed (rows are hidden, not leaked), so it is not a security card.
- No in-repo policy writes such a bound.
Why held, not queued. #5930's decision request (
5900897393) names this card as one of the three live divergences its option B closes. The shared lowering at the seams carries the whole-day bound, including the last day, to every face, and the read scope inherits it.- Queued now, this card would add an eleventh copy of the whole-day call, which B's per-face deletion cards then remove.
- The hold lasts only until the maintainer rules, and the ruling is already asked.
Restart-when: the maintainer rules #5930.
- Under A or B, this card is folded into the first seam card (shared lowering at the engine / RLS seam) and closed as folded, with its pins carried there.
- Under C, it is dispatched as it stands, with the scope below.
Scope, if dispatched under C.
- The read-scope compiler follows ADR-0053 D-D1's whole-day rule and D-A1's temporal coercion, in the order D-E3 fixes: widen the bare day first, then convert the bound.
- [finding] nextUtcCalendarDay('9999-12-31') answers '10000-01-01', so on SQLite a datetime $lte '9999-12-31' or a $between maximum on that day answers no rows #20600's last supported day holds.
- Pins: the card's four-row table, answering the same rows as
SqlDriver.findon a declareddatetime; the last-day row; a declareddatecontrol.
- An RLS
- addedarea:reportsBusiness reporting — dashboards, reports, the numbers a manager readsBusiness reporting — dashboards, reports, the numbers a manager readsbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Sep 30, 2026 objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsTriage: restart check — #5930 is ruled (A's end state in B's order, v18). This card now waits for the convergence's analytics-face seam card, and is folded into it when that card is filed
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T01:59Z. ⛔ Not a claim, ⛔ not a dispatch.The
Restart-when:of this card's grade (5901385131) is met: the maintainer ruled #5930 (5902355785). The ruling fixes the order of work:- the ADR-0053 D-D1 amendment;
- the engine / RLS seam lowering;
- the analytics-face seams, plus the F5 / F11 vocabulary;
- per-face deletions.
This face, the analytics read-scope compiler, is closed by step 3's lowering, which carries the whole-day bound and the last day.
Nothing is dispatched now. The step-3 card doesn't exist yet, and folding into an unfiled card isn't possible. So the hold stays, with a narrower condition:
Restart-when: the #5930 program files its step-3 card (the analytics-face seams). That card carries this card's pins (the four-row table, the last-day row, the declared
datecontrol), and this card is closed as folded into it.The grade is unchanged (p2). The pointer is on #5930 for the
domain:engineseat.objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsClosed as folded into #20810 (#5930 step 3), with its pins carried there
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T10:05Z. ⛔ Not a claim, ⛔ not a dispatch.This card's hold named the convergence's analytics-face seam card (
5902581313). That card is #20810, graded today. Its grade carries this card's pins:- the four-row table, for an RLS
usingbound and for a caller filter, againstSqlDriver.find; - the last-day row;
- the declared
datecontrol.
The defect is fixed there, at the shared seam, ⛔ not by a per-face copy here.
- the four-row table, for an RLS
- added 4 commits that reference this issue
on Oct 7, 2026
Filing gate: ① a product defect with a measured
reach:. Finding class (b).reach:is the published exportcompileScopedFilterToSqlof@objectstack/service-analytics, which is the RLS / tenant scope merged into the NativeSQL statement (native-sql-strategy.ts:668) and into the/analytics/sqlecho (objectql-strategy.ts:578). The #5930 investigation measured it by probe at3711e0b763(os-dev-report5900611736 on #5930,out_of_scope_findings[0]; PR #20732's design doc §2.5 item 1). No HTTP door was measured. That is the same reach standard #20661 was filed and graded on. The readings are the dev's; the seat read the named code onorigin/main.Filed by the
domain:engineexecution seat 1 (session_01DEvba2nBuD4tWzfq8r8NFY,os-support-ai). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim. Reader: triage, then the seat of the lane that ownsservice-analytics.What happens
compileScopedFilterToSql({t: {$lte: '2026-07-28'}}, 't')compiles to"t"."t" <= ?and binds'2026-07-28'unchanged. The probe executed it on SQLite over ISO-text rows2026-07-27T10:00Z,2026-07-28T10:00Z,2026-07-29T10:00Zandnull:compileScopedFilterToSql)SqlDriver.find, column declareddatetimeformulamatchesFilterCondition(the RLScheckon writes)So one RLS
usingpolicy with a bare-day upper bound would hide the named day's rows in NativeSQL analytics, while CRUD reads and the policy's owncheckadmit them. The dev found no in-repo policy that writes a bare-day bound. PostgreSQL and MySQL were not measured.Contract
NativeSQLStrategytoday, and any future raw-SQL strategy) must coerce through the driver's dialect-aware temporal coercion".< next-day). Its emitter list does not include the read-scope compiler.origin/main:packages/services/service-analytics/src/read-scope-sql.tscallsnextUtcCalendarDay0 times.Scope for whoever takes it
9999-12-31means "has a value").SqlDriver.findon a declareddatetime; the last-day row; and a declareddatecontrol. WiringTEMPORAL_CASESthrough this face is the design doc's suggestion (§4.2), not a gate.Dedupe
search_issuesinobjectstack-ai/objectstack, open and closed:2026-02-30) is rolled over as a datetime comparand and is a 500 on PostgreSQL as a date comparand, and a non-ISO datetime comparand is read in the host zone #20549 (the temporal comparand door). None is the read scope's temporal bound: read-scope-sql compiles$eq: [...]in a policy scope ascol = ?with the array bound (read-scope-sql.ts:1273) — outside ruling 乙's shared face; a bare array fails closed as 500, not 400 #19975, service-analytics: the NativeSQL read-scope compiler and the/analytics/sqlecho compile two scope shapes the shared comparand faces refuse (plain-object comparand under$eq, null member in$in): one scope, two answers across faces #20018 and service-analytics: the NativeSQL execute face and the /analytics/sql echo bind a read-scope filter placeholder ({current_user_id}, an unknown {token}) as a literal string, where the ObjectQL face resolves it — one scope, different rows across faces #20075 are read-scope comparand and placeholder cards, all closed.Dedupe words:
read-scope-sql whole-day lte·compileScopedFilterToSql temporal coercion·RLS read scope bare day datetimeGenerated by Claude Code