# Contract v3.5: changes from v3.4

*W-contract-v35, 2026-09-28 15:15 Beirut onward. Brief: `briefs/W-contract-v35.md` (Sprint V15 of `SPRINT-CHAIN-2026-09-27.md`, after V13 build-3 and V14 design pass 13). Trigger: Thomas 14:30, ruled by Vesper 14:45 (`inbox/rook/2026-09-28-1450-vesper-ruled-money-in-out-track-under-wallets-in-...`): Situation gets a `Money in/out` track under `Wallets in`, and the read model gets the series that feeds it. Pass 13 drew the track on a fixture and proposed the field (d2-tracks NOTES (e)); this file names it for Bolo's B10. The second item writes pass 12's cap display rule (build-3 CHECKS §0, item 2) into the contract. This is not a release. Nothing ships until B10 writes the series. No rename and no alias: the v3.4 vocabulary stands, and v3.5 adds to it.*

## How to check this

```
cd /home/botbox/.openclaw/workspace/projects/caverio/companion-2026-09-26
python3 contract/validate.py                    # exit 0: 152 passed, 0 failed (contract/VALIDATE.out)
python3 contract/validate.py --v34-compat       # exit 1 by design: 8 of 8 v3.5 vectors fail on v3.4
python3 contract/validate.py --v33-compat       # exit 1 by design: 6 of 6 (line-identical to v3.4's VALIDATE.out)
python3 contract/validate.py --v32-compat       # exit 1 by design: 7 of 7 (line-identical)
python3 contract/validate.py --v31-compat       # exit 1 by design: 17 of 17 (line-identical)
python3 contract/validate.py --v3-compat        # exit 1 by design: 5 of 5 (line-identical)
python3 contract/migrate_v34_to_v35.py --check  # exit 0: the one-time migration over now-v34 and build-2's staged Solana document
python3 contract/migrate_v32_to_v33.py --check  # exit 0 (re-pointed at legacy-v34's schemas, item 4)
python3 contract/validate.py --doc <document>   # exit 0 on a valid v3.5 document (For Bolo, below)
grep -n downshift contract/*.py                 # no output
grep -n netUsd contract/fixtures/*.json         # no output
```

"Before" means v3.4 exactly as it stood at 15:15, before the first edit. `legacy-v34/` holds `nowat_v34.py`, `clocks_v34.py`, `adapter_v34.py`, `schemas_v34.py`, `projection_v34.py`, `coverage_v34.py`, `board_v34.py`, `companion-v34.schema.json`, `now-v34.schema.json`, `COMPANION-CONTRACT-v34.md`, `state-vectors-v34.json`, `now-v34.json` (the v3.4 `fixtures/now.json`), the other four fixtures (`spring-v34.json`, `agrippa-v34.json`, `mossy-v34.json`, `board-v34.json`), `board-staging-v34.json` (the v3.4 `staging/board.json`), `vectors_v34.py`, `validate_v34.py` and `VALIDATE-v34.out`, with `legacy-v34/SHA256` (`sha256sum -c` passes, 20 files). Key hashes: nowat `fd2c2f8f…`, clocks `d56ef6b8…`, companion schema `33715de9…`, now schema `7b89c378…`, contract `a523e5ac…`, vectors `f2563286…`, now fixture `54b952ba…`, VALIDATE `572ebb29…`. Both generators reproduced v3.4 byte for byte before the first edit (`make.py`, `vectors.py`, `sha256sum -c`). A v3.5 vector counts only if it passes on v3.5 **and** fails on v3.4.

**Vector ids.** V72 to V79, 79 in total. Each carries `briefId` and `since: v3.5`. V01 to V71 are byte-equal to v3.4 (`v34-vectors-unchanged`). The before-and-after texts of V37 to V71 in `VALIDATE.out` equal v3.4's, apart from the label `v3.4:` now reading `v3.5:` (compared line by line, 105 of 105).

## Item 1. `flow` on the Now row (§7.2, new §7.7)

