Skip to content

feat(changelog): PR numbers as source of truth — stamp at merge + verify in CI (0203) - #192

Merged
crs48 merged 5 commits into
mainfrom
feat/changelog-pr-stamp-at-merge
Jun 18, 2026
Merged

feat(changelog): PR numbers as source of truth — stamp at merge + verify in CI (0203)#192
crs48 merged 5 commits into
mainfrom
feat/changelog-pr-stamp-at-merge

Conversation

@crs48

@crs48 crs48 commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Implements exploration 0203 (docs/explorations/0203_[_]_CHANGELOG_PR_NUMBERS_AS_SOURCE_OF_TRUTH.md), the successor to 0202.

Why

The changelog isn't actually broken — production resolves every entry's PR number. But the number is a deploy-time side-channel: it's absent from committed source (8/34 fragments had no pr in the repo), absent from PR CI and previews, and self-heals only at deploy. So it keeps looking broken (kebab-case "branch-name" fragments with no number), and a malformed fragment would fail the production deploy — freezing the whole live changelog — rather than the PR.

This makes the PR number a first-class, visible, verified property of the fragment, with no second human commit.

What

  • C — stamp at merge (.github/workflows/stamp-pr-number.yml): when a merged PR touched a changelog fragment, write the PR number (straight from the merge event — no heuristics) into any fragment lacking one and commit it back to main with [skip ci]. The author still commits once; the bot does the "second commit". The merge's own deploy already published the number via resolve-prs, so [skip ci] avoids a redundant deploy. Degrades to a ::warning if the token can't push to a protected main.
  • D — verify on PRs (ci.yml changelog job): runs validate-changelog.ts + a new resolve-prs.mjs --check dry run. A malformed or numberless-and-unresolvable entry now fails the PR, not the prod deploy. Lightweight (plain Node + on-the-fly tsx, no workspace install).
  • A — net kept: resolve-prs.mjs still fills any gap at deploy (direct pushes, etc.).
  • Hardening: loadPrGallery bounds its build-time fetch with AbortSignal.timeout(5s) so a hung host can't stall the deploy.

Verified locally

  • --check: 28 baked, 6 resolve via git subject, exit 0, zero files written.
  • --check with CHANGELOG_BASE_REF: a PR-new unresolvable fragment → ::error + exit 1.
  • validate-changelog.ts: exit 1 on a malformed fragment.
  • Both workflow YAMLs parse; 35 fragments valid.

Follow-up (ops, can't be done in-PR)

  • Provision CHANGELOG_BOT_TOKEN (GitHub App / PAT) with main ruleset bypass so C persists to source. Until then C degrades to a warning and the deploy-time net keeps the live site correct.
  • stamp-pr-number.yml runs from the base branch's copy, so it can't stamp its own introducing PR — this PR's fragment is filled by the deploy-time net; the stamp takes effect for subsequent PRs.

🤖 Generated with Claude Code

xNet Test and others added 5 commits June 18, 2026 07:21
Successor to 0202. Diagnoses why the changelog keeps *seeming* broken even
though production resolves 35/35: the PR number is a deploy-time side-channel
(absent from committed source, absent from PR CI/previews, self-healing only at
deploy). Recommends stamping the number at merge from the event payload (bot
write-back, no human second commit) + a PR-CI validate/dry-resolve so an entry
can never silently look broken, with deploy-time resolve-prs kept as the net.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…203)

- resolve-prs.mjs gains a --check mode: dry-run resolution for PR CI that never
  writes and exits non-zero when a fragment introduced by the PR can't be
  resolved to a PR number (pre-existing gaps stay warnings). Scoped via
  CHANGELOG_BASE_REF.
- loadPrGallery() bounds its build-time fetch with AbortSignal.timeout(5s) so a
  slow/hung visuals host can't stall the production deploy.
- changelog.ts header documents the stamp-at-merge + dry-run flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- New stamp-pr-number.yml: on a merged PR that touched a changelog fragment,
  write the PR number (from the merge event) into any fragment lacking one and
  commit it back to main with [skip ci]. Degrades to a warning if the token
  can't push to a protected main; resolve-prs.mjs stays the deploy-time net.
- ci.yml gains a lightweight `changelog` PR job: validate-changelog + resolve-prs
  --check, so a malformed/numberless entry fails the PR, not the prod deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@crs48
crs48 temporarily deployed to pr-192 June 18, 2026 14:33 — with GitHub Actions Inactive
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Preview removed for PR #192.

github-actions Bot added a commit that referenced this pull request Jun 18, 2026
@crs48
crs48 merged commit d687ad8 into main Jun 18, 2026
18 of 19 checks passed
@crs48
crs48 deleted the feat/changelog-pr-stamp-at-merge branch June 18, 2026 15:00
github-actions Bot added a commit that referenced this pull request Jun 18, 2026
crs48 added a commit that referenced this pull request Jun 18, 2026
Follow-up to #192. Switches `stamp-pr-number.yml` from a long-lived PAT
to a **GitHub App** installation token, the more secure way to let the
merge-time stamp push land in `main`.

## What
- `stamp-pr-number.yml` mints a short-lived token via
`actions/create-github-app-token`, gated on the `CHANGELOG_APP_ID` repo
**variable** (usable in `if`), with `continue-on-error` and a
`secrets.CHANGELOG_BOT_TOKEN || github.token` fallback chain. So it
**never hard-fails**: unconfigured → falls back → existing graceful
`::warning`; configured → token can bypass the `main` ruleset and the PR
number persists to source.
- Doc + in-file setup notes updated.

## One-time ops to activate (after merge)
1. Create a GitHub App with repository **Contents: write**; install it
on `crs48/xNet`.
2. Set repo **variable** `CHANGELOG_APP_ID` and **secret**
`CHANGELOG_APP_PRIVATE_KEY`.
3. Add the App to the **`main` ruleset bypass list**.

Until then, behaviour is unchanged (deploy-time `resolve-prs.mjs` keeps
the live site correct).

No user-facing change → `skip-changelog`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
crs48 added a commit that referenced this pull request Jun 18, 2026
Adds
[`docs/changelog-pr-stamping-setup.md`](https://github.com/crs48/xNet/blob/docs/changelog-stamping-runbook/docs/changelog-pr-stamping-setup.md)
— the one-time GitHub-App setup that activates the merge-time changelog
PR-number stamping shipped in #192/#193:

1. Create a GitHub App (Contents: write), install it on the repo.
2. Set the `CHANGELOG_APP_ID` variable + `CHANGELOG_APP_PRIVATE_KEY`
secret.
3. Add the App to the `main` ruleset bypass list.

Includes verification steps, troubleshooting, and the PAT alternative.
Docs-only → `skip-changelog`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant