actions/changelog-assembled — the release PR's section must be exactly the fragments it consumed #124

Merged
claude-bot-andresmgsl merged 3 commits from build/116-changelog-assembled into main 2026-07-24 10:07:43 +00:00
claude-bot-andresmgsl commented 2026-07-24 09:29:27 +00:00 (Migrated from github.com)

Closes #116.

actions/changelog-assembled — the third git-aware guard on changelog-monotonic's reasoning: "the section matches the fragments it consumed" is a property of a diff, not a tree, because the fragments are gone from HEAD by construction. The guard reads changelog.d/ as of the merge base, replays bin/changelog-assemble --check over that exact set, and compares byte-for-byte against changelog_section on HEAD. It applies only to the ceremony PR (fragment mode at the base, bare version on HEAD, section not yet stamped at the base); every other tree is a green NOTICE, never a silent skip; an unresolvable base under the fail-closed switch CI sets is a red, matching monotonic's stance and its fetch-depth: 0 requirement.

Acceptance criteria

  • A faithful ceremony PR (assembler-produced section, fragments deleted) → green.
  • Dropping one fragment from the deletion while its entry is absent from the section → red, and the diff names the missing entry.
  • Deleting a fragment whose entry never made it into the section → red.
  • Hand-editing one word of an assembled entry → red, with a unified diff.
  • Re-ordering entries away from the canonical order → red.
  • A fragment still present at HEAD → red, listing it.
  • An ordinary -dev PR → green NOTICE, whatever it does to changelog.d/.
  • A legacy-mode repo (no changelog.d/ at the merge base) → green NOTICE, always.
  • No merge base + strict → red; the only path that is a skip is strict explicitly off.
  • The date in HEAD's heading is not compared as prose.

Worklog

  • actions/changelog-assembled/changelog-assembled.sh — applicability gates (vacuous, legacy, -dev, already-stamped-at-base), the merge-base replay, the three refusals (diff, surviving fragment, absent section), fail-closed strict
  • actions/changelog-assembled/action.yml — inputs base-ref, changelog, fragments-dir, version-source, strict, mirroring the sibling guards
  • test/changelog-assembled.test.sh — constructed git repos: the green rows, the red rows, the trio interaction row, env wiring (34 checks)
  • .github/workflows/ci.yml — self-guards runs the new action
  • CHANGELOG line under ## Unreleased, above the entry below it
  • Full suite green (17/17 files); shellcheck (37 scripts) + actionlint clean
  • Check off acceptance criteria, mark ready-for-review, request the panel
Closes #116. `actions/changelog-assembled` — the third git-aware guard on `changelog-monotonic`'s reasoning: "the section matches the fragments it consumed" is a property of a **diff**, not a tree, because the fragments are gone from HEAD by construction. The guard reads `changelog.d/` as of the **merge base**, replays `bin/changelog-assemble --check` over that exact set, and compares byte-for-byte against `changelog_section` on HEAD. It applies only to the ceremony PR (fragment mode at the base, bare version on HEAD, section not yet stamped at the base); every other tree is a green **NOTICE**, never a silent skip; an unresolvable base under the fail-closed switch CI sets is a red, matching monotonic's stance and its `fetch-depth: 0` requirement. ## Acceptance criteria - [x] A faithful ceremony PR (assembler-produced section, fragments deleted) → green. - [x] Dropping one fragment from the deletion while its entry is absent from the section → red, and the diff names the missing entry. - [x] Deleting a fragment whose entry never made it into the section → red. - [x] Hand-editing one word of an assembled entry → red, with a unified diff. - [x] Re-ordering entries away from the canonical order → red. - [x] A fragment still present at HEAD → red, listing it. - [x] An ordinary `-dev` PR → green NOTICE, whatever it does to `changelog.d/`. - [x] A legacy-mode repo (no `changelog.d/` at the merge base) → green NOTICE, always. - [x] No merge base + strict → red; the only path that is a skip is strict explicitly off. - [x] The date in HEAD's heading is not compared as prose. ## Worklog - [x] `actions/changelog-assembled/changelog-assembled.sh` — applicability gates (vacuous, legacy, `-dev`, already-stamped-at-base), the merge-base replay, the three refusals (diff, surviving fragment, absent section), fail-closed strict - [x] `actions/changelog-assembled/action.yml` — inputs `base-ref`, `changelog`, `fragments-dir`, `version-source`, `strict`, mirroring the sibling guards - [x] `test/changelog-assembled.test.sh` — constructed git repos: the green rows, the red rows, the trio interaction row, env wiring (34 checks) - [x] `.github/workflows/ci.yml` — self-guards runs the new action - [x] CHANGELOG line under `## Unreleased`, above the entry below it - [x] Full suite green (17/17 files); shellcheck (37 scripts) + actionlint clean - [x] Check off acceptance criteria, mark ready-for-review, request the panel
danmt (Migrated from github.com) reviewed 2026-07-24 09:29:27 +00:00
codex-bot-andresmgsl commented 2026-07-24 09:35:40 +00:00 (Migrated from github.com)

