SKOOR Risk Money movement. Skoored by AI.

Skoor Risk — product requirements

A modern risk platform for money movement. One place for a bank to see every dollar that moves across its rails, score it, and decide, so growth in programs, rails, and countries never outruns control.

Status: v0, Milestone A. Live against Column-shaped events (simulated on the demo tenant; a bank's own sandbox once its key is connected). Every number on the platform carries its n; where nothing has been measured the platform says null.

1. The problem

A modern bank runs its own core, its own rails, and hundreds of programs on top of them: payroll companies, remittance apps, marketplaces, lenders, each with its own customers and its own flow of funds. The bank's picture of what is safe is still assembled by hand: exports, sheets, and the memory of whoever has been there longest. Every new program, rail, or country adds another sheet and another person. Skoor turns that picture into a product with an API, a record, and metrics of its own.

2. Who uses it

UserWhat they need from the platform
Bank risk leadOne screen per program: the Program Risk Skoor, the KRIs that are breaching, what is waiting on a person. A board pack once a month.
Compliance officerA queue of alerts with the narrative already drafted, the evidence attached, and a written policy that says which decisions the platform makes alone. A record an examiner can walk.
The engineer who owns the railsNothing new to build: the platform reads the webhook events the bank already emits, acts through the API the bank already has, and leaves the ledger alone.
A program's ops leadTheir own KRIs, their open requests from the bank, their queue, inside their tools or inside Claude.

3. The six layers

3.1 Ingest

Reads the bank's webhook events: signature verified on the raw body (Column-Signature, HMAC-SHA256), deduplicated by event id, safe under redelivery, keyed by program → entity → account → transfer → counterparty. Statuses and enums are read from the bank's API verbatim so a real event and a simulated one are indistinguishable downstream: ACH PRE_REVIEW … MANUAL_REVIEW … RETURNED, wire MANUAL_REVIEW | HOLD, real-time manual_review_approved | manual_review_rejected, entity verification_status (UNVERIFIED | PENDING | MANUAL_REVIEW | VERIFIED | DENIED), is_high_risk, pep_status, review_reasons, account status (open | closed | frozen), card decisioning_details.decision_reason. Column is the first integration.

API: POST /webhooks/column/… (per program endpoint; see the ingest module), admin API for tenants and programs.

3.2 Program KRIs

Per program, over a rolling window (60 days for network return thresholds): ACH return rates against NACHA's thresholds (unauthorized 0.5%, administrative 3%, overall 15%), reserve coverage (program reserve ÷ rolling 60-day ACH debit volume, the bank's own go-live rule), manual-review aging, hold aging, overdraft and freeze events, velocity, counterparty concentration, verification denial rate, card decline mix. Each KRI carries value | null, n, threshold, and a status (ok | watch | breach | unmeasured). KRIs roll up into a Program Risk Skoor (prs-v1), null until at least three KRIs have n above zero.

API: snapshots in kri_snapshots; rendered on /programs/:id.

3.3 The boundary — automated or reviewed

A versioned, data-only policy draws the line between the decisions the platform may make alone and the ones that wait for a person. See §5.

3.4 Actions

Clear or cancel a held transfer, pause a card, suspend a card account, freeze an account, send a request to a program, assemble a periodic review. Every action is attributed to the person who requested it and, for freeze, request-to-program, and suspend, to a second, distinct approver. Actions execute through the bank's own API with the tenant's own key; in this build only sandbox (test_) keys may execute, and a tenant without a key gets a recorded dry run that says so.

API: POST /api/actions, POST /api/actions/:id/approve (Bearer token proves the tenant; X-Operator names the person).

3.5 The record

Every disposition and every action request is hash-chained per tenant (sha256(prevHash | canonical(body))) under an advisory lock, so two writers cannot claim the same predecessor. Verification recomputes every hash from stored fields and reports the first break. The examiner export is JSON lines for a date range: the policy version in force, alerts, dispositions, actions.

API: GET /api/record/verify, GET /api/record/export?from=&to=.

3.6 The program product

The same platform opened to the businesses that bank on the tenant: their KRIs, their open requests, their queue. Planned for Milestone B (partner portal and Claude connector).

4. The Transaction Risk Skoor

Definition. A number from 0 to 100 computed from what the platform has observed about a transfer: the program's history, the counterparty's history, the entity's verification and screening state, velocity, amount against declared activity, and the rail. Version trs-v1.

What it carries. The score, the band, the version, the observation count n behind the baselines, a confidence in [0, 1], and the list of signals with their weights. All of it is stored on the transfer and shown on /transfers/:id.

