D30-F01-A03 / Released engineering topic

Event-Sourced Balance Fold: Rebuild an Exact Balance from Complete Committed History

A production-minded guide to Event-Sourced Balance Fold.

Event-Sourced Balance Fold: Rebuild an Exact Balance from Complete Committed HistoryD30 / D30-F01

A balance of 97350 can be correct, incomplete or accidentally double-counted. The number alone cannot tell you which. To rebuild a trustworthy balance, you need the account movements and evidence that the supplied committed history is complete.

This tutorial constructs one account's exact posted balance in Python and TypeScript. You will calculate the running balance, keep a reversal/replacement pair atomic, distinguish economic and knowledge cutoffs, and verify a checkpoint before resuming its suffix.

Every transaction below is synthetic. Cedar Teaching Bank is a fictional entity, CB-A, using ledger CB-GENERAL. USD values are integer cents. Ada's deposit account is a liability of the bank, so its normal side is credit. There is no market dataset, live customer history or actual banking incident.

Start with a signed movement

A debit contributes a positive raw movement; a credit contributes a negative raw movement. Sum only the target account's legs. Then display the result according to its normal side:

Plain text
raw delta = debit amounts − credit amounts
raw balance = previous raw balance + raw delta
normal balance = raw for debit-normal accounts
normal balance = −raw for credit-normal accounts

Funding Ada with 100000 means Dr cash 100000 and Cr Ada 100000. Ada's raw balance becomes−100000, while its normal deposit balance becomes 100000. A subsequent Dr Ada 2500 reduces that normal balance to 97500.

Keep this institutional perspective throughout. A credit-normal liability is not a customer's debit-normal cash asset. The Double-Entry Posting Engine develops these signs and accepted journal constraints.

Replay committed groups, not arbitrary messages

The replay input is an ordered export of accepted journal groups. Pending requests and rejected commands do not count. A posting group contains one journal. A correction group contains two: the exact inverse of the retained original, followed by a replacement. The linkage names all three journals.

Microsoft's official Event Sourcing pattern provides the architectural context for reconstructing projections from ordered events. This tutorial chooses a narrower financial contract: balanced journals, exact integer movements and an explicitly trusted committed export. It does not recommend replacing every database with event sourcing.

Here is the canonical calculation:

GroupAda raw deltaAda normal balance
Funding−100000100000
Erroneous Ada→Ben 2500+250097500
Reverse 2500, replace Ada→Cora 2400−2500+2400=−10097600
Fee 250+25097350

Notice that the correction is one net movement of−100 raw, equivalent to+100 normal for Ada.

Funding yields 100000, the wrong transfer 97500, the complete correction 97600 and the fee 97350 in Ada normal minor USD.

Open the full-size diagram.

The full ledger still balances: cash +100000, Ada −97350, Ben 0, Cora −2400 and fee income −250 sum to 0 in debit-positive raw units. That independent check catches a sign error even when a single account's path looks plausible.

Validate coverage before adding

Suppose the export contains funding, the wrong transfer and the correction, but omits the fee. Adding those rows returns 97600. That is a valid economic view for an earlier cutoff, but it is incomplete evidence for the source head at sequence 5.

The trusted source head supplies a stream identity, scope, observation time, final committed sequence and receipt reference independently of the exported list. The fold requires every sequence from 1 through that head, or a checked checkpoint prefix plus every later suffix sequence. It does not infer completeness merely from the last supplied row.

This profile uses a transactional committed ledger counter from the append-only posting adapter. A generic PostgreSQL nextval sequence can have rollback gaps, so consecutive IDs from such a sequence would not provide the same completeness rule. PostgreSQL 18 sequence documentation

A group with the same identity and identical payload rejects as DUPLICATE_GROUP. Changed content under the same identity rejects as GROUP_ID_CONFLICT. A novel group arriving out of order rejects; the reader does not silently sort it. Journal IDs and business operation IDs must remain unique across the scoped history.

An empty source with trusted head 0 is a complete zero balance. An unexplained opening scalar is not evidence.

Give each clock one job

The exported journal retains effective_at, source_available_at and recorded_at. Its containing group adds available_at; the source head adds observed_at.

