# Exact-tree market-observation adversarial probes

Use during immutable-candidate reviews of price refresh, valuation, and wealth UI changes.

## Keep the verdict bound when the staged index moves

Record `git write-tree` before inspection. If another actor restages changes during review, do not silently transfer evidence to the new index and do not abandon the requested immutable tree. Continue source inspection through `git show <tree>:<path>` or materialize that tree in an isolated temporary worktree/archive for tests. Report both the reviewed tree and the later observed index tree. Only evidence actually executed against the requested tree counts as test evidence for it.

## Economic no-op versus processing success

Provider processing counts are not economic mutation counts. A quote may be fetched and accepted while its append-only economic observation deduplicates to an existing payload.

Trace all downstream writer gates. They must use newly inserted economic observations, not provider calls, processed/updated response counts, cache coverage, fresh-unchanged observations, or all-assets-valued status.

Counterprobe:

1. Seed a held instrument and a fresh canonical price, but no valuation snapshots.
2. Run the refresh in cache-only/no-new-economic-observation mode.
3. Assert zero provider calls where promised, zero new price observations, zero valuation snapshots, and zero aggregate wealth snapshots.
4. Force a metadata-only replay with changed fetch/job metadata and the same economic payload; repeat all zero-write assertions except explicitly mutable operational job/audit status.

A batch helper that calls a valuation writer whenever coverage is complete violates the no-op contract even if that writer often deduplicates. Prove invocation absence as well as final row counts, because a legitimate fixture may have no prior valuation to deduplicate against.

## Stable source identity and correction lineage

When provider + canonical instrument + precise provider timestamp identifies one source observation, derive identity from exactly those normalized dimensions. Normalize provider case and timestamp representation before hashing: semantically equivalent UTC spellings such as `2026-08-27T10:00:00Z` and `2026-08-27T10:00:00+00:00` must resolve to the same identity. Keep provider symbol, market, price type, mapping metadata, quality classification, and payload values outside the identity. Mutable mapping fields must not split correction lineage.

Counterprobe the same provider, instrument, and instant with changed price, provider symbol, market metadata, price type, provider case, and equivalent timestamp spelling. Expected: one source identity, monotonically increasing payload versions, and each economic/metadata correction superseding the prior observation. Then replay an identical payload with changed job/fetch/retry/cache metadata and require a strict no-op. Assert both `COUNT(DISTINCT source_observation_id)=1` and the exact version/supersession chain; row-count-only checks miss split version-1 lineages.

Normalization must also reach the mutable current-price projection. If observation identity lowercases the provider but the projection lookup/upsert uses its original casing, equivalent `Mock`/`mock` writes can produce one observation lineage yet two current rows and different returned current-row IDs. Require one normalized projection row and one stable returned projection ID across equivalent provider casing.

## Transaction state and concurrency locking

A `BEGIN IMMEDIATE` guarded only by `if not conn.in_transaction` does not serialize callers that already entered a deferred transaction. Two connections can each execute `BEGIN`, read the same predecessor, and then race to upgrade their shared locks; SQLite may reject one with `OperationalError: database is locked` even though the clean-connection concurrency regression passes.

Add two parallel correction probes:

1. clean connections, proving versions `1/2/3`, exact predecessor links, no errors, and one stable current-row ID;
2. connections already inside a deferred transaction, proving the same outcome or an explicit fail-fast API contract that rejects caller-owned transactions before any read/write.

Resolve transaction ownership deliberately: acquire the write lock at an outer boundary before a deferred transaction begins, expose a transaction-aware locked writer, or reject pre-existing transactions. Do not claim the helper "defensively locks" merely because it conditionally starts `BEGIN IMMEDIATE`; inspect and test the `conn.in_transaction` branch.

## Failure atomicity across append-only observation and current projection

A serialized happy path can still violate the writer contract when one item fails between the immutable observation insert and the mutable current-row upsert. This is especially dangerous when a batch catches broad `Exception` values and continues: the failed item's transaction can remain open, and the next successful item's commit can persist an orphaned observation while the corresponding current projection is absent.

Run a deterministic savepoint/rollback counterprobe:

1. Add a temporary SQLite trigger that aborts the current-row insert/update for instrument A, after its observation insert would have run.
2. Execute A's store and catch the expected database exception.
3. Assert the connection does not retain A's pending observation outside an explicitly owned savepoint.
4. On the same connection, successfully store instrument B.
5. Re-query after B commits and require A to have neither side persisted while B has both observation and current projection.

If B's commit persists A's observation, ordinary idempotency and parallel version-allocation tests are insufficient. Wrap each isolated item in a savepoint and `ROLLBACK TO` it before continuing, or make the store own a complete transaction with rollback on every exception. Never blindly roll back a caller-owned outer transaction. Catch provider/network failures at the provider boundary; do not relabel arbitrary SQLite integrity, schema, lock, or programming failures as provider failures. The append-only observation and mutable current projection are one atomic unit unless the product contract explicitly and safely models independently durable raw observations.

## UI price-date truth

Do not label a model timeline date or dashboard `as_of` date as the price date. Wealth models commonly carry actual component provenance separately (`source_date`, quote timestamp, FX date). Probe a current model point built from stale/carried components: the UI must show authoritative source dates or call the timeline date `Modellstand`, never `Verwendeter Kursstichtag`.

Quality counters must retain a declared unit. Do not combine source categories, accounts, unknown-account counts, and instrument valuations under one label such as `Bewertungen`. Prefer backend-projected counts with explicit granularity.