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
18 changes: 16 additions & 2 deletions .github/workflows/release-plz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,22 @@ on:
# `release-plz update` downloads uffs-text 0.5.120 as baseline,
# exits 0, proposes bumps only when the published crates' files
# change. See issue #241 (publish-day umbrella).
push:
branches: [main]
#
# • 2026-06-10 (Path B): push trigger DISABLED again — but this time
# by design, not because of a bug. release-plz "proposes bumps only
# when the published crates' files change" (the line above) is
# exactly the limitation that makes it the WRONG driver for binary
# releases: a change to uffs-cli/core/daemon/… never bumps anything.
# Under Path B, BINARY releases are cut manually and on-demand via
# `just release-pr` + `just release-tag` (the sole owner of the
# lockstep workspace version), and release-plz is demoted to a
# manual, workflow_dispatch-only helper for the 2 leaf libs. A
# normal merge to `main` intentionally triggers no release. See
# `docs/architecture/release-automation-plan.md` §8 (Path B row) and
# `just/build.just` `release-pr` / `release-tag`.
#
# push: # ← Path B: disabled by design (manual releases)
# branches: [main]
workflow_dispatch:

# Default to ZERO permissions; each job grants only what it needs.
Expand Down
1 change: 1 addition & 0 deletions docs/architecture/release-automation-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -2087,6 +2087,7 @@ Mirror the format of
| R8 OIDC wire-up + env-name doc fix | 2026-06-10 | After the R8 bootstrap published `uffs-time` + `uffs-text` v0.5.120 by hand (token), the dormant `crates-io-publish` job in `release-plz.yml` still carried a commented-out placeholder publish step, and §R7/§5.6 of this plan named the OIDC environment `crates-io-production` while the live workflow uses `environment: crates.io-publish` — a mismatch that would have caused trusted-publisher registration to silently fail (crates.io requires the registered environment name to match the workflow's `environment:` value exactly). | (1) Bootstrap publish had to come first (chicken-and-egg: trusted publishers can only be registered on crates that already exist), so the publish step was deliberately left dormant through R7 and only wired after R8. (2) The `crates-io-production` name was an early-draft placeholder in the plan that was never reconciled against the workflow when the job was scaffolded in R7. | Replaced the placeholder with a real publish path: `rust-lang/crates-io-auth-action@bbd81622…` (v1.0.4, SHA-pinned) mints a short-lived OIDC token, then a `cargo metadata`-derived, dependency-ordered `cargo publish --locked` loop publishes the publishable set (today `uffs-time`, `uffs-text`). Job stays dormant behind the `ENABLE_CRATES_IO_PUBLISH` repo-variable gate + the `crates.io-publish` environment (required reviewers). Renamed all `crates-io-production` → `crates.io-publish` across §R7, §5.6, §5.7 checklist, §R9, and the risk table so the doc matches the workflow exactly. | Unblocks the R9 cutover: the maintainer now only needs to register trusted publishers (env `crates.io-publish`), create the GitHub Environment with reviewers, set the repo variable, and revoke the bootstrap token — no further workflow edits required. R7 advances to "wired"; R8 marked 🟢 (bootstrap done). |
| R4 git-only baseline dead end → registry-baseline flip | 2026-06-10 | The 2026-06-09 R4 re-activation failed 4 consecutive push runs (last: run [27242337110](https://github.com/skyllc-ai/UltraFastFileSearch/actions/runs/27242337110)) with `failed to determine next versions → run cargo package → no matching package named uffs-broker-protocol found, location searched: crates.io index`; the push trigger was re-commented out (workflow_dispatch-only), so the #387/#388 merges to `main` triggered nothing — discovered when the expected post-R9-arming release PR never appeared. | In `git_only = true` mode release-plz hardcodes `cargo package --allow-dirty --workspace` inside the latest-tag worktree, packaging ALL members regardless of `release = false` config (confirmed CLI 0.3.158 run #79; re-confirmed locally 0.3.157 with `RUST_LOG=debug`). Never-publish crates with versioned internal deps (`uffs-broker` → `uffs-broker-protocol`) can never resolve from the crates.io index; dropping the `version =` instead fails `cargo package` with "all dependencies must have a version requirement" (both directions tested locally) — git-only mode is structurally incompatible with this workspace, and the workflow header's old "re-enable after bootstrap publishes the internal crates" condition was unsatisfiable (those crates are never-publish by design). | Flipped `git_only = false` in `release-plz.toml` — the registry-baseline mode its own R4 comment block planned for post-R8. With `uffs-time`/`uffs-text` v0.5.120 live on crates.io (R8 bootstrap), release-plz downloads the published `.crate` files as baseline and never invokes the worktree packaging step. Verified locally: `release-plz update` downloads uffs-text 0.5.120, exits 0, proposes bumps only when the published crates' files change. Push trigger re-enabled in the same PR; both deferral notices in the workflow header rewritten as historical records. | R4's auto-trigger is genuinely live for the first time. The R4 dashboard row's 2026-06-09 note ("push trigger re-enabled") was true for ~hours then silently regressed — corrected by this row per append-only discipline. First release-plz release PR (→ first OIDC publish, R9) now fires on the next `feat:`/`fix:`/`perf:` commit touching `crates/uffs-time` or `crates/uffs-text` files. |
| R8 prep — publishable set narrowed (12 → 2) + credential path | 2026-06-10 | Pre-publish audit for R8 found the "12/13 publishable crates" figure used throughout this plan and `release-automation-baseline.md` (R0/R3.5/R6 entries) is no longer the live state. `cargo metadata --no-deps` (authoritative, resolves workspace inheritance) shows exactly **2** members with `publish = ANY`: `uffs-time` and `uffs-text`. All 17 others resolve to `publish = []` (false) — `uffs-broker`, `uffs-broker-protocol`, and `uffs-security` were flipped back to `publish.workspace = true` (= false) during the publishability deep-dive (name-squat reservations / internal-only; see `docs/refactor/crates-io-publishability-deep-dive.md` §7.3–7.5), and the root-manifest comment lagged behind (corrected in PR [#385](https://github.com/skyllc-ai/UltraFastFileSearch/pull/385)). | The strategic publish scrub happened incrementally across the deep-dive without a single "recount" pass; historical plan/baseline entries are point-in-time records and stay unedited per append-only discipline — this row is the correction of record. | **Publish sequence for R8/R9 (dependency-ordered):** both publishable crates are dependency-free leaves with zero internal deps, so the bootstrap is simply (1) `cargo publish -p uffs-time`, (2) `cargo publish -p uffs-text` (order interchangeable); dry-runs verified green for both, names available on crates.io. No other crate can join the set without first flipping its never-publish internal deps (`uffs-polars`/`uffs-security`/`uffs-format` → blocking `uffs-mft` → blocking `uffs-client`/`uffs-mcp`/`uffs-cli`) — a deliberate architecture decision, not a gap. **Credential path:** crates.io Trusted Publishing is configured per-crate in the crate's settings page and therefore requires the crate to already exist — no pending-publisher flow exists — so the **first** publish of each crate must use a maintainer `CARGO_REGISTRY_TOKEN` (manual `cargo publish` or repo secret); after bootstrap, configure the trusted publisher for both crates and flip the dormant R7 OIDC job (`vars.ENABLE_CRATES_IO_PUBLISH`), then revoke the token. | R8's dress-rehearsal target (`uffs-time` only) is unchanged and now fully unblocked. R9 "full workspace" should be re-read as "the publishable set" (currently 2 crates). |
| Path B — decouple binary release from crates.io publishing | 2026-06-10 | After R8 bootstrap (uffs-time/uffs-text live at 0.5.120), the maintainer surfaced the core mismatch R5 never made explicit: release-plz only bumps the version when a **publishable** crate changes, but the only publishable crates are the two dependency-free leaves — so a change to `uffs-cli`/`uffs-core`/`uffs-daemon`/… **never** triggers a version bump or a binary build. The pre-R5 mental model ("touch anything → bump → binaries") silently became "touch a leaf lib → bump." Confirmed structural, not config: release-plz computes versions by running `cargo package -p <crate>` per tracked crate, which hard-fails for any crate with a versioned path-dep on an unpublished crate (run #79: `uffs-broker → uffs-broker-protocol`), so only the two leaves are trackable at all. | release-plz is a crates.io **publishing** tool; it versions what the registry can package. Retrofitting it as the **binary-application** release driver (R3–R5) inherited registry-grounded semantics that don't match a Windows binary tool whose release cadence is "any meaningful change, on the maintainer's command." One shared lockstep workspace version + one tool = exactly one entity may own bumps; release-plz can only see the leaves. | Decouple the two cadences. **(1) Binary release = manual on-demand:** new `just release-pr [patch\|minor\|major]` bumps the lockstep `[workspace.package].version` via `cargo set-version`, refreshes the lockfile, and opens a `release/vX.Y.Z` PR (main requires PRs); after squash-merge, `just release-tag` creates the signed `vX.Y.Z` tag → `release.yml` builds the 15-platform binaries. This is the sole owner of the version number (restores the pre-R5 `just ship` model without resurrecting the ~1430 LOC R5 deleted — `cargo set-version` + two thin recipes). **(2) crates.io publishing = release-plz, demoted:** the `push: branches: [main]` trigger in `release-plz.yml` is disabled **by design** (workflow_dispatch-only); release-plz no longer auto-drives anything. Publishing the 2 leaf libs is manual/occasional (they rarely change). Kept intact: R1 commit-convention hooks, R2 git-cliff changelog, the dormant R7 OIDC publish job, `release.yml`. **(3) Name-squat protection** for the valuable names (`uffs-cli`/`uffs-core`/`uffs-daemon`/`uffs-mcp`) handled separately via 0.0.0 placeholder publishes from a throwaway workspace (the long-deferred R6 step 6). | R3–R5's "release-plz drives versioning" premise is intentionally walked back for the **binary** cadence; release-plz retained for the **library** cadence only. A normal merge to `main` now triggers no release — manual-on-demand by design. Future "publish the closure" (R9 Path A — make uffs-cli + its dep tree publishable) remains open and is now a pure config flip + dependency-ordered publish work, with the valuable names pre-reserved. Dashboard: this is a new terminal branch of the plan parallel to R9. |

## 9. Cross-references

Expand Down
73 changes: 67 additions & 6 deletions just/build.just
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,75 @@ version:
@printf "\033[0;34m📋 Current version:\033[0m\n"
@cargo metadata --format-version 1 | jq -r '.packages[] | select(.name == "uffs-cli") | .version'

# Version bump — retired in Phase R5.
# ── Manual on-demand binary release (Path B) ────────────────────────
#
# Version increments now happen automatically via release-plz
# when release-triggering commits (feat/fix/perf/security) land
# on `main`. See `docs/architecture/release-automation-plan.md`.
# UFFS ships its BINARY on your command, not automatically. release-plz
# only versions the 2 publishable leaf libs (uffs-time / uffs-text) — it
# physically cannot see changes to uffs-cli/core/daemon/… (their
# `cargo package` can't resolve unpublished internal deps), so it never
# drives binary releases. To cut a binary release that captures ANY
# change anywhere in the workspace:
#
# 1. just release-pr [patch|minor|major] # opens the version-bump PR
# 2. (review + squash-merge that PR on GitHub)
# 3. just release-tag # tags main → builds binaries
#
# Step 1 bumps the lockstep `[workspace.package].version`, refreshes the
# lockfile, and opens a `release/vX.Y.Z` PR (main requires PRs). Step 3
# creates the signed `vX.Y.Z` tag on main, which fires release.yml's
# 15-platform binary build. Nothing happens on a normal merge — that is
# the manual-on-demand design, not a bug.
#
# (`ship` / `phase2-ship` are the DEV validate+push lanes — unrelated to
# cutting a release.)

# Retained alias — the retired R5 stub now points at the real flow.
version-bump:
@printf "\033[0;33m⚠️ version-bump retired — release-plz handles this automatically\033[0m\n"
@printf "\033[0;36m See docs/architecture/release-automation-plan.md §Phase R5\033[0m\n"
@printf "\033[0;36m💡 'version-bump' is now 'just release-pr [patch|minor|major]'.\033[0m\n"
@printf "\033[0;36m Then merge the PR and run 'just release-tag'.\033[0m\n"

# Open a version-bump release PR (default bump: patch).
release-pr level="patch":
#!/usr/bin/env bash
set -euo pipefail
printf "\033[0;34m🚢 Cutting release PR (%s bump)…\033[0m\n" "{{ level }}"
# ── Preflight: clean tree on an up-to-date main ──
branch=$(git branch --show-current)
[ "$branch" = "main" ] || { printf "\033[0;31m❌ run from main (you are on %s)\033[0m\n" "$branch"; exit 1; }
[ -z "$(git status --porcelain)" ] || { printf "\033[0;31m❌ working tree not clean\033[0m\n"; exit 1; }
git fetch origin main --quiet
[ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ] || { printf "\033[0;31m❌ local main is behind origin/main — pull first\033[0m\n"; exit 1; }
# ── Bump the lockstep workspace version + refresh the lockfile ──
cargo set-version --bump "{{ level }}"
new=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name=="uffs-cli") | .version')
cargo update --workspace --quiet
# ── Commit on a release branch + open the PR (main requires PRs) ──
rel="release/v${new}"
git switch -c "$rel"
git commit -aqm "chore(release): v${new}"
git push -u origin "$rel" --quiet
body="Cut binary release v${new} (Path B manual trigger). Bumps the lockstep workspace version; after squash-merge run 'just release-tag' to tag main and build the 15-platform binaries. No crate code changed — release-cut commit only."
gh pr create --base main --head "$rel" --title "chore(release): v${new}" --body "$body"
printf "\n\033[0;32m✅ Release PR opened for v%s.\033[0m\n" "$new"
printf "\033[0;36m Next: review + squash-merge, then run: just release-tag\033[0m\n"

# After the release PR is merged: tag main → build binaries.
release-tag:
#!/usr/bin/env bash
set -euo pipefail
branch=$(git branch --show-current)
[ "$branch" = "main" ] || { printf "\033[0;31m❌ run from main (you are on %s)\033[0m\n" "$branch"; exit 1; }
git fetch origin main --quiet
git pull --ff-only origin main --quiet
ver=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name=="uffs-cli") | .version')
tag="v${ver}"
if git rev-parse "$tag" >/dev/null 2>&1; then
printf "\033[0;31m❌ tag %s already exists\033[0m\n" "$tag"; exit 1
fi
git tag -s "$tag" -m "Release $tag"
git push origin "$tag" --quiet
printf "\n\033[0;32m✅ Tagged %s and pushed — release.yml is building the binaries.\033[0m\n" "$tag"
printf "\033[0;36m Watch: just release-watch · Status: just release-status\033[0m\n"

# Build dev binary.
build:
Expand Down
Loading