# PostFinance baseline, mapping, and historical valuation activation contract

Use for broker-statement baseline activation where source holdings, canonical ISIN identities, provider valuation lines, historical prices, and FX must become production state without inventing history.

## 1. Historical provider contracts

### Frankfurter FX
- Send a stable, transparent product `User-Agent`; never hard-code a rate.
- Query the requested business date and direction explicitly (for example EUR→CHF).
- Parse the actual V2 response shape and verify the returned response date equals the candidate date before accepting it.
- Preserve the existing bounded previous-business-day search. Persist the accepted candidate date as `rate_date`, not the request timestamp.
- A mocked regression must assert header, base/quote direction, request date, returned date, Decimal rate, and persisted date. A temporary/in-memory live probe may verify availability without mutating production.

### Historical equity prices
- If `requested_as_of` is supplied, use only the historical endpoint. Never satisfy it from a current profile quote.
- Select only rows with `price_date <= requested_as_of`; reject future rows before persistence.
- An exact row wins. Otherwise use only the latest prior eligible trading row inside the existing bounded window.
- If no eligible row exists or the endpoint is restricted, return/persist no fabricated quote and emit `price_missing` (optionally accompanied by a diagnostic rejection code).
- Freshness must not map a future date to age zero. Test exact, previous trading day, future-only payload, empty/restricted history, and idempotent rerun.

## 2. Canonical identity and valuation-line separation

- Canonical identity is normalized uppercase ISIN. Enforce uniqueness both in application queries (`UPPER(isin)`) and with an expression unique index on `UPPER(isin)` for non-empty values.
- Store broker source symbol, broker source venue, and source currency separately from provider symbol, provider venue, and valuation currency.
- If the statement does not evidence a venue, persist `source_venue=unknown`; an LSE/FMP valuation line is not evidence of the broker execution venue.
- Never create a second instrument for a case-variant ISIN. Migration preflight must reject existing case-insensitive duplicates.
- Mapping activation may update identity/provider metadata only; it must not touch quantities, cash, transactions, prices, or cost basis.

## 3. Atomic mapping batches

- Apply the approved mapping set in one immediate transaction.
- A top-level `commit=False` is insufficient unless every nested helper propagates it. Quality/alert helpers are common hidden commit points.
- Test atomicity directly: begin transaction, apply one mapping with `commit=False`, roll back, and assert no mapping, metadata, alert, or audit row survives.
- Also inject a failure after an early mapping in a multi-item batch and prove all earlier writes roll back.
- Audit each mapping with ISIN, source symbol, source venue, valuation symbol, valuation currency, and provider; add one batch-level audit summary.

## 4. Baseline supersession gates

A manual placeholder may be superseded only when all are true:
- same broker account;
- same canonical instrument/ISIN;
- exact quantity;
- exactly one active initial-snapshot placeholder;
- manual/unlineaged source semantics;
- no external transaction ID, row hash, broker lineage, non-zero gross amount, explicit price, or other evidenced historical cost basis.

If an exact-quantity manual placeholder is attached to another account, block and report an account conflict; do not silently treat it as the same broker baseline.

Implementation rules:
- preserve the original row;
- mark it superseded/voided with an explicit reason and source-record reference;
- insert the source-lineaged baseline row with a correction/supersession link;
- never synthesize a sale, negative counter-booking, historical buy, dividend, or cost basis;
- statement market price/value is baseline valuation, not historical purchase price;
- replay of the same confirmation is idempotent.

## 5. Cash and document-total reconciliation

Broker statements may display rounded FX rates while calculating currency totals with higher internal precision. Therefore multiplying displayed cash amounts by displayed rates may not reproduce the document aggregate.

Persist and report separately:
- each original cash component label, currency, and amount;
- displayed FX rate and statement date;
- unrounded multiplication result and rounded display result;
- source-reported per-currency account total where present;
- row-level position sum;
- source-reported aggregate cash, positions, and document total;
- exact deltas and a textual derivation.

The source-reported aggregate may be the authoritative additional CHF view, but never alter an original component or create a balancing/runding item. If the difference cannot be explained by documented display/internal-rate rounding, stop confirmation.

## 6. Production activation gates

Before the first productive write: stop the scheduler, capture table/digest sentinels, create a SQLite-consistent backup, verify SHA-256 and `integrity_check=ok`, and rehearse migration plus activation on an independent copy.

After deployment:
1. apply only the explicitly approved mappings;
2. regenerate the real deterministic preview;
3. stop on any supersession/account/cost-basis/cash gate failure;
4. confirm once only when every gate passes;
5. replay confirmation for idempotency;
6. run the historical valuation date twice only after baseline success;
7. compare transaction, cost-basis, cash, bank, budget, GET-side-effect, and row-count sentinels;
8. restore the scheduler only after the controlled run passes.

A policy/benchmark draft remains inactive until separately confirmed. If TrueWealth/Crypto or price/FX coverage is incomplete, report `coverage_incomplete` and suppress global policy breaches and ADD/HOLD/TRIM actions.

## 7. Versioned policy-draft repository format

When repository safety rules prohibit policy drafts in JSON locations intended for production/export data, convert the versioned draft to genuine YAML rather than renaming the extension:

