# Quote refresh and non-persisting provider dry runs

Use this audit for market/price refresh features where normal rendering must remain cache-only and production UAT must call providers without mutating financial state.

## Separate the user actions

Trace frontend helper → component handler → router → service → provider → persistence for each action:

1. **View reload:** GET/local reads only; prove zero provider calls.
2. **Refresh all:** server-resolved current target (`price_date=null`) and `only_missing=false`.
3. **Refresh missing:** same target with `only_missing=true`.
4. **Single refresh:** only the selected instrument; do not chain a batch or reuse a stale summary `as_of` date.

Do not infer semantics from button labels.

## Preserve four dates

Keep these contract fields distinct:

- requested target date;
- exchange-specific latest completed effective session;
- accepted actual provider date;
- final common data date, normally the minimum eligible actual date across all required positions, or null when complete common coverage is unavailable.

Weekend adjustment alone is insufficient. Probe exchange holidays, timezones, and market-open requests; never label an in-progress quote as a final close.

## Position-first coverage, call-level deduplication

Start coverage from active held positions, including unmapped and missing-symbol rows. Deduplicate provider calls by instrument/mapping, then project the result back to every position.

Reject these false-coverage patterns:

- selecting only mapped instruments makes missing symbols vanish from totals;
- unheld catalog instruments enter portfolio coverage;
- duplicate holdings collapse into one reported position;
- request limits silently truncate work;
- cache checks return only booleans and lose actual-date/provenance evidence.

Missing mapping/symbol rows should remain visible zero-call results.

## Honest typed outcomes

Use typed item results with position/instrument identity, mapped provider/symbol, requested/effective/actual dates, status, attempts, sanitized errors, cache/persistence result, and dry-run state. Composite providers should retain a sanitized attempt trace instead of only the final provider.

Separate `updated` (persisted), `would_update` (dry-run success), `cached`, `missing`, `failed`, and `provider_calls`. A non-dry-run action with `updated=0` needs an explicit warning; an already-complete missing-only run is an honest `noop`.

## Dedicated production dry-run boundary

Prefer a dedicated `POST .../dry-run` route whose router forces `dry_run=true`. Avoid parsing/replaying request bodies in security middleware just to determine authorization. Under fail-closed production policy, permit the route only through a narrow explicit boundary such as loopback-only; normal refresh remains blocked remotely.

A provider dry run may consume quota, but must skip:

- price/quote and chart/cache upserts;
- commits;
- alerts and audit events;
- analysis/run/valuation snapshots;
- instrument status or metadata changes.

Keep `updated=0`; report fresh observations as `would_update`. Do not present DB-derived `valued`/`complete` from unchanged persisted state as if they were dry-run results.

## Deterministic verification

Add tests proving:

1. GET rendering and drawer opening call no provider.
2. Refresh-all ignores valid cache for provider-call selection.
3. Missing-only skips valid cache but reports missing mappings/symbols.
4. Single refresh does not invoke batch or use stale summary dates.
5. Weekend, holiday, and market-open cases preserve all date meanings.
6. Rate-limit retries expose attempts without changing instrument status.
7. Shared instruments make one provider call and multiple position results.
8. Partial coverage has honest denominators/errors and null common date.
9. `updated=0` is explicit.
10. Dry run calls providers while before/after fingerprints or counts remain stable for prices, caches, alerts, audits, analysis runs, and valuations.
11. Security tests permit only the intended local dry-run path and keep normal refresh blocked.

Frontend tests should assert exact request bodies for all/missing/single actions, prove reload is GET-only, and render zero-update, partial-error, and common-date results.
