The first version of qbit-release-notes: customer-facing release notes for every release of a QQQ app (owner request 2026-10-15). Design: docs/SPEC.md.
Tables (release_notes_release, _item, _settings, _user_state): standard QQQ tables with a Liquibase changelog. Items reference their release by foreign key (ON DELETE CASCADE), which satisfies kof22's foreign-key guardrail. Settings are seeded insert-only with require approval, all users and notify.
Detection: runs once per version across pods and restarts, guarded by a unique (app, version) key. Pods that lose the insert race re-read and stop. Development versions are ignored. The host calls ReleaseNotesRuntime.of(qInstance).detectInBackground() after startup.
Sources (ReleaseSource SPI):
GitHubReleaseSource, TAG mode: releases after the previous version up to this one.
GitHubReleaseSource, WINDOW mode: releases published since the previous release, for a separately released frontend.
Both modes collect the PRs linked from the release bodies. The token is read from the environment at call time.
ComponentVersionsSource: qbit and platform versions that changed.
Drafting (ReleaseNotesDrafter SPI): a deterministic RuleBasedDrafter.
It uses Release notes: text written by authors, cleans PR titles and categorises from Conventional Commit types and labels.
It excludes internal work: chore/ci/build/test/refactor/docs, dependency and infrastructure changes, and skip labels.
ReleaseNotesSanitizer blocks PR and issue numbers, GitHub links, commit hashes, ticket keys, e-mail and IP addresses, internal hosts and secret-looking text.
Nothing is published automatically unless settings say AUTO_PUBLISH.
Actions: Publish, Skip and Redraft are record actions with the standard review and result screens; Check for new release is a table action. None of them are in the nav.
Publishing: sends one NotificationSignal through QAlerts.notify.
Category RELEASE, severity INFO, audience all users or a permission.
Link /app/whats-new?release=<id>. Source key release-notes:<app>:<version>, so a resend updates the existing notification instead of duplicating it.
Delivery honours each user's existing per-category notification preferences (mute, email opt-in) in qbit-alerts. No dependency on qbit-alerts.
UI API (release-notes 1.0.0): GET /qbit/release-notes/releases, GET /qbit/release-notes/summary and POST /qbit/release-notes/seen.
Only customer fields are returned.
The committed OpenAPI is covered by a contract test.
Help bundle: covers the app, tables, fields and processes.
Tests
./mvnw verify: 25 tests pass and the JaCoCo gate is met.
Once-per-version detection with 8 concurrent "pods".
The approval flow (publish, notification, What's new with the per-user unread indicator, mark seen).
Auto-publish, including auto-skip when a release has no customer-visible change.
Permission audiences.
Internal references blocking publish.
Redraft keeping hand-written items.
Drafting rules and the sanitizer.
The GitHub source against recorded payloads, including a stale higher tag and draft releases.
Changelog runs twice; help coverage; UI API contract.
After merge, the release captain cuts and tags 0.1.0-RC.1 (no release:rc label: that flow currently publishes without a tag or GitHub release).
## What
The first version of `qbit-release-notes`: customer-facing release notes for every release of a QQQ app (owner request 2026-10-15). Design: `docs/SPEC.md`.
- **Tables** (`release_notes_release`, `_item`, `_settings`, `_user_state`): standard QQQ tables with a Liquibase changelog. Items reference their release by foreign key (`ON DELETE CASCADE`), which satisfies kof22's foreign-key guardrail. Settings are seeded insert-only with require approval, all users and notify.
- **Detection**: runs once per version across pods and restarts, guarded by a unique (app, version) key. Pods that lose the insert race re-read and stop. Development versions are ignored. The host calls `ReleaseNotesRuntime.of(qInstance).detectInBackground()` after startup.
- **Sources** (`ReleaseSource` SPI):
- `GitHubReleaseSource`, TAG mode: releases after the previous version up to this one.
- `GitHubReleaseSource`, WINDOW mode: releases published since the previous release, for a separately released frontend.
- Both modes collect the PRs linked from the release bodies. The token is read from the environment at call time.
- `ComponentVersionsSource`: qbit and platform versions that changed.
- **Drafting** (`ReleaseNotesDrafter` SPI): a deterministic `RuleBasedDrafter`.
- It uses `Release notes:` text written by authors, cleans PR titles and categorises from Conventional Commit types and labels.
- It excludes internal work: chore/ci/build/test/refactor/docs, dependency and infrastructure changes, and skip labels.
- `ReleaseNotesSanitizer` blocks PR and issue numbers, GitHub links, commit hashes, ticket keys, e-mail and IP addresses, internal hosts and secret-looking text.
- Nothing is published automatically unless settings say `AUTO_PUBLISH`.
- **Actions**: Publish, Skip and Redraft are record actions with the standard review and result screens; Check for new release is a table action. None of them are in the nav.
- **Publishing**: sends one `NotificationSignal` through `QAlerts.notify`.
- Category `RELEASE`, severity INFO, audience all users or a permission.
- Link `/app/whats-new?release=<id>`. Source key `release-notes:<app>:<version>`, so a resend updates the existing notification instead of duplicating it.
- Delivery honours each user's existing per-category notification preferences (mute, email opt-in) in qbit-alerts. No dependency on qbit-alerts.
- **UI API** (`release-notes` 1.0.0): `GET /qbit/release-notes/releases`, `GET /qbit/release-notes/summary` and `POST /qbit/release-notes/seen`.
- Only customer fields are returned.
- The committed OpenAPI is covered by a contract test.
- **Help bundle**: covers the app, tables, fields and processes.
## Tests
`./mvnw verify`: 25 tests pass and the JaCoCo gate is met.
- Once-per-version detection with 8 concurrent "pods".
- The approval flow (publish, notification, What's new with the per-user unread indicator, mark seen).
- Auto-publish, including auto-skip when a release has no customer-visible change.
- Permission audiences.
- Internal references blocking publish.
- Redraft keeping hand-written items.
- Drafting rules and the sanitizer.
- The GitHub source against recorded payloads, including a stale higher tag and draft releases.
- Changelog runs twice; help coverage; UI API contract.
After merge, the release captain cuts and tags 0.1.0-RC.1 (no `release:rc` label: that flow currently publishes without a tag or GitHub release).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
The first version of
qbit-release-notes: customer-facing release notes for every release of a QQQ app (owner request 2026-10-15). Design:docs/SPEC.md.release_notes_release,_item,_settings,_user_state): standard QQQ tables with a Liquibase changelog. Items reference their release by foreign key (ON DELETE CASCADE), which satisfies kof22's foreign-key guardrail. Settings are seeded insert-only with require approval, all users and notify.ReleaseNotesRuntime.of(qInstance).detectInBackground()after startup.ReleaseSourceSPI):GitHubReleaseSource, TAG mode: releases after the previous version up to this one.GitHubReleaseSource, WINDOW mode: releases published since the previous release, for a separately released frontend.ComponentVersionsSource: qbit and platform versions that changed.ReleaseNotesDrafterSPI): a deterministicRuleBasedDrafter.Release notes:text written by authors, cleans PR titles and categorises from Conventional Commit types and labels.ReleaseNotesSanitizerblocks PR and issue numbers, GitHub links, commit hashes, ticket keys, e-mail and IP addresses, internal hosts and secret-looking text.AUTO_PUBLISH.NotificationSignalthroughQAlerts.notify.RELEASE, severity INFO, audience all users or a permission./app/whats-new?release=<id>. Source keyrelease-notes:<app>:<version>, so a resend updates the existing notification instead of duplicating it.release-notes1.0.0):GET /qbit/release-notes/releases,GET /qbit/release-notes/summaryandPOST /qbit/release-notes/seen.Tests
./mvnw verify: 25 tests pass and the JaCoCo gate is met.After merge, the release captain cuts and tags 0.1.0-RC.1 (no
release:rclabel: that flow currently publishes without a tag or GitHub release).🤖 Generated with Claude Code