| | File and line (v3.5) | Before (v3.4) | After (v3.5) |
|---|---|---|---|
| **The field** | Contract §7.2 row `flow` (`COMPANION-CONTRACT-v3.md:455`), §7.7 (`:503`); `schemas.py:563` `NO_NET`, `:564` `FlowInterval`, `:577` `Flow`, `:618` `NowRow.flow` (required at `:629`) | no field; pass 13 NOTES (e) proposed one | `flow: Field {value: {intervals: [{start, end, availableAt, buyUsd, sellUsd, swaps, reason?}], source: "tape"}, reason}`, required on the row as `wallets` is |
| **Grid** | §7.7; `nowat.py:379` `admit_flow`, `:125` `flow_grid_reason` | | `flow.intervals[]` has `wallets.intervals[]`'s `start` and `end` values in the same order; otherwise refused, naming the first mismatching interval: `flow.intervals[1] [12:05, 12:09) does not match wallets.intervals[1] [12:05, 12:10); not admitted` (`none` for a missing one). V77 |
| **USD** | §7.7 "Sums" and "Which fields these are"; `nowat.py:335` `flow_from_swaps`, `:312` `admit_swap` | | `buyUsd` = sum of `quoteAmount x priceUsd` over the interval's admitted `side: buy` swaps, each at its own `priceUsd`, exact decimals then to the cent; `sellUsd` the same over `side: sell`; `swaps` the count. §7.7 names the fields: the contract's documents carry no swap record, so `side`, `quoteAmount` and `priceUsd` are the tape's (the B10 queue's names). The sums are the interval form of §2.3's `volume1h` and `netFlow1h` (basis `tape`, unit `usd`). `quoteAmount` is in the quote asset §7.2 names in `PriceRead.native.asset`. `priceUsd` is the USD price of one unit of that asset, `1` for a USD stable (§7.2's `tape_usd`), bound by §0.18. The source is `tape:<chain>`, as `PriceRead.source`. The USD is the tape's own conversion and never a provider figure. A swap without a tape USD price makes its interval null with `usd conversion unavailable` |
| **Completed and stable** | §7.7; `flow_from_swaps` (write), `nowat.py:563` `flow_at` (read, beside `wallets_at` at `:558`, the same `_avail(i, t)` read as v3.4's `nowat.py:411`) | | written only when `end <= asOf`; read at `t` when `availableAt <= t`; a swap sits in the interval whose `[start, end)` holds its `availableAt` (§7.4's as-of rule), so a late swap lands in a later interval and a written interval never changes. V72, V73 |
| **`availableAt`** | §7.7 | | the later of `end` and the `availableAt` of the interval's last admitted swap. Under the as-of rule that is always `end`, and §7.7 says so. An interval available before its end is refused (`flow interval available before its end; not admitted`); one ending after `asOf` too. V77 case `available-before-end` |
| **Net derived** | §7.7; `nowat.py:410` `flow_net`; schema `NO_NET` on the interval and the series | | no `netUsd` anywhere; the schema refuses it by name (`not: {required: [netUsd]}` plus `additionalProperties`); `admit_flow` refuses it with `key netUsd is not in the contract (net is buyUsd - sellUsd, derived by the reader); not admitted`, naming the path; `now_at` lists the row under `refusals[]` (`nowat.py:660`). V76 |
| **Unread interval** | §7.7; `FlowInterval` if/then/else | | present, with `buyUsd`, `sellUsd` and `swaps` null and one `reason` (`tape gap in this interval`, proposed in the brief and now in §10; or `usd conversion unavailable`); never 0. Half-null is refused (schema, and `flow interval partly read; not admitted`). V74 |
| **Chains** | §7.7; `nowat.py:304` `tape_refusal` | | `capabilities.tape` known false: `flow` null with `Tape: not read on this chain.`; unreported: `Tape: source support not reported; not computed.` (the §3.7 pattern). V75 |
| **The scalars** | §2.3 (`:151`), §7.7 | `volume1h`, `netFlow1h` | unchanged. §2.3 and §7.7 each say in one sentence that they are scalars over the trailing hour, `flow` is the interval series, and the reader derives neither from the other |
| **Rule outputs** | `now_at`, `row_at` | | do not read `flow`. `flow-rule-outputs-unchanged`: `now_at` on the v3.5 `fixtures/now.json` equals `now_at` on `legacy-v34/now-v34.json`, and both equal the frozen v3.4 code's answer, at 7 clocks. The build-3 parity files reproduce byte for byte (Verification) |
| **§10** | `COMPANION-CONTRACT-v3.md:592` "Flow (v3.5, §7.7)" | | the null reasons and the refusals, word for word |

