Skip to content

timeline: includeManaged source parity + delta/deep-link contract tests#1147

Merged
nadaverell merged 5 commits into
mainfrom
timeline-followups
Jul 10, 2026
Merged

timeline: includeManaged source parity + delta/deep-link contract tests#1147
nadaverell merged 5 commits into
mainfrom
timeline-followups

Conversation

@nadaverell

@nadaverell nadaverell commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Follow-ups from the #1111 review that were deferred at merge, now done.

includeManaged parity (TimelineSource substitutability)

A consumer passing includeManaged: false got server-side filtering in local mode but nothing in retained mode — the retained endpoint has no include_managed param, and the local path's 'all' preset re-includes managed rows anyway, so the flag's behavior depended on the source. applyClientFilters now enforces it uniformly for both sources, mirroring the Go store's IsManaged predicate (owned, or ReplicaSet/Pod/Event). Default (unset) keeps machinery rows — the swimlane's pod/RS child lanes need them — so Radar's own UI behavior is unchanged, and no Radar Hub change is needed.

Contract tests

  • Store conformance suite (internal/timeline/store_conformance_test.go): one suite run against both EventStore implementations, pinning the Seq/delta contract — strictly increasing arrival numbers, oldest-first delta pages that resume a burst beyond the page limit losslessly, relist dupes never re-delivered, K8s-event count bumps re-arriving at the frontier exactly once. The two stores enforce this with unrelated mechanisms; a violation is silent client-side event loss.
  • Delta-sync client orchestration (client.deltaSync.test.ts): the useChanges queryFn body is extracted verbatim into runDeltaSyncFetch (state passed explicitly; behavior identical) and tested at the fetch boundary — cursor priming from headers, merge-by-id, empty-but-headered delta advancing the cursor, cached-reference return on empty delta, epoch-reset full resync, and the full+deltas ≡ fresh-full equivalence.
  • Deep-link contract (TimelineView.urlparams.test.ts): parseTimeMode / writeTimelineParams / onlyHighFreqDiffer / timeModeEqual exported as test seams and round-trip tested — including maxRangeDays capping of hand-typed ?from&to and the replace-vs-push rule that keeps window pans from flooding history (pinning the back/forward-loop fix).

Verification

  • Go: full sweep green in both modules, conformance suite passes against MemoryStore and SQLiteStore
  • make tsc clean · web vitest 69/69 (31 new) · k8s-ui vitest 1152/1152
  • No runtime behavior change outside the includeManaged: false path (which nothing in Radar's UI sets today)

Note

Low Risk
Primary product change is client-side includeManaged: false parity, which the PR states Radar UI does not use today; the rest is test refactors and exports with behavior-preserving extraction of delta sync.

Overview
includeManaged: false now behaves the same for local and retained timeline sources. Client-side filtering in applyClientFilters mirrors Go TimelineEvent.IsManaged (owned resources plus ReplicaSet/Pod/Event kinds). Unset/default still keeps machinery rows for swimlanes; only an explicit false strips them.

EventStore seq/delta behavior is centralized in pkg/timeline/storetest. Duplicate informer-ID, K8s bump upsert, stale bump, enrichment preservation, and delta paging tests are removed from per-store SQLite/memory test files and run once via RunConformance for both MemoryStore and SQLiteStore.

useChanges delta orchestration is testable without React. The queryFn logic moves into exported runDeltaSyncFetch with explicit meta/cache/now; client.deltaSync.test.ts covers cursor priming from headers, merge-by-id, empty deltas advancing the cursor, cached reference on no-op deltas, epoch-mismatch full resync, and incremental vs fresh-full equivalence.

Timeline deep-link URL helpers are exported and covered (parseTimeMode, writeTimelineParams, onlyHighFreqDiffer, timeModeEqual) in TimelineView.urlparams.test.ts for round-trips, retention caps, and replace-vs-push history rules.

Reviewed by Cursor Bugbot for commit b714938. Bugbot is set up for automated code reviews on this repo. Configure here.

The retained endpoint has no include_managed param and the local path's
'all' preset re-includes managed rows server-side, so a consumer's
includeManaged=false filtered in one mode and not the other — the
TimelineSource abstraction wasn't substitutable. Enforce it client-side
in applyClientFilters for both sources, mirroring the Go store's
IsManaged predicate. Default (unset) keeps machinery rows, which the
swimlane's pod/RS child lanes require — Radar's own UI is unchanged.
Three contracts whose regressions are invisible until a user hits them:

- A store-agnostic conformance suite run against both EventStore
  implementations pins the Seq contract (strictly increasing arrival
  numbers, oldest-first delta pages that resume a burst beyond the page
  limit losslessly, relist dupes never re-delivered, K8s-event bumps
  re-arriving at the frontier exactly once). The two stores enforce it
  with unrelated mechanisms; a violation surfaces as silent client-side
  event loss, not an error.

- The delta-sync client orchestration (full fetch → delta poll →
  epoch-mismatch resync) is extracted from the useChanges queryFn into
  runDeltaSyncFetch with state passed explicitly, and tested at the
  fetch boundary: cursor priming from headers, merge-by-id, empty-delta
  cursor advance, cached-reference return, epoch-reset resync, and the
  full+deltas ≡ fresh-full equivalence.

- TimelineView's URL-param functions (parse/write/push-vs-replace/
  equality) are exported as test seams and round-trip tested, pinning
  the deep-link contract and the back-forward loop fix.
@nadaverell nadaverell requested a review from hisco as a code owner July 10, 2026 19:29

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a68d7f7. Configure here.

Comment thread web/src/api/timelineSource.ts
… suite

The contract-level tests existed three times — mirrored per-store copies
plus the new suite. The suite now lives once in pkg/timeline/storetest
(httptest-style, importable by any EventStore implementation), MemoryStore
runs it from the pkg module's own tests, SQLiteStore from internal, and
the thirteen mirrored per-store copies are deleted. It also absorbs two
properties the first cut missed: the cursor keys on arrival order (a late
event with an older timestamp still lands past it), and enrichment
preservation across bare/enriched bumps. Store files keep only
implementation-specific behavior: migrations, quarantine, retention,
timestamp encoding, ring recency.
The retained fetch key doesn't carry the flag, so flipping it alone
served the previous filtered array. The local memo gets it too — it is
client-enforced now and must not depend on riding the server query key.
Consolidation review recovered assertions the extraction dropped and
gaps the first cut never had: the arrival-order property now exercises
AppendBatch's per-row seq assignment and pins newest-first no-cursor
ordering; a relist dupe must keep the ORIGINAL row untouched (keep-first
event type), not merely stay collapsed; enrichment preservation is
asserted through Query — the path consumers read — as well as GetEvent;
and the package doc states the suite's honest scope (arrival-order/
delta/upsert identity, not a full EventStore certification).
@nadaverell nadaverell merged commit 44c08c1 into main Jul 10, 2026
9 checks passed
@nadaverell nadaverell deleted the timeline-followups branch July 10, 2026 20:43
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.

1 participant