# Market-provider and valuation-collection audit

Use this checklist when reviewing bulk equity/ETF quote refresh, historical EOD retrieval, or a daily portfolio valuation collector for completeness, provider safety, and resumability.

## Trace every collection entry point

Inventory API routes, CLI commands, scheduled jobs, dashboard actions, and direct service calls separately. For each path record:

- how eligible instruments are selected;
- whether selection is holdings-aware or scans the whole mapped catalog;
- default and maximum limits;
- ordering, offset/cursor, `has_more`, and repeated-request behavior;
- whether provider calls are current, historical, or chart-only;
- writes and metadata side effects;
- retry, pacing, checkpoint, and terminal-run behavior.

Do not conclude that the full portfolio is covered merely because an orchestrator has no explicit limit. A UI/API path may impose a smaller default, and catalog rows ahead of held instruments may consume the entire batch.

## Deterministic completeness probe

Seed more eligible instruments than the default batch size (for example 22 against a default of 10), invoke the exact production-facing batch contract, and report:

- eligible count;
- response `total`/processed count;
- provider-call count;
- stored quote count;
- identities selected on a repeated request.

A bounded endpoint without offset/cursor that repeats the same ordered first page is not pagination and cannot complete the collection by retrying.

## Structured provider failures

Trace transport errors through provider client → quote/result DTO → batch response → run reason codes → metadata writes. Preserve categories rather than collapsing them into `missing` or parsing error strings:

- `no_data`;
- `configuration` / key missing;
- `auth`;
- `entitlement` / endpoint restricted;
- `rate_limited`;
- `network` / timeout;
- provider 5xx;
- invalid payload;
- unsupported symbol/pair;
- unsupported historical capability.

Declared exception classes that are never raised/caught do not constitute classification. Verify batch warnings/errors and persisted reason codes retain the category without exposing secrets or raw provider payloads.

## Historical capability and date integrity

Require explicit provider capabilities such as `supports_latest`, `supports_historical_eod`, supported markets/currencies, pacing, and batch limits. A shared `get_price(symbol, price_date=...)` signature is unsafe when some implementations silently ignore `price_date`.

For every historical path, independently prove:

- the endpoint is historical rather than profile/latest/previous-close;
- rows after the requested cutoff are rejected;
- prior-trading-day fallback is bounded and explicit;
- actual source date/timestamp is persisted;
- `source_timestamp <= requested_cutoff` is checked before storage;
- current-only fallback providers are excluded;
- malformed timestamps fail closed rather than inheriting the requested date.

Run a synthetic current-as-history counterexample: return a valid current quote while requesting an earlier date and inspect both `price_date` and `price_timestamp`. Never store the requested date merely because it was passed to the provider.

Keep intraday/chart history separate from valuation-grade historical EOD capability.

## `suspected_inactive` and other metadata side effects

A missing quote is not evidence that an instrument is inactive. Probe key-missing, auth, entitlement, 429, network, timeout, provider 5xx, invalid payload, and genuine no-data separately, then compare instrument metadata and audit rows before/after.

Do not automatically set `suspected_inactive` for transient/provider failures. Prefer a review alert for genuine repeated `no_data`, with manual confirmation or a documented multi-date evidence threshold. Verify that a later successful quote resolves any review state; permanent one-way mutation after one miss is a release blocker.

Distinguish paths: an API quote service, CLI bulk refresher, and daily valuation orchestrator may have different side effects despite sharing providers.

## Retry, pacing, and real resume

Sequential iteration alone is not pacing. Require bounded retry/backoff, `Retry-After` handling for 429, an injectable sleeper for deterministic tests, and a documented minimum request interval where needed.

For process-level resume, aggregate run counters are insufficient. Persist per-run/per-instrument stage state, attempt count, structured error category, and retry eligibility/next time. Test interruption after several successes, restart, and prove completed items are not fetched or repointed again.

Audit terminal semantics carefully:

- treating `partial` as idempotently complete prevents recovery from transient misses;
- restarting `running` from item zero is replay, not cursor resume;
- immutable final snapshots may require a new linked attempt rather than mutation;
- uniqueness on `(source_key, as_of, fingerprint)` can accidentally prohibit a corrective attempt.

## Minimal safe implementation order

1. Introduce structured provider outcomes and capability declarations.
2. Centralize cutoff/source-date validation before any price write.
3. Remove provider-failure-driven instrument-status mutation.
4. Make eligibility holdings-aware and expose complete pagination semantics, or process all bounded holdings.
5. Add sequential pacing and categorized bounded retries.
6. Add per-item checkpoints if restart-safe resume is a requirement.
7. Add deterministic probes for over-limit completeness, repeated-page identity, current-as-history, transient-failure side effects, and interrupted resume.

## Report shape

Return concrete findings ordered by severity. Include exact current-file line references, affected entry points, violated invariant, and an observed or deterministic failure mode. Separate existing safe paths from unsafe siblings. End with the smallest implementation seams and the verification commands/probe outputs. Confirm the repository tree remained unchanged for a read-only audit.