# Scheduled canonical valuation architecture audit

Use this reference when inventorying or extending FinanceManager market/valuation automation before a sprint.

For code-grounded implementation maps that separate provider control totals from additive component valuations, repair immutable activity semantics in readers, or add managed-total external-flow/nullflow confirmation, continue with `references/provider-component-valuation-cashflow-activation-map.md`.

## Read-only inventory sequence

1. Establish repository branch/SHA and require a clean worktree.
2. Locate deployment units first (`deploy/systemd`, cron references, scheduler/worker dependencies), then inspect installed user units and compare their checksums with repository copies.
3. Trace the installed unit's `ExecStart` through CLI dispatch into the service function.
4. Build a write-set from SQL and helper calls: run ledger, prices, FX, canonical valuations, analysis/benchmark snapshots, alerts, and audit events.
5. Map consumers separately: current-value UI, historical performance engine, and source-specific cockpit logic may consume different canonical tables.
6. Check runtime status and bounded journal history read-only. Do not trigger a production run merely to verify an inventory.
7. Verify the repository is still clean.

## FinanceManager architecture pattern

The current bounded pattern is one CLI orchestrator behind one user-systemd oneshot/timer, not an in-process worker framework. `run-daily-market-valuation` owns locking, provider calls, FX, immutable valuation/analysis snapshots, run status, and completion audit. A user-triggered quote refresh may call the same orchestrator after quote coverage is complete.

Canonical sources remain intentionally separate:

- equities/ETFs: audited market run and `portfolio_valuation_snapshots`;
- managed portfolio totals: confirmed `account_value_snapshots`;
- cash: confirmed/reconciled `cash_account_snapshots`;
- crypto current values: crypto prices/positions, with historical portfolio valuations required for performance;
- performance: explicit account scope, valuation boundaries, and cashflow coverage.

Therefore, a successful equity market run is not equivalent to a complete household or investment-portfolio valuation.

## Schema-first activation review

Before proposing a migration, map every sprint requirement to existing canonical tables, version/supersession fields, provenance, quality reasons, immutable ingestion batches/items, scope classification, cash-flow coverage, and FX storage. Treat missing request fields, source adapters, routes, scheduler wiring, or provider-to-canonical promotion as activation gaps—not schema gaps. Add schema only when required configuration must be durable/queryable and cannot be derived safely.

Trace provider/parser → Preview → Confirm → canonical persistence → pure performance loader/engine → API → cockpit consumers. Keep provider ingestion, canonical persisted inputs, and return calculation separate. Prefer a strict source adapter that reuses shared append-only versioning, audit, and confirmation idempotency over provider-specific performance logic.

For tracking modes, do not reuse a cash-only `balance_mode` as a portfolio-performance mode. Derive modes such as ledger-revalued, provider-total, or baseline-only from existing role/source metadata only when deterministic; otherwise persist a validated explicit mode. Ensure coverage APIs accept the same period/account scope as performance APIs when the underlying coverage service is period-aware.

## Extension review checklist

Before broadening the job, verify:

- Selection reads only active, confirmed, non-voided canonical records; staging/preview rows are excluded.
- Every source applies `source_as_of <= requested_as_of`; historical runs never use the latest row without an as-of predicate.
- Source calendars are explicit: exchange business-day semantics must not silently become 24/7 crypto semantics.
- The idempotency fingerprint includes every material input: positions/quantities, selected cash or managed-total snapshot IDs/versions, mappings, policy/benchmark choice, and requested/effective dates.
- Complete replay returns the prior result; partial/failed replay is safely resumable without colliding with immutable snapshot IDs or triggers.
- Exceptions finalize the run as `failed` with bounded reason codes and audit provenance; no run remains `running` indefinitely.
- Operational status distinguishes complete, partial, unavailable, and failed. A scheduler exit code must reflect the intended alerting contract.
- GET/read projections remain write-free.
- User-supplied balances and historical files stay Preview → Confirm → Audit; automation only consumes confirmed results.
- Prefer extending the existing orchestrator and timer over creating a parallel scheduler unless isolation, cadence, or failure-domain requirements genuinely differ.

## High-value pitfalls

- A fingerprint containing only positions and policy can incorrectly replay after cash, mapping, or managed-total inputs change.
- “Latest cash” without an as-of cutoff can leak future evidence into backfills.
- Deterministic run IDs plus immutable `INSERT OR REPLACE` outputs can make partially materialized retries unsafe; test partial-after-some-writes, not only all-missing retries.
- Running migrations inside every scheduled valuation couples data production to unrelated schema compatibility work. Treat migration readiness as an explicit deployment prerequisite and make job failure finalization robust.
- Weekday scheduling is sufficient for exchange closes but not for 24/7 canonical crypto valuations. If one timer remains, source stages need distinct effective-date rules.

## Source-specific activation and transaction-boundary review

When one timer/CLI dispatches several independently activated valuation sources, a global enable flag is not a sufficient source boundary. Trace both gates separately:

1. global scheduler/write activation;
2. an explicit scheduled-source allowlist or source-specific activation gate.

A prior source-activation audit must not silently make that source eligible when a later release intends to schedule only one source. Prefer dispatching only allowlisted workers; do not rely on each unwanted worker returning `not_activated`. Dry-run must cover the complete orchestration path and prove zero writes to prices, alerts, run rows, snapshots, and audit rows—not merely call a lower-level quote refresher with `dry_run=True`.

Map commit ownership explicitly. Helper-level commits inside price/FX persistence defeat outer rollback and source isolation. For each source, identify every `commit()` between provider fetch, `running` run creation, canonical input persistence, valuation snapshot, completion audit, and final status. Either keep the source apply phase atomic under one transaction or implement a deliberate resumable state machine that always finalizes exceptions as `failed`; never assume an outer rollback can undo committed helper writes.

## Canonical provider identity and effective-day readers

Provider provenance requires more than a human-readable provider label. Verify a stable canonical provider key, provider-specific asset/symbol identity, quote currency, provider timestamp, fetch timestamp, requested as-of, and selected input row ID. Validate the returned provider identity against the canonical asset mapping before promotion. If the provider returns the base currency directly, record explicit identity/no-FX provenance rather than leaving `fx_rate_to_base=1` unexplained.

Append-only corrections may legitimately create several physical snapshot versions for one account/day. Therefore, “one daily value” means one effective row selected consistently by every consumer. Inventory all performance, coverage, setup-card, cockpit, and export queries—not only the main performance loader. A query that groups and sums raw `portfolio_valuation_snapshots` by day can double-count superseded versions even when another reader correctly ranks `snapshot_version DESC`. Prefer one shared canonical effective-snapshot reader with explicit source precedence and day canonicalization.

## Verification scenarios

At minimum, use synthetic tests for:

1. complete replay with no additional writes;
2. same-day input change invalidating the fingerprint;
3. partial run after some snapshots were written, followed by successful retry;
4. provider/FX exception finalizing a failed run;
5. historical valuation rejecting a later cash/managed-total snapshot;
6. weekend exchange valuation versus same-day crypto valuation;
7. read-only API/cockpit projections producing zero writes;
8. concurrent invocation rejected by the lock.
