# PR9 — Website versioning integration

Incremental, additive integration layer that lets the existing website serve both
protocol versions honestly. No redesign, no route changes, no deployment.

## What this changes

| Concern | Before | After |
| --- | --- | --- |
| V2 addresses on the website | Hand-maintained literals in the nested site (`app/lib/contracts.ts`, 134 entries) | Generated from `baseline/deployments.json` (`generated/website-addresses.generated.ts`) |
| V3 exposure | Implicit / undefined | Explicit flags, all default-off (`src/version-flags.ts`) |
| Version honesty in UI | Ad-hoc copy | Fixed truth-label vocabulary (`src/truth-labels.ts`) |
| V3 money movement | Undefined | Fail-closed gate requiring explicit selection + deployment + status + pilot evidence (`src/v3-action-gate.ts`) |

V2 behavior, V2 routes, and the existing visual design are untouched. This
package adds constants, flags, labels, and a gate; it renders nothing.

## Layout

```
pr9/website/
  scripts/generate_website_addresses.py     generator + --check drift gate
  generated/website-addresses.generated.ts  generated constants (committed)
  src/version-flags.ts                      default-off V3 feature/write flags
  src/truth-labels.ts                       the five allowed truth labels
  src/v3-action-gate.ts                     fail-closed versioned action gate
  src/v2-legacy-adapter.ts                  drop-in replacement for the site's V2 map
  src/index.ts                              import surface for the website
  tests/                                    vitest suite (36 tests)
```

## Commands

```bash
python3 pr9/website/scripts/generate_website_addresses.py          # regenerate
python3 pr9/website/scripts/generate_website_addresses.py --check  # CI drift gate
npm --prefix pr9/website ci
npm --prefix pr9/website test
npm --prefix pr9/website run typecheck
```

## Address generation

`baseline/deployments.json` is the only source of truth for deployed addresses.
The generator projects it into a typed module and records the source SHA-256
(`DEPLOYMENTS_SHA256`) so a stale committed file fails `--check` and the
`address-generation` test rather than shipping drift.

`V3_ADDRESSES` is intentionally empty: no V3 deployment exists in the canonical
inventory, which is exactly why every V3 money-moving gate is currently blocked
with `v3-deployment-address-missing`.

## Flags (all default-off)

| Flag | Env key | Meaning |
| --- | --- | --- |
| `v3UiEnabled` | `NEXT_PUBLIC_VAULTFIRE_V3_UI` | Render V3 surfaces at all |
| `v3ReadsEnabled` | `NEXT_PUBLIC_VAULTFIRE_V3_READS` | Allow V3 reads |
| `v3WritesEnabled` | `VAULTFIRE_V3_WRITES` | Allow V3 state changes |
| `v3MoneyMovementEnabled` | `VAULTFIRE_V3_MONEY_MOVEMENT` | Allow V3 fund movement |
| `v3AuditedPilotEnabled` | `VAULTFIRE_V3_AUDITED_PILOT` | Assert audited-pilot envelope |

Only the exact string `"true"` enables a flag. `1`, `TRUE`, `yes`, `on` are read
as **off** and reported in `configErrors`. No flag cascades to another. No
environment value can change the default protocol version: `resolveActiveVersion`
returns `"v3"` only for an explicit in-session user selection **and** an enabled
UI flag.

## Truth labels

`Legacy V2`, `V3 test deployment`, `V3 audited pilot`, `Deprecated`, `Read-only`.

- V2 deployments are `Legacy V2`, or `Deprecated` when the canonical status says so.
- V3 surfaces are `V3 test deployment` unless the audited-pilot flag is on **and**
  a verified V3 deployment record exists; only then may they read `V3 audited pilot`.
- Any surface that cannot currently submit a state change also carries `Read-only`.

## Fail-closed gate

`evaluateVersionedAction(request, evidence)`:

- No V3 selection → V2 path, allowed, unchanged behavior, zero V3 dependencies.
- V3 selection → every gate must be affirmatively proven. Unknown evidence
  (`undefined`/`null` status report or pilot gate) is a blocker, not a pass.
- A blocked V3 intent stays reported as `version: "v3"` so the UI surfaces the
  failure instead of quietly executing the same money movement against V2.

Blocker codes: `v3-not-explicitly-selected`, `v3-not-acknowledged`,
`v3-ui-flag-off`, `v3-reads-flag-off`, `v3-writes-flag-off`,
`v3-money-movement-flag-off`, `v3-audited-pilot-flag-off`,
`v3-deployment-address-missing`, `v3-protocol-status-unavailable`,
`v3-protocol-status-degraded`, `v3-pilot-gate-unavailable`,
`v3-pilot-gate-not-ready`.

## Wiring into the nested website (follow-up, not done here)

The nested site (`v2-repo/repos/theloopbreaker-site`) is a vendored mirror and is
git-ignored in this repository, so it is not modified by this patch. The intended
one-file follow-up in that repo, which preserves the existing exports and design:

```ts
// app/lib/contracts.ts
import { getABI } from "./abis/abi-map";
import { legacyContractsForChain } from "@vaultfire/website-versioning";

export const BASE_CONTRACTS = legacyContractsForChain("base", getABI);
export const AVALANCHE_CONTRACTS = legacyContractsForChain("avalanche", getABI);
export const ARBITRUM_CONTRACTS = legacyContractsForChain("arbitrum", getABI);
export const POLYGON_CONTRACTS = legacyContractsForChain("polygon", getABI);
```

`tests/website-map-parity.spec.ts` already proves this swap is behavior-neutral:
it parses the site's current literals and asserts all 134 `(name, chain) → address`
pairs match the generated constants exactly. It resolves the site from
`VAULTFIRE_V2_SITE_ROOT`, `<repo>/v2-repo/repos/theloopbreaker-site`, or the
sibling canonical checkout, and skips when none is present.
