[v0.1/Release] Cut and publish v0.1.0 from the gated revision #31

Open
opened 2026-09-03 16:18:09 +09:00 by natsukium · 2 comments
Owner

Parent: #12 (Release completion). Depends on every P0 and P1 child tracked there.

Why

The first public contract is defined by the exact source revision, generated schemas, documentation, and artifact that are tagged together. Passing gates on a nearby revision is not sufficient.

Scope

  • Confirm every P0/P1 child is closed and accepted decisions landed in the owning reference/explanation twins.
  • Complete the manual/schema/help/completions/man/skills/felis/CHANGELOG.md cascade.
  • Set the inherited workspace version and finalize the Unreleased changelog section.
  • Run all release gates on a clean exact revision.
  • Create the annotated v0.1.0 tag and publish immutable references and the Linux artifact.

Acceptance criteria

  • The release commit is clean and all generated artifacts are current.
  • just check, schema/proto/compatibility gates, and the supported Linux smoke/publish gate pass on that commit.
  • The tag points exactly to the gated commit.
  • Published source, schemas, docs, and binary report the same build identity.
  • #12 can be closed without any unchecked P0/P1 or release-completion item.
Parent: #12 (Release completion). Depends on every P0 and P1 child tracked there. ## Why The first public contract is defined by the exact source revision, generated schemas, documentation, and artifact that are tagged together. Passing gates on a nearby revision is not sufficient. ## Scope - Confirm every P0/P1 child is closed and accepted decisions landed in the owning reference/explanation twins. - Complete the manual/schema/help/completions/man/`skills/felis`/`CHANGELOG.md` cascade. - Set the inherited workspace version and finalize the Unreleased changelog section. - Run all release gates on a clean exact revision. - Create the annotated `v0.1.0` tag and publish immutable references and the Linux artifact. ## Acceptance criteria - [ ] The release commit is clean and all generated artifacts are current. - [ ] `just check`, schema/proto/compatibility gates, and the supported Linux smoke/publish gate pass on that commit. - [ ] The tag points exactly to the gated commit. - [ ] Published source, schemas, docs, and binary report the same build identity. - [ ] #12 can be closed without any unchecked P0/P1 or release-completion item.
Author
Owner

Triage plan (2026-09-03)

Source-grounded triage against main at 69076d42, reviewed through seven rounds of an independent reviewer (pi sol/luna) until it passed with no findings. The dependency order that supersedes the tracker's is posted on #12. Where a "Review amendments" section below conflicts with an earlier section, the amendment is the decision.

Claim check

