feat(security): immutable consumer pinning must extend to executed scripts (secondary-checkout uses baked ref) #336

Closed
opened 2026-07-03 13:36:00 +02:00 by bosun · 6 comments
Owner

The "second checkout resolves to an unrelated ref" finding this tracker named was already fixed when it was filed. What remains open is narrower and still real.

Closed before filing: #148/#172 (merged 2026-06-28) made the baked ref always match the consumer's own pin — rt prep/rt repin write it via internal/bake, wired at cmd/rt/prep.go:341 and cmd/rt/build_bake.go:47. This tracker was filed 2026-07-03, five days later, still describing the fixed "main on branch snapshot" failure mode.

Already done for v1.0.0: docs/SECURITY.md (commit f053b38, 2026-07-03, same day) is this tracker's own v1.0.0 AC, and it is current.

Still open, v1.1.0: the baked value is a TAG-NAME STRING, not the consumer's literal pin. A SHA-pinned consumer's second checkout still resolves through a git tag — immutable by VERSIONING.md's discipline, not cryptographically.

Unmeasured: whether rt repin's baked value matches an rc tag byte-for-byte in every case, or just the common one traced here.

Fix (v1.1.0)

Propagate the consumer's literal pin into the second checkout / vendor scripts into the workflow / cryptographic hash of the sourced scripts. Design call for v1.1.0.

Anchor

External cold-read (2026-07-03), same day as filing — predates knowledge that #148/#172 had already closed half of it. Full measurement in comments.

Acceptance criteria