- load the published JSON object and serialize it as YAML with a safe, project-compatible parser;
- reload with the parser's safe-load API and assert deep semantic equality against the original object;
- preserve string-valued percentages, nulls, booleans, status fields, effective dates, restrictions, and benchmark metadata exactly;
- assert both the declared allocation total and the sum of allocation targets equal exactly 100;
- keep `inactive_pending_explicit_confirmation` and do not introduce a policy- or benchmark-confirmation field;
- update every tracked reference from `.json` to `.yaml` and prove no stale reference remains;
- do not weaken the repository scanner or add a path exception merely to retain the JSON file.

Treat this as a documentation/policy-draft representation change only. It must not activate mappings, benchmarks, policies, imports, or production data.

## 8. Account-conflict decision boundary

A technically green branch may be merged while a real baseline confirmation remains blocked by a cross-account placeholder conflict, provided deployment and every production write remain stopped. Separate these states explicitly:

1. **Technical merge readiness:** code review fixes, repository safety, full CI, inactive policy/benchmark, and final diff are green.
2. **Production activation readiness:** account identity, placeholder lineage, supersession eligibility, backup, migration rehearsal, and explicit owner decision are complete.

For an unresolved same-quantity position on another account, build a read-only decision packet after the technical merge. Open SQLite with `file:<db>?mode=ro` and `PRAGMA query_only=ON`, inspect the original broker document directly, and emit masked identifiers only. Include masked account identity/name, account and platform types, canonical instrument/ISIN, quantity, creation path/time, source and transaction lineage, external-ID/row-hash/source-reference presence, cost-basis evidence status, and active/inactive/placeholder classification for both manual and statement positions. Distinguish source-reported average-cost snapshot metadata from evidenced historical transaction cost basis. Test three possibilities without executing any:

- erroneous manual account assignment, allowed only with positive evidence that the placeholder belongs to the same broker account;
- independent holdings on different real accounts, where equal quantity alone is not duplicate evidence and both positions remain;
- legacy aliases for one real account, which requires explicit audited account reconciliation before supersession.

Quantify resulting position counts and aggregate instrument quantity under each variant. Rank evidence as: broker/source lineage or owner-confirmed provenance; explicit account-alias mapping/audit; platform/account labels; then matching instrument, quantity, and timing. Exact quantity and temporal proximity are corroboration, not proof. If only circumstantial evidence exists, recommend the conservative account-separated interpretation and state what owner confirmation would make same-account supersession admissible. Never alter the account, loosen the gate, partially confirm another instrument, or manufacture a transaction to make activation pass.

## 9. Candidate-bound owner-confirmed cross-account exception

When the owner positively confirms that one specific manual placeholder was assigned to the wrong account and represents the same broker holding, do not relax the ordinary same-account gate globally. Model a narrow resolution object that binds:

- exact manual candidate/transaction ID;
- source account and authoritative target account;
- canonical instrument/ISIN;
- exact quantity;
- owner-confirmation timestamp and actor;
- a stable reason code and human-readable rationale.

Apply these rules:

1. Include the complete resolution object in both preview and confirm canonical payload hashing. Confirm must reject any changed, omitted, broadened, future-dated, or stale decision.
2. Require exactly one eligible cross-account candidate. Multiple matches remain a hard ambiguity block.
3. Reuse every same-account safety invariant: placeholder-only source semantics, no external lineage/row hash, no evidenced cost basis, no extra active history, and no other source-backed transaction.
4. Preserve the original transaction and its account ID. Mark it auditably voided/superseded and point to the authoritative baseline; never rewrite the account, delete history, or synthesize a sale/counter-booking.
5. Keep unrelated instruments on the ordinary same-account path. One owner decision is not a general cross-account authorization.
6. After confirm, assert the exact aggregate active quantity, original-row retention, void reason, target baseline quantity, and unchanged unrelated account/history fields. Replay immediately and require the same batch ID with `idempotent=true`.

## 10. Productive frontend artifact and responsive acceptance gate

A green frontend build in a development worktree does not prove the production preview service is serving it. Before final sign-off:

1. Inspect the frontend service `WorkingDirectory` and `ExecStart`. If it runs `vite preview` or serves `dist`, run `npm ci` **and the production build in the deployed worktree** before restart. Restarting after dependency installation alone can leave a stale `dist` live.
2. Verify a known-new static asset or hashed bundle directly after restart. A favicon/version sentinel is useful for distinguishing a fresh artifact from stale UI.
3. Drive the production URL with a real browser at exact laptop, iPad, and mobile viewports. Assert no page-level horizontal overflow, no console errors, expected position/data-quality counts, and visible critical instrument ticker/quantity evidence.
4. Wide tablet tables may use an intentional internal `overflow-x-auto` scroller, but must not clip columns with `overflow-hidden` or widen the document. Add `min-w-0` to grid/flex ancestors and bound nowrap KPI values.
5. Mobile cards used as acceptance evidence should expose ticker and quantity directly rather than hiding them only in a detail drawer.
6. Capture and visually inspect screenshots; DOM assertions alone can miss clipped KPI text, unusable columns, and stale bundles.
7. Treat every UAT finding as a release gate: fix it, rerun frontend tests/typecheck/build, merge through green CI, redeploy, rebuild, restart, and repeat the same browser matrix before restoring the scheduler.