## Item 2. The cap display rule as a v3.5 line (§7.6a)

| | File and line (v3.5) | Before (v3.4) | After (v3.5) |
|---|---|---|---|
| **The rule** | Contract §7.6a (`COMPANION-CONTRACT-v3.md:493`), §0.20, the §7.2 `market.marketCap` row, §2.3 v3.5 paragraph | §7.2 and §8: "the page prints `~`" unless `rpc_supply`; the pages (pass 12, build-3) print `cap not read` instead, so the page and the contract disagreed | a figure prints only when `market_cap_at(row, t)` returns `basis: rpc_supply` (a supply read valid at `t` whose `availableAt <= t`); every other outcome (`estimate`, any `provider:*`, no read, a read available after `t`) prints `cap not read` with the title `supply not read at <moment>` |
| **Callable** | `nowat.py:706` `cap_display_at(row, t, exact=False)` returns `{text, title, basis}`; `:693` `cap_figure` (the figure without a mark) | none; `mc_text` returned `~$2.66M` | `text` is the figure or `cap not read`, never a tilde; `title` null with a figure; `basis` the rule's, except that a stored `provider:*` basis is kept so the row is counted, not printed |
| **Board** | `board.py:79` `entry_cap_display(entryCap)` | build-3's `setEntry()` stored `mcBasis` and applied the rule in the page | the entry cap prints by the entry's stored basis: the figure only on `rpc_supply` |
| **Legacy formatter** | `nowat.py:698` `mc_text`, docstring | the only formatter | kept byte for byte in output (it now calls `cap_figure`) for the frozen vectors and the pass 7 strings the validator compares (`now-v33`, unchanged and passing). Nothing new calls it; §7.6a says so |
| **About sentence** | §7.6a | | `A market cap is never estimated. Where the supply was not read at the moment shown, the cap slot says cap not read; the price and the change still print.` (pass 12's text as build-3 prints it) |
| **Checks** | `validate.py` `cap-display` (`:1061`) | | `cap_display_at` over every row of `fixtures/now.json` and build-3's `now-supply.json` at rail minutes 0 to 60: no tilde, a figure exactly when `market_cap_at` is `rpc_supply`, a title only on `cap not read`. Row-moments: build-3 estimate 338, rpc_supply 28; fixtures estimate 366 |

**V78 inputs are build-3's synthetic supply fixture** (`build-3/data/fixtures/now-supply.json`, read only, not edited: SPRING supply 987,654,321, observed 16:59:54Z, available 17:00:00Z, valid to 18:00:00Z): `$2.63M` at 17:27:30Z; `cap not read`, titled `supply not read at 16:59:59Z`, at 16:59:59Z (build-3's edge); `$1.66M` at 17:00:00Z. These are build-3 CHECKS §5's strings (`MC $2.63M`, `MC $1.66M`), without the page's `MC ` chrome.

## Item 3. Fixtures carry the series

