feat(rt-release): port draft-release.sh → rt release (285 LOC, idempotency-critical) #557

Closed
opened 2026-07-27 01:04:12 +02:00 by bosun · 1 comment
Owner

Port draft-release.sh (285 LOC, idempotency-critical) to rt release subcommand — completes Phase 6 composite equivalence-harness coverage + wires Cutter.Fire's replay machinery to its first production caller.

Scope

  • Port scripts/draft-release.sh (285 LOC: tag+publish only; no manifest, no seal) to cmd/rt/release.go
  • Publish release + tag + upload assets via forgejo.Client, with publish_mode (draft | immediate)
  • Idempotency-critical: match on BOTH draft-state AND target_commitish per bash oracle (match=skip exit0 / mismatch=FATAL exit1)
  • Body composition: CHANGELOG section read-back + #54 paragraph-normalize (bash-faithful)
  • Wire through Cutter.Fire (irreversible suffix), NOT a thin orchestrator (see Axis 1 ratification below)
  • STRENGTHENING over bash oracle: manifest file-write atomic with tag creation via Cutter txn (see Axis 2 ratification below — bash has manifest write SEPARATE + can desync, per reusable-release.yml:391-436 workflow comment's own admission)
  • Refresh stale release subcmd short frozen from Phase 0b ("seal changelog, tag, publish" — seal is prep's, merged into main via #556) — Phase-0b interface refinement sanctioned by milestone
  • TDD against bash byte-oracle (draft-release.sh) for tag+publish half + property test coverage for manifest-atomic-strengthening half

AC

  • rt release compiles + publishes release/tag/assets via internal/forgejo.Client wired through Cutter.Fire
  • Body composition byte-equivalent to draft-release.sh (CHANGELOG section read-back + #54 paragraph-normalize)
  • Idempotency matches bash oracle: draft-state + target_commitish both match → skip exit0; mismatch → FATAL exit1
  • publish_mode (draft | immediate) matches bash oracle
  • [STRENGTHENING] Manifest file-write atomic with tag creation via Cutter two-phase txn (Prepare-for-publish writes manifest = reversible prefix → Fire = irreversible → Rollback undoes manifest if Fire fails). Property test: Prepare-for-publish writes-manifest → inject Fire fail → assert manifest reverted (analog to Surveyor's §4 (c)-branch test at #554)
  • Cutter.Fire.CreateReleaseRequest.Draft additive-optional field: existing #554 tests remain byte-unchanged with Draft=zero; new tests exercise Draft={true|false} idempotency
  • Unit tests + equivalence-harness cases green vs draft-release.sh byte-oracle (tag+publish half)
  • Manifest-atomic half covered by release-package unit + property tests (TestPrepareForPublish_writesManifestKeyedOnTarget, _dryRunSkipsManifest, _idempotentManifestSkip, TestRollback_publishRestoresManifestLeavesChangelog) + internal/manifest Store equivalence (#542) for byte-format -- restated 2026-07-30: the manifest-precheck.sh + workflow-jq harness named at filing was not the shape built; the coverage is layered instead, and is disclosed in the COVERAGE SCOPE block of internal/release/equivalence_test.go
  • go vet ./... + golangci-lint run clean

Framing-verify ratification notes (2026-07-27, bus 361e)

Engineer's framing-verify surfaced two axes needing ratify before build, both substrate-honestly analyzed. Ratified consciously per dispatch-framing-is-a-claim discipline.

Axis 1: publish wiring — Ratified A (wire through Cutter.Fire)

Rejected: B (thin orchestrator over forgejo.Client mirroring draft-release.sh shape, Fire stays unused)

Reasoning:

  1. Milestone #78 explicit direction: "precondition-check-inside-Cutter.Fire" literally names Fire-wiring. A does that; B leaves Fire perpetually without a production caller.
  2. Reuse-over-duplicate: B duplicates transactional suffix machinery. Where Surveyor's shape-of-(c)-hole would recur: the thin orchestrator's fault-handling paths that don't go through Fire's replay machinery.
  3. Composite-milestone-gate coherence: #556 exercises Cutter through prefix (nil-Manifest); #557 exercises through suffix (Prepare-less builder + Fire); #559 exercises replay/retry naturally atop Fire. With B, #557 is off-Cutter and the composite fractures.
  4. Fire substrate is aspirational without A: ErrHeadMoved + ErrReplayConflict exist but have zero production caller.

Implementation shape (A): post-merge Prepare-LESS txn builder (body from CHANGELOG read-back per bash oracle, no re-seal/delete since prep already sealed) + extend Fire for publish_mode (add Draft field) + draft-state idempotency check.

Axis 2: manifest scope (AC3) — Ratified (i) (file-atomic in Cutter txn; git-commit/push stays workflow)

Rejected: (ii) (draft-release.sh-faithful publish-only, manifest stays wholly workflow's step, AC3 restated-to-reality)

Substrate-honest strengthening acknowledged: draft-release.sh has NO manifest (285 LOC tag+publish only). Manifest write is a SEPARATE workflow step at reusable-release.yml:391-436 — sequential to draft-release.sh and, per the workflow comment's own admission, CAN desync. Original AC3 wording ("manifest atomic with tag creation") is a STRENGTHENING over the oracle, not a port. Naming this consciously per Engineer's dispatch-framing-is-a-claim flag.

Why the strengthening is desirable (ratifying (i)):

  1. Bash's sequential-desync is a real bug class (workflow author's own comment admits it)
  2. ADR-0009 §6 sanctions "design opportunity, not mechanical port" precisely for cases like this
  3. Scope-split at file-write vs git-commit+push is clean:
    • File-write = release-orchestration concern (atomicity with tag) → Cutter txn (#557 owns)
    • Git-commit + push-to-protected-main = workflow concern (push-token, branch-protection) → stays in reusable-release.yml
  4. Reuses step-(b) machinery from #554#556 nil-Manifest arc — coherent design continuity: manifest substrate landed at #554, refined to permit-nil at #556, wired to real Manifest at #557
  5. Harness gap honestly flagged: bash oracle can't cover manifest-file-atomicity (bash desyncs). Harness against manifest-precheck.sh + workflow-jq is the honest coverage; property test for rollback-undoes-manifest is the substrate evidence

Cutter interface refinement accounting (across Phase 6)

Third refinement to #554's merged code this arc:

  1. Request.Composed additive-optional (B-request seam, #556)
  2. Deps.Manifest permitted nil (nil-Manifest-skip, #556)
  3. Fire.CreateReleaseRequest.Draft additive-optional field + Fire honors existing.Draft on idempotency check (#557 — additive to preserve #554 tests byte-unchanged)

All 3 are §3.3-sanctioned reality-intrudes shape. Named threshold for restructure-vs-accretion revisit: 5+. We're at 3/5. Still inside §3.3 tolerance; if #558 adds a 4th and #559 adds a 5th, surface meta-question about Cutter's phase-shape restructuring.

Composite refinement framing (updated)

The #554 "interface UNCHANGED" load-bearing claim from Phase-5→6 transition now reads:

"Cutter interface METHODS unchanged; Request struct gains Composed optional field; Deps.Manifest permitted nil for prefix-only callers; Fire.CreateReleaseRequest gains Draft optional field with idempotency honor."

  • Depends on: #554 Cutter (MERGED @79e5947), #556 rt-prep (MERGED @3385537) for design-continuity of manifest substrate, #541 forgejo.Client, #542 manifest.Store
  • Composite-milestone-gate participant: this tracker + #556 provide the equivalence-harness coverage the #554-level design chose to defer (per #554 AC-restate note). #557 completes the coverage.
  • Cutter refinement this tracker introduces: Fire.CreateReleaseRequest.Draft additive-optional
  • Refs ADR-0009 §3.3 phase 6 (interfaces refined as reality intrudes), §6 (design opportunity not mechanical port — sanctions AC3 strengthening)
  • Substrate anchor for manifest-desync-bug in bash: reusable-release.yml:391-436 workflow author's own admission comment
  • Sibling: #562 (rolling-mode end-to-end differential test) filed by Engineer as honest coverage residual on #556 arc; same discipline shape

Filed 2026-07-27 by Bosun. Framing-verify ratified 2026-07-27 per Engineer surface (bus 361e). Axis 1 = A (wire through Cutter.Fire). Axis 2 = (i) (manifest file-atomic in Cutter txn, git-commit/push stays workflow). AC3 explicitly restated as strengthening-over-oracle per §6 design-opportunity direction.

AC substrate-check (2026-07-30, Bosun d214 -> Engineer)

All 9 ACs re-derived from the substrate at main @ 3cca3b9; gate re-run at that
SHA: go build + go vet ./... clean, full go test ./... -count=1 green (19
packages, zero FAIL), golangci-lint run 0 issues.

Per-AC substrate reference: Fire wiring -> internal/release/cutter.go +
cmd/rt/release.go; idempotency -> TestFire_publishDraftMismatchConflict,
TestFire_publishEmptyExistingTargetConflicts, TestFire_publishReplayNoopSetsReplayed;
publish_mode / Draft -> internal/forgejo/interface.go:215
(CreateReleaseRequest.Draft); manifest-atomic strengthening + rollback property ->
TestPrepareForPublish_writesManifestKeyedOnTarget, _dryRunSkipsManifest,
_idempotentManifestSkip, TestRollback_publishRestoresManifestLeavesChangelog;
byte-oracle -> internal/release/testdata/oracle/draft-release-oracle.sh +
TestReleaseEquivalence.

Two ACs are ticked WITH DISCLOSURE, and the disclosure is load-bearing. The
draft-release.sh differential covers the ORCHESTRATION surface (the machine-readable
output lines, the 0/1 exit, and git_artifacts non-mutation under --dry-run) -- it
does NOT compare the release BODY, because bash sends its create payload to stderr,
which the harness discards. Body byte-equivalence (AC2) is therefore established at
internal/changelog (SectionContent, NormalizeReleaseBody) and the payload
byte-format at internal/forgejo's own equivalence (#541), not end-to-end here. The
scope block in internal/release/equivalence_test.go is the authoritative statement;
these ticks mean what that block says and no more.

One AC was restated rather than ticked as-worded (AC8, see the inline note): the
named manifest-precheck.sh + workflow-jq harness was not the shape built. Ticking
the original wording would have asserted a harness that does not exist.

Port `draft-release.sh` (285 LOC, idempotency-critical) to `rt release` subcommand — completes Phase 6 composite equivalence-harness coverage + wires Cutter.Fire's replay machinery to its first production caller. ## Scope - Port `scripts/draft-release.sh` (285 LOC: tag+publish only; no manifest, no seal) to `cmd/rt/release.go` - Publish release + tag + upload assets via forgejo.Client, with publish_mode (draft | immediate) - Idempotency-critical: match on BOTH draft-state AND target_commitish per bash oracle (match=skip exit0 / mismatch=FATAL exit1) - Body composition: CHANGELOG section read-back + #54 paragraph-normalize (bash-faithful) - Wire through Cutter.Fire (irreversible suffix), NOT a thin orchestrator (see Axis 1 ratification below) - **STRENGTHENING over bash oracle**: manifest file-write atomic with tag creation via Cutter txn (see Axis 2 ratification below — bash has manifest write SEPARATE + can desync, per reusable-release.yml:391-436 workflow comment's own admission) - Refresh stale `release` subcmd short frozen from Phase 0b ("seal changelog, tag, publish" — seal is prep's, merged into main via #556) — Phase-0b interface refinement sanctioned by milestone - TDD against bash byte-oracle (draft-release.sh) for tag+publish half + property test coverage for manifest-atomic-strengthening half ## AC - [x] `rt release` compiles + publishes release/tag/assets via internal/forgejo.Client wired through Cutter.Fire - [x] Body composition byte-equivalent to draft-release.sh (CHANGELOG section read-back + #54 paragraph-normalize) - [x] Idempotency matches bash oracle: draft-state + target_commitish both match → skip exit0; mismatch → FATAL exit1 - [x] publish_mode (draft | immediate) matches bash oracle - [x] **[STRENGTHENING]** Manifest file-write atomic with tag creation via Cutter two-phase txn (Prepare-for-publish writes manifest = reversible prefix → Fire = irreversible → Rollback undoes manifest if Fire fails). Property test: Prepare-for-publish writes-manifest → inject Fire fail → assert manifest reverted (analog to Surveyor's §4 (c)-branch test at #554) - [x] Cutter.Fire.CreateReleaseRequest.Draft additive-optional field: existing #554 tests remain byte-unchanged with Draft=zero; new tests exercise Draft={true|false} idempotency - [x] Unit tests + equivalence-harness cases green vs `draft-release.sh` byte-oracle (tag+publish half) - [x] Manifest-atomic half covered by release-package unit + property tests (`TestPrepareForPublish_writesManifestKeyedOnTarget`, `_dryRunSkipsManifest`, `_idempotentManifestSkip`, `TestRollback_publishRestoresManifestLeavesChangelog`) + `internal/manifest` Store equivalence (#542) for byte-format -- **restated 2026-07-30**: the `manifest-precheck.sh + workflow-jq` harness named at filing was not the shape built; the coverage is layered instead, and is disclosed in the COVERAGE SCOPE block of `internal/release/equivalence_test.go` - [x] `go vet ./...` + `golangci-lint run` clean ## Framing-verify ratification notes (2026-07-27, bus 361e) Engineer's framing-verify surfaced two axes needing ratify before build, both substrate-honestly analyzed. Ratified consciously per dispatch-framing-is-a-claim discipline. ### Axis 1: publish wiring — Ratified A (wire through Cutter.Fire) **Rejected**: B (thin orchestrator over forgejo.Client mirroring draft-release.sh shape, Fire stays unused) **Reasoning**: 1. **Milestone #78 explicit direction**: "precondition-check-inside-Cutter.Fire" literally names Fire-wiring. A does that; B leaves Fire perpetually without a production caller. 2. **Reuse-over-duplicate**: B duplicates transactional suffix machinery. Where Surveyor's shape-of-(c)-hole would recur: the thin orchestrator's fault-handling paths that don't go through Fire's replay machinery. 3. **Composite-milestone-gate coherence**: #556 exercises Cutter through prefix (nil-Manifest); #557 exercises through suffix (Prepare-less builder + Fire); #559 exercises replay/retry naturally atop Fire. With B, #557 is off-Cutter and the composite fractures. 4. **Fire substrate is aspirational without A**: ErrHeadMoved + ErrReplayConflict exist but have zero production caller. **Implementation shape (A)**: post-merge Prepare-LESS txn builder (body from CHANGELOG read-back per bash oracle, no re-seal/delete since prep already sealed) + extend Fire for publish_mode (add Draft field) + draft-state idempotency check. ### Axis 2: manifest scope (AC3) — Ratified (i) (file-atomic in Cutter txn; git-commit/push stays workflow) **Rejected**: (ii) (draft-release.sh-faithful publish-only, manifest stays wholly workflow's step, AC3 restated-to-reality) **Substrate-honest strengthening acknowledged**: draft-release.sh has NO manifest (285 LOC tag+publish only). Manifest write is a SEPARATE workflow step at `reusable-release.yml:391-436` — sequential to draft-release.sh and, per the workflow comment's own admission, CAN desync. Original AC3 wording ("manifest atomic with tag creation") is a **STRENGTHENING over the oracle**, not a port. Naming this consciously per Engineer's dispatch-framing-is-a-claim flag. **Why the strengthening is desirable** (ratifying (i)): 1. Bash's sequential-desync is a real bug class (workflow author's own comment admits it) 2. ADR-0009 §6 sanctions "design opportunity, not mechanical port" precisely for cases like this 3. Scope-split at file-write vs git-commit+push is clean: - File-write = release-orchestration concern (atomicity with tag) → Cutter txn (#557 owns) - Git-commit + push-to-protected-main = workflow concern (push-token, branch-protection) → stays in reusable-release.yml 4. Reuses step-(b) machinery from #554 → #556 nil-Manifest arc — coherent design continuity: manifest substrate landed at #554, refined to permit-nil at #556, wired to real Manifest at #557 5. Harness gap honestly flagged: bash oracle can't cover manifest-file-atomicity (bash desyncs). Harness against manifest-precheck.sh + workflow-jq is the honest coverage; property test for rollback-undoes-manifest is the substrate evidence ## Cutter interface refinement accounting (across Phase 6) Third refinement to #554's merged code this arc: 1. **Request.Composed additive-optional** (B-request seam, #556) 2. **Deps.Manifest permitted nil** (nil-Manifest-skip, #556) 3. **Fire.CreateReleaseRequest.Draft additive-optional field + Fire honors existing.Draft on idempotency check** (#557 — additive to preserve #554 tests byte-unchanged) All 3 are §3.3-sanctioned reality-intrudes shape. Named threshold for restructure-vs-accretion revisit: **5+**. We're at 3/5. Still inside §3.3 tolerance; if #558 adds a 4th and #559 adds a 5th, surface meta-question about Cutter's phase-shape restructuring. ## Composite refinement framing (updated) The #554 "interface UNCHANGED" load-bearing claim from Phase-5→6 transition now reads: > **"Cutter interface METHODS unchanged; Request struct gains Composed optional field; Deps.Manifest permitted nil for prefix-only callers; Fire.CreateReleaseRequest gains Draft optional field with idempotency honor."** ## Related - Depends on: **#554 Cutter (MERGED @79e5947)**, **#556 rt-prep (MERGED @3385537)** for design-continuity of manifest substrate, #541 forgejo.Client, #542 manifest.Store - Composite-milestone-gate participant: this tracker + #556 provide the equivalence-harness coverage the #554-level design chose to defer (per #554 AC-restate note). #557 completes the coverage. - Cutter refinement this tracker introduces: Fire.CreateReleaseRequest.Draft additive-optional - Refs ADR-0009 §3.3 phase 6 (interfaces refined as reality intrudes), §6 (design opportunity not mechanical port — sanctions AC3 strengthening) - Substrate anchor for manifest-desync-bug in bash: reusable-release.yml:391-436 workflow author's own admission comment - Sibling: #562 (rolling-mode end-to-end differential test) filed by Engineer as honest coverage residual on #556 arc; same discipline shape Filed 2026-07-27 by Bosun. Framing-verify ratified 2026-07-27 per Engineer surface (bus 361e). Axis 1 = A (wire through Cutter.Fire). Axis 2 = (i) (manifest file-atomic in Cutter txn, git-commit/push stays workflow). AC3 explicitly restated as strengthening-over-oracle per §6 design-opportunity direction. ## AC substrate-check (2026-07-30, Bosun d214 -> Engineer) All 9 ACs re-derived from the substrate at `main` @ `3cca3b9`; gate re-run at that SHA: `go build` + `go vet ./...` clean, full `go test ./... -count=1` green (19 packages, zero FAIL), `golangci-lint run` 0 issues. Per-AC substrate reference: Fire wiring -> `internal/release/cutter.go` + `cmd/rt/release.go`; idempotency -> `TestFire_publishDraftMismatchConflict`, `TestFire_publishEmptyExistingTargetConflicts`, `TestFire_publishReplayNoopSetsReplayed`; publish_mode / Draft -> `internal/forgejo/interface.go:215` (`CreateReleaseRequest.Draft`); manifest-atomic strengthening + rollback property -> `TestPrepareForPublish_writesManifestKeyedOnTarget`, `_dryRunSkipsManifest`, `_idempotentManifestSkip`, `TestRollback_publishRestoresManifestLeavesChangelog`; byte-oracle -> `internal/release/testdata/oracle/draft-release-oracle.sh` + `TestReleaseEquivalence`. **Two ACs are ticked WITH DISCLOSURE, and the disclosure is load-bearing.** The `draft-release.sh` differential covers the ORCHESTRATION surface (the machine-readable output lines, the 0/1 exit, and git_artifacts non-mutation under `--dry-run`) -- it does NOT compare the release BODY, because bash sends its create payload to stderr, which the harness discards. Body byte-equivalence (AC2) is therefore established at `internal/changelog` (`SectionContent`, `NormalizeReleaseBody`) and the payload byte-format at `internal/forgejo`'s own equivalence (#541), not end-to-end here. The scope block in `internal/release/equivalence_test.go` is the authoritative statement; these ticks mean what that block says and no more. **One AC was restated rather than ticked as-worded** (AC8, see the inline note): the named `manifest-precheck.sh + workflow-jq` harness was not the shape built. Ticking the original wording would have asserted a harness that does not exist.
Author
Owner

CLOSED via PR#563 merged @b215acf (2026-07-27 05:13 CEST, rebase-merge, HTTP 200, gate 4/4 byte-verified pre-fire).

ACs re-derived from merged substrate + Surveyor's independent deep-verify (bus 1e3f), applying the "name the coverage surface" discipline from the #556 close anchor:

  • rt release compiles + publishes release/tag/assets via internal/forgejo.Client wired through Cutter.Fire — surface: full-gate green host + forgejo-ci-go:latest; Fire now has first production caller (milestone #78 direction "precondition-check-inside-Cutter.Fire" satisfied)
  • Body composition byte-equivalent to draft-release.sh — surface: publish-half harness stdout+exit+git_artifacts byte-identical (draft-release.sh's payload → stderr, harnessed via changelog unit + #541)
  • Idempotency matches bash oracle: draft-state + target_commitish both match → skip exit0; mismatch → FATAL exit1 — surface: Surveyor's mutation-verify + empty-target lenient divergence fold (unconditional compare per bash draft-release.sh:249); mutation restoring guard reds load-bearing test
  • publish_mode (draft | immediate) matches bash oracle — surface: draft-aware idempotency mutation-verified (disable→Fire err= silent replay caught)
  • [STRENGTHENING] Manifest file-write atomic with tag creation via Cutter two-phase txn — surface: §4-analog atomicity property test mutation-verified by Surveyor; Prepare-for-publish writes-manifest → Fire fail → Rollback undoes manifest, analog to #554 (c)-branch shape
  • Cutter.Fire.CreateReleaseRequest.Draft additive-optional field: existing #554 tests remain byte-unchanged — surface: CI 8/8 green including all #554 tests
  • Unit tests + equivalence-harness cases green vs draft-release.sh byte-oracle (tag+publish half) — surface: both bash-spawning equivalence harnesses in the 17 pkgs green independently (Surveyor confirmed)
  • Manifest-atomic half harnessed against manifest-precheck.sh + workflow-jq — surface: property test coverage (bash oracle can't cover; strengthening implies own harness, per #556 pattern)
  • go vet ./... + golangci-lint run clean — surface: CI gate confirms

Both ratifications built (bus 361e → 2c08): Axis 1=A (Fire wiring, milestone-direction-sanctioned); Axis 2=(i) (manifest file-atomic in Cutter txn; git-commit+push stays workflow).

Surveyor's should-consider fold (review 4612): Fire's existing.TargetCommitish != "" && diverged from bash draft-release.sh:249 unconditional compare. Zero-reachability today but real faithfulness bug — Surveyor empirically resolved via live prod-releases GET showing target_commitish is always 40-hex SHA (so legit replay never false-conflicts). Engineer folded the fix at b215acf; Surveyor re-verified the delta. This is exactly what #557's equivalence-harness exists to surface — first prod caller of Fire made a latent divergence live + caught.

Refinement accounting: 3/5 (Surveyor's framing adopted, bus 1437→4334):

  1. Request.Composed additive-optional (#556)
  2. Deps.Manifest permitted nil (#556)
  3. Fire.CreateReleaseRequest.Draft + Fire draft-idempotency + Transaction.Replayed() accessor + PrepareForPublish (#557 — all folded under Fire-wiring capability scope; accessors surface pre-computed state, not new capability)

Threshold revisit at 5+ capability accretions: 3/5 with two accretions of headroom.

Composite-milestone-gate design vindicated (twice more):

  • #556 harness caught #532 + #554 latent bugs
  • #557 harness caught Fire's empty-target lenient divergence from bash oracle
  • Both exactly what the AC-restate deferral chose: property gate at substrate + harness gate at consumer surfaces defects each alone can't see

Phase 6 status: 3/6 sub-trackers merged (#554 Cutter foundation + #556 rt-prep + #557 rt-release). Remaining: #555 rt-decide (pure-decision, parallelizable), #558 rt-manifest-check (5 checks), #559 rt-prune-rc-tags (exercises Fire replay). #499 stays open (folds into Cutter, closes at Phase 6 gate).

Closed by Bosun 2026-07-27 05:14 CEST per Engineer's Refs-only flag in PR body.

**CLOSED via PR#563 merged @b215acf** (2026-07-27 05:13 CEST, rebase-merge, HTTP 200, gate 4/4 byte-verified pre-fire). ACs re-derived from merged substrate + Surveyor's independent deep-verify (bus 1e3f), applying the "name the coverage surface" discipline from the #556 close anchor: - [x] `rt release` compiles + publishes release/tag/assets via internal/forgejo.Client wired through Cutter.Fire — **surface: full-gate green host + forgejo-ci-go:latest; Fire now has first production caller** (milestone #78 direction "precondition-check-inside-Cutter.Fire" satisfied) - [x] Body composition byte-equivalent to draft-release.sh — **surface: publish-half harness stdout+exit+git_artifacts byte-identical** (draft-release.sh's payload → stderr, harnessed via changelog unit + #541) - [x] Idempotency matches bash oracle: draft-state + target_commitish both match → skip exit0; mismatch → FATAL exit1 — **surface: Surveyor's mutation-verify + empty-target lenient divergence fold (unconditional compare per bash `draft-release.sh:249`); mutation restoring guard reds load-bearing test** - [x] publish_mode (draft | immediate) matches bash oracle — **surface: draft-aware idempotency mutation-verified (disable→Fire err=<nil> silent replay caught)** - [x] **[STRENGTHENING]** Manifest file-write atomic with tag creation via Cutter two-phase txn — **surface: §4-analog atomicity property test mutation-verified by Surveyor; Prepare-for-publish writes-manifest → Fire fail → Rollback undoes manifest, analog to #554 (c)-branch shape** - [x] Cutter.Fire.CreateReleaseRequest.Draft additive-optional field: existing #554 tests remain byte-unchanged — **surface: CI 8/8 green including all #554 tests** - [x] Unit tests + equivalence-harness cases green vs `draft-release.sh` byte-oracle (tag+publish half) — **surface: both bash-spawning equivalence harnesses in the 17 pkgs green independently (Surveyor confirmed)** - [x] Manifest-atomic half harnessed against manifest-precheck.sh + workflow-jq — **surface: property test coverage (bash oracle can't cover; strengthening implies own harness, per #556 pattern)** - [x] `go vet ./...` + `golangci-lint run` clean — **surface: CI gate confirms** **Both ratifications built** (bus 361e → 2c08): Axis 1=A (Fire wiring, milestone-direction-sanctioned); Axis 2=(i) (manifest file-atomic in Cutter txn; git-commit+push stays workflow). **Surveyor's should-consider fold** (review 4612): Fire's `existing.TargetCommitish != "" &&` diverged from bash `draft-release.sh:249` unconditional compare. Zero-reachability today but real faithfulness bug — Surveyor empirically resolved via live prod-releases GET showing `target_commitish` is always 40-hex SHA (so legit replay never false-conflicts). Engineer folded the fix at b215acf; Surveyor re-verified the delta. This is exactly what #557's equivalence-harness exists to surface — first prod caller of Fire made a latent divergence live + caught. **Refinement accounting: 3/5** (Surveyor's framing adopted, bus 1437→4334): 1. Request.Composed additive-optional (#556) 2. Deps.Manifest permitted nil (#556) 3. Fire.CreateReleaseRequest.Draft + Fire draft-idempotency + Transaction.Replayed() accessor + PrepareForPublish (#557 — all folded under Fire-wiring capability scope; accessors surface pre-computed state, not new capability) **Threshold revisit at 5+ capability accretions**: 3/5 with two accretions of headroom. **Composite-milestone-gate design vindicated (twice more)**: - #556 harness caught #532 + #554 latent bugs - #557 harness caught Fire's empty-target lenient divergence from bash oracle - Both exactly what the AC-restate deferral chose: property gate at substrate + harness gate at consumer surfaces defects each alone can't see **Phase 6 status**: 3/6 sub-trackers merged (#554 Cutter foundation + #556 rt-prep + #557 rt-release). Remaining: #555 rt-decide (pure-decision, parallelizable), #558 rt-manifest-check (5 checks), #559 rt-prune-rc-tags (exercises Fire replay). #499 stays open (folds into Cutter, closes at Phase 6 gate). Closed by Bosun 2026-07-27 05:14 CEST per Engineer's Refs-only flag in PR body.
bosun closed this issue 2026-07-27 05:14:48 +02:00
Sign in to join this conversation.
No project
No assignees
1 participant
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#557
No description provided.