docs(decisions): move PR #19's handoff out of the frozen DECISIONS-LOG.md
gate / gate (pull_request) Successful in 1m35s
gate / conformance (pull_request) Has been skipped

This branch originally appended ~99 lines to the tail of DECISIONS-LOG.md. That
tail is the hunk every concurrent branch collides on: measured against the live
Gitea on 2026-09-29, four of the five open PRs were mergeable:false and
DECISIONS-LOG.md was the ONLY conflicting path in all four. PR #19 was one of them.
Nothing was wrong with the change; the log's storage format serialised work that
was otherwise parallel.

So the handoff now lives in its own file, decisions/2026-09-29-conformance-trigger-kubeconfig.md,
in the one-entry-per-file layout PR #24 introduces (frontmatter date/branch/title,
plus "What changed" / "What I verified" / "What I did NOT verify"). Concurrent
branches then touch disjoint paths and there is nothing to merge.

The branch was rebuilt on today's main with the DECISIONS-LOG.md change dropped
entirely; infra/gitea/README.md and infra/gitea/conformance-trigger-kubeconfig.sh
are byte-identical to the original commits. The original commit that touched ONLY
DECISIONS-LOG.md -- the measurement that PR #19's CI has no approval-policy check
-- was dropped as a commit and its substance folded into the new entry verbatim in
meaning, including every UNVERIFIED it recorded.

