Compare commits

..
Author SHA1 Message Date
jakman85andClaude Opus 5 6586919132 fix(ci): CLUSTER_AVAILABLE=true would have FAKED A GREEN over 54 skipped tests
gate / gate (pull_request) Successful in 1m36s
gate / conformance (pull_request) Has been skipped
Wiring L2/L3 into CI was a tracked next step, described in the plan as needing only
vars.CLUSTER_AVAILABLE. Attempting it surfaced that the variable does not enable L2/L3 at
all -- it only changes which way the stage fails, and before this commit one of those ways
was a false pass.

MEASURED, before the guard:
  CLUSTER_AVAILABLE=true ./ci/conformance.sh   ->  rc=0
  "[junit] wrote skipped JUnit for 54 scaffolded test(s)"
  "[ ok ] L2/L3 conformance passed for touched subset"

run.sh exits 0 in scaffold mode when it cannot reach a cluster, marking every test
<skipped>. That is correct and honest FOR run.sh. It is catastrophic in ci/conformance.sh,
which reaches that code only because the environment DECLARED a cluster available -- so it
reported a pass over 54 tests that never executed. One Gitea variable away from a fake green
on a gate that is now an ENFORCING required status check, and nothing caught it because
nobody had ever set the variable.

The CI runner holds no cluster credential by design (automountServiceAccountToken: false),
so the local run this stage attempts can never succeed. The in-cluster trigger path that
would make L2/L3 actually run is still unimplemented, and is blocked on a privilege-
escalation design decision that is the operator's to make (plan section 4: any principal
that can `create jobs` in ns conformance can run arbitrary code as the privileged
conformance-runner SA, because Kubernetes places no `use` check on serviceAccountName).

