## Outcome **Audit result:** the current V2 path cannot create corrections, but most required security machinery already exists. The smallest safe extension needs **no database migration and no new HTTP route**: reuse the existing append-only correction columns/triggers, medication preview route, generic capture-confirm route, worker transaction, and opaque event references. ### Minimal exact changes - **`medication_contract.py`** - Extend `_validate_action_data_v2()` around **lines 613–677**: - permit `mode="correction"`; - add paired fields `correction_target_ref` and `correction_reason`; - require both only for correction mode; prohibit them otherwise; - require no `planned_event_ref` for corrections; - retain compatibility with already-queued V2 payloads lacking the two new fields. - Extend `resolve_action_preview()` around **lines 379–477**: - resolve the correction target using the existing `resolve_event()`; - require a latest, structured V2 event whose effective status is `administered`; - require the same medication relation; - use the target’s actual structured quantity/form/strength/route as the correction baseline; - verify `preset_revision` against `public_action_context_token(key, prescription, None, target)`. - Existing `action_preview_revision()` at **lines 305–337** already binds: - complete normalized payload; - target revision/current state; - prescription and plan state; - occurrence time; - correction fields; - idempotency key. - Existing latest-revision rejection at **lines 455–459** should remain authoritative. - **`capture_contract.py`** - **No production logic change required.** - `validate_capture_payload()` at **lines 147–404** already delegates V2 data validation and binds the normalized medication payload. - Keep top-level `corrects_entry_id=None`: that field targets `capture_entries`, not medication-event identities. The medication correction relation belongs in `data.correction_target_ref`. - **`health_dashboard_action_worker.py`** - In `apply_capture_action()` around **lines 1046–1096**: - do not run ordinary duplicate-administration detection for correction mode—the target itself can otherwise be mistaken for a duplicate; - continue recomputing preview state inside `BEGIN IMMEDIATE`. - Extend the V2 insert around **lines 1249–1273**: - insert `event_type='corrected'`; - set `corrects_event_id`, `corrected_target_status='administered'`, and required `correction_reason`; - persist corrected structured values in `actual_quantity_value`, `actual_dosage_form`, and `actual_strength`; - retain the existing V2 source marker and action hash as `business_revision`. - Do not update or delete the original. Existing schema triggers and the unique correction-origin index enforce immutability and linear history. - **`read_api.py`** - Extend `_record_medications()` around **lines 3513–3712**: - expose correction capability only for a latest, structured V2 event with effective status `administered` and an unambiguous prescription; - reuse `correction_preview_revision` at **line 3698** as the target-baseline token; - expose a bounded boolean such as `v2_correction_allowed`; do not expose DB IDs or revisions. - **`health_dashboard_server.py`** - Existing routes are sufficient: - preview: `/health-actions/medication-preview`, **lines 1414–1438**; - confirm/queue: `/health-actions/capture`, **lines 1439–1448**. - Extend `_medication_preview_error()` around **lines 1216–1261** with curated correction-target/reason/stale-target errors. - Harden `_pending_action_identity()` / `write_action_payload()` around **lines 1082–1161**: - identify `capture_entry` requests by idempotency key; - same key + same payload returns/reuses the pending or completed action; - same key + different action hash returns `409`; - securely inspect the existing capture receipt hash so conflicting content cannot replace a prior receipt or inherit its status. - **`dashboard-v5-record.js`** - Extend `administrationCaptureDialog()` around **lines 816–870**: - support correction mode with the selected event as baseline; - show original versus corrected structured values and require a bounded reason; - include target reference and target token in the frozen V2 payload; - retain the same idempotency key and frozen payload across lost-response retries. - In medication rendering around **lines 873–886**, show “Korrigieren” only when `v2_correction_allowed` is true. - Do not route V2 corrections through the legacy V1 dialog at **lines 755–814**. ### Security invariants 1. Original structured event is never updated or deleted. 2. Every correction is a new immutable row linked to the latest revision. 3. Target must be opaque, same-medication, structured V2, and effectively administered. 4. Preview binds target state, full payload, occurrence time, reason, and idempotency key. 5. Worker re-resolves all state under one immediate transaction. 6. Same-key replay is exactly-once; conflicting reuse is rejected before receipt replacement. 7. Public responses expose only opaque references, booleans, bounded codes, and curated messages. 8. Generic capture IDs and database IDs never cross the browser boundary. ### Required tests - Successful V2 correction preserves the original row exactly and appends one linked correction. - Corrected structured quantity/form/strength/route and reason round-trip through read API/report. - Reject legacy, unstructured, planned, missed, wrong-medication, ambiguous, or superseded targets. - Concurrent correction after preview is rejected as stale. - Mutation of target, reason, values, time, mode, or idempotency key invalidates the preview. - Lost confirm response plus same-key replay creates exactly one correction. - Same key with different payload conflicts while pending and after processing. - Duplicate override cannot create a second correction. - Existing historical/planned V2 captures and already-queued old-shape V2 payloads remain valid. - Database integrity, foreign keys, immutable triggers, and unique correction-origin index remain valid. ## Verification - Focused existing medication suites: **21 passed**. - Base remained exactly at the requested commit. - Final Git state clean; `git diff --check` clean. - **Files created or modified:** none. - **Issues encountered:** none; read-only audit only.