# Idea: timeline ordering — confirm/harden the sequence-is-primary invariant ## Original note "The timeline is time ordered. That is always derived from the sequence ordering. That means the primary sequence is simply sequence ordered." — reads as a statement of intent/invariant rather than a bug report, so it's unclear whether this needs a code change or is just confirming existing behavior. ## Current state (confirmed in code) - `IndexKey::IngestedLog { sequence, timestamp }` (`src/engine/index_key.rs:10-13`) derives `Ord` with `sequence` as the primary field and `timestamp` only as a (practically unused) tie-breaker, since `sequence` is already unique. - `sequence` is a strictly monotonic counter incremented per ingested line (`src/engine/ingest.rs:13,52`); `timestamp` is a separate wall-clock `Timestamp::now()` read at the same point in the ingest loop (`src/engine/ingest.rs:18,93`) — not derived from `sequence`. - The default log listing path (`logs_in_sequence_range`, `src/engine/snapshot/mod.rs:180-208`, used by the `Logs` query in `src/engine/snapshot/query.rs:118-172`) is explicitly sequence-order, with an existing comment documenting this (`src/engine/snapshot/mod.rs:176-179`). - The only other ordering mode is `sort_by` (sort by a declared param's value, sequence as tie-break, via `SortHelperKey`, `src/engine/snapshot/sort.rs`) — there's no "sort by timestamp" mode distinct from sequence order. - **The one place ordering is genuinely timestamp-driven**: the `Timeline` query (`src/engine/snapshot/query.rs:173-198`) buckets logs by `timestamp.as_millis() / bucket_ms` (`:191`), not by sequence. ## Why this is an idea, not a ticket The invariant the note describes already appears to hold for the default log listing — sequence is the primary and effectively only ordering key there. The one place `timestamp` (not `sequence`) drives grouping is the `Timeline` bucketing, and `sequence`/`timestamp` are captured independently rather than one being derived from the other. In the current single-threaded ingest loop they're expected to co-vary, but nothing guards against a backward wall-clock step (NTP correction, clock skew) between two `Timestamp::now()` calls — if that ever happened, sequence-ordered views and timestamp-bucketed views could disagree on relative order for the affected lines. This has not been observed as an actual bug; it's a latent gap worth a decision. ## Open question Is the ask just "confirm this holds" (in which case: it does, for the default listing — no code change needed), or do you want the `Timeline` bucketing hardened so it can never diverge from sequence order even under clock skew (e.g. clamp each line's timestamp to be `>= previous line's timestamp`, or derive bucket assignment from sequence position rather than raw wall-clock read)? The fix is small either way, but scoping it as a ticket needs that decision first. ## Implications and things to consider ### Performance - Clamping `timestamp` to be monotonic (`max(this_line_ts, previous_line_ts)`) is O(1) per line — negligible cost, just one extra comparison in the existing ingest loop (`src/engine/ingest.rs:13-18,52,93`). Deriving bucket assignment from sequence instead of wall-clock would be a larger change (see below) but isn't inherently more expensive at ingest time. - No performance concern either way — this is a correctness/consistency question, not a hot-path cost question. ### Code structure - If bucket assignment moves from wall-clock `timestamp` to something sequence-derived, `Timeline`'s bucketing (`src/engine/snapshot/query.rs:173-198`) still fundamentally needs a *time value* per bucket (users read timeline buckets as "logs between 10:32:00 and 10:32:05", not "logs 100-150") — sequence can't replace timestamp as the bucketing axis, only guard it. The realistic fix is the monotonic-clamp approach, not a switch to sequence-based bucketing. - Clamping at ingest time (`ingest.rs`) is the natural single place to fix this, rather than patching every read path that consumes `timestamp` — confirm there's exactly one ingest entry point today (there appear to be two: stdin/file tailing and Prometheus metrics polling, per `src/engine/ingest.rs` — a clock-skew guard needs to apply to both, and they'd need to share one "last observed timestamp" reference point to clamp against consistently). ### Functionality - A clamped timestamp is no longer "true" wall-clock time for that line — if any UI surface displays a log's literal ingest timestamp (as opposed to just using it for bucketing), clamping would make a displayed timestamp lie slightly during a clock step-back event. Low probability, but worth deciding whether clamping is silent or surfaced (e.g. a flag on the log entry noting its timestamp was adjusted). - This only matters at all during an actual backward clock step, which in most deployments (a piped CLI process, not a long-running server through NTP corrections) may never occur in practice — worth weighing whether this is worth any code change versus documenting it as an accepted limitation. ### Not yet considered - No investigation into whether the same two independently-captured timestamp/sequence pattern exists anywhere else besides ingest (e.g. alert evaluation timestamps, or the Prometheus-polling ingest path specifically) — the research so far only confirmed the stdin/file ingest loop. - No check on how a forward clock jump (not just backward) would interact with `Timeline` bucketing — a large forward jump would create a sparse/empty bucket range, which is a different (probably more common) failure mode than the backward-step case this idea focuses on, and might be worth addressing in the same pass if one happens.