The stage now BLOCKS (exit 1) when a cluster is declared but every test skipped, which is
what the merge-gate decision matrix already said should happen ("conformance skipped while
CLUSTER_AVAILABLE=true -> BLOCK"). Verified: rc=1, naming the count and the reason.

The plan document claimed CLUSTER_AVAILABLE was "the only remaining gate". Corrected in
place with the measurement and an explicit do-not-set-this-yet.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-29 02:58:26 -05:00
jakman85 e8e6b9c681 Merge pull request 'fix(gitea): admins could override the gate' (#10) from fix/gitea-admins-could-override-the-gate into main
gate / gate (push) Successful in 1m34s
gate / conformance (push) Has been skipped
Reviewed-on: #10
Reviewed-by: Keith Jackson <[email protected]>
2026-09-29 07:43:52 +00:00
jakman85andClaude Opus 5 fcc66c7858 fix(gitea): admins could override the gate -- the enforcement was not enforcing
gate / gate (pull_request) Successful in 1m34s
gate / conformance (pull_request) Has been skipped
enable_status_check and required_approvals were set earlier today and recorded as done.
They did not bite. block_admin_merge_override was false, and Gitea lets repo admins
override merge requirements without it. jakman85 holds owner, so both requirements were
bypassable by exactly the account that performs every merge.

The evidence was already on the record: PRs #5-#9 all merged while required_approvals: 1
was in force, with ZERO approvals recorded on any of them.

Two process failures, both the same shape as the defect this whole session started with:

1. Verified with the wrong view. /branches/main reports protected, enable_status_check and
   required_approvals but NOT block_admin_merge_override -- it showed everything green while
   the rule was overridable. /branch_protections/main is authoritative.
2. Probed for a field name assumed to exist. This Gitea (1.24.3) has no `apply_to_admins`;
   asking jq for it returns null, which reads like "set to false" but means "key absent", so
   a PATCH that silently did nothing produced a convincing before/after. jq returning null
   is not a value -- it may be an absence. Enumerate with to_entries.

require-gate.sh now sets block_admin_merge_override: true; block-admin-override.sh fixes it
in isolation. Both carry the field-name warning.

This PR is the test. If it merges WITHOUT an approval, the fix did not work.

Also records the governance question: with only jakman85 (human) and gitea-admin (agent),
a human-authored PR now needs the automation account to approve it. That is ceremony, not
review, and the choice between keeping it, dropping approvals in favour of the status
check, or adding a second human is the operator's to make.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-29 02:36:38 -05:00
jakman85 b9e8a4f9e8 Merge pull request 'docs(ci): close out the hardening; correct a claim that shipped wrong' (#9) from docs/close-out-ci-hardening into main
gate / gate (push) Successful in 1m34s
gate / conformance (push) Has been skipped
Reviewed-on: #9
2026-09-29 07:32:52 +00:00
6 changed files with 163 additions and 5 deletions

No files matched your search

+40
View File
@@ -4924,3 +4924,43 @@ being unable to recur.
4. `required_approvals: 1` means a PR you open as `jakman85` needs `gitea-admin` to approve.
If that friction bites, either open agent work as `gitea-admin` and review as `jakman85`, or
drop back to relying on the status check alone.
## 2026-09-29 (later still) — the enforcement we "turned on" was overridable by the account that merges
`enable_status_check: true` and `required_approvals: 1` were set earlier today and recorded as
done. **They did not bite.** `block_admin_merge_override` was `false`, and Gitea lets repository
admins override merge requirements without it. `jakman85` holds `owner`, so both requirements
were bypassable by exactly the account that performs every merge.
**The evidence was already on the record and nobody read it:** PRs #5, #6, #7, #8 and #9 all
merged while `required_approvals: 1` was in force, with **zero approvals recorded on any of
them**. A one-line check (`/pulls/{n}/reviews`) would have shown it at any point.
Two process failures, both mine, both the same shape as the original defect:
1. **I verified with the wrong view.** `/branches/main` reports `protected`,
`enable_status_check` and `required_approvals` but NOT `block_admin_merge_override`. It
showed everything green while the rule was overridable. The authoritative object is
`/branch_protections/main` — read it and enumerate its keys.
2. **I probed for a field name I assumed existed.** This Gitea (1.24.3) has no
`apply_to_admins`. Asking `jq` for it returns `null`, which reads exactly like "set to
false" but means "key absent" — so a PATCH that silently did nothing looked like a real
before/after. **`jq` returning null is not a value; it may be an absence.** Enumerate with
`to_entries`.
Now `block_admin_merge_override: true`, applied and read back. `infra/gitea/require-gate.sh`
sets it, and `infra/gitea/block-admin-override.sh` fixes it in isolation.
**This is still only a setting.** It is proven when a green PR is REFUSED a merge until
approved — and that has not been observed yet. This PR is the test: if it merges without an
approval, the fix did not work and the finding stands.
### The governance question this exposes
There are two accounts: `jakman85` (owner, the human) and `gitea-admin` (the agent). With
approvals now genuinely required, an agent-authored PR is approved by the human — which is the
intent. But a PR authored by `jakman85` needs `gitea-admin` to approve it, i.e. the automation
account rubber-stamping the human. That is ceremony, not review. Options: keep it and accept
the ceremony; drop `required_approvals` to 0 and rely on the status check, which is the gate
that actually verifies something; or add a second human account. **This is a decision for the
operator, not a default to be chosen silently.**
+40
View File
@@ -61,5 +61,45 @@ if [ -d "${CONF_ART}/coverage" ]; then
cp -r "${CONF_ART}/coverage/." "${COVERAGE_DIR}/"
fi
# ---------------------------------------------------------------------------------------
# DID IT ACTUALLY RUN? -- the guard without which this stage FAKES A GREEN.
#
# run.sh exits 0 in "scaffold mode" when it cannot reach a cluster: it emits a JUnit file
# whose every testcase is <skipped> and returns success. That is correct FOR run.sh -- an
# honest skip is its documented no-cluster behaviour. It is catastrophic HERE, because
# reaching this point means the environment DECLARED CLUSTER_AVAILABLE=true, and this stage
# would otherwise print "L2/L3 conformance passed" over 54 tests that never executed.
#
# MEASURED 2026-09-29, before this guard existed:
# CLUSTER_AVAILABLE=true ./ci/conformance.sh -> rc=0
# "[junit] wrote skipped JUnit for 54 scaffolded test(s)"
# "[ ok ] L2/L3 conformance passed for touched subset"
# One Gitea variable away from a fake green on an enforcing merge gate. That is exactly the
# HARD RULE 4 violation this repo's whole gate design exists to prevent, and nothing caught
# it because nobody had ever set the variable.
#
# A skip while a cluster is DECLARED is a contradiction: either the declaration is wrong or
# the cluster is unreachable. The merge-gate decision matrix already says this case is BLOCK
# ("conformance skipped while CLUSTER_AVAILABLE=true -> BLOCK (it should have run)"). This
# makes the stage itself honour that instead of reporting a pass.
JUNIT="${CONF_ART}/report.junit.xml"
if [ -f "$JUNIT" ]; then
total="$(grep -o '<testcase' "$JUNIT" | wc -l | tr -d ' ')"
skipped="$(grep -o '<skipped' "$JUNIT" | wc -l | tr -d ' ')"
if [ "${total:-0}" -gt 0 ] && [ "${skipped:-0}" -ge "${total:-0}" ]; then
err "CLUSTER_AVAILABLE=true, but the L2/L3 suite SKIPPED every one of its ${total} tests"
err " (scaffold mode -- run.sh could not reach a cluster and exited 0)."
err " This stage will NOT report that as a pass. Either the cluster is unreachable from"
err " this runner, or CLUSTER_AVAILABLE is set when it should not be."
err " NOTE: the in-cluster trigger path is still UNIMPLEMENTED -- see"
err " docs/plans/l2-l3-conformance-ci-wiring.md. Setting CLUSTER_AVAILABLE=true does not"
err " by itself make L2/L3 run; it only makes this stage attempt a LOCAL run that the CI"
err " runner has no credential for (automountServiceAccountToken: false, by design)."
summary "- conformance-l2-l3: **BLOCKED — declared available but every test skipped** (not a pass)"
exit 1
fi
log "L2/L3 executed: ${total} testcase(s), ${skipped} skipped"
fi
ok "L2/L3 conformance passed for touched subset"
summary "- conformance-l2-l3: **passed**"
+15 -2
View File
@@ -38,8 +38,21 @@ agents. So the credential model must be decided by the operator before anything
| Runner deployment | `apps/wave-1/gitea-actions.yaml` | act_runner + DinD (Docker executor). **`automountServiceAccountToken: false`** — the runner and its job containers hold NO SA token. Broad egress allowed (`- {}`), so it can *reach* the kube-API VIP `192.168.1.49:6443`, but has no credential. |
| Secrets encryption | `.sops.yaml` (repo root) | age recipient `age175l8m…mfzg`; encrypts `data`/`stringData` of `*.sops.yaml` under `platform|apps|catalog/**` and `secrets/**`. Private key is the in-cluster `sops-age` Secret Flux uses. |
**Cluster reality:** the "Phase 0 / no cluster" language in these files is stale — the dev cluster
is LIVE (Phase 6). So `CLUSTER_AVAILABLE` is the only remaining gate, exactly as the blueprint says.
**Cluster reality:** the "Phase 0 / no cluster" language in these files is stale — the cluster is
LIVE (and it is now PROD; dev was decommissioned 2026-09-12).
> **CORRECTION (2026-09-29): `CLUSTER_AVAILABLE` is NOT "the only remaining gate", and setting it
> today is actively DANGEROUS.** Measured: with `CLUSTER_AVAILABLE=true` and no reachable cluster,
> `conformance/run.sh` exits 0 in scaffold mode having marked all 54 tests `<skipped>`, and
> `ci/conformance.sh` then printed `[ ok ] L2/L3 conformance passed for touched subset` — **rc 0, a
> fake green**, on a merge gate that is now an enforcing required status check. The CI runner has
> no cluster credential at all (`automountServiceAccountToken: false`, by design), so the "local
> run" this stage attempts can never succeed; it can only skip and, before the guard added on
> 2026-09-29, lie about it.
>
> `ci/conformance.sh` now BLOCKS (exit 1) when a cluster is declared but every test skipped. **Do
> not set `CLUSTER_AVAILABLE=true` until §3's trigger path actually exists** — the variable does
> not enable L2/L3, it only changes which way this stage fails.
---
+21 -2
View File
@@ -55,8 +55,27 @@ All four steps are complete and each was verified, not assumed:
| 4. Collaborator + approvals | ✅ `jakman85` is owner; `required_approvals: 1` |
Direct push to `main` was **provoked and rejected** (`pre-receive hook declined`) as
`gitea-admin`, a site admin — so protection is not merely declared, it holds for the account
agents authenticate as.
`gitea-admin`, a site admin — so push protection is not merely declared, it holds for the
account agents authenticate as.
### 5. Block the admin override — the step the first pass MISSED
`enable_status_check` and `required_approvals` were set on 2026-09-29 and **still did not
bite**, because `block_admin_merge_override` was `false`. Gitea lets repo admins override
merge requirements without it, and `jakman85` holds `owner` — so the two settings that looked
like the gate were overridable by precisely the account that merges.
**The evidence was sitting in plain sight:** PRs #5 through #9 all merged with
`required_approvals: 1` in force and **zero approvals recorded on any of them**. Checking the
*branch* view (`/branches/main`) hid this — it does not expose the field. Read the rule object
(`/branch_protections/main`) and enumerate its keys.
> **Do not probe for field names you assume exist.** This Gitea (1.24.3) has no
> `apply_to_admins`; asking `jq` for it returns `null`, which reads like "set to false" but
> means "key absent". Enumerate with `to_entries` and act on what is really there.
Now `block_admin_merge_override: true`. **That is still only a setting, not evidence** — it is
proven when a green PR is REFUSED a merge until approved, and not before.
### `require-gate.sh` / `verify-then-enforce.sh`
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Stop repository admins from overriding the merge requirements on `main`.
#
# WHY. Gitea lets repo admins override branch-protection merge requirements unless
# `block_admin_merge_override` is set. It was FALSE, and jakman85 holds `owner` -- so
# required_approvals and the required status check were both overridable by the one account
# that actually merges. Evidence: PRs #5-#9 all merged with ZERO approvals recorded against
# `required_approvals: 1`.
#
# NOTE ON FIELD NAMES: this Gitea (1.24.3) has NO `apply_to_admins` field. Querying it with jq
# returns null, which reads like "set to false" but actually means "key absent". Enumerate the
# real keys (`to_entries`) rather than probing names you assume exist.
#
# Recoverable: an owner can PATCH this back to false if it gets in the way.
set -euo pipefail
HOST=gitea.home.keithjackson.dev; OWNER=Home-Network; REPO=home-network-k3s-impl
creds="$(printf 'protocol=https\nhost=%s\n\n' "$HOST" | git credential fill)"
U="$(sed -n 's/^username=//p' <<<"$creds")"; P="$(sed -n 's/^password=//p' <<<"$creds")"
api() { curl -sS --config <(printf 'user = "%s:%s"\n' "$U" "$P") "$@"; }
B="https://${HOST}/api/v1/repos/${OWNER}/${REPO}/branch_protections/main"
echo "==> before: block_admin_merge_override = $(api "$B" | jq -r .block_admin_merge_override)"
api -X PATCH -H 'Content-Type: application/json' \
-d '{"block_admin_merge_override": true}' "$B" >/dev/null
AFTER=$(api "$B" | jq -r .block_admin_merge_override)
echo "==> after: block_admin_merge_override = $AFTER"
[ "$AFTER" = true ] || { echo "PATCH did not take -- do not record this as enforced." >&2; exit 1; }
api "$B" | jq -r '{enable_push,enable_status_check,status_check_contexts,required_approvals,block_admin_merge_override}'
cat <<'NOTE'
NOW PROVE IT -- the setting is not the evidence. On the next PR:
* let the gate go green, then try to merge WITHOUT approving it.
* Gitea must REFUSE. If it merges, this still is not doing its job.
Do not record approvals as enforced until you have seen a merge refused.
NOTE
+11 -1
View File
@@ -12,6 +12,15 @@
# * NOT `gate / conformance (*)` -- that job is `if: false`, and a skipped Gitea job reports
# SUCCESS ("Has been skipped"). Requiring it would pass VACUOUSLY forever while nothing
# ran, which under HARD RULE 4 is worse than a deadlock: a deadlock is loud.
# block_admin_merge_override: true is LOAD-BEARING and was missing on the first pass. Gitea
# lets repo admins override merge requirements without it, so required_approvals and the
# required status check are both bypassable by exactly the account that does the merging.
# Measured: PRs #5-#9 merged with ZERO approvals recorded against required_approvals: 1.
#
# FIELD NAMES: this Gitea (1.24.3) has NO `apply_to_admins`. Asking jq for it returns null,
# which reads like "false" but means "key absent" -- enumerate real keys with `to_entries`
# instead of probing names you assume exist. That mistake cost a wasted PATCH.
#
# * NOT the `(push)` variant -- after PR #6 there are no per-branch push runs at all, so a
# PR would never produce that context.
set -euo pipefail
@@ -28,7 +37,8 @@ jq -n '{
required_approvals: 1,
block_on_rejected_reviews: true,
dismiss_stale_approvals: true,
block_on_outdated_branch: false
block_on_outdated_branch: false,
block_admin_merge_override: true
}' | api -X PATCH -H 'Content-Type: application/json' --data @- \
"https://${HOST}/api/v1/repos/${OWNER}/${REPO}/branch_protections/main" >/dev/null