Evidence Spine

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:

ColumnPurpose
event_idUUIDv7 — time-ordered, globally unique
pillarWhich pillar emitted it: research, execution, wealth, or safety
subject_type + subject_idWhat this event is about: backtest_run/r1, transition/DC, order/BO123
kindWhat happened: CLAIMED, VALIDATED, PROMOTED, REVERTED, RECONCILED, REJECTED, or DRIFT_DETECTED
code_shaGit SHA of the code that produced it (null for external broker events)
config_hashBLAKE2b hash of the canonical config snapshot
data_refsJSONB — references to input data (snapshot IDs, run IDs, as-of dates)
payloadJSONB — the claim itself (metrics, fills, thresholds, before/after)
sig_typeWho asserts this: internal (code-asserted), broker_reported, or operator_attested
prev_event_idSelf-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.events append-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 CLAIMED events with config hash + data refs on save_result.
  • Broker token events and settings changes are bridged into the spine.
  • Go-live transitions emit PROMOTED/REVERTED events — 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_policy refuses unless a VALIDATED event exists for the source research run.
  • go_live() refuses without a VALIDATED event.
  • 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 Reconciler with pluggable Authority (broker, CAS, etc.) and Projection (open_trades, policy positions, wealth holdings). Refactors both existing reconcilers onto it.
  • Holdings↔portfolio-sum reconciliation (closes Telegram-only gap).
  • Drift feedback loop: DRIFT_DETECTED events seed new research signals — the closed loop becomes live.
  • /explain_signal persists 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 provenanceEvery claim pinned to git SHA + config hash + data refs
Go-live transition history destroyed on each changeAppend-only PROMOTED/REVERTED event chain
Two backtest systems share no validationOne shared validation framework with walk-forward, OOS, calibration
Two reconciliation systems share no abstractionSingle unified Reconciler with pluggable authorities
Calibration is CLI-only, options-only, not persistedPersisted calibration table with error bars for both options and equity
No walk-forward or out-of-sample testingTrain/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 None

From 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:

EndpointPurpose
GET /api/evidence/{subject_type}/{subject_id}Full event chain for a subject
GET /api/evidence/{subject_type}/{subject_id}/provenanceProvenance summary: code SHA, config, inputs
GET /api/evidence/driftRecent drift events with severity
GET /api/trust/gatesPer-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 VALIDATED event 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_attested event.
  • No secrets in events. payload and data_refs never carry credentials — broker creds remain Fernet-encrypted in brokers.broker_connections.
  • Isolation enforced. The evidence package must never import the live execution surface — guarded by an AST-based import isolation test.

Files

FilePurpose
db/016_evidence.sqlSchema: evidence.events table + indexes
arb_bot/evidence/__init__.pyPublic exports: EvidenceStore, EvidenceWriter, helpers
arb_bot/evidence/store.pyAppend-only EvidenceStore
arb_bot/evidence/writer.pyEvidenceWriter facade with auto-chaining
arb_bot/evidence/provenance.pyuuid7, config_hash, git_sha
arb_bot/evidence/projection.pygo_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.