docs(decisions): move PR #19's handoff out of the frozen DECISIONS-LOG.md
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:
1 parent
ae5c7385a5
commit
ad5de2dba9
1 file changed
+125
@@ -0,0 +1,125 @@
|
||||
---
|
||||
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**.
|
||||
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 `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.
|
||||
Reference in new issue
Block a user