refactor(#148 Phase 2): drop toolkit_ref input + drop consumer-side line + check-self-bootstrap reads uses: @ref #172
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#172
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
#148 Phase 1 (build-bake mechanism) shipped construction-enforcement:
_release.yml+_manifest-check.ymlhave a hardcodedref:value baked byrelease-prep.shat cut-time, so the cut tag points at a reusable that loads itself at the matching ref.But the
toolkit_refworkflow_call input is still declared (required: false,default: 'main') and the consumer wrapper still passestoolkit_ref: vX.Y.Z. The duplication isn't eliminated yet — just made consistency-checked.Phase 2 finishes the substrate refactor.
Scope
_release.yml+_manifest-check.yml:toolkit_refdeclaration from theworkflow_call.inputsschema${{ inputs.toolkit_ref }}references with the baked-ref value or a runtime-read mechanism (e.g., the prune-rc-tags.sh call at_release.ymlline ~490 currently passesinputs.toolkit_refas PINNED_REF)Consumer wrappers (
release.yml+manifest-check.yml):toolkit_ref:line entirelyscripts/check-self-bootstrap.sh:toolkit_ref:inrelease.ymlto reading the@<ref>from theuses:line as the single source of truthdocs/integration.md:toolkit_ref:Activation lag — operator coordination needed
When Phase 2 PR merges, push:main fires release.yml @ OLD pinned ref. The OLD
_release.ymlstill has thetoolkit_refinput declaration. Consumer release.yml dropped the input → defaults to'main'. OLD code checks out toolkit at main → runs Phase-2-aware code → bake step rewrites + prep commit lands → rolling PR.After rolling PR merges + the cut succeeds + the cut tag has the Phase-2
_release.yml(no input), the consumer's release.yml is consistent with the new schema.Risk: between Phase 2 PR merge and the next re-pin, there's a narrow window where:
toolkit_ref:_release.ymlstill requirestoolkit_ref(Phase 1 state)So no actual breakage, just transient inconsistency. Acceptable.
Cellblock impact: cellblock pins
_release.yml@v0.10.1-rc.1(current). Phase 2 ships in a future version (vX). When cellblock bumps@vX, they'd need to droptoolkit_ref:line. Match-bumps-with-schema-shifts is the natural cadence — no extra step required.Composition
uses: @refline to check, notoolkit_ref:cross-wrapper alignment neededWhat this PR will NOT do
uses: ...@<ref>semantics (consumers still pin to a specific tag)Refs
quartermaster referenced this issue2026-06-27 14:27:35 +02:00
quartermaster referenced this issue2026-06-27 14:38:13 +02:00
workflow_ref empirical-probe result (#219, Surveyor bd11)
Threshold: POPULATED —
github.workflow_refresolves on the current runner (Forgejo 15.0.2 / forgejo-runner 12.8.2). The status-encoded probe exited 0 (non-empty) on #219's CI run.integration.md L532's "Forgejo Actions doesn't expose ${{ github.workflow_ref }} reliably across versions" is empirically stale for the current version. The field works.
What this means for fork-C
The doc-stated blocker is LIFTED — fork-C (runtime-read calling-ref instead of toolkit_ref input) moves from "doc says unreliable → reject" to "field populates → viable-pending-one-more-test."
Honest scoping caveat (per Surveyor): the test was a TOP-LEVEL workflow. It proves the field populates. It does NOT prove the narrower fork-C sub-case — that a REUSABLE workflow called via
uses: .../_release.yml@<ref>sees its OWN called-ref (vs the caller's). That sub-case needs:That's the follow-up if Engineer pursues fork-C after operator's decision.
Net for #172 decision-space
Operator owns the scope call. If lean (C), Surveyor or Engineer runs the nuance-test next; result either confirms-fork-C or escalates to A/B with C closed.
Refs: #219 (probe PR, closed throwaway), Surveyor bd11 (the conclusive result + scoping).
Operator-lean: (C) workflow_ref runtime-read, pending nuance-test
Operator weighed pros/cons across the three forks + leans (C). Dispatching the reusable+caller empirical test next:
The nuance question: does a REUSABLE workflow called via
uses: .../tiny-reusable.yml@<some-ref>see its OWN called-ref in${{ github.workflow_ref }}(vs the caller's ref)?Test shape (per Surveyor bd11's status-encoded-comparison design): create a tiny reusable workflow + a tiny consumer-that-uses-it. The consumer hardcodes
uses: <reusable>@<chosen-ref>+ passesexpected_ref: <chosen-ref>as input. The reusable echoes${{ github.workflow_ref }}AND status-encodes the comparison: exit 0 if matches expected_ref, exit 1 if mismatch. The exit code is the answer-surface (log-API stays UI-only).If test passes → (C) is viable; Engineer redesigns #172 from "drop input" to "replace build-bake with workflow_ref runtime-read." Wider scope than the original 4-item issue, but cleaner architecture. Build-bake mechanism may stay as belt-and-suspenders fallback (C' hybrid) or be fully removed depending on Engineer's design preference.
If test fails (reusable sees caller's ref, not its own) → (C) closes, fallback to (A) bake-at-re-pin with detached-bake mechanism. (B) explicitly off the table per substrate-state-care discipline (won't ship a known 483d regression).
Surveyor dispatched for the test; Engineer's /compact-rest authorized continues pending result.
Reusable+caller nuance-test: CONCLUSIVE — fork-C CLOSES
Engineer ran the test in PR #221 (now closed/cleaned-up). Result:
github.workflow_refinside a called reusable reports the CALLER's ref, NOT the reusable's own pinned tag.Empirical evidence (Engineer's two-job status-encode extension of Surveyor's design):
assert_nonemptyjob: SUCCESS → workflow_ref populates in reusable context (consistent with Surveyor #219 top-level threshold)assert_owns_tagjob: FAILURE → workflow_ref does NOT contain the reusable's own pinned tag (wfref-pin-v0, a real rc-pin-shape test tag)Non-empty + not-own-tag = workflow_ref carries the CALLER_REF resolution. The reusable cannot learn its own pin from workflow_ref.
assert_nonemptypassing proves the reusable resolved + ran at the tag (rules out resolution failure as the cause of mismatch).Implication
(C) closes. Falls back to (A) bake-at-re-pin with detached-bake mechanism. (B) explicitly off the table (substrate-state-care won't ship a known 483d regression).
The detached-bake mechanism is the architectural sub-question: rc tag + main share a commit at re-pin but need DIFFERENT baked values (main: 'main', rc.N: 'rc.N') → tag must point at a detached bake-only commit (orphan or sibling). New machinery for the re-pin discipline.
Engineer starting (A) recon now; will surface concrete (A) design + option-tree before building (same recon-before-build discipline they applied for fork-C). Recon synthesis from the original #172 thread (the substrate-state-care analysis + 483d preservation argument) stands as background.
Refs: Engineer 74d9 (the test result), Surveyor bd11 (top-level threshold), Surveyor 383d (handoff + push-restriction-blocks-tag-creation), #221 (test PR, closed throwaway).
#172 fork-C closed → (A) bake-at-re-pin: concrete design surface + option-tree
Fork-C result (probe #221, cleaned up):
github.workflow_refinside a reusable called viauses:...@<tag>reports the caller's ref, not the reusable's own pinned tag (assert_nonempty=pass + assert_owns_tag=fail = caller_ref by elimination). The reusable can't recover its own rc-pin at runtime → C not viable. (B off table.) Settled fallback: (A) bake-at-re-pin.The clean architectural story for (A)
The
toolkit_refinput exists today only as a fallback for the resolve-refelsebranch, because rc tags are never baked (release-prep.shbakes only at cut-prep, not re-pin → rc tags carryBUILD_BAKED='main'). If we bake the rc tag too, then every pinnable ref carries its own baked value:baked=vX.Y.Z→ref=vX.Y.Zbaked=vX.Y.Z-rc.N→ref=vX.Y.Z-rc.Nbaked=main→ref=main(correct for@mainpins)So resolve-ref collapses to
ref=$BUILD_BAKED_TOOLKIT_REFunconditionally — theif baked != mainbranch + the input fallback both vanish. The input isn't relocated; it's structurally eliminated. That is the single-source-of-truth invariant #172 wants, achieved structurally (not enforcement-caught).The detached-bake mechanism (the one real new component)
At re-pin, the rc tag is created at main's HEAD today. But main's HEAD must carry
bake='main'(so it doesn't poison the next re-pin), while the rc tag must carrybake='vX.Y.Z-rc.N'. Same commit, two required values → the rc tag must point at a detached commit = (main HEAD + bake-rewrite of the two reusables), reachable only via the tag, never on a branch. Sketch:check-self-bootstrap: already compatible (no diff-logic change)
_release.yml/_manifest-check.ymlare inDEFAULT_COMPOSE_SCRIPTS, but the#184NORM_SEDnormalizes the bake-marker line. The detached commit is byte-identical to main except that normalized line → HEAD vs rc-tag compares equal → no spurious re-pin red. The only #172 change here is dropping thetoolkit_ref:extraction + alignment (lines 155-189), keepinguses:@refonly.Option-tree
(A) Pursue Phase-2 (drop input + detached-bake at re-pin). Achieves single-source-of-truth structurally; resolve-ref simplifies. Sub-fork on where the detached-bake lives:
lib/build_bake.shor a tinyscripts/repin-bake.sh <ref>; leave the consumer-wrapper edit + PR as today's documented manual re-pin steps. Smallest new surface, bats-testable + mutation-verifiable, matches the existing manual-re-pin model, and isolates the high-blast-radius part (a botched detached-bake silently poisons the rc tag → the exact 483d regression we're protecting).scripts/repin.shautomating tag + wrapper edits + PR. More reproducible, but a bigger new abstraction than re-pin has today.(D) Hold Phase-2. Keep the input; rely on check-self-bootstrap's existing
uses:@ref↔toolkit_ref:alignment enforcement (lines 177-189) as the drift guard. Right answer if the detached-bake cost is judged too high for the benefit right now — but note it keeps TWO sources (enforcement-caught) vs (A)'s ONE (structural), so it forgoes the actual #172 invariant. Revisit if Forgejo ever gains a reliable own-ref mechanism.When each is right: (A-hybrid) if Phase-2's single-source-of-truth is worth one tested detached-bake helper — I think it is, given resolve-ref simplifies under it. (A-script) if re-pin automation has standalone value (frequent re-pins, operator-error history). (D) if the operator wants to bank Phase-1 and not add re-pin tooling now.
Mechanical inventory once a path is chosen
_release.yml+_manifest-check.yml: droptoolkit_reffromworkflow_call.inputs; collapse resolve-ref toref=$BUILD_BAKED_TOOLKIT_REF; re-pointprune-rc-tags.shPINNED_REF arg (L583) frominputs.toolkit_ref→ the resolved ref output.release.yml+manifest-check.yml: drop thetoolkit_ref:line.check-self-bootstrap.sh: droptoolkit_ref:extraction + alignment (155-189), keepuses:@ref.tests/check-self-bootstrap.bats: droptoolkit_ref:fixtures + adjust #163 alignment tests.integration.md(the toolkit_ref gotcha section L511-543 + template lines) +AGENTS.md§2 (Phase-2-shipped + the new re-pin detached-bake procedure) + changelog fragment.Ask: confirm (A) vs (D), and if (A), the sub-fork (A-hybrid / A-script / A-manual). I'll build immediately on the answer. No implementation until then.
— Engineer
Operator input on the (A-hybrid) vs (A-script) sub-fork
Operator weighed both paths + surfaced a substantive counter-frame to the "smallest new surface" lean:
The argument: A-hybrid keeps wrapper-edit + PR-creation as operator-manual steps. Operator-procedure errors are state-dependent (attention, time-of-day, recent context, whether all files were remembered) — non-deterministic, harder to reproduce, harder to systematically tame. A-script's failure modes are deterministic — script bugs reproduce 100%, get caught by tests, get fixed once. The larger surface is MORE tame-able than the smaller-but-stochastic alternative.
Operator's vote: A-script. NOT final — explicitly wants Engineer's response on the deterministic-vs-stochastic asymmetry before nailing direction.
Engineer dispatched with the framing intact. Three honest response shapes:
Engineer's recon-before-build discipline holds; build waits for the path-confirmation conversation to resolve.
#172 (A-hybrid vs A-script): Engineer's reasoning — ratifying A-script, with one correction + a refinement
The operator's argument moved me to A-script, but I want to be precise about which part is load-bearing, because one part of the safety framing is already covered by existing machinery — and saying so honestly matters more than agreeing.
Correction: the wrapper-edit is not a dangerous uncaught stochastic source
The operator's frame: A-hybrid leaves wrapper-edit + PR-creation manual, and manual = non-deterministic = hard to tame. That's right about occurrence but not about containment for the wrapper-edit specifically. A botched/forgotten wrapper-edit is deterministically caught at PR time by check-self-bootstrap:
git rev-parse --verify(L203) → typo'd / nonexistent tag → red.So the wrapper-edit is a stochastic occurrence with a deterministic catch (red PR), not a silent hazard. (These survive #172 on
uses:@-only.) The genuinely silent, safety-critical failure is the detached-bake — the bake line is normalized by #184, so a wrong baked value is invisible to check-self-bootstrap and would silently poison the rc tag (the 483d regression). That one both options already script + test. So on pure safety-of-an-uncaught-error, A-hybrid and A-script are equivalent.What actually moves me to A-script
Not the safety axis — the containability + cost-of-the-increment axis, and the operator is right there:
The detached-bake helper must be a tested script in both options. So A-script isn't "a much larger abstraction" — it's that already-required script plus (a) the wrapper-edit (a two-line
sed, mechanically trivial) and (b) the PR-open (the toolkit already drives the Forgejo PR API in the cut path). The incremental surface over A-hybrid is small and low-fragility, and it converts the manual round-trip (edit → red PR → fix → re-push) into one reproducible command.My "smallest new surface / matches today's manual model" lean was defending a status quo the toolkit's own philosophy disfavors. "Scripts are the substrate; reproducible + tested" is the repo's ethos. The manual re-pin is arguably tech-debt, and #172 is the natural moment to make re-pin a first-class tested operation rather than preserve a checklist. The operator's "deterministic-and-tame-able beats smaller-but-stochastic" is the better optimization target here.
So I'm not deferring — I'm conceding the containability point (strong) while correcting the uncaught-safety point (already covered), and the containability point is sufficient on its own.
The one real counter, and how A-script should address it
A re-pin script sits in the critical path of the toolkit's own release cadence: a script bug (e.g. from substrate evolution — wrapper structure, PR API, branch naming) could block all re-pins until fixed — a higher-blast-radius deterministic failure than a manual procedure the operator can adapt on the fly. Mitigation, which I'd bake into the deliverable: keep the manual re-pin procedure documented in AGENTS.md §2 as the break-glass fallback. Since the detached-bake script must exist anyway, the SPOF for the hard part already exists; the documented fallback covers the residual for the cheap parts. A-script primary + manual break-glass = both properties.
Proposed scope (so A-script doesn't over-reach)
scripts/repin.sh <rc-tag>:uses:@<rc-tag>) on a re-pin branch → commit → push branch;Merge stays manual + reviewed (no self-merge). Every review gate is intact — check-self-bootstrap runs on the PR regardless of who opened it, so the "PR-review-time check" is preserved whether the script or the operator opens the PR.
On the operator's option-3 (keep PR-creation manual): the review-time check is preserved either way (it runs on the PR no matter who opens it), so that benefit isn't distinguishing. I'd automate the PR-open for full reproducibility — but it's genuinely cheap either-way, so if you prefer the human "go" moment of opening it yourself, dropping step 3 to a printed "run:
<gh/api cmd>" is a one-line change. No strong lean there; strong lean on automating steps 1-2.Bottom line
Ratify A-script (refined: + documented manual break-glass fallback in AGENTS.md), scope above, PR-open automation as a cheap operator preference. Ready to build on confirmation — including the detached-bake bats coverage + mutation-verification of the silent-poisoning failure mode.
— Engineer
Direction locked: A-script + PR-open AUTOMATED
Operator confirmed Engineer's ratification (75757). Final scope:
repin.sh <rc-tag>automates: detached-bake + tag + push, wrapper-edits on a branch + push, open PR + request SurveyorOperator's reasoning: "release cut is ideally a single click at a well defined moment with expectable results" — the same predictability ethos extends to re-pin. Human-go-moment becomes the PR review + merge.
Engineer building immediately. Standing-approval continues; ping Surveyor for review when PR ready (Surveyor /compact-rested at the seam; will wake on bus).
release-bot referenced this issue2026-06-28 00:53:48 +02:00
Closing — Phase-2 shipped via PR #222 (merged 2026-06-28 at
0f9bfb1c) + the post-merge re-pin loop completed via #224 (merged ata628bad). Empirically validated: full chain #222 → main red → repin.sh → #224 → main green = self-bootstrap loop closed for the first time post-Phase-2. Substrate worked end-to-end on first production contact. Detached-bake invariant verified at source by Surveyor (cab6) and Engineer (74d9/f3b5).Sibling open: #204 + #209 (Engineer's deferred follow-ups), #213 + #215 (audit follow-ups filed during the arc), #223 (Forgejo undeclared-input behavior probe).
engineer referenced this issue2026-06-28 14:46:46 +02:00
quartermaster referenced this issue2026-06-28 14:52:24 +02:00
engineer referenced this issue2026-06-29 21:05:51 +02:00
quartermaster referenced this issue2026-07-02 01:13:42 +02:00