Attestkeep docs

Try a policy before enforcing it

A policy is easy to read and hard to predict. Whether Enforce would refuse anything in your cluster depends on your images, your scan results and your triage decisions — not on the policy text.

That is why installations sit in Audit for months: the operator cannot see what switching would cost, so they never switch. The replay answers it from the cluster's own records. Every admission review is already in the ledger; a draft policy is decided against those rows again, by the same code the admission webhook runs, and the differences are reported before anything is saved.

Nothing is written. No pod is affected, no scan is queued, no registry is contacted, and the licence meter is not touched. The one trace it leaves is an audit-log entry (policy_whatif, "replayed a draft policy") and a counter, attestkeep_policy_whatif_runs_total.

In the console

Policy editor → What if, beside Apply to cluster. Pick a period — 7, 30 or 90 days — and press it. The button is disabled while the form has errors: the draft goes through the same validation a save does, so a draft that cannot be tried is a draft that cannot be applied.

The panel reports three figures — unchanged, would be denied, would be allowed — the gate behind each difference, and up to two hundred of the changed admissions, most recent first.

The request

POST /api/v1/policies/whatif, session-authenticated. It needs the same authority as reading a policy, not writing one, because it writes nothing.

{
  "policy": { "...": "the same body POST/PUT /api/v1/policies accept" },
  "days": 30
}

days is 1 to 90 and defaults to 30. An invalid draft is refused with 400 and the exact message Apply would have given.

What the answer contains

FieldWhat it is
evaluatedLedger rows the draft was decided against.
distinct_imagesHow many distinct digests those rows cover.
break_glass_skippedBypassed admissions, counted apart and not replayed: a bypass decided them, not a policy.
truncatedThe row cap — 5000, newest first — was reached, so the period is only partly covered.
unchanged / would_deny / would_allowThe three figures the panel shows.
would_deny_by_reasonKeyed by the labels attestkeep_admission_denials_total uses — threshold, known_exploited, provenance, registry, tag, digest, signature, attestation, unscanned, end_of_life, quota, context.
would_allow_by_reasonKeyed by the original denial's label, recovered from the recorded reason. other when it cannot be recovered — the ledger stores the sentence a deployer was shown, not the label the metric carried.
samplesOnly the changed rows, most recent first, at most 200. The reason is the draft's own sentence.
contextWhat the draft's deploymentContext gate would touch: its effective mode, how many replayed rows carried each of the six signals (every signal is a key, even at zero), and escalated_denials — refusals that happened only because of where the pod ran. rows_without_context counts rows recorded before migration 0020, which replay with no context at all; above zero it comes with a note saying so, because an empty array cannot tell "carried no signals" from "nobody was looking".

What it covers

The caveat that matters: the current scan record

Decisions are replayed against each image's scan record as it stands now, not as it stood at the time. That is what the note on every response says, and it is the one thing that can mislead.

The ledger records what was decided. The scan store records what is known today. An image scanned three times since that admission is judged on today's findings; a CVE published last week counts against a pod admitted last month. This is the right behaviour for the question being asked — "would this policy be safe to turn on" is about the images as they are — but it is not a reconstruction of history, and an evidence package is still the only thing that reports what was actually decided.

Two more things the replay does not reproduce:

An image with no scan record at all is decided the way admission decides a cold digest — by the draft's admissionTiming, or by the installation's webhook.coldImagePolicy when the draft leaves it empty — and lands under unscanned when that refuses it.

Why the answer can be trusted

The replay does not have its own copy of the rules. One function is the whole per-image admission decision — break-glass, the registry allow-list, :latest, digest enforcement, the meter, the cold-image timing, and the severity, known-exploited, provenance and VEX evaluation — and both the admission webhook and this endpoint call it. The preview and the gate cannot drift, because there is only one of them.