Three reference pairs match, and both source balances reconcile to their own movement histories. The two closing balances still differ by 700 cents.
That is possible without a programming error. One source includes an internal-only outflow; the other withdrew its reported evidence for that row. A matching result cannot erase the difference. A balance proof must show each source's full opening-to-closing bridge, then compare the sources honestly.
In this tutorial you will build that proof in Python and TypeScript. You will keep independent versioned controls, use one knowledge cutoff for both movements and controls, and distinguish numeric failure from missing evidence or unresolved investigation.
All Cedar records are synthetic. Entity CB-A owns the reconciled bank asset in ledger CB-GENERAL. Amounts are exact minor units; the main example uses USD cents.
Specify the balance being proved
The selected profile is a booked cash-asset balance under a UTC booking-date window. Opening is at the start midnight; closing is at the exclusive end midnight. A query covers 1–366 civil days and chooses one supported currency.
An inflow increases the owner's bank asset. An outflow decreases it. A financial reversal is another booked, already-directed movement. Keep both original and reversal; do not remove the original or negate the reversal again.
These choices matter because a booked balance is not interchangeable with an available or value-date balance. SWIFT's CGI-MP guide, printed page 27, distinguishes complete-statement booked controls from interim page balances. Adyen's reconciliation documentation illustrates a starting/incoming/outgoing/ending bridge, while its report descriptions show why booking dates, value dates and timezones must be aligned.
Our normalized UTC schema is an authored teaching profile. It does not parse either provider's report or silently reinterpret their timezones.
Obtain controls independently
A closing value calculated from the movement list is an expected close. It is not an independent reported close.
Each source therefore supplies two independently observed histories: opening and closing controls. Every control head binds source, side, ledger, entity, routing account, currency, booked basis and exact boundary. Its independent revision head and observation define the control history that must be supplied.
A control version records amount, revision, exact predecessor, observer receipt and source-version reference. Revisions begin at one and continue without gaps. Complete future history must still be validated before cutoff filtering. Duplicate copies and conflicting same-version payloads reject; arbitrary input order cannot select a winner.
A control can be withdrawn. If the latest visible version is withdrawn, its amount is unavailable for proof. Do not revive the superseded control merely because it would produce a convenient residual.
Signed control amounts permit negative bank-asset balances and zero. They are canonical strings with at most 120 magnitude digits. Movement amounts remain positive strings with at most 30 digits, with direction carrying the sign. Derived totals may exceed an input bound; arbitrary-precision arithmetic preserves them.
Use one knowledge cutoff for all evidence
The query cutoff K means “known to the trusted observer at this instant.” A complete movement group becomes visible when its receipt is at or before K. Controls use the same inclusive rule, selecting the latest visible version.
Source and control heads are independently supplied, and both must cover K. The observer receipt is not physical database COMMIT-time proof. A head states completeness relative to the declared extraction log; JSON does not authenticate that claim.
Validate complete source and control histories before applying K or the booking window. A missing future-unselected tail remains incomplete evidence. A malformed future control remains malformed. Those checks prevent historical selection from laundering an incomplete export.
When one side is independently complete and the other is not, preserve the complete side's numeric diagnostic. A known one-cent local residual must remain a failure even if the opposite source is missing evidence. Structural or conflicting input rejects globally before arithmetic.
Build both bridges from every movement
For each complete source:
net movement = total inflow − total outflow
expected close = independent opening + net movement
local residual = independent reported close − expected close
Include all selected in-window movements of the chosen currency. Missing references, blocked reference buckets and unmatched records do not disappear. Other supported currencies are counted and excluded explicitly; their amounts are never added to the chosen currency.
The canonical corrected evidence is:
| USD cents | Internal | External |
|---|---|---|
| Independent opening | 10000 | 10000 |
| Total inflow | 6200 | 6200 |
| Total outflow | 1900 | 1200 |
| Net movement | 4300 | 5000 |
| Expected close | 14300 | 15000 |
| Independent reported close | 14300 | 15000 |
| Local residual | 0 | 0 |
Internal net includes +5000, −1200, +1200 reversal and −700. External net includes +5000, +1200 reversal and −1200 after the external missing-reference row was withdrawn. Both local residuals are zero. Raw closing difference, internal minus external, is −700.
That is a cross-source disagreement between two internally consistent bridges. No matching count changes it.
Align revised movements with revised controls
Immediately before the 22 October 09:00 receipt, external evidence still includes the 1100 outflow and the missing-reference 700 outflow:
5000 − 1100 + 1200 − 700 = 4400
10000 + 4400 = 14400
At that K, the selected external closing control is its earlier 14400 version. The later 15000 version is not yet available.
At the receipt, the whole external source revision group enters and the independently supplied later control becomes visible. The booked outflow becomes 1200 and the missing-reference external row is withdrawn. Now external net is 5000 and reported close is 15000.
This is a synthetic chronology that deliberately aligns two independent observations. The implementation does not infer one control from the other history. If a source revision arrived before its corresponding control, a residual or missing-evidence result would remain visible.
Explain a difference with a row, not a plug
An investigated reconciling item identifies a complete current selected row reference: source, row, revision, sequence and source-version reference. It contains a reason and availability receipt. Its signed amount is derived from the movement.
It cannot supply an arbitrary residual amount. A stale, withdrawn, out-of-window or wrong-currency row is not valid current explanation evidence. A row cannot be reused by two items. Host authorization and investigation quality remain trusted preconditions; a matching score does not establish them.
Let R_I be signed internal item total and R_E signed external item total:
adjusted internal close = internal close + R_E − R_I
adjusted residual = adjusted internal close − external close
For internal-only outflow I-MISSING, the derived signed amount is −700:
14300 + 0 − (−700) = 15000
15000 − 15000 = 0
The arithmetic is explained. The investigated item is still unresolved. The aggregate result is indeterminate / RECONCILING_ITEMS_REMAIN, not a clean pass. No journal was posted and no suspense item was cleared.
A raw opening discrepancy also remains visible. If internal opening were 9300 and external opening 10000, a current-period −700 item could make adjusted closing balances agree while concealing an older difference. This contract explicitly fails the nonzero opening difference.
Keep outcome layers separate
| Layer | Pass condition | Why it can remain unknown or fail |
|---|---|---|
| Source evidence | Complete declared source log under K/window | Missing tail, observation beyond export, unsupported scope |
| Control evidence | Complete history with selected opening/closing upserts | Missing, future or withdrawn latest control |
| Local bridge | Reported close equals opening plus every movement | Nonzero local residual |
| Cross-source comparison | Equal raw openings and explained closing comparison | Opening discrepancy or unexplained adjusted residual |
| Overall proof | Both complete bridges, equal controls, no unresolved items | Any missing evidence or unresolved investigation prevents clean pass |
The precedence preserves known numeric failure: structural rejection first; then known bridge failure, opening discrepancy and adjusted closing discrepancy; then missing evidence; then unresolved items; finally a clean pass.
Unknown quantities are null, never zero. A trace is returned only for an independently complete side. Its running balance follows selected-version observer sequence, an audit order rather than a claim about intraday availability.
Implement the four-argument API
The Python entry point is:
result = balance_proof(
case["query"],
case["internal_groups"],
case["external_groups"],
case["context"],
)
TypeScript uses balanceProof with the same wire arguments. Each module embeds an unchanged pinned selection core and a local proof extension. It has no sibling topic runtime dependency. The proof can validate sides independently without manufacturing an opposing trusted head.
Output retains per-side source evidence, selected control versions, both bridges, raw/adjusted comparison and item diagnostics. Money and sequence arithmetic use Python integers or TypeScript BigInt. JSON carries canonical strings. No monetary tolerance, rounding or generic zero floor is introduced.
A structural rejection returns no arithmetic result. An opposite evidence gap may coexist with an independently known side failure, and that failure remains the overall result.
Explore the guided proof lab
Open the balance-proof lab. It begins with the explained-but-unresolved canonical preview. Begin replays the source and control receipts together under K; Step crosses the atomic revision boundary.
Compare the no-register case, a clean identical bridge, wrong closing, hidden opening discrepancy, an opposite source gap, withdrawn control, omitted control tail, negative balances, huge exact controls, equal-net distinct histories and deliberately forged trusted claims. A copied closing-control comparison changes only a labeled local hypothetical variant.
The workflow keeps every decision grounded in available evidence:
Verify arithmetic and its trust boundary
The 65 independently authored financial/status cases preceded the proof core. Tests assert their literal amounts, residuals, classifications and selected control versions; compare complete outputs across Python and TypeScript; and run all 105 complete shared selection expectations against the pinned prefix.
Additional checks cover full 512-version control chains, 513-version rejection, Unicode scalar/whitespace reasons, future-version payload hiding, input/output detachment, currency separation and seeded source/control delivery permutations. Browser QA executes the actual embedded TypeScript implementation at 390, 768 and 1440 pixels. See validation, proof data contract, independent calculations and source references.
One intentionally adversarial fixture removes a movement and supplies a forged reduced trusted head plus a matching closing control. Its arithmetic passes. That example demonstrates the limit: this algorithm cannot authenticate trusted host claims or establish real-world completeness.
A clean numeric bridge also does not prove the two sources contain identical records. Distinct inflow/outflow histories can have the same net. Source authentication, matching, investigation, posting authority and economic correctness remain separately inspectable.
Continue with trial-balance validation for account-level debit/credit controls, or revisit suspense-account resolution when an investigated item needs authorized posting. Explaining a difference does not perform that posting.
ReferencesPrimary sources and evidence notesExpand the source trail, evidence role, and limitations behind the engineering choices.
Expand the source trail, evidence role, and limitations behind the engineering choices.
Sources accessed 2026-10-25. All monetary examples and control histories are synthetic.
SWIFT CGI-MP bank-to-customer reporting
Source: CGI-MP WG2 ISO 20022 camt.05x.001.08 best practices.
Version: v1.1, August 2023; printed page 27 (PDF page index 26).
Accessed: 2026-10-25.
Supports: whole-statement opening/closing booked controls differ from interim page controls; page bridges link interim balances and booked items.
Limitations: market-practice profile, not this normalized UTC API or a claim that every message/provider uses our boundary choices. The tutorial does not parse XML or reproduce its example transaction tables.
Adyen reconcile balances
Source: Reconcile balances, sections Reports and Final balance reconciliation.
Version: live official documentation checked on access date.
Accessed: 2026-10-25.
Supports: opening/incoming/outgoing/ending bridge and report-specific booking/value-date and timezone distinctions.
Limitations: Adyen-specific. Our booked UTC profile is not an adapter for its value-date report or CET/CEST reporting boundaries. Provider rows are not copied.
BIL credit/debit and reversal context
Source: BIL camt.053.001.02 Implementation Guidelines.
Version: v2.1, 2023-08-08; pages 58–59.
Accessed: 2026-10-25.
Supports: separate booked entry direction and reversal indicator; selected account-owner mapping retains the already-directed reversal movement.
Limitations: provider/version-specific, not a universal sign normalizer or full parser.
Local independent specification
Source: Proof contract, source contract, claim ledger, independent calculations, proof fixtures.
Version: d30-balance-proof-v1, frozen 2026-10-25.
Accessed: 2026-10-25.
Supports: strict wire choices, independent declared expected amounts/statuses, source/control provenance and pass/fail/indeterminate precedence.
Limitations: synthetic bounded implementation evidence. Cross-language agreement is not external financial validation or source authentication.
Full dependency-light reference implementations in both supported languages.
/** Conservative exact proposals; no parser, source authentication or posting. */
type Obj=Record<string,any>;
const QUERY=['schema_version','ledger_id','entity_id','reconciliation_account_id','period_start','period_end','knowledge_cutoff','reference_policy'];
const HEAD=['schema_version','source_id','side','ledger_id','entity_id','reconciliation_account_id','coverage_start','coverage_end','observed_at','head_sequence','receipt_ref'];
const GROUP=['schema_version','group_id','source_id','available_at','events'];
const EVENT=['sequence','row_id','revision','supersedes_revision','source_version_ref','action','movement'];
const MOVEMENT=['booking_status','booking_date','value_date','currency','direction','amount_minor','reference','description','is_reversal'];
const IDENT=/^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/;
const POSITIVE=/^[1-9][0-9]{0,29}$/;
const NONNEGATIVE=/^(0|[1-9][0-9]{0,29})$/;
const DATE=/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
const STAMP=/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/;
const INDETERMINATE=new Set(['SOURCE_SEQUENCE_GAP','INCOMPLETE_SOURCE','KNOWLEDGE_BEYOND_SOURCE','COVERAGE_WINDOW_MISMATCH']);
export class ReconciliationError extends Error {}
function need(condition:any,code:string):asserts condition {if(!condition)throw new ReconciliationError(code)}
const object=(v:any):v is Obj=>v!==null&&typeof v==='object'&&!Array.isArray(v)&&[Object.prototype,null].includes(Object.getPrototypeOf(v));
const exact=(v:any,keys:string[]):v is Obj=>object(v)&&Object.keys(v).length===keys.length&&keys.every(k=>Object.hasOwn(v,k));
const full=(r:RegExp,s:string)=>r.exec(s)?.[0]===s;
const identifier=(v:any)=>typeof v==='string'&&full(IDENT,v);
const positive=(v:any)=>typeof v==='string'&&full(POSITIVE,v);
const nonnegative=(v:any)=>typeof v==='string'&&full(NONNEGATIVE,v);
const compare=(a:string,b:string)=>a<b?-1:a>b?1:0;
const compareInteger=(a:string,b:string)=>BigInt(a)<BigInt(b)?-1:BigInt(a)>BigInt(b)?1:0;
function civil(v:any):number|null {
if(typeof v!=='string'||!full(DATE,v)||v.slice(0,4)==='0000')return null;
const d=new Date(v+'T00:00:00.000Z');
return Number.isFinite(d.getTime())&&d.toISOString().slice(0,10)===v?d.getTime():null;
}
function timestamp(v:any):boolean {
if(typeof v!=='string'||!full(STAMP,v)||v.slice(0,4)==='0000')return false;
const d=new Date(v);return Number.isFinite(d.getTime())&&d.toISOString()===v;
}
function same(a:any,b:any):boolean {
if(Array.isArray(a))return Array.isArray(b)&&a.length===b.length&&a.every((v,i)=>same(v,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 stable(v:any):string {
if(Array.isArray(v))return '['+v.map(stable).join(',')+']';
if(object(v))return '{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+stable(v[k])).join(',')+'}';
return JSON.stringify(v);
}
function validateMovement(m:any,available:string){
need(exact(m,MOVEMENT),'INVALID_SCHEMA');
need(m.booking_status==='booked','UNSUPPORTED_BOOKING_STATUS');
need(civil(m.booking_date)!==null&&civil(m.value_date)!==null,'INVALID_DATE');
need(m.booking_date<=available.slice(0,10),'FUTURE_BOOKING_DATE');
need(typeof m.currency==='string'&&['USD','EUR'].includes(m.currency),'UNSUPPORTED_CURRENCY');
need(typeof m.direction==='string'&&['inflow','outflow'].includes(m.direction),'INVALID_DIRECTION');
need(positive(m.amount_minor),'INVALID_AMOUNT');need(typeof m.is_reversal==='boolean','INVALID_REVERSAL_FLAG');
need(typeof m.description==='string'&&[...m.description].length<=240&&[...m.description].every(c=>{const n=c.codePointAt(0)!;return !(n<32||(n>=127&&n<=159)||(n>=0xd800&&n<=0xdfff))}),'INVALID_DESCRIPTION');
if(m.reference!==null){const r=m.reference;need(exact(r,['namespace','value']),'INVALID_REFERENCE');need(identifier(r.namespace),'INVALID_REFERENCE');need(typeof r.value==='string'&&r.value.length<=140&&[...r.value].every(c=>c.codePointAt(0)!>=32&&c.codePointAt(0)!<=126),'INVALID_REFERENCE')}
}
function validateEvent(e:any,available:string){
need(exact(e,EVENT),'INVALID_SCHEMA');need(positive(e.sequence),'INVALID_SEQUENCE');
need(identifier(e.row_id)&&identifier(e.source_version_ref),'INVALID_IDENTIFIER');
need(positive(e.revision),'INVALID_REVISION');
need(e.supersedes_revision===null||positive(e.supersedes_revision),'INVALID_REVISION');
need(typeof e.action==='string'&&['upsert','withdraw'].includes(e.action),'INVALID_REVISION_ACTION');
if(e.action==='upsert')validateMovement(e.movement,available);else need(e.movement===null,'INVALID_REVISION_ACTION');
}
function groupErrors(groups:any[],head:Obj){
const errors:string[]=[];
for(const g of groups){try{
need(exact(g,GROUP),'INVALID_GROUP');need(identifier(g.group_id)&&identifier(g.source_id),'INVALID_IDENTIFIER');
need(g.schema_version==='d30-reconciliation-group-v1','UNSUPPORTED_SCHEMA');need(g.source_id===head.source_id,'SOURCE_MISMATCH');
need(timestamp(g.available_at),'INVALID_TIMESTAMP');need(g.available_at<=head.observed_at,'CLOCK_ORDER_VIOLATION');
need(Array.isArray(g.events)&&g.events.length>=1&&g.events.length<=32,'INVALID_GROUP');
}catch(error){if(error instanceof ReconciliationError)errors.push(error.message);else throw error}}
if(errors.length)throw new ReconciliationError(errors.sort()[0]);
}
function rowReference(head:Obj,event:Obj):Obj {
return {source_id:head.source_id,row_id:event.row_id,revision:event.revision,sequence:event.sequence,source_version_ref:event.source_version_ref};
}
function selectedRow(head:Obj,group:Obj,event:Obj):Obj {
return {row_ref:rowReference(head,event),group_id:group.group_id,available_at:group.available_at,movement:structuredClone(event.movement)};
}
function selectSource(query:Obj,groups:any,head:Obj):[Obj[],Obj[],string,Obj]{
need(Array.isArray(groups)&&groups.length<=4096,'INVALID_GROUP');groupErrors(groups,head);
need(groups.reduce((n,g)=>n+g.events.length,0)<=4096,'INVALID_GROUP');
const errors:string[]=[];
for(const g of groups)for(const event of g.events){try{validateEvent(event,g.available_at)}catch(error){if(error instanceof ReconciliationError)errors.push(error.message);else throw error}}
if(errors.length)throw new ReconciliationError(errors.sort()[0]);
const grouped=new Map<string,Obj[]>();
for(const g of groups){const canonical={...g,events:[...g.events].sort((a,b)=>compareInteger(a.sequence,b.sequence)||compare(stable(a),stable(b)))};const list=grouped.get(g.group_id)??[];list.push(canonical);grouped.set(g.group_id,list)}
need(![...grouped.values()].some(items=>items.some(g=>!same(g,items[0]))),'GROUP_ID_CONFLICT');
need(![...grouped.values()].some(items=>items.length>1),'DUPLICATE_GROUP');
const identities=new Map<string,Obj[]>();
for(const g of groups)for(const e of g.events){const key=JSON.stringify([e.row_id,e.revision]),list=identities.get(key)??[];list.push(e);identities.set(key,list)}
need(![...identities.values()].some(items=>items.some(e=>!same(e,items[0]))),'EVENT_ID_CONFLICT');
need(![...identities.values()].some(items=>items.length>1),'DUPLICATE_EVENT');
const events:Obj[]=groups.flatMap(g=>g.events);
need(new Set(events.map(e=>e.sequence)).size===events.length,'SEQUENCE_ID_CONFLICT');
for(const g of groups)need(new Set(g.events.map((e:Obj)=>e.row_id)).size===g.events.length,'DUPLICATE_ROW_IN_GROUP');
const sequences=events.map(e=>BigInt(e.sequence)).sort((a,b)=>a<b?-1:a>b?1:0),headSequence=BigInt(head.head_sequence);
need(sequences.length===0||sequences.at(-1)!<=headSequence,'HEAD_SEQUENCE_MISMATCH');
need(sequences.every((n,i)=>n===BigInt(i+1)),'SOURCE_SEQUENCE_GAP');
need(BigInt(sequences.length)===headSequence,'INCOMPLETE_SOURCE');
const canonical=[...grouped.values()].map(items=>items[0]).sort((a,b)=>compareInteger(a.events[0].sequence,b.events[0].sequence));
for(const g of canonical)need(BigInt(g.events.at(-1).sequence)-BigInt(g.events[0].sequence)+1n===BigInt(g.events.length),'GROUP_INTERVAL_CONFLICT');
let lastAvailable:string|null=null,visibleThrough='0';
const latestAll=new Map<string,[Obj,Obj]>(),latestVisible=new Map<string,[Obj,Obj]>();
for(const g of canonical){
need(lastAvailable===null||g.available_at>=lastAvailable,'CLOCK_ORDER_VIOLATION');lastAvailable=g.available_at;
for(const e of g.events){
const previous=latestAll.get(e.row_id);
if(head.side==='internal')need(e.revision==='1'&&e.supersedes_revision===null&&e.action==='upsert'&&previous===undefined,'INTERNAL_ROW_IMMUTABLE');
else if(previous===undefined){need(e.revision==='1'&&e.supersedes_revision===null,'INVALID_REVISION');need(e.action==='upsert','INVALID_REVISION_ACTION')}
else{const prior=previous[1];need(BigInt(e.revision)===BigInt(prior.revision)+1n&&e.supersedes_revision===prior.revision,'INVALID_REVISION');if(e.action==='withdraw')need(prior.action==='upsert','INVALID_REVISION_ACTION')}
latestAll.set(e.row_id,[g,e]);if(g.available_at<=query.knowledge_cutoff)latestVisible.set(e.row_id,[g,e]);
}
if(g.available_at<=query.knowledge_cutoff)visibleThrough=g.events.at(-1).sequence;
}
const active:Obj[]=[],withdrawn:Obj[]=[];let outside=0;
for(const [rowId,[g,e]]of [...latestVisible.entries()].sort((a,b)=>compare(a[0],b[0]))){
const row=selectedRow(head,g,e);
if(e.action==='withdraw'){delete row.movement;withdrawn.push(row)}
else if(e.movement.booking_date>=query.period_start&&e.movement.booking_date<query.period_end)active.push(row);
else outside++;
}
const coverage:Obj={};for(const k of ['source_id','receipt_ref','observed_at','coverage_start','coverage_end','head_sequence'])coverage[k]=head[k];
coverage.validated_through_sequence=head.head_sequence;coverage.visible_through_sequence=visibleThrough;
return[active,withdrawn,String(outside),coverage];
}
export function selectReconciliationRows(query:any,internalGroups:any,externalGroups:any,context:any,expectedSchema='d30-exact-reference-match-v1'):Obj {
try{
need(exact(query,QUERY),'INVALID_SCHEMA');need(query.schema_version===expectedSchema,'UNSUPPORTED_SCHEMA');
need(['ledger_id','entity_id','reconciliation_account_id'].every(k=>identifier(query[k])),'INVALID_IDENTIFIER');
const start=civil(query.period_start),end=civil(query.period_end);need(start!==null&&end!==null,'INVALID_DATE');
need((end-start)/86400000>=1&&(end-start)/86400000<=366,'INVALID_PERIOD');
need(timestamp(query.knowledge_cutoff),'INVALID_TIMESTAMP');
need(query.knowledge_cutoff>=query.period_end+'T00:00:00.000Z','KNOWLEDGE_BEFORE_PERIOD_END');
need(query.reference_policy==='BYTE_EXACT_ASCII_V1','UNSUPPORTED_REFERENCE_POLICY');
need(exact(context,['internal_head','external_head']),'INVALID_CONTEXT');
for(const side of ['internal','external']){
const h=context[side+'_head'];need(exact(h,HEAD),'INVALID_CONTEXT');
need(h.schema_version==='d30-reconciliation-head-v1'&&h.side===side,'INVALID_CONTEXT');
need(['source_id','ledger_id','entity_id','reconciliation_account_id','receipt_ref'].every(k=>identifier(h[k])),'INVALID_CONTEXT');
need(['ledger_id','entity_id','reconciliation_account_id'].every(k=>h[k]===query[k]),'SCOPE_MISMATCH');
const a=civil(h.coverage_start),b=civil(h.coverage_end);need(a!==null&&b!==null&&a<b,'INVALID_CONTEXT');
need(timestamp(h.observed_at)&&nonnegative(h.head_sequence),'INVALID_CONTEXT');
need(h.coverage_start<=query.period_start&&h.coverage_end>=query.period_end,'COVERAGE_WINDOW_MISMATCH');
need(query.knowledge_cutoff<=h.observed_at,'KNOWLEDGE_BEYOND_SOURCE');
}
need(context.internal_head.source_id!==context.external_head.source_id,'INVALID_CONTEXT');
const selected:Obj={internal:[],external:[],withdrawn:{internal:[],external:[]},out_of_window_counts:{internal:'0',external:'0'}},coverage:Obj={};
for(const [side,groups]of [['internal',internalGroups],['external',externalGroups]]as const){const [active,withdrawn,count,cov]=selectSource(query,groups,context[side+'_head']);selected[side]=active;selected.withdrawn[side]=withdrawn;selected.out_of_window_counts[side]=count;coverage[side]=cov}
return{status:'ok',code:'RECONCILIATION_ROWS_SELECTED',coverage,selected};
}catch(error){if(error instanceof ReconciliationError)return{status:INDETERMINATE.has(error.message)?'indeterminate':'rejected',code:error.message,coverage:null,selected:null};throw error}
}
export function referenceKey(reference:any):[string,string]|null {
if(reference===null||reference.value.replace(/ /g,'')===''||reference.value==='NOTPROVIDED')return null;
return[reference.namespace,reference.value];
}
export function exactReferenceMatch(query:any,internalGroups:any,externalGroups:any,context:any):Obj {
const result=selectReconciliationRows(query,internalGroups,externalGroups,context);
if(result.status!=='ok')return{...result,matches:[],unmatched:[],ambiguous:[]};
const buckets=new Map<string,{key:[string,string];internal:Obj[];external:Obj[]}>(),unmatched:Obj[]=[];
for(const side of ['internal','external']as const)for(const row of result.selected[side]){
const key=referenceKey(row.movement.reference);
if(key===null)unmatched.push({side,row_ref:structuredClone(row.row_ref),reason:'MISSING_REFERENCE',mismatches:[]});
else{const encoded=JSON.stringify(key),bucket=buckets.get(encoded)??{key,internal:[],external:[]};bucket[side].push(row);buckets.set(encoded,bucket)}
}
const matches:Obj[]=[],ambiguous:Obj[]=[];
for(const bucket of [...buckets.values()].sort((a,b)=>compare(a.key[0],b.key[0])||compare(a.key[1],b.key[1]))){
const [namespace,value]=bucket.key,left=bucket.internal,right=bucket.external;
if(left.length>1||right.length>1)ambiguous.push({reference:{namespace,value},internal_row_refs:left.map(r=>structuredClone(r.row_ref)),external_row_refs:right.map(r=>structuredClone(r.row_ref)),reason:'NON_UNIQUE_REFERENCE'});
else if(left.length===0||right.length===0){const side=left.length?'internal':'external',row=(left.length?left:right)[0];unmatched.push({side,row_ref:structuredClone(row.row_ref),reason:'NO_COUNTERPART',mismatches:[]})}
else{
const [l,r]=[left[0],right[0]],[a,b]=[l.movement,r.movement];
const fields=['currency','direction','amount_minor','is_reversal'].filter(k=>a[k]!==b[k]);
if(fields.length){for(const [side,row]of [['internal',l],['external',r]]as const)unmatched.push({side,row_ref:structuredClone(row.row_ref),reason:'FIELD_MISMATCH',mismatches:[...fields]})}
else matches.push({reference:{namespace,value},internal_row_ref:structuredClone(l.row_ref),external_row_ref:structuredClone(r.row_ref),currency:a.currency,direction:a.direction,amount_minor:a.amount_minor,is_reversal:a.is_reversal,booking_date_gap_days:(civil(b.booking_date)!-civil(a.booking_date)!)/86400000});
}
}
unmatched.sort((a,b)=>(a.side==='internal'?0:1)-(b.side==='internal'?0:1)||compare(a.row_ref.row_id,b.row_ref.row_id)||compareInteger(a.row_ref.revision,b.row_ref.revision));
return{status:'ok',code:'EXACT_MATCH_COMPLETE',coverage:result.coverage,selected:result.selected,matches,unmatched,ambiguous};
}
// Balance-proof extension. The preceding shared exact core is pinned byte-for-byte.
const PROOF_QUERY=[...QUERY,'currency','basis'];
const PROOF_CONTEXT=['internal_head','external_head','internal_controls','external_controls','reconciling_items'];
const CONTROL_HEAD=['schema_version','control_id','source_id','side','ledger_id','entity_id','reconciliation_account_id','role','currency','basis','boundary_at','head_revision','observed_at','receipt_ref'];
const CONTROL_VERSION=['schema_version','control_id','revision','supersedes_revision','available_at','source_version_ref','action','amount_minor'];
const ITEM=['item_id','side','row_ref','reason','available_at'];
const ROW_REF=['source_id','row_id','revision','sequence','source_version_ref'];
const SIGNED_CONTROL=/^(0|-?[1-9][0-9]{0,119})$/;
const PYTHON_SPACE=new Set([9,10,11,12,13,28,29,30,31,32,133,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288]);
function proofQueryHeads(query:any,context:any):Obj {
need(exact(query,PROOF_QUERY),'INVALID_SCHEMA');need(query.schema_version==='d30-balance-proof-v1','UNSUPPORTED_SCHEMA');
need(typeof query.currency==='string'&&['USD','EUR'].includes(query.currency),'UNSUPPORTED_CURRENCY');
need(query.basis==='booked-cash-asset-utc-v1','UNSUPPORTED_BALANCE_BASIS');
need(['ledger_id','entity_id','reconciliation_account_id'].every(k=>identifier(query[k])),'INVALID_IDENTIFIER');
const start=civil(query.period_start),end=civil(query.period_end);need(start!==null&&end!==null,'INVALID_DATE');
need((end-start)/86400000>=1&&(end-start)/86400000<=366,'INVALID_PERIOD');need(timestamp(query.knowledge_cutoff),'INVALID_TIMESTAMP');
need(query.knowledge_cutoff>=query.period_end+'T00:00:00.000Z','KNOWLEDGE_BEFORE_PERIOD_END');
need(query.reference_policy==='BYTE_EXACT_ASCII_V1','UNSUPPORTED_REFERENCE_POLICY');need(exact(context,PROOF_CONTEXT),'INVALID_CONTEXT');
const gaps:Obj={};
for(const side of ['internal','external']){
const h=context[side+'_head'];need(exact(h,HEAD),'INVALID_CONTEXT');need(h.schema_version==='d30-reconciliation-head-v1'&&h.side===side,'INVALID_CONTEXT');
need(['source_id','ledger_id','entity_id','reconciliation_account_id','receipt_ref'].every(k=>identifier(h[k])),'INVALID_CONTEXT');
need(['ledger_id','entity_id','reconciliation_account_id'].every(k=>h[k]===query[k]),'SCOPE_MISMATCH');
const a=civil(h.coverage_start),b=civil(h.coverage_end);need(a!==null&&b!==null&&a<b,'INVALID_CONTEXT');
need(timestamp(h.observed_at)&&nonnegative(h.head_sequence),'INVALID_CONTEXT');
if(!(h.coverage_start<=query.period_start&&h.coverage_end>=query.period_end))gaps[side]='COVERAGE_WINDOW_MISMATCH';
else if(query.knowledge_cutoff>h.observed_at)gaps[side]='KNOWLEDGE_BEYOND_SOURCE';
}
need(context.internal_head.source_id!==context.external_head.source_id,'INVALID_CONTEXT');return gaps;
}
function proofSource(query:Obj,groups:any,head:Obj,gap:string|undefined):Obj {
try{
const [active,withdrawn,count,coverage]=selectSource(query,groups,head);
if(gap)return{status:'indeterminate',code:gap,coverage:null,selected:null};
return{status:'ok',code:'SOURCE_SELECTED',coverage,selected:{active,withdrawn,out_of_window_count:count}};
}catch(error){if(!(error instanceof ReconciliationError)||!INDETERMINATE.has(error.message))throw error;return{status:'indeterminate',code:gap??error.message,coverage:null,selected:null}}
}
function controlSelection(query:Obj,sourceHead:Obj,side:string,role:string,bundle:any):Obj {
need(exact(bundle,['head','versions']),'INVALID_CONTROL_SCHEMA');const h=bundle.head,versions=bundle.versions;
need(exact(h,CONTROL_HEAD),'INVALID_CONTROL_HEAD');need(h.schema_version==='d30-balance-control-head-v1','INVALID_CONTROL_HEAD');
need(['control_id','source_id','ledger_id','entity_id','reconciliation_account_id','receipt_ref'].every(k=>identifier(h[k])),'INVALID_CONTROL_HEAD');
need(h.side===side&&h.role===role&&h.source_id===sourceHead.source_id&&['ledger_id','entity_id','reconciliation_account_id','currency','basis'].every(k=>h[k]===query[k]),'CONTROL_SCOPE_MISMATCH');
need(timestamp(h.boundary_at)&×tamp(h.observed_at),'INVALID_CONTROL_TIMESTAMP');
const boundary=query[role==='opening'?'period_start':'period_end']+'T00:00:00.000Z';
need(h.boundary_at===boundary,'CONTROL_BOUNDARY_MISMATCH');need(h.observed_at>=boundary,'CONTROL_CLOCK_ORDER_VIOLATION');
need(nonnegative(h.head_revision)&&BigInt(h.head_revision)<=512n,'INVALID_CONTROL_HEAD');
need(Array.isArray(versions)&&versions.length<=512,'INVALID_CONTROL_SCHEMA');const errors:string[]=[];
for(const v of versions){
try{
need(exact(v,CONTROL_VERSION),'INVALID_CONTROL_SCHEMA');need(v.schema_version==='d30-balance-control-v1','INVALID_CONTROL_SCHEMA');
need(v.control_id===h.control_id,'CONTROL_SCOPE_MISMATCH');
need(positive(v.revision)&&BigInt(v.revision)<=512n&&(v.supersedes_revision===null||(positive(v.supersedes_revision)&&BigInt(v.supersedes_revision)<=512n)),'INVALID_CONTROL_REVISION');
need(identifier(v.source_version_ref),'INVALID_CONTROL_SCHEMA');need(timestamp(v.available_at),'INVALID_CONTROL_TIMESTAMP');
need(boundary<=v.available_at&&v.available_at<=h.observed_at,'CONTROL_CLOCK_ORDER_VIOLATION');
need(typeof v.action==='string'&&['upsert','withdraw'].includes(v.action),'INVALID_CONTROL_ACTION');
if(v.action==='upsert')need(typeof v.amount_minor==='string'&&full(SIGNED_CONTROL,v.amount_minor),'INVALID_CONTROL_AMOUNT');
else need(v.amount_minor===null,'INVALID_CONTROL_ACTION');
}catch(error){if(error instanceof ReconciliationError)errors.push(error.message);else throw error}
}
if(errors.length)throw new ReconciliationError(errors.sort(compare)[0]);
const identities=new Map<string,Obj[]>();for(const v of versions)identities.set(v.revision,[...(identities.get(v.revision)??[]),v]);
need(![...identities.values()].some(items=>items.some(v=>!same(v,items[0]))),'CONTROL_VERSION_CONFLICT');
need(![...identities.values()].some(items=>items.length>1),'DUPLICATE_CONTROL_VERSION');
const ordered:Obj[]=[...versions].sort((a,b)=>compareInteger(a.revision,b.revision));
need(!ordered.length||BigInt(ordered.at(-1)!.revision)<=BigInt(h.head_revision),'CONTROL_HEAD_MISMATCH');
let last:string|null=null;for(const v of ordered){need(last===null||v.available_at>=last,'CONTROL_CLOCK_ORDER_VIOLATION');last=v.available_at}
if(ordered.length!==Number(h.head_revision)||ordered.some((v,i)=>BigInt(v.revision)!==BigInt(i+1)))return{status:'indeterminate',code:'INCOMPLETE_CONTROL_HISTORY',coverage:null,selected:null};
let prior:Obj|null=null;for(const v of ordered){need(v.supersedes_revision===(prior===null?null:prior.revision),'INVALID_CONTROL_REVISION');need(!(v.action==='withdraw'&&(prior===null||prior.action==='withdraw')),'INVALID_CONTROL_ACTION');prior=v}
if(query.knowledge_cutoff>h.observed_at)return{status:'indeterminate',code:'KNOWLEDGE_BEYOND_CONTROL',coverage:null,selected:null};
const visible=ordered.filter(v=>v.available_at<=query.knowledge_cutoff),selected=visible.length?structuredClone(visible.at(-1)!):null;
const coverage:Obj={};for(const k of ['control_id','source_id','receipt_ref','observed_at','head_revision'])coverage[k]=h[k];
Object.assign(coverage,{validated_through_revision:h.head_revision,visible_through_revision:visible.length?visible.at(-1)!.revision:'0'});
const code=!ordered.length?'CONTROL_MISSING':selected===null?'CONTROL_NOT_AVAILABLE':selected.action==='withdraw'?'CONTROL_WITHDRAWN':'CONTROL_SELECTED';
return{status:code==='CONTROL_SELECTED'?'ok':'indeterminate',code,coverage,selected};
}
function validateItemRegister(items:any):void {
need(Array.isArray(items)&&items.length<=512,'INVALID_RECONCILING_ITEM');
for(const item of items){
need(exact(item,ITEM),'INVALID_RECONCILING_ITEM');need(identifier(item.item_id)&&typeof item.side==='string'&&['internal','external'].includes(item.side),'INVALID_RECONCILING_ITEM');
const ref=item.row_ref;need(exact(ref,ROW_REF)&&['source_id','row_id','source_version_ref'].every(k=>identifier(ref[k]))&&positive(ref.revision)&&positive(ref.sequence),'INVALID_RECONCILING_ITEM');
const reason=item.reason;
need(typeof reason==='string'&&[...reason].length>=1&&[...reason].length<=240&&[...reason].some(c=>!PYTHON_SPACE.has(c.codePointAt(0)!))&&[...reason].every(c=>{const n=c.codePointAt(0)!;return !(n<32||(n>=127&&n<=159)||(n>=0xd800&&n<=0xdfff))}),'INVALID_RECONCILING_ITEM');
need(timestamp(item.available_at),'INVALID_RECONCILING_ITEM');
}
const identities=new Map<string,Obj[]>();for(const item of items)identities.set(item.item_id,[...(identities.get(item.item_id)??[]),item]);
need(![...identities.values()].some(values=>values.some(item=>!same(item,values[0]))),'RECONCILING_ITEM_CONFLICT');
need(![...identities.values()].some(values=>values.length>1),'DUPLICATE_RECONCILING_ITEM');
const rows=items.map(i=>JSON.stringify([i.side,i.row_ref.source_id,i.row_ref.row_id]));need(new Set(rows).size===rows.length,'RECONCILING_ROW_REUSED');
}
function resolveItems(query:Obj,items:Obj[],sources:Obj):Obj[] {
return [...items].sort((a,b)=>compare(a.item_id,b.item_id)).map(item=>{
const source=sources[item.side],row=source.status==='ok'?source.selected.active.find((r:Obj)=>same(r.row_ref,item.row_ref)):undefined;
let code:string;
if(source.status!=='ok')code='RECONCILING_SOURCE_INDETERMINATE';
else if(!row)code='STALE_RECONCILING_EVIDENCE';
else if(item.available_at<row.available_at)throw new ReconciliationError('ITEM_CLOCK_ORDER_VIOLATION');
else if(item.available_at>query.knowledge_cutoff)code='RECONCILING_ITEM_NOT_AVAILABLE';
else if(row.movement.currency!==query.currency)code='RECONCILING_CURRENCY_MISMATCH';
else code='RECONCILING_ITEM_RETAINED';
const amount=code==='RECONCILING_ITEM_RETAINED'?(BigInt(row.movement.amount_minor)*(row.movement.direction==='inflow'?1n:-1n)).toString():null;
return{...structuredClone(item),status:amount!==null?'ok':'indeterminate',code,signed_amount_minor:amount};
});
}
function bridge(query:Obj,side:string,source:Obj,controls:Obj):Obj {
const numeric=['opening_control_ref','closing_control_ref','opening_minor','inflow_minor','outflow_minor','net_movement_minor','expected_closing_minor','reported_closing_minor','residual_minor','excluded_currency_count'];
if(source.status!=='ok'||['opening','closing'].some(r=>controls[r].status!=='ok'))return{status:'indeterminate',code:'SIDE_EVIDENCE_INDETERMINATE',currency:query.currency,...Object.fromEntries(numeric.map(k=>[k,null])),trace:[]};
const opening=controls.opening.selected,closing=controls.closing.selected;
const ref=(v:Obj)=>({source_id:controls.opening.coverage.source_id,control_id:v.control_id,revision:v.revision,source_version_ref:v.source_version_ref});
const rows:Obj[]=source.selected.active,chosen=rows.filter(r=>r.movement.currency===query.currency).sort((a,b)=>compareInteger(a.row_ref.sequence,b.row_ref.sequence));
let run=BigInt(opening.amount_minor),inflow=0n,outflow=0n;const trace:Obj[]=[];
for(const row of chosen){
const m=row.movement,amount=BigInt(m.amount_minor),signed=m.direction==='inflow'?amount:-amount;
if(signed>0n)inflow+=amount;else outflow+=amount;run+=signed;
trace.push({row_ref:structuredClone(row.row_ref),group_id:row.group_id,available_at:row.available_at,booking_date:m.booking_date,direction:m.direction,is_reversal:m.is_reversal,amount_minor:m.amount_minor,signed_amount_minor:signed.toString(),running_balance_minor:run.toString()});
}
const residual=BigInt(closing.amount_minor)-run;
return{status:residual===0n?'pass':'fail',code:residual===0n?'BRIDGE_PASS':'BRIDGE_RESIDUAL',currency:query.currency,opening_control_ref:ref(opening),closing_control_ref:ref(closing),opening_minor:opening.amount_minor,inflow_minor:inflow.toString(),outflow_minor:outflow.toString(),net_movement_minor:(inflow-outflow).toString(),expected_closing_minor:run.toString(),reported_closing_minor:closing.amount_minor,residual_minor:residual.toString(),excluded_currency_count:String(rows.length-chosen.length),trace};
}
function proofComparison(query:Obj,sources:Obj,controls:Obj,items:Obj[]):Obj {
const amount=(side:string,role:string):bigint|null=>controls[side][role].status==='ok'?BigInt(controls[side][role].selected.amount_minor):null;
const io=amount('internal','opening'),eo=amount('external','opening'),ic=amount('internal','closing'),ec=amount('external','closing');
const opening=io!==null&&eo!==null?io-eo:null,closing=ic!==null&&ec!==null?ic-ec:null;
const completeItems=Object.values(sources).every(s=>s.status==='ok')&&items.every(i=>i.status==='ok');
const sum=(side:string)=>items.filter(i=>i.side===side).reduce((n,i)=>n+BigInt(i.signed_amount_minor),0n);
const left=completeItems?sum('internal'):null,right=completeItems?sum('external'):null;
const adjusted=ic!==null&&ec!==null&&completeItems?ic+right!-left!:null,residual=adjusted!==null?adjusted-ec!:null;
const missing=!completeItems||['internal','external'].some(s=>['opening','closing'].some(r=>controls[s][r].status!=='ok'));
let status:string,code:string;
if(opening!==null&&opening!==0n){status='fail';code='OPENING_BALANCES_DIFFER'}
else if(residual!==null&&residual!==0n){status='fail';code='CLOSING_BALANCES_DIFFER'}
else if(missing){status='indeterminate';code='COMPARISON_INDETERMINATE'}
else if(items.length){status='indeterminate';code='RECONCILING_ITEMS_REMAIN'}
else{status='pass';code='CONTROLS_AGREE'}
const values:Record<string,bigint|null>={opening_difference_minor:opening,closing_difference_minor:closing,internal_item_signed_minor:left,external_item_signed_minor:right,adjusted_internal_closing_minor:adjusted,adjusted_closing_residual_minor:residual};
return{status,code,...Object.fromEntries(Object.entries(values).map(([k,v])=>[k,v===null?null:v.toString()])),unresolved_item_count:String(items.length)};
}
export function balanceProof(query:any,internalGroups:any,externalGroups:any,context:any):Obj {
try{
const gaps=proofQueryHeads(query,context),sources:Obj={},controls:Obj={},errors:string[]=[];
for(const [side,groups]of [['internal',internalGroups],['external',externalGroups]]as const){
try{sources[side]=proofSource(query,groups,context[side+'_head'],gaps[side])}catch(error){if(error instanceof ReconciliationError)errors.push(error.message);else throw error}
const bundle=context[side+'_controls'];if(!exact(bundle,['opening','closing'])){errors.push('INVALID_CONTROL_SCHEMA');continue}
controls[side]={};for(const role of ['opening','closing']){try{controls[side][role]=controlSelection(query,context[side+'_head'],side,role,bundle[role])}catch(error){if(error instanceof ReconciliationError)errors.push(error.message);else throw error}}
if(!errors.length)need(bundle.opening.head.control_id!==bundle.closing.head.control_id,'CONTROL_SCOPE_MISMATCH');
}
try{validateItemRegister(context.reconciling_items)}catch(error){if(error instanceof ReconciliationError)errors.push(error.message);else throw error}
if(errors.length)throw new ReconciliationError(errors.sort(compare)[0]);
const items=resolveItems(query,context.reconciling_items,sources),bridges:Obj={};for(const s of ['internal','external'])bridges[s]=bridge(query,s,sources[s],controls[s]);
const comparison=proofComparison(query,sources,controls,items);let status:string,code:string;
if(Object.values(bridges).some(b=>b.status==='fail')){status='fail';code='SIDE_BRIDGE_FAILED'}
else if(comparison.status==='fail'){status='fail';code=comparison.code}
else if(Object.values(sources).some(s=>s.status!=='ok')||['internal','external'].some(s=>['opening','closing'].some(r=>controls[s][r].status!=='ok'))||items.some(i=>i.status!=='ok')){status='indeterminate';code='EVIDENCE_INDETERMINATE'}
else if(items.length){status='indeterminate';code='RECONCILING_ITEMS_REMAIN'}
else{status='pass';code='BALANCE_PROOF_PASS'}
return{status,code,source_evidence:structuredClone(sources),control_evidence:structuredClone(controls),bridges,comparison,reconciling_items:items};
}catch(error){if(error instanceof ReconciliationError)return{status:'rejected',code:error.message,source_evidence:null,control_evidence:null,bridges:null,comparison:null,reconciling_items:[]};throw error}
}
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.
