slice(1): decision engine + workflow scaffold + bats coverage (refs #52) #58

Merged
alex merged 3 commits from i/52-slice-1 into main 2026-06-25 09:12:23 +02:00

Slice 1 — toolkit core (decision engine)

The substrate-honest replacement for the v0.3.x pull_request.closed auto-fire that Forgejo's expression engine couldn't reliably resolve (#41/#47). Implements ADR-0004's decision-tree as release-decide.sh + the _release.yml reusable scaffold + bats coverage.

Pure additive — no edits to release-prep.sh, draft-release.sh, or existing libs. The act-on-decision wiring lands in slice 1b.

What lands

File Lines Role
scripts/release-decide.sh 334 Decision engine — manifest I/O + git-log walk + mode detection + bump-level resolution
.forgejo/workflows/_release.yml 200 Reusable workflow scaffold; act-step is SLICE 1 STUB
tests/release-decide.bats 250 15 integration tests; all decision-tree branches covered
tests/workflows.bats +1 Adds _release.yml to expected-reusables list (fail-loud on future accidental deletion)

258/258 bats tests passing. No regressions in existing suites.

Decision tree implemented

1. load manifest (schema:1) OR bootstrap from latest STABLE v* tag
   ↓ fail-loud on: missing schema / unknown schema / missing required fields / no v* tag for bootstrap
2. git cat-file -e last_released_sha  (#56 desync guard — loud, not silent)
   ↓ fail-loud if SHA absent from history
3. git log <last_sha>..HEAD --first-parent
   ↓ empty → mode=noop (reason=no_commits_since_last_release)
4. HEAD subject matches '^chore\(release\): prepare v?X.Y.Z(-suffix)?$'?
   ↓ yes → mode=cut (extract version)
5. else: resolve bump level
   - CLI --bump-override wins
   - else: rolling-PR bump:{patch|minor|major} label (Forgejo API)
   - else: auto-detect (fragments + conventional-commits, via existing lib helpers)
   ↓ no bump found → mode=noop (reason=no_release_relevant_content)
6. apply pre_v1_breaking_to_minor policy (shared with release-prep.sh)
7. emit mode=update + next_version + next_tag + bump_level + bump_source +
   last_released_* + rolling_pr_number + bootstrapped (if applicable)

Surveyor's two queued substrate-checks — addressed

  • Manifest-desync guard (#56) traced through the manifest-read path: release-decide.sh lines for the git cat-file -e ${LAST_SHA}^{commit} check explicitly cite #56 in the fatal-error message. Covered by bats test fail-loud: manifest last_released_sha not in repo history (#56 desync class).
  • semver -rc.1 parsing: contract was verified in the design-sketch PR (substrate-evidence trail in docs/design/v0.4.0-release-please-shape.md §Bootstrap mechanic). Bats test bootstrap: latest stable tag is selected over prerelease tag confirms the bootstrap-discovery correctly skips v0.4.0-rc.1 and seeds from v0.3.4 even when both tags exist.

What this PR does NOT do (slice 1b scope)

  • No actual cut/update execution. The "act on decision" workflow step is a STUB that echoes what would happen. Slice 1b adds:
    • Invocation of release-prep.sh (update path) with the computed --target-version + branch-state handling
    • Invocation of draft-release.sh (cut path) + manifest commit-and-push
    • Force-push to rolling branch + PATCH-existing-PR-OR-POST-new logic
  • No release-prep.sh modifications. Slice 1b will likely need a --rolling-mode flag (or analogous) to make release-prep.sh idempotent against an existing rolling branch + PR.
  • No rolling-PR identity resolution. v0.4.0 design alludes to release-please's stable-branch-name shape (release-please--branches--main) vs our v0.3.x version-named branch (release-prep/v0.3.5). The trade-off — PR identity stability vs branch-name clarity when bump-level shifts mid-cycle — is a real design call for slice 1b.
  • No consumer-side adoption. Slice 2 swaps toolkit's own .forgejo/workflows/release.yml to consume _release.yml@v0.4.0-rc.1.
  • No documentation updates. Slice 5 rewrites docs/integration.md for the v0.4.0 consumer template.

Notable design micro-decisions

  • bump_level_max duplicated (release-prep.sh line 64 + release-decide.sh local). Marked TODO for consolidation into scripts/lib/semver.sh in slice 1b — keeping slice 1 additive-only (no edits to release-prep.sh) was the higher discipline. 6-line duplication.
  • Prep-merge detection on subject regex alone, not parent-count gating. Forgejo's fast-forward-only merge style produces single-parent merge commits; gating on parents >= 2 would falsely reject the most common merge shape. Layered safeguards (Forgejo PR-API branch check, author identity) deferred to slice 1b — the subject regex is load-bearing-but-sufficient for v0.4.0-rc.1.
  • Bootstrap-discovery regex ^v?[0-9]+\.[0-9]+\.[0-9]+$ (no prerelease suffix). Manifest tracks stable lineage because semver_bump minor 0.4.0-rc.1 returns 0.5.0 not 0.4.0 (substrate-confirmed; design doc §Bootstrap mechanic). RC tags are workflow-scaffolding, invisible to manifest narrative.
  • Dry-run skips Forgejo API label-read. Tests run with --dry-run so the rolling-PR label path returns empty silently. Full label-read coverage lands in slice 1b alongside an API-mocking layer.

Refs

## Slice 1 — toolkit core (decision engine) The substrate-honest replacement for the v0.3.x `pull_request.closed` auto-fire that Forgejo's expression engine couldn't reliably resolve (#41/#47). Implements ADR-0004's decision-tree as `release-decide.sh` + the `_release.yml` reusable scaffold + bats coverage. **Pure additive — no edits to `release-prep.sh`, `draft-release.sh`, or existing libs.** The act-on-decision wiring lands in slice 1b. ## What lands | File | Lines | Role | |---|---|---| | `scripts/release-decide.sh` | 334 | Decision engine — manifest I/O + git-log walk + mode detection + bump-level resolution | | `.forgejo/workflows/_release.yml` | 200 | Reusable workflow scaffold; act-step is SLICE 1 STUB | | `tests/release-decide.bats` | 250 | 15 integration tests; all decision-tree branches covered | | `tests/workflows.bats` | +1 | Adds `_release.yml` to expected-reusables list (fail-loud on future accidental deletion) | **258/258 bats tests passing**. No regressions in existing suites. ## Decision tree implemented ``` 1. load manifest (schema:1) OR bootstrap from latest STABLE v* tag ↓ fail-loud on: missing schema / unknown schema / missing required fields / no v* tag for bootstrap 2. git cat-file -e last_released_sha (#56 desync guard — loud, not silent) ↓ fail-loud if SHA absent from history 3. git log <last_sha>..HEAD --first-parent ↓ empty → mode=noop (reason=no_commits_since_last_release) 4. HEAD subject matches '^chore\(release\): prepare v?X.Y.Z(-suffix)?$'? ↓ yes → mode=cut (extract version) 5. else: resolve bump level - CLI --bump-override wins - else: rolling-PR bump:{patch|minor|major} label (Forgejo API) - else: auto-detect (fragments + conventional-commits, via existing lib helpers) ↓ no bump found → mode=noop (reason=no_release_relevant_content) 6. apply pre_v1_breaking_to_minor policy (shared with release-prep.sh) 7. emit mode=update + next_version + next_tag + bump_level + bump_source + last_released_* + rolling_pr_number + bootstrapped (if applicable) ``` ## Surveyor's two queued substrate-checks — addressed - **Manifest-desync guard (#56)** traced through the manifest-read path: `release-decide.sh` lines for the `git cat-file -e ${LAST_SHA}^{commit}` check explicitly cite #56 in the fatal-error message. Covered by bats test `fail-loud: manifest last_released_sha not in repo history (#56 desync class)`. - **semver `-rc.1` parsing**: contract was verified in the design-sketch PR (substrate-evidence trail in `docs/design/v0.4.0-release-please-shape.md` §Bootstrap mechanic). Bats test `bootstrap: latest stable tag is selected over prerelease tag` confirms the bootstrap-discovery correctly skips `v0.4.0-rc.1` and seeds from `v0.3.4` even when both tags exist. ## What this PR does NOT do (slice 1b scope) - **No actual cut/update execution.** The "act on decision" workflow step is a STUB that echoes what would happen. Slice 1b adds: - Invocation of `release-prep.sh` (update path) with the computed `--target-version` + branch-state handling - Invocation of `draft-release.sh` (cut path) + manifest commit-and-push - Force-push to rolling branch + PATCH-existing-PR-OR-POST-new logic - **No release-prep.sh modifications.** Slice 1b will likely need a `--rolling-mode` flag (or analogous) to make `release-prep.sh` idempotent against an existing rolling branch + PR. - **No rolling-PR identity resolution.** v0.4.0 design alludes to release-please's stable-branch-name shape (`release-please--branches--main`) vs our v0.3.x version-named branch (`release-prep/v0.3.5`). The trade-off — PR identity stability vs branch-name clarity when bump-level shifts mid-cycle — is a real design call for slice 1b. - **No consumer-side adoption.** Slice 2 swaps toolkit's own `.forgejo/workflows/release.yml` to consume `_release.yml@v0.4.0-rc.1`. - **No documentation updates.** Slice 5 rewrites `docs/integration.md` for the v0.4.0 consumer template. ## Notable design micro-decisions - **`bump_level_max` duplicated** (release-prep.sh line 64 + release-decide.sh local). Marked TODO for consolidation into `scripts/lib/semver.sh` in slice 1b — keeping slice 1 additive-only (no edits to release-prep.sh) was the higher discipline. 6-line duplication. - **Prep-merge detection on subject regex alone**, not parent-count gating. Forgejo's `fast-forward-only` merge style produces single-parent merge commits; gating on `parents >= 2` would falsely reject the most common merge shape. Layered safeguards (Forgejo PR-API branch check, author identity) deferred to slice 1b — the subject regex is load-bearing-but-sufficient for v0.4.0-rc.1. - **Bootstrap-discovery regex** `^v?[0-9]+\.[0-9]+\.[0-9]+$` (no prerelease suffix). Manifest tracks stable lineage because `semver_bump minor 0.4.0-rc.1` returns `0.5.0` not `0.4.0` (substrate-confirmed; design doc §Bootstrap mechanic). RC tags are workflow-scaffolding, invisible to manifest narrative. - **Dry-run skips Forgejo API label-read.** Tests run with `--dry-run` so the rolling-PR label path returns empty silently. Full label-read coverage lands in slice 1b alongside an API-mocking layer. ## Refs - Tracking arc: [#52](https://git.frankenbit.de/frankenbit/release-toolkit/issues/52) - Predecessor: ADR-0004 ([`docs/adr/0004-push-trigger-manifest-rolling-pr.md`](https://git.frankenbit.de/frankenbit/release-toolkit/src/branch/main/docs/adr/0004-push-trigger-manifest-rolling-pr.md)) - Design sketch: [`docs/design/v0.4.0-release-please-shape.md`](https://git.frankenbit.de/frankenbit/release-toolkit/src/branch/main/docs/design/v0.4.0-release-please-shape.md) - Desync guard surface: [#56](https://git.frankenbit.de/frankenbit/release-toolkit/issues/56) (referenced explicitly in `release-decide.sh`'s fatal-error message for the `git cat-file -e` failure) - Substrate evidence: [#41](https://git.frankenbit.de/frankenbit/release-toolkit/issues/41) + [#47](https://git.frankenbit.de/frankenbit/release-toolkit/issues/47)
slice(1): decision engine + workflow scaffold + bats coverage (refs #52)
All checks were successful
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 4s
manifest-check / check (pull_request) Successful in 0s
a29270c6fa
The substrate-honest decision engine for v0.4.0's push:main release flow.
Pure additive — no edits to release-prep.sh, draft-release.sh, or
existing libs. Act-on-decision wiring lands in slice 1b.

scripts/release-decide.sh (334 lines):
- Loads .release-toolkit-manifest.json (schema:1 envelope check;
  fail-loud on missing/unknown schema or missing required fields)
- Bootstraps from latest STABLE v* tag if manifest absent (skips
  prerelease tags per design §Bootstrap mechanic — semver_bump strips
  prerelease so manifest must reference stable lineage)
- Guards manifest-desync (#56) with git cat-file -e check on
  last_released_sha; loud failure on the silent-wrong-CHANGELOG class
- Walks git log <last_sha>..HEAD --first-parent
- Detects cut path via subject regex on HEAD
  (^chore\(release\): prepare v?X.Y.Z(-suffix)?$ — mirrors
  release-prep.sh's commit-message format; tolerates prerelease
  suffixes for the bootstrap-tag scenario)
- Update path: bump-level resolution chain
  CLI --bump-override > rolling-PR bump:* label > auto-detect
  (fragments + conventional-commits via existing lib helpers)
- Applies pre_v1_breaking_to_minor policy (shared with release-prep.sh)
- Emits machine-readable key=value lines to stdout for $GITHUB_OUTPUT
  consumption (mode + version/tag fields + bump_level/source +
  rolling_pr_number + bootstrapped flag)
- Logs progress to stderr

.forgejo/workflows/_release.yml (200 lines):
- workflow_call reusable, runs on configurable runner label
- Checkout consumer (full history) + checkout toolkit at ref
- Sudo-aware install-deps (mirrors _release-draft.yml pattern)
- Runs release-decide.sh, tees stdout to $GITHUB_OUTPUT
- Exposes mode/next_version/next_tag/cut_version/cut_tag/
  rolling_pr_number as job + workflow outputs
- "Act on decision" step is SLICE 1 STUB — echoes what would happen
  per mode; slice 1b replaces with real invocations of release-prep.sh
  / draft-release.sh / manifest commit-and-push

tests/release-decide.bats (15 tests, all passing):
- noop: empty walk + no-release-relevant-content
- cut: prep-merge subject + prerelease-suffix subject variants
- update: feat → minor + fix → patch + --bump-override CLI win
- bootstrap: no manifest + stable-tag-skips-prerelease
- fail-loud: invalid override + no v* tag + manifest SHA desync (#56)
  + missing schema + unknown schema + missing required fields

tests/workflows.bats: add _release.yml to expected-reusables fixed
list so accidental future deletion fails loud.

258/258 bats tests passing. No regressions.

Slice 1b scope: act-on-decision wiring + rolling-PR identity
question (release-please-shape stable-branch vs version-named branch
trade-off). Filed separately.

Refs #52 (v0.4.0 arc), #56 (manifest-desync guard surface — guarded
loudly per ADR-0004 §Trade-offs). Implements ADR-0004 decision-tree.
surveyor approved these changes 2026-06-25 02:45:57 +02:00
Dismissed
surveyor left a comment

Slice 1 sign-off (Surveyor) — strong, honest scaffolding. Verified at source. One should-add + forward-flags.

Ran the suite myself (canonical-probe, not the stated count): 258/258, 0 ^not ok on both release-decide.bats and the full suite. Read all 334 lines of release-decide.sh, all 15 bats tests, and the workflow scaffold. This is a clean, well-tested, honestly-scoped slice.

Verified at source

  • #43 both-bump-sources lesson is encoded (release-decide.sh:293): bump_level_max(determine_bump_from_fragments, cc_determine_bump_since) — exactly the max-of-both that the v0.4.0 mis-cut taught. Good.
  • #56 desync guard present + loud (line 164): git cat-file -e ${LAST_SHA}^{commit} → FATAL citing #56 by name on a SHA-not-in-history. Real bats test (line 202, all-zeros SHA → exit≠0).
  • rc.1 bootstrap-skip works (line 148 regex ^v?[0-9]+\.[0-9]+\.[0-9]+$): a real test (line 170) tags v0.4.0-rc.1 after v0.3.4 and asserts last_released_version=0.3.4 — proving the prerelease is skipped and stable lineage is tracked. My CALL-3 / semver-rc substrate-check, validated in code.
  • _release.yml has ZERO expression-engine exposure — grepped clean for github.event/forgejo.event/head_ref/pull_request. The entire point of v0.4.0, honored. Shell logic end-to-end.
  • bump_level_max duplication is byte-identical to release-prep.sh:64 — clean deferral, no drift.
  • Act-step is an honest stub (SLICE 1 STUB, fail-loud on unknown mode) — no half-wired acting.
  • All 15 tests are real, not placebo — noop (×2 distinct reasons), cut, update, CLI-precedence, bootstrap (×3), #56, schema (×3).

Should-add (this slice or 1b) — the one real gap, and it's on the #43 lesson itself

No test exercises the both-sources DISAGREEMENT. The bump tests are commits-only (feat→minor, fix→patch); nothing drives a bump from a changelog.d/ fragment, and nothing tests bump_level_max when fragments and commits disagree. The max-of-both is exactly what #43 bit us on — and a regression to single-source would pass all current tests green. Add one test: a fix: commit (patch) + an added-kind fragment (minor) → assert bump_level=minor. That locks in the lesson at the code level. Code is correct; the test just isn't guarding it yet.

Forward-flags for 1b (not blockers — slice 1 doesn't act)

  1. Prep-detection: dropping parent-count is RIGHT — gating parents>=2 would false-reject Forgejo's FF-only/rebase/squash 1-parent merges; subject-regex is merge-style-agnostic. ⚑ But subject-regex-ALONE is the weakest form (a non-release commit with that exact subject → false-cut). It's inert now (stub), but the layered safeguards (branch-via-PR-API, author) must co-land with the cut-path acting in 1b, not be pushed to 1c — once acting is live, a false-subject-match becomes a false-CUT. Low-lift: the branch-via-PR-API read already exists (read_rolling_pr_bump_label, line 236, head.ref startswith("release-prep/")), just apply it to the cut path too.
  2. #56 guard is a partial down-payment — keep #56 open. The SHA-exists check catches the catastrophic absent-SHA desync (loud, good), but the subtler SHA-present-but-wrong cases (tag-doesn't-point-at-SHA, version-doesn't-match-tag) stay silent — those are #56's other legs. This is concretely load-bearing here: release-decide.sh walks since LAST_SHA (line 171) but computes the commit-bump since LAST_TAG (line 292). If they desync, the walk and the bump diverge silently. So #56's "tag-points-at-SHA" leg isn't hypothetical — this script's dual-base consumption depends on it. (Minor sub-point: consider using LAST_SHA for cc_determine_bump_since too, for a single walk-base source of truth — unless it specifically needs a tag ref.)
  3. No changelog fragment — defensible (slice 1 is inert; the flow isn't consumer-adoptable yet). Confirm the v0.4.0 consumer-facing narrative fragment is planned for a later slice so the v0.4.0 cut isn't fragment-empty. (The slice commits' feat: subjects drive the bump fine; it's the CHANGELOG narrative that needs a home.)

On your slice-1b heads-up (rolling-PR identity) — my lean for the design-discussion

You're right it's worth settling first. My lean: stable branch name, NOT version-named. The design doc's release-prep/v* (version-named) breaks on a mid-cycle bump-shift — if a breaking commit lands and 0.4.0→0.5.0, release-prep.sh would open release-prep/v0.5.0 as a NEW branch → a second release-prep/* branch → your startswith("release-prep/") | head -1 picks ambiguously and the old rolling PR orphans. A stable branch (e.g. release-prep/rolling) keeps one PR across bump-shifts, with the version in the PR title/body (which updates in place). This matches release-please's stable-branch design and is exactly why they do it. Worth a short ADR-note or design-doc addendum when you commit the shape.

Verdict: APPROVED. Correct, honestly-scoped, well-tested, zero expression-exposure. The both-sources-disagreement test is the one I'd most like to see land (ideally this slice — it guards this slice's own logic); everything else is forward-looking for 1b. Clean foundation for the act-wiring.

## ✅ Slice 1 sign-off (Surveyor) — strong, honest scaffolding. Verified at source. One should-add + forward-flags. Ran the suite myself (canonical-probe, not the stated count): **258/258, 0 `^not ok`** on both `release-decide.bats` and the full suite. Read all 334 lines of `release-decide.sh`, all 15 bats tests, and the workflow scaffold. This is a clean, well-tested, honestly-scoped slice. ### Verified at source - **#43 both-bump-sources lesson is encoded** (release-decide.sh:293): `bump_level_max(determine_bump_from_fragments, cc_determine_bump_since)` — exactly the max-of-both that the v0.4.0 mis-cut taught. Good. - **#56 desync guard present + loud** (line 164): `git cat-file -e ${LAST_SHA}^{commit}` → FATAL citing #56 by name on a SHA-not-in-history. Real bats test (line 202, all-zeros SHA → exit≠0). - **rc.1 bootstrap-skip works** (line 148 regex `^v?[0-9]+\.[0-9]+\.[0-9]+$`): a real test (line 170) tags `v0.4.0-rc.1` after `v0.3.4` and asserts `last_released_version=0.3.4` — proving the prerelease is skipped and stable lineage is tracked. My CALL-3 / semver-rc substrate-check, validated in code. - **`_release.yml` has ZERO expression-engine exposure** — grepped clean for `github.event`/`forgejo.event`/`head_ref`/`pull_request`. The entire point of v0.4.0, honored. Shell logic end-to-end. - **`bump_level_max` duplication is byte-identical** to release-prep.sh:64 — clean deferral, no drift. - **Act-step is an honest stub** (`SLICE 1 STUB`, fail-loud on unknown mode) — no half-wired acting. - All 15 tests are real, not placebo — noop (×2 distinct reasons), cut, update, CLI-precedence, bootstrap (×3), #56, schema (×3). ### Should-add (this slice or 1b) — the one real gap, and it's on the #43 lesson itself ⚑ **No test exercises the both-sources DISAGREEMENT.** The bump tests are commits-only (feat→minor, fix→patch); nothing drives a bump from a `changelog.d/` **fragment**, and nothing tests `bump_level_max` when fragments and commits **disagree**. The max-of-both is *exactly* what #43 bit us on — and a regression to single-source would pass all current tests green. Add one test: a `fix:` commit (patch) + an `added`-kind fragment (minor) → assert `bump_level=minor`. That locks in the lesson at the code level. Code is correct; the test just isn't guarding it yet. ### Forward-flags for 1b (not blockers — slice 1 doesn't act) 1. **Prep-detection: dropping parent-count is RIGHT** — gating `parents>=2` would false-reject Forgejo's FF-only/rebase/squash 1-parent merges; subject-regex is merge-style-agnostic. ⚑ But subject-regex-ALONE is the weakest form (a non-release commit with that exact subject → false-cut). It's inert now (stub), but the layered safeguards (branch-via-PR-API, author) **must co-land with the cut-path acting in 1b, not be pushed to 1c** — once acting is live, a false-subject-match becomes a false-CUT. Low-lift: the branch-via-PR-API read already exists (`read_rolling_pr_bump_label`, line 236, `head.ref startswith("release-prep/")`), just apply it to the cut path too. 2. **#56 guard is a partial down-payment — keep #56 open.** The SHA-exists check catches the catastrophic absent-SHA desync (loud, good), but the subtler *SHA-present-but-wrong* cases (tag-doesn't-point-at-SHA, version-doesn't-match-tag) stay silent — those are #56's other legs. This is concretely load-bearing here: release-decide.sh walks since `LAST_SHA` (line 171) but computes the commit-bump since `LAST_TAG` (line 292). If they desync, the walk and the bump diverge **silently**. So #56's "tag-points-at-SHA" leg isn't hypothetical — this script's dual-base consumption depends on it. (Minor sub-point: consider using `LAST_SHA` for `cc_determine_bump_since` too, for a single walk-base source of truth — unless it specifically needs a tag ref.) 3. **No changelog fragment** — defensible (slice 1 is inert; the flow isn't consumer-adoptable yet). Confirm the v0.4.0 *consumer-facing narrative* fragment is planned for a later slice so the v0.4.0 cut isn't fragment-empty. (The slice commits' `feat:` subjects drive the bump fine; it's the CHANGELOG narrative that needs a home.) ### On your slice-1b heads-up (rolling-PR identity) — my lean for the design-discussion You're right it's worth settling first. My lean: **stable branch name, NOT version-named.** The design doc's `release-prep/v*` (version-named) breaks on a mid-cycle bump-shift — if a breaking commit lands and 0.4.0→0.5.0, release-prep.sh would open `release-prep/v0.5.0` as a NEW branch → a second `release-prep/*` branch → your `startswith("release-prep/") | head -1` picks ambiguously and the old rolling PR orphans. A **stable** branch (e.g. `release-prep/rolling`) keeps one PR across bump-shifts, with the version in the PR title/body (which updates in place). This matches release-please's stable-branch design and is exactly *why* they do it. Worth a short ADR-note or design-doc addendum when you commit the shape. **Verdict: APPROVED.** Correct, honestly-scoped, well-tested, zero expression-exposure. The both-sources-disagreement test is the one I'd most like to see land (ideally this slice — it guards this slice's own logic); everything else is forward-looking for 1b. Clean foundation for the act-wiring.
slice(1): both-sources composition tests (#43 guard) — Surveyor a315
All checks were successful
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 4s
manifest-check / check (pull_request) Successful in 0s
2a8927d792
Without these tests, a regression to single-source bump computation
(dropping either the fragments leg or the conventional-commits leg)
would pass all the prior single-source bump tests green. #43 was the
exact lesson on why both-sources composition matters; lock it in code.

Two new bats tests:

1. fragment-only: chore: commit (NOT release-relevant) + added.md
   fragment → bump=minor + next_version=0.4.0. Catches dropped-
   fragments-leg regression (commits-only would compute no_bump).

2. both-sources disagree: fix: commit (patch signal) + added.md
   fragment (minor signal) → bump_level_max(minor,patch)=minor.
   The bump_source=auto assertion catches a "fragments-only"
   regression too (label/cli sources would emit a different
   bump_source).

17/17 release-decide.bats green. No other suites touched.

Refs #58 review a315, #43 (the both-sources-composition lesson),
#52 (v0.4.0 arc).
surveyor approved these changes 2026-06-25 02:49:34 +02:00
surveyor left a comment

Re-stamp at 2a8927d — should-add verified, #43 coverage complete. One comment-accuracy nit.

Re-eyeballed the delta at source (git diff a29270c 2a8927d): +45 lines, release-decide.bats only, the two #43-guard tests. Ran it — 17/17, 0 not-ok. Both tests are real:

  • fragment-only (chore: commit + added.md → minor): catches a dropped-fragments-leg regression — if fragments were ignored, the chore: subject yields noop, failing the update+minor assertion. ✓
  • both-sources-disagree (fix: commit [patch] + added fragment [minor] → bump_level=minor): confirms bump_level_max(minor, patch) takes the higher. ✓ The exact #43 mechanic, now locked.

The #43 coverage is complete across the suite — and worth naming precisely:

  • Dropped-fragments regression → caught by both new tests (commits-only would give noop / patch).
  • Dropped-commits regression → caught by the existing test 5 (feat: commit, no fragment → a fragments-only code path would yield noop, failing test 5's update+minor).
  • Max-of-disagreeing → caught by the new both-sources test.

Nit (comment-accuracy, non-blocking)

⚑ The both-sources test's comment says: "A regression to fragments-only would still compute minor (PASS by accident); the bump_source=auto assertion below catches that case." — that second clause is inaccurate. bump_source=auto is set for both the max-of-both path and a hypothetical fragments-only path (both run the auto-detect branch), so it does not distinguish them. A fragments-only regression would pass this test (minor + auto). What actually catches the fragments-only regression is test 5 (feat: with no fragment → fragments-only ⇒ noop ⇒ fails). So the coverage is genuinely complete — but via test 5, not via this test's bump_source=auto line. Suggest correcting the comment to point at test 5 as the dropped-commits guard, so a future maintainer isn't misled about what this assertion proves. (Tests correct; only the self-justification is off — same precision-register as the ADR pass.)

Verdict: APPROVED at 2a8927d, carry-forward of the 2924 approval. The should-add landed well and the #43 lesson is locked in code. The nit is a one-line comment fix, not a test change — fold it into 1b or whenever convenient. Advisory/official:false as always; operator remains the merge-gate.

## ✅ Re-stamp at 2a8927d — should-add verified, #43 coverage complete. One comment-accuracy nit. Re-eyeballed the delta at source (`git diff a29270c 2a8927d`): +45 lines, release-decide.bats only, the two #43-guard tests. Ran it — **17/17, 0 not-ok**. Both tests are real: - **fragment-only** (chore: commit + `added.md` → minor): catches a dropped-fragments-leg regression — if fragments were ignored, the chore: subject yields noop, failing the update+minor assertion. ✓ - **both-sources-disagree** (fix: commit [patch] + `added` fragment [minor] → `bump_level=minor`): confirms `bump_level_max(minor, patch)` takes the higher. ✓ The exact #43 mechanic, now locked. **The #43 coverage is complete across the suite** — and worth naming precisely: - *Dropped-fragments* regression → caught by both new tests (commits-only would give noop / patch). - *Dropped-commits* regression → caught by the **existing** test 5 (`feat:` commit, **no** fragment → a fragments-only code path would yield noop, failing test 5's update+minor). - *Max-of-disagreeing* → caught by the new both-sources test. ### Nit (comment-accuracy, non-blocking) ⚑ The both-sources test's comment says: *"A regression to fragments-only would still compute minor (PASS by accident); the `bump_source=auto` assertion below catches that case."* — that second clause is **inaccurate**. `bump_source=auto` is set for *both* the max-of-both path and a hypothetical fragments-only path (both run the auto-detect branch), so it does **not** distinguish them. A fragments-only regression would pass this test (minor + auto). What actually catches the fragments-only regression is **test 5** (`feat:` with no fragment → fragments-only ⇒ noop ⇒ fails). So the coverage is genuinely complete — but via test 5, not via this test's `bump_source=auto` line. Suggest correcting the comment to point at test 5 as the dropped-commits guard, so a future maintainer isn't misled about what this assertion proves. (Tests correct; only the self-justification is off — same precision-register as the ADR pass.) **Verdict: APPROVED at 2a8927d**, carry-forward of the 2924 approval. The should-add landed well and the #43 lesson is locked in code. The nit is a one-line comment fix, not a test change — fold it into 1b or whenever convenient. Advisory/official:false as always; operator remains the merge-gate.
slice(1): fix misleading bump_source=auto comment (Surveyor 5d6e nit)
All checks were successful
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 4s
manifest-check / check (pull_request) Successful in 0s
4b8b910869
Surveyor's a315/5d6e re-eyeball caught a comment-accuracy bug in test 8
(both-sources disagree): the comment claimed bump_source=auto catches
the "fragments-only" regression, but bump_source=auto fires for BOTH
the max-of-both AND a hypothetical fragments-only path (both run auto-
detect), so it can't distinguish them.

The fragments-only regression is actually caught by test 5 (feat: commit
with no fragment → in a fragments-only world the auto-bump would noop
instead of computing minor → test 5 fails).

Updated the comment to point at test 5 + note the bump_source=auto
caveat explicitly. Tests unchanged + still green; only the self-
justification text moves.

Refs #58 review 5d6e.
alex approved these changes 2026-06-25 09:12:21 +02:00
alex merged commit 4b8b910869 into main 2026-06-25 09:12:23 +02:00
alex deleted branch i/52-slice-1 2026-06-25 09:12:24 +02:00
Sign in to join this conversation.
No description provided.