The useful promise is simple: when a caller loses a posting response, operations staff can identify what happened and engineers can repeat the request without crediting the account again. That promise needs a retained business identity and an atomic storage boundary.
We will build a small, inspectable protocol for one fictional institution, Cedar Teaching Bank. Its code uses exact integer money and a persisted JSON snapshot model. An accompanying PostgreSQL profile separately tests real concurrent sessions. The distinction matters: a pure function can model a transaction, but it cannot make two independent processes share one commit.
Start with Double-Entry Posting Engine for exact balanced journals and Append-Only Journal Enforcement for the tested storage boundary.
Start with the result you need to preserve
Our synthetic funding instruction has business identity CB-GENERAL / CB-A / OP-FUND-1 and journal identity J-FUND-1. It debits the bank's cash asset by 100000 USD cents and credits Ada's deposit liability by the same amount. The customer deposit is the bank's liability; debit/credit effects depend on account type. GnuCash accounting concepts, Bank of England, printed p.16.
The independently derived debit and credit totals are each 100000. Raw movements are cash +100000, deposits −100000; both normal-side balances increase by 100000. The service accepts journal sequence 1, then loses the response.
The caller's timeout does not prove rollback. A retry with OP-FUND-2 would assert a second business instruction. The correct retry keeps OP-FUND-1 and the original complete command. The service finds its accepted record and returns the same journal/result. There is still one funding journal.
| Arrival | Decision | Journal count |
|---|---|---|
Original 100000-cent funding | POSTED, sequence 1 | 1 |
| Exact repeat after lost response | REPLAYED, original sequence/result | 1 |
Same business ID, balanced amount changed to 100001 | IDEMPOTENCY_CONFLICT | 1 |
| Fresh business ID but reused journal ID | JOURNAL_ID_CONFLICT | 1 |
All accounts, amounts and arrivals are synthetic. The counts and movements are author-derived expectations, not claims about observed payment traffic.
Decide what “the same instruction” means
Business identity is the tuple of ledger, entity and business-operation ID. A journal ID is another identifier, separately unique within that scope. Neither an amount nor a network request ID defines the whole business identity.
The selected command includes schema version, ledger/entity, caller journal ID, business-operation ID, currency, effective time, source-availability time, booking period and ordered legs. Every one of those fields participates in equality. Object property order is irrelevant; leg array order matters. Reordering two legs is deliberately a conflict in this profile, even if their net arithmetic is unchanged.
Open the diagram at full size. Notice that the accepted command and original result remain together. Comparing only a hash of selected fields can accidentally omit a field that changes accounting meaning. This reference stores the complete command and compares its parsed structure. It does not claim cryptographic commitment or RFC 8785 conformance.
For a command c, let K(c) be its scoped business identity and E(c) its complete semantic value. With accepted register A:
A[K(c)] exists and E(c) equals stored command → return stored result
A[K(c)] exists and E(c) differs → reject identity conflict
The equality comparison is exact. There is no monetary tolerance. A value such as 9007199254740993 is a decimal string parsed directly to Python integer or TypeScript BigInt; passing through Number can lose a unit before comparison. The independent equality case is 9007199254740000 + 993 = 9007199254740993.
Authorize first, then resolve prior acceptance
A guessed business ID must not disclose another institution's accepted result. The server first selects an authorized ledger/entity scope. A request-body ledger_id is a requested destination, not proof of permission.
Once authorized, the reducer validates the strict command syntax and looks up accepted identity. If an accepted command is found, it returns the original result before rechecking today's account status, open periods or recording clock. Ada's account may have closed after funding. That does not make the old funding outcome disappear. A fresh instruction must still pass current eligibility and period policy.
Current policy therefore governs new work while accepted history remains stable. This ordering is part of the declared implementation contract, not a universal API standard.
Keep the wire small and exact
The journal has exactly ten fields; each leg has exactly account ID, side and amount. Missing or unknown fields reject. Identifiers use a bounded ASCII grammar. Amounts are positive decimal strings with at most 30 digits; no zero, signs, exponents, decimal points, whitespace or numeric JSON coercion. There are 2–32 ordered legs referring to at least two distinct accounts.
Each journal uses one legal entity, ledger and currency. USD and EUR remain separate. Gregorian UTC timestamps have exactly three fractional digits. Effective and source-availability times cannot exceed trusted recording time for new work. Recording time is evaluated inside the transaction; it is not proof of the exact physical commit instant.
New work requires active matching accounts and an open booking period. A prior effective month with a current booking month needs an explicit trusted adjustment flag and explanation. The canonical default rejects a silent period move. The tutorial demonstrates the selected policy without making jurisdictional recognition or regulatory claims.
Make persistence visible in the reference code
The downloadable Python reference and TypeScript reference each embed their own posting validator. They need no sibling runtime imports. Both expose a pure transition returning a new state plus response.
state = new_state(accounts)
first = post_idempotent(state, command, context, trusted_scope)
saved = json.loads(json.dumps(first["state"]))
retry = post_idempotent(saved, command, context, trusted_scope)
assert retry["response"]["result"] == first["response"]["result"]
assert len(retry["state"]["journals"]) == 1
The state stores journals and accepted operation records. Each operation retains the full command and original complete result. Rejected new work creates neither a journal nor a successful-looking identity reservation. Input and returned result copies cannot mutate stored history by sharing the same nested object.
The JSON round trip demonstrates that the association is part of persisted state, not an accidental process-local cache. It still assumes trusted storage. Changing both a stored command and outcome can evade local linkage checks; independent integrity evidence is addressed later in the domain.
The host must still serialize writers
If two processes read an empty snapshot, each can locally compute sequence 1. Saving the second snapshot over the first loses work. The test suite deliberately demonstrates that limitation; it is not presented as a concurrency pass.
The actual PostgreSQL profile locks a ledger-control row, resolves identity, validates fresh work and commits journal, identity, original result and transactional counter together. Non-null composite uniqueness provides another invariant. Every writing path must participate. Database constraints are useful, but a row CHECK cannot maintain an arbitrary cross-row journal sum. PostgreSQL constraints, explicit locking.
Read Committed alone does not serialize a complex business protocol. In particular, concurrent conflict behavior needs to be understood instead of assuming one insert-or-select statement always observes the winning result. The selected ledger lock provides the explicit order in this teaching profile. PostgreSQL transaction isolation.
After a serialization failure, retry the complete transaction's deciding logic with the unchanged business identity. Do not turn an IDEMPOTENCY_CONFLICT into an endless technical retry. An acknowledged commit with a missing response and a rollback before commit are different events, and durable identity resolves both. PostgreSQL serialization failure handling.
Verify both the protocol and its limits
Run python tests/run_tests.py in the package. The shared fixture bundle covers 87 scenarios and 106 transitions, with independent expected reasons/counts and selected exact amounts. Python and TypeScript compare their entire snapshots and response objects. Coverage includes changed ordered legs and caller journal ID, large-money precision, 32-leg totals exceeding the per-leg limit, scope denial before replay, malformed wire, accepted replay after policy/clock changes, and fresh policy rejection.
The validation record separates those portable results from actual PostgreSQL sessions: identical concurrent requests, changed-payload races, first-writer rollback, discarded responses, policy changes, scope isolation and restart. Passing arithmetic in two languages is not database evidence; actual SQL sessions are not proof of independent retention or financial legitimacy.
Use the guided lab to step through response loss and compare accepted identity with arrival count. Change the retry amount to see conflict, then reset. A static fallback is the diagram and worked table above.
Historical example: not useful. A real incident would add narrative appeal while leaving the decisive payload, policy and timing evidence unavailable. Controlled synthetic fixtures are more reproducible for this mechanism.
You now have a precise question to ask of any posting service: which durable business identity binds the complete accepted instruction to its original outcome, and which transaction makes that association atomic? Continue with Correcting and Reversing Entry Algorithm to preserve a mistake while committing linked new corrective instructions.
Primary-source roles, versions and jurisdiction limits: REFERENCES.
Rendered from the canonical Mermaid sources linked by this article.
Resolve accepted identity before fresh policy
Authorize first, compare complete semantic commands, and apply current policy only to new work.
An accepted retry does not become a new instruction because the account or period later closed. Unauthorized callers cannot inspect that stored outcome.
References6 primary 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.
R01 — Constraints
- Organization: PostgreSQL Global Development Group.
- Source type/version: Official PostgreSQL 18 documentation; no page-specific effective date.
- URL: §5.5 Constraints.
- Accessed: 2026-10-24; package reviewed 2026-10-25.
- Jurisdiction: Technical platform, no banking regulatory scope.
- Supports: Composite non-null keys, uniqueness, references and limits of cross-row CHECK assertions.
- Limitations: Does not define business identity, payload meaning, approval or this transaction algorithm.
R02 — Transaction Isolation
- Organization: PostgreSQL Global Development Group.
- Source type/version: Official PostgreSQL 18 documentation; no page-specific effective date.
- URL: §13.2 Transaction Isolation.
- Accessed: 2026-10-24.
- Jurisdiction: Technical platform.
- Supports: Read Committed snapshots, concurrent ON CONFLICT behavior and explicit serializing controls.
- Limitations: An insert-conflict action alone is not an entire idempotency protocol or exactly-once guarantee.
R03 — Serialization Failure Handling
- Organization: PostgreSQL Global Development Group.
- Source type/version: Official PostgreSQL 18 documentation; no page-specific effective date.
- URL: §13.5 Serialization Failure Handling.
- Accessed: 2026-10-24.
- Jurisdiction: Technical platform.
- Supports: Whole-transaction retry for SQLSTATE 40001 and careful treatment of other error codes.
- Limitations: No eventual completion guarantee or permission to retry changed business meaning as a transient conflict.
R04 — Transactions and Explicit Locking
- Organization: PostgreSQL Global Development Group.
- Source type/version: Official PostgreSQL 18 documentation.
- URLs: §3.4 Transactions, §13.3 Explicit Locking.
- Accessed: 2026-10-24.
- Jurisdiction: Technical platform.
- Supports: Transactional atomicity, row-lock lifetime and the local storage reasoning.
- Limitations: Does not prove execution of this profile, hardware durability, external payment atomicity or protection from its owner.
R05 — Accounting Concepts
- Organization: GnuCash project.
- Source type/version: Official accounting-software concepts guide v5; no page-specific effective date.
- URL: §2.1 Accounting Concepts.
- Accessed: 2026-10-24.
- Jurisdiction: General explanation, no banking regulatory scope.
- Supports: Debit/credit and normal-side interpretation.
- Limitations: Not legal recognition, product terms or an actual bank chart of accounts.
R06 — Bank deposit perspective
- Organization/authors: Bank of England; Michael McLeay, Amar Radia, Ryland Thomas.
- Source type/date: Central-bank explanation, Quarterly Bulletin 2014 Q1, published 2014-03-14.
- URL: Money creation in the modern economy, printed p.16.
- Accessed: 2026-10-24.
- Jurisdiction: UK explanatory context.
- Supports: Deposits are liabilities in a commercial bank's books.
- Limitations: No historical quantity, monetary-policy claim or observed customer transaction is imported into the synthetic example.
Evidence and rights
Institution, accounts, commands, arrivals and money are author-created synthetic data. Counts/totals are author-derived. Key scope, strict wire schema, ordered-leg equality, snapshot model and replay ordering are implementation choices frozen in Stage 0. No confidential logs, private customer data, provider observations or empirical delivery claims are reproduced. Sources are paraphrased and linked.
Full dependency-light reference implementations in both supported languages.
/** Exact journal validation and a pure committed-snapshot teaching model.
* No database, network, sibling imports, implicit coercion, or BigInt JSON values.
*/
type RecordValue = 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 ACCOUNT = ['account_id','ledger_id','entity_id','currency','account_type','normal_side','status'];
const LEG = ['account_id','side','amount_minor'];
const CONTEXT = ['recorded_at','open_periods','allow_current_period_adjustment','adjustment_reason'];
const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/;
const AMOUNT = /^[1-9][0-9]{0,29}$/;
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])$/;
const NORMAL: Record<string,string> = {asset:'debit',expense:'debit',liability:'credit',equity:'credit',income:'credit'};
const currencies = new Set(['USD','EUR']);
const object = (x:any): x is RecordValue => x !== null && typeof x === 'object' && !Array.isArray(x) && (Object.getPrototypeOf(x) === Object.prototype || Object.getPrototypeOf(x) === null);
const exact = (x:any, fields:string[]) => object(x) && Object.keys(x).length === fields.length && fields.every(k => Object.hasOwn(x,k));
const identifier = (x:any) => typeof x === 'string' && IDENTIFIER.test(x) && !/[\r\n]/.test(x);
const period = (x:any) => typeof x === 'string' && PERIOD.test(x) && x.length === 7 && x.slice(0,4) !== '0000';
const copy = (x:any) => structuredClone(x);
// Match Python str.strip exactly; ECMAScript trim differs for NEL/control separators/BOM.
const trimText=(v:string)=>v.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 timestamp(x:any): boolean {
if (typeof x !== 'string' || x.length !== 24 || !TIME.test(x) || x.slice(0,4) === '0000') return false;
const parsed = new Date(x);
return Number.isFinite(parsed.getTime()) && parsed.toISOString() === x;
}
function contextOk(x:any): boolean {
return object(x) && ['recorded_at','open_periods'].every(k => Object.hasOwn(x,k)) && Object.keys(x).every(k => CONTEXT.includes(k))
&& timestamp(x.recorded_at) && Array.isArray(x.open_periods) && x.open_periods.every(period) && new Set(x.open_periods).size === x.open_periods.length
&& (!Object.hasOwn(x,'allow_current_period_adjustment') || typeof x.allow_current_period_adjustment === 'boolean')
&& (!Object.hasOwn(x,'adjustment_reason') || typeof x.adjustment_reason === 'string');
}
function accountsOk(x:any): boolean {
if (!Array.isArray(x)) return false;
const seen = new Set();
for (const a of x) {
if (!exact(a,ACCOUNT) || !['account_id','ledger_id','entity_id'].every(k => identifier(a[k])) || seen.has(a.account_id)
|| typeof a.currency !== 'string' || !currencies.has(a.currency) || typeof a.account_type !== 'string' || !Object.hasOwn(NORMAL,a.account_type)
|| a.normal_side !== NORMAL[a.account_type] || !['active','closed'].includes(a.status)) return false;
seen.add(a.account_id);
}
return true;
}
function rejected(code:string, totals:any=null): any {
return {status:'rejected',code,totals,movements:[],journal:null};
}
export function validateJournal(command:any, accounts:any, context:any): any {
if (!accountsOk(accounts)) return rejected('INVALID_ACCOUNTS');
if (!contextOk(context)) return rejected('INVALID_CONTEXT');
if (!exact(command,COMMAND)) return rejected('INVALID_SCHEMA');
if (command.schema_version !== 'd30-journal-v1') return rejected('UNSUPPORTED_SCHEMA');
if (!['ledger_id','entity_id','journal_id','business_operation_id'].every(k => identifier(command[k]))) return rejected('INVALID_IDENTIFIER');
if (typeof command.currency !== 'string' || !currencies.has(command.currency)) return rejected('UNSUPPORTED_CURRENCY');
if (!timestamp(command.effective_at) || !timestamp(command.source_available_at)) return rejected('INVALID_TIMESTAMP');
if (command.effective_at > context.recorded_at) return rejected('FUTURE_EFFECTIVE_TIME');
if (command.source_available_at > context.recorded_at) return rejected('FUTURE_SOURCE_AVAILABILITY');
if (!period(command.booking_period)) return rejected('INVALID_SCHEMA');
if (!context.open_periods.includes(command.booking_period)) return rejected('PERIOD_CLOSED');
if (command.booking_period !== command.effective_at.slice(0,7) && !(context.allow_current_period_adjustment === true && trimText(context.adjustment_reason ?? ''))) return rejected('UNAUTHORIZED_PERIOD_ADJUSTMENT');
if (!Array.isArray(command.legs) || command.legs.length < 2 || command.legs.length > 32) return rejected('INVALID_LEGS');
const lookup = new Map<string,RecordValue>(accounts.map((a:RecordValue) => [a.account_id,a]));
const raw = new Map<string,bigint>();
let debit = 0n, credit = 0n;
for (const leg of command.legs) {
if (!exact(leg,LEG)) return rejected('INVALID_SCHEMA');
if (!identifier(leg.account_id)) return rejected('INVALID_IDENTIFIER');
if (!['debit','credit'].includes(leg.side)) return rejected('INVALID_SIDE');
if (typeof leg.amount_minor !== 'string' || !AMOUNT.test(leg.amount_minor) || /[\r\n]/.test(leg.amount_minor)) return rejected('INVALID_AMOUNT');
const account = lookup.get(leg.account_id);
if (!account) return rejected('UNKNOWN_ACCOUNT');
if (account.status !== 'active') return rejected('ACCOUNT_CLOSED');
if (account.ledger_id !== command.ledger_id || account.entity_id !== command.entity_id) return rejected('ACCOUNT_SCOPE_MISMATCH');
if (account.currency !== command.currency) return rejected('CURRENCY_MISMATCH');
const amount = BigInt(leg.amount_minor);
if (leg.side === 'debit') debit += amount; else credit += amount;
raw.set(leg.account_id,(raw.get(leg.account_id) ?? 0n) + (leg.side === 'debit' ? amount : -amount));
}
if (raw.size < 2) return rejected('DISTINCT_ACCOUNTS_REQUIRED');
const totals = {debit_minor:debit.toString(),credit_minor:credit.toString(),residual_minor:(debit-credit).toString()};
if (debit !== credit) return rejected('UNBALANCED_JOURNAL',totals);
const movements = [...raw].map(([account_id,value]) => ({account_id,raw_delta_minor:value.toString(),normal_delta_minor:(lookup.get(account_id)!.normal_side === 'debit' ? value : -value).toString()}));
return {status:'accepted',code:'VALID_JOURNAL',totals,movements,journal:copy(command)};
}
export function initialState(accounts:any, ledger_id='CB-GENERAL', entity_id='CB-A'): any {
if (!accountsOk(accounts) || !identifier(ledger_id) || !identifier(entity_id)) throw new Error('Invalid trusted initial state');
return {ledger_id,entity_id,accounts:copy(accounts),journals:[]};
}
export function postJournal(state:any, command:any, context:any): any {
const base = copy(state);
const fail = (code:string,totals:any=null) => ({...rejected(code,totals),state:base});
if (!exact(state,['ledger_id','entity_id','accounts','journals']) || !identifier(state.ledger_id) || !identifier(state.entity_id) || !accountsOk(state.accounts) || !Array.isArray(state.journals)) return fail('INVALID_STATE');
let previous:string|null = null;
for (let i=0;i<state.journals.length;i++) {
const j = state.journals[i];
if (!object(j) || j.sequence !== String(i+1) || !timestamp(j.recorded_at) || j.ledger_id !== state.ledger_id || j.entity_id !== state.entity_id || (previous !== null && j.recorded_at < previous)) return fail('INVALID_STATE');
previous = j.recorded_at;
}
if (object(command) && (command.ledger_id !== state.ledger_id || command.entity_id !== state.entity_id)) return fail('SCOPE_FORBIDDEN');
const decision = validateJournal(command,state.accounts,context);
if (decision.status === 'rejected') return {...decision,state:base};
if (previous !== null && context.recorded_at < previous) return fail('RECORDED_TIME_REGRESSION');
if (state.journals.some((j:RecordValue) => j.journal_id === command.journal_id)) return fail('JOURNAL_ID_CONFLICT');
const committed = {...copy(command),sequence:String(state.journals.length+1),recorded_at:context.recorded_at,adjustment_reason:command.booking_period !== command.effective_at.slice(0,7) ? trimText(context.adjustment_reason ?? '') : ''};
base.journals.push(copy(committed));
return {...decision,status:'committed',code:'POSTED',journal:committed,state:base};
}
// Embedded validator keeps the downloadable reference independent of siblings.
const STATE = ['schema_version','ledger_id','entity_id','accounts','journals','operations'];
const STATE_VERSION = 'd30-idempotent-state-v1';
export function newState(accounts:any,ledger_id='CB-GENERAL',entity_id='CB-A'):any {
return {...initialState(accounts,ledger_id,entity_id),schema_version:STATE_VERSION,operations:[]};
}
function wireError(c:any):string|null {
if (!exact(c,COMMAND)) return 'INVALID_SCHEMA';
if (c.schema_version !== 'd30-journal-v1') return 'UNSUPPORTED_SCHEMA';
if (!['ledger_id','entity_id','journal_id','business_operation_id'].every(k=>identifier(c[k]))) return 'INVALID_IDENTIFIER';
if (typeof c.currency !== 'string' || !currencies.has(c.currency)) return 'UNSUPPORTED_CURRENCY';
if (!timestamp(c.effective_at) || !timestamp(c.source_available_at)) return 'INVALID_TIMESTAMP';
if (!period(c.booking_period)) return 'INVALID_SCHEMA';
if (!Array.isArray(c.legs) || c.legs.length<2 || c.legs.length>32) return 'INVALID_LEGS';
for (const leg of c.legs) {
if (!exact(leg,LEG)) return 'INVALID_SCHEMA';
if (!identifier(leg.account_id)) return 'INVALID_IDENTIFIER';
if (!['debit','credit'].includes(leg.side)) return 'INVALID_SIDE';
if (typeof leg.amount_minor!=='string' || !AMOUNT.test(leg.amount_minor) || /[\r\n]/.test(leg.amount_minor)) return 'INVALID_AMOUNT';
}
return null;
}
function semanticValue(x:any):string {
if (Array.isArray(x)) return '['+x.map(semanticValue).join(',')+']';
if (object(x)) return '{'+Object.keys(x).sort().map(k=>JSON.stringify(k)+':'+semanticValue(x[k])).join(',')+'}';
return JSON.stringify(x);
}
function same(a:any,b:any):boolean { return semanticValue(a)===semanticValue(b); }
function snapshotOk(s:any):boolean {
if (!exact(s,STATE) || s.schema_version!==STATE_VERSION || !identifier(s.ledger_id) || !identifier(s.entity_id)
|| !Array.isArray(s.accounts) || !Array.isArray(s.journals) || !Array.isArray(s.operations) || s.journals.length!==s.operations.length) return false;
const ids=new Set(), business=new Set(); let prior:string|null=null;
for (let i=0;i<s.journals.length;i++) {
const j=s.journals[i], op=s.operations[i];
if (!exact(op,['command','result'])) return false;
const c=op.command, r=op.result;
if (wireError(c)!==null || c.ledger_id!==s.ledger_id || c.entity_id!==s.entity_id || ids.has(c.journal_id) || business.has(c.business_operation_id)
|| !exact(r,['status','code','totals','movements','journal']) || r.status!=='committed' || r.code!=='POSTED'
|| !exact(j,[...COMMAND,'sequence','recorded_at','adjustment_reason']) || !same(r.journal,j)
|| !same(Object.fromEntries(COMMAND.map(k=>[k,j[k]])),c) || j.sequence!==String(i+1) || !timestamp(j.recorded_at)
|| typeof j.adjustment_reason!=='string' || (prior!==null && j.recorded_at<prior)) return false;
prior=j.recorded_at; ids.add(c.journal_id); business.add(c.business_operation_id);
}
return true;
}
/** Pure snapshot transition; the host supplies trusted authorization and atomic persistence. */
export function postIdempotent(state:any,command:any,context:any,authorized_scope:any):any {
const base=copy(state);
const fail=(code:string)=>({state:base,response:{status:'rejected',code,result:null}});
if (!exact(authorized_scope,['ledger_id','entity_id']) || !identifier(authorized_scope.ledger_id) || !identifier(authorized_scope.entity_id)
|| !object(state) || state.ledger_id!==authorized_scope.ledger_id || state.entity_id!==authorized_scope.entity_id) return fail('SCOPE_FORBIDDEN');
if (!snapshotOk(state)) return fail('INVALID_STATE');
const error=wireError(command); if(error) return fail(error);
if(command.ledger_id!==state.ledger_id || command.entity_id!==state.entity_id) return fail('SCOPE_FORBIDDEN');
for(const op of state.operations) {
if(op.command.business_operation_id===command.business_operation_id) {
if(!same(op.command,command)) return fail('IDEMPOTENCY_CONFLICT');
return {state:base,response:{status:'replayed',code:'REPLAYED',result:copy(op.result)}};
}
}
if(state.journals.some((j:RecordValue)=>j.journal_id===command.journal_id)) return fail('JOURNAL_ID_CONFLICT');
const decision=validateJournal(command,state.accounts,context);
if(decision.status==='rejected') return fail(decision.code);
if(state.journals.length && context.recorded_at<state.journals.at(-1).recorded_at) return fail('RECORDED_TIME_REGRESSION');
const journal={...copy(command),sequence:String(state.journals.length+1),recorded_at:context.recorded_at,adjustment_reason:command.booking_period!==command.effective_at.slice(0,7)?trimText(context.adjustment_reason??''):''};
const result={...decision,status:'committed',code:'POSTED',journal};
base.journals.push(copy(journal)); base.operations.push({command:copy(command),result:copy(result)});
return {state:base,response:{status:'committed',code:'POSTED',result}};
}
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.
