# D30-F05-A02 — Correcting and Reversing Entry Algorithm

An append-only ledger needs a way to repair a balanced but wrong instruction. This package implements **one exact full reversal plus one independently validated replacement, committed together with a correction relationship**. Original amounts, accounts, leg order and clocks remain intact. Practitioners can trace the correction; builders can verify that an invalid replacement never leaves a stranded reversal.

## Learning contract and prerequisites

After this tutorial, construct an exact inverse journal, calculate the net corrected account movements, implement durable correction identity, and explain the transaction and knowledge-time boundaries. Read Double-Entry Posting Engine, Append-Only Journal Enforcement and Idempotent Journal Posting first. A debit raises an asset's normal balance; a credit raises a liability's normal balance. Customer deposits are liabilities on the fictional bank's books, rather than customer-owned cash assets on its books.

Prerequisite foundations: Units, Dimensions, Scale, and Normalization; Rounding, Precision, Tolerance, and Significant Digits; Identifiers, Keys, Joins, and Data Grain; Timestamps, Time Zones, Calendars, and Observation Time; Revisions, Vintages, and Point-in-Time Availability.

## Definitions, scope and variants

An **original** is an accepted complete journal. Its **reversal** flips each stored leg's side without changing its positive amount, account or position. A **replacement** states the intended new instruction and passes ordinary posting validation. A **correction relationship** records the three journal IDs, reason, correction identity and recording clock. Corrected status is derived from relationships, never an overwritten flag on the original.

| Variant | Effect | This package |
|---|---|---|
| Full reversal plus replacement | Original and inverse cancel; replacement remains | Implemented atomically |
| Delta adjustment | Adds only the difference | Excluded; requires separate allocation/lineage rules |
| Partial reversal | Cancels a specified subset or amount | Excluded |
| Scheduled accrual reversal | A future scheduled accounting operation | Later Accrual/Reversal Scheduler |
| Restatement | Framework-specific recognition and disclosure | Not determined by this algorithm |

The teaching institution is **Cedar Teaching Bank**, entirely fictional, entity CB-A and ledger CB-GENERAL. No jurisdiction-specific accounting or bank approval rule is asserted. Current account status must be active. One full reversal is allowed per original; a replacement can subsequently be corrected as a new original. Reversal-of-reversal shortcuts, cycles and self references are rejected.

## Input data contract

[DATA-CONTRACT.md](research/DATA-CONTRACT.md) is the exact wire and output contract. [cases.json](datasets/cases.json) is synthetic, redistributable teaching input. No private bank/customer records are used.

Money is positive decimal **string** minor units, 1..30 digits, at most 999999999999999999999999999999 per leg. USD and EUR each have exponent 2 in the verified ISO snapshot; journals balance separately by currency. Python uses int, TypeScript BigInt. Totals may exceed the per-leg limit. There is no floating-point tolerance, implicit rounding or currency conversion. Preserve 2..32 ordered legs and at least two distinct accounts; repeated account legs are allowed.

Identifiers use ASCII `[A-Za-z0-9][A-Za-z0-9._:-]{0,63}`. Exact field sets are required. Unknown fields, missing/null values, numeric amounts, exponents, leading zeros and unrecognized enums reject. Accounts and policy are trusted state; wire clients cannot authorize themselves.

All clocks are valid Gregorian UTC millisecond strings, years 0001..9999, no leap second. Effective time is the economic date; source availability says when the correcting instruction became available; recorded_at is the server recording clock inside a transaction. Both input clocks must be <=recorded_at. Recording time cannot go backwards. It does not prove the physical commit instant. A successful database COMMIT establishes acceptance.

Booking period is a distinct YYYY-MM classification and must currently be open. Normally it equals the effective month. An explicitly authorized current-period adjustment may retain a prior effective month while booking in an open current period; trusted `allow_current_period_adjustment=true` and a nonempty `adjustment_reason` are both required. Merely changing booking_period is rejected. This minimal policy does not decide legal recognition for prior-period errors.

## Output, state and invariants