The issue is a procedure, not a code claim; its premises check out with two corrections.

  • No tag, single Unreleased section: git tag empty; CHANGELOG.md:14 is the only ## [ header. CHANGELOG.md:11-12 and docs/reference/workspace.md:157-176 describe the pre-release versioning stance the release commit has to close out.
  • "Set the inherited workspace version" — inaccurate. Nothing inherits a version: Cargo.toml:5-14 [workspace.package] has edition, rust-version, license, repository, publish, but no version; each of the eleven crates sets version = "0.1.0" itself (crates/*/Cargo.toml:3). nix/package.nix:57-59 reads the version from crates/felis-cli/Cargo.toml only. So either #31 moves the crates to version.workspace = true (a small mechanical change that makes the "set the version" step one line) or the wording changes to "set every crate's version". Recommend the former, done before the release commit so the release diff is only the number.
  • Gates the issue lists vs gates that exist: just check (justfile: fmt-check + clippy + nextest + deny) mirrors pr.yml:31-43; the schema gate is pr.yml's --all-features nextest run through the schema sync test (crates/felis-client-core/src/config.rs:2476, just schema); the proto codegen gate is pr.yml:45-63; the Linux smoke/publish gate is pr.yml:366-477. The "compatibility gate" the issue names does not exist yet (see #19 report: the pre-commit hook is skipped in the sandbox), and the tag trigger does not exist (see #18). Both are prerequisites, as the tracker says.
  • "Published source, schemas, docs, and binary report the same build identity": today the binary's identity is a bare short hash (crates/build-common.rs:5-33), the docs package (flake.nix:252-262, .#docs) carries no version, and the schema file (crates/felis-client-core/felis-config.schema.json) has no version stamp. Without #17 this criterion cannot be checked mechanically.
  • Cascade surfaces named (manual/schema/help/completions/man/skills/felis/CHANGELOG.md): help/completions/man are generated from clap at package time (nix/package.nix:120-134), so "current" means the clap definitions are final; the schema is a committed file with a sync test; skills/felis/SKILL.md is hand-maintained and product-shipped (CLAUDE.md "Skills"), so it is the one surface a grep sweep must cover.

Post-snapshot commits (8a82e069, 5a1a49a2, 69076d42) are user-affecting (config key rename, CLI separator) — verify they each have a CHANGELOG.md Unreleased entry during the sweep (I did not verify this; add it to the checklist).

Verdict

accept-with-changes. Keep it as the terminal issue, but (1) replace "inherited workspace version" with the version.workspace = true migration as a pre-step, (2) add the two rehearsal outputs from #18 (green rc tag, red mismatch tag) as acceptance evidence, and (3) split the "cross-repository consistency sweep" into a concrete grep list so it is not open-ended on release day.

Approach

Ordered checklist for the release commit and tag; every step is something the human runs or a CI run URL to record in the issue.

Pre-step (own commit, before the release commit)

  • Cargo.toml: add version = "0.1.0" under [workspace.package]; each crates/*/Cargo.toml:3version.workspace = true. nix/package.nix:59 keeps reading felis-cli's manifest (it resolves through lib.importTOML? No — importTOML on the crate file will now see version = { workspace = true }, not a string). Change it to read the workspace ../Cargo.toml .workspace.package.version. Add a unit-ish check in just release-check (#18) that nix eval --raw .#felis.version equals the tag.
  • docs/reference/workspace.md:167-176 "Versioning": say the crate version is workspace-inherited and floats together (the prose already says "float together").

Release commit (chore(release): v0.1.0)

  1. Confirm #12's P0/P1 list plus the three post-snapshot CLI blockers (#42, #53, #54 — the tracker comments name them) are closed; for each accepted decision, the owning reference/explanation twin cites it (doc-cascade skill grep: issue number and the decision's key term).
  2. Generated surfaces current: just schema (no diff), just proto (no diff), felis __mangen/completions are build-time so nothing to commit; run just check.
  3. CHANGELOG.md: rename ## [Unreleased]## [0.1.0] - 2026-MM-DD, add a fresh empty [Unreleased] above, fix the sentence at :11-12 ("until a first release is tagged"), add the Keep-a-Changelog link footers ([Unreleased]: …/compare/v0.1.0...HEAD, [0.1.0]: …/releases/tag/v0.1.0).
  4. Docs wording that assumes no tag: docs/reference/workspace.md:164-166 ("until the compatibility freeze the dev wire carries no promise"), docs/reference/ipc.md:1685-1687, docs/how-to/update-felis.md:99 ("first tagged release"), docs/how-to/install.md:143,154 and README.md:20,23 (tag-pinned flake refs, per #18). Grep sweep terms: Unreleased, first release, first tag, compatibility freeze, each \main` revision, git+https://git.natsukium.com/natsukium/feliswithout?ref=`.
  5. skills/felis/SKILL.md: any verb/flag added by the P1 CLI issues (#20, #23, #25, #26, #27, #53, #54, and felis version from #17) appears with its --format shape; run the skill's examples against the built binary once.
  6. Support claim: only x86_64-linux in install.md:130-146, workspace.md:276-281, README; Windows stays "CI evidence".
  7. Version: the pre-step made this a one-line edit if the number changes; for 0.1.0 it does not.

Gates on the exact commit

  • Push the release commit to main; wait for pr.yml fully green including linux-publish and proto-compat (#19). Record the run URL.
  • Locally: just release-check v0.1.0 (#18) on a clean tree (git status --porcelain empty; nix flake metadata --json | jq .dirtyRevision null); nix build .#felis && ./result/bin/felis --version prints felis 0.1.0 (<rev> ) with no -dirty and <rev> = git rev-parse HEAD.
  • Rehearsal: push v0.1.0-rc.1 (annotated) at the release commit; release.yml green, prerelease created. Then delete the rc release/tag or leave it marked prerelease — decide and document in workspace.md.

Tag and publish

  • git tag -a v0.1.0 -m "felis 0.1.0" on the gated commit; git push origin v0.1.0 (never --force, per CLAUDE.md).
  • release.yml runs: verify-tag, build, smoke, niks3 push, Forgejo release with schema + proto + Linux store-path/narHash + CHANGELOG body.
  • Post-checks: nix run 'git+https://git.natsukium.com/natsukium/felis?ref=refs/tags/v0.1.0' -- --version on a machine with the cache configured substitutes (no compile) and reports the tag identity; felis version --format json (#17) against a daemon started from that build shows all three identities equal; the docs package (.#docs) built from the tag, if a version stamp was added, matches.
  • Close #31, then #12 (tracker) with the run URLs.

Tests that prove it

Release is proven by the CI runs and the post-checks above; the only code-level test this issue adds is the package.nix version-source change being covered by just release-check's nix eval comparison and by checks.home-manager-module (flake.nix, evaluates the package).

Dependencies

Everything in #12's P0/P1 list plus #42/#53/#54 (#53/#54 are already landed at 5a1a49a2; #42 is open). Direct blockers for this issue's own steps: #17 (identity to compare), #18 (tag workflow + just release-check), #19 (the compatibility gate the acceptance criteria name), #30 (the wire baseline must be final before the tag freezes it — "after every other wire edit" in the tracker order), #29 (golden schemas/conversations frozen at the tag). The tracker's recommended order still holds; #31 is last.

Risk/effort

S for the commit itself; M for the sweep, since it is the one moment every hand-maintained mirror (CHANGELOG, skill, install docs, support matrix) has to agree and no test covers prose. Main risk: tagging a commit whose main run was green but whose docs still say "each main revision" or whose skill lists a verb #23 renamed — the grep list above is the mitigation. Secondary: the version.workspace = true migration changing nix/package.nix's version lookup; do it as its own commit and let pr.yml's linux-smoke (nix build .#felis) prove it.

Labels

Keep priority/P0, release/v0.1.0. Edit the scope line "Set the inherited workspace version" to "Move crate versions to version.workspace = true, then set the workspace version", and add acceptance items: "rc-tag rehearsal run recorded" and "post-publish nix run …?ref=refs/tags/v0.1.0 -- --version substitutes from the cache and reports the tag identity".

Review amendments (round 2)

  • The local pre-tag check and the post-publish check compare felis version --format json .cli.revision (full) with git rev-parse HEAD and .cli.dirty == false; felis --version is a human smoke line and is not compared.

Review amendments (round 3)

  • The tag workflow (#18) reruns just check, generated-proto and schema sync, proto-compat, build, and smoke on the exact tag SHA; #31 does not rely on the earlier main run. The first-tag compatibility base is the committed first-release acknowledgment, removed in the first post-tag commit.
## Triage plan (2026-09-03) Source-grounded triage against `main` at `69076d42`, reviewed through seven rounds of an independent reviewer (`pi` sol/luna) until it passed with no findings. The dependency order that supersedes the tracker's is posted on #12. Where a "Review amendments" section below conflicts with an earlier section, the amendment is the decision. ## Claim check The issue is a procedure, not a code claim; its premises check out with two corrections. - **No tag, single Unreleased section**: `git tag` empty; `CHANGELOG.md:14` is the only `## [` header. `CHANGELOG.md:11-12` and `docs/reference/workspace.md:157-176` describe the pre-release versioning stance the release commit has to close out. - **"Set the inherited workspace version" — inaccurate.** Nothing inherits a version: `Cargo.toml:5-14` `[workspace.package]` has `edition`, `rust-version`, `license`, `repository`, `publish`, but no `version`; each of the eleven crates sets `version = "0.1.0"` itself (`crates/*/Cargo.toml:3`). `nix/package.nix:57-59` reads the version from `crates/felis-cli/Cargo.toml` only. So either #31 moves the crates to `version.workspace = true` (a small mechanical change that makes the "set the version" step one line) or the wording changes to "set every crate's version". Recommend the former, done before the release commit so the release diff is only the number. - **Gates the issue lists vs gates that exist**: `just check` (`justfile`: fmt-check + clippy + nextest + deny) mirrors `pr.yml:31-43`; the schema gate is `pr.yml`'s `--all-features` nextest run through the schema sync test (`crates/felis-client-core/src/config.rs:2476`, `just schema`); the proto codegen gate is `pr.yml:45-63`; the Linux smoke/publish gate is `pr.yml:366-477`. The "compatibility gate" the issue names does **not** exist yet (see #19 report: the pre-commit hook is skipped in the sandbox), and the tag trigger does not exist (see #18). Both are prerequisites, as the tracker says. - **"Published source, schemas, docs, and binary report the same build identity"**: today the binary's identity is a bare short hash (`crates/build-common.rs:5-33`), the docs package (`flake.nix:252-262`, `.#docs`) carries no version, and the schema file (`crates/felis-client-core/felis-config.schema.json`) has no version stamp. Without #17 this criterion cannot be checked mechanically. - **Cascade surfaces named** (manual/schema/help/completions/man/`skills/felis`/`CHANGELOG.md`): help/completions/man are generated from clap at package time (`nix/package.nix:120-134`), so "current" means the clap definitions are final; the schema is a committed file with a sync test; `skills/felis/SKILL.md` is hand-maintained and product-shipped (`CLAUDE.md` "Skills"), so it is the one surface a grep sweep must cover. Post-snapshot commits (`8a82e069`, `5a1a49a2`, `69076d42`) are user-affecting (config key rename, CLI separator) — verify they each have a `CHANGELOG.md` Unreleased entry during the sweep (I did not verify this; add it to the checklist). ## Verdict **accept-with-changes.** Keep it as the terminal issue, but (1) replace "inherited workspace version" with the `version.workspace = true` migration as a pre-step, (2) add the two rehearsal outputs from #18 (green rc tag, red mismatch tag) as acceptance evidence, and (3) split the "cross-repository consistency sweep" into a concrete grep list so it is not open-ended on release day. ## Approach Ordered checklist for the release commit and tag; every step is something the human runs or a CI run URL to record in the issue. ### Pre-step (own commit, before the release commit) - `Cargo.toml`: add `version = "0.1.0"` under `[workspace.package]`; each `crates/*/Cargo.toml:3` → `version.workspace = true`. `nix/package.nix:59` keeps reading `felis-cli`'s manifest (it resolves through `lib.importTOML`? No — `importTOML` on the crate file will now see `version = { workspace = true }`, not a string). Change it to read the workspace `../Cargo.toml` `.workspace.package.version`. Add a unit-ish check in `just release-check` (#18) that `nix eval --raw .#felis.version` equals the tag. - `docs/reference/workspace.md:167-176` "Versioning": say the crate version is workspace-inherited and floats together (the prose already says "float together"). ### Release commit (`chore(release): v0.1.0`) 1. Confirm #12's P0/P1 list plus the three post-snapshot CLI blockers (#42, #53, #54 — the tracker comments name them) are closed; for each accepted decision, the owning reference/explanation twin cites it (doc-cascade skill grep: issue number and the decision's key term). 2. Generated surfaces current: `just schema` (no diff), `just proto` (no diff), `felis __mangen`/`completions` are build-time so nothing to commit; run `just check`. 3. `CHANGELOG.md`: rename `## [Unreleased]` → `## [0.1.0] - 2026-MM-DD`, add a fresh empty `[Unreleased]` above, fix the sentence at `:11-12` ("until a first release is tagged"), add the Keep-a-Changelog link footers (`[Unreleased]: …/compare/v0.1.0...HEAD`, `[0.1.0]: …/releases/tag/v0.1.0`). 4. Docs wording that assumes no tag: `docs/reference/workspace.md:164-166` ("until the compatibility freeze the dev wire carries no promise"), `docs/reference/ipc.md:1685-1687`, `docs/how-to/update-felis.md:99` ("first tagged release"), `docs/how-to/install.md:143,154` and `README.md:20,23` (tag-pinned flake refs, per #18). Grep sweep terms: `Unreleased`, `first release`, `first tag`, `compatibility freeze`, `each \`main\` revision`, `git+https://git.natsukium.com/natsukium/felis` without `?ref=`. 5. `skills/felis/SKILL.md`: any verb/flag added by the P1 CLI issues (#20, #23, #25, #26, #27, #53, #54, and `felis version` from #17) appears with its `--format` shape; run the skill's examples against the built binary once. 6. Support claim: only `x86_64-linux` in `install.md:130-146`, `workspace.md:276-281`, README; Windows stays "CI evidence". 7. Version: the pre-step made this a one-line edit if the number changes; for 0.1.0 it does not. ### Gates on the exact commit - Push the release commit to `main`; wait for `pr.yml` fully green including `linux-publish` and `proto-compat` (#19). Record the run URL. - Locally: `just release-check v0.1.0` (#18) on a clean tree (`git status --porcelain` empty; `nix flake metadata --json | jq .dirtyRevision` null); `nix build .#felis && ./result/bin/felis --version` prints `felis 0.1.0 (<rev> )` with no `-dirty` and `<rev>` = `git rev-parse HEAD`. - Rehearsal: push `v0.1.0-rc.1` (annotated) at the release commit; `release.yml` green, prerelease created. Then delete the rc release/tag or leave it marked prerelease — decide and document in workspace.md. ### Tag and publish - `git tag -a v0.1.0 -m "felis 0.1.0"` on the gated commit; `git push origin v0.1.0` (never `--force`, per `CLAUDE.md`). - `release.yml` runs: verify-tag, build, smoke, niks3 push, Forgejo release with schema + proto + Linux store-path/narHash + CHANGELOG body. - Post-checks: `nix run 'git+https://git.natsukium.com/natsukium/felis?ref=refs/tags/v0.1.0' -- --version` on a machine with the cache configured substitutes (no compile) and reports the tag identity; `felis version --format json` (#17) against a daemon started from that build shows all three identities equal; the docs package (`.#docs`) built from the tag, if a version stamp was added, matches. - Close #31, then #12 (tracker) with the run URLs. ### Tests that prove it Release is proven by the CI runs and the post-checks above; the only code-level test this issue adds is the `package.nix` version-source change being covered by `just release-check`'s `nix eval` comparison and by `checks.home-manager-module` (`flake.nix`, evaluates the package). ## Dependencies Everything in #12's P0/P1 list plus #42/#53/#54 (#53/#54 are already landed at `5a1a49a2`; #42 is open). Direct blockers for this issue's own steps: **#17** (identity to compare), **#18** (tag workflow + `just release-check`), **#19** (the compatibility gate the acceptance criteria name), **#30** (the wire baseline must be final before the tag freezes it — "after every other wire edit" in the tracker order), **#29** (golden schemas/conversations frozen at the tag). The tracker's recommended order still holds; #31 is last. ## Risk/effort **S** for the commit itself; **M** for the sweep, since it is the one moment every hand-maintained mirror (CHANGELOG, skill, install docs, support matrix) has to agree and no test covers prose. Main risk: tagging a commit whose `main` run was green but whose docs still say "each `main` revision" or whose skill lists a verb #23 renamed — the grep list above is the mitigation. Secondary: the `version.workspace = true` migration changing `nix/package.nix`'s version lookup; do it as its own commit and let `pr.yml`'s `linux-smoke` (`nix build .#felis`) prove it. ## Labels Keep `priority/P0`, `release/v0.1.0`. Edit the scope line "Set the inherited workspace version" to "Move crate versions to `version.workspace = true`, then set the workspace version", and add acceptance items: "rc-tag rehearsal run recorded" and "post-publish `nix run …?ref=refs/tags/v0.1.0 -- --version` substitutes from the cache and reports the tag identity". ## Review amendments (round 2) - The local pre-tag check and the post-publish check compare `felis version --format json` `.cli.revision` (full) with `git rev-parse HEAD` and `.cli.dirty == false`; `felis --version` is a human smoke line and is not compared. ## Review amendments (round 3) - The tag workflow (#18) reruns `just check`, generated-proto and schema sync, `proto-compat`, build, and smoke on the exact tag SHA; #31 does not rely on the earlier `main` run. The first-tag compatibility base is the committed `first-release` acknowledgment, removed in the first post-tag commit.
natsukium referenced this issue from a commit 2026-09-08 03:25:55 +09:00
Author
Owner

Release preparation (2026-09-08)

Everything up to the tag is landed. No tag has been created or pushed — the tag push and the release workflow are the remaining manual steps, listed at the bottom.

What landed

PR Merged as
Pre-step #223 workspace: single-source the crate version 49af3c8a
Release commit #224 release: cut v0.1.0 83b959e5

The gated revision is 83b959e57a5bd1c689665a80ebec168423877e52 (main).

CI on each PR head, all required contexts green (Forgejo exposes them as job URLs, not run-level ones):

  • #223 (2db46cc9): runs /natsukium/felis/actions/runs/2567 (bench), 2568 (darwin), 2569 (fuzz), 2570 (pr), 2571 (windows). Every context success; pr / publish felis, bench / full-suite, fuzz / nightly long-run and the two docs-only no-ops skipped, as they are on any PR.
  • #224 (83b959e5): runs /natsukium/felis/actions/runs/2583 (darwin), 2584 (pr), 2585 (windows). The change is docs-only, so the docs-only gate satisfied the Windows and darwin build contexts through their no-op jobs; pr / nix flake check, pr / cargo build / clippy / test / deny, pr / wire schema is compatible with the base and pr / frontend smoke (x86_64-linux) all ran and are success.

Release commit checklist (plan items 1-7)

  1. P0/P1 children closed. #13 #14 #15 #16 #17 #18 #19 #45 #49 (P0), #20 #21 #22 #23 #24 #25 #26 #27 #28 #29 #30 #46 #47 #48 #50 #51 #52 (P1), plus the post-snapshot CLI blockers #42 #53 #54 — all closed. Every remaining open issue in the repo is priority/P2; only #12 and #31 remain from this tracker.
  2. Generated surfaces current. just schema and just proto produce no diff on the release commit.
  3. CHANGELOG. ## [Unreleased] became ## [0.1.0] - 2026-09-08, a fresh empty ## [Unreleased] opens above it, the intro's "until a first release is tagged" sentence now states the released rule, and both link footers are in place.
  4. Pre-tag wording sweep. Terms Unreleased, first release, first tag, compatibility freeze, each \main` revision, and unpinned git+https://git.natsukium.com/natsukium/felisrefs, acrossdocs/, README.md, CHANGELOG.md, CONTRIBUTING.md, skills/, scripts/, nix/, .forgejo/`. See "Where HEAD differs from the plan" below for what was changed and what was deliberately left.
  5. skills/felis/SKILL.md. Checked against the built binary: every verb the skill names exists, and its point/stream split matches the binary's --format possible values verb by verb (list/info/spawn/send/kill/evict/tag/daemon status/daemon stop/config check/doctor/version/window retarget take json; capture/search/notifications subscribe take jsonl). Its one pre-release sentence was updated.
  6. Support claim. x86_64-linux only, consistent across README.md ("Four targets are on the roadmap… x86_64-linux has a passed runtime gate"), docs/reference/workspace.md "Build and platform matrix" and "Release gate", and docs/how-to/install.md ("The Windows deliverable" keeps Windows at CI evidence).
  7. Version. 0.1.0 needed no bump; the pre-step made it one line for next time.

Gates on the exact merged revision

Run in a clean detached checkout of 83b959e5 (git status --porcelain empty, nix flake metadata reports dirtyRevision: null):

$ just release-check v0.1.0          # with a throwaway local annotated tag, deleted after
release-verify: v0.1.0 is an annotated tag at 83b959e57a5bd1c689665a80ebec168423877e52
release-verify: the workspace is at 0.1.0 and every crate inherits it
release-verify: CHANGELOG.md carries the 0.1.0 section
release-verify: the working tree is clean

$ nix build .#felis && ./result/bin/felis version --format json
{"v":1,"cli":{"version":"0.1.0","revision":"83b959e57a5bd1c689665a80ebec168423877e52","dirty":false},
 "client":{"version":"0.1.0","revision":"83b959e57a5bd1c689665a80ebec168423877e52","dirty":false},
 "client_status":"ok","daemon":null,"daemon_status":"untyped"}

$ scripts/release/verify.sh --identity v0.1.0 83b959e5… version.json
release-verify: the artifact reports 0.1.0 (83b959e57a5bd1c689665a80ebec168423877e52), clean

.cli.revision equals git rev-parse HEAD and .cli.dirty is false. just check and just prose-check passed on the commit before it was pushed.

Where HEAD differs from the plan (2026-09-03)

The plan was written against 69076d42; these are the points where the tree has moved on and HEAD won.

  • #30 closed without a protocol-2.0 reset. The wire stays at major 1, minor 10. Nothing in the release commit touches PROTOCOL_MAJOR.
  • The pre-step had a fourth consumer. Beyond nix/package.nix, scripts/release/verify.sh read every crate's [package] version (the file did not exist when the plan was written), and scripts/make-macos-app.sh read felis-cli's. Both now read [workspace.package] version; the gate's per-crate loop became an inheritance check, with a new self-test case for a crate that pins a version of its own. tests/ was migrated with the eleven crates so no member is left pinned.
  • just release-check already exists, so the plan's "add a nix eval comparison to it" was not needed as a new recipe; checks.home-manager-module and nix build .#felis both evaluated the changed lookup before the pre-step merged.
  • docs/how-to/update-felis.md no longer carries the "first tagged release" sentence the plan names at :99; #25's rewrite replaced that section.
  • Tag-pinned flake refs: docs/how-to/install.md already documents the pinned form in its own "Installing a specific release" section (?ref=refs/tags/v0.1.0), which #18 added. The quick-start refs in README.md and install.md were left unpinned on purpose: the workspace matrix already says the tag build is the supported artifact and a main build is a preview of the next one, and pinning the quick start would put a version number in two more files at every release. Say so if you want them pinned instead.
  • Proto-surface pre-release wording left as it is. docs/reference/ipc.md, docs/reference/workspace.md "Versioning" and docs/explanation/architecture/ipc.md still describe the freeze in the future tense, and crates/felis-protocol/proto/BREAKING.md still carries the first-release acknowledgment. Per the round-3 amendment that acknowledgment is the compatibility base this tag is checked against and is retired in the first commit after the tag; the wording flips with it, in that commit, not this one. Only the CLI epoch's rename rule was flipped now, because docs/reference/cli.md already stated it unconditionally and the shipped skill contradicted it.
  • An rc tag's release notes will be empty. release.yml builds a candidate's body from ## [Unreleased], which the release commit just emptied. That is cosmetic — verify.sh does not ask a candidate for a CHANGELOG section — but do not read the empty body as a failed rehearsal.

Remaining manual steps

Run from a clean checkout of 83b959e5.

1. Rehearse with a release candidate.

git fetch origin
git switch --detach 83b959e57a5bd1c689665a80ebec168423877e52
git tag -a v0.1.0-rc.1 -m "felis 0.1.0-rc.1"
just release-check v0.1.0-rc.1
git push origin v0.1.0-rc.1

Then watch release.yml (Actions → release): verify-tag, the reruns, linux-publish, and a prerelease release page for v0.1.0-rc.1 carrying felis-x86_64-linux.txt, felis-config.schema.json, and felis.proto. Record the run URL here.

2. Cut the release tag on the same commit, once the rehearsal is green.

git tag -a v0.1.0 -m "felis 0.1.0"
git push origin v0.1.0          # never --force

release.yml runs the whole gate on the tag's own revision and publishes the release page (draft → assets → published).

3. Post-publish checks, on a machine that has the cache configured as a substituter:

nix run 'git+https://git.natsukium.com/natsukium/felis?ref=refs/tags/v0.1.0' -- version --format json

It must substitute rather than compile, and report "version":"0.1.0", "revision":"83b959e57a5bd1c689665a80ebec168423877e52", "dirty":false — the same identity felis-x86_64-linux.txt on the release page names. Compare the attached felis.proto and felis-config.schema.json with the tag's own copies.

4. Afterwards: retire the first-release acknowledgment in crates/felis-protocol/proto/BREAKING.md against the published baseline and flip the proto-surface wording with it (the round-3 amendment's post-tag commit), decide whether the rc release page stays or is deleted and record that in workspace.md, then close #31 and #12 with the run URLs.

## Release preparation (2026-09-08) Everything up to the tag is landed. **No tag has been created or pushed** — the tag push and the release workflow are the remaining manual steps, listed at the bottom. ### What landed | | PR | Merged as | | --- | --- | --- | | Pre-step | #223 `workspace: single-source the crate version` | `49af3c8a` | | Release commit | #224 `release: cut v0.1.0` | `83b959e5` | **The gated revision is `83b959e57a5bd1c689665a80ebec168423877e52`** (`main`). CI on each PR head, all required contexts green (Forgejo exposes them as job URLs, not run-level ones): - #223 (`2db46cc9`): runs `/natsukium/felis/actions/runs/2567` (bench), `2568` (darwin), `2569` (fuzz), `2570` (pr), `2571` (windows). Every context `success`; `pr / publish felis`, `bench / full-suite`, `fuzz / nightly long-run` and the two docs-only no-ops `skipped`, as they are on any PR. - #224 (`83b959e5`): runs `/natsukium/felis/actions/runs/2583` (darwin), `2584` (pr), `2585` (windows). The change is docs-only, so the docs-only gate satisfied the Windows and darwin build contexts through their no-op jobs; `pr / nix flake check`, `pr / cargo build / clippy / test / deny`, `pr / wire schema is compatible with the base` and `pr / frontend smoke (x86_64-linux)` all ran and are `success`. ### Release commit checklist (plan items 1-7) 1. **P0/P1 children closed.** #13 #14 #15 #16 #17 #18 #19 #45 #49 (P0), #20 #21 #22 #23 #24 #25 #26 #27 #28 #29 #30 #46 #47 #48 #50 #51 #52 (P1), plus the post-snapshot CLI blockers #42 #53 #54 — all `closed`. Every remaining open issue in the repo is `priority/P2`; only #12 and #31 remain from this tracker. 2. **Generated surfaces current.** `just schema` and `just proto` produce no diff on the release commit. 3. **CHANGELOG.** `## [Unreleased]` became `## [0.1.0] - 2026-09-08`, a fresh empty `## [Unreleased]` opens above it, the intro's "until a first release is tagged" sentence now states the released rule, and both link footers are in place. 4. **Pre-tag wording sweep.** Terms `Unreleased`, `first release`, `first tag`, `compatibility freeze`, `each \`main\` revision`, and unpinned `git+https://git.natsukium.com/natsukium/felis` refs, across `docs/`, `README.md`, `CHANGELOG.md`, `CONTRIBUTING.md`, `skills/`, `scripts/`, `nix/`, `.forgejo/`. See "Where HEAD differs from the plan" below for what was changed and what was deliberately left. 5. **`skills/felis/SKILL.md`.** Checked against the built binary: every verb the skill names exists, and its point/stream split matches the binary's `--format` possible values verb by verb (`list`/`info`/`spawn`/`send`/`kill`/`evict`/`tag`/`daemon status`/`daemon stop`/`config check`/`doctor`/`version`/`window retarget` take `json`; `capture`/`search`/`notifications subscribe` take `jsonl`). Its one pre-release sentence was updated. 6. **Support claim.** `x86_64-linux` only, consistent across `README.md` ("Four targets are on the roadmap… `x86_64-linux` has a passed runtime gate"), `docs/reference/workspace.md` "Build and platform matrix" and "Release gate", and `docs/how-to/install.md` ("The Windows deliverable" keeps Windows at CI evidence). 7. **Version.** `0.1.0` needed no bump; the pre-step made it one line for next time. ### Gates on the exact merged revision Run in a clean detached checkout of `83b959e5` (`git status --porcelain` empty, `nix flake metadata` reports `dirtyRevision: null`): ``` $ just release-check v0.1.0 # with a throwaway local annotated tag, deleted after release-verify: v0.1.0 is an annotated tag at 83b959e57a5bd1c689665a80ebec168423877e52 release-verify: the workspace is at 0.1.0 and every crate inherits it release-verify: CHANGELOG.md carries the 0.1.0 section release-verify: the working tree is clean $ nix build .#felis && ./result/bin/felis version --format json {"v":1,"cli":{"version":"0.1.0","revision":"83b959e57a5bd1c689665a80ebec168423877e52","dirty":false}, "client":{"version":"0.1.0","revision":"83b959e57a5bd1c689665a80ebec168423877e52","dirty":false}, "client_status":"ok","daemon":null,"daemon_status":"untyped"} $ scripts/release/verify.sh --identity v0.1.0 83b959e5… version.json release-verify: the artifact reports 0.1.0 (83b959e57a5bd1c689665a80ebec168423877e52), clean ``` `.cli.revision` equals `git rev-parse HEAD` and `.cli.dirty` is `false`. `just check` and `just prose-check` passed on the commit before it was pushed. ### Where HEAD differs from the plan (2026-09-03) The plan was written against `69076d42`; these are the points where the tree has moved on and HEAD won. - **#30 closed without a protocol-2.0 reset.** The wire stays at major 1, minor 10. Nothing in the release commit touches `PROTOCOL_MAJOR`. - **The pre-step had a fourth consumer.** Beyond `nix/package.nix`, `scripts/release/verify.sh` read every crate's `[package] version` (the file did not exist when the plan was written), and `scripts/make-macos-app.sh` read `felis-cli`'s. Both now read `[workspace.package] version`; the gate's per-crate loop became an inheritance check, with a new self-test case for a crate that pins a version of its own. `tests/` was migrated with the eleven crates so no member is left pinned. - **`just release-check` already exists**, so the plan's "add a `nix eval` comparison to it" was not needed as a new recipe; `checks.home-manager-module` and `nix build .#felis` both evaluated the changed lookup before the pre-step merged. - **`docs/how-to/update-felis.md`** no longer carries the "first tagged release" sentence the plan names at `:99`; #25's rewrite replaced that section. - **Tag-pinned flake refs**: `docs/how-to/install.md` already documents the pinned form in its own "Installing a specific release" section (`?ref=refs/tags/v0.1.0`), which #18 added. The quick-start refs in `README.md` and `install.md` were **left unpinned** on purpose: the workspace matrix already says the tag build is the supported artifact and a `main` build is a preview of the next one, and pinning the quick start would put a version number in two more files at every release. Say so if you want them pinned instead. - **Proto-surface pre-release wording left as it is.** `docs/reference/ipc.md`, `docs/reference/workspace.md` "Versioning" and `docs/explanation/architecture/ipc.md` still describe the freeze in the future tense, and `crates/felis-protocol/proto/BREAKING.md` still carries the `first-release` acknowledgment. Per the round-3 amendment that acknowledgment is the compatibility base *this* tag is checked against and is retired in the first commit after the tag; the wording flips with it, in that commit, not this one. Only the CLI epoch's rename rule was flipped now, because `docs/reference/cli.md` already stated it unconditionally and the shipped skill contradicted it. - **An rc tag's release notes will be empty.** `release.yml` builds a candidate's body from `## [Unreleased]`, which the release commit just emptied. That is cosmetic — `verify.sh` does not ask a candidate for a CHANGELOG section — but do not read the empty body as a failed rehearsal. ### Remaining manual steps Run from a clean checkout of `83b959e5`. **1. Rehearse with a release candidate.** ```sh git fetch origin git switch --detach 83b959e57a5bd1c689665a80ebec168423877e52 git tag -a v0.1.0-rc.1 -m "felis 0.1.0-rc.1" just release-check v0.1.0-rc.1 git push origin v0.1.0-rc.1 ``` Then watch `release.yml` (Actions → `release`): `verify-tag`, the reruns, `linux-publish`, and a **prerelease** release page for `v0.1.0-rc.1` carrying `felis-x86_64-linux.txt`, `felis-config.schema.json`, and `felis.proto`. Record the run URL here. **2. Cut the release tag** on the same commit, once the rehearsal is green. ```sh git tag -a v0.1.0 -m "felis 0.1.0" git push origin v0.1.0 # never --force ``` `release.yml` runs the whole gate on the tag's own revision and publishes the release page (draft → assets → published). **3. Post-publish checks**, on a machine that has the cache configured as a substituter: ```sh nix run 'git+https://git.natsukium.com/natsukium/felis?ref=refs/tags/v0.1.0' -- version --format json ``` It must substitute rather than compile, and report `"version":"0.1.0"`, `"revision":"83b959e57a5bd1c689665a80ebec168423877e52"`, `"dirty":false` — the same identity `felis-x86_64-linux.txt` on the release page names. Compare the attached `felis.proto` and `felis-config.schema.json` with the tag's own copies. **4. Afterwards**: retire the `first-release` acknowledgment in `crates/felis-protocol/proto/BREAKING.md` against the published baseline and flip the proto-surface wording with it (the round-3 amendment's post-tag commit), decide whether the rc release page stays or is deleted and record that in `workspace.md`, then close #31 and #12 with the run URLs.
Sign in to join this conversation.
No description provided.