fix(ci): check out the siax-templates pin directly instead of comparing main #120

Merged
admin merged 1 commits from fix/templates-pin-0pt-20260926 into main 2026-09-26 18:47:55 +00:00
Owner

Problem

.gitea/workflows/release.yml's G17 gate cloned sax3l/siax-templates on the moving branch main and then required that clone's HEAD to equal the static SIAX_TEMPLATES_PINNED_SHA. Any merge in siax-templates — including merges that touch nothing under ci/v2/reference-hooks — therefore fails this repo's release chain, with an error that says the gate is unreviewed when in fact nothing about the reviewed gate changed. Findings CD-03 (HIGH) and CI-RED-02.

It is red right now, measured today:

$ git clone --quiet --branch main https://git.siax.io/sax3l/siax-templates.git /tmp/x
$ git -C /tmp/x rev-parse HEAD
6612f6ae93c079806f64dc7c80eff746bac6a32c      # main moved 2026-09-26 12:10Z
# pinned: 09504e3a593ee8ce0eaba606df31c349a3d52277  -> old logic exits 1

Root cause

The pin was enforced by comparing it against a branch tip instead of by checking it out. That makes the gate's pass/fail depend on unrelated activity in another repo. SIAX_TEMPLATES_REF: main was the load-bearing mistake, not the pin itself.

What this change does

  • The step now git init + git remote add + fetches the pinned commit by SHA (fetch --depth 1 origin $SIAX_TEMPLATES_PINNED_SHA) and checks it out detached. No branch is consulted.
  • Fallback: if the server refuses a by-SHA fetch (uploadpack.allowReachableSHA1InWant off), it fetches the branches and checks the same commit out of them. Either path lands the work tree on exactly the pinned commit, never on a branch tip.
  • Fails closed on the thing that actually matters: if the pinned commit cannot be checked out (removed or rewritten upstream), the step emits a ::error:: and exits 1.
  • The post-checkout actual_sha == pin assertion is kept, so the gate still proves on the run log which commit it executed.
  • SIAX_TEMPLATES_REF is removed from env: rather than left dangling, so the moving-branch shape cannot quietly return.
  • The pin value is unchanged. 09504e3a593ee8ce0eaba606df31c349a3d52277 was verified to still exist upstream via the Gitea API before writing anything (GET /api/v1/repos/admin/siax-templates/git/commits/09504e3a… → 200, "Upload complete release evidence and validate artifact receipt (#138)", 2026-09-25T00:23:04Z; it is two commits behind main). Since the pinned commit exists, there was no reason to re-pin, and re-pinning would have undone the deliberate review in #119.
  • Two comments that asserted the old "refuses to run if main has moved past the pin" behaviour (.siax/hooks/G17.sh header, .siax/hooks/README.md "Pin status") are corrected in the same change, because this change makes them false.

