# v2.0 release notes draft

This file stages the v2.0 release notes for the lock ceremony.
Until the ceremony, it lives here so it does not collide with the
`## [X.Y.Z]` section check in `scripts/release.py`. At lock time,
the maintainer moves the section below into `CHANGELOG.md` as
`## [2.0.0] - YYYY-MM-DD`, populates the date, and runs the
release script.

The draft is inventory-style, not feature-list. v2.0 ships no
new behavior; it locks the surface that the v1.9.x bridge
sequence validated.

---

## [2.0.0] - YYYY-MM-DD (DRAFT — do not move into CHANGELOG.md until lock)

**v2.0 schema-lock-and-tag ceremony.** Locks the public surface
that the v1.9.x bridge sequence validated. No new features, no
new fingerprints, no schema additions beyond the experimental →
stable promotions listed below. Every surface in
`docs/stability.md` carries the **Stable** guarantee from this
tag forward: breaking it requires a major version bump and a
deprecation window in the prior minor release.

For the validation evidence behind the lock, see
`validation/v2.0-validation-summary.md`. For the corpus-firing
truth-of-record, see `validation/v2.0-corpus-run.md`. For the
security-audit closure trail, see
`docs/security-audit-resolutions.md`.

### Promoted from experimental to stable

Each surface below shipped pre-v2.0 with an `EXPERIMENTAL` label.
Shape is preserved unchanged across the promotion; only the
label is removed. See the schema-lock disposition table in
`docs/roadmap.md` for the per-surface rationale. The
introducing commit is linked so a reader can verify the surface
shipped where this table claims it did.

