# VETS Coin API Guide (Live)

Purpose: living reference for partner/internal API calls.

This guide is part of the public project trust stack. Partners can use it to compare authentication, rollout, and integration guidance against the same live status, transparency, and reliability surfaces used elsewhere across VETS Coin.

Related companion:
- `flask_api/docs/partner_threat_model_appendix_2026-04-28.md`
- `flask_api/docs/partner_webhook_retry_profile_and_slo_2026-04-28.md`

## Versioning

Current API version: v1 (implicit, URL paths are unversioned)

Versioning policy:
- Breaking changes will be noted in the changelog and ideally rolled out behind a new path prefix (e.g., `/api/v2/...`).
- Non-breaking additions (new fields/endpoints) can ship without version changes.
- Deprecation window: at least 30 days for partner-facing breaking changes, with reminders at 7 days and 1 day prior to cutoff.

Partner migration checklist:
- Confirm new endpoint paths and auth requirements.
- Validate signing format against the new spec.
- Test in staging with a new key and idempotency headers.
- Roll out to production with monitoring and rollback plan.

## Changelog

- 2026-06-19: Added bounded recurring-incident context to public ops-job status:
  - endpoints:
    - `GET /api/public/system-status`
    - `GET /status`
  - behavior:
    - `status.checks.ops_jobs.jobs[*]` now includes:
      - `incident_state` (`clean|recovered|open|missing`)
      - `latest_non_ok_at`
      - `latest_non_ok_status`
    - `/status` now renders recovered/open job-history detail inline so recent failures stop looking like active breakage when the latest row is already healthy

- 2026-06-18: Added partner sandbox verification surface and contract direction:
  - endpoints:
    - `GET /api/partner/sandbox/verify`
  - behavior:
    - sandbox keys now have a deterministic no-loss onboarding endpoint for signature, nonce, idempotency, and fixture-contract validation
    - live keys receive `403 sandbox_key_required` on that endpoint
    - `GET /api/partner/capabilities` now exposes `key_mode`, sandbox availability, and the sandbox verify endpoint
  - docs:
    - added internal contract memo:
      - `docs/VETS_Partner_Sandbox_Contract_And_Promotion_Gate_2026-06-18.md`

- 2026-03-08: Expanded admin 403 digest triage and export surfaces (tasks `I-224`, `I-225`, `I-231`, `I-232`, `I-234`, `I-236`, `I-237`, `I-238`, `I-1212`, `I-1213`):
  - endpoints:
    - `GET /admin/ops/403-digest`
    - `GET /admin/ops/403-digest/export.json`
    - `GET /admin/ops/403-digest/export.csv`
    - `GET /admin/ops/403-digest/probe-trend.png`
    - `GET|POST /admin/ops/403-digest/suppressions`
    - `GET /admin/ops/403-digest/suppressions/export.json`
  - behavior:
    - 403 digest now supports probe baseline selector via `baseline_mode=rolling_24h|same_hour_yesterday|same_weekday_hour_4w`.
    - Added recurring fingerprint rollup (`path+ip+ua`) with first/last seen and streak counters.
    - Added top-new-path detection (`top_new_paths`) against prior `new_path_lookback_days` window.
    - Added paired denied-vs-probe hourly overlay (`paired_overlay`) for divergence triage.
    - Added probe-spike persistence signal (`probe_spike_signal`) with configurable `spike_consecutive_hours`.
    - Added suppression rules + allowlist support from `ADMIN_403_DIGEST_SUPPRESSIONS_FILE` with explicit rule expiry tracking and suppressed-hit summaries.
    - Added origin tagging metadata (`probe_origin_tags`) and per-row `origin_tag` / `origin_confidence`.
