Operations

FX Repair Flag Review Guide

How to review and reconcile foreign-exchange repair flags after an upgrade that changed how FX position bookings fold movement entries. Requires organization.manage (staff admin). Applies to tenants migrated through tenant migrations 000300–000303.

What a flag is

A flag marks a movement entry that the upgrade's backfill may have folded into a position baseline at the wrong time — typically a draft created before a valuation but posted after it, which the buggy path consumed before its value belonged in the baseline. The subsequent period-end revaluation then computed a phantom gain/loss and posted an FX_REVALUATION journal.

A flag is a review item, not an error and not a deletion. Some flags are false positives (the deferred-posting signature can match legitimately folded entries); the lifecycle exists so a human decides, with the correction and its rationale recorded.

Flag states: OPEN → IN_REVIEW → RESOLVED or DISMISSED.

The review workflow

All actions are available in the admin UI at Admin → FX Repair Flags (/admin/fx-repair) and via the API. The API paths (all under /api/v1/accounting/cycle/currency/fx-repair-flags):

StepCallEffect
List queueGET /?status=OPENshows flags + reason/journal/account
ClaimPOST /:id/claimOPEN → IN_REVIEW, assigns you
Inspect candidatesGET /:id/candidatesposted FX_REVALUATION journals on the position, each with its net effect on the account
ResolvePOST /:id/resolve?currency=<BASE>posts the mirrored reversal journal and closes the flag, atomically
DismissPOST /:id/dismisscloses a false positive — note required, no journal
ReleasePOST /:id/releaseIN_REVIEW → OPEN, frees a stale claim

Resolve — the guarded path

resolve requires three things and fails closed on each:

  1. currency query param must equal the position's functional base (the currency the booking was folded in — e.g. USD). The UI fills this from the tenant's business settings; the backend rejects a mismatch.
  2. reversal_of_entry_id must be a candidate journal on the flag's own position — same account, same currency, POSTED, type FX_REVALUATION. A same-amount revaluation on a different position or booked under a different base is rejected.
  3. The journal's net effect on the account must equal the flagged entry's skipped contribution (foreign net × entry-date rate). If it doesn't, the flag stays open — pick a different candidate or dismiss.

On success: a JOURNAL_REVERSAL correction posts, the flag records reviewer, note, resolved amount and both journal links — all in one transaction. The event lands in the shared audit_trail.

Ownership policy

Claims are exclusive: once claimed, only the claimant can resolve or dismiss the flag. Any reviewer can release a claim to return it to the open queue — the escape hatch for stale claims, and itself audited.

Dismiss — the false-positive path

The deferred-posting signature also matches entries that were folded legitimately. If inspection shows the flagged journal is a normal revaluation or the consumed entry truly belonged in the baseline, dismiss with a note explaining why. Nothing is posted; the flag closes as DISMISSED.

What resolution does NOT do

  • The wrongly-consumed row stays consumed — the booking already folded it; un-consuming would re-fold and double the correction.
  • The phantom journal stays POSTED with its reversal linked — history is preserved, not rewritten.
  • Nothing is auto-deleted; every transition is on the shared audit trail.

Operator drill / evidence

scripts/fx_flag_reconciliation_drill.sh runs the entire lifecycle against a live stack on a planted corrupted state: real flag → API claim → candidates → resolve → ledger net asserted → audit record confirmed → negative probes (duplicate target, wrong/missing currency) all rejected. Run it on a staging tenant before touching real production flags:

DRILL_EMAIL=... DRILL_PASSWORD=... TENANT_SCHEMA=tenant_x \
  bash scripts/fx_flag_reconciliation_drill.sh

Output lands in evidence/fx-repair-drill/. Use that same output as the evidence record when you resolve real flags on a live tenant.