# Assay v3.0.0 Release Runbook

**Target date**: 2026-03-05
**Release type**: major feature release (breaking)

## Scope

- Workspace/package version bump to `3.0.0`
- Changelog section for `v3.0.0`
- Publish Rust crates and Python package (`assay-it`) on the same release line

## Breaking API Notes (assay-core)

This release introduces intentional public API breakage in MCP policy/decision model structs.

- `assay_core::mcp::policy::ToolPolicy` adds:
  - `allow_classes: Option<Vec<String>>`
  - `deny_classes: Option<Vec<String>>`
- `assay_core::mcp::decision::DecisionData` adds:
  - `tool_classes: Vec<String>`
  - `matched_tool_classes: Vec<String>`
  - `match_basis: Option<String>`
  - `matched_rule: Option<String>`

Semver implication:

- External code constructing these structs via struct literals must add the new fields.
- This is why the release line is `v3.0.0` instead of `v2.19.0`.

Replay/evidence contract note:

- `assay_core::mcp::decision::ReplayDiffBasis` was later widened in the Wave39/Wave40 line with
  additive deny-convergence and replay-compat fields.
- That widening is treated as intentional public `v3` contract surface, not as an internal-only
  shape that will be shimmed retroactively in patch releases.
- External crates constructing `ReplayDiffBasis` directly must update alongside the `v3` line.

## Preflight

```bash
git fetch origin --tags
git checkout main
git pull --ff-only
```

Confirm release line:

```bash
rg -n '^version\s*=\s*"' Cargo.toml crates/*/Cargo.toml assay-python-sdk/Cargo.toml -g 'Cargo.toml'
git tag --sort=-creatordate | head -n 10
```

Expected before cut:

- previous tag visible: `v2.18.0`
- no mixed internal crate versions across release line

## Mandatory Release Gates

```bash
PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 cargo test --workspace
PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 cargo clippy --workspace --all-targets -- -D warnings
```

Smoke checks:

```bash
cargo run -p assay-cli -- coverage \
  --input scripts/ci/fixtures/coverage/input_basic.jsonl \
  --out /tmp/release-smoke-coverage.json \
  --out-md /tmp/release-smoke-coverage.md \
  --routes-top 5 \
  --declared-tool read_document \
  --declared-tool web_search \
  --declared-tool web_search_alt

PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 cargo test -p assay-cli mcp_wrap_coverage_cli_smoke_writes_report
```

## Rust Publish Order (crates.io)

If publish automation is not used, publish in dependency order.

```bash
# foundation
cargo publish -p assay-common
cargo publish -p assay-policy
cargo publish -p assay-monitor
cargo publish -p assay-evidence
cargo publish -p assay-adapter-api

# protocol adapters
cargo publish -p assay-adapter-acp
cargo publish -p assay-adapter-a2a
cargo publish -p assay-adapter-ucp

# runtime
cargo publish -p assay-core
cargo publish -p assay-metrics
cargo publish -p assay-sim
cargo publish -p assay-registry
cargo publish -p assay-mcp-server

# user-facing
cargo publish -p assay-cli
```

Post-publish check:

```bash
cargo search assay-core | head -n 3
cargo search assay-cli | head -n 3
```

## Python Publish (PyPI)

```bash
cd assay-python-sdk
python3 -m build
python3 -m twine check dist/*
python3 -m twine upload dist/*
cd -
```

Post-publish check:

```bash
python3 -m pip index versions assay-it | head -n 20
```

## Tag + GitHub Release

```bash
git tag -a v3.0.0 -m "Assay v3.0.0"
git push origin v3.0.0
```

Create GitHub release notes from `CHANGELOG.md` `v3.0.0` section.

## GitHub Action Marketplace v2 Check

For the marketplace listing, ensure the Action docs stay aligned with the released CLI behavior:

- `--coverage-out` and `--state-window-out` documented correctly
- `assay coverage --out-md` and `--routes-top` reflected where relevant
- no claims beyond shipped behavior on `main`

## Rollback / Abort

If any mandatory gate fails:

- do not publish any package
- fix on a release branch
- rerun mandatory gates and smoke checks

If partial publish occurred:

- do not retag
- cut `v3.0.1` with forward fix
- document partial state in release notes
