Skip to content

feat: enrich ViolationRecord with per-record occurrence count and first-observed timestamp #820

Description

@dottorblaster

The Violations tab needs two per-row fields that don't exist today, both on ViolationRecord, both computable at merge time with data that already flows through the pipeline.

Occurrences. We only expose status.violationCount, a policy-level aggregate incremented in mergeScrapedViolations() for every scraped record — it can't be decomposed into "how many times did this executable/container/pod combo fire". The UI needs a per-row count distinct from that aggregate.

Age. ViolationRecord.timestamp is refreshed to the newest occurrence on every merge (the if v.Timestamp.After(...) branch), so it's a last-seen time. There's no first-seen timestamp to compute age from, and id is a monotonic counter, not wall-clock, so it can't stand in.

Both are cheap because the agent already buffers one record per violation event (violationbuf.Buffer.Record() once per reportViolation, 10k ring buffer) and the controller sees every raw event at merge — it just discards the count on dedup and never remembers the first sighting. So this is controller-side only: no agent/proto/BPF change. The existing Last Occurrence column keeps deriving from timestamp; only Occurrences and Age are new.

We should add occurrences (init 1 on first observation, ++ on each matched duplicate) and firstObservedTimestamp (set once at creation, never mutated).

Notes:

  • occurrences: new records init 1, matched records occurrences++. The merge loop iterates every scraped event, so multiple duplicates in one batch are counted naturally.
  • occurrences stays distinct from violationCount — the latter (per its own doc-comment) includes trimmed/cleared records and isn't strongly consistent, so don't try to make them reconcile exactly.
  • firstObservedTimestamp: set in the new-record (else) branch from the scraped event's own timestamp (the earliest we know of), not time.Now(); never touch it on a match — that's the whole point vs timestamp.
  • Mark both +optional; occurrences gets +kubebuilder:default=1.
  • No agent/proto/BPF change; metav1.Time deepcopy is handled by codegen.
  • Related: Feature Request: Is it possible to assign violation ID chronologically ?. #764 (violation ID ordering) touches the same record semantics.

Touchpoints:

  • api/v1alpha1/violation_record.go: add Occurrences int64 and FirstObservedTimestamp metav1.Time to ViolationRecord; in mergeScrapedViolations() set occurrences: 1 + firstObservedTimestamp in the new-record branch, and s.Violations[idx].Occurrences++ in the matched branch.
  • api/v1alpha1/violation_record_test.go: assert per-record counts across multi-batch merges and across the maxViolationRecords trim; assert firstObservedTimestamp is stamped on creation and stays stable across re-scrapes while timestamp advances.
  • Regenerate CRD manifests + deepcopy (make manifests generate), and docs/crd.adoc / docs/crds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    Status
    Todo

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions