# v2.0 lock ceremony

The exact, mechanical steps to cut 2.0.0 once the lock-time corpus run is in.
Everything substantive (catalog C1/C2, CAL14, schema hardening SH1-SH9, the
security-review fixes, and the F1/F2/G3 docs) is already shipped, so this file is
the only remaining recipe. Run it top to bottom; each step lists exact targets.

## Precondition: the lock-time corpus run (optional but recommended)

C3, the CT-enabled full-corpus calibration re-run, refreshes the cert-fed
surfaces and confirms CAL14 on real input. The nine Bayesian nodes are
DNS / identity-fed, so the existing passes already calibrate the layer; C3 is a
refresh, not a blocker. If run, repopulate `validation/v2.0-corpus-run.md` from
the runbook. The release workflow refuses to tag while that file contains
`<TO BE POPULATED>`; it is already populated from the v1.9.14 scan, so this gate
is green either way.

## G1: schema lock (description bump, label strip)

1. `docs/recon-schema.json` and `recon_tool/data/recon-schema.json` (keep
   byte-identical, mirror with `cp` then `diff -q`):
   - Root `description`: `Stable v1.0 contract` becomes `Stable v2.0 contract`.
   - Same description: `additive changes are non-breaking within 1.x` becomes
     `within 2.x`.
2. `README.md`: `v1.0 stability contract` becomes `v2.0 stability contract`.
3. EXPERIMENTAL labels: a sweep on 2026-06-05 found zero EXPERIMENTAL labels in
   user-facing surfaces (`recon_tool/`, the schema, `README.md`); they were
   stripped in v1.9.11 and the CI gate enforces zero. Confirm the gate is still
   green; no edits expected.
4. `recon doctor` first line: confirm it reads "v2.0 stable schema" once the
   schema description says v2.0 (the line is derived from the schema text). Spot
   check `recon doctor` output.
5. Gate: `tests/test_json_schema_file.py`, `tests/test_schema_disposition.py`,
   the full suite.

## G2: fusion default-on (behavior change, the substantive part of the lock)

This is the one real behavior change in 2.0, so it carries test updates and is
deliberately done at the lock, not earlier.

1. `recon_tool/cli.py`: the two `fusion: bool = typer.Option(False, "--fusion", ...)`
   defaults (single-domain command near line 293, batch command near line 416)
   become `typer.Option(True, "--fusion/--no-fusion", ...)`. Update the help text
   to say fusion runs by default and `--no-fusion` opts out.
2. Clean-panel disclosure rule: with fusion on by default, the panel must stay
   readable on sparse targets. Confirm the fusion panel section only renders when
   there is signal, and that a one-line note discloses fusion is on (see the G2
   row in `docs/roadmap.md` for the exact wording intent).
3. Test updates (measured 2026-06-05 by flipping the default and running the
   full suite): the flip breaks exactly one test,
   `tests/test_fusion_robustness.py::TestCLIFlagCombinations::test_fusion_off_emits_empty_posteriors`,
   which asserts that a lookup without `--fusion` emits empty fusion arrays. The
   other 2766 tests pass, because they drive the engine functions (`infer`,
   `format_tenant_json`) with explicit fusion arguments rather than the CLI
   default. So G2's test work is small and known: add `--no-fusion`, point that
   one test at `--no-fusion`, and add a test that the default path now emits
   `posterior_observations` and `fusion_enabled: true`.
4. `docs/`: README and any quickstart that says fusion is opt-in updates to
   "on by default, `--no-fusion` to disable".

## G4: changelog move, version bump, tag

1. Move the body of `validation/v2.0-release-notes-draft.md` into `CHANGELOG.md`
   as the `## [2.0.0] - <date>` entry (keep the draft file or delete it; the
   CHANGELOG becomes the source of record).
2. `docs/migration-v2.md`: remove the `Status: draft` banner; it ships populated.
3. Version bump 1.9.x to 2.0.0 in `pyproject.toml`, `recon_tool/__init__.py`
   (`_FALLBACK_VERSION`), and `uv.lock`. Version-bump-last: do this after G1 and
   G2 pass the full gate.
4. Run the local quality gate that mirrors CI (pytest, ruff, pyright,
   `scripts/validate_fingerprint.py`), then the real-apex guard.
5. Commit, push, watch CI green, then run the interactive `release.py` (or tag
   `v2.0.0` by hand) so `release.yml` publishes to PyPI.
6. `recon --version` prints `2.0.0`; `recon doctor` first line reads the v2.0
   schema line.

## What is NOT changing at the lock

Wire format (every v1.9.x JSON parses under 2.0), CLI command names, MCP tool
names, the default panel blocks, and `bayesian_network.yaml` topology. 2.0 is a
lock plus the fusion-default flip, not a feature release.
