diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb176614..91652e59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,7 +111,7 @@ jobs: --source-sha "$SOURCE_SHA" --dist "$RUNNER_TEMP/rehearsal-dist" - name: Rehearse the exact installed wheel offline run: | - python scripts/rehearse_v150.py --dist "$RUNNER_TEMP/rehearsal-dist" \ + python scripts/rehearse_v151.py --dist "$RUNNER_TEMP/rehearsal-dist" \ --source-sha "$SOURCE_SHA" --work-dir "$RUNNER_TEMP/rehearsal" - name: Upload sanitized pre-merge evidence uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index a31a25d0..891b9cc9 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -63,7 +63,7 @@ jobs: env: SOURCE_SHA: ${{ inputs.expected_sha }} run: | - python scripts/rehearse_v150.py --dist "$RUNNER_TEMP/candidate" \ + python scripts/rehearse_v151.py --dist "$RUNNER_TEMP/candidate" \ --source-sha "$SOURCE_SHA" --work-dir "$RUNNER_TEMP/rehearsal" cp "$RUNNER_TEMP/rehearsal/rehearsal.json" "$RUNNER_TEMP/candidate/rehearsal.json" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5293614d..3751022d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ later entries are regular releases. ## Unreleased +No additional changes recorded. + +## 1.5.1 — release + +Cross-provider installation, audit provenance, safe initialization, remote-observer diagnostics, and Board/status clarity. See [release notes](docs/v151-release-notes.md) and the [qualification contract](docs/v151-qualification.md). + - Local audit publication now binds each reviewer seal to the exact Actions job, matrix lane, run, and first attempt that produced it. Independent Codex and Claude lanes in one run no longer make publication ambiguous, while a diff --git a/README.md b/README.md index 02f6a33d..eb7d7c5c 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,12 @@ Code Mower is supervised-pilot, bring-your-own-agent-loop software. It is not a drop-in unattended merge gate. Humans still own credentials, repository policy, reviewer promotion, and exceptional decisions. -This source defines Code Mower `v1.5.0`, with package spec -`code-mower==1.5.0`. Confirm the release tag on GitHub Releases and the package +This source defines Code Mower `v1.5.1`, with package spec +`code-mower==1.5.1`. Confirm the release tag on GitHub Releases and the package version on the selected index before using an index install command; source version and publication state are separate facts. See the -[v1.5.0 release notes](https://github.com/codemower-ai/code-mower/blob/main/docs/v150-release-notes.md) -and [qualification contract](https://github.com/codemower-ai/code-mower/blob/main/docs/v150-qualification.md). +[v1.5.1 release notes](https://github.com/codemower-ai/code-mower/blob/main/docs/v151-release-notes.md) +and [qualification contract](https://github.com/codemower-ai/code-mower/blob/main/docs/v151-qualification.md). After publication, the GitHub Release and linked release issue carry the observed source SHA, artifact digests, canary outcomes, publication run and reinstall evidence. @@ -25,7 +25,7 @@ not claimed by its immutable qualification record. Documentation on `main` follows the source on `main`. For an installed release, read its immutable versioned guide, such as the -[`v1.5.0` guide](https://github.com/codemower-ai/code-mower/blob/v1.5.0/docs/try-in-10-minutes.md), +[`v1.5.1` guide](https://github.com/codemower-ai/code-mower/blob/v1.5.1/docs/try-in-10-minutes.md), and confirm the tag and package exist before using pinned install commands. ## What Code Mower Adds @@ -54,13 +54,13 @@ one stable `pipx` installation: ```bash python3.12 --version export CODE_MOWER_PYTHON="$(command -v python3.12)" -pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 command -v code-mower code-mower --version ``` `command -v code-mower` should print the path you expect and `code-mower ---version` should print `code-mower 1.5.0` before you point Code Mower at a +--version` should print `code-mower 1.5.1` before you point Code Mower at a repository. If you do not have pipx, install it from the [official pipx installation guide](https://pipx.pypa.io/stable/installation/). @@ -285,7 +285,7 @@ for both workflows, supported behavior, and the trust boundary. ## Current Capabilities And Limits -| Area | v1.5.0 posture | +| Area | v1.5.1 posture | | --- | --- | | Default builders and reviewers | Claude Code + Codex | | Session hosts | Codex, Claude Code, and Cursor qualified; other identities recognized but require explicit handoff/provider transport | @@ -299,7 +299,7 @@ for both workflows, supported behavior, and the trust boundary. GitLab, Bitbucket, broad unattended rollout, uncalibrated merge gates, Devin peer-orchestrator/reviewer parity, a hosted work-order CLI, a required Graphify -dependency, Slack telemetry/Board links, and rich Slack UX are outside v1.5.0. The current priorities +dependency, Slack telemetry/Board links, and rich Slack UX are outside v1.5.1. The current priorities and boundaries are recorded in [Current State And Roadmap](https://github.com/codemower-ai/code-mower/blob/main/docs/current-state-and-roadmap.md). @@ -400,9 +400,9 @@ does not need rebuilding. See - [Cloud Data Contract](https://github.com/codemower-ai/code-mower/blob/main/docs/cloud-data-contract.md) - [Release Qualification](https://github.com/codemower-ai/code-mower/blob/main/docs/release-qualification.md) - [Public Release Checklist](https://github.com/codemower-ai/code-mower/blob/main/docs/public-release-checklist.md) -- [v1.5.0 Release Notes](https://github.com/codemower-ai/code-mower/blob/main/docs/v150-release-notes.md) +- [v1.5.1 Release Notes](https://github.com/codemower-ai/code-mower/blob/main/docs/v151-release-notes.md) - [v1.4.2 Release Notes](https://github.com/codemower-ai/code-mower/blob/main/docs/v142-release-notes.md) -- [v1.5.0 Qualification Contract](https://github.com/codemower-ai/code-mower/blob/main/docs/v150-qualification.md) +- [v1.5.1 Qualification Contract](https://github.com/codemower-ai/code-mower/blob/main/docs/v151-qualification.md) - [v1.4.2 Qualification Record](https://github.com/codemower-ai/code-mower/blob/main/docs/v142-qualification.md) - [Release History And Archived Plans](https://github.com/codemower-ai/code-mower/blob/main/docs/release-history.md) - [Changelog](https://github.com/codemower-ai/code-mower/blob/main/CHANGELOG.md) diff --git a/code-mower-package-manifest.json b/code-mower-package-manifest.json index 4f532282..20f1127a 100644 --- a/code-mower-package-manifest.json +++ b/code-mower-package-manifest.json @@ -307,6 +307,21 @@ "source": "docs/v150-release-runbook.md", "target": "docs/v150-release-runbook.md" }, + { + "kind": "doc", + "source": "docs/v151-qualification.md", + "target": "docs/v151-qualification.md" + }, + { + "kind": "doc", + "source": "docs/v151-release-notes.md", + "target": "docs/v151-release-notes.md" + }, + { + "kind": "doc", + "source": "docs/v151-release-runbook.md", + "target": "docs/v151-release-runbook.md" + }, { "kind": "package", "source": "generated", @@ -2290,6 +2305,6 @@ "module": "code_mower", "name": "code-mower", "source_layout": "src/code_mower", - "version": "1.5.0" + "version": "1.5.1" } } diff --git a/docs/build-loop-in-30-minutes.md b/docs/build-loop-in-30-minutes.md index 32885241..6c316161 100644 --- a/docs/build-loop-in-30-minutes.md +++ b/docs/build-loop-in-30-minutes.md @@ -57,14 +57,14 @@ If path A has not been completed in this repository, do this reviewer-gate checkpoint first. If you already have a merged setup PR with Codex and Claude audit evidence, skip to section 2. -Confirm `code-mower==1.5.0` is visible on the selected package index before +Confirm `code-mower==1.5.1` is visible on the selected package index before running this install block. Prepublication qualification uses the retained -candidate wheel from the [v1.5.0 release runbook](v150-release-runbook.md). +candidate wheel from the [v1.5.1 release runbook](v151-release-runbook.md). ```bash python3.12 --version export CODE_MOWER_PYTHON="$(command -v python3.12)" -pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 gh auth status >/dev/null 2>&1 && echo "gh auth ok" || { echo "gh auth NOT ready"; false; } code-mower init --easy code-mower init --easy --apply --output-dir .code-mower.generated diff --git a/docs/current-state-and-roadmap.md b/docs/current-state-and-roadmap.md index 09ff87a6..4ef04fff 100644 --- a/docs/current-state-and-roadmap.md +++ b/docs/current-state-and-roadmap.md @@ -22,12 +22,12 @@ dry-run-first. ## Current Source And Published Baseline -This source defines Code Mower `v1.5.0`, with package spec -`code-mower==1.5.0`. Confirm the release tag on GitHub Releases and the package +This source defines Code Mower `v1.5.1`, with package spec +`code-mower==1.5.1`. Confirm the release tag on GitHub Releases and the package version on the selected index before using an index install command; source version and publication state are separate facts. See the -[v1.5.0 release notes](https://github.com/codemower-ai/code-mower/blob/main/docs/v150-release-notes.md) -and [qualification contract](https://github.com/codemower-ai/code-mower/blob/main/docs/v150-qualification.md). +[v1.5.1 release notes](https://github.com/codemower-ai/code-mower/blob/main/docs/v151-release-notes.md) +and [qualification contract](https://github.com/codemower-ai/code-mower/blob/main/docs/v151-qualification.md). After publication, the GitHub Release and linked release issue carry the observed source SHA, artifact digests, canary outcomes, publication run and reinstall evidence. @@ -310,7 +310,7 @@ administration/readiness; #920 consumes the immutable candidate to obtain one accepted completion and one accepted confirmed cancellation under an explicit numeric cap while preserving every attempt and reservation; #923 records the tag, publication and independent reinstall evidence. -Slack telemetry/Board/cloud links and rich UX remain v1.5.1. Slack consumes the +Slack telemetry/Board/cloud links and rich UX remain v1.6.0. Slack consumes the durable lifecycle instead of scraping terminal or Board output and carries no raw private context or private reviewer findings. diff --git a/docs/early-adopter-invite-runbook.md b/docs/early-adopter-invite-runbook.md index 16710b2f..c70b3e5f 100644 --- a/docs/early-adopter-invite-runbook.md +++ b/docs/early-adopter-invite-runbook.md @@ -1,7 +1,7 @@ # Early Adopter Invite Runbook Source target: v1.5.0 supervised-pilot baseline. Release invitations and pinned -index installs target `code-mower==1.5.0` only after that version is visible on +index installs target `code-mower==1.5.1` only after that version is visible on the selected package index. Use this runbook for the first 5-10 friendly users before widening Code Mower @@ -41,8 +41,8 @@ Want to try Code Mower for 10 minutes? It is an OSS local-first tool for setting up AI peer-programmer/reviewer lanes on your real codebase, with optional privacy-first cloud reporting. -After v1.5.0 is published, start here: -https://github.com/codemower-ai/code-mower/blob/v1.5.0/docs/try-in-10-minutes.md +After v1.5.1 is published, start here: +https://github.com/codemower-ai/code-mower/blob/v1.5.1/docs/try-in-10-minutes.md Cloud sharing is optional. The default bundle excludes source code, raw diffs, model transcripts, raw stdout/stderr, auth output, and secrets. @@ -57,7 +57,7 @@ Before inviting a user: ```bash python3.12 --version export CODE_MOWER_PYTHON="$(command -v python3.12)" - pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 + pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 code-mower --version ``` diff --git a/docs/first-user-install-rehearsal.md b/docs/first-user-install-rehearsal.md index 3cf550e2..96c10828 100644 --- a/docs/first-user-install-rehearsal.md +++ b/docs/first-user-install-rehearsal.md @@ -1,12 +1,12 @@ # First-User Install Rehearsal -v1.5.0 uses the exact install pin `code-mower==1.5.0`. Verify that version is +v1.5.1 uses the exact install pin `code-mower==1.5.1`. Verify that version is published on the selected index, then verify the command path and version after -installing. The [v1.5.0 qualification contract](v150-qualification.md) defines +installing. The [v1.5.1 qualification contract](v151-qualification.md) defines the required evidence. After publication, the GitHub Release and linked release issue carry the observed source SHA, artifact digests, canary outcomes, publication run and reinstall evidence. Use the -[candidate runbook](v150-release-runbook.md) for +[candidate runbook](v151-release-runbook.md) for prepublication local-wheel rehearsals. Offline preparation does not establish live Slack readiness. @@ -59,7 +59,7 @@ Use the current public tag or release candidate: ```bash code-mower migration package-install-rehearsal \ - --package-spec code-mower==1.5.0 \ + --package-spec code-mower==1.5.1 \ --allow-package-index \ --python "$(command -v python3.12)" \ --json @@ -83,7 +83,7 @@ For a fixed output directory: ```bash code-mower migration package-install-rehearsal \ - --package-spec code-mower==1.5.0 \ + --package-spec code-mower==1.5.1 \ --allow-package-index \ --python "$(command -v python3.12)" \ --work-dir /tmp/code-mower-first-user-rehearsal \ @@ -127,7 +127,7 @@ deciding the package index or the release is broken. For pipx: ```bash export CODE_MOWER_PYTHON="$(command -v python3.12)" -PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 code-mower --version ``` @@ -137,7 +137,7 @@ For uv: env -u UV_INDEX -u UV_DEFAULT_INDEX -u UV_INDEX_URL -u UV_EXTRA_INDEX_URL \ -u UV_FIND_LINKS -u UV_NO_INDEX -u UV_OFFLINE \ uv --no-config --no-cache tool install --python 3.12 --reinstall \ - --default-index https://pypi.org/simple/ code-mower==1.5.0 + --default-index https://pypi.org/simple/ code-mower==1.5.1 code-mower --version ``` @@ -168,7 +168,7 @@ repository after the package install succeeds: ```bash code-mower migration package-install-rehearsal \ - --package-spec code-mower==1.5.0 \ + --package-spec code-mower==1.5.1 \ --allow-package-index \ --repo-path /path/to/external-repo \ --python "$(command -v python3.12)" \ @@ -260,7 +260,7 @@ When a product repository already has Code Mower wrapper files, the same ```bash code-mower migration package-install-rehearsal \ - --package-spec code-mower==1.5.0 \ + --package-spec code-mower==1.5.1 \ --allow-package-index \ --repo-path /path/to/product-repo \ --python "$(command -v python3.12)" \ @@ -321,9 +321,9 @@ If this fails, fix the first-user path before cutting or promoting a release. ## Stable Package-Index Release Procedure The following v1.4.2 publication commands are historical evidence, not the -v1.5.0 sequence. For v1.5.0 build the merge-SHA candidate first, qualify it in +v1.5.1 sequence. For v1.5.1 build the merge-SHA candidate first, qualify it in #918 and explicitly authorized #920, then tag/publish the unchanged source SHA -and the same artifacts through #923. Follow [the current runbook](v150-release-runbook.md). +and the same artifacts through #923. Follow [the current runbook](v151-release-runbook.md). Publish and rehearse the package-index artifacts in this order. After the release tag exists at the release commit, dispatch both package-index diff --git a/docs/friendly-user-rollout-v05.md b/docs/friendly-user-rollout-v05.md index 968c513e..f2b20dd5 100644 --- a/docs/friendly-user-rollout-v05.md +++ b/docs/friendly-user-rollout-v05.md @@ -1,7 +1,7 @@ # Friendly-User Rollout Plan Source target: v1.5.0 supervised-pilot baseline. Release invitations and pinned -index installs target `code-mower==1.5.0` only after that version is visible on +index installs target `code-mower==1.5.1` only after that version is visible on the selected package index. This is the operating plan for the first 5-10 friendly users before Code Mower @@ -33,14 +33,14 @@ out in the invite: ```bash python3.12 --version export CODE_MOWER_PYTHON="$(command -v python3.12)" -pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 ``` -This source defines Code Mower `v1.5.0`, with package spec -`code-mower==1.5.0`. Confirm the release tag on GitHub Releases and the package +This source defines Code Mower `v1.5.1`, with package spec +`code-mower==1.5.1`. Confirm the release tag on GitHub Releases and the package version on the selected index before using an index install command; source version and publication state are separate facts. After publication, see the -[v1.5.0 release](https://github.com/codemower-ai/code-mower/releases/tag/v1.5.0). +[v1.5.1 release](https://github.com/codemower-ai/code-mower/releases/tag/v1.5.1). ## Invite Criteria diff --git a/docs/install.md b/docs/install.md index f651b09c..14522922 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,12 +1,12 @@ # Install And Bootstrap -v1.5.0 uses the exact install pin `code-mower==1.5.0`. Confirm that version is +v1.5.1 uses the exact install pin `code-mower==1.5.1`. Confirm that version is published on the selected index, then verify the command path and version after -installing. The [v1.5.0 qualification contract](v150-qualification.md) defines +installing. The [v1.5.1 qualification contract](v151-qualification.md) defines the required evidence. After publication, the GitHub Release and linked release issue carry the observed source SHA, artifact digests, canary outcomes, publication run and reinstall evidence. Use the -[candidate runbook](v150-release-runbook.md) for +[candidate runbook](v151-release-runbook.md) for prepublication local-wheel rehearsals. Offline preparation does not establish live Slack readiness. @@ -49,7 +49,7 @@ UV_BOOTSTRAP="$HOME/.local/share/code-mower-bootstrap/uv" python3.12 -m venv "$UV_BOOTSTRAP" "$UV_BOOTSTRAP/bin/python" -m pip install --upgrade uv "$UV_BOOTSTRAP/bin/python" -m uv --version -"$UV_BOOTSTRAP/bin/python" -m uv tool install --python 3.12 code-mower==1.5.0 +"$UV_BOOTSTRAP/bin/python" -m uv tool install --python 3.12 code-mower==1.5.1 ``` The module form works even when uv is not yet on `PATH`. After installation, @@ -78,7 +78,7 @@ code-mower --version ``` `command -v code-mower` must print the path belonging to the installer you -chose, and `code-mower --version` must print `code-mower 1.5.0`. A version that +chose, and `code-mower --version` must print `code-mower 1.5.1`. A version that does not match, or a path from a different installer, means an older command is still winning on `PATH`; resolve that before running anything against a repository. @@ -118,7 +118,7 @@ v1.5.0 includes the local audit publisher, which generates gate and standalone verification helpers. Commit that generated set to the repository's default branch before switching local Claude/Codex wrappers to workflow publication. A PR's copy of the verifier has no publication authority. -Before v1.5.0 is published, use its reviewed candidate wheel; afterward, use the +Before v1.5.1 is published, use its reviewed candidate wheel; afterward, use the exact published pin below. The generated self-hosted audit job seals the verdict digest in an immutable @@ -138,7 +138,7 @@ Install with pipx and an explicit Python 3.12+ interpreter: ```bash python3.12 --version export CODE_MOWER_PYTHON="$(command -v python3.12)" -pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 code-mower --version ``` @@ -159,7 +159,7 @@ To replace an existing pipx install with an exact release, use `--force` so the old venv cannot keep serving the previous package: ```bash -PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 code-mower --version ``` @@ -172,7 +172,7 @@ export PIPX_HOME="$CODE_MOWER_AGENT_TOOLS/pipx" export PIPX_BIN_DIR="$CODE_MOWER_AGENT_TOOLS/bin" export PIPX_LOG_DIR="$CODE_MOWER_AGENT_TOOLS/logs" mkdir -p "$PIPX_HOME" "$PIPX_BIN_DIR" "$PIPX_LOG_DIR" -PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 "$PIPX_BIN_DIR/code-mower" --version ``` @@ -183,7 +183,7 @@ interactive shell profile: ```bash uv python install 3.12 -uv tool install --python 3.12 code-mower==1.5.0 +uv tool install --python 3.12 code-mower==1.5.1 code-mower --version ``` @@ -193,7 +193,7 @@ installed command directly from the uv tool bin directory for that session. To replace an existing uv tool install with an exact release: ```bash -uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.5.0 +uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.5.1 code-mower --version ``` @@ -244,7 +244,7 @@ command -v code-mower code-mower --version pipx uninstall code-mower uv python install 3.12 -uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.5.0 +uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.5.1 hash -r command -v code-mower code-mower --version @@ -264,7 +264,7 @@ For pipx: ```bash python3.12 --version export CODE_MOWER_PYTHON="$(command -v python3.12)" -PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 code-mower --version ``` @@ -272,7 +272,7 @@ For uv: ```bash uv python install 3.12 -uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.5.0 +uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.5.1 code-mower --version ``` diff --git a/docs/mirror-removal-runbook.md b/docs/mirror-removal-runbook.md index 305452e9..d8a8e3f0 100644 --- a/docs/mirror-removal-runbook.md +++ b/docs/mirror-removal-runbook.md @@ -32,7 +32,7 @@ Run: ```bash code-mower migration package-install-rehearsal \ - --package-spec code-mower==1.5.0 \ + --package-spec code-mower==1.5.1 \ --allow-package-index \ --repo-path /path/to/product-repo \ --json diff --git a/docs/oss-v1-checklist.md b/docs/oss-v1-checklist.md index 5f24fe76..8acda779 100644 --- a/docs/oss-v1-checklist.md +++ b/docs/oss-v1-checklist.md @@ -47,7 +47,7 @@ history opens the repository. They should be able to confirm: ## Current v1.0 Baseline The historical public-release baseline below is the published `v1.4.2`. -For v1.5.0 follow [the immutable candidate-first runbook](v150-release-runbook.md); +For v1.5.1 follow [the immutable candidate-first runbook](v151-release-runbook.md); these publication and dogfood steps record the previous release procedure. Before widening that release, record: diff --git a/docs/package-customization.md b/docs/package-customization.md index 863a3aa5..f0beedbc 100644 --- a/docs/package-customization.md +++ b/docs/package-customization.md @@ -286,7 +286,7 @@ run: ```bash code-mower migration package-install-rehearsal \ - --package-spec code-mower==1.5.0 \ + --package-spec code-mower==1.5.1 \ --allow-package-index \ --repo-path /path/to/product-repo \ --json diff --git a/docs/provider-matrix.md b/docs/provider-matrix.md index 296c1b99..15ea51dc 100644 --- a/docs/provider-matrix.md +++ b/docs/provider-matrix.md @@ -30,7 +30,7 @@ agent hosting a session is the default orchestrator. The lease, shared Jira brief, and explicit Cursor qualification have been available since `code-mower==1.4.0` and are present in -`code-mower==1.5.0`; see [Participants And Sessions](sessions.md) for +`code-mower==1.5.1`; see [Participants And Sessions](sessions.md) for the operating contract. ## Provider Classes diff --git a/docs/public-release-checklist.md b/docs/public-release-checklist.md index 91bda23c..c19e27ff 100644 --- a/docs/public-release-checklist.md +++ b/docs/public-release-checklist.md @@ -1,8 +1,8 @@ # Code Mower Public Release Checklist -v1.5.0 uses the exact install pin `code-mower==1.5.0`. Verify the command path -and version after installing. Use the [v1.5.0 qualification contract](v150-qualification.md) -for required observations and the [candidate runbook](v150-release-runbook.md) +v1.5.1 uses the exact install pin `code-mower==1.5.1`. Verify the command path +and version after installing. Use the [v1.5.1 qualification contract](v151-qualification.md) +for required observations and the [candidate runbook](v151-release-runbook.md) for prepublication local-wheel rehearsals and publication. Sanitized observed results belong on #923 and the GitHub Release. Index commands select the release after publication; offline preparation does not establish live Slack readiness. @@ -19,8 +19,8 @@ not know the original reference repos. - Apache-2.0 `LICENSE` and `NOTICE` are present. - The package has public releases and reports its version with `code-mower --version`. -- The v1.5.0 source defines package-index entrypoint `code-mower==1.5.0` - (GitHub tag `v1.5.0`). Confirm that the tag and package version are published +- The v1.5.1 source defines package-index entrypoint `code-mower==1.5.1` + (GitHub tag `v1.5.1`). Confirm that the tag and package version are published before using the index command. Its first-run setup diagnostic is `code-mower doctor --adoption --repo OWNER/REPO`, and `code-mower lanes status --repo OWNER/REPO` @@ -29,7 +29,7 @@ not know the original reference repos. [#952](https://github.com/codemower-ai/code-mower/issues/952) closed; `doctor --preflight` and `doctor --v05` remain compatibility presets for scripts. -- The v1.5.0 supervised-pilot source includes Python 3.12+ install hardening, +- The v1.5.1 supervised-pilot source includes Python 3.12+ install hardening, hosted-builder doctor postures, non-expiring token diagnostics, native redacted lane status, local Board, Board history, spend/verdict timelines, owner queue, optional metadata-only agent cards, Board doctor, Board reset, @@ -138,9 +138,9 @@ Before tagging a public release, run these from a clean standalone checkout: First finalize the README opening release statement and the matching CHANGELOG entry, release notes, qualification contract and publication instructions in -the reviewed final release preparation PR. For v1.5.0 that head must include -#1037 and its fixed Slack callback boundary. Follow the -[immutable release text gate](pypi-release.md#immutable-release-text-gate-v150-onward): +the reviewed final release preparation PR. For v1.5.1 that head must include +the five reliability revisions tracked by #1050. Follow the +[immutable release text gate](pypi-release.md): run `python src/code_mower/release_identity.py --tag vX.Y.Z` with the actual proposed tag before creating it. Publication progress belongs in the release issue, not in temporary promises inside the immutable public text. Independent diff --git a/docs/pypi-release.md b/docs/pypi-release.md index 47973b4f..014e5860 100644 --- a/docs/pypi-release.md +++ b/docs/pypi-release.md @@ -1,22 +1,23 @@ # PyPI Release Runbook -Code Mower users install from PyPI. For v1.5.0, build the immutable merge-SHA +Code Mower users install from PyPI. For v1.5.1, build the immutable merge-SHA candidate first, qualify those bytes through #918 and explicitly authorized #920, then tag and publish the unchanged SHA through #923. The final candidate -head must include #1037 and the reviewed release documentation. The release +head must include the five reliability revisions tracked by #1050 and the +reviewed release documentation. The release workflow retrieves the retained candidate and verifies it without rebuilding. -Follow the [v1.5.0 runbook](v150-release-runbook.md) and -[qualification contract](v150-qualification.md); observed evidence belongs on +Follow the [v1.5.1 runbook](v151-release-runbook.md) and +[qualification contract](v151-qualification.md); observed evidence belongs on #923 and the GitHub Release. The v1.4.2 post-merge section below is preserved historical evidence. ```bash CODE_MOWER_PYTHON="$(command -v python3.12)" -pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 ``` v1.4.2 is published; its steps below are the executed record of that release. -They are not the v1.5.0 candidate-first sequence. All mutating steps require the +They are not the v1.5.1 candidate-first sequence. All mutating steps require the supervisor and the recorded owner release decision. @@ -32,14 +33,14 @@ source candidate with publication pending #915 even after publication finished. Editing `main` cannot repair that tagged README or the README embedded in its package. Never rewrite a published tag to correct the wording. -1. Choose the exact release tag, such as `v1.5.0`. Set both the project version - in `pyproject.toml` and `src/code_mower/__init__.py` to `1.5.0`. -2. Add exactly one matching `## 1.5.0` (or `## v1.5.0`) CHANGELOG heading as +1. Choose the exact release tag, such as `v1.5.1`. Set both the project version + in `pyproject.toml` and `src/code_mower/__init__.py` to `1.5.1`. +2. Add exactly one matching `## 1.5.1` (or `## v1.5.1`) CHANGELOG heading as the first versioned entry; an `Unreleased` section may precede it. Describe - what the release contains. A neutral heading such as `## 1.5.0 — release` + what the release contains. A neutral heading such as `## 1.5.1 — release` works before publication and remains true afterward. 3. Set the README's opening source identity statement to - `This source defines Code Mower v1.5.0, with package spec code-mower==1.5.0.` + `This source defines Code Mower v1.5.1, with package spec code-mower==1.5.1.` Markdown backticks and line wrapping are supported. Keep this statement before the first `##` heading and keep its tag and install spec exact. Follow it with the durable instruction to confirm the release tag on GitHub @@ -55,14 +56,15 @@ package. Never rewrite a published tag to correct the wording. name (the tag does not need to exist): ```bash - .venv/bin/python src/code_mower/release_identity.py --tag v1.5.0 + .venv/bin/python src/code_mower/release_identity.py --tag v1.5.1 .venv/bin/python -m code_mower.migration release-readiness --json ``` 5. Obtain independent review on the exact final preparation PR head, green CI, - and the authoritative Code Mower gate before merge. For v1.5.0, require #1037 - and the final reviewed release notes, qualification contract and publication - instructions on that head. After the recorded owner merge process, bind the + and the authoritative Code Mower gate before merge. For v1.5.1, require the + five reliability revisions tracked by #1050 and the final reviewed release + notes, qualification contract and publication instructions on that head. + After the recorded owner merge process, bind the actual merge SHA and build the candidate once. Complete #918 and explicitly capped #920 on that wheel before the #923 owner release decision, tag or publication. Re-run identity on that exact checkout; @@ -89,7 +91,7 @@ always requires final-state text, including TestPyPI rehearsals and GitHub releases marked prerelease. Neither the index nor that flag bypasses the gate. The executed v1.4.2 commands below remain a historical record. Do not mechanically -substitute v1.5.0: its candidate-before-tag procedure is in the current runbook. +substitute v1.5.1: its candidate-before-tag procedure is in the current runbook. ## Current Status @@ -171,7 +173,7 @@ should be the `/releases/latest` result, and exact-version installs should resolve from PyPI. ```bash -RELEASE_VERSION="${RELEASE_VERSION:-1.5.0}" +RELEASE_VERSION="${RELEASE_VERSION:-1.5.1}" RELEASE_TAG="v$RELEASE_VERSION" gh release view "$RELEASE_TAG" \ --repo codemower-ai/code-mower \ @@ -2143,7 +2145,7 @@ being verified instead of copying an older version pin through this reusable section: ```bash -export RELEASE_VERSION="${RELEASE_VERSION:-1.5.0}" +export RELEASE_VERSION="${RELEASE_VERSION:-1.5.1}" export RELEASE_TAG="v$RELEASE_VERSION" export RELEASE_SPEC="code-mower==$RELEASE_VERSION" export RELEASE_WHEEL_STEM="code_mower-${RELEASE_VERSION}" @@ -2293,7 +2295,7 @@ The primary README command stays on the exact current release so an adopter, an agent, and the release rehearsal all install the same artifact: ```bash -RELEASE_VERSION="${RELEASE_VERSION:-1.5.0}" +RELEASE_VERSION="${RELEASE_VERSION:-1.5.1}" RELEASE_SPEC="code-mower==$RELEASE_VERSION" CODE_MOWER_PYTHON="$(command -v python3.12)" pipx install --python "$CODE_MOWER_PYTHON" "$RELEASE_SPEC" diff --git a/docs/quickstart.md b/docs/quickstart.md index 9c8d29fe..5be3d19f 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -11,7 +11,7 @@ To see the value loop before you touch a product repository, open the [Demo Calibration Example](../examples/demo-calibration/README.md), the [Board Demo Rehearsal](../examples/board-demo/README.md), and the [First-User Demo Transcript](first-user-demo-transcript.md) (a v1.4.0 -illustrative shape, not the v1.5.0 source pin). +illustrative shape, not the v1.5.1 source pin). ## 1. Install @@ -24,7 +24,7 @@ is the first-class isolated path: ```bash uv python install 3.12 -uv tool install --python 3.12 code-mower==1.5.0 +uv tool install --python 3.12 code-mower==1.5.1 code-mower --version ``` @@ -33,14 +33,14 @@ For a laptop or workstation that already uses pipx: ```bash python3.12 --version export CODE_MOWER_PYTHON="$(command -v python3.12)" -pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 code-mower --version ``` -`1.5.0` is the supervised-pilot release line. Confirm it is visible on the +`1.5.1` is the supervised-pilot release line. Confirm it is visible on the selected package index before using these pinned commands. Release qualification uses the exact wheel described by -[the candidate runbook](v150-release-runbook.md). If you want a future prerelease instead +[the candidate runbook](v151-release-runbook.md). If you want a future prerelease instead of this exact release target, use: ```bash @@ -502,7 +502,7 @@ export bundle, upload dry run, and CodeMower.com dogfood dry run. ```bash code-mower migration package-install-rehearsal \ - --package-spec code-mower==1.5.0 \ + --package-spec code-mower==1.5.1 \ --allow-package-index \ --python "$(command -v python3.12)" \ --json diff --git a/docs/release-history.md b/docs/release-history.md index a241e84b..3e016c47 100644 --- a/docs/release-history.md +++ b/docs/release-history.md @@ -11,6 +11,9 @@ guidance; use [Install And Bootstrap](install.md) instead. ## Current Release Line +- [v1.5.1 release notes](v151-release-notes.md) +- [v1.5.1 qualification contract](v151-qualification.md) +- [v1.5.1 candidate and publication runbook](v151-release-runbook.md) - [v1.5.0 release notes](v150-release-notes.md) (publication evidence belongs on the GitHub Release) - [v1.5.0 qualification contract](v150-qualification.md) (observed evidence belongs on #923 and the GitHub Release) - [v1.5.0 candidate and publication runbook](v150-release-runbook.md) diff --git a/docs/sessions.md b/docs/sessions.md index 2ac78b13..31b64f53 100644 --- a/docs/sessions.md +++ b/docs/sessions.md @@ -6,7 +6,7 @@ your conversation is the default orchestrator when its role is eligible. The participant picker, host-led session brief, single-orchestrator lease, shared Jira tracker brief, controller host telemetry, and explicit Cursor -qualification documented below are included in `code-mower==1.5.0`. +qualification documented below are included in `code-mower==1.5.1`. Install that pin when following release documentation, or use a contributor checkout when testing later source changes. Role-specific admission and startup lease commands described here are included in the same release; diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index b2640360..7bd3b9a2 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -216,7 +216,7 @@ If pipx should own the command, reinstall the exact release with cache bypass: ```bash export CODE_MOWER_PYTHON="$(command -v python3.12)" -PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 hash -r code-mower --version ``` @@ -227,7 +227,7 @@ path: ```bash pipx uninstall code-mower -uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.5.0 +uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.5.1 hash -r command -v code-mower code-mower --version diff --git a/docs/try-in-10-minutes.md b/docs/try-in-10-minutes.md index fd19a6d4..9034d3b2 100644 --- a/docs/try-in-10-minutes.md +++ b/docs/try-in-10-minutes.md @@ -21,8 +21,8 @@ Use this install matrix: | Environment | Command shape | | --- | --- | -| Laptop/workstation | `pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.0` | -| Hosted agent, CI box, or minimal Linux VM | `uv tool install --python 3.12 code-mower==1.5.0` | +| Laptop/workstation | `pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.1` | +| Hosted agent, CI box, or minimal Linux VM | `uv tool install --python 3.12 code-mower==1.5.1` | | Code Mower contributor checkout | `scripts/dev-python -m venv .venv` then `.venv/bin/python -m pip install -e ".[test]"` | For a cold laptop install: @@ -30,7 +30,7 @@ For a cold laptop install: ```bash python3.12 --version export CODE_MOWER_PYTHON="$(command -v python3.12)" -pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 command -v code-mower code-mower --version ``` @@ -48,10 +48,10 @@ For a repository that already has generated Code Mower support, follow [Upgrade An Existing Repository](upgrade-existing-repo.md) before copying a new `.code-mower.generated` tree. -`1.5.0` is the supervised-pilot release line. Confirm it is visible on the +`1.5.1` is the supervised-pilot release line. Confirm it is visible on the selected package index before using these pinned commands. Release qualification uses the exact wheel from -[the candidate runbook](v150-release-runbook.md). To follow a future prerelease line +[the candidate runbook](v151-release-runbook.md). To follow a future prerelease line instead of pinning this exact build: ```bash diff --git a/docs/upgrade-existing-repo.md b/docs/upgrade-existing-repo.md index 627c16fb..74be67a3 100644 --- a/docs/upgrade-existing-repo.md +++ b/docs/upgrade-existing-repo.md @@ -27,16 +27,16 @@ Upgrade the installer that owns the active command before generating or comparing setup. Running `setup-drift` under 1.4.2 only compares the repository with 1.4.2's packaged files. -Confirm `code-mower==1.5.0` is visible on the selected package index before +Confirm `code-mower==1.5.1` is visible on the selected package index before running either upgrade block. Prepublication qualification uses the retained -candidate wheel from the [v1.5.0 release runbook](v150-release-runbook.md). +candidate wheel from the [v1.5.1 release runbook](v151-release-runbook.md). For an existing pipx install: ```bash python3.12 --version export CODE_MOWER_PYTHON="$(command -v python3.12)" -PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.5.0 +PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.5.1 hash -r command -v code-mower code-mower --version @@ -46,14 +46,14 @@ For an existing uv tool install: ```bash uv python install 3.12 -uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.5.0 +uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.5.1 hash -r command -v code-mower code-mower --version ``` Run only the block for the installer that should keep owning the command. The -final line must print `code-mower 1.5.0`, and `command -v` must still identify +final line must print `code-mower 1.5.1`, and `command -v` must still identify that installer. If it does not, resolve the competing pipx/uv/checkout path before changing repository files. This is the tool upgrade; the reviewed repository setup upgrade follows below. diff --git a/docs/v151-qualification.md b/docs/v151-qualification.md new file mode 100644 index 00000000..ee7534ef --- /dev/null +++ b/docs/v151-qualification.md @@ -0,0 +1,43 @@ +# v1.5.1 qualification contract and evidence matrix + +This document defines the immutable acceptance contract for #1056 under the +v1.5.1 epic #1050. Observed results belong on #1056, the public implementation +PRs, and the GitHub Release. Do not edit qualified source to insert later +results or private data. + +## Identity + +| Item | Required identity | +| --- | --- | +| Release source | The release PR's actual `mergeCommit.oid` after independent exact-head review, CI, and authoritative gate | +| Candidate | First successful attempt of `Code Mower Immutable Candidate`, dispatched on `main` while `GITHUB_SHA` equals that merge SHA | +| Artifacts | Retained `code_mower-1.5.1-py3-none-any.whl`, `code_mower-1.5.1.tar.gz`, `candidate.json`, and `rehearsal.json` | +| Publication | Annotated `v1.5.1` tag, retained candidate run, production publication run, non-publishing release-event run, and byte-identical GitHub assets | +| Installed release | Canonical PyPI download whose version and SHA-256 match the accepted candidate | + +## Required observations + +| Boundary | Acceptance | +| --- | --- | +| Source | #1051, #1052, #1058, #1059, #1057, and #1060 are ancestors; release text is final; privacy, package inventory, Python 3.12–3.14, containment, wheel rehearsal, Board qualification, exact-head independent review, and gate pass | +| Fresh hosted install | A minimal hosted environment without uv or pipx bootstraps through the documented Python path, installs the exact candidate, and reports `code-mower 1.5.1` | +| Upgrade | A verified v1.5.0 install upgrades to the exact candidate while preserving synthetic configuration and state | +| Remote observer | Checkout-free orchestrator-only doctor uses the packaged-starter observer plan, keeps local/provider/campaign checks quiet, and emits no local path | +| Safe init | Existing root policy is preserved; implicit packaged-starter apply refuses with exact recovery; generated lane targets are unique | +| Board | Transient and persistent lifecycle, version discoverability, optional/unavailable lineage, empty-workflow presentation, and closed privacy projection pass | +| Graphify | Installed reader accepts the qualified generation, excludes `doc_ref` from code, and returns bounded completeness/readiness results | +| Slack | Basic private-workspace setup, doctor, authorization, and lifecycle commands pass. Slack telemetry remains deferred to v1.6.0 | +| Audit publication | A single-lane result and a multi-lane result bind to their exact source job; an ineligible writer lane can self-exclude without blocking an eligible lane | +| Hosted Board canary | Exactly one bounded hosted Board canary records provider completion and exit, the closed Board projection, privacy boundary, aggregate campaign ACU cap, every create attempt, and uncertainty; authorized usage and settled usage are separate facts | +| Cloud evidence | Only allowlisted metadata is uploaded to codemower.com; a fresh authenticated dashboard observation verifies the accepted upload separately from its receipt | +| Publish/reinstall | The unchanged candidate is verified before tag, publication does not rebuild, GitHub assets match byte-for-byte, release-event publication jobs stay disabled, and a clean canonical reinstall matches the release identity | + +Raw source, diffs, prompts, transcripts, provider output, credentials, local +paths, private Slack content, private graph data, and private Board/session state +remain local. A successful upload receipt does not prove aggregate freshness; +the fresh dashboard observation is a separate requirement. + +Follow [the v1.5.1 runbook](v151-release-runbook.md). Any source or packaged +document change after candidate creation invalidates the candidate. A failed, +missing, expired, rerun, or ambiguous candidate is a stop and does not authorize +a replacement build without a newly reviewed release source. diff --git a/docs/v151-release-notes.md b/docs/v151-release-notes.md new file mode 100644 index 00000000..0970465b --- /dev/null +++ b/docs/v151-release-notes.md @@ -0,0 +1,56 @@ +# Code Mower v1.5.1 Release Notes + +v1.5.1 is a focused reliability release based on five independent v1.5.0 +adoption passes. It keeps the v1.5.0 product boundary: Claude and Codex remain +the default, Graphify stays optional, and Slack remains the basic private +workspace control surface. Slack telemetry, hosted aggregation, Board links in +Slack, and richer Slack UX remain deferred to v1.6.0. + +## What changed + +- **Hosted installation is explicit.** The docs provide a Python-based uv + bootstrap for minimal machines, distinguish a remote observer from a local + operator, and state the evidence and role boundaries for dry runs. +- **Tests are host-independent.** Release workflow subprocess tests use the + interpreter running the suite and disable interactive Git credential prompts. +- **Audit publication is lane-exact.** Reviewer seals bind to the exact source + job, matrix lane, run, and attempt. A Codex lane can self-exclude while a + Claude lane in the same workflow publishes independently. +- **Remote observers no longer need a checkout.** `code-mower doctor --adoption + --orchestrator-only --repo OWNER/REPO` uses a labeled packaged-starter plan, + keeps irrelevant local checks quiet, and redacts local paths from its + share-oriented result. +- **Initialization protects repository policy.** `code-mower init --easy` + detects an existing root `code-mower.yml`, refuses an implicit packaged + starter beside it, prints exact recovery commands, and emits each selected + lane configuration once. +- **Status is more precise.** Missing optional lineage policy no longer hides a + readable PR or green gate. Terminal and Board views distinguish optional from + unreadable lineage, render empty recent workflows as `none`, and show + `Serving version: …` in the primary Board header. + +## Upgrade + +Install or upgrade one stable tool environment, then confirm its identity: + +```bash +uv tool install --python 3.12 --reinstall --refresh-package code-mower \ + code-mower==1.5.1 +code-mower --version +code-mower doctor --adoption --repo OWNER/REPO --concise +``` + +A laptop may use `pipx` instead; a hosted machine with neither tool can follow +[Install And Bootstrap](install.md). Existing repositories should preview +`code-mower migration setup-drift` and `code-mower init --easy` before applying +generated files. + +## Qualification boundary + +The release is built once from the reviewed release PR merge SHA and qualified +as the exact retained wheel and sdist. Acceptance covers fresh hosted install, +v1.5.0 upgrade, remote observer, safe init, transient and persistent Board, +Graphify, basic Slack, single-lane and multi-lane audit publication, one bounded +hosted Board canary, metadata-only codemower.com upload, canonical PyPI install, +and GitHub Release identity. The immutable contract is +[v1.5.1 qualification](v151-qualification.md). diff --git a/docs/v151-release-runbook.md b/docs/v151-release-runbook.md new file mode 100644 index 00000000..d61813e9 --- /dev/null +++ b/docs/v151-release-runbook.md @@ -0,0 +1,126 @@ +# v1.5.1 immutable candidate and publication runbook + +The release PR performs no tag or package publication. Qualification consumes +the exact retained candidate built from its merge SHA. Index commands select +`code-mower==1.5.1` only after publication. Slack telemetry remains deferred to v1.6.0. + +## 1. Review and merge the release PR + +Require one writer, independent exact-head audit with no P0/P1/P2 findings, +Python 3.12–3.14 CI, containment, Board qualification, wheel rehearsal, +release-integrity checks, and the authoritative gate. Confirm every required +implementation PR in [the qualification contract](v151-qualification.md) is an +ancestor. Bind the merged PR and SHA: + +```bash +set -euo pipefail +REPO=codemower-ai/code-mower +RELEASE_PR=REPLACE_WITH_RELEASE_PR +test "$(gh pr view "$RELEASE_PR" --repo "$REPO" --json state --jq '.state')" = MERGED +RELEASE_SHA="$(gh pr view "$RELEASE_PR" --repo "$REPO" --json mergeCommit --jq '.mergeCommit.oid')" +[[ "$RELEASE_SHA" =~ ^[0-9a-f]{40}$ ]] +``` + +## 2. Build and retain the merge-SHA candidate once + +Dispatch while `main` still equals the release SHA. The workflow rejects a +branch mismatch and any rerun. + +```bash +gh workflow run release-candidate.yml --repo "$REPO" --ref main \ + -f expected_sha="$RELEASE_SHA" -f release_pr="$RELEASE_PR" +``` + +Inspect the workflow inputs and first-attempt result, bind its exact run ID, and +download the retained artifacts: + +```bash +CANDIDATE_RUN_ID=REPLACE_WITH_VERIFIED_RUN_ID +CANDIDATE_DIR="$PWD/v151-candidate-$CANDIDATE_RUN_ID" +gh run download "$CANDIDATE_RUN_ID" --repo "$REPO" \ + --name code-mower-candidate --dir "$CANDIDATE_DIR" +python scripts/release_candidate.py verify --dist "$CANDIDATE_DIR" \ + --source-sha "$RELEASE_SHA" --require-candidate +``` + +`candidate.json` and `rehearsal.json` must bind the release PR, source SHA, +wheel digest, sdist digest, complete inventories, and every required installed +wheel rehearsal. Retain this pair. Publication downloads it and does not rebuild. +A source or packaged-doc change invalidates it. + +## 3. Qualify the exact candidate + +Use disposable environments and the retained wheel for: + +- a fresh install without uv or pipx using the documented Python bootstrap; +- an upgrade from v1.5.0 with state preservation; +- checkout-free remote observer doctor output with no local path; +- safe init with an existing root configuration and unique generated targets; +- transient and persistent Board lifecycle plus version/status presentation; +- Graphify build, status, connection, and query-reader compatibility; +- the basic Slack lifecycle, without adding telemetry or richer UX; and +- single-lane and multi-lane audit publication, including writer-lane + self-exclusion and exact `source_job_id` binding. + +Record only public identifiers and sanitized outcomes. Keep source, logs, +credentials, Slack content, graph content, transcripts, and local paths private. + +## 4. Observe the bounded hosted Board canary + +Use the owner-authorized numeric aggregate campaign ACU cap and one bounded +provider create. Reconcile an uncertain create before any retry. Record every +reservation and create attempt, provider completion and provider exit, the +closed Board projection, privacy outcome, authorized usage, settled usage, and +any remaining billing uncertainty separately. Do not infer provider exit from +logical Code Mower completion. + +Upload only the approved metadata-only evidence to codemower.com. Record the +upload receipt, then use a fresh dashboard view in an authenticated session to verify the new +evidence independently. An accepted HTTP response with a stale aggregate is +not the dashboard observation. + +## 5. Owner decision, unchanged tag, and publication + +After the exact candidate and hosted observation pass, recheck the release PR, +review, CI, gate, candidate digests, codemower.com evidence, and current PyPI +state. Tag the release merge SHA and run a no-publish verification first: + +```bash +git fetch origin "$RELEASE_SHA" +test "$(gh pr view "$RELEASE_PR" --repo "$REPO" --json mergeCommit --jq '.mergeCommit.oid')" = "$RELEASE_SHA" +git tag -a v1.5.1 "$RELEASE_SHA" -m 'Code Mower v1.5.1' +git push origin refs/tags/v1.5.1 +test "$(git rev-list -n 1 v1.5.1)" = "$RELEASE_SHA" +gh workflow run release.yml --repo "$REPO" --ref v1.5.1 \ + -f expected_sha="$RELEASE_SHA" -f candidate_run_id="$CANDIDATE_RUN_ID" \ + -f publish_testpypi=false -f publish_pypi=false +``` + +Verify the selected run's tag, SHA, candidate identity, digests, inventories, +and rehearsal. Then publish the same candidate: + +```bash +gh workflow run release.yml --repo "$REPO" --ref v1.5.1 \ + -f expected_sha="$RELEASE_SHA" -f candidate_run_id="$CANDIDATE_RUN_ID" \ + -f publish_testpypi=false -f publish_pypi=true +``` + +Keep release-event publish variables explicitly false and bind +`CODE_MOWER_CANDIDATE_RUN_ID` to the accepted run. Create the GitHub Release +with the two files from `CANDIDATE_DIR`, the immutable release notes, and only +sanitized observed identifiers. Require the release-event verification run to +consume the same candidate and skip both publication jobs. Compare downloaded +GitHub assets byte-for-byte with the retained pair. + +## 6. Independent canonical reinstall and closeout + +Download `code-mower==1.5.1` from canonical PyPI without cache or extra indexes. +Verify the wheel and sdist digests, `code-mower --version`, installed metadata, +remote observer, safe init, Board, Graphify, and basic Slack behavior. Never +downgrade live v2 claims during rollback; use disposable state for rollback +checks. + +Record the release SHA, candidate/publication/release-event run IDs, artifact +digests, canonical reinstall, hosted canary, and codemower.com dashboard result +on #1056 and the GitHub Release. Close #951 and #945 only after the Board canary +is observed, then close #1050 and #1056 when every required item is complete. diff --git a/pyproject.toml b/pyproject.toml index 9519e0a0..92fa7732 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "code-mower" -version = "1.5.0" +version = "1.5.1" description = "Multi-reviewer AI code audit orchestration" requires-python = ">=3.12" readme = "README.md" @@ -48,6 +48,9 @@ where = ["src"] "docs/v150-release-notes.md", "docs/v150-qualification.md", "docs/v150-release-runbook.md", + "docs/v151-release-notes.md", + "docs/v151-qualification.md", + "docs/v151-release-runbook.md", "docs/slack-setup.md", "docs/graphify-setup.md", ] diff --git a/scripts/rehearse_v150.py b/scripts/rehearse_v151.py similarity index 99% rename from scripts/rehearse_v150.py rename to scripts/rehearse_v151.py index 198887c2..fa6f214f 100644 --- a/scripts/rehearse_v150.py +++ b/scripts/rehearse_v151.py @@ -214,7 +214,7 @@ def cli(python, *args, expected=0): py = fresh / "bin/python" pip(py, "install", "--no-cache-dir", "--index-url", "https://pypi.org/simple/", wheel) pip(py, "check") - assert cli(py, "--version").strip() == "code-mower 1.5.0" + assert cli(py, "--version").strip() == "code-mower 1.5.1" installed(py, """ import importlib.util import code_mower @@ -308,7 +308,7 @@ def state_hashes(): pip(py, "install", "--no-cache-dir", "--index-url", "https://pypi.org/simple/", old_wheel) assert cli(py, "--version").strip() == "code-mower 1.4.2" pip(py, "install", "--no-index", "--no-deps", "--upgrade", wheel) - assert cli(py, "--version").strip() == "code-mower 1.5.0" + assert cli(py, "--version").strip() == "code-mower 1.5.1" assert before == state_hashes() checks.append("upgrade_1_4_2_to_exact_wheel_preserves_synthetic_state") pip(py, "install", "--no-index", "--no-deps", "--force-reinstall", old_wheel) diff --git a/scripts/release_candidate.py b/scripts/release_candidate.py index 3d123bef..0e65a1b8 100644 --- a/scripts/release_candidate.py +++ b/scripts/release_candidate.py @@ -1,4 +1,4 @@ -"""Build once, inspect, and verify an exact-source v1.5.0 artifact pair. +"""Build once, inspect, and verify an exact-source v1.5.1 artifact pair. This script never tags, publishes, contacts Slack or invokes a provider. The candidate workflow supplies the merged PR identity; local builds are rehearsals. @@ -17,7 +17,7 @@ import tarfile import zipfile -VERSION = "1.5.0" +VERSION = "1.5.1" SCHEMA = "code_mower.release_candidate.v1" NAMES = (f"code_mower-{VERSION}-py3-none-any.whl", f"code_mower-{VERSION}.tar.gz") MODULES = ( @@ -26,9 +26,9 @@ "slack_setup.py", "slack_readiness.py", "supervisor_contract_v2.py", "templates/slack/hosted-app-manifest.json", ) -DOCS = ("v150-release-notes.md", "v150-qualification.md", "v150-release-runbook.md", +DOCS = ("v151-release-notes.md", "v151-qualification.md", "v151-release-runbook.md", "slack-setup.md", "graphify-setup.md") -REHEARSAL_SCHEMA = "code_mower.v150_rehearsal.v1" +REHEARSAL_SCHEMA = "code_mower.v151_rehearsal.v1" CANARY_EQUIVALENCE_SCHEMA = "code_mower.canary_candidate_equivalence.v1" GRAPHIFY_CHECKS = ( "graphify_doc_ref_excluded_reader_available", @@ -57,9 +57,9 @@ "code_mower/templates/workflows/local-audit-publication.yml.j2", "code_mower/templates/workflows/trailer-comment-labeler.yml.j2", f"code_mower-{VERSION}.data/data/share/code-mower/docs/graphify-setup.md", - f"code_mower-{VERSION}.data/data/share/code-mower/docs/v150-qualification.md", - f"code_mower-{VERSION}.data/data/share/code-mower/docs/v150-release-notes.md", - f"code_mower-{VERSION}.data/data/share/code-mower/docs/v150-release-runbook.md", + f"code_mower-{VERSION}.data/data/share/code-mower/docs/v151-qualification.md", + f"code_mower-{VERSION}.data/data/share/code-mower/docs/v151-release-notes.md", + f"code_mower-{VERSION}.data/data/share/code-mower/docs/v151-release-runbook.md", f"code_mower-{VERSION}.dist-info/METADATA", f"code_mower-{VERSION}.dist-info/RECORD", }) diff --git a/src/code_mower/__init__.py b/src/code_mower/__init__.py index 9fb85aaa..d25c237e 100644 --- a/src/code_mower/__init__.py +++ b/src/code_mower/__init__.py @@ -1,3 +1,3 @@ """Code Mower package.""" -__version__ = "1.5.0" +__version__ = "1.5.1" diff --git a/src/code_mower/package_manifest.py b/src/code_mower/package_manifest.py index b9fa0720..43a6b66f 100644 --- a/src/code_mower/package_manifest.py +++ b/src/code_mower/package_manifest.py @@ -646,6 +646,9 @@ ("docs/v150-release-notes.md", "docs/v150-release-notes.md", "doc"), ("docs/v150-qualification.md", "docs/v150-qualification.md", "doc"), ("docs/v150-release-runbook.md", "docs/v150-release-runbook.md", "doc"), + ("docs/v151-release-notes.md", "docs/v151-release-notes.md", "doc"), + ("docs/v151-qualification.md", "docs/v151-qualification.md", "doc"), + ("docs/v151-release-runbook.md", "docs/v151-release-runbook.md", "doc"), ("docs/graphify-setup.md", "docs/graphify-setup.md", "doc"), ("docs/v140-release-runbook.md", "docs/v140-release-runbook.md", "doc"), ("docs/v140-release-notes.md", "docs/v140-release-notes.md", "doc"), diff --git a/src/code_mower/release_readiness.py b/src/code_mower/release_readiness.py index 555bab57..c93d1095 100644 --- a/src/code_mower/release_readiness.py +++ b/src/code_mower/release_readiness.py @@ -25,7 +25,7 @@ "docs/pypi-release.md", "docs/public-release-checklist.md", "docs/release-qualification.md", - "docs/v150-release-runbook.md", + "docs/v151-release-runbook.md", ) REQUIRED_PUBLIC_PACKAGE_SPEC_DOC_PATHS = ( "README.md", @@ -1492,18 +1492,18 @@ def _job_text(job: Any) -> str: def _candidate_runbook_checks(repo_path: Path) -> tuple[list[str], list[str]]: - """The v1.5 sequence qualifies the merge-SHA artifacts before tagging. + """The v1.5.1 sequence qualifies the merge-SHA artifacts before tagging. The v1.4 post-publication campaign runbook stays historical. Checking its ordering against a new version would require tagging before qualification. These are static documentation checks, not private acceptance evidence. """ - text = _read_text_if_exists(repo_path / "docs/v150-release-runbook.md") + text = _read_text_if_exists(repo_path / "docs/v151-release-runbook.md") order = ( "## 1. Review and merge", "## 2. Build and retain", - "gh workflow run release-candidate.yml", "## 3. Private acceptance", - "## 4. Explicitly authorize", "## 5. Owner decision", - 'git tag -a v1.5.0 "$RELEASE_SHA"', + "gh workflow run release-candidate.yml", "## 3. Qualify the exact candidate", + "## 4. Observe the bounded hosted Board canary", "## 5. Owner decision", + 'git tag -a v1.5.1 "$RELEASE_SHA"', "-f publish_testpypi=false -f publish_pypi=false", "-f publish_testpypi=false -f publish_pypi=true", "## 6. Independent canonical reinstall", @@ -1513,10 +1513,13 @@ def _candidate_runbook_checks(repo_path: Path) -> tuple[list[str], list[str]]: "--json mergeCommit --jq '.mergeCommit.oid'", "--require-candidate", "candidate.json", "rehearsal.json", '-f candidate_run_id="$CANDIDATE_RUN_ID"', - 'test "$(git rev-list -n 1 v1.5.0)" = "$RELEASE_SHA"', - "accepted completion", "accepted confirmed-cancellation", - "aggregate campaign ACU", "Count and disclose every reservation", - "does not rebuild", "Never downgrade live v2 claims", + 'test "$(git rev-list -n 1 v1.5.1)" = "$RELEASE_SHA"', + "fresh install without uv or pipx", "upgrade from v1.5.0", + "remote observer", "safe init", "Graphify", "basic Slack lifecycle", + "single-lane", "multi-lane", "aggregate campaign ACU", + "provider exit", "authorized usage", "settled usage", + "metadata-only", "fresh dashboard", "does not rebuild", + "Slack telemetry remains deferred to v1.6.0", "independent exact-head audit", "authoritative gate", ) return _unordered_markers(text, order), [item for item in assertions if item not in text] @@ -1600,7 +1603,7 @@ def render_release_readiness(repo_path: Path) -> dict[str, Any]: ) if candidate_workflow_used: missing_runbook_markers, missing_runbook_assertions = _candidate_runbook_checks(repo_path) - runbook_markers = ("docs/v150-release-runbook.md: candidate, private acceptance, canaries, tag, publish",) + runbook_markers = ("docs/v151-release-runbook.md: candidate, private acceptance, canaries, tag, publish",) runbook_assertions = ("merge SHA and retained artifact binding; explicit owner gates",) # Legacy checks above describe the preserved v1.4 publication procedure. # The new procedure has its own ordered gates and artifact assertions. @@ -2118,7 +2121,7 @@ def render_release_readiness(repo_path: Path) -> dict[str, Any]: "title": "After merge: build once, then #918 and explicitly authorized #920 before tagging/publication", "command": 'gh workflow run release-candidate.yml --repo codemower-ai/code-mower --ref main ' '-f expected_sha="$RELEASE_SHA" -f release_pr="$RELEASE_PR"', - "url": "https://github.com/codemower-ai/code-mower/blob/main/docs/v150-release-runbook.md", + "url": "https://github.com/codemower-ai/code-mower/blob/main/docs/v151-release-runbook.md", }) incomplete_dispatch_actions = _incomplete_dispatch_actions(workflow, next_actions) incomplete_documented_dispatches = _incomplete_documented_dispatches(workflow, docs) diff --git a/tests/test_release_hygiene.py b/tests/test_release_hygiene.py index bdbd86c3..4e2fdf0a 100644 --- a/tests/test_release_hygiene.py +++ b/tests/test_release_hygiene.py @@ -103,7 +103,7 @@ def _reported_manifest_identity(manifest_bytes: bytes) -> dict: class ReleaseHygieneTests(unittest.TestCase): def test_version_is_current_supervised_pilot_release(self) -> None: - self.assertEqual(__version__, "1.5.0") + self.assertEqual(__version__, "1.5.1") def test_dogfood_repo_has_real_root_config(self) -> None: config_path = ROOT / "code-mower.yml" @@ -268,7 +268,7 @@ def test_install_and_upgrade_docs_cover_agent_paths(self) -> None: self.assertIn("Cold Install Vs Upgrade", install) self.assertIn("Switching Between pipx And uv", install) self.assertIn("uv tool install --python 3.12 --reinstall --refresh-package", install) - self.assertIn("code-mower==1.5.0", troubleshooting) + self.assertIn("code-mower==1.5.1", troubleshooting) self.assertNotIn("code-mower==0.8.0b1", troubleshooting) self.assertIn("pipx uninstall code-mower", install) for env_name in ("PIPX_HOME", "PIPX_BIN_DIR", "PIPX_LOG_DIR"): @@ -1244,7 +1244,7 @@ def test_direct_cli_execution_points_to_package_or_dev_wrapper(self) -> None: ) self.assertNotEqual(completed.returncode, 0) - self.assertIn("pipx install code-mower==1.5.0", completed.stderr) + self.assertIn("pipx install code-mower==1.5.1", completed.stderr) self.assertIn("scripts/dev-python -m venv .venv", completed.stderr) self.assertIn(".venv/bin/code-mower", completed.stderr) self.assertNotIn("PYTHONPATH=src", completed.stderr) @@ -5741,11 +5741,11 @@ def test_package_materializer_can_run_from_extracted_checkout(self) -> None: (output_dir / "src/code_mower/cloud_client/dogfood.py").is_file() ) self.assertIn( - 'version = "1.5.0"', + 'version = "1.5.1"', (output_dir / "pyproject.toml").read_text(encoding="utf-8"), ) self.assertIn( - '__version__ = "1.5.0"', + '__version__ = "1.5.1"', (output_dir / "src/code_mower/__init__.py").read_text( encoding="utf-8" ), @@ -7759,7 +7759,7 @@ def test_normalized_release_version_matches_packaging_semantics(self) -> None: self.assertTrue(agree(left, right)) different = ( - ("1.4.2", "1.5.0"), + ("1.4.2", "1.5.1"), ("1.4.2", "1.4.2rc1"), ("1.4.2", "1.4.2.post1"), ("1.4.2", "1.4.2.dev1"), @@ -7831,7 +7831,7 @@ def test_requested_candidate_version_accepts_only_exact_requirements(self) -> No "code-mower===1.4.2", "code-mower==1.4.*", "code-mower==1.4.2,!=1.4.2", - "code-mower>=1.4.2,<1.5.0", + "code-mower>=1.4.2,<1.5.1", "code-mower[coworker]==1.4.2", 'code-mower==1.4.2; python_version >= "3.12"', "code-mower==not-a-version", @@ -8261,10 +8261,10 @@ def test_release_readiness_reports_package_index_promotion_gate(self) -> None: payload = release_readiness.render_release_readiness(ROOT) self.assertEqual(payload["status"], "pass") - self.assertEqual(payload["version"], "1.5.0") - self.assertEqual(payload["release_tag"], "v1.5.0") - self.assertEqual(payload["alpha_tag"], "v1.5.0") - self.assertEqual(payload["package_index_spec"], "code-mower==1.5.0") + self.assertEqual(payload["version"], "1.5.1") + self.assertEqual(payload["release_tag"], "v1.5.1") + self.assertEqual(payload["alpha_tag"], "v1.5.1") + self.assertEqual(payload["package_index_spec"], "code-mower==1.5.1") check_ids = {check["id"]: check for check in payload["checks"]} self.assertEqual(check_ids["package-version-consistency"]["status"], "pass") self.assertEqual( @@ -8273,7 +8273,7 @@ def test_release_readiness_reports_package_index_promotion_gate(self) -> None: ) manifest_check = check_ids["committed-package-manifest-version"] self.assertEqual(manifest_check["status"], "pass") - self.assertEqual(manifest_check["detail"]["manifest_version"], "1.5.0") + self.assertEqual(manifest_check["detail"]["manifest_version"], "1.5.1") self.assertEqual(check_ids["testpypi-gate"]["status"], "pass") self.assertEqual(check_ids["pypi-gate"]["status"], "pass") self.assertEqual(check_ids["trusted-publishing-runbook"]["status"], "pass") @@ -8283,7 +8283,7 @@ def test_release_readiness_reports_package_index_promotion_gate(self) -> None: self.assertEqual(check_ids["public-support-redaction-guidance"]["status"], "pass") commands = {action["id"]: action["command"] for action in payload["next_actions"]} urls = {action["id"]: action.get("url", "") for action in payload["next_actions"]} - self.assertIn("--ref v1.5.0", commands["dry-run-release-workflow"]) + self.assertIn("--ref v1.5.1", commands["dry-run-release-workflow"]) self.assertNotIn("--ref main", commands["dry-run-release-workflow"]) self.assertIn("publish-testpypi-candidate", commands) self.assertNotIn("testpypi-install-rehearsal", commands) @@ -8296,9 +8296,9 @@ def test_release_readiness_reports_package_index_promotion_gate(self) -> None: self.assertIn("CODE_MOWER_CANDIDATE_RUN_ID", release) self.assertIn("CODE_MOWER_TESTPYPI_PUBLISH", release) self.assertIn("CODE_MOWER_PYPI_PUBLISH", release) - self.assertIn('--title "Code Mower v1.5.0"', release) + self.assertIn('--title "Code Mower v1.5.1"', release) self.assertIn('--notes-file "$GITHUB_RELEASE_NOTES"', release) - self.assertIn('$CANDIDATE_DIR/code_mower-1.5.0-py3-none-any.whl', release) + self.assertIn('$CANDIDATE_DIR/code_mower-1.5.1-py3-none-any.whl', release) self.assertEqual( actions["create-github-release"]["required_env"], ["CANDIDATE_DIR", "CANDIDATE_RUN_ID", "GITHUB_RELEASE_NOTES"], @@ -8426,7 +8426,7 @@ def test_release_readiness_fails_on_materialized_package_version_drift( check_ids = {check["id"]: check for check in payload["checks"]} check = check_ids["materialized-package-version-consistency"] self.assertEqual(check["status"], "fail") - self.assertEqual(check["detail"]["source_version"], "1.5.0") + self.assertEqual(check["detail"]["source_version"], "1.5.1") self.assertEqual(check["detail"]["generated_init_version"], "0.0.0") def test_release_readiness_fails_on_committed_manifest_version_drift(self) -> None: @@ -8442,7 +8442,7 @@ def test_release_readiness_fails_on_committed_manifest_version_drift(self) -> No self.assertEqual(payload["status"], "fail") self.assertEqual(check["status"], "fail") self.assertEqual(check["detail"]["manifest_version"], "0.5.0b53") - self.assertEqual(check["detail"]["init_version"], "1.5.0") + self.assertEqual(check["detail"]["init_version"], "1.5.1") def _manifest_drift_check(self, mutate: Callable[[dict], None]) -> dict: committed = json.loads( @@ -11132,8 +11132,8 @@ def test_public_release_baseline_helpers_derive_announcement_links(self) -> None self.assertEqual( code_mower_versioning.public_baseline_sentence(__version__), ( - "This source defines Code Mower `v1.5.0`, with package spec " - "`code-mower==1.5.0`. Confirm the release tag on GitHub Releases " + "This source defines Code Mower `v1.5.1`, with package spec " + "`code-mower==1.5.1`. Confirm the release tag on GitHub Releases " "and the package version on the selected index before using an " "index install command; source version and publication state are " "separate facts." @@ -11143,7 +11143,7 @@ def test_public_release_baseline_helpers_derive_announcement_links(self) -> None code_mower_versioning.tagged_doc_url(__version__), ( "https://github.com/codemower-ai/code-mower/blob/" - "v1.5.0/docs/try-in-10-minutes.md" + "v1.5.1/docs/try-in-10-minutes.md" ), ) @@ -11203,7 +11203,7 @@ def test_public_docs_match_current_commands_and_privacy_boundary(self) -> None: encoding="utf-8" ) self.assertIn("Documentation on `main` follows the source on `main`", readme) - self.assertIn("included in `code-mower==1.5.0`", sessions) + self.assertIn("included in `code-mower==1.5.1`", sessions) self.assertIn("# Code Mower v1.4.2 Release Notes", release_notes) self.assertIn("The privacy boundary is unchanged.", release_notes) release_history = (ROOT / "docs" / "release-history.md").read_text( @@ -11257,11 +11257,11 @@ def test_current_release_docs_record_package_index_procedure(self) -> None: for text in (readme, current_state, rollout): self.assertIn(current_status, " ".join(text.split())) self.assertIn( - "The v1.5.0 source defines package-index entrypoint `code-mower==1.5.0`\n" - " (GitHub tag `v1.5.0`)", + "The v1.5.1 source defines package-index entrypoint `code-mower==1.5.1`\n" + " (GitHub tag `v1.5.1`)", public_release, ) - self.assertIn("The v1.5.0 supervised-pilot source includes", public_release) + self.assertIn("The v1.5.1 supervised-pilot source includes", public_release) self.assertIn( "`code-mower lanes status --repo OWNER/REPO` as the operator snapshot", " ".join(public_release.split()), @@ -11639,7 +11639,7 @@ def test_install_docs_cover_supported_adoption_paths(self) -> None: self.assertIn("Python 3.12 or newer", install) self.assertIn('pipx install --python "$CODE_MOWER_PYTHON"', install) - self.assertIn("uv tool install --python 3.12 code-mower==1.5.0", install) + self.assertIn("uv tool install --python 3.12 code-mower==1.5.1", install) self.assertIn( 'PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON"', install, @@ -12022,7 +12022,7 @@ def test_next_steps_includes_cloud_upload_dry_run_after_export(self) -> None: "doctor --adoption --repo codemower-ai/code-mower", doctor_step["command"], ) - self.assertIn("code-mower==1.5.0", package_step["command"]) + self.assertIn("code-mower==1.5.1", package_step["command"]) self.assertIn("--allow-package-index", package_step["command"]) self.assertIn("current published PyPI package", package_step["why"]) self.assertIn("first_user_readiness", package_step["why"]) diff --git a/tests/test_release_v142.py b/tests/test_release_v142.py index 3569d3f1..61a7d645 100644 --- a/tests/test_release_v142.py +++ b/tests/test_release_v142.py @@ -85,8 +85,8 @@ def test_current_docs_do_not_still_call_v141_the_current_release(self): def test_shared_baseline_sentence_matches_the_published_version(self): sentence = versioning.public_baseline_sentence(__version__) - self.assertIn("`v1.5.0`", sentence) - self.assertIn("`code-mower==1.5.0`", sentence) + self.assertIn("`v1.5.1`", sentence) + self.assertIn("`code-mower==1.5.1`", sentence) for relative in ("README.md", "docs/current-state-and-roadmap.md", "docs/friendly-user-rollout-v05.md"): with self.subTest(doc=relative): @@ -188,11 +188,11 @@ def test_never_expiry_is_what_init_actually_advertises(self): class PublicReleaseChecklistTests(unittest.TestCase): - def test_checklist_names_v150_as_the_source_entrypoint(self): + def test_checklist_names_v151_as_the_source_entrypoint(self): checklist = " ".join(_read("docs/public-release-checklist.md").split()) self.assertIn( - "The v1.5.0 source defines package-index entrypoint " - "`code-mower==1.5.0` (GitHub tag `v1.5.0`). Confirm that the tag " + "The v1.5.1 source defines package-index entrypoint " + "`code-mower==1.5.1` (GitHub tag `v1.5.1`). Confirm that the tag " "and package version are published before using the index command.", checklist, ) @@ -362,15 +362,15 @@ def test_release_records_claim_upgrade_coverage_that_exists(self): class VersionIdentityTests(unittest.TestCase): - def test_source_version_is_1_5_0(self): - self.assertEqual(__version__, "1.5.0") + def test_source_version_is_1_5_1(self): + self.assertEqual(__version__, "1.5.1") def test_committed_manifest_version_matches_source(self): manifest = package_module.generate_committed_package_manifest(ROOT) self.assertEqual(manifest["package"]["version"], __version__) def test_release_tag_for_current_version(self): - self.assertEqual(release_readiness._release_tag_for_version(__version__), "v1.5.0") + self.assertEqual(release_readiness._release_tag_for_version(__version__), "v1.5.1") class RunbookIdentityTests(unittest.TestCase): @@ -626,7 +626,7 @@ def test_board_demo_does_not_claim_serve_opens_a_browser(self): class InstalledPromptPackTests(unittest.TestCase): def test_literal_starter_and_explicit_config_walkthrough(self): - """Exercise installed 1.5.0 code, with no provider login or network doctor probes.""" + """Exercise installed 1.5.1 code, with no provider login or network doctor probes.""" with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) supplied = os.environ.get("CODE_MOWER_QUALIFICATION_WHEEL") @@ -660,7 +660,7 @@ def test_literal_starter_and_explicit_config_walkthrough(self): from code_mower import cli, package from code_mower.config import load_config assert Path(code_mower.__file__).resolve().is_relative_to(Path(sys.argv[1]).resolve()) -assert code_mower.__version__ == '1.5.0' +assert code_mower.__version__ == '1.5.1' empty_store = Path.cwd() / 'empty-provider-store' empty_store.mkdir() def run(args, doctor=False): diff --git a/tests/test_release_v150.py b/tests/test_release_v151.py similarity index 89% rename from tests/test_release_v150.py rename to tests/test_release_v151.py index 41d91c50..9ac78bdd 100644 --- a/tests/test_release_v150.py +++ b/tests/test_release_v151.py @@ -22,7 +22,7 @@ spec = importlib.util.spec_from_file_location("release_candidate", ROOT / "scripts/release_candidate.py") candidate = importlib.util.module_from_spec(spec) spec.loader.exec_module(candidate) -spec = importlib.util.spec_from_file_location("rehearse_v150", ROOT / "scripts/rehearse_v150.py") +spec = importlib.util.spec_from_file_location("rehearse_v151", ROOT / "scripts/rehearse_v151.py") rehearsal = importlib.util.module_from_spec(spec) with patch.dict(sys.modules, {"release_candidate": candidate}): spec.loader.exec_module(rehearsal) @@ -35,19 +35,19 @@ def setUp(self): self.addCleanup(self.temp.cleanup) self.dist = Path(self.temp.name) with zipfile.ZipFile(self.dist / candidate.NAMES[0], "w") as archive: - archive.writestr("code_mower-1.5.0.dist-info/METADATA", - "Name: code-mower\nVersion: 1.5.0\nRequires-Dist: PyYAML>=6.0\nRequires-Dist: packaging>=23.2\n") + archive.writestr("code_mower-1.5.1.dist-info/METADATA", + "Name: code-mower\nVersion: 1.5.1\nRequires-Dist: PyYAML>=6.0\nRequires-Dist: packaging>=23.2\n") for module in candidate.MODULES: archive.writestr("code_mower/" + module, b"synthetic") for doc in candidate.DOCS: - archive.writestr("code_mower-1.5.0.data/data/share/code-mower/docs/" + doc, b"synthetic") + archive.writestr("code_mower-1.5.1.data/data/share/code-mower/docs/" + doc, b"synthetic") with tarfile.open(self.dist / candidate.NAMES[1], "w:gz") as archive: for path in (["src/code_mower/" + m for m in candidate.MODULES] + ["docs/" + d for d in candidate.DOCS]): - info = tarfile.TarInfo("code_mower-1.5.0/" + path) + info = tarfile.TarInfo("code_mower-1.5.1/" + path) info.size = 9 archive.addfile(info, io.BytesIO(b"synthetic")) - self.manifest = {"schema": candidate.SCHEMA, "version": "1.5.0", "source_sha": SHA, + self.manifest = {"schema": candidate.SCHEMA, "version": "1.5.1", "source_sha": SHA, "kind": "candidate", "release_pr": 42, "artifacts": {name: candidate.digest(self.dist / name) for name in candidate.NAMES}, "inventory": candidate.inspect(self.dist)} @@ -93,7 +93,7 @@ def test_mixed_extra_marker_cannot_hide_a_default_dependency(self): wheel = self.dist / candidate.NAMES[0] with zipfile.ZipFile(wheel) as archive: files = {name: archive.read(name) for name in archive.namelist()} - files["code_mower-1.5.0.dist-info/METADATA"] += ( + files["code_mower-1.5.1.dist-info/METADATA"] += ( b'Requires-Dist: slack-sdk; python_version >= "3.12" or extra == "coworker"\n' ) with zipfile.ZipFile(wheel, "w") as archive: @@ -116,7 +116,7 @@ def setUp(self): self.temp = tempfile.TemporaryDirectory() self.addCleanup(self.temp.cleanup) self.dist = Path(self.temp.name) - # A future version uses the manifest's wheel identity, not a v1.5.0 key. + # A future version uses the manifest's wheel identity, not a v1.5.1 key. self.wheel = "code_mower-1.5.1-py3-none-any.whl" self.manifest = {"source_sha": SHA, "artifacts": {self.wheel: "b" * 64}} self.evidence = {"schema": candidate.REHEARSAL_SCHEMA, "status": "pass", @@ -170,23 +170,23 @@ def setUp(self): self.final, self.final_sha, 101, changes={ "code_mower/audit_publication.py": b"final audit publication", - "code_mower-1.5.0.data/data/share/code-mower/docs/v150-qualification.md": + "code_mower-1.5.1.data/data/share/code-mower/docs/v151-qualification.md": b"final qualification", - "code_mower-1.5.0.dist-info/METADATA": self._metadata(b"final description"), - "code_mower-1.5.0.dist-info/RECORD": b"final record", + "code_mower-1.5.1.dist-info/METADATA": self._metadata(b"final description"), + "code_mower-1.5.1.dist-info/RECORD": b"final record", }, ) @staticmethod def _metadata(description=b"prior description"): - return (b"Name: code-mower\nVersion: 1.5.0\nSummary: stable\n" + return (b"Name: code-mower\nVersion: 1.5.1\nSummary: stable\n" b"Requires-Dist: PyYAML>=6.0\nRequires-Dist: packaging>=23.2\n\n" + description) def _wheel_files(self): files = { - "code_mower-1.5.0.dist-info/METADATA": self._metadata(), - "code_mower-1.5.0.dist-info/RECORD": b"prior record", - "code_mower-1.5.0.dist-info/entry_points.txt": + "code_mower-1.5.1.dist-info/METADATA": self._metadata(), + "code_mower-1.5.1.dist-info/RECORD": b"prior record", + "code_mower-1.5.1.dist-info/entry_points.txt": b"[console_scripts]\ncode-mower=code_mower.cli:main\n", "code_mower/audit_publication.py": b"prior audit publication", "code_mower/release_readiness.py": b"prior release readiness", @@ -196,7 +196,7 @@ def _wheel_files(self): } files.update({"code_mower/" + module: b"stable required module" for module in candidate.MODULES}) - files.update({"code_mower-1.5.0.data/data/share/code-mower/docs/" + doc: + files.update({"code_mower-1.5.1.data/data/share/code-mower/docs/" + doc: b"stable documentation" for doc in candidate.DOCS}) return files @@ -210,7 +210,7 @@ def _write_candidate(self, path, sha, release_pr, changes=None): with tarfile.open(path / candidate.NAMES[1], "w:gz") as archive: for member in (["src/code_mower/" + name for name in candidate.MODULES] + ["docs/" + name for name in candidate.DOCS]): - info = tarfile.TarInfo("code_mower-1.5.0/" + member) + info = tarfile.TarInfo("code_mower-1.5.1/" + member) info.size = len(b"synthetic") archive.addfile(info, io.BytesIO(b"synthetic")) manifest = { @@ -242,9 +242,9 @@ def test_closed_non_canary_delta_passes_with_explicit_attestation(self): self.assertTrue(result["required_canary_members_unchanged"]) self.assertEqual(result["changed_wheel_members"], sorted({ "code_mower/audit_publication.py", - "code_mower-1.5.0.data/data/share/code-mower/docs/v150-qualification.md", - "code_mower-1.5.0.dist-info/METADATA", - "code_mower-1.5.0.dist-info/RECORD", + "code_mower-1.5.1.data/data/share/code-mower/docs/v151-qualification.md", + "code_mower-1.5.1.dist-info/METADATA", + "code_mower-1.5.1.dist-info/RECORD", })) self.assertEqual(set(result["changed_wheel_member_sha256"]), set(result["changed_wheel_members"])) @@ -270,8 +270,8 @@ def test_metadata_header_change_fails_closed(self): final = self.root / "bad-metadata" metadata = self._metadata(b"final description").replace(b"Summary: stable", b"Summary: changed") self._write_candidate(final, self.final_sha, 101, changes={ - "code_mower-1.5.0.dist-info/METADATA": metadata, - "code_mower-1.5.0.dist-info/RECORD": b"final record", + "code_mower-1.5.1.dist-info/METADATA": metadata, + "code_mower-1.5.1.dist-info/RECORD": b"final record", }) with patch.object(candidate, "run", return_value=""), \ self.assertRaisesRegex(ValueError, "metadata headers"): @@ -330,7 +330,7 @@ def validate(): run[field] = value def test_publication_requires_verified_artifacts_and_named_rehearsal_before_copy(self): - self.assertNotIn("code_mower-1.5.0-py3-none-any.whl", self.publish) + self.assertNotIn("code_mower-1.5.1-py3-none-any.whl", self.publish) self.assertNotIn("python -m build", self.publish) verification = self.publish.index("python scripts/release_candidate.py verify") self.assertIn("--require-candidate", self.publish[verification:]) @@ -344,7 +344,7 @@ def test_ci_exercises_the_installed_wheel_without_claiming_a_candidate(self): self.assertEqual(job["steps"][0]["with"]["ref"], "${{ env.SOURCE_SHA }}") commands = "\n".join(step.get("run", "") for step in job["steps"]) self.assertIn("python scripts/release_candidate.py build", commands) - self.assertIn('python scripts/rehearse_v150.py --dist "$RUNNER_TEMP/rehearsal-dist"', commands) + self.assertIn('python scripts/rehearse_v151.py --dist "$RUNNER_TEMP/rehearsal-dist"', commands) self.assertNotIn("--release-pr", commands) self.assertIn("release_rehearsal", jobs["package"]["needs"]) self.assertIn('test "${{ needs.release_rehearsal.result }}" = "success"', jobs["package"]["steps"][0]["run"]) @@ -375,34 +375,35 @@ def test_graph_exception_only_allows_transport_disabled_fixture_git_reads(self): class ReleaseContractTests(unittest.TestCase): def test_identity_and_readiness(self): - self.assertEqual(__version__, "1.5.0") + self.assertEqual(__version__, "1.5.1") self.assertEqual(release_readiness.render_release_readiness(ROOT)["status"], "pass") - def test_removing_private_acceptance_or_moving_tag_first_blocks(self): - text = (ROOT / "docs/v150-release-runbook.md").read_text() - for bad in (text.replace("## 3. Private acceptance", "## Removed acceptance"), - text.replace('git tag -a v1.5.0 "$RELEASE_SHA"', "tag removed"), + def test_removing_exact_candidate_qualification_or_moving_tag_first_blocks(self): + text = (ROOT / "docs/v151-release-runbook.md").read_text() + for bad in (text.replace("## 3. Qualify the exact candidate", "## Removed qualification"), + text.replace('git tag -a v1.5.1 "$RELEASE_SHA"', "tag removed"), text.replace("aggregate campaign ACU", "unspecified budget")): with self.subTest(text=bad[:10]), patch.object(release_readiness, "_read_text_if_exists", return_value=bad): order, assertions = release_readiness._candidate_runbook_checks(ROOT) self.assertTrue(order or assertions) - def test_bounded_canary_carry_forward_is_closed_and_machine_verified(self): - runbook = (ROOT / "docs/v150-release-runbook.md").read_text() - qualification = (ROOT / "docs/v150-qualification.md").read_text() + def test_patch_release_contract_keeps_scope_and_observations_explicit(self): + runbook = (ROOT / "docs/v151-release-runbook.md").read_text() + qualification = (ROOT / "docs/v151-qualification.md").read_text() for marker in ( - "compare-canary-surface", - "same wheel-member inventory", - "Unknown, added or dynamically loaded wheel members fail closed", - "An owner comment alone cannot waive this comparison", - "exact final candidate", - "private no-provider installation and administration lifecycle", + "fresh install without uv or pipx", + "upgrade from v1.5.0", + "remote observer", + "safe init", + "basic Slack lifecycle", + "Slack telemetry remains deferred to v1.6.0", + "metadata-only", + "fresh dashboard", ): with self.subTest(marker=marker): self.assertIn(marker, runbook) - self.assertIn("code_mower.canary_candidate_equivalence.v1", qualification) - self.assertIn("any operational member difference requires newly authorized canaries", - qualification) + self.assertIn("one bounded hosted Board canary", qualification) + self.assertIn("authorized usage and settled usage are separate facts", qualification) def test_later_versions_do_not_revert_to_building_at_publication(self): with patch.object(release_readiness, "_python_package_version", return_value="1.5.1"): @@ -415,7 +416,7 @@ def test_later_versions_do_not_revert_to_building_at_publication(self): ordered = checks["post-merge-release-runbook-ordered"]["detail"] asserted = checks["post-merge-release-runbook-asserted"]["detail"] self.assertEqual(ordered["release_tag"], "v1.5.1") - self.assertIn("docs/v150-release-runbook.md", ordered["required_commands"][0]) + self.assertIn("docs/v151-release-runbook.md", ordered["required_commands"][0]) self.assertNotIn("gh release create v1.5.1", ordered["required_commands"]) self.assertEqual(asserted["required_assertions"], ["merge SHA and retained artifact binding; explicit owner gates"]) @@ -429,9 +430,9 @@ def test_later_versions_do_not_revert_to_building_at_publication(self): def test_later_versions_still_reject_missing_candidate_runbook_gates(self): original = release_readiness._read_text_if_exists - runbook_path = ROOT / "docs/v150-release-runbook.md" + runbook_path = ROOT / "docs/v151-release-runbook.md" for marker, check_id, detail in ( - ("## 3. Private acceptance", "post-merge-release-runbook-ordered", "missing_or_out_of_order"), + ("## 3. Qualify the exact candidate", "post-merge-release-runbook-ordered", "missing_or_out_of_order"), ("aggregate campaign ACU", "post-merge-release-runbook-asserted", "missing_assertions"), ): def read(path, marker=marker):