# Contract v3.3: changes from v3.2

*W-contract-v33, 2026-09-28 00:18 to 00:40 Beirut. Brief: `briefs/W-contract-v33.md`. Review: `~/.openclaw/wiki/coordination/inbox/vesper/2026-09-27-bolo-contract-v3-review.md`, section "B6 artifact-triggered v3.2 re-probe, 2026-09-28 00:02 to 00:04 Beirut". Evidence reused: `.../companion-chain-20260927/b6/20260927T210207Z/RESULT.json` and `EXTRA-BOUNDARIES.json`. B2 receipt `2026-09-27-2356-bolo-b2-partial-...`, H4 receipt `2026-09-28-0004-bolo-h4-...`, pins `inbox/bolo/2026-09-27-2335-vesper-ack-...`. This is not a release. Nothing ships until Bolo re-probes v3.3. No page or component changed; pass 7 Now and pass 8 Board strings are byte-equal (below).*

## How to check this

```
cd /home/botbox/.openclaw/workspace/projects/caverio/companion-2026-09-26
python3 contract/validate.py                 # exit 0: 129 passed, 0 failed (contract/VALIDATE.out)
python3 contract/validate.py --v32-compat    # exit 1 by design: 7 of 7 v3.3 negative vectors fail on v3.2
python3 contract/validate.py --v31-compat    # exit 1 by design: 17 of 17 v3.2 negative vectors still fail on v3.1
python3 contract/validate.py --v3-compat     # exit 1 by design: 5 of 5 v3.1 negative vectors still fail on v3
```

"Before" means v3.2 exactly as Bolo re-probed it at B6. `legacy-v32/` holds `nowat_v32.py`, `clocks_v32.py`, `adapter_v32.py`, `schemas_v32.py`, `companion-v32.schema.json`, `now-v32.schema.json`, `COMPANION-CONTRACT-v32.md`, `state-vectors-v32.json` and `now-v32.json` (the v3.2 `fixtures/now.json`). Their sha256 values (`legacy-v32/SHA256`) equal `sourceHashes` in his B6 `RESULT.json` (nowat `cf989be8…`, clocks `848413b2…`, adapter `05e46360…`, schema `1efb73fc…`, now schema `1432bc36…`, schemas.py `53ea2612…`, contract `12d2b7a5…`, vectors `2d7c417b…`, now fixture `724869db…`). The snapshot was taken before the first edit. `--v32-compat` loads `nowat_v32.py` bound to `clocks_v32.py` (not the v3.3 clocks). v3.2 had no callable for price moves, so `legacy-v32/pricemove_v32.py` transcribes v3.2's `make.py` rule (lines 1003 to 1014, quoted in its docstring) to give V62 a before, the same way v3.2 did with `nowat_v31.py`. `VALIDATE-v32.out` and `pricemove_v32.py` are in `SHA256` too but were not in Bolo's hashes. A vector counts only if it passes on v3.3 **and** fails on v3.2.

**Vector ids.** V59 to V65, 65 in total. Each carries `briefId`: B6-1, B6-2, B6-3 (Part 1 items 1 to 3), P2-anchor (Part 2), P3-seam, P3-consequences, P3-absence (Part 3). V37 to V41 are byte-equal to v3.1. V42 to V58 keep their ids, cases and expectations; their inputs are now in the v3.3 shapes. `validate.py` maps them back (`downshift`: `native`/`usd` to `price`, Field to bare clock, `history` to `historyFrom`, `estimate` to `assumed_supply`, `previous_material` to `previous_same_kind`) and checks that V01 to V58 then equal `legacy-v32/state-vectors-v32.json` apart from descriptions (`v32-vectors-unchanged`). The v3.1 and v3 "before" texts of V37 to V58 in `VALIDATE.out` are identical to v3.2's `VALIDATE.out` (diffed).