`new_state(accounts)` / `newState(accounts)` initializes a trusted scoped snapshot. `post_journal` / `postJournal` seeds accepted originals. `correct_journal` / `correctJournal` returns `{status, code, outcome, resulting_state}`. Status is committed, replayed or rejected; rejected outcome is null. On success outcome includes relationship, reversal and replacement. Both journals preserve all strict command fields plus string sequence, recorded_at and adjustment_reason. State contains accounts, immutable journals, operation identities, correction identities and the next string sequence.

The reference uses copy-on-success: inputs are never mutated, and rejection/replay returns the unchanged snapshot. It models atomicity by exposing only the final candidate. It is neither a concurrent datastore nor a security boundary. A database adapter must commit both journals, identities, link and counter under one ledger lock. See the companion [PostgreSQL control profile](../D30-F05-A01-append-only-journal-enforcement/README.md) for separately executed role, transaction and concurrency evidence. Caller success must follow COMMIT; an SQL function's returned row before COMMIT is provisional.

Correction idempotency is durable scoped `(ledger_id, entity_id, correction_id)`. A semantically identical command replays the original outcome even after the current period closes. Changed reason, clocks, IDs, amounts or ordered legs under the same key conflict. Object property ordering is irrelevant; leg-array order remains semantic. Comparison is versioned exact semantic JSON, not a cryptographic hash or RFC8785 commitment. Trusted ledger/entity authorization precedes replay lookup; fresh account/period policy follows lookup for new commands. Retain committed identities for as long as duplicate economic posting must be prevented.

## Mathematics and independent worked example

For leg i, amount m_i is a positive integer in minor units and sign s_i is +1 for debit, -1 for credit. Raw movement for account a is `v_a = sum(s_i*m_i for legs on a)`. Balanced means `sum_a v_a = 0`. Normal-side movement equals v_a for assets/expenses and -v_a for liabilities/equity/income. A negative normal balance is diagnostic, not automatically an overdraft rejection.

Exact reversal defines `v_reverse,a = -v_original,a`. Therefore `v_original,a + v_reverse,a + v_replacement,a = v_replacement,a` for every account. Cancellation must hold account by account, not just in total. It cannot justify a reversal that sends the offset to a different beneficiary.

Synthetic USD cents, zero opening:

| Journal | Ordered legs | Ada liability | Ben liability | Cora liability |
|---|---|---:|---:|---:|
| Funding | Dr cash 100000; Cr Ada 100000 | 100000 | 0 | 0 |
| Wrong transfer | Dr Ada 2500; Cr Ben 2500 | 97500 | 2500 | 0 |
| Exact reversal | Cr Ada 2500; Dr Ben 2500 | Not separately committed | Not separately committed | Not separately committed |
| Replacement | Dr Ada 2400; Cr Cora 2400 | 97600 | 0 | 2400 |

Independent arithmetic: Ada=100000-2500+2500-2400=97600; Ben=2500-2500=0; Cora=2400. Cash remains100000. Deposit liabilities=97600+0+2400=100000. Across all four journals, debits=credits=100000+2500+2500+2400=107400. The reversal preserves original positions: Ada-credit is first and Ben-debit second. The correction pair is one visible committed operation.

![Original, exact inverse and replacement with independent closing balances](visuals/static/correction-lineage.svg)

The drawing connects the immutable original to both new journals. It shows why balance alone cannot detect a wrong destination and why the intermediate inverse must not become a separately committed correction.

## Algorithm and pseudocode

1. Check exact envelope shape, version, identifiers and trusted ledger/entity scope.
2. Resolve an existing correction identity. Replay the stored outcome if identical; otherwise conflict.
3. Validate reason and locate original. Reject missing, already reversed or reversal targets.
4. Validate replacement's full strict journal contract and its agreement with envelope scope/clocks/period.
5. Reject reused/self journal IDs and reused operation identities.
6. Derive ordered inverse legs from stored original, never caller-supplied inverse amounts.
7. Validate inverse against current account/period/time policy.
8. Stage both journals and all identities/link/counters in one isolated candidate or database transaction.
9. Commit the whole operation; discard all staged effects if any step fails.

```text
authorize scope
if committed correction identity exists: replay or conflict
lock ledger and recheck identity/target uniqueness
derive exact inverse from preserved original
validate inverse and replacement
stage inverse, replacement, relationship, identity, counter
COMMIT all; return accepted result only after success
```

```mermaid
flowchart TD
  A[Authorized correction request] --> B{Committed identity exists?}
  B -->|Same payload| C[Replay preserved outcome]
  B -->|Changed payload| D[Reject conflict]
  B -->|New| E[Lock ledger and check original]
  E --> F[Derive inverse and validate replacement]
  F --> G{Both valid?}
  G -->|No| H[Discard entire candidate]
  G -->|Yes| I[Commit two journals and immutable link]
```

The replay path resolves prior accepted work. The new-operation path has one publication boundary after both candidates pass. The tested SQL adapter must recheck after obtaining the lock.

## Implementation and validation

Run Python: `python tests/test_correction.py output-python.json`. Compile TypeScript tests with ES2022 and NodeNext, then pass datasets/cases.json and an output path to the compiled test. See [VALIDATION.md](research/VALIDATION.md) for exact repository commands. Shared fixtures cover canonical arithmetic, large integers beyond JavaScript safe Number, malformed values, account/currency/scope errors, clocks, current-period adjustments, closed periods, failed replacement, immutable original, retry/conflict, competing reversal and limited knowledge cutoffs. Full outputs, including snapshots/nulls/reasons/identities, must match across languages.

The knowledge fixture shows original transfer balances at a 21 October knowledge cutoff and corrected balances at a 23 October cutoff, both queried economically through 20 October. It preserves later correction recording time instead of silently backdating knowledge. The simple inclusive filter is not the full Bitemporal Ledger Versioning algorithm.

## Visual explanations and practical limits

Use [the guided correction lab](visuals/animated/correction-lab.html) to step through locating, deriving, validating, staging, committing and retrying. Its candidate display is clearly separate from committed balances. Canonical, failure and policy-boundary scenarios use the same TypeScript implementation as the reference. Static fallback is the lineage drawing and table above.

Full reversals may legitimately fail after accounts close. This profile rejects them; an institution needs its own approved compensating workflow. A database owner or administrator remains trusted; append-only app permissions do not prove tamper evidence against infrastructure. Exact arithmetic does not establish correct business intent, available funds, fraud authorization or external settlement. Counter ordering is local to the ledger; it is neither a distributed consensus protocol nor wall-clock proof. See [claim ledger](research/CLAIM-LEDGER.md).

Historical-example decision: **not useful** for the first deterministic variant. Synthetic wrong-beneficiary/amount cases expose the relevant boundaries without inventing a bank's customer history. A later real case requires authoritative instruction identity, timestamps, accounting perspective, recognition policy and redistribution rights. PostgreSQL conformance evidence is technical testing, not a historical bank dataset.

## Related topics and primary evidence

Prerequisites: [Double-Entry Posting Engine](../../D30-F01-ledger-posting/D30-F01-A01-double-entry-posting-engine/README.md), [Append-Only Journal Enforcement](../D30-F05-A01-append-only-journal-enforcement/README.md), [Idempotent Journal Posting](../../D30-F01-ledger-posting/D30-F01-A02-idempotent-journal-posting/README.md). The planned next stage introduces Event-Sourced Balance Fold and Bitemporal Ledger Versioning; these are roadmap handoffs, not claims that those tutorials are already delivered.

Debit/credit normal-side conventions follow [GnuCash v5 Accounting Concepts](https://www.gnucash.org/docs/v5/C/gnucash-guide/basics-accounting1.html). The all-or-none transaction boundary is supported by [PostgreSQL18 Transactions](https://www.postgresql.org/docs/18/tutorial-transactions.html); row-lock serialization is supported by [Explicit Locking](https://www.postgresql.org/docs/18/explicit-locking.html). The exact correction envelope, full-pair policy and limits are implementation choices, not requirements asserted by those sources. [REFERENCES.md](REFERENCES.md) records source roles, dates and limitations.