| Surface | Introduced | Introducing commit |
|---|---|---|
| `--fusion` CLI flag | v0.11 → v1.9.0 (fusion layer) | [`65cedb0`](https://github.com/blisspixel/recon/commit/65cedb0) |
| `--explain-dag` CLI flag | v1.9.0 | [`65cedb0`](https://github.com/blisspixel/recon/commit/65cedb0) |
| `slug_confidences` JSON field | v0.11 | [`890454b`](https://github.com/blisspixel/recon/commit/890454b) |
| `posterior_observations` JSON field | v1.9.0 | [`65cedb0`](https://github.com/blisspixel/recon/commit/65cedb0) |
| MCP `get_posteriors` tool | v1.9.0 | [`65cedb0`](https://github.com/blisspixel/recon/commit/65cedb0) |
| MCP `explain_dag` tool | v1.9.0 | [`65cedb0`](https://github.com/blisspixel/recon/commit/65cedb0) |
| `chain_motifs` JSON field | v1.7.0 | [`8bafe3f`](https://github.com/blisspixel/recon/commit/8bafe3f) |
| `wildcard_sibling_clusters` field (in `cert_summary`) | v1.7.0 | [`8bafe3f`](https://github.com/blisspixel/recon/commit/8bafe3f) |
| `deployment_bursts` field (in `cert_summary`) | v1.7.0 | [`8bafe3f`](https://github.com/blisspixel/recon/commit/8bafe3f) |
| `infrastructure_clusters` JSON field | v1.8.0 | [`3088b9a`](https://github.com/blisspixel/recon/commit/3088b9a) |
| `ecosystem_hyperedges` JSON field (batch wrapper) | v1.8.0 | [`3088b9a`](https://github.com/blisspixel/recon/commit/3088b9a) |
| `evidence_conflicts` JSON field | v1.7.0 | [`8bafe3f`](https://github.com/blisspixel/recon/commit/8bafe3f) |

### Schema lock

- `docs/recon-schema.json` description bumped from "Stable v1.0
  contract" to "Stable v2.0 contract." Field set, types, and
  semantics unchanged from v1.9.14 except for the
  experimental → stable promotions above.
- All ~45 stable JSON fields previously covered by the v1.0
  contract remain covered; the Bayesian fusion fields
  (`slug_confidences`, `posterior_observations`) and the v1.7 /
  v1.8 structural fields move into the stable set per the table
  above.
- The data-file schemas (`fingerprints`, `signals`, `profiles`,
  per-domain CT cache, TenantInfo result cache,
  `RECON_CONFIG_DIR` environment variable) remain stable as
  documented in `docs/stability.md`.

### MCP tools

All 22 MCP tools listed in `docs/stability.md` remain stable.
The two MCP fusion tools (`get_posteriors`, `explain_dag`) move
from experimental to stable per the promotion table above.

### CLI commands and flags

Every CLI command and flag listed as **Stable** in
`docs/stability.md` (as of v1.9.14) carries that guarantee
forward. The two CLI fusion flags (`--fusion`, `--explain-dag`)
move from experimental to stable per the promotion table above.

### Default behavior change: Bayesian fusion is now default-on

Through v1.9.x, the Bayesian inference layer was opt-in behind
`--fusion`. v2.0 flips the default. The math runs on every
lookup; the default panel keeps the same clean shape it has had
since v1.x; the credible interval surfaces in `--json` always
and in the panel only when it modifies the rule-based verdict.
Users who want the v1.x rule-based-only output can pass
`--no-fusion`.

| Behavior | v1.x | v2.0 |
|---|---|---|
| Bayesian inference computation | Opt-in via `--fusion` | Always-on |
| `posterior_observations` in `--json` | Only when `--fusion` set | Always present |
| `slug_confidences` in `--json` | Only when `--fusion` set | Always present |
| Credible interval in default panel | Not shown (panel has no fusion path) | Shown only when `sparse=true` on a reported node OR the posterior mode disagrees with the deterministic slug |
| Credible interval under `--verbose` | Shown when combined with `--fusion` | Always shown |
| Opt-out flag | n/a | `--no-fusion` (new; documented stable) |
| `--fusion` flag | Required to enable | Deprecated no-op, kept for v2.0.x back-compat, removed in v2.1 |
| `--explain-dag` | Opt-in, implies `--fusion` | Opt-in, unchanged |

The disclosure rule means the default panel is quiet when the
rule-based and Bayesian layers agree (the easy case, roughly 70
percent of corpus runs) and loud when they disagree (hardened
targets, sparse evidence, conflicting signals). v1.9.4
hardened-adversarial validation showed 64 percent sparse-flag
firing on hardened targets; that is the population the rule is
designed to escalate.

The change is additive to `--json`: every consumer that ignores
unknown fields continues to work without modification. SIEM
mappings that target the rule-based slugs (`docs/schema.md`
canonical field set) need no change. SIEM consumers who want
the v1.x rule-based-only payload can pass `--no-fusion`.

### Python support

Tested range: Python 3.10 through 3.13, unchanged from v1.9.14.

### Bayesian network

The v1.9.10 stability disposition table holds at v2.0. All 9
nodes are `stable`; `okta_idp` is kept with the corpus-exposure
caveat recorded in `recon_tool/data/bayesian_network.yaml`. No
topology change, no CPT change since v1.9.6.

### Security

No new security work in v2.0. The May-2026 audit cycle closed
through v1.9.4, v1.9.9, v1.9.13, and v1.9.14 per
`docs/security-audit-resolutions.md`. Scanner re-flags against
pre-closure commits are resolved in favor of the closure record;
this is the project's authoritative disposition policy.

| Topic | Severity | Closed in | Closing commit |
|---|---|---|---|
| MCP doctor/install can execute shadowed `recon_tool` package | High | v1.9.9 | [v1.9.9 tag](https://github.com/blisspixel/recon/releases/tag/v1.9.9) |
| A/AAAA CNAME validation can trigger internal DNS lookups | Medium | v1.9.4 (initial) | [`6abbae1`](https://github.com/blisspixel/recon/commit/6abbae1) |
| Same finding, re-introduced by v1.9.13 terminus check | Medium | v1.9.14 (re-closure on type-dependent-answer path) | [`2eed239`](https://github.com/blisspixel/recon/commit/2eed239) |
| CNAME chain walking can query and leak internal DNS names | Medium | Mitigated v1.9.3.5 / v1.9.4 / v1.9.13 / v1.9.14 (residual documented) | [`f8b12dd`](https://github.com/blisspixel/recon/commit/f8b12dd), [`6abbae1`](https://github.com/blisspixel/recon/commit/6abbae1), [`bbf68e7`](https://github.com/blisspixel/recon/commit/bbf68e7), [`2eed239`](https://github.com/blisspixel/recon/commit/2eed239) |
| Splunk SIEM example uses unescaped regex slugs | Informational | v1.9.4 | [`6abbae1`](https://github.com/blisspixel/recon/commit/6abbae1) |

### Pre-v2.0 release sequence (validation evidence)

The v1.9.x bridge sequence cleared the per-release gates in
`docs/roadmap.md`'s release table. Each row below lists the
release, the gate cleared, the introducing commit, and the
validation memo a reviewer can read to verify the work landed
as described.

| Release | Gate cleared | Tag | Memo |
|---|---|---|---|
| v1.9.3 | Bayesian-network topology surgery | [v1.9.3](https://github.com/blisspixel/recon/releases/tag/v1.9.3) | `validation/v1.9.3-calibration.md` |
| v1.9.4 | Hardened-adversarial validation across 50-domain stratified corpus | [v1.9.4](https://github.com/blisspixel/recon/releases/tag/v1.9.4) | `validation/v1.9.4-calibration.md` |
| v1.9.5 | Per-node stability dispositions (8 of 9 stable; `okta_idp` `not yet`) | [v1.9.5](https://github.com/blisspixel/recon/releases/tag/v1.9.5) | `validation/v1.9.5-stability.md` |
| v1.9.6 | CPT-change discipline + audit-finding closures | [v1.9.6](https://github.com/blisspixel/recon/releases/tag/v1.9.6) | `validation/v1.9.6-stability-update.md` |
| v1.9.7 | Metadata-coverage gate flip + 298-detection backfill | [v1.9.7](https://github.com/blisspixel/recon/releases/tag/v1.9.7) | `CHANGELOG.md` v1.9.7 |
| v1.9.8 | Catalog metadata richness on three signals | [v1.9.8](https://github.com/blisspixel/recon/releases/tag/v1.9.8) | `validation/v1.9.8-metadata-audit.md` |
| v1.9.9 | Detection-gap UX surfaces; MCP shadow-load closure on Python 3.10 | [v1.9.9](https://github.com/blisspixel/recon/releases/tag/v1.9.9) | `validation/v1.9.9-detection-gap-ux.md` |
| v1.9.10 | Stratified-corpus pre-lock validation; Bayesian re-validation | [v1.9.10](https://github.com/blisspixel/recon/releases/tag/v1.9.10) | `validation/v1.9.10-pre-lock.md` |
| v1.9.11 | Documentation polish + v2.0 prep worklist cleared | [v1.9.11](https://github.com/blisspixel/recon/releases/tag/v1.9.11) | `validation/v1.9.11-trend-table.md` |
| v1.9.12 | Panel-display polish + doctor schema-fields + Mermaid evidence-DAG | [v1.9.12](https://github.com/blisspixel/recon/releases/tag/v1.9.12) | `CHANGELOG.md` v1.9.12 |
| v1.9.13 | CNAME chain walker hardening (entry-point + redirect_domain filter) | [v1.9.13](https://github.com/blisspixel/recon/releases/tag/v1.9.13) | `docs/security-audit-resolutions.md` |
| v1.9.14 | Revert v1.9.13 terminus-only A/AAAA check after type-dependent-answer path re-opened v1.9.4 leak | [v1.9.14](https://github.com/blisspixel/recon/releases/tag/v1.9.14) | `validation/v2.0-corpus-run.md`, `validation/v2.0-validation-summary.md` |

The real-corpus validation gate
(`validation/v2.0-corpus-run.md`) was populated against v1.9.14
with 4264 counted domains; multi-cloud rollup fires on 37.2%,
the `okta_idp` Bayesian-network node clears the
criterion-(c) N≥10 threshold by an order of magnitude (134
strict slug firings, 543 with the lower-tier signal included),
and the ceiling-firing divergence vs synthetic corpus is
documented as structural (sparse synthetic fixtures vs rich
real apexes).

### Lock-ceremony checklist (maintainer)

- [x] `validation/v2.0-corpus-run.md` populated (no `<TO BE POPULATED>` strings remain). Done in commit `cca815c` from the v1.9.14 corpus scan.
- [ ] `docs/recon-schema.json` description bumped from "v1.0 contract" to "v2.0 contract."
- [ ] Bayesian-fusion default-on flip applied. Code changes:
      - Remove `if fusion or explain_dag:` gate at `recon_tool/cli.py:2374`; compute Bayesian inference unconditionally.
      - Add `--no-fusion` opt-out flag with typer `--fusion/--no-fusion` pair semantics (default True).
      - Gate panel-rendering of the credible interval in `recon_tool/formatter.py` on `--verbose` OR on the disagreement rule (`sparse=true` on a reported node, or posterior-mode/deterministic-slug disagreement).
      - Update `docs/stability.md` `--fusion` row to "Bayesian fusion control (default on v2.0+; `--no-fusion` opts out)" and document `--no-fusion` as a stable surface.
      - Update README example panel to show both the easy case (panel unchanged from v1.x) and the disagreement case (interval surfaces inline).
- [ ] This section moved from `validation/v2.0-release-notes-draft.md` into `CHANGELOG.md` under the next `## [2.0.0] - <date>` heading; date populated to the day the script runs.
- [ ] `scripts/release.py` from a clean `main` checkout. Script enforces ruff / pyright / pytest gates and prompts before tagging.
- [ ] After tag push, GHA `release.yml` runs test → build → publish-pypi (OIDC) → github-release.
- [ ] After PyPI publish completes, delete this draft file (`rm validation/v2.0-release-notes-draft.md`).

The script does the version bump, commit, tag, and push. The
GHA workflow does the PyPI upload via the existing OIDC
Trusted Publisher; no static tokens involved.