No content was upgraded. The script is still UNEXECUTED: nothing minted, no Gitea
object written, no kubectl mutation, and the Gitea variables API shape remains
documentation rather than measurement.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
jakman85andClaude Opus 5 committed 2026-09-29 22:46:35 -05:00
1 parent 4459e3be09
commit 7f5a32f629
1 file changed
+124
@@ -0,0 +1,124 @@
---
date: 2026-09-29
branch: feat/conformance-trigger-kubeconfig
title: The trigger credential script — it REFUSES, on purpose, and it is UNEXECUTED
---
## What changed
Delivered `infra/gitea/conformance-trigger-kubeconfig.sh` —
`docs/plans/l2-l3-conformance-ci-wiring.md` §3 item 6 — plus its section in
`infra/gitea/README.md`. **The script is UNEXECUTED.** Minting a cluster credential and
changing Gitea variables are operator actions; nothing was minted, no Gitea object was
written, no `kubectl` mutation was made. Everything below is a claim about the *script*,
not about the platform.
*What it does.* Mints a token for `conformance/conformance-trigger` (TokenRequest), wraps
**only that token** with the cluster CA and server into a kubeconfig, stores it as the
Actions secret `CONFORMANCE_TRIGGER_KUBECONFIG` (legal name — **not** the reserved `GITEA_`
prefix), and sets `vars.CLUSTER_AVAILABLE` only as a separately-requested,
precondition-gated, TTY-confirmed final step. The token is never printed and never reaches
argv: it goes into a 0600 file and into the PUT **body file**. The script refuses to run
under `set -x`.
*The design decision worth reviewing: TWO precondition classes.*
- **mint-blocking (cluster-side):** SA/Role/RoleBinding present, and ClusterPolicy
`conformance-trigger-job-shape` present + `Enforce` + `failurePolicy: Fail` + Ready. If any
fails, **nothing is minted**. Rationale: Kubernetes puts no `use` check on a Job's
`serviceAccountName`, so a `create jobs` token in that namespace IS the privileged
`conformance-runner` identity unless the admission policy bounds it. RBAC cannot fix that
(plan §4); minting before the boundary is live would hand CI the escalation the boundary
exists to close.
- **arm-blocking (repo-side):** digest-pinned `CONFORMANCE_RUNNER_IMAGE`, an init-container
plus the admitted image in `chainsaw-job.yaml`, and `CONFORMANCE_TRIGGER_KUBECONFIG`
referenced in `ci/conformance.sh` **code rather than a comment**. These block only the
variable, because storing the secret early is harmless and is the plan's own order.
**Measured in the tree at the time of writing: all three arm-blocking checks FAIL** (no
`CONFORMANCE_RUNNER_IMAGE` in `conformance/versions.env`; `chainsaw-job.yaml` is still the
skeleton with an empty `workspace` emptyDir and the stock chainsaw image; `ci/conformance.sh`
names the trigger kubeconfig only in its TODO comment). So on a live run today the script
would provision the secret and then **refuse to arm `CLUSTER_AVAILABLE`**, naming each
missing item — which is the intended behaviour, not a defect. §3 items 1, 2 and 4 are the
work that unblocks it.
*Both writes are verified the way `set-ci-token.sh` learned to.* `curl -sS` exits 0 on an
HTTP error, so status alone proves nothing: each write checks `%{http_code}` **and** reads
the object back. For the secret that is presence-by-name only (Gitea never returns secret
values) and the script says so rather than implying the content was checked; for the variable
it asserts the value reads back as exactly `true`.
*Noted, not fixed at the time (since fixed on this branch).* `infra/gitea/README.md`'s
detector table still named `GITEA_SETTINGS_TOKEN` — the illegal name PR #15 replaced with
`SETTINGS_API_TOKEN`. The second commit on this branch corrects the README's table and its
closing "remaining step" line, and records what `ci/gitea-settings.sh` actually does
(`TOKEN="${SETTINGS_API_TOKEN:-${GITEA_SETTINGS_TOKEN:-}}"` — the old name survives only as a
local-run environment variable, never as CI wiring, because the secret that would feed it
cannot be created at all). Documentation only, no behaviour change. Still NOT fixed, because
it is outside this PR's diff: `ci/gitea-settings.sh:45` instructs the reader to create a repo
secret `GITEA_SETTINGS_TOKEN`, which is impossible.
*Rework, 2026-09-29.* This branch originally appended ~99 lines to the tail of
`DECISIONS-LOG.md`. That is the hunk every concurrent branch collided on and the sole reason
PR #19 was `mergeable: false`. The branch was rebuilt on today's `main` with the
`DECISIONS-LOG.md` change dropped entirely, and this file is that handoff moved into
`decisions/`. The changes to `infra/gitea/**` are byte-identical to the original three
commits; the commit that contained only the log append was dropped and its substance folded
in below.
## What I verified
*Of the script, before it stores anything:* it PROVES the credential narrow — `auth whoami`
must return `system:serviceaccount:conformance:conformance-trigger`; a real `get jobs`
round-trip must succeed; then an `auth can-i` matrix asserts BOTH halves — granted
(`create/get jobs`, `get pods`, `get pods/log`) and **denied** (`delete jobs`, `create
pods/exec`, `get secrets`, `create jobs` in `default`, `get nodes`,
`'*' '*' --all-namespaces`). A mismatch aborts before anything is stored, and the message
says explicitly **not** to widen `conformance/incluster/rbac.yaml` to make it pass. That is a
property of the code as read, not of an execution.
*Of PR #19's CI, measured rather than assumed — and it contradicts the brief.* The brief for
this work stated this PR's CI would FAIL `approval-policy` because `infra/gitea/**` is
security-relevant. Measured on the PR head commit's `/statuses`:
gate / gate (pull_request) success Successful in 1m34s
gate / conformance (pull_request) pending Blocked by required conditions
`.gitea/workflows/` on `main` contains only `gate.yml` and the dormant `merge-gate.yaml`.
**No path-scoped approval policy exists**, so nothing machine-enforces "security-relevant
paths need a human"; that rule rests entirely on `required_approvals: 1` plus a reviewer
actually reading the diff. Another instance of the CLAUDE.md §2b failure mode — a control
believed to exist because a brief said so. If the policy is intended, it is missing and needs
its own change. And per this repo's own rule, that green `gate` tick is **not** evidence this
script works: the gate never executes `infra/gitea/**`, and the L2/L3 stage skipped.
*Gate on the reworked branch (pinned toolbox, from the agent worktree, `DEV_KUBECONFIG=/dev/null`
plus the `ENGINE` bind-mount shim per CLAUDE.md §3):* `MERGE GATE: UNDETERMINED`,
**rc 3**, re-run on the reworked branch (diff base `origin/main @ cadf64a51492` ... `HEAD @
864185494f1f`; touched requirement resolved as `R-ENV-01`). `spec-immutability`, `lint`, `l0`,
`render-golden` and `l1` all green; the gate decision names exactly one stage that did not run
— `gitea-settings` (`SETTINGS_API_TOKEN` is a credential this agent does not hold, unwired
repo-wide). The `conformance` stage also reported SKIPPED (`CLUSTER_AVAILABLE!=true`, Phase 0).
`ci/decisions-log.sh` is not on this branch's base, so that stage did not appear in this run.
rc 3 is UNDETERMINED, not green, and is not reported as green here.
## What I did NOT verify
**The script has never executed.** Not one line of it has run against Gitea or against the
cluster. Only `bash -n` was run (clean); `shellcheck` is not on the host and there is no
shellcheck stage in the gate.
- The Gitea variables API shape (POST-creates / PUT-updates, body field `value` not `data`)
is from **documentation, not measurement**. The script tries POST then falls back to PUT,
prints the response body on failure, and treats the read-back as the authority.
- The Kyverno readiness field (`.status.ready` vs. a Ready condition) is handled both ways
and treats "neither present" as a block rather than assuming ready — but which form this
cluster actually reports is **unmeasured**.
- Whether the kubeconfig's embedded server URL is reachable from the runner pod **cannot be
known from here** — hence `--server`, and a hard refusal on a loopback address.
- No credential was minted, no Actions secret was created, `vars.CLUSTER_AVAILABLE` was not
set, and no `can-i` matrix was ever run against a real token. The narrowness argument above
is read from the RBAC and the script, not observed.
- The gate proves only its L0/L1 offline subset. L2/L3 conformance did not run, so nothing
here is evidence about runtime behaviour.