ClockMeaning in this reference
effective_atWhen the journal contributes economically
source_available_atWhen underlying evidence became available to posting
recorded_atTrusted server recording time retained from posting
group.available_atObserver receipt after successful whole-group COMMIT
head.observed_atLatest instant for which this trusted export can establish completeness

A recording clock is not proof of physical COMMIT time. The correction's reversal and replacement may have different recorded times inside the transaction. PostgreSQL transaction semantics make grouped changes atomically visible; our export preserves that boundary with one group receipt rather than inventing equal recording clocks. PostgreSQL 18 transactions

The example's correction members were recorded at 22 October 08:01:00.100 and 08:01:00.300. Their receipt is 08:02:00.000. At 08:01:00.200, the answer is still 97500. At 08:01:30.000, after both recording clocks but before receipt, it is still 97500. At 08:02:00.000, both members enter and the answer becomes 97600.

Two correction recording times precede one receipt; knowledge before receipt sees 97500 and at receipt sees 97600.

Open the full-size diagram.

The trusted adapter must obtain head and groups from one consistent committed snapshot. Separate Read Committed statements may observe different snapshots; a pure function cannot establish that an exporter followed this rule. PostgreSQL 18 isolation documentation

Ask two precise questions

The query has an economic cutoff E and a knowledge cutoff K. Equality is inclusive on both axes. First select complete groups whose available_at≤K; then select each journal whose effective_at≤E.

Plain text
R(a,E,K) =
  sum over groups received by K
    sum over journals effective by E
      sum target-account signed legs

All sums are exact integer minor units. The resulting normal balance is R for debit-normal accounts or−R for credit-normal accounts. No rounding applies.

Set E to 20 October 23:59:59.999 and K at the 24 October 12:00 source observation. The 23 October fee does not contribute economically, so Ada's normal balance is 97600. The complete export still validates through sequence 5. Its visible fee trace contains no selected journals and delta 0.

Set E before every transaction and retain the later K: the balance is 0 with complete evidence. Set E in 2027 with the same observed K: the answer contains only already accepted known contributions. It is not a forecast of later activity.

A K later than head.observed_at rejects as KNOWLEDGE_BEYOND_EXPORT. A stale source snapshot cannot justify a later completeness claim.

Check a checkpoint before using its number

A checkpoint after sequence 2 at fixed E = 20 October stores raw Ada −97500. It identifies stream, scope, account, currency, normal side, effective cutoff, group-end sequence, final group/recording/receipt metadata and provenance.

The caller's checkpoint must match the complete trusted registry entry. A matching checkpoint ID alone is insufficient. The reference registry retains the complete prefix groups, so the reader revalidates lineage and independently recomputes the cached balance before seeding the suffix.

For this example, the checked suffix correction contributes−100 raw:

Plain text
checked prefix raw −97500
+ correction raw delta −100
= final raw −97600
= normal deposit balance 97600

The economically excluded fee contributes 0 at fixed E = 20 October. A checkpoint cannot split a correction pair, overlap the suffix or come after K. Changing E requires a new genesis replay because the cached number was calculated under a different economic question.

This teaching reference intentionally rechecks the full prefix. It demonstrates cache correctness and provenance; it does not claim a replay speedup, compact authenticated storage or cryptographic tamper proof.

Run the reference and inspect its evidence

The public functions are fold_balance(query, groups, accounts, context) and foldBalance. They have no database, external-service or sibling-topic runtime dependency.

Python
import json
from implementations.python.balance_fold import fold_balance

with open("datasets/fold-cases.json", encoding="utf-8") as handle:
    case = json.load(handle)["cases"][7]

result = fold_balance(case["query"], case["groups"],
                      case["accounts"], case["context"])
assert result["balance"]["normal_balance_minor"] == "97350"
assert result["coverage"]["validated_through_sequence"] == "5"

Python uses exact integers; TypeScript uses BigInt internally. Monetary JSON values remain strings, including values above JavaScript Number's safe integer range. The per-leg limit is 10^30−1; aggregate balances can exceed that bound without truncation.

Success returns balance, complete coverage, visible group trace and a new checkpoint registry candidate. Rejection returns a typed code with null balance/coverage/checkpoint and an empty trace. It never presents a partial number as a complete answer.

