feat(release): implement Cutter transactional cut + fold #499 fail-atomic (ADR-0009 §6) #554

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

Implement internal/release.Cutter transactional cut per ADR-0009 §6 (folds #499 fail-atomic design).

Motivation

Phase 6 core substrate. internal/release.Cutter is the transactional engine underlying all cut orchestrators (rt decide, rt prep, rt release). Per ADR-0009 §6, #499 fail-atomic lands here as first-class transactional design (not a follow-up).

Scope

  • internal/release/*.go — Cutter implementing #505 interface with §6 transactional invariant
  • #499 fail-atomic: two-phase commit (reversible prefix + irreversible suffix + idempotent replay)
  • Fragment-consumption atomicity: cut either fully consumes fragments OR fully aborts; no orphan state
  • Overlapping-cut concurrency guard (per #499)
  • TDD: unit tests + property invariant (transactional); harness coverage at consumer level (see AC restate note)

AC

  • internal/release.Cutter implements #505 interface + §6 transactional design
  • #499 fail-atomic property invariant test green (fragment-consumption atomicity; no orphan on failure)
  • Concurrency guard tested (overlapping cuts detected + rejected)
  • Unit tests + §4 property invariants green; equivalence-harness coverage lands with #556/#557 consumers (see AC restate note)
  • go vet ./... + golangci-lint run clean

AC restate note (2026-07-27, framing-verify surface)

Engineer's framing-verify (bus 6464) surfaced that the original AC bullet "unit tests + equivalence-harness cases green" is not satisfiable at the #554 level, because the bash pipeline's topology Y — release-prep.sh (seal) in workflow run N and draft-release.sh (tag+publish) in a separate workflow run M across a PR merge — means no single bash script exists that does what the Cutter does in one transaction. That non-atomicity IS #499; the Cutter's ADR-0009 §6 design opportunity is to UNIFY them.

Consequence: no #554-local bash oracle to diff against. The milestone-level bats-oracle gate is satisfied by consumers #556 (rt prep → release-prep.bats) and #557 (rt release → draft-release.bats), both of which DO have bash oracles.

Precedent: Phase 5 internal/selfboot.CommitBake (a library, not a subcommand) was gated by tree-SHA property invariant, not subcommand-diff. Same shape.

Design decisions folded into #554 (ratified 2026-07-27):

  1. Prefix = working-tree-only. No git commit/push inside Cutter's prefix; that stays in rt prep (#556). Isolates the CommitBake ambient-identity carry-forward to the consumer; Cutter git-side-effect-free in prefix; rollback = file restore.
  2. Fire = CreateReleaseDraft(Target=expectedHead) with idempotent-replay via GetReleaseByTag + manifest-compare (ErrReplayConflict on disagree / no-op on match).
  3. flock non-blocking (LOCK_EX|LOCK_NB) at construction, repo-root path → ErrConcurrentCut. Workflow-level concurrency deferred to Phase 7 verify (schema-hazard open Q, #499 remedy 1).
  4. Fragment transactionality: Cutter owns delete-on-fold (last prefix step); Rollback restores from Transaction.ConsumedFragments() in-memory. §4 exactly-one-of invariant.
  • Depends on: #505 (interfaces), #542 (manifest.Store atomic tmp+rename), #547 selfboot.CommitBake, #541 forgejo.Client
  • Folds: #499 (fail-atomic transactional cut design)
  • Downstream: rt decide/prep/release/manifest-check/prune-rc-tags orchestrators all use Cutter; equivalence-harness coverage from #556 + #557
  • Refs ADR-0009 §3.3 phase 6, §6 (fail-atomic design), §3.3 (interfaces refined as reality intrudes)

Filed 2026-07-27 by Bosun on operator continuation for Phase 6 dispatch. AC restated 2026-07-27 per Engineer framing-verify (bus 6464) + Bosun ratify.

Implement `internal/release.Cutter` transactional cut per ADR-0009 §6 (folds #499 fail-atomic design). ## Motivation Phase 6 core substrate. `internal/release.Cutter` is the transactional engine underlying all cut orchestrators (`rt decide`, `rt prep`, `rt release`). Per ADR-0009 §6, #499 fail-atomic lands here as first-class transactional design (not a follow-up). ## Scope - `internal/release/*.go` — Cutter implementing #505 interface with §6 transactional invariant - **#499 fail-atomic**: two-phase commit (reversible prefix + irreversible suffix + idempotent replay) - **Fragment-consumption atomicity**: cut either fully consumes fragments OR fully aborts; no orphan state - Overlapping-cut concurrency guard (per #499) - TDD: unit tests + property invariant (transactional); harness coverage at consumer level (see AC restate note) ## AC - [x] `internal/release.Cutter` implements #505 interface + §6 transactional design - [x] #499 fail-atomic property invariant test green (fragment-consumption atomicity; no orphan on failure) - [x] Concurrency guard tested (overlapping cuts detected + rejected) - [x] Unit tests + §4 property invariants green; equivalence-harness coverage lands with #556/#557 consumers (see AC restate note) - [x] `go vet ./...` + `golangci-lint run` clean ## AC restate note (2026-07-27, framing-verify surface) Engineer's framing-verify (bus 6464) surfaced that the original AC bullet *"unit tests + equivalence-harness cases green"* is not satisfiable at the #554 level, because the bash pipeline's topology Y — `release-prep.sh` (seal) in workflow run N and `draft-release.sh` (tag+publish) in a **separate** workflow run M across a PR merge — means **no single bash script exists** that does what the Cutter does in one transaction. That non-atomicity IS #499; the Cutter's ADR-0009 §6 design opportunity is to UNIFY them. Consequence: no #554-local bash oracle to diff against. The milestone-level bats-oracle gate is satisfied by consumers **#556 (rt prep → release-prep.bats)** and **#557 (rt release → draft-release.bats)**, both of which DO have bash oracles. Precedent: Phase 5 `internal/selfboot.CommitBake` (a library, not a subcommand) was gated by tree-SHA property invariant, not subcommand-diff. Same shape. **Design decisions folded into #554 (ratified 2026-07-27):** 1. **Prefix = working-tree-only.** No git commit/push inside Cutter's prefix; that stays in `rt prep` (#556). Isolates the CommitBake ambient-identity carry-forward to the consumer; Cutter git-side-effect-free in prefix; rollback = file restore. 2. **Fire = `CreateReleaseDraft(Target=expectedHead)` with idempotent-replay** via `GetReleaseByTag` + manifest-compare (`ErrReplayConflict` on disagree / no-op on match). 3. **flock non-blocking (`LOCK_EX|LOCK_NB`)** at construction, repo-root path → `ErrConcurrentCut`. Workflow-level concurrency deferred to Phase 7 verify (schema-hazard open Q, #499 remedy 1). 4. **Fragment transactionality:** Cutter owns delete-on-fold (last prefix step); `Rollback` restores from `Transaction.ConsumedFragments()` in-memory. §4 exactly-one-of invariant. ## Related - Depends on: #505 (interfaces), #542 (manifest.Store atomic tmp+rename), #547 selfboot.CommitBake, #541 forgejo.Client - Folds: #499 (fail-atomic transactional cut design) - Downstream: rt decide/prep/release/manifest-check/prune-rc-tags orchestrators all use Cutter; equivalence-harness coverage from #556 + #557 - Refs ADR-0009 §3.3 phase 6, §6 (fail-atomic design), §3.3 (interfaces refined as reality intrudes) Filed 2026-07-27 by Bosun on operator continuation for Phase 6 dispatch. AC restated 2026-07-27 per Engineer framing-verify (bus 6464) + Bosun ratify.
Owner

Complete — PR #560 merged @79e5947 (state=closed, merged=true, rebase; main HEAD = 79e5947, content-verified: restoreFragments-first-in-(c) + removeFragment seam + the new regression test all present on main).

All 5 ACs satisfied (re-derived from the merged substrate + CI 8/8 green):

  1. Cutter implements #505var _ Cutter = (*cutter)(nil) compile-assert; Prepare/Fire/Rollback prefix/suffix design.
  2. §4 fail-atomic property test green — transactionality_test.go: success + gate-fail + injected (b) manifest-fail + injected (c) partial-delete, all assert the exactly-one-of invariant.
  3. Concurrency guard — TestNewCutter_concurrentCut (second NewCutter on the same root → ErrConcurrentCut).
  4. Unit + §4 property green; equivalence-harness coverage deferred to #556/#557 per the AC-restate note.
  5. go vet + golangci-lint run clean (0 issues; re-verified in forgejo-ci-go:latest).

Review: one round — Surveyor 4606 caught a genuine §4 silent-loss on the (c) partial-delete rollback path (restored manifest+changelog but not fragments); fixed in the same PR (restoreFragments first in the (c) rollback) with a reproduce-red → fix → green closed loop, the injection now a permanent regression case. APPROVED 4607, independently verified.

#499 stays open — it folds into this Cutter as the ADR-0009 §6 design, and per the milestone it closes at the phase gate (when the orchestrators #555–559 land and the harness is green), not at this PR.

Closing #554 (implementation complete + merged). Downstream: #556 (rt prep) / #557 (rt release) consume this Cutter and carry the equivalence-harness coverage; #555 (rt decide) is pure-decision, not a Cutter consumer.

Complete — **PR #560 merged @`79e5947`** (state=closed, merged=true, rebase; main HEAD = `79e5947`, content-verified: `restoreFragments`-first-in-(c) + `removeFragment` seam + the new regression test all present on main). All 5 ACs satisfied (re-derived from the merged substrate + CI 8/8 green): 1. `Cutter` implements #505 — `var _ Cutter = (*cutter)(nil)` compile-assert; `Prepare`/`Fire`/`Rollback` prefix/suffix design. 2. §4 fail-atomic property test green — `transactionality_test.go`: success + gate-fail + injected `(b)` manifest-fail + injected `(c)` partial-delete, all assert the exactly-one-of invariant. 3. Concurrency guard — `TestNewCutter_concurrentCut` (second `NewCutter` on the same root → `ErrConcurrentCut`). 4. Unit + §4 property green; equivalence-harness coverage deferred to #556/#557 per the AC-restate note. 5. `go vet` + `golangci-lint run` clean (0 issues; re-verified in `forgejo-ci-go:latest`). **Review:** one round — Surveyor 4606 caught a genuine §4 silent-loss on the `(c)` partial-delete rollback path (restored manifest+changelog but not fragments); fixed in the same PR (`restoreFragments` first in the `(c)` rollback) with a reproduce-red → fix → green closed loop, the injection now a permanent regression case. APPROVED 4607, independently verified. **#499 stays open** — it folds into this Cutter as the ADR-0009 §6 design, and per the milestone it closes at the **phase gate** (when the orchestrators #555–559 land and the harness is green), not at this PR. Closing #554 (implementation complete + merged). Downstream: #556 (`rt prep`) / #557 (`rt release`) consume this Cutter and carry the equivalence-harness coverage; #555 (`rt decide`) is pure-decision, not a Cutter consumer.
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#554
No description provided.