| Fixture | v3.5 |
|---|---|
| `fixtures/now.json` SPRING | Pass 13's table, exactly: 16:27 to 16:32Z buy 38,450, sell 4,120, **1,184 swaps**; 16:32 to 16:42Z 9,580 / 14,260, **412**; 16:42 to 16:57Z 18,740 / 7,210, **367**; 16:57 to 17:27Z 26,380 / 11,890, **301** (the swap counts are my choice, recorded here and in `make.py:1178` `SPRING_FLOW`). `availableAt` is each interval's end. `illustrative: true` on every interval, because T5 holds no per-swap USD. The reader's net: +14,490 at 17:27:30Z and -4,680 at 16:42:30Z (pass 13 prints `net +$14.5K in` and `net -$4.7K out`); none at 16:30Z |
| the other rows | The same rule as their wallets (`make.py:1184` `flow_field`): every fixture chain (robinhood, bsc, solana) reports tape true, so every row carries a series on its wallets grid. AGRIPPA has 7 intervals, LUMEN 6 and MARLIN 5; OBOL and KESTREL have no wallets intervals, so their series is empty. Values are illustrative: buy 55 x the interval's wallets, sell 21 x wallets + 400 x (i mod 3), swaps 3 x wallets + i. **MARLIN's 16:57 to 17:07Z interval is a tape gap**, the one hatch in a document. No row is null by chain, because no fixture row sits on Base, the one chain with tape not read. V75 covers that case |
| `fixtures/agrippa.json`, `mossy.json`, `spring.json` | Situation documents with no Now row and no wallets interval series, so nothing to mirror. They change only in `meta.contractVersion` (`companion-v3.5`) and the etag computed over it |
| `fixtures/board.json` | The stamp and the etag only; no `entryCap` (checked equal to `legacy-v34/board-v34.json` apart from those two) |
| `staging/board.json` | **Not touched** (sha256 `44f56c22…`, equal to `legacy-v34/board-staging-v34.json`). It keeps `companion-v3.3` and validates under `legacy-v34/`'s schema; its entryCap recomputes from the v3.5 SPRING row |
| Bolo's staged documents | Not touched. `build-2/data/staged/solana-now.json` was read once, sha256 checked |

`flow-fixtures` (`validate.py:998`) checks every row: the grid equals wallets, it is admitted, `availableAt` is the end, invented values are illustrative, SPRING equals pass 13, exactly one hatch, and no `netUsd` in any fixture.

## Item 4. Schemas, version, and the migration