How it was verified (commands actually run, on this branch's exact YAML)

The step's run: script was extracted from the committed YAML with yaml.safe_load and executed as-is, with one substitution: https://oauth2:${SIAX_CI_TOKEN}@git.siax.io/ → https://git.siax.io/, so auth came from the machine's git credential helper and no token was ever written to disk.

# What Result
1 python3 -c "yaml.safe_load(open('.gitea/workflows/release.yml'))" parses; env no longer contains SIAX_TEMPLATES_REF
2 bash -n + shellcheck -s bash on the extracted step both clean, exit 0, no exclusions
3 Happy path: step run with pin 09504e3a… against live siax-templates exit 0 — siax-templates G17 reference @ 09504e3a593ee8ce0eaba606df31c349a3d52277 (pinned commit, checked out directly); rev-parse HEAD = the pin; ci/v2/reference-hooks/G17.sh present and chmod +x succeeded on all 4 hooks + 2 lib/*.sh
4 Fallback path forced (plain git fetch origin, then checkout the pin) exit 0, HEAD = 09504e3a… — pin is reachable from a branch, so the fallback lands on the same commit
5 Fail-closed: pin set to deadbeef… (commit does not exist) by-SHA fetch refused → fallback fetch → checkout fails → ::error::siax-templates commit deadbeef… not found …, exit 1
6 Old logic on the same inputs, for contrast clone --branch main → HEAD 6612f6ae… ≠ pin → would exit 1 (the bug)
7 sh scripts/check-masterplan-lock.sh (runs on every branch push) masterplan lock check OK
8 pnpm install --frozen-lockfile → pnpm run typecheck install exit 0; tsc -p tsconfig.check.json exit 0
9 pnpm run test (vitest, CI-blocking) 95 files passed, 22 skipped; 1117 tests passed, 184 skipped, exit 0

Not run: pnpm run build (next build) and the integration job — this diff touches no TypeScript, only a Gitea workflow and two comment blocks; the release chain itself cannot be executed outside CI (it needs SIAX_CI_TOKEN, registry, cosign and AUD0 secrets, none of which are provisioned on this repo — see .siax/hooks/README.md).

Risk

Low, and confined to the gate's own fetch step.

  • The supply-chain property is preserved, not weakened: CI still runs exactly one specific reviewed commit of siax-templates, asserted after checkout. The property that is removed is "fail whenever someone else merges", which was noise, not security.
  • The checkout is shallow on the by-SHA path. Verified safe: nothing under ci/v2/reference-hooks/ uses git history (grep for git rev-parse|git log|git describe|SIAX_TEMPLATES_DIR → no hits).
  • Same authenticated-clone-at-runtime mechanism and same SIAX_CI_TOKEN as before; no new secrets, no new permissions, no deploy behaviour touched.
  • New failure mode if someone force-pushes away the pinned commit in siax-templates: the gate fails with an explicit message telling the reviewer to re-review and bump the pin. That is the intended fail-closed direction.

Rollback

git revert 65ee88a — single commit, three files, no state anywhere else. The gate returns to comparing main (and to being red). Nothing is deployed, tagged, or published by this PR.

Out of scope, noticed while working here

Not touched, because they belong to the pin-bump work item, not this one:

  • .siax/hooks/README.md "Pin status" still names 384e35060b91… and .siax/hook-pins.json templateRef still names a401880aa19a…; both predate #119's bump of the workflow to 09504e3a…. release.yml is authoritative and is the value CI uses — this PR's README edit says so explicitly.
  • The same moving-branch pattern exists in 0pt-motor, act0 and h0st; those are being fixed in their own PRs.

🤖 Generated with Claude Code

## Problem `.gitea/workflows/release.yml`'s G17 gate cloned `sax3l/siax-templates` on the **moving** branch `main` and then required that clone's HEAD to equal the static `SIAX_TEMPLATES_PINNED_SHA`. Any merge in `siax-templates` — including merges that touch nothing under `ci/v2/reference-hooks` — therefore fails this repo's release chain, with an error that says the gate is unreviewed when in fact nothing about the reviewed gate changed. Findings **CD-03 (HIGH)** and **CI-RED-02**. It is red right now, measured today: ``` $ git clone --quiet --branch main https://git.siax.io/sax3l/siax-templates.git /tmp/x $ git -C /tmp/x rev-parse HEAD 6612f6ae93c079806f64dc7c80eff746bac6a32c # main moved 2026-09-26 12:10Z # pinned: 09504e3a593ee8ce0eaba606df31c349a3d52277 -> old logic exits 1 ``` ## Root cause The pin was enforced by *comparing* it against a branch tip instead of by *checking it out*. That makes the gate's pass/fail depend on unrelated activity in another repo. `SIAX_TEMPLATES_REF: main` was the load-bearing mistake, not the pin itself. ## What this change does - The step now `git init` + `git remote add` + **fetches the pinned commit by SHA** (`fetch --depth 1 origin $SIAX_TEMPLATES_PINNED_SHA`) and checks it out detached. No branch is consulted. - Fallback: if the server refuses a by-SHA fetch (`uploadpack.allowReachableSHA1InWant` off), it fetches the branches and checks the same commit out of them. Either path lands the work tree on exactly the pinned commit, never on a branch tip. - Fails closed on the thing that actually matters: if the pinned commit cannot be checked out (removed or rewritten upstream), the step emits a `::error::` and exits 1. - The post-checkout `actual_sha == pin` assertion is **kept**, so the gate still proves on the run log which commit it executed. - `SIAX_TEMPLATES_REF` is removed from `env:` rather than left dangling, so the moving-branch shape cannot quietly return. - **The pin value is unchanged.** `09504e3a593ee8ce0eaba606df31c349a3d52277` was verified to still exist upstream via the Gitea API before writing anything (`GET /api/v1/repos/admin/siax-templates/git/commits/09504e3a…` → 200, "Upload complete release evidence and validate artifact receipt (#138)", 2026-09-25T00:23:04Z; it is two commits behind `main`). Since the pinned commit exists, there was no reason to re-pin, and re-pinning would have undone the deliberate review in #119. - Two comments that asserted the old "refuses to run if `main` has moved past the pin" behaviour (`.siax/hooks/G17.sh` header, `.siax/hooks/README.md` "Pin status") are corrected in the same change, because this change makes them false. ## How it was verified (commands actually run, on this branch's exact YAML) The step's `run:` script was extracted from the committed YAML with `yaml.safe_load` and executed as-is, with one substitution: `https://oauth2:${SIAX_CI_TOKEN}@git.siax.io/` → `https://git.siax.io/`, so auth came from the machine's git credential helper and no token was ever written to disk. | # | What | Result | |---|------|--------| | 1 | `python3 -c "yaml.safe_load(open('.gitea/workflows/release.yml'))"` | parses; `env` no longer contains `SIAX_TEMPLATES_REF` | | 2 | `bash -n` + `shellcheck -s bash` on the extracted step | both clean, exit 0, no exclusions | | 3 | Happy path: step run with pin `09504e3a…` against live `siax-templates` | exit 0 — `siax-templates G17 reference @ 09504e3a593ee8ce0eaba606df31c349a3d52277 (pinned commit, checked out directly)`; `rev-parse HEAD` = the pin; `ci/v2/reference-hooks/G17.sh` present and `chmod +x` succeeded on all 4 hooks + 2 `lib/*.sh` | | 4 | Fallback path forced (plain `git fetch origin`, then checkout the pin) | exit 0, HEAD = `09504e3a…` — pin is reachable from a branch, so the fallback lands on the same commit | | 5 | Fail-closed: pin set to `deadbeef…` (commit does not exist) | by-SHA fetch refused → fallback fetch → `checkout` fails → `::error::siax-templates commit deadbeef… not found …`, **exit 1** | | 6 | Old logic on the same inputs, for contrast | `clone --branch main` → HEAD `6612f6ae…` ≠ pin → would exit 1 (the bug) | | 7 | `sh scripts/check-masterplan-lock.sh` (runs on every branch push) | `masterplan lock check OK` | | 8 | `pnpm install --frozen-lockfile` → `pnpm run typecheck` | install exit 0; `tsc -p tsconfig.check.json` exit 0 | | 9 | `pnpm run test` (vitest, CI-blocking) | **95 files passed, 22 skipped; 1117 tests passed, 184 skipped**, exit 0 | Not run: `pnpm run build` (next build) and the integration job — this diff touches no TypeScript, only a Gitea workflow and two comment blocks; the release chain itself cannot be executed outside CI (it needs `SIAX_CI_TOKEN`, registry, cosign and AUD0 secrets, none of which are provisioned on this repo — see `.siax/hooks/README.md`). ## Risk Low, and confined to the gate's own fetch step. - The supply-chain property is preserved, not weakened: CI still runs exactly one specific reviewed commit of `siax-templates`, asserted after checkout. The property that is *removed* is "fail whenever someone else merges", which was noise, not security. - The checkout is shallow on the by-SHA path. Verified safe: nothing under `ci/v2/reference-hooks/` uses git history (`grep` for `git rev-parse|git log|git describe|SIAX_TEMPLATES_DIR` → no hits). - Same authenticated-clone-at-runtime mechanism and same `SIAX_CI_TOKEN` as before; no new secrets, no new permissions, no deploy behaviour touched. - New failure mode if someone force-pushes away the pinned commit in `siax-templates`: the gate fails with an explicit message telling the reviewer to re-review and bump the pin. That is the intended fail-closed direction. ## Rollback `git revert 65ee88a` — single commit, three files, no state anywhere else. The gate returns to comparing `main` (and to being red). Nothing is deployed, tagged, or published by this PR. ## Out of scope, noticed while working here Not touched, because they belong to the pin-bump work item, not this one: - `.siax/hooks/README.md` "Pin status" still names `384e35060b91…` and `.siax/hook-pins.json` `templateRef` still names `a401880aa19a…`; both predate #119's bump of the workflow to `09504e3a…`. `release.yml` is authoritative and is the value CI uses — this PR's README edit says so explicitly. - The same moving-branch pattern exists in `0pt-motor`, `act0` and `h0st`; those are being fixed in their own PRs. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
admin added 1 commit 2026-09-26 18:08:39 +00:00
fix(ci): check out the siax-templates pin directly instead of comparing main
CI (SIAX Cloud) / integration (push) Skipped
Security Scan (SIAX Cloud) / security (push) Successful in 16s
CI (SIAX Cloud) / security (push) Successful in 22s
masterplan-lock / check (push) Successful in 21s
masterplan-lock / check (pull_request) Successful in 7s
CI (SIAX Cloud) / security (pull_request) Successful in 19s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 1m2s
CI (SIAX Cloud) / quality (push) Successful in 1m56s
CI (SIAX Cloud) / quality (pull_request) Successful in 1m16s
CI (SIAX Cloud) / integration (pull_request) Successful in 1m15s
65ee88a01c
The G17 gate cloned sax3l/siax-templates on the moving branch `main` and
then required that clone's HEAD to equal SIAX_TEMPLATES_PINNED_SHA. Every
merge in siax-templates -- including ones that touch nothing under
ci/v2/reference-hooks -- therefore turned this repo's release chain red
while nothing was actually unsafe (CD-03 / CI-RED-02). It is red right now:
siax-templates main is at 6612f6ae, the pin is 09504e3a.

The pin is still a real supply-chain control; what was wrong was tying it
to someone else's branch tip. The step now fetches the pinned commit by
SHA (shallow, no branch consulted) and checks it out detached, falling back
to fetching the branches and taking the same commit out of them if the
server refuses a by-SHA fetch. It fails closed on the thing that actually
matters -- the reviewed commit no longer existing upstream -- and the
post-checkout HEAD assertion is kept so the gate still proves what it ran.

SIAX_TEMPLATES_REF is removed rather than left unused, so the moving-branch
shape cannot quietly come back. The pin value itself is unchanged: commit
09504e3a is confirmed present in siax-templates (Gitea API, two commits
behind main). Two comments that described the old "refuses to run if main
moved" behaviour are corrected in the same change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CODEOWNERS rules requested review from sax3l 2026-09-26 18:08:39 +00:00
siax-bot approved these changes 2026-09-26 18:47:24 +00:00
admin merged commit b89b1e81de into main 2026-09-26 18:47:55 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/0pt#120