# Append-only domain corrections through preview/confirm queues

Use this pattern when an existing immutable domain event must be corrected without exposing database IDs or letting the HTTP process write the domain database.

## Minimal architecture

1. Keep the generic capture-version target separate from the domain-event target. If the browser is correcting a medication/event ledger row rather than a generic capture row, carry an opaque domain target inside the domain payload; do not misuse a generic `corrects_entry_id` with a different identity namespace.
2. Reuse existing preview and generic confirm/queue routes when they already enforce authenticated exact-origin requests, one-time CSRF on confirm, bounded strict parsing, and atomic private queueing. A correction does not need a new route merely because it is a new domain mode.
3. Resolve the opaque target during preview and again inside the worker transaction. Require unique resolution, the same domain entity, supported structured provenance/version, an allowed effective status, and no existing child correction.
4. Bind the preview revision to the complete normalized payload, target business revision, related-entity revision, current plan/consumption state, occurrence time, correction reason, and idempotency key.
5. Insert a new immutable correction row referencing the target. Never update/delete the original. Combine application checks with immutable update/delete triggers and a unique correction-origin index for linear history.
6. Persist the effective target status and corrected structured fields explicitly. Readers show both the superseded original and latest effective correction using opaque references only.

## Contract evolution without breaking queued actions

When extending an in-use payload version with correction-only fields:

- Accept the old exact non-correction shape and the new exact shape, not arbitrary optional keys.
- Preserve omission while normalizing an old queued action if action/status hashes were computed from its prior shape. Adding blank fields can change the hash and break polling-token/receipt binding during deployment.
- Require the complete new field pair for correction mode; prohibit partial pairs and correction fields on other modes.
- If omission cannot be preserved, require and verify an explicit queue drain before deployment.

## Duplicate and idempotency handling

A correction is not an ordinary duplicate administration. Its target may match all unchanged structured values, so ordinary duplicate detection can falsely reject a correction that changes only time, route metadata, or notes.

- Exclude the target from duplicate queries or skip ordinary duplicate detection for correction mode.
- Use latest-target checks and the unique correction-origin constraint to prevent duplicate corrections.
- Same idempotency key plus same full payload replays to one correction, including after a lost response.
- Same key plus different action hash conflicts while pending and after processing.
- Bind status tokens to key plus action hash; conflicting content must never replace a receipt or inherit its status.
- For filesystem queues, include capture actions in pending identity by idempotency key and inspect completed receipts with no-follow, owner/mode/type/size checks.

## Reader/UI capability gating

Expose a bounded capability boolean only after the server verifies that the event is latest, structured, supported, effectively correctable, and has an unambiguous related entity. The browser must not infer eligibility from labels or source text.

The dialog displays original versus proposed values, requires a bounded reason, freezes the exact previewed payload and key, preserves both across ambiguous responses, and invalidates the preview on any edit.

## Focused adversarial tests

- Happy path appends one linked correction and leaves every original column unchanged.
- Wrong entity, legacy/unstructured source, disallowed status, ambiguous target, and superseded target fail closed.
- A concurrent child correction after preview is rejected under the worker transaction.
- Mutating target, values, time, reason, mode, or idempotency key invalidates preview.
- Same-key replay creates one correction; conflicting reuse fails pending and processed.
- Duplicate override cannot create a second correction.
- Old non-correction payload shape remains hash-compatible during deployment.
- Integrity, foreign keys, immutable triggers, and unique correction-origin constraints remain valid.
