From ec2661bbd0a25a3b0b7aabeac1508e4a3f528211 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Mon, 21 Sep 2026 17:56:36 -0700 Subject: [PATCH 1/5] Prepare v1.6.0 release qualification --- CHANGELOG.md | 22 ++++ README.md | 26 ++--- code-mower-package-manifest.json | 17 ++- docs/README.md | 2 +- docs/cloud-benchmarking.md | 2 +- docs/current-state-and-roadmap.md | 95 +++++++++-------- docs/docs-manifest.yml | 9 ++ docs/early-adopter-invite-runbook.md | 4 +- docs/first-user-install-rehearsal.md | 24 ++--- docs/graphify-setup.md | 2 +- docs/install.md | 34 +++--- docs/jira-cloud-setup.md | 2 +- docs/provider-matrix.md | 2 +- docs/public-release-checklist.md | 19 ++-- docs/pypi-release.md | 42 ++++---- docs/release-history.md | 3 + docs/sessions.md | 2 +- docs/slack-setup.md | 17 +-- docs/upgrade-existing-repo.md | 16 +-- docs/v160-qualification.md | 58 ++++++++++ docs/v160-release-notes.md | 75 +++++++++++++ docs/v160-release-runbook.md | 153 +++++++++++++++++++++++++++ pyproject.toml | 5 +- release.yml | 24 ++--- src/code_mower/__init__.py | 2 +- src/code_mower/package_manifest.py | 3 + src/code_mower/release_readiness.py | 51 ++++++--- tests/test_release_contract.py | 81 +++++++------- tests/test_release_hygiene.py | 52 ++++----- tests/test_release_v142.py | 20 ++-- 30 files changed, 621 insertions(+), 243 deletions(-) create mode 100644 docs/v160-qualification.md create mode 100644 docs/v160-release-notes.md create mode 100644 docs/v160-release-runbook.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 77b281f3..614e3d26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,28 @@ later entries are regular releases. actionable diagnostics. Lineage controls are recognized only as standalone HTML control-comment lines outside inline and fenced examples (#1104). +## 1.6.0 — operational clarity and minimum telemetry + +Code Mower 1.6.0 makes local operational state safer to interpret and adds a +closed, metadata-only lifecycle-summary contract for the optional Slack control +surface. See the [release notes](docs/v160-release-notes.md) and the +[qualification contract](docs/v160-qualification.md). + +- Reconcile doctor warnings with their JSON states and keep hosted-only checks + out of ordinary local adoption diagnostics (#1064 / #1099). +- Replace managed Board services atomically, verify either the new or restored + binding from host state, and refuse ambiguous reconciliation (#1082 / #1100, + hardened by #1103). +- Treat an unmanaged pull request as a neutral observer state when no lineage + policy is configured, without weakening configured lineage enforcement + (#1083 / #1097). +- Make adoption diagnostics share-safe by default and require an explicit local + view for private paths and identifiers (#1084 / #1102). +- Freeze the provider-neutral `code_mower.controlSurfaceSessionSummary.v1` + contract, its accepted and rejected fixtures, capability gate, transition + suppression, local Board projection, and metadata-only cloud emitter + (#921 / #1098). + ## 1.5.2 — documentation and repository maintenance Documentation ownership, maintained user journeys, release identity, and package templates now have one validated source each. See [release notes](docs/v152-release-notes.md) and the [qualification contract](docs/v152-qualification.md). diff --git a/README.md b/README.md index 1010f20f..86d25bdb 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,13 @@ 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.2`, with package spec -`code-mower==1.5.2`. Confirm the release tag on GitHub Releases and the package +This source defines Code Mower `v1.6.0`, with package spec +`code-mower==1.6.0`. 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. Python 3.12 or newer is required. -See the [release notes](https://github.com/codemower-ai/code-mower/blob/main/docs/v152-release-notes.md) -and [qualification contract](https://github.com/codemower-ai/code-mower/blob/main/docs/v152-qualification.md). +See the [release notes](https://github.com/codemower-ai/code-mower/blob/main/docs/v160-release-notes.md) +and [qualification contract](https://github.com/codemower-ai/code-mower/blob/main/docs/v160-qualification.md). Historical v1.4.x artifacts and qualification records remain unchanged. The v1.4.2 release did not claim the bounded hosted Devin canary tracked by @@ -24,9 +24,9 @@ The v1.4.2 release did not claim the bounded hosted Devin canary tracked by 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.2` guide](https://github.com/codemower-ai/code-mower/blob/v1.5.2/docs/try-in-10-minutes.md), -and confirm the tag and package exist before using pinned install commands. +read its immutable versioned guide. After v1.6.0 publication, use the +[`v1.6.0` guide](https://github.com/codemower-ai/code-mower/blob/v1.6.0/docs/try-in-10-minutes.md). +Confirm the tag and package exist before using pinned install commands. ## What Code Mower Adds @@ -102,7 +102,7 @@ ID with `session lease renew --session-id SESSION_ID` or `session lease release --dry-run` or `--no-lease` for read-only work. Codex, Claude Code, and Cursor are qualified for the shared session, telemetry, -lease, and Jira-authority contract in the current v1.5.2 release. Devin, Grok +lease, and Jira-authority contract in the current v1.6.0 source line. Devin, Grok Bot, Antigravity, Muse, and custom hosts are recognized for briefs and provenance, while their execution remains an explicit handoff or provider-specific transport. See @@ -230,7 +230,7 @@ and the [Cloud Data Contract](https://github.com/codemower-ai/code-mower/blob/ma ## Optional Hosted Slack -v1.5.2 retains the basic Slack control surface introduced in v1.5.0 for one +v1.6.0 retains the basic Slack control surface introduced in v1.5.0 for one private workspace and one authorized private, unshared channel. A Code Mower team administrator opens **Setup → Manage Slack integration** in the hosted dashboard and completes OAuth @@ -259,7 +259,7 @@ for both workflows, supported behavior, and the trust boundary. ## Current Capabilities And Limits -| Area | v1.5.2 posture | +| Area | v1.6.0 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 | @@ -269,11 +269,11 @@ for both workflows, supported behavior, and the trust boundary. | Forge and merge gate | GitHub | | Cloud | Optional metadata/report upload; no upload by default | | Graphify | Optional bounded local repository-graph provider behind the packet contract; no default dependency and no network access for the provider | -| Slack | Optional hosted OAuth and `/codemower` control surface for one private workspace/channel; exact bindings, qualified supervisor, and numeric caps gate work | +| Slack | Optional hosted OAuth and `/codemower` control surface for one private workspace/channel; exact bindings, qualified supervisor, and numeric caps gate work; metadata-only lifecycle summaries require exact hosted capability acceptance | 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.2. The current priorities +dependency, Slack-to-Board links, and rich Slack UX are outside v1.6.0. 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). @@ -281,7 +281,7 @@ and boundaries are recorded in Graphify's optional bounded provider foundation landed in v1.4.0; its complete qualified integration, scorecard and query behavior shipped in v1.4.1 and -remain available in v1.5.2. It is separately installed into an operator-owned +remain available in v1.6.0. It is separately installed into an operator-owned environment, explicitly activated, and outside the base dependency set: a default Claude + Codex install adds no Graphify dependency, no indexer, no background service, and no watcher. diff --git a/code-mower-package-manifest.json b/code-mower-package-manifest.json index 9363d99f..2fcdf96b 100644 --- a/code-mower-package-manifest.json +++ b/code-mower-package-manifest.json @@ -342,6 +342,21 @@ "source": "docs/v152-release-runbook.md", "target": "docs/v152-release-runbook.md" }, + { + "kind": "doc", + "source": "docs/v160-qualification.md", + "target": "docs/v160-qualification.md" + }, + { + "kind": "doc", + "source": "docs/v160-release-notes.md", + "target": "docs/v160-release-notes.md" + }, + { + "kind": "doc", + "source": "docs/v160-release-runbook.md", + "target": "docs/v160-release-runbook.md" + }, { "kind": "package", "source": "generated", @@ -2375,6 +2390,6 @@ "module": "code_mower", "name": "code-mower", "source_layout": "src/code_mower", - "version": "1.5.2" + "version": "1.6.0" } } diff --git a/docs/README.md b/docs/README.md index c2263c43..103ffada 100644 --- a/docs/README.md +++ b/docs/README.md @@ -40,6 +40,6 @@ historical release evidence and are not current operating guidance. ## Lifecycle -The manifest currently classifies 63 supporting, 53 frozen, and 5 archived document(s). +The manifest currently classifies 63 supporting, 56 frozen, and 5 archived document(s). See [Documentation lifecycle](documentation-lifecycle.md) before adding, moving, or changing release-sensitive documentation. diff --git a/docs/cloud-benchmarking.md b/docs/cloud-benchmarking.md index 12fa1ec6..9d1bb655 100644 --- a/docs/cloud-benchmarking.md +++ b/docs/cloud-benchmarking.md @@ -5,7 +5,7 @@ reports, and the local Board without a hosted account. CodeMower.com is an optional destination for longitudinal team reporting and future aggregate benchmarks. -## Current v1.5.2 Client Surface +## Current v1.6.0 Client Surface The current client can: diff --git a/docs/current-state-and-roadmap.md b/docs/current-state-and-roadmap.md index 189edf34..102b9804 100644 --- a/docs/current-state-and-roadmap.md +++ b/docs/current-state-and-roadmap.md @@ -22,15 +22,17 @@ dry-run-first. ## Current Source And Published Baseline -This source defines Code Mower `v1.5.2`, with package spec -`code-mower==1.5.2`. Confirm the release tag on GitHub Releases and the package +This source defines Code Mower `v1.6.0`, with package spec +`code-mower==1.6.0`. 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.2 release notes](https://github.com/codemower-ai/code-mower/blob/main/docs/v152-release-notes.md) -and [qualification contract](https://github.com/codemower-ai/code-mower/blob/main/docs/v152-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. +version and publication state are separate facts. The latest published baseline +remains `v1.5.2` until the entry gates and qualification contract for #1105 are +complete. See the +[v1.6.0 release notes](https://github.com/codemower-ai/code-mower/blob/main/docs/v160-release-notes.md), +[qualification contract](https://github.com/codemower-ai/code-mower/blob/main/docs/v160-qualification.md), +and [candidate runbook](v160-release-runbook.md). The GitHub Release and #1105 +will carry observed source SHA, artifact digests, canary, soak, publication, +and reinstall evidence after those observations exist. Historical v1.4.x artifacts and qualification records remain unchanged. `v1.4.0`, `v1.4.1` and `v1.4.2` have all shipped, and the v1.4.0 and v1.4.1 @@ -133,10 +135,12 @@ future hosted-service work. - Graphify's bounded provider foundation shipped in v1.4.0; its complete qualified integration, scorecard and query behavior shipped in v1.4.1. It remains optional and has no default dependency. -- Slack in v1.5.2 supports one private workspace with explicit member, repository +- Slack in v1.6.0 retains one private workspace with explicit member, repository and private-channel mappings plus private start, status, answer and - confirmed-cancel interactions. Telemetry, Board links, Slack Connect, public - channels and richer Slack UX remain planned v1.6 work. + confirmed-cancel interactions. Metadata-only lifecycle summaries remain + disabled until the hosted service advertises the exact accepted contract. + Slack-to-Board links, Slack Connect, public channels and richer Slack UX are + deferred. - Provider cost fields remain unknown when the provider does not return them. - A successful release campaign proves installation and operational transport, not builder quality or reviewer promotion readiness. @@ -147,32 +151,32 @@ future hosted-service work. ## Current Release And v1.6.0 -`v1.5.2` is released and is the current supported package. It retains the -basic Slack boundary from v1.5.0 and adds the five reliability revisions from -#1050: hosted-install clarity, host-independent tests, lane-exact audit seals, -checkout-free remote doctor, safe existing-repository initialization, and -clearer status/version reporting. - -The [v1.6.0 milestone](https://github.com/codemower-ai/code-mower/milestone/2) -and [epic #1066](https://github.com/codemower-ai/code-mower/issues/1066) are the -live trackers. The planned children are: - -1. Board inventory filters, version parity, stale detection, and service - guidance ([#1063](https://github.com/codemower-ai/code-mower/issues/1063)); -2. doctor warning taxonomy and hosted-posture scope - ([#1064](https://github.com/codemower-ai/code-mower/issues/1064)); -3. hosted adoption prompts, current documentation, and a normal 24-hour release - soak with two independent install or upgrade passes - ([#1065](https://github.com/codemower-ai/code-mower/issues/1065)); -4. the closed, versioned optional Slack telemetry contract and OSS emitters - ([#921](https://github.com/codemower-ai/code-mower/issues/921)); and -5. hosted validation, aggregation, export/deletion, and fresh authenticated - views for that contract - ([#978](https://github.com/codemower-ai/code-mower/issues/978)). - -#1063, #1064, and the documentation portion of #1065 can proceed in parallel. -#921 freezes the shared contract and fixtures before #978 enables hosted -ingest. None of this planned work is part of v1.5.2. +`v1.5.2` is released and remains the current supported package. The `v1.6.0` +source line now contains the completed doctor taxonomy, atomic Board replacement, +neutral unmanaged-lineage behavior, share-safe adoption diagnostics, and the +closed metadata-only control-surface summary contract. The optional client +emitter remains fail-closed unless the hosted service advertises the exact +accepted contract identity. + +The [v1.6.0 milestone](https://github.com/codemower-ai/code-mower/milestone/2), +[epic #1066](https://github.com/codemower-ai/code-mower/issues/1066), and +[release issue #1105](https://github.com/codemower-ai/code-mower/issues/1105) +are the live trackers. Three entry gates remain before the immutable candidate: + +1. [#1063](https://github.com/codemower-ai/code-mower/issues/1063) — Board + repository filters, invoking/serving version parity, stale-service detection, + and exact service guidance; +2. [#1104](https://github.com/codemower-ai/code-mower/issues/1104) — bounded, + payload-aware audit comment ingestion and reserved control parsing; and +3. [CodeMower.com #978](https://github.com/codemower-ai/code-mower/issues/978) + — deployed validation, tenant isolation, retention, export/deletion, + aggregate counts, freshness, and exact capability advertisement. + +After those gates complete, #1105 serializes the one immutable build, bounded +private Slack canary, local-versus-hosted reconciliation, 24-hour soak, two +independent installation passes, exact-head release audits, publication, and +canonical reinstall. None of those observations is claimed by this source +preparation. ## Near-Term Roadmap @@ -338,13 +342,16 @@ 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.6.0. Slack consumes the -durable lifecycle instead of scraping terminal or Board output and carries no -raw private context or private reviewer findings. - -The source implementation and qualification contract are complete. Consult #923 -and the GitHub Release for the observed lifecycle, canary, publication and -canonical reinstall state. +The v1.6.0 source adds capability-gated, metadata-only lifecycle summaries to +the local Board and optional cloud emitter. Slack-to-Board links and rich UX +remain deferred. Slack consumes the durable lifecycle instead of scraping +terminal or Board output and carries no raw private context or private reviewer +findings. + +The source implementation and qualification contract are complete. This +statement covers the basic v1.5.0 interaction boundary. +Consult #923 and the GitHub Release for the observed v1.5.0 lifecycle, canary, +publication and canonical reinstall state. ## Delivery Order diff --git a/docs/docs-manifest.yml b/docs/docs-manifest.yml index 9aa9415c..37b74c9b 100644 --- a/docs/docs-manifest.yml +++ b/docs/docs-manifest.yml @@ -362,3 +362,12 @@ documents: - path: docs/v152-release-runbook.md status: frozen sha256: 7a7492e3e297d920dbb76b7c25339f3865d545c108a0114e65b1a3d989ad3bcd +- path: docs/v160-qualification.md + status: frozen + sha256: 17195cba43abebc48ff6fd736c622fa3e0d4d8927e58d85f5d7905a0b6f2fde7 +- path: docs/v160-release-notes.md + status: frozen + sha256: 702d1a3dcb44b1f85675343a1aa4ac4e8cfe1bd5c446dea9d60920dd7c28d0e0 +- path: docs/v160-release-runbook.md + status: frozen + sha256: 87ab40d83790ed3b07667b40828770b17f398bfb538a0ba88ed53975e6e9f2c9 diff --git a/docs/early-adopter-invite-runbook.md b/docs/early-adopter-invite-runbook.md index ca16f75d..f55c7d51 100644 --- a/docs/early-adopter-invite-runbook.md +++ b/docs/early-adopter-invite-runbook.md @@ -40,8 +40,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.2 is published, start here: -https://github.com/codemower-ai/code-mower/blob/v1.5.2/docs/try-in-10-minutes.md +After v1.6.0 is published, start here: +https://github.com/codemower-ai/code-mower/blob/v1.6.0/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. diff --git a/docs/first-user-install-rehearsal.md b/docs/first-user-install-rehearsal.md index ec75355f..03ea72f8 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.2 uses the exact install pin `code-mower==1.5.2`. Verify that version is +v1.6.0 uses the exact install pin `code-mower==1.6.0`. Verify that version is published on the selected index, then verify the command path and version after -installing. The [v1.5.2 qualification contract](v152-qualification.md) defines +installing. The [v1.6.0 qualification contract](v160-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](v152-release-runbook.md) for +[candidate runbook](v160-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.2 \ + --package-spec code-mower==1.6.0 \ --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.2 \ + --package-spec code-mower==1.6.0 \ --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.2 +PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.6.0 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.2 + --default-index https://pypi.org/simple/ code-mower==1.6.0 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.2 \ + --package-spec code-mower==1.6.0 \ --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.2 \ + --package-spec code-mower==1.6.0 \ --allow-package-index \ --repo-path /path/to/product-repo \ --python "$(command -v python3.12)" \ @@ -321,10 +321,10 @@ 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.2 sequence. For v1.5.2 build the merge-SHA candidate first, apply the -[v1.5.2 qualification contract](v152-qualification.md), then tag and publish the +v1.6.0 sequence. For v1.6.0 build the merge-SHA candidate first, apply the +[v1.6.0 qualification contract](v160-qualification.md), then tag and publish the unchanged source SHA and artifact pair. Record the public evidence on #1078 and -follow [the current runbook](v152-release-runbook.md). +follow [the current runbook](v160-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/graphify-setup.md b/docs/graphify-setup.md index 8c9b571a..7e57419f 100644 --- a/docs/graphify-setup.md +++ b/docs/graphify-setup.md @@ -2,7 +2,7 @@ Graphify's bounded provider foundation landed in v1.4.0; its complete qualified integration, scorecard and query behavior shipped in v1.4.1, were extended in -v1.5.0, and remain available in current v1.5.2. It is an **optional** local +v1.5.0, and remain available in current v1.6.0. It is an **optional** local repository-graph provider, separately installed into an operator-owned environment, explicitly activated, and outside the base dependency set: a default Claude + Codex installation adds no Graphify diff --git a/docs/install.md b/docs/install.md index 928072bf..9b46bf8e 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,11 +1,11 @@ # Install And Bootstrap -v1.5.2 uses the exact install pin `code-mower==1.5.2` and requires Python +v1.6.0 uses the exact install pin `code-mower==1.6.0` and requires Python 3.12 or newer. Confirm that version is published on the selected index, then verify the command path and version after installing. The -[qualification contract](v152-qualification.md) defines the required evidence. Use the -[candidate runbook](v152-release-runbook.md) for prepublication local-wheel rehearsals. +[qualification contract](v160-qualification.md) defines the required evidence. Use the +[candidate runbook](v160-release-runbook.md) for prepublication local-wheel rehearsals. @@ -47,7 +47,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.2 +"$UV_BOOTSTRAP/bin/python" -m uv tool install --python 3.12 code-mower==1.6.0 ``` The module form works even when uv is not yet on `PATH`. After installation, @@ -76,7 +76,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.2`. A version that +chose, and `code-mower --version` must print `code-mower 1.6.0`. 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. @@ -111,12 +111,12 @@ wrapper/pin drift checks, see ## Local audit workflow publication -The local audit publisher introduced in v1.5.0 remains included in v1.5.2. It +The local audit publisher introduced in v1.5.0 remains included in v1.6.0. It generates `.github/workflows/local-audit-publication.yml` alongside the updated labelers, 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.2 is published, use its reviewed candidate wheel; afterward, use the +Before v1.6.0 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 @@ -136,7 +136,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.2 +pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.6.0 code-mower --version ``` @@ -157,7 +157,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.2 +PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.6.0 code-mower --version ``` @@ -170,7 +170,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.2 +PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.6.0 "$PIPX_BIN_DIR/code-mower" --version ``` @@ -181,7 +181,7 @@ interactive shell profile: ```bash uv python install 3.12 -uv tool install --python 3.12 code-mower==1.5.2 +uv tool install --python 3.12 code-mower==1.6.0 code-mower --version ``` @@ -191,7 +191,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.2 +uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.6.0 code-mower --version ``` @@ -206,7 +206,7 @@ With pipx: ```bash PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" \ - 'code-mower[coworker]==1.5.2' + 'code-mower[coworker]==1.6.0' code-mower context --help ``` @@ -214,7 +214,7 @@ With uv: ```bash uv tool install --python 3.12 --reinstall --refresh-package code-mower \ - 'code-mower[coworker]==1.5.2' + 'code-mower[coworker]==1.6.0' code-mower context --help ``` @@ -242,7 +242,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.2 +uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.6.0 hash -r command -v code-mower code-mower --version @@ -262,7 +262,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.2 +PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.6.0 code-mower --version ``` @@ -270,7 +270,7 @@ For uv: ```bash uv python install 3.12 -uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.5.2 +uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.6.0 code-mower --version ``` diff --git a/docs/jira-cloud-setup.md b/docs/jira-cloud-setup.md index 85baf4c0..a4424770 100644 --- a/docs/jira-cloud-setup.md +++ b/docs/jira-cloud-setup.md @@ -214,7 +214,7 @@ GitHub state only. Controller dry-run never dispatches, merges, or writes Jira. This shared session-brief contract, the Jira REST commands, and the double write guard described elsewhere in this guide have been available since -`code-mower==1.4.0` and remain present in the current `code-mower==1.5.2`. +`code-mower==1.4.0` and remain present in the current `code-mower==1.6.0`. `code-mower session start` adds a `tracker` section to the operating brief whenever `tracker.kind` is `jira_cloud`. Codex, Claude, and every other diff --git a/docs/provider-matrix.md b/docs/provider-matrix.md index 9db13a8b..039981d1 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.2`; see [Participants And Sessions](sessions.md) for +`code-mower==1.6.0`; 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 aa251cab..01a38fdc 100644 --- a/docs/public-release-checklist.md +++ b/docs/public-release-checklist.md @@ -1,10 +1,10 @@ # Code Mower Public Release Checklist -v1.5.2 uses the exact install pin `code-mower==1.5.2`. Verify the command path -and version after installing. Use the [v1.5.2 qualification contract](v152-qualification.md) -for required observations and the [candidate runbook](v152-release-runbook.md) +v1.6.0 uses the exact install pin `code-mower==1.6.0`. Verify the command path +and version after installing. Use the [v1.6.0 qualification contract](v160-qualification.md) +for required observations and the [candidate runbook](v160-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 +results belong on #1105 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.2 source defines package-index entrypoint `code-mower==1.5.2` - (GitHub tag `v1.5.2`). Confirm that the tag and package version are published +- The v1.6.0 source defines package-index entrypoint `code-mower==1.6.0` + (GitHub tag `v1.6.0`). 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.2 supervised-pilot source includes Python 3.12+ install hardening, +- The v1.6.0 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, @@ -143,8 +143,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.2 that head must include -the five reliability revisions tracked by #1050. Follow the +the reviewed final release preparation PR. For v1.6.0 that head must include +#1063 and #1104, and CodeMower.com #978 must advertise the exact accepted +telemetry contract before the immutable candidate is built. 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 diff --git a/docs/pypi-release.md b/docs/pypi-release.md index 1c7c158e..a8d69670 100644 --- a/docs/pypi-release.md +++ b/docs/pypi-release.md @@ -4,13 +4,13 @@ Code Mower users install from PyPI. For the current release, build the immutable merge-SHA candidate first, qualify those retained bytes, then tag and publish the unchanged SHA. The release workflow retrieves and verifies the candidate without -rebuilding. Follow the [v1.5.2 runbook](v152-release-runbook.md) and -[qualification contract](v152-qualification.md); observed evidence belongs on the release +rebuilding. Follow the [v1.6.0 runbook](v160-release-runbook.md) and +[qualification contract](v160-qualification.md); observed evidence belongs on the release issue and GitHub Release. ```bash CODE_MOWER_PYTHON="$(command -v python3.12)" -pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.5.2 +pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.6.0 ``` @@ -27,14 +27,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.2`. Set both the project version - in `pyproject.toml` and `src/code_mower/__init__.py` to `1.5.2`. -2. Add exactly one matching `## 1.5.2` (or `## v1.5.2`) CHANGELOG heading as +1. Choose the exact release tag, such as `v1.6.0`. Set both the project version + in `pyproject.toml` and `src/code_mower/__init__.py` to `1.6.0`. +2. Add exactly one matching `## 1.6.0` (or `## v1.6.0`) 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.2 — release` + what the release contains. A neutral heading such as `## 1.6.0 — 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.2, with package spec code-mower==1.5.2.` + `This source defines Code Mower v1.6.0, with package spec code-mower==1.6.0.` 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 @@ -50,21 +50,21 @@ 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.2 + .venv/bin/python src/code_mower/release_identity.py --tag v1.6.0 .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.2, 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; + and the authoritative Code Mower gate before merge. For v1.6.0, require + #1063 and #1104 on that head, plus an exact accepted-contract health response + from the deployed CodeMower.com #978 consumer. Bind the actual merge SHA and + build the candidate once. Complete #1105's bounded private Slack telemetry + canary, local-versus-hosted reconciliation, 24-hour soak, two independent + installation passes, and exact-candidate audits before the owner release + decision, tag, or publication. Re-run identity on that exact checkout; publish the retained pair with the same SHA and candidate workflow run ID. -For releases after v1.5.2, let the final candidate soak for at least 24 hours +For v1.6.0 and later releases, let the final candidate soak for at least 24 hours after its last source or packaged-document change and complete at least two independent cold-install or upgrade passes during that window. A candidate change restarts the clock. An emergency patch may shorten the soak only when @@ -92,7 +92,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.2: its candidate-before-tag procedure is in the current runbook. +substitute v1.6.0: its candidate-before-tag procedure is in the current runbook. ## Current Status @@ -174,7 +174,7 @@ should be the `/releases/latest` result, and exact-version installs should resolve from PyPI. ```bash -RELEASE_VERSION="${RELEASE_VERSION:-1.5.2}" +RELEASE_VERSION="${RELEASE_VERSION:-1.6.0}" RELEASE_TAG="v$RELEASE_VERSION" gh release view "$RELEASE_TAG" \ --repo codemower-ai/code-mower \ @@ -2146,7 +2146,7 @@ being verified instead of copying an older version pin through this reusable section: ```bash -export RELEASE_VERSION="${RELEASE_VERSION:-1.5.2}" +export RELEASE_VERSION="${RELEASE_VERSION:-1.6.0}" export RELEASE_TAG="v$RELEASE_VERSION" export RELEASE_SPEC="code-mower==$RELEASE_VERSION" export RELEASE_WHEEL_STEM="code_mower-${RELEASE_VERSION}" @@ -2296,7 +2296,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.2}" +RELEASE_VERSION="${RELEASE_VERSION:-1.6.0}" 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/release-history.md b/docs/release-history.md index 325915cb..41c3bcbd 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.6.0 release notes](v160-release-notes.md) +- [v1.6.0 qualification contract](v160-qualification.md) +- [v1.6.0 candidate and publication runbook](v160-release-runbook.md) - [v1.5.2 release notes](v152-release-notes.md) - [v1.5.2 qualification contract](v152-qualification.md) - [v1.5.2 candidate and publication runbook](v152-release-runbook.md) diff --git a/docs/sessions.md b/docs/sessions.md index b9f65d02..8b662fa0 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.2`. +qualification documented below are included in `code-mower==1.6.0`. 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/slack-setup.md b/docs/slack-setup.md index fba40848..aa7986eb 100644 --- a/docs/slack-setup.md +++ b/docs/slack-setup.md @@ -10,13 +10,14 @@ Slack conveys a bounded request to the qualified supervisor; it does not run an agent or gain provider, review, approval, or merge authority. Hosted Devin is a bounded builder, never an orchestrator qualification. -Live operation requires the current immutable reviewed `code-mower==1.5.2` -package, or a later compatible release, and a separately qualified hosted -deployment. The private bridge verifies its implementation lock and +Live operation requires the immutable reviewed `code-mower==1.6.0` package +after publication, or the exact retained candidate during qualification, plus +a separately qualified hosted deployment. The private bridge verifies its implementation lock and rejects editable/VCS installs for live operation. Version alone is insufficient. -Telemetry readiness, Board/cloud links, a general integrations picker, Slack -Connect, public channels, DMs, and rich Slack UX are outside v1.5.2 and remain -planned v1.6 work. +Metadata-only lifecycle summaries are included in v1.6.0 but remain disabled +unless the hosted service advertises the exact accepted contract identity. +Board links in Slack, a general integrations picker, Slack Connect, public +channels, DMs, and rich Slack UX remain outside v1.6.0. ## Hosted setup for a workspace administrator @@ -28,10 +29,10 @@ not create or import a Slack app manifest. 1. Sign in to Code Mower, select the intended team, then open **Setup → Manage Slack integration**. Supply the exact Slack workspace ID. If the deployment supports an Enterprise Grid workspace, also supply the expected enterprise - ID; v1.5.2 still rejects organization-wide installation. + ID; v1.6.0 still rejects organization-wide installation. 2. Choose **Install**, review Slack's consent screen, and authorize the Code - Mower app in that same workspace. The v1.5.2 hosted app requests only the bot + Mower app in that same workspace. The v1.6.0 hosted app requests only the bot `commands` scope. It does not request message or channel history, posting, files, email, user tokens, Events API subscriptions, Socket Mode, or an organization-wide grant. Token rotation is enabled. diff --git a/docs/upgrade-existing-repo.md b/docs/upgrade-existing-repo.md index 0088739d..caf169f6 100644 --- a/docs/upgrade-existing-repo.md +++ b/docs/upgrade-existing-repo.md @@ -21,22 +21,22 @@ universal prompt in [Orchestrator Prompt Pack](orchestrator-prompt-pack.md) so it reports the same active command, exact version, posture-specific doctor, lanes status, and owner click-list as the primary orchestrator. -## 2. Upgrade The Tool To v1.5.2 +## 2. Upgrade The Tool To v1.6.0 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. +comparing setup. Running `setup-drift` under an older release only compares the +repository with that older release's packaged files. -Confirm `code-mower==1.5.2` is visible on the selected package index before +Confirm `code-mower==1.6.0` is visible on the selected package index before running either upgrade block. Prepublication qualification uses the retained -candidate wheel from the [v1.5.2 release runbook](v152-release-runbook.md). +candidate wheel from the [v1.6.0 release runbook](v160-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.2 +PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.6.0 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.2 +uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.6.0 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.2`, and `command -v` must still identify +final line must print `code-mower 1.6.0`, 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/v160-qualification.md b/docs/v160-qualification.md new file mode 100644 index 00000000..88086cd0 --- /dev/null +++ b/docs/v160-qualification.md @@ -0,0 +1,58 @@ +# v1.6.0 qualification contract and evidence matrix + +This document defines the immutable acceptance contract for #1105. Observed +results belong on #1105, the release pull request, and the GitHub Release. Do +not edit qualified source to insert later results, credentials, private data, +or operational transcripts. + +## Entry gates + +Candidate construction is refused until the final release commit contains all +of the following and each dependency has its own accepted exact-head evidence: + +| Gate | Required state | +| --- | --- | +| Board clarity | #1063 merged: repository-filtered inventory, invoking/serving version parity, stale-service detection, and exact restart/promotion guidance | +| Audit history | #1104 merged: bounded payload-aware comment pagination and exact recognition of reserved lineage controls | +| Hosted consumer | CodeMower.com #978 deployed before client emission; `/api/health` advertises contract commit `99b657ae9822a689b46d21c41695a4cfb28a177d` and fixture-manifest SHA-256 `9e87c52812a49a1c72d0e0d2448661a3ef17cb8539ca8e029c6669ea9738d62e` | +| Source scope | #1064/#1099, #1082/#1100/#1103, #1083/#1097, #1084/#1102, and #921/#1098 are ancestors of the release commit | +| Release text | The v1.6 epic, release notes, roadmap, package identity, and current documentation agree on final scope | + +The first immutable-candidate workflow run before all entry gates hold is +invalid evidence and must not be reused. + +## Identity + +| Item | Required identity | +| --- | --- | +| Release source | The release PR's actual `mergeCommit.oid` after exact-head review, complete CI, and the 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.6.0-py3-none-any.whl`, `code_mower-1.6.0.tar.gz`, `candidate.json`, and `rehearsal.json` | +| Telemetry contract | OSS contract commit and fixture-manifest digest named in the entry-gate table, accepted byte-for-byte by the deployed consumer | +| Publication | Annotated `v1.6.0` 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 | Every entry gate above is complete; Python 3.12–3.14 CI, release-integrity, privacy, documentation, package, Graphify, Board, Slack, cloud capability, independent exact-head review, and the authoritative gate pass | +| Candidate | One merge-SHA wheel/sdist pair is built once, retained, verified, and reused without rebuilding; a changed source or packaged document invalidates it | +| Board | Repository filters, invoking and serving versions, stale-service classification, atomic replacement/rollback, local lifecycle-summary projection, and exact recovery guidance agree | +| Audit history | Histories exceeding the former private-context payload limit remain complete and bounded; prose examples do not become controls; malformed real controls, mutation, truncation, duplication, or budget exhaustion fail closed | +| Hosted capability | The deployed authenticated health response advertises the exact accepted contract identity before emission; older or mismatched consumers cause the client to emit nothing | +| Slack telemetry canary | One owner-authorized private lifecycle exercises start/status/answer/cancel or terminal completion as applicable; only meaningful summaries cross the boundary; local Board state reconciles with authenticated hosted totals and freshness | +| Privacy and isolation | No task/message prose, answers, source, diffs, prompts, transcripts, response URLs, Slack identities, credentials, private paths, graph/context data, or raw provider output is uploaded; wrong tenant/repository access is denied | +| Retention and control | Hosted retention, export, deletion, capability disablement, and rollback are verified; disabling either client emission or hosted acceptance stops new telemetry independently | +| Installation | Exact wheel fresh install, v1.5.2 upgrade with synthetic state, disposable rollback to the digest-verified v1.5.2 wheel, uninstall preservation, basic Slack, Graphify, Board service, and public-package paths pass | +| Soak and adoption | The unchanged candidate soaks for at least 24 hours and receives two independent install or upgrade passes | +| Publish and reinstall | Publication reuses the retained bytes; GitHub and PyPI assets and checksums agree; the release-event run does not republish; a clean canonical reinstall matches release identity and behavior | + +Raw source, diffs, prompts, transcripts, provider output, credentials, private +Slack content, private graph data, private Board/session state, and tenant +mappings remain local or in their authorized protected store. Public evidence +contains only immutable public identifiers and sanitized outcomes. + +Follow [the v1.6.0 runbook](v160-release-runbook.md). A failed, missing, +expired, rerun, ambiguous, pre-entry-gate, or superseded candidate is a stop. +It does not authorize a replacement build without newly reviewed release source. diff --git a/docs/v160-release-notes.md b/docs/v160-release-notes.md new file mode 100644 index 00000000..8b56f760 --- /dev/null +++ b/docs/v160-release-notes.md @@ -0,0 +1,75 @@ +# Code Mower v1.6.0 Release Notes + +v1.6.0 improves operational clarity and adds the minimum metadata-only +telemetry needed to observe work requested through the basic private-workspace +Slack control surface. It does not add Slack-to-Board links, rich interactive +cards, per-user analytics, orchestration authority, or a new background +service. + +## What changed + +- **Doctor results are consistent and scoped.** Human-readable warnings now + agree with their JSON state, hosted-only posture does not appear in an + ordinary local adoption check, and remediation names the command that can + establish the missing evidence (#1064 / #1099). +- **Managed Board replacement is atomic and truthful.** Replacement captures + the prior definition, applies the new one once, and reconciles the actual + definition, supervisor, process, listener, repository, arguments, and version. + It reports success only when the new binding is proved, reports rollback only + when the previous binding is proved, and otherwise returns one recovery action + (#1082 / #1100, hardened by #1103). +- **Unmanaged pull requests remain observable.** When no lineage policy is + configured, the Board and lane status preserve readable pull-request and gate + state while labeling lineage as optional. A configured lineage policy still + fails closed (#1083 / #1097). +- **Adoption diagnostics are share-safe by default.** Concise, advanced, and + JSON adoption reports omit private local paths and identifiers unless the + operator explicitly selects the local-only view (#1084 / #1102). +- **The Slack control surface has a closed lifecycle summary.** The additive + `code_mower.controlSurfaceSessionSummary.v1` event carries bounded state, + lifecycle reason, operation counts, owner-action class, pull-request presence, + terminal duration, and normalized usage availability. It carries no command + or message prose, answers, source, diffs, prompts, transcripts, response URLs, + Slack identities, credentials, private paths, graph data, or raw provider + output (#921 / #1098). +- **Emission remains capability-gated.** A client emits only after the hosted + service advertises the exact contract version and fixture-manifest digest. + It emits the first observation and meaningful transitions while suppressing + timestamp-only polling and changing nonterminal elapsed-time or usage samples. + Old clients remain compatible and an unrecognized hosted capability fails + closed. + +## Release entry boundary + +The source preparation does not establish release acceptance. Issue #1105 may +build the one immutable candidate only after all three entry gates are complete: + +1. #1063 merges Board inventory filters, invoking/serving version parity, stale + service detection, and exact service guidance. +2. #1104 merges payload-aware audit-comment ingestion and reserved lineage + control parsing. +3. CodeMower.com #978 deploys the backward-compatible consumer and advertises + the exact accepted OSS contract identity. + +After those gates merge, the retained candidate still needs the bounded private +Slack canary, local-versus-hosted reconciliation, privacy and tenant checks, +clean install, v1.5.2 upgrade, rollback, Graphify and Board rehearsals, a +24-hour soak, two independent installation passes, exact-head release audits, +publication, and canonical reinstall. Observed results belong on #1105 and the +GitHub Release, not in this source document. + +## Upgrade + +Before publication, use only the retained candidate wheel selected by #1105. +After publication, install one stable tool environment and confirm its identity: + +```bash +uv tool install --python 3.12 --reinstall --refresh-package code-mower \ + code-mower==1.6.0 +code-mower --version +code-mower doctor --adoption --repo OWNER/REPO --concise +``` + +Existing repositories should preview `code-mower migration setup-drift` before +applying generated files. Follow [Install And Bootstrap](install.md) and the +[v1.6.0 qualification contract](v160-qualification.md). diff --git a/docs/v160-release-runbook.md b/docs/v160-release-runbook.md new file mode 100644 index 00000000..4f3e5e21 --- /dev/null +++ b/docs/v160-release-runbook.md @@ -0,0 +1,153 @@ +# v1.6.0 immutable candidate and publication runbook + +The release PR performs no tag, immutable build, production enablement, or +publication. Qualification consumes one exact retained candidate built from +the release PR merge SHA after every entry gate is complete. + +## 0. Prove the release entry gates + +Do not dispatch the candidate workflow until #1063 and #1104 are merged and the +CodeMower.com #978 deployment advertises the exact accepted contract identity from +the [qualification contract](v160-qualification.md). Confirm each merge is an +ancestor of the prospective release head, inspect the authenticated hosted +health response, and record only public deployment and contract identifiers. + +Production client emission remains disabled during this check. A health response +that is unauthenticated, stale, missing either identity, or names another digest +does not satisfy the gate. + +## 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, documentation lifecycle and rendering checks, package +projection and wheel rehearsal, release-integrity checks, and the authoritative gate. +Confirm every required implementation is an ancestor, then 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 every rerun. + +```bash +gh workflow run release-candidate.yml --repo "$REPO" --ref main \ + -f expected_sha="$RELEASE_SHA" -f release_pr="$RELEASE_PR" +``` + +Bind the first successful attempt and download its retained artifacts: + +```bash +CANDIDATE_RUN_ID=REPLACE_WITH_VERIFIED_RUN_ID +CANDIDATE_DIR="$PWD/v160-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, +artifact digests, inventories, and every installed-wheel rehearsal. Retain this +pair. Publication downloads it and does not rebuild it. + +## 3. Qualify the exact candidate + +Use disposable environments and the retained wheel for: + +- a fresh install without uv or pipx using Python 3.12; +- an upgrade from v1.5.2 with state preservation and disposable rollback to the + manifest's digest-verified v1.5.2 wheel; +- the checkout-free remote observer and safe init paths; +- payload-aware long audit histories, explanatory marker-name prose, valid + controls, malformed real controls, pagination mutation, and bounded refusal; +- Board repository filtering, invoking/serving version parity, stale-service + guidance, atomic replacement/rollback, and lifecycle-summary projection; +- Graphify installed-reader compatibility and bounded synthetic queries; +- basic Slack lifecycle readiness without claiming a live canary; and +- capability-gated lifecycle-summary suppression, privacy, and tenant fixtures. + +Record only public identifiers and sanitized outcomes. Keep source, logs, +credentials, Slack content, graph content, transcripts, mappings, and local +paths private. + +## 4. Run one bounded private Slack telemetry canary + +Obtain the owner's numeric task and aggregate campaign ACU cap before creating +paid provider work. Provider authorized usage and settled usage are separate facts. +Use the exact candidate and the deployed #978 consumer. Exercise a bounded +private lifecycle and reconcile: + +- the local Board's current summary and transition count; +- authenticated hosted aggregate totals and freshness; +- suppression of timestamp-only polls and nonterminal elapsed/usage changes; +- metadata-only field inventory and tenant/repository isolation; +- retention, export, deletion, client disablement, hosted disablement, and + rollback; and +- provider exit separately from logical completion and billing settlement. + +Any contract mismatch, private-field appearance, unexplained count difference, +wrong-tenant visibility, unavailable rollback, or ambiguous provider exit stops +qualification. Never replace observed failure with an owner override. + +## 5. Soak and independent adoption + +Keep the candidate bytes unchanged for at least 24 hours after the accepted +canary. During that interval, obtain two independent installation or upgrade +passes in clean environments. Each pass must verify the artifact digest, +version, doctor, safe initialization, basic Slack readiness, Graphify, Board, +and uninstall or rollback behavior. A source or packaged-document change starts +qualification again with newly reviewed release source. + +## 6. Owner decision, unchanged tag, and publication + +After every qualification row passes, recheck the release PR, independent +reviews, CI, gate, candidate digests, hosted capability, canary, soak, adoption +passes, 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.6.0 "$RELEASE_SHA" -m 'Code Mower v1.6.0' +git push origin refs/tags/v1.6.0 +test "$(git rev-list -n 1 v1.6.0)" = "$RELEASE_SHA" +gh workflow run release.yml --repo "$REPO" --ref v1.6.0 \ + -f expected_sha="$RELEASE_SHA" -f candidate_run_id="$CANDIDATE_RUN_ID" \ + -f publish_testpypi=false -f publish_pypi=false +``` + +Verify the tag, SHA, candidate identity, digests, inventories, and rehearsal. +Then publish the same candidate: + +```bash +gh workflow run release.yml --repo "$REPO" --ref v1.6.0 \ + -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 +from the retained wheel and sdist, immutable release notes, and sanitized public +evidence. Require the release-event run to consume the same candidate and skip +both publication jobs. Compare downloaded GitHub assets byte-for-byte. + +## 7. Independent canonical reinstall and closeout + +Download `code-mower==1.6.0` from canonical PyPI without cache or extra indexes. +Verify wheel and sdist digests, `code-mower --version`, installed metadata, +documentation inventory, safe init, Board status and service guidance, +Graphify, basic Slack, and capability-gated telemetry. Exercise rollback only +in disposable state. + +Record the release SHA, candidate, canary, soak, independent adoption, +publication, release-event, artifact digest, and canonical reinstall evidence +on #1105 and the GitHub Release. Close #1105 and #1066 only after the public +package and release evidence agree. diff --git a/pyproject.toml b/pyproject.toml index e62dbdbf..34d0b419 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "code-mower" -version = "1.5.2" +version = "1.6.0" description = "Multi-reviewer AI code audit orchestration" requires-python = ">=3.12" readme = "README.md" @@ -54,6 +54,9 @@ where = ["src"] "docs/v152-release-notes.md", "docs/v152-qualification.md", "docs/v152-release-runbook.md", + "docs/v160-release-notes.md", + "docs/v160-qualification.md", + "docs/v160-release-runbook.md", "docs/slack-setup.md", "docs/graphify-setup.md", ] diff --git a/release.yml b/release.yml index 3b61dc71..6260db98 100644 --- a/release.yml +++ b/release.yml @@ -1,10 +1,10 @@ schema: code_mower.release.v1 -version: 1.5.2 -previous_version: 1.5.1 -previous_wheel_sha256: add824ee30ae876f241735f6db2cc555ff2afbf7965e3967a753b86d76cf6818 -tag: v1.5.2 -package_spec: code-mower==1.5.2 -stage: stable +version: 1.6.0 +previous_version: 1.5.2 +previous_wheel_sha256: 44c7082b1fec2983d85ac7ce7feb7cf54b2b7d26e020e073716614d480118147 +tag: v1.6.0 +package_spec: code-mower==1.6.0 +stage: candidate python: minimum: "3.12" tested: @@ -12,9 +12,9 @@ python: - "3.13" - "3.14" documents: - notes: docs/v152-release-notes.md - qualification: docs/v152-qualification.md - runbook: docs/v152-release-runbook.md + notes: docs/v160-release-notes.md + qualification: docs/v160-qualification.md + runbook: docs/v160-release-runbook.md installation: docs/install.md publication: docs/pypi-release.md required_modules: @@ -27,9 +27,9 @@ required_modules: - supervisor_contract_v2.py - templates/slack/hosted-app-manifest.json required_docs: - - docs/v152-release-notes.md - - docs/v152-qualification.md - - docs/v152-release-runbook.md + - docs/v160-release-notes.md + - docs/v160-qualification.md + - docs/v160-release-runbook.md - docs/slack-setup.md - docs/graphify-setup.md rehearsal_schema: code_mower.release_rehearsal.v1 diff --git a/src/code_mower/__init__.py b/src/code_mower/__init__.py index 16e74b84..6f80c52a 100644 --- a/src/code_mower/__init__.py +++ b/src/code_mower/__init__.py @@ -1,3 +1,3 @@ """Code Mower package.""" -__version__ = "1.5.2" +__version__ = "1.6.0" diff --git a/src/code_mower/package_manifest.py b/src/code_mower/package_manifest.py index f6897a27..505d9510 100644 --- a/src/code_mower/package_manifest.py +++ b/src/code_mower/package_manifest.py @@ -690,6 +690,9 @@ ("docs/v152-release-notes.md", "docs/v152-release-notes.md", "doc"), ("docs/v152-qualification.md", "docs/v152-qualification.md", "doc"), ("docs/v152-release-runbook.md", "docs/v152-release-runbook.md", "doc"), + ("docs/v160-release-notes.md", "docs/v160-release-notes.md", "doc"), + ("docs/v160-qualification.md", "docs/v160-qualification.md", "doc"), + ("docs/v160-release-runbook.md", "docs/v160-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 aebdc5ec..935e03d3 100644 --- a/src/code_mower/release_readiness.py +++ b/src/code_mower/release_readiness.py @@ -9,6 +9,7 @@ from pathlib import Path from typing import Any +from packaging.version import Version import yaml from . import __version__ @@ -1511,15 +1512,41 @@ def _candidate_runbook_checks(repo_path: Path) -> tuple[list[str], list[str]]: except release_metadata_module.ReleaseMetadataError as exc: return [f"valid {release_metadata_module.MANIFEST_PATH}: {exc}"], [] text = _read_text_if_exists(repo_path / metadata.documents["runbook"]) - order = ( - "## 1. Review and merge", "## 2. Build and retain", - "gh workflow run release-candidate.yml", "## 3. Qualify the exact candidate", - "## 4. Observe the bounded hosted Board canary", "## 5. Owner decision", - f'git tag -a {metadata.tag} "$RELEASE_SHA"', - "-f publish_testpypi=false -f publish_pypi=false", - "-f publish_testpypi=false -f publish_pypi=true", - "## 6. Independent canonical reinstall", - ) + if Version(metadata.version) >= Version("1.6.0"): + order = ( + "## 0. Prove the release entry gates", + "#1063", "#1104", "#978", + "## 1. Review and merge", "## 2. Build and retain", + "gh workflow run release-candidate.yml", + "## 3. Qualify the exact candidate", + "## 4. Run one bounded private Slack telemetry canary", + "## 5. Soak and independent adoption", + "## 6. Owner decision", + f'git tag -a {metadata.tag} "$RELEASE_SHA"', + "-f publish_testpypi=false -f publish_pypi=false", + "-f publish_testpypi=false -f publish_pypi=true", + "## 7. Independent canonical reinstall", + ) + release_assertions = ( + "exact accepted contract identity", "24 hours", + "two independent installation", "capability-gated telemetry", + ) + else: + order = ( + "## 1. Review and merge", "## 2. Build and retain", + "gh workflow run release-candidate.yml", + "## 3. Qualify the exact candidate", + "## 4. Observe the bounded hosted Board canary", + "## 5. Owner decision", + f'git tag -a {metadata.tag} "$RELEASE_SHA"', + "-f publish_testpypi=false -f publish_pypi=false", + "-f publish_testpypi=false -f publish_pypi=true", + "## 6. Independent canonical reinstall", + ) + release_assertions = ( + "single-lane", "multi-lane", "fresh dashboard", + "Slack telemetry remains deferred to v1.6.0", + ) assertions = ( "--json state --jq '.state')\" = MERGED", "--json mergeCommit --jq '.mergeCommit.oid'", @@ -1528,11 +1555,11 @@ def _candidate_runbook_checks(repo_path: Path) -> tuple[list[str], list[str]]: f'test "$(git rev-list -n 1 {metadata.tag})" = "$RELEASE_SHA"', "fresh install without uv or pipx", f"upgrade from v{metadata.previous_version}", "remote observer", "safe init", "Graphify", "basic Slack lifecycle", - "single-lane", "multi-lane", "aggregate campaign ACU", + "aggregate campaign ACU", "provider exit", "authorized usage", "settled usage", - "metadata-only", "fresh dashboard", "does not rebuild", - "Slack telemetry remains deferred to v1.6.0", + "metadata-only", "does not rebuild", "independent exact-head audit", "authoritative gate", + *release_assertions, ) return _unordered_markers(text, order), [item for item in assertions if item not in text] diff --git a/tests/test_release_contract.py b/tests/test_release_contract.py index 7c8b3235..b4432433 100644 --- a/tests/test_release_contract.py +++ b/tests/test_release_contract.py @@ -38,19 +38,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.2.dist-info/METADATA", - "Name: code-mower\nVersion: 1.5.2\nRequires-Dist: PyYAML>=6.0\nRequires-Dist: packaging>=23.2\n") + archive.writestr("code_mower-1.6.0.dist-info/METADATA", + "Name: code-mower\nVersion: 1.6.0\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.2.data/data/share/code-mower/docs/" + doc, b"synthetic") + archive.writestr("code_mower-1.6.0.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.2/" + path) + info = tarfile.TarInfo("code_mower-1.6.0/" + path) info.size = 9 archive.addfile(info, io.BytesIO(b"synthetic")) - self.manifest = {"schema": candidate.SCHEMA, "version": "1.5.2", "source_sha": SHA, + self.manifest = {"schema": candidate.SCHEMA, "version": "1.6.0", "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)} @@ -96,7 +96,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.2.dist-info/METADATA"] += ( + files["code_mower-1.6.0.dist-info/METADATA"] += ( b'Requires-Dist: slack-sdk; python_version >= "3.12" or extra == "coworker"\n' ) with zipfile.ZipFile(wheel, "w") as archive: @@ -119,8 +119,8 @@ 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.2 key. - self.wheel = "code_mower-1.5.2-py3-none-any.whl" + # A future version uses the manifest's wheel identity, not a v1.6.0 key. + self.wheel = "code_mower-1.6.0-py3-none-any.whl" self.manifest = {"source_sha": SHA, "artifacts": {self.wheel: "b" * 64}} self.evidence = {"schema": candidate.REHEARSAL_SCHEMA, "status": "pass", "source_sha": SHA, "artifact_sha256": "b" * 64, @@ -173,23 +173,23 @@ def setUp(self): self.final, self.final_sha, 101, changes={ "code_mower/audit_publication.py": b"final audit publication", - "code_mower-1.5.2.data/data/share/code-mower/docs/v152-qualification.md": + "code_mower-1.6.0.data/data/share/code-mower/docs/v160-qualification.md": b"final qualification", - "code_mower-1.5.2.dist-info/METADATA": self._metadata(b"final description"), - "code_mower-1.5.2.dist-info/RECORD": b"final record", + "code_mower-1.6.0.dist-info/METADATA": self._metadata(b"final description"), + "code_mower-1.6.0.dist-info/RECORD": b"final record", }, ) @staticmethod def _metadata(description=b"prior description"): - return (b"Name: code-mower\nVersion: 1.5.2\nSummary: stable\n" + return (b"Name: code-mower\nVersion: 1.6.0\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.2.dist-info/METADATA": self._metadata(), - "code_mower-1.5.2.dist-info/RECORD": b"prior record", - "code_mower-1.5.2.dist-info/entry_points.txt": + "code_mower-1.6.0.dist-info/METADATA": self._metadata(), + "code_mower-1.6.0.dist-info/RECORD": b"prior record", + "code_mower-1.6.0.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", @@ -199,7 +199,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.2.data/data/share/code-mower/docs/" + doc: + files.update({"code_mower-1.6.0.data/data/share/code-mower/docs/" + doc: b"stable documentation" for doc in candidate.DOCS}) return files @@ -213,7 +213,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.2/" + member) + info = tarfile.TarInfo("code_mower-1.6.0/" + member) info.size = len(b"synthetic") archive.addfile(info, io.BytesIO(b"synthetic")) manifest = { @@ -245,9 +245,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.2.data/data/share/code-mower/docs/v152-qualification.md", - "code_mower-1.5.2.dist-info/METADATA", - "code_mower-1.5.2.dist-info/RECORD", + "code_mower-1.6.0.data/data/share/code-mower/docs/v160-qualification.md", + "code_mower-1.6.0.dist-info/METADATA", + "code_mower-1.6.0.dist-info/RECORD", })) self.assertEqual(set(result["changed_wheel_member_sha256"]), set(result["changed_wheel_members"])) @@ -273,8 +273,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.2.dist-info/METADATA": metadata, - "code_mower-1.5.2.dist-info/RECORD": b"final record", + "code_mower-1.6.0.dist-info/METADATA": metadata, + "code_mower-1.6.0.dist-info/RECORD": b"final record", }) with patch.object(candidate, "run", return_value=""), \ self.assertRaisesRegex(ValueError, "metadata headers"): @@ -341,7 +341,7 @@ def test_publication_requires_verified_artifacts_and_named_rehearsal_before_copy self.assertIn("python -m pip install", dependency_step["run"]) self.assertIn("PyYAML>=6.0", dependency_step["run"]) self.assertIn("packaging>=23.2", dependency_step["run"]) - self.assertNotIn("code_mower-1.5.2-py3-none-any.whl", self.publish) + self.assertNotIn("code_mower-1.6.0-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:]) @@ -459,40 +459,41 @@ def test_mismatched_tag_or_package_spec_is_rejected(self): class ReleaseContractTests(unittest.TestCase): def test_identity_and_readiness(self): - self.assertEqual(__version__, "1.5.2") + self.assertEqual(__version__, "1.6.0") self.assertEqual(release_metadata.load_release_metadata(ROOT).version, __version__) self.assertEqual(release_renderer.render(ROOT, check=True), []) self.assertEqual(release_readiness.render_release_readiness(ROOT)["status"], "pass") def test_removing_exact_candidate_qualification_or_moving_tag_first_blocks(self): - text = (ROOT / "docs/v152-release-runbook.md").read_text() + text = (ROOT / "docs/v160-release-runbook.md").read_text() for bad in (text.replace("## 3. Qualify the exact candidate", "## Removed qualification"), - text.replace('git tag -a v1.5.2 "$RELEASE_SHA"', "tag removed"), + text.replace('git tag -a v1.6.0 "$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_patch_release_contract_keeps_scope_and_observations_explicit(self): - runbook = (ROOT / "docs/v152-release-runbook.md").read_text() - qualification = (ROOT / "docs/v152-qualification.md").read_text() + runbook = (ROOT / "docs/v160-release-runbook.md").read_text() + qualification = (ROOT / "docs/v160-qualification.md").read_text() for marker in ( "fresh install without uv or pipx", - "upgrade from v1.5.1", + "upgrade from v1.5.2", "remote observer", "safe init", "basic Slack lifecycle", - "Slack telemetry remains deferred to v1.6.0", + "bounded private Slack telemetry canary", "metadata-only", - "fresh dashboard", + "24 hours", + "two independent installation", ): with self.subTest(marker=marker): self.assertIn(marker, runbook) - self.assertIn("A new paid\nprovider canary", qualification) - self.assertIn("unnecessary", qualification) + self.assertIn("CodeMower.com #978 deployed", qualification) + self.assertIn("local Board state reconciles", 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.2"): + with patch.object(release_readiness, "_python_package_version", return_value="1.6.0"): payload = release_readiness.render_release_readiness(ROOT) checks = {c["id"]: c for c in payload["checks"]} for name in ("distribution-build-and-verify", "post-merge-release-runbook-ordered", @@ -501,9 +502,9 @@ def test_later_versions_do_not_revert_to_building_at_publication(self): self.assertEqual(checks[name]["status"], "pass") ordered = checks["post-merge-release-runbook-ordered"]["detail"] asserted = checks["post-merge-release-runbook-asserted"]["detail"] - self.assertEqual(ordered["release_tag"], "v1.5.2") - self.assertIn("docs/v152-release-runbook.md", ordered["required_commands"][0]) - self.assertNotIn("gh release create v1.5.2", ordered["required_commands"]) + self.assertEqual(ordered["release_tag"], "v1.6.0") + self.assertIn("docs/v160-release-runbook.md", ordered["required_commands"][0]) + self.assertNotIn("gh release create v1.6.0", ordered["required_commands"]) self.assertEqual(asserted["required_assertions"], ["merge SHA and retained artifact binding; explicit owner gates"]) self.assertEqual(payload["next_actions"][0]["id"], "immutable-candidate-first") @@ -511,12 +512,12 @@ def test_later_versions_do_not_revert_to_building_at_publication(self): dispatches = [a for a in payload["next_actions"] if "gh workflow run release.yml" in a["command"]] self.assertEqual(len(dispatches), 3) for action in dispatches: - self.assertIn("--ref v1.5.2", action["command"]) + self.assertIn("--ref v1.6.0", action["command"]) self.assertIn('-f candidate_run_id="$CANDIDATE_RUN_ID"', action["command"]) def test_later_versions_still_reject_missing_candidate_runbook_gates(self): original = release_readiness._read_text_if_exists - runbook_path = ROOT / "docs/v152-release-runbook.md" + runbook_path = ROOT / "docs/v160-release-runbook.md" for marker, check_id, detail in ( ("## 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"), @@ -525,7 +526,7 @@ def read(path, marker=marker): text = original(path) return text.replace(marker, "") if path == runbook_path else text with self.subTest(marker=marker), \ - patch.object(release_readiness, "_python_package_version", return_value="1.5.2"), \ + patch.object(release_readiness, "_python_package_version", return_value="1.6.0"), \ patch.object(release_readiness, "_read_text_if_exists", side_effect=read): checks = release_readiness.render_release_readiness(ROOT)["checks"] check = next(c for c in checks if c["id"] == check_id) diff --git a/tests/test_release_hygiene.py b/tests/test_release_hygiene.py index f74a8d7d..2e4a348f 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.2") + self.assertEqual(__version__, "1.6.0") def test_dogfood_repo_has_real_root_config(self) -> None: config_path = ROOT / "code-mower.yml" @@ -267,7 +267,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.2", troubleshooting) + self.assertIn("code-mower==1.6.0", 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"): @@ -1246,7 +1246,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.2", completed.stderr) + self.assertIn("pipx install code-mower==1.6.0", 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) @@ -5764,11 +5764,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.2"', + 'version = "1.6.0"', (output_dir / "pyproject.toml").read_text(encoding="utf-8"), ) self.assertIn( - '__version__ = "1.5.2"', + '__version__ = "1.6.0"', (output_dir / "src/code_mower/__init__.py").read_text( encoding="utf-8" ), @@ -7780,7 +7780,7 @@ def test_normalized_release_version_matches_packaging_semantics(self) -> None: self.assertTrue(agree(left, right)) different = ( - ("1.4.2", "1.5.2"), + ("1.4.2", "1.6.0"), ("1.4.2", "1.4.2rc1"), ("1.4.2", "1.4.2.post1"), ("1.4.2", "1.4.2.dev1"), @@ -7852,7 +7852,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.2", + "code-mower>=1.4.2,<1.6.0", "code-mower[coworker]==1.4.2", 'code-mower==1.4.2; python_version >= "3.12"', "code-mower==not-a-version", @@ -8282,10 +8282,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.2") - self.assertEqual(payload["release_tag"], "v1.5.2") - self.assertEqual(payload["alpha_tag"], "v1.5.2") - self.assertEqual(payload["package_index_spec"], "code-mower==1.5.2") + self.assertEqual(payload["version"], "1.6.0") + self.assertEqual(payload["release_tag"], "v1.6.0") + self.assertEqual(payload["alpha_tag"], "v1.6.0") + self.assertEqual(payload["package_index_spec"], "code-mower==1.6.0") check_ids = {check["id"]: check for check in payload["checks"]} self.assertEqual(check_ids["package-version-consistency"]["status"], "pass") self.assertEqual( @@ -8294,7 +8294,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.2") + self.assertEqual(manifest_check["detail"]["manifest_version"], "1.6.0") 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") @@ -8304,7 +8304,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.2", commands["dry-run-release-workflow"]) + self.assertIn("--ref v1.6.0", 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) @@ -8317,9 +8317,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.2"', release) + self.assertIn('--title "Code Mower v1.6.0"', release) self.assertIn('--notes-file "$GITHUB_RELEASE_NOTES"', release) - self.assertIn('$CANDIDATE_DIR/code_mower-1.5.2-py3-none-any.whl', release) + self.assertIn('$CANDIDATE_DIR/code_mower-1.6.0-py3-none-any.whl', release) self.assertEqual( actions["create-github-release"]["required_env"], ["CANDIDATE_DIR", "CANDIDATE_RUN_ID", "GITHUB_RELEASE_NOTES"], @@ -8447,7 +8447,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.2") + self.assertEqual(check["detail"]["source_version"], "1.6.0") self.assertEqual(check["detail"]["generated_init_version"], "0.0.0") def test_release_readiness_fails_on_committed_manifest_version_drift(self) -> None: @@ -8463,7 +8463,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.2") + self.assertEqual(check["detail"]["init_version"], "1.6.0") def _manifest_drift_check(self, mutate: Callable[[dict], None]) -> dict: committed = json.loads( @@ -11153,8 +11153,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.2`, with package spec " - "`code-mower==1.5.2`. Confirm the release tag on GitHub Releases " + "This source defines Code Mower `v1.6.0`, with package spec " + "`code-mower==1.6.0`. 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." @@ -11164,7 +11164,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.2/docs/try-in-10-minutes.md" + "v1.6.0/docs/try-in-10-minutes.md" ), ) @@ -11222,7 +11222,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.2`", sessions) + self.assertIn("included in `code-mower==1.6.0`", 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( @@ -11271,11 +11271,11 @@ def test_current_release_docs_record_package_index_procedure(self) -> None: for text in (readme, current_state): self.assertIn(current_status, " ".join(text.split())) self.assertIn( - "The v1.5.2 source defines package-index entrypoint `code-mower==1.5.2`\n" - " (GitHub tag `v1.5.2`)", + "The v1.6.0 source defines package-index entrypoint `code-mower==1.6.0`\n" + " (GitHub tag `v1.6.0`)", public_release, ) - self.assertIn("The v1.5.2 supervised-pilot source includes", public_release) + self.assertIn("The v1.6.0 supervised-pilot source includes", public_release) self.assertIn( "`code-mower lanes status --repo OWNER/REPO` as the operator snapshot", " ".join(public_release.split()), @@ -11644,7 +11644,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.2", install) + self.assertIn("uv tool install --python 3.12 code-mower==1.6.0", install) self.assertIn( 'PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON"', install, @@ -12023,7 +12023,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.2", package_step["command"]) + self.assertIn("code-mower==1.6.0", 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 508de61f..df1852c7 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.2`", sentence) - self.assertIn("`code-mower==1.5.2`", sentence) + self.assertIn("`v1.6.0`", sentence) + self.assertIn("`code-mower==1.6.0`", sentence) for relative in ("README.md", "docs/current-state-and-roadmap.md"): with self.subTest(doc=relative): self.assertIn(sentence, " ".join(_read(relative).split())) @@ -183,11 +183,11 @@ def test_never_expiry_is_what_init_actually_advertises(self): class PublicReleaseChecklistTests(unittest.TestCase): - def test_checklist_names_v152_as_the_source_entrypoint(self): + def test_checklist_names_v160_as_the_source_entrypoint(self): checklist = " ".join(_read("docs/public-release-checklist.md").split()) self.assertIn( - "The v1.5.2 source defines package-index entrypoint " - "`code-mower==1.5.2` (GitHub tag `v1.5.2`). Confirm that the tag " + "The v1.6.0 source defines package-index entrypoint " + "`code-mower==1.6.0` (GitHub tag `v1.6.0`). Confirm that the tag " "and package version are published before using the index command.", checklist, ) @@ -357,15 +357,15 @@ def test_release_records_claim_upgrade_coverage_that_exists(self): class VersionIdentityTests(unittest.TestCase): - def test_source_version_is_1_5_2(self): - self.assertEqual(__version__, "1.5.2") + def test_source_version_is_1_6_0(self): + self.assertEqual(__version__, "1.6.0") 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.2") + self.assertEqual(release_readiness._release_tag_for_version(__version__), "v1.6.0") class RunbookIdentityTests(unittest.TestCase): @@ -621,7 +621,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.2 code, with no provider login or network doctor probes.""" + """Exercise installed 1.6.0 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") @@ -655,7 +655,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.2' +assert code_mower.__version__ == '1.6.0' empty_store = Path.cwd() / 'empty-provider-store' empty_store.mkdir() def run(args, doctor=False): From 28a1d463a7552f00d4425eeb608bd88ef3f2dd8d Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Mon, 21 Sep 2026 18:22:20 -0700 Subject: [PATCH 2/5] Reconcile merged v1.6 release scope --- CHANGELOG.md | 12 ++++++++--- docs/current-state-and-roadmap.md | 17 ++++++++-------- docs/docs-manifest.yml | 6 +++--- docs/quickstart.md | 14 ++++++------- docs/troubleshooting.md | 4 ++-- docs/v160-qualification.md | 5 ++--- docs/v160-release-notes.md | 33 +++++++++++++++++++++---------- docs/v160-release-runbook.md | 11 ++++++----- 8 files changed, 61 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 614e3d26..0e3b099e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,11 +26,17 @@ surface. See the [release notes](docs/v160-release-notes.md) and the - Replace managed Board services atomically, verify either the new or restored binding from host state, and refuse ambiguous reconciliation (#1082 / #1100, hardened by #1103). -- Treat an unmanaged pull request as a neutral observer state when no lineage - policy is configured, without weakening configured lineage enforcement - (#1083 / #1097). +- Filter Board inventory by identity-verified repository, expose invoking, + serving, installed, managed-service, and restart state consistently, and give + stale managed or transient Boards an exact recovery command (#1063 / #1109). +- Treat an ordinary pull request with no Code Mower provenance as neutral + `unmanaged`, while keeping a visible malformed Code Mower claim actionable + and fail-closed (#1083 / #1097). - Make adoption diagnostics share-safe by default and require an explicit local view for private paths and identifiers (#1084 / #1102). +- Isolate spend evidence across repositories and attach a pre-PR builder record + only when its branch exactly matches one fetched pull request, improving cost + coverage without exporting new fields or private data (#1106 / #1108). - Freeze the provider-neutral `code_mower.controlSurfaceSessionSummary.v1` contract, its accepted and rejected fixtures, capability gate, transition suppression, local Board projection, and metadata-only cloud emitter diff --git a/docs/current-state-and-roadmap.md b/docs/current-state-and-roadmap.md index 102b9804..4f145fa0 100644 --- a/docs/current-state-and-roadmap.md +++ b/docs/current-state-and-roadmap.md @@ -153,22 +153,23 @@ future hosted-service work. `v1.5.2` is released and remains the current supported package. The `v1.6.0` source line now contains the completed doctor taxonomy, atomic Board replacement, -neutral unmanaged-lineage behavior, share-safe adoption diagnostics, and the -closed metadata-only control-surface summary contract. The optional client +identity-verified Board inventory and version guidance, neutral `unmanaged` +state for ordinary pull requests with no Code Mower provenance, share-safe +adoption diagnostics, cross-repository cost isolation, and the closed +metadata-only control-surface summary contract. Visible malformed Code Mower +claims remain actionable and fail-closed. The optional client emitter remains fail-closed unless the hosted service advertises the exact accepted contract identity. The [v1.6.0 milestone](https://github.com/codemower-ai/code-mower/milestone/2), [epic #1066](https://github.com/codemower-ai/code-mower/issues/1066), and [release issue #1105](https://github.com/codemower-ai/code-mower/issues/1105) -are the live trackers. Three entry gates remain before the immutable candidate: +are the live trackers. Board clarity #1063 is complete through merged PR #1109. +Two entry gates remain before the immutable candidate: -1. [#1063](https://github.com/codemower-ai/code-mower/issues/1063) — Board - repository filters, invoking/serving version parity, stale-service detection, - and exact service guidance; -2. [#1104](https://github.com/codemower-ai/code-mower/issues/1104) — bounded, +1. [#1104](https://github.com/codemower-ai/code-mower/issues/1104) — bounded, payload-aware audit comment ingestion and reserved control parsing; and -3. [CodeMower.com #978](https://github.com/codemower-ai/code-mower/issues/978) +2. [CodeMower.com #978](https://github.com/codemower-ai/code-mower/issues/978) — deployed validation, tenant isolation, retention, export/deletion, aggregate counts, freshness, and exact capability advertisement. diff --git a/docs/docs-manifest.yml b/docs/docs-manifest.yml index 37b74c9b..1c63a75a 100644 --- a/docs/docs-manifest.yml +++ b/docs/docs-manifest.yml @@ -364,10 +364,10 @@ documents: sha256: 7a7492e3e297d920dbb76b7c25339f3865d545c108a0114e65b1a3d989ad3bcd - path: docs/v160-qualification.md status: frozen - sha256: 17195cba43abebc48ff6fd736c622fa3e0d4d8927e58d85f5d7905a0b6f2fde7 + sha256: 2ba9ce90304b11141e8eb101990a4246eb86c82242884b8ff035e31ea0a78d42 - path: docs/v160-release-notes.md status: frozen - sha256: 702d1a3dcb44b1f85675343a1aa4ac4e8cfe1bd5c446dea9d60920dd7c28d0e0 + sha256: f67192691b6859f8d2ab02dda23fc354c7580bb9fdd16b4badcb7c498d99a8c8 - path: docs/v160-release-runbook.md status: frozen - sha256: 87ab40d83790ed3b07667b40828770b17f398bfb538a0ba88ed53975e6e9f2c9 + sha256: e66f9a030c5eef660a9b1231e316391d17e2be8fa2ca38586cb272afc1111791 diff --git a/docs/quickstart.md b/docs/quickstart.md index 7e4296f7..50829168 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.2 source pin). +illustrative shape, not the v1.6.0 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.2 +uv tool install --python 3.12 code-mower==1.6.0 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.2 +pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.6.0 code-mower --version ``` -`1.5.2` is the supervised-pilot release line. Confirm it is visible on the +`1.6.0` 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](v152-release-runbook.md). If you want a future prerelease instead +[the candidate runbook](v160-release-runbook.md). If you want a future prerelease instead of this exact release target, use: ```bash @@ -243,7 +243,7 @@ do not put them in repository configuration. ```bash PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" \ - 'code-mower[coworker]==1.5.2' + 'code-mower[coworker]==1.6.0' code-mower init --easy --context-connection example-context --dry-run code-mower init --easy --context-connection example-context --apply code-mower context connect coworker --connection example-context @@ -507,7 +507,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.2 \ + --package-spec code-mower==1.6.0 \ --allow-package-index \ --python "$(command -v python3.12)" \ --json diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 9b2b7ce0..36c65c73 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -238,7 +238,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.2 +PIP_NO_CACHE_DIR=1 pipx install --force --python "$CODE_MOWER_PYTHON" code-mower==1.6.0 hash -r code-mower --version ``` @@ -249,7 +249,7 @@ path: ```bash pipx uninstall code-mower -uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.5.2 +uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mower==1.6.0 hash -r command -v code-mower code-mower --version diff --git a/docs/v160-qualification.md b/docs/v160-qualification.md index 88086cd0..820e0263 100644 --- a/docs/v160-qualification.md +++ b/docs/v160-qualification.md @@ -12,10 +12,9 @@ of the following and each dependency has its own accepted exact-head evidence: | Gate | Required state | | --- | --- | -| Board clarity | #1063 merged: repository-filtered inventory, invoking/serving version parity, stale-service detection, and exact restart/promotion guidance | | Audit history | #1104 merged: bounded payload-aware comment pagination and exact recognition of reserved lineage controls | | Hosted consumer | CodeMower.com #978 deployed before client emission; `/api/health` advertises contract commit `99b657ae9822a689b46d21c41695a4cfb28a177d` and fixture-manifest SHA-256 `9e87c52812a49a1c72d0e0d2448661a3ef17cb8539ca8e029c6669ea9738d62e` | -| Source scope | #1064/#1099, #1082/#1100/#1103, #1083/#1097, #1084/#1102, and #921/#1098 are ancestors of the release commit | +| Source scope | #1063/#1109, #1064/#1099, #1082/#1100/#1103, #1083/#1097, #1084/#1102, #921/#1098, and #1106/#1108 are ancestors of the release commit | | Release text | The v1.6 epic, release notes, roadmap, package identity, and current documentation agree on final scope | The first immutable-candidate workflow run before all entry gates hold is @@ -39,7 +38,7 @@ invalid evidence and must not be reused. | Source | Every entry gate above is complete; Python 3.12–3.14 CI, release-integrity, privacy, documentation, package, Graphify, Board, Slack, cloud capability, independent exact-head review, and the authoritative gate pass | | Candidate | One merge-SHA wheel/sdist pair is built once, retained, verified, and reused without rebuilding; a changed source or packaged document invalidates it | | Board | Repository filters, invoking and serving versions, stale-service classification, atomic replacement/rollback, local lifecycle-summary projection, and exact recovery guidance agree | -| Audit history | Histories exceeding the former private-context payload limit remain complete and bounded; prose examples do not become controls; malformed real controls, mutation, truncation, duplication, or budget exhaustion fail closed | +| Audit history | Histories exceeding the former private-context payload limit remain complete and bounded; ordinary prose does not become control data; malformed real controls, mutation, truncation, duplication, or budget exhaustion fail closed | | Hosted capability | The deployed authenticated health response advertises the exact accepted contract identity before emission; older or mismatched consumers cause the client to emit nothing | | Slack telemetry canary | One owner-authorized private lifecycle exercises start/status/answer/cancel or terminal completion as applicable; only meaningful summaries cross the boundary; local Board state reconciles with authenticated hosted totals and freshness | | Privacy and isolation | No task/message prose, answers, source, diffs, prompts, transcripts, response URLs, Slack identities, credentials, private paths, graph/context data, or raw provider output is uploaded; wrong tenant/repository access is denied | diff --git a/docs/v160-release-notes.md b/docs/v160-release-notes.md index 8b56f760..a9f448ac 100644 --- a/docs/v160-release-notes.md +++ b/docs/v160-release-notes.md @@ -18,10 +18,16 @@ service. It reports success only when the new binding is proved, reports rollback only when the previous binding is proved, and otherwise returns one recovery action (#1082 / #1100, hardened by #1103). -- **Unmanaged pull requests remain observable.** When no lineage policy is - configured, the Board and lane status preserve readable pull-request and gate - state while labeling lineage as optional. A configured lineage policy still - fails closed (#1083 / #1097). +- **Board inventory identifies the process the current CLI is operating.** + `board list --repo` includes only listeners whose identity verifies the + repository. Board inventory and lane status expose invoking, serving, + installed, managed-service, and restart state consistently. Stale managed + services and verified transient Boards receive exact restart or promotion + commands (#1063 / #1109). +- **Unmanaged pull requests remain observable.** An ordinary pull request with + no Code Mower provenance is neutral `unmanaged`; the Board and lane status + preserve its readable pull-request and gate state. A visible malformed Code + Mower claim remains actionable and fail-closed (#1083 / #1097). - **Adoption diagnostics are share-safe by default.** Concise, advanced, and JSON adoption reports omit private local paths and identifiers unless the operator explicitly selects the local-only view (#1084 / #1102). @@ -38,17 +44,24 @@ service. timestamp-only polling and changing nonterminal elapsed-time or usage samples. Old clients remain compatible and an unrecognized hosted capability fails closed. +- **Cross-repository cost coverage is isolated and attributable.** Shared + builder and reviewer ledgers separate explicitly different repositories, and + a pre-PR builder record links only when its branch exactly matches one fetched + pull request. Missing, ambiguous, malformed, and same-repository unattributable + evidence remains fail-closed. The change adds no upload fields and exports no + subscription access, elapsed time, token counts, source, diffs, prompts, + transcripts, issue bodies, raw output, credentials, or local paths + (#1106 / #1108). ## Release entry boundary -The source preparation does not establish release acceptance. Issue #1105 may -build the one immutable candidate only after all three entry gates are complete: +The Board implementation gate is complete through #1063 / #1109. This source +preparation does not establish release acceptance. Issue #1105 may build the +one immutable candidate only after both remaining entry gates are complete: -1. #1063 merges Board inventory filters, invoking/serving version parity, stale - service detection, and exact service guidance. -2. #1104 merges payload-aware audit-comment ingestion and reserved lineage +1. #1104 merges payload-aware audit-comment ingestion and reserved lineage control parsing. -3. CodeMower.com #978 deploys the backward-compatible consumer and advertises +2. CodeMower.com #978 deploys the backward-compatible consumer and advertises the exact accepted OSS contract identity. After those gates merge, the retained candidate still needs the bounded private diff --git a/docs/v160-release-runbook.md b/docs/v160-release-runbook.md index 4f3e5e21..a87582d5 100644 --- a/docs/v160-release-runbook.md +++ b/docs/v160-release-runbook.md @@ -6,11 +6,12 @@ the release PR merge SHA after every entry gate is complete. ## 0. Prove the release entry gates -Do not dispatch the candidate workflow until #1063 and #1104 are merged and the -CodeMower.com #978 deployment advertises the exact accepted contract identity from -the [qualification contract](v160-qualification.md). Confirm each merge is an -ancestor of the prospective release head, inspect the authenticated hosted -health response, and record only public deployment and contract identifiers. +Board clarity #1063 / #1109 must be an ancestor of the prospective release +head. Do not dispatch the candidate workflow until #1104 is also merged and the +CodeMower.com #978 deployment advertises the exact accepted contract identity +from the [qualification contract](v160-qualification.md). Confirm each merge, +inspect the authenticated hosted health response, and record only public +deployment and contract identifiers. Production client emission remains disabled during this check. A health response that is unauthenticated, stale, missing either identity, or names another digest From 60420f7deffa4806291284f881d606bd60edde1d Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Mon, 21 Sep 2026 18:58:38 -0700 Subject: [PATCH 3/5] Reconcile final v1.6 entry gate --- CHANGELOG.md | 13 +++++++------ docs/current-state-and-roadmap.md | 22 ++++++++++++---------- docs/docs-manifest.yml | 6 +++--- docs/public-release-checklist.md | 7 +++++-- docs/pypi-release.md | 7 +++++-- docs/v160-qualification.md | 10 ++++++---- docs/v160-release-notes.md | 30 +++++++++++++++++++----------- docs/v160-release-runbook.md | 15 +++++++++------ tests/test_release_contract.py | 4 +++- 9 files changed, 69 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e3b099e..5b9ecfda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,7 @@ later entries are regular releases. ## Unreleased -- Audit comment ingestion now uses GitHub-specific response, aggregate-byte, - item, and request-page budgets. Oversized pages reduce `per_page` and restart - safely, complete histories receive a stable reread, and omissions, duplicate - IDs, edits, incomplete terminal proof, and budget exhaustion fail with - actionable diagnostics. Lineage controls are recognized only as standalone - HTML control-comment lines outside inline and fenced examples (#1104). +- No changes yet. ## 1.6.0 — operational clarity and minimum telemetry @@ -37,6 +32,12 @@ surface. See the [release notes](docs/v160-release-notes.md) and the - Isolate spend evidence across repositories and attach a pre-PR builder record only when its branch exactly matches one fetched pull request, improving cost coverage without exporting new fields or private data (#1106 / #1108). +- Bound GitHub audit-comment history by response, aggregate-byte, item, and + request-page budgets; adaptively restart smaller pages; prove a stable + terminal history; and fail closed on omissions, duplicate or changed IDs, + truncation, and exhausted budgets. Recognize lineage controls only as exact + standalone HTML comments outside fenced Markdown, while malformed controls + from trusted authorities remain fail-closed (#1104 / #1107). - Freeze the provider-neutral `code_mower.controlSurfaceSessionSummary.v1` contract, its accepted and rejected fixtures, capability gate, transition suppression, local Board projection, and metadata-only cloud emitter diff --git a/docs/current-state-and-roadmap.md b/docs/current-state-and-roadmap.md index 4f145fa0..cdc3ba06 100644 --- a/docs/current-state-and-roadmap.md +++ b/docs/current-state-and-roadmap.md @@ -164,16 +164,18 @@ accepted contract identity. The [v1.6.0 milestone](https://github.com/codemower-ai/code-mower/milestone/2), [epic #1066](https://github.com/codemower-ai/code-mower/issues/1066), and [release issue #1105](https://github.com/codemower-ai/code-mower/issues/1105) -are the live trackers. Board clarity #1063 is complete through merged PR #1109. -Two entry gates remain before the immutable candidate: - -1. [#1104](https://github.com/codemower-ai/code-mower/issues/1104) — bounded, - payload-aware audit comment ingestion and reserved control parsing; and -2. [CodeMower.com #978](https://github.com/codemower-ai/code-mower/issues/978) - — deployed validation, tenant isolation, retention, export/deletion, - aggregate counts, freshness, and exact capability advertisement. - -After those gates complete, #1105 serializes the one immutable build, bounded +are the live trackers. Board clarity #1063 is complete through merged PR #1109, +and audit-history hardening #1104 is complete through merged PR #1107. Hosted +PR #542 merged the compatible consumer source at +`bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`, without establishing production +acceptance. One entry gate remains before the immutable candidate: + +1. [CodeMower.com #978](https://github.com/codemower-ai/code-mower/issues/978) + — complete the production migration and deployment, advertise the exact + accepting capability, and finish hosted validation, tenant isolation, + retention, export/deletion, aggregate-count, and freshness evidence. + +After that gate completes, #1105 serializes the one immutable build, bounded private Slack canary, local-versus-hosted reconciliation, 24-hour soak, two independent installation passes, exact-head release audits, publication, and canonical reinstall. None of those observations is claimed by this source diff --git a/docs/docs-manifest.yml b/docs/docs-manifest.yml index 1c63a75a..9586876b 100644 --- a/docs/docs-manifest.yml +++ b/docs/docs-manifest.yml @@ -364,10 +364,10 @@ documents: sha256: 7a7492e3e297d920dbb76b7c25339f3865d545c108a0114e65b1a3d989ad3bcd - path: docs/v160-qualification.md status: frozen - sha256: 2ba9ce90304b11141e8eb101990a4246eb86c82242884b8ff035e31ea0a78d42 + sha256: 9232a7fe13306955e4bb7b92889f4ceb71cc585f5ed30c3f91be2e189980a93f - path: docs/v160-release-notes.md status: frozen - sha256: f67192691b6859f8d2ab02dda23fc354c7580bb9fdd16b4badcb7c498d99a8c8 + sha256: 052c5d9cdd126816c120f0c2090a3770174daa89e3cc0dc9301a7cf1775ebd81 - path: docs/v160-release-runbook.md status: frozen - sha256: e66f9a030c5eef660a9b1231e316391d17e2be8fa2ca38586cb272afc1111791 + sha256: 2333dd92ef5c8ad0ea7bb87d9d08f6de340b5d37cb51ece15ea97e0e15f45d57 diff --git a/docs/public-release-checklist.md b/docs/public-release-checklist.md index 01a38fdc..859a717b 100644 --- a/docs/public-release-checklist.md +++ b/docs/public-release-checklist.md @@ -144,8 +144,11 @@ 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.6.0 that head must include -#1063 and #1104, and CodeMower.com #978 must advertise the exact accepted -telemetry contract before the immutable candidate is built. Follow the +#1063/#1109 and #1104/#1107. Hosted PR #542 at +`bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88` must be migrated and deployed in +production, and CodeMower.com #978 must advertise the exact accepted telemetry +contract with complete hosted acceptance evidence before the immutable +candidate is built. 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 diff --git a/docs/pypi-release.md b/docs/pypi-release.md index a8d69670..887203fe 100644 --- a/docs/pypi-release.md +++ b/docs/pypi-release.md @@ -56,8 +56,11 @@ package. Never rewrite a published tag to correct the wording. 5. Obtain independent review on the exact final preparation PR head, green CI, and the authoritative Code Mower gate before merge. For v1.6.0, require - #1063 and #1104 on that head, plus an exact accepted-contract health response - from the deployed CodeMower.com #978 consumer. Bind the actual merge SHA and + #1063/#1109 and #1104/#1107 on that head, plus an exact accepted-contract + health response and complete hosted acceptance evidence from the production + CodeMower.com #978 deployment. Hosted PR #542 at + `bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88` is an implementation prerequisite, + not deployment evidence. Bind the actual merge SHA and build the candidate once. Complete #1105's bounded private Slack telemetry canary, local-versus-hosted reconciliation, 24-hour soak, two independent installation passes, and exact-candidate audits before the owner release diff --git a/docs/v160-qualification.md b/docs/v160-qualification.md index 820e0263..4a668c3d 100644 --- a/docs/v160-qualification.md +++ b/docs/v160-qualification.md @@ -8,13 +8,15 @@ or operational transcripts. ## Entry gates Candidate construction is refused until the final release commit contains all -of the following and each dependency has its own accepted exact-head evidence: +completed source prerequisites and the remaining hosted gate has accepted +production evidence: | Gate | Required state | | --- | --- | -| Audit history | #1104 merged: bounded payload-aware comment pagination and exact recognition of reserved lineage controls | -| Hosted consumer | CodeMower.com #978 deployed before client emission; `/api/health` advertises contract commit `99b657ae9822a689b46d21c41695a4cfb28a177d` and fixture-manifest SHA-256 `9e87c52812a49a1c72d0e0d2448661a3ef17cb8539ca8e029c6669ea9738d62e` | -| Source scope | #1063/#1109, #1064/#1099, #1082/#1100/#1103, #1083/#1097, #1084/#1102, #921/#1098, and #1106/#1108 are ancestors of the release commit | +| Audit history (complete) | #1104/#1107 merged: all audit consumers use bounded payload-aware pagination with stable terminal-history proof, and recognize lineage controls only as exact standalone HTML comments outside fenced Markdown | +| Hosted implementation (complete) | Hosted PR #542 merged at `bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`; this source merge is not production deployment or acceptance evidence | +| Hosted production acceptance (open) | CodeMower.com #978 completes migration and deployment before client emission; authenticated `/api/health` advertises contract commit `99b657ae9822a689b46d21c41695a4cfb28a177d` and fixture-manifest SHA-256 `9e87c52812a49a1c72d0e0d2448661a3ef17cb8539ca8e029c6669ea9738d62e`; hosted acceptance evidence is complete | +| Source scope | #1063/#1109, #1064/#1099, #1082/#1100/#1103, #1083/#1097, #1084/#1102, #921/#1098, #1106/#1108, and #1104/#1107 are ancestors of the release commit | | Release text | The v1.6 epic, release notes, roadmap, package identity, and current documentation agree on final scope | The first immutable-candidate workflow run before all entry gates hold is diff --git a/docs/v160-release-notes.md b/docs/v160-release-notes.md index a9f448ac..b786b9db 100644 --- a/docs/v160-release-notes.md +++ b/docs/v160-release-notes.md @@ -52,21 +52,29 @@ service. subscription access, elapsed time, token counts, source, diffs, prompts, transcripts, issue bodies, raw output, credentials, or local paths (#1106 / #1108). +- **Audit-comment history is bounded and complete.** Local audits, hosted + labelers, lane status, gate health, and the generated gate share one + payload-aware reader. It reduces page size and restarts safely, proves a + stable terminal page, and fails closed on oversized items, exhausted budgets, + omissions, duplicate or changed IDs, and incomplete history. Lineage controls + are recognized only as exact standalone HTML comments outside fenced + Markdown; explanatory prose is ignored while malformed controls from trusted + authorities remain fail-closed (#1104 / #1107). ## Release entry boundary -The Board implementation gate is complete through #1063 / #1109. This source -preparation does not establish release acceptance. Issue #1105 may build the -one immutable candidate only after both remaining entry gates are complete: +The Board and audit-history prerequisites are complete through #1063 / #1109 +and #1104 / #1107. Hosted PR #542 merged the compatible consumer implementation +at `bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`; that merge alone does not +establish production acceptance. Issue #1105 may build the one immutable +candidate only after CodeMower.com #978 completes the production migration and +deployment, the authenticated health response advertises the exact accepted OSS +contract identity, and the hosted acceptance evidence is complete. -1. #1104 merges payload-aware audit-comment ingestion and reserved lineage - control parsing. -2. CodeMower.com #978 deploys the backward-compatible consumer and advertises - the exact accepted OSS contract identity. - -After those gates merge, the retained candidate still needs the bounded private -Slack canary, local-versus-hosted reconciliation, privacy and tenant checks, -clean install, v1.5.2 upgrade, rollback, Graphify and Board rehearsals, a +After the hosted entry gate completes, the retained candidate still needs the +bounded private Slack canary, local-versus-hosted reconciliation, privacy and +tenant checks, clean install, v1.5.2 upgrade, rollback, Graphify and Board +rehearsals, a 24-hour soak, two independent installation passes, exact-head release audits, publication, and canonical reinstall. Observed results belong on #1105 and the GitHub Release, not in this source document. diff --git a/docs/v160-release-runbook.md b/docs/v160-release-runbook.md index a87582d5..309236a6 100644 --- a/docs/v160-release-runbook.md +++ b/docs/v160-release-runbook.md @@ -6,12 +6,15 @@ the release PR merge SHA after every entry gate is complete. ## 0. Prove the release entry gates -Board clarity #1063 / #1109 must be an ancestor of the prospective release -head. Do not dispatch the candidate workflow until #1104 is also merged and the -CodeMower.com #978 deployment advertises the exact accepted contract identity -from the [qualification contract](v160-qualification.md). Confirm each merge, -inspect the authenticated hosted health response, and record only public -deployment and contract identifiers. +Board clarity #1063 / #1109 and audit-history hardening #1104 / #1107 must be +ancestors of the prospective release head. Hosted PR #542 merged at +`bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`, but that source merge does not +satisfy the release entry gate. Do not dispatch the candidate workflow until +CodeMower.com #978 completes the production migration and deployment, the +authenticated health response advertises the exact accepted contract identity +from the [qualification contract](v160-qualification.md), and the hosted +acceptance evidence is complete. Record only public deployment and contract +identifiers. Production client emission remains disabled during this check. A health response that is unauthenticated, stale, missing either identity, or names another digest diff --git a/tests/test_release_contract.py b/tests/test_release_contract.py index b4432433..9533a71d 100644 --- a/tests/test_release_contract.py +++ b/tests/test_release_contract.py @@ -489,7 +489,9 @@ def test_patch_release_contract_keeps_scope_and_observations_explicit(self): ): with self.subTest(marker=marker): self.assertIn(marker, runbook) - self.assertIn("CodeMower.com #978 deployed", qualification) + self.assertIn("Hosted production acceptance (open)", qualification) + self.assertIn("bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88", qualification) + self.assertIn("CodeMower.com #978 completes migration and deployment", qualification) self.assertIn("local Board state reconciles", qualification) def test_later_versions_do_not_revert_to_building_at_publication(self): From de4a90f0ade9614957e1678307452eece0f566a2 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Mon, 21 Sep 2026 19:09:28 -0700 Subject: [PATCH 4/5] Finalize v1.6 release entry contract --- docs/current-state-and-roadmap.md | 29 ++++++++++++++-------------- docs/docs-manifest.yml | 6 +++--- docs/public-release-checklist.md | 11 ++++++----- docs/pypi-release.md | 11 ++++++----- docs/v160-qualification.md | 10 +++++----- docs/v160-release-notes.md | 32 ++++++++++++++++++++----------- docs/v160-release-runbook.md | 27 ++++++++++++++------------ release.yml | 13 ++++++++++++- tests/test_release_contract.py | 25 +++++++++++++++++++++--- 9 files changed, 105 insertions(+), 59 deletions(-) diff --git a/docs/current-state-and-roadmap.md b/docs/current-state-and-roadmap.md index cdc3ba06..991c9724 100644 --- a/docs/current-state-and-roadmap.md +++ b/docs/current-state-and-roadmap.md @@ -164,22 +164,23 @@ accepted contract identity. The [v1.6.0 milestone](https://github.com/codemower-ai/code-mower/milestone/2), [epic #1066](https://github.com/codemower-ai/code-mower/issues/1066), and [release issue #1105](https://github.com/codemower-ai/code-mower/issues/1105) -are the live trackers. Board clarity #1063 is complete through merged PR #1109, -and audit-history hardening #1104 is complete through merged PR #1107. Hosted -PR #542 merged the compatible consumer source at -`bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`, without establishing production -acceptance. One entry gate remains before the immutable candidate: - -1. [CodeMower.com #978](https://github.com/codemower-ai/code-mower/issues/978) - — complete the production migration and deployment, advertise the exact - accepting capability, and finish hosted validation, tenant isolation, - retention, export/deletion, aggregate-count, and freshness evidence. - -After that gate completes, #1105 serializes the one immutable build, bounded +are the live trackers. All immutable-candidate entry gates are complete. Board +clarity #1063 merged through PR #1109, and audit-history hardening #1104 merged +through PR #1107. Hosted PR #542 merged at +`bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`; production deployment +`6581697672` succeeded in two steps; production deployment +`dpl_HxhK4CHrYPkCCzjxS9rGjSuBG8C8` is Ready; authenticated health advertises the +exact accepting contract; the migration ledger is 79/79; and one sanitized +probe was accepted exactly once and isolated to `jeff-internal`. Public evidence +is recorded in +[#978 comment 5770184083](https://github.com/codemower-ai/code-mower/issues/978#issuecomment-5770184083). + +After the release PR merges, #1105 serializes the one immutable build, bounded private Slack canary, local-versus-hosted reconciliation, 24-hour soak, two independent installation passes, exact-head release audits, publication, and -canonical reinstall. None of those observations is claimed by this source -preparation. +canonical reinstall. #978 remains open until the retained candidate completes +the canary and local-versus-hosted aggregate reconciliation. None of those +post-merge observations is claimed by this source preparation. ## Near-Term Roadmap diff --git a/docs/docs-manifest.yml b/docs/docs-manifest.yml index 9586876b..4c20db9d 100644 --- a/docs/docs-manifest.yml +++ b/docs/docs-manifest.yml @@ -364,10 +364,10 @@ documents: sha256: 7a7492e3e297d920dbb76b7c25339f3865d545c108a0114e65b1a3d989ad3bcd - path: docs/v160-qualification.md status: frozen - sha256: 9232a7fe13306955e4bb7b92889f4ceb71cc585f5ed30c3f91be2e189980a93f + sha256: 39e99c1a27aaf2405e9280e3ca7bc1c9465ad3a5965c7e71e39dc558a011173d - path: docs/v160-release-notes.md status: frozen - sha256: 052c5d9cdd126816c120f0c2090a3770174daa89e3cc0dc9301a7cf1775ebd81 + sha256: 07bc43f511070dc4da114040f4e8f58a33cc639cb7aa3950c0508d14f0cfac59 - path: docs/v160-release-runbook.md status: frozen - sha256: 2333dd92ef5c8ad0ea7bb87d9d08f6de340b5d37cb51ece15ea97e0e15f45d57 + sha256: a14703176f7d2bc009ff4285eaad72f452f636c93198089cca62f10944d25e76 diff --git a/docs/public-release-checklist.md b/docs/public-release-checklist.md index 859a717b..8c1927ca 100644 --- a/docs/public-release-checklist.md +++ b/docs/public-release-checklist.md @@ -144,11 +144,12 @@ 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.6.0 that head must include -#1063/#1109 and #1104/#1107. Hosted PR #542 at -`bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88` must be migrated and deployed in -production, and CodeMower.com #978 must advertise the exact accepted telemetry -contract with complete hosted acceptance evidence before the immutable -candidate is built. Follow the +#1063/#1109 and #1104/#1107. Recheck the completed hosted entry evidence before +building: PR #542 at `bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`, GitHub +deployment `6581697672`, Ready production deployment +`dpl_HxhK4CHrYPkCCzjxS9rGjSuBG8C8`, and the exact accepting capability and +tenant-isolated acceptance probe recorded in #978 comment `5770184083`. A +changed schema, version, digest, or accepting state reopens the gate. 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 diff --git a/docs/pypi-release.md b/docs/pypi-release.md index 887203fe..016d1482 100644 --- a/docs/pypi-release.md +++ b/docs/pypi-release.md @@ -56,11 +56,12 @@ package. Never rewrite a published tag to correct the wording. 5. Obtain independent review on the exact final preparation PR head, green CI, and the authoritative Code Mower gate before merge. For v1.6.0, require - #1063/#1109 and #1104/#1107 on that head, plus an exact accepted-contract - health response and complete hosted acceptance evidence from the production - CodeMower.com #978 deployment. Hosted PR #542 at - `bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88` is an implementation prerequisite, - not deployment evidence. Bind the actual merge SHA and + #1063/#1109 and #1104/#1107 on that head. Recheck the completed hosted entry + evidence: PR #542 at `bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`, + GitHub deployment `6581697672`, Ready production deployment + `dpl_HxhK4CHrYPkCCzjxS9rGjSuBG8C8`, and the exact accepting capability and + tenant-isolated acceptance probe recorded in #978 comment `5770184083`. Any + changed schema, version, digest, or accepting state reopens the gate. Bind the actual merge SHA and build the candidate once. Complete #1105's bounded private Slack telemetry canary, local-versus-hosted reconciliation, 24-hour soak, two independent installation passes, and exact-candidate audits before the owner release diff --git a/docs/v160-qualification.md b/docs/v160-qualification.md index 4a668c3d..79fb7140 100644 --- a/docs/v160-qualification.md +++ b/docs/v160-qualification.md @@ -8,14 +8,14 @@ or operational transcripts. ## Entry gates Candidate construction is refused until the final release commit contains all -completed source prerequisites and the remaining hosted gate has accepted -production evidence: +completed source prerequisites and the hosted entry evidence below remains +valid: | Gate | Required state | | --- | --- | | Audit history (complete) | #1104/#1107 merged: all audit consumers use bounded payload-aware pagination with stable terminal-history proof, and recognize lineage controls only as exact standalone HTML comments outside fenced Markdown | -| Hosted implementation (complete) | Hosted PR #542 merged at `bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`; this source merge is not production deployment or acceptance evidence | -| Hosted production acceptance (open) | CodeMower.com #978 completes migration and deployment before client emission; authenticated `/api/health` advertises contract commit `99b657ae9822a689b46d21c41695a4cfb28a177d` and fixture-manifest SHA-256 `9e87c52812a49a1c72d0e0d2448661a3ef17cb8539ca8e029c6669ea9738d62e`; hosted acceptance evidence is complete | +| Hosted implementation (complete) | Hosted PR #542 merged at `bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88` with exact-head Claude audit, Code Mower gate, and full CI passing | +| Hosted production acceptance (complete) | GitHub deployment `6581697672` succeeded in two steps and production deployment `dpl_HxhK4CHrYPkCCzjxS9rGjSuBG8C8` is Ready; authenticated `/api/health` advertises capability schema `code_mower.controlSurfaceSessionSummaryCapability.v1`, summary schema `code_mower.controlSurfaceSessionSummary.v1`, capability version `1`, fixture-manifest SHA-256 `9e87c52812a49a1c72d0e0d2448661a3ef17cb8539ca8e029c6669ea9738d62e`, and `accepting: true`; closed-first `accepting: false` was verified; the migration ledger is 79/79 with digest `dec1a7338629e50e9edc5a927295736e4d74563775dd9af29bd38f8d3a234cdd`; sanitized probe upload `650c9bb7-2d51-4b1e-877a-7f2bdf54c174` was accepted exactly once and isolated to `jeff-internal`; public evidence is #978 comment `5770184083` | | Source scope | #1063/#1109, #1064/#1099, #1082/#1100/#1103, #1083/#1097, #1084/#1102, #921/#1098, #1106/#1108, and #1104/#1107 are ancestors of the release commit | | Release text | The v1.6 epic, release notes, roadmap, package identity, and current documentation agree on final scope | @@ -29,7 +29,7 @@ invalid evidence and must not be reused. | Release source | The release PR's actual `mergeCommit.oid` after exact-head review, complete CI, and the 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.6.0-py3-none-any.whl`, `code_mower-1.6.0.tar.gz`, `candidate.json`, and `rehearsal.json` | -| Telemetry contract | OSS contract commit and fixture-manifest digest named in the entry-gate table, accepted byte-for-byte by the deployed consumer | +| Telemetry contract | Capability schema, summary schema, version, and fixture-manifest digest named in the entry-gate table, accepted byte-for-byte by the deployed consumer | | Publication | Annotated `v1.6.0` 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 | diff --git a/docs/v160-release-notes.md b/docs/v160-release-notes.md index b786b9db..9849d522 100644 --- a/docs/v160-release-notes.md +++ b/docs/v160-release-notes.md @@ -63,18 +63,28 @@ service. ## Release entry boundary -The Board and audit-history prerequisites are complete through #1063 / #1109 -and #1104 / #1107. Hosted PR #542 merged the compatible consumer implementation -at `bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`; that merge alone does not -establish production acceptance. Issue #1105 may build the one immutable -candidate only after CodeMower.com #978 completes the production migration and -deployment, the authenticated health response advertises the exact accepted OSS -contract identity, and the hosted acceptance evidence is complete. +All release entry prerequisites are complete. Board and audit-history work +merged through #1063 / #1109 and #1104 / #1107. Hosted PR #542 merged at +`bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`, and production deployment +`6581697672` completed its two-step rollout and production deployment +`dpl_HxhK4CHrYPkCCzjxS9rGjSuBG8C8` is Ready. The authenticated health response +advertises capability schema +`code_mower.controlSurfaceSessionSummaryCapability.v1`, summary schema +`code_mower.controlSurfaceSessionSummary.v1`, capability version `1`, fixture +manifest SHA-256 +`9e87c52812a49a1c72d0e0d2448661a3ef17cb8539ca8e029c6669ea9738d62e`, +and `accepting: true`. The 79/79 migration ledger digest is +`dec1a7338629e50e9edc5a927295736e4d74563775dd9af29bd38f8d3a234cdd`. +A sanitized metadata-only probe was accepted exactly once as upload +`650c9bb7-2d51-4b1e-877a-7f2bdf54c174` and remained visible only in its +`jeff-internal` tenant projection. Public evidence is #978 comment +`5770184083`. -After the hosted entry gate completes, the retained candidate still needs the -bounded private Slack canary, local-versus-hosted reconciliation, privacy and -tenant checks, clean install, v1.5.2 upgrade, rollback, Graphify and Board -rehearsals, a +After this release PR merges, #1105 may build the one immutable candidate. The +hosted acceptance probe does not replace the retained-candidate Slack canary; +that candidate still needs the bounded private Slack canary, +local-versus-hosted reconciliation, privacy and tenant checks, clean install, +v1.5.2 upgrade, rollback, Graphify and Board rehearsals, a 24-hour soak, two independent installation passes, exact-head release audits, publication, and canonical reinstall. Observed results belong on #1105 and the GitHub Release, not in this source document. diff --git a/docs/v160-release-runbook.md b/docs/v160-release-runbook.md index 309236a6..2d44faec 100644 --- a/docs/v160-release-runbook.md +++ b/docs/v160-release-runbook.md @@ -6,19 +6,22 @@ the release PR merge SHA after every entry gate is complete. ## 0. Prove the release entry gates -Board clarity #1063 / #1109 and audit-history hardening #1104 / #1107 must be +Board clarity #1063 / #1109 and audit-history hardening #1104 / #1107 are ancestors of the prospective release head. Hosted PR #542 merged at -`bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`, but that source merge does not -satisfy the release entry gate. Do not dispatch the candidate workflow until -CodeMower.com #978 completes the production migration and deployment, the -authenticated health response advertises the exact accepted contract identity -from the [qualification contract](v160-qualification.md), and the hosted -acceptance evidence is complete. Record only public deployment and contract -identifiers. - -Production client emission remains disabled during this check. A health response -that is unauthenticated, stale, missing either identity, or names another digest -does not satisfy the gate. +`bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88`; production deployment +`6581697672` succeeded in two steps; production deployment +`dpl_HxhK4CHrYPkCCzjxS9rGjSuBG8C8` is Ready; and authenticated `/api/health` +advertised the exact accepted contract identity: capability schema, summary +schema, version, fixture-manifest digest, and `accepting: true` required by the +[qualification contract](v160-qualification.md). The hosted receipt also binds +the 79/79 migration ledger and one exactly-once tenant-isolated sanitized probe. +The public record is #978 comment `5770184083`. + +Recheck the authenticated health response immediately before candidate +dispatch. A stale or unauthenticated response, `accepting: false`, missing +identity, or different schema, version, or digest reopens the entry gate. The +hosted service accepting the contract and its sanitized acceptance probe do not +claim that a v1.6 candidate has emitted or reconciled telemetry. ## 1. Review and merge the release PR diff --git a/release.yml b/release.yml index 6260db98..10d0e053 100644 --- a/release.yml +++ b/release.yml @@ -4,7 +4,7 @@ previous_version: 1.5.2 previous_wheel_sha256: 44c7082b1fec2983d85ac7ce7feb7cf54b2b7d26e020e073716614d480118147 tag: v1.6.0 package_spec: code-mower==1.6.0 -stage: candidate +stage: stable python: minimum: "3.12" tested: @@ -18,6 +18,17 @@ documents: installation: docs/install.md publication: docs/pypi-release.md required_modules: + - audit_labeler_lib.py + - builder_lineage.py + - board.py + - board_service.py + - lane_status.py + - doctor.py + - doctor_checks/share_safe.py + - cloud_client/operations.py + - control_surface_summary.py + - control_surface_session_summary.schema.json + - control_surface_session_summary.fixture-manifest.json - context_graph_lifecycle.py - context_graph_query.py - context_graph_command.py diff --git a/tests/test_release_contract.py b/tests/test_release_contract.py index 9533a71d..f68fae49 100644 --- a/tests/test_release_contract.py +++ b/tests/test_release_contract.py @@ -460,7 +460,22 @@ def test_mismatched_tag_or_package_spec_is_rejected(self): class ReleaseContractTests(unittest.TestCase): def test_identity_and_readiness(self): self.assertEqual(__version__, "1.6.0") - self.assertEqual(release_metadata.load_release_metadata(ROOT).version, __version__) + metadata = release_metadata.load_release_metadata(ROOT) + self.assertEqual(metadata.version, __version__) + self.assertEqual(metadata.stage, "stable") + self.assertTrue( + { + "audit_labeler_lib.py", + "builder_lineage.py", + "board.py", + "board_service.py", + "doctor_checks/share_safe.py", + "cloud_client/operations.py", + "control_surface_summary.py", + "control_surface_session_summary.schema.json", + "control_surface_session_summary.fixture-manifest.json", + }.issubset(metadata.required_modules) + ) self.assertEqual(release_renderer.render(ROOT, check=True), []) self.assertEqual(release_readiness.render_release_readiness(ROOT)["status"], "pass") @@ -489,9 +504,13 @@ def test_patch_release_contract_keeps_scope_and_observations_explicit(self): ): with self.subTest(marker=marker): self.assertIn(marker, runbook) - self.assertIn("Hosted production acceptance (open)", qualification) + self.assertIn("Hosted production acceptance (complete)", qualification) self.assertIn("bcddaa25c633f2dcf8fa2077d6ecb8004c1d8f88", qualification) - self.assertIn("CodeMower.com #978 completes migration and deployment", qualification) + self.assertIn("GitHub deployment `6581697672` succeeded", qualification) + self.assertIn("dpl_HxhK4CHrYPkCCzjxS9rGjSuBG8C8", qualification) + self.assertIn("code_mower.controlSurfaceSessionSummaryCapability.v1", qualification) + self.assertIn("`accepting: true`", qualification) + self.assertIn("650c9bb7-2d51-4b1e-877a-7f2bdf54c174", qualification) self.assertIn("local Board state reconciles", qualification) def test_later_versions_do_not_revert_to_building_at_publication(self): From 98eea9a793cd3e2739175016693d2e447b852ac8 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Mon, 21 Sep 2026 19:16:42 -0700 Subject: [PATCH 5/5] Bind v1.6 readiness to candidate qualification --- src/code_mower/release_readiness.py | 2 +- tests/test_release_contract.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/code_mower/release_readiness.py b/src/code_mower/release_readiness.py index 935e03d3..33164ebd 100644 --- a/src/code_mower/release_readiness.py +++ b/src/code_mower/release_readiness.py @@ -1529,7 +1529,7 @@ def _candidate_runbook_checks(repo_path: Path) -> tuple[list[str], list[str]]: ) release_assertions = ( "exact accepted contract identity", "24 hours", - "two independent installation", "capability-gated telemetry", + "two independent installation", "capability-gated lifecycle-summary", ) else: order = ( diff --git a/tests/test_release_contract.py b/tests/test_release_contract.py index f68fae49..dce78349 100644 --- a/tests/test_release_contract.py +++ b/tests/test_release_contract.py @@ -481,9 +481,15 @@ def test_identity_and_readiness(self): def test_removing_exact_candidate_qualification_or_moving_tag_first_blocks(self): text = (ROOT / "docs/v160-release-runbook.md").read_text() - for bad in (text.replace("## 3. Qualify the exact candidate", "## Removed qualification"), - text.replace('git tag -a v1.6.0 "$RELEASE_SHA"', "tag removed"), - text.replace("aggregate campaign ACU", "unspecified budget")): + for bad in ( + text.replace("## 3. Qualify the exact candidate", "## Removed qualification"), + text.replace('git tag -a v1.6.0 "$RELEASE_SHA"', "tag removed"), + text.replace("aggregate campaign ACU", "unspecified budget"), + text.replace( + "capability-gated lifecycle-summary", + "unspecified capability qualification", + ), + ): 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)