> ## 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.

# Hosted evidence consumption

# Hosted Evidence Consumption — App-Side Plan

> **Status:** Planning (docs/spec/test-only). This document changes **no runtime
> behavior**, adds **no fetch**, adds **no URL config**, and turns on **no feature
> flag**. It defines how the private WalletWall app will *later* consume the public
> `Wallet-Wall/walletwall-vault` repo's static hosted evidence artifact — read-only,
> fail-closed, and feature-flagged — once that artifact is actually live and reviewed.
> **Last reviewed:** 2026-06-25.

This is a **bridge step** between the merged vault-side static-evidence publishing
work and the future **WalletWall Intelligence Connector** planning PR. It records the
contract, the boundaries, the validation gaps, and the rollout phases so that the
eventual implementation PR has a reviewed plan to build against instead of inventing
scope at implementation time.

It is a planning and audit reference, not a runtime artifact. Nothing here triggers a
code import, network fetch, contract write, or signing operation.

Related:

* [Custody & Legal Boundaries](custody-legal-boundaries.md) — canonical boundary reference.
* [Vault/PQ Public Reference Sync Map](../vault-pq-public-reference-sync.md) — per-release public-repo capability tracking.
* [Proof-Readiness Stack](../architecture/proof-readiness-stack.md) — where evidence sits in the proof-ready architecture.
* [`Wallet-Wall/walletwall-vault`](https://github.com/Wallet-Wall/walletwall-vault) — the public reference repo that owns the artifact and its validators.

***

## Purpose

The public vault repo now commits a **static evidence artifact** — the exact bytes a
read-only static host *would* serve — together with TypeScript and Rust validators, a
committed `Cargo.lock`, and a **manual, gated publish workflow**. This document defines
how the app will eventually read that artifact as **informational, validated evidence
metadata**, so the Stablecoin Vault / Vault Candidate surfaces can show *which*
validated evidence shape backs the readiness narrative — **without** the app proving
anything, custodying anything, or depending on a live endpoint.

The goal is a safe, reviewed consumption seam that:

* stays **disabled by default** until the hosted artifact is live and reviewed,
* **fails closed** to local static copy on every error path,
* never sends wallet data, never signs, never writes, never proves, and
* never overclaims app-side proof, production-grade ZK, custody, mainnet, or wallet safety (the exact phrasings to avoid are enumerated in the copy boundaries below).

***

## Non-goals (out of scope)

This plan does **not** propose, promise, or imply — now or as a side effect of the
future implementation PR it describes:

* runtime fetching, a live network call, or any HTTP request in this PR,
* hardcoding a live hosted URL, or adding URL configuration in this PR,
* implementing a feature flag (only the *strategy* is defined here),
* connector, plugin, MCP, or server code; OAuth, secrets, or API keys,
* write endpoints, wallet transactions, signing, or custody behavior,
* an app-side prover, verifier, circuit, or any app runtime proving,
* production custody of funds or private-key handling,
* mainnet deposits, withdrawals, or any mainnet custody claim,
* yield, interest, or returns,
* any guarantee of quantum safety, wallet safety, or fund protection,
* a runtime dependency of the app on the public vault repo or on GitHub Pages being online.

The **WalletWall Intelligence Connector** is explicitly **future work**, planned in a
separate PR. This document is the bridge step before it, not the connector itself.

***

## Current vault-side status

Tracked from the public repo `Wallet-Wall/walletwall-vault`.

| Item                           | Status                                                                                                                                                                                                                                                                             |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Static artifact committed      | **Yes** — `evidence/zk/hosted/v1/zk-adapter-evidence-response.json` (public PR #90, `@0.9.14`, merge commit `abbf591`).                                                                                                                                                            |
| Publish workflow documentation | **Yes** — `docs/Static_Hosted_Evidence_Publish_Workflow.md` (plus the target-decision and publishing-controls docs).                                                                                                                                                               |
| Manual gated publish workflow  | **Yes** — `.github/workflows/publish-static-evidence.yml`.                                                                                                                                                                                                                         |
| Auto-publish on merge          | **No** — the workflow is `workflow_dispatch`-only; there is no `push`/`schedule` trigger. Merging it published nothing.                                                                                                                                                            |
| Default dispatch behavior      | Dry run (`publish: false`) — validate + stage only, no deploy.                                                                                                                                                                                                                     |
| Hosted endpoint live?          | **Not assumed live.** Going live still requires a maintainer to manually run the workflow with `publish: true`, the protected `github-pages` environment approval to be granted, GitHub Pages to be enabled, **and** the security review named in the vault's target-decision doc. |
| Validators                     | TypeScript (`npm run validate:static-artifact`, `npm run validate:zk-response`) and a cross-language Rust `zkvm/evidence-validator` crate with a committed `Cargo.lock`.                                                                                                           |

**The app must treat the hosted URL as not-yet-live.** Consumption is planned against
the *committed artifact shape and its validators as the source of truth*, not against
any assumed running service.

***

## Expected artifact path

The versioned static path inside the public vault repo is:

```
evidence/zk/hosted/v1/zk-adapter-evidence-response.json
```

It is byte-for-byte identical to the canonical example it copies, and is the literal
payload a static host would serve. The `v1` path segment is the contract version; a
breaking change to the served shape would publish under a new versioned path.

The committed artifact is a **nested envelope**:

```jsonc theme={null}
{
  "schema": "walletwall.zk-adapter-evidence-response.v1",   // outer envelope
  "status": 200,
  "ok": true,
  "servedAt": "…Z",        // the only non-deterministic field
  "etag": "0x…",           // keccak256 of the canonical adapter JSON — the cache key
  "adapter": {
    "schema": "walletwall.zk-verifier-adapter.v1",
    "onChainVerifier": { "onChainVerification": false, "sp1Verifier": "mock", "custody": false },
    "proof": { "status": "gated", "generated": false },
    "limitations": [ "…" ]
  },
  "limitations": [ "…" ]
}
```

***

## Expected future hosted URL / config key

**No live URL exists yet, and none is invented here.** When the artifact is published
and reviewed, the hosted URL will be the static host's stable, versioned URL for the
path above (e.g. a GitHub Pages or equivalent static-host URL ending in
`/evidence/zk/hosted/v1/zk-adapter-evidence-response.json`). The exact origin is the
vault maintainers' decision at publish time and is **out of scope for this PR**.

The app already reserves the configuration keys for this (see *Feature flag strategy*):

* `VITE_VAULT_EVIDENCE_ENDPOINT_ENABLED` — must be the exact string `"true"` to enable.
* `VITE_VAULT_EVIDENCE_ENDPOINT_URL` — the hosted artifact URL (HTTPS only).

These keys already exist in the disabled-by-default app seam. **This PR adds no URL
value, no `.env` entry, and does not enable the flag.** The live URL is supplied — and
reviewed — only in the future implementation PR, after the artifact is actually live.

***

## Feature flag strategy

Consumption stays **disabled by default** until the hosted artifact is live and
reviewed. The existing app seam already encodes this:

* Both `VITE_VAULT_EVIDENCE_ENDPOINT_ENABLED === "true"` **and** a non-empty
  `VITE_VAULT_EVIDENCE_ENDPOINT_URL` are required before any endpoint mode activates.
* Either flag missing, malformed, or absent resolves to the **disabled / reference**
  config — fail-closed by construction.
* The configuration value is **optional**: with no env provided, the app uses the
  deterministic local reference packet and makes no network call.
* Enabling the flag is a deliberate, reviewed action in the future implementation PR —
  **never** a default, and never wired to a hardcoded URL.

***

## Fail-closed behavior

Every uncertain path resolves to the safe local state — the deterministic reference
packet and its calm "reference / informational" copy. The app **never crashes, never
blocks a core flow, and never throws** on evidence-consumption failure.

| Condition                                          | Result                                                                                                 |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Flag disabled / not set                            | Local reference packet; no network call.                                                               |
| URL missing                                        | Fail closed → local reference packet.                                                                  |
| URL not HTTPS                                      | Fail closed → local reference packet.                                                                  |
| Fetch timeout                                      | Fall back → local reference packet.                                                                    |
| HTTP error (non-200)                               | Fall back → local reference packet.                                                                    |
| Malformed JSON                                     | Fall back → local reference packet.                                                                    |
| Schema/version invalid                             | Fall back → local reference packet.                                                                    |
| Stale `servedAt` (older than the freshness window) | Fall back → local reference packet.                                                                    |
| Public vault repo / host offline                   | Fall back → local reference packet; **no hidden dependency** on the repo or GitHub Pages being online. |

Core product flows do not depend on hosted evidence. It is **additive enrichment
only** — its absence is invisible to the user except for a calm "informational"
status note.

***

## Cache / ETag behavior expected from the vault side

The app consumes the vault's cache contract; it does not invent its own.

* The response carries a strong **`etag` equal to `keccak256` of the canonical adapter
  JSON**. The served content and its cache key are derived from the same bytes, so
  they can never disagree.
* The app fetches once, stores the `etag`, and sends `If-None-Match` on any re-fetch.
* A matching `If-None-Match` yields **`304 Not Modified`** (no body) — the app keeps
  its cached evidence. A stale `If-None-Match` yields a fresh `200`.
* **`servedAt` is the only non-deterministic field and must never be used as a cache
  key** — the `etag` is the cache key. `servedAt` is used only for the freshness
  window.
* Long-lived cache headers plus the strong `ETag` / `If-None-Match` revalidation pair
  are honored natively by a static host; the app adds no proxy or re-execution layer.

***

## Validation expectations before app consumption

The app trusts the **vault repo's validators as the source of truth** for the
artifact's correctness, and re-checks structure defensively — but **never overclaims
cryptographic proof**. Structural validation is a shape check, not a cryptographic
verification.

Before the future implementation PR enables consumption, it must:

1. **Verify schema/version fields.** Confirm the served shape matches the expected
   contract version and identity fields, and reject anything else (fail closed).
2. **Compare `etag` / canonical expectations where feasible.** Treat the `etag` as the
   cache key and integrity signal; do not recompute a cryptographic proof in the app.
3. **Confirm the non-overclaim invariants** carried in the artifact — e.g.
   `onChainVerification` is explicitly `false`, proof status is a gated/non-production
   value, the verifier is `mock`/non-production, and `limitations` are present and
   surfaced.
4. **Trust, but do not re-prove.** The app makes **no app-side prover/verifier claim**
   unless that is separately designed and reviewed.

### Known reconciliation gaps — RESOLVED in the Phase 1 reconciliation PR

The existing app seam was written against a *specified* contract before the artifact
was committed. Two shape gaps existed between the app's validator and the vault's
now-committed artifact. Both are now reconciled **in code + tests** (the nested/flat
shape alignment landed in app PR #1304). The seam went on to have its one-shot `GET`
wired behind the disabled-by-default flag in the Phase 2 gated-consumption PR (see
"Rollout phases" below) — still disabled by default in production, enabled only via
Vercel preview env config. Connector/plugin integration remains future work.

* **Flat vs nested shape — RESOLVED.** `src/lib/vault-evidence-endpoint-schema.js`
  `validateEndpointResponse()` previously accepted only a **flat** response
  (`response.schema === "walletwall.zk-verifier-adapter.v1"`, top-level
  `onChainVerification`, `proofStatus`, `sp1Verifier`, `limitations`). It now also
  accepts the committed **nested envelope**
  (`schema === "walletwall.zk-adapter-evidence-response.v1"` with the adapter under
  `adapter`, and `adapter.onChainVerifier.onChainVerification`). The validator
  dispatches on the top-level `schema`, reaches into the nested `adapter`, and applies
  the **same** non-overclaim invariants (`onChainVerification` must be explicitly
  `false`, no overclaiming `adapter.proof.status` / `adapter.onChainVerifier.sp1Verifier`,
  `adapter.limitations` present) — strengthening, never relaxing, validation. The legacy
  flat shape stays intentionally supported. `buildEndpointEvidenceEnrichment()`
  normalizes either shape into the same enrichment object.
* **Artifact path default — RESOLVED.** `buildEndpointEvidenceEnrichment()` previously
  defaulted `artifactPath` to `evidence/zk/zk-verifier-adapter.json`. The default is now
  the exported `PUBLISHED_ARTIFACT_PATH`,
  `evidence/zk/hosted/v1/zk-adapter-evidence-response.json`, matching the committed
  static path. An explicit `artifactPath` on a response is still honored. (This is a
  path label only — not a live URL; nothing is fetched from it here.)

Both reconciliations are pinned by `test/vault-evidence-endpoint-reconciliation.test.mjs`
and the nested/flat fixtures in `test/fixtures/zk-adapter-evidence-response.mjs`. Any
unrecognized, malformed, overclaiming, or stale artifact still **fails closed** to the
local reference packet, so the disabled-by-default posture is preserved with no risk.

***

## App consumption design (boundary)

The consumption seam is **read-only and additive**. It enriches the existing
`VaultCandidateReadinessPacket` data shape; the component renders the same shape from
the reference path and the (future) endpoint path, so no component rewrite is required.

The future implementation must observe these hard constraints:

* **Read-only `GET` only** — no `POST`, `PUT`, `PATCH`, or `DELETE`.
* **No wallet data sent** to the hosted evidence endpoint.
* **No user-specific evidence** — the artifact is a single, global, deterministic file;
  the request carries no user, wallet, or query parameters.
* **No private keys, no signatures, no transaction signing** in the request or response handling.
* **No RPC calls** caused by the hosted evidence fetch.
* **No prover execution and no app runtime proving** — the app never runs SP1, a
  circuit, or any proving step.
* **No custody behavior** of any kind.
* **No production-ZK claims**, **no mainnet custody claims**, and **no wallet-safety guarantees**.
* One-shot fetch only — no polling, no background loops, credentials omitted, timeout
  always enforced, HTTPS only (HTTP allowed only for explicit localhost in dev/test).

***

## UX, copy & claim boundaries

The UI describes what the artifact *is*, never more.

**Use:**

* "static evidence artifact" or "validated evidence metadata",
* calm, bounded status copy (e.g. "Validated", "Reference packet", "Endpoint unavailable"),
* clearly mark unavailable or stale evidence as **informational only** — never an error wall, never a blocker.

**Avoid (these claims must not appear):**

* "proof verified in app",
* "production ZK",
* "mainnet safe",
* "custody secured",
* "wallet protected",
* any "quantum-proof" / "guaranteed safe" / "risk-free" / "funds are protected" phrasing.

Unavailable, stale, or invalid evidence is surfaced as a neutral informational note
that the app is using its local reference packet — not as a failure the user must act on.

***

## Telemetry & privacy boundaries

* **No wallet data, address, or user identifier** is sent with the evidence fetch — the
  request is parameterless.
* **No new analytics events** are introduced by this plan. Any future developer
  observability is **dev-only** (gated, e.g. `import.meta.env.DEV`), logs only coarse
  status/reason metadata (status, reason code, whether an error occurred), and **never**
  logs response bodies, URLs with secrets, or user data.
* One-shot resolution — no repeated background telemetry, no per-render logging.

***

## Rollout phases

The vault-side rollout sequence is: static-host publish → read-only staging → **app
feature-flag validation**. The app-side phases below begin at that last step.

* **Phase 0 — Plan (PR #1303).** Docs/spec/test-only. No runtime change, no fetch, no
  URL, flag stays disabled.
* **Phase 1 — Reconcile validators (this PR). DONE.** The app validator/mapper now
  accept the committed nested envelope and default to the published artifact path
  (code + tests). Still **disabled by default**; **no live fetch wired**, no URL config
  added, flag not enabled.
* **Phase 2 — Gated consumption behind the flag (this PR). DONE.** The one-shot
  read-only `GET` seam is now wired end-to-end: `src/lib/vault-evidence-endpoint-page-state.js`
  composes config resolution + the runtime fetch + status building, and
  `src/hooks/useVaultEvidenceEndpointStatus.js` calls it once on mount from
  `StablecoinVaultPage.jsx`. Still **disabled by default in production** — only a
  Vercel **preview** deployment's env vars enable it, per
  [`docs/operations/evidence-endpoint-preview-rehearsal.md`](../operations/evidence-endpoint-preview-rehearsal.md).
  A cross-repo contract test (`test/hosted-evidence-artifact-contract.test.mjs`) pins
  the real published artifact against the app's validator/mapper, and
  `test/hosted-evidence-endpoint-provenance.test.mjs` confirms the enriched UI
  explicitly attributes the data to the hosted endpoint.
* **Phase 3 — Reviewed enablement.** After the hosted artifact is live, reviewed in
  production context, and the preview rehearsal has run without incident, enable in
  production via config — additive, informational, reversible by flipping the flag off.
* **Future — Intelligence Connector.** Separate planning PR; out of scope here.

***

## Acceptance criteria

For **this** planning PR:

* [x] Planning doc exists at `docs/product/hosted-evidence-consumption.md`.
* [x] Doc states no live hosted URL is assumed and none is invented.
* [x] Doc states the feature flag defaults disabled until live and reviewed.
* [x] Doc states read-only `GET` only; no `POST`/`PUT`/`PATCH`/`DELETE`.
* [x] Doc states no wallet data is sent.
* [x] Doc states no prover execution and no app runtime proving.
* [x] Doc states no private keys, signatures, or transactions.
* [x] Doc states no production-ZK, mainnet-custody, or wallet-safety claims.
* [x] Doc states the connector/plugin integration is future work.
* [x] Docs index pointer added in `docs/README.md`.
* [x] A docs guard test pins the above invariants.
* [x] No runtime behavior changed; no fetch, URL config, or feature-flag implementation added.

For the **Phase 1 reconciliation PR** (this PR):

* [x] Validator reconciled to the nested envelope and published artifact path, with tests.
* [x] Nested/flat fixtures and reconciliation tests added; legacy flat shape kept supported.
* [x] Wrong-schema, missing-adapter, malformed-adapter, overclaiming, and stale artifacts
  all fail closed to the reference packet.
* [x] Seam stays disabled by default; no live fetch wired, no URL config, flag not enabled.
* [x] Calm informational copy; no overclaim; unavailable/stale shown as informational only.

For the **Phase 2 gated-consumption PR** (this PR):

* [x] One-shot read-only `GET` wired behind the existing disabled-by-default flag
  (`src/lib/vault-evidence-endpoint-page-state.js` + `src/hooks/useVaultEvidenceEndpointStatus.js`).
* [x] All fail-closed paths already covered end-to-end by
  `test/vault-evidence-endpoint-runtime.test.mjs` and
  `test/vault-evidence-endpoint-page-state.test.mjs` (disabled, no URL, non-HTTPS,
  timeout, HTTP error, malformed JSON, invalid schema, stale `servedAt`).
* [x] The reviewed artifact URL is supplied only via Vercel **preview** environment
  config (never hardcoded, never production) — see
  `docs/operations/evidence-endpoint-preview-rehearsal.md`.
* [x] Cross-repo contract test pins the real published artifact
  (`test/hosted-evidence-artifact-contract.test.mjs`).
* [x] Provenance check confirms enriched evidence is never rendered as locally derived
  (`test/hosted-evidence-endpoint-provenance.test.mjs`).

***

## Follow-up implementation PR scope

Steps 1–3 below are done (Phase 1 + Phase 2, the latter this PR). Step 4 is ongoing.

1. ~~Reconcile `vault-evidence-endpoint-schema.js` / `-client.js` to the committed nested
   envelope and the published artifact path, with tests (no fetch yet).~~ **Done.**
2. ~~Wire the existing one-shot read-only `GET` seam behind the disabled-by-default flag,
   validated against the live, reviewed artifact URL in a non-production environment.~~
   **Done (this PR)** — see `docs/operations/evidence-endpoint-preview-rehearsal.md`.
3. ~~Add the reviewed live URL via config (not a hardcoded literal) only after the
   artifact is live and the security review is complete.~~ **Done (this PR)** — the URL is
   supplied only via Vercel preview env config, pinned to a specific commit SHA.
4. Keep all hard constraints in this doc enforced by the existing
   `vault-pq-integration-safety` guard and per-surface tests. (Ongoing — re-run after
   every change to a vault/pq surface file.)

The **WalletWall Intelligence Connector** remains future work in its own planning PR.
