diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53891db..420a34e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,6 +72,7 @@ jobs: docs/GRAALVM.md \ docs/JAVA_LIBRARY_BEST_PRACTICES.md \ docs/PROJECT_STATE.md \ + docs/RELEASING.md \ docs/adr/INDEX.md \ docs/adr/019-pre-10-stability-policy.md \ docs/adr/020-spring-boot-4-starter.md \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6db2f86..12ead66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ may break public API until 1.0.0 ships. ## [Unreleased] +## [0.2.0] - 2026-08-06 + +Full parity with the 2026-08 Fanar spec: madhab-aware `Fanar-Sadiq-2`, custom personas, +streamed + emotional TTS, a rich voice catalogue, culturally-aligned image prompt revision, +and typed envelope-code error routing. Pre-1.0 ([ADR-019](docs/adr/019-pre-10-stability-policy.md)): +this release contains three breaking changes, marked below. Not yet on Maven Central — install +via `./mvnw install` from a clone, or download the artifacts attached to this release. + ### Added - **`fanar-core`** — streaming TTS: `AudioClient.speechStream(request)` returns @@ -123,5 +131,6 @@ from a clone, or download the artifacts attached to this release. end-to-end since they shape the prompt text. - **Fanar `stop` parameter** — silently dropped server-side; documented in tests. -[Unreleased]: https://github.com/omahjoub/fanar-java/compare/v0.1.0...HEAD +[Unreleased]: https://github.com/omahjoub/fanar-java/compare/v0.2.0...HEAD +[0.2.0]: https://github.com/omahjoub/fanar-java/releases/tag/v0.2.0 [0.1.0]: https://github.com/omahjoub/fanar-java/releases/tag/v0.1.0 diff --git a/README.md b/README.md index 2566ef9..35b4542 100644 --- a/README.md +++ b/README.md @@ -133,4 +133,5 @@ ChatClient chatClient(ChatModel model, ChatMemory memory) { // Spring AI typ - [ADRs](docs/adr/INDEX.md) — non-obvious design decisions. - [Library best practices](docs/JAVA_LIBRARY_BEST_PRACTICES.md) — internal hygiene rules. - [Contributing](docs/CONTRIBUTING.md) — workflow, conventions. +- [Releasing](docs/RELEASING.md) — maintainer runbook for cutting a release. - [Fanar OpenAPI spec](api-spec/openapi.json) — the wire contract we model (normative; [YAML twin](api-spec/openapi.yaml) provided for convenience). diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 90a097a..62cd71f 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -122,52 +122,16 @@ not on the threshold. ## Releasing a new version -We use the **release-and-bump** flow: every tagged commit's `pom.xml` matches the release version -exactly — no `-SNAPSHOT` suffix appears in published artifacts. The release workflow guards this -by failing fast if the pom doesn't match the resolved version. - -Step by step: - -1. **Cut a release branch** from `main`: - ```bash - git switch -c release/0.1.0 main - ./mvnw -B versions:set -DnewVersion=0.1.0 -DgenerateBackupPoms=false - git commit -am "release: 0.1.0" - git push -u origin release/0.1.0 - ``` - -2. **Dry-run the release workflow** from the release branch: - `Actions → Release → Run workflow → Branch: release/0.1.0, version: 0.1.0, dry_run: true`. - The workflow builds, verifies, stages 10 consumable artifacts (9 library jars + BOM `.pom`), - uploads them to the workflow run page, and stops short of creating a GitHub Release. Inspect - the artifact list — every filename should end in `0.1.0.jar` / `0.1.0.pom`. (Sample apps are - not shipped — they're cloneable demos, not pinnable artifacts.) - -3. **Open a PR** `release/0.1.0 → main`. Review checks the version bump and changelog. - -4. **Merge** — squash or merge-commit, doesn't matter. The release branch only has one commit - (the version bump), so both produce equivalent history on main. - -5. **Tag the merged commit**: - ```bash - git switch main && git pull - git tag -a v0.1.0 -m "Release 0.1.0" - git push origin v0.1.0 - ``` - Tag-push fires the release workflow again, this time creating the GitHub Release with the - 12 artifacts attached and auto-generated PR notes. - -6. **Bump main back to the next snapshot** — mandatory follow-up: - ```bash - git switch -c bump/0.2.0-SNAPSHOT main - ./mvnw -B versions:set -DnewVersion=0.2.0-SNAPSHOT -DgenerateBackupPoms=false - git commit -am "build: bump to 0.2.0-SNAPSHOT" - ``` - PR → main. Without this, every subsequent dev build still says `0.1.0` and trying to release - `0.1.0` again will fail noisily. - -The `Verify pom version matches release version` step in `release.yml` enforces step 1 — if you -forget the `versions:set` commit, the workflow fails with a one-line fix instruction. +We use the **release-and-bump** flow (Pattern B): every tagged commit's `pom.xml` matches the +release version exactly — no `-SNAPSHOT` suffix appears in published artifacts — and `main` is +bumped back to the next snapshot immediately after tagging. The `release.yml` workflow enforces +the version invariant by failing fast if the pom doesn't match the resolved version, and only a +tag push creates a real release (`workflow_dispatch` is for dry runs). + +The full replayable checklist — preflight, changelog finalization, release branch, dry run, +tag, bump-back, troubleshooting, and the release-notes template — lives in +**[docs/RELEASING.md](RELEASING.md)**. Follow it verbatim; when a release surprises you, fix +the runbook in the same PR as the fix. ## Where to ask questions diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..5f34f56 --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,165 @@ +# Releasing — maintainer runbook + +The replayable, copy-paste release process. `CONTRIBUTING.md` links here; this file is the +source of truth for cutting a release. When something goes wrong during a release, fix the +process **here** in the same PR as the fix — that's how the next release avoids the same hole. + +## The invariants (why the process looks like this) + +- **Pattern B ("release-and-bump")**: every tagged commit's `pom.xml` carries the exact release + version — no `-SNAPSHOT` ever ships. `release.yml` enforces this with the + *Verify pom version matches release version* step, which fails with a one-line fix + instruction if the release branch wasn't prepared. +- **Only a tag push creates a real release.** `workflow_dispatch` exists for **dry runs** — + running it with `dry_run: false` would create a release (and tag) from whatever ref you + picked, bypassing the reviewed-main-commit guarantee. Don't. +- **The bump-back is part of the release**, not an afterthought. Until it merges, every dev + build claims the released version, and re-releasing that version fails noisily. +- **What ships**: exactly 10 artifacts — 9 library jars (`fanar-core`, `fanar-json-jackson2`, + `fanar-json-jackson3`, `fanar-obs-slf4j`, `fanar-obs-otel`, `fanar-obs-micrometer`, + `fanar-interceptor-logging`, `fanar-spring-boot-4-starter`, `fanar-spring-ai-starter`) plus + `fanar-java-bom-.pom`. Sample apps and test modules are deliberately excluded. + +## The checklist + +Set these two variables once and paste commands as-is: + +```bash +VERSION=0.2.0 # the version being released +NEXT=0.3.0-SNAPSHOT # main's next development version +``` + +### 0 — Preflight (on `main`) + +- [ ] CI green on `main` (Java 21 + 25 matrix, JaCoCo 100 %, doclint, dep-analyze). +- [ ] Live e2e run performed (`FANAR_API_KEY=… ./mvnw -pl e2e -am verify`) and every failure + triaged: known-gated reds (documented in the test Javadocs) are expected; anything new + gets fixed or explicitly accepted **before** releasing. +- [ ] GraalVM native smoke green (PR-time workflow on the last merged PR, or run the + `graalvm.yml` dispatch). +- [ ] `docs/PROJECT_STATE.md` reflects reality (its cadence rule: updated in the same PR as + whatever moved). +- [ ] `CHANGELOG.md` `## [Unreleased]` is complete — every shipped change present, breaking + changes marked **Breaking** with migration notes (ADR-019 requires the callout). +- [ ] Working tree clean; no stray untracked files that could ride along in commits. + +### 1 — Finalize the changelog + +Still on `main` (the edit is committed on the release branch in step 2): + +- [ ] Rename `## [Unreleased]` → `## [$VERSION] - ` and add a 2–4 line intro + paragraph (what the release is; pre-1.0 caveat; install note). +- [ ] Insert a fresh empty `## [Unreleased]` above it. +- [ ] Update the link refs at the bottom: + + ``` + [Unreleased]: https://github.com/omahjoub/fanar-java/compare/v$VERSION...HEAD + [$VERSION]: https://github.com/omahjoub/fanar-java/releases/tag/v$VERSION + ``` + +### 2 — Cut the release branch + +```bash +git switch -c release/$VERSION main +./mvnw -B versions:set -DnewVersion=$VERSION -DgenerateBackupPoms=false +git add pom.xml '*/pom.xml' CHANGELOG.md +git commit -m "release: $VERSION" +git push -u origin release/$VERSION +``` + +- [ ] The commit touches only poms + `CHANGELOG.md` (add paths explicitly — never `git add -A`). + +### 3 — Dry-run the release workflow + +GitHub → Actions → **Release** → *Run workflow* → Branch: `release/$VERSION`, +version: `$VERSION`, `dry_run: true`. + +- [ ] *Verify pom version* step passed. +- [ ] Full build green (the release jars are exactly what this build produces). +- [ ] Download the `fanar-java-$VERSION` workflow artifact: exactly 10 files, every name ending + in `$VERSION.jar` / `$VERSION.pom`, no `-SNAPSHOT` anywhere. + +### 4 — Release PR + +- [ ] Open PR `release/$VERSION → main`. Review scope: the version bump + the finalized + changelog section. Nothing else should be in the diff. +- [ ] Merge (squash or merge-commit — one commit either way). + +### 5 — Tag: the actual release trigger + +```bash +git switch main && git pull +git tag -a v$VERSION -m "Release $VERSION" +git push origin v$VERSION +``` + +The tag push fires `release.yml` for real: pom guard → full build → GitHub Release +`Fanar Java SDK v$VERSION` with the 10 artifacts attached and auto-generated PR notes. + +- [ ] Release page exists with all 10 assets. +- [ ] Replace the auto-generated notes with curated notes (template below), keeping the + auto-generated PR list at the bottom if useful. + +### 6 — Bump main back (mandatory) + +```bash +git switch -c bump/$NEXT main +./mvnw -B versions:set -DnewVersion=$NEXT -DgenerateBackupPoms=false +git add pom.xml '*/pom.xml' +git commit -m "build: bump to $NEXT" +git push -u origin bump/$NEXT +``` + +- [ ] PR → main, merge. + +### 7 — Post-release + +- [ ] Update `docs/PROJECT_STATE.md` (move the release from *Planned*, refresh the snapshot + date) — can ride in the bump-back PR. +- [ ] Delete the `release/$VERSION` branch (the tag preserves the commit). +- [ ] Optional smoke: clone at the tag and `./mvnw install`, or resolve an attached jar into a + scratch project. + +## Troubleshooting + +- **Pom-guard failure** ("pom.xml version is 'X-SNAPSHOT' but the release expects 'X'"): the + `versions:set` commit is missing from the ref being released. The workflow's error message + prints the fix; prepare the release branch (step 2) properly and re-run. +- **Release workflow failed after the tag push** (or the tag landed on the wrong commit): while + we are *not* on Maven Central, tags are cheap to redo — delete the GitHub Release if it was + created (`gh release delete v$VERSION`), delete the tag (`git push origin :refs/tags/v$VERSION` + and `git tag -d v$VERSION`), fix, re-tag. **Once artifacts publish to Maven Central this + stops being an option** — Central is immutable; a broken release then becomes a new patch + version, never a re-tag. +- **Dry-run artifacts look wrong** (missing module, stray `-SNAPSHOT`): fix on the release + branch, push, re-run the dispatch. Dry runs are free — iterate there, never on tags. +- **Forgot the bump-back** and the next `versions:set` conflicts or a dev build shipped a + release version somewhere: do the bump-back immediately; it's idempotent. + +## Release-notes template + +```markdown +# $VERSION — + +<2–3 sentence headline: what this release is and why it exists.> + +## Highlights +**.** <1–3 sentences, name the entry-point types/methods, link the ADR if one exists.> + +## ⚠️ Breaking changes (pre-1.0, ADR-019) +1. + +## Install +Not yet on Maven Central: `./mvnw install` from a clone, or use the attached jars +(9 library jars + `fanar-java-bom` for version alignment). Pair `fanar-core` with +`fanar-json-jackson3` (Jackson 3) or `fanar-json-jackson2` (Jackson 2). + +**Full changelog:** [CHANGELOG.md](https://github.com/omahjoub/fanar-java/blob/v$VERSION/CHANGELOG.md) · [v...v$VERSION](https://github.com/omahjoub/fanar-java/compare/v...v$VERSION) +``` + +## When Maven Central arrives + +This runbook covers the GitHub-Release era. The Central Portal flow (namespace verification, +GPG signing, `central-publishing-maven-plugin`, staging validation) will slot in between steps +3 and 5 — extend this file in the same PR that adds the publishing machinery, and revisit the +re-tag guidance above (Central is immutable).