Files
jakman85andClaude Opus 5 8427b28b0e
gate / gate (pull_request) Successful in 1m39s
gate / conformance (pull_request) Has been skipped
fix(obs): the deploy notification was inhibited before it could send
FluxRolloutInProgress fired correctly on the first real deploy and its mail
was suppressed before Alertmanager dispatched it. One label was wrong.

kube-prometheus-stack ships general.rules -> InfoInhibitor:

  group by (namespace) (ALERTS{severity="info"} == 1)
    unless on (namespace) group by (namespace)
      (ALERTS{alertname!="InfoInhibitor", alertstate="firing",
              severity=~"warning|critical"} == 1)

which fires for any namespace holding a firing info alert AND NOTHING ELSE
wrong in it, and the chart's third inhibit_rule (source alertname=InfoInhibitor
-> target severity="info", equal [namespace]) then drops exactly that alert.

For a deploy notification that is the worst possible interaction, because the
suppressed case is the ONLY case that matters: on a healthy rollout of a healthy
cluster flux-system holds no warning or critical alert, so the notification is
inhibited precisely when it is wanted, and would arrive only when something else
in flux-system was already broken. Measured on prod: InfoInhibitor went active
2026-09-30T04:58:48Z, the same moment the alert began firing for 12
Kustomizations, and amtool showed no FluxRolloutInProgress in Alertmanager
either active or --inhibited, with zero send failures.

Upstream's intent is explicit and fair: severity "info" means "only worth
showing next to a real problem". A deploy notice is not that, so it must not
claim that severity. FluxRolloutInProgress now carries `notification: deploy`
and no severity at all, and the route matches on that label — no inhibit_rule
in the chart targets anything but severity values, and omitting severity also
stops InfoInhibitor firing for flux-system rather than merely working around it.
FluxRolloutStuck keeps severity: warning and is unaffected.

Also verified on prod in the process, all previously UNVERIFIED: the 33 metric
series exist exactly as designed, the `revision` label IS populated (so the
lastAppliedRevision/artifact.revision paths are right), the podAnnotations roll
forced a new KSM pod, and the rule both fired for 12 lagging Kustomizations and
then CLEARED on convergence — the completion signal's whole mechanism, on real
data. Routing proven by `amtool config routes test notification=deploy` ->
email-deploy.

Still unverified: no mail observed in the inbox. The 226->227 email counter
increment during the window cannot be attributed (no receiver label, other
alerts active), and I am not claiming it either way. See the handoff entry.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-30 00:05:10 -05:00
..

decisions/ — the handoff log, one file per entry

This directory is the decision log. ../DECISIONS-LOG.md is the frozen archive of everything written before 2026-09-29 and is never edited again (see "The archive is frozen" below).

Why this exists

DECISIONS-LOG.md was append-only in a single file, and every agent appended to its tail. Two branches that both append hit the same last hunk, so git cannot auto-merge them: the second one to be reviewed conflicts, and so does the third, and the fourth. Measured on 2026-09-29: four of the five open PRs were unmergeable, and DECISIONS-LOG.md was the only conflicting file in all four:

#22 fix/crafty-oauth2-proxy-signin      CONFLICT (content): DECISIONS-LOG.md
#20 feat/conformance-job-template       CONFLICT (content): DECISIONS-LOG.md
#19 feat/conformance-trigger-kubeconfig CONFLICT (content): DECISIONS-LOG.md
#18 feat/conformance-runner-image       CONFLICT (content): DECISIONS-LOG.md
#17 feat/ci-is-the-approver             clean  (it touched no log entry)

Nothing was wrong with any of those changes. The log's storage format serialised work that was otherwise parallel, and it got worse with every concurrent branch: N branches produce N-1 rebases, each of which invalidates any gate run that preceded it.

The fix is the obvious one for concurrent appenders: stop sharing a file. Each handoff is its own file. Two agents writing handoffs touch disjoint paths, so there is nothing for git to merge and nothing to conflict. This scales to any number of parallel branches with zero coordination — which is the point, since the agents are deliberately isolated from each other in separate worktrees and cannot coordinate.

There is deliberately no index file and no counter. An index would be a shared mutable file — exactly the thing that just failed — and a sequence number would have two agents racing for 0043. The directory listing is the index.

Writing an entry

One file per handoff, named:

decisions/YYYY-MM-DD-<slug>.md

<slug> is lowercase [a-z0-9-]. Use your branch name's last segment — it is already unique per unit of work, which is precisely the property the filename needs. A branch fix/crafty-oauth2-proxy-signin handed off on 2026-09-29 writes:

decisions/2026-09-29-crafty-oauth2-proxy-signin.md

The file is YAML frontmatter followed by three required sections:

---
date: 2026-09-29
branch: fix/crafty-oauth2-proxy-signin
title: The Keycloak gate works — Crafty's own login was the wall
---

## What changed

Prose. What you actually did, and why.

## What I verified

The measurements, with their numbers. Quote the field you read, not the prose you
believed. Name the gate result and its exit code.

## What I did NOT verify

Everything else. An honest "UNVERIFIED" is worth more than an optimistic "green" —
this repo has been burned by the difference more than once.

All three headings are required and enforced (ci/decisions-log.sh). They are CLAUDE.md §7's contract; making them a checked structure is what stops an entry degrading into "done ✅".

Reading the log

The whole log, oldest first:

ls decisions/[0-9]*.md | sort

The current PM handoff — the newest entry. Ties within a day are broken by what landed on main last, which is the honest answer, so ask git rather than the filename:

git log --diff-filter=A --format='%H' --name-only -- 'decisions/[0-9]*.md' \
  | grep '^decisions/' | head -1

For a quick skim of who said what:

grep -H '^title:' decisions/[0-9]*.md | sort

The archive is frozen

DECISIONS-LOG.md is read-only history. Its SHA-256 is pinned in decisions/ARCHIVE.sha256 and ci/decisions-log.sh fails the merge gate if the file changes. That is not ceremony: without it, the next agent to read runbooks/ or an old CLAUDE.md habit appends to the tail again and re-creates the conflict for everybody else, silently, one PR at a time.

If the archive genuinely must change (a leaked credential to redact, say), that is a deliberate, reviewed act: change it, run sha256sum DECISIONS-LOG.md > decisions/ARCHIVE.sha256, and say in your handoff entry why. Re-pinning is visible in the diff. Appending is not.