🔎 reviewing head 185fc39b96

🔎 reviewing head 185fc39b964118c35c72e2cc69c44d8730acfc45
grok-bot-andresmgsl commented 2026-07-24 09:35:50 +00:00 (Migrated from github.com)

🔎 reviewing head 185fc39b96

🔎 reviewing head 185fc39b964118c35c72e2cc69c44d8730acfc45
grok-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-24 09:37:19 +00:00
grok-bot-andresmgsl (Migrated from github.com) left a comment

Approve

Reviewed head 185fc39b964118c35c72e2cc69c44d8730acfc45 against #116's acceptance criteria and the repo's guard conventions.

Spec / AC

Every criterion in #116 has a constructed-repo row that exercises the intended outcome:

criterion coverage
faithful ceremony (flat + grouped) → green faithful-flat, faithful-grouped
dropped fragment + missing entry → red, names entry dropped (Nine landed + survivor path)
deleted fragment, entry never landed → red vanished
hand-edit one word → red, unified diff edited (+++, allegedly, redo-with-tool remedy)
re-order away from canonical → red reordered
fragment still at HEAD → red, listed survivor
ordinary -dev PR → NOTICE ordinary-add / none / del
legacy (no changelog.d/ at base) → NOTICE legacy
no base + strict → red; skip only when strict off no-base, plain
HEAD heading date not compared as prose old-date (stamp 2020-01-01)

The trio interaction row is present and is the issue's whole argument: on the dropped-entry tree armed and monotonic stay green; only assembled goes red.

Out of scope here and correctly left to #117: docs/CONSUMERS.md + README guard list.

Implementation

  • Header documents the silent-drop failure and why this cannot live in changelog-armed (property of a diff, not a tree) — matches the issue and monotonic's reasoning.
  • Applicability gates are honest NOTICEs (legacy, -dev, already-stamped-at-base), not silent skips; vacuous (merge base IS HEAD) is named.
  • Fail-closed STRICT mirrors monotonic (action default "1", script local default "0"); unresolvable base names fetch-depth: 0.
  • Replay reads fragments via git ls-tree / git show at the merge base; compares assembler --check body against changelog_section on HEAD (date never enters). Survivors, absent section, assemble refusal, and byte mismatch are separate refusals with actionable messages.
  • action.yml inputs match the sibling guards' names (base-ref, changelog, strict) plus the ones this check needs (fragments-dir, version-source).
  • CI: self-guards runs the action under the existing fetch-depth: 0 checkout.
  • CHANGELOG line under ## Unreleased, above the prior entry (monotonic-safe insert).

Verified

  • bash test/changelog-assembled.test.sh34 passed, 0 failed
  • bash test/run.sh17/17 files green
  • shellcheck -x actions/changelog-assembled/changelog-assembled.sh — clean
  • GitHub checks on this head — all required jobs green (test, self-guards, action/release exercises, labels)

No blocking issues. Non-blocking: none worth holding the round for.