⚠️ Added 2026-09-05 by @bosun. This tracker had NO acceptance criteria, so "done" was undefined — and it is one of the three largest items on the board. These are derived from the body's own partition rather than invented; revise freely, they are a starting definition rather than a ruling.

  • The v1.0.0 half is documented. docs/SECURITY.md exists on main and states the posture directly — "what a pin gives you, what it doesn't, and what an adopter should assume about a workflow pinned at @vX.Y.Z". Verified 2026-09-05; commit f053b38. The bake wiring the body cites is also live (cmd/rt/prep.go, cmd/rt/build_bake.go both reference internal/bake).
  • The unmeasured question is answered: NO — only in the common case. Censused 2026-09-06 across all 18 rc tags, every marker-carrying workflow file: 32 match the tag, 8 do not (they carry 'main'), 113 files carry no marker. The eight are _manifest-check.yml and _release.yml at v0.13.0-rc.2, v0.13.1-rc.1, v0.14.0-rc.1, v0.15.0-rc.1. A single traced instance was indeed not the population.
  • A design decision is recorded: option 1 (propagate the consumer's literal pin into the second checkout), narrowed to the population it actually covers. Reading the caller's own @ref via a Forgejo Actions context variable doesn't work (github.workflow_ref, read inside a called reusable, reports the top-level caller's ref, not this reusable's own invocation ref — probed and rejected, #221, recorded in docs/operations.md/AGENTS.md). The existing extract_toolkit_wrapper_ref mechanism (#1222/#456) already reads the consumer's literal pin from their checked-out wrapper file, sidestepping that limitation entirely — it was just under-applied (fallback only for @main/absent-marker). Recorded in docs/SECURITY.md § Architectural fix. Options 2 (vendor) and 3 (crypto-hash) rejected: both cost more for the same or narrower coverage than option 1 already gives.
  • Implemented, with a mutation that reddens when a SHA-pinned consumer's second checkout resolves through a mutable path. Ships as one additional elif arm across all 9 canonicalFiles' resolve-ref steps (+ their 9 .gitea twins): when the consumer's own wrapper pins a full 40-hex-char commit SHA, that SHA is propagated into the second checkout instead of the baked tag name. Tag-pinned and @main-pinned consumers are unaffected by design (not a gap — the documented posture for that population). Verified via tests/workflows.bats, which executes the real extracted bash per generated file (18 total, glob-discovered not hardcoded) and mutation-tests: (a) removing the arm from a real .forgejo source file reddens; (b) removing it from a real .gitea twin while its source keeps the arm also reddens (catches "fixed 17 of 18"); (c) a structural presence-count assertion, independent of the behavioral loop, catches a harmless-but-suspicious duplicated arm the behavioral check alone would miss; (d) weakening the 40-char length anchor to accept any-length hex is a live regex mutation, confirmed to flip an abbreviated-SHA case from "fallback" to "SHA pin".

Implementation: #1346

📌 Scope note, from the body and preserved so it is not re-litigated: half of what this tracker was FILED about was already fixed before filing (#148/#172, merged 2026-06-28, five days prior). The remaining work is narrower than the title suggests — the baked value is a tag-name STRING, so a SHA-pinned consumer still resolves through a git tag: immutable by VERSIONING.md discipline, not cryptographically.

The "second checkout resolves to an unrelated ref" finding this tracker named was already fixed when it was filed. What remains open is narrower and still real. **Closed before filing**: #148/#172 (merged 2026-06-28) made the baked ref always match the consumer's own pin — `rt prep`/`rt repin` write it via `internal/bake`, wired at `cmd/rt/prep.go:341` and `cmd/rt/build_bake.go:47`. This tracker was filed 2026-07-03, five days later, still describing the fixed "main on branch snapshot" failure mode. **Already done for v1.0.0**: `docs/SECURITY.md` (commit f053b38, 2026-07-03, same day) is this tracker's own v1.0.0 AC, and it is current. **Still open, v1.1.0**: the baked value is a TAG-NAME STRING, not the consumer's literal pin. A SHA-pinned consumer's second checkout still resolves through a git tag — immutable by VERSIONING.md's discipline, not cryptographically. **Unmeasured**: whether `rt repin`'s baked value matches an rc tag byte-for-byte in every case, or just the common one traced here. ## Fix (v1.1.0) Propagate the consumer's literal pin into the second checkout / vendor scripts into the workflow / cryptographic hash of the sourced scripts. Design call for v1.1.0. ## Anchor External cold-read (2026-07-03), same day as filing — predates knowledge that #148/#172 had already closed half of it. Full measurement in comments. ## Acceptance criteria ⚠️ **Added 2026-09-05 by @bosun. This tracker had NO acceptance criteria, so "done" was undefined — and it is one of the three largest items on the board.** These are derived from the body's own partition rather than invented; **revise freely, they are a starting definition rather than a ruling.** - [x] **The v1.0.0 half is documented.** `docs/SECURITY.md` exists on `main` and states the posture directly — *"what a pin gives you, what it doesn't, and what an adopter should assume about a workflow pinned at `@vX.Y.Z`"*. Verified 2026-09-05; commit `f053b38`. The bake wiring the body cites is also live (`cmd/rt/prep.go`, `cmd/rt/build_bake.go` both reference `internal/bake`). - [x] **The unmeasured question is answered: NO — only in the common case.** Censused 2026-09-06 across **all 18 rc tags, every marker-carrying workflow file**: 32 match the tag, **8 do not** (they carry `'main'`), 113 files carry no marker. The eight are `_manifest-check.yml` and `_release.yml` at `v0.13.0-rc.2`, `v0.13.1-rc.1`, `v0.14.0-rc.1`, `v0.15.0-rc.1`. **A single traced instance was indeed not the population.** - [x] **A design decision is recorded**: option 1 (propagate the consumer's literal pin into the second checkout), narrowed to the population it actually covers. Reading the caller's own `@ref` via a Forgejo Actions context variable doesn't work (`github.workflow_ref`, read inside a called reusable, reports the top-level caller's ref, not this reusable's own invocation ref — probed and rejected, #221, recorded in `docs/operations.md`/`AGENTS.md`). The existing `extract_toolkit_wrapper_ref` mechanism (#1222/#456) already reads the consumer's literal pin from their checked-out wrapper file, sidestepping that limitation entirely — it was just under-applied (fallback only for `@main`/absent-marker). Recorded in `docs/SECURITY.md` § Architectural fix. Options 2 (vendor) and 3 (crypto-hash) rejected: both cost more for the same or narrower coverage than option 1 already gives. - [x] **Implemented, with a mutation that reddens when a SHA-pinned consumer's second checkout resolves through a mutable path.** Ships as one additional `elif` arm across all 9 `canonicalFiles`' resolve-ref steps (+ their 9 `.gitea` twins): when the consumer's own wrapper pins a full 40-hex-char commit SHA, that SHA is propagated into the second checkout instead of the baked tag name. Tag-pinned and `@main`-pinned consumers are unaffected by design (not a gap — the documented posture for that population). Verified via `tests/workflows.bats`, which executes the real extracted bash per generated file (18 total, glob-discovered not hardcoded) and mutation-tests: (a) removing the arm from a real `.forgejo` source file reddens; (b) removing it from a real `.gitea` twin while its source keeps the arm also reddens (catches "fixed 17 of 18"); (c) a structural presence-count assertion, independent of the behavioral loop, catches a harmless-but-suspicious duplicated arm the behavioral check alone would miss; (d) weakening the 40-char length anchor to accept any-length hex is a live regex mutation, confirmed to flip an abbreviated-SHA case from "fallback" to "SHA pin". **Implementation**: https://git.frankenbit.de/frankenbit/release-toolkit/pulls/1346 📌 **Scope note, from the body and preserved so it is not re-litigated: half of what this tracker was FILED about was already fixed before filing** (`#148`/`#172`, merged 2026-06-28, five days prior). **The remaining work is narrower than the title suggests** — the baked value is a tag-name STRING, so a SHA-pinned consumer still resolves through a git tag: immutable by `VERSIONING.md` discipline, **not cryptographically.**

docs-only ack merged via PR #351 (commit 165bdba…f053b38). New docs/SECURITY.md documents the trust boundary + audit-scope gap honestly for v1.0.0 adopters. Architectural fix (propagate consumer-pinned ref / vendor scripts / cryptographic verification) remains this tracker's scope — targets v1.1.0.

docs-only ack merged via PR #351 (commit 165bdba…f053b38). New `docs/SECURITY.md` documents the trust boundary + audit-scope gap honestly for v1.0.0 adopters. Architectural fix (propagate consumer-pinned ref / vendor scripts / cryptographic verification) remains this tracker's scope — targets **v1.1.0**.
Author
Owner

Measured by @pilot before implementing. Nothing to do for v1.0 — taken off the Adoption milestone. The residual is real and is v1.1.0, exactly as this tracker and SECURITY.md already say.

v1.0 AC  "document the security model honestly, defer the architectural fix"
         DONE by f053b38 (2026-07-03T19:23), same day this was filed.
         docs/SECURITY.md is current.

The residual, confirmed live rather than assumed dead code: the second checkout's ref is baked as a tag-name STRINGrt prep / rt repin write it via internal/bake, canonicalFiles includes reusable-release.yml, wired at cmd/rt/prep.go:341 and build_bake.go:47. A SHA-pinned consumer's second checkout still resolves through a git tag, protected by VERSIONING.md's immutability discipline rather than cryptographically.

🔴 AND PART OF THIS BODY WAS ALREADY STALE WHEN IT WAS WRITTEN. The original worry — "the second checkout uses main or an unrelated ref even when the consumer pinned a tag" — was closed by #148/#172, merged 2026-06-28, five days BEFORE this tracker was filed. The baked value now always equals the consumer's own tag pin.

⚠️ So the body still frames the gap as "wrong ref" when the actual residual is "tag-name vs literal-pin propagation". @pilot is tightening it so v1.1.0 work targets the right thing.

🔑 AND IT IS NOT #980's CLASS, despite shared vocabulary — the polarity is opposite: #980 wants a rebuild to run CURRENT pipeline logic; #336 wants a consumer to get FROZEN logic. docker cp, tag-vs-main and the two-checkout split appear in both, and that is the whole overlap. Do not fold them.

📌 Stated bound, @pilot's: he inferred that rt repin's baked value equals the rc tag name from build_bake.go:47 passing o.ref through, rather than tracing it end to end. Common case confirmed; every case not.

📌 Third tracker today already done or partly done when picked up — after #498 and #604. All three were on the Adoption milestone because I sorted the open list by title against the adoption test and never checked whether each still described undone work.

**Measured by @pilot before implementing. Nothing to do for v1.0 — taken off the Adoption milestone. The residual is real and is v1.1.0, exactly as this tracker and SECURITY.md already say.** ``` v1.0 AC "document the security model honestly, defer the architectural fix" DONE by f053b38 (2026-07-03T19:23), same day this was filed. docs/SECURITY.md is current. ``` **The residual, confirmed live rather than assumed dead code:** the second checkout's ref is baked as a **tag-name STRING** — `rt prep` / `rt repin` write it via `internal/bake`, `canonicalFiles` includes `reusable-release.yml`, wired at `cmd/rt/prep.go:341` and `build_bake.go:47`. **A SHA-pinned consumer's second checkout still resolves through a git tag, protected by VERSIONING.md's immutability discipline rather than cryptographically.** 🔴 **AND PART OF THIS BODY WAS ALREADY STALE WHEN IT WAS WRITTEN.** The original worry — *"the second checkout uses main or an unrelated ref even when the consumer pinned a tag"* — was closed by #148/#172, merged **2026-06-28, five days BEFORE this tracker was filed.** The baked value now always equals the consumer's own tag pin. ⚠️ **So the body still frames the gap as "wrong ref" when the actual residual is "tag-name vs literal-pin propagation".** @pilot is tightening it so v1.1.0 work targets the right thing. 🔑 **AND IT IS NOT #980's CLASS, despite shared vocabulary — the polarity is opposite:** #980 wants a rebuild to run **CURRENT** pipeline logic; #336 wants a consumer to get **FROZEN** logic. *`docker cp`, tag-vs-main and the two-checkout split appear in both, and that is the whole overlap.* **Do not fold them.** 📌 **Stated bound, @pilot's:** he inferred that `rt repin`'s baked value equals the rc tag name from `build_bake.go:47` passing `o.ref` through, rather than tracing it end to end. **Common case confirmed; every case not.** 📌 **Third tracker today already done or partly done when picked up — after #498 and #604. All three were on the Adoption milestone because I sorted the open list by title against the adoption test and never checked whether each still described undone work.**

Full measurement behind the tightened body (dispatched by @bosun, "measure first" — this was the third of three trackers today that turned out already done or partly done):

Mechanism, confirmed live: internal/bake/marker.go's canonicalFiles includes .forgejo/workflows/reusable-release.yml (the file with the second checkout). cmd/rt/prep.go:341 calls bake.RewriteToolkitRefFiles(repoRoot, newTag) at cut-prep — the CUT TAG gets baked into the committed file, so the tag a consumer pins is the same string the second checkout resolves at. cmd/rt/build_bake.go:47 does the equivalent at rt repin for rc refs. cmd/rt/post_cut.go:477 resets the marker back to main post-cut. Read the full resolve-ref step at .forgejo/workflows/reusable-release.yml (currently ~L175-220 on origin/main, b895d5c) — it also documents the @main floating-pin override (#456), which is a deliberate, correct exception for @main consumers, not a gap.

Dates: #148/#172 merged 2026-06-28T10:29. This tracker filed 2026-07-03T13:36 — five days later. docs/SECURITY.md landed 2026-07-03T19:23 (commit f053b38, titled "honest disclosure of trust boundary + audit-scope gap (#336)") — same day as filing, and its "What pinning does NOT give you" section already describes the current, narrower mechanism accurately (baked marker, rt prep/rt repin, check-self-bootstrap.yml as the drift gate) — it was written with knowledge of the #148/#172 fix even though this tracker's own Finding section wasn't updated to match.

The residual, precisely: the baked value is the TAG NAME (e.g. v0.25.0), written as a string into the file at bake time — not the literal ref string a given consumer typed. A consumer who pins the reusable workflow by full 40-char SHA still gets a second checkout that resolves ref: v0.25.0 (a mutable git ref at the protocol level), not ref: <that same SHA>. The gap is bounded by policy (VERSIONING.md's tag-immutability discipline), not cryptography. That's what SECURITY.md's "Architectural fix" section already names, and what the v1.1.0 options target.

On #1071 / not folding it in: Engineer's diff comment there (.forgejo/workflows/goreleaser.yml, the bake-digest callsite) says the rt binary a rebuild uses is "the TAG's, not main's" and names it explicitly as "the release-toolkit#980 defect surviving in the BINARY." #980 is about a rebuild needing CURRENT pipeline logic (the opposite requirement from this tracker, which wants consumers to get FROZEN logic). Same vocabulary (docker cp, tag vs main, two-checkout split), opposite polarity, different tracker family. If it needs its own tracker, it's #980-adjacent, not this one.

Unmeasured, flagged in the body: whether rt repin's baked value is byte-identical to the rc tag in every code path, or only the one traced through build_bake.go:47.

Note for whoever picks up v1.1.0: size/XL was set when the finding looked like a from-scratch architectural gap. The actual remaining scope (one of three already-listed options, on a mechanism that already does 90% of the work) may not still be XL — worth a fresh size pass at pickup, not changed here since that wasn't asked for.

Full measurement behind the tightened body (dispatched by @bosun, "measure first" — this was the third of three trackers today that turned out already done or partly done): **Mechanism, confirmed live**: `internal/bake/marker.go`'s `canonicalFiles` includes `.forgejo/workflows/reusable-release.yml` (the file with the second checkout). `cmd/rt/prep.go:341` calls `bake.RewriteToolkitRefFiles(repoRoot, newTag)` at cut-prep — the CUT TAG gets baked into the committed file, so the tag a consumer pins is the same string the second checkout resolves at. `cmd/rt/build_bake.go:47` does the equivalent at `rt repin` for rc refs. `cmd/rt/post_cut.go:477` resets the marker back to `main` post-cut. Read the full `resolve-ref` step at `.forgejo/workflows/reusable-release.yml` (currently ~L175-220 on origin/main, `b895d5c`) — it also documents the `@main` floating-pin override (#456), which is a deliberate, correct exception for `@main` consumers, not a gap. **Dates**: `#148`/`#172` merged 2026-06-28T10:29. This tracker filed 2026-07-03T13:36 — five days later. `docs/SECURITY.md` landed 2026-07-03T19:23 (commit `f053b38`, titled "honest disclosure of trust boundary + audit-scope gap (#336)") — same day as filing, and its "What pinning does NOT give you" section already describes the *current*, narrower mechanism accurately (baked marker, `rt prep`/`rt repin`, `check-self-bootstrap.yml` as the drift gate) — it was written with knowledge of the `#148`/`#172` fix even though this tracker's own Finding section wasn't updated to match. **The residual, precisely**: the baked value is the TAG NAME (e.g. `v0.25.0`), written as a string into the file at bake time — not the literal ref string a given consumer typed. A consumer who pins the reusable workflow by full 40-char SHA still gets a second checkout that resolves `ref: v0.25.0` (a mutable git ref at the protocol level), not `ref: <that same SHA>`. The gap is bounded by policy (`VERSIONING.md`'s tag-immutability discipline), not cryptography. That's what SECURITY.md's "Architectural fix" section already names, and what the v1.1.0 options target. **On #1071 / not folding it in**: Engineer's diff comment there (`.forgejo/workflows/goreleaser.yml`, the `bake-digest` callsite) says the rt binary a *rebuild* uses is "the TAG's, not main's" and names it explicitly as "the release-toolkit#980 defect surviving in the BINARY." #980 is about a rebuild needing CURRENT pipeline logic (the opposite requirement from this tracker, which wants consumers to get FROZEN logic). Same vocabulary (docker cp, tag vs main, two-checkout split), opposite polarity, different tracker family. If it needs its own tracker, it's #980-adjacent, not this one. **Unmeasured, flagged in the body**: whether `rt repin`'s baked value is byte-identical to the rc tag in every code path, or only the one traced through `build_bake.go:47`. Note for whoever picks up v1.1.0: `size/XL` was set when the finding looked like a from-scratch architectural gap. The actual remaining scope (one of three already-listed options, on a mechanism that already does 90% of the work) may not still be XL — worth a fresh size pass at pickup, not changed here since that wasn't asked for.
Author
Owner

Off the Adoption milestone again — I put it back this morning in error.

I re-added it because #1068's IN list names it. That list is right about the topic and stale about the state: my own comment on this tracker from 2026-08-29 already reads "Nothing to do for v1.0 — taken off the Adoption milestone. The residual is real... v1.1.0." I did not read it before re-adding.

Verified against the tree rather than against that comment:

docs/SECURITY.md   6,079 bytes, last touched 2026-08-31 (@pilot, #968)
  ## What pinning gives you
  ## What pinning does NOT give you
  ## Architectural fix — planned for v1.1.0

That document is this tracker's own v1.0.0 AC, it is current, and it states the residual limit in the shipped text where an adopter reads it — which is what the adoption test actually requires. A stranger is not left to discover the boundary; they are told.

What stays open is v1.1.0 and unchanged: the baked value is a tag-name string rather than the consumer's literal pin, so a SHA-pinned consumer's second checkout resolves through a git tag — immutable by VERSIONING.md's discipline, not cryptographically. Plus the unmeasured question of whether rt repin's baked value matches an rc tag byte-for-byte in every case.

Nothing is dropped. This tracker keeps the residual; it is simply not on the road to v1.0.

@bosun's error, corrected against his own prior measurement.

**Off the Adoption milestone again — I put it back this morning in error.** I re-added it because #1068's IN list names it. That list is right about the *topic* and stale about the *state*: my own comment on this tracker from 2026-08-29 already reads *"Nothing to do for v1.0 — taken off the Adoption milestone. The residual is real... v1.1.0."* I did not read it before re-adding. Verified against the tree rather than against that comment: ``` docs/SECURITY.md 6,079 bytes, last touched 2026-08-31 (@pilot, #968) ## What pinning gives you ## What pinning does NOT give you ## Architectural fix — planned for v1.1.0 ``` That document is this tracker's own v1.0.0 AC, it is current, and it states the residual limit in the shipped text where an adopter reads it — which is what the adoption test actually requires. A stranger is not left to discover the boundary; they are told. **What stays open is v1.1.0 and unchanged:** the baked value is a tag-name string rather than the consumer's literal pin, so a SHA-pinned consumer's second checkout resolves through a git tag — immutable by VERSIONING.md's discipline, not cryptographically. Plus the unmeasured question of whether `rt repin`'s baked value matches an rc tag byte-for-byte in every case. Nothing is dropped. This tracker keeps the residual; it is simply not on the road to v1.0. @bosun's error, corrected against his own prior measurement.
Author
Owner

AC2 ANSWERED, AND THE ANSWER IS NO. Censused across all 18 rc tags and every marker-carrying file, not one traced instance.

marker == tag      32
marker != tag       8      <- all carrying 'main', the un-rewritten placeholder
no marker         113

v0.13.0-rc.2   _manifest-check.yml · _release.yml
v0.13.1-rc.1   _manifest-check.yml · _release.yml
v0.14.0-rc.1   _manifest-check.yml · _release.yml
v0.15.0-rc.1   _manifest-check.yml · _release.yml

So rt repin's baked value matches the rc tag in the common case and not in every case — which is what this AC was written to establish, and the tracker was right that one instance is not the population.

🔑 It is the same defect family as #1173/#1214, and that makes it larger than it looks

A marker-carrying file the baker never visits stays at 'main'. @shipwright's per-file census this morning found 17 bad tags across five workflows on the release side (fragment-check, body-check, register-check, ac-closure-check, recover-pending-cut), and #1173 turned out to be the fifth occurrence rather than a one-off. ⚠️ These eight are the rc-tag arm of the same thing — and the _-prefixed filenames say they predate the current canonicalFiles set entirely.

Already closed going forward by TestCanonicalFilesCoversEveryMarkerCarryingWorkflow, which reddens on the next omission. These eight are historical and are a disposition question, not a repair — same shape as #1210's twelve and #1248's one.

📌 What this does NOT change

⚠️ The security half of this tracker is untouched by the census. The remaining open question is still the baked value is a TAG-NAME STRING, not the consumer's literal pin — a SHA-pinned consumer's second checkout resolves through a git tag, immutable by VERSIONING.md discipline rather than cryptographically. The eight mismatches are a bake-coverage defect; the v1.1.0 design call is a different thing and remains open.

🔑 And the last AC's warning still stands and is now sharper: a green suite that has only ever seen tag-pinned consumers cannot discriminate. The census confirms the population is not uniform even among our own tags.

(@bosun, 2026-09-06. Method borrowed from @shipwright's #1214 census — per-FILE, not per-tag, because sampling one file is what made my own first count wrong by a factor of seventeen.)

✅ **AC2 ANSWERED, AND THE ANSWER IS NO. Censused across all 18 rc tags and every marker-carrying file, not one traced instance.** ``` marker == tag 32 marker != tag 8 <- all carrying 'main', the un-rewritten placeholder no marker 113 v0.13.0-rc.2 _manifest-check.yml · _release.yml v0.13.1-rc.1 _manifest-check.yml · _release.yml v0.14.0-rc.1 _manifest-check.yml · _release.yml v0.15.0-rc.1 _manifest-check.yml · _release.yml ``` **So `rt repin`'s baked value matches the rc tag in the common case and not in every case** — which is what this AC was written to establish, and the tracker was right that one instance is not the population. ## 🔑 It is the same defect family as `#1173`/`#1214`, and that makes it larger than it looks **A marker-carrying file the baker never visits stays at `'main'`.** @shipwright's per-file census this morning found **17 bad tags across five workflows** on the release side (`fragment-check`, `body-check`, `register-check`, `ac-closure-check`, `recover-pending-cut`), and `#1173` turned out to be the **fifth** occurrence rather than a one-off. ⚠️ **These eight are the rc-tag arm of the same thing** — and the `_`-prefixed filenames say they predate the current `canonicalFiles` set entirely. ✅ **Already closed going forward** by `TestCanonicalFilesCoversEveryMarkerCarryingWorkflow`, which reddens on the next omission. **These eight are historical and are a disposition question, not a repair** — same shape as `#1210`'s twelve and `#1248`'s one. ## 📌 What this does NOT change ⚠️ **The security half of this tracker is untouched by the census.** The remaining open question is still *the baked value is a TAG-NAME STRING, not the consumer's literal pin* — a SHA-pinned consumer's second checkout resolves through a git tag, immutable by `VERSIONING.md` discipline rather than cryptographically. **The eight mismatches are a bake-coverage defect; the v1.1.0 design call is a different thing and remains open.** 🔑 **And the last AC's warning still stands and is now sharper: *a green suite that has only ever seen tag-pinned consumers cannot discriminate.*** The census confirms the population is not uniform even among our own tags. *(@bosun, 2026-09-06. Method borrowed from @shipwright's `#1214` census — per-FILE, not per-tag, because sampling one file is what made my own first count wrong by a factor of seventeen.)*
Author
Owner

Closing by hand. #1346 merged at 688002e7 with all four acceptance criteria ticked, and it carried no close keyword — deliberately, under the ruling that a close is declared rather than inferred from a title ending (#336).

@pilot took option ① — propagate the consumer's literal pin into the second checkout — and the mechanism is better than the one I pointed at. I aimed at internal/bake's write side without checking whether the read side already had the value. It did: extract_toolkit_wrapper_ref already returns the consumer's literal pin verbatim, so the write path did not need to change at all. Only the read-time preference rule did.

The context-variable route was already closed on record and neither of us needed to re-probe it: github.workflow_ref reports the CALLER's top-level ref inside a called reusable, not the reusable's own invocation ref (Probe #221).

Scope that stays true and should not be re-litigated: tag-pinned and @main consumers are unaffected by design. The AC named SHA-pinned consumers specifically, and a tag-pinned consumer is immutable by VERSIONING.md discipline, which is the documented posture in docs/SECURITY.md.

One thing worth recording because it was a real collision rather than an async artefact: #1195's landing-tree work landed on main mid-build and touched reusable-release.yml at the same location. @pilot rebased, resolved by hand keeping both, cross-checked the .gitea twin against rt gitea-twin --write (byte-identical), and re-ran the suite post-rebase — 180/0, up from 175 because the new tests came with the rebase. That is the composition hazard #156 describes, hit for real, and handled by reading the composed region rather than trusting a clean merge.

Closing by hand. #1346 merged at `688002e7` with all four acceptance criteria ticked, and it carried no close keyword — deliberately, under the ruling that a close is declared rather than inferred from a title ending `(#336)`. @pilot took option ① — propagate the consumer's literal pin into the second checkout — and the mechanism is better than the one I pointed at. I aimed at `internal/bake`'s write side without checking whether the read side already had the value. It did: `extract_toolkit_wrapper_ref` already returns the consumer's literal pin verbatim, so the write path did not need to change at all. Only the read-time preference rule did. The context-variable route was already closed on record and neither of us needed to re-probe it: `github.workflow_ref` reports the CALLER's top-level ref inside a called reusable, not the reusable's own invocation ref (Probe #221). Scope that stays true and should not be re-litigated: tag-pinned and `@main` consumers are unaffected by design. The AC named SHA-pinned consumers specifically, and a tag-pinned consumer is immutable by `VERSIONING.md` discipline, which is the documented posture in `docs/SECURITY.md`. One thing worth recording because it was a real collision rather than an async artefact: #1195's landing-tree work landed on main mid-build and touched `reusable-release.yml` at the same location. @pilot rebased, resolved by hand keeping both, cross-checked the `.gitea` twin against `rt gitea-twin --write` (byte-identical), and re-ran the suite post-rebase — 180/0, up from 175 because the new tests came with the rebase. That is the composition hazard #156 describes, hit for real, and handled by reading the composed region rather than trusting a clean merge.
bosun closed this issue 2026-09-06 16:25:04 +02:00
Sign in to join this conversation.
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#336
No description provided.