**Bolo's own probes.** `reprobe.py` and `strengthen.py` were copied unmodified to `/tmp/v33probe/`. Their `OUT` is their own folder, so they wrote only there; `C` already points at the working tree `contract/`. His evidence root was not written: the directory listing and timestamps are unchanged. Both exit 0. The results are in `VALIDATE.out` (after the compat runs):
- `futureBuyMustNotRewritePastEntry`: prefix `[12:00, 12:05)` 1, cumulative 1. **With the later append: still 1, cumulative 1** (v3.2: 0).
- `futureEventEarlierAvailability` and `EXTRA-BOUNDARIES.futureMaterialSourceClock`: `callableRefusal` **`evidence clock after its availability; not admitted`**, `renderedLatest` **null** (v3.2: null refusal, the 2099 event rendered as Latest). `schemaErrors` is still `[]`: the schema does not compare clocks, and v3.3 does not claim it does.
- `nanPrice`, `infinitePrice`, `negativePrice`, and the four `priceBoundaries` (NaN, Infinity, -1, 0): callable **`price is not a finite positive decimal; not admitted`** (v3.2: null for all four). His `strengthen.py` adds a `price` key to a v3.3 read. The schema refuses that key as an additional property, and the callable checks it as a price string before anything else.
- validator exit 0; the pass12 prior-probe copy exits 0; the supply control is admitted and excluded when moved to 2099; the baseline stays 87 across the window slide; `--v31-compat` and `--v3-compat` exit 1 as expected.
- `historyPresent` reads `doc.get('historyFrom')` and `row.get('history')`, so it now prints nulls. The field is `now.history` (Part 2): `{since: 2026-09-14T16:27:00Z, kind: persisted}`. The `missingField:history` probe likewise asks about a row key that never existed. Both are harness labels, not contract state.

## Part 1. Bolo's release holds