- 2026-02-27: Added compact tuple-validity/flag-key-count helpers + direct CSV copy alias parity (tasks `I-1132`, `I-1133`, `I-1134`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes:
      - `windows_meta_order_bucket_tuple_is_valid` (bool contract check; `tuple_len == 3`)
      - `windows_meta_order_bucket_flags_keys_count` (expected `4`)
  - UI:
    - readiness quick-links row now includes `Copy CSV URL (Direct)` alias for direct-link wording parity.
  - payload additions:
    - `windows_meta_order_bucket_tuple_is_valid` (compact mode)
    - `windows_meta_order_bucket_flags_keys_count` (compact mode)
  - tests:
    - expanded authenticated coverage for direct CSV copy alias and new tuple/flag-count metadata across requested/default/custom ordering.
- 2026-02-27: Added compact tuple-length/flag-key-order helpers + baseline direct JSON copy alias parity (tasks `I-1129`, `I-1130`, `I-1131`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes:
      - `windows_meta_order_bucket_tuple_len` (tuple length contract helper, expected `3`)
      - `windows_meta_order_bucket_flags_keys` (ordered flag keys: `["is_default","is_requested","is_asc","is_custom"]`)
  - UI:
    - readiness quick-links row now includes `Copy JSON URL (Direct)` alias for baseline JSON wording parity.
  - payload additions:
    - `windows_meta_order_bucket_tuple_len` (compact mode)
    - `windows_meta_order_bucket_flags_keys` (compact mode)
  - tests:
    - expanded authenticated coverage for baseline direct JSON copy alias and new tuple/flags metadata across requested/default/custom ordering.
- 2026-02-27: Added compact hex/csv helper fields + compact direct copy alias parity (tasks `I-1126`, `I-1127`, `I-1128`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes:
      - `windows_meta_order_bucket_flags_code_hex` (hex string form of `windows_meta_order_bucket_flags_code`, e.g. `0x06`)
      - `windows_meta_order_bucket_tuple_csv` (`bucket_code,bucket_short,bucket_label`)
  - UI:
    - readiness quick-links row now includes `Copy JSON Compact URL (Direct)` alias pointing at compact direct URL (`compact=1`).
  - payload additions:
    - `windows_meta_order_bucket_flags_code_hex` (compact mode)
    - `windows_meta_order_bucket_tuple_csv` (compact mode)
  - tests:
    - expanded authenticated coverage for compact direct copy alias and new hex/csv helper fields across requested/default/custom ordering.
- 2026-02-27: Added compact flags bitmask/tuple helpers + compact+meta direct copy alias parity (tasks `I-1123`, `I-1124`, `I-1125`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes:
      - `windows_meta_order_bucket_flags_code` (bitmask: `1=default,2=requested,4=asc,8=custom`)
      - `windows_meta_order_bucket_tuple` (`[bucket_code,bucket_short,bucket_label]`)
  - UI:
    - readiness quick-links row now includes `Copy JSON Compact+Meta URL (Direct)` alias pointing at explicit `include_windows_meta=1`.
  - payload additions:
    - `windows_meta_order_bucket_flags_code` (compact mode)
    - `windows_meta_order_bucket_tuple` (compact mode)
  - tests:
    - expanded authenticated coverage for compact+meta direct copy alias and bitmask/tuple semantics across requested/default/custom ordering.
- 2026-02-27: Added compact bucket flags/code helpers + normalized compact copy alias parity (tasks `I-1120`, `I-1121`, `I-1122`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes:
      - `windows_meta_order_bucket_flags` (`is_default`,`is_requested`,`is_asc`,`is_custom`)
      - `windows_meta_order_bucket_code` (`0|1|2` as string)
  - UI:
    - readiness quick-links row now includes `Copy JSON Compact URL (Normalized)` alias pointing to explicit `include_windows_meta=0`.
  - payload additions:
    - `windows_meta_order_bucket_flags` (compact mode)
    - `windows_meta_order_bucket_code` (compact mode)
  - tests:
    - expanded authenticated coverage for normalized compact copy alias and bucket flags/code semantics across requested/default/custom ordering.
- 2026-02-27: Added compact short/default bucket helpers + compact+meta normalized direct-link alias (tasks `I-1117`, `I-1118`, `I-1119`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes:
      - `windows_meta_order_bucket_short` (`def|req_asc|req_custom`)
      - `windows_meta_order_bucket_is_default`
  - UI:
    - readiness quick-links row now includes direct-link alias `Export JSON Compact+Meta (Normalized)` using explicit `include_windows_meta=1`.
  - payload additions:
    - `windows_meta_order_bucket_short` (compact mode)
    - `windows_meta_order_bucket_is_default` (compact mode)
  - tests:
    - expanded authenticated coverage for compact+meta normalized direct-link alias and short/default bucket semantics across requested/default/custom ordering.
- 2026-02-27: Added compact bucket-label/requested-split helpers + no-meta direct-link alias (tasks `I-1114`, `I-1115`, `I-1116`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes:
      - `windows_meta_order_bucket_label` (`Default Asc|Requested Asc|Requested Custom`)
      - `windows_meta_order_bucket_is_requested`
  - UI:
    - readiness quick-links row now includes direct-link alias `Export JSON Compact (No Meta)` using explicit `include_windows_meta=0`.
  - payload additions:
    - `windows_meta_order_bucket_label` (compact mode)
    - `windows_meta_order_bucket_is_requested` (compact mode)
  - tests:
    - expanded authenticated coverage for no-meta direct-link alias and bucket label/requested split semantics across requested/default/custom ordering.
- 2026-02-27: Added compact non-custom/rank helpers + explicit compact+meta normalization copy label (tasks `I-1111`, `I-1112`, `I-1113`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes:
      - `windows_meta_order_is_requested_non_custom`
      - `windows_meta_order_bucket_rank` (`0|1|2`)
  - UI:
    - readiness quick-links row now includes `Copy JSON Compact+Meta URL (Normalized)` using explicit `include_windows_meta=1`.
  - payload additions:
    - `windows_meta_order_is_requested_non_custom` (compact mode)
    - `windows_meta_order_bucket_rank` (compact mode)
  - tests:
    - expanded authenticated coverage for compact+meta normalization copy label and requested/default/custom rank semantics.
- 2026-02-27: Added compact order bucket/requested-asc helpers + explicit no-meta compact copy control (tasks `I-1108`, `I-1109`, `I-1110`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes:
      - `windows_meta_order_bucket` (`default_asc|requested_asc|requested_custom`)
      - `windows_meta_order_is_requested_asc`
  - UI:
    - readiness quick-links row now includes `Copy JSON Compact URL (No Meta)` using explicit `include_windows_meta=0` for normalized handoff links.
  - payload additions:
    - `windows_meta_order_bucket` (compact mode)
    - `windows_meta_order_is_requested_asc` (compact mode)
  - tests:
    - expanded authenticated coverage for compact no-meta quick-links copy control and requested/default/custom bucket semantics.
- 2026-02-27: Added compact requested/custom/default booleans + digest copy parity (tasks `I-1105`, `I-1106`, `I-1107`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes:
      - `windows_meta_order_is_requested_custom`
      - `windows_meta_order_is_default`
  - UI:
    - readiness quick-links row now includes `Copy 403 Digest URL` beside `Open 403 Digest`.
  - payload additions:
    - `windows_meta_order_is_requested_custom` (compact mode)
    - `windows_meta_order_is_default` (compact mode)
  - tests:
    - expanded authenticated coverage for digest quick-links copy parity and requested/default/custom compact-order boolean semantics.
- 2026-02-27: Added quick-links detail CSV copy parity + compact requested/asc booleans (tasks `I-1102`, `I-1103`, `I-1104`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes:
      - `windows_meta_order_is_requested_windows`
      - `windows_meta_order_is_asc`
  - UI:
    - readiness quick-links row now includes `Copy CSV URL` beside `Export CSV (7/14/30)`.
  - payload additions:
    - `windows_meta_order_is_requested_windows` (compact mode)
    - `windows_meta_order_is_asc` (compact mode)
  - tests:
    - expanded authenticated coverage for quick-links-row `Copy CSV URL` parity and requested/custom/default compact-order boolean semantics.
- 2026-02-27: Added incidents wording alias parity + quick-links summary+points copy parity + custom-order bool (tasks `I-1099`, `I-1100`, `I-1101`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes `windows_meta_order_is_custom` as the inverse companion to `windows_meta_order_is_default_asc`.
  - UI:
    - incidents timeline now includes `Copy Incident URL` alias (detail JSON endpoint with non-JSON wording).
    - readiness quick-links row now includes `Copy CSV Summary+Points URL`.
  - payload additions:
    - `windows_meta_order_is_custom` (compact mode)
  - tests:
    - expanded authenticated coverage for incidents alias wording parity, quick-links-row summary+points CSV copy parity, and custom/default ordering booleans.
- 2026-02-27: Added incident/detail alias parity + quick-links summary copy parity + default-order bool (tasks `I-1095`, `I-1096`, `I-1097`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes `windows_meta_order_is_default_asc` (boolean single-check signal for default ascending order behavior).
  - UI:
    - incidents timeline now includes `Copy Incident Detail URL`.
    - readiness quick-links row now includes `Copy CSV Summary URL` parity control.
  - payload additions:
    - `windows_meta_order_is_default_asc` (compact mode)
  - tests:
    - expanded authenticated coverage for incident detail alias control, quick-links-row summary CSV copy parity, and default-vs-custom ordering boolean semantics.
- 2026-02-27: Added detail-copy label parity + compact order-key traceability (tasks `I-1092`, `I-1093`, `I-1094`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact payload now includes `windows_meta_order_keys` (ordered key snapshot used for order evaluation/debug traceability).
  - UI:
    - incidents timeline now includes `Copy Detail JSON URL` alias beside existing detail copy controls.
    - readiness quick-links row now includes `Copy JSON URL` for baseline JSON link/copy parity.
  - payload additions:
    - `windows_meta_order_keys` (compact mode)
  - tests:
    - expanded authenticated coverage for detail-copy alias visibility, quick-links-row baseline JSON copy parity, and order-key metadata in default + custom window scenarios.
- 2026-02-27: Added incident/detail copy wording parity + compact order-source metadata (tasks `I-1089`, `I-1090`, `I-1091`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - compact 403-noise payload now includes `windows_meta_order_source` (`default|requested_windows`) to indicate whether window ordering came from explicit query input.
  - UI:
    - incidents timeline now includes `Copy Incident JSON URL`.
    - readiness quick-links row now includes `Copy JSON Compact URL` alongside existing compact+meta copy control.
  - payload additions:
    - `windows_meta_order_source` (compact mode)
  - tests:
    - expanded authenticated coverage for compact-order source semantics (`default` and `requested_windows`) and new copy controls.
- 2026-02-27: Added quick-links copy parity + compact order metadata semantics (tasks `I-1086`, `I-1087`, `I-1088`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1&include_windows_meta=1`
  - behavior:
    - compact payload now includes `windows_meta_order` (`asc|custom`) so BI jobs can validate window ordering semantics.
  - UI:
    - incidents timeline now includes `Copy Postmortem URL`.
    - readiness quick-links row now includes `Copy JSON Compact+Meta URL` for direct link/copy parity.
  - payload additions:
    - `windows_meta_order` (compact mode)
  - tests:
    - expanded authenticated coverage for quick-links-row compact+meta copy parity, postmortem URL copy control, and custom window-order metadata.
- 2026-02-27: Added copy/export parity controls + compact map-count metadata (tasks `I-1083`, `I-1084`, `I-1085`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1&include_windows_meta=1`
  - behavior:
    - compact metadata payload now includes `windows_meta_map_count` for strict map/list/key parity checks.
  - UI:
    - incidents timeline now includes `Copy Draft JSON URL`.
    - readiness `Admin 403 Noise Trend` copy controls now include `Copy CSV URL` (full detail export URL).
  - payload additions:
    - `windows_meta_map_count` (compact mode with `include_windows_meta=1`)
  - tests:
    - expanded authenticated coverage for `Copy Draft JSON URL`, `Copy CSV URL`, and `windows_meta_map_count`.
- 2026-02-27: Added export/copy parity controls + compact keys-count metadata (tasks `I-1080`, `I-1081`, `I-1082`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1&include_windows_meta=1`
  - behavior:
    - compact metadata payload now includes `windows_meta_keys_count` for strict schema validation checks.
  - UI:
    - incidents timeline now includes `Copy Draft TXT URL`.
    - readiness `Admin 403 Noise Trend` copy controls now include `Copy JSON URL` (baseline full export URL).
  - payload additions:
    - `windows_meta_keys_count` (compact mode with `include_windows_meta=1`)
  - tests:
    - expanded authenticated coverage for `Copy JSON URL`, `Copy Draft TXT URL`, and `windows_meta_keys_count`.
- 2026-02-27: Added compact-link export discoverability + incident minimal URL copy controls (tasks `I-1077`, `I-1078`, `I-1079`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1&include_windows_meta=1`
  - behavior:
    - compact metadata payload now includes `windows_meta_keys` (ordered window key list) in addition to `windows_meta_map`.
  - UI:
    - incidents timeline now includes:
      - `Copy Draft MD Minimal URL`
      - `Copy Draft TXT Minimal URL`
    - readiness `Admin 403 Noise Trend` card now includes direct compact links:
      - `Export JSON Compact (7/14/30)`
      - `Export JSON Compact+Meta (7/14/30)`
  - payload additions:
    - `windows_meta_keys` (compact mode with `include_windows_meta=1`)
  - tests:
    - expanded authenticated coverage for readiness compact links, incident minimal copy controls, and `windows_meta_keys`.
- 2026-02-27: Added readiness/incident handoff copy-link enhancements + compact metadata map (tasks `I-1073`, `I-1074`, `I-1075`, `I-1076`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1&include_windows_meta=1`
  - behavior:
    - compact 403-noise metadata payload now includes `windows_meta_map` keyed by window (`"7"`, `"14"`, `"30"`) for O(1) downstream lookups.
  - UI:
    - readiness `Admin 403 Noise Trend` card now includes one-click copy controls for:
      - `Copy CSV Summary URL`
      - `Copy CSV Summary+Points URL`
      - `Copy JSON Compact URL`
      - `Copy JSON Compact+Meta URL`
    - incidents timeline now includes direct minimal links:
      - `Draft MD Minimal`
      - `Draft TXT Minimal`
  - payload additions:
    - `windows_meta_map` (compact mode with `include_windows_meta=1`)
  - tests:
    - expanded authenticated coverage for readiness copy-control rendering, incidents minimal direct links, and `windows_meta_map`.
- 2026-02-27: Added minimal draft quick actions + compact export windows metadata (tasks `I-1069`, `I-1071`, `I-1072`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1&include_windows_meta=1`
  - behavior:
    - incidents table now includes:
      - `Create Draft Minimal` (markdown minimal template)
      - `Create Draft TXT Minimal` (text minimal template)
    - compact 403-noise export can include window-level metadata records (`windows_meta`) without returning `trends`.
  - payload additions:
    - `windows_meta` (compact mode, optional)
    - `windows_meta_count` (compact mode, optional)
  - tests:
    - added authenticated coverage for compact metadata option and minimal quick-action presence.
- 2026-02-27: Added incident draft template variants + compact 403-noise JSON mode + readiness quick export links (tasks `I-1067`, `I-1068`, `I-1070`):
  - endpoints:
    - `GET /admin/ops/incidents/draft.md?template=v1|minimal`
    - `GET /admin/ops/incidents/draft.txt?template=v1|minimal`
    - `GET /admin/ops/readiness/403-noise/export.json?compact=1`
  - behavior:
    - incident markdown/text drafts now support `template=minimal` for short responder ticket bodies.
    - compact 403-noise JSON mode omits detailed `trends` points and returns window summaries via `summary_by_window`.
  - UI:
    - readiness card now exposes direct links for:
      - `Export CSV Summary (7/14/30)`
      - `Export CSV Summary+Points (7/14/30)`
  - tests:
    - added authenticated coverage for minimal draft templates and compact JSON export behavior.
- 2026-02-27: Added hybrid 403-noise CSV appendix mode + JSON window summary map + incidents `Create Draft TXT` action (tasks `I-1064`, `I-1065`, `I-1066`):
  - endpoints:
    - `GET /admin/ops/readiness/403-noise/export.csv?mode=summary&include_points=1`
  - behavior:
    - summary CSV mode now supports `include_points=1` to append compact per-day point rows after each summary row.
    - 403-noise JSON export now includes top-level `summary_by_window` map keyed by window days (`"7"`, `"14"`, `"30"`).
  - payload additions:
    - `summary_by_window` (JSON export)
  - UI:
    - incidents timeline now includes `Create Draft TXT` quick action (copies draft JSON URL and opens text draft).
  - tests:
    - added authenticated coverage for JSON `summary_by_window` and CSV `mode=summary&include_points=1`.
- 2026-02-27: Added draft text companion + readiness CSV summary mode + suppression-state enum (tasks `I-1060`, `I-1061`, `I-1062`, `I-1063`):
  - endpoints:
    - `GET /admin/ops/incidents/draft.txt`
    - `GET /admin/ops/readiness/403-noise/export.csv?mode=summary`
  - behavior:
    - `draft.txt` is generated from the same incident draft payload as `draft.json` and `draft.md`.
    - readiness CSV export now supports `mode=summary` for one-row-per-window executive reporting.
    - 403-noise anomaly payload now includes `suppression_state` enum (`none|insufficient_baseline`) for downstream BI filtering.
  - payload additions:
    - `anomaly.suppression_state`
  - UI:
    - incidents timeline now includes `Copy Draft MD URL` one-click action beside `Create Draft`.
  - tests:
    - added authenticated coverage for `draft.txt` and `mode=summary` CSV export.
    - added auth-gate smoke coverage for `/admin/ops/incidents/draft.txt`.
- 2026-02-27: Added incident markdown draft export + readiness 403-noise CSV companion and suppression annotation (tasks `I-1056`, `I-1057`, `I-1058`, `I-1059`):
  - endpoints:
    - `GET /admin/ops/incidents/draft.md`
    - `GET /admin/ops/readiness/403-noise/export.csv`
  - behavior:
    - `draft.md` is generated from the same payload as `draft.json`, so human and automation outputs stay aligned.
    - readiness 403-noise exports now include explicit suppression context when anomaly detection is muted due to insufficient baseline points.
  - payload additions:
    - `anomaly.suppressed_due_to_insufficient_baseline`
    - `anomaly.suppression_points_missing`
    - `anomaly.suppression_hint`
  - UI:
    - incidents timeline now includes `Create Draft` quick action (copies draft JSON URL and opens markdown draft).
    - readiness card now includes `Export CSV (7/14/30)` and suppression hint messaging.
  - tests:
    - added authenticated coverage for `draft.md` and `403-noise/export.csv`.
    - added auth-gate smoke coverage for both new endpoints.
- 2026-02-27: Added readiness 403-noise anomaly flag logic (task `I-1052`):
  - behavior:
    - `_collect_admin_403_noise_trend(...)` now emits anomaly metadata comparing latest day noise ratio vs baseline median.
    - anomaly triggers when delta exceeds configurable threshold.
  - payload additions:
    - `anomaly.is_anomalous`
    - `anomaly.reason` (`latest_vs_baseline_median_delta|normal|insufficient_baseline`)
    - `anomaly.delta_threshold_pct`
    - `anomaly.baseline_days`
    - `anomaly.min_points`
    - `anomaly.baseline_points`
    - `anomaly.baseline_median_noise_ratio_pct`
    - `anomaly.delta_vs_baseline_pct`
  - config:
    - `READINESS_ADMIN_403_NOISE_ANOMALY_DELTA_PCT` (default `20`)
    - `READINESS_ADMIN_403_NOISE_ANOMALY_BASELINE_DAYS` (default `7`)
    - `READINESS_ADMIN_403_NOISE_ANOMALY_MIN_POINTS` (default `3`)
  - UI:
    - readiness `Admin 403 Noise Trend` card now shows `Anomaly flag` status + delta/threshold context.
  - tests:
    - added helper anomaly-spike coverage and readiness card assertion.
- 2026-02-27: Added readiness 403-noise trend export endpoint (task `I-1051`):
  - endpoint:
    - `GET /admin/ops/readiness/403-noise/export.json`
  - behavior:
    - exports admin 403 denied/probe trend payloads for dashboard windows (`7`, `14`, `30` days by default).
    - supports `days=<7|14|30>` for single-window export or `windows=7,14,30` for multi-window export.
    - each trend row includes denied/probe totals, overall noise ratio, sparkline, and daily points.
  - UI:
    - readiness `Admin 403 Noise Trend` card now includes `Export JSON (7/14/30)` shortcut.
  - tests:
    - added authenticated export payload coverage.
    - added auth-gate smoke coverage for `/admin/ops/readiness/403-noise/export.json`.
- 2026-02-27: Added automatic incident draft generator endpoint (task `I-933`):
  - endpoint:
    - `GET /admin/ops/incidents/draft.json`
  - behavior:
    - composes a structured incident ticket draft for the selected timeline row (`index`) using:
      - incident window/detail context,
      - preflight guard delta summary (`day_offset` configurable),
      - latest runbook-check status + non-ok checks,
      - top recent error signatures from `event_logs`.
    - returns deterministic `schema_version` (`ops_incident_draft.v1`) with `signals`, `evidence_links`, and `recommended_actions` for automation and manual triage.
  - query controls:
    - incident selection: `hours`, `limit`, `source`, `severity`, `since`, `until`, `index`, `context`
    - signal controls: `preflight_day_offset`, `top_errors_hours`, `top_errors_limit`, `top_errors_top_n`
  - UI:
    - incidents timeline now includes `Draft JSON` quick action per row.
  - tests:
    - added authenticated draft payload coverage.
    - added auth-gate smoke coverage for `/admin/ops/incidents/draft.json`.
- 2026-02-27: Completed preflight guard wave `I-1046`..`I-1050` plus anomaly handoff export `I-1053`:
  - endpoint:
    - `GET /admin/ops/preflight-guards/anomalies.csv`
  - behavior:
    - exports only currently flagged anomaly rows from the 7-day baseline detector (same filters as anomalies JSON).
    - includes anomaly/baseline metadata + per-section changed-field counts for ticket handoff.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_is_nonnegative` (bool; numeric parse only).
  - readiness:
    - added helper text for:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_is_nonpositive` numeric-only emission context.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_is_nonnegative` numeric-only emission + semantics (`true` when sign bucket is `zero|positive`).
    - added preflight guard quick links for `Anomalies JSON` and `Anomalies CSV`.
  - tests:
    - expanded admin delta-summary parity coverage:
      - nonnegative parity (`254=false`, `255=true`, `256=true`) and non-numeric suppression.
    - added authenticated coverage for `/admin/ops/preflight-guards/anomalies.csv`.
    - added auth-gate smoke coverage for anomalies CSV endpoint.
    - added digest stability coverage for explicit-field regressed links when preflight context includes `...sign_bucket_is_nonnegative`.
- 2026-02-24: Added preflight-guard anomalies endpoint with 7-day baseline spike detection (task `I-931`):
  - endpoint:
    - `GET /admin/ops/preflight-guards/anomalies.json`
  - behavior:
    - compares each day’s preflight-guard drift against recent baseline change counts.
    - flags unusual rows using configurable z-score / flat-baseline rules.
    - supports focused anomaly views via `section=`, `fields=`, and `fields_preset=...`.
  - query controls:
    - `days` (default `30`), `baseline_days` (default `7`), `min_baseline_points` (default `3`), `zscore_threshold` (default `2.0`), `min_change_count` (default `2`).
  - tests:
    - added authenticated endpoint coverage for anomaly detection and preset-filter behavior.
    - added auth-gate smoke coverage for anomalies endpoint.
- 2026-02-24: Added readiness `Admin 403 Noise Trend` card with 7-day denied/probe telemetry (task `I-932`):
  - readiness:
    - `/admin/ops/readiness` now shows a dedicated `Admin 403 Noise Trend` card with:
      - 7-day denied vs probe totals.
      - window noise ratio (`probe / (probe + denied)`).
      - 7-day ASCII sparkline for noise ratio trend.
      - latest-day denied/probe snapshot + direct link to `/admin/ops/403-digest`.
  - backend:
    - added daily trend aggregation helper for `admin_403_denied` + `admin_403_probe` and ratio sparkline rendering.
  - tests:
    - added helper coverage for daily ratio math and trend shaping.
    - expanded readiness page assertions for new card content.
- 2026-02-24: Added sign-bucket-is-nonzero emission helper + sign-bucket-is-nonpositive metadata (waves `I-1041`..`I-1045`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_is_nonzero` appears only when `include_fields_max` parses as numeric and sign-bucket metadata is available.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_is_nonpositive=true` only when sign bucket is `negative` or `zero`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_is_nonpositive` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for sign-bucket-is-nonpositive parity (`254=true`, `255=true`, `256=false`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes sign-bucket-is-nonpositive metadata.
- 2026-02-24: Added sign-bucket-is-zero emission-context helper + sign-bucket-is-nonzero metadata (waves `I-1036`..`I-1040`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_is_zero` appears only when `include_fields_max` parses as numeric and sign-bucket metadata is available.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_is_nonzero=true` only when sign bucket is `negative` or `positive`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_is_nonzero` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for sign-bucket-is-nonzero parity (`254=true`, `255=false`, `256=true`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes sign-bucket-is-nonzero metadata.
- 2026-02-24: Added sign-bucket-is-zero emission helper + sign-bucket-is-zero metadata (waves `I-1031`..`I-1035`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_matches_index` appears only when `include_fields_max` parses as numeric and sign-bucket metadata is available.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_is_zero=true` only when sign bucket is `zero`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_is_zero` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for sign-bucket-is-zero parity (`254=false`, `255=true`, `256=false`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes sign-bucket-is-zero metadata.
- 2026-02-24: Added sign-bucket-sign emission helper + sign-bucket/index parity metadata (waves `I-1021`..`I-1030`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_matches_sign=true` when sign bucket equals sign metadata.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_matches_index=true` when sign bucket aligns with side index mapping (`-1=negative`, `0=zero`, `1=positive`).
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_matches_sign` (bool; numeric parse only).
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket_matches_index` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for sign-bucket/sign parity and sign-bucket/index parity (`254/255/256=true`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes sign-bucket/sign parity and sign-bucket/index parity metadata.
- 2026-02-24: Added side-index-sign-is-nonnegative emission helper + side-index-sign-bucket alias metadata (waves `I-1016`..`I-1020`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_nonnegative` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket` mirrors sign metadata as canonical `negative|zero|positive` enum alias.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_bucket` (string; numeric parse only).
  - tests:
    - expanded admin coverage for side-index-sign-bucket parity (`254=negative`, `255=zero`, `256=positive`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes side-index-sign-bucket metadata.
- 2026-02-24: Added side-index-sign-is-nonpositive emission helper + side-index-sign-is-nonnegative metadata (waves `I-1011`..`I-1015`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_nonpositive` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_nonnegative=true` when side-index sign is `zero` or `positive`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_nonnegative` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for side-index-sign-is-nonnegative parity (`254=false`, `255=true`, `256=true`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes side-index-sign-is-nonnegative metadata.
- 2026-02-24: Added side-index-sign-is-positive emission helper + side-index-sign-is-nonzero/nonpositive metadata (waves `I-1001`..`I-1010`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_positive` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_nonzero=true` only when side-index sign is not `zero`.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_nonpositive=true` when side-index sign is `negative` or `zero`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_nonzero` (bool; numeric parse only).
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_nonpositive` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for side-index-sign-is-nonzero parity (`254=true`, `255=false`, `256=true`) and side-index-sign-is-nonpositive parity (`254=true`, `255=true`, `256=false`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes side-index-sign-is-nonzero and side-index-sign-is-nonpositive metadata.
- 2026-02-24: Added side-index-sign-is-negative emission helper + side-index-sign-is-positive metadata (waves `I-996`..`I-1000`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_negative` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_positive=true` only when side-index sign equals `positive`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_positive` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for side-index-sign-is-positive parity (`254=false`, `255=false`, `256=true`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes side-index-sign-is-positive metadata.
- 2026-02-24: Added side-index-sign/index emission helper + side-index-sign-is-negative metadata (waves `I-991`..`I-995`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_matches_index` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_negative=true` only when side-index sign equals `negative`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_negative` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for side-index-sign-is-negative parity (`254=true`, `255=false`, `256=false`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes side-index-sign-is-negative metadata.
- 2026-02-24: Added side-index-sign-zero emission helper + side-index-sign/index parity metadata (waves `I-986`..`I-990`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_zero` appears only when `include_fields_max` parses as numeric and side-index-sign metadata is available.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_matches_index=true` means side-index sign aligns with side-index mapping (`-1=negative`, `0=zero`, `1=positive`).
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_matches_index` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for side-index-sign/index parity (`254/255/256=true`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes side-index-sign/index parity metadata.
- 2026-02-23: Added side-label-midpoint emission helper + side-index-sign metadata + side-index-sign-is-zero metadata (waves `I-976`..`I-985`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_label_is_midpoint` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign` maps side index to `negative|zero|positive` for `-1|0|1`.
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_zero=true` only when side-index sign equals `zero`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_sign` (string; numeric parse only).
      - `include_fields_max_requested_numeric_midpoint_side_index_sign_is_zero` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for side-index-sign parity (`254=negative`, `255=zero`, `256=positive`) and side-index-sign-is-zero parity (`254=false`, `255=true`, `256=false`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes side-index-sign and side-index-sign-is-zero metadata.
- 2026-02-23: Added side-index-midpoint emission helper + side-label-side parity metadata + side-label-midpoint metadata (waves `I-966`..`I-975`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_index_is_midpoint` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side_label_matches_side=true` means side label aligns with side token mapping (`below=min`, `exact=midpoint`, `above=max`).
      - `include_fields_max_requested_numeric_midpoint_side_label_is_midpoint=true` only when side label equals `exact`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_label_matches_side` (bool; numeric parse only).
      - `include_fields_max_requested_numeric_midpoint_side_label_is_midpoint` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for side-label-side parity (`254/255/256=true`) and side-label-is-midpoint parity (`254=false`, `255=true`, `256=false`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes side-label-side parity and side-label-is-midpoint metadata.
- 2026-02-23: Added side-label-index emission helper + side-index-side parity metadata + side-index-midpoint metadata (waves `I-956`..`I-965`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_label_matches_index` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side_index_matches_side=true` means side index aligns with side token mapping (`-1=min`, `0=midpoint`, `1=max`).
      - `include_fields_max_requested_numeric_midpoint_side_index_is_midpoint=true` only when side index equals `0`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_index_matches_side` (bool; numeric parse only).
      - `include_fields_max_requested_numeric_midpoint_side_index_is_midpoint` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for side-index-side parity (`254/255/256=true`) and side-index-is-midpoint parity (`254=false`, `255=true`, `256=false`) with non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes side-index-side parity and side-index-is-midpoint metadata.
- 2026-02-23: Added midpoint-side-label emission helper + side-label-is-exact metadata + side-label-index parity metadata (waves `I-946`..`I-955`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_label` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side_label_is_exact` appears only when `include_fields_max` parses as numeric and is true only for `side_label=exact`.
      - `include_fields_max_requested_numeric_midpoint_side_label_matches_index=true` means side label aligns with side index mapping (`-1=below`, `0=exact`, `1=above`).
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_label_is_exact` (bool; numeric parse only).
      - `include_fields_max_requested_numeric_midpoint_side_label_matches_index` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for midpoint-side-label-is-exact parity (`254=false`, `255=true`, `256=false`) plus side-label-index-parity checks (`254/255/256=true`) and non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes side-label-is-exact and side-label-index-parity metadata.
- 2026-02-23: Added midpoint-side-is-midpoint emission helper + midpoint-side-label parity coverage + midpoint-side-label digest link guard (wave `I-941`,`I-942`,`I-943`,`I-944`,`I-945`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_is_midpoint` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side_label` maps side index `-1/0/1` to `below|exact|above`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_label` (`below|exact|above`; numeric parse only).
  - tests:
    - expanded admin coverage for midpoint-side-label parity (`254=below`, `255=exact`, `256=above`) and non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes midpoint-side-label metadata.
- 2026-02-23: Added midpoint-side-index emission helper + midpoint-side-is-midpoint metadata + midpoint-side-is-midpoint digest link guard (wave `I-926`,`I-927`,`I-928`,`I-929`,`I-930`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_side_index` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side_is_midpoint=true` only at exact midpoint.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side_is_midpoint` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for midpoint-side-is-midpoint parity (`254=false`, `255=true`, `256=false`) and non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes midpoint-side-is-midpoint metadata.
- 2026-02-23: Added midpoint-delta-abs emission helper + midpoint-side metadata + midpoint-side-index metadata (waves `I-916`..`I-925`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_delta_abs` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_side` / `_midpoint_side_index` semantics for below/equal/above midpoint triage.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_side` (`min|midpoint|max`; numeric parse only).
      - `include_fields_max_requested_numeric_midpoint_side_index` (`-1|0|1`; numeric parse only).
  - tests:
    - expanded admin coverage for midpoint-side and midpoint-side-index parity (`254`, `255`, `256`) and non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes midpoint-side and midpoint-side-index metadata.
- 2026-02-23: Added midpoint-delta emission helper + midpoint-delta absolute metadata + midpoint-delta-abs digest link guard (wave `I-911`,`I-912`,`I-913`,`I-914`,`I-915`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_delta_signed` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_delta_abs=0` means requested numeric max is exactly at midpoint.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_delta_abs` (absolute value of midpoint signed delta; numeric parse only).
  - tests:
    - expanded admin coverage for midpoint-delta absolute parity (`254=1`, `255=0`, `256=1`) and non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes midpoint-delta absolute metadata.
- 2026-02-23: Added midpoint-reference emission helper + midpoint-delta signed metadata + midpoint-delta digest link guard (wave `I-906`,`I-907`,`I-908`,`I-909`,`I-910`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_reference` appears only when `include_fields_max` parses as numeric.
      - `include_fields_max_requested_numeric_midpoint_delta_signed < 0` trends toward min, `0` is midpoint, and `> 0` trends toward max.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_delta_signed` (`requested_numeric - midpoint_reference`; numeric parse only).
  - tests:
    - expanded admin coverage for midpoint-delta signed parity (`254=-1`, `255=0`, `256=+1`) and non-numeric suppression.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes midpoint-delta metadata.
- 2026-02-23: Added midpoint-reference helpers + midpoint-reference metadata + midpoint-reference digest link guard (wave `I-901`,`I-902`,`I-903`,`I-904`,`I-905`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_midpoint_tie=true` implies `include_fields_max_requested_numeric_nearest_boundary=tie`.
      - `include_fields_max_requested_numeric_midpoint_reference` is derived from current min/max clamp bounds (`10` and `500`).
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_reference` (int; numeric parse only, current midpoint `255`).
  - tests:
    - expanded admin coverage for midpoint-reference parity, including `254/255/256` nearest-boundary and tie behavior.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes midpoint-reference metadata.
- 2026-02-23: Added nearest-tie helper notes + midpoint-tie metadata + midpoint-context digest link guard (wave `I-896`,`I-897`,`I-898`,`I-899`,`I-900`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_nearest_boundary=tie` indicates equal distance to min and max boundaries.
      - midpoint tie currently occurs at `include_fields_max=255` for min/max bounds `10` and `500`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_midpoint_tie` (bool; numeric parse only).
  - tests:
    - expanded admin coverage for midpoint-tie parity across representative values (including `255=true` and non-tie values).
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes midpoint-tie metadata.
- 2026-02-23: Added absolute-distance zero helper notes + nearest-boundary metadata + nearest-context digest link guard (wave `I-891`,`I-892`,`I-893`,`I-894`,`I-895`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_distance_abs_to_min=0` means requested max exactly equals min boundary (`10`).
      - `include_fields_max_requested_numeric_distance_abs_to_max=0` means requested max exactly equals max boundary (`500`).
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_nearest_boundary` (`min|max|tie`; numeric parse only).
  - tests:
    - expanded admin coverage for nearest-boundary parity across representative values (including midpoint `255 -> tie`).
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes nearest-boundary metadata.
- 2026-02-23: Added in-range boundary helper + absolute-distance metadata + distance-metadata digest link guard (wave `I-886`,`I-887`,`I-888`,`I-889`,`I-890`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_distance_direction=in_range` includes exact boundary values (`10`,`500`).
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_distance_abs_to_min` (absolute distance to min bound `10`; numeric parse only).
      - `include_fields_max_requested_numeric_distance_abs_to_max` (absolute distance to max bound `500`; numeric parse only).
  - tests:
    - expanded admin coverage for absolute-distance parity across boundary, in-range, and off-range numeric requests.
    - added digest coverage ensuring explicit-field regressed links remain stable when preflight context includes distance-direction + absolute-distance metadata.
- 2026-02-23: Added distance-sign helpers + numeric distance-direction metadata + explicit-max digest link stability guard (wave `I-881`,`I-882`,`I-883`,`I-884`,`I-885`):
  - readiness:
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_distance_to_min < 0` means requested numeric max is below min clamp (`10`).
      - `include_fields_max_requested_numeric_distance_to_max < 0` means requested numeric max is above max clamp (`500`).
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_distance_direction` (`below_min|in_range|above_max`; numeric parse only).
  - tests:
    - expanded admin coverage for distance-direction parity across boundary, in-range, and off-range numeric requests.
    - added digest coverage ensuring explicit-field regressed summary links stay stable when preflight context carries non-default include-fields max metadata.
- 2026-02-23: Added ultra forensic combo preset + boundary-distance metadata + boundary-none helper semantics (wave `I-876`,`I-877`,`I-878`,`I-879`,`I-880`):
  - readiness:
    - `Preflight Guard Delta` combo copy controls now include:
      - `Copy 65536B+500 URL`
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_at_boundary=none` means numeric requested max was not exactly `10` or `500`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_distance_to_min` (requested numeric minus `10`; numeric parse only).
      - `include_fields_max_requested_numeric_distance_to_max` (`500` minus requested numeric; numeric parse only).
  - tests:
    - added admin coverage for distance-to-boundary parity across boundary, in-range, and off-range numeric requests.
- 2026-02-23: Added max forensic combo + boundary-state metadata + responder sparse-link guard (wave `I-871`,`I-872`,`I-873`,`I-874`,`I-875`):
  - readiness:
    - `Preflight Guard Delta` combo copy controls now include:
      - `Copy 32768B+500 URL`
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_is_negative=true` indicates malformed low numeric input.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_at_boundary` (`min|max|none`; numeric parse only).
  - tests:
    - added admin coverage for `include_fields_max_requested_numeric_at_boundary` parity across boundary and non-boundary numeric values.
    - added digest coverage for single `responder` section regressions with explicit `fields=` and threshold-suppressed include-fields links.
- 2026-02-23: Added extreme forensic combo preset + negative-numeric metadata + boundary parity/artefact sparse-link guards (wave `I-866`,`I-867`,`I-868`,`I-869`,`I-870`):
  - readiness:
    - `Preflight Guard Delta` combo copy controls now include:
      - `Copy 16384B+500 URL`
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_clamped_to` is the final applied cap after numeric clamp.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_is_negative` (bool; numeric parse only).
  - tests:
    - added admin coverage for exact-boundary numeric parity (`include_fields_max=10` and `include_fields_max=500`) across requested numeric metadata fields.
    - added digest coverage for single `artifact` section regressions with explicit `fields=` and threshold-suppressed include-fields links.
- 2026-02-23: Added forensic combo preset + requested-max clamped metadata + single-section threshold-suppressed explicit-field guard (wave `I-861`,`I-862`,`I-863`,`I-864`,`I-865`):
  - readiness:
    - `Preflight Guard Delta` combo copy controls now include:
      - `Copy 8192B+500 URL`
    - helper text now clarifies:
      - `include_fields_max_requested_numeric_in_range=true` means the numeric requested value was already inside `10..500`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_clamped_to` (final effective numeric cap after clamp; numeric parse only).
  - tests:
    - added admin coverage for zero-value clamp parity (`include_fields_max=0`) and new clamped metadata behavior.
    - added digest coverage for stable explicit-field regressed links when only a single section is regressed and include-fields links are threshold-suppressed.
- 2026-02-23: Added deep combo preset + requested-max numeric range metadata + threshold-suppressed explicit-fields order guard (wave `I-856`,`I-857`,`I-858`,`I-859`,`I-860`):
  - readiness:
    - `Preflight Guard Delta` combo copy controls now include:
      - `Copy 4096B+300 URL`
    - helper text now clarifies:
      - `include_fields_max_requested_numeric` appears only when `include_fields_max` parses as numeric.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric_in_range` (`true` when parsed requested numeric is within `10..500`).
  - tests:
    - added admin coverage for negative numeric input sign preservation (`include_fields_max=-5` keeps `include_fields_max_requested_numeric=-5`).
    - added digest coverage ensuring sparse regressed-section ordering remains stable with explicit `fields=` when include-fields links are threshold-suppressed.
- 2026-02-23: Added expanded combo presets + parsed requested-max numeric metadata + explicit-fields sparse-order guard (wave `I-851`,`I-852`,`I-853`,`I-854`,`I-855`):
  - readiness:
    - `Preflight Guard Delta` combo copy controls now include:
      - `Copy 1024B+100 URL`
      - `Copy 2048B+300 URL`
    - helper text now clarifies:
      - `include_fields_max_effective_source` is `default` for empty/non-numeric input and `requested` for numeric input.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now conditionally includes:
      - `include_fields_max_requested_numeric` (int; present only when `include_fields_max` parses as numeric).
  - tests:
    - added admin coverage ensuring non-numeric input keeps `include_fields_max_effective_source=default` while preserving string echo.
    - added digest coverage for stable sparse regressed-section ordering when explicit `fields=` filters drive summary links.
- 2026-02-22: Added richer combo presets + max-source metadata + sparse-section order guard (wave `I-846`,`I-847`,`I-848`,`I-849`,`I-850`):
  - readiness:
    - `Preflight Guard Delta` combo copy controls now include:
      - `Copy 2048B+100 URL`
      - `Copy 2048B+200 URL`
    - helper text now clarifies:
      - `include_fields_max_requested` appears only when `include_fields_max` query param is non-empty.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now includes:
      - `include_fields_max_effective_source` (`default|requested`)
  - tests:
    - added admin coverage for trimmed numeric input handling (`" 50 "` => `requested="50"`).
    - added digest coverage for stable regressed-section link ordering with sparse section sets.
- 2026-02-22: Added max-cap diagnostics copy presets + full clamp bounds metadata + threshold-suppressed ordering guard (wave `I-841`,`I-842`,`I-843`,`I-844`,`I-845`):
  - readiness:
    - `Preflight Guard Delta` max-count copy controls now include:
      - `Copy Max200 URL`
      - `Copy Max500 URL`
    - added helper row clarifying invalid max behavior:
      - non-numeric `include_fields_max` falls back to default (`200`) without setting `clamped=true`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now includes:
      - `include_fields_max_changed_fields_min` (`10`)
      - `include_fields_max_changed_fields_max` (`500`)
  - tests:
    - added admin coverage confirming `include_fields_max_requested` is omitted for blank/whitespace query values.
    - added digest coverage for stable regressed-section link ordering when include-fields links are suppressed by threshold.
- 2026-02-22: Added readiness max-default visibility + combo copy presets + summary requested-max echo (wave `I-836`,`I-837`,`I-838`,`I-839`,`I-840`):
  - readiness:
    - `Preflight Guard Delta` now shows `Max-count default` sourced from summary metadata (`200` by default).
    - added combo copy controls:
      - `Copy 512B+25 URL`
      - `Copy 1024B+50 URL`
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now includes:
      - `include_fields_max_requested` (only when `include_fields_max` query param is provided).
  - tests:
    - added digest coverage ensuring regressed section summary links maintain stable section ordering:
      - `responder -> synthetic -> digest -> token -> artifact`
    - added admin coverage confirming `include_fields_max_changed_fields_clamped` stays `false` for in-range requests (`25`,`50`,`100`).
- 2026-02-22: Added preflight max-count copy presets + clamp metadata fields + digest link guard test (wave `I-831`,`I-832`,`I-833`,`I-834`,`I-835`):
  - readiness:
    - `Preflight Guard Delta` now includes max-count copy controls for summary+fields URLs:
      - `Copy Max25 URL`
      - `Copy Max50 URL`
      - `Copy Max100 URL`
    - added helper row documenting max-count clamp policy:
      - `include_fields_max` is clamped to `10..500` (default `200`).
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now also includes:
      - `include_fields_max_changed_fields_default` (`200`)
      - `include_fields_max_changed_fields_clamped` (`true|false`)
  - digest tests:
    - added coverage ensuring digest-generated preflight summary links do not emit `include_fields_max=` unless explicitly supported/configured in the digest link builder.
- 2026-02-22: Completed preflight readiness/copy wave + include-fields max knob (wave `I-794`,`I-797`,`I-799`,`I-826`,`I-827`,`I-828`,`I-829`,`I-830`):
  - readiness:
    - `Preflight Guard Delta` now includes JSON copy controls next to CSV copies:
      - `Copy Delta JSON URL`
      - `Copy History JSON URL`
    - card now also surfaces explicit JSON export rows:
      - `Delta export JSON URL`
      - `History export JSON URL`
    - shows `Byte-limit clamp max` (`250000`) and concrete regressed section copy buttons (`responder`, `token`).
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now supports:
      - `include_fields_max=<int>` (safe clamp: `10..500`, default `200`).
    - existing metadata remains:
      - `include_fields_max_changed_fields`
      - `include_fields_bytes_limit_max`
  - tests:
    - added admin coverage for new JSON copy controls in readiness.
    - added admin coverage for `include_fields_max` clamp behavior (`low`, `high`, `invalid`).
    - added digest coverage for negative threshold clamp (`OPS_DAILY_DIGEST_PREFLIGHT_INCLUDE_FIELDS_MAX_CHANGE_COUNT<0` => `0`).
- 2026-02-22: Added readiness threshold/env visibility + regressed-template copy controls + max-changed-fields metadata (wave `I-821`,`I-822`,`I-823`,`I-824`,`I-825`):
  - readiness:
    - `Preflight Guard Delta` now shows `Digest include-fields threshold` (current env-resolved value).
    - added `Digest regressed-section template copies`:
      - `Copy Regressed Template (fields on)`
      - `Copy Regressed Template (fields off)`
    - added placeholder guidance:
      - replace `SECTION_NAME` with `responder|synthetic|digest|token|artifact`.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now includes:
      - `include_fields_max_changed_fields` (current cap for changed-field list, currently `200`).
  - daily digest test coverage:
    - added clamp behavior test for `OPS_DAILY_DIGEST_PREFLIGHT_INCLUDE_FIELDS_MAX_CHANGE_COUNT=0` (always disable inline `include_fields=1` links).
  - readiness/admin test coverage:
    - added assertions that thresholded digest copy URLs preserve explicit-fields-over-preset precedence.
- 2026-02-22: Added thresholded digest-copy presets + effective byte-limit echo + inline-threshold visibility (wave `I-816`,`I-817`,`I-818`,`I-819`,`I-820`):
  - readiness:
    - `Preflight Guard Delta` now includes thresholded digest summary copy controls:
      - `Copy Digest URL (fields on)`
      - `Copy Digest URL (fields off)`
    - helper examples now explain byte-budget intent for:
      - `512` (tickets), `1024` (chat), `2048` (postmortems)
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now includes:
      - `include_fields_bytes_limit_effective`
    - this echoes post-clamp effective bytes limit (including `0` when unset/disabled).
  - daily digest text:
    - preflight section now includes explicit threshold visibility line:
      - `preflight_guard_include_fields_inline: threshold=<N> change_count=<M> applied=<yes|no>`
  - test coverage:
    - added parity assertion that `include_fields_bytes_limit=0` behaves exactly like unset.
    - added digest assertions for threshold visibility and include-fields on/off behavior.
- 2026-02-22: Added digest include-fields thresholding + byte-budget copy presets + byte-limit applied introspection (wave `I-808`,`I-811`,`I-812`,`I-813`,`I-814`,`I-815`):
  - daily digest text:
    - preflight summary links now include `include_fields=1` only when `change_count` is within a safe threshold.
    - threshold is configurable via:
      - `OPS_DAILY_DIGEST_PREFLIGHT_INCLUDE_FIELDS_MAX_CHANGE_COUNT` (default `25`).
  - readiness:
    - `Preflight Guard Delta` now adds copy presets for byte-budget summary links:
      - `include_fields_bytes_limit=512`
      - `include_fields_bytes_limit=1024`
      - `include_fields_bytes_limit=2048`
    - helper text now explicitly calls out server-side byte-limit clamping.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now includes:
      - `include_fields_bytes_limit_applied` (`true|false`)
    - existing `field_bytes_over_limit` + `include_fields_bytes_limit` semantics remain.
  - test coverage:
    - added digest tests for threshold-on/threshold-off include-fields link behavior.
    - added admin test for high byte-limit case proving `field_bytes_over_limit` stays `False` when limit is sufficient.
- 2026-02-22: Added summary+fields copy control + include-fields byte limit + copy-color help + digest explicit-field normalization tests (wave `I-806`,`I-807`,`I-809`,`I-810`):
  - readiness:
    - `Preflight Guard Delta` now includes:
      - `Summary + fields URL Copy` (`include_fields=1` deep-link copy control).
      - inline helper text documenting copy-status colors (`ok` green, `fail` red).
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now supports:
      - `include_fields_bytes_limit=<int>`
    - when byte-limit mode is used, response includes:
      - `field_bytes_over_limit` (`true|false`)
      - `include_fields_bytes_limit` (echoed when applied)
    - `changed_fields_truncated` now also reflects byte-limit truncation.
  - daily digest tests:
    - added coverage for explicit-field list normalization (trim/dedupe/order-preserve) in preflight summary links.
    - added coverage that explicit-field fallback also works when only `fields_filter` is present.
- 2026-02-22: Added include-fields readiness deep link + digest explicit-fields fallback + copy-status color states + field-bytes metric (wave `I-801`,`I-802`,`I-803`,`I-804`,`I-805`):
  - readiness:
    - `Preflight Guard Delta` now includes a direct `Summary + fields URL` row pointing to:
      - `GET /admin/ops/preflight-guards/delta.summary.json?...&include_fields=1`
    - preflight copy status now uses explicit visual states:
      - success: `admin-copy-state-ok`
      - failure: `admin-copy-state-fail`
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now also includes:
      - `changed_fields_bytes` (UTF-8 byte size of the returned compact `changed_fields` list)
  - daily digest text:
    - preflight summary links now fall back to explicit `fields=` when preflight context indicates resolved explicit fields.
    - invalid `fields_preset` values are ignored in generated summary links.
- 2026-02-22: Added preset-preserving digest links + readiness filter-effect visibility + combined copy-URL encoding coverage (wave `I-788`,`I-789`,`I-795`):
  - readiness:
    - `Preflight Guard Delta` now shows `Filters effective` (`none|preset|explicit_fields`) from summary filter resolution.
    - copy URL controls are now validated for combined `fields` + `fields_preset` query contexts (encoded comma handling).
  - daily digest text:
    - preflight summary links now preserve active `fields_preset` when present in preflight context:
      - `preflight_guard_delta_summary_json`
      - `preflight_guard_delta_summary_regressed_<section>`
- 2026-02-22: Added summary field-list toggle + effective-filter marker + copy-status timeout reset (wave `I-787`,`I-790`,`I-791`):
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json` now includes:
      - `filters_effective` (`none|preset|explicit_fields`) to show how filters were resolved.
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_fields=1` now adds compact changed-field payload:
      - `changed_fields` (sorted, capped at 200)
      - `changed_fields_total`
      - `changed_fields_truncated`
      - `fields_preset_resolved_fields` is still included only when preset-driven filtering is applied.
  - readiness:
    - preflight copy-status labels now auto-clear after a short timeout so the card stays clean after repeated copy actions.
- 2026-02-22: Added preflight regressed-section summary links + preset resolved-fields echo + extra readiness copy controls (wave `I-778`,`I-783`,`I-786`):
  - readiness:
    - `Preflight Guard Delta` now includes quick-copy controls for:
      - `Summary URL`
      - `Delta CSV URL`
      - `History CSV URL`
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json` now includes:
      - `fields_preset_resolved_fields` when `fields_preset` is actively applied.
  - daily digest text:
    - when preflight severity is `regressed`, digest now includes section-filtered summary links:
      - `.../preflight-guards/delta.summary.json?day_offset=1&section=<section>&include_compare_timestamps=1`
- 2026-02-22: Added readiness summary-copy control + preflight preset CSV parity + digest compare-timestamp summary link (wave `I-776`,`I-782`,`I-784`,`I-785`):
  - readiness:
    - `Preflight Guard Delta` card now includes a `Copy Summary URL` control beside the current summary endpoint URL.
    - added helper note clarifying filter precedence: explicit `fields=` overrides `fields_preset`.
  - preflight CSV exports:
    - `GET /admin/ops/preflight-guards/delta.csv` metadata includes:
      - `fields_preset=<value|none>`
    - `GET /admin/ops/preflight-guards/history.csv` metadata includes:
      - `fields_preset=<value|none>`
  - daily digest text:
    - when preflight baseline exists and changes are listed, digest now includes:
      - `preflight_guard_delta_summary_json: /admin/ops/preflight-guards/delta.summary.json?day_offset=1&include_compare_timestamps=1`
- 2026-02-22: Added preflight fields presets + compare timestamps toggle + digest section-count object (wave `I-772`,`I-774`,`I-775`):
  - readiness:
    - `Preflight Guard Delta` selector now supports:
      - `fields_preset=docs_mode|token_secret|synthetic_thresholds`
    - preset selection is threaded through quick export links (`delta.json`, `delta.csv`, `history.csv`, `history.json`, `delta.summary.json`).
  - preflight exports:
    - `GET /admin/ops/preflight-guards/delta.json` now echoes:
      - `fields_preset`
      - `fields_preset_applied`
      - `fields_filter_explicit`
    - `GET /admin/ops/preflight-guards/delta.summary.json` now echoes:
      - `fields_preset`
      - `fields_preset_applied`
    - `GET /admin/ops/preflight-guards/delta.summary.json?include_compare_timestamps=1` now adds:
      - `compare_timestamps.current_generated_at_utc`
      - `compare_timestamps.baseline_generated_at_utc`
    - `GET /admin/ops/preflight-guards/delta.csv` and `GET /admin/ops/preflight-guards/history.csv` now include `fields_preset` metadata comments.
    - `GET /admin/ops/preflight-guards/history.json` now echoes:
      - `fields_preset`
      - `fields_preset_applied`
  - digest artifact:
    - top-level `preflight_section_counts` now mirrors section counts (`all|responder|synthetic|digest|token|artifact`) outside `metrics`.
- 2026-02-22: Added readiness section-count summary drilldowns + compact changed-sections field (wave `I-769`,`I-771`,`I-773`):
  - readiness:
    - `Preflight Guard Delta` section-count row now renders clickable links for:
      - `all`, `responder`, `synthetic`, `digest`, `token`, `artifact`
    - each link opens matching:
      - `GET /admin/ops/preflight-guards/delta.summary.json?...&section=<section>&fields=...`
    - card now shows explicit current-summary URL hint line.
  - delta summary payload:
    - `GET /admin/ops/preflight-guards/delta.summary.json` now includes:
      - `changed_sections` (compact array of section names with non-zero change counts).
- 2026-02-22: Added delta-summary cache validators + normalization coverage + digest section-count line (wave `I-767`,`I-768`,`I-770`):
  - `GET /admin/ops/preflight-guards/delta.summary.json`:
    - now returns cache validators:
      - `ETag`
      - `Last-Modified`
    - supports conditional `304` with:
      - `If-None-Match`
      - `If-Modified-Since`
  - digest text:
    - preflight section now includes section-level changed-field counts:
      - `preflight_guard_section_counts: all=... responder=... synthetic=... digest=... token=... artifact=...`
  - test coverage:
    - added normalization assertions for `delta.summary.json` invalid `section` fallback and malformed `fields` normalization.
- 2026-02-22: Added digest preflight section-count metrics + lightweight delta summary endpoint (wave `I-754`,`I-755`):
  - daily digest metrics:
    - now includes section-level preflight changed-field counters:
      - `preflight_guards_changed_fields_all`
      - `preflight_guards_changed_fields_responder`
      - `preflight_guards_changed_fields_synthetic`
      - `preflight_guards_changed_fields_digest`
      - `preflight_guards_changed_fields_token`
      - `preflight_guards_changed_fields_artifact`
  - new endpoint:
    - `GET /admin/ops/preflight-guards/delta.summary.json?day_offset=1`
      - lightweight drift summary payload for polling:
        - compare metadata (`day_offset`, `compare_day_utc`, `summary_status`, `previous_found`)
        - severity (`delta_severity`, `improved_signals`, `regressed_signals`)
        - compact counts (`change_count`, `changed_fields_by_section_counts`)
      - supports `section=` + `fields=` filters on summary counts.
- 2026-02-22: Added readiness quick section exports + delta CSV section-count metadata + readiness section summary (wave `I-751`,`I-752`,`I-753`):
  - readiness:
    - `Preflight Guard Delta` card now includes quick section export links (`section=...`) for direct delta JSON/CSV triage workflows.
    - card now surfaces section-level change-count summary (`all`, `responder`, `synthetic`, `digest`, `token`, `artifact`) derived from delta section counts.
  - delta CSV metadata:
    - `GET /admin/ops/preflight-guards/delta.csv` now includes:
      - `changed_fields_all`
      - `changed_fields_responder`
      - `changed_fields_synthetic`
      - `changed_fields_digest`
      - `changed_fields_token`
      - `changed_fields_artifact`
- 2026-02-22: Added history `section` filters + readiness exact delta URL hint + digest severity line (wave `I-741`,`I-743`,`I-744`):
  - preflight history exports:
    - `GET /admin/ops/preflight-guards/history.csv` now supports:
      - `section=all|responder|synthetic|digest|token|artifact`
    - `GET /admin/ops/preflight-guards/history.json` now supports:
      - `section=all|responder|synthetic|digest|token|artifact`
      - response now echoes `section_filter`
  - readiness:
    - `Preflight Guard Delta` card now supports `section` + `fields` selectors and surfaces exact current delta CSV URL for copy/paste triage.
  - daily digest:
    - preflight section now includes:
      - `preflight_guard_delta_severity: <stable|improved|regressed> improved=<n> regressed=<n>` when previous-day baseline exists.
- 2026-02-22: Added preflight delta grouped field summary + readiness day-offset passthrough + digest severity metric (wave `I-731`,`I-732`,`I-738`):
  - preflight delta JSON:
    - `GET /admin/ops/preflight-guards/delta.json` now includes grouped change summaries:
      - `changed_fields`
      - `changed_fields_by_section` (`responder|synthetic|digest|token|artifact`)
      - `changed_fields_by_section_counts`
  - readiness:
    - `Preflight Guard Delta` card export links now pass through currently selected `day_offset`, preserving compare-window context across export clicks.
  - daily digest metrics:
    - now includes `preflight_guards_delta_severity` (`stable|improved|regressed`) for parity with readiness badge state.
- 2026-02-22: Added preflight export filters + digest delta CSV link (wave `I-722`,`I-723`,`I-725`):
  - preflight guard export filters:
    - `GET /admin/ops/preflight-guards/delta.csv` now supports:
      - `section=all|responder|synthetic|digest|token|artifact`
      - `fields=<comma-separated field names>`
    - `GET /admin/ops/preflight-guards/delta.json` now supports:
      - `fields=...`
      - `section=...`
    - `GET /admin/ops/preflight-guards/history.csv` now supports:
      - `fields=...`
    - `GET /admin/ops/preflight-guards/history.json` now supports:
      - `fields=...`
  - daily digest:
    - preflight section now includes direct delta CSV link when guard changes are detected:
      - `/admin/ops/preflight-guards/delta.csv?day_offset=1`
- 2026-02-22: Added preflight guard history JSON + readiness delta selector/severity (wave `I-721`,`I-724`,`I-729`):
  - new endpoint:
    - `GET /admin/ops/preflight-guards/history.json?days=30`
      - JSON mirror of history CSV rows for dashboard integrations.
  - preflight delta payloads:
    - `GET /admin/ops/preflight-guards/delta.json` now includes:
      - `delta_severity` (`stable|improved|regressed`)
      - `improved_signals`
      - `regressed_signals`
    - `GET /admin/ops/preflight-guards/delta.csv` metadata now includes same severity/signal counts.
  - readiness:
    - `Preflight Guard Delta` card now supports `day_offset` selection (1..30 days) and renders severity badge with improved/regressed signal counts.
- 2026-02-22: Added preflight guard delta/history exports (wave `I-709`,`I-711`,`I-713`):
  - new endpoints:
    - `GET /admin/ops/preflight-guards/delta.json?day_offset=1`
      - compares current guard snapshot vs prior UTC-day artifact (`day_offset` selectable, `1..30`).
    - `GET /admin/ops/preflight-guards/delta.csv?day_offset=1`
      - ticket-friendly CSV of changed guard fields (`previous -> current`) for selected compare day.
    - `GET /admin/ops/preflight-guards/history.csv?days=30`
      - flattened daily history rows (`snapshot_day_utc,generated_at_utc,field,value,artifact_path`) from retained preflight-guard artifacts.
  - readiness:
    - `Preflight Guard Delta` card now links directly to delta JSON/CSV and history CSV exports.
- 2026-02-22: Added strict synthetic-zero preflight mode + preflight-guard artifact retention/delta view (wave `I-635`,`I-636`,`I-637`):
  - deploy preflight script:
    - added `--strict-synthetic-zero`
    - hard-fails preflight when synthetic zero guard reports `synthetic_export_zero_guard_status=fail`
    - `--json` payload now includes `inputs.strict_synthetic_zero`
  - preflight guard artifact persistence:
    - `/admin/ops/preflight-guards.json` now writes:
      - latest artifact: `PREFLIGHT_GUARDS_ARTIFACT_FILE` (default `/home/vets-coin/logs/preflight_guards_latest.json`)
      - daily artifact: `preflight_guards_YYYYMMDD.json` in the same folder
    - retention pruning controlled by `PREFLIGHT_GUARDS_ARTIFACT_RETENTION_DAYS` (default `30`)
  - readiness:
    - `/admin/ops/readiness` now includes `Preflight Guard Delta` card:
      - compares current guard snapshot vs previous UTC-day artifact
      - surfaces summary (`changed|no_change|baseline_missing`) and field-level guard drift lines.
- 2026-02-21: Added digest preflight-guard mode-change snippet (wave `I-640`):
  - daily ops digest:
    - now captures a `preflight_guards` snapshot inside digest artifacts.
    - compares current guard modes vs previous UTC day artifact.
    - text digest now includes:
      - current key guard states (`responder_docs_link`, synthetic/export retention, token-secret explicit-set)
      - previous-day snapshot availability/timestamp
      - explicit `preflight_guard_change` rows when fields changed.
  - digest metrics now include preflight drift counters/status:
    - `preflight_guards_change_count`
    - `preflight_guards_previous_day_found`
    - `preflight_guards_summary_status`
    - `preflight_guards_delta_severity`
    - `preflight_guards_responder_docs_mode`
    - `preflight_guards_responder_docs_ok`
- 2026-02-21: Added readiness preflight-guard mode chip + deploy preflight JSON mode (wave `I-633`,`I-634`):
  - readiness:
    - `/admin/ops/readiness` `Responder Docs-Link Mode` card now includes a linked mode chip:
      - `preflight-guards mode=<dry-run|enforced>`
      - link target: `GET /admin/ops/preflight-guards.json`
  - deploy preflight script:
    - `bash scripts/deploy_preflight.sh --json`
    - emits machine-readable summary with:
      - `overall_status`
      - `summary` counts (`pass|warn|fail`)
      - `key_guard_states`
      - parsed `checks` and output tail
    - exit code remains aligned with normal preflight result.
- 2026-02-21: Added digest concentration thresholding + preflight guard env visibility/exports (wave `I-628`,`I-631`,`I-632`):
  - daily ops digest:
    - warns when top export-token `requested_by` concentration exceeds configured thresholds:
      - `OPS_DAILY_DIGEST_WARN_LINK_INBOX_EXPORT_TOKEN_TOP_REQUESTED_BY_SHARE_PCT` (24h)
      - `OPS_DAILY_DIGEST_WARN_LINK_INBOX_EXPORT_TOKEN_TOP_REQUESTED_BY_SHARE_PCT_7D` (7d)
    - metrics/text now include concentration status (`ok|warn`) per window.
  - preflight guard JSON:
    - `GET /admin/ops/preflight-guards.json?include_env_rows=1` now appends safe `_env_row` metadata rows for drift audits.
  - preflight guard CSV export:
    - `GET /admin/ops/preflight-guards/export.csv?include_env_rows=1`
    - includes flattened guard rows + optional env rows with secret-safe redaction (`[set]`/`[unset]`).
- 2026-02-21: Added usage chart legend overlays for annotation markers (wave `I-627`):
  - `GET /admin/ops/alerts-monitor/export-usage.png`:
    - now draws legend overlay keys for marker annotations:
      - `L` = latest
      - `M` = max
    - response header now includes:
      - `X-Usage-Legend: L=latest,M=max`
  - `GET /admin/ops/alerts-monitor/export-usage.svg`:
    - now includes annotation overlays + visible legend (`L latest`, `M max`)
    - response header now includes:
      - `X-Usage-Legend: L=latest,M=max`
- 2026-02-21: Added synthetic zero-history failed-nonzero highlight in readiness (wave `I-626`):
  - readiness:
    - `/admin/ops/readiness` synthetic zero-history table now visually highlights rows where:
      - `status=failed|error`
      - `total_filtered > 0`
    - highlighted status label renders as `failed (non-zero)` to separate true export failures from benign zero-volume streaks.
- 2026-02-21: Added readiness docs-link drift view + synthetic zero-history CSV export (wave `I-624`,`I-625`):
  - readiness:
    - `Responder Docs-Link Mode` now includes drift vs previous UTC day:
      - baseline presence/day
      - new/resolved/unchanged docs-link error sets.
    - docs-link snapshots are persisted per day under `RESPONDER_WORKFLOW_DOCS_LINK_HISTORY_DIR`
      (default `/home/vets-coin/logs/responder_docs_link_history`).
  - new export endpoint:
    - `GET /admin/ops/readiness/synthetic-zero-history.csv?limit=10&days=45`
    - exports recent synthetic zero-history rows (`executed_at`, `status`, `total_filtered`, `message`) for postmortem attachments.
  - guard snapshot endpoint:
    - `/admin/ops/preflight-guards.json` now includes responder docs-link `history_dir`.
- 2026-02-21: Added docs-link enforcement cutover surfaces + guard snapshot endpoint (wave `I-621`,`I-622`,`I-623`,`I-629`,`I-630`):
  - readiness:
    - added standalone `Responder Docs-Link Mode` card with mode, linkage status, paths, and active errors.
  - admin ops endpoint:
    - `GET /admin/ops/preflight-guards.json` returns key preflight guard modes/toggles (docs-link, synthetic thresholds, digest/retention knobs).
  - docs-link checker script:
    - `check_responder_workflow_docs_link.py` now supports machine-readable output:
      - `--format json` (or `--json`)
  - preflight:
    - added one-off CLI override `--enforce-docs-link` to force docs-link hard-fail behavior for that invocation.
  - runbook:
    - added dry-run to enforced cutover checklist in `docs/ops/deploy_preflight.md`.
- 2026-02-21: Added readiness docs-link dry-run gate + synthetic zero-history table (wave `I-605`,`I-606`):
  - readiness:
    - launch gate `Responder workflow docs linkage` now supports dry-run warn mode before hard-fail cutover.
    - synthetic export card now includes recent zero-volume history rows (`executed/status/total_filtered/message`).
  - preflight:
    - responder docs-link check now warns in dry-run mode by default.
    - set `RESPONDER_WORKFLOW_DOCS_LINK_ENFORCE_FAIL=1` to fail preflight when linkage is broken.
  - env:
    - `LINK_INBOX_EVENTS_EXPORT_SYNTHETIC_ZERO_HISTORY_LIMIT` controls readiness history row count.
- 2026-02-21: Added PNG incident annotations + digest top-requester metric (wave `I-602`,`I-604`):
  - alert monitor PNG usage export now annotates:
    - latest-day total marker (`L:<total>`)
    - max-day marker (`M:<total>`)
  - `GET /admin/ops/alerts-monitor/export-usage.png` now emits headers:
    - `X-Usage-Annotated`
    - `X-Usage-Latest-Total`
    - `X-Usage-Max-Total`
  - daily ops digest metrics/text now include top token `requested_by` actor summary for:
    - 24h window
    - trailing 7d window
    - including per-window share percent.
- 2026-02-21: Added usage SVG export + telemetry requester filtering (wave `I-601`,`I-603`):
  - alert monitor usage exports:
    - `GET /admin/ops/alerts-monitor/export-usage.svg?days=7&source=all|export|token&w=900&h=240`
  - telemetry endpoint:
    - `GET /admin/ops/link-inbox/events/export-telemetry.json` supports
      `requested_by=<value>|all` filtering.
  - alert monitor UI:
    - added `Usage SVG` quick action beside CSV/JSON/PNG usage exports.
- 2026-02-21: Added export usage PNG + synthetic zero-guard unification + responder helper upgrades (wave `I-591`..`I-600`):
  - alert monitor usage exports:
    - `GET /admin/ops/alerts-monitor/export-usage.png?days=7&source=all|export|token&w=800&h=220`
    - usage CSV/JSON now include token-request `requested_by` top sources.
  - export telemetry:
    - `GET /admin/ops/link-inbox/events/export-telemetry.json` now includes `requested_by` per row + `requested_by_counts`.
  - responder helper script:
    - added `--expires-at-utc` (mutually exclusive with `--ttl-seconds`)
    - added `--print-curl`
    - added `--max-rows` policy warning threshold.
  - synthetic zero-guard:
    - shared parser utility now drives preflight/readiness/digest/checker behavior.
    - readiness includes `Synthetic export zero-volume streak` launch gate.
    - digest includes synthetic zero-guard status/streak/thresholds/latest total.
  - preflight:
    - now validates responder workflow docs linkage (`check_responder_workflow_docs_link.py`).
    - synthetic zero-volume advisory now supports warn/fail thresholds via:
      `LINK_INBOX_EVENTS_EXPORT_SYNTHETIC_ZERO_STREAK_WARN_DAYS` and
      `LINK_INBOX_EVENTS_EXPORT_SYNTHETIC_ZERO_STREAK_FAIL_DAYS`.
- 2026-02-21: Added synthetic/export guardrails + responder helper upgrades (wave `I-580`..`I-590`):
  - digest:
    - weekly export-mode adoption snippet (`json/csv/token` share %) in daily digest text/metrics.
    - synthetic export check trend (last 7 runs) with `ok|warn|failed|other` counts + sparkline.
  - readiness:
    - `/admin/ops/readiness` now adds fail-gate:
      `Synthetic export required when digest enabled (48h)`.
  - alert monitor exports:
    - `GET /admin/ops/alerts-monitor/export-usage.json?days=7&source=all|export|token`
    - `GET /admin/ops/alerts-monitor/export-usage.csv?days=7&source=all|export|token`
  - alert monitor UI:
    - synthetic-export status chip + quick link to filtered ops jobs view.
  - responder helper script:
    - added `--format shell`, `--incident-id`, and `--verify-token` options.
  - preflight:
    - advisory warning when synthetic export `total_filtered` remains zero across consecutive runs (`LINK_INBOX_EVENTS_EXPORT_SYNTHETIC_ZERO_DAYS_WARN`, now legacy warn alias).
- 2026-02-21: Added responder URL helper + alert-monitor usage CSV + synthetic-check readiness card (wave `I-560`,`I-571`,`I-573`):
  - helper script for responders:
    - `/home/vets-coin/flask_api/scripts/generate_link_inbox_export_download_url.py`
    - emits signed `export-download.csv` URL with explicit `--ttl-seconds` and required `--audit-note`.
  - alert-monitor usage export:
    - `GET /admin/ops/alerts-monitor/export-usage.csv?days=7`
  - readiness:
    - `/admin/ops/readiness` now includes `Synthetic Export Check` card (`link_inbox_export_synthetic` cron status/age/trend).
- 2026-02-21: Added export-usage monitor trend + nightly synthetic check + digest preset usage (wave `I-556`,`I-557`,`I-559`):
  - alert monitor UI:
    - `/admin/ops/alerts-monitor` now surfaces 7-day link-inbox export usage trend (`json/csv/token`).
  - synthetic export check:
    - added runner script:
      `/home/vets-coin/flask_api/scripts/run_link_inbox_export_synthetic_check.py`
    - added wrapper (daily via ops digest wrapper when enabled):
      `/home/vets-coin/scripts/run_link_inbox_export_synthetic_check.sh`
    - cron row job name:
      `link_inbox_export_synthetic`
  - digest metrics/text:
    - includes recent window-preset usage counts (`last_outage`, `last_digest_warn`, `none`, `total`).
  - preflight:
    - checks synthetic export wrapper exists when `LINK_INBOX_EVENTS_EXPORT_SYNTHETIC_ENABLED=1`.
- 2026-02-21: Expanded export telemetry + preset UX + preflight warning semantics (wave `I-551`,`I-552`,`I-553`,`I-555`,`I-558`):
  - `/admin/ops/link-inbox` quick-actions now include `window_preset` selector for Verify Events exports.
  - CSV export preset debug comments are now opt-in:
    - `debug=1` includes `window_preset_applied` and `window_preset_reason` comment lines.
  - new telemetry endpoint:
    - `GET /admin/ops/link-inbox/events/export-telemetry.json`
  - digest metrics now include token failure counters:
    - `link_inbox_export_token_fail_secret_missing`
    - `link_inbox_export_token_fail_decode_error`
    - `link_inbox_export_token_fail_expired`
  - preflight behavior:
    - token-flow check still fails on sign/verify errors
    - preflight now emits `WARN` when explicit `LINK_INBOX_EVENTS_EXPORT_TOKEN_SECRET` is unset and fallback secret is used
- 2026-02-21: Expanded export presets/token safety checks (wave `I-532`,`I-535`,`I-540`):
  - events export preset windows:
    - `window_preset=last_digest_warn|last_outage` (applies `at_from`/`at_to` when manual window is not provided)
  - events export metadata:
    - `window_preset`, `window_preset_applied`, `window_preset_reason`
  - digest warn trigger:
    - emits warning when token mode is used and `LINK_INBOX_EVENTS_EXPORT_TOKEN_SECRET` is unset
      (`link_inbox_export_token_secret_unset token_requests=...`)
  - preflight:
    - added token sign/verify dry-run:
      `/home/vets-coin/flask_api/scripts/check_link_inbox_export_token_flow.py`
- 2026-02-21: Expanded link-inbox export controls + readiness guardrails (wave `I-531`,`I-534`,`I-536`,`I-539`):
  - events export metadata:
    - hard-limit telemetry: `requested_limit`, `max_rows`, `effective_limit`, `total_filtered`, `returned_count`, `truncated`
  - events JSON fast-count mode:
    - `count_only=1` (returns counts/metadata with empty `events`)
  - events CSV/GZIP optional checksum header:
    - `checksum=1` returns `X-Export-SHA256`
  - readiness gate:
    - `Link-Inbox Export Token Secret` pass/warn signal
  - env:
    - `LINK_INBOX_EVENTS_EXPORT_MAX_ROWS` hard ceiling
- 2026-02-21: Expanded resolved-verify export/readiness/preflight tooling (wave `I-521..I-530`):
  - events export filters:
    - `at_from` / `at_to` (UTC timestamps)
    - `status_code_family` (`2xx|4xx|5xx|timeout|other`)
  - JSON projection:
    - `fields` (repeatable or CSV list from export column set)
  - CSV signed download mode:
    - generate token: `GET /admin/ops/link-inbox/events/export.csv?...&download_token=1`
    - consume token: `GET /admin/ops/link-inbox/events/export-download.csv?download_token=...`
  - digest warnings:
    - `OPS_DAILY_DIGEST_WARN_RESOLVED_VERIFY_SKIP_NO_URL_MAX_COUNT`
    - `OPS_DAILY_DIGEST_WARN_RESOLVED_VERIFY_SKIP_NO_URL_MAX_SHARE_PCT`
  - read-model normalization:
    - `summary.resolved_verify_events_count` guaranteed non-negative and backfilled from `checked_total + skipped_total` when missing
  - readiness/preflight/tooling:
    - readiness now shows `ops_digest_retention` 7-run status sparkline
    - preflight checks latest `ops_digest_retention` cron row freshness (default 48h)
    - added timer installer helper: `/home/vets-coin/scripts/install_ops_digest_retention_timer.sh`
- 2026-03-10: Expanded resolved-verification digest/admin/runbook surfaces (waves `I-471..I-490`):
  - digest analytics:
    - abrupt restart detector on `events_scanned` drop vs previous artifact
    - sparse median-baseline marker (`compare_reference_sparse_baseline`) with trend-confidence degradation
    - median compare range timestamps (`compare_reference_oldest_point_at_utc`, `compare_reference_latest_point_at_utc`)
    - carry-forward annotation now includes `carry_forward_previous_artifact_at_utc`
    - owner coverage metrics (`reopened_with_owner_count`, `reopened_with_owner_pct`) and top owner regression (`top_owner_regression_owner`, `top_owner_regression_delta`)
    - confidence now includes skip-share contribution, confidence sparkline trend, and optional warn floor (`OPS_DAILY_DIGEST_RESOLVED_VERIFY_CONFIDENCE_MIN`)
    - error taxonomy expanded to `dns_tls|connect|read|timeout|other` with percentage shares (`error_family_share_pct`)
    - combined deep link for skip+reopened verification event export
    - resolved-verification sub-schema marker (`resolved_verify_schema_version`)
  - admin endpoints/UI:
    - added `GET /admin/ops/link-inbox/verification-summary.json`
    - `/admin/ops/config` now shows a one-row resolved-verification threshold preview card
  - runbook checks:
    - added `resolved_verify_compare_mode` validation check for compare-mode env values
    - supports alias env `OPS_DAILY_DIGEST_RESOLVED_VERIFY_TREND_MODE=prev|median7`
- 2026-02-21: Expanded resolved-verify automation surfaces (wave `I-511..I-520`):
  - events export filters:
    - `skip_reason` (`no_url|cooldown|other`)
    - `issue_id`
    - `url_contains`
  - CSV gzip mode:
    - `GET /admin/ops/link-inbox/events/export.csv?...&compress=1`
  - digest enhancements:
    - `resolved_verify_skip_no_url_top_issue_ids` line
    - stale-event-age warn trigger via `OPS_DAILY_DIGEST_WARN_RESOLVED_VERIFY_LAST_EVENT_AGE_HOURS`
  - read-model additions:
    - `warn` boolean
    - `summary.compare_status` (`improving|regressing|flat|unknown`)
  - readiness/preflight:
    - readiness now tracks `ops_digest_retention` health
    - preflight verifies digest-retention wrapper executable when enabled
- 2026-02-21: Expanded resolved-verification exports/read-model/preflight (wave `I-501..I-510`):
  - added CSV timeline export:
    - `GET /admin/ops/link-inbox/events/export.csv`
  - added `event` multi-select filters to JSON/CSV events exports (supports aliases: `ok`, `reopened`, `skip`, `triage`)
  - resolved-verification digest now includes `resolved_verify_events_count`
  - digest artifact now includes top-level `artifact_schema_version`
  - read-model endpoint now includes:
    - `artifact_schema_version`
    - `reopened_only_inbox_url` (absolute when `WEB_DOMAIN` is set)
    - cache headers (`ETag`, `Last-Modified`, `Cache-Control`) with `304` support
  - preflight now validates digest artifact freshness via:
    - `/home/vets-coin/flask_api/scripts/check_ops_digest_freshness.py`
  - added daily retention wrapper:
    - `/home/vets-coin/scripts/run_ops_digest_retention_daily.sh`
- 2026-02-21: Added resolved-verification operational surfaces (wave `I-427..I-430`):
  - digest reopened-only inbox shortcut (`/admin/ops/link-inbox?status=assigned&verify_result=reopened`)
  - digest drill-down events link (`/admin/ops/link-inbox/events/export.json?event_prefix=resolved_verify_`)
  - admin events export endpoint:
    - `GET /admin/ops/link-inbox/events/export.json`
  - admin digest read-model endpoint:
    - `GET /admin/ops/digest/resolved-verification.json`
  - artifact retention helper script:
    - `/home/vets-coin/flask_api/scripts/retain_ops_digest_artifacts.py`
- 2026-02-21: Expanded resolved-verification digest analytics (wave `I-421..I-426`):
  - status-family deltas vs previous artifact (`2xx/4xx/5xx/timeout/other`)
  - 5xx-share and timeout-share thresholds with warn-trigger telemetry
  - zero-check streak warning suppression in scheduled UTC downtime windows
  - explicit streak-reset annotation when checks resume
  - trend-confidence marker based on previous artifact freshness/same-day context
- 2026-02-21: Expanded resolved-verification digest analytics (wave `I-414..I-420`):
  - restart/truncation indicator for resolved-verify event stream
  - carry-forward warning when compare baseline is unavailable
  - compare mode toggle (`previous` vs `median_7d`)
  - reopened owner attribution and owner rollup
  - recency line (`last_event_at`, `last_event_age_hours`)
  - verification error-family taxonomy (`timeout`, `dns_tls`, `http_failures`, `other_exceptions`)
  - compact confidence score (`confidence_score`, `confidence_band`)
- 2026-02-21: Expanded daily ops digest resolved-verification analytics and artifact schema:
  - confidence/min-sample note
  - reopened anomaly flag vs rolling weekly median
  - pass-rate sparkline history points
  - pass-rate SLA threshold evaluation
  - verification throughput (`checks/hour`)
- 2026-02-17: Normalized admin incident severity aliases (`warning`, `failed|fail|error`) across:
  - `/admin/ops/incidents`
  - `/admin/ops/incidents/export`
  - `/admin/ops/incidents/export.json`
- 2026-02-17: Added alert/status log retention telemetry to alert monitor surfaces:
  - UI: `/admin/ops/alerts-monitor`
  - JSON: `/admin/ops/alerts-monitor/export.json`
- 2026-02-17: Added alert monitor export bundle endpoint:
  - `GET /admin/ops/alerts-monitor/export.zip`
- 2026-02-17: Added alert monitor run-history CSV endpoint:
  - `GET /admin/ops/alerts-monitor/runs/export`
- 2026-02-17: Added admin incident detail endpoint with neighbor context:
  - `GET /admin/ops/incidents/detail.json`
- 2026-02-17: Alert monitor ZIP export now includes checksum manifest:
  - `alerts_monitor_export_manifest.json`
- 2025-12-18: Added Salutes partner API (credit/debit/balance) with HMAC auth, idempotency, and ledger storage.
- 2025-12-18: Added admin Salutes JSON ledger endpoint (`/api/salutes/ledger`) and partner rate limiting option.
- 2025-12-18: Added wallet connect + wallet info endpoints and admin backup list/download endpoints.
- 2026-01-03: Added partner user lookup, partner wallet info, and partner donation-claim endpoint.
- 2026-01-03: Added partner user provisioning + partner webhooks with delivery queue and retry script.
- 2026-02-13: Added admin webhook delivery operations (event replay + CSV export) in partner dashboard.
- 2026-02-13: Published OpenAPI spec at `GET /api/openapi.yaml` (see `flask_api/docs/openapi.yaml`).
- 2026-02-13: Added partner sandbox key mode + CIDR support for `allowed_ips` (admin-managed).
- 2026-02-16: Added transparency anomaly export endpoints with filter support:
  - `GET /transparency/audit-anomalies/export`
  - `GET /transparency/audit-anomalies/export.json`
- 2026-02-16: Added selected-run vs latest anomaly-type diff exports:
  - `GET /transparency/audit-anomalies/diff/export`
  - `GET /transparency/audit-anomalies/diff/export.json`
- 2026-02-16: Removed deprecated admin backup compatibility endpoint:
  - `POST /admin/trigger-backup` (use `POST /admin/backup/run`)
- 2026-02-16: Added public anomaly metadata JSON endpoints:
  - `GET /transparency/audit-anomalies/runs.json`
  - `GET /transparency/audit-anomalies/trend.json`
- 2026-02-16: Added public anomaly summary endpoint:
  - `GET /transparency/audit-anomalies/summary.json`
- 2026-02-16: Extended anomaly diff/trend exports:
  - diff CSV now includes `pct_delta_percent`
  - trend JSON accepts `metric=sigs|rows`
- 2026-02-16: Added arbitrary run-to-run diff API:
  - `GET /transparency/audit-anomalies/diff/compare.json?run_a=&run_b=`
- 2026-02-16: Added run-to-run diff CSV export:
  - `GET /transparency/audit-anomalies/diff/compare.csv?run_a=&run_b=`
- 2026-02-16: Published transparency OpenAPI supplement:
  - `GET /api/openapi-transparency.yaml`
- 2026-02-16: Added transparency alert snapshot endpoint:
  - `GET /transparency/audit-anomalies/alerts.json`
  - `GET /transparency/audit-anomalies/alerts.csv`

## Ops Digest Artifact (Internal Automation)

Daily digest writes a machine-readable artifact JSON file:
- Path: `OPS_DAILY_DIGEST_JSON_FILE` (default `/home/vets-coin/logs/ops_daily_digest_latest.json`)
- Top-level schema/version marker:
  - `artifact_schema_version` (int)

Relevant path for resolved verification fields:
- `snapshot.link_diff.resolved_verification`

Resolved verification sub-object fields:
- `resolved_verify_schema_version` (int; resolved-verification sub-schema marker)
- `available` (bool)
- `window_hours` (int)
- `checked_total` (int)
- `reopened_total` (int)
- `passed_total` (int)
- `skipped_total` (int)
- `resolved_verify_events_count` (int; count of `resolved_verify_*` events within the digest window)
- `skip_reason_counts` (object: `no_url`, `cooldown`, `other`)
- `top_skip_no_url_issue_ids` (array of `{issue_id, count}`)
- `top_skip_no_url_urls` (array of `{url, count}`)
- `top_skip_cooldown_issue_ids` (array of `{issue_id, count}`)
- `resolved_verify_skip_telemetry_available` (bool)
- `status_family_counts` (object: `2xx`, `4xx`, `5xx`, `timeout`, `other`)
- `top_reopened_urls` (array of rows)
- `pass_rate_pct` (float or null)
- `previous_pass_rate_pct` (float or null)
- `pass_rate_delta_pp` (float or null)
- `pass_rate_trend` (`up|down|flat|n/a`)
- `pass_rate_trend_symbol` (`^|v|=|n/a`)
- `has_previous_snapshot` (bool)
- `previous_checked_total` (int or null)
- `previous_reopened_total` (int or null)
- `previous_passed_total` (int or null)
- `checked_delta` (int or null)
- `reopened_delta` (int or null)
- `passed_delta` (int or null)
- `skipped_delta` (int or null)
- `compare_status` (`improving|regressing|flat|n/a`)
- `compare_schema_compatible` (bool)
- `compare_deltas_suppressed` (bool)
- `compare_suppressed_reason` (string)
- `previous_artifact_generated_at_utc` (UTC timestamp or empty string)
- `passed_to_reopened_ratio` (float or null)
- `previous_passed_to_reopened_ratio` (float or null)
- `passed_to_reopened_ratio_delta` (float or null)
- `checks_per_hour` (float)
- `min_sample` (int)
- `confidence_low` (bool)
- `pass_rate_sla_min_pct` (float)
- `pass_rate_sla_breach` (bool)
- `pass_rate_history_points` (array of `{generated_at_utc, checked_total, pass_rate_pct}`)
- `pass_rate_sparkline` (ASCII sparkline)
- `compare_mode` (`previous|median_7d`)
- `compare_mode_provenance` (`from_previous_artifact|from_median_history`)
- `compare_reference_label` (string)
- `compare_reference_pass_rate_pct` (float or null)
- `compare_reference_points` (int)
- `compare_reference_available` (bool)
- `compare_reference_oldest_point_at_utc` (UTC timestamp or empty string)
- `compare_reference_latest_point_at_utc` (UTC timestamp or empty string)
- `compare_reference_sparse_baseline` (bool)
- `compare_reference_sparse_min_points` (int)
- `carry_forward_warning` (bool)
- `carry_forward_reason` (string)
- `carry_forward_previous_artifact_at_utc` (UTC timestamp or empty string)
- `first_event_at` (UTC timestamp or empty string)
- `last_event_at` (UTC timestamp or empty string)
- `last_event_age_hours` (float or null)
- `status_family_delta_share_pp` (object of per-family percentage-point deltas vs previous artifact)
- `reopened_owner_heatmap_7d` (array of `{owner, reopened_total}`)
- `resolved_verify_event_link` (admin event-log drill-down URL for the current digest window)
- `resolved_verify_feature_flags` (machine-safe feature map for downstream parsers)
- `reopened_weekly_counts` (array of 7 daily counts, oldest->newest)
- `reopened_weekly_median` (float)
- `reopened_daily_equiv` (float)
- `reopened_spike_min` (int)
- `reopened_spike_flag` (bool)
- `stream_restart_detected` (bool)
- `stream_restart_reason` (string)
- `stream_restart_prev_last_event_at` (UTC timestamp or empty string)
- `stream_restart_curr_last_event_at` (UTC timestamp or empty string)
- `stream_restart_prev_events_scanned` (int or null)
- `stream_restart_curr_events_scanned` (int or null)
- `stream_restart_events_scanned_drop_flag` (bool)
- `events_scanned_delta` (int or null)
- `events_scanned_drop_ratio_pct` (float)
- `events_scanned_drop_threshold_pct` (float)
- `stream_restart_repeat_count_7d` (int)
- `stream_restart_repeat_window_runs` (int)
- `stream_restart_repeat_threshold` (int)
- `reopened_owner_counts` (object of assignee -> count)
- `reopened_owner_primary_counts` (object of owner.primary -> count)
- `top_reopen_owners` (array of `{assignee, count}`)
- `top_reopened_owner_hints` (array of `{issue_id, owner_primary, url}`)
- `reopened_with_owner_count` (int)
- `reopened_with_owner_pct` (float)
- `top_owner_regression_owner` (string)
- `top_owner_regression_delta` (int)
- `status_family_deltas` (object: `2xx`, `4xx`, `5xx`, `timeout`, `other`; values are int deltas or null)
- `family_5xx_share_pct` (float)
- `family_5xx_share_max_pct` (float)
- `family_5xx_share_breach` (bool)
- `family_timeout_share_pct` (float)
- `family_timeout_share_max_pct` (float)
- `family_timeout_share_breach` (bool)
- `downtime_window_utc` (string; `HH-HH` or empty)
- `zero_streak_warn_suppressed` (bool)
- `zero_streak_reset` (bool)
- `zero_streak_previous_days` (int)
- `previous_artifact_age_hours` (float or null)
- `trend_confidence` (`low|medium|high`)
- `trend_confidence_reason` (string)
- `error_family_counts` (object: `dns_tls`, `connect`, `read`, `timeout`, `other`)
- `error_family_share_pct` (object: percent share per error family)
- `confidence_score` (float `0..100`)
- `confidence_band` (`low|medium|high`)
- `confidence_sample_ratio_pct` (float)
- `confidence_stability_ratio_pct` (float)
- `confidence_skip_quality_ratio_pct` (float)
- `confidence_skip_share_pct` (float)
- `confidence_min` (float; configured warn floor)
- `confidence_history_points` (array of `{generated_at_utc, confidence_score}`)
- `confidence_score_sparkline` (ASCII sparkline)
- `zero_checked_streak_days` (int)

## Admin Ops Resolved-Verify Read Model

Internal admin endpoints for dashboard/triage automation:
- `GET /admin/ops/link-inbox/events/export.json`
- `GET /admin/ops/link-inbox/events/export.csv`
  - `GET /admin/ops/link-inbox/events/export-download.csv` (short-lived signed token mode)
  - Query:
    - `hours` (default `24`)
    - `limit` (default `500`)
    - `event_prefix` (default `resolved_verify_`)
    - `event` (repeatable or CSV list; supports aliases `ok`, `reopened`, `skip`, `triage`)
    - `skip_reason` (repeatable or CSV list; `no_url|cooldown|other`)
    - `status_code_family` (repeatable or CSV list; `2xx|4xx|5xx|timeout|other`)
    - `issue_id` (exact match)
    - `url_contains` (substring match, case-insensitive)
    - `at_from` / `at_to` (UTC timestamp; `YYYY-MM-DDTHH:MM:SSZ`)
    - `window_preset` (`last_digest_warn|last_outage`; ignored when `at_from`/`at_to` are explicitly provided)
    - `fields` (JSON export only; repeatable or CSV list from `at,event,issue_id,url,status_code,skip_reason,to_status,assignee,user_id,error`)
    - `count_only` (`0|1`; JSON export only; when `1`, returns counts/metadata with `events=[]`)
    - `include_triage` (`0|1`, default `0`)
    - `compress` (`0|1`; when `1`, returns `application/gzip` as `link_inbox_events.csv.gz`)
    - `checksum` (`0|1`; CSV/GZIP exports only; when `1`, response includes `X-Export-SHA256`)
    - `one_time` (`0|1`; token mint only; when `1`, token replay is blocked after first successful download)
    - `download_token`:
      - `1` on `/export.csv` returns signed URL metadata JSON
      - signed token value on `/export-download.csv` returns CSV/GZIP payload
    - `debug` (`0|1`; CSV export only; when `1`, includes preset-resolution debug comment lines)
  - export metadata fields:
    - `requested_limit`, `max_rows`, `effective_limit`, `total_filtered`, `returned_count`, `truncated`
    - `window_preset`, `window_preset_applied`, `window_preset_reason`
- `GET /admin/ops/link-inbox/events/export-telemetry.json`
  - Query:
    - `hours` (default `24`)
    - `limit` (default `200`, max `2000`)
    - `source` (`all|export|token`)
    - `requested_by` (`all` default, or specific requester tag such as `ops_primary`)
  - Returns recent `event_logs` telemetry rows for:
    - `ops_link_inbox_events_export`
    - `ops_link_inbox_export_token_request`
  - Includes summary counters:
    - `event_type_counts`, `outcome_counts`, `mode_counts`, `window_preset_counts`, `requested_by_counts`
    - `token_failure_counts` (`secret_missing`, `decode_error`, `expired`, `replay`)
  - Includes selected filter echo:
    - `requested_by_filter`
- `POST /admin/ops/link-inbox/events/export-checksum/verify.json`
  - Admin helper endpoint for uploaded evidence bundles (`multipart/form-data` file field `bundle` or `file`).
  - Returns SHA-256 for raw upload plus optional gunzip hash/preview when payload is GZIP.
  - Optional `expected_sha256` compares expected checksum against raw and gunzip variants.
- `POST /admin/ops/link-inbox/triage/bulk-no-url`
  - Bulk no-url triage helper for remediation waves.
  - Applies status/assignee updates to matching `resolved_verify_result=no_url` issues and records remediation metadata note tags.
- Responder helper:
  - TTL mode:
    - `python3 /home/vets-coin/flask_api/scripts/generate_link_inbox_export_download_url.py --ttl-seconds 900 --audit-note \"INC-123 export\" --incident-id \"INC-123\" --window-preset last_outage --one-time --verify-token --print-curl --format shell`
  - Absolute-expiry mode:
    - `python3 /home/vets-coin/flask_api/scripts/generate_link_inbox_export_download_url.py --expires-at-utc 2026-02-22T01:30:00Z --audit-note \"INC-123 export\" --incident-id \"INC-123\" --window-preset last_outage --one-time --max-rows 1000 --verify-token --format shell`
  - Generates signed `/admin/ops/link-inbox/events/export-download.csv?download_token=...` URL with audit metadata in token payload.
- `GET /admin/ops/digest/resolved-verification.json`
  - Returns latest artifact-derived resolved verification summary, relevant warn reasons, and triage links.
  - Includes cache headers (`ETag`, `Last-Modified`, `Cache-Control`) and supports conditional `304`.
  - Includes:
    - `warn` (bool, true when any resolved-verify warn reasons are active)
    - `summary.compare_status`
    - `summary.resolved_verify_events_count` (normalized contract field)
    - `summary.skip_no_url_trend_sparkline_7d` / `summary.skip_no_url_share_trend_sparkline_7d`
    - `links.resolved_verify_event_link` (direct `/admin/event-logs` drill-down link)
- `GET /admin/ops/link-inbox/verification-summary.json`
  - Returns latest resolved-verification summary block from ops digest artifact for link-inbox workflows.
  - Includes:
    - `summary` (compact normalized metrics/status)
    - `resolved_verification` (full sub-object passthrough)
    - `links.reopened_only_compare`
    - `links.skip_reopened_events`
    - `links.resolved_verify_event_link`

## Authentication (Partner HMAC)

Headers:
- X-Partner-Key: key_id
- X-Partner-Timestamp: unix epoch seconds
- X-Partner-Signature: hex hmac_sha256(secret, "{ts}.{method}.{path}.{body}")
- X-Partner-Nonce: replay-protection nonce (enabled by default; required only when strict nonce enforcement is enabled or an endpoint/policy requires it)
- Idempotency-Key: idempotency key (required by default for mutation endpoints)

Notes:
- `path` excludes query string (e.g. `/api/salutes/balance`)
- `body` is canonical JSON (sorted keys, no whitespace). Empty body is `""`.
- Timestamp window: ±5 minutes.
- Optional rate limit via `PARTNER_RATE_LIMIT_PER_MINUTE`.
- Nonce controls:
  - `PARTNER_NONCE_ENABLED` default `1`
  - `PARTNER_NONCE_REQUIRED` default `0`
  - `PARTNER_NONCE_TTL_SECONDS` default `600`
- Idempotency controls:
  - `PARTNER_IDEMPOTENCY_ENABLED` default `1`
  - `PARTNER_IDEMPOTENCY_REQUIRED` default `1`
- Partner key allowlists (`allowed_ips`) support exact IPs and CIDR blocks (comma-separated).
- Sandbox keys (admin flag) are forced to a lower rate limit:
  - `PARTNER_SANDBOX_RATE_LIMIT_PER_MINUTE` (default 30)
- Scopes are stored per key (CSV). Common scopes: `read`, `credit`, `debit`, `ledger`, `donation`, `users`, `webhooks`.
- Responses include `X-Request-ID` for trace correlation.

OpenAPI:
- `GET /api/openapi.yaml`
- `GET /api/openapi-transparency.yaml` (public transparency supplement)

Generated SDK starters:
- `GET /developers/sdk/manifest.json`
- `GET /developers/sdk/manifest.sig` (manifest signature envelope)
- `GET /developers/sdk/python-starter.zip`
- `GET /developers/sdk/typescript-starter.zip`
- `manifest.json` includes `version` + `version_source` for build provenance.

OpenAPI changelog:
- `GET /developers/openapi-changelog.md`
- `GET /developers/openapi-changelog.json`
- `GET /developers/openapi-changelog.rss`
- `GET /developers/openapi-changelog.atom`

API deprecation migration artifacts:
- `GET /developers/deprecations.json`
- `GET /developers/deprecations-playbook.md`

Regenerate deprecation migration playbook:
```bash
python3 /home/vets-coin/flask_api/scripts/generate_api_deprecation_playbook.py \
  --config /home/vets-coin/flask_api/config/api_deprecations.json \
  --out-md /home/vets-coin/flask_api/docs/deprecations/migration_playbook.md
```

Regenerate SDK starters from OpenAPI:
```bash
python3 /home/vets-coin/flask_api/scripts/generate_sdk_starters.py \
  --spec /home/vets-coin/flask_api/docs/openapi.yaml \
  --spec /home/vets-coin/flask_api/docs/openapi-transparency.yaml \
  --out-dir /home/vets-coin/flask_api/docs/sdk
```
Version stamping priority:
- `--version` CLI flag
- `SDK_VERSION` / `APP_RELEASE_VERSION` / `RELEASE_VERSION` / `APP_VERSION_TAG`
- `APP_GIT_SHA` / `VETS_GIT_SHA`
- fallback: `0.1.0-dev`

OpenAPI lint + breaking-change check:
```bash
python3 /home/vets-coin/flask_api/scripts/check_openapi_contracts.py \
  --spec /home/vets-coin/flask_api/docs/openapi.yaml \
  --spec /home/vets-coin/flask_api/docs/openapi-transparency.yaml \
  --baseline-dir /home/vets-coin/flask_api/docs/openapi/baseline
```

Partner unauthorized contract verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_partner_auth_surface_contract.py
python3 /home/vets-coin/flask_api/scripts/check_partner_auth_surface_contract.py --base-url https://vets-coin.com
```

Partner sandbox/live boundary verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_partner_mode_boundary_contract.py
```

Partner token-status contract verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_partner_token_status_contract.py
```

Partner discovery/status surface verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_partner_discovery_surface_contract.py
```

Partner sandbox state tool contract verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_partner_sandbox_state_tool_contract.py
```

Partner sandbox verify/promotion surface verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_partner_sandbox_surface_contract.py
```

Partner sandbox webhook rehearsal surface verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_partner_sandbox_webhook_rehearsal_contract.py
```

Partner sandbox user-provisioning surface verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_partner_sandbox_user_provisioning_contract.py
```

Partner sandbox donation-preview surface verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_partner_sandbox_donation_preview_contract.py
```

Partner live mutation success-contract verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_partner_live_mutation_contract.py
```

Partner live error/recovery contract verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_partner_live_error_contract.py
```

Partner machine-readable error-catalog contract verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_partner_error_catalog_contract.py
```

Public token proof surface verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_public_token_proof_surface_contract.py
python3 /home/vets-coin/flask_api/scripts/check_public_token_proof_surface_contract.py --base-url https://vets-coin.com
```

Token transaction-sync runtime freshness verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_transaction_sync_runtime_freshness.py
```

Public-stats cache runtime freshness verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_public_stats_cache_runtime_freshness.py
```

Holder-cache runtime freshness verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_holder_cache_runtime_freshness.py
```

Token snapshot jobs runtime freshness verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_token_snapshot_jobs_runtime_freshness.py
```

Public ops-jobs status/policy verification:
```bash
cd /home/vets-coin/flask_api
PYTHONPATH=/home/vets-coin/flask_api python3 scripts/check_public_ops_jobs_contract.py
python3 /home/vets-coin/flask_api/scripts/check_public_ops_jobs_contract.py --base-url https://vets-coin.com
```

Lint policy:
- missing `operationId` is a lint error by default
- override for temporary migration only: `--allow-missing-operation-id`

Generate OpenAPI changelog artifacts:
```bash
python3 /home/vets-coin/flask_api/scripts/generate_openapi_changelog.py \
  --spec /home/vets-coin/flask_api/docs/openapi.yaml \
  --spec /home/vets-coin/flask_api/docs/openapi-transparency.yaml \
  --baseline-dir /home/vets-coin/flask_api/docs/openapi/baseline \
  --out-md /home/vets-coin/flask_api/docs/openapi/changelog/latest.md \
  --out-json /home/vets-coin/flask_api/docs/openapi/changelog/latest.json
```

## Quickstart (Partner)

1. Ask an admin to create a Partner Key in the admin UI.
2. Use the key to call `GET /api/partner/capabilities` to verify HMAC auth, scopes, and time sync.

Recommended: use the signing helper script (never hardcode secrets into source control):
```bash
python3 /home/vets-coin/flask_api/scripts/sign_partner_request.py \
  --method GET \
  --path /api/partner/capabilities \
  --key-id CHANGE_ME_KEY_ID \
  --secret CHANGE_ME_SECRET
```

Raw `curl` example (placeholders are literal; replace them):
```bash
TS="$(date +%s)"
METHOD="GET"
PATH="/api/partner/capabilities"
BODY=""  # empty for GET

# SIGNATURE = hex(hmac_sha256(SECRET, "{ts}.{method}.{path}.{body}"))
curl -sS -D - \
  -H "X-Partner-Key: CHANGE_ME_KEY_ID" \
  -H "X-Partner-Timestamp: ${TS}" \
  -H "X-Partner-Signature: CHANGE_ME_SIGNATURE" \
  -H "X-Partner-Nonce: $(uuidgen | tr '[:upper:]' '[:lower:]')" \
  "https://vets-coin.com${PATH}"
```

Note:
- `https://vets-coin.com` is the current canonical base URL.
- `https://api.vets-coin.com` is planned, but do not use it until the TLS certificate explicitly includes that subdomain.

## Error Responses (Partner)

Partner endpoints generally return:
```json
{"success":false,"error":"unauthorized"}
```

When an API error catalog entry is attached to the live response, `error_catalog.example_request`
is contextualized to the actual failed request path and method so partners can replay or debug the
same route directly instead of inferring from a generic catalog example.

This now applies to partner sandbox/live boundary errors and other partner-specific contract
failures too, including `sandbox_key_required`, `live_key_required`, `forbidden`,
`replay_detected`, `idempotency_required`, `idempotency_invalid`, and `idempotency_replay`,
not just generic `401 unauthorized` failures.

The same docs/bootstrap and bounded recovery navigation now also applies to the live salutes
partner-auth branch (`GET /api/salutes/balance`, `GET /api/salutes/ledger`, `POST /api/salutes/credit`, and
`POST /api/salutes/debit`) for `unauthorized`, `forbidden`, `replay_detected`, and
`rate_limited` responses, so salutes auth failures no longer return a thinner envelope than the
adjacent partner discovery/runtime surfaces.

Partner auth and idempotency failures now also include top-level `docs_links` plus a bounded
`links` block so integrators can recover without guessing the next surface:
- `developers_api_guide`
- `developers_api_errors`
- `developers_api_errors_json`
- `public_quickstart_verify_key`
- `openapi`
- `openapi_transparency`
- `sdk_manifest`
- `sdk_manifest_sig`
- `sdk_python_starter`
- `sdk_typescript_starter`
- `openapi_changelog_json`
- `openapi_changelog_rss`

Route-specific partner validation/recovery errors now follow the same pattern whenever the
response exposes partner recovery `links`, so sandbox/live request-shape failures keep the
docs/bootstrap bundle machine-readable at the top level instead of only inside endpoint-specific
navigation.

Recovery navigation remains available in `links` too:
- `developers_api_guide`
- `developers_api_errors`
- `developers_api_errors_json`
- `public_quickstart_verify_key`
- `openapi`
- `openapi_transparency`
- `sdk_manifest`
- `sdk_manifest_sig`
- `sdk_python_starter`
- `sdk_typescript_starter`
- `openapi_changelog_json`
- `openapi_changelog_rss`
- `partner_errors`
- `public_system_status`
- `public_system_status_incidents`
- `public_system_status_uptime`
- `public_system_status_policy`
- `public_status_page`
- for non-sandbox-specific partner route failures, the response also includes:
  - `partner_capabilities`
  - `partner_status`
  - `partner_token_status`
  - `partner_usage_analytics`
  - `partner_webhook_events`
  - `partner_webhook_replay`
  - `partner_webhook_signature_verify`
  - `partner_webhook_dlq`
  - `sandbox_verify`
- sandbox/live boundary failures now also expose contract-specific recovery links:
  - `sandbox_key_required`
    - `sandbox_verify`
    - `sandbox_token_status`
    - `sandbox_promotion_report`
  - `live_key_required`
    - `sandbox_verify`
    - `sandbox_promotion_report`
    - `sandbox_endpoint` when there is a direct sandbox companion for the rejected live mutation route

Common error values:
- `unauthorized` (HTTP 401): missing/invalid HMAC headers, stale timestamp, unknown key, IP not allowed, or nonce required/invalid when strict nonce enforcement is active
- `forbidden` (HTTP 403): valid key but missing scope
- `live_key_required` (HTTP 403): sandbox key attempted a live mutation endpoint that now requires post-promotion live credentials
  Published live mutation examples now also expose top-level `docs_links` plus `links.sandbox_endpoint` when a direct sandbox companion route exists.
- `rate_limited` (HTTP 429): request rate exceeded
- `replay_detected` (HTTP 409): nonce replay detected when nonce replay protection is active for the request (send a fresh `X-Partner-Nonce`)
- `idempotency_required` (HTTP 400): mutation endpoint called without `Idempotency-Key`
- `idempotency_invalid` (HTTP 400): malformed `Idempotency-Key`
- `idempotency_replay` (HTTP 409): duplicate `Idempotency-Key` for the same partner key

## Partner capabilities (HMAC)

### GET /api/partner/capabilities
Returns allowed scopes and a list of partner-relevant endpoints.

Caching:
- Supports `ETag` / `If-None-Match`.
- Supports `Last-Modified` / `If-Modified-Since`.
- Returns `304 Not Modified` when validators match.

Response:
```json
{"success":true,"partner":"PartnerCo","key_id":"...","scopes":["read","credit"],"endpoints":[...]}
```

Additional fields:
- `key_mode`: `live` or `sandbox`
- `sandbox.enabled_for_key`: whether the current key can use sandbox-only endpoints
- `sandbox.verify_endpoint`: deterministic onboarding endpoint for sandbox keys
- `sandbox.fixture_endpoints`: currently advertised deterministic sandbox companion surfaces
  - includes `/api/partner/sandbox/promotion-report` so the operator promotion gate is discoverable from the same sandbox bootstrap contract
- `docs`
  - authenticated self-serve docs/tooling bundle now includes:
    - `/developers/api-guide.md`
    - `/developers/api-errors`
    - `/developers/api-errors.json`
    - `/api/public/quickstart/verify-key`
    - `/api/openapi.yaml`
    - `/api/openapi-transparency.yaml`
    - `/developers/sdk/manifest.json`
    - `/developers/sdk/manifest.sig`
    - `/developers/sdk/python-starter.zip`
    - `/developers/sdk/typescript-starter.zip`
    - `/developers/openapi-changelog.json`
    - `/developers/openapi-changelog.rss`
- `links`
  - always includes:
    - `/api/partner/status`
    - `/api/partner/token-status`
    - `/api/partner/errors.json`
    - `/api/partner/webhooks/signature/verify`
    - `/api/public/system-status`
    - `/api/public/system-status/incidents?hours=168&limit=30`
    - `/api/public/system-status/uptime?hours=168&include_maintenance=0`
    - `/api/public/system-status/policy`
    - `/status`
  - live keys also receive:
    - `/api/partner/analytics/usage`
    - `/api/partner/webhook-events`
    - `/api/partner/webhook-events/replay`
    - `/api/partner/webhooks/dlq`
  - sandbox keys also receive:
    - `/api/partner/sandbox/verify`
    - `/api/partner/sandbox/token-status`
    - `/api/partner/sandbox/promotion-report`
- endpoint item `mode`:
  - omitted: callable with either live or sandbox keys when the listed scope is present
  - `sandbox_only`: requires a sandbox key
  - `live_only`: requires a promoted live key even when the sandbox key has the listed scope

### GET /api/partner/errors.json
Machine-readable partner error taxonomy and retry guidance.

Purpose:
- give partners one stable endpoint for current `error_code` meanings
- classify retry behavior and remediation without scraping prose docs
- keep sandbox-vs-live onboarding behavior explicit in machine-readable form

Response shape highlights:
- `entries[]`
  - `error_code`
  - `title`
  - `category`
  - `http_status`
  - `retry_class`
  - `retry_behavior`
  - `retry_after_seconds`
  - `available_in_modes`
  - `endpoint_groups`
  - `recovery_links`
    - per-error route map for status, verification, and replay-safe recovery follow-through
    - `rpc_unavailable` now also includes `/api/public/rpc-circuit-breaker`
    - the recovery/docs bundle now also includes:
      - `/api/public/quickstart/verify-key`
      - `/api/openapi.yaml`
      - `/developers/sdk/manifest.json`
      - `/developers/openapi-changelog.json`
- `by_code`
  - direct keyed lookup mirror for client SDKs and test harnesses
  - mirrors the same `recovery_links` blocks as `entries[]`
- when the catalog is served for a live key, top-level `links` and live-safe `recovery_links` now also include:
  - `GET /api/partner/analytics/usage`
  - `GET /api/partner/webhook-events`
  - `POST /api/partner/webhook-events/replay`
  - `GET /api/partner/webhooks/dlq`

Use this together with:
- `GET /api/partner/capabilities`
- `GET /api/partner/status`
- `GET /api/partner/token-status`
- `GET /api/public/system-status`
- `GET /api/public/system-status/incidents?hours=168&limit=30`
- `GET /api/public/system-status/uptime?hours=168&include_maintenance=0`
- `GET /api/public/system-status/policy`
- `POST /api/partner/webhooks/signature/verify`

### GET /api/partner/sandbox/verify
Deterministic no-loss onboarding endpoint for sandbox keys only.

Purpose:
- verify HMAC signing without touching live balances
- validate partner nonce/idempotency handling safely
- practice against a stable fixture contract before live promotion

Behavior:
- requires a sandbox partner key
- returns `403 sandbox_key_required` when called with a live key
- returns deterministic fixture payloads only; no live mutation occurs
- both success and live-key rejection payloads now expose top-level `docs_links`

Response shape highlights:
- `mode: sandbox`
- `fixture_profile: deterministic_partner_onboarding_v1`
- `docs_links`
  - mirrors the shipped docs/bootstrap bundle exposed by authenticated partner discovery surfaces
- `verification`
  - `signature_verified`
  - `nonce_policy`
  - `idempotency_policy`
  - `safe_for_replay_testing`
- `fixtures`
  - `salutes_balance`
  - `wallet_verification`
  - `token_context`
- `recovery`
  - `state_reset_available_via_operator`
  - `partner_self_service_reset_available`
  - `rehearsal_reset_mode`
  - `reset_scope`
  - `promotion_review_endpoint`
  - `suggested_rehearsal_order`
- `links`
  - now also includes the same docs/bootstrap bundle and public status diagnostics used by the higher-level partner surfaces
  - `partner_capabilities`
  - `partner_status`
  - `partner_token_status`
  - `partner_errors`
  - `sandbox_token_status`
  - `sandbox_promotion_report`

Current boundary:
- this endpoint is the safe first step in the sandbox path
- sandbox salutes practice mirrors stay off-chain and fake-state only
- promotion to live keys remains manual/operator-approved

### GET /api/partner/sandbox/promotion-report
Partner-facing sandbox promotion-readiness report for sandbox keys only.

Purpose:
- let a sandbox partner inspect observed fake-state rehearsal evidence before operator review
- expose the manual promotion gate structure in the same authenticated sandbox surface
- avoid requiring admin access just to understand what is still needed before live approval

Behavior:
- requires a sandbox partner key
- optional `user_id` narrows the observed fake-state view to one rehearsal subject
- optional `limit` bounds recent observed events; values clamp to a safe range
- returns a sanitized projection of the shipped operator promotion report
- now includes observed sandbox user-provisioning evidence alongside salutes rehearsal evidence
- now includes observed sandbox webhook registry/test evidence from the off-chain sandbox webhook state companion file
- now includes additive sandbox webhook simulator/replay evidence derived from the same webhook companion state, while final webhook approval remains manual
- does not expose local filesystem paths
- does not auto-promote a partner to live access
- returns `403 sandbox_key_required` when called with a live key
- both success and live-key rejection payloads now expose top-level `docs_links`

Response shape highlights:
- `mode: sandbox`
- `fixture_profile: deterministic_partner_sandbox_promotion_v1`
- `promotion_policy: sandbox_to_live_manual_approval`
- `docs_links`
  - mirrors the shipped docs/bootstrap bundle exposed by authenticated partner discovery surfaces
- `readiness_summary`
  - `manual_review_gate_ids`
  - `not_observed_gate_count`
  - `not_observed_gate_ids`
- `observed_practice_state`
- `observed_practice_state.user_provisioning`
- `observed_practice_state.webhook_registry`
- `recovery`
  - `state_reset_available_via_operator`
  - `partner_self_service_reset_available`
  - `rehearsal_reset_mode`
  - `reset_request_context`
  - `manual_review_gate_ids`
  - `not_observed_gate_ids`
  - `suggested_rerun_endpoints`
- `promotion_gate`
- `links`
  - now also includes the same docs/bootstrap bundle and public status diagnostics used by the higher-level partner surfaces
  - `sandbox_verify`
  - `sandbox_token_status`
  - `partner_status`
  - `partner_token_status`
  - `sandbox_users`
  - `sandbox_webhooks`
  - `partner_capabilities`
  - `partner_errors`

Recoverability notes:
- sandbox reset remains operator-mediated; partners do not receive a self-service reset endpoint
- when observed state is incomplete, rerun the suggested sandbox endpoints and request a fresh `GET /api/partner/sandbox/promotion-report`
- the recovery block is sanitized and does not expose local filesystem paths or operator-only state-file locations

### POST /api/partner/sandbox/donation-claim
Deterministic sandbox donation-claim rehearsal for sandbox keys only.

Purpose:
- let partners validate donation-claim response parsing without creating real faucet keys or affiliate conversion rows
- keep the donation-claim onboarding path inside the same authenticated no-loss sandbox boundary
- give sandbox callers an explicit preview endpoint after live donation-claim became `live_only`

Behavior:
- requires a sandbox partner key with `donation` scope
- accepts the same body shape as the live partner donation-claim endpoint
- requires `campaign_id`
- requires `amount` and now rejects missing, non-numeric, or non-positive preview amounts with bounded `400` errors
- if `wallet_address` is supplied, it must be a valid Solana wallet address
- returns deterministic preview fields only; no DB writes, faucet keys, or outbound email occur
- returns `claim_status=sandbox_preview`
- returns `403 sandbox_key_required` when called with a live key
- route-specific `400` responses now reuse the same sandbox donation recovery hub, including:
  - `/api/partner/sandbox/donation-claim`
  - `/api/partner/sandbox/promotion-report`
  - `/api/partner/status`
  - `/api/partner/errors.json`

Response shape highlights:
- `mode: sandbox`
- `fixture_profile: deterministic_partner_sandbox_donation_claim_v1`
- `docs_links`
  - mirrors the shipped docs/bootstrap bundle exposed by authenticated partner discovery surfaces
- `status: success`
- `claim_code`
- `claim_url`
- `claim_campaign_id`
- `requested_claim`
- `idempotency`
- `live_side_effects_performed: false`
- `links`
  - now also includes the sandbox hub navigation plus the live donation endpoint

### GET /api/partner/sandbox/users
Sandbox user inventory for sandbox keys only.

Purpose:
- let partners inspect sandbox-created users without touching live user rows
- keep user-provisioning practice inside the authenticated no-loss sandbox path
- provide a safe read companion to sandbox user create/update/wallet-link rehearsal

Behavior:
- requires a sandbox partner key with `read` scope
- returns sandbox-only user rows stored in the sandbox user state file
- supports `status`, `q`, `include_wallets`, `limit`, `offset`, and `cursor`
- returns `403 sandbox_key_required` when called with a live key
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- route-specific failures such as `invalid_cursor` and `invalid_status` now also include that same sandbox user recovery hub

### POST /api/partner/sandbox/users
Sandbox user creation rehearsal for sandbox keys only.

Behavior:
- requires a sandbox partner key with `users` scope
- accepts the same core body shape as live partner user creation
- writes only to sandbox user state
- does not create live `users`, `partner_user_links`, or `user_wallets` rows
- returns the same success envelope shape as live create with `mode=sandbox`
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- route-specific failures such as `external_id_required`, `invalid_wallet_address`, `invalid_status`, `email_in_use`, `wallet_in_use`, and sandbox-state `not_found` now also include that same sandbox user recovery hub

### GET /api/partner/sandbox/users/<id>
Fetch one sandbox user owned by this sandbox partner key.

Behavior:
- requires a sandbox partner key with `read` scope
- supports `include_wallets=1`
- optional `external_id` resolves the sandbox user by partner external id
- never reads live user tables
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- route-specific `not_found` failures now also include the sandbox user recovery hub

### PATCH /api/partner/sandbox/users/<id>
Sandbox user update rehearsal for sandbox keys only.

Behavior:
- requires a sandbox partner key with `users` scope
- accepts the same update fields as live partner user update
- writes only to sandbox user state
- returns `403 sandbox_key_required` when called with a live key
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- route-specific failures such as `not_found`, `invalid_status`, and `no_updates` now also include the sandbox user recovery hub

### POST /api/partner/sandbox/users/<id>/wallets
Sandbox wallet-link rehearsal for sandbox partner users.

Behavior:
- requires a sandbox partner key with `users` scope
- accepts the same core body shape as live partner wallet-link
- writes only to sandbox user state
- never creates live wallet rows
- still enforces bounded errors such as `invalid_wallet_address` and `wallet_in_use`
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- route-specific failures such as `invalid_wallet_address`, `not_found`, and `wallet_in_use` now also include the sandbox user recovery hub

### GET /api/partner/sandbox/webhooks
Sandbox webhook registry inventory for sandbox keys only.

Purpose:
- let partners rehearse webhook registry parsing without touching live partner webhook rows
- keep registration lifecycle practice inside the authenticated no-loss sandbox path
- provide a safe companion to the existing sandbox webhook payload rehearsal endpoints

Behavior:
- requires a sandbox partner key with `webhooks` scope
- returns sandbox-only webhook rows stored in the webhook sandbox state file
- supports `event_type`, `is_active`, `limit`, `offset`, and `cursor`
- returns `403 sandbox_key_required` when called with a live key
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- route-specific failures such as `invalid_cursor`, `invalid_is_active`, and `invalid_event_type` now also include that same sandbox webhook recovery hub

### POST /api/partner/sandbox/webhooks
Sandbox webhook registration rehearsal for sandbox keys only.

Behavior:
- requires a sandbox partner key with `webhooks` scope
- accepts the same core body shape as live webhook creation
- writes only to sandbox webhook state
- does not create live partner webhook rows
- does not trigger delivery
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- route-specific failures such as `invalid_event_type`, `invalid_target_url`, and `https_required` now also include that same sandbox webhook recovery hub

### DELETE /api/partner/sandbox/webhooks/<id>
Sandbox webhook disable rehearsal for sandbox keys only.

Behavior:
- requires a sandbox partner key with `webhooks` scope
- marks the sandbox webhook inactive in sandbox state only
- does not touch live partner webhook rows
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- route-specific `not_found` failures now also include the sandbox webhook recovery hub

### POST /api/partner/sandbox/webhooks/<id>/test
Sandbox webhook test-delivery rehearsal for sandbox keys only.

Purpose:
- let partners practice webhook registry plus test-send handling together inside the no-loss sandbox path
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- reuse the same signature format as other sandbox webhook simulation surfaces

Behavior:
- requires a sandbox partner key with `webhooks` scope
- requires an active sandbox webhook id
- never sends a live webhook
- returns a signed simulation payload using the stored sandbox webhook secret
- success payloads now also expose additive sandbox hub navigation in `links`
- does not write live webhook-event rows
- route-specific `not_found` failures now also include the sandbox webhook recovery hub

### GET /api/partner/sandbox/salutes/balance?user_id=...
Sandbox salutes practice balance for sandbox keys only.

Purpose:
- practice signed read calls with a required query parameter
- validate partner-side parsing against the same success/error envelope used by live partner reads
- inspect the current fake-state sandbox balance without touching the live Salutes ledger

Behavior:
- requires a sandbox partner key
- requires `user_id`
- returns a per-partner, per-user fake-state balance with a default starting balance of `1000`
- echoes the requested `user_id` and exposes a linked sandbox ledger route
- returns `403 sandbox_key_required` when called with a live key
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- route-specific failures such as `user_id_required` and `user_id_too_long` now also include the sandbox salutes recovery hub

### GET /api/partner/sandbox/salutes/ledger?user_id=...
Sandbox salutes practice ledger for sandbox keys only.

Purpose:
- let partners rehearse ledger pagination/filter parsing without touching the live Salutes ledger
- verify that fake-state mutations replay through the same success envelope shape as live reads

Behavior:
- requires a sandbox partner key with `ledger` scope
- requires `user_id`
- returns fake-state ledger entries for the partner key and requested user only
- supports `limit`, `offset`, `order`, `reason`, and `source`
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- route-specific failures such as `user_id_required` and `user_id_too_long` now also include the sandbox salutes recovery hub

### POST /api/partner/sandbox/salutes/credit
Sandbox salutes practice credit for sandbox keys only.

Purpose:
- let partners rehearse mutation-style requests, idempotency handling, and balance parsing without live loss
- provide a no-chain, no-DB-mutation proving ground before live access is granted

Behavior:
- requires a sandbox partner key with `credit` scope
- requires JSON body fields:
  - `user_id`
  - `amount`
- optional fields:
  - `reason`
  - `source`
  - `idempotency_key` in body or `Idempotency-Key` header
- writes only to the sandbox fake-state file
- returns `400 amount_must_be_integer` for non-numeric amounts
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- route-specific parse failures such as `amount_must_be_integer` and `user_id_required` now also include the sandbox salutes recovery hub

### POST /api/salutes/credit
Live Salutes credit mutation.

Behavior:
- requires a live key with `credit` scope
- sandbox keys now receive `403 live_key_required`
- the error detail points partners back to:
  - `/api/partner/sandbox/salutes/credit`
  - `/api/partner/sandbox/promotion-report`

### POST /api/partner/sandbox/salutes/debit
Sandbox salutes practice debit for sandbox keys only.

Behavior:
- requires a sandbox partner key with `debit` scope
- mirrors the fake-state/idempotency contract of sandbox credit
- returns `409 insufficient_balance` when the sandbox practice balance would go negative
- does not touch live balances or chain state
- success payloads now also expose:
  - `docs_links`
  - additive sandbox hub navigation in `links`
- route-specific parse failures plus `insufficient_balance` now also include the sandbox salutes recovery hub

### POST /api/salutes/debit
Live Salutes debit mutation.

Behavior:
- requires a live key with `debit` scope
- sandbox keys now receive `403 live_key_required`
- the error detail points partners back to:
  - `/api/partner/sandbox/salutes/debit`
  - `/api/partner/sandbox/promotion-report`

### GET /api/partner/sandbox/webhook-events?user_id=...
Sandbox webhook rehearsal inventory for sandbox keys only.

Purpose:
- let partners inspect replay-safe webhook rows derived from sandbox salutes activity
- keep event rehearsal tied to the same fake-state ledger instead of inventing a second sandbox event store
- avoid live webhook-event row mutation while still exercising webhook consumers against realistic event shapes

Behavior:
- requires a sandbox partner key
- requires `user_id`
- derives `salutes.credit` and `salutes.debit` event rows from the sandbox salutes ledger
- supports `limit`, `offset`, `order`, and optional `event_type`
- returns only replay-safe sandbox event rows
- success payloads now also expose top-level `docs_links`
- `links` now include additive sandbox webhook simulator, replay, partner-status, and adjacent salutes rehearsal navigation
- route-specific failures such as `user_id_required`, `user_id_too_long`, and `invalid_event_type` now also include that same sandbox webhook-event recovery hub

### POST /api/partner/sandbox/webhook-events/replay
Sandbox webhook replay rehearsal for sandbox keys only.

Purpose:
- let partners practice downstream webhook handling using signed payloads that map to the fake-state sandbox salutes ledger
- keep the replay path deterministic and no-loss while moving closer to the live webhook contract

Behavior:
- requires a sandbox partner key
- requires `event_ids`
- accepts either numeric ledger ids or `sandbox-salutes-<id>` event ids
- never sends a live webhook
- never writes live `partner_webhook_events` rows
- appends a bounded replay audit row into the sandbox webhook companion state so operator promotion evidence can recover the rehearsal later
- returns signed simulation payloads with live-like headers such as:
  - `X-Webhook-Event`
  - `X-Webhook-Id`
  - `X-Webhook-Timestamp`
  - `X-Webhook-Signature`
- defaults to a sandbox-only shared secret when none is supplied
- success payloads now also expose top-level `docs_links`
- `links` now include additive sandbox webhook simulator and replay discovery plus a replay-safe event list route when a single sandbox user subject is implied by the replay batch
- route-specific failures such as `invalid_reason_tag` and `event_ids_required` now also include that same sandbox webhook-event recovery hub

### GET /api/partner/sandbox/token-status
Deterministic sandbox token diagnostics fixture for sandbox keys only.

Purpose:
- let partners practice parsing token-runtime diagnostics before calling the live token surface
- provide a stable fixture contract for onboarding and automated contract tests
- keep the onboarding path read-only and no-loss

Behavior:
- requires a sandbox partner key
- returns deterministic token/supply/holder snapshot data only
- uses `rpc_url: sandbox://deterministic-fixture` so clients can distinguish sandbox from live truth
- returns `403 sandbox_key_required` when called with a live key
- both success and live-key rejection payloads now expose top-level `docs_links` so the docs/bootstrap bundle remains machine-readable without parsing `links`
- now includes a recovery block describing:
  - operator-mediated reset truth
  - reset scope (`salutes`, `users`, `webhooks`)
  - suggested rehearsal order beginning with sandbox verify and sandbox token-status before promotion review
- links now include `sandbox_promotion_report` so partners can move from deterministic diagnostics to the manual promotion-readiness report without leaving the authenticated sandbox path
- links now also include `partner_errors` so onboarding clients can jump directly into the machine-readable error/retry catalog from the deterministic token-runtime fixture
- success payloads now also expose `docs_links` plus the shipped docs/bootstrap bundle inside `links`, so sandbox-only clients can discover quickstart preview, SDK bundles, and OpenAPI artifacts without jumping back to `GET /api/partner/capabilities`
- mirrors the live `snapshot_policy` block so onboarding parsers can validate cadence and threshold semantics before touching live diagnostics
- mirrors the live snapshot-job success provenance fields (`latest_success_at`, `latest_success_age_hours`) so onboarding parsers can practice the same recovered-vs-missing logic before touching live diagnostics
- now mirrors the latest live additive token-runtime fields too:
  - `snapshots.transaction_sync`
  - `snapshot_policy.transaction_sync`
  - `snapshots.token_distribution.fetch_retry_summary`
  - `snapshots.holders_cache.latest_at`
  - `snapshots.holders_cache.age_hours`

### GET /api/partner/sandbox/webhook-simulator/scenarios
Deterministic sandbox webhook scenario catalog for sandbox keys only.

Purpose:
- keep webhook rehearsal inside the authenticated sandbox path
- let partners preview stable fixture events before generating signed test payloads
- avoid any live delivery, queueing, or partner DB mutation

Behavior:
- requires a sandbox partner key
- returns deterministic scenario rows from the hosted simulator catalog
- does not mutate live webhook delivery state
- returns `403 sandbox_key_required` when called with a live key
- success payloads now also expose top-level `docs_links`
- `links` now include additive sandbox webhook registry/event/replay navigation instead of only the simulator run shortcut

### GET /api/partner/sandbox/webhook-simulator/run
Returns sandbox webhook simulator metadata for the signed onboarding flow.

Response highlights:
- `method: POST`
- `required_fields: ["scenario_id"]`
- `optional_fields: ["secret", "key_id"]`
- `safe_for_replay_testing: true`
- success payloads now also expose top-level `docs_links` plus additive sandbox webhook event/replay navigation in `links`

### POST /api/partner/sandbox/webhook-simulator/run
Builds a deterministic sandbox webhook simulation payload for signature and replay-safe rehearsal.

Purpose:
- let partners practice webhook signature verification with sandbox-authenticated fixtures
- keep the rehearsal path deterministic and no-loss
- reuse the same scenario catalog while making the simulation explicit inside the partner sandbox contract

Behavior:
- requires a sandbox partner key
- never sends a live webhook
- never writes webhook event rows
- appends a bounded simulator-run audit row into the sandbox webhook companion state so operator promotion evidence can recover which scenarios were rehearsed
- requires explicit `scenario_id`
- returns `400 scenario_id_required` when `scenario_id` is omitted
- returns `400 invalid_scenario_id` when `scenario_id` does not match the hosted simulator catalog
- returns deterministic headers/body for the selected scenario
- defaults to a sandbox-only shared secret when none is supplied, so the response is always usable for rehearsal
- returns `403 sandbox_key_required` when called with a live key
- success payloads now also expose top-level `docs_links`
- `links` now include additive sandbox webhook registry/event/replay navigation so simulator-only clients can still discover the broader no-loss rehearsal path
- route-specific `400` responses reuse the same sandbox webhook simulator recovery links, including:
  - `/api/partner/sandbox/webhook-simulator/scenarios`
  - `/api/partner/sandbox/webhook-simulator/run`
  - `/api/partner/sandbox/webhook-events/replay`
  - `/api/partner/sandbox/webhooks`

### GET /api/partner/token-status
Read-only VETS token runtime snapshot for partners. This is the token-facing companion to `/api/partner/status`.

It is intended for:
- supply/circulating checks
- treasury/reserve balance visibility
- holder-count sanity checks
- mint/freeze authority review
- latest holder-growth and token-distribution snapshot status
- latest token-sync cron/runtime health

Response shape highlights:
- top-level:
  - `mint`
  - `decimals`
  - `rpc_url` (redacted; no query-string secrets)
  - `docs_links`
    - mirrors the shipped docs/bootstrap bundle exposed by the other authenticated partner discovery surfaces
- `chain`
  - `total_supply`
  - `treasury_balance`
  - `reserve_balance`
  - `tracked_treasury_balance`
  - `circulating_supply`
  - `holders`
  - `holders_source`
  - `holders_snapshot_count`
  - `holders_onchain_lower_bound`
  - `mint_authorities`
- `snapshots`
  - `evidence_links`
  - `holder_growth`
  - `holders_cache`
  - `token_distribution`
  - `transaction_sync`
  - `snapshot_jobs`
- `snapshot_policy`
  - `holder_growth`
  - `holders_cache`
  - `token_distribution`
  - `transaction_sync`
  - `snapshot_jobs_rollup`

Notes:
- `holders_onchain_lower_bound` comes from the RPC largest-accounts view and is only a lower bound.
- `holders` now prefers the cached/snapshotted holder count when available instead of the RPC lower bound.
- `snapshots.holders_cache.status` is `ok`, `stale`, or `missing`.
- `snapshots.holders_cache.latest_at` and `age_hours` let partners distinguish an old cache from a missing one.
- `snapshots.holder_growth` now carries additive freshness fields: `freshness_status`, `stale_after_hours`, and `age_hours` when a holder snapshot row exists.
- when no holder-growth snapshot row exists yet, `snapshots.holder_growth` remains present as a read-only summary object with `freshness_status=missing`.
- `snapshots.token_distribution.status` still reflects mismatch/error correctness.
- `snapshots.token_distribution.freshness_status` separately reflects whether the snapshot artifact itself is fresh or stale.
- `snapshots.token_distribution.fetch_retry_summary` now exposes whether the latest clean artifact recovered from transient upstream RPC/provider retries before succeeding.
- when no token-distribution artifact exists yet, `snapshots.token_distribution` remains present as a read-only summary object with `status=missing` and `freshness_status=missing`.
- `snapshots.transaction_sync` exposes the latest `refresh_txs` cron row from the `update_transactions.py` runtime with the same additive freshness and incident-state semantics.
- `snapshot_policy.transaction_sync` documents the expected `15` minute cadence and stale threshold for that runtime job.
- `snapshots.evidence_links.transaction_sync` points partners at the relevant public status surfaces for that runtime:
  - `/api/public/transaction-sync-freshness.json`
  - `/api/public/transaction-sync-freshness.csv`
  - `/transparency/transaction-sync-freshness`
  - `/api/public/system-status`
  - `/api/public/system-status/uptime?hours=168&include_maintenance=0`
  - `/api/public/system-status/incidents?hours=168&limit=30`
  - `/api/public/system-status/policy`
  - `/status`
- `snapshots.snapshot_jobs` now exposes the latest `cron_logs` row for:
  - `holder_growth_snapshot`
  - `token_distribution_snapshot`
  - each per-job summary now also exposes additive runtime context:
    - `latest_success_at`
    - `latest_success_age_hours`
    - `freshness_status=ok|stale|missing`
    - `stale_after_hours`
    - `age_hours`
    - `incident_state=clean|recovered|open|missing`
    - `latest_non_ok_at`
  - this lets partners distinguish "latest row recovered from a recent failure" from "no recent successful run exists"
- `snapshots.snapshot_jobs_rollup` now adds a bounded 30-day rollup for those two jobs:
  - `status=ok|warn|bad`
  - `missing_jobs`
  - `failed_count_30d` and `warning_count_30d`
  - `failed_open_jobs` vs `failed_recovered_jobs`
  - `warning_open_jobs` vs `warning_recovered_jobs`
  - `latest_non_ok_at`
- `links` now includes direct public evidence surfaces for this diagnostics chain:
  - `/api/public/public-stats-cache-freshness.json`
  - `/api/public/public-stats-cache-freshness.csv`
  - `/transparency/public-stats-cache-freshness`
  - `/api/public/token-mint-authorities.json`
  - `/api/public/token-mint-authorities.csv`
  - `/transparency/token-mint-authorities`
  - `/api/public/system-status/uptime?hours=168&include_maintenance=0`
  - `/api/public/holder-growth-freshness.json`
  - `/api/public/holder-growth-freshness.csv`
  - `/transparency/holder-growth-freshness`
  - `/api/public/holder-cache-freshness.json`
  - `/api/public/holder-cache-freshness.csv`
  - `/transparency/holder-cache-freshness`
  - `/api/public/token-distribution-freshness.json`
  - `/api/public/token-distribution-freshness.csv`
  - `/transparency/token-distribution-freshness`
  - `/api/public/token-snapshot-jobs-rollup.json`
  - `/api/public/token-snapshot-jobs-rollup.csv`
  - `/api/public/system-status`
  - `/api/public/system-status/incidents?hours=168&limit=30`
  - `/api/public/system-status/policy`
  - `/status`
  - `/transparency/token-distribution`
  - `/transparency/token-distribution.csv`
- `links` also includes the authenticated companion/recovery surfaces most partners need while triaging token-runtime issues:
  - `/api/partner/errors.json`
  - `/api/partner/capabilities`
  - `/api/partner/status`
- `snapshots.evidence_links` now groups those public proof URLs by runtime family so integrators can discover the most relevant evidence surface without inferring it from the top-level links block.
- `snapshots.evidence_links.*` now includes the same bounded public uptime surface anywhere it already advertises public system-status provenance.
  - `chain` now links:
    - the public-stats cache freshness proof family for supply / treasury / reserve runtime context
    - the public mint-authority proof family for mint/freeze authority review
- `snapshot_policy` now makes the interpretation contract explicit:
  - per-family `stale_after_hours`
  - expected daily cadence for the holder-growth and token-distribution snapshot jobs
  - allowed freshness/correctness status values
  - the snapshot-job rollup window plus open vs recovered problem-field semantics
- holder-growth and distribution status are best-effort readouts and do not mutate chain state.

### GET /api/public/token-mint-authorities.json
Public proof surface for the live mint and freeze authority state used by partner token-status governance review.

Response shape highlights:
- `mint`
- `rpc_url`
- `review_status`: `locked`, `open`, or `unavailable`
- `authority_state.mint_authority`
- `authority_state.freeze_authority`
- `authority_state.mint_authority_disabled`
- `authority_state.freeze_authority_disabled`
- `mint_account.ok`
- `mint_account.error`
- `mint_account.token_program`
- `mint_account.decimals_expected`
- `mint_account.decimals_reported`
- `mint_account.decimals_match`
- `mint_account.supply_raw`
- `policy_review.expected_review_status`
- `policy_review.expectation_configured`
- `policy_review.mismatch`
- `policy_review.mismatch_status`
- `policy_review.unavailable_status`
- `policy_review.reason`
- `trust_context`

### GET /api/public/token-mint-authorities.csv
CSV companion for the token mint-authority proof surface.

Columns:
- `mint`
- `rpc_url`
- `review_status`
- `mint_authority`
- `freeze_authority`
- `mint_authority_disabled`
- `freeze_authority_disabled`
- `mint_account_ok`
- `mint_account_error`
- `token_program`
- `is_initialized`
- `decimals_expected`
- `decimals_reported`
- `decimals_match`
- `supply_raw`
- `generated_at_utc`

### GET /transparency/token-mint-authorities
Browser-facing companion for the token mint-authority proof surfaces.

This page renders:
- current review status
- mint and redacted RPC URL
- mint and freeze authority state
- mint account token-program / initialization / decimals / supply fields
- the interpretation contract for `locked`, `open`, and `unavailable`

### GET /api/public/holder-growth-freshness.json
Public proof surface for the latest holder-growth snapshot artifact used by partner token-status diagnostics.

Response shape highlights:
- `snapshot_date`
- `holders_count`
- `total_supply`
- `created_at`
- `freshness_status`: `ok`, `stale`, or `missing`
- `stale_after_hours`
- `age_hours`
- `trust_context`

### GET /api/public/holder-growth-freshness.csv
CSV export companion for the holder-growth freshness proof surface.

Columns:
- `snapshot_date`
- `holders_count`
- `total_supply`
- `mint`
- `source`
- `created_at`
- `freshness_status`
- `stale_after_hours`
- `age_hours`
- `generated_at_utc`

### GET /transparency/holder-growth-freshness
Browser-facing companion for holder-growth freshness proof.

This page renders:
- snapshot date
- freshness status
- created time
- age and stale threshold
- holders count
- total supply
- mint
- source

### GET /api/public/holder-cache-freshness.json
Public proof surface for the cached holder tables used by holder-growth and partner token-status diagnostics.

Response shape highlights:
- `status`: `ok`, `stale`, or `missing`
- `stale_after_hours`
- `latest_at`
- `age_hours`
- `details.token_holders`
- `details.vets_token_accounts`
- `trust_context`

### GET /api/public/holder-cache-freshness.csv
CSV companion for holder-cache freshness evidence.

Columns:
- `source_key`
- `table`
- `column`
- `latest_at`
- `age_hours`
- `overall_status`
- `stale_after_hours`
- `generated_at_utc`
- `source_ok`
- `source_error`

### GET /transparency/holder-cache-freshness
Browser-facing companion for the holder-cache freshness proof surfaces. It presents the same freshness truth as the JSON and CSV endpoints in a public transparency page.

### GET /api/public/token-distribution-freshness.json
Public proof surface for the latest token-distribution snapshot artifact used by partner token-status diagnostics.

Response shape highlights:
- `generated_at`
- `mint`
- `decimals`
- `rpc_url`
- `tolerance_ui`
- `rows_count`
- `status`: `ok`, `warn`, `failed`, or `missing`
- `mismatches`
- `errors`
- `expected_ui`
- `actual_ui`
- `delta_ui`
- `fetch_retry_summary`: bounded retry evidence for the snapshot fetch path, including `retry_events`, `recovered_fetches`, and `exhausted_fetches`
- `freshness_status`: `ok`, `stale`, or `missing`
- `stale_after_hours`
- `age_hours`
- `trust_context`

### GET /api/public/token-distribution-freshness.csv
CSV companion for the token-distribution freshness/correctness proof surface.

Columns:
- `generated_at`
- `mint`
- `decimals`
- `rpc_url`
- `tolerance_ui`
- `rows_count`
- `status`
- `mismatches`
- `errors`
- `expected_ui`
- `actual_ui`
- `delta_ui`
- `freshness_status`
- `stale_after_hours`
- `age_hours`
- `generated_at_utc`

### GET /transparency/token-distribution-freshness
Browser-facing companion for the token-distribution freshness/correctness proof surface.

This page renders:
- generated time
- freshness status
- age and stale threshold
- row count
- correctness status
- mint / decimals / RPC / tolerance
- mismatch / error / expected / actual / delta summary

Promotion status:
- linked from `/api/partner/token-status`
- linked from `/trust`
- linked from `/status`
- linked from `/trust`
- linked from `/status`

### GET /api/public/transaction-sync-freshness.json
Public proof surface for the latest `refresh_txs` cron row from the `update_transactions.py` runtime, used by partner token-status diagnostics.

Response shape highlights:
- `job_name`
- `status`
- `message`
- `executed_at`
- `latest_success_at`
- `latest_success_age_hours`
- `freshness_status`
- `stale_after_hours`
- `age_hours`
- `incident_state`
- `latest_non_ok_at`
- `window_days`
- `trust_context`

### GET /api/public/transaction-sync-freshness.csv
CSV companion for the token transaction-sync freshness proof surface.

Columns:
- `job_name`
- `status`
- `message`
- `executed_at`
- `latest_success_at`
- `latest_success_age_hours`
- `freshness_status`
- `stale_after_hours`
- `age_hours`
- `incident_state`
- `latest_non_ok_at`
- `window_days`
- `generated_at_utc`

### GET /transparency/transaction-sync-freshness
Browser-facing companion for the token transaction-sync freshness proof family.

Page highlights:
- latest runtime status
- freshness status and age
- stale threshold
- incident state
- latest success timestamp and age
- latest non-ok timestamp
- bounded 30-day provenance window

### GET /api/public/token-snapshot-jobs-rollup.json
Public proof surface for the bounded token snapshot-job cron provenance used by partner token-status diagnostics.

Response shape highlights:
- `snapshot_jobs.holder_growth_snapshot`
- `snapshot_jobs.token_distribution_snapshot`
  - each per-job summary now also exposes:
    - `freshness_status`
    - `stale_after_hours`
    - `age_hours`
    - `incident_state`
    - `latest_non_ok_at`
- `snapshot_jobs_rollup.status`
- `snapshot_jobs_rollup.missing_jobs`
- `snapshot_jobs_rollup.failed_count_30d`
- `snapshot_jobs_rollup.warning_count_30d`
- `snapshot_jobs_rollup.failed_open_jobs`
- `snapshot_jobs_rollup.failed_recovered_jobs`
- `snapshot_jobs_rollup.warning_open_jobs`
- `snapshot_jobs_rollup.warning_recovered_jobs`
- `snapshot_jobs_rollup.latest_non_ok_at`
- `trust_context`

### GET /api/public/token-snapshot-jobs-rollup.csv
CSV companion for the token snapshot-job rollup proof surface.

Columns:
- `job_name`
- `latest_status`
- `latest_message`
- `latest_executed_at`
- `rollup_status`
- `window_days`
- `missing_jobs_csv`
- `failed_count_30d`
- `warning_count_30d`
- `failed_open_jobs_csv`
- `failed_recovered_jobs_csv`
- `warning_open_jobs_csv`
- `warning_recovered_jobs_csv`
- `latest_non_ok_at`
- `generated_at_utc`

### GET /transparency/token-snapshot-jobs-rollup
Browser-facing companion for the token snapshot-job rollup proof family.

Page highlights:
- rollup status
- 30-day window size
- missing jobs
- failed and warning row counts
- latest non-ok timestamp
- open vs recovered failed/warning job lists
- latest per-job status, executed time, and message

Promotion status:
- linked from `/api/partner/token-status`

Helper script:
`python3 /home/vets-coin/flask_api/scripts/sign_partner_request.py --method POST --path /api/salutes/credit --key-id CHANGE_ME_KEY_ID --secret CHANGE_ME_SECRET --json '{"user_id":"4","amount":100}'`

## Salutes API (Partner)

### POST /api/salutes/credit
Credits Salutes to a user.

Body:
```json
{"user_id":"4","amount":100,"reason":"event_participation","source":"patriotchat"}
```

Response:
```json
{"success":true,"user_id":"4","balance":100}
```

### POST /api/salutes/debit
Debits Salutes from a user.

Body:
```json
{"user_id":"4","amount":25,"reason":"content_boost","source":"patriotchat"}
```

Response:
```json
{"success":true,"user_id":"4","balance":75}
```

### GET /api/salutes/balance?user_id=4
Returns current balance.

Response:
```json
{"success":true,"user_id":"4","balance":75}
```

### GET /api/salutes/ledger?user_id=4
Admin-only JSON ledger (requires admin session) or partner access with `ledger` scope.
Partner access returns entries filtered to the partner's key.

Response:
```json
{"success":true,"user_id":"4","balance":75,"ledger":[...]}
```

## Partner Identity API (HMAC)

### GET /api/partner/user-lookup?email=...&username=...&wallet=...
Lookup a user by email, username, or wallet. Returns a minimal profile for mapping to `user_id`.

Optional query params:
- `q` legacy alias (deprecated; sunset target `2026-06-30T00:00:00Z`)
- `include_wallets=1` to include known wallets
- `cursor=<last_seen_user_id>` for write-resilient paging
- `limit` (max 50)
- `offset` (legacy paging)
- invalid cursor values return `400 {"error_code":"invalid_cursor"}`.

Response:
```json
{"success":true,"count":1,"users":[{"id":4,"username":"mark","display_name":"Mark","status":"active","is_verified":true,"wallet_address":"...","wallets":[...]}]}
```

Success payloads also include `docs_links` and `links` fields that point to the OpenAPI spec, partner status/error surfaces, and the live user/wallet lifecycle endpoints.
Route-specific failures such as `invalid_cursor`, `query_required`, `invalid_status`, `db unavailable`, and `server error` now also include that same live user/wallet recovery hub.

### GET /api/partner/users
List users linked to the current partner key.

Optional query params:
- `status=active|suspended|banned|pending`
- `q=<search text>` (username/display name/email/external_id)
- `include_wallets=1`
- `cursor=<last_seen_user_id>`
- `limit` / `offset`
- invalid cursor values return `400 {"error_code":"invalid_cursor"}`.

Success payloads also include `docs_links` and `links` fields that point to the live user lookup, wallet diagnostic templates, partner webhook registry, and public status proof surfaces.
Route-specific failures such as `invalid_cursor`, `invalid_status`, `db unavailable`, and `server error` now also include that same live user/wallet recovery hub.

### GET /api/partner/wallet-info/<wallet>
Returns SOL + VETS balances for a wallet (HMAC required, no session lock).

Response:
```json
{"success":true,"wallet":"...","sol_balance":1.23,"vets_balance":456.0,"lp_mint":null,"lp_amount":null}
```

Success payloads also include `docs_links` plus `links.partner_wallet_verification` and the related live user templates.
Route-specific failures such as `invalid_wallet_address` and `rpc_unavailable` now also include that same live wallet diagnostic recovery hub.

### GET /api/partner/wallet-verification/<wallet>
Returns wallet verification freshness for partner-owned wallet links (read scope).

Response:
```json
{
  "success": true,
  "wallet": "...",
  "wallet_states": {
    "linked": true,
    "connected_session": false,
    "verified": true,
    "stale_verification": false,
    "hidden": false
  },
  "verification": {
    "verified": true,
    "verified_at_utc": "2026-04-01T12:00:00Z",
    "verification_age_days": 16,
    "freshness": "fresh",
    "status_text": "Verified 16d ago",
    "reverify_days": 60
  },
  "owner": {
    "user_id": 42,
    "external_id": "patriotchat-42",
    "is_primary": true,
    "is_hidden": false
  }
}
```

Success payloads also include `docs_links` plus `links.partner_user` and `links.partner_wallet_info` for the verified wallet owner.
Route-specific failures such as `invalid_wallet_address`, `db unavailable`, `not_found`, and `server error` now also include that same live wallet diagnostic recovery hub.

## Partner Donation Claims (HMAC)

### POST /api/partner/donation-claim
Partner-authenticated version of the donation webhook (issues a faucet claim if enabled).

Behavior:
- requires a live key with `donation` scope
- sandbox keys now receive `403 live_key_required`
- now requires `campaign_id`
- now requires numeric positive `amount`
- if `wallet_address` is supplied, it must be a valid Solana wallet address
- sandbox callers should use:
  - `/api/partner/sandbox/donation-claim`
  - `/api/partner/sandbox/promotion-report`

Body:
```json
{"campaign_id":"spring_drive","amount":50,"email":"donor@example.com","wallet_address":"...","faucet_campaign_id":"12"}
```

Response:
```json
{"status":"success","claim_code":"...","claim_url":"...","email_sent":true,"claim_status":"issued","claim_campaign_id":"12","partner_key_id":"..."}
```

Success payloads also include `docs_links` plus `links.partner_donation_claim`, `links.sandbox_donation_claim`, and wallet diagnostic links when a wallet address is part of the claim request.
Runtime-unavailable or downstream non-200 partner donation-claim responses now also keep those same docs/recovery links so operators can move between live claim intake, sandbox rehearsal, and wallet diagnostics without leaving the authenticated contract.
Route-specific validation failures such as `campaign_id_required`, `amount_must_be_numeric`, `amount_must_be_positive`, and `invalid_wallet_address` now also include that same live donation recovery hub.

## Partner User Provisioning (HMAC)

### POST /api/partner/users
Create or link a user to an external ID.

Behavior:
- requires a live key with `users` scope
- sandbox keys now receive `403 live_key_required`
- sandbox callers should use:
  - `/api/partner/sandbox/users`
  - `/api/partner/sandbox/users/<id>`
  - `/api/partner/sandbox/users/<id>/wallets`
  - `/api/partner/sandbox/promotion-report`

Body:
```json
{"external_id":"patriotchat-123","email":"user@example.com","display_name":"Jane","wallet_address":"...","link_existing":true}
```

Response:
```json
{"success":true,"user":{"id":12,"username":"...","status":"active"},"external_id":"patriotchat-123"}
```

Success payloads also include `docs_links` plus `links.partner_user` and `links.partner_wallet_link` so the newly linked live surfaces are immediately discoverable.
Route-specific failures such as `external_id_required`, `invalid_wallet_address`, `invalid_status`, `email_in_use`, `db unavailable`, conflict-style wallet-link errors, and `server error` now also include that same live user lifecycle recovery hub.

### GET /api/partner/users/<id>
Fetch a user owned by this partner. Use `include_wallets=1` to include known wallets.
Success payloads also include `docs_links` plus live wallet diagnostic links when the user already has a wallet.
Route-specific failures such as `db unavailable`, `not_found`, and `server error` now also include that same live user lifecycle recovery hub.

### PATCH /api/partner/users/<id>
Update display name or status.

Behavior:
- requires a live key with `users` scope
- sandbox keys now receive `403 live_key_required`
- sandbox callers should use:
  - `/api/partner/sandbox/users/<id>`

Body:
```json
{"display_name":"New Name","status":"active"}
```

Success payloads also include `docs_links` plus `links.partner_user` and the wallet-link template for the updated live user.
Route-specific failures such as `invalid_status`, `no_updates`, `db unavailable`, `not_found`, and `server error` now also include that same live user lifecycle recovery hub.

### POST /api/partner/users/<id>/wallets
Link a wallet to a partner-owned user.

Behavior:
- requires a live key with `users` scope
- sandbox keys now receive `403 live_key_required`
- sandbox callers should use:
  - `/api/partner/sandbox/users/<id>/wallets`

Body:
```json
{"wallet_address":"...","label":"Primary","is_primary":true}
```

Success payloads also include `docs_links` plus `links.partner_wallet_verification` for the newly linked wallet.
Route-specific failures such as `invalid_wallet_address`, `db unavailable`, `not_found`, wallet-link conflicts, and `server error` now also include that same live user lifecycle recovery hub.

## Partner Webhooks (HMAC)

### GET /api/partner/webhooks
List current webhooks.
Success payloads also include `docs_links` plus live/sandbox webhook registry, replay, and signature-verification links.
Route-specific failures such as `invalid_cursor`, `invalid_is_active`, `invalid_event_type`, `db unavailable`, and `server error` now also include that same webhook recovery hub.

### POST /api/partner/webhooks
Create a webhook for an event type.

Behavior:
- requires a live key with `webhooks` scope
- sandbox keys now receive `403 live_key_required`
- sandbox webhook rehearsal remains available through:
  - `/api/partner/sandbox/webhooks`
  - `/api/partner/sandbox/webhook-simulator/scenarios`
  - `/api/partner/sandbox/webhook-simulator/run`

Body:
```json
{"event_type":"salutes.credit","target_url":"https://partner.example.com/webhooks/vets","secret":"optional"}
```

Success payloads also include `docs_links` plus direct links to the created live webhook, its test route, and the sandbox webhook rehearsal branch.
Route-specific failures such as `invalid_event_type`, `invalid_target_url`, `https_required`, `db unavailable`, and `server error` now also include that same webhook recovery hub.

### DELETE /api/partner/webhooks/<id>
Disable a webhook.

Behavior:
- requires a live key with `webhooks` scope
- sandbox keys now receive `403 live_key_required`
- sandbox callers should use:
  - `/api/partner/sandbox/webhooks`

Success payloads also include `docs_links` plus direct links back to the live webhook registry, the deleted webhook id, and the webhook test route template.
Route-specific failures such as `db unavailable` and `server error` now also include that same webhook recovery hub.

### POST /api/partner/webhooks/<id>/test
Queue a test event to the webhook.

Behavior:
- requires a live key with `webhooks` scope
- sandbox keys now receive `403 live_key_required`
- sandbox callers should use:
  - `/api/partner/sandbox/webhooks/<id>/test`

Success payloads also include `docs_links` plus direct links to the live webhook registry, event inventory, and signature verification helper.
Route-specific failures such as `db unavailable`, `not_found`, `send_failed`, and `server error` now also include that same webhook recovery hub.

Webhook headers:
- `X-Webhook-Event`
- `X-Webhook-Timestamp`
- `X-Webhook-Signature` (HMAC SHA256 of `{ts}.{body}`)
- `X-Webhook-Id`

Webhook payload:
```json
{"event_id":"123","event_type":"salutes.credit","data":{...}}
```

Retry processing:
```
python3 /home/vets-coin/flask_api/scripts/process_partner_webhooks.py
```

Retry/backoff controls (SLO-oriented):
- `PARTNER_WEBHOOK_MAX_ATTEMPTS` (default `5`)
- `PARTNER_WEBHOOK_TIMEOUT_SECONDS` (default `5`)
- `PARTNER_WEBHOOK_RETRY_BASE_SECONDS` (default `30`)
- `PARTNER_WEBHOOK_RETRY_MAX_SECONDS` (default `900`)
- `PARTNER_WEBHOOK_RETRY_JITTER_SECONDS` (default `15`)

Live retry profile is exposed on:
- `GET /api/partner/capabilities` (`webhook_retry_profile`)
- `GET /api/partner/analytics/usage` (`webhook_delivery_health.retry_profile`)
- `GET /api/partner/status` (`webhook_retry_profile`, `delivery_slo_controls`)
- `GET /api/partner/webhooks/dlq` (`retry_profile`)

Admin delivery ops (admin session required):
- `POST /admin/partners/webhook-events/<event_id>/replay`
- `GET /admin/partners/webhook-events/export?webhook_status=&webhook_key=&limit=`

## Telegram MVP Foundation API

### POST /api/telegram/webhook
Signed Telegram webhook ingress (bot mode foundation).

Security:
- Requires `X-Telegram-Bot-Api-Secret-Token` matching `TELEGRAM_WEBHOOK_SECRET_TOKEN`.
- Stores replay keys in DB (`telegram_webhook_replay`) to suppress duplicate update processing.

Response shape:
```json
{"success":true,"accepted":true,"duplicate":false,"update_id":123}
```

### POST /api/telegram/init/verify
Verifies Telegram WebApp `init_data` against `TELEGRAM_BOT_TOKEN` and freshness window.

Body:
```json
{"init_data":"query_id=...&user=...&auth_date=...&hash=..."}
```

### POST /api/telegram/wallet-claim/verify
Telegram wallet claim verifier using shared wallet proof signature logic used by `/api/wallet-proof/verify`.

Body:
```json
{
  "init_data":"...",
  "challenge_id":123,
  "wallet":"<SOL_WALLET>",
  "message":"VETS Wallet Verification\\nWallet: ...\\nNonce: ...",
  "signature":"<base64_signature>",
  "user_id":42
}
```

Mismatch behavior:
- returns `409` with `error_code=identity_wallet_mismatch` when the Telegram identity already has a different claimed wallet.
- returns `409` with `error_code=challenge_consumed|challenge_expired` for replay-safe re-submission handling.

### POST /api/telegram/wallet-proof/challenge
Sessionless challenge generator for Telegram mini-app wallet proof flow.

Body:
```json
{"init_data":"...","wallet":"<SOL_WALLET>"}
```

### GET /telegram/miniapp
Telegram mini-app UI shell for identity status, linked wallet status, wallet claim/proof, and retry states.

Bot deep-link commands in webhook:
- `/start`
- `/wallet`
- `/miniapp`

### POST /api/telegram/missions/list
Returns mission catalog + user mission progress for mini-app mission UI.

Body:
```json
{"init_data":"query_id=...&user=...&auth_date=...&hash=..."}
```

### POST /api/telegram/missions/enroll
Idempotent mission enrollment endpoint.

Body:
```json
{
  "init_data":"...",
  "mission_key":"daily_checkin_v1",
  "idempotency_key":"mission-enroll.1713390000.abcd1234"
}
```

Supports `Idempotency-Key` header and/or `idempotency_key` body.

### POST /api/telegram/missions/progress
Idempotent + replay-safe mission progress endpoint with anti-abuse controls.

Body:
```json
{
  "init_data":"...",
  "mission_key":"daily_checkin_v1",
  "action_key":"checkin",
  "replay_key":"mission-replay.1713390000.abcd1234",
  "idempotency_key":"mission-progress.1713390000.abcd1234"
}
```

Failure code highlights:
- `abuse_interval_limited` (HTTP `429`)
- `abuse_hourly_limited` (HTTP `429`)
- `replay_key_required`
- `idempotency_required`
- `mission_not_found`
- `mission_inactive` (HTTP `409`)

### POST /api/telegram/missions/history
Returns mission timeline rows from mission audit storage for daily mission UX.

Body:
```json
{
  "init_data":"...",
  "mission_key":"daily_checkin_v1",
  "limit":20
}
```

Response includes:
- `history[]` rows (`outcome`, `details`, `created_at_utc`, `tone`)
- `daily_streak` summary (`current_streak_days`, `best_streak_days`, `last_checkin_date_utc`)

### POST /api/telegram/missions/catalog
Read-only active mission catalog for Telegram mini-app consumption.

Body:
```json
{"init_data":"query_id=...&user=...&auth_date=...&hash=..."}
```

Response includes active mission rows with safe public fields:
- `mission_key`
- `title`
- `description`
- `target_count`
- `reward_amount_salutes`
- `cadence`
- `auto_enroll`
- `is_active`
- `revision`

### GET|POST /api/telegram/games/catalog
Read-only Telegram game catalog for the mini-app games hub.

Accepted auth input:
- `GET` query string:
  - `?init_data=query_id=...&user=...&auth_date=...&hash=...`
- `POST` JSON body:
```json
{"init_data":"query_id=...&user=...&auth_date=...&hash=..."}
```

Response includes:
- `telegram_user_id`
- `identity_status`
- `is_admin`
- `games[]`
  - `game_key`
  - `title`
  - `status`
  - `display_order`
  - `launchable`
  - optional `badge`
- `campaigns[]`

Current parity note:
- this route is live in `routes/telegram.py` and now documented in `flask_api/docs/openapi.yaml`
- additional Telegram game/admin route families still need a larger parity sweep and should be queued separately instead of expanded ad hoc

### GET /api/telegram/engagement/leaderboard-preview
Read-only leaderboard preview for the Telegram mini-app home surface.

Auth input:
- `?init_data=query_id=...&user=...&auth_date=...&hash=...`

Response includes:
- `telegram_user_id`
- `cached`
- `snapshot`
  - `id`
  - `snapshot_date`
  - `created_at_utc`
- `rows[]`
  - `rank`
  - `display_name`
  - `score`
  - `score_label`

### GET /api/telegram/engagement/activity-preview
Read-only recent activity preview for the Telegram mini-app home surface.

Auth input:
- `?init_data=query_id=...&user=...&auth_date=...&hash=...`

Response includes:
- `telegram_user_id`
- `cached`
- `snapshot`
  - `feed_key`
  - `generated_at_utc`
  - `expires_at_utc`
- `rows[]`
  - `event_type`
  - `user_display_name`
  - `short_message`
  - `created_at_utc`
  - `relative_age_label`

Current parity note:
- both engagement preview routes are live in `routes/telegram.py` and now documented in `flask_api/docs/openapi.yaml`
- the broader Telegram game/admin parity sweep still remains queued and should stay bounded

### POST /api/telegram/games/spin-wheel/status
Read-only spin wheel status for the Telegram mini-app.

Body:
```json
{"init_data":"query_id=...&user=...&auth_date=...&hash=..."}
```

Response includes:
- `telegram_user_id`
- `game_key`
- `available_today`
- `already_played_today`
- `daily_spin_available`
- `bonus_spin_available`
- `spin_availability_mode`
- `active_campaign_key`
- `active_theme_key`
- `reward_options_model`
- `reward_options[]`
  - `slice_id`
  - `reward_type`
  - `reward_amount`
  - `weight`

### POST /api/telegram/games/spin-wheel/play
Execute a spin wheel play for the Telegram mini-app.

Body:
```json
{"init_data":"query_id=...&user=...&auth_date=...&hash=...","spin_key":"optional-free-spin-key","launch_source":"browser_open"}
```

Response includes:
- `reason`
- `telegram_user_id`
- `game_key`
- `already_played_today`
- `active_campaign_key`
- `result`
  - `reward_salutes`
  - `reward_amount`
  - `reward_type`
  - `reward_label`
  - `winning_slice_id`
  - `play_source_type`

Conflict notes:
- `409 game_launch_blocked`
- `409 already_played_today`
- `409 free_spins_unavailable`
- `400 spin_key_required`

Current parity note:
- both spin wheel runtime routes are live in `routes/telegram.py` and now documented in `flask_api/docs/openapi.yaml`
- remaining public Telegram game runtime and admin-game/bot parity work should stay bounded

### POST /api/telegram/games/recon-grid/status
Read-only Recon Grid status for the Telegram mini-app.

Body:
```json
{"init_data":"query_id=...&user=...&auth_date=...&hash=..."}
```

Response includes:
- `telegram_user_id`
- `game_key`
- `level`
- `board_size`
- `ship_sizes`
- `missile_balance`
- `current_board_index`
- `available_today`
- `interaction_enabled`
- `tile_interaction_enabled`

### POST /api/telegram/games/recon-grid/start
Start or continue a Recon Grid round for the Telegram mini-app.

Body:
```json
{"init_data":"query_id=...&user=...&auth_date=...&hash=...","launch_source":"browser_open"}
```

Response includes:
- `reason`
  - `started`
  - `continued`
  - `in_progress`
- `telegram_user_id`
- `game_key`
- `level`
- `missile_balance`
- `in_progress`
- `interaction_enabled`
- `tile_interaction_enabled`

Conflict notes:
- `409 game_launch_blocked`
- `409 already_played_today`

### POST /api/telegram/games/recon-grid/reveal
Reveal one Recon Grid tile for the Telegram mini-app.

Body:
```json
{"init_data":"query_id=...&user=...&auth_date=...&hash=...","tile_index":4}
```

Response includes:
- `reason`
  - `reveal_recorded`
  - `round_completed`
  - `round_resolved`
- `telegram_user_id`
- `game_key`
- `level`
- `missile_balance`
- `can_start_next_board`
- `reveal`
  - `tile_index`
  - `hit`
  - `ship_completed`
  - `board_completed`
- optional `result`
- optional `miss_reward`

Conflict notes:
- `400 invalid_tile_index`
- `409 game_launch_blocked`
- `409 already_played_today`
- `409 tile_already_revealed`

Current parity note:
- Recon public runtime status/start/reveal routes are now documented in `flask_api/docs/openapi.yaml`
- remaining Telegram runtime/docs backlog is now mostly purchase and admin/operator coverage

### POST /api/telegram/games/promos/claim
Claim a one-time promo reward for the Telegram mini-app.

Body:
```json
{"init_data":"query_id=...&user=...&auth_date=...&hash=...","promo_key":"welcome_drop","claim_key":"claim-key-1"}
```

Response includes:
- `reason`
  - `promo_claimed`
- `telegram_user_id`
- `claim`
  - `promo_key`
  - `title`
  - `reward_payload`
  - `reward_summary`
  - `amount_label`
- optional `spin_status`
  - `free_spin_balance`
  - `promo_spin_tokens_balance`
  - `promo_spin_integration_status`
- optional `recon_status`
  - `missile_balance`

Conflict notes:
- `409 promo_already_claimed`
- `409 promo_disabled`
- `409 promo_expired`
- `400 promo_key_required`
- `400 claim_key_required`
- `400 promo_not_found`
- `400 promo_reward_invalid`

### POST /api/telegram/games/recon-grid/purchase
Purchase a Recon missile bundle for the Telegram mini-app.

Body:
```json
{"init_data":"query_id=...&user=...&auth_date=...&hash=...","bundle_key":"salutes_patrol_pack","purchase_key":"purchase-key-1"}
```

Response includes:
- `reason`
  - `purchase_recorded`
- `telegram_user_id`
- `game_key`
- `missile_balance`
- `miniapp_salutes_balance`
- `miniapp_salutes_runtime`
- `miniapp_vets_balance`
- `miniapp_vets_wallet_linked`
- `miniapp_vets_wallet_verified`
- `miniapp_vets_balance_status`
- `missile_purchase_currency`
- `missile_purchase_options`
- `purchase`
  - `bundle_key`
  - `payment_currency`
  - `payment_amount`
  - `missiles_awarded`
  - `amount_label`

Conflict notes:
- `409 game_launch_blocked`
- `409 insufficient_salutes`
- `409 insufficient_vets`
- `409 linked_wallet_required`
- `409 wallet_verification_required`
- `409 vets_balance_unavailable`
- `400 invalid_bundle`
- `400 purchase_key_required`
- `400 unsupported_payment_currency`

### POST /api/telegram/games/items/purchase
Purchase a generic mini-app item bundle such as spin plays.

Body:
```json
{"init_data":"query_id=...&user=...&auth_date=...&hash=...","item_type":"spin_play","bundle_key":"spin_salutes_patrol_pack","purchase_key":"purchase-key-spin-1"}
```

Response includes:
- `reason`
  - `purchase_recorded`
- `telegram_user_id`
- `game_key`
- `free_spin_balance`
- `promo_spin_tokens_balance`
- `miniapp_salutes_balance`
- `miniapp_vets_balance`
- `miniapp_vets_wallet_linked`
- `miniapp_vets_wallet_verified`
- `miniapp_vets_balance_status`
- `spin_purchase_options`
- `purchase`
  - `bundle_key`
  - `item_type`
  - `payment_currency`
  - `payment_amount`
  - `bundle_amount`
  - `amount_label`

Conflict notes:
- `409 game_launch_blocked`
- `409 insufficient_salutes`
- `409 insufficient_vets`
- `409 linked_wallet_required`
- `409 wallet_verification_required`
- `409 vets_balance_unavailable`
- `400 invalid_bundle`
- `400 purchase_key_required`
- `400 unsupported_payment_currency`
- `400 unsupported_item_type`

Current parity note:
- promo claim, Recon purchase, and generic game-item purchase routes are now documented in `flask_api/docs/openapi.yaml`
- remaining Telegram runtime/docs backlog should now be limited to admin/operator game and bot surfaces

### GET /api/telegram/admin/bot/preview
Admin-session bot preview payload for rollout telemetry, command drift, alerts, and readiness summary.

Query params:
- `hours` (default `168`)
- `live=1` to attempt live Bot API checks during the preview build

Response includes:
- `telemetry`
  - totals such as `callback_used`, `command_send_failures`
  - 24h delta blocks
- `bootstrap_readiness`
  - `live_check_attempted`
  - `webhook_update_types.expected`
- `command_sync`
  - `status`
  - `error_code` when live command sync was not requested or failed
- `command_helper`
  - `bot_identity_expected`
- `command_preview`
- `checklist`
- `alerts`

### GET /api/telegram/admin/bot/bootstrap-readiness
Admin-session readiness snapshot for bot token/webhook/mini-app configuration and optional live Bot API checks.

Query params:
- `live=1` to call `getMe`, `getWebhookInfo`, and `getMyCommands`

Response includes:
- `live_check_attempted`
- `config`
  - `token_configured`
  - `webhook_secret_configured`
  - `miniapp_url_configured`
- `checks`
  - `bot_api_reachable`
  - `webhook_info_reachable`
- `webhook_info`
- `webhook_update_types.expected`
- `command_sync`
  - `status`
  - `missing_commands`
  - `extra_commands`
- optional `errors`

### GET /api/telegram/admin/bot/command-helper
Admin-session BotFather helper payload with the expected mini-app URL, webhook URL, and canonical command block.

Response includes:
- `bot_identity_expected`
- `expected_urls`
  - `miniapp_url`
  - `webhook_url`
  - `webhook_allowed_updates`
- `botfather_commands_block`
- `display_block`

Current parity note:
- Telegram admin bot preview/bootstrap/helper routes are now documented in `flask_api/docs/openapi.yaml`
- the remaining Telegram runtime/docs backlog should now be limited to admin/operator game surfaces

### GET /api/telegram/admin/games/promos
Admin-session list of Recon promo definitions.

Query params:
- `include_disabled=1` to include disabled promos in the result set

Response includes:
- `promos[]`
  - `promo_key`
  - `enabled`
  - other saved promo fields when present

### POST /api/telegram/admin/games/promos
Admin-session create/update route for Recon promo definitions.

Body example:
```json
{"promo_key":"welcome_drop","title":"Welcome Drop","reward_payload":{"missiles":10,"spin_tokens":1},"enabled":true}
```

Response includes:
- `reason`
  - `promo_saved`
- `promo`
  - `promo_key`
  - `enabled`

### POST /api/telegram/admin/games/recon-grid/manual-grant
Admin-session support/promotion route for manually granting Recon missiles.

Body example:
```json
{"telegram_user_id":9001,"missiles":9,"grant_key":"sir-mark-test","note":"support"}
```

Response includes:
- `reason`
  - `manual_grant_recorded`
- `grant_duplicate`
- `grant`
  - `telegram_user_id`
  - `linked_user_id`
  - `missiles_awarded`
  - `source_key`
- `recon_status`
  - `missile_balance`

Conflict/error notes:
- `400` validation failures
- `404 target_not_found`
- `503 db_unavailable`

Current parity note:
- Telegram admin promo-list/save and Recon manual-grant routes are now documented in `flask_api/docs/openapi.yaml`
- the remaining Telegram runtime/docs backlog should now be limited to broader admin game analytics/catalog/operator surfaces

### GET /api/telegram/admin/games/overview
Admin-session top-level analytics summary across Telegram game runtimes.

Query params:
- `days`
- `preset`
- `games=recon_grid_v1,spin_wheel_v1`

Response includes:
- `summary_cards`
- `games`
- `daily_trend`
- `daily_trend_count`
- `incident_health`
- game-filtered output when `games=` is provided

### GET /api/telegram/admin/games/spin-wheel
Admin-session analytics view scoped to Spin Wheel.

Query params:
- `days`
- `preset`

Response includes:
- `game`
  - `game_key=spin_wheel_v1`
- `daily_trend`
- `reward_distribution`
- `reward_distribution_count`
- `summary_cards`
- `incident_health`

### GET /api/telegram/admin/games/recon-grid
Admin-session analytics view scoped to Recon Grid.

Query params:
- `days`
- `preset`

Response includes:
- `game`
  - `game_key=recon_grid_v1`
- `daily_trend`
- `level_breakdown`
- `level_breakdown_count`
- `summary_cards`
- `incident_health`
- `launch_mode_split`

### GET /api/telegram/admin/games/rollout-checklist
Admin-session rollout-readiness checklist built from the shared Telegram game analytics payload.

Query params:
- `days`
- `preset`
- `games=...`

Response includes:
- `summary_cards`
- `incident_health`
- `launch_mode_split`
- `checklist`
  - `status`
  - `items[]`
    - examples in current contract coverage:
      - `spin_status`
      - `recon_schema_ready`
      - `utc_lock_anomalies`

### GET /api/telegram/admin/games/overview/export
Admin-session export for the shared game overview analytics dataset.

Query params:
- `format=json|csv`
- `days`
- `preset`
- `games=...`

Response modes:
- `format=json`
  - `date_from`
  - `date_to`
  - `game_keys`
  - `rows`
  - `row_count`
- `format=csv`
  - header currently includes:
    - `day_utc,game_key,plays,completions,completion_rate,average_reward,reward_total,availability_failures,already_played,invalid_actions,duplicate_actions`

Current parity note:
- Telegram admin overview/spin/recon analytics, rollout checklist, and overview export routes are now documented in `flask_api/docs/openapi.yaml`
- the remaining Telegram runtime/docs backlog should now be limited to the broader admin catalog/audience/user-activity operator endpoints

### GET /api/telegram/admin/games/wheel-campaigns
Admin-session list of Spin Wheel campaign definitions.

Response includes:
- `campaigns[]`
  - `campaign_key`
  - `status`
  - optional slice metadata when present

### POST /api/telegram/admin/games/wheel-campaigns
Admin-session create/update route for Spin Wheel campaign definitions.

Response includes:
- `reason`
  - `campaign_saved`
- `campaign`

### GET /api/telegram/admin/games/catalog-campaigns
Admin-session list of cross-game catalog campaign definitions.

Response includes:
- `campaigns[]`
  - `campaign_key`
  - `game_key`
  - access/campaign fields when present

### POST /api/telegram/admin/games/catalog-campaigns
Admin-session create/update route for cross-game catalog campaign definitions.

Response includes:
- `reason`
  - `campaign_saved`
- `campaign`

### GET /api/telegram/admin/games/audience-groups
Admin-session list of Telegram game audience groups.

Response includes:
- `groups[]`
  - `group_key`
  - membership and status fields when present

### POST /api/telegram/admin/games/audience-groups
Admin-session create/update route for audience-group definitions.

Response includes:
- `reason`
  - `group_saved`
- `group`

### GET /api/telegram/admin/games/user-profiles
Admin-session list of Telegram game user profiles.

Query params:
- `limit`

Response includes:
- `profiles[]`
  - `telegram_user_id`
  - profile/user metadata fields when present

### GET /api/telegram/admin/games/user-activity/<telegram_user_id>
Admin-session activity summary for one Telegram game user.

Response includes:
- `activity`

### GET /api/telegram/admin/games/catalog-preview
Admin-session preview of which game campaigns are visible to one Telegram user.

Query params:
- `telegram_user_id` required
- `linked_user_id`
- `admin_view=1`

Response includes:
- `telegram_user_id`
- `linked_user_id`
- `campaigns[]`

Current parity note:
- Telegram admin campaign/audience/profile/catalog-preview operator routes are now documented in `flask_api/docs/openapi.yaml`
- the remaining Telegram runtime/docs backlog should now be limited to any still-undocumented edge admin endpoints beyond this grouped operator family
### Admin mission catalog APIs (admin session required)

1. `GET /api/telegram/admin/missions`
- List all mission catalog entries with activity flags.

2. `POST /api/telegram/admin/missions`
- Create mission catalog entry with strict validation.

3. `PATCH /api/telegram/admin/missions/<mission_key>`
- Update editable mission fields with revision/audit write.

4. `POST /api/telegram/admin/missions/<mission_key>/activate`
5. `POST /api/telegram/admin/missions/<mission_key>/deactivate`
- Explicit publish/unpublish toggles for `is_active`.

6. `GET /api/telegram/admin/missions/<mission_key>/stats`
- Mission usage summary from enrollments and mission action audit rows.

7. `GET /api/telegram/admin/missions/<mission_key>/analytics?days=<1..90>`
- Daily mission performance trend (UTC day buckets) for admin analytics.
- Includes counts for enrolls, accepted progress, completions, abuse limits, replay/idempotency suppression.

8. Admin HTML mission authoring page
- `GET /telegram/admin/missions` (admin session required)
- Supports create/edit/activate/deactivate form flows backed by mission catalog APIs.

### GET /api/partner/analytics/usage
Per-key usage analytics snapshot (requests, errors, optional latency percentiles, webhook delivery health).

Query params:
- `hours` (default `24`, max `720`)

Additive response highlights:
- success payloads now also expose top-level `docs_links`
- `links` now include additive partner webhook debugging routes, sandbox webhook rehearsal/recovery routes, partner status, and public status recovery surfaces
- the shared live webhook/debug hub now also keeps these companion routes on the same authenticated payload:
  - `partner_capabilities`
  - `partner_token_status`
  - `partner_errors`
  - `sandbox_verify`
  - `sandbox_promotion_report`
  - bounded `/api/public/system-status*` and `/status` proof links
- route-specific `db unavailable` failures now also include that same recovery-link hub

### GET /api/partner/webhook-events
Partner-facing webhook event feed with status/range filters for replay workflows.

Query params:
- `status=all|pending|failed|delivered|dead_letter`
- `reason_tag=<string>`
- `from=<ISO8601 UTC>`
- `to=<ISO8601 UTC>`
- `webhook_id=<id>`
- `limit`, `offset`, `cursor`

Additive response highlights:
- success payloads now also expose top-level `docs_links`
- `links` now include additive partner webhook replay/debugging routes, sandbox rehearsal counterparts, partner status, and public status recovery surfaces
- `links` also mirrors the shared live webhook/debug hub used by analytics, replay, signature verification, and DLQ responses:
  - `partner_capabilities`
  - `partner_token_status`
  - `partner_errors`
  - `sandbox_verify`
  - bounded public status proof links
- route-specific validation failures such as `invalid_cursor`, `invalid_from`, `invalid_to`, `invalid_status`, and `invalid_reason_tag` now also include that same recovery-link hub

### POST /api/partner/webhook-events/replay
Batch replay webhook events by id (requires `Idempotency-Key`).

Behavior:
- requires a live key with `webhooks` scope
- sandbox keys now receive `403 live_key_required`
- sandbox callers should use:
  - `/api/partner/sandbox/webhook-events/replay`

Body:
```json
{"event_ids":[101,102,103],"reason_tag":"receiver_fix","note":"receiver deployed fix"}
```

Additive response highlights:
- success payloads now also expose top-level `docs_links`
- `links` now include additive live webhook events/debugging routes plus sandbox recovery/rehearsal routes so partners can move between live incident response and no-loss rehearsal without leaving the authenticated contract
- `links` also keeps the shared live webhook/debug hub on the same response:
  - `partner_capabilities`
  - `partner_token_status`
  - `partner_errors`
  - `sandbox_verify`
  - `sandbox_promotion_report`
  - bounded public status proof links
- route-specific validation failures such as `invalid_reason_tag` and `event_ids_required` now also include that same recovery-link hub

### POST /api/partner/webhooks/signature/verify
Webhook signature verification test endpoint for partner receiver debugging.

Body:
```json
{"secret":"...","timestamp":"1700000000","payload":{"event_id":"123","event_type":"salutes.credit","data":{}},"signature":"<hex>"}
```

Success payloads also include `docs_links` plus the shared live webhook/debug hub:
- partner registry and replay routes
- `partner_capabilities`
- `partner_status`
- `partner_token_status`
- `partner_errors`
- `partner_webhook_dlq`
- `sandbox_verify`
- `sandbox_promotion_report`
- bounded public status proof links
- request-shape failures such as `secret_required` and `signature_inputs_required` now also include that same recovery-link hub

### GET /api/partner/webhooks/dlq
Dead-letter queue summary with reason-tag rollups and remediation playbook links.

Additive response highlights:
- success payloads now also expose top-level `docs_links`
- `links` now include additive analytics, replay, sandbox recovery, and partner/public status navigation so DLQ triage can stay inside the authenticated contract
- the shared live webhook/debug hub now also keeps these companion routes on the same response:
  - `partner_capabilities`
  - `partner_token_status`
  - `partner_errors`
  - `sandbox_verify`
  - `sandbox_promotion_report`
  - bounded public status proof links
- route-specific `db unavailable` failures now also include that same recovery-link hub

### GET /api/partner/status
Partner status summary with endpoint percentile snapshot, webhook DLQ health, and incident-impact hints.

Additive response highlights:
- `key_mode`
  - `live` or `sandbox`
- `docs_links`
  - authenticated docs/tooling bundle for quickstart/spec/SDK/changelog discovery
- `status_page_links`
  - always includes:
    - `/api/partner/capabilities`
    - `/api/partner/token-status`
    - `/api/partner/analytics/usage`
    - `/api/partner/webhooks/dlq`
    - `/api/partner/webhook-events`
    - `/api/partner/webhook-events/replay`
    - `/api/partner/webhooks/signature/verify`
    - `/api/partner/errors.json`
    - `/api/public/system-status`
    - `/api/public/system-status/incidents?hours=168&limit=30`
    - `/api/public/system-status/uptime?hours=168&include_maintenance=0`
    - `/api/public/system-status/policy`
    - `/api/public/system-status/incidents`
    - `/status`
  - sandbox keys also receive:
    - `/api/partner/sandbox/verify`
    - `/api/partner/sandbox/token-status`
    - `/api/partner/sandbox/promotion-report`

### GET /api/salutes/conversion-policy
Returns the active conversion policy settings (fixed rate, caps, enablement).

Response:
```json
{"success":true,"salutes_per_vets":1000000,"daily_cap_salutes":1000000,"monthly_cap_salutes":10000000,"conversion_enabled":false,"redemption_enabled":false,"mode":"fixed"}
```

### POST /api/salutes/convert
Placeholder for Salute -> VETS conversion. Returns `conversion_disabled` or `redemption_disabled` unless enabled.

## Transparency Exports (Public, Read-Only)

These endpoints do not require partner HMAC auth. They are intended for transparency dashboards, reporting, and external reconciliation.

### GET /transparency/audit-anomalies
Human-readable anomalies page with filters.

Query params:
- `run`: `latest` (default) or historical run token like `20260215T113203Z`
- `severity`: `bad` or `warn`
- `signature`: substring match
- `types`: substring match on anomaly types
- `min_abs_delta`: minimum absolute delta threshold
- `limit`: max rows (25-2000)

### GET /transparency/audit-anomalies/export
Downloads filtered anomalies as CSV.
Supports the same query params as `/transparency/audit-anomalies`, including `run`.

Example:
```bash
curl -sS "https://vets-coin.com/transparency/audit-anomalies/export?severity=bad&min_abs_delta=1000&limit=100" -o audit_anomalies.csv
```

### GET /transparency/audit-anomalies/export.json
Returns filtered anomalies as JSON.
Supports the same query params as `/transparency/audit-anomalies`, including `run`.

Example:
```bash
curl -sS "https://vets-coin.com/transparency/audit-anomalies/export.json?severity=bad&limit=50"
```

Response:
```json
{"success":true,"filters":{"severity":"bad","types":"","signature":"","min_abs_delta":0.0,"limit":50},"count":1,"rows":[...]}
```

### GET /transparency/audit-anomalies/diff/export
Downloads selected historical run vs latest run anomaly-type deltas as CSV.

Query params:
- `run`: required historical run token (example: `20260215T113203Z`)

Example:
```bash
curl -sS "https://vets-coin.com/transparency/audit-anomalies/diff/export?run=20260215T113203Z" -o audit_anomaly_type_diff.csv
```

If `run` is missing or resolves to `latest`, returns `400`.

CSV columns:
- `type`
- `selected`
- `latest`
- `delta`
- `pct_delta_percent`

CSV files include leading `#` metadata lines (selected run + generation metadata).

### GET /transparency/audit-anomalies/diff/export.json
Returns selected historical run vs latest run anomaly-type deltas as JSON.

Query params:
- `run`: required historical run token (example: `20260215T113203Z`)
- `include_rows`: optional (`true|false`, default false). When true, includes selected/latest row context arrays.
- `row_limit`: optional context row cap (default 100, max 250)

Example:
```bash
curl -sS "https://vets-coin.com/transparency/audit-anomalies/diff/export.json?run=20260215T113203Z"
```

Response:
```json
{"success":true,"selected_run":"20260215T113203Z","selected_label":"2026-02-15 11:32 UTC","latest_generated_at":"2026-02-16T07:00:00Z","selected_total":12,"latest_total":8,"total_delta":4,"count":3,"rows":[...]}
```

### GET /transparency/audit-anomalies/diff/compare.json
Returns anomaly-type deltas between any two run selectors.

Query params:
- `run_a`: required (`latest` or historical token like `20260215T113203Z`)
- `run_b`: required (`latest` or historical token like `20260214T100000Z`)
- `include_rows`: optional (`true|false`, default false). When true, includes run A/B row context arrays.
- `row_limit`: optional context row cap (default 100, max 250)

Example:
```bash
curl -sS "https://vets-coin.com/transparency/audit-anomalies/diff/compare.json?run_a=20260215T113203Z&run_b=latest"
```

### GET /transparency/audit-anomalies/diff/compare.csv
Downloads anomaly-type deltas between any two run selectors as CSV.

Query params:
- `run_a`: required (`latest` or historical token like `20260215T113203Z`)
- `run_b`: required (`latest` or historical token like `20260214T100000Z`)
- `include_rows`: optional (`true|false`, default false). When true, appends context row sections.
- `row_limit`: optional context row cap (default 100, max 250)

Example:
```bash
curl -sS "https://vets-coin.com/transparency/audit-anomalies/diff/compare.csv?run_a=20260215T113203Z&run_b=latest" -o audit_anomaly_compare.csv
```

CSV files include leading `#` metadata lines (run labels + generated-at).

### GET /transparency/audit-anomalies/summary.json
Returns the selected run summary payload and optional selected-vs-latest comparison metadata.

Query params:
- `run`: optional run selector (`latest` default)
- `include_top_anomalies`: optional (`true|false`, default false)
- `top_limit`: optional (default 10, max 50; only used when `include_top_anomalies=true`)

Example:
```bash
curl -sS "https://vets-coin.com/transparency/audit-anomalies/summary.json?run=20260215T113203Z"
```

### GET /transparency/audit-anomalies/runs.json
Returns available anomaly run options (latest + historical), selection state, and completeness flags.

Query params:
- `run`: optional run selector (`latest` default); invalid values return latest with `selection_warning`.

Example:
```bash
curl -sS "https://vets-coin.com/transparency/audit-anomalies/runs.json"
```

### GET /transparency/audit-anomalies/trend.json
Returns trend points used by the anomalies run-trend UI.

Query params:
- `run`: optional selected run (`latest` default)
- `metric`: optional `sigs` (default) or `rows`
- `include_status_counts`: optional (`true|false`, default false), returns aggregated `ok/warn/bad/other` counts
- supports filter params for generated `href` links:
  - `severity`, `signature`, `types`, `min_abs_delta`, `limit`

Example:
```bash
curl -sS "https://vets-coin.com/transparency/audit-anomalies/trend.json?severity=bad&limit=100"
```

### GET /transparency/audit-anomalies/alerts.json
Returns threshold-evaluated alert snapshot for a selected run (or latest).

Query params:
- `run`: optional selected run (`latest` default)
- `sigs_increase_threshold_pct`: optional percent threshold for signature spikes (default env-backed, 50)
- `rows_increase_threshold_pct`: optional percent threshold for row spikes (default env-backed, 50)
- `include_recommendations`: optional (`true|false`, default true)

Example:
```bash
curl -sS "https://vets-coin.com/transparency/audit-anomalies/alerts.json?sigs_increase_threshold_pct=50&rows_increase_threshold_pct=50"
```

Response:
```json
{"success":true,"selected_run":"latest","snapshot":{"alert_level":"warn","should_alert":true,"alerts":[...]}}
```

### GET /transparency/audit-anomalies/alerts.csv
Returns a one-row CSV snapshot for spreadsheet workflows, including:
- current alert level and boolean `should_alert`
- current/baseline run ids
- current run delta values
- configured threshold values
- emitted alert codes and messages

Example:
```bash
curl -sS "https://vets-coin.com/transparency/audit-anomalies/alerts.csv" -o audit_anomaly_alerts.csv
```

## Wallet API (App)

### GET /wallet-connect/<wallet>
Links a wallet to a user session; creates user if needed.

### GET /api/wallet-info/<wallet>
Returns live SOL + VETS balances (RPC).

### GET /api/wallet-proof/challenge?wallet=...
Returns a signed-message challenge for wallet verification (login required).

### POST /api/wallet-proof/verify
Verifies signed challenge and marks wallet as verified (login required).

### POST /api/log
Client event logger (records to event_logs).

Body:
```json
{"wallet_address":"...","event_type":"wallet_connected","details":"phantom"}
```

## Public Stats

### GET /api/public-stats
Returns token stats. Uses on-chain supply/treasury and DB fallbacks for volume.

Response:
```json
{"success":true,"stats":{"total_supply":...,"circulating_supply":...,"holders":...,"holders_cached":...,"holders_onchain_lower_bound":...,"holders_source":"cache","volume_24h":...}}
```

Notes:
- `holders` now prefers the cached holder tables when available.
- `holders_onchain_lower_bound` is retained as a best-effort RPC lower bound from largest token accounts.
- `allow_stale=1` enables cache fallback when RPC is unavailable and the cache artifact is still within `PUBLIC_STATS_CACHE_MAX_AGE_HOURS`.
- the stale-fallback artifact is refreshed hourly on the host by `vets-coin-public-stats-cache.timer`.
- when stale fallback cannot be served, degraded responses now still include a `cache` block explaining whether fallback was disabled, missing, invalid, or stale.
- live responses now also expose the same `cache` diagnostics family so callers can see cached-at timing and cache-write truth without waiting for a degraded window.
- `cache.proof_url` points to a standalone public cache-freshness/provenance proof surface.
- `cache.proof_csv_url` points to the CSV export companion for that proof surface.
- `cache.proof_page_url` points to the browser-facing companion for that proof surface.
Rate limit: `RATE_LIMIT_PUBLIC_API_PER_MIN`

### GET /api/public/public-stats-cache-freshness.json
Public proof surface for the cache artifact behind `/api/public-stats` stale fallback.

Response shape highlights:
- `freshness_status`: `ok`, `stale`, `missing`, or `invalid`
- `reason`
- `cached_at_utc`
- `age_hours`
- `max_age_hours`
- `cache_file`
- `stats_available`
- `stats_fields`
- `cached_stats`
- `rpc_circuit`
- `trust_context`

### GET /api/public/public-stats-cache-freshness.csv
CSV export companion for the public-stats cache proof surface.

Columns:
- `freshness_status`
- `reason`
- `cached_at_utc`
- `age_hours`
- `max_age_hours`
- `cache_file`
- `stats_available`
- `stats_fields`
- `rpc_circuit_is_open`
- `rpc_last_success_at_utc`
- `rpc_last_success_source`
- `rpc_provider`
- `rpc_fallback_configured`
- `generated_at_utc`

### GET /transparency/public-stats-cache-freshness
Browser-facing companion for the public-stats cache freshness/provenance proof surface.

This page renders:
- freshness status
- cache reason
- cached time
- age and max-age threshold
- cache file path
- stats-field presence
- RPC breaker provenance
- cached stats snapshot

### GET /api/public/system-status
Returns a public readiness snapshot for core API health, audit freshness, alert-monitor freshness, and automation jobs.

Response:
```json
{"success":true,"generated_at":"...","overall":"ok","checks":{"api":{},"audit_anomalies":{},"alert_monitor":{},"ops_jobs":{"jobs":[]}},"status":{"generated_at":"...","overall":"ok","checks":{"api":{},"audit_anomalies":{},"alert_monitor":{},"ops_jobs":{"jobs":[]}}}}
```

Compatibility:
- top-level `overall`, `checks`, `domains`, `maintenance`, and `policy` are available for lightweight probes and partner tooling
- the nested `status` object remains shipped for backward compatibility and mirrors the same payload family

Rate limit: `RATE_LIMIT_PUBLIC_API_PER_MIN`

Alias:
- `GET /status.json` (same payload, same rate limit)

Additive token/runtime checks now also include:
- `holder_cache`
- `holder_growth`
- `token_mint_authorities`
- `token_distribution`
- `token_snapshot_jobs`

`checks.ops_jobs.jobs[]` highlights:
- `status`
- `public_status`
- `source_status`
- `public_degrades`
- `incident_state`
- `latest_non_ok_at`
- `latest_non_ok_status`
- for wallet-paused `security_scan` warnings: `deferred_scope`, `deferred_reason`
- for `ops_daily_digest` warnings backed by the latest digest artifact: `status_reason`, `warn_reason_count`, `warn_reason_code`, `failed_rows_24h`, `warn_threshold`
- for `ops_daily_digest` runs that recovered from transient DB connectivity during artifact generation: `db_retry_recovered_last_run`, `db_retry_attempts_used`, `db_retry_retry_events`, `db_retry_sleep_seconds`, `db_retry_configured_attempts`, `db_retry_last_error`

`checks.ops_jobs_reliability` highlights:
- `window_days`
- `job_count`
- `avg_success_streak`
- `avg_mttr_minutes`
- `jobs_with_mttr_samples`
- `unresolved_failure_jobs`

`checks.token_mint_authorities` highlights:
- `status`
- `review_status`
- `expected_review_status`
- `expectation_configured`
- `mismatch`
- `reason`
- `mint_authority_disabled`
- `freeze_authority_disabled`

### GET /api/public/system-status/policy
Returns the effective public-status interpretation contract.

Response highlights:
- `policy.source`
- `policy.version`
- `policy.exclude_maintenance_default`
- `policy.checks.audit_anomalies.warn_degrades`
- `policy.checks.token_mint_authorities.expected_review_status`
- `policy.checks.token_mint_authorities.mismatch_status`
- `policy.checks.token_mint_authorities.unavailable_status`
- `policy.checks.token_mint_authorities.reason`
- `policy.jobs`

### GET /api/public/system-status/trend?limit=144
Returns rolling status history from server-side snapshot persistence (JSONL file).

Response:
```json
{"success":true,"limit":144,"count":144,"counts":{"ok":120,"warn":24,"bad":0,"other":0},"points":[...]}
```
Notes:
- `limit` optional, clamped to `10..1000`.
- History source defaults to `/home/vets-coin/logs/status_history.jsonl`.

### GET /api/public/system-status/incidents?hours=168&limit=30
Returns incident windows derived from status history points.

Response:
```json
{"success":true,"hours":168,"limit":30,"points_considered":240,"counts":{"total":2,"active":0,"resolved":2,"bad":1,"warn":1},"incidents":[...]}
```
Notes:
- `hours` optional, clamped to `1..4320` (180 days).
- `limit` optional, clamped to `1..200`.
- Incident rows include `started_at`, `resolved_at`, `duration_seconds`, `level`, and `affected_checks`.

### GET /api/public/system-status/incidents.rss?hours=168&limit=30
RSS feed companion for incident windows, intended for external subscribers and status inbox rules.

Notes:
- Uses the same `hours`, `limit`, and maintenance filters as JSON incidents endpoint.
- Feed item description includes duration + affected checks.

### GET /api/public/system-status/uptime?hours=168
Returns windowed uptime percentages for overall status and each core check.

Response:
```json
{"success":true,"allowed_hours":[24,168,720],"selected_hours":168,"point_count":240,"overall":{"ok_pct":99.1,"warn_pct":0.9,"bad_pct":0.0,"degraded_pct":0.9},"checks":{"api":{},"audit_anomalies":{},"alert_monitor":{},"ops_jobs":{}}}
```
Notes:
- `hours` supports `24`, `168`, or `720` (default `168`).
- `degraded_pct` is `warn + bad`.

### GET /api/public/release-manifest
Returns a signed release manifest containing deploy metadata, checksum entries, and migration-note snippets.

Response:
```json
{"success":true,"manifest":{"source":"public_release_manifest.v1","version":"...","deploy_utc":"...","entries":[{"name":"openapi.yaml","sha256":"...","bytes":12345}],"migration_notes":["..."],"signature_alg":"hmac-sha256","signature":"..."}}
```
Notes:
- Alias: `GET /api/public/release-manifest.json`
- Signature companion: `GET /api/public/release-manifest.sig`

### GET /api/public/data-retention
Returns the public retention matrix (data class, storage, retention window, purpose, delete path).

Response:
```json
{"success":true,"retention":{"generated_at":"...","count":11,"rows":[{"data":"Partner request logs","storage":"MySQL: partner_request_logs","retention":"90 day rolling window","purpose":"...","delete_path":"..."}]}}
```
Notes:
- HTML companion page: `GET /data-retention`

### GET /api/public/latency-percentiles?hours=24
Returns public smoke latency percentiles (`p50/p95/p99`) per endpoint.

Response:
```json
{"success":true,"latency":{"window_hours":24,"endpoint_count":20,"endpoints":[{"endpoint":"/status","sample_count":30,"error_rate_pct":0.0,"p50_ms":180.0,"p95_ms":410.0,"p99_ms":620.0}]}}
```
Notes:
- Alias: `GET /api/public/latency-percentiles.json`
- `hours` supports `24`, `168`, or `720` (default `24`).

### GET /api/public/latency-percentiles.rss?hours=24
Returns RSS outlier feed for endpoints currently at `warn`/`bad` percentile/error-rate levels.

Notes:
- Uses same `hours` window semantics as JSON percentile endpoint.
- Emits only warn/bad rows, sorted by severity + `p95`.

### GET /api/public/status-feeds.json
Returns machine-readable feed index for status incidents, anomaly alerts, changelog, and latency feeds.

Response:
```json
{"success":true,"count":6,"health_url":"https://vets-coin.com/api/public/status-feeds/health.json","feeds":[{"name":"system_status_incidents_rss","url":"https://vets-coin.com/api/public/system-status/incidents.rss","content_type":"application/rss+xml","etag":"a1b2c3d4e5f6","cache_ttl_seconds":120,"recommended_poll_seconds":300}]}
```
Notes:
- Each feed row includes subscriber tuning metadata:
  - `etag`
  - `cache_ttl_seconds`
  - `recommended_poll_seconds`
- Supports HTTP `If-None-Match` with `ETag` response header.

### GET /api/public/status-feeds/health.json
Returns per-feed last-build telemetry (freshness + generation lag).

Response:
```json
{"success":true,"count":6,"counts":{"ok":6,"warn":0,"bad":0},"status_feeds_url":"https://vets-coin.com/api/public/status-feeds.json","feeds":[{"name":"system_status_incidents_rss","last_build_utc":"2026-03-07T00:12:00Z","generation_lag_seconds":180,"freshness_status":"ok","stale_after_seconds":21600}]}
```
Notes:
- Designed for machine polling/alerting around stale feed artifacts.
- Supports HTTP `If-None-Match` with `ETag` response header.

### GET /api/public/status-feeds/health.csv
Returns spreadsheet-friendly CSV companion for feed-health telemetry.

Notes:
- Mirrors the same feed-health fields as JSON endpoint.
- Useful for SOC/operator dashboards that ingest CSV directly.

### Status Endpoint Quick Map
Use these as the default operator/partner entry points:

| Endpoint | Best use | Default cURL |
|---|---|---|
| `GET /status.json` | Alias for latest public status payload. | `curl -sS "https://vets-coin.com/status.json"` |
| `GET /api/public/system-status` | Top-level readiness snapshot. | `curl -sS "https://vets-coin.com/api/public/system-status"` |
| `GET /api/public/system-status/uptime?hours=168` | 7-day SLO/uptime summary. | `curl -sS "https://vets-coin.com/api/public/system-status/uptime?hours=168"` |
| `GET /api/public/system-status/incidents?hours=168&limit=30` | Last 7 days of incident windows. | `curl -sS "https://vets-coin.com/api/public/system-status/incidents?hours=168&limit=30"` |
| `GET /api/public/system-status/incidents.rss?hours=168&limit=30` | RSS feed for incident subscribers. | `curl -sS "https://vets-coin.com/api/public/system-status/incidents.rss?hours=168&limit=30"` |
| `GET /api/public/latency-percentiles?hours=24` | Endpoint latency percentiles from smoke telemetry. | `curl -sS "https://vets-coin.com/api/public/latency-percentiles?hours=24"` |
| `GET /api/public/latency-percentiles.rss?hours=24` | RSS feed for latency outlier subscribers. | `curl -sS "https://vets-coin.com/api/public/latency-percentiles.rss?hours=24"` |
| `GET /api/public/status-feeds.json` | Feed index with URLs/content-types for machine-readable subscribers. | `curl -sS "https://vets-coin.com/api/public/status-feeds.json"` |
| `GET /api/public/status-feeds/health.json` | Feed freshness/generation-lag telemetry for subscriber alerting. | `curl -sS "https://vets-coin.com/api/public/status-feeds/health.json"` |
| `GET /api/public/status-feeds/health.csv` | CSV companion for feed-health telemetry. | `curl -sS "https://vets-coin.com/api/public/status-feeds/health.csv"` |
| `GET /api/public/release-manifest` | Signed release artifact with checksums and deploy notes. | `curl -sS "https://vets-coin.com/api/public/release-manifest"` |
| `GET /api/public/data-retention` | Public retention policy matrix for operational datasets. | `curl -sS "https://vets-coin.com/api/public/data-retention"` |

### GET /api/transactions?limit=200
Returns recent on-chain transactions from the local index.

Notes:
- `limit` is optional; defaults to `API_TRANSACTIONS_DEFAULT_LIMIT` (200).
- Hard cap at `API_TRANSACTIONS_MAX_LIMIT` (1000).

### GET /api/transactions/latest?limit=50
Returns the most recent on-chain transactions from the local index.

Notes:
- `limit` is optional; defaults to `API_TRANSACTIONS_LATEST_DEFAULT_LIMIT` (50).
- Hard cap at `API_TRANSACTIONS_MAX_LIMIT` (1000).

## Public API Quickstart (Copy/Paste)

Base URL: `https://vets-coin.com`

### Token stats
```
curl -s https://vets-coin.com/api/public-stats
```

### Public system status
```
curl -s https://vets-coin.com/api/public/system-status
```

### Public system status alias
```
curl -s https://vets-coin.com/status.json
```

### Public system status trend
```
curl -s "https://vets-coin.com/api/public/system-status/trend?limit=288"
```

### Public system incidents
```
curl -s "https://vets-coin.com/api/public/system-status/incidents?hours=168&limit=20"
```

### Public uptime summary
```
curl -s "https://vets-coin.com/api/public/system-status/uptime?hours=168"
```

### Latest transactions (limited)
```
curl -s "https://vets-coin.com/api/transactions/latest?limit=50"
```

### Transactions (recent window)
```
curl -s "https://vets-coin.com/api/transactions?limit=200"
```

### Swap URL (Jupiter)
```
curl -s https://vets-coin.com/api/vets/swap-url
```

### Donation claim config
```
curl -s https://vets-coin.com/api/vets/donation-claim
```

### Donation webhook (signed POST)
`POST /webhook/donation` accepts JSON donation payloads. In secure mode, include:
- `X-Webhook-Timestamp`: unix epoch seconds
- `X-Webhook-Signature`: `sha256=<hex>` or raw hex of `HMAC_SHA256("{timestamp}.{raw_body}")`
- `X-Webhook-Nonce` (optional but recommended): unique delivery id for replay detection

Example (signature generation + POST):
```bash
ts="$(date +%s)"
body='{"campaign_id":"demo","amount":10,"email":"donor@example.com","wallet_address":"DemoWallet"}'
sig="$(printf '%s.%s' "$ts" "$body" | openssl dgst -sha256 -hmac "$DONATION_WEBHOOK_SECRET" -hex | awk '{print $2}')"
curl -sS -X POST "https://vets-coin.com/webhook/donation" \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Timestamp: $ts" \
  -H "X-Webhook-Signature: sha256=$sig" \
  -H "X-Webhook-Nonce: demo-$(uuidgen 2>/dev/null || echo nonce)" \
  -d "$body"
```

Auth mode controls:
- `DONATION_WEBHOOK_AUTH_MODE=off`: no signature check.
- `DONATION_WEBHOOK_AUTH_MODE=log`: signature failures are logged (`donation_webhook_auth_warn`) but request continues.
- `DONATION_WEBHOOK_AUTH_MODE=enforce`: missing/invalid/replay signatures are rejected (`401`/`409`) and logged as `donation_webhook_auth_failed`.
- `DONATION_WEBHOOK_SIG_TTL_SECONDS` controls timestamp skew/replay window (default `300`, clamped `60..3600`).

### Wallet balances (SOL + VETS)
```
curl -s https://vets-coin.com/api/wallet-info/<WALLET_ADDRESS>
```

### Client event log (optional)
```
curl -s -X POST https://vets-coin.com/api/log \
  -H "Content-Type: application/json" \
  -d '{"wallet_address":"<WALLET>","event_type":"wallet_connected","details":"phantom"}'
```

## Status Codes (common)
- 200: success
- 400: validation error
- 401: unauthorized (HMAC invalid/missing)
- 403: forbidden (admin-only)
- 409: insufficient balance (debit)
- 429: rate limited
- 503: dependency unavailable (DB/RPC)

## Admin (Session) Endpoints

These require a logged-in admin session (`session.is_admin`):
- `/admin/overview`
- `/admin/users`
- `/admin/users/<id>`
- `/admin/users/export`
- `/admin/wallets`
- `/admin/wallets/export`
- `/admin/partner-usage`
- `/admin/faucet/analytics`
- `/admin/health`
- `/admin/chain/status` (RPC status + supply)
- `/admin/chain/balance?owner=...` (token balance lookup)
- `/admin/chain/transfer-plan` (POST, returns unsigned plan)
- `/admin/helius/check` (quick Helius token-transfers sanity check)
- `/admin/backups` (JSON list of archives)
- `/admin/backups/<filename>` (download)
- `/admin/backups/ui` (UI)
- `/admin/backup/run` (POST; canonical manual backup trigger from admin overview)
- `/admin/partner-keys` (UI create/revoke)
- `/admin/salutes` (UI)
- `/admin/token/distribution` (UI)
- `/admin/token/distribution/refresh` (POST)
- `/admin/token/distribution/export` (CSV)
- `/api/salutes/ledger` (JSON)
- `/admin/audit/export` (CSV)
- `/admin/user-activity/<id>`
- `/admin/faucet/campaigns` (create/list)
- `/admin/faucet/campaigns/<id>` (edit)
- `/admin/faucet/campaigns/<id>/keys` (generate/list)
- `/admin/faucet/claims` (view)
- `/admin/faucet/claims/export` (CSV)
- `/admin/faucet/bans` (add/lift)
- `/admin/faucet/bans/export` (CSV)
- `/admin/faucet/transfer-plan` (POST, build unsigned transfer plan)
- `/admin/ops/incidents` (UI timeline)
- `/admin/ops/incidents/export` (CSV)
- `/admin/ops/incidents/export.json` (JSON)
- `/admin/ops/incidents/detail.json` (JSON row drilldown + neighbors)
- `/admin/ops/incidents/draft.json` (JSON incident draft payload for automation)
- `/admin/ops/incidents/draft.md` (Markdown incident draft generated from `draft.json` payload; supports `template=v1|minimal`)
- `/admin/ops/incidents/draft.txt` (Plain-text incident draft generated from `draft.json` payload; supports `template=v1|minimal`)
  - shared filters: `hours`, `limit`, `source`, `severity` (`warn|warning|bad|failed|fail|error`), `since`, `until`
  - `since` / `until` format: UTC `YYYY-MM-DDTHH:MM:SSZ`
- `/admin/ops/readiness/403-noise/export.json` (JSON denied/probe/noise trend windows; includes top-level `summary_by_window`; supports `compact=1` summary-only mode and `include_windows_meta=1` for compact metadata rows)
- `/admin/ops/readiness/403-noise/export.csv` (CSV denied/probe/noise trend windows with suppression metadata; supports `mode=detail|summary` and `include_points=1` in summary mode)
- `/admin/ops/alerts-monitor` (UI)
- `/admin/ops/alerts-monitor/export` (CSV)
- `/admin/ops/alerts-monitor/export.json` (JSON, includes retention + `recent_runs`)
- `/admin/ops/alerts-monitor/export.zip` (ZIP bundle with CSV + JSON + checksum manifest)
- `/admin/ops/alerts-monitor/runs/export` (CSV, `recent_runs` focused)
- `/admin/ops/alerts-monitor/export-usage.csv` (CSV, usage trend; supports `days` + `source=all|export|token`; includes `requested_by_top` metadata comment)
- `/admin/ops/alerts-monitor/export-usage.json` (JSON mirror of usage trend; supports `days` + `source=all|export|token`; includes `requested_by_top`)
- `/admin/ops/alerts-monitor/export-usage.png` (PNG sparkline; supports `days` + `source=all|export|token` + `w` + `h`; includes latest/max annotations and `X-Usage-*` headers)
- `/admin/ops/alerts-monitor/export-usage.svg` (SVG sparkline; supports `days` + `source=all|export|token` + `w` + `h`)
- `/admin/ops/security` (webhook probe/auth telemetry + contact first-response SLA dashboard)
- `/admin/ops/security/export.json` (webhook probe/auth + contact SLA snapshot JSON; includes `contact_overdue_leaders` top-3 topics by `pending_overdue_24h` for triage routing)
- `/admin/ops/security/export.csv` (webhook probe/auth + contact SLA CSV export; supports `mode=summary` to emit counts + contact summary/leaders only)
- `/admin/ops/security/trend.json` (daily webhook probe/auth event trend, `days=1..90`)
- `/admin/ops/security/contact-sla/trend.json` (daily contact intake/first-response trend with backlog delta, `days=1..90`; supports `compact=1` to omit per-topic arrays for low-bandwidth polling)
- `/admin/ops/security/contact-sla/trend.csv` (CSV export of contact SLA trend by day/topic)
- `/admin/ops/security/contact-overdue-leaders.json` (top overdue contact topics for triage routing, supports `hours` + `limit`)
- `POST /admin/ops/security/contact-first-response` (admin action to record first response for a contact intake `contact_key`)

### Admin incident drilldown example (session-auth)

Request:
```bash
curl -sS "https://vets-coin.com/admin/ops/incidents/detail.json?hours=72&limit=250&source=cron_logs&severity=bad&index=0&context=1" \
  -H "Cookie: session=YOUR_ADMIN_SESSION_COOKIE"
```

Response shape:
```json
{
  "success": true,
  "count": 12,
  "index": 0,
  "context": 1,
  "previous_index": null,
  "next_index": 1,
  "selected": {"incident_id":"...","index":0,"total":12,"occurred_at":"...","severity":"bad","source":"cron_logs","title":"...","detail":"...","occurred_at_display":"..."},
  "neighbors": [{"incident_id":"...","selected":true}, {"incident_id":"...","selected":false}]
}
```

### Alert monitor ZIP manifest example (session-auth)

Export bundle:
```bash
curl -sS "https://vets-coin.com/admin/ops/alerts-monitor/export.zip?lines=120" \
  -H "Cookie: session=YOUR_ADMIN_SESSION_COOKIE" \
  -o alerts_monitor_export_bundle.zip
```

Manifest payload (`alerts_monitor_export_manifest.json`):
```json
{
  "generated_at": "2026-02-17T04:03:00Z",
  "source": "admin_ops_alert_monitor_export_zip",
  "entries": [
    {"name":"alerts_monitor_export.csv","sha256":"...","bytes":12345},
    {"name":"alerts_monitor_export.json","sha256":"...","bytes":6789}
  ]
}
```

## Admin (Token) Endpoints

Legacy token endpoints (deprecated; admin session required):
- `/admin/download-backup` (removed; returns 410)
- `/admin/send-alert` (removed; returns 410)
- `/admin/alert` (removed; returns 410)

## Public / Other

- `/api/public/quickstart/verify-key` (public deterministic preview for partner bootstrap order)
- `/api/public-stats` (public)
- `/api/public/system-status` (public)
- `/api/public/system-status/trend` (public, `limit` query param)
- `/api/public/system-status/uptime` (public, `hours=24|168|720`)
- `/api/public/system-status/incidents` (public, `hours` + `limit` query params)
- `/api/public/system-status/incidents.rss` (public RSS companion)
- `/api/public/latency-percentiles` (public, `hours=24|168|720`)
- `/api/public/latency-percentiles.rss` (public RSS companion)
- `/api/public/status-feeds.json` (public machine-readable feed index)
- `/api/public/status-feeds/health.json` (public per-feed freshness telemetry)
- `/api/public/status-feeds/health.csv` (public CSV telemetry companion)
- `/api/public/release-manifest` + `/api/public/release-manifest.sig` (public signed release metadata)
- `/api/public/data-retention` + `/data-retention` (public retention matrix JSON + HTML)
- `/api/transactions` and `/api/transactions/latest` (public, `limit` query param)
- `/api/vets/swap-url` (public, swap link + label)
- `/api/vets/donation-claim` (public, donation-claim config + webhook URL)
- `/transparency/token-distribution` (public)
- `/transparency/token-distribution.csv` (public)
- `/api/test-db` (admin-only)
- `/health` (public)
- `/webhook/donation` (public POST, validates internally)

### GET /api/public/quickstart/verify-key

Public deterministic preview for partner bootstrap automation before the first signed request.

Query params:
- `api_key` required
- `client` optional label for the caller surface (`cli`, `browser`, etc.)

Behavior:
- this is a preview surface, not real HMAC auth proof
- returns `verification_scope=deterministic_preview_only`
- infers `key_mode` from demo-key keywords when possible:
  - `sandbox` / `test` -> `sandbox`
  - `live` / `prod` -> `live`
- otherwise falls back to a deterministic preview mode so client bootstrap tooling can still rehearse JSON parsing

Sandbox preview example:
```bash
curl -sS "https://vets-coin.com/api/public/quickstart/verify-key?api_key=sandbox_demo_key&client=cli"
```

Live preview example:
```bash
curl -sS "https://vets-coin.com/api/public/quickstart/verify-key?api_key=live_demo_key&client=cli"
```

Response fields:
- top-level `docs_links`
  - `/developers/api-guide.md`
  - `/developers/api-errors.json`
  - `/developers/getting-started-wizard.json`
  - `/developers/schema-explorer.json`
  - `/api/openapi.yaml`
  - `/developers/sdk/manifest.json`
- `result.key_mode`
- `result.mode_source`
- `result.first_signed_call`
- `result.next_step_order`
- `result.links`

Failure behavior:
- missing `api_key` returns `400 api_key_required` with recovery `links` to:
  - `/developers/getting-started-wizard.json`
  - `/developers/schema-explorer.json`
  - `/developers/api-guide.md`
  - `/developers/api-errors.json`
  - `/status`
- rate limiting returns `429 rate_limited` with:
  - `retry_after_seconds`
  - `detail.limit_per_minute`
  - `detail.retry_scope=public_quickstart_verify_key_per_ip`
  - the same recovery/docs link bundle so bootstrap tooling can recover without guessing

Preview orders:
- sandbox:
  - `/api/partner/capabilities`
  - `/api/partner/sandbox/verify`
  - `/api/partner/sandbox/token-status`
  - `/api/partner/sandbox/promotion-report`
  - `/api/partner/errors.json`
- live:
  - `/api/partner/capabilities`
  - `/api/partner/status`
  - `/api/partner/token-status`
  - `/api/partner/errors.json`

## Debug (Should be restricted in production)

- `/debug/session`
- `/debug/whoami`
- `/debug/testuser-check`

## Rate Limits (Config)

Environment variables:
- `RATE_LIMIT_PUBLIC_API_PER_MIN` (default 120) for `/api/public-stats`, `/api/public/system-status`, `/api/public/system-status/trend`, `/api/public/system-status/uptime`, `/api/public/system-status/incidents`, `/api/public/system-status/incidents.rss`, `/api/public/latency-percentiles`, `/api/public/latency-percentiles.rss`, `/api/public/status-feeds.json`, `/api/public/status-feeds/health.json`, `/api/public/status-feeds/health.csv`, `/api/public/release-manifest`, `/api/public/release-manifest.sig`, `/api/public/data-retention`, `/api/transactions`, `/api/transactions/latest`, `/api/vets/swap-url`, `/api/vets/donation-claim`, `/api/wallet-info/<wallet>`, `/api/log`
- `PUBLIC_STATUS_HISTORY_FILE` (default `/home/vets-coin/logs/status_history.jsonl`)
- `PUBLIC_STATUS_HISTORY_MAX_LINES` (default `5000`)
- `PUBLIC_STATUS_HISTORY_TRIM_BYTES` (default `2000000`)
- `RATE_LIMIT_WEBHOOK_PER_MIN` (default 60) for `/webhook/donation`
- `DONATION_WEBHOOK_AUTH_MODE` (`off|log|enforce`) for `/webhook/donation`
- `DONATION_WEBHOOK_SECRET` shared HMAC secret for donation webhook auth
- `DONATION_WEBHOOK_SIG_TTL_SECONDS` timestamp skew/replay window in seconds
- `WEBHOOK_PROBE_MAX_PER_MIN` non-POST `/webhook/*` probe threshold before temporary IP blocklist escalation
- `PARTNER_RATE_LIMIT_PER_MINUTE` (optional global cap for partner HMAC endpoints)
- `PARTNER_NONCE_REQUIRED` (default `0`; global strict mode for all partner endpoints)
- `PARTNER_IDEMPOTENCY_ENABLED` (default `1`)
- `PARTNER_IDEMPOTENCY_REQUIRED` (default `1` for mutation endpoints)
- `PARTNER_IDEMPOTENCY_TTL_SECONDS` (default `86400`)
- `PARTNER_WEBHOOK_MAX_ATTEMPTS` + `PARTNER_WEBHOOK_TIMEOUT_SECONDS` for webhook delivery
- `RATE_LIMIT_FAUCET_PER_MIN` (default 30) for `/claim-faucet`
- `API_TRANSACTIONS_DEFAULT_LIMIT` (default 200) for `/api/transactions`
- `API_TRANSACTIONS_LATEST_DEFAULT_LIMIT` (default 50) for `/api/transactions/latest`
- `API_TRANSACTIONS_MAX_LIMIT` (default 1000) cap for `/api/transactions` and `/api/transactions/latest`