| | File and line (v3.5) | Before (v3.4) | After (v3.5) |
|---|---|---|---|
| **Schemas** | `schemas.py`, emitted by `make.py` into the six `*.schema.json` | `$id .../contract/v3.4/...` | `$id .../contract/v3.5/...`; `FlowInterval`, `Flow`, `NowRow.flow` (required). Hashes: `companion.schema.json` `91ea623c0344e7ea…`, `now.schema.json` **`f07e820596b25463936200a65d68e71162afea28f718bc0221392bcca709b2fa`** |
| **Version** | `schemas.py:707` `Meta.contractVersion` `const companion-v3.5`; `make.py:258`; contract §11 (`:708`) | `companion-v3.3` (v3.4 kept it) | `companion-v3.5`. `flow` is a new required field, which is not additive, so the stamp moves |
| **A v3.4 document is refused** | V79 (`I4-v34-refused`) | valid | SPRING's v3.4 row: `'flow' is a required property`; the v3.4 meta: `'companion-v3.5' was expected` |
| **Migration** | `contract/migrate_v34_to_v35.py` (`migrate` `:72`, `migrate_row` `:57`, `check` `:104`) | | deterministic; adds `flow: {value: null, reason: 'flow not written by this writer'}` after `wallets` on every row and moves `companion-v3.3` to `companion-v3.5`, nothing else (`meta.etag` left as written). Exit 2 and nothing written on anything half-shaped: some rows with `flow` and some without, a stamp that is neither v3.3 nor v3.5, a v3.5 stamp on rows without flow, flow under the v3.3 stamp, a row without wallets, or a document that is neither a payload with `now.value` nor a bare Now |
| **Its check** (`--check`) | | | `legacy-v34/now-v34.json`: 6 rows, stamp moved; v3.5 Now schema 0 errors, Companion 0; the input had 6 errors under v3.5; dropping `flow` and the stamp gives back the input exactly; `now_at` equal to v3.4's at `asOf` and `history.since`. Output **`/tmp/now-v34-migrated-v35.json` sha256 `cfe02d48dc4bd1563ef186bd0d3e33a4d2bc9fbbf073c29d047151f3ae6e19a4`**. Build-2's staged copy `build-2/data/staged/solana-now.json` (sha256 checked `cd11bc32…`, read only): a bare Now with no `meta`, so there is no stamp to move; 1 row (OP); v3.5 Now schema 0 errors; `now_at` unchanged (OP refused, `original surface clock not captured`). Output **`/tmp/solana-now-migrated-v35.json` sha256 `da00837b44bf965cd1973ecd3f1b1da47bb211ce6d4e26e45310e196e6311082`**. It is idempotent (a second run migrates 0 rows) and refuses the half-shaped cases. That second output is what build-4 runs on until B10 writes real series |
| **`migrate_v32_to_v33.py --check`** | `migrate_v32_to_v33.py:123` | validated against "the current schemas" (v3.4's) | now validates against `legacy-v34/`'s schemas, the ones it was written for (the current schemas require `flow`, which no v3.2 document had), then takes the second step through `migrate_v34_to_v35` into v3.5 with 0 errors. Its first four output lines, including both hashes (`ca73b290…`, `168b3f86…`), are line-identical to v3.4's |

## Vectors (V72 to V79)

| Id | briefId | What it pins | v3.5 | v3.4 (legacy-v34/) |
|---|---|---|---|---|
| V72 | I1-flow-series | A normal series: 4 wallets intervals, asOf 12:16; ETH swaps at their own `priceUsd` (2,500 and 2,502.20): interval 1 is 6,251.10 in and 3,000.00 out over 3 swaps; interval 2 is 0 in and 7,500.00 out, including a zero-size buy (a read zero); interval 3 is read and empty (0, 0, 0); interval 4 is open and not written. `availableAt` is each end; the output is schema-valid (`Flow`); the reader at 12:07 sees 1 interval, net +3,251.10, and at 12:10 sees [+3,251.10, -7,500] | pass | `AttributeError: no flow_from_swaps / flow_at` |
| V73 | I1-flow-late | A 4 ETH buy at source 12:03, available 12:06:30. Written at 12:05:30, interval 1 is 5,000.00 (1 swap). Written at 12:16, interval 1 is still 5,000.00 and the late buy is in interval 2 (10,000.00) | pass | no callable |
| V74 | I1-flow-hatch | A tape gap 12:06 to 12:08 makes interval 2 null with `tape gap in this interval`, even though a swap inside it was seen. A swap without `priceUsd` makes interval 3 null with `usd conversion unavailable`. Schema: a null interval with its reason is valid, a read zero is valid, half-null is invalid, and null without a reason is invalid | pass | no callable |
| V75 | I1-flow-chain | Tape known false gives null with `Tape: not read on this chain.`; unreported gives `Tape: source support not reported; not computed.`; true gives a series | pass | no callable |
| V76 | I1-flow-netUsd | `netUsd` on an interval is refused at `flow.value.intervals[0].netUsd`, and at series level at `flow.value.netUsd`, with the named reason. `now_at` lists NETROW under `refusals` and keeps FLOW. The schema refuses both. The control is admitted and valid | pass | no callable |
| V77 | I1-flow-grid | A second interval ending 12:09 against wallets' 12:10 is refused, naming `flow.intervals[1]` and both spans. A series one short is refused, naming `flow.intervals[2]` against `none`. An interval available at its last swap (12:03:05) before its end (12:05) is refused | pass | no callable |
| V78 | I2-cap-display | build-3's supply fixture: `$2.63M` (rpc_supply) at live; `cap not read` / `supply not read at 16:59:59Z` one second before availability; `$1.66M` at 17:00:00Z. The estimate row prints `cap not read` / `supply not read at 17:27Z`, where `mc_text` printed `~$2.66M`. The provider row prints `cap not read`, basis `provider:dexscreener`. Board entry, stored estimate: `cap not read` / `supply not read at 17:11Z`; stored rpc_supply: `$1.66M` | pass | `AttributeError: no cap_display_at`; `LookupError` for the Board display |
| V79 | I4-v34-refused | SPRING's v3.4 row under v3.5 NowRow gives `'flow' is a required property`; the v3.4 meta gives `'companion-v3.5' was expected` | pass | both valid |

`--v34-compat`: 8 of 8 fail on v3.4, each with the reason above (`VALIDATE.out` after the main run).

**The earlier vectors' rows.** V65 schema-checks its document's rows as `NowRow`, and those rows (written for v3.4, V01 to V71 frozen) carry no `flow`. The validator now checks rows of vectors older than v3.5 against the frozen v3.4 schema (`sub_schema34`, `validate.py:790`), and it checks their one-time migration (`migrate_v34_to_v35.migrate_row`) against v3.5. Both pass.

## For Bolo (B10)

What B10 writes, for every admitted token on a staging document, from this file:

1. **Field.** `rows[].flow`, a Field: `{value: {intervals: [...], source: "tape"}, reason: null}`, or `{value: null, reason: <one of the null forms>}`. Required on every row from v3.5 on.
2. **Interval.** `{start, end, availableAt, buyUsd, sellUsd, swaps}`, plus `reason` only on an unread interval (and `illustrative` only in fixtures). No other key. **No `netUsd`**, on an interval or on the series: the reader computes `buyUsd - sellUsd`, and a stored net is refused by name.
3. **Grid.** Exactly `wallets.intervals[]`'s `start` and `end` values, in the same order, for the same row. Only completed intervals: `end <= asOf`.
4. **USD.** `buyUsd` is the sum over the interval's admitted `side: buy` swaps of `quoteAmount x priceUsd`, each swap at its own `priceUsd`; `sellUsd` is the same over `side: sell`; `swaps` is the count of both. Make the products exact and round the sums to the cent. `priceUsd` must make the product the swap's USD value: with `quoteAmount` in the quote asset (ETH on SPRING's pool), `priceUsd` is that asset's USD price at the swap, from your tape's own conversion. If your tape's `priceUsd` is the token's price, multiply the token amount instead; the contract fixes the product, not the column. Never a provider figure.
5. **Bin and clock.** A swap belongs to the interval whose `[start, end)` holds its `availableAt`, not its block time. `availableAt` of an interval is the later of `end` and its last admitted swap's `availableAt`, which comes out as `end`.
6. **Null forms.** Chain tape not reported true: `flow` null with `Tape: not read on this chain.` (known false) or `Tape: source support not reported; not computed.` (unreported). A tape gap overlapping an interval: `buyUsd`, `sellUsd` and `swaps` null with `reason: "tape gap in this interval"`. A counted swap without a tape USD price: the same with `usd conversion unavailable`. Never 0 for a missing read; 0 means read and empty.
7. **Stamp.** `meta.contractVersion: companion-v3.5` on a payload. A bare Now carries no stamp. Pin `now.schema.json` sha256 `f07e820596b25463936200a65d68e71162afea28f718bc0221392bcca709b2fa` (`$id .../contract/v3.5/now.schema.json`). The v3.4 pin `7b89c378…` refuses every B10 row, because v3.4 has no `flow`.
8. **The command.** From `companion-2026-09-26/`:
   ```
   python3 contract/validate.py --doc <path to the staging document>
   ```
   It takes a payload with `now.value` or a bare Now. It checks the v3.5 schemas, runs `admit_flow` on every row (grid, `netUsd`, early or open intervals, half-null) and `now_at` at the document's `asOf`, and prints each row's flow. Exit 0 and `document: OK` means admitted. On build-2's staged Solana document as it is today it exits 1 (`rows/0: 'flow' is a required property`); on its migrated copy it exits 0 (`OP: flow null: flow not written by this writer`).

**Where this file differs from the B10 queue** (`inbox/bolo/2026-09-28-1450-vesper-b10-queued-...`); **this file wins in each case**:
- **`availableAt`.** The queue says "availableAt the interval's last admitted swap". Here it is the later of `end` and that clock. A series stamped at its last swap's clock would be readable before the interval closes, while a later swap in the same interval could still arrive, so the interval's totals would change at a later `t`, which is exactly what §7.4's as-of rule forbids. An interval available before its end is refused (V77).
- **Per-interval nulls.** The queue names only the chain-level null. An unread interval inside a read series is null with a reason, never 0 (V74).
- **The Field wrapper and the stamp.** The queue names neither. `flow` is a Field (§0.5), and the stamp is `companion-v3.5`.
- Everything else agrees: the grid, completed intervals only, USD at each swap's `priceUsd` from `side` and `quoteAmount`, net derived and never stored, and `volume1h` and `netFlow1h` unchanged.

## Item 6. v3.5 candidates, carried (reserved, nothing added)

Neither item 1 nor item 2 needed any of v3.4 Item 7's three candidates, so all three stay listed and reserved, unchanged:
1. **pump.fun decode as an absence state** (B7 order 5): a §10 reason such as `curve decode not measured`, and a rule for which Now fields inherit it.
2. **`surfacedAt` from the new writer** (B7 order 6): a basis or source on `surfacedAt`, so a real surface clock can be told from one inferred from first-seen-in-tape.
3. **Native SOL quote**: the SOL/USD conversion source and its clocks, and how old a conversion may be relative to the read. For flow on Solana the same question applies to `priceUsd` (item 4 of For Bolo). It is still a candidate, not a field.

## Choices you may want to overrule

1. **build-0 was restored, not re-synced.** The brief asks for a `scripts/build0-fixtures.py` rerun, byte-equal build-0 innerText, and no page file touched under `build-*`. Those three cannot all hold in v3.5. The rerun rewrites `build-0/index.html` (its generated illustrative list), and build-0 prints the contract stamp (`lib/frame.js:119`) and the count of illustrative fields. I ran it and recorded the exact rendered difference (Verification). Then I restored `build-0/` to its committed state (`git checkout -- build-0/index.html build-0/fixtures`); its fixtures equal `legacy-v34/*-v34.json` byte for byte. To re-sync build-0 on v3.5, run `python3 scripts/build0-fixtures.py`. The rendered change is the one listed under Verification, and nothing else.
2. **The stamp moved** to `companion-v3.5`, as the brief asks. That is why build-0's text changes on a re-sync. `staging/board.json` keeps `companion-v3.3` and is checked against the frozen v3.4 schema.
3. **`flow` is a Field** (`{value: {intervals, source}, reason}`), per §0.5. Pass 13 proposed `{intervals, source, reason}` (contract §12.2 item 28).
4. **An interval available before its end is refused** (§12.2 item 26). It follows from the brief's rule, and it closes the queue's wording.
5. **A swap without a tape USD price nulls its whole interval**, `swaps` included, rather than leaving a partial sum with a count (§12.2 item 27).
6. **`priceUsd` is read as the quote asset's USD price**, the only reading under which `quoteAmount x priceUsd` is USD (For Bolo item 4).
7. **SPRING's flow is `illustrative: true`**, although SPRING is grade A: T5 has no per-swap USD. The example rows' formula is mine.
8. **`now_at` does not refuse a row for a missing `flow`**; the schema does (§12.2 item 29). This keeps the rule callables' answers, the build-3 parity files and every page that ports `nowat.py` unchanged. `now_at` refuses only a present, defective `flow`.
9. **The earlier vectors' rows are checked against the frozen v3.4 schema**, plus their migration against v3.5, instead of editing V65 (V01 to V71 stay byte-equal).
10. **`migrate_v32_to_v33.py --check` was re-pointed** at `legacy-v34/`'s schemas and extended by the second step. Otherwise v3.5 would fail a v3.4 check by design.
11. **`cap_display_at` returns a null title with a figure.** The page's own title (`Market cap at the last read, HH:MMZ`) is chrome. With no tape read the rule still answers `cap not read`; Now's scale line omits the whole line in that case (build-3's no-price form), and the cap slot is what §7.6a governs.
12. **`validate.py --doc`** is new: it is the command Bolo needs, and the brief asked for one.
13. **The migration leaves `meta.etag` as written.** It changes nothing it was not asked to change; a live writer stamps its own etag.

## Verification

- `python3 contract/validate.py`: exit 0, **152 passed, 0 failed** (was 140). New checks: `v34-vectors-unchanged`, `negative:V72` to `V79`, `flow-fixtures`, `flow-rule-outputs-unchanged`, `cap-display`; `board-entry-cap` rewritten (above). The page-string check `now-v33` (SPRING against pass 7 at 17:27, 16:57, 16:42 and 16:32Z, through the legacy `mc_text`) is byte-identical to v3.4's line and passes.
- `--v34-compat`: exit 1, 8 of 8 fail on v3.4. `--v33-compat`, `--v32-compat`, `--v31-compat`, `--v3-compat`: exit 1, 6 of 6, 7 of 7, 17 of 17 and 5 of 5. All four outputs are line-identical to their sections of v3.4's `VALIDATE.out` (compared by script).
- `migrate_v34_to_v35.py --check`: exit 0. `migrate_v32_to_v33.py --check`: exit 0.
- `grep -n downshift contract/*.py`: nothing. `grep -n netUsd contract/*.py contract/*.json contract/fixtures/*.json`: every hit is the refusal or its test (the `R_NET_STORED` text and its path finder in `nowat.py`, `NO_NET` in `schemas.py` and the six emitted schemas, V76's refused inputs and its description in `vectors.py` and `state-vectors.json`, and the validator's own checks). **`contract/fixtures/*.json`: no hit.**
- **Parity guard:** `build-3/scripts/dump-nowat.py`, run against the v3.5 `contract/nowat.py` into `/tmp/v35/parity/` at each file's own clocks and documents. `cmp` against `build-3/scripts/parity-*.py.json`: **9 of 9 byte-equal** (`now`, `now-since`, `now-extra`, `spring-now`, `spring-now-since`, `spring-now-extra`, `solana-now`, `now-supply`, `now-v32`). `flow` changes no existing rule output. The same guard ran after every `nowat.py` edit.
- **Generators:** `make.py` and `vectors.py` rerun give byte-identical fixtures, schemas and vectors (`sha256sum -c`). Hashes: `fixtures/now.json` `b16a9a476955a359…`, `state-vectors.json` `aa398ab55d8bb7a7…`, `nowat.py` `1bca3dace60521be…`.
- **innerText over http** (`/tmp/v35/innertext.cjs`, v3.4's harness saving full text; sha256 and length of `document.body.innerText`). The pages were build-0 `now`, `board`, `situation?f=spring` and `index`; build-1 `now`, `situation` and `index`; and every html page under `design-pass7` to `design-pass10`. The before run matched v3.4's recorded after run on all 19.
  - **build-1 and every design pass page:** identical before and after (15 of 15). build-1 reads its own `data/` copies and is not affected by the fixtures.
  - **build-0 with `build0-fixtures.py` rerun:** 4 pages differ, in exactly these lines and nothing else (full diff in `/tmp/v35/before` vs `/tmp/v35/after`):
    - `now`, `board`, `situation?f=spring`: `Contract companion-v3.3` becomes `Contract companion-v3.5`;
    - `index`: `406 fields ... carry illustrative: true` becomes `428`, and `now.json` `276 illustrative fields` becomes `298`. The 22 new ones are the flow intervals: SPRING 4, AGRIPPA 7, LUMEN 6 and MARLIN 5.
  - **build-0 restored** (choice 1): all 19 pages identical to before (`/tmp/v35/innertext.restored.json`).
  - build-3 pages read their own `data/` copies and were not rerun.
- No file under any `design-pass*` or `build-*` directory was changed by this worker: `build-0/` was restored, and `build-1/`, `build-2/`, `build-3/` and the design passes were only read. `build-3/scripts/verify-output.txt` shows as modified in git, with mtime 15:19:04 and a one-line change to a fetch-order line (`documents fixtures/now.json fixtures/spring.json` swapped to `fixtures/spring.json fixtures/now.json`). This worker never ran build-3's `verify.cjs` or wrote that file, so another process did; it is left as found. No deploy. Nothing sent. No em dashes in any file written here.

## Still open (not in this brief)

- **B10** writes the series on staging (For Bolo), then re-pins to the v3.5 `now.schema.json`. Bolo's B6 probes were not rerun for v3.5.
- **build-0 re-sync** on v3.5 (choice 1), if wanted.
- **build-4** wires pass 13's `flowAt()` to `rows[].flow` (pass 13 NOTES (e) 6), reads the null forms as the not-read hatch, and adds the About line from §7.7.
- **Pass 13's design questions** (NOTES (e) 1 to 4: totals against rates, the shared scale, whether Money in/out counts as an observation in the chips, labels at narrow moments) are page questions. The field supports all of them: totals are stored, and a rate is total / interval length. Nothing here decides them.
- Everything in CHANGES-v3.4 "Still open" stands: Bolo's re-probe and B4 re-pin (now to v3.5), the bd issue naming the winner, the provenance gate, the G2 page strings, circulating supply, B4 persistence, and `entryCap` entering `fixtures/board.json` with Board's read model.

Status: DONE