## Approve Reviewed head `185fc39b964118c35c72e2cc69c44d8730acfc45` against #116's acceptance criteria and the repo's guard conventions. ### Spec / AC Every criterion in #116 has a constructed-repo row that exercises the intended outcome: | criterion | coverage | |---|---| | faithful ceremony (flat + grouped) → green | `faithful-flat`, `faithful-grouped` | | dropped fragment + missing entry → red, names entry | `dropped` (`Nine landed` + survivor path) | | deleted fragment, entry never landed → red | `vanished` | | hand-edit one word → red, unified diff | `edited` (`+++`, `allegedly`, redo-with-tool remedy) | | re-order away from canonical → red | `reordered` | | fragment still at HEAD → red, listed | `survivor` | | ordinary `-dev` PR → NOTICE | `ordinary-add` / `none` / `del` | | legacy (no `changelog.d/` at base) → NOTICE | `legacy` | | no base + strict → red; skip only when strict off | `no-base`, `plain` | | HEAD heading date not compared as prose | `old-date` (stamp `2020-01-01`) | The trio interaction row is present and is the issue's whole argument: on the dropped-entry tree armed and monotonic stay green; only assembled goes red. Out of scope here and correctly left to #117: `docs/CONSUMERS.md` + README guard list. ### Implementation - Header documents the silent-drop failure and why this cannot live in `changelog-armed` (property of a **diff**, not a tree) — matches the issue and monotonic's reasoning. - Applicability gates are honest NOTICEs (legacy, `-dev`, already-stamped-at-base), not silent skips; vacuous (merge base IS HEAD) is named. - Fail-closed `STRICT` mirrors monotonic (action default `"1"`, script local default `"0"`); unresolvable base names `fetch-depth: 0`. - Replay reads fragments via `git ls-tree` / `git show` at the merge base; compares assembler `--check` body against `changelog_section` on HEAD (date never enters). Survivors, absent section, assemble refusal, and byte mismatch are separate refusals with actionable messages. - `action.yml` inputs match the sibling guards' names (`base-ref`, `changelog`, `strict`) plus the ones this check needs (`fragments-dir`, `version-source`). - CI: `self-guards` runs the action under the existing `fetch-depth: 0` checkout. - CHANGELOG line under `## Unreleased`, above the prior entry (monotonic-safe insert). ### Verified - `bash test/changelog-assembled.test.sh` — **34 passed, 0 failed** - `bash test/run.sh` — **17/17 files green** - `shellcheck -x actions/changelog-assembled/changelog-assembled.sh` — clean - GitHub checks on this head — all required jobs green (`test`, `self-guards`, action/release exercises, labels) No blocking issues. Non-blocking: none worth holding the round for.
codex-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-24 09:37:22 +00:00
codex-bot-andresmgsl (Migrated from github.com) left a comment

The implementation satisfies #116 at 185fc39b964118c35c72e2cc69c44d8730acfc45. It reconstructs the merge-base fragment set, replays the shared assembler, compares the extracted HEAD section body byte-for-byte, reports unified differences, rejects surviving fragments and missing sections, and preserves the specified NOTICE/fail-closed applicability behavior. The action wiring uses the required inputs and CI runs it beside the existing changelog guards with full history; the behavior change also has an Unreleased entry.

Verified locally:

  • bash test/run.sh — 17/17 test files passed, including all 34 changelog-assembled checks and the three-guard interaction cases.
  • bash .github/scripts/shellcheck-all.sh — 37 tracked scripts clean.
  • bash .github/scripts/actionlint-all.sh — 6 workflow/action files clean.
  • git diff --check 6ec9aa24fac2d93324b02e8a7a5a54c6e2612265...HEAD — clean.

GitHub's current check runs for this head are complete and green (with the expected conditionally skipped release jobs). No blocking findings.

The implementation satisfies #116 at `185fc39b964118c35c72e2cc69c44d8730acfc45`. It reconstructs the merge-base fragment set, replays the shared assembler, compares the extracted HEAD section body byte-for-byte, reports unified differences, rejects surviving fragments and missing sections, and preserves the specified NOTICE/fail-closed applicability behavior. The action wiring uses the required inputs and CI runs it beside the existing changelog guards with full history; the behavior change also has an Unreleased entry. Verified locally: - `bash test/run.sh` — 17/17 test files passed, including all 34 `changelog-assembled` checks and the three-guard interaction cases. - `bash .github/scripts/shellcheck-all.sh` — 37 tracked scripts clean. - `bash .github/scripts/actionlint-all.sh` — 6 workflow/action files clean. - `git diff --check 6ec9aa24fac2d93324b02e8a7a5a54c6e2612265...HEAD` — clean. GitHub's current check runs for this head are complete and green (with the expected conditionally skipped release jobs). No blocking findings.
kimi-bot-andresmgsl commented 2026-07-24 09:49:37 +00:00 (Migrated from github.com)

🔎 reviewing head 185fc39b96

🔎 reviewing head 185fc39b964118c35c72e2cc69c44d8730acfc45
kimi-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-24 10:04:31 +00:00
kimi-bot-andresmgsl (Migrated from github.com) left a comment

Review: PR #124 — actions/changelog-assembled (head 185fc39)

Verdict: APPROVE

Reviewed against Closes #116 in a detached worktree at 185fc39b964118c35c72e2cc69c44d8730acfc45 (merge base 6ec9aa2).

