diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4bd175c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,56 @@ +name: Samaritan CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: samaritan-ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 11.13.0 + run_install: false + + - name: Install Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install locked dependencies + run: pnpm install --frozen-lockfile + + - name: Reject moderate or greater judge-runtime advisories + run: pnpm security:audit + + - name: Verify the complete clean-clone judge path + run: pnpm judge:check + + - name: Verify tracked public artifacts + run: pnpm public:audit + + - name: Install isolated Phase 0 capture dependencies + run: pnpm --dir phase0 install --frozen-lockfile + + - name: Typecheck Phase 0 capture tools + run: pnpm --dir phase0 exec tsc -p tsconfig.json --noEmit + + - name: Test Phase 0 capture tools + run: pnpm --dir phase0 test diff --git a/.gitignore b/.gitignore index 4c9e14a..35cd6f9 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,14 @@ data/ *.sqlite-wal # Phase 0 local secrets and raw licensed captures. +.wallet/ phase0/.wallet/ phase0/.tokens/ samples/ + +# Local design explorations, social renders, and one-off verification helpers +# are deliberately outside the public bounty submission. The production UI and +# licence-safe judge artifacts live under apps/ and public/ respectively. +content/ +design/ +.spain-belgium-verify*.ts diff --git a/README.md b/README.md index b6e1215..4d517c0 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,91 @@ # SAMARITAN -**An autonomous sports-trading system.** Deterministic signal algorithms detect market inefficiencies in real time; Claude agents act as the judgment layer; every decision is scored, ranked, and anchored on-chain. +**A governed autonomous sports-trading system.** Deterministic signal algorithms detect market inefficiencies; Claude acts only as a bounded judgment layer; deterministic code controls risk and paper execution; decisions are recorded in an append-only local ledger. Decision receipts verify offline and devnet anchoring tooling is implemented, but no anchor transaction has been submitted. - **Testing ground:** TxODDS "Trading Tools and Agents" bounty — World Cup Hackathon (Superteam Earn / Solana). 16,000 USDT track prize. **Submission deadline: July 19, 2026.** -- **Real mission:** a durable, money-making trading harness for sports and prediction markets. The hackathon is chapter one, not the book. +- **Product direction:** durable decision-control and evidence infrastructure for sports and prediction-market teams. The bounty build is paper-only. ## Status -**PHASE: PHASE 1 COMPLETE — PHASE 2 SIGNAL CORE STARTED.** The strict Node 22 workspace, canonical event contracts, evidence-bearing mapping registry, TXLine SSE clients, Gamma-refreshed Polymarket WebSocket source, streaming replay adapters, and append-only SQLite/DuckDB stores are implemented. The local research database holds 9,025,361 normalized TXLine outcomes and 85,249,582 mapped Polymarket points. Combined three-source live and two-source archive replay smokes pass. Tonight's synchronized capture supplies Phase-3 microstructure evidence while Phase 2 proceeds. +**PHASE: PHASE 4 PAPER REASONING RUNTIME IMPLEMENTED — BOUNDED EVIDENCE COLLECTION IN PROGRESS.** The strict Node 22 workspace, canonical event contracts, evidence-bearing mapping registry, TXLine SSE clients, Gamma-refreshed Polymarket WebSocket source, streaming replay adapters, append-only SQLite/DuckDB stores, feature engine, detectors, chronological research harness, bounded Claude layer, deterministic paper risk, and paper portfolio are implemented. Live and captured-replay source adapters feed the same `runPaperSession` conductor; the strategy runtime receives canonical events rather than a live/replay mode flag. This is an implemented orchestration boundary, not a claim that Samaritan is already a deployed 24/7 autonomous service. The local research database holds 9,025,361 normalized TXLine outcomes and 85,249,582 mapped Polymarket points. The synchronized Spain-Belgium study found no supporting post-TXLine STALE_QUOTE case, so that detector remains disabled. + +**Evidence correction and registration:** Samaritan invalidated its original dynamic-total result after finding a future-informed selector and inflated emission semantics, preserved the artifact, repaired both defects, and reran the unchanged chronological boundary. The corrected Total Goals–only `CONSENSUS_MOVE` family has 38 held-out normalized buy cases across 18 fixtures, mean `+132.7` probability bps after the 100 bps historical proxy, and a fixture-clustered 95% interval of `+14.3` to `+243.9` bps. This remains sampled-price historical signal evidence—not alpha, profitability, or executable fills. On July 18 Deborah registered the exact corrected v2 configuration for forward paper observation only. No qualifying v2 observation existed at registration, and the real-money gate remains closed. See [the registration record](docs/10-paper-study-v2-registration.md), [corrected v4 report](docs/research/historical-gate-study-causal-economic-v4.md), and [v1 invalidation record](docs/research/historical-gate-study-v1-invalidation.md). + +The Phase-4 paper spine runs canonical events through deterministic detection, ledgers signals before model I/O, measures Haiku/Opus wall-clock decision latency from local observation time, and waits for both that latency and the current sports-order placement delay before deterministic risk and depth-aware simulation. Haiku and Opus can submit only strict structured judgments; neither can size or place an order. Real invocation metadata can be appended to a separate local hash chain. Receipt generation accepts a reference only after that local chain and exact record verify; the portable offline receipt does not independently prove membership. This is a local integrity audit—not Anthropic/provider attestation, independently signed billing proof, or proof of model quality. The July 12 v1 registration and empty ledgers remain preserved and suspended. Registered v2 uses fresh versioned ledgers; registration removes the human protocol-status block but does not bypass fixture admission. No trading credential is connected and the real-money gate remains closed. + +Both semifinal paired captures failed closed and are inadmissible. France–Spain stopped before kickoff. England–Argentina hit a watchdog-detected Polymarket stall and never produced complete terminal manifests, final score state, public resolution, or a verified mapping. Neither fixture may be replayed into v2 or used for a real-Claude claim. + +## Public release links + +- **Hosted judge MVP:** [samaritan-txline-2026.asaborodaniel.chatgpt.site](https://samaritan-txline-2026.asaborodaniel.chatgpt.site/) +- **One-call judge evidence:** [public read-only evidence endpoint](https://samaritan-txline-2026.asaborodaniel.chatgpt.site/api/judge/evidence) +- **Demo video:** `[YOUTUBE_OR_LOOM_URL — pending recording and public playback verification]` +- **Public repository:** [github.com/Pavilion-devs/samaritan](https://github.com/Pavilion-devs/samaritan) + +The hosted MVP has passed signed-out desktop/mobile route checks and strict API-method checks. The video placeholder remains a release blocker and must be replaced only after public playback is watched end to end. + +## Judge quickstart + +Requirements: Node `22.23.1` and pnpm `11.13.0`. + +```bash +corepack enable +pnpm install --frozen-lockfile +pnpm judge +``` + +Then open `http://127.0.0.1:4173`. `pnpm judge` first runs the complete clean-clone verification gate, then serves the read-only dashboard. It needs no TXLine credential, Anthropic key, wallet, RPC write, paid subscription, or private archive. + +Command also contains an optional live TXLine connectivity pulse. A hosted runtime enables it only when all three server-side values are present: `TXLINE_JWT`, `TXLINE_API_TOKEN`, and `TXLINE_SERVICE_LEVEL_ID=12`; local development may instead use the ignored, validated `phase0/.tokens/mainnet.json`. Missing or invalid credentials produce a truthful degraded card and do not affect the frozen judge evidence. The public pulse exposes only mainnet/SL12, check time, connection status, a rounded latency bucket, aggregate fixture count, and coarse HTTP-response freshness. It never returns fixture identities, names, kickoffs, raw rows, odds, probabilities, credentials, or study observations. + +On the exact final public commit, the verification gate must prove: + +- all TypeScript checks and the complete release test suite pass; the final count must be recorded only after that commit is frozen; +- a deterministic synthetic case traverses the shared `runPaperSession` conductor from canonical events through settlement, using deterministic model stubs; +- the public Decision Receipt verifies offline; +- the frozen dashboard bundle contains only allowlisted, derived artifacts; and +- the production dashboard builds from that clean-clone bundle. + +The public experience presents two judge-demo cases while keeping historical sampled-price research as a separate third evidence class: + +1. **Captured refusal:** the Spain–Belgium replay is real synchronized evidence and correctly ends in no trade. Only bucketed TXLine movement is public. +2. **Synthetic full lifecycle:** `pnpm demo` sends 20 invented canonical events through the same `runPaperSession` conductor used by the source adapters, then exercises signal → triage → thesis → deterministic risk → intent → paper fill → close → settlement → receipt. Its two model boundaries are deterministic stubs, it makes zero external calls, and it is visibly synthetic and permanently excluded from performance evidence. + +Useful standalone commands: + +```bash +pnpm demo +pnpm receipt:verify -- public/artifacts/dashboard/synthetic-decision-receipt.json +pnpm public:audit +pnpm judge:check +``` + +The downloadable receipt is locally verified but **not Solana-anchored**. Offline prepare, explicitly human-gated devnet submission, and read-only network verification tooling exist; the bounty release intentionally accesses no signer and submits no transaction, so there is no explorer link. + +### Judge evidence map + +| Bounty criterion | Where to inspect | +|---|---| +| Core Functionality & Data Ingestion | **Command** optional derived connectivity pulse; **Matchroom** derived captured replay; [technical overview](docs/submission/technical-overview.md); exact endpoints in [API feedback](docs/submission/txline-api-feedback.md) | +| Autonomous Operation | `pnpm demo` and **Proof** demonstrate deterministic end-to-end orchestration with model stubs—not an authentic Claude trading case; the forward-paper governance boundary is in [the v2 registration record](docs/10-paper-study-v2-registration.md) | +| Clean, deterministic, defensible logic/code | Risk verdict and pre-action ledger in the downloadable receipt; detector/risk tests; identical canonical live/replay contracts | +| Innovation/novelty | Strict Claude thesis boundary plus non-overridable deterministic authority, fail-closed evidence admission, and portable decision receipts | +| Production readiness | `pnpm judge:check`, restart-safe portfolio reconstruction, public-artifact audit, security headers, explicit failure states, and the signed-out [hosted MVP](https://samaritan-txline-2026.asaborodaniel.chatgpt.site/) | + +Every public surface separates **real captured retrospective evidence**, **historical sampled-price research**, and **synthetic engineering proof**. Registration is a governance fact, not a performance result. -Phase 2 now has rolling velocity/acceleration/EWMA/CUSUM features, feed freshness and score context, research-gated `CONSENSUS_MOVE`/`XMARKET_DIVERGENCE`/`FADER_CANDIDATE`, timestamp-merged DuckDB replay, bounded forward-horizon labeling, streaming threshold-grid/classification/Brier/CLV metrics, and an evidence-weighted dynamic main-total selector. Numeric thresholds and selector weights remain deliberately unset outside explicit tests and smoke studies until the Phase-3 gate. Decisions locked so far: | Decision | Choice | |---|---| -| Execution modes | **Both** — paper trading (verifiable ledger) AND real-money Polymarket execution | -| Wallets | Solana mainnet wallet ready (for free real-time data tier); devnet also for testing | +| Execution modes | **Bounty: paper only.** Real-money Polymarket execution remains a gated post-bounty architecture item | +| Wallets | No judge wallet and no execution wallet connected; any devnet proof submission is explicitly human-gated | | Data source | TXLine (TxODDS) — free World Cup tier, Service Level 12 (real-time, mainnet) | | v1 markets | Match Result + dynamically selected main full-time Over/Under only | | Signal source reality | Free tier exposes `TXLineStablePriceDemargined` consensus, not named bookmakers; Momentum uses `CONSENSUS_MOVE`, not bookmaker leader-lag | | AI | Claude — Opus 4.8 (analysis), Haiku 4.5 (triage); custom harness, NOT a prompt template | | Claude budget | $200 operating target; $300 hard project ceiling | -| Real-money envelope | $50 bankroll ceiling; $15 maximum aggregate live exposure initially; per-trade/drawdown caps set at the Phase-3 gate | +| Real-money architecture | Disabled and outside the bounty proof; no production trading credential or adapter is connected | | Stack | Node 22 + strict TypeScript/pnpm/vitest; SQLite + DuckDB | ## Docs @@ -33,9 +97,16 @@ Decisions locked so far: | [docs/03-strategy-playbook.md](docs/03-strategy-playbook.md) | The edges, the math, the risk framework — and the open questions we're discussing before building | | [docs/04-algorithms.md](docs/04-algorithms.md) | The detector math: trigger conditions, free parameters, sizing, scoring — the spec the gate study fills with numbers | | [docs/05-harness.md](docs/05-harness.md) | The agent harness machine room: case lifecycle, context assembly, two-speed deliberation, memory stores, hooks, budgets | +| [docs/06-gate-study.md](docs/06-gate-study.md) | Phase-3 historical/live evidence, detector decisions, cost sensitivity, and the evidence behind the now-registered forward-paper-only v2 protocol | +| [docs/UI.md](docs/UI.md) | Approved Floodlight UI/UX direction: information architecture, visual system, evidence states, replay behavior, and acceptance criteria | | [docs/PHASE-1-STATUS.md](docs/PHASE-1-STATUS.md) | Implemented backbone contracts, verification evidence, and the remaining Phase-1 work | | [docs/PHASE-2-STATUS.md](docs/PHASE-2-STATUS.md) | Current feature/detector/replay implementation, real-archive smoke evidence, and open Phase-2 work | -| [plan.md](plan.md) | 7 phases to submission (July 18) with exit criteria and the mandatory Phase-3 gate | +| [docs/PHASE-4-STATUS.md](docs/PHASE-4-STATUS.md) | Strict reasoning contracts, deterministic paper-risk/execution slice, verification, and remaining integration work | +| [docs/08-winning-submission-plan.md](docs/08-winning-submission-plan.md) | Release-candidate win plan, evidence checkpoint, remaining authority gates, and demo strategy | +| [docs/10-paper-study-v2-registration.md](docs/10-paper-study-v2-registration.md) | Deborah's exact forward-paper-only v2 registration, frozen limits, stopping rules, and closed real-money boundary | +| [docs/submission/public-release-scope.md](docs/submission/public-release-scope.md) | Exact public inclusion/exclusion rules, clean-clone gate, and dependency isolation | +| [docs/submission/third-party-notices.md](docs/submission/third-party-notices.md) | Data-service, font, asset, and software attribution boundaries | +| [plan.md](plan.md) | 7 phases to the July 19 hard deadline, with exit criteria and the mandatory Phase-3 gate | | [AGENTS.md](AGENTS.md) | Hard invariants + conventions for any AI agent writing Samaritan code | ## Timeline pressure diff --git a/apps/dashboard/.openai/hosting.json b/apps/dashboard/.openai/hosting.json new file mode 100644 index 0000000..8f94828 --- /dev/null +++ b/apps/dashboard/.openai/hosting.json @@ -0,0 +1 @@ +{"project_id":"appgprj_6a5b79be488081919908518fe0d45c8a"} diff --git a/apps/dashboard/index.html b/apps/dashboard/index.html new file mode 100644 index 0000000..fe246ca --- /dev/null +++ b/apps/dashboard/index.html @@ -0,0 +1,17 @@ + + + + + + + + Samaritan Matchroom + + +
+ + + diff --git a/apps/dashboard/src/App.tsx b/apps/dashboard/src/App.tsx new file mode 100644 index 0000000..87d8f70 --- /dev/null +++ b/apps/dashboard/src/App.tsx @@ -0,0 +1,13 @@ +import { CasebookApp } from "./Casebook"; +import { CommandApp } from "./Command"; +import { MatchroomApp } from "./Matchroom"; +import { ProofApp } from "./Proof"; +import { StudyApp } from "./Study"; + +export function App() { + if (window.location.pathname === "/matchroom") return ; + if (window.location.pathname === "/casebook") return ; + if (window.location.pathname === "/study") return ; + if (window.location.pathname === "/proof") return ; + return ; +} diff --git a/apps/dashboard/src/Casebook.tsx b/apps/dashboard/src/Casebook.tsx new file mode 100644 index 0000000..27df73c --- /dev/null +++ b/apps/dashboard/src/Casebook.tsx @@ -0,0 +1,317 @@ +import { startTransition, useDeferredValue, useEffect, useState } from "react"; +import type { CasebookCaseSummary, CasebookSnapshot } from "../../../src/dash/public-contract"; +import { loadCasebook } from "./api"; +import { BrandMark, Icon, MobileNavigation, Navigation, ProvenanceBadge, Topbar } from "./Shell"; + +type Filters = { + fixture: string; + market: string; + detector: string; + disposition: string; + outcome: string; + lane: string; + source: string; + date: string; +}; + +const emptyFilters: Filters = { + fixture: "", + market: "", + detector: "", + disposition: "", + outcome: "", + lane: "", + source: "", + date: "" +}; + +function percentage(value: number, digits = 2) { + return `${(value * 100).toFixed(digits)}%`; +} + +function points(value: number) { + return `${value >= 0 ? "+" : ""}${(value * 100).toFixed(2)}pp`; +} + +function movementBps(value: number) { + return `${value >= 0 ? "+" : ""}${value.toLocaleString("en-US")} bps`; +} + +function compactHash(value: string) { + return `${value.slice(0, 12)}…${value.slice(-8)}`; +} + +function caseDate(value: string) { + return new Intl.DateTimeFormat("en-GB", { + day: "2-digit", + month: "short", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + timeZone: "UTC", + timeZoneName: "short" + }).format(new Date(value)); +} + +function SelectFilter({ label, value, options, onChange }: { + label: string; + value: string; + options: string[]; + onChange: (value: string) => void; +}) { + return ( + + ); +} + +function CasebookHero({ snapshot }: { snapshot: CasebookSnapshot }) { + return ( +
+
+ +

Every measured observation.
One honest exemplar.

+

The index contains all {snapshot.corpus.marketEventCases} goal×market observations reported by this one verified capture replay. The detail pane is a deterministic exemplar, not the whole corpus or a performance sample.

+
+
+
Goal×market observations{snapshot.statistics.totalCases.toString().padStart(2, "0")}{snapshot.corpus.goalEvents} goals · one captured fixture
+
Moved before TXLine{snapshot.corpus.movedBeforeTxlineCases}pre-trigger repricing observed
+
No material reprice{snapshot.corpus.noMaterialRepriceCases}inside the 30-second window
+
Executed{snapshot.statistics.executedCases}operational execution not reached
+
Execution runtimeNot enteredretrospective research only
+
Full reported corpus projected{snapshot.statistics.reconciledCases} observations internally reconciled · capture identity checked separately
+
+
+ ); +} + +function CaseFilters({ snapshot, search, onSearch, filters, onFilter, onClear }: { + snapshot: CasebookSnapshot; + search: string; + onSearch: (value: string) => void; + filters: Filters; + onFilter: (key: keyof Filters, value: string) => void; + onClear: () => void; +}) { + const dateOptions = [...new Set(snapshot.cases.map((item) => item.occurredAt.slice(0, 10)))].sort(); + const hasFilters = Boolean(search || Object.values(filters).some(Boolean)); + return ( +
+ +
+ onFilter("fixture", value)} /> + onFilter("market", value)} /> + onFilter("detector", value)} /> + onFilter("disposition", value)} /> + onFilter("outcome", value)} /> + onFilter("lane", value)} /> + onFilter("source", value)} /> + onFilter("date", value)} /> +
+ +
+ ); +} + +function CaseIndex({ cases, total, nextEvidence, selectedCaseId }: { + cases: CasebookCaseSummary[]; + total: number; + nextEvidence: CasebookSnapshot["nextEvidence"]; + selectedCaseId: string; +}) { + return ( + + ); +} + +function DecisionRail({ selected }: { selected: CasebookSnapshot["selectedCase"] }) { + const finalState = selected.decision.ordersPlaced === 0 ? "Execution not entered" : `${selected.decision.ordersPlaced} orders placed`; + return ( +
+
Retrospective lifecycle

Feasibility rail

{finalState}
+
+ {selected.lifecycle.map((stage, index) => ( +
+ {String(index + 1).padStart(2, "0")} + + {stage.label}{stage.detail} + {stage.timingLabel} +
+ ))} +
+
+ ); +} + +function EvidenceReadout({ selected }: { selected: CasebookSnapshot["selectedCase"] }) { + const readout = selected.evidenceReadout; + return ( +
+
Captured executable-book evidence

What the capture contained

Moved first
+
+
TXLine movement{movementBps(readout.consensusMoveFromBaselineBps)}25-bps bucket · level withheld
+
Best bid{percentage(readout.bestBid)}Executable book
+
Best ask{percentage(readout.bestAsk)}Executable entry
+
Spread{points(readout.spread)}At signal observation
+
Public-data boundaryRelative onlyNo exact TXLine level or series
+
Pre-trigger market move{movementBps(readout.preTriggerMarketMoveBps)}Before TXLine first seen
+
+
Timing conclusion{readout.movementConclusion}

The public book was already repriced. The timing sequence did not support a tradeable stale quote.

+
+ ); +} + +function AnalysisBoundary({ selected }: { selected: CasebookSnapshot["selectedCase"] }) { + return ( +
+
+ +
Analyst boundary

No operational thesis requested

Retrospective feasibility ended before the analyst stage; no Claude thesis ran for this captured case.

Invalidation: {selected.analysis.invalidation}
+
+
+ +
Execution boundary

Operational cost not applicable

{selected.analysis.costReason}

Research-only path · execution and wallet layers were not entered
+
+
+ ); +} + +function EvidenceSequence({ selected }: { selected: CasebookSnapshot["selectedCase"] }) { + return ( +
+
Derived captured sequence

Captured evidence timeline

{selected.evidence.length} observations
+
MomentSourceObservationBest askAssessment
+ {selected.evidence.map((row) => ( +
+ {row.offsetLabel}{row.observedAt.slice(11, 23)} UTC + {row.source} + {row.observation} + {percentage(row.bestAsk)} + {row.assessment} +
+ ))} +
+ ); +} + +function CaseDetail({ snapshot }: { snapshot: CasebookSnapshot }) { + const selected = snapshot.selectedCase; + return ( +
+
+
Deterministic detail exemplar{selected.summary.caseId}{caseDate(selected.summary.occurredAt)}
+
+ {selected.match.home.code}{selected.match.home.name} + {selected.match.clockLabel}{selected.match.scoreAtCursor.home}–{selected.match.scoreAtCursor.away}goal {selected.summary.goalOrdinal} first seen + {selected.match.away.name}{selected.match.away.code} +
+
Retrospective feasibility verdict

{selected.summary.reason}.

{selected.decision.explanation}

+ Open captured Matchroom replay +
+ + + + +
+ Underlying capture identity checkedCorpus is locally committed, not a member of the replay identity hash + Identity hash{compactHash(selected.proof.identityHash)} + Corpus SHA-256{compactHash(selected.proof.corpusCommitment)} + Derived evidence only +
+
+ ); +} + +function matchesFilters(item: CasebookCaseSummary, search: string, filters: Filters) { + const haystack = [item.caseId, item.fixtureLabel, item.marketLabel, item.detector, item.reason, item.disposition].join(" ").toLowerCase(); + return (!search || haystack.includes(search.toLowerCase())) && + (!filters.fixture || item.fixtureLabel === filters.fixture) && + (!filters.market || item.marketFamily === filters.market) && + (!filters.detector || item.detector === filters.detector) && + (!filters.disposition || item.disposition === filters.disposition) && + (!filters.outcome || item.executionOutcome === filters.outcome) && + (!filters.lane || item.evidenceLane === filters.lane) && + (!filters.source || item.source === filters.source) && + (!filters.date || item.occurredAt.startsWith(filters.date)); +} + +function CasebookView({ snapshot }: { snapshot: CasebookSnapshot }) { + const [search, setSearch] = useState(""); + const deferredSearch = useDeferredValue(search.trim()); + const [filters, setFilters] = useState(emptyFilters); + const visibleCases = snapshot.cases.filter((item) => matchesFilters(item, deferredSearch, filters)); + const selectedExemplarVisible = visibleCases.some((item) => item.caseId === snapshot.corpus.selectedExemplar.caseId); + const updateFilter = (key: keyof Filters, value: string) => setFilters((current) => ({ ...current, [key]: value })); + const clear = () => { setSearch(""); setFilters(emptyFilters); }; + return ( +
+ +
+ +
+ + +
+ + {selectedExemplarVisible ? :

{visibleCases.length === 0 ? "No observation matches" : "Detail exemplar filtered out"}

{visibleCases.length === 0 ? "The current filters exclude every reported observation." : "The visible rows remain part of the complete corpus, but only the explicitly marked exemplar has the three-state Matchroom detail."} Clear filters to reopen the selected exemplar.

} +
+
+ +
+
+ ); +} + +function CasebookLoading() { + return
Samaritan / Casebook

Checking the captured corpus

Projecting every reported goal×market observation and reconciling it with the local replay evidence.

; +} + +function CasebookError({ retry }: { retry: () => void }) { + return
Fail-closed boundary

Casebook evidence unavailable

The captured replay record changed or could not be reconstructed. Samaritan will not display a partial or fabricated case.

; +} + +export function CasebookApp() { + const [snapshot, setSnapshot] = useState(null); + const [failure, setFailure] = useState(false); + const [attempt, setAttempt] = useState(0); + + useEffect(() => { + const controller = new AbortController(); + setFailure(false); + loadCasebook(controller.signal) + .then((nextSnapshot) => startTransition(() => setSnapshot(nextSnapshot))) + .catch((error: unknown) => { + if (error instanceof DOMException && error.name === "AbortError") return; + setFailure(true); + }); + return () => controller.abort(); + }, [attempt]); + + if (failure) return { setSnapshot(null); setAttempt((value) => value + 1); }} />; + if (!snapshot) return ; + return ; +} diff --git a/apps/dashboard/src/Command.tsx b/apps/dashboard/src/Command.tsx new file mode 100644 index 0000000..058a808 --- /dev/null +++ b/apps/dashboard/src/Command.tsx @@ -0,0 +1,291 @@ +import { startTransition, useEffect, useState } from "react"; +import type { + CommandFixture, + CommandSnapshot, + PublicBookPoint +} from "../../../src/dash/public-contract"; +import { loadCommand, loadTxlinePulse, type TxlinePulse } from "./api"; +import { frozenCaptureWindowLabel } from "./command-schedule"; +import { BrandMark, Icon, MobileNavigation, Navigation, ProvenanceBadge, Topbar } from "./Shell"; + +function percent(value: number, digits = 2) { + return `${(value * 100).toFixed(digits)}%`; +} + +function movementBps(value: number) { + return `${value >= 0 ? "+" : ""}${value.toLocaleString("en-US")} bps`; +} + +function compactHash(value: string) { + return `${value.slice(0, 10)}…${value.slice(-7)}`; +} + +function dateParts(value: string) { + const date = new Date(value); + return { + day: new Intl.DateTimeFormat("en-GB", { day: "2-digit", timeZone: "UTC" }).format(date), + month: new Intl.DateTimeFormat("en-GB", { month: "short", timeZone: "UTC" }).format(date).toUpperCase(), + time: new Intl.DateTimeFormat("en-GB", { hour: "2-digit", minute: "2-digit", hour12: false, timeZone: "UTC" }).format(date) + }; +} + +function MiniProbabilityChart({ featured }: { featured: CommandSnapshot["featuredCase"] }) { + const points: PublicBookPoint[] = featured.chart; + const frame = { left: 28, right: 624, top: 24, bottom: 194 }; + const minimumOffset = Math.min(...points.map((point) => point.offsetMs)); + const maximumOffset = Math.max(...points.map((point) => point.offsetMs)); + const goalPoint = points.find((point) => point.offsetMs === 0); + if (!goalPoint || minimumOffset === maximumOffset) { + throw new Error("Featured capture chart is missing its trigger-aligned evidence window"); + } + const x = (offset: number) => frame.left + ((offset - minimumOffset) / (maximumOffset - minimumOffset)) * (frame.right - frame.left); + const y = (probability: number) => frame.top + ((0.3 - probability) / 0.15) * (frame.bottom - frame.top); + const path = (value: (point: PublicBookPoint) => number) => points.map((point, index) => `${index === 0 ? "M" : "L"}${x(point.offsetMs).toFixed(1)} ${y(value(point)).toFixed(1)}`).join(" "); + const bid = path((point) => point.bestBid); + const ask = path((point) => point.bestAsk); + const bidReverse = [...points].reverse().map((point) => `L${x(point.offsetMs).toFixed(1)} ${y(point.bestBid).toFixed(1)}`).join(" "); + const goalX = x(0); + return ( +
+
Public executable bookBest bid Best ask
+ + {featured.home.name}–{featured.away.name} public {featured.marketOutcomeLabel} book around the first goal + The public executable bid and ask repriced before the goal reached Samaritan. Exact TXLine probability levels are withheld. + + + + + + GOAL FIRST SEEN + T−5s{featured.clockLabel}T+30s + +
+ ); +} + +function SystemDeck({ snapshot }: { snapshot: CommandSnapshot }) { + return ( +
+
+ + Snapshot posture{snapshot.system.label}{snapshot.system.detail} +
+
+ {snapshot.system.feeds.map((feed) => ( +
+ {feed.statusLabel}{feed.label}{feed.detail} +
+ ))} +
+
Offline snapshotGenerated artifact · read only
+
+ ); +} + +function LiveTxlinePulse() { + const [pulse, setPulse] = useState(null); + const [failed, setFailed] = useState(false); + const [attempt, setAttempt] = useState(0); + + useEffect(() => { + const controller = new AbortController(); + setFailed(false); + loadTxlinePulse(controller.signal) + .then((next) => startTransition(() => setPulse(next))) + .catch((error: unknown) => { + if (error instanceof DOMException && error.name === "AbortError") return; + setFailed(true); + }); + const refresh = window.setTimeout(() => setAttempt((value) => value + 1), 60_000); + return () => { + controller.abort(); + window.clearTimeout(refresh); + }; + }, [attempt]); + + const connected = pulse?.status === "connected" && !failed; + const status = failed ? "degraded" : pulse?.status ?? "checking"; + const freshness = failed ? "unknown" : pulse?.freshnessClass ?? "unknown"; + const checkedAt = pulse?.checkedAt; + return ( +
+
+ LIVE DERIVED METADATA · NOT STUDY EVIDENCE + {status} +
+
+
Official server-side checkTXLine mainnet · SL12{connected ? "Fixture snapshot reached through the credential boundary." : "Connectivity is unavailable or degraded; no feed state is inferred."}
+
Latency bucket{pulse?.latencyMsRounded === null || pulse === null ? "—" : `≤ ${pulse.latencyMsRounded} ms`}
+
Aggregate fixtures{pulse?.aggregateFixtureCount ?? "—"}
+
Response freshness{freshness}
+
Checked{checkedAt ? `${checkedAt.slice(11, 19)} UTC` : "Waiting"}
+
+
Connectivity metadata only. No fixture identity, kickoff, raw row, odds, probability, or v2 observation is exposed or admitted.
+
+ ); +} + +function FeaturedCase({ snapshot }: { snapshot: CommandSnapshot }) { + const featured = snapshot.featuredCase; + const minutes = Math.floor(featured.clockSeconds / 60); + const seconds = featured.clockSeconds % 60; + const orderLabel = featured.ordersPlaced === 0 ? "Execution runtime not entered" : `${featured.ordersPlaced} orders placed`; + return ( +
+
+
Case {featured.caseId}
+
+ {featured.home.code}{featured.home.name} + {featured.clockLabel}{featured.scoreLabel}first goal seen + {featured.away.name}{featured.away.code} +
+ Retrospective opportunity review + +

{featured.conclusion}

+
+
TXLine movement{movementBps(featured.consensusMoveFromBaselineBps)}25-bps bucket · level withheld
+
Executable ask{percent(featured.bestAsk)}
+
Pre-trigger market move{movementBps(featured.preTriggerMarketMoveBps)}
+
+
Retrospective feasibility verdict{orderLabel} · research only
+ Open captured case replay +
+
+ +
Market repriced first{movementBps(featured.preTriggerMarketMoveBps)}public {featured.marketOutcomeLabel} book move before TXLine first seen
+
+
+ ); +} + +function FixtureCard({ fixture, browserNowMs, lead }: { fixture: CommandFixture; browserNowMs: number; lead: boolean }) { + const kickoff = dateParts(fixture.kickoffUtc); + const capture = dateParts(fixture.captureStartUtc); + return ( +
+
{kickoff.day}{kickoff.month}
+
+
At export: {fixture.statusLabel}{frozenCaptureWindowLabel(fixture.captureStartUtc, fixture.captureEndUtc, browserNowMs)}
+
{fixture.home.code}{fixture.home.name}vs{fixture.away.name}{fixture.away.code}
+
Capture begins{capture.time} UTCKickoff{kickoff.time} UTC
+
Capture only{fixture.identityStatus === "exact_match_confirmed" ? "Identity confirmed at export" : "Historical config reviewed at export"} · non-tradeable
+
+
+ ); +} + +function CaptureSchedule({ snapshot }: { snapshot: CommandSnapshot }) { + const [browserNowMs, setBrowserNowMs] = useState(() => Date.now()); + useEffect(() => { + const refresh = window.setInterval(() => setBrowserNowMs(Date.now()), 60_000); + return () => window.clearInterval(refresh); + }, []); + return ( + + ); +} + +function RecentCases({ snapshot }: { snapshot: CommandSnapshot }) { + return ( +
+
Captured case record

Replay-checked captured cases

Open Casebook
+
CaseMarket readEvidenceOutcome
+ {snapshot.recentCases.map((item) => ( + + {item.home.code}{item.fixtureLabel}{item.caseId} · retrospective feasibility + {item.marketLabel}{movementBps(item.preTriggerMarketMoveBps)} before signal + Real capture · retrospective{item.canonicalEvents.toLocaleString("en-US")} events + {item.dispositionLabel}{item.reason} + + ))} +
{snapshot.additionalCaseState.label}{snapshot.additionalCaseState.detail}
+
+ ); +} + +function StudyPanel({ snapshot }: { snapshot: CommandSnapshot }) { + const study = snapshot.study; + const counts = study.qualifyingCounts; + const observationCopy = counts.signals === 0 + ? "Zero fresh observations qualify." + : `${counts.signals} fresh signal${counts.signals === 1 ? " qualifies" : "s qualify"} from the registered v2 ledger.`; + return ( +
+
Evidence governance

Paper protocol audit

{study.statusLabel}
+
V2 is registered for forward paper only.{observationCopy} Invalidated v1 remains separate audit history; the real-money gate is closed.
+
+
{counts.filledMatches} / {study.requiredFilledMatches}fresh filled matches
+
{counts.fills} / {study.requiredFills}fresh fills
+
+
Protocol statusActive forward paperConfig hash{compactHash(study.configHash)}
+
+ ); +} + +function CommandProof({ snapshot }: { snapshot: CommandSnapshot }) { + const validLedgerChains = [snapshot.proof.bountyLedgerValid, snapshot.proof.longRunLedgerValid].filter(Boolean).length; + return ( + + ); +} + +function CommandView({ snapshot }: { snapshot: CommandSnapshot }) { + return ( +
+ +
+ + +
+ +
+
+
+ +
+
+ ); +} + +function CommandLoading() { + return
Samaritan / Command

Assembling the evidence desk

Revalidating configured fixtures, offline ledger state, and the captured replay before anything is shown.

; +} + +function CommandError({ retry }: { retry: () => void }) { + return
Fail-closed boundary

Command evidence unavailable

A fixture or evidence identity could not be verified. Samaritan will not substitute stale or fabricated artifact state.

; +} + +export function CommandApp() { + const [snapshot, setSnapshot] = useState(null); + const [failure, setFailure] = useState(false); + const [attempt, setAttempt] = useState(0); + + useEffect(() => { + const controller = new AbortController(); + setFailure(false); + loadCommand(controller.signal) + .then((nextSnapshot) => startTransition(() => setSnapshot(nextSnapshot))) + .catch((error: unknown) => { + if (error instanceof DOMException && error.name === "AbortError") return; + setFailure(true); + }); + return () => controller.abort(); + }, [attempt]); + + if (failure) return { setSnapshot(null); setAttempt((value) => value + 1); }} />; + if (!snapshot) return ; + return ; +} diff --git a/apps/dashboard/src/Matchroom.tsx b/apps/dashboard/src/Matchroom.tsx new file mode 100644 index 0000000..60934a0 --- /dev/null +++ b/apps/dashboard/src/Matchroom.tsx @@ -0,0 +1,370 @@ +import { + startTransition, + useEffect, + useEffectEvent, + useState +} from "react"; +import type { + MatchroomSnapshot, + PublicBookPoint, + ReplayState, + ReplayStepId +} from "../../../src/dash/public-contract"; +import { loadMatchroom } from "./api"; +import { BrandMark, Icon, MobileNavigation, Navigation, ProvenanceBadge, Topbar } from "./Shell"; + +const replayOrder: ReplayStepId[] = ["pre", "goal", "post"]; + +function percent(value: number, digits = 2) { + return `${(value * 100).toFixed(digits)}%`; +} + +function points(value: number) { + const amount = value * 100; + return `${amount >= 0 ? "+" : ""}${amount.toFixed(2)}pp`; +} + +function movementBps(value: number) { + return `${value >= 0 ? "+" : ""}${value.toLocaleString("en-US")} bps`; +} + +function utcTime(value: string) { + return `${new Date(value).toISOString().slice(11, 23)} UTC`; +} + +function originalDate(value: string) { + return new Intl.DateTimeFormat("en-GB", { + day: "2-digit", + month: "short", + year: "numeric", + timeZone: "UTC" + }).format(new Date(`${value}T00:00:00Z`)); +} + +function compactHash(value: string) { + return `${value.slice(0, 18)}…${value.slice(-7)}`; +} + +function formatDuration(ms: number) { + return ms >= 1000 ? `${(ms / 1000).toFixed(3)}s` : `${ms}ms`; +} + +function ContextBar({ snapshot }: { snapshot: MatchroomSnapshot }) { + return ( +
+
+ + {snapshot.match.competition} · {snapshot.match.stage}{snapshot.match.home.name} vs {snapshot.match.away.name} + +
+ +
+ + Original match date{originalDate(snapshot.match.originalMatchDate)} +
+
+ ); +} + +function MatchMasthead({ snapshot }: { snapshot: MatchroomSnapshot }) { + return ( +
+
+ + Captured replay · public reference +
+
+ {snapshot.match.home.code} + {snapshot.match.home.name}{snapshot.match.home.code} +
+
+ {snapshot.match.clockLabel} +
{snapshot.match.scoreAtCursor.home}:{snapshot.match.scoreAtCursor.away}
+ Goal {snapshot.match.goalOrdinal} first seen +
+
+ {snapshot.match.away.name}{snapshot.match.away.code} + {snapshot.match.away.code} +
+
+ Exact market + {snapshot.market.label} + {snapshot.market.period} · research-only mapping +
+
+ ); +} + +const chartFrame = { left: 62, right: 830, top: 27, bottom: 282 }; +const chartMinimum = 0.15; +const chartMaximum = 0.3; + +function chartX(offset: number, minimum: number, maximum: number) { + return chartFrame.left + ((offset - minimum) / (maximum - minimum)) * (chartFrame.right - chartFrame.left); +} + +function chartY(probability: number) { + return chartFrame.top + ((chartMaximum - probability) / (chartMaximum - chartMinimum)) * (chartFrame.bottom - chartFrame.top); +} + +function linePath(pointsToDraw: PublicBookPoint[], value: (point: PublicBookPoint) => number, minimum: number, maximum: number) { + return pointsToDraw.map((point, index) => `${index === 0 ? "M" : "L"}${chartX(point.offsetMs, minimum, maximum).toFixed(1)} ${chartY(value(point)).toFixed(1)}`).join(" "); +} + +function chartSegments(snapshot: MatchroomSnapshot) { + const pointsToDraw = snapshot.replay.chart; + if (snapshot.replay.availabilityGaps.length === 0) return [pointsToDraw]; + const firstSeen = Date.parse(snapshot.replay.firstSeenAt); + const gapOffsets = snapshot.replay.availabilityGaps.map((gap) => ({ + start: Date.parse(gap.startedAt) - firstSeen, + end: Date.parse(gap.endedAt) - firstSeen + })); + const segments: PublicBookPoint[][] = []; + let current: PublicBookPoint[] = []; + for (const point of pointsToDraw) { + const previous = current.at(-1); + if (previous && gapOffsets.some((gap) => previous.offsetMs <= gap.end && point.offsetMs >= gap.start)) { + if (current.length > 1) segments.push(current); + current = []; + } + current.push(point); + } + if (current.length > 1) segments.push(current); + return segments; +} + +function ProbabilityChart({ snapshot, state }: { snapshot: MatchroomSnapshot; state: ReplayState }) { + const minimum = Math.min(...snapshot.replay.chart.map((point) => point.offsetMs)); + const maximum = Math.max(...snapshot.replay.chart.map((point) => point.offsetMs)); + const segments = chartSegments(snapshot); + const cursorX = chartX(state.offsetMs, minimum, maximum); + const goalX = chartX(0, minimum, maximum); + + return ( +
+ + Public {snapshot.market.outcome} order book around selected goal {snapshot.match.goalOrdinal} + The public executable bid and ask repriced before the goal reached Samaritan. Exact TXLine probability levels are withheld. + + + + {segments.map((segment, index) => { + const ask = linePath(segment, (point) => point.bestAsk, minimum, maximum); + const bidReverse = [...segment].reverse().map((point) => `L${chartX(point.offsetMs, minimum, maximum).toFixed(1)} ${chartY(point.bestBid).toFixed(1)}`).join(" "); + return ; + })} + {segments.map((segment, index) => point.bestBid, minimum, maximum)} />)} + {segments.map((segment, index) => point.bestAsk, minimum, maximum)} />)} + + + +
{utcTime(state.observedAt)}Derived summaries only · unavailable feed intervals render as gaps
+
+ ); +} + +function ProbabilityPanel({ snapshot, state, activeId, playing, onSelect, onTogglePlay }: { + snapshot: MatchroomSnapshot; + state: ReplayState; + activeId: ReplayStepId; + playing: boolean; + onSelect: (id: ReplayStepId) => void; + onTogglePlay: () => void; +}) { + return ( +
+
+
Licence-safe market evidence

Relative TXLine movement & public book

+
Best bidBest askBid–ask spread
+
+
+
TXLine movement{movementBps(state.consensusMoveFromBaselineBps)}25-bps bucket from case baseline · absolute level withheld
+
Executable bid / ask{(state.bestBid * 100).toFixed(2)}/{(state.bestAsk * 100).toFixed(2)}%{points(state.spread)} measured spread
+
Pre-trigger market move{movementBps(snapshot.replay.preTriggerMarketMoveBps)}Public executable market · no TXLine level disclosed
+
+
+ +

{state.conclusionTitle} {state.conclusionBody}

+ Retrospective pass +
+ +
+ +
+ {snapshot.replay.states.map((item) => )} +
+ 1× +
+
+ ); +} + +function DecisionRail({ snapshot, state }: { snapshot: MatchroomSnapshot; state: ReplayState }) { + const executionLabel = snapshot.decision.ordersPlaced === 0 ? "Execution not entered" : `${snapshot.decision.ordersPlaced} orders placed`; + return ( + + ); +} + +function EvidencePanel({ snapshot, activeId }: { snapshot: MatchroomSnapshot; activeId: ReplayStepId }) { + const sourceClass = { Polymarket: "poly", TXLine: "tx", Samaritan: "sam" } as const; + const sourceMark = { Polymarket: "P", TXLine: "TX", Samaritan: "S" } as const; + return ( +
+
Evidence provenance

Sequence around the goal

Case · {snapshot.caseId}
+
+ + + + {snapshot.evidence.map((row) => ( + + + + + + + + ))} + +
MomentSourceCaptured observationExecutable askAssessment
{row.offsetLabel}{sourceMark[row.source]}{row.source}{row.observation}{percent(row.bestAsk)}Best ask{row.assessment}
+
+
+ ); +} + +function ProofPanel({ snapshot }: { snapshot: MatchroomSnapshot }) { + return ( + + ); +} + +function LoadingScreen() { + return
Samaritan / Matchroom

Loading captured replay

Assembling licence-safe derived evidence. The retrospective case is shown only after local replay checks pass.

; +} + +function ErrorScreen({ retry }: { retry: () => void }) { + return
Fail-closed boundary

Evidence unavailable

The captured replay could not be checked, so Samaritan is not presenting partial or fabricated match evidence.

; +} + +function Matchroom({ snapshot }: { snapshot: MatchroomSnapshot }) { + const [activeId, setActiveId] = useState(snapshot.replay.activeStateId); + const [playing, setPlaying] = useState(false); + const state = snapshot.replay.states.find((item) => item.id === activeId) ?? snapshot.replay.states[1]!; + + const advanceReplay = useEffectEvent(() => { + const currentIndex = replayOrder.indexOf(activeId); + if (currentIndex >= replayOrder.length - 1) { + setPlaying(false); + return; + } + startTransition(() => setActiveId(replayOrder[currentIndex + 1]!)); + }); + + useEffect(() => { + if (!playing) return; + const timer = window.setInterval(advanceReplay, 1400); + return () => window.clearInterval(timer); + }, [playing]); + + function selectState(id: ReplayStepId) { + setPlaying(false); + startTransition(() => setActiveId(id)); + } + + function togglePlay() { + if (playing) { + setPlaying(false); + return; + } + if (activeId === "post") setActiveId("pre"); + setPlaying(true); + } + + return ( +
+ +
+ + +
+ +
+ + +
+
+ + +
+
+ +
+
+ ); +} + +export function MatchroomApp() { + const [snapshot, setSnapshot] = useState(null); + const [failure, setFailure] = useState(false); + const [attempt, setAttempt] = useState(0); + + useEffect(() => { + const controller = new AbortController(); + setFailure(false); + loadMatchroom(controller.signal) + .then((nextSnapshot) => startTransition(() => setSnapshot(nextSnapshot))) + .catch((error: unknown) => { + if (error instanceof DOMException && error.name === "AbortError") return; + setFailure(true); + }); + return () => controller.abort(); + }, [attempt]); + + if (failure) return { setSnapshot(null); setAttempt((value) => value + 1); }} />; + if (!snapshot) return ; + return ; +} diff --git a/apps/dashboard/src/Proof.tsx b/apps/dashboard/src/Proof.tsx new file mode 100644 index 0000000..9eeaaa7 --- /dev/null +++ b/apps/dashboard/src/Proof.tsx @@ -0,0 +1,391 @@ +import { startTransition, useEffect, useState } from "react"; +import { + BrandMark, + Icon, + type IconName, + MobileNavigation, + Navigation, + ProvenanceBadge, + Topbar, + formatUsdMicros +} from "./Shell"; + +const RECEIPT_PATH = "/artifacts/dashboard/synthetic-decision-receipt.json"; +const VERIFY_COMMAND = "pnpm receipt:verify -- public/artifacts/dashboard/synthetic-decision-receipt.json"; +const RECEIPT_HASH_DOMAIN = "samaritan.decision-receipt/v1"; + +type ProofLedgerEntry = { + atTsMs: number; + entryHash: string; + kind: string; + previousHash: string; + sequence: number; +}; + +type ProofReceipt = { + receiptType: string; + receiptId: string; + generatedAtTsMs: number; + provenance: { + evidenceClass: string; + label: string; + performanceUse: string; + synthetic: boolean; + }; + agents: { + runs: Array<{ + invocationClass: string; + model: string; + stage: string; + status: string; + actualCostNanoUsd: number; + }>; + totalActualCostNanoUsd: number; + }; + lifecycle: { + finalStatus: string; + orderedEventKinds: string[]; + triage: { decision: string; priority: string }; + thesis: { recommendation: string } | null; + risk: { + decision: string; + realMoneyGate: string; + stakeMicroUsd: number | null; + } | null; + execution: { + adapter: string; + status: string; + } | null; + settlement: { + won: boolean; + } | null; + }; + ledger: { + caseEntries: ProofLedgerEntry[]; + finalHeadHash: string; + rowsAtGeneration: number; + verificationAtGeneration: string; + }; + integrity: { + algorithm: string; + canonicalization: string; + domain: string; + receiptHash: string; + }; + solanaAnchor: unknown | null; +}; + +export type BrowserProofVerification = { + valid: boolean; + canonicalHashValid: boolean; + disclosedChainValid: boolean; + lifecycleOrderValid: boolean; + syntheticBoundaryValid: boolean; + anchorAbsent: boolean; + issues: string[]; +}; + +type ProofLoad = { + receipt: ProofReceipt; + verification: BrowserProofVerification; +}; + +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +function parseProofReceipt(value: unknown): ProofReceipt { + if (!isRecord(value)) throw new Error("Receipt is not an object"); + if (!isRecord(value.provenance) || !isRecord(value.agents) || !isRecord(value.lifecycle)) { + throw new Error("Receipt is missing its provenance or lifecycle envelope"); + } + if (!isRecord(value.ledger) || !isRecord(value.integrity) || !Array.isArray(value.ledger.caseEntries)) { + throw new Error("Receipt is missing its integrity or ledger envelope"); + } + if (!Array.isArray(value.agents.runs) || !Array.isArray(value.lifecycle.orderedEventKinds)) { + throw new Error("Receipt is missing its disclosed agent or event sequence"); + } + return value as unknown as ProofReceipt; +} + +function stableJson(value: unknown): string { + const sort = (child: unknown): unknown => { + if (Array.isArray(child)) return child.map(sort); + if (isRecord(child)) { + return Object.fromEntries( + Object.entries(child) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, nested]) => [key, sort(nested)]) + ); + } + return child; + }; + return JSON.stringify(sort(value)); +} + +async function sha256Hex(value: string): Promise { + if (!globalThis.crypto?.subtle) throw new Error("Browser SHA-256 is unavailable"); + const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)); + return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +/** + * Recomputes the portable receipt commitment and checks only the relationships + * disclosed in the public artifact. The CLI below remains the strict schema and + * semantic verifier; neither verifier replays private source payloads. + */ +export async function verifyBrowserProof(value: unknown): Promise { + const receipt = parseProofReceipt(value); + const issues: string[] = []; + const entries = receipt.ledger.caseEntries; + const body = Object.fromEntries(Object.entries(receipt).filter(([key]) => key !== "integrity")); + const expectedReceiptHash = await sha256Hex(`${RECEIPT_HASH_DOMAIN}\n${stableJson(body)}`); + const canonicalHashValid = + receipt.integrity.algorithm === "sha256" && + receipt.integrity.domain === RECEIPT_HASH_DOMAIN && + receipt.integrity.canonicalization === "samaritan-stable-json-v1" && + receipt.integrity.receiptHash === expectedReceiptHash; + if (!canonicalHashValid) issues.push("Canonical receipt hash mismatch"); + + const disclosedChainValid = entries.length > 0 && entries.every((entry, index) => { + if (index === 0) return Number.isSafeInteger(entry.sequence) && entry.sequence > 0; + const previous = entries[index - 1]; + return previous !== undefined && + entry.sequence === previous.sequence + 1 && + entry.previousHash === previous.entryHash; + }) && entries.at(-1)?.entryHash === receipt.ledger.finalHeadHash; + if (!disclosedChainValid) issues.push("Disclosed ledger links or head do not reconcile"); + + const lifecycleOrderValid = + receipt.lifecycle.orderedEventKinds.length === entries.length && + receipt.lifecycle.orderedEventKinds.every((kind, index) => entries[index]?.kind === kind); + if (!lifecycleOrderValid) issues.push("Lifecycle ordering does not match ledger ordering"); + + const syntheticBoundaryValid = + receipt.provenance.synthetic === true && + receipt.provenance.evidenceClass === "synthetic_proving_fixture" && + receipt.provenance.performanceUse === "excluded_synthetic" && + receipt.agents.totalActualCostNanoUsd === 0 && + receipt.agents.runs.every((run) => run.invocationClass === "synthetic_stub" && run.actualCostNanoUsd === 0); + if (!syntheticBoundaryValid) issues.push("Synthetic or zero-external-model-call boundary changed"); + + const anchorAbsent = receipt.solanaAnchor === null; + if (!anchorAbsent) issues.push("This page only accepts the frozen unanchored proving receipt"); + + return { + valid: issues.length === 0, + canonicalHashValid, + disclosedChainValid, + lifecycleOrderValid, + syntheticBoundaryValid, + anchorAbsent, + issues + }; +} + +function compactHash(value: string) { + return `${value.slice(0, 14)}…${value.slice(-10)}`; +} + +function formatTime(value: number) { + return new Intl.DateTimeFormat("en-GB", { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + fractionalSecondDigits: 3, + timeZone: "UTC" + }).format(new Date(value)); +} + +const eventPresentation: Record = { + signal_received: { label: "Signal detected", eyebrow: "Deterministic detector", icon: "pulse" }, + triage_decision: { label: "Triage escalated", eyebrow: "Haiku-shaped boundary", icon: "spark" }, + thesis_submitted: { label: "Thesis submitted", eyebrow: "Opus-shaped boundary", icon: "case" }, + analysis_completed: { label: "Analysis locked", eyebrow: "Strict schema exit", icon: "check" }, + risk_verdict: { label: "Paper allowed. Money refused.", eyebrow: "Deterministic risk authority", icon: "shield" }, + execution_intent: { label: "Paper intent ledgered", eyebrow: "Before action", icon: "lock" }, + paper_execution: { label: "Paper adapter acted", eyebrow: "No venue order", icon: "play" }, + case_terminal: { label: "Case terminal", eyebrow: "Runtime outcome", icon: "check" }, + position_opened: { label: "Paper position opened", eyebrow: "Accounting", icon: "chart" }, + position_closed: { label: "Paper position marked", eyebrow: "Pre-kickoff close", icon: "clock" }, + position_settled: { label: "Paper position settled", eyebrow: "Synthetic resolution", icon: "proof" } +}; + +function eventDetail(receipt: ProofReceipt, kind: string) { + if (kind === "triage_decision") return `${receipt.lifecycle.triage.decision} · ${receipt.lifecycle.triage.priority}`; + if (kind === "thesis_submitted") return receipt.lifecycle.thesis?.recommendation ?? "No thesis"; + if (kind === "risk_verdict") { + const risk = receipt.lifecycle.risk; + if (!risk) return "No risk verdict"; + const stake = risk.stakeMicroUsd === null ? "no stake" : `${formatUsdMicros(risk.stakeMicroUsd)} paper stake`; + return `${risk.decision} paper · ${stake} · real money ${risk.realMoneyGate}`; + } + if (kind === "paper_execution") { + return `${receipt.lifecycle.execution?.status ?? "unknown"} · ${receipt.lifecycle.execution?.adapter ?? "paper"} adapter`; + } + if (kind === "case_terminal") return receipt.lifecycle.finalStatus.replaceAll("_", " "); + if (kind === "position_settled") return receipt.lifecycle.settlement?.won ? "synthetic outcome: won" : "synthetic outcome: lost"; + return kind.replaceAll("_", " "); +} + +function ProofHero({ receipt }: { receipt: ProofReceipt }) { + const risk = receipt.lifecycle.risk; + return ( +
+
+ + A decision boundary, made inspectable +

THE MONEY PATH
SAMARITAN REFUSED.

+

What was refused: every path from an agent recommendation to real money. A deterministic Opus-shaped stub recommended a paper trade; code-owned risk kept the money gate closed and exercised only the synthetic paper lifecycle.

+
+
+
Decision boundaryZero real orders
+
+
01 · Agent-shaped thesis{receipt.lifecycle.thesis?.recommendation.replaceAll("_", " ") ?? "No thesis"}May recommend. Cannot size or execute.
+ +
02 · Deterministic authority{risk?.decision ?? "closed"} paper{risk?.stakeMicroUsd ? formatUsdMicros(risk.stakeMicroUsd) : "$0.00"} fixed · real money {risk?.realMoneyGate ?? "closed"}
+ +
03 · Money boundaryREFUSEDPaper adapter only · no wallet or venue order
+
+
An LLM never touches money here.The synthetic case continues after the refusal solely to prove paper execution, close, settlement, and receipt wiring.
+
+
+ ); +} + +function ProofTruthStrip({ receipt }: { receipt: ProofReceipt }) { + return ( +
+
Evidence classSynthetic proving fixtureNo real match or licensed feed record
+
Models{receipt.agents.runs.length} deterministic stubsZero Anthropic API cost or calls
+
Performance useExcludedNot alpha, P&L, or fill evidence
+
ExecutionPaper onlyZero real orders · gate closed
+
SolanaNot submittedNo anchor or network verification
+
+ ); +} + +function ProofLedger({ receipt }: { receipt: ProofReceipt }) { + return ( +
+
+
Append-only · ordered before action

Decision ledger

+ {receipt.ledger.caseEntries.length} disclosed case events +
+
Seq.BoundaryCommitted eventUTCEntry hash
+
+ {receipt.ledger.caseEntries.map((entry) => { + const presentation = eventPresentation[entry.kind] ?? { label: entry.kind, eyebrow: "Committed event", icon: "proof" as const }; + return ( +
+ {String(entry.sequence).padStart(2, "0")} + + {presentation.eyebrow}{presentation.label}{eventDetail(receipt, entry.kind)} + + {compactHash(entry.entryHash)} +
+ ); + })} +
+
{receipt.ledger.rowsAtGeneration} total ledger rows at generationThe case events above follow the study-initialization commitment; each row links to the previous disclosed hash.
+
+ ); +} + +function VerificationInspector({ receipt, verification }: ProofLoad) { + const checks = [ + { label: "Canonical receipt hash", detail: "Recomputed in this browser", pass: verification.canonicalHashValid }, + { label: "Disclosed ledger links", detail: "Sequences, links, and final head reconcile", pass: verification.disclosedChainValid }, + { label: "Lifecycle ordering", detail: "Event kinds match committed row order", pass: verification.lifecycleOrderValid }, + { label: "Synthetic boundary", detail: "Stubs, zero model cost, performance excluded", pass: verification.syntheticBoundaryValid }, + { label: "External anchor", detail: "Absent; no network verification claimed", pass: verification.anchorAbsent } + ]; + return ( + + ); +} + +function ReproduceProof() { + return ( +
+
Do not trust the screenshot

Verify the frozen receipt offline.

The repository verifier enforces the strict schema, recomputes the canonical receipt hash, and checks disclosed lifecycle relationships. It does not query TXLine, Polymarket, Anthropic, a wallet, or Solana.

+
Exact command · repository root{VERIFY_COMMAND}Open evidence JSON
+

Assurance boundary: this verifies portable commitments and disclosed relationships. It does not replay private source payloads, prove a real model invocation, establish profitability, or claim an external timestamp.

+
+ ); +} + +function ProofView({ receipt, verification }: ProofLoad) { + return ( +
+ +
+ +
+ + +
+ +
Synthetic engineering proof. Permanently excluded from every performance claim.No external model call · no real order · no wallet · no submitted Solana anchor.
+
+ +
+
+ ); +} + +function ProofLoading() { + return
Samaritan / Proof

Recomputing the receipt

Checking the frozen canonical commitment, disclosed ledger links, event order, provenance, and anchor boundary.

; +} + +function ProofError({ retry }: { retry: () => void }) { + return
Fail-closed evidence boundary

Proof did not reconcile

The frozen receipt could not be loaded or its public commitments changed. Samaritan will not show a partial verification result.

; +} + +export function ProofApp() { + const [loaded, setLoaded] = useState(null); + const [failure, setFailure] = useState(false); + const [attempt, setAttempt] = useState(0); + + useEffect(() => { + const controller = new AbortController(); + setFailure(false); + fetch(RECEIPT_PATH, { headers: { accept: "application/json" }, signal: controller.signal }) + .then(async (response) => { + if (!response.ok) throw new Error(`Receipt request failed with status ${response.status}`); + const value = await response.json() as unknown; + const receipt = parseProofReceipt(value); + const verification = await verifyBrowserProof(receipt); + if (!verification.valid) throw new Error(verification.issues.join("; ")); + return { receipt, verification }; + }) + .then((next) => startTransition(() => setLoaded(next))) + .catch((error: unknown) => { + if (error instanceof DOMException && error.name === "AbortError") return; + setFailure(true); + }); + return () => controller.abort(); + }, [attempt]); + + if (failure) return { setLoaded(null); setAttempt((value) => value + 1); }} />; + if (!loaded) return ; + return ; +} diff --git a/apps/dashboard/src/Shell.tsx b/apps/dashboard/src/Shell.tsx new file mode 100644 index 0000000..2b76699 --- /dev/null +++ b/apps/dashboard/src/Shell.tsx @@ -0,0 +1,124 @@ +import type { ReactNode } from "react"; + +export type ProductRoute = "command" | "matchroom" | "casebook" | "study" | "proof"; + +export type IconName = + | "arrow" + | "case" + | "chart" + | "check" + | "chevron" + | "clock" + | "command" + | "cup" + | "lock" + | "minus" + | "pause" + | "play" + | "proof" + | "pulse" + | "replay" + | "shield" + | "spark" + | "system"; + +export function Icon({ name, className }: { name: IconName; className?: string }) { + const paths: Record = { + arrow: <>, + case: <>, + chart: , + check: , + chevron: , + clock: <>, + command: <>, + cup: , + lock: <>, + minus: , + pause: , + play: , + proof: <>, + pulse: , + replay: <>, + shield: <>, + spark: <>, + system: <> + }; + return ; +} + +export function BrandMark() { + return ; +} + +export type ProvenanceTone = "capture" | "configured" | "historical" | "offline" | "synthetic"; + +export function ProvenanceBadge({ label, tone }: { label: string; tone: ProvenanceTone }) { + return