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]>
8.1 KiB
date, branch, title
| date | branch | title |
|---|---|---|
| 2026-09-29 | feat/conformance-trigger-kubeconfig | 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-shapepresent +Enforce+failurePolicy: Fail+ Ready. If any fails, nothing is minted. Rationale: Kubernetes puts nousecheck on a Job'sserviceAccountName, so acreate jobstoken in that namespace IS the privilegedconformance-runneridentity 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 inchainsaw-job.yaml, andCONFORMANCE_TRIGGER_KUBECONFIGreferenced inci/conformance.shcode 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.
Diff base origin/main @ c59fe9f923c4 ... this branch's head; touched requirement resolved as
R-ENV-01. spec-immutability, decisions-log, 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). The new
decisions-log stage — merged to main as PR #24 after this rework began — validated this very
file: "archive frozen, 2 entries well-formed", with DECISIONS-LOG.md unchanged and matching
decisions/ARCHIVE.sha256. 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
valuenotdata) 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.readyvs. 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_AVAILABLEwas not set, and nocan-imatrix 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.