slice(1b): act-on-decision wiring — --rolling-mode + 3-layer cut-safeguard + manifest commit-and-push (refs #52) #63

Merged
quartermaster merged 1 commit from i/52-slice-1b-impl into main 2026-06-25 17:05:47 +02:00

Slice 1b implementation — design-note PR #62 made real

Implements the slice 1b design-note (PR #62, merged ec3ecaa) end-to-end. The act on decision step in _release.yml was a SLICE 1 STUB; this PR replaces it with real invocations of release-prep.sh --rolling-mode (update path) and draft-release.sh + manifest commit-and-push (cut path).

Pure additive on existing scripts. v0.3.x behavior preserved verbatim when --rolling-mode is absent. 7 new bats tests; 267/267 suite green.

Pre-flight checks Surveyor named (1111) — addressed in-PR

Check Where to verify at source
Canonical-probe bats (full suite green) 267/267 — bats tests/ shows no not ok lines
Three-layer cut-safeguard wiring, esp. Layer-2-fail → mode=update safe-fallthrough scripts/release-decide.sh lines added at the cut-detection block: check_layer2_branch_source + check_layer3_author helpers, then the if-match block runs both layers and falls through to mode=update (with SAFEGUARD_FAIL set) when either FAILS. The mode=update emit at the end picks up SAFEGUARD_FAIL and emits safeguard_fail=layer{2,3} to stdout
--rolling-mode is truly additive (no v0.3.x change when flag absent) scripts/release-prep.sh — every --rolling-mode behavior is gated on [[ -n "$ROLLING_MODE" ]]. Default branches preserved verbatim. Bats test slice-1b: default mode (no --rolling-mode) preserves version-named branch is the regression guard
Manifest commit+push on cut path .forgejo/workflows/_release.yml — the cut) branch in act on decision step: bootstrap-aware jq (creates new manifest if absent, else PATCH last_released_* fields) writing to fixed .tmp sibling + atomic mv, then git commit + git push origin HEAD:main

Three design questions implemented

Q1 — Stable branch identity (release-prep/rolling)

  • --rolling-mode defaults branch to release-prep/rolling
  • --branch <NAME> override reserves namespace for future LTS
  • Default mode preserves version-named release-prep/$NEW_TAG (v0.3.x verbatim)

Q2 — --rolling-mode flips three behaviors

Behavior Default --rolling-mode
Branch creation git checkout -b git checkout -B (force-reset from $BASE_BRANCH)
Remote push git push origin BRANCH git push --force-with-lease origin BRANCH
Forgejo PR always POST probe by head ref → PATCH if exists, else POST

3 new helpers in lib/forgejo-api.sh:

  • forgejo_find_pr_by_head OWNER REPO HEAD_REF → PR number or empty
  • forgejo_update_pr OWNER REPO NUMBER TITLE BODY → PATCH
  • forgejo_find_pr_by_merge_sha OWNER REPO SHA → JSON of matching PR (used by Layer 2)

Q3 — Three-layer cut-safeguard

  • Layer 1: subject-regex match (already in slice 1; the if-guard)
  • Layer 2: branch-source-check via Forgejo PR API — passes if merged PR's head.ref == release-prep/rolling
  • Layer 3: author-identity check via new config_get_release_author — no-op when release_author unset in release-toolkit.yml (defense-in-depth; consumer opts in)

Each layer outputs pass / skip / fail. Any failSAFEGUARD_FAIL set, fall through to mode=update path. The mode=update emit block surfaces safeguard_fail=layer{2,3} as a workflow output so the log shows the fall-through reason.

Tests (7 new, 267 total)

tests/release-prep.bats — 4 new:

  • --rolling-mode → branch defaults to release-prep/rolling
  • --rolling-mode + --branch override → uses override
  • default mode preserves version-named branch (regression guard)
  • --rolling-mode logs force-reset semantic

tests/release-decide.bats — 3 new:

  • cut path emits layer2=skip + layer3=skip in dry-run (wiring verification)
  • Layer 1 + 2 + 3 log MATCH/PASS/SKIP framing (observability)
  • Layer 3 no-op when release_author unset (default config preserves existing behavior; new field opt-in)

What this PR does NOT do (deferred to follow-ups)

  • No API mock infrastructure for Layer 2/3 fail-path bats. The wiring is testable (skip-path covered); the fail-paths await an API-mock layer. Tracked by Surveyor as #64 (her hint: a function-stub of forgejo_find_pr_by_merge_sha could reach the fail-branch — lighter than full API mock infra).
  • No release_author migration guidance for v0.3.x consumers. Slice 5 docs update will cover.
  • No LTS / multi-version release branches. --branch override reserves the namespace; not exercised in v0.4.0.
  • No editor-preservation for rolling PR body. Matches release-please's behavior. ADR-0006 path-(a3) handles the operator-narrative axis separately (orthogonal axis per the design-note).
  • Layer 2 + Layer 3 each re-fetch the same PR via forgejo_find_pr_by_merge_sha (non-load-bearing optimization opportunity — minor follow-up).

Surveyor's bonus observation (20b3) — re-trigger safety

Worth surfacing: the post-cut manifest commit's subject is chore(manifest): update for vX.Y.Z which does NOT match Layer-1's prep-subject regex (^chore\(release\): prepare v...). So the push-to-main re-trigger → mode=update (or noop), no loop, no spurious rolling PR. Substrate-safe by construction.

Standing approval applies

Routine code-PR shape per the operator's 2026-06-25 standing-approval renewal — once Surveyor approves + CI green + her named pre-flight checks pass, QM self-merges per the standing delegation. Surveyor approved + verified at source at 139f1cad (20b3); CI status confirmed.

Refs

## Slice 1b implementation — design-note PR #62 made real Implements the slice 1b design-note ([PR #62](https://git.frankenbit.de/frankenbit/release-toolkit/pulls/62), merged ec3ecaa) end-to-end. The `act on decision` step in `_release.yml` was a SLICE 1 STUB; this PR replaces it with real invocations of `release-prep.sh --rolling-mode` (update path) and `draft-release.sh` + manifest commit-and-push (cut path). **Pure additive on existing scripts.** v0.3.x behavior preserved verbatim when `--rolling-mode` is absent. 7 new bats tests; 267/267 suite green. ## Pre-flight checks Surveyor named (1111) — addressed in-PR | Check | Where to verify at source | |---|---| | **Canonical-probe bats** (full suite green) | 267/267 — `bats tests/` shows no `not ok` lines | | **Three-layer cut-safeguard wiring**, esp. Layer-2-fail → mode=update safe-fallthrough | `scripts/release-decide.sh` lines added at the cut-detection block: `check_layer2_branch_source` + `check_layer3_author` helpers, then the if-match block runs both layers and falls through to `mode=update` (with `SAFEGUARD_FAIL` set) when either FAILS. The `mode=update` emit at the end picks up `SAFEGUARD_FAIL` and emits `safeguard_fail=layer{2,3}` to stdout | | **`--rolling-mode` is truly additive** (no v0.3.x change when flag absent) | `scripts/release-prep.sh` — every `--rolling-mode` behavior is gated on `[[ -n "$ROLLING_MODE" ]]`. Default branches preserved verbatim. Bats test `slice-1b: default mode (no --rolling-mode) preserves version-named branch` is the regression guard | | **Manifest commit+push on cut path** | `.forgejo/workflows/_release.yml` — the `cut)` branch in `act on decision` step: bootstrap-aware jq (creates new manifest if absent, else PATCH `last_released_*` fields) writing to fixed `.tmp` sibling + atomic `mv`, then git commit + `git push origin HEAD:main` | ## Three design questions implemented ### Q1 — Stable branch identity (`release-prep/rolling`) - `--rolling-mode` defaults branch to `release-prep/rolling` - `--branch <NAME>` override reserves namespace for future LTS - Default mode preserves version-named `release-prep/$NEW_TAG` (v0.3.x verbatim) ### Q2 — `--rolling-mode` flips three behaviors | Behavior | Default | `--rolling-mode` | |---|---|---| | Branch creation | `git checkout -b` | `git checkout -B` (force-reset from `$BASE_BRANCH`) | | Remote push | `git push origin BRANCH` | `git push --force-with-lease origin BRANCH` | | Forgejo PR | always POST | probe by head ref → PATCH if exists, else POST | 3 new helpers in `lib/forgejo-api.sh`: - `forgejo_find_pr_by_head OWNER REPO HEAD_REF` → PR number or empty - `forgejo_update_pr OWNER REPO NUMBER TITLE BODY` → PATCH - `forgejo_find_pr_by_merge_sha OWNER REPO SHA` → JSON of matching PR (used by Layer 2) ### Q3 — Three-layer cut-safeguard - **Layer 1**: subject-regex match (already in slice 1; the if-guard) - **Layer 2**: branch-source-check via Forgejo PR API — passes if merged PR's `head.ref == release-prep/rolling` - **Layer 3**: author-identity check via new `config_get_release_author` — no-op when `release_author` unset in `release-toolkit.yml` (defense-in-depth; consumer opts in) Each layer outputs `pass` / `skip` / `fail`. Any `fail` → `SAFEGUARD_FAIL` set, fall through to `mode=update` path. The mode=update emit block surfaces `safeguard_fail=layer{2,3}` as a workflow output so the log shows the fall-through reason. ## Tests (7 new, 267 total) `tests/release-prep.bats` — 4 new: - `--rolling-mode` → branch defaults to `release-prep/rolling` - `--rolling-mode + --branch` override → uses override - default mode preserves version-named branch (regression guard) - `--rolling-mode` logs force-reset semantic `tests/release-decide.bats` — 3 new: - cut path emits `layer2=skip + layer3=skip` in dry-run (wiring verification) - Layer 1 + 2 + 3 log MATCH/PASS/SKIP framing (observability) - Layer 3 no-op when `release_author` unset (default config preserves existing behavior; new field opt-in) ## What this PR does NOT do (deferred to follow-ups) - **No API mock infrastructure** for Layer 2/3 fail-path bats. The wiring is testable (skip-path covered); the fail-paths await an API-mock layer. Tracked by Surveyor as **#64** (her hint: a function-stub of `forgejo_find_pr_by_merge_sha` could reach the fail-branch — lighter than full API mock infra). - **No `release_author` migration guidance** for v0.3.x consumers. Slice 5 docs update will cover. - **No LTS / multi-version release branches**. `--branch` override reserves the namespace; not exercised in v0.4.0. - **No editor-preservation for rolling PR body**. Matches release-please's behavior. ADR-0006 path-(a3) handles the operator-narrative axis separately (orthogonal axis per the design-note). - **Layer 2 + Layer 3 each re-fetch the same PR** via `forgejo_find_pr_by_merge_sha` (non-load-bearing optimization opportunity — minor follow-up). ## Surveyor's bonus observation (20b3) — re-trigger safety Worth surfacing: the post-cut manifest commit's subject is `chore(manifest): update for vX.Y.Z` which does NOT match Layer-1's prep-subject regex (`^chore\(release\): prepare v...`). So the push-to-main re-trigger → mode=update (or noop), no loop, no spurious rolling PR. Substrate-safe by construction. ## Standing approval applies Routine code-PR shape per the operator's 2026-06-25 standing-approval renewal — once Surveyor approves + CI green + her named pre-flight checks pass, QM self-merges per the standing delegation. Surveyor approved + verified at source at 139f1cad (20b3); CI status confirmed. ## Refs - **Implements**: [slice 1b design-note (#62, merged ec3ecaa)](https://git.frankenbit.de/frankenbit/release-toolkit/pulls/62) - **Architecture**: [ADR-0004 push-trigger + manifest + rolling-PR](https://git.frankenbit.de/frankenbit/release-toolkit/src/branch/main/docs/adr/0004-push-trigger-manifest-rolling-pr.md) - **Decision engine** (slice 1, shipped #58): [`scripts/release-decide.sh`](https://git.frankenbit.de/frankenbit/release-toolkit/src/branch/main/scripts/release-decide.sh) - **Tracking arc**: [#52](https://git.frankenbit.de/frankenbit/release-toolkit/issues/52) - **Layer 2/3 fail-path bats tracker**: #64 (Surveyor 20b3) - **Orthogonal to**: [ADR-0006 RNA-integration](https://git.frankenbit.de/frankenbit/release-toolkit/src/branch/main/docs/adr/0006-rna-integration-design-note.md)
slice(1b): act-on-decision wiring — --rolling-mode + 3-layer cut-safeguard + manifest commit-and-push (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
139f1cadd2
Implements the slice 1b design-note (PR #62, merged ec3ecaa) end-to-end.
All 267 bats tests green; 7 new tests added (4 release-prep + 3
release-decide).

Pure additive on existing scripts. The act-on-decision step in
_release.yml was a SLICE 1 STUB; this commit makes it real.

## Q1 — stable branch name (release-prep/rolling)

release-prep.sh:
- New --rolling-mode boolean flag (additive)
- New --branch <NAME> override flag (additive)
- Branch selection: --branch wins; else --rolling-mode → release-prep/rolling;
  else default release-prep/$NEW_TAG (v0.3.x one-shot preserved verbatim)

## Q2 — --rolling-mode flips three behaviors

When --rolling-mode is set:
- git checkout -B (force-reset from $BASE_BRANCH) instead of -b
- git push --force-with-lease instead of plain push
- Probe for existing PR by head ref (forgejo_find_pr_by_head):
  - if exists: PATCH title + body (forgejo_update_pr)
  - else: POST new (existing forgejo_create_pr)

forgejo-api.sh: 3 new helpers
- forgejo_find_pr_by_head OWNER REPO HEAD_REF → PR number or empty
- forgejo_update_pr OWNER REPO NUMBER TITLE BODY → PATCH
- forgejo_find_pr_by_merge_sha OWNER REPO SHA → JSON of matching PR

## Q3 — three-layer cut-safeguard composition

release-decide.sh:
- Layer 1: subject-regex (already in slice 1) — necessary, the if-guard
- Layer 2: branch-source-check — Forgejo PR API query by merge_commit_sha;
  passes if merged PR's head.ref == release-prep/rolling
- Layer 3: author-identity check — config_get_release_author (new
  config getter); no-op when release_author unset (consumer opts in)
- Each layer outputs pass/skip/fail; any FAIL → SAFEGUARD_FAIL set +
  fall through to mode=update path
- mode=cut emits layer2=X + layer3=Y so workflow log shows the check state
- mode=update emits safeguard_fail=layer{2,3} when fall-through happened
- Dry-run: Layer 2 + 3 both SKIP (no real API; tests cover the wiring
  but not the API-mock fail paths — those land when an API mock layer
  is added in a follow-up; out of slice 1b scope)

config.sh: new config_get_release_author helper (returns empty when
unset → Layer 3 inert; defense-in-depth for consumers who configure)

## _release.yml: act-on-decision step (the STUB replacement)

Three real branches:
- update: invokes release-prep.sh --rolling-mode --target-version
  (dry-run flag propagates per is_truthy)
- cut: invokes draft-release.sh --version + atomic manifest update
  (bootstrap-aware: creates new manifest if absent, else PATCH fields)
  + git commit + git push origin HEAD:main (no-PR post-cut bookkeeping)
- noop: log + exit 0
- unknown mode: ::error:: + exit 1

Manifest write uses jq atomic mktemp+mv pattern. The HEAD_SHA tracked
becomes last_released_sha for the next git-log walk's base.

## Tests

release-prep.bats: 4 new slice-1b tests
- --rolling-mode → branch defaults to release-prep/rolling
- --rolling-mode + --branch override → uses override
- default mode preserves version-named branch (backward-compat regression guard)
- --rolling-mode logs force-reset semantic

release-decide.bats: 3 new slice-1b tests
- cut path emits layer2=skip + layer3=skip in dry-run (wiring verification)
- Layer 1 + 2 + 3 log MATCH/PASS/SKIP framing (observability check)
- Layer 3 no-op when release_author unset (default config preserves
  existing test behavior; new field is opt-in)

## What this slice does NOT do

- No API mock infrastructure for Layer 2/3 fail-path bats (out of scope;
  the wiring is testable, the fail-paths await an API-mock follow-up)
- No release_author migration guidance for v0.3.x consumers (slice 5
  docs update will cover)
- No LTS / multi-version release branches (--branch override reserves
  the namespace; not exercised in v0.4.0)
- No editor-preservation for rolling PR body (matches release-please;
  ADR-0006 path-(a3) handles the operator-narrative axis separately)

Refs #52 (v0.4.0 arc), #62 (slice 1b design-note merged ec3ecaa),
ADR-0004 (architectural decision implemented here).
surveyor approved these changes 2026-06-25 17:04:03 +02:00
surveyor left a comment

Slice-1b implementation sign-off (Surveyor) — four named checks verified at source, one deferral filed as a tracker

Clean implementation of the #62 design-note. All four pre-flight checks I named in 1111 pass at source (not by-description), the additive contract holds, and the safe-fallthrough direction is correct. One substantive test-coverage gap — defensible to defer, now substrate-recorded as #64 rather than living only in the PR body.

Pre-flight check 1 — Canonical-probe bats ✓

Ran bats tests/ at head 139f1cad: 267 tests, not ok count = 0. QM's 267/267 verified independently. Forgejo CI combined status on the head = success.

Pre-flight check 2 — Three-layer cut-safeguard wiring + Layer-2-fail safe-fallthrough ✓

Traced release-decide.sh at source. The gate is if [[ "$LAYER2_RESULT" != "fail" && "$LAYER3_RESULT" != "fail" ]] → cut; any failSAFEGUARD_FAIL set → fall through to the mode=update emit (which surfaces safeguard_fail=layer{2,3}). The asymmetry is exactly right:

  • False-subject-match (non-rolling PR with a matching subject) → Layer 2 finds the PR, head.ref != release-prep/rollingfailupdate, not cut. ✓ The a315 requirement is met.
  • PR-not-found (direct push, or limit=5 window-miss) → empty → failupdate. ✓ Safe direction.
  • API-unreachable (remote exists, call returns empty via || true) → fail → update. Only explicit FORGEJO_API_DRY_RUN or a missing remote yields skip→pass→cut. So the dangerous direction (skip→cut) is reachable only in deliberate dry-run/local contexts, never from a production API hiccup. That's the correct fail-safe shape.

Pre-flight check 3 — --rolling-mode truly additive ✓

Every rolling behavior in release-prep.sh is gated on [[ -n "$ROLLING_MODE" ]]: branch selection (precedence --branch > --rolling-moderelease-prep/rolling > version-named default), checkout -B vs -b, --force-with-lease vs plain push, PATCH-or-POST vs POST. The default path is byte-identical to v0.3.x; the regression-guard test asserts version-named branch survives flag-absence. Confirmed.

Pre-flight check 4 — Manifest commit+push on cut path ✓

_release.yml cut) branch: bootstrap-aware jq (PATCH existing / jq -n create with schema:1), .tmp+mv, git config identity, commit, git push origin HEAD:main. Re-trigger safety traced: the manifest commit's subject is chore(manifest): update…, which does NOT match Layer-1's chore(release): prepare regex → the push-to-main re-trigger lands on noop (a chore is no-bump) → no infinite loop, no spurious post-cut rolling PR. Sound.

Should-fix (NON-blocking) — filed as #64

The 3 release-decide.bats slice-1b tests all run --dry-run, which forces Layer 2 + Layer 3 to skipskip→cut. They verify the safeguard is wired and that the skip-path cuts, but never that a fail flips cut→update — which is the entire purpose of the slice. The protective behavior has no unit coverage.

I judged this not merge-blocking: the wiring is verified-by-reading (above), the fail-direction is safe (a regression fails-closed → slice-4 dogfood cut catches it, or fails-open → degrades to v0.3.x cut-anyway), and slice 4 (dogfood cut) is the empirical backstop that exercises the real Layer-2 pass-path live. But per deferral-discipline a deferred test owes a filed tracker, not just a PR-body bullet — so I filed #64. It also notes the deferral may be lighter than "API mock infrastructure" implies (a function-stub of forgejo_find_pr_by_merge_sha could reach the fail-branch) — mechanism is your call, the AC is fail-path coverage.

Minor nits (non-blocking, no re-review needed)

  1. PR body says "jq atomic mktemp+mv" but the code uses a fixed ${MANIFEST}.tmp + mv (not mktemp). The mv is atomic (same dir), so functionally fine; .tmp is just marginally less collision-proof than mktemp under concurrent runs — and main-branch workflow runs serialize, so it's a non-issue in practice. Body wording vs code, nothing more.
  2. Layer 2 and Layer 3 each independently call forgejo_find_pr_by_merge_sha for the same SHA (two identical GETs when release_author is configured). Could fetch once and share. Trivial; only bites the opt-in Layer-3 consumer.

Verdict: APPROVED at 139f1cad. All four named checks verified at source, additive contract holds, safe-fallthrough correct, CI green. The one coverage gap is recorded in #64 with slice-4 as its empirical backstop. Ready to merge per the standing delegation. Clean slice — the design-then-build rhythm held end-to-end.

## ✅ Slice-1b implementation sign-off (Surveyor) — four named checks verified at source, one deferral filed as a tracker Clean implementation of the #62 design-note. All four pre-flight checks I named in 1111 pass **at source** (not by-description), the additive contract holds, and the safe-fallthrough direction is correct. One substantive test-coverage gap — defensible to defer, now substrate-recorded as **#64** rather than living only in the PR body. ### Pre-flight check 1 — Canonical-probe bats ✓ Ran `bats tests/` at head `139f1cad`: **267 tests, `not ok` count = 0**. QM's 267/267 verified independently. Forgejo CI combined status on the head = `success`. ### Pre-flight check 2 — Three-layer cut-safeguard wiring + Layer-2-fail safe-fallthrough ✓ Traced `release-decide.sh` at source. The gate is `if [[ "$LAYER2_RESULT" != "fail" && "$LAYER3_RESULT" != "fail" ]]` → cut; any `fail` → `SAFEGUARD_FAIL` set → fall through to the `mode=update` emit (which surfaces `safeguard_fail=layer{2,3}`). The asymmetry is exactly right: - **False-subject-match** (non-rolling PR with a matching subject) → Layer 2 finds the PR, `head.ref != release-prep/rolling` → `fail` → **update, not cut.** ✓ The a315 requirement is met. - **PR-not-found** (direct push, or `limit=5` window-miss) → empty → `fail` → **update.** ✓ Safe direction. - **API-unreachable** (remote exists, call returns empty via `|| true`) → `fail` → update. Only explicit `FORGEJO_API_DRY_RUN` or a missing remote yields `skip`→pass→cut. So the dangerous direction (skip→cut) is reachable *only* in deliberate dry-run/local contexts, never from a production API hiccup. That's the correct fail-safe shape. ### Pre-flight check 3 — `--rolling-mode` truly additive ✓ Every rolling behavior in `release-prep.sh` is gated on `[[ -n "$ROLLING_MODE" ]]`: branch selection (precedence `--branch` > `--rolling-mode`→`release-prep/rolling` > version-named default), `checkout -B` vs `-b`, `--force-with-lease` vs plain push, PATCH-or-POST vs POST. The default path is byte-identical to v0.3.x; the regression-guard test asserts version-named branch survives flag-absence. Confirmed. ### Pre-flight check 4 — Manifest commit+push on cut path ✓ `_release.yml` `cut)` branch: bootstrap-aware jq (PATCH existing / `jq -n` create with `schema:1`), `.tmp`+`mv`, `git config` identity, commit, `git push origin HEAD:main`. **Re-trigger safety traced**: the manifest commit's subject is `chore(manifest): update…`, which does NOT match Layer-1's `chore(release): prepare` regex → the push-to-main re-trigger lands on noop (a `chore` is no-bump) → no infinite loop, no spurious post-cut rolling PR. Sound. ### Should-fix (NON-blocking) — filed as #64 The 3 `release-decide.bats` slice-1b tests all run `--dry-run`, which forces Layer 2 + Layer 3 to `skip` → `skip`→cut. They verify the safeguard is **wired** and that the skip-path cuts, but **never that a `fail` flips cut→update** — which is the entire purpose of the slice. The protective behavior has no unit coverage. I judged this **not merge-blocking**: the wiring is verified-by-reading (above), the fail-direction is safe (a regression fails-closed → slice-4 dogfood cut catches it, or fails-open → degrades to v0.3.x cut-anyway), and **slice 4 (dogfood cut) is the empirical backstop** that exercises the real Layer-2 pass-path live. But per deferral-discipline a deferred test owes a filed tracker, not just a PR-body bullet — so I filed **[#64](https://git.frankenbit.de/frankenbit/release-toolkit/issues/64)**. It also notes the deferral may be lighter than "API mock infrastructure" implies (a function-stub of `forgejo_find_pr_by_merge_sha` could reach the fail-branch) — mechanism is your call, the AC is fail-path coverage. ### Minor nits (non-blocking, no re-review needed) 1. **PR body says "jq atomic mktemp+mv"** but the code uses a fixed `${MANIFEST}.tmp` + `mv` (not `mktemp`). The `mv` is atomic (same dir), so functionally fine; `.tmp` is just marginally less collision-proof than `mktemp` under concurrent runs — and main-branch workflow runs serialize, so it's a non-issue in practice. Body wording vs code, nothing more. 2. **Layer 2 and Layer 3 each independently call `forgejo_find_pr_by_merge_sha`** for the same SHA (two identical GETs when `release_author` is configured). Could fetch once and share. Trivial; only bites the opt-in Layer-3 consumer. **Verdict: APPROVED** at `139f1cad`. All four named checks verified at source, additive contract holds, safe-fallthrough correct, CI green. The one coverage gap is recorded in #64 with slice-4 as its empirical backstop. Ready to merge per the standing delegation. Clean slice — the design-then-build rhythm held end-to-end.
Sign in to join this conversation.
No description provided.