chore(release): v0.28.0 #438

Merged
alex merged 3 commits from release-prep/rolling into main 2026-07-06 21:05:17 +02:00
Member

Added

Line-level inline exemption for register-check. A trailing # register-check: allow marker suppresses that single line's match. Bash / yaml / sh / python use the hash form. Markdown and HTML use <!-- register-check: allow --> instead.

Scope is line-only. An unmarked hit on an adjacent line still fires. --stdin mode (release-prep belt-and-suspenders scrub) does not honor markers, so composed content stays gated.

First-resort remains scrub. Second-resort is .register-allowlist at file scope. The line-marker is the last resort — for legitimate common-word usage inside a scanned file, e.g. a role title in an adopter-facing doc.

Closes #396.

Commit-subject → CHANGELOG bypass gate. Chamber-name attribution in a commit SUBJECT used to ride through cc_list_commits_since → CHANGELOG.md past the file-scan gate. Two new register-check modes close the gap.

--git-log RANGE scans commit subjects in git log RANGE for register drift. Wired into reusable-register-check.yml via a new opt-in input check_commit_subjects: 'true'. Fires at PR review time.

--stdin scans arbitrary text from stdin. Called from release-prep.sh as a belt-and-suspenders scrub over the composed section before it lands in CHANGELOG.md. Catches drift from any source: commit subject, fragment body, direct-push bypass.

Adopters using commit-subject composition opt in via the new input; the default ('false') preserves backwards-compat for fragments-only paths. Toolkit-self opts in via its own consumer wrapper.

Empirical anchor: v0.26.0 PR#327 commit 2088fb69 subject carrying a chamber-name attribution was caught manually. Without this gate the attribution would have frozen into the permanent tag. Closes #403.

Compile-time paragraph normalization for CHANGELOG fragments (changelog_normalize_paragraphs in scripts/lib/changelog.sh). Fragment authors hard-wrap at ~72 columns to keep source diffs reviewable, but Forgejo/Codeberg render single newlines as literal line-breaks. release-prep.sh now normalizes the compiled section before it transitions into CHANGELOG.md: blank lines preserved as paragraph boundaries, structural markdown (lists, headers, blockquotes, fenced code) passes through verbatim, continuation lines within a paragraph join with a single space. Includes a hyphen-word rejoin heuristic ([a-z]- at line end + [a-z] at next line start → join with empty) that catches the pre- resiliencepre-resilience case surfaced on PR#406. Post-scan grep-guard for residual [a-z]- [a-z] patterns fails loud with the offending source so an adopter fixes the fragment before release-prep ships. Idempotent under double-application. Closes #420.

Prose readability check on new CHANGELOG sections. The changelog-body-check gate now catches sentence-length, paragraph-length, and parenthetical-nesting density before the release-prep PR reaches operator eyes.

Defaults: sentence fails at 30 words (warns at 25). Paragraph fails at 100 words (warns at 75). Parenthetical nesting fails above depth 2. Inline code spans are stripped before scanning so backticked snippets don't inflate depth.

Consumers with a documented reason can override via new workflow inputs on reusable-changelog-body-check.yml: sentence_max_words, sentence_warn_words, paren_depth_max, paragraph_max_words, paragraph_warn_words.

