What is the Evidence Spine?
The Evidence Spine is an immutable, append-only trust overlay that runs across every pillar of Portfolio Planner — research, execution, and wealth. Whenever the system makes or observes a claim (a backtest result, a broker fill, a go-live promotion, a portfolio holding snapshot), it records an Evidence Event that pins that claim to the exact code version (git SHA), resolved configuration (content hash), and input data that produced it. No money figure, trade, or decision is written without provenance.
The motto is "One workspace for wealth, research, and guarded execution". The Evidence Spine turns that motto into an invariant: every claim the product makes is independently traceable, reproducible, and reconciled. "Evidence in front" is not a feature — it is an invariant the system cannot violate.
Why was this built?
Before the Evidence Spine, trust was scattered and fragile:
- No provenance anywhere. Backtests, live trades, broker fills, and portfolio holdings were persisted, but none recorded which code version, which configuration, or which input data produced them. A number on screen was a number — you had to trust it came from the right code and config.
- Go-live transition history was destroyed. Promoting a strategy from dry-run to shadow to live overwrote a mutable singleton. The confidence report that justified the promotion was recomputed on demand — never snapshotted at the moment of decision.
- Two parallel backtest systems. The options backtester and the equity research engine had different run-id conventions, different persistence, and no shared validation framework. Neither had walk-forward or out-of-sample testing.
- Two parallel reconciliation systems. The options bot and live policies each had their own reconciliation — no shared abstraction, no continuous drift-feedback loop.
- Calibration was CLI-only. The only way to check whether a backtest matched live dry-run trades was a CLI text report — nothing was persisted, and only options strategies had it.
The Evidence Spine fixes these gaps. It does not replace existing tables — it is a trust overlay that mirrors claims into a single, append-only event log at the moment they are asserted. Read models stay mutable for performance; the spine stays immutable for trust.
The Evidence Record
The core abstraction is the evidence.events table — an append-only PostgreSQL table (never UPDATE, never DELETE). Every row records:
| Column | Purpose |
|---|---|
event_id | UUIDv7 — time-ordered, globally unique |
pillar | Which pillar emitted it: research, execution, wealth, or safety |
subject_type + subject_id | What this event is about: backtest_run/r1, transition/DC, order/BO123 |
kind | What happened: CLAIMED, VALIDATED, PROMOTED, REVERTED, RECONCILED, REJECTED, or DRIFT_DETECTED |
code_sha | Git SHA of the code that produced it (null for external broker events) |
config_hash | BLAKE2b hash of the canonical config snapshot |
data_refs | JSONB — references to input data (snapshot IDs, run IDs, as-of dates) |
payload | JSONB — the claim itself (metrics, fills, thresholds, before/after) |
sig_type | Who asserts this: internal (code-asserted), broker_reported, or operator_attested |
prev_event_id | Self-referential chain — walk the full audit trail of a subject |
The Four Phases
The Evidence Spine rolls out over four cumulative phases, each building on the last. Phase 1 shipped on main. Phases 2–4 are planned.
Phase 1 — The Substrate (Complete)
Goal: Every claim gets provenance. No gates yet.
What shipped:
- The
evidence.eventsappend-only table with full schema and indexes. EvidenceStore(append + read),EvidenceWriter(auto-chaining, never crashes callers).- Provenance capture:
uuid7()(time-ordered IDs),config_hash()(BLAKE2b),git_sha()(cached process-start). - Research runs emit
CLAIMEDevents with config hash + data refs onsave_result. - Broker token events and settings changes are bridged into the spine.
- Go-live transitions emit
PROMOTED/REVERTEDevents — history is no longer destroyed.
Benefit: Any research run, any broker fill, any go-live transition can now be traced to its code version, configuration, and input data. The foundation is in place.
Phase 2 — Validation Gates
Goal: Research must earn its way forward. Nothing promotes to live capital without evidenced validation.
What will ship:
- Shared validation framework (
arb_bot/validation/): walk-forward rolling train/test splits, strict out-of-sample holdout, cost/tax realism check, fold-to-fold stability guard, persisted calibration (backtest vs. live dry-run per-trade Δ₹ with error bars). create_policyrefuses unless aVALIDATEDevent exists for the source research run.go_live()refuses without aVALIDATEDevent.- Options backtester gains cost/tax parity with the research engine.
Benefit: The promotion pipeline becomes evidence-gated. A backtest that passes in-sample but blows up out-of-sample cannot reach live capital. Calibration error bars are persisted and operator-visible — you can see, quantitatively, how closely the backtest predicts reality before committing.
Phase 3 — Unified Reconciliation & Drift Loop
Goal: Trust is bidirectional — forward validation AND continuous backward reconciliation.
What will ship:
- Single shared
Reconcilerwith pluggableAuthority(broker, CAS, etc.) andProjection(open_trades, policy positions, wealth holdings). Refactors both existing reconcilers onto it. - Holdings↔portfolio-sum reconciliation (closes Telegram-only gap).
- Drift feedback loop:
DRIFT_DETECTEDevents seed new research signals — the closed loop becomes live. /explain_signalpersists last signal per cycle;/provenance <subject_id>walks the event chain.
Benefit: Reconciliation is continuous and feeds back. A live policy that drifts from its backtest is detected, recorded, and creates a research signal — the loop closes. Every strategy decision becomes replayable post-hoc.
Phase 4 — Trust Center & Hardening
Goal: Evidence becomes a product surface — not just infrastructure.
What will ship:
- Trust Center dashboard page: one operator view showing provenance walks, drift timeline, gate status for each strategy, calibration bands, and transition history — rendered in the React dashboard.
- Daily self-audit job verifying event-chain integrity and detecting missing writers (claims without provenance).
- Retention rules consistent with existing bounded tables.
- Parity tests proving live-math = research-math exercised through the spine.
Benefit: The North-Star invariant holds: every claim is traceable, reproducible, and reconciled. The Trust Center proves it to the operator. Evidence is not assumed — it is visible and auditable.
Benefits Summary
| Problem (Before) | Solution (After) |
|---|---|
| Numbers on screen have unknown provenance | Every claim pinned to git SHA + config hash + data refs |
| Go-live transition history destroyed on each change | Append-only PROMOTED/REVERTED event chain |
| Two backtest systems share no validation | One shared validation framework with walk-forward, OOS, calibration |
| Two reconciliation systems share no abstraction | Single unified Reconciler with pluggable authorities |
| Calibration is CLI-only, options-only, not persisted | Persisted calibration table with error bars for both options and equity |
| No walk-forward or out-of-sample testing | Train/test splits + OOS holdout built into the validation gate |
| Policy holdings audit is Telegram-only (no DB row) | Drift events persisted and fed into the research feedback loop |
How to Consume — API & UI
The Evidence Spine is accessible through the EvidenceStore reader API and the /api/evidence/* dashboard endpoints (arriving in Phase 2, with the Trust Center UI in Phase 4). Phase 1 provides the storage and write paths; the query surface for operators builds incrementally.
From Python (available today)
from arb_bot.evidence import EvidenceStore
# Trace a research run's full provenance chain
store = EvidenceStore()
events = store.list_events("backtest_run", run_id)
for e in events:
print(f"{e['kind']} at {e['ts']} — code: {e['code_sha'][:7]}, config: {e['config_hash'][:8]}")
# Get the latest state for a subject
latest = store.latest_for("transition", "DC")
print(f"DC is currently: {latest['payload']['to']}")
# Walk the full event chain via prev_event_id
event = store.latest_for("transition", "DC")
while event:
print(event['kind'], event['payload'])
event = store.get(event['prev_event_id']) if event['prev_event_id'] else NoneFrom SQL (dashboard, reporting)
-- Show the full provenance chain for a research run
SELECT kind, ts, code_sha, config_hash, payload->'metrics'->>'win_rate' AS win_rate
FROM evidence.events
WHERE subject_type = 'backtest_run' AND subject_id = $1
ORDER BY ts ASC;
-- Latest transition state per strategy
SELECT DISTINCT ON (subject_id)
subject_id, kind, payload->>'to' AS state, ts
FROM evidence.events
WHERE subject_type = 'transition'
ORDER BY subject_id, ts DESC;
-- All drift events in the last 7 days
SELECT * FROM evidence.events
WHERE kind = 'DRIFT_DETECTED' AND ts > now() - INTERVAL '7 days'
ORDER BY ts DESC;From the Dashboard (Phase 2–4)
The Trust Center dashboard page (Phase 4) will provide a visual operator surface. In the meantime, Phase 2 adds API endpoints:
| Endpoint | Purpose |
|---|---|
GET /api/evidence/{subject_type}/{subject_id} | Full event chain for a subject |
GET /api/evidence/{subject_type}/{subject_id}/provenance | Provenance summary: code SHA, config, inputs |
GET /api/evidence/drift | Recent drift events with severity |
GET /api/trust/gates | Per-strategy gate status: which strategies have VALIDATED events, which are eligible for promotion |
GET /api/trust/calibration/{strategy} | Calibration error bands: backtest vs. live per-trade Δ₹ |
Safety Properties
- Never crashes trading. The spine is a trust overlay — it places no orders and never appears on the entry/exit critical path. A writer failure is logged and swallowed.
- Gates fail closed. A missing
VALIDATEDevent blocks promotion — never the reverse. Degraded mode defaults to DRY_RUN. - Operator can override, but can't override silently. Promotion gates are human-overridable, but every override emits an
operator_attestedevent. - No secrets in events.
payloadanddata_refsnever carry credentials — broker creds remain Fernet-encrypted inbrokers.broker_connections. - Isolation enforced. The evidence package must never import the live execution surface — guarded by an AST-based import isolation test.
Files
| File | Purpose |
|---|---|
db/016_evidence.sql | Schema: evidence.events table + indexes |
arb_bot/evidence/__init__.py | Public exports: EvidenceStore, EvidenceWriter, helpers |
arb_bot/evidence/store.py | Append-only EvidenceStore |
arb_bot/evidence/writer.py | EvidenceWriter facade with auto-chaining |
arb_bot/evidence/provenance.py | uuid7, config_hash, git_sha |
arb_bot/evidence/projection.py | go_live_state_from_events — rebuilds state from events |
tests/evidence/ | 21 tests: store, writer, provenance, migration, projection, isolation |
Reading More
The full design spec is at docs/superpowers/specs/2026-07-05-evidence-spine-design.md. The Phase 1 implementation plan is at docs/superpowers/plans/2026-07-05-evidence-spine-phase1.md.