Verification and reproduction
This chapter explains how calculations are checked and how a result can be reproduced. Five layers of verification target different classes of error; they do not constitute empirical validation of the model.
- Invariants by construction. The Rust suite asserts identities that must hold for every run. This is
cargo test --release --workspace. - Closed forms from outside the engine. The M0 fixtures were computed in exact rational arithmetic before the engine existed; the engine must reproduce them within two cents.
- An independent re-derivation. The in-browser auditor independently recomputes selected USD ledger metrics and Agreement contract rows in TypeScript and compares them with Rust. It does not validate every Coin, hedge or Monte Carlo output.
- Determinism. The same configuration and seed give byte-identical postings, so every figure is a function of a config hash and a seed.
- Named reproduction commands. Every row of the Model Card comes from a runnable example in the crate.
The historical test snapshot on 3 September 2026 recorded 88 passing tests with 2 ignored (the directional checks at the end of this chapter): 42 unit tests inside the forwardflow crate, 32 in tests/invariants.rs, 9 in tests/exposure.rs and 5 API tests in backend/src/api/forwardflow_api.rs.
Vocabulary follows the lock of 2 September 2026: the Buyer pays, the Holder receives, a stop is the Buyer ceasing payments, an early completion is the Buyer paying the remaining schedule in cash. Engine identifiers keep their older names and appear in code font: Owner is the Holder’s ledger account, Obligor(id) a Buyer’s, non_performance a stop, settled an early completion.
What the tests are allowed to assume
Spec §6 (v1.1; §6.3 and §6.5 reworded for the flat fee in v1.5) sets the rule: acceptance rests on internal invariants, not on reproducing the earlier Python engine, which ran on different inputs and is a directional reference only. So the suite has two kinds of test: gates, identities and orderings that hold by construction, where red blocks a commit; and directional checks against memo numbers, which print, never gate, and are marked #[ignore] so a plain cargo test skips them.
Tolerances sit at the top of tests/invariants.rs: money within MONEY_TOL = 0.02 dollars per Agreement lifetime against the unquantized fixtures, IRR within IRR_TOL = 0.0001 (0.01 percentage points), WAL within 0.01 months. Two cents is the rounding the fixtures do not model: convention C2 (cumulative fee rounding) wobbles at most one cent per stream, convention C1 (the final payment absorbing the schedule residual) at most one more.
The invariant suite
Every test in tests/invariants.rs and tests/exposure.rs, grouped by the spec §6 item it serves, then the groups the spec added later: the September stop (v1.5), input validation and the risk desk (v1.6).
§6.1 Ledger conservation
| Test | Guarantee |
|---|---|
conservation_to_the_cent_across_scenarios | Three runs (pure contract math; a busy 24-cohort book with every exit kind on a volatile bridge; a 37-month term with the C1 residual in play) satisfy ledger.rs::Ledger::conservation_sum() == 0 and verify_balances(). Money moves; it is never created. |
§6.2 Closed-form contract math
| Test | Guarantee |
|---|---|
closed_form_contract_math_matches_m0_fixtures | For every fixture case (36, 48, 60, 84 months; N = 0, 1, 3) the Holder’s total inflow, BTC Now’s take, payback month, WAL and monthly IRR match the fixtures within tolerance. Everything completes; nothing is suppressed. |
implied_paper_rate_matches_fixtures | contract.rs::ContractTerms::implied_monthly_rate × 12 equals the fixture’s nominal implied financing rate within 1e-5 (16.50% at 60 months). |
multi_cohort_metrics_use_gross_flows | A flat 24-cohort book of identical Agreements has exactly the per-Agreement multiple (1.329549 at the fixture price and fee) and a WAL of 31 + 11.5 = 42.5 months. outputs.rs::analyze uses gross flows; netting same-month deliveries against purchases would corrupt both. |
agreement_deserialization_rebuilds_schedule | The schedule is derived, not serialized; an Agreement round-tripped through JSON still knows its payments and fee state. |
tiny_strike_schedule_never_goes_negative | A $10 strike over 84 months leaves a non-negative final payment (convention C1), conserves, and completes. |
below_par_multiple_gets_a_negative_implied_rate | A multiple below 1.0 implies a negative rate; exactly 1.0 implies zero and a straight-line obligation. |
§6.3 The fee identity
| Test | Guarantee |
|---|---|
fee_identity_exact_to_the_cent | With N = 0 the lifetime fee equals money::cents(fee_rate × terminal) exactly at 36, 48, 60, 84 and 120 months. With N = 1 it equals cents(fee_rate × (terminal − payment 1)): the first payment is never delivered, so it carries no fee. Convention C2 in fees.rs::FeeState::split, made testable. |
paper_spread_identity | Above par the Holder’s premium over coin cost is BTC Now’s origination margin: 240 Agreements at 105% give exactly $720,000, par zero, 95% −$720,000; the revenue timeline sums to take plus spread. |
§6.4 The N-payments identity
| Test | Guarantee |
|---|---|
n_payments_identity | Holder cash with N payments retained differs from N = 0 by exactly \( \sum_{k \le N} \mathrm{PMT}_k ,(1 - f) \), and BTC Now’s take rises by the retained payments less the fee they would have carried, which is exactly what the Holder loses, at every term for N = 1 and 3. |
§6.5 Term parametricity
| Test | Guarantee |
|---|---|
term_parametricity_sweep_is_smooth_and_monotone | Sweeping 24 to 96 months at the program’s flat 5%: ContractTerms::fee_rate() stays 5% (never term-derived, spec v1.5), the payment falls, the lifetime fee equals cents(0.05 × delivered) at every term, IRR falls at a fixed multiple. No discontinuity at 60. |
§6.6 Determinism
| Test | Guarantee |
|---|---|
seeded_determinism | Two runs of the same busy config at seed 1234 serialize to identical posting lists; a different seed does not. |
dispersion_is_inert_on_a_flat_path | Strike dispersion scales with realized volatility, zero on a flat path, so the toggle changes no cent and the fixtures hold with it on. |
intramonth_dispersion_smears_strikes_and_preserves_the_mean | On a volatile bridge strikes within a cohort differ, the book-mean strike stays within 3%, conservation holds, IRR moves under 2 points. |
§6.7 Orderings that are gated
Memo numbers never gate. Orderings that must hold whatever the numbers are do.
| Test | Guarantee |
|---|---|
defaults_reduce_owner_irr | A 40% lifetime prior on a flat path, with a 12% haircut pinned so the sales are lossy, costs yield against the no-stop case at the same 105% price, produces stops and a positive shortfall. |
rational_mode_suppresses_when_nobody_is_underwater | On a strongly rising path the rational mode suppresses draws on in-the-money Buyers and the realized stop count falls below the naive mode’s. |
conviction_walks_fire_on_deep_drawdown | On a collapse to 30% of entry with X = 50%, Y = 6, all 50 Agreements walk. |
settlements_deliver_remaining_schedule_on_upside | On a run to $200,000 early completions occur, every MakeWholeDelivery posting comes from the Buyer’s account (cash-only, v1.5), and gross deliveries equal terminal less payment 1 per Agreement whatever the mix. |
haircut_and_sale_cost_are_live_inputs | Same seed and hazards, so the same stop count; Holder recovery falls from frictionless to base to harsh. |
crash_with_flow_continuing_beats_single_strike | A permanent −70% from month 1 hurts a book that keeps originating through the crater less than one that bought at the top. |
runoff_caps_size_never_rate | Stopping origination at month 6 shrinks the position and leaves the IRR identical to 1e-9. |
drawdown_multipliers_scale_defaults_by_state | The graded multiplier leaves a flat path unchanged, cuts stops on a rise (×0.5 in the money), raises them in a crash. |
take_profit_gate_blocks_settlements_below_all_in | A 0% gate silences every early completion on a flat path, where the coin never beats the $88,500 all-in price. |
take_profit_gate_opens_above_threshold | The gate opens once the path clears all-in × 1.10; a gate of 10× never opens. |
inverse_price_solver_finds_the_clearing_price | outputs.rs::solve_purchase_price recovers 1.05 from the IRR that 1.05 produces, clears a higher hurdle lower, and returns None when no Holder inflow exists. |
shock_designer_overlays_any_path | paths.rs::apply_shock: pre-shock path untouched, trough exactly 1 − Z, monotone ramp, a recovery ending at R × the unshocked level. |
The September stop (spec v1.5)
| Test | Guarantee |
|---|---|
stop_sale_waterfall_identities_across_regimes | On a rising path with naive stops every stopped row satisfies refund = min(A, max(0, V + A − P)), shortfall = max(0, R − V), surplus = max(0, V − P), delivered = scheduled deliveries after the first N + (V − refund), and returns no coin. All three regimes occur. Book refunds and surplus equal the row sums; every refund is a StopRefund posting to an Obligor; BTC Now’s take is origination plus flow fees and nothing else. |
stop_sale_regime_one_exact_cents | One stop, every cent by hand; worked below. Also pins the sale lag: 0 days sells at the missed date’s mark, 30.4375 days at the next, 18 days strictly between. |
agreement_table_reports_usd_shortfall | On a crash every row ties (net + fee = gross; capital P&L = net − purchase price; completed rows within 2 cents of expected net), only stops carry a shortfall, a lossy stop refunds nothing and returns no coin, and the book shortfall is the row sum. On a rising path refunded stops carry no shortfall. |
Inputs that fail fast
| Test | Guarantee |
|---|---|
absurd_inputs_fail_fast_with_named_input | A 1,700-month term, a 150% annual stop rate, an infinite price, a negative sale cost, a negative multiple, a negative purchase percentage, a 100% prior and a NaN prior each panic naming the spec input (input #6, #7, #1, #16, #5, #6b). The API maps these to HTTP 400. |
new_inputs_fail_fast_with_named_input | A 150% shock (#17), an origination stop at month 0 (#18) and a 200-day sale lag (#24) are rejected by name. |
The risk desk (spec v1.6, tests/exposure.rs)
| Test | Guarantee |
|---|---|
two_lines_match_the_plan_table | exposure.rs::two_lines reproduces the plan’s §1 table at the base terms: schedule line \( (88{,}500 - 1{,}475,t)/60{,}000 \), capital line \( \max(0,, 60{,}000 - 1{,}401.25,(t-1)) / 0.95 / 60{,}000 \): 1.0526 at month 1, 0.4872 at month 24, zero from month 44. Both fall every month. |
capital_line_is_piecewise_when_it_would_cross_the_schedule_line | At 160% of coin cost the early capital line sits above the Purchase Price and switches to paid + U/(1 − fee), flagged per row; at par it is direct throughout. |
greeks_vanish_on_a_riskless_book | With no exits on a flat path delta, gamma and vega are exactly zero and theta is the positive markup accrual; only the commitment delta is non-zero as of month 0. |
greeks_have_the_signs_of_a_short_put_on_a_stopping_book | On a falling bridge with stops: positive delta, negative vega. (The plan records a positive ensemble vega at the base config, where the Holder is long the surplus; on 3 September 2026 the risk endpoint at BASE_CONFIG and its own bump defaults returned about +$29,700 per vol point, so the −$24,000 in the spec’s v1.6 change block is stale. The test covers the falling case only.) |
greeks_are_sensitivities_not_draw_noise | A 1 bp bump flips at most two exits in 240 Agreements; delta at 2% and 5% bumps agrees in sign and within a factor of two. |
ladder_and_coverage_tie_to_each_other | Ladder totals equal the coverage row for the same month, cells sum to totals, the credit book row is the sum of the vintages. |
rational_boundary_walks_the_book_in_a_crash_and_spares_it_in_a_rally | Under zero believed drift on a collapse to 20% of entry all 30 Buyers cross the frontier at the first unpaid date; a believing Buyer (μ = 25%) on a rally never walks; a coin at 45% of entry at payment 1 walks at payment 1. |
bump_overlay_scales_prices_and_vol | paths.rs::apply_bump leaves month 0 alone, scales every later price, holds strikes when asked, and with a zero vol factor gives constant log returns. |
new_inputs_fail_fast_with_named_input | A negative lattice volatility is rejected as input #25. |
Unit tests inside the crate
The frontier tests in boundary.rs are the ones a risk desk will ask about: frontier_covers_every_payment_and_is_visible_from_payment_one (60 rows; at μ = 25%, σ = 41.4% the walk region is visible at payment 1 and sits between 20% and 100% of entry), pessimist_robot_walks_at_par (μ = 0 walks at or above par from the earliest dates), frontier_is_scale_invariant_in_the_strike, higher_sigma_deepens_the_frontier, walk_cost_and_belief_deepen_the_frontier, walk_region_sits_where_the_refund_is_zero (walking beats settling only where the refund is zero, so the crossing lies at most one grid step above the schedule line) and rejects_absurd_params.
The rest check one module each: contract.rs (the schedule sums to terminal at any term; $88,500, $1,475.00 and 16.50% at 60 months; the inverse input 16.50% → $1,475.07), defaults.rs (the baseline curve hits its lifetime target over reachable ages; the 60-month shape matches the actuarial buckets; custom yearly shares realize exactly), paths.rs (bridges pin both endpoints, the bootstrap is seed-stable, replay rebases), fees.rs (an exact lifetime total when the per-payment fee is not a whole cent), ledger.rs (an unquantized amount panics; conservation and balances), money.rs (half-even rounding) and outputs.rs (IRR). The five API tests check that simulate returns the fixture numbers, an invalid config maps to 400 with the named input, Monte Carlo summarizes, the risk endpoint returns the exposure layer with four frontiers of 60 rows, and the price solver recovers about $63,000 at the fixture IRR.
The M0 fixtures
M0_FIXTURES.md and tests/fixtures/m0_closed_form_fixtures.json were produced on 10 July 2026 by a Python generator in exact rational arithmetic (Fraction, no floats in the money). Every aggregate was computed by closed-form annuity identities and again by a month-by-month loop, and asserted equal; IRR and the implied rate were solved by bisection and by Newton to 1e-12. The fixtures share no code with the engine.
The scenario is pure contract math: a flat path, no stops, no early completions, a $60,000 strike, 1.475× (terminal $88,500), a purchase price of $63,000 (105% of coin cost, the default until spec v1.4 moved it to par on 12 July 2026). contract_math_config in tests/invariants.rs reproduces it: a zero-volatility bridge, BaselineCurve { lifetime: 0.0 }, zero early-completion propensity, conviction off, one Agreement.
Why the fixture fee is 0.75% per year times term years
The program’s fee is a flat 5% of every dollar delivered to the Holder (Marc, 2026-08-31; spec v1.5). The fixtures were generated under the July product’s fee, 0.75% per year of terminal value times the term in years: 2.25% at 36 months, 3.00% at 48, 3.75% at 60, 5.25% at 84, 7.50% at 120. Regenerating them at 5% would cost their independence, since the generator would be edited in the same session as the engine. So the fixture configs pin the flat rate per term to exactly the historical value:
servicing_fee_rate: dec!(0.0075) * Decimal::from(term_months) / dec!(12),
purchase_pct_of_strike: dec!(1.05),
Nothing in the engine derives the fee from the term any more; contract.rs::ContractTerms::fee_rate returns servicing_fee_rate unchanged. The fixture tests prove the arithmetic at the pinned rates; term_parametricity_sweep_is_smooth_and_monotone proves the program’s rule at 5%.
Two fixture identities recur in the auditor. Conservation: Holder total plus BTC Now take equals $88,500 at every term and every N. The fee identity: with N = 0 the lifetime fee is fee_rate × terminal to the cent ($3,318.75 at 60 months); with N ≥ 1 it is fee_rate × (terminal − N × PMT). The 60-month, N = 0 effective IRR of 13.3226% matched the Python engine’s contractual bridge IRR of 13.32% exactly.
The exact-cents stop sale, worked
stop_sale_regime_one_exact_cents is the suite’s one stop computed entirely by hand, at the base terms and program defaults: par purchase, 5% fee, 25 bp sale cost, 18-day lag. The path is a custom anchor set, 100% of entry through month 5 and 60% from month 6, extended flat. Conviction is set to X = 0, Y = 1 so the Buyer walks at the first payment date after the drawdown appears.
The Buyer makes payments 1 to 6, \( A = 6 \times 1{,}475 = 8{,}850 \). Payment 1 went to BTC Now; payments 2 to 6 were delivered, \( 5 \times 1{,}475 = 7{,}375 \). Payment 7 is missed, so the exit month is 7. engine.rs::stop_sale prices the sale at \( 7 + 18/30.4375 = 7.59 \) months, log-linearly between the marks at months 7 and 8, both $36,000 here:
\[ V = 36{,}000 \times e^{-\text{haircut}} \times (1 - 0.0025) = 35{,}910.00 . \]
The waterfall (spec v1.5, R-1033) with \( P = 88{,}500 \) and \( R = P - A = 79{,}650 \):
\[ \text{refund} = \min\big(A,\ \max(0,\ V + A - P)\big) = \min(8{,}850,\ \max(0,\ -43{,}740)) = 0 , \] \[ \text{delivered} = V - \text{refund} = 35{,}910.00, \qquad \text{shortfall} = \max(0,\ R - V) = 43{,}740.00, \qquad \text{surplus} = \max(0,\ V - P) = 0 . \]
The fee on the delivery is 5% of $35,910.00, which is $1,795.50, so the StopSaleDelivery posting to the Holder is $34,114.50. Both postings land at month \( \lceil 7.59 \rceil = 8 \). A zero refund posts nothing. Lifetime, the row shows delivered gross \( 7{,}375 + 35{,}910 = 43{,}285.00 \) and a fee of exactly 5% of that, $2,164.25: five payment fees of $73.75 plus $1,795.50, with no rounding residue, which is convention C2 doing its job.
The second half moves the lag on a path with a further anchor at 50% at month 8: a 0-day lag sells at month 7’s mark, a 30.4375-day lag at month 8’s, and 18 days lands strictly between.
The in-browser auditor
The workbench page has a section titled “The audit”. Its button fetches the current run with include_postings: true and hands the response to audit.ts::runAudit, an independent TypeScript implementation that reads only the raw postings, the per-Agreement rows and the configuration, works in exact integer cents (toCents, halfEvenDiv), and uses a scan-and-bisect IRR written from the definition. Each check reports the engine figure, the recomputed figure, the difference and a pass flag.
Below, \( V \) is the recorded sale, \( A \) the payments made, \( P \) the Purchase Price (strike × multiple), \( N \) the payments retained, \( f \) the fee rate.
A. Ledger integrity
| Check | Formula |
|---|---|
| A1 | Every posting debits one account and credits another, so \( \sum_{\text{accounts}} \Delta = 0 \) exactly. |
| A2 | Holder outflow = Σ postings from Owner; A2b: equal to Σ PurchasePrice postings, the only outflow kind. |
| A3 | Holder inflow = Σ postings to Owner. |
| A4 | Origination take = Σ OriginationFee postings (the first N payments, routed whole). |
| A5 | Servicing take = Σ FlowFee postings. |
| A6 | Total take = A4 + A5. No stop-sale share reaches BTC Now; the surplus above \( P \) is the Holder’s. |
| A7 | The revenue timeline re-derived from fee postings plus per-row paper spread at each origination month, every month within a cent, summing to take + spread within two cents. |
| A8 | Paper spread = Σ (purchase price − strike) over the rows. |
B. Metrics from postings
| Check | Formula |
|---|---|
| B1 | Net IRR: monthly net Owner flows rebuilt from postings; the \( r \) zeroing \( \sum_t \text{flow}_t / (1+r)^t \); annualized \( (1+r)^{12} - 1 \); within 0.01 pp (convention C3). If either side finds no IRR, both must. |
| B2 | WAL = \( \sum_t t \cdot \text{in}_t / \sum_t \text{in}_t \) on gross inflows, within 1e-6 months. |
| B3 | Undiscounted multiple = Σ gross inflows / Σ gross outflows, within 1e-9. |
| B4 | Payback = first month \( m > 0 \) with cumulative net cash ≥ 0. |
| B5@m | Cash recovery at marker month m (12, 24, 36 when reached) = gross cash through m ÷ total invested. |
| B6 | The cumulative net cash series ends at inflow − outflow. |
C. Contract math per Agreement
| Check | Formula |
|---|---|
| C1 | Convention C1: PMT = floor-to-cent(\( P / n \)); the last payment is \( P - (n-1),\mathrm{PMT} \); the schedule sums to \( P \) for every row. |
| C2 | Expected net = half-even\( \big((P - \text{first } N)(1 - f)\big) \). |
| C3 | Purchase price = half-even(strike × purchase percentage), per row, since strikes differ under dispersion. |
| C4 | Convention C2: lifetime fee = half-even(\( f \times \) delivered) exactly, though single postings wobble ±1 cent. |
| C5 | Delivered gross by outcome: completed, \( P - \text{first } N \); early completion, \( P \) less the retained payments made; open, the scheduled deliveries so far; stop, scheduled deliveries after the first N + \( (V - \text{refund}) \). Each row is also re-summed from its own postings. |
| C6 | Capital P&L = Holder net − purchase price. |
| C7 | Book shortfall = Σ row shortfalls. |
| C8 | Coin to Buyers = Σ row coin (early completions only; a stop returns none). |
| C9 | Σ row fees = Σ FlowFee postings. |
| C10 | Σ row origination = Σ OriginationFee postings. |
| C11 | Exit split: each of the seven counts re-tallied from row outcomes, summing to the book. |
| C12 | Per stop: refund = \( \min(A, \max(0, V + A - P)) \), shortfall = \( \max(0, (P - A) - V) \), surplus = \( \max(0, V - P) \), all exact. |
| C13 | Σ row refunds = Σ StopRefund postings; C13b: the book refund total = Σ row refunds. |
| C14 | Book surplus = Σ row surplus. |
Checks A and B see only postings and headline outputs (A7 and A8 also read each row’s purchase price and strike); checks C see the rows and the configuration, so a defect in agreement_table that the ledger did not share fails C and passes A.
The audit harness
web/app/forwardflow/audit-harness.ts runs the same runAudit from Node against the engine at http://localhost:8080 on four adversarial configurations and exits non-zero on any failure:
| Config | What it stresses |
|---|---|
| base bridge | BASE_CONFIG: 24 cohorts × 10, 43% vol bridge, 40% prior, 5%, par, 25 bp, 18-day lag, dispersion on, seed 42. |
| everything on, awkward term 48 | 1.62× over 48 months, N = 3, 105% price, custom yearly shares, rational mode, conviction, a gated early completion, a 2% haircut, 50 bp, a shock with recovery, an origination stop, seed 1337. |
| replay from Nov 2013, N = 0, no defaults | Historical replay from bar 21, no stops, 5% early-completion propensity. |
| micro-strike, term 2 | A $3.37 coin over two months with a 30% prior: every cent-rounding edge at once. |
With the engine up: cd web && npx tsx app/forwardflow/audit-harness.ts. On 3 September 2026 it printed 32/32, 32/32, 32/32 and 29/29 ties on 240, 70, 240 and 15 Agreements (18,057, 1,041, 18,346 and 68 postings). The count varies because B5 is one check per cash-recovery marker reached.
Determinism
The engine is seeded end to end with ChaCha20. engine.rs::run seeds one generator from config.seed, and that generator draws the path alone. Since spec v1.6 every Agreement owns its own stream, engine.rs::agreement_rng(seed, id), keyed from the seed and the Agreement id through splitmix64; its strike dispersion draw at origination and its monthly stop and early-completion draws come from it, and the monthly draws are taken whether or not the price makes them matter. A price bump or a changed exit elsewhere in the book changes decisions, never the random numbers behind them.
Monte Carlo (outputs.rs::run_monte_carlo) runs \( n \) simulations at seeds \( \text{seed}, \ldots, \text{seed}+n-1 \) in parallel, so run \( k \) is reproducible alone as a single simulate call at seed + k. The API memoizes identical Monte Carlo requests on the run count and the config JSON, which is sound only because of this.
One consequence: the same seed draws differently across engine versions when the draw structure changes. The archived Model Card records that v1.6’s per-Agreement streams moved the single-vintage replay median of the same morning from 38.2% to 47.9%. A figure is reproducible against the engine build and data stamped on it.
Reproducing the archived Model Card examples
The archived v2.1 Model Card §8 names w0108_refresh and the suite; the crate’s other examples print the related sets. These commands preserve the historical research recipe, not a promise that later engine versions reproduce its numbers. From backend/forwardflow:
| Command | Prints |
|---|---|
cargo run --release --example w0108_refresh | Every §6 row and the sensitivity cells at seed 42: the zero-drift ensembles, the 70% prior, the full-history stress, the paced replay over the 90 feasible starts, the single-vintage replay, and the auxiliary WAL, payback and month-12 cash. |
cargo run --release --example deck_repro | The deck figure set with the zero-drift regime pinned by year, plus sensitivity cells. |
cargo run --release --example co8_numbers | The trailing-24-month regime family and month-12 collections. |
cargo run --release --example trailing_regime | The unpinned zero-drift bootstrap at current volatility for the trailing 24 and 36 months, printing the series end and bar count first. |
cargo test --release --workspace | The suite. |
All four examples build the same SimConfig ($60,000, 1.475×, 60 months, N = 1, 5%, par, drawdown multipliers on, 2.5% early-completion propensity, seed 42) and vary only the regime and the prior. The archived Model Card’s production table is that config at 1 × 200 Agreements over 1,200 paths for the single vintage and 24 × 20 over 600 paths for the paced book. The current risk endpoint (POST /api/forwardflow/risk) returns the exposure layer for any configuration. Its response headers identify the engine build and data; the workspace retains that answer’s identity and request in its completed record and evidence export. A historical specification label alone is not a current result identity.
The tornado golden check
tornado_golden_check_vs_memo_reference is marked #[ignore] and runs only on request:
cargo test --release -p forwardflow -- --ignored golden --nocapture
It builds the memo configuration (zero-drift bootstrap from January 2017, N = 0, 105% price, 40% prior, 24 × 20, seed 42), takes the median IRR over 400 runs, and repeats for five stresses: an origination stop at month 3, a permanent −70% crash over three months with flow continuing, a 90% lifetime prior, a sale at 20% of spot (haircut \( -\ln 0.2 \)), and the behavioral floor (X = 0, Y = 2). It prints each delta beside the July 2026 reference deltas (−0.2, +2.4, −9.6, −4.3, −21.6 pp).
“Directional, non-blocking” means three things. Absolute medians are never compared, because the reference predates the v1.2 residual-coin rule, the v1.4 t+1 clock and the v1.5 stop. Only what should survive engine generations is asserted: the credit stress within ±1 pp, negative signs on the three credit and severity rows, the ordering floor ≫ prior ≫ haircut, the two flow rows under 3 pp, and the crash row negative. And the test is ignored, so no build waits on 2,400 Monte Carlo runs and no memo number can turn the suite red. Its companion memo_neighborhood_zero_drift prints the median against the memo’s 9.41% and asserts nothing.
The test’s comment records the July 2026 observation under spec v1.4: base 11.21%, the 90% prior at −9.96 pp. Run on 3 September 2026 under spec v1.6 it printed the following and failed its first gate:
| Row | Engine, 2026-09-03 | Reference, July 2026 |
|---|---|---|
| base median | 11.97% | 9.41% |
| origination stop at month 3 | +1.24 pp | −0.17 pp |
| crash −70% over 3 months, flow continues | −2.04 pp | +2.43 pp |
| 90% lifetime prior | −5.13 pp | −9.59 pp |
| sale at 20% of spot | −10.73 pp | −4.31 pp |
| behavioral floor (X = 0, Y = 2) | −32.86 pp | −21.55 pp |
The ±1 pp credit gate and the ordering (the haircut row now exceeds the prior row) do not survive the September waterfall. The 90% prior costs about half of what it cost the July engine, consistent with archived Model Card §6: under the ruled waterfall a stop whose sale covers the remaining schedule costs the Holder only future yield, and a stop in the money hands the surplus to the Holder, whereas the reference capped every recovery at the schedule. memo_neighborhood_zero_drift printed 14.64% over 200 runs the same day. Neither touches the build. Both say the July reference is no longer a neighborhood of the September product and the gates are due for a recut on the current engine. That is what non-blocking is for: the number is on the record and the suite stays green while the product moves.
Reproducing a workspace result
Save the completed run package from the workspace. Keep its request arguments, responses, engine identity and any warnings together. Research comparisons and specialist analyses need their own endpoint arguments: a simulation configuration alone does not reproduce a surface, hedge or monthly analysis.
- Confirm the engine source build and historical-data digest match the original response. A specification label alone does not identify a build.
- Use the completed request, including endpoint options, surface and hedge structure where applicable. Do not substitute a draft edited after the run.
- For a distribution, retain the seed sequence and number of runs. For a historical analysis, retain its start range and overrides.
- Compare the numerical result and its missing/ambiguous-result flags. Use the independent ledger tie-out where postings are available.
Legacy chart and CSV exports can carry a short sha8 configuration fingerprint. It is not an encoding of the configuration and is not the full run identity. A chart image alone cannot reproduce an analysis. MCP supplies a separate canonical replay object and result digest; see Read the result.
Deterministic reproduction is scoped to the same build, data and arguments. Floating-point identity across different compilers or hardware is not established by the repository’s tests.
Completed Research, preview and drift-sensitivity exports use recognized evidence package kinds with reporting.objective, capital/selection conventions and reporting.calculation_version. The verifier accepts bounded packages up to 64 MiB; a JSON draft import is a separate operation and does not restore completed results. Keep the request, raw observations and client calculation version together so another analyst can reproduce the table, not only the backend response. Old unsupported exports must not be relabeled as a verified new package without checking their contents.
What the suite does not prove
The invariant tests check the specified arithmetic and identities for the cases they exercise. They do not prove that the hazard shape, the multipliers, the early-completion propensity or the rational Buyer’s belief are right; the repository does not include an observed Agreement dataset that validates those priors. The tornado reference is stale and has not been recut. The auditor re-derives the run it is given and says nothing about whether that run’s assumptions describe the world. The limits chapter takes those up.
Entry-funded Coin checks in 0.6.0
tests/coin_funding.rs independently checks 240 par Agreements require 240 BTC with entry dispersion on or off, premium/discount funding, actual cohort counts, original-flow IRR residuals, cohort gross reconciliation, and matching unhedged/hedged funding. The same test preserves the original USD ledger return. Coin lifecycle regressions cover stopped collateral with and without refunds, final-payment stops and signed surplus exposure; independent Gaussian quadrature checks the known-stop delta. Long-sale-tail tests preserve posting dates at 18, 45, 90 and 120 days.
The September 13 allocator audit and implementation evidence live under docs/reviews/2026-09-13-coin-allocator-audit/ in the repository. A historical golden for a deliberately corrected Coin hedge changes with the release; unchanged Agreement cash and surface-node controls remain tested separately.
Monthly Coin sensitivity checks in 0.6.1
tests/coin_monthly_age.rs checks performing receipt sensitivity against independently differentiated fixed receipts at every monthly age, including the final receipt, several term lengths and different origination-payment counts. It also checks half and opposite futures sizing and zero exposure after receipt booking. The performing surface now has a node at every payment age; a three-month interpolation must not spread a receipt transition over three months.
The correction deliberately changes Coin-delta hedge cash. The lifecycle and extended-domain tests reconcile the hedge’s actual monthly cash and independently solve the resulting original BTC flow IRRs, rather than replacing unexplained aggregate goldens. Existing quarterly surface nodes and unhedged numerical controls were compared separately. Monthly construction takes approximately three times the work of the former quarterly core grid; route admission includes a retained-surface allowance, but this is not a production-capacity certification.
The frozen audit is docs/reviews/2026-09-13-separate-workspace-audit/; its separate remediation/ folder records the correction, numerical controls and browser checks. Those records preserve their original build identities. Deployment acceptance is recorded separately in docs/releases/2026-09-14-coin-usd-audit/RELEASE.md.