diff --git a/MAINTAINING.md b/MAINTAINING.md index 631b793..166e0ac 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -57,7 +57,7 @@ Written 2026-07-05 @ v0.1.114 (main @ fe35cf7). This is the institutional-knowle ## 5. Release process -Flow (the `np-release` skill orchestrates this): bump `CMakeLists.txt` VERSION → `release_notes/vX.md` → append `CHANGELOG.md` → `scripts/release-check.sh` → PII sweep → commit → tag → push → watch CI → verify GitHub Release assets + Pages. +Flow (the `np-release` skill orchestrates this): bump `CMakeLists.txt` VERSION **and `notepatra-mcp/Cargo.toml`** → `release_notes/vX.md` → append `CHANGELOG.md` → `scripts/release-check.sh` → PII sweep → commit → tag → push → watch CI → verify GitHub Release assets + Pages → **`cd notepatra-mcp && cargo publish`**. Gates in `scripts/` (release-check.sh calls most): `stale-text-check.sh` (canonical counts, capability claims, installer filenames), `verify-download-sizes.sh` (published bytes vs docs claims), `test-install-selection.sh` (lite/full artifact pick), `post-release-verify.sh`, `bump_version.sh`, `smoke-multiprocess.sh`. @@ -75,6 +75,7 @@ CI traps: - **GitHub push protection** rejects anything key-shaped (`sk-…`, `ghp_…`) even all-zero fakes — build key-shaped test literals from concatenated fragments. Recovery: `reset --soft`, fix, re-commit, move tag. - `Closes #N` in a direct push does NOT close issues — only PR merges do; follow up with `gh issue close N`. - Install scripts **hard-fail** verification when the SHA/sig source is unreachable (Tier 1). Every new security check gets classified into exactly one tier first: hard-fail / soft-warn / skip-when-tool-missing. +- **crates.io is a distribution surface and nobody was watching it.** `notepatra-mcp` 0.1.118/0.1.119 were published by hand on 2026-07-18, then the channel rotted for eight releases — `cargo install notepatra-mcp` served 0.1.119, the one release whose Windows named-pipe transport deadlocked on every verb and had never completed a tool call. `release-check.sh` now hard-fails when crates.io's `max_version` is not the previous tag (soft-warn when the registry is unreachable). Publishing is still manual and happens AFTER the tag; the gate catches the omission on the next release, not the current one. **User-pause rule:** "do not CI until I say" / "stop shipping" / "wait" = no push, no tag, no commit-to-main. Work locally, batch fixes, resume only on an explicit "ship it". Status questions do not unpause. diff --git a/scripts/release-check.sh b/scripts/release-check.sh index a8228de..53d5dbc 100755 --- a/scripts/release-check.sh +++ b/scripts/release-check.sh @@ -9,6 +9,9 @@ # • docs/index.html stat card / hero / sticky CTA mention the tag # • All 22+ test executables build (notepatra_all_tests target) # • ctest passes +# • notepatra-mcp/Cargo.toml version matches the tag +# • crates.io already carries the PREVIOUS release (the sidecar publish +# is manual and silently rotted for 8 releases once already) # • git working tree is clean # • No vendored binary (vendor/, build/, etc.) is staged # @@ -241,6 +244,47 @@ if [ -d notepatra-mcp ]; then fi fi +echo +echo "── crates.io ──" +# The sidecar is published to crates.io as `notepatra-mcp`. Nothing in CI does +# this, so it is a manual step — and a manual step that is not a gate is a step +# that stops happening. It did: 0.1.118 and 0.1.119 went up on 2026-07-18 and +# then the channel rotted for EIGHT releases, leaving `cargo install +# notepatra-mcp` serving 0.1.119 — the one release whose Windows named-pipe +# transport deadlocked on every verb and had never completed a tool call. +# +# The invariant that would have caught it on the very next release: by the time +# you cut vN, the PREVIOUS tag must already be on crates.io. Checking for the +# version being cut would be wrong — that one is published after the tag. +prev_tag="$(git describe --tags --abbrev=0 2>/dev/null || true)" +prev_ver="${prev_tag#v}" +crate_ver="$(grep -m1 -oE '^version = "[0-9]+\.[0-9]+\.[0-9]+"' notepatra-mcp/Cargo.toml \ + | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true)" + +check "notepatra-mcp/Cargo.toml version matches the tag being cut" \ + "[ \"$crate_ver\" = \"$VERSION\" ]" + +if [ -z "$prev_ver" ] || [ "$prev_ver" = "$VERSION" ]; then + echo " ⚠ no earlier tag to compare against — crates.io currency unchecked" +elif published="$(curl -fsSL --max-time 15 -A notepatra-release-check \ + https://crates.io/api/v1/crates/notepatra-mcp 2>/dev/null \ + | grep -oE '"max_version":"[0-9]+\.[0-9]+\.[0-9]+"' \ + | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" && [ -n "$published" ]; then + # Hard-fail: a stale registry ships a known-broken sidecar to anyone + # running `cargo install`, and nothing else in the pipeline notices. + check "crates.io is current (published $published = previous tag $prev_ver)" \ + "[ \"$published\" = \"$prev_ver\" ]" + if [ "$published" != "$prev_ver" ]; then + echo " crates.io serves $published but the last release was $prev_ver." + echo " cd notepatra-mcp && cargo publish # then re-run this script" + fi +else + # Soft-warn: offline or crates.io down is not a reason to block a release. + echo " ⚠ could not reach crates.io — published sidecar version unverified" +fi +echo +echo " → after tagging: cd notepatra-mcp && cargo publish (publishes $VERSION)" + echo echo "── tag ──" if git rev-parse "$TAG" >/dev/null 2>&1; then