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):
| Step | Call | Effect |
|---|---|---|
| List queue | GET /?status=OPEN | shows flags + reason/journal/account |
| Claim | POST /:id/claim | OPEN → IN_REVIEW, assigns you |
| Inspect candidates | GET /:id/candidates | posted FX_REVALUATION journals on the position, each with its net effect on the account |
| Resolve | POST /:id/resolve?currency=<BASE> | posts the mirrored reversal journal and closes the flag, atomically |
| Dismiss | POST /:id/dismiss | closes a false positive — note required, no journal |
| Release | POST /:id/release | IN_REVIEW → OPEN, frees a stale claim |
Resolve — the guarded path
resolve requires three things and fails closed on each:
currencyquery 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.reversal_of_entry_idmust be a candidate journal on the flag's own position — same account, same currency, POSTED, typeFX_REVALUATION. A same-amount revaluation on a different position or booked under a different base is rejected.- 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.