lookout findings diff
Diff a health report against the previous run and report what CHANGED — new, ongoing, escalated, resolved, suppressed — instead of re-listing every open finding; the command that makes a scheduled scan produce a digest an operator will keep reading.
MCP tool: k8s_findings_diff (MCP profile: triage)
lookout findings diff [flags]| Flag | Type | Default | Meaning |
|---|---|---|---|
--report | string | - | the finding report to classify: - reads stdin (the usual lookout health | lookout findings diff --report -), or a file path. Either wire format is accepted, detected per line, so the upstream command does not need —format=json |
--store | string | — | path to the sentinel’s SQLite store (its —store file), where the previous run’s state lives. Required: finding state lives in the sentinel’s —store SQLite file; a diff with nowhere to persist would report everything new on every run |
--store-cluster | string | — | read/write the store for THIS cluster, treating —store as the multi-cluster stem the sentinel was given: —store=/var/lib/lookout/lookout.db —store-cluster=prod-us opens /var/lib/lookout/lookout-prod-us.db (issue #410). Set it only against a sentinel running —clusters/—clusters-from; a single-cluster sentinel writes the literal —store path |
--cluster | string | — | cluster label to bind these findings to; becomes the first segment of every subject key. Give the same value on every run for a cluster — changing it makes every subject look new. This labels rows INSIDE the store; —store-cluster picks the store FILE. Left empty with —store-cluster set, it defaults to that name |
--transitions | string | — | emit only these transition classes, comma-separated: new|ongoing|escalated|resolved|suppressed (empty = all). --transitions=new,escalated,resolved is the digest view: everything that changed, nothing that didn’t |
--dry-run | bool | — | classify and print, but do not advance the stored state. Use to preview a report without consuming it — a normal run is not repeatable, because after it the second run’s findings are all ongoing |
Common flags (every lookout command)
Section titled “Common flags (every lookout command)”| Flag | Type | Default | Meaning |
|---|---|---|---|
--namespace | string | — | limit the scan to one namespace |
-A | bool | — | scan all namespaces |
--workload | string | — | target one workload as <Kind>/<namespace>/<name>, e.g. Deployment/prod/api |
--since | duration | — | how far back to look (0 = command default) |
--format | string | logfmt | output format: logfmt|json (one record per line either way) |
--timeout | duration | 10s | abort the invocation after this long (exit 1) |
--kubeconfig | string | — | path to a kubeconfig file, instead of $KUBECONFIG / ~/.kube/config |
--context | string | — | kubeconfig context to read, instead of its current-context. Selects a cluster for THIS invocation only — nothing is written back — so concurrent invocations can target different clusters. Reported as context=<name> in the summary line |
--exemptions | string | — | path to a git-reviewed exemption file (YAML); covered findings are ANNOTATED with their reason and expiry and counted as exempt=<n> in the summary, never dropped |
Finding kinds
Section titled “Finding kinds”Every kind= this command can emit, and the severities it carries them at. Nothing else appears in its output; a kind absent from a run means the check looked and found nothing. See the finding-kind glossary for the whole vocabulary.
| Kind | Severity | Claim |
|---|---|---|
findings.transition | critical, warning, info | a finding subject changed state since the previous run (new|ongoing|escalated|resolved|suppressed); the severity is the underlying finding’s current one, not a judgment about the transition |
Output fields
Section titled “Output fields”Beyond the shared envelope fields (kind, severity, namespace, kind_of_object, name, reason, message, fingerprint, exempt_reason, exempt_expires):
| Field | Meaning |
|---|---|
transition | how this subject changed since the previous run: new|ongoing|escalated|resolved|suppressed |
subject_key | the normalized instance-grain key this diff tracks: <cluster>/<namespace>/<kind_of_object>/<normalized-name>/<canonical-reason>. Distinct from the envelope’s class-level fingerprint; pass it to lookout findings ack |
prev_severity | the severity recorded at the previous run; absent on new. Compare with the envelope’s severity to see a de-escalation, which stays classified ongoing |
first_seen | when this subject was first observed, RFC 3339 — carried across runs, so it is the “broken since” timestamp, not this run’s clock |
last_seen | when this subject was last observed, RFC 3339 |
ack_until | expiry of the operator ack window on a suppressed subject, RFC 3339 |
ack_by | who took the ack, as forwarded by the caller |
skipped_no_subject | summary line only, present when non-zero: report records that named no object and were therefore not diffed — health.category scorecard rows, scan.check_skipped, and the other narration kinds. A diff is over subjects; those lines are not subjects, and diffing them would collapse them all into one empty key |
Output contract
Section titled “Output contract”Output: one finding per line (logfmt; —format=json for one JSON object
per line), keys in fixed order; healthy resources emit nothing. The final
line is always the summary: scanned=
Examples
Section titled “Examples”lookout health --store=/var/lib/lookout/lookout.db | lookout findings diff --report=- --store=/var/lib/lookout/lookout.db --cluster=prod-eastlookout health | lookout findings diff --report=- --store=/var/lib/lookout/lookout.db --cluster=prod-east --transitions=new,escalated,resolvedlookout findings diff --report=/tmp/scan.logfmt --store=/var/lib/lookout/lookout.db --cluster=prod-east --dry-run