manifest-vs-actual-history guard: extend manifest-check.sh for v0.4.0 silent-desync class #56

Closed
opened 2026-06-25 01:46:27 +02:00 by quartermaster · 0 comments

Why this exists now (and not deferred to discovery)

v0.4.0 (#52) introduces .release-toolkit-manifest.json as the base of the git-log walk that decides cut-vs-update. The manifest's last_released_sha is load-bearing for correctness: a wrong SHA → wrong commit-walk window → wrong CHANGELOG content + wrong bump computation.

The convention "operators don't hand-edit the manifest" is not a guard. A desynced manifest produces a silent-wrong CHANGELOG — the workflow runs successfully, the rolling PR opens with the wrong content, and the operator sees nothing flagged unless they catch the diff during PR review.

Silent-wrong is exactly the failure class #41 taught us to guard loudly. Filing now so the v0.4.0 design doesn't ship without a tracker for the obvious next gap.

Desync sources to catch

The manifest can drift from actual history via:

  1. Hand-edit (operator accidentally edits the manifest JSON)
  2. Lost commit (force-push to main removes the post-cut manifest-update commit)
  3. Branch surgery (rebase / revert across the cut SHA leaves the manifest pointing at a non-existent or wrong-content commit)
  4. Bootstrap edge (first v0.4.0 run reads from latest tag; if the tag was hand-created without ceremony, the SHA-version pairing may be inconsistent)
  5. Multi-package future (not in v0.4.0 scope but worth the schema headroom)

Proposed guard mechanics

Extend manifest-check.sh (currently checks release-toolkit.yml + version_files + [Unreleased] section + manifest >= tag) with steps:

  • Step 5: last_released_sha exists in the local git history (git cat-file -e $LAST_SHA)
  • Step 6: the commit at last_released_sha was a release-prep merge (subject regex match against ^chore\(release\): prepare v[0-9]+\.[0-9]+\.[0-9]+)
  • Step 7: the tag last_released_tag exists AND points at last_released_sha (or at a git tag-attached annotated tag whose target is that SHA)
  • Step 8: last_released_version matches the version-string embedded in last_released_tag (mod tag_format)

Each step emits OK/FAIL/SKIP lines like the existing manifest-check steps.

A failed check halts the workflow + emits the specific reason. Silent → loud.

What this is NOT

  • Not a v0.4.0 blocker. v0.4.0 ships with the convention "don't hand-edit" + this tracker filed. The extended check is a follow-up issue, not a release-gate.
  • Not a write-fence. The check runs as a pull_request CI step (existing manifest-check.yml consumer); doesn't prevent the manifest from being mutated, just catches the mutation before merge to main.
  • Not a multi-package guard. That's a v0.5+ concern; the schema envelope (schema: 1) gives forward-compat room.

Refs

  • Design sketch §Risk 2 (manifest desync): PR #55 — flagged by Surveyor's design-review (d296) as the new load-bearing silent-failure surface
  • v0.4.0 arc: #52
  • Lesson predecessor: #41 silent-failure class (Forgejo expression-engine fail-opens silently)

Filed: 2026-06-25 from Surveyor's #55 design-review d296 catch.

## Why this exists now (and not deferred to discovery) v0.4.0 (#52) introduces `.release-toolkit-manifest.json` as the **base of the git-log walk** that decides cut-vs-update. The manifest's `last_released_sha` is load-bearing for correctness: a wrong SHA → wrong commit-walk window → wrong CHANGELOG content + wrong bump computation. The convention "operators don't hand-edit the manifest" is **not a guard**. A desynced manifest produces a *silent-wrong* CHANGELOG — the workflow runs successfully, the rolling PR opens with the wrong content, and the operator sees nothing flagged unless they catch the diff during PR review. Silent-wrong is exactly the failure class #41 taught us to guard loudly. Filing now so the v0.4.0 design doesn't ship without a tracker for the obvious next gap. ## Desync sources to catch The manifest can drift from actual history via: 1. **Hand-edit** (operator accidentally edits the manifest JSON) 2. **Lost commit** (force-push to main removes the post-cut manifest-update commit) 3. **Branch surgery** (rebase / revert across the cut SHA leaves the manifest pointing at a non-existent or wrong-content commit) 4. **Bootstrap edge** (first v0.4.0 run reads from latest tag; if the tag was hand-created without ceremony, the SHA-version pairing may be inconsistent) 5. **Multi-package future** (not in v0.4.0 scope but worth the schema headroom) ## Proposed guard mechanics Extend `manifest-check.sh` (currently checks `release-toolkit.yml` + `version_files` + `[Unreleased]` section + `manifest >= tag`) with steps: - **Step 5**: `last_released_sha` exists in the local git history (`git cat-file -e $LAST_SHA`) - **Step 6**: the commit at `last_released_sha` was a release-prep merge (subject regex match against `^chore\(release\): prepare v[0-9]+\.[0-9]+\.[0-9]+`) - **Step 7**: the tag `last_released_tag` exists AND points at `last_released_sha` (or at a `git tag`-attached annotated tag whose target is that SHA) - **Step 8**: `last_released_version` matches the version-string embedded in `last_released_tag` (mod `tag_format`) Each step emits `OK`/`FAIL`/`SKIP` lines like the existing manifest-check steps. A failed check halts the workflow + emits the specific reason. Silent → loud. ## What this is NOT - Not a v0.4.0 blocker. v0.4.0 ships with the convention "don't hand-edit" + this tracker filed. The extended check is a follow-up issue, not a release-gate. - Not a write-fence. The check runs as a `pull_request` CI step (existing `manifest-check.yml` consumer); doesn't prevent the manifest from being mutated, just catches the mutation before merge to main. - Not a multi-package guard. That's a v0.5+ concern; the schema envelope (`schema: 1`) gives forward-compat room. ## Refs - Design sketch §Risk 2 (manifest desync): [PR #55](https://git.frankenbit.de/frankenbit/release-toolkit/pulls/55) — flagged by Surveyor's design-review (d296) as the new load-bearing silent-failure surface - v0.4.0 arc: [#52](https://git.frankenbit.de/frankenbit/release-toolkit/issues/52) - Lesson predecessor: [#41](https://git.frankenbit.de/frankenbit/release-toolkit/issues/41) silent-failure class (Forgejo expression-engine fail-opens silently) Filed: 2026-06-25 from Surveyor's #55 design-review d296 catch.
Sign in to join this conversation.
No milestone
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#56
No description provided.