Acceptance criteria (issue #116), one by one

All ten are exercised by rows in test/changelog-assembled.test.sh, which I ran: 34/34 pass.

  • Faithful ceremony PR → green: faithful flat and faithful grouped rows pass.
  • Dropped fragment, entry absent → red and the diff names the missing entry: the dropped-entry diff names the missing entry passes; the survivor row also lists changelog.d/9.md by path.
  • Deleted fragment whose entry never landed → red: a deleted fragment whose entry never landed fails passes.
  • Hand-edited word → red with a unified diff: rows assert +++, the edited word, and the "never to hand-edit" guidance — pass.
  • Re-ordered entries → red: re-ordered entries fail passes (the suite hand-builds the section with 9 before 12).
  • Fragment still present at HEAD → red, listing it: STILL PRESENT rows pass, both when the entry is missing and when it is present.
  • Ordinary -dev PR → green NOTICE whatever it does to changelog.d/: three rows (add / no-op / delete) pass.
  • Legacy repo (no changelog.d/ at the merge base) → green NOTICE: passes, on a release tree.
  • No merge base + strict → red; skip only when strict is off: both directions pass, for both an unresolvable ref and a non-git tree, and the strict failure names fetch-depth: 0 as the fix.
  • Date not compared as prose: the old-date row stamps 2020-01-01 and stays green; the mechanism is sound — --check prints the body only and changelog_section extracts below the heading, so no heading (or date) ever enters the diff.

The issue's headline interaction row is in the suite and passes: on the dropped-entry tree, changelog-armed and changelog-monotonic stay green and this guard is the only red.

Issue tasks

  • changelog-assembled.sh, action.yml, the test file, and the ci.yml step (with fetch-depth: 0 already present) are all present. The action.yml inputs (base-ref, changelog, fragments-dir, version-source, strict) mirror the sibling guards' names and defaults exactly — I diffed them against changelog-monotonic/action.yml by hand, including the strict default "1" in the action vs "0" in the script.
  • The docs row is explicitly deferred to #117 by the issue itself — correctly absent here.

What I ran

  • bash test/changelog-assembled.test.sh — 34/34 pass.
  • bash test/run.sh — the new suite is green; 7 pre-existing files fail identically at the merge base (verified by re-running the same suites on main at 6ec9aa2: version 3, facts 2, drill-recorded 2, changelog-armed 2, issueflow-reconcile 11, ruling 43 failures). All are environmental on this box (no node/npm for the package-json cases; gh-mock-dependent reconcile suites), not caused by this PR.
  • The exact CI sweep scripts, with shellcheck 0.10.0 and actionlint 1.7.7 fetched locally: .github/scripts/shellcheck-all.sh (37 tracked scripts) and .github/scripts/actionlint-all.sh (6 workflows) — both clean. (actionlint against action.yml directly misfires, which is why the sweep deliberately lints workflows only.)
  • An independent hand-built two-commit repo (base with two fragments + -dev version, HEAD with the real assembler's write-mode edit): green, exit=0. I also confirmed the trailing blank line between the stamped body and the next heading does not false-positive — command substitution strips trailing newlines on both sides of the diff symmetrically.

Adversarial probes beyond the suite

  • A stray notes.txt in changelog.d/ at the merge base: the replay extracts it (strays included) and the run goes red with the assembler's own refusal — exactly what a real ceremony over that tree would have done. Fail-closed in the right direction.
  • A repo-prefixed fragment (rig-66.md) consumed by a faithful ceremony: green.
  • Symlink or tab-named paths at the base would mis-materialize in the replay, but both directions degrade to red (a failed git show under set -e, or a byte diff), never to a false green. Not worth changing.

Conventions

set -euo pipefail in the executable, set -u in the test, mawk-compatible awk (ran under mawk 1.3.4), whole-version $2 == ver matching throughout, one CHANGELOG.md line under ## Unreleased, and the header comment carries the why (#116, #112, #114) including why this cannot live in changelog-armed. The guard is its own file with its own driving test, per the repo's one-piece-of-logic-per-file rule.

Non-blocking nit: the success line and the refusals are all well-worded, but the "already stamped at the merge base" NOTICE fires before any fragment work, so a branch sitting on an un-rearmed release with genuinely new fragment changes also gets the NOTICE — correct per the spec (it is not the ceremony), just worth knowing the NOTICE is doing double duty there. No action needed.

# Review: PR #124 — actions/changelog-assembled (head 185fc39) **Verdict: APPROVE** Reviewed against `Closes #116` in a detached worktree at `185fc39b964118c35c72e2cc69c44d8730acfc45` (merge base `6ec9aa2`). ## Acceptance criteria (issue #116), one by one All ten are exercised by rows in `test/changelog-assembled.test.sh`, which I ran: **34/34 pass**. - Faithful ceremony PR → green: `faithful flat` and `faithful grouped` rows pass. - Dropped fragment, entry absent → red and the diff names the missing entry: `the dropped-entry diff names the missing entry` passes; the survivor row also lists `changelog.d/9.md` by path. - Deleted fragment whose entry never landed → red: `a deleted fragment whose entry never landed fails` passes. - Hand-edited word → red with a unified diff: rows assert `+++`, the edited word, and the "never to hand-edit" guidance — pass. - Re-ordered entries → red: `re-ordered entries fail` passes (the suite hand-builds the section with 9 before 12). - Fragment still present at HEAD → red, listing it: `STILL PRESENT` rows pass, both when the entry is missing and when it is present. - Ordinary `-dev` PR → green NOTICE whatever it does to `changelog.d/`: three rows (add / no-op / delete) pass. - Legacy repo (no `changelog.d/` at the merge base) → green NOTICE: passes, on a release tree. - No merge base + strict → red; skip only when strict is off: both directions pass, for both an unresolvable ref and a non-git tree, and the strict failure names `fetch-depth: 0` as the fix. - Date not compared as prose: the `old-date` row stamps `2020-01-01` and stays green; the mechanism is sound — `--check` prints the body only and `changelog_section` extracts below the heading, so no heading (or date) ever enters the diff. The issue's headline interaction row is in the suite and passes: on the dropped-entry tree, `changelog-armed` and `changelog-monotonic` stay green and this guard is the only red. ## Issue tasks - `changelog-assembled.sh`, `action.yml`, the test file, and the `ci.yml` step (with `fetch-depth: 0` already present) are all present. The `action.yml` inputs (`base-ref`, `changelog`, `fragments-dir`, `version-source`, `strict`) mirror the sibling guards' names and defaults exactly — I diffed them against `changelog-monotonic/action.yml` by hand, including the strict default "1" in the action vs "0" in the script. - The docs row is explicitly deferred to #117 by the issue itself — correctly absent here. ## What I ran - `bash test/changelog-assembled.test.sh` — 34/34 pass. - `bash test/run.sh` — the new suite is green; 7 pre-existing files fail identically at the merge base (verified by re-running the same suites on `main` at `6ec9aa2`: version 3, facts 2, drill-recorded 2, changelog-armed 2, issueflow-reconcile 11, ruling 43 failures). All are environmental on this box (no `node`/`npm` for the package-json cases; gh-mock-dependent reconcile suites), not caused by this PR. - The exact CI sweep scripts, with shellcheck 0.10.0 and actionlint 1.7.7 fetched locally: `.github/scripts/shellcheck-all.sh` (37 tracked scripts) and `.github/scripts/actionlint-all.sh` (6 workflows) — both clean. (actionlint against `action.yml` directly misfires, which is why the sweep deliberately lints workflows only.) - An independent hand-built two-commit repo (base with two fragments + `-dev` version, HEAD with the real assembler's write-mode edit): green, `exit=0`. I also confirmed the trailing blank line between the stamped body and the next heading does not false-positive — command substitution strips trailing newlines on both sides of the diff symmetrically. ## Adversarial probes beyond the suite - A stray `notes.txt` in `changelog.d/` at the merge base: the replay extracts it (strays included) and the run goes red with the assembler's own refusal — exactly what a real ceremony over that tree would have done. Fail-closed in the right direction. - A repo-prefixed fragment (`rig-66.md`) consumed by a faithful ceremony: green. - Symlink or tab-named paths at the base would mis-materialize in the replay, but both directions degrade to red (a failed `git show` under `set -e`, or a byte diff), never to a false green. Not worth changing. ## Conventions `set -euo pipefail` in the executable, `set -u` in the test, mawk-compatible awk (ran under mawk 1.3.4), whole-version `$2 == ver` matching throughout, one CHANGELOG.md line under `## Unreleased`, and the header comment carries the why (#116, #112, #114) including why this cannot live in `changelog-armed`. The guard is its own file with its own driving test, per the repo's one-piece-of-logic-per-file rule. Non-blocking nit: the success line and the refusals are all well-worded, but the "already stamped at the merge base" NOTICE fires before any fragment work, so a branch sitting on an un-rearmed release with genuinely new fragment changes also gets the NOTICE — correct per the spec (it is not the ceremony), just worth knowing the NOTICE is doing double duty there. No action needed.
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: heavy-duty/ceremony#124
No description provided.