Files
home-network-k3s-impl/decisions
jakman85andClaude Opus 5 f1d16f44b8
gate / gate (pull_request) Failing after 1m43s
gate / conformance (pull_request) Has been skipped
chore(decisions): record the CI reading, and why the metrics policy is NOT here
Follow-up to the branch's one substantive commit; no code change.

CI on 99560c2d, read from the commit's /statuses rather than from a tick:
  gate / gate (pull_request)         state=failure   desc='Failing after 1m44s'
  gate / conformance (pull_request)  state=pending   desc='Blocked by required conditions'
The second line is PENDING, not the `success` + "Has been skipped" trap -- the
conformance job is gated on the gate job, which failed, so it never ran.

MY LOCAL `approval-policy: green` WAS MEANINGLESS AND I NEARLY REPORTED IT AS
EVIDENCE. ci/approval-policy.sh:47 reads GITHUB_EVENT_NAME, unset outside CI, so
it short-circuits to "N/A": a clean local gate STRUCTURALLY cannot catch a
sensitive-path problem. Verified by re-running with
GITHUB_EVENT_NAME=pull_request PR_NUMBER=34, where the stage evaluates and fails
CLOSED on the missing token while every other stage stays green -- which bounds
the CI failure to that one stage. I could NOT read CI's job log to confirm it
from CI's own output: this Gitea answers 404 on /actions/runs and on
/actions/jobs/{task}/logs, so only /actions/tasks is available. Attribution
therefore rests on the local reproduction plus the sensitive-path rule, not on
CI's log -- weaker evidence than PR #28 had, named rather than papered over.

Branch protection and the runner re-measured against the live API (CLAUDE.md
2b), not inherited from the CLAUDE.md table: protected=True,
user_can_push=False, enable_status_check=True, contexts=['gate / gate
(pull_request)'], required_approvals=1; runner id=1 incluster-dind online. So a
human is required on every PR here regardless of path. I did NOT provoke a
rejected push to main, so user_can_push is the API's word only.

DECLINED, with reasons: bundling Ines Dubois's metrics-ingress NetworkPolicy.
The coordinator asked for my judgement and the answer is to queue it as the next
PR, not to drop it. Principally because it is a MODEL question, not a policy to
bolt on: schema 1.6.0's observability.metricsEndpoint already renders the
monitoring-namespace ingress allow, and what the model cannot express is a
SECOND metrics endpoint belonging to a sidecar the RENDERER injected. Keying a
bespoke policy off the pod label auth-mode=forward-auth hardcodes "forward-auth
implies 44180" instead of deriving it -- the "list, don't derive" anti-pattern
oauth2-proxy-cookie-names-distinct assertion (5) forbids, one directory from the
check that forbids it. Also: it is a trust-boundary ingress allow and a
different requirement set (R-NET-01/02, R-OBS-01 vs R-IDP-02), so it should not
be reviewed in the shadow of an --api-route headline; the conflict argument does
not apply to my diff, which regenerates TWO goldens not five, and a
CiliumNetworkPolicy is a different document in files git merges independently;
every fact in the finding is hers and I have measured none of it; and the
PodMonitor it serves is in HER PR #29, so shipping the policy alone would open
an ingress allow for a scraper that selects nothing.

Also records that the sidecars' metrics remain UNSCRAPABLE and that this branch
does not change that.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-10-01 22:30:01 -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.