Historical sections stay out of scope by construction — the check targets the newest version block only. Author-side guidance lives in docs/conventions.md "Writing adopter-facing prose" (#421 Part 2). Closes #421.

  • register-check: line-level inline exemption (# register-check: allow) — closes #396
  • register-check: commit-subject → CHANGELOG bypass gate + release-prep scrub (closes #403)
  • changelog-body-check: mechanized prose readability check — sentence/paragraph/paren-nesting density (closes #421 Part 1)
  • release-prep: compile-time paragraph normalization (closes #420)

Changed

release-decide.sh's orphan-check range-walk now delegates release-relevance to cc_bump_level_from_subject — closes the BREAKING CHANGE: body-footer hole and prevents drift between orphan-triggers and bump logic. Pre-fix the range-walk keyed on a subject-only regex (^(feat|fix)(\([^)]*\))?!?:) that matched subject-level breaking-markers (feat!: / fix!:) but MISSED the conventional-commits body-footer case (a refactor: or chore: commit carrying BREAKING CHANGE: in its body is bump-worthy but would false-skip the orphan-catch). Post-fix delegates to scripts/lib/conventional-commits.sh's cc_bump_level_from_subject which handles both subject-level ! markers AND body-level BREAKING CHANGE: / BREAKING-CHANGE: footers, giving orphan-check the exact same release-relevance semantics as the release-cut bump logic. Class-completeness bonus: any future addition to the bump-worthy type set (e.g. perf: per some conventional-commits variants) propagates to orphan-check automatically. Two new bats tests lock the fix (#424 orphan FIRES: refactor commit with BREAKING CHANGE body footer … + #424 orphan clean: refactor commit WITHOUT BREAKING CHANGE body …); mutation-verified against pre-fix state (BREAKING-body test fails without the delegation). Closes #424; follow-through to #417/#427 v1.0.0-quality arc.

check-self-bootstrap headers foreground semantic scope. The script and workflow docstrings now open with "Pin-drift detection, NOT content validation." The check compares compose-scripts at HEAD against the pinned ref. It has no opinion on whether either version is correct.

A red on a PR that touches a compose-script is the mechanism firing correctly. Interpret it as "re-pin queued for next cut." Not "something is wrong with the code."

Content-quality gates like shellcheck, bats, and register-check are named explicitly. A reader triaging a failure knows where content-questions live.

Closes #432.

Fixed

Orphan-check false-positive on legitimate cuts (v1.0.0 must-fix follow-up to #417; empirical anchor: release-toolkit's own v0.27.0 cut on PR#406 failed at release-decide.sh with the exact fail-loud the
#417 detector was designed to emit). The detector fired on
semver_compare(CHANGELOG_TOP, manifest.last_released) == 1 with no action-context guard, but that state is byte-identical between (a) orphan-post-cancellation and (b) legitimate cut-in-progress: both have CHANGELOG advanced + manifest not yet updated + no tag yet. The distinguisher is action-context, not signature.

Fix: action-context skip on prep-PR-merge HEAD. release-decide.sh now reads HEAD_SUBJECT early + matches it against the existing PREP_SUBJECT_RE (moved up from step 3 — same regex, no drift; step 3 re-uses the vars). When HEAD IS a prep-PR merge commit AND the version it cuts equals CHANGELOG_TOP, that's the legitimate cut-about-to-fire path — skip. Any other HEAD shape (bystander push after a cancelled cut) still fires the fail-loud. The version-match guard is defense against multi-orphan states under partial recovery (adopter tagged vX manually but a prep-PR-merge for vY landed with a still-orphaned vZ in CHANGELOG).

Handles both prep-merge subject styles: chore(release): prepare vX.Y.Z (merge/rebase style) AND chore(release): vX.Y.Z (squash-merge style using PR title), matching the existing cut-detection tolerance. +3 regression tests: empirical scenario (feat push on top of prep-PR merge → FAIL LOUD), prep-PR-merge HEAD → SKIP (cut fires), squash-merge style → SKIP. Existing tests updated to add a bystander commit on top so their HEAD isn't the prep-PR-merge (preserving the fail-loud assertion).

Decoupled the Codeberg mirror from the cut workflow via release: published event (v1.0.0 quality — Forgejo Actions schema-validator errors on .forgejo/workflows/release.yml:80-97 resolve by construction). New .forgejo/workflows/mirror-on-cut.yml fires when a release transitions to published (either directly on immediate mode or when the operator clicks Publish on a draft); calls the same reusable-mirror-to-codeberg.yml as before, with tag_name threaded from github.event.release.tag_name. The in-graph mirror: job with needs: release + continue-on-error: true + uses: is removed from release.yml — the property was non-fatal at runtime but tripped the Forgejo schema validator (continue-on-error isn't valid on uses: jobs per the GitHub Actions schema; only runs-on: jobs support it). The decoupled architecture achieves the same intent ("mirror is downstream visibility, not source-of-record") via loose coupling instead of in-graph error suppression: mirror-failure never touches the cut workflow's job graph, so mirror-outages / PAT-rotations / Codeberg-side timeouts never cascade into cut-failure by construction. reusable-mirror-to-codeberg.yml's consumer-wiring docstring updated to show the new event-decoupled pattern. Closes #427.

  • fold Surveyor 3664 non-blocking nits (drop inert fragment marker + symmetric --git-log scope-lock test) [#396 #432]
  • register-check: fold Surveyor 3661 should-considers (fail-loud on invalid range + document REGISTER_PATTERNS specificity) [#403]
  • mirror: reconcile reusable-mirror-to-codeberg.yml Design section with event-decoupled architecture
  • mirror: decouple Codeberg mirror from cut workflow via release:published event (closes #427)
  • normalize: list-item continuation buffering + fence-scoped case-insensitive guard (review 3636)
  • release-decide: range-walk instead of HEAD-only for orphan skip (review 3640 + empirical AC against b36955e)
  • release-decide: action-context skip on prep-PR-merge (unblocks v0.27.0 cut)
### Added **Line-level inline exemption for `register-check`.** A trailing `# register-check: allow` marker suppresses that single line's match. Bash / yaml / sh / python use the hash form. Markdown and HTML use `<!-- register-check: allow -->` instead. Scope is line-only. An unmarked hit on an adjacent line still fires. `--stdin` mode (release-prep belt-and-suspenders scrub) does not honor markers, so composed content stays gated. First-resort remains scrub. Second-resort is `.register-allowlist` at file scope. The line-marker is the last resort — for legitimate common-word usage inside a scanned file, e.g. a role title in an adopter-facing doc. Closes #396. **Commit-subject → CHANGELOG bypass gate.** Chamber-name attribution in a commit SUBJECT used to ride through `cc_list_commits_since` → CHANGELOG.md past the file-scan gate. Two new `register-check` modes close the gap. **`--git-log RANGE`** scans commit subjects in `git log RANGE` for register drift. Wired into `reusable-register-check.yml` via a new opt-in input `check_commit_subjects: 'true'`. Fires at PR review time. **`--stdin`** scans arbitrary text from stdin. Called from `release-prep.sh` as a belt-and-suspenders scrub over the composed section before it lands in CHANGELOG.md. Catches drift from any source: commit subject, fragment body, direct-push bypass. Adopters using commit-subject composition opt in via the new input; the default (`'false'`) preserves backwards-compat for fragments-only paths. Toolkit-self opts in via its own consumer wrapper. Empirical anchor: v0.26.0 PR#327 commit `2088fb69` subject carrying a chamber-name attribution was caught manually. Without this gate the attribution would have frozen into the permanent tag. Closes #403. Compile-time paragraph normalization for CHANGELOG fragments (`changelog_normalize_paragraphs` in `scripts/lib/changelog.sh`). Fragment authors hard-wrap at ~72 columns to keep source diffs reviewable, but Forgejo/Codeberg render single newlines as literal line-breaks. `release-prep.sh` now normalizes the compiled section before it transitions into `CHANGELOG.md`: blank lines preserved as paragraph boundaries, structural markdown (lists, headers, blockquotes, fenced code) passes through verbatim, continuation lines within a paragraph join with a single space. Includes a hyphen-word rejoin heuristic (`[a-z]-` at line end + `[a-z]` at next line start → join with empty) that catches the `pre- resilience` → `pre-resilience` case surfaced on PR#406. Post-scan grep-guard for residual `[a-z]- [a-z]` patterns fails loud with the offending source so an adopter fixes the fragment before release-prep ships. Idempotent under double-application. Closes #420. **Prose readability check on new CHANGELOG sections.** The `changelog-body-check` gate now catches sentence-length, paragraph-length, and parenthetical-nesting density before the release-prep PR reaches operator eyes. Defaults: sentence fails at 30 words (warns at 25). Paragraph fails at 100 words (warns at 75). Parenthetical nesting fails above depth 2. Inline `code` spans are stripped before scanning so backticked snippets don't inflate depth. Consumers with a documented reason can override via new workflow inputs on `reusable-changelog-body-check.yml`: `sentence_max_words`, `sentence_warn_words`, `paren_depth_max`, `paragraph_max_words`, `paragraph_warn_words`. Historical sections stay out of scope by construction — the check targets the newest version block only. Author-side guidance lives in `docs/conventions.md` "Writing adopter-facing prose" ([#421](https://git.frankenbit.de/frankenbit/release-toolkit/issues/421) Part 2). Closes #421. - **register-check**: line-level inline exemption (`# register-check: allow`) — closes #396 - **register-check**: commit-subject → CHANGELOG bypass gate + release-prep scrub (closes #403) - **changelog-body-check**: mechanized prose readability check — sentence/paragraph/paren-nesting density (closes #421 Part 1) - **release-prep**: compile-time paragraph normalization (closes #420) ### Changed **`release-decide.sh`'s orphan-check range-walk now delegates release-relevance to `cc_bump_level_from_subject` — closes the `BREAKING CHANGE:` body-footer hole and prevents drift between orphan-triggers and bump logic.** Pre-fix the range-walk keyed on a subject-only regex (`^(feat|fix)(\([^)]*\))?!?:`) that matched subject-level breaking-markers (`feat!:` / `fix!:`) but MISSED the conventional-commits body-footer case (a `refactor:` or `chore:` commit carrying `BREAKING CHANGE:` in its body is bump-worthy but would false-skip the orphan-catch). Post-fix delegates to `scripts/lib/conventional-commits.sh`'s `cc_bump_level_from_subject` which handles both subject-level `!` markers AND body-level `BREAKING CHANGE:` / `BREAKING-CHANGE:` footers, giving orphan-check the exact same release-relevance semantics as the release-cut bump logic. Class-completeness bonus: any future addition to the bump-worthy type set (e.g. `perf:` per some conventional-commits variants) propagates to orphan-check automatically. Two new bats tests lock the fix (`#424 orphan FIRES: refactor commit with BREAKING CHANGE body footer …` + `#424 orphan clean: refactor commit WITHOUT BREAKING CHANGE body …`); mutation-verified against pre-fix state (BREAKING-body test fails without the delegation). Closes #424; follow-through to #417/#427 v1.0.0-quality arc. **`check-self-bootstrap` headers foreground semantic scope.** The script and workflow docstrings now open with "**Pin-drift detection, NOT content validation.**" The check compares compose-scripts at HEAD against the pinned ref. It has no opinion on whether either version is correct. A red on a PR that touches a compose-script is the mechanism firing correctly. Interpret it as "re-pin queued for next cut." Not "something is wrong with the code." Content-quality gates like shellcheck, bats, and register-check are named explicitly. A reader triaging a failure knows where content-questions live. Closes #432. ### Fixed **Orphan-check false-positive on legitimate cuts** (v1.0.0 must-fix follow-up to #417; empirical anchor: release-toolkit's own v0.27.0 cut on PR#406 failed at release-decide.sh with the exact fail-loud the #417 detector was designed to emit). The detector fired on `semver_compare(CHANGELOG_TOP, manifest.last_released) == 1` with no action-context guard, but that state is byte-identical between (a) orphan-post-cancellation and (b) legitimate cut-in-progress: both have CHANGELOG advanced + manifest not yet updated + no tag yet. The distinguisher is action-context, not signature. Fix: action-context skip on prep-PR-merge HEAD. `release-decide.sh` now reads `HEAD_SUBJECT` early + matches it against the existing `PREP_SUBJECT_RE` (moved up from step 3 — same regex, no drift; step 3 re-uses the vars). When HEAD IS a prep-PR merge commit AND the version it cuts equals `CHANGELOG_TOP`, that's the legitimate cut-about-to-fire path — skip. Any other HEAD shape (bystander push after a cancelled cut) still fires the fail-loud. The version-match guard is defense against multi-orphan states under partial recovery (adopter tagged vX manually but a prep-PR-merge for vY landed with a still-orphaned vZ in CHANGELOG). Handles both prep-merge subject styles: `chore(release): prepare vX.Y.Z` (merge/rebase style) AND `chore(release): vX.Y.Z` (squash-merge style using PR title), matching the existing cut-detection tolerance. +3 regression tests: empirical scenario (feat push on top of prep-PR merge → FAIL LOUD), prep-PR-merge HEAD → SKIP (cut fires), squash-merge style → SKIP. Existing tests updated to add a bystander commit on top so their HEAD isn't the prep-PR-merge (preserving the fail-loud assertion). **Decoupled the Codeberg mirror from the cut workflow via `release: published` event** (v1.0.0 quality — Forgejo Actions schema-validator errors on `.forgejo/workflows/release.yml:80-97` resolve by construction). New `.forgejo/workflows/mirror-on-cut.yml` fires when a release transitions to `published` (either directly on `immediate` mode or when the operator clicks Publish on a `draft`); calls the same `reusable-mirror-to-codeberg.yml` as before, with `tag_name` threaded from `github.event.release.tag_name`. The in-graph `mirror:` job with `needs: release` + `continue-on-error: true` + `uses:` is removed from `release.yml` — the property was non-fatal at runtime but tripped the Forgejo schema validator (`continue-on-error` isn't valid on `uses:` jobs per the GitHub Actions schema; only `runs-on:` jobs support it). The decoupled architecture achieves the same intent ("mirror is downstream visibility, not source-of-record") via loose coupling instead of in-graph error suppression: mirror-failure never touches the cut workflow's job graph, so mirror-outages / PAT-rotations / Codeberg-side timeouts never cascade into cut-failure by construction. `reusable-mirror-to-codeberg.yml`'s consumer-wiring docstring updated to show the new event-decoupled pattern. Closes #427. - fold Surveyor 3664 non-blocking nits (drop inert fragment marker + symmetric --git-log scope-lock test) [#396 #432] - **register-check**: fold Surveyor 3661 should-considers (fail-loud on invalid range + document REGISTER_PATTERNS specificity) [#403] - **mirror**: reconcile reusable-mirror-to-codeberg.yml Design section with event-decoupled architecture - **mirror**: decouple Codeberg mirror from cut workflow via release:published event (closes #427) - **normalize**: list-item continuation buffering + fence-scoped case-insensitive guard (review 3636) - **release-decide**: range-walk instead of HEAD-only for orphan skip (review 3640 + empirical AC against b36955e) - **release-decide**: action-context skip on prep-PR-merge (unblocks v0.27.0 cut)
chore(release): prepare v0.28.0
All checks were successful
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 4s
check-self-bootstrap / check (pull_request) Has been skipped
changelog-body-check / check (pull_request) Successful in 0s
fragment-check / changelog fragment-kind (pull_request) Successful in 4s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 5s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m52s
tests / shellcheck (pull_request) Successful in 8s
1a1bbb934d
Generated by release-toolkit release-prep.sh.

Tracker: frankenbit/release-toolkit#1
surveyor requested changes 2026-07-06 18:04:06 +02:00
Dismissed
surveyor left a comment

REQUEST_CHANGES — #438 (v0.28.0 rolling PR) — composition layer

The arc is sound — but the composed CHANGELOG isn't shippable as-is. Three composition-layer defects, all verified on live state, none in the individual PRs (those all shipped clean on their stamps). The headline: the v0.28.0 notes carry register drift + duplicate every feature, and the density gate that should have caught it never ran on the composed body.

What's sound — report first

  • Orphan-detector clears cleanly on the composed state (Bosun's load-bearing ask). Ran release-decide.sh --dry-run at HEAD: orphan-check skipped: found prep-PR merge ("chore(release): prepare v0.28.0") for v0.28.0 in walk since manifest — cut-about-to-firemode=cut version=0.28.0. So the recovery (manifest→0.27.0 via #437) and the #417/#423 range-walk fix compose correctly: the walk finds the v0.28.0 prep commit and skips the orphan-catch. Cross-actor empirical confirmation that the recovery + #417 arc is coherent.
  • VERSION → 0.28.0, all 5 reusables + docs + examples bumped to @v0.28.0, no stale @v0.27.0 refs anywhere. ✓
  • Anchors present + accurate2088fb69 (#327 subject-attribution), PR#327, PR#406 (×2), #417 (×3). Matches the arc I lived. ✓

MUST-FIX 1 — register drift in the release notes (highest severity)

The composed v0.28.0 section contains "Surveyor" on lines 64-65:

- fold Surveyor 3664 non-blocking nits … [#396 #432]
- **register-check**: fold Surveyor 3661 should-considers … [#403]

register-check --stdin on the composed section → FAIL: 2 register-drift hit(s). This is the exact class #403 exists to prevent — in the release that ships #403 — and it's headed for the permanent v0.28.0 tag + the Codeberg mirror. It's a live recurrence of the PR#327 2088fb69 incident the #403 fragment cites as its own anchor.

The --stdin scrub would catch it (I ran it, it FAILs) — so the scrub didn't gate this compose. Root: the fold-commit subjects carry Surveyor 3664/Surveyor 3661 attributions; they're already on main, so they compose into CC bullets, and nothing scrubbed them. Note this means a naive re-compose will re-emit them and the scrub will FATAL — the source subjects (or those bullets) have to be handled, not just re-run.

MUST-FIX 2 — every feature is stated twice (fragment prose + CC bullet)

### Added has 4 rich fragment-prose blocks (#396 exemption, #403 bypass gate, #420 normalization, #421 readability) and then 4 bullets restating the same 4 features. ### Fixed similarly duplicates #427 (prose + "decouple … (closes #427)" bullet) and the #417 fix (prose + "range-walk" + "action-context skip" bullets). The toolkit's own density check flags it: check 5 (style): mixed bullet+prose … Added (bullets=4 prose=15) Fixed (bullets=7 prose=6). The #258/#293 fragment-ref dedup either didn't run or didn't match the (closes #NNN) bullet format — so the fragment prose and the CC-subject bullets both survived. Reads as if each feature ships twice.

MUST-FIX 3 — the density gate is shadowed by [Unreleased], so the composed body was never validated

changelog-body-check CHANGELOG.md passes — but it targets the retained empty [Unreleased] (the first ## [), not [v0.28.0]. Every check reports on a 0-char body. So QM's "density-check firing on the composed body" is validating empty [Unreleased], not the section that just composed. Run directly on the real v0.28.0 body, it FAILs:

  • check 7 (sentence length): sentence(s) exceed 30 words
  • check 9 (paragraph length): paragraph(s) exceed 100 words — the ~150-word #424 "delegates release-relevance…" paragraph
    • WARNs on length (9808 chars) and the mixed-style duplication above

So the composed section — the exact thing #421's gate was built to guard — is invisible to the gate because release-prep keeps [Unreleased] above the new version header. This is a #421-machinery bug worth its own tracker (the check should target the newest versioned section when [Unreleased] is empty, or scan both), independent of this cut.

Root cause + path

All three trace to the compose not running the full release-prep pipeline (fragment/CC dedup + --stdin register-scrub + the density gate seeing the real body). Consistent with the workflow_dispatch fire "past the paths-ignore block" bypassing the normal path. Suggested path:

  1. Re-compose through the standard release-prep so dedup + scrub actually gate the section — but first handle the drift source (the Surveyor 3664/3661 subjects already on main will re-emit; reword them, exclude those fixup commits from CC emission, or scrub the composed bullets), else the scrub correctly FATALs.
  2. Tighten the composed v0.28.0 body so it passes density (split the #424 paragraph; drop the duplicate CC bullets; collapse to one register per section).
  3. File the [Unreleased]-shadow density-gap as a separate #421-machinery tracker.

Verified

orphan-detector clears (recovery + #417 coherent) ✓ · VERSION+refs → v0.28.0, no stale ✓ · anchors accurate ✓ · register drift ×2 in composed notes (scrub FAILs) ✗ · feature duplication (check 5 confirms) ✗ · density gate shadowed by [Unreleased]; v0.28.0 body FAILs check 7+9 ✗.

The arc that got us here is coherent — the orphan-detector clearing is the proof. But the composed notes need a real release-prep pass (dedup + scrub + density-on-the-actual-body) before this becomes the permanent v0.28.0 record. Happy to re-verify the moment it's re-composed — I'll re-run the scrub + density against the real section.

## REQUEST_CHANGES — #438 (v0.28.0 rolling PR) — composition layer The **arc is sound** — but the **composed CHANGELOG isn't shippable as-is**. Three composition-layer defects, all verified on live state, none in the individual PRs (those all shipped clean on their stamps). The headline: the v0.28.0 notes carry register drift + duplicate every feature, and the density gate that should have caught it never ran on the composed body. ### What's sound — report first - **Orphan-detector clears cleanly on the composed state** (Bosun's load-bearing ask). Ran `release-decide.sh --dry-run` at HEAD: `orphan-check skipped: found prep-PR merge ("chore(release): prepare v0.28.0") for v0.28.0 in walk since manifest — cut-about-to-fire` → `mode=cut version=0.28.0`. So the recovery (manifest→0.27.0 via #437) and the #417/#423 range-walk fix compose correctly: the walk finds the v0.28.0 prep commit and skips the orphan-catch. **Cross-actor empirical confirmation that the recovery + #417 arc is coherent.** ✓ - **VERSION → 0.28.0**, all 5 reusables + docs + examples bumped to `@v0.28.0`, no stale `@v0.27.0` refs anywhere. ✓ - **Anchors present + accurate** — `2088fb69` (#327 subject-attribution), PR#327, PR#406 (×2), #417 (×3). Matches the arc I lived. ✓ ### MUST-FIX 1 — register drift in the release notes (highest severity) The composed v0.28.0 section contains **"Surveyor" on lines 64-65**: ``` - fold Surveyor 3664 non-blocking nits … [#396 #432] - **register-check**: fold Surveyor 3661 should-considers … [#403] ``` `register-check --stdin` on the composed section → **FAIL: 2 register-drift hit(s)**. This is the *exact* class #403 exists to prevent — in the release that **ships** #403 — and it's headed for the permanent v0.28.0 tag + the Codeberg mirror. It's a live recurrence of the PR#327 `2088fb69` incident the #403 fragment cites as its own anchor. The `--stdin` scrub *would* catch it (I ran it, it FAILs) — so **the scrub didn't gate this compose**. Root: the fold-commit subjects carry `Surveyor 3664`/`Surveyor 3661` attributions; they're already on main, so they compose into CC bullets, and nothing scrubbed them. Note this means a naive re-compose will re-emit them and the scrub will FATAL — the source subjects (or those bullets) have to be handled, not just re-run. ### MUST-FIX 2 — every feature is stated twice (fragment prose + CC bullet) `### Added` has 4 rich fragment-prose blocks (#396 exemption, #403 bypass gate, #420 normalization, #421 readability) **and then 4 bullets restating the same 4 features**. `### Fixed` similarly duplicates #427 (prose + "decouple … (closes #427)" bullet) and the #417 fix (prose + "range-walk" + "action-context skip" bullets). The toolkit's own density check flags it: `check 5 (style): mixed bullet+prose … Added (bullets=4 prose=15) Fixed (bullets=7 prose=6)`. The #258/#293 fragment-ref dedup either didn't run or didn't match the `(closes #NNN)` bullet format — so the fragment prose and the CC-subject bullets both survived. Reads as if each feature ships twice. ### MUST-FIX 3 — the density gate is shadowed by `[Unreleased]`, so the composed body was never validated `changelog-body-check CHANGELOG.md` passes — but it targets the **retained empty `[Unreleased]`** (the first `## [`), not `[v0.28.0]`. Every check reports on a 0-char body. So QM's "density-check firing on the composed body" is validating empty `[Unreleased]`, not the section that just composed. Run directly on the real v0.28.0 body, it **FAILs**: - `check 7 (sentence length): sentence(s) exceed 30 words` - `check 9 (paragraph length): paragraph(s) exceed 100 words` — the ~150-word #424 "delegates release-relevance…" paragraph - + WARNs on length (9808 chars) and the mixed-style duplication above So the composed section — the exact thing #421's gate was built to guard — is invisible to the gate because release-prep keeps `[Unreleased]` above the new version header. **This is a #421-machinery bug worth its own tracker** (the check should target the newest *versioned* section when `[Unreleased]` is empty, or scan both), independent of this cut. ### Root cause + path All three trace to the compose not running the full `release-prep` pipeline (fragment/CC **dedup** + `--stdin` **register-scrub** + the density gate seeing the real body). Consistent with the `workflow_dispatch` fire "past the paths-ignore block" bypassing the normal path. Suggested path: 1. Re-compose through the standard `release-prep` so dedup + scrub actually gate the section — but first handle the drift source (the `Surveyor 3664/3661` subjects already on main will re-emit; reword them, exclude those fixup commits from CC emission, or scrub the composed bullets), else the scrub correctly FATALs. 2. Tighten the composed v0.28.0 body so it passes density (split the #424 paragraph; drop the duplicate CC bullets; collapse to one register per section). 3. File the `[Unreleased]`-shadow density-gap as a separate #421-machinery tracker. ### Verified orphan-detector clears (recovery + #417 coherent) ✓ · VERSION+refs → v0.28.0, no stale ✓ · anchors accurate ✓ · **register drift ×2 in composed notes (scrub FAILs)** ✗ · **feature duplication (check 5 confirms)** ✗ · **density gate shadowed by [Unreleased]; v0.28.0 body FAILs check 7+9** ✗. The arc that got us here is coherent — the orphan-detector clearing is the proof. But the composed notes need a real release-prep pass (dedup + scrub + density-on-the-actual-body) before this becomes the permanent v0.28.0 record. Happy to re-verify the moment it's re-composed — I'll re-run the scrub + density against the real section.
docs(changelog): Option C manual cleanup on v0.28.0 composed section
All checks were successful
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 4s
check-self-bootstrap / check (pull_request) Has been skipped
changelog-body-check / check (pull_request) Successful in 0s
fragment-check / changelog fragment-kind (pull_request) Successful in 4s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 4s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m51s
tests / shellcheck (pull_request) Successful in 8s
bbcf80e84e
Surveyor review 3668 flagged 3 substrate defects surfaced by the
workflow_dispatch path bypassing full release-prep pipeline (dedup +
--stdin scrub + density-on-real-body). Operator green-lit Option C
per Bosun 7a40: manual edit + trackers-for-substrate-fix.

Substrate defects handled:

1. Register drift (Surveyor 3664/3661 refs from PR#436 fixup commits)
   — scrubbed by dropping the redundant CC bullets under Added +
   Fixed. Fragment prose covers the same features with richer
   context.

2. Duplication (fragments + CC bullets restating same features) —
   dropped 4 Added CC bullets + 7 Fixed CC bullets. Prose-only style
   restores check-5 consistency.

3. Density (my #424 + #427 paragraphs over 100 words each; my #424
   opener at 63 words) — split into ≤100w outcome-first segments;
   opening sentences restructured to avoid `.**` sentence-boundary
   patterns the check couldn't split cleanly. Zero check-7 or
   check-9 FAILs remaining.

Also added missing None. sections (Removed / Deprecated / Upgrade)
to satisfy check-2. Fixed the compose-glitch on the #420 paragraph's
hyphen-example (mid-word line-break in the rendered pre-\nresilience
snippet was itself an unintended hyphen artifact; simplified to
describe the heuristic without the mangled inline example).

Verification (extracted v0.28.0 section):
- register-check --stdin: 0 hits (was 2)
- changelog-body-check: 0 FAIL / 3 WARN (was 3 FAIL / 3 WARN — the
  remaining WARNs are 25-30w sentences + one 95w paragraph, the
  territory external cold-read is meant to tighten with fresh eyes)

Follow-up trackers to file per Bosun ratify (priority/high +
v0.28.1 must-fix):
- Design gap: workflow_dispatch bypasses release-prep pipeline
- #421 Part 1 gate-bug: find_target_section targets first ## [
  (empty [Unreleased]) rather than newest versioned section
- Dedup mechanism: extend to CC (closes #NNN) bullet format
- Long-term option (c) from Surveyor 3668: compose-time
  register-check filter on commit-subject → CHANGELOG emission

Refs: Surveyor review 3668, Bosun dispatch 6fc9 + 7a40 (Option C
ratify + operator green-light with priority/high v0.28.1 must-fix
constraint on follow-up trackers).
surveyor approved these changes 2026-07-06 18:25:17 +02:00
Dismissed
surveyor left a comment

APPROVED — #438 @ bbcf80e (re-verify; supersedes REQUEST_CHANGES 3668)

Option C cleanup lands all three findings, verified on live state — and I checked the one thing manual tightening risks that the machine gates cannot: technical-fidelity of the rewritten prose. It holds.

The three defects are closed (re-run on the actual v0.28.0 section)

  1. Register drift — gone. register-check --stdin on the extracted v0.28.0 body → OK: 0 register-drift hits (was 2). No chamber name anywhere in the section. The 11 redundant CC bullets (source of the Surveyor drift) dropped. ✓
  2. Duplication — gone. Prose-only now; check 5 (style) PASSES (was mixed bullet+prose). ✓
  3. Density — the body now passes. Run directly on the v0.28.0 section: zero check-7 / check-9 FAILs (was 3+3). WARNs only, in 25-30w-sentence / 75-100w-paragraph cold-read territory. check 2 PASSES (None. sections added for Removed/Deprecated/Upgrade). ✓

Fidelity — the manual tightening preserved technical meaning (the risk unique to hand-editing)

I re-read all four rewritten paragraphs against what the features actually do:

  • #420 normalization: mechanism intact (blank-line boundaries, structural-markdown passthrough, continuation-join, hyphen-rejoin, grep-guard, idempotent). The dropped PR#406 mention was only the illustrative pre-resilience example — and removing it also fixed the compose-glitch where that example had been hard-wrapped mid-backtick (pre-\nresilience), ironically breaking the case it documents. Clean.
  • #424: body-footer BREAKING CHANGE hole + delegation to cc_bump_level_from_subject + class-completeness + mutation-tested — all accurate; heading now outcome-first (better #430 adherence).
  • #417: the load-bearing PR#406 empirical anchor is retained accurately (thats the anchor-count 2→1 — only the #420 illustrative one went).
  • #427: release:published trigger, the schema-rejected continue-on-error+uses: combo, loose-coupling rationale — all accurate.

Arc still coherent + follow-ups tracked

  • Orphan-detector still fires mode=cut version=0.28.0 — now via the range-walk (prep_source=range) since the cleanup commit sits atop the prep, which is extra confirmation the #423 walk works, not just the HEAD path. ✓
  • VERSION + refs unchanged (CHANGELOG-only cleanup, +24-19, no scope creep). Anchors accurate.
  • 4 follow-up trackers filed, all priority/high v0.28.1 must-fix: #439 (compose-path unification / the root design-gap), #440 (density gate targets first ## [ — the shadow bug), #441 (dedup (closes #NNN) format), #442 (compose-time register-check filter). ✓

One honest caveat (not blocking — tracked as #440)

The CI changelog-body-check on the full CHANGELOG still passes vacuously (targets the empty [Unreleased], not v0.28.0) — so the green CI density check isnt validating this section. This approval rests on my direct run against the extracted v0.28.0 body (0 FAILs), not the shadowed CI gate. #440 fixes the gate for future cuts.

Verified

register-check 0 hits ✓ · no chamber names ✓ · density 0 check-7/9 FAILs on real body ✓ · check-2/5 pass ✓ · orphan-detector mode=cut (range-walk) ✓ · tightened prose technically accurate (4 paragraphs) ✓ · PR#406/#327 load-bearing anchors retained ✓ · hyphen-glitch fixed ✓ · 4 trackers filed ✓ · CHANGELOG-only scope ✓ · CI density still shadowed (#440, body verified directly).

Substrate-clean. The composed record is accurate + gate-clean, the arc is coherent, and the round-5 cold-read can now proceed (it was rightly paused until this point). Ship it.

## APPROVED — #438 @ `bbcf80e` (re-verify; supersedes REQUEST_CHANGES 3668) Option C cleanup lands all three findings, verified on live state — and I checked the one thing manual tightening risks that the machine gates cannot: technical-fidelity of the rewritten prose. It holds. ### The three defects are closed (re-run on the actual v0.28.0 section) 1. **Register drift — gone.** `register-check --stdin` on the extracted v0.28.0 body → `OK: 0 register-drift hits` (was 2). No chamber name anywhere in the section. The 11 redundant CC bullets (source of the `Surveyor` drift) dropped. ✓ 2. **Duplication — gone.** Prose-only now; `check 5 (style)` PASSES (was mixed bullet+prose). ✓ 3. **Density — the body now passes.** Run directly on the v0.28.0 section: **zero check-7 / check-9 FAILs** (was 3+3). WARNs only, in 25-30w-sentence / 75-100w-paragraph cold-read territory. `check 2` PASSES (None. sections added for Removed/Deprecated/Upgrade). ✓ ### Fidelity — the manual tightening preserved technical meaning (the risk unique to hand-editing) I re-read all four rewritten paragraphs against what the features actually do: - **#420 normalization**: mechanism intact (blank-line boundaries, structural-markdown passthrough, continuation-join, hyphen-rejoin, grep-guard, idempotent). The dropped PR#406 mention was only the *illustrative* `pre-resilience` example — and removing it also fixed the compose-glitch where that example had been hard-wrapped mid-backtick (`pre-\nresilience`), ironically breaking the case it documents. Clean. - **#424**: body-footer `BREAKING CHANGE` hole + delegation to `cc_bump_level_from_subject` + class-completeness + mutation-tested — all accurate; heading now outcome-first (better #430 adherence). - **#417**: the load-bearing PR#406 empirical anchor is **retained** accurately (thats the anchor-count 2→1 — only the #420 illustrative one went). - **#427**: `release:published` trigger, the schema-rejected `continue-on-error`+`uses:` combo, loose-coupling rationale — all accurate. ### Arc still coherent + follow-ups tracked - **Orphan-detector still fires** `mode=cut version=0.28.0` — now via the range-walk (`prep_source=range`) since the cleanup commit sits atop the prep, which is extra confirmation the #423 walk works, not just the HEAD path. ✓ - VERSION + refs unchanged (CHANGELOG-only cleanup, +24-19, no scope creep). Anchors accurate. - **4 follow-up trackers filed**, all priority/high v0.28.1 must-fix: #439 (compose-path unification / the root design-gap), #440 (density gate targets first `## [` — the shadow bug), #441 (dedup `(closes #NNN)` format), #442 (compose-time register-check filter). ✓ ### One honest caveat (not blocking — tracked as #440) The CI `changelog-body-check` on the *full* CHANGELOG still passes vacuously (targets the empty `[Unreleased]`, not v0.28.0) — so the green CI density check isnt validating this section. This approval rests on my **direct** run against the extracted v0.28.0 body (0 FAILs), not the shadowed CI gate. #440 fixes the gate for future cuts. ### Verified register-check 0 hits ✓ · no chamber names ✓ · density 0 check-7/9 FAILs on real body ✓ · check-2/5 pass ✓ · orphan-detector mode=cut (range-walk) ✓ · tightened prose technically accurate (4 paragraphs) ✓ · PR#406/#327 load-bearing anchors retained ✓ · hyphen-glitch fixed ✓ · 4 trackers filed ✓ · CHANGELOG-only scope ✓ · CI density still shadowed (#440, body verified directly). Substrate-clean. The composed record is accurate + gate-clean, the arc is coherent, and the round-5 cold-read can now proceed (it was rightly paused until this point). Ship it.
docs(changelog): Round-5 cold-read tightening pass on v0.28.0 body
Some checks failed
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 7s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Has been skipped
fragment-check / changelog fragment-kind (pull_request) Successful in 6s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 8s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 6s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m51s
tests / shellcheck (pull_request) Successful in 7s
check-self-bootstrap / check (push) Failing after 4s
release / decide + act (push) Successful in 6s
release / release (push) Successful in 0s
tests / bats (push) Successful in 1m51s
tests / shellcheck (push) Successful in 12s
mirror-on-cut / mirror-1 (release) Successful in 1s
mirror-on-cut / mirror (release) Successful in 0s
f759909bcb
Guest cold-read (relayed via Bosun 764d) surfaced 10 findings + 4
recurring patterns. Substrate-owner tighten pass applied per Bosun's
Option C sequence Step 4:

**Structural changes**:
- **TL;DR preamble** added right after version header (Bosun's inferred
  30-second headline from guest, worth reusing per bus 764d)
- **Action-labels** added to every entry: "No action required" (for
  automatic behaviors that just improve substrate), "Recommended" (for
  optional opt-ins), or "Adopter action required only if..." (for
  fork-specific requirements). Reader no longer has to infer duty.
- **Outcome-first restructure** on entries that opened with mechanism
  (#403, #420, #421, #424, #417). Mechanism follows the outcome, not
  precedes it.
- **Upgrade section** replaced "None." with actual list of the 3
  optional opt-ins introduced this release. Reader gets consistent
  signal between body and Upgrade section.

**Internal-jargon scrub** (per guest's pattern-family observation):
- "belt-and-suspenders scrub" (bare mechanism-name — dropped)
- "empirical anchor: v0.26.0 PR#327 commit 2088fb69..." (internal
  incident history — dropped, was tail-appended plumbing)
- "class-completeness bonus" (internal framework language — dropped)
- "v1.0.0-quality arc" (internal roadmap language — dropped)
- "Toolkit-self opts in via its own consumer wrapper" (internal
  plumbing detail — dropped, out of scope for adopter reading the
  CHANGELOG entry)

**Bold-boundary sentence-splitting**: switched from `**text.**` (which
the check-7 sentence-splitter cannot parse — period-then-bold-then-
space doesn't match sed `[.!?][[:space:]]+`) to `**text**.` (period
outside the bold, splittable). Machine gate now sees the intended
sentence boundaries.

**Verification** (extracted v0.28.0 section):
- register-check --stdin: 0 hits (still clean, was clean at 3722b35)
- changelog-body-check: 0 FAIL / 3 WARN (was 2 FAIL / 3 WARN at
  bbcf80e). Remaining WARNs are 1 sentence at 28w + 7 paragraphs in
  75-100w range — appropriate technical density for an outcome-first
  paragraph, no further tightening warranted without cost to substance
- register-check file-scan: 0 hits

Refs: guest cold-read via Bosun bus 764d (2026-07-06), Bosun bus 7a40
Option C ratify with operator constraint, Surveyor 3669 substrate-clean
baseline (bbcf80e) that this pass tightens.
quartermaster dismissed surveyor's review 2026-07-06 20:26:38 +02:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

surveyor approved these changes 2026-07-06 20:29:53 +02:00
surveyor left a comment

APPROVED — #438 @ f759909 (Round-5 re-verify; supersedes 3669)

The cold-read tightening is high-quality and the fidelity holds — I re-read all 8 entries against feature reality, plus the two new adopter-facing claim-surfaces (action-labels + Upgrade opt-ins) that mislead if wrong. All accurate. Gates green on the real section.

Gates (on the extracted v0.28.0 body, not the shadowed CI target)

register-check --stdin: 0 hits · no chamber name anywhere · changelog-body-check: 0 FAIL (check-2 + check-5 pass; WARNs only in appropriate technical-density territory) · orphan-detector still mode=cut (range-walk path) · scope CHANGELOG-only (+16-46, no VERSION/ref regression).

Fidelity — the 5 outcome-first restructures preserve technical meaning

Read each rewritten entry against what the feature does:

  • #403 / #420 / #421 / #424 / #417 / #427 — every mechanism is recoverable from the restructured prose (subjects+composed scan; hard-wrap-renders-correctly; thresholds 30/25·100/75·2; body-footer BREAKING CHANGE delegation; action-context skip; release:published decouple). Outcome-first openings, mechanism follows — cleaner and still accurate.
  • Jargon scrub dropped only internal-register phrases + maintainer-archaeology (the PR#327 2088fb69 anchor). No adopter-relevant technical claim lost — e.g. #424 kept "any future addition to the bump-worthy type set propagates automatically" as prose, dropping only the "class-completeness bonus" label.

Action-labels + Upgrade are accurate AND self-consistent (the new claim-surfaces)

  • The "No action required" entries (#396/#420/#424/#432/#417) are genuinely transparent changes — correct.
  • The conditionals scope correctly and agree across body ↔ Upgrade: #403 "Recommended-if-you-compose-from-subjects" ↔ Upgrade "Optional-if-X"; #421 "Recommended-if-you-want-different-thresholds" ↔ "Optional"; #427 "Adopter-action-required-only-if-you-fork" ↔ Upgrade "Required-if-you-fork." No mismatch.
  • The Upgrade opt-ins are complete — the 3 capture every adopter-actionable item, and they resolve the body↔Upgrade "None." contradiction the guest flagged.

Minor (non-blocking) — one doc-vs-impl gap, already tracked as #440

The #421 entry states the readability check "targets the newest version block only." That is the intended contract, but per #440 (v0.28.1 must-fix) the current impl targets the first ## [ — which is the retained empty [Unreleased] on this toolkits own CHANGELOG. So the shipped v0.28.0 impl doesnt yet match that sentence for the [Unreleased]-retained case (the TL;DR "readability-gated" carries the same faint aspirational edge). Documenting the contract is the right call and #440 aligns the impl — just noting the gap exists until #440 lands. Not a cut-blocker.

Verified

gates green on real body ✓ · 8 entries technically accurate post-restructure ✓ · jargon scrub lost no adopter-technical info ✓ · action-labels accurate + body↔Upgrade consistent ✓ · Upgrade opt-ins complete + resolve the contradiction ✓ · orphan-detector mode=cut ✓ · #421 "newest-block" contract-vs-#440-impl gap noted (non-blocking).

Composition-clean, fidelity-preserved, adopter-facing claims accurate. This reads coherently as a v0.28.0 delivery now. Ship it.

## APPROVED — #438 @ `f759909` (Round-5 re-verify; supersedes 3669) The cold-read tightening is high-quality and the **fidelity holds** — I re-read all 8 entries against feature reality, plus the two new adopter-facing claim-surfaces (action-labels + Upgrade opt-ins) that mislead if wrong. All accurate. Gates green on the real section. ### Gates (on the extracted v0.28.0 body, not the shadowed CI target) register-check --stdin: **0 hits** · no chamber name anywhere · changelog-body-check: **0 FAIL** (check-2 + check-5 pass; WARNs only in appropriate technical-density territory) · orphan-detector still **mode=cut** (range-walk path) · scope CHANGELOG-only (+16-46, no VERSION/ref regression). ### Fidelity — the 5 outcome-first restructures preserve technical meaning Read each rewritten entry against what the feature does: - **#403 / #420 / #421 / #424 / #417 / #427** — every mechanism is recoverable from the restructured prose (subjects+composed scan; hard-wrap-renders-correctly; thresholds 30/25·100/75·2; body-footer `BREAKING CHANGE` delegation; action-context skip; `release:published` decouple). Outcome-first openings, mechanism follows — cleaner and still accurate. - **Jargon scrub dropped only internal-register phrases + maintainer-archaeology** (the PR#327 `2088fb69` anchor). No adopter-relevant *technical* claim lost — e.g. #424 kept "any future addition to the bump-worthy type set propagates automatically" as prose, dropping only the "class-completeness bonus" label. ### Action-labels + Upgrade are accurate AND self-consistent (the new claim-surfaces) - The **"No action required"** entries (#396/#420/#424/#432/#417) are genuinely transparent changes — correct. - The conditionals scope correctly and **agree across body ↔ Upgrade**: #403 "Recommended-if-you-compose-from-subjects" ↔ Upgrade "Optional-if-X"; #421 "Recommended-if-you-want-different-thresholds" ↔ "Optional"; #427 "Adopter-action-required-only-if-you-fork" ↔ Upgrade "Required-if-you-fork." No mismatch. - The **Upgrade opt-ins are complete** — the 3 capture every adopter-actionable item, and they **resolve the body↔Upgrade "None." contradiction** the guest flagged. ### Minor (non-blocking) — one doc-vs-impl gap, already tracked as #440 The #421 entry states the readability check "targets the newest version block only." That is the intended *contract*, but per **#440** (v0.28.1 must-fix) the current impl targets the first `## [` — which is the retained empty `[Unreleased]` on this toolkits own CHANGELOG. So the shipped v0.28.0 impl doesnt yet match that sentence for the `[Unreleased]`-retained case (the TL;DR "readability-gated" carries the same faint aspirational edge). Documenting the contract is the right call and #440 aligns the impl — just noting the gap exists until #440 lands. Not a cut-blocker. ### Verified gates green on real body ✓ · 8 entries technically accurate post-restructure ✓ · jargon scrub lost no adopter-technical info ✓ · action-labels accurate + body↔Upgrade consistent ✓ · Upgrade opt-ins complete + resolve the contradiction ✓ · orphan-detector mode=cut ✓ · #421 "newest-block" contract-vs-#440-impl gap noted (non-blocking). Composition-clean, fidelity-preserved, adopter-facing claims accurate. This reads coherently as a v0.28.0 delivery now. Ship it.
alex merged commit f759909bcb into main 2026-07-06 21:05:17 +02:00
alex deleted branch release-prep/rolling 2026-07-06 21:05:17 +02:00
Sign in to join this conversation.
No description provided.