# Proximo 0.7.0 — Public Release Runbook

**Date:** 2026-06-23
**Carries:** the PROVE increments now on internal `main` (`<internal-sha>`, CI green) —
head-pinning (`expected_head`), keyed-by-default PROVE, malformed-pin validation, and the
unpinned discoverability hint.
**Also in this release:** strip `CLAUDE.md` from the public mirror (closes the open audit item).

## Version decision — 0.7.0 (minor), not 0.6.6

Keyed-by-default is a **behavior-changing default that auto-migrates** an existing unkeyed
ledger on first run (seals + archives it non-destructively, starts a fresh keyed log). A minor
bump signals "notable change" honestly; a patch number would under-signal a migration that
touches user data. Pre-1.0 still holds (`0.x`).

## Pre-flight (already true — verify, don't redo)

- Feature code on internal `main` (origin = internal gitea) at `<internal-sha>`; gitea gitea CI green.
- Full suite green, ruff + pyright clean, leak-audit CLEAN.
- `CHANGELOG.md` `## [Unreleased]` holds the Added + Changed for all three increments.
- Public `github/main` is the curated orphan (`<curated-sha>`); it diverges from gitea `main` by design.
- Remotes inverted: `origin` = internal gitea (private), `github` = public. **Never** force-push `github`.

---

## Phase 0 — prep on internal `main` (Claude; GREEN, reversible)

Must land **before** `release.sh` (the version gate requires a real `## [0.7.0]` heading, and the
public tree must strip `CLAUDE.md`).

1. **Strip `CLAUDE.md` from the public tree (TDD).** Add `"CLAUDE.md"` to `DENY_PREFIXES` in
   `scripts/release_leak_audit.py`; add a test in `tests/test_release_leak_audit.py` asserting
   `CLAUDE.md` lands in `stripped` (and is NOT scanned). Verify: `release_leak_audit.py audit`
   now reports one fewer published file with `CLAUDE.md` in the stripped list.
2. **Finalize the CHANGELOG.** Rename `## [Unreleased]` → `## [0.7.0] — 2026-06-23`; add a fresh
   empty `## [Unreleased]` above it. Under `[0.7.0]` add a prominent **Upgrade** note:
   > On first run after upgrade, an existing **unkeyed** ledger is sealed, archived
   > (`audit.log.unkeyed-<stamp>-<head8>`, never deleted), and a new **keyed** log is started.
   > A loud warning prints the new head — re-pin `PROXIMO_AUDIT_EXPECTED_HEAD` to it. Opt out
   > of keying with `PROXIMO_AUDIT_KEYED=off`.
3. Commit (conventional), leak-audit, push `origin` (internal gitea; conscience + GREEN), confirm CI.

## Phase 1 — cut the release (Claude; mechanical, internal only)

4. `scripts/release.sh 0.7.0` — sets `pyproject` + `__init__` to `0.7.0`, runs the local gate
   (version consistency [passes now that CHANGELOG has `0.7.0`], ruff, version test, leak-audit).
   It **never pushes** — stops at "ready".
5. Commit the bump: `release: cut 0.7.0 — PROVE head-pinning + keyed-by-default + CLAUDE.md strip`.
6. `git tag v0.7.0`.
7. `uv run python scripts/version_tools.py release-check v0.7.0` — asserts tag == pyproject ==
   `__init__` == CHANGELOG top-released heading. Must pass before anything public.
8. Internal push: `git push origin main --tags` (gitea; conscience + leak-clean; GREEN).

## Phase 2 — public flip (REQUIRES John's GO; RED — each trips the maude gate)

9. **Publish the curated public tree to GitHub (FF-only):**
   ```
   T=$(uv run python scripts/release_leak_audit.py build-tree)   # strips .gitea/ + CLAUDE.md; refuses leaks
   C=$(git commit-tree "$T" -p github/main -m "release: v0.7.0")
   git push github "$C:main"        # fast-forward only — NEVER --force
   ```
10. **`gh release create v0.7.0`** (publish the GitHub Release). Publishing fires **both**:
    - `release.yml` → builds + publishes the **signed GHCR** multi-arch image, and
    - `release-pypi.yml` → tokenless **PyPI** publish via OIDC, which **pauses** on the `pypi`
      environment for a required reviewer.
11. **John approves the paused PyPI publish job** (GitHub "review deployments" → approve).
    Tokenless OIDC; this click is John's hand by design.

## Phase 3 — verify (Claude, post-publish)

12. **PyPI**: `0.7.0` is the latest; fresh-venv acceptance (clean venv → install `proximo-proxmox`
    → `proximo --version` / import) succeeds.
13. **GHCR**: `ghcr.io/john-broadway/proximo:0.7.0` present; verify the signature/attestation.
14. **GitHub**: Release page live; `github/main` fast-forwarded to the new curated commit; the
    public tree does NOT contain `CLAUDE.md`.
15. Ripple the **public version claims** (profile `john-broadway/README.md`, site) to 0.7.0 — the
    "board" follow-on (those are separate public pushes = John's hand).

## Split summary

| Phase | Who | Zone |
|---|---|---|
| 0 — CLAUDE.md strip + CHANGELOG, internal push | Claude | GREEN (internal) |
| 1 — release.sh, tag, internal push + tags | Claude | GREEN (internal) |
| 2 — github FF push, gh release, **PyPI approve** | **John's GO / hand** | RED (public) |
| 3 — verify PyPI/GHCR/GitHub, board ripple | Claude (ripple pushes = John) | mixed |

## Honesty / risk notes

- The auto-migration belongs in the **GitHub Release notes prominently**, not only the CHANGELOG —
  users upgrading must see it.
- The past node-name in already-published history stays as-is (recommendation: HOLD — it's a
  node name, not a secret; reserve history-rewrite for a real secret). Not touched by this release.
- `github` is FF-only forever. The full per-commit history lives on internal gitea.