Bands.

BandSkoorWhat the policy does
Clear0–29The platform may close an alert on it under policy. Logged, sampled, never silent.
Review30–69A person decides, with the narrative and the evidence already drafted.
Hold70–100Held first, decided second. Fail closed.
UnscorednullToo little history to score (program history below the minimum). Treated as Review, never as Clear.

Null rule. Below the minimum program history the Skoor is null, not zero. A hard signal (a denied entity, a sanctioned jurisdiction) scores regardless of history and always routes to a person.

Confidence. Grows with the program's, the entity's, and the counterparty's history; null when the score is null. A template narrative never carries a confidence.

5. The policy (v1)

Questionv1 answer
What may the platform close alone?Detectors repeat_pattern and return_rate_drift_within_threshold, only in the Clear band, only without a hard signal, only when the program has at least 30 observations, and only when the draft narrative recommends close.
What always waits for a person?First-time counterparty above declared activity; any freeze, limit change, or request to a program; a sanctions, PEP, or adverse-media match; a sanctioned or embargoed jurisdiction; a denied entity transacting; a structuring pattern; anything in the Hold band; a reserve-coverage or return-rate breach.
Which actions need a second approver?freeze_account, request_to_program, suspend_card_account. The requester cannot approve.
Hold bandThe transfer is held before anyone decides.
Can the automated side widen?Not on its own. A draft that recommends anything but close leaves the alert waiting for a person. A detector moves from reviewed to automated only in a new policy version, after the labeled set shows it is safe.

The policy is rendered at /policy and recorded on every alert (policyVersion) and disposition.

6. Triage

For every opened alert the platform drafts a narrative in the shape an examiner expects: what happened, what the evidence shows, what was checked, what is recommended, plus a recommendation, a confidence, and rationale. Claude drafts (the model alias is configuration, default claude-sonnet-5); a person signs. Without a model, or on any error, a deterministic template is assembled from the evidence with no model and no confidence. A person's decision that differs from the draft's recommendation is recorded as an overturn; that rate is one of the six metrics.

7. The six metrics

Each is { value | null, n }, computed over a window (default 30 days), persisted daily to metrics_daily, rendered at /metrics and GET /api/metrics.

MetricFormulan
CalibrationPer band: transfers with a bad label ÷ transfers with any labellabeled transfers in the band
CoverageTransfers with a Skoor ÷ transfers; programs whose latest snapshot has a Program Skoor ÷ programs; event families understood ÷ families knowntransfers; programs; distinct event types seen
Precision and recallHold band vs labels: TP ÷ (TP+FP), TP ÷ (TP+FN), bad = positiveTP+FP; TP+FN
Auto-closed and overturnedAlerts auto_closed ÷ alerts opened; human dispositions that overturn the draft ÷ human dispositionsalerts; human dispositions
Time to decisionMedian and p90 hours from openedAt to closedAt, automated and reviewed separatelyclosed alerts per route
Approved without waitingCount and dollars of Clear-band transfers that never raised an alertscored transfers

Labels come from outcomes (a return with an unauthorized code, a dispute), from reviewers, and from the eval set; the Skoor is right when higher bands go bad more often and the curve holds month over month.

8. What is live and what is planned

Live in this build: schema and migrations; Column signature verification; the policy v1; triage with structured drafts and template fallback; chained dispositions with human overturn tracking; actions with the two-approver rule and sandbox-only execution; chain verification and examiner export; the six metrics; the dashboard (overview, program, alerts, alert detail with decide/actions, transfer Skoor breakdown, metrics, policy, record, this PRD); fixtures labeled simulated.

Owned by sibling modules in the same build: ingest and the Column client, the Skoor and KRI engines, the detectors, the simulator, the eval set.

Planned (Milestone B): the program portal and Claude connector; an event-sourced action log so approvals and executions sit on the chain; stored tenant policies with seeding; verified operator identities; stablecoin wallet screening on the same alert rail; live (non-sandbox) execution once a bank says so.

9. Open questions

  1. Which program-level limits should be first-class actions (per-program ACH debit cap, per-entity velocity cap) rather than requests to the program?
  2. Should the Hold band hold at the rail (hold: true on origination) or at the platform (a request to the program to hold)? Today the platform records the hold and asks the bank's API.
  3. What is the minimum program history for a Skoor? v1 uses 30 observations; the calibration curve should set it.
  4. How much of the narrative may be shown to the program in the partner product without disclosing the bank's detectors?