Current account status is not posting eligibility for old journals: an account closed today still contributes its accepted past history. Likewise today's open periods do not invalidate a historical booking. The reader validates immutable accounting conventions and retained adjustment evidence rather than rerunning the current posting policy.

Explore the failure boundary

Open the guided replay lab. Begin with the fully populated canonical preview. Step through receipt cutoffs around the correction, then compare an earlier E, a missing tail, duplicate/conflicting groups, a checked checkpoint and huge exact funding. Step and Play use the same deterministic transitions; Back and Reset make the boundary reproducible.

The 84-case fixture matrix includes these independent expectations and rejects malformed clocks, scope/currency mismatch, missing unselected history, double reversals and corrupted checkpoints. Full-output Python/TypeScript parity compares the exact values, typed codes, nulls, ordered traces and complete checkpoint registry payloads. Validation record

The source head, immutable catalog and registry remain trusted inputs. Completeness is relative to that head, not proof that every real-world transaction was posted. Available funds, holds, legal recognition, collectability and external tamper verification need additional models.

Continue the ledger sequence

Idempotent Journal Posting protects posting identity before events reach this export. Correcting and Reversing Entry Algorithm constructs the linked pair. Bitemporal Ledger Versioning compares economic and observer-knowledge histories. Available/Ledger Balance Calculation adds the separate question of funds held from spending.

Historical-example decision: not useful. Controlled synthetic history provides complete expected movements and clocks without inventing a real customer's ledger. Architecture/database facts are primary-sourced; schemas and limits are explicit implementation choices; all money arithmetic is author-derived. Complete data contract and reference ledger.

ReferencesPrimary sources and evidence notes

Expand the source trail, evidence role, and limitations behind the engineering choices.

Accessed: 25 October 2026. All monetary fixtures are synthetic and author-derived. No employer/customer data or external dataset is redistributed.

IDPrimary source and versionRole and supported claimApplicability limit
FS01Microsoft Azure Architecture Center, Event Sourcing pattern, live official documentation accessed2026-10-25Ordered event replay, projections, compensating history and snapshot contextArchitecture guidance; does not define accounting or prove speed
FS02PostgreSQL 18, Transaction Isolation section 13.2Committed snapshot behavior and distinct statement snapshots under Read CommittedExporter consistency is a trusted adapter obligation
FS03PostgreSQL 18, Transactions section 3.4All-or-nothing visibility of grouped transaction changesPortable replay and receipt timestamps do not prove actual COMMIT instant/durability
FS04PostgreSQL 18, Sequence Manipulation Functions section 9.17nextval rollback gaps distinguish SQL sequences from this committed counterThis profile intentionally uses the Stage 1 transactional ledger counter
FS05Frozen D30 Stage 0 and Stage 2 contracts, repository author specification, ratified2026-10-25Exact accounting signs, strict fields, units, cutoffs, registry and reason limitsTeaching implementation choices, jurisdiction-neutral fictional bank

Source versions and ownership

PostgreSQL links explicitly select major version18. Microsoft documentation is a live page; the access date identifies the checked text, not an immutable page version. The local source ledger is research/SOURCE-LEDGER.md. The frozen complete data contract is mirrored in examples/DATA-CONTRACT.md.

Claim classification

Architecture/database statements cite FS01–FS04. The complete head/group/query/checkpoint shapes, resource bounds, cutoff equality, retained adjustment reason and typed rejection policy are implementation choices. The100000 funding,2500 wrong transfer,2400 replacement and250 fee are synthetic. Balances100000,97500,97600,97350 are independently calculated arithmetic. No causal business-performance, speed, universal regulatory or forecast claim is made.

Historical-example decision

Not useful. No named historical ledger is needed to test deterministic replay, whole-group visibility or missing evidence. A real-case extension would require redistributable journal data, immutable account conventions, actual consistent export receipts and complete correction lineage; a few headline transactions would not suffice.

Supports: FS01–FS04 support only the specifically attributed architecture/database facts above.

Limitations: Schemas, cutoffs, resource limits, arithmetic and fictional account data are author choices; no source proves authenticity or performance of this portable reader.

balance-fold.ts
/** Auditable committed-group replay. No I/O, database, posting or sibling imports. */
type Obj=Record<string,any>;
const COMMAND=['schema_version','ledger_id','entity_id','journal_id','business_operation_id','currency','effective_at','source_available_at','booking_period','legs'];
const LEG=['account_id','side','amount_minor'];
const ACCOUNT=['account_id','ledger_id','entity_id','currency','account_type','normal_side','status'];
const EXPORTED=['command','sequence','recorded_at','adjustment_reason'];
const GROUP=['schema_version','group_id','kind','ledger_id','entity_id','available_at','journals','correction'];
const LINK=['correction_id','original_journal_id','reversal_journal_id','replacement_journal_id','reason'];
const HEAD=['stream_id','ledger_id','entity_id','observed_at','head_sequence','receipt_ref'];
const QUERY=['schema_version','ledger_id','entity_id','account_id','currency','effective_cutoff','knowledge_cutoff','checkpoint'];
const CP=['schema_version','checkpoint_id','stream_id','ledger_id','entity_id','account_id','currency','normal_side','through_sequence','effective_cutoff','raw_balance_minor','last_group_id','last_recorded_at','last_available_at','provenance_ref'];
const NORMAL:Record<string,string>={asset:'debit',expense:'debit',liability:'credit',equity:'credit',income:'credit'};
const IDENT=/^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/;
const MONEY=/^[1-9][0-9]{0,29}$/;
const SEQUENCE=/^(0|[1-9][0-9]{0,29})$/;
const SIGNED=/^(0|-?[1-9][0-9]*)$/;
const TIME=/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/;
const PERIOD=/^[0-9]{4}-(0[1-9]|1[0-2])$/;
class FoldError extends Error {}
function need(ok:any,code:string):asserts ok {if(!ok)throw new FoldError(code)}
const object=(x:any):x is Obj=>x!==null&&typeof x==='object'&&!Array.isArray(x)&&(Object.getPrototypeOf(x)===Object.prototype||Object.getPrototypeOf(x)===null);
const exact=(x:any,fields:string[]):x is Obj=>object(x)&&Object.keys(x).length===fields.length&&fields.every(k=>Object.hasOwn(x,k));
const full=(r:RegExp,s:string)=>r.exec(s)?.[0]===s;
const ident=(x:any)=>typeof x==='string'&&full(IDENT,x);
const sequence=(x:any,zero=true)=>typeof x==='string'&&full(SEQUENCE,x)&&(zero||x!=='0');
const trim=(s:string)=>s.replace(/^[\u0009-\u000d\u001c-\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+|[\u0009-\u000d\u001c-\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+$/g,'');
function same(a:any,b:any):boolean {
 if(Array.isArray(a))return Array.isArray(b)&&a.length===b.length&&a.every((x,i)=>same(x,b[i]));
 if(object(a))return object(b)&&Object.keys(a).length===Object.keys(b).length&&Object.keys(a).every(k=>Object.hasOwn(b,k)&&same(a[k],b[k]));
 return a===b;
}
function timestamp(x:any):boolean {
 if(typeof x!=='string'||x.length!==24||!full(TIME,x)||x.slice(0,4)==='0000')return false;
 const d=new Date(x);return Number.isFinite(d.getTime())&&d.toISOString()===x;
}
function catalog(accounts:any):Map<string,Obj>{
 need(Array.isArray(accounts),'INVALID_ACCOUNTS');
 const result=new Map<string,Obj>();
 for(const a of accounts){
  need(exact(a,ACCOUNT),'INVALID_ACCOUNTS');
  need(['account_id','ledger_id','entity_id'].every(k=>ident(a[k])),'INVALID_ACCOUNTS');
  need(!result.has(a.account_id),'INVALID_ACCOUNTS');
  need(typeof a.account_type==='string'&&Object.hasOwn(NORMAL,a.account_type),'INVALID_ACCOUNTS');
  need(a.normal_side===NORMAL[a.account_type],'INVALID_ACCOUNTS');
  need(typeof a.currency==='string'&&['USD','EUR'].includes(a.currency),'INVALID_ACCOUNTS');
  need(typeof a.status==='string'&&['active','closed'].includes(a.status),'INVALID_ACCOUNTS');
  result.set(a.account_id,a);
 }
 return result;
}
function validateHead(h:any){
 need(exact(h,HEAD),'INVALID_CONTEXT');
 need(['stream_id','ledger_id','entity_id','receipt_ref'].every(k=>ident(h[k])),'INVALID_CONTEXT');
 need(timestamp(h.observed_at)&&sequence(h.head_sequence),'INVALID_CONTEXT');
}
function validateJournal(j:any,h:Obj,accounts:Map<string,Obj>):Obj{
 need(exact(j,EXPORTED),'INVALID_SCHEMA');need(sequence(j.sequence,false),'INVALID_SEQUENCE');
 need(timestamp(j.recorded_at),'INVALID_TIMESTAMP');need(typeof j.adjustment_reason==='string','INVALID_SCHEMA');
 const c=j.command;need(exact(c,COMMAND),'INVALID_SCHEMA');
 need(c.schema_version==='d30-journal-v1','UNSUPPORTED_SCHEMA');
 need(['ledger_id','entity_id','journal_id','business_operation_id'].every(k=>ident(c[k])),'INVALID_IDENTIFIER');
 need(c.ledger_id===h.ledger_id&&c.entity_id===h.entity_id,'SCOPE_MISMATCH');
 need(typeof c.currency==='string'&&['USD','EUR'].includes(c.currency),'UNSUPPORTED_CURRENCY');
 need(timestamp(c.effective_at)&&timestamp(c.source_available_at),'INVALID_TIMESTAMP');
 need(c.effective_at<=j.recorded_at&&c.source_available_at<=j.recorded_at,'CLOCK_ORDER_VIOLATION');
 need(typeof c.booking_period==='string'&&full(PERIOD,c.booking_period)&&c.booking_period.slice(0,4)!=='0000','INVALID_SCHEMA');
 need(c.booking_period!==c.effective_at.slice(0,7)?Boolean(trim(j.adjustment_reason)):j.adjustment_reason==='','INVALID_ADJUSTMENT_EVIDENCE');
 need(Array.isArray(c.legs)&&c.legs.length>=2&&c.legs.length<=32,'INVALID_LEGS');
 const distinct=new Set();let debit=0n,credit=0n;
 for(const l of c.legs){
  need(exact(l,LEG),'INVALID_SCHEMA');need(ident(l.account_id),'INVALID_IDENTIFIER');
  need(typeof l.side==='string'&&['debit','credit'].includes(l.side),'INVALID_SIDE');
  need(typeof l.amount_minor==='string'&&full(MONEY,l.amount_minor),'INVALID_AMOUNT');
  const a=accounts.get(l.account_id);need(a!==undefined,'UNKNOWN_ACCOUNT');
  need(a.ledger_id===h.ledger_id&&a.entity_id===h.entity_id,'SCOPE_MISMATCH');need(a.currency===c.currency,'CURRENCY_MISMATCH');
  distinct.add(l.account_id);if(l.side==='debit')debit+=BigInt(l.amount_minor);else credit+=BigInt(l.amount_minor);
 }
 need(distinct.size>=2,'DISTINCT_ACCOUNTS_REQUIRED');need(debit===credit,'UNBALANCED_JOURNAL');return c;
}
function validateHistory(groups:any,h:Obj,accounts:Map<string,Obj>){
 need(Array.isArray(groups)&&groups.length<=4096,'INVALID_GROUP');
 const knownGroups=new Map<string,Obj>(),journals=new Map<string,Obj>(),business=new Set<string>(),corrections=new Set<string>(),reversedIds=new Set<string>(),reversalIds=new Set<string>();
 let expected=1n,lastRecorded:string|null=null,lastAvailable:string|null=null;
 for(const g of groups){
  need(exact(g,GROUP),'INVALID_GROUP');need(ident(g.group_id),'INVALID_IDENTIFIER');
  if(knownGroups.has(g.group_id)){need(!same(knownGroups.get(g.group_id),g),'DUPLICATE_GROUP');throw new FoldError('GROUP_ID_CONFLICT')}
  knownGroups.set(g.group_id,g);need(g.schema_version==='d30-committed-group-v1','UNSUPPORTED_SCHEMA');
  need(g.ledger_id===h.ledger_id&&g.entity_id===h.entity_id,'SCOPE_MISMATCH');
  need(typeof g.kind==='string'&&['posting','correction'].includes(g.kind),'INVALID_GROUP');
  need(timestamp(g.available_at),'INVALID_TIMESTAMP');
  need(g.available_at<=h.observed_at&&(lastAvailable===null||g.available_at>=lastAvailable),'CLOCK_ORDER_VIOLATION');
  need(Array.isArray(g.journals)&&g.journals.length===(g.kind==='posting'?1:2),'INVALID_GROUP');
  if(g.kind==='posting')need(g.correction===null,'INVALID_CORRECTION_LINK');
  const local:Obj[]=[];
  for(const j of g.journals){
   const c=validateJournal(j,h,accounts),n=BigInt(j.sequence);
   need(n>=expected,'OUT_OF_ORDER_GROUP');need(n<=expected,'SEQUENCE_GAP');
   need(j.recorded_at<=g.available_at&&(lastRecorded===null||j.recorded_at>=lastRecorded),'CLOCK_ORDER_VIOLATION');
   need(!journals.has(c.journal_id)&&local.every(x=>x.command.journal_id!==c.journal_id),'DUPLICATE_JOURNAL');
   need(!business.has(c.business_operation_id)&&local.every(x=>x.command.business_operation_id!==c.business_operation_id),'DUPLICATE_BUSINESS_OPERATION');
   local.push(j);lastRecorded=j.recorded_at;expected++;
  }
  if(g.kind==='correction'){
   const l=g.correction;need(exact(l,LINK),'INVALID_CORRECTION_LINK');
   need(['correction_id','original_journal_id','reversal_journal_id','replacement_journal_id'].every(k=>ident(l[k])),'INVALID_CORRECTION_LINK');
   need(typeof l.reason==='string'&&[...l.reason].length>=1&&[...l.reason].length<=240&&l.reason===trim(l.reason)&&[...l.reason].every(c=>c.codePointAt(0)!<0xd800||c.codePointAt(0)!>0xdfff),'INVALID_CORRECTION_LINK');
   need(!corrections.has(l.correction_id),'INVALID_CORRECTION_LINK');
   need(journals.has(l.original_journal_id),'ORIGINAL_NOT_FOUND');need(!reversedIds.has(l.original_journal_id),'ALREADY_REVERSED');need(!reversalIds.has(l.original_journal_id),'REVERSAL_OF_REVERSAL');
   const [r,p]=local.map(j=>j.command);
   need(l.reversal_journal_id===r.journal_id&&l.replacement_journal_id===p.journal_id,'INVALID_CORRECTION_LINK');
   need(['ledger_id','entity_id','currency','effective_at','source_available_at','booking_period'].every(k=>r[k]===p[k]),'INVALID_CORRECTION_LINK');
   const original=journals.get(l.original_journal_id)!.command;need(original.currency===r.currency,'CURRENCY_MISMATCH');
   const inverse=original.legs.map((leg:Obj)=>({...leg,side:leg.side==='debit'?'credit':'debit'}));
   need(same(r.legs,inverse),'INVALID_REVERSAL');corrections.add(l.correction_id);reversedIds.add(l.original_journal_id);reversalIds.add(r.journal_id);
  }
  for(const j of local){journals.set(j.command.journal_id,j);business.add(j.command.business_operation_id)}
  lastAvailable=g.available_at;
 }
 need(expected-1n<=BigInt(h.head_sequence),'HEAD_SEQUENCE_MISMATCH');need(expected-1n>=BigInt(h.head_sequence),'INCOMPLETE_EXPORT');
}
function calculate(groups:Obj[],account:Obj,effective:string,knowledge:string,initialRaw=0n):[bigint,Obj[],Obj[]]{
 let raw=initialRaw;const trace:Obj[]=[],visible:Obj[]=[];
 for(const g of groups){
  if(g.available_at>knowledge)continue;let delta=0n;const selected:string[]=[];
  for(const j of g.journals){const c=j.command;if(c.effective_at<=effective){selected.push(c.journal_id);for(const l of c.legs)if(l.account_id===account.account_id)delta+=BigInt(l.amount_minor)*(l.side==='debit'?1n:-1n)}}
  raw+=delta;visible.push(g);trace.push({group_id:g.group_id,first_sequence:g.journals[0].sequence,last_sequence:g.journals.at(-1).sequence,available_at:g.available_at,selected_journal_ids:selected,raw_delta_minor:delta.toString(),raw_balance_minor:raw.toString(),normal_balance_minor:(account.normal_side==='debit'?raw:-raw).toString()});
 }
 return [raw,trace,visible];
}
function validateCheckpoint(cp:any){
 need(exact(cp,CP),'CHECKPOINT_MISMATCH');need(cp.schema_version==='d30-balance-checkpoint-v1','CHECKPOINT_MISMATCH');
 need(['checkpoint_id','stream_id','ledger_id','entity_id','account_id','provenance_ref'].every(k=>ident(cp[k])),'CHECKPOINT_MISMATCH');
 need(['USD','EUR'].includes(cp.currency)&&['debit','credit'].includes(cp.normal_side),'CHECKPOINT_MISMATCH');
 need(sequence(cp.through_sequence)&&timestamp(cp.effective_cutoff),'CHECKPOINT_MISMATCH');
 need(typeof cp.raw_balance_minor==='string'&&cp.raw_balance_minor.length<=128&&full(SIGNED,cp.raw_balance_minor),'CHECKPOINT_MISMATCH');
 if(cp.through_sequence==='0')need(cp.last_group_id===null&&cp.last_recorded_at===null&&cp.last_available_at===null&&cp.raw_balance_minor==='0','CHECKPOINT_MISMATCH');
 else need(ident(cp.last_group_id)&&timestamp(cp.last_recorded_at)&&timestamp(cp.last_available_at),'CHECKPOINT_MISMATCH');
}
export function foldBalance(query:any,groups:any,accounts:any,context:any):any{
 const failed={status:'rejected',code:'',balance:null,coverage:null,trace:[],checkpoint:null,checkpoint_registry_entry:null};
 try{
  const cats=catalog(accounts);need(exact(context,['head','checkpoint_registry']),'INVALID_CONTEXT');const h=context.head;validateHead(h);
  need(Array.isArray(context.checkpoint_registry),'INVALID_CONTEXT');
  for(const entry of context.checkpoint_registry){need(exact(entry,['checkpoint','prefix_groups']),'INVALID_CONTEXT');validateCheckpoint(entry.checkpoint);need(Array.isArray(entry.prefix_groups),'INVALID_CONTEXT');need(entry.prefix_groups.length<=4096,'INVALID_GROUP')}
  need(exact(query,QUERY),'INVALID_SCHEMA');need(query.schema_version==='d30-balance-fold-v1','UNSUPPORTED_SCHEMA');
  need(['ledger_id','entity_id','account_id'].every(k=>ident(query[k])),'INVALID_IDENTIFIER');need(query.ledger_id===h.ledger_id&&query.entity_id===h.entity_id,'SCOPE_MISMATCH');
  need(cats.has(query.account_id),'UNKNOWN_ACCOUNT');const account=cats.get(query.account_id)!;
  need(account.ledger_id===h.ledger_id&&account.entity_id===h.entity_id,'SCOPE_MISMATCH');need(typeof query.currency==='string'&&['USD','EUR'].includes(query.currency),'UNSUPPORTED_CURRENCY');need(query.currency===account.currency,'CURRENCY_MISMATCH');
  need(timestamp(query.effective_cutoff)&&timestamp(query.knowledge_cutoff),'INVALID_TIMESTAMP');need(query.knowledge_cutoff<=h.observed_at,'KNOWLEDGE_BEYOND_EXPORT');need(Array.isArray(groups),'INVALID_GROUP');
  const cp=query.checkpoint;let prefix:Obj[]=[],prefixTrace:Obj[]=[];
  if(cp!==null){
   validateCheckpoint(cp);
   need(['stream_id','ledger_id','entity_id'].every(k=>cp[k]===h[k])&&cp.account_id===query.account_id&&cp.currency===query.currency&&cp.normal_side===account.normal_side,'CHECKPOINT_MISMATCH');
   need(cp.effective_cutoff===query.effective_cutoff,'CHECKPOINT_QUERY_MISMATCH');
   const entries:Obj[]=[];for(const entry of context.checkpoint_registry){need(exact(entry,['checkpoint','prefix_groups']),'INVALID_CONTEXT');need(object(entry.checkpoint),'INVALID_CONTEXT');if(entry.checkpoint.checkpoint_id===cp.checkpoint_id)entries.push(entry)}
   need(entries.length>0,'UNKNOWN_CHECKPOINT');need(entries.length===1,'INVALID_CONTEXT');need(same(entries[0].checkpoint,cp),'CHECKPOINT_MISMATCH');
   need(cp.last_available_at===null||cp.last_available_at<=query.knowledge_cutoff,'CHECKPOINT_AFTER_KNOWLEDGE');
   prefix=entries[0].prefix_groups;validateHistory(prefix,{...h,head_sequence:cp.through_sequence},cats);
   const last=prefix.at(-1);need(cp.last_group_id===(last?.group_id??null)&&cp.last_recorded_at===(last?.journals.at(-1).recorded_at??null)&&cp.last_available_at===(last?.available_at??null),'CHECKPOINT_MISMATCH');
   const prefixResult=calculate(prefix,account,cp.effective_cutoff,h.observed_at);prefixTrace=prefixResult[1];
   need(prefixResult[0].toString()===cp.raw_balance_minor,'CHECKPOINT_BALANCE_MISMATCH');
   if(groups.length){const first=groups[0];need(object(first)&&Array.isArray(first.journals)&&first.journals.length>0,'INVALID_GROUP');const seq=object(first.journals[0])?first.journals[0].sequence:null;need(sequence(seq,false),'INVALID_SEQUENCE');need(BigInt(seq)>BigInt(cp.through_sequence),'CHECKPOINT_OVERLAP')}
  }
  const allGroups=[...prefix,...groups];validateHistory(allGroups,h,cats);
  const replay=cp===null?calculate(groups,account,query.effective_cutoff,query.knowledge_cutoff):calculate(groups,account,query.effective_cutoff,query.knowledge_cutoff,BigInt(cp.raw_balance_minor));
  const raw=replay[0],trace=cp===null?replay[1]:[...prefixTrace,...replay[1]],visible=cp===null?replay[2]:[...prefix,...replay[2]],last=visible.at(-1),through=last?.journals.at(-1).sequence??'0';
  const checkpoint={schema_version:'d30-balance-checkpoint-v1',checkpoint_id:'CP-'+through+'-'+query.effective_cutoff.replace(/[^0-9]/g,''),stream_id:h.stream_id,ledger_id:h.ledger_id,entity_id:h.entity_id,account_id:query.account_id,currency:query.currency,normal_side:account.normal_side,through_sequence:through,effective_cutoff:query.effective_cutoff,raw_balance_minor:raw.toString(),last_group_id:last?.group_id??null,last_recorded_at:last?.journals.at(-1).recorded_at??null,last_available_at:last?.available_at??null,provenance_ref:h.receipt_ref};
  return {status:'ok',code:'BALANCE_FOLDED',balance:{account_id:query.account_id,currency:query.currency,normal_side:account.normal_side,raw_balance_minor:raw.toString(),normal_balance_minor:(account.normal_side==='debit'?raw:-raw).toString()},coverage:{stream_id:h.stream_id,receipt_ref:h.receipt_ref,head_sequence:h.head_sequence,validated_through_sequence:h.head_sequence,visible_through_sequence:through,checkpoint_id:cp?.checkpoint_id??null,observed_at:h.observed_at},trace,checkpoint,checkpoint_registry_entry:{checkpoint:structuredClone(checkpoint),prefix_groups:structuredClone(visible)}};
 }catch(error){if(error instanceof FoldError)return {...failed,code:error.message};throw error}
}
Full-height labbalance fold labOpen full screen
Written by

Fintech engineer building market-data and financial systems, and the author of every article, glossary record, and reference implementation on The Fintech Builder.