Skip to content

SecretsKeyBackupUnproven has had no series to read since #106 landed, and the guard it relies on is already satisfied #400

Description

@Gerrrt

SecretsKeyBackupUnproven is the only thing that says the age-key proof has gone stale, and since #106's implementation landed it has had nothing to read. Found from oracle against the Prometheus HTTP API on 2026-09-08, not from the host.

What the monitoring host reads

homelab_key_recipient_last_proof_timestamp_seconds              (no series)
homelab_job_last_success_timestamp_seconds{homelab_job="verify-key-backup"}
                                                  1788497914  = 2026-09-04 04:41:54Z
homelab_job_max_age_seconds{homelab_job="verify-key-backup"}   7776000
homelab_deploy_revision_info{host="prometheus"}   revision="17ce90aa03ac"   (= main)
homelab_deploy_behind_commits{host="prometheus"}  0
ALERTS{alertname=~"SecretsKeyBackupUnproven|ScheduledJobNeverRan"}   (empty)

The host's checkout is at main, so this is not a stale tree. The series the rule joins on has never been written.

Why

e5115901 (2026-09-04 23:15Z) moved SecretsKeyBackupUnproven from the job series to homelab_key_recipient_last_proof_timestamp_seconds, per ADR-0024. That series is written by scripts/key-recipients.sh --record, which is called from exactly two places:

  • scripts/verify-key-backup.sh §7 — a human run with removable media mounted;
  • scripts/add-recipient.sh — adding a recipient.

Neither has run since e5115901 merged. The last proof was 2026-09-04 04:41Z, nineteen hours before the emitter existed, so it wrote the old job series and nothing else. install-timers.sh names the script only in a comment; no timer calls it.

The rule's own comment anticipates the empty-file case and hands it off:

ScheduledJobNeverRan still speaks for the case before ANY verification has happened, because key-recipients.sh has not written its file yet and there are no per-recipient series to fire on.

That holds only if no verification has ever happened. Here one had — under the old series — so homelab_job_last_success_timestamp_seconds{homelab_job="verify-key-backup"} exists, ScheduledJobNeverRan (max_age unless last_success) is satisfied, and the handoff lands on nothing. ADR-0024's "a recipient that has never been verified is recorded as 0, not omitted" is true of the file once it exists; the file itself is omitted until a human runs the proof.

stacks/observability/prometheus/tests/backup.test.yaml feeds the rule a populated series, so the tests cannot see this either.

What it costs

The 2026-09-04 proof ages past ninety days on 2026-12-03, and nothing will say so — the old series is excluded from ScheduledJobStale on purpose, and the new rule has no input. This is the .env failure ADR-0024 used as its own example, one layer up: the proof exists, and nothing here can see whether it is going stale.

It also means the estate currently reads cleaner than before e5115901: the previous rule would have fired on the old series on 2026-12-03; the new one will not.

How it could resolve

Two shapes, not exclusive:

  1. Seed the file from a timer. key-recipients.sh --record with no --proved writes every current recipient with its prior timestamp, or 0 — it is safe to run unattended and carries forward existing proofs. Running it from install-timers.sh --install, or from the nightly verify-backups unit, means the series exists from the first deploy. Note the seed would write 0 for the one recipient that was proved on 2026-09-04, because that proof predates the file; the alert would then fire as "never proved" until the next mounted-media run. Loud is the right side to fail on here, and the description already tells the operator what to do, but it is worth knowing before the first run.
  2. Guard the absence. An absent_over_time(homelab_key_recipient_last_proof_timestamp_seconds[6h]) rule, on the same footing as ScheduledJobMetricsAbsent — with the caveat backup.rules.yaml's header gives about absent() and label synthesis. That catches this case and the case where the .prom file is deleted later.

Either way, the ScheduledJobNeverRan sentence in the rule comment should say what it actually covers.

Refs #106, #294, ADR-0024. Recorded on the Lemmiwinks wiki at infrastructure/scheduled_jobs §3, which will be corrected to point here — it currently attributes the gap to converge running report-only, which the deploy metrics above rule out.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions