Skip to content

feat(ledger): double-entry, where a stored balance cannot exist - #21

Merged
isaacismaelx14 merged 1 commit into
mainfrom
feat/phase-7a-local-ledger
Aug 22, 2026
Merged

feat(ledger): double-entry, where a stored balance cannot exist#21
isaacismaelx14 merged 1 commit into
mainfrom
feat/phase-7a-local-ledger

Conversation

@isaacismaelx14

Copy link
Copy Markdown
Member

Phase 7A. 06 calls ledger-first "the single most consequential engineering decision in the system, and the one most likely to be compromised for convenience", so the shortcuts it warns about are made unavailable rather than discouraged.

  • No balance column, so a balance cannot be stored.
  • No method that writes a single entry, so nothing can post one side of a movement.
  • No update, no delete, so history cannot be repaired in place.

What remains is post, which writes a balanced transaction or writes nothing.

Law XXI is the part to read closely

An implementation that can produce a negative balance can mint currency.

Every account a movement touches is checked after the entries are written and inside the same database transaction, with those accounts SELECT ... FOR UPDATE locked first.

Without the lock, two concurrent debits can each read a sufficient balance, each pass the check, and together overdraw. There is a test that fires both at once and asserts exactly one survives.

Precision

bigint end to end. Summed in Postgres, cast to text, widened to BigInt. A SUM over bigint arriving as a JavaScript number would be the floating point error 06 forbids, entering through the one place nobody looks.

A value past MAX_SAFE_INTEGER round-trips exactly, and twenty small movements conserve to the subunit.

Three things the domain caught that I had wrong

The tests found all three, which is the domain validator from Phase 0 doing its job:

  1. An entry must carry its transaction's idempotency key, so one half of a movement cannot be deduplicated apart from the other.
  2. An entry must carry its transaction's type, so a distribution cannot contain an entry that reads as a transfer.
  3. Kredbits cannot hold a negative value. That is why the overdraft check reads an unbranded bigint: the type cannot express the thing being detected, and branding first raises a range error instead of reporting the overdraft.

A finding, not a gap

post cannot create the opening supply. A transaction must sum to zero, and creating money is precisely the movement that does not. Genesis belongs to the Central Bank, which is Phase 8. The tests seed an opening balance directly and say so in a comment.

Verification

Against a real Postgres, stable across three runs. Verified by mutation: removing the Law XXI check fails three tests, including the concurrent overdraft.

374 tests. Format, lint, typecheck and build clean.

Not in this PR

Phase 7B is the Official global ledger and lives in kreds-network. The roadmap is explicit that it must implement its own accounting domain rather than importing this AGPL package: same principles, separate boundary.

Phase 7A. 06 calls ledger-first "the single most consequential
engineering decision in the system, and the one most likely to be
compromised for convenience", so the shortcuts it warns about are made
unavailable rather than discouraged.

There is no balance column, so a balance cannot be stored. There is no
method that writes a single entry, so nothing can post one side of a
movement. There is no update and no delete, so history cannot be
repaired in place. What remains is post, which writes a balanced
transaction or writes nothing at all.

Law XXI is the one worth reading the code for. 06: "an implementation
that can produce a negative balance can mint currency." So every account
a movement touches is checked after the entries are written and inside
the same database transaction, with those accounts locked first. Without
the lock two concurrent debits can each read a sufficient balance, each
pass, and together overdraw, which is exactly the path that mints. There
is a test that runs both at once.

Amounts are bigint end to end, summed in Postgres and read back as text
before being widened. A SUM over bigint that arrived as a JavaScript
number would be the floating point error 06 forbids, entering through
the one place nobody looks. A value past MAX_SAFE_INTEGER round-trips
exactly, and there is a test for that too.

Three things the domain enforced that this did not know about until the
tests ran: an entry must carry its transaction's idempotency key, an
entry must carry its transaction's type, and Kredbits cannot hold a
negative value at all. The last one is why the overdraft check reads an
unbranded bigint: the type cannot express the thing being detected, and
branding first would raise a range error instead of reporting the
overdraft.

A finding rather than a gap: post cannot create the opening supply,
because a transaction must sum to zero and creating money is precisely
the movement that does not. Genesis belongs to the Central Bank, which
is Phase 8. The tests seed an opening balance directly and say so.

Verified against a real Postgres, including by mutation: removing the
Law XXI check fails three tests, one of them the concurrent overdraft.
@isaacismaelx14
isaacismaelx14 merged commit 7eed7bc into main Aug 22, 2026
1 check passed
@isaacismaelx14
isaacismaelx14 deleted the feat/phase-7a-local-ledger branch August 22, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant