Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
56 changes: 10 additions & 46 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
165 changes: 165 additions & 0 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
@@ -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-<V>.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] - <today YYYY-MM-DD>` 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 — <one-line theme>

<2–3 sentence headline: what this release is and why it exists.>

## Highlights
**<Capability>.** <1–3 sentences, name the entry-point types/methods, link the ADR if one exists.>

## ⚠️ Breaking changes (pre-1.0, ADR-019)
1. <What broke — and the one-line migration.>

## 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<prev>...v$VERSION](https://github.com/omahjoub/fanar-java/compare/v<prev>...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).
Loading