| Bolo item | File and line (v3.3) | Vector | Before (v3.2) | After (v3.3) |
|---|---|---|---|---|
| **B6 1. Entrants are observed-first and immutable per interval** | `nowat.py:184` `wallets_from_buys(buys, bounds, t=None)`: admitted buys at `t` (`clocks.admit_pair`, `availableAt <= t`); a wallet's entry is its **`observedFirstBuy`** (earliest `availableAt`, then source clock, then input order); bars count that `availableAt` in `[start, end)`; each entrant also carries **`earliestChainBuy`** and `lateEvidence`, evidence only. Contract §7.4 "v3.3, the as-of rule" (line 471) names `observedFirstBuy` (drives counts) and `earliestChainBuy` (evidence only); §12.4 item 5. Schema `Wallets` description (`schemas.py`, `DEFS["Wallets"]`). | **V59** (B6-1), Bolo's exact buys A and B, bounds `[11:00, 11:05)` and `[12:00, 12:05)`, at `t` 12:00, 12:05, 12:10 and 13:00, plus his exact no-`t` call | `[12:00, 12:05)` 0 after the append at 12:10 (sorted by source clock first); `[0, 0]` at 12:10 and 13:00; his exact call `[0]` | `[0, 1]` at every `t`, cumulative 1; at 12:10 `lateEvidence: true`, `earliestChainBuy.at` 11:00; his exact call `[1]` |
| **B6 2. Source clock never after availability; anchor never after the event** | `clocks.py:83` `admit_pair(at, availableAt, asOf=None)` and `:101` `admit_anchor(anchor_at, at)`; reasons at `:34` and `:35`. Used by `nowat.py:114` `admit_price_read` (read and its conversion), `:163` `admit_material_event`, `:184` `wallets_from_buys`, `:286` `supply_valid_at` (`observedAt`, `availableAt`), `:329` `state_at` (history entries); `projection.py:142` (safety event) and `:187` (retention) when they carry both clocks. Fields at `t` read only admitted evidence: `reads_at` `:267`, `material_at` `:344` (so `latest_at` `:348`, For you and the order too), `state_at`. Contract §0.17 (line 47), §3.7 "v3.3, clock order" (line 304), §7.3, catalogue §10 "Clock order and price boundary (v3.3)" (line 517). **The JSON Schema does not compare clocks**; all three V60 negatives are schema-valid and the vector asserts that. | **V60** (B6-2): Bolo's exact instance (`kind: first`, `at: 2099-01-01T00:00:00Z`, `availableAt: 2026-09-14T17:27:30Z`) refused, and not Latest at 17:27:30Z; a price move whose anchor (16:57) is after it (16:42) refused; a price read at 17:27:10 available 17:27:06 refused; a valid event admitted | the 2099 event admitted (null) and rendered as Latest; anchor after the event admitted; the late read refused, but with the wrong reason (`evidence carries no availableAt; not admitted`) | `evidence clock after its availability; not admitted`; `latestAt` null; `anchor after the event; not admitted`; the read refused with `evidence clock after its availability; not admitted` |
| **B6 hardening. Price admission binds the schema boundary at runtime** | `nowat.py:102` `positive_decimal` (the schema's DEC pattern `:60`, bound at runtime, and value above zero) runs before any `Decimal` arithmetic, on `native.amount`, `usd.value`, a conversion `quote` and any leftover v3.2 `price` key; `:71` `R_PRICE`. Schema `PDEC` (`schemas.py:448`) refuses zero too. `adapter.py:24-45`: raw reserves must match u64 and SOL a canonical decimal before arithmetic (`reserve is not a canonical decimal; not admitted`; v3.2 admitted raw `-1000000000` with SOL `-1`); zero reserves stay admissible (contract §12.2 item 25). `adapter.admit_price_read` is the same function as `nowat.admit_price_read`. Contract §0.18 (line 48). | **V61** (B6-3): NaN, Infinity, -1 and 0 refused at the callable and by the schema; `0.0014216742830688294` admitted by both | the callable admitted all four (the schema refused the first three and admitted `0`) | `price is not a finite positive decimal; not admitted` for all four; the real price admitted |

## Part 2. Canonical names and the anchor (the 23:35 pins; resolves CHANGES-v3.2 items 5 and 6)

| Item | File and line | Vector | Before (v3.2) | After (v3.3) |
|---|---|---|---|---|
| **`now.historyFrom` becomes `now.history: {since, kind}`** | `schemas.py:616` (`kind` in `persisted \| prospective`; `required` updated); `nowat.py:424` `now_at` reads `history.since`; `make.py:1333` (`kind: persisted`: the fixture claims history from SPRING's tape start, as v3.2's `historyFrom` did). Contract §7 intro (line 412), §7.1 (a `prospective` store never yields an original `surfacedAt` for anything that surfaced before `since`), §7.6, §12.4 item 3. The refusal text `history not kept before <clock>` is unchanged. Removed from the schema, not aliased. | V47 (input renamed), V65 (`prospective`) | `historyFrom` | `history.since`, `history.kind` |
| **`assumed_supply` becomes `estimate`** | `schemas.py:437` enum `rpc_supply \| estimate \| provider:dexscreener`; `nowat.py:298` `market_cap_at`; `make.py:1139, 1292`. Reason `supply not read; market cap estimated, supply assumed` unchanged. Contract §2.3 (line 144) gains Bolo's H4 sentence: `rpc_supply` only inside the read's validity interval, total supply not circulating, the page keeps `~` until a circulating basis exists, which v3.3 does not define. No supply integration. | V48 (expectations renamed) | `assumed_supply` | `estimate`; the tilde stays |
| **Price-move anchor: the previous material event of any kind, else `firstSeenAt`** | `nowat.py:231` `price_moves(reads, others, first_seen)`: 25% against the price at the row's previous material event of any kind (strictly earlier clock; price moves found so far count); the price at a clock is the last USD read at or before it (`price_at` `:221`); none earlier gives `first_seen`. `nowat.py:49` `ANCHOR_KINDS`: `previous_same_kind` removed, `previous_material` added; nothing else in the enum changed. `schemas.py:544`. `make.py:1055` now calls `NW.price_moves` after entries and top-20 events are known. Contract §7.3 table and note, §12.2 item 19 (closed), §12.4 item 4. | **V62** (P2-anchor): reads 1, 1.3, 1.6, 1.7 at 10:00 to 10:30 and a first-of-its-kind event at 10:20. At 10:30 the rules disagree (+31% against the previous move, +6% against the 10:20 event) | two price moves (10:10 against `first_seen`, 10:30 against the 10:10 move) | one price move, 10:10, anchor `previous_material` at 10:00; 10:30 is not material |
| **SPRING under the pin: byte-equal derivation** | `validate.py:945` compares every row's material set (kind, at, availableAt, changedWording, line), Latest, For you and order with `legacy-v32/now-v32.json`: equal for all six rows. SPRING's strings at 17:27, 16:57, 16:42 and 16:32Z still equal pass 7 NOTES (c). | `now-v33` | anchors 16:42 `first_seen` 16:32; 16:57 `previous_same_kind` 16:42; 17:27 `previous_same_kind` 16:57 | 16:42 `previous_material` 16:32 (the first read); 16:57 `previous_material` 16:42; 17:27 `previous_material` **17:08:05** (first profiled trader), whose price is the 16:57 read, so it is the same +58% and the same line `price +58% since 16:57Z`. Only these three anchors changed. |
| **`contractVersion` `companion-v3.3`** | `schemas.py:679`, `make.py` meta, `vectors.py` doc, schema `$id`s `.../contract/v3.3/...`, titles. Contract header and §11. | all fixtures | `companion-v3.2` | `companion-v3.3` |
| **`--v32-compat`** | `validate.py:74` `_legacy32`, `:89` flag, `:92` `downshift`; the compat block runs `NEG33` against `legacy-v32/`. | V59 to V65 | | 7 of 7 fail on v3.2, exit 1 |

## Part 3. The B2 seam

| Item | File and line | Vector | Before (v3.2) | After (v3.3) |
|---|---|---|---|---|
| **Typed native quote** | `schemas.py:456` `PriceRead`: `{at, availableAt, pool, basis: tape, source, native: {asset, amount, reason}, usd: {value, basis, conversion, reason}}`; `:450` `Conversion {source: provider:<name>, quote, at, availableAt}`; `native.asset` pattern `SYMBOL@chain` (`ETH@rh`, `SOL@sol`, `USDC@base`). `nowat.py:114` `admit_price_read`: `tape_usd` needs a USD stable native asset with `native.amount == usd.value` and no conversion; `native_x_provider` needs a conversion that passes `admit_pair` and `usd.value == native.amount x quote` exactly (else `conversion_mismatch`, as V58); `unavailable` needs `usd.value` null with a reason. A provider-converted value relabelled `tape_usd` is refused with `usd basis misdeclared; not admitted`. Unknown keys are refused (`price read has fields outside the contract; not admitted`). Contract §7.2 `clock.priceReads[]` row, catalogue §10 "Native quote and USD (v3.3)" (line 524). | **V63** (P3-seam): an ETH-quoted read relabelled `tape_usd` refused; the same read as `native_x_provider` with its conversion admitted; a wrong product `conversion_mismatch`; a conversion whose own clock is after its availability refused; `unavailable` with the native quote admitted; `native_x_provider` without a conversion refused | `price` was USD only; the relabel was admitted; an unavailable USD could not be expressed (refused as a provider tick) | refusals and admissions as listed |
| **USD at `t` and its consequences** | `nowat.py:257` `usd_at` (a `native_x_provider` value counts only once `conversion.availableAt <= t`), `:272` `usd_reads_at`, `:276` `gaps_at` (every read without USD at `t` is a gap with `usd conversion unavailable`), `:298` `market_cap_at` and `:312` `change_at` (null with `no USD price at this moment` when no USD read exists at `t`, or the first-seen read's USD is not yet available), `:462` `price_text(reads, t)`, `:416` `row_at` returns `gaps`. Contract §7.2 `market.change` and `clock.gaps[]` rows. | **V64** (P3-consequences): at 10:00:10 the read is present but its conversion (10:00:30) is not: cap and change null with `no USD price at this moment`; at 10:05 the cap is 0.0045 x 1e9 (estimate); at 10:15 the cap still uses read 1 and read 2 is a gap | the last read's price whatever its basis: a 4.5M cap at 10:00:10; a `TypeError` at 10:15 | as described |
| **`NowRow.surfacedAt` is a Field; the state history may be empty** | `schemas.py:588` (Field), `:596` (`minItems` removed), `:530` `since` a Field in `RowState` and `StateHistoryEntry`. `nowat.py:424` `now_at`: a row with `surfacedAt.value` null is never on Now and is listed in `refusals[]` with its reason; `:329` `state_at`: with an empty history the state at `asOf` is the stored projection with `since` null and `state history begins at <history.since>`, and before `asOf` it is null with that reason. `make.py:1135, 1289` (Fields), `now_state` (`since` Field). Contract §7.1, §7.2 `surfacedAt`, `state` and `stateHistory` rows, catalogue §10 "Absent surface and state evidence (v3.3)" (line 530), §12.2 item 24. | **V65** (P3-absence): a staged row (prospective store since 10:00Z) with `surfacedAt` null and empty history is schema-valid, absent at 10:30, 11:00 and 12:00Z, and listed in `refusals` with `original surface clock not captured`; the same row with `surfacedAt` 11:00Z is present from 11:00Z, with `since` null and `state history begins at 2026-09-27T10:00:00Z` at `asOf`, and a null state at 11:30Z | a bare ISO `surfacedAt` and `minItems: 1`; `now_at` raised `ValueError noclock: None` on a null surface clock | as described |

## Part 4. The Situation's market object

| Item | File and line | Vector | Before (v3.2) | After (v3.3) |
|---|---|---|---|---|
| `MarketCapV32` and `ChangeV32` renamed `MarketCap` and `Change` (one name each; old names gone) and used by the Situation | `schemas.py:435, 441`; `Market.changeFromFirstSeen` `:224` and `Market.provider.marketCap` `:230`. `make.py:58` `cap_obj`, `:64` `change_obj`. Same values: SPRING +87% and 2660000, AGRIPPA and MOSSY unchanged. SPRING's and AGRIPPA's `firstSeenAt` and `firstSeenPrice` are their minute-5 T5 reads (the same baseline the v3.2 `note` named); MOSSY's are null with `first tape read not in the fixture` (it is all illustrative). The provider cap's `supply` is null with `supply not read; market cap estimated, supply assumed` and `assumedSupply` null with `provider figure; no supply assumed by us`. The v3.1 `now.sections` rows keep bare Fields (`make.py` takes `.value`). Contract §2.3 table and note. | fixtures, schema | bare `PriceField`s | `Change` and `MarketCap` objects |

**Regression on build-0, from data only: read this before deploying.** build-0 reads both Situation fields as bare Fields (`build-0/lib/components/c04-numrow.js:23, 27`, `build-0/lib/pages/situation.js:135, 137`). With the v3.3 fixtures, the Situation page's "since first tape read" and "market cap" cells print no number for SPRING, AGRIPPA and MOSSY. The shot checks still say ALL OK, because an empty cell is not an orphan number. Each of those four places needs a one-line G2 change to read `.value`. Page changes were out of scope, so I did not make it. `build-0/CHECKS.md` "v3.3 fixtures" says the same. Do not deploy build-0 with these fixtures before that change.

## Fixtures and build-0

- `make.py` regenerates all five with the renames and the new `PriceRead`. **The fixture predates the seam.** T5 holds `priceUsd` only and no conversion record, so every read has `native: {asset: null, amount: null, reason: "native quote not in the T5 extract"}` and `usd: {value: <T5 priceUsd>, basis: tape_usd, conversion: null}`, with a `note` saying so. No conversion is invented. SPRING's pool is ETH-quoted (B2), so a live SPRING read would be `native_x_provider` or `unavailable`, never `tape_usd`. Contract §7.6 and §12.2 item 21 say this. No number from Bolo's B2 capture is in any fixture.
- The Now strings are byte-equal to v3.2: SPRING `~$2.66M`, `+87% since seen`, readout `$0.00266 · 939 entered · 1 profiled · none seen · top 20: 0.59 · safety not read`, Latest `first profiled trader · 17:08Z`, For you `In · 2 material since entry · 17:11Z` / `3 since look`, and the rewinds at 16:57, 16:42 and 16:32Z. The row order, the material sets, Latest, For you and the order equal v3.2 for all six rows. `board.json` is unchanged apart from `contractVersion` and the `etag` that hashes it (diffed against the v3.2 file, sha256 `39cf7041…`).
- sha256: spring `f7915fba7afa7897…`, agrippa `f00a133029e53783…`, mossy `c1a1b66687fe20d6…`, now `54b952babb64beee…`, board `7b50b4728be3f840…`.
- `scripts/build0-fixtures.py` copied all five byte for byte (`cmp` identical). `NODE_PATH=/home/botbox/node_modules node scripts/build0-shots.cjs` exit 0, **ALL OK**, appended to `build-0/CHECKS.md` under "v3.3 fixtures". The regression above is recorded there.

## Decisions (Bolo's three mapping questions)

1. **Entrant: observed-first or first-chain-buy?** Observed-first drives every count (`observedFirstBuy`, earliest `availableAt`), and a past interval never changes. `earliestChainBuy` is kept as evidence only, and at most a provenance note.
2. **Names: `historyFrom`/`assumed_supply` or `history.since`/`estimate`?** `now.history {since, kind}` and `estimate`, per the 23:35 pins. The old names are removed from the schema, not aliased.
3. **Anchor: the previous price move or the previous material event?** The previous material event of any kind (its price is the last USD read at or before it), else `firstSeenAt`, per the 23:35 pin. `previous_same_kind` is gone, and SPRING's derivation is unchanged.

## For B2

Bolo's staged audit envelope (`b2/spring-0xfce2…-t-20260927T204426Z.json`, schema `spring-b2-stage/1`) mapped to v3.3, field by field. It stays an audit envelope. This is the shape a staged row can take, not a license to wire it.

| B2 envelope | v3.3 field | Expressible now? |
|---|---|---|
| `identity.{symbol, chain, address}`, `availableProduct.pool` | `NowRow.identity.{symbol, chain, address}`, `identity.pool` = `robinhood:<pool>` | yes |
| `availableProduct.priceReads[].sourceAt` (block time) | `PriceRead.at` | yes |
| `.availableAt` | `PriceRead.availableAt` (`admit_pair`: `sourceAt <= availableAt`) | yes |
| `.receivedAt` | no field; our receipt sits between the two clocks and stays in the store | not carried, by design |
| `.priceQuote` (executed ETH per SPRING), `.unit` `ETH/SPRING` | `native.amount` (full precision), `native.asset` `ETH@rh` | **yes, new in v3.3** |
| `.poolPriceQuote` (post-swap pool price) | no field; the read is the executed swap price (`at` is that swap's block time) | not carried; say if you want the pool quote instead |
| `.basis: tape`, `.locator` | `basis: tape`, `source: tape:robinhood`; the locator stays in the store (a `note` may cite it) | yes |
| `usdPrice: null`, `usdReason` (DexScreener conversion, no tape USD) | `usd: {value: null, basis: unavailable, conversion: null, reason: "usd conversion not available at this moment; native quote retained"}`. `native_x_provider` needs `conversion.at` and `availableAt`, and H4 found provider market timestamps absent, so today it is `unavailable`, not `native_x_provider` | **yes, new in v3.3** |
| `blockers.marketCap` | `market.marketCap.value` null with `no USD price at this moment`, `basis: estimate`, `supply` null | **yes, new in v3.3** |
| `firstSeenAt: null`, `blockers.changeSinceSeen` | `identity.firstSeenAt`, `change.firstSeenAt`, `change.firstSeenPrice`, `change.value`: Fields null with your reason | yes (the reason string must enter §10 before a page prints it) |
| `window` | `clock.window` | yes |
| `history.{since, scope: prospective_staging_receipt_only, replayBeforeSince: refused}` | `now.history: {since, kind: prospective}`; any `t < since` refuses with `history not kept before <clock>` | **yes, new in v3.3** (the name and the kind) |
| `blockers.wallets` | `wallets.intervals: []`, `cumulativeDistinctEntrants` and `latestEntry` null with a reason | yes |
| `blockers.whyNow` (no wording-changing event) | `latest` null with `no material event that changed wording yet`, `materialEvents: []` | yes |
| `blockers.situationId` | `situationId` null with a reason | yes |
| `blockers.surfacedAt` (no original surface publication clock) | `surfacedAt: {value: null, reason: "original surface clock not captured"}`: the row is schema-valid and **never on Now**; `now_at` lists it in `refusals[]` | expressible, and it **remains a refusal**. That is correct, not a gap: B4 cannot create a past surface clock |
| history before `since` | `t < history.since` refuses | **remains a refusal**, correctly |
| `blockers.state` (no evidence-qualified projection) | `stateHistory: []` with `since` null and `state history begins at <since>` is expressible. `RowState.word`, `sentence` and `whyHere` are still required: the honest word without a projection is `unknown`, with the projection's refusal as the sentence. Do not substitute Developing. B4's prospective writer produces history entries forward from `since` | partly: the absence of history, yes; a null word, no (use `unknown`). The row is off Now anyway while `surfacedAt` is null |

## For the re-probe

Vector ids: **V59** (B6-1, entrants, your exact buys), **V60** (B6-2, your exact 2099 event, anchor after event, late price read), **V61** (B6-3, your four price strings), **V62** (P2-anchor), **V63** (P3-seam), **V64** (P3-consequences), **V65** (P3-absence). V37 to V58 are unchanged in meaning (V42 to V58 in v3.3 shapes; `v32-vectors-unchanged` proves the mapping) and still green.

```
cd /home/botbox/.openclaw/workspace/projects/caverio/companion-2026-09-26
python3 contract/validate.py                 # 129 passed, 0 failed, exit 0
python3 contract/validate.py --v32-compat    # 7 of 7 fail on v3.2 (legacy-v32/, your B6 sourceHashes), exit 1 by design
python3 contract/validate.py --v31-compat    # 17 of 17 fail on v3.1, exit 1 by design
python3 contract/validate.py --v3-compat     # 5 of 5 fail on v3, exit 1 by design
# your harness, unmodified, run from a copy (it writes only beside itself):
mkdir -p /tmp/v33probe && cp <b6>/reprobe.py <b6>/strengthen.py /tmp/v33probe/ && cd /tmp/v33probe && python3 -B reprobe.py && python3 -B strengthen.py
```

New code to read: `clocks.admit_pair` and `admit_anchor`; in `nowat.py`: `admit_price_read`, `admit_material_event`, `wallets_from_buys`, `price_moves`, `usd_at`, `gaps_at`, `state_at` and `now_at`. Also `adapter.admit_curve` (pattern checks), and `validate.py` `downshift`, `_legacy32` and `run_now_vector`.

## Choices you may want to overrule (contract §12.2 items 21 to 25)

1. **Pre-seam fixture as `tape_usd` with an absent native amount** (item 21). The adapter admits that combination only when the absence carries a reason; a present non-stable asset with `tape_usd` is refused.
2. **`source` kept on `PriceRead`** (item 22). It is what makes the schema refuse a relabelled provider tick (V53).
3. **Exact product** for `native_x_provider` (item 23), the same rule as V58.
4. **Empty history before `asOf`** (item 24): the state is null before `asOf`, since the Now document carries no evidence to re-run the projection at an earlier `t`.
5. **Zero curve reserves admitted** (item 25): a reserve is not a price.
6. **Unknown `PriceRead` keys refused at the callable** (`price read has fields outside the contract; not admitted`). This binds the schema's `additionalProperties: false` at runtime, as your hardening note suggested.

## Still open (not in this brief)

- **G2, build-0 Situation page:** read `.value` of `market.changeFromFirstSeen` and `market.provider.marketCap` (four lines, above). Until then the two cells are empty.
- **G2, page strings for the new nulls:** `no USD price at this moment` (price line, cap, change, readout price), `usd conversion unavailable` (gap hatch on the rail), and a refused row (`original surface clock not captured`). The contract defines the reasons; the frozen pages do not print them yet. `price_text` returns the reason for now.
- **Board's H1 entry cap:** the contract says it uses only reads with USD at `t` (§7.2), but `board.json` has no entry-cap field and no code computes one at `t`. It needs its field and rule when Board gets its v3.3 read model.
- **Circulating supply:** not defined (H4). The tilde stays.
- **B4 history persistence** before any live rewind claim, unchanged from v3.2.
- The other event `values` payloads are still generic (Bolo M2 item 12).

Status: DONE
