> ## Documentation Index
> Fetch the complete documentation index at: https://docs.walletwall.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Wallet Risk Report Model

> The canonical, versioned WalletRiskReport.v1 schema and read-only composition layer — how it differs from the Vault Readiness Packet and the cohort-level Exposure Report.

# Wallet Risk Report Model

<Warning>
  `WalletRiskReport.v1` adds **no new scoring**, **no new provider**, **no Dune execution**,
  and **no wallet connection or signing**. It is a read-only composition layer over EXISTING
  canonical scorers — Quantum Vault Readiness, Stablecoin Vault Readiness, Migration
  Readiness, and Whale Watcher behavioral signals. This PR ships the model, the read-only
  API, and tests only — the report page/UI is intentionally deferred.
</Warning>

`WalletRiskReport.v1` is the canonical **per-wallet** risk assessment for a single public
Ethereum address. It composes four existing dimensions — quantum exposure readiness,
stablecoin/concentration exposure, dormancy/activity, and deterministic behavioral signals
— into one object with an overall canonical readiness tier, without inventing any new
score, tier, or driver vocabulary.

Source: [`src/lib/wallet-risk-report/`](https://github.com/sirmrdrgod/walletwall/tree/main/src/lib/wallet-risk-report)
(`schema.js`, `build-wallet-risk-report.js`) and
[`api/wallet-risk-report.js`](https://github.com/sirmrdrgod/walletwall/blob/main/api/wallet-risk-report.js).

## Three report-shaped objects — do not confuse them

This is the **third** distinct report/packet object in the codebase. Each has a different
scope and a different purpose:

| Object                                                                        | Scope                                | Purpose                                                             | Signed?                                          |
| ----------------------------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------------------- | ------------------------------------------------ |
| **Vault Readiness Packet** (`buildVaultReadinessPacket.js`)                   | Per-wallet, vault-specific           | A bounded, shareable "proof packet" for the Vault Candidate surface | Yes, via `api/readiness-packet.js` (ECDSA P-256) |
| **Exposure Report Edition** (`exposure-report/buildExposureReportEdition.js`) | **Cohort** (many wallets aggregated) | A citable, dated snapshot of Holder Wall cohort exposure stats      | No                                               |
| **Wallet Risk Report** (this page)                                            | **Per-wallet**, general-purpose      | The canonical cross-dimension risk assessment for one wallet        | No (v1)                                          |

The Wallet Risk Report is the only one of the three that spans **quantum + concentration +
dormancy + behavior** in a single object with an overall readiness tier. It never aggregates
across wallets (that's the Exposure Report's job) and it is not a vault-specific artifact
(that's the Readiness Packet's job).

## Schema shape

```
WalletRiskReport {
  reportType: 'walletwall.wallet-risk-report'
  reportVersion: 'v1'
  subject: { address, chain }
  generatedAt: string | null          // caller-supplied; the model never reads a clock
  modelVersions: { ... }              // which composed pipeline produced each dimension
  status: 'assessed' | 'insufficient_evidence'
  overall: {
    riskTier: TIER_KEYS.* | null      // risk-tiers.js — no new tier taxonomy
    riskTierLabel: string | null
    recommendation: RECOMMENDATION_KEYS.*
    compositeScoreNote: string        // explicit "no single composite score" statement
  }
  dimensions: {
    quantum: Dimension
    concentration: Dimension
    dormancyActivity: Dimension
    behavior: Dimension
  }
  evidenceConfidence: { overall, scorable, reasons, perDimension }
  dataSources: [{ id, type, freshness, queryRunAt, cachedAt }]
  missingEvidence: string[]
  riskDrivers: string[]               // RISK_DRIVER_KEYS values only
  recommendedNextActions: string[]    // deterministic, read-only-verb copy
  boundaries: { custodyClaim: false, yieldClaim: false, insuranceClaim: false, quantumProofClaim: false, productionVaultClaim }
  disclaimers: string[]               // non-strippable; always non-empty
  narrative: null                     // ALWAYS null in v1 — see "AI narrative" below
}

Dimension {
  status: 'observed' | 'partial' | 'unknown'
  confidence: 'high' | 'medium' | 'low'
  scorable: boolean
  reasonCodes: string[]               // src/lib/wallet-evidence/evidence-quality.js SCORING_REASON_CODES
  source: string                      // which existing module/function produced this
  value: object | null
}
```

## What each dimension actually is

<Columns cols={2}>
  <Card title="Quantum" icon="atom">
    Reuses `api/quantum-readiness.js`'s already-composed Quantum Vault Readiness pipeline
    (score/band/exposureLevel, higher = better-prepared) plus its freshness-aware evidence
    diagnostics. The separate, higher-is-worse "Quantum Exposure Score" pipeline
    (`src/lib/quantum-exposure.js`) is intentionally **not** composed in v1 — see
    "Deferred work" below.
  </Card>

  <Card title="Concentration" icon="coins">
    Reuses `getStablecoinVaultReadiness()` (stablecoin balance / portfolio share / dormancy
    / signature exposure), which implements the OR-of-four-facts null-gate: score/tier stay
    `null` only when **none** of the four primary facts were observed.
  </Card>

  <Card title="Dormancy / activity" icon="clock">
    Built directly from the Dune-sourced `dormancy` facts (`daysDormant`, `dormancyBucket`,
    `lastActiveAt`) already fetched for the Quantum dimension — no separate query.
  </Card>

  <Card title="Behavior" icon="whale">
    Reuses `deriveWhaleWatcherSignals()` — the same deterministic `WalletSignal[]` engine
    Whale Watcher itself uses. An empty signal array is a valid, meaningful "no notable
    behavior" result, distinct from "no evidence" (`status: 'unknown'`), which is set only
    when neither a Dune 12-week baseline nor live transaction events existed to evaluate.
  </Card>
</Columns>

## The overall readiness tier

The `overall.riskTier` is derived by calling `buildStablecoinVaultReadiness()` — the **same**
composition point existing Vault Readiness surfaces already use — not a new tier-derivation
rule. That function resolves the strongest categorical signal across whichever dimension
inputs are available and bridges the result to `risk-tiers.js`'s canonical 5-tier vocabulary
(`monitor` → `review` → `prepare` → `migrate` → `vaultPrototype` / "Vault Candidate") via
`getCanonicalRiskTier()`.

There is **no separate composite risk score**. `overall.compositeScoreNote` says so
explicitly on every report — each dimension carries its own score/label on its own scale;
they are never averaged or blended into one number (see "What's deterministic, inferred, or
unavailable" below for why: the dimensions genuinely use different scales and null-gate
rules, and collapsing them would erase real distinctions the underlying models were built
to preserve).

## Deterministic vs. inferred vs. unavailable

| Category                              | Meaning                                                                         | Example                                                                                                |
| ------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| **Deterministic (observed)**          | Read directly from a source, or a pure deterministic function of observed facts | `dimensions.quantum.value.vaultReadinessScore` when Dune facts exist                                   |
| **Deterministic (inferred)**          | A pure, deterministic derivation with no invented data                          | `overall.riskTier`, derived from dimension outputs via the existing composition rules                  |
| **Unavailable (`status: 'unknown'`)** | No evidence source existed                                                      | `dimensions.behavior` when neither a Dune 12-week baseline nor live wallet transactions were available |

`AI narrative generation is optional and strictly downstream — never computed by this
module.` `narrative` is always `null` in v1. A future PR may add an opt-in narrative step
that reads a finished `WalletRiskReport` and produces plain-English copy; it must never be
required to interpret the report, and it must never write back into the deterministic
fields above.

## Honesty rules (enforced by tests)

* **Unknown is never zero.** A dimension with no evidence reports `status: 'unknown'`, not a
  fabricated `'monitor'`/`0`/`false`.
* **Stale/conflicting evidence lowers confidence, not necessarily severity.** A stale Dune
  dormancy fact downgrades `dimensions.dormancyActivity.confidence`, but does not by itself
  change `overall.riskTier`.
* **If nothing can be honestly assessed, the report says so structurally** —
  `status: 'insufficient_evidence'`, `overall.riskTier: null`,
  `overall.recommendation: 'unsupported'` — never a fabricated tier.
* **The client cannot inject any computed field.** The read-only API
  (`GET /api/wallet-risk-report?address=0x…`) never reads a request body, and the only
  client-controlled input is the wallet address itself.
* **Dune data is never described as live.** `dataSources[].type` distinguishes
  `dune_scheduled` from `live_provider`; the app-wide Dune spend-path rules
  ([docs/operations/dune-spend-path-controls.md](/operations/dune-spend-path-controls))
  apply unchanged — this endpoint only reads Dune's cache, never executes or reads live
  results.
* **Mock/demo data is never served as a real report.** Unlike `/api/wallet`, this endpoint
  does not fall back to `mockWalletProvider` when no live provider key is configured — the
  concentration and behavior dimensions honestly degrade to `unknown` instead.

## Deferred work (intentional v1 scope)

Not built in this PR — listed here so a follow-up doesn't need to rediscover the reasoning:

* **Report page / UI.** This PR ships the model, API, tests, and docs only.
* **Quantum Exposure Score pipeline** (`deriveWalletSignatureExposure` →
  `deriveQuantumExposureScore`). That pipeline has never been server-composed anywhere in
  this codebase; wiring it here would mean writing new composition logic rather than reusing
  an existing one. v1 composes the already fully server-composed Quantum Vault Readiness
  pipeline instead.
* **Dune 12-week Whale Watcher cohort baseline.** The behavior dimension currently relies
  only on live wallet transactions (when a provider key is configured) for its baseline;
  most report subjects are outside the curated 12-week active-wallet cohort anyway.
* **ENS resolution.** The endpoint requires a raw `0x` address; resolve via
  `/api/resolve-wallet` first.
* **Report persistence, sharing, signing, or a database.** Every call recomputes the report
  fresh from current cache/live state.
* **Portfolio / multi-wallet batching.**
* **Reconciling Vault's two disagreeing threshold sets** (legacy 30/60 vs. canonical
  25/50/75) — a pre-existing, separate gap this PR does not touch.

## Related

<Columns cols={2}>
  <Card title="Wallet Evidence Model" icon="shield-halved" href="/architecture/wallet-evidence-model">
    The provenance-aware `/api/wallet` layer this report's concentration dimension reads
    balances from.
  </Card>

  <Card title="Model Layer" icon="layer-group" href="/architecture/model-layer">
    The shared `SourceMetadata` / `DataQuality` / `WalletSignal` conventions this report's
    behavior dimension builds on.
  </Card>
</Columns>
