# ADR-006: PRICE_UNMATCHED uses a frozen, source-replayed manual review ## Status Accepted ## Date 2026-08-06 ## Context The fixed price table can legitimately lack an exact normalized price key. Treating every such result as a permanent failure makes staff re-upload unchanged XML after a separate price decision, while accepting edited spreadsheets or mutable price-table changes would create a second intake path and weaken provenance. ## Decision Only a non-empty error set entirely composed of `PRICE_UNMATCHED` becomes `review_required`. ARR stores one privacy-minimized review case per run, grouped missing keys, revisioned actor/price history and candidate comparisons. It creates no Finance version, daily download, rejected Finance version, `arr.processing_failed`, or monthly event. After every key has a non-negative integer value, ARR freezes canonical JSON bound to the job, case, original XML SHA-256, business date, processor version, rule hash and complete issue set. The operator/API surface uses integer text (including explicit `0`); ARR normalizes that value to exact `numeric(18,2)` and canonical `.00` manifest text before replay. Finalization re-materializes the registered source XML and replays it with the fixed price table plus that manifest. Independent validation must accept the same manifest before one atomic Finance-version activation, case completion and `arr.daily_version_committed` event. ## Consequences - `POST /api/jobs` remains XML-only; there is no Excel draft/download/upload-back workflow. - Staff can change only a reviewed missing key's non-negative integer price, including explicit `0`; fixed pricing rules and XML fields remain immutable. - Review mutations require session authentication, CSRF and optimistic revision checks. Frozen item/manifest facts are immutable and permanently audited without free-form notes or guest detail. - Infrastructure failures after freeze become `generation_failed` and may retry with the identical manifest. A deterministic final replay failure closes the review and run as failed. Processor/rule identity drift requires cancel then fresh upload. - The legacy `direct_mcp` path uses an isolated v3 success/failure projection: it rejects manual overrides and maps price misses to normal v3 failures, so it cannot gain review behavior from the active v4 processor. ## Related - `database/017_daily_price_review.sql` - `database/018_daily_review_manual_override_artifact.sql` - `arr_web/programmatic.py` - `arr_ingestion/postgres.py` - `arr-opera-daily-ingest/scripts/process_daily.py`