Skip to content

feat(querier): drift query over the audit stream (RFC 0010, H5.3) - #165

Merged
jensholdgaard merged 6 commits into
mainfrom
rfc0010-drift-query
Jun 9, 2026
Merged

feat(querier): drift query over the audit stream (RFC 0010, H5.3)#165
jensholdgaard merged 6 commits into
mainfrom
rfc0010-drift-query

Conversation

@jensholdgaard

@jensholdgaard jensholdgaard commented Jun 9, 2026

Copy link
Copy Markdown
Owner

What

Implements RFC 0010 — Audit-stream queries & template drift (the drift verb head + audit-tree scan + the §6.3 fixed aggregation) and discharges RFC 0001 scenario H5.3. RFC 0010 was just merged (#164) and is the contract this PR codes to.

The thesis gap this closes: RFC 0001 §6.7 specified drift detection as SQL "for spec clarity"; RFC 0002 §6.3 deferred the audit-stream query path; RFC 0005 already persists the events. RFC 0010 is that future capability — a single closed DSL query head over the per-tenant audit/ Parquet stream.

Drift surface as implemented

  • IR (dsl::ir) — a top-level enum Statement { Logs(Query), Drift(DriftQuery) }. A DriftQuery { from: Time, to: Time } is its own shape, not a flag on a row query (make-invalid-states-unrepresentable, RFC 0010 §6.1). from/to reuse the RFC 0002 §7 Time production verbatim.
  • Parser (dsl::parse)parse_statement dispatches on the leading drift token: drift from <t1> to <t2>. The head admits no trailing | stages (closed form, §6.1). The RFC 0002 parse entry stays log-only and rejects a drift head.
  • Structured (dsl::structured)parse_structured_statement accepts {"drift":{"from":…,"to":…}} (deny-unknown-fields, no predicate/stages sibling). The published JSON Schema gains a drift_query fragment under a root oneOf.
  • Compile + exec (drift module) — lowers DriftQuery to a DataFusion Filter → Aggregate → Sort plan over the audit files: event_type IN ('template_widened','template_type_expanded') AND timestamp in half-open [from, to), GROUP BY template_idwidening_count=COUNT(*), MIN(old_version), MAX(new_version), MIN(timestamp) (first_seen), MAX(timestamp) (last_seen), ORDER BY widening_count DESC, template_id ASC. Built programmatically — no raw SQL.
  • ResultDriftResult { rows: Vec<DriftRow>, stats: QueryStats }, a distinct shape from the log QueryResult (RFC 0010 §6.4). DriftRow carries plain owned scalars; no datafusion/arrow/SQL type crosses the public surface.
  • Entry pointQuerier::run_drift(&DriftQuery, &TenantId, now_unix_nano).

Audit-tree scan + per-tenant prune

The scan roots at <bucket>/audit/tenant_id=<percent-encoded tenant>/ — tenant isolation is the partition prune (the leading Hive key, RFC 0005 §3.4 / §6.5), not a post-scan filter. The window drives a day-granularity year/month/day prune (the audit layout has no hour segment) that skips whole partition dirs that can't overlap [from, to) before any footer opens; an exact timestamp predicate then trims the boundary days. Conservative: an unparseable/non-leaf path is never pruned, so the row-level timestamp filter stays the correctness authority. A missing tenant dir is an empty result (RFC0010.5), not an error.

Hazards / invariants addressed

  • H5 (template schema evolution / drift) — drift is now the first-class detection query; a post-deploy cluster of template_widened events surfaces as drift from -7d to now.
  • H6 (no DataFusion/SQL leakage) — the surface is DSL-only; the §6.3 SQL is anchored programmatically. RFC0010.8 asserts no engine token escapes parse errors and that the public path is parse_statement → DriftQuery → DriftRow.
  • CLAUDE.md §3.7 (multi-tenancy) — every path takes a tenant; isolation is the partition root; a drift query with no tenant is unrepresentable (the tenant arg is required).

Tests

  • RFC0010.1–.8 acceptance tests in crates/ourios-querier/tests/drift.rs (greppable /// Scenario RFC0010.N doc-comments), seeded via the production ParquetAuditSink: drifted-templates-with-counts, half-open boundary exclusion, event_type scoping (rejected_degenerate/compaction excluded), tenant isolation, empty→empty, ordering + tie-break, aggregate version/time bounds, no-SQL-leakage.
  • H5.3 relocated — the real test (h5_3_drift_query_returns_templates_that_gained_a_version) lives in tests/drift.rs (the miner crate can't run a querier, cf. RFC0001.5/.6 already relocated to rfc0001_query_semantics.rs). The miner hazards.rs #[ignore]/todo!() stub is replaced with a one-line pointer comment.

Out of scope (per RFC 0010 §3.2 / §8)

The general audit-stream aggregation engine, the compaction-event query surface, and the §9 finer forks (default window, tie-break alternatives, etc.) stay deferred exactly as the RFC says.

Verification

Reproduced CI locally: cargo test --all-features577 passed; 0 failed; 29 ignored (the 29 are pre-existing red-gate stubs in other RFCs; H5.3 is no longer among them). cargo fmt --all --check clean. cargo clippy --all-targets --all-features -- -D warnings clean. No docs touched, so mdbook build not required.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added drift queries using "drift from to " (DSL + structured JSON).
    • Drift results return per-template aggregates (widening counts, min/max versions, first/last seen) with deterministic ordering and half-open window semantics.
    • Tenant-scoped safety checks to prevent cross-tenant data exposure.
  • Tests

    • Added comprehensive drift integration tests and supporting fixtures.
    • Removed an ignored test stub.
  • Bug Fixes

    • Clearer storage error message ("failed to read storage").

Implement RFC 0010's `drift from <t1> to <t2>` verb head: a closed,
audit-stream query that answers "which templates gained a version in the
half-open window [from, to)". Discharges RFC 0001 scenario H5.3.

- DSL surface: a top-level `Statement` enum (Logs | Drift) makes the two
  query shapes mutually exclusive (RFC 0010 §6.1, make-invalid-states-
  unrepresentable). The string head `drift from <t> to <t>` and the
  structured `{"drift":{"from","to"}}` object both lower to one
  `DriftQuery` IR, reusing the RFC 0002 §7 `time` production verbatim and
  foreclosing trailing `|` stages. Published JSON Schema gains a `drift`
  fragment alongside the log query.
- Audit-tree scan + drift aggregation: a new `drift` module scans
  `audit/tenant_id=<enc>/` (tenancy is a partition prune, RFC0010.4 /
  §3.7), day-granularity window prune over the RFC 0005 audit layout, and
  lowers to a DataFusion Filter → Aggregate → Sort plan: event_type IN
  (template_widened, template_type_expanded), timestamp in [from, to),
  GROUP BY template_id → widening_count/min(old_version)/max(new_version)/
  first_seen/last_seen, ORDER BY widening_count DESC, template_id ASC
  (RFC 0010 §6.3/§6.6). No SQL/DataFusion type crosses the public surface
  (H6 / RFC0010.8) — `DriftRow`/`DriftResult` carry plain scalars.
- Tests: RFC0010.1–.8 acceptance tests in ourios-querier/tests/drift.rs,
  seeded via the production ParquetAuditSink. H5.3 relocated here from the
  miner crate (which can't run a querier, cf. RFC0001.5/.6); the miner
  hazards stub's #[ignore]/todo!() is replaced with a pointer comment.

The general audit aggregation and the §9 finer forks stay deferred per
RFC 0010 §3.2/§8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jensholdgaard, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 32 minutes and 56 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a9315be4-1cd6-44b6-bf58-079dd904ed9c

📥 Commits

Reviewing files that changed from the base of the PR and between e9cb752 and 027afc9.

📒 Files selected for processing (1)
  • crates/ourios-querier/src/drift.rs
📝 Walkthrough

Walkthrough

Adds RFC 0010 "drift" queries: new Statement/DriftQuery IR, text and JSON parsing/serialization, DataFusion-based execution over tenant audit Parquet with pruning and path-safety, public Querier::run_drift API, and unit/integration tests validating behavior.

Changes

RFC 0010 Drift Query Implementation

Layer / File(s) Summary
Query IR types and statement contract
crates/ourios-querier/src/dsl/ir.rs, crates/ourios-querier/src/dsl/mod.rs
New Statement enum dispatches between Logs(Query) and Drift(DriftQuery); DriftQuery contains from/to time bounds; DSL re-exports include new entry points.
Text DSL parsing and serialization
crates/ourios-querier/src/dsl/parse.rs, crates/ourios-querier/src/dsl/display.rs
parse_statement dispatches on leading drift to produce Statement::Drift or Statement::Logs; Parser::parse_drift_query parses drift from <time> to <time>; serialize_statement and serialize_drift render canonical drift text; parse remains log-only.
Structured (JSON) DSL parsing and schema
crates/ourios-querier/src/dsl/structured.rs, crates/ourios-querier/src/dsl/structured_query.schema.json
parse_structured_statement dispatches on presence of "drift" to return Statement::Drift or Statement::Logs; strict RawDrift envelope enforces exact keys; schema uses oneOf for log vs drift shapes; tests added for success and rejection cases.
Drift query execution and aggregation
crates/ourios-querier/src/drift.rs, crates/ourios-querier/src/compile.rs
run_drift resolves window bounds (normalizes reversed bounds), discovers tenant-scoped *.parquet files, prunes day partitions conservatively, validates tenant isolation via canonical paths, builds a DataFusion plan filtering by qualifying template event types and timestamp bounds, aggregates per template_id (widening_count, min/max versions, first/last timestamps), sorts by widening_count DESC then template_id ASC, decodes into DriftRows, and returns DriftResult with IO stats; resolve_time made pub(crate).
Public API surface
crates/ourios-querier/src/lib.rs
Adds crate re-exports DriftResult/DriftRow and Querier::run_drift public async method that delegates to drift::run_drift; QueryError::Storage display string changed to “failed to read storage” and tests updated accordingly.
Test infrastructure and integration suite
crates/ourios-querier/tests/common/mod.rs, crates/ourios-querier/tests/drift.rs, crates/ourios-miner/tests/hazards.rs
tests/common adds audit-event builders and write_audit; tests/drift.rs implements RFC0010 acceptance tests (RFC0010.1–.8) plus relocated H5.3; hazards.rs removes old H5.3 stub and documents relocation.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Querier
  participant WindowResolver
  participant AuditScanner
  participant DataFusion
  participant Decoder
  Client->>Querier: run_drift(query, tenant, now_unix_nano)
  Querier->>WindowResolver: resolve [from,to) nanoseconds
  Querier->>AuditScanner: discover/prune tenant parquet files
  AuditScanner->>Querier: validated file list
  Querier->>DataFusion: execute filter+aggregate plan
  DataFusion->>Decoder: return record batches
  Decoder->>Querier: decoded DriftRow list + stats
  Querier->>Client: DriftResult{rows, stats}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • jensholdgaard/ourios#149: Prior structured-query schema/DSL work that this PR extends to add the drift_query envelope and structured parsing dispatch.
  • jensholdgaard/ourios#146: Related compile/time-resolution helper changes; resolve_time visibility here is aligned with that work.
  • jensholdgaard/ourios#90: Earlier boundary/storage error wording tests related to QueryError::Storage expectations updated in this PR.

Poem

🐰 I sniffed the audit stream at dawn's first light,
Templates widened, hopping out of sight.
From parse to plan, through parquet and time,
I tally the drift, one carrot at a time. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly summarizes the main change: implementing drift query support over the audit stream (RFC 0010) and resolving scenario H5.3.
Description check ✅ Passed The PR description comprehensively covers all required sections: What (implementation), Related (RFC 0010 #164, RFC 0001 H5.3), and includes testing verification (cargo test, fmt, clippy results).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rfc0010-drift-query

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jensholdgaard
jensholdgaard requested a review from Copilot June 9, 2026 13:05
@jensholdgaard

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements RFC 0010’s drift audit-stream query surface in ourios-querier, including DSL/structured parsing, compilation+execution over the per-tenant audit/ Parquet stream, and acceptance tests (also discharging RFC 0001 scenario H5.3 via relocation into querier tests).

Changes:

  • Adds a new top-level DSL Statement with a closed-form drift from <t1> to <t2> query (DriftQuery) alongside existing log queries.
  • Implements drift execution over the audit stream (DataFusion plan: filter → aggregate → sort) and exposes Querier::run_drift plus DriftResult/DriftRow.
  • Adds RFC0010.1–.8 acceptance tests and structured-query JSON schema support for drift statements.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
crates/ourios-querier/tests/drift.rs New RFC0010 acceptance tests + relocated RFC0001 H5.3 drift scenario.
crates/ourios-querier/tests/common/mod.rs Adds audit-stream event fixtures and a helper to write real audit Parquet data for drift tests.
crates/ourios-querier/src/lib.rs Wires in drift module, re-exports drift result types, and adds Querier::run_drift.
crates/ourios-querier/src/dsl/structured.rs Adds parse_structured_statement to support structured drift statements; keeps parse_structured log-only.
crates/ourios-querier/src/dsl/structured_query.schema.json Updates schema root to oneOf log-query vs drift-query statement shapes.
crates/ourios-querier/src/dsl/parse.rs Introduces parse_statement for drift/log dispatch; keeps parse log-only and drift-rejecting.
crates/ourios-querier/src/dsl/mod.rs Exposes new statement-level parsing/serialization APIs and IR types.
crates/ourios-querier/src/dsl/ir.rs Adds Statement and DriftQuery IR to make drift/log shapes unambiguous.
crates/ourios-querier/src/dsl/display.rs Adds canonical serialization for drift/log statements (serialize_statement).
crates/ourios-querier/src/drift.rs New drift compile+exec implementation over audit Parquet files with pruning and result decoding.
crates/ourios-querier/src/compile.rs Makes resolve_time pub(crate) for reuse by drift window resolution.
crates/ourios-miner/tests/hazards.rs Replaces the ignored H5.3 stub with a pointer to the relocated querier drift test.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/ourios-querier/src/drift.rs
jensholdgaard and others added 2 commits June 9, 2026 17:27
…iles

Mirror the log-query path: every drift audit file must canonicalize under
the tenant's canonical audit/tenant_id=… root, so a symlinked *.parquet
cannot resolve into another tenant's tree (RFC0010.4 / §3.7). Adds a
unix symlink-escape test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…not Display

QueryError::Storage Display hides detail (H6 — no DataFusion leak), so the
symlink-escape test now matches the variant and asserts detail contains
"escapes tenant partition root" — confirming the tenant-root backstop
fired rather than an unrelated read error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
crates/ourios-querier/src/dsl/display.rs (1)

16-33: ⚡ Quick win

Add a colocated round-trip test for the new statement serializer.

serialize_statement is now the public entrypoint for the new drift statement shape, but this module still only exercises Query round-trips. A small Statement::Drift serialize→parse_statement assertion here would protect the new branch and keep the coverage next to the code that owns it.

Suggested test
 #[cfg(test)]
 mod tests {
-    use crate::dsl::{parse, serialize};
+    use crate::dsl::{parse, parse_statement, serialize, serialize_statement};
+    use crate::dsl::ir::{DriftQuery, Statement, Time};

     /// A small, diverse corpus that exercises every production; each must
     /// round-trip through serialize→parse to the same IR.
@@
     fn floats_serialise_without_an_exponent() {
         // A small-magnitude float must serialise as fixed-point `digits.digits`
         // (the lexer rejects an exponent form), and round-trip.
         use crate::dsl::ir::{CmpOp, Field, OrdOp, Predicate, Query, Value};
         for f in [0.000_000_001_f64, 0.000_25, 123_456.789] {
@@
             assert_eq!(parse(&s).unwrap(), q, "round-trip failed for {s:?}");
         }
     }
+
+    #[test]
+    fn drift_statement_round_trips_through_canonical_form() {
+        let statement = Statement::Drift(DriftQuery {
+            from: Time::Duration {
+                neg: true,
+                literal: "7d".into(),
+            },
+            to: Time::Now,
+        });
+
+        let serialized = serialize_statement(&statement);
+        let reparsed = parse_statement(&serialized).unwrap();
+
+        assert_eq!(reparsed, statement);
+    }
 }

As per coding guidelines, crates/**/src/**/*.rs: Unit tests must be colocated next to the code for anything non-trivial.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ourios-querier/src/dsl/display.rs` around lines 16 - 33, Add a
colocated unit test in this file that constructs a DriftQuery (or
Statement::Drift wrapping one), calls serialize_statement on it, then calls
super::parse_statement (or parse_statement) to parse the produced string and
asserts the parsed Statement equals the original; reference serialize_statement
and Statement::Drift for the round‑trip, and you can reuse helper
functions/types like DriftQuery and write_time/serialize_drift to build the
expected serialized form if needed. Ensure the test is annotated with
#[cfg(test)] and #[test] and lives in the same module so it exercises the
serialize_statement -> parse_statement round‑trip for the drift branch.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ourios-querier/src/drift.rs`:
- Around line 193-220: Move the day-window prune so it's checked before
performing expensive directory reads: inside the loop that processes
`stack.pop()`, call `day_partition_in_window(&dir, start, end)` first and if it
returns false, skip listing `dir` (i.e., continue) so you avoid calling
`std::fs::read_dir(&dir)` for out-of-window `day=...` directories; preserve
current behavior of only appending `parquets` when in-window (the
`files.append(&mut parquets)` call) and still push subdirectories for non-day or
unparseable paths as needed.

In `@crates/ourios-querier/src/dsl/parse.rs`:
- Around line 1497-1504: The test drift_is_not_a_reserved_field_in_a_log_query
currently parses "true | limit 5" and never exercises the 'drift' token; change
it to call parse_statement with a log pipeline that contains the identifier
drift after the head (e.g. "true | where drift == 1" or "true | stats count by
drift") and update the assertion to expect a parsing failure (Err) for an
unknown field rather than successful Statement::Logs parsing — specifically
invoke parse_statement("...") and assert it returns an Err and that the error
indicates an unknown/unknown-field for "drift" so the test fails if 'drift' is
treated specially as a statement head.

In `@crates/ourios-querier/src/lib.rs`:
- Around line 491-498: The Display message for the QueryError::Storage variant
is store-specific and should be made generic before exposing run_drift; update
the Display implementation for QueryError (the branch that currently returns
"failed to read the log store") to a store-neutral message such as "storage
operation failed" or "failed to access storage" and then update the
pinned-message tests in this file that assert the old string to expect the new
generic text; locate the Display impl for QueryError, the Storage variant, and
the pinned message tests in this file and change both the emitted message and
test expectations accordingly so run_drift (and drift::run_drift) no longer
point operators to the wrong subsystem.

---

Nitpick comments:
In `@crates/ourios-querier/src/dsl/display.rs`:
- Around line 16-33: Add a colocated unit test in this file that constructs a
DriftQuery (or Statement::Drift wrapping one), calls serialize_statement on it,
then calls super::parse_statement (or parse_statement) to parse the produced
string and asserts the parsed Statement equals the original; reference
serialize_statement and Statement::Drift for the round‑trip, and you can reuse
helper functions/types like DriftQuery and write_time/serialize_drift to build
the expected serialized form if needed. Ensure the test is annotated with
#[cfg(test)] and #[test] and lives in the same module so it exercises the
serialize_statement -> parse_statement round‑trip for the drift branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c51cec33-a889-4b5c-bc0c-29d826052df7

📥 Commits

Reviewing files that changed from the base of the PR and between d733287 and d9a0924.

📒 Files selected for processing (12)
  • crates/ourios-miner/tests/hazards.rs
  • crates/ourios-querier/src/compile.rs
  • crates/ourios-querier/src/drift.rs
  • crates/ourios-querier/src/dsl/display.rs
  • crates/ourios-querier/src/dsl/ir.rs
  • crates/ourios-querier/src/dsl/mod.rs
  • crates/ourios-querier/src/dsl/parse.rs
  • crates/ourios-querier/src/dsl/structured.rs
  • crates/ourios-querier/src/dsl/structured_query.schema.json
  • crates/ourios-querier/src/lib.rs
  • crates/ourios-querier/tests/common/mod.rs
  • crates/ourios-querier/tests/drift.rs

Comment thread crates/ourios-querier/src/drift.rs Outdated
Comment thread crates/ourios-querier/src/dsl/parse.rs
Comment thread crates/ourios-querier/src/lib.rs
Comment thread crates/ourios-querier/tests/drift.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Comment thread crates/ourios-querier/src/dsl/parse.rs
Comment thread crates/ourios-querier/src/drift.rs
Comment thread crates/ourios-querier/src/drift.rs
jensholdgaard and others added 2 commits June 9, 2026 18:23
…neutral storage error

Restructure audit_files_in_window so an out-of-window day= leaf is
skipped before it is listed (the prune now gates read_dir, not just the
file append); year=/month=/tenant_id= dirs are non-leaf and still
descended. De-duplicate canonical audit paths so an in-tenant symlink
can't double-read/double-count, mirroring the log path. Short-circuit an
empty [from, to) window before any audit-tree IO or DataFusion planning.
Reword the QueryError::Storage Display to the store-neutral "failed to
read storage" now that drift surfaces it for the audit stream too,
still without leaking the detail (H6).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t token in its guard test

The 'drift is not a log query' message used a \ line-continuation that
preserved source indentation as runs of spaces in the operator-facing
text; build it with concat! so it renders with single spaces. The
mid-query guard test now parses a query that actually contains a drift
token after the head (attr.drift), asserting it stays a normal Logs
query so the test fails if a mid-query drift were ever treated specially.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread crates/ourios-querier/src/drift.rs
…as zero

decode_drift_rows read .value(i) without NULL checks, but the audit
schema marks template_id/old_version/new_version (and the timestamp
aggregates) nullable — a corrupt/foreign audit file could decode a NULL
group key as template_id 0. Guard each and surface QueryError::Storage;
add a decode unit test on a NULL group key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants