Why you cannot just fix it

What editing history destroys, and what reversal preserves.

About 14 minutes

TheoryWhy this exists

June's rent was RM3,500. The entry says RM35,000. You find it in the third week of July, and by then June's accounts have already gone out: to the bank, as part of a facility review, and to your tax agent.

On screen there is a field containing 35000. Changing it to 3500 takes two seconds and makes the ledger correct. That is the tempting fix, and it is worth being exact about what it destroys.

What the two-second fix costs

After the edit, the books say June's rent was RM3,500. True. But two documents are now in circulation that say the expense was RM35,000, and the ledger they were drawn from no longer contains that figure anywhere.

The bank's analyst compares her copy to your current trial balance and finds June's expenses RM31,500 apart. She asks what happened. You explain — and you are probably telling the truth. But nothing in the records corroborates you, because the record of the error was the thing you deleted. Your explanation and the books are now two separate artefacts, and only one of them is verifiable.

Now put yourself on the other side of that conversation. An auditor looking at the edited row sees a line that reads RM3,500 and has no memory of ever reading anything else. Here is the problem: that is exactly what a cover-up looks like too. A business that overstated an expense to reduce taxable profit, issued the accounts, and then quietly corrected the ledger once someone started asking questions produces an identical artefact — a clean row, a stale report, and a verbal explanation.

If honest corrections and concealment are indistinguishable in the record, then the record cannot support honesty. It cannot clear you. That is the real loss, and it lands on the careful bookkeeper, not the fraudster.

What reversal preserves

The alternative keeps both facts instead of choosing one:

  1. The original entry stays exactly as posted: rent RM35,000.
  2. A reversing entry is posted, dated when you found the error, that is the mirror image of the original — the same accounts and amounts with debits and credits swapped. Its net effect on every balance is to cancel the first entry.
  3. A fresh, correct entry is posted: rent RM3,500.

Three entries where the naive approach had one. The current balances are identical to what the edit would have produced. What you additionally have is a legible sequence: this was recorded, it was wrong, it was cancelled on this date, and this replaced it. The bank's stale report now reconciles — not because it agrees with the closing balance, but because the ledger explains the difference itself.

Writing a correction into history requires no trace; writing a correction as history requires you to leave one. That does not prove honesty — anyone who can post entries can post a plausible-looking correction sequence — but it gives a reviewer a dated trail to question instead of a clean row and a verbal explanation.

Immutability starts at posting, not at creation

None of this argues that every keystroke is permanent. A draft entry is a working document — nobody has relied on it, nothing has been derived from it, and editing or deleting it destroys no evidence. You should be able to change a draft freely, including throwing it away.

The moment that changes is posting. Posting is the act of asserting that this is what happened. From then on the entry is part of the record other people may have relied on, and the only honest way to change its effect is to add something new.

The line is not "old things are frozen". It is: the moment a fact becomes assertable by others, the only way to change it is to append.

"The numbers come out the same either way"

They do. That objection is correct, and it is beside the point.

Balances are half of what a ledger provides. The other half is provenance: for any figure in any report, the ability to trace it back to the events that produced it and the sequence in which they were recorded. Provenance is what makes a balance a claim you can defend rather than a number you are asserting.

An edited ledger can still be arithmetically perfect. What it has stopped being is evidence — and evidence, not arithmetic, is why anyone outside your business trusts your accounts at all.

PracticeDoing it in Cynco

Correcting a posted entry in Cynco is a two-step operation rather than an edit. The shape is the same wherever you meet it, so it is worth doing once deliberately.

The correction workflow

Open Accounting → Journal and find the entry. A posted entry's amounts and accounts are not editable — that is the design, not a permissions problem. What you can do is reverse it. Reversing generates a new entry that mirrors the original: same accounts, same amounts, debits and credits swapped, dated when you are making the correction rather than when the original was posted.

Then post a fresh entry with the correct figures.

You end up with three entries in the journal:

  1. Rent RM35,000 — the original, untouched.
  2. The reversal, RM35,000 the other way.
  3. Rent RM3,500 — correct.

Balances match what an edit would have produced. The difference is that the journal now tells the story, and anyone holding an older copy of the accounts can see why theirs differs.

The exact placement of the reverse action varies by screen and changes as the product evolves, so look for it on the entry itself rather than trusting a description of a button.

Narrations are the part only you can write

The system generates the reversal's mechanics. It cannot generate the reason. Reversals are the entries most likely to be read by a stranger, so write for that reader:

  • Weak: "Reversal" / "Correction" / "Fix"
  • Useful: "Reverse JE-0412 — June rent keyed as RM35,000, actual RM3,500 per tenancy agreement"

Reference the entry you are reversing, state what was wrong, and name the source document that establishes the correct figure. Eighteen months later that line is the difference between a two-minute audit query and an afternoon of reconstruction.

Drafts are different

A draft entry has no evidentiary weight, and Cynco treats it accordingly — edit the amounts, change the accounts, delete it entirely. Nothing is preserved because nothing was asserted.

That makes drafts the right place to work out anything you are unsure about. The cost of thinking in a draft is zero; the cost of thinking in the ledger is a reversal trail explaining that you were undecided.

Try it yourself

Do this in a test or sandbox context, not your live books:

  1. Go to Accounting → Journal and post a small entry you know is wrong — say RM500 of rent that should have been RM50.
  2. Try to edit the posted amount. Confirm for yourself that you cannot.
  3. Reverse the entry.
  4. Post the correct RM50 entry.
  5. Read all three entries in date order. Check that the account balances are what RM50 alone would have given you.

Step 5 is where the two properties coexist: balances as if only the correct entry existed, history as if nothing had been hidden. That is the whole argument for immutability, visible in four rows.

Then run the same experiment on a draft: create one, edit it twice, delete it. Notice that nothing remains — and that nothing needed to.

Try to edit a posted entry. Then do it the way the ledger allows.

TechnologyHow it is built

"Don't edit posted entries" is a policy. Policies decay. The engineering question is how to make the edit impossible, so that the guarantee survives everyone who later works on the system.

Application checks are not enough

The obvious implementation is a guard in the service layer:

ts
if (entry.status === "posted") {
  throw new Error("Cannot modify a posted entry");
}

This is worth having, because it produces a good error message. It is not a guarantee. Consider the ways around it, none of which require malice:

  • A new feature writes to the ledger tables without going through the service that holds the check.
  • A backfill script runs raw SQL at 2am to repair a data issue.
  • Someone with production database access runs an UPDATE by hand.
  • A future refactor moves the check and drops it in the move.

Every one of those is a Tuesday afternoon in a real codebase. A rule enforced only by convention holds until the first person who does not know about it.

Two structural defences apply. The first you have already met: a single write path. In Cynco, the GL posting engine is the only code that writes ledger rows, which collapses "every feature must remember the rule" into "one function enforces it". The second is to push the constraint below the application entirely — into the database, where a trigger or a revoked privilege rejects an UPDATE on a posted row regardless of which client issued it, ORM or psql or migration script. A trigger does not care how good your reasons were.

Enforce invariants at the lowest layer that can see the violation. Anything above that layer is advice.

The balance chain: making edits non-local

Suppose someone does get an UPDATE through. Immutability that can be violated silently is barely better than no immutability, so the second design goal is tamper evidence: an edit must be detectable after the fact.

A running balance chain does this. Each ledger entry stores not just its own movement but the resulting balance, computed from its predecessor:

text
entry 41  +1,200   balance 8,400
entry 42  -3,500   balance 4,900
entry 43    +800   balance 5,700

Now change entry 42's amount from 3,500 to 350. Its stored balance of 4,900 no longer follows from entry 41, and every balance after it is wrong too. The edit cannot stay local: to hide it you must rewrite every subsequent row consistently, which is far harder than one UPDATE and far more visible.

Cynco runs a balance-chain reconciliation process for exactly this reason. Recomputing the chain and comparing it against the stored balances turns "did anything change?" from an unanswerable question into a scheduled job with a boolean answer. The value is in it being routine — a check you run only when you already suspect something detects almost nothing.

Cryptographic hash chaining is the stronger version of the same idea: each row includes a hash of the previous row, so any alteration invalidates every hash downstream.

The same problem, solved the same way elsewhere

Git does not let you edit a commit. git revert creates a new commit that undoes an earlier one — the reversing entry, precisely. Even git commit --amend, which looks like an edit, writes a new object with a new hash and leaves the old one reachable in the reflog.

Event-sourced systems make the same choice: state is a fold over an append-only log, and a mistake is corrected by appending a compensating event. You cannot un-emit an event that downstream consumers have already processed, which is the software version of "the bank already has June's accounts".

Three domains, independently arriving at append-only with compensating writes. That convergence is a signal that the constraint comes from the problem rather than from accounting tradition.

What immutability does not buy you

Be precise, or you will over-trust the property:

  • Drafts are deletable, and should be. Nothing has been asserted, so nothing needs preserving. This is the one legitimate hard delete in a ledger system, and it means DELETE privileges are scoped to draft rows rather than revoked outright or granted outright.
  • A correct-looking wrong posting is still wrong. Debiting Equipment instead of Repairs is a judgement error. It balances, it chains, it verifies — and it is incorrect. Immutability guarantees that the record of your decision is faithful, not that the decision was right.
  • Immutability is not access control. It stops history being rewritten. It does nothing about who could read that history, or who was allowed to post in the first place.

The property is narrow and it is worth having: whatever the books say happened, that is genuinely what was recorded, when it was recorded. Everything else is a different control.