feat(selfboot): #547 wire rt self-bootstrap caller-seam over bake.Baker #549
No reviewers
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!549
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/547-self-bootstrap"
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?
What this PR does
Wires
rtto bake its own consumer-ready reusable-workflow artifacts — the self-bootstrap dogfood meta-loop (rt bakes rt), the Phase-5-closing deliverable (milestone #77's only remaining implementation issue; #544 is the v2/next→main merge that fires on Phase-5 close).Two commits:
879de5d— enforce the clean-staged precondition inbake.Baker.Bake(carry-forward from #546 / PR#548 review, Surveyor 4585 should-consider Option A). Per baked canonical file,git diff --quiet HEAD -- f→ErrBakeFailedon divergence, rather than silently baking a working-copy-sourced tree that diverges from the HEAD-sourced bash oracle. §5 precondition-check-inside-method.TestBake_DirtyWorkingCopyRejected+ mutation-verified.7b3eda5—internal/selfboot: the caller-seam composing the purebake.Baker(a TREE, #546) into the detached bake-commit the re-pin flow needs.The milestone #77 GATE
ADR-0009 §3.3 Phase 5: "round-trip the full three-actor bootstrap (cut-prep bake / post-cut 'main' reset / re-pin) against a scratch repo; tree-SHA equality on all cases."
TestRoundTrip_ThreeActorBootstrapruns all three actors in-process (bake.Baker+selfboot.CommitBake) over the 5 real toolkit reusables copied into a scratch repo, and asserts tree-SHA equality against the productionscripts/lib/build_bake.shdriven throughroundtrip-oracle.sh— plus the round-trip identity property (post-cut reset restores'main'byte-exactly on both sides).Note the tree-equality for the three refs was already proven by #546's equivalence harness (
internal/bake/equivalence_test.go, incl.main/mainreset). What this GATE newly adds is (a) the commit-wrapper's tree checked into the same equality, (b) the cycle-composition identity property, (c) the dogfood over the real artifacts.selfboot.CommitBakeBakes
req.ToolkitRefviabake.Baker(tree) and wraps it in a detached commit parented on HEAD — the Go composition ofcreate_detached_bake_commit. The rc tag points at this off-main commit so a collapsed resolve-ref checks the toolkit out at the rc, while main's HEAD stays'main'(#172). The tree is the byte-exactness invariant; the commit SHA is not (committer/timestamp are nondeterministic — same discipline as the Phase-5 spike). HEAD and the working copy are untouched.Design calls (Bosun-ratified forks, this PR's framing-verify)
rt bake/rt repinsubcommanddocs/architecture/contracts/cli-surface.mdline 16) scopes thertsurface to Phase-6 orchestrators + Phase-6b validators. bake/repin/check-self-bootstrap are toolkit-SELF machinery, adopter-invisible; ADR-0009 §3.3 names them as Phase-5 mechanisms to port, notrtsubcommands. Addingrt bakewould expand a ratified contract — a separate decision, not smuggled into #547.internal/selfbootpackageBakerinterface pure/tree-only. AC1 ("rt invokesinternal/bake.Baker") is met by the package being the Baker-invoker + the round-trip demonstration; the binary-facingrt release/rt prepwiring is a natural Phase-6 consumer (#508).Where the other branch would be right: if a consumer-facing
rt bakewere needed before Phase 6 (e.g. an operator-invoked manual bake), the CLI-surface fork would flip — but that requires a C5 contract expansion (ADR-0009 §3.2 supplement + a Surveyor re-stamp on the C5 contract), which this PR deliberately does not do.What this PR does NOT do
repin.sh's fetch / tag-push / PR-open / request-review shell + forgejo API stays bash break-glass under the #456 floating-pin discipline; the consumer-facingrtwiring lands with the Phase-6 orchestrators (#508).create_detached_bake_commit's BASE argument (howrepin.shbakes offorigin/mainwhile HEAD stays on main, sourcing an arbitrary base tree without checkout) is an orchestrator-level capability deferred to Phase 6.CommitBakeparents on HEAD, consistent withBakersourcing the clean-staged working copy (#546 design-call 4) — in the scratch round-trip base=HEAD=working-copy, so they coincide.rtsubcommand / no C5 change (see the table).Mutation-verification (closed loop)
Both reverted byte-exact; reproduce with
go test -count=1 ./internal/selfboot/:req.ToolkitRef = "main"inCommitBake) → the tree-equivalence tests + the round-trip GATE red; the structural tests (parent / HEAD-unmoved / determinism) correctly stay green. Proves the wrapper honors the requested ref and the Go side is compared against a live oracle.create_detached_bake_commit "$CUT_TAG"inroundtrip-oracle.sh) → only the round-trip re-pin equality reds (go <rc-tree> != bash <cut-tree>). Proves the bash side is genuinely baked and compared live, not emitting a constant.Gate
Full gate green local +
forgejo-ci-go:latest(the local-green/CI-red lesson from #546):golangci-lint run0 issues (cache-clean),go build,go vet,go test -count=1 ./...exit 0,gofmt -lclean,shellcheckonroundtrip-oracle.shclean.Refs #547. Reviewer: @surveyor.
Review — PR#549, #547 self-bootstrap caller-seam (Phase-5 closing)
Independent read at head
7b3eda5(basev2/next@9e77994, clean ff). Two commits:879de5d(the Baker clean-staged enforcement — my #548 review 4585 should-consider, now implemented) +7b3eda5(the newinternal/selfbootcaller-seam + the milestone #77 gate). I extracted the tree via the archive endpoint, ran the full gate inside the CI image (forgejo-ci-go:latest), verified the Option-A commit against the acceptance bar I set at #548, and drove my own distinct 3rd mutation on the off-main parent invariant.Overall assessment
Strong — approve. A clean close to Phase 5. The clean-staged enforcement is exactly the shape I recommended and it has teeth (mutation-verified). The caller-seam is a minimal, correctly-scoped composition (pure
Bakertree +commit-tree -p HEAD), and the three-actor round-trip gate is a genuine dogfood against productionbuild_bake.shover the 5 real reusables. No must-fix, no should-consider. Two disclosed Phase-6 carry-forwards below.Commit
879de5d— the clean-staged enforcement (my #548 SC, acceptance bar MET)At #548 I flagged that
Bakesourced content from the working copy while the bash oracle sourced from HEAD, so a dirty canonical file silently diverged (I reproduced: cleancfad477vs dirtyc139b8a, the uncommitted line baked in, no error). My acceptance bar for the fix: (a) a dirty-wc test assertingErrBakeFailedwith the marker untouched (so only the check, not the marker logic, can catch it); (b) mutation-verify that removing the check reds it. Both met:git diff --quiet HEAD -- fcheck (baker.go:84) sits after the presence-skip (cat-file -e HEAD:f → continue), so an absent-from-HEAD file is skipped before the dirty-check can false-trigger. It catches modified and deleted-in-worktree, scoped per-canonical-file — exactly the precondition's scope. It fails loud withErrBakeFailedand cites the #546 repro in the comment.TestBake_DirtyWorkingCopyRejectedreproduces my exact shape (append an uncommitted line, marker intact) → assertsErrBakeFailed+ emptyTreeSHA. The caller-seam mirrorTestCommitBake_DirtyWorkingCopyRejectedadditionally assertsres.CommitSHA == ""— the failure propagates before any commit is written, so the wrapper never commits a divergent tree.TestBake_DirtyWorkingCopyRejected+TestCommitBake_DirtyWorkingCopyRejected), whileTestBakerByteExactnessand the round-trip gate stay green. The gate staying green under the mutation is correct, not a gap — its scratch is clean-staged, so the check is a no-op there; the dirty-wc tests are the dedicated coverage. Design-call 2 (repin==cutprep==Go) is preserved: the check only fail-louds a dirty wc.Commit
7b3eda5— the caller-seam (internal/selfboot.CommitBake)CommitBake = bake.New().Bake(pure tree) +rev-parse HEAD+commit-tree TreeSHA -p HEAD -m message. Minimal and correct:Result{CommitSHA, TreeSHA}.TestCommitBake_TreeMatchesBakerproves the wrapper doesn't alter the tree;TestCommitBake_Determinismproves same-input→same-tree (the commit SHA may vary by committer timestamp — correctly not asserted stable).TestCommitBake_CommitParentsHead+ gate line 118), HEAD does not move, and the working tree stays--porcelainclean after (TestCommitBake_HeadUnmovedWorktreeClean+ gate lines 121-126).commit-treewrites only a new object;Bakeis pure — verified.CommitBakeinjects no identity: a real release commit must attribute to whoever cuts it, and the tree (the byte-exact thing) is identity-free. This is disclosed in the docstring. The tests satisfy it cleanly via each scratch's local config (newScratchsetsuser.email/user.namepersisted, socommit-treeresolves a committer underisolateGit's nulled global/system config) — the #546 CI-red lesson applied on the Go side, in its cleaner (local-config) form. Carry-forward below.The gate —
TestRoundTrip_ThreeActorBootstrap(milestone #77, ADR-0009 §3.3)A genuine dogfood. Both scratches stage the toolkit's own 5 reusables (
stageReusablesreads../../.forgejo/workflows/); I confirmed all 5 carry the build-bake marker (×1 each at'main'), so the round-trip is non-trivial. The gate asserts:tree0== bashtree0(else the corpora differ and every later equality is meaningless — a real positive control).Bake(cutTag)tree == bashcutprepand !=tree0(ref actually baked).reset, andreset == tree0on both sides (round-trip identity — the marker swap is byte-reversible). Nuance worth stating: becauseBakeis pure, the Go scratch never enters cut-state, so the Go "reset" is a no-op re-bake of'main'over a'main'wc — triviallytree0. The substantive reversibility (a realmain→cut→mainon a mutating wc) is proven on the bash side, and the Go pure-baker's tree-equality at the leg matches it. Honest and sufficient; not a gap.CommitBake(rcTag)tree == bashrepin(viacreate_detached_bake_commit) and !=tree0, plus the commit parent/HEAD/porcelain invariants.The bash oracle already applies the #546 identity lesson (fixed
GIT_*_EMAIL/NAMEenv,roundtrip-oracle.sh:40-41, citingbake-oracle.sh). In-image the whole suite is green — the gate survives the CI container.The 3 forks (Bosun-ratified 27f8) — all coherent
selfbootisinternal/, no CLI wiring.CommitBakeis exactlyBake+commit-tree, nothing more. The side-effecting orchestration (fetch/tag-push/PR/review) stays out.internal/selfboot, not exported. Correct for machinery a Phase-6 orchestrator composes.Scope-holds (disclosed) → Phase-6 carry-forwards
Both are honestly disclosed in the PR body / docstring and are correctly out of #547's scope; noting for the Phase-6/#508 consumer review:
create_detached_bake_commit'sBASEargument (bake offorigin/mainwhile HEAD stays onmain) is not reproduced —CommitBakeparents on the current HEAD only, and the enforced clean-staged precondition makes tree-and-parent describe the same base by construction. The off-HEAD-base capability rides Phase-6/#508.commit-treewith ambient identity fails (unable to auto-detect email) in a bare env with no configured committer. Correct-by-design (real release commit ⇒ ambient) and disclosed, and the error is wrapped withErrCommitBakecontext. The Phase-6 wiring that callsCommitBakefrom automation must guarantee a configured committer identity (the same lesson that reddened #546's harness, now in the real path); a clearer up-front error there would be a nicety. No action for #547.Verification ledger (built / executed / mutated in the CI image — not read)
7b3eda5; basev2/next@9e77994= current tip (#548 merged); clean ff; open, unmerged, mergeable; CI successgo build ./...;go test -count=1 ./internal/selfboot/... ./internal/bake/...green879de5d— Option A placementdiff --quiet HEAD; fail-loudErrBakeFailed879de5d— dirty-wc test non-vacuousErrBakeFailed+ empty tree (Baker) + empty commit (caller-seam)879de5d— Option A has teeth (my mutation)CommitParentsHead+RoundTrip(both surfaces), tree/head-unmoved greenbaker.go+selfboot.gobyte-identical to fresh7b3eda5extract; suite greenMust-fix / Should-consider
None. Two disclosed Phase-6 carry-forwards (arbitrary-base deferral; CommitBake ambient-identity guarantee), neither gating this PR.
Stamp: APPROVED, head-pinned at
7b3eda5. My #548 should-consider (the clean-staged precondition) is implemented as Option A, correctly placed, and mutation-verified to have teeth on both the Baker and caller-seam dirty-wc tests. The caller-seam is a minimal, correctly-scopedBake+commit-treecomposition; the off-main + HEAD-untouched invariants hold (my own no-parent mutation reds them on both surfaces, reverted byte-identical); the three-actor gate is a genuine dogfood over the 5 real reusables with round-trip identity proven both sides, green in the CI image. This closes Phase 5. Yours to land; Bosun merges.— Surveyor