ADORSYS-GIS / ADORSYS-GIS/lightbridge-authz
[Story]: Per-grain query APIs with source as a dimension (grain guard; closes the scope-ownership and multi-scope gaps)
- Dominant language
- Rust
- Stars
- 0
- Forks
- 1
- Avg merge
- 6h 42m
- Merged PRs (30d)
- 246
Description
Parent epic: #581
### Story Statement
As a dashboard consumer, I query each telemetry grain through its own closed, typed endpoint with `source` as a first-class filter and group-by — and a query that would mix grains does not typecheck.
### Real Intent
Extend the ADR-0022 closed-query contract from one endpoint over one table to one endpoint per grain, making the audit's F3 failure (optional grain filter defaulting to off ⇒ every KPI double-counts) structurally impossible, and closing #570/#578 while the surface is open.
### Background and Context
`/usage/v1/usage/query` today: four-value scope enum, eight optional exact-match filters, eight-value group-by enum, double-gated bucket interval, `FROM usage_events` hardcoded. governance#183 names this contract the shape to copy; governance#187 demands the grain guard ("preferably unrepresentable in the request type"); governance#186 lists the execution-grain drill-downs; governance#188 demands NULL cost survive to the wire.
### Source of truth (links)
ADR-0027 Decision 5 · ADR-0022 · governance#183 · governance#186 · governance#187 · governance#188 · #570 · #578
### Acceptance Criteria
1. Per-grain endpoints (`requests`, `executions`, `facts`, `seats` query routes), each with its own request type; no shared table/grain parameter exists — cross-grain aggregation is unrepresentable. Each endpoint declares its authoritative table in exactly one place.
2. `source` added as filter + group-by on every grain; enum-or-bind-bound like every other field (no free-text SQL fragment).
3. #570 closed: `scope_id` ownership verified — a caller reading another tenant's scope gets 403, not data; two-tenant integration test proves it.
4. #578 closed: multi-scope (`scope_ids`) or owner-scoped queries, ownership-checked per item; the LIMIT-drops-newest-buckets bug fixed (order or window such that truncation never silently drops the most recent buckets).
5. NULL money survives serialization as `null` (never 0, never omitted); aggregates report unknown-count alongside totals; no `unwrap_or(0)` on any monetary path (governance#188).
6. The budget spend-reader contract (`/usage/v1/spend/query`, `Option` micro-USD) is byte-stable — its existing integration tests still pass unmodified.
7. OpenAPI updated for every new route; contract tests assert publication.
### Out of Scope
Continuous aggregates (sibling story); dashboard/console wiring (#508); latency percentile changes.
### Dependencies and Blockers
Grain tables landed (execution + day/seat stories). #570's ownership check needs an authority for scope→tenant resolution — decide (shared DB lookup vs authz-api call) in implementation, fail-closed either way.
### Assumptions
The mTLS split-listener posture stays; ownership check is defense-in-depth on top of it, not a replacement.
### Implementation Notes
Reuse `append_dimension`/`date_bin` builder patterns per grain; keep `validate_bucket_interval`'s regex+allowlist double gate. Per-grain repos share the fixed-row-shape trick (unselected dims as `NULL::text`).
### Test Expectations
Two-tenant 403 test (fail-first); per-grain group-by/filter integration tests mirroring the existing `repo_it_tests.rs` coverage; newest-bucket-truncation regression test; NULL-cost wire-shape test.
### Verification evidence
To be attached on the implementing PR.
### Human accountable owner
Stephane Segning Lambou
### AI Usage
AI-assisted (Claude): research and drafting. Reviewed and owned by Stephane Segning Lambou.
Contributor guide
Assessment
This issue has not been assessed yet.