# Exact posting contract

Frozen2026-10-25; version `d30-journal-v1`. Complete ordered single-currency journal on one entity/ledger; exact positive integer minor-unit legs.

## API
`validate_journal(command,accounts,context)`/`validateJournal` returns `{status,code,totals,movements,journal}`. Valid: accepted/VALID_JOURNAL; totals `{debit_minor,credit_minor,residual_minor}`; movements `{account_id,raw_delta_minor,normal_delta_minor}` in first-account-appearance order; journal is a complete copied command. Rejected: rejected/typed code, null journal, empty movements. Only UNBALANCED_JOURNAL returns totals on failure; earlier failures have null totals.

`initial_state(accounts,ledger_id,entity_id)`/`initialState` constructs `{ledger_id,entity_id,accounts,journals:[]}`; defaults CB-GENERAL/CB-A; invalid trusted constructor raises programmer error. `post_journal(state,command,context)`/`postJournal` adds returned new state. Success is committed/POSTED; journal adds string sequence, recorded_at, adjustment_reason. Reject preserves state by value. Inputs do not mutate; output journal and stored copy do not alias.

This is a pure snapshot model, not durable COMMIT. An adapter must authorize scope and protect policy/catalog/counter in one ledger transaction.

## Exact schemas
| Object | Exact fields/rules |
|---|---|
| Journal | schema_version,ledger_id,entity_id,journal_id,business_operation_id,currency,effective_at,source_available_at,booking_period,legs. Reject unknown/missing. |
| Leg | account_id,side,amount_minor. Side debit/credit. Ordered2–32, two distinct accounts; repeats retained. |
| Account | account_id,ledger_id,entity_id,currency,account_type,normal_side,status. Active/closed; asset/expense debit-normal; liability/equity/income credit-normal. |
| Context | Required recorded_at/open_periods; optional allow_current_period_adjustment/adjustment_reason. Reject unknown; defaults false/empty; actual boolean/string; unique valid periods. |

Identifiers match ASCII `^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$` exactly, without trailing line breaks. Catalog IDs are unique within the supplied catalog; each leg's entity/ledger is checked.

Money matches `^[1-9][0-9]{0,29}$` exactly. Reject numeric JSON, boolean/null/missing, signs, zero, whitespace, leading zeros, exponent/fraction text. USD/EUR exponent 2 are separate denominations. Raw output is debit-positive; normal output flips credit-normal signs. Aggregate integers can exceed per-leg limit. No null-to-zero, rounding, conversion, adjustment, interpolation, or outlier removal.

Time is genuine Gregorian UTC `YYYY-MM-DDTHH:mm:ss.sssZ`, years 0001–9999, no leap second. Fixed width makes lexical comparisons safe within this subset. Effective/source≤recorded; effective may precede source availability. Recorded means server recording inside transaction, not final COMMIT time.

Booking period valid nonzero-year YYYY-MM must be open and normally match effective month. Mismatch needs trusted authority and trimmed nonempty reason; preserve all clocks/explanation. A client journal flag is unknown-field rejection. This teaching policy does not decide framework-specific recognition.

Recording times nondecrease; equal times use sequence. A successful portable append increments the committed-list sequence; reject consumes none. Real DB needs locked transactional ledger counter; nextval allocation is not proof.

## Failure precedence
1. Trusted accounts INVALID_ACCOUNTS; context INVALID_CONTEXT.
2. Exact journal shape INVALID_SCHEMA; version UNSUPPORTED_SCHEMA.
3. Identifiers INVALID_IDENTIFIER; currency UNSUPPORTED_CURRENCY.
4. Timestamp INVALID_TIMESTAMP; future effective FUTURE_EFFECTIVE_TIME; future source FUTURE_SOURCE_AVAILABILITY.
5. Period syntax INVALID_SCHEMA; not open PERIOD_CLOSED; unauthorized mismatch UNAUTHORIZED_PERIOD_ADJUSTMENT.
6. Legs type/count INVALID_LEGS.
7. Each ordered leg: exact fields INVALID_SCHEMA; ID INVALID_IDENTIFIER; side INVALID_SIDE; amount INVALID_AMOUNT; lookup UNKNOWN_ACCOUNT; closed ACCOUNT_CLOSED; scope ACCOUNT_SCOPE_MISMATCH; currency CURRENCY_MISMATCH.
8. Distinct accounts DISTINCT_ACCOUNTS_REQUIRED; totals UNBALANCED_JOURNAL.

Wrapper first checks trusted snapshot structural/sequence/time consistency INVALID_STATE and configured scope SCOPE_FORBIDDEN; then validator. After valid candidate: RECORDED_TIME_REGRESSION and JOURNAL_ID_CONFLICT. Journal uniqueness does not provide business idempotency.

Inputs are JSON-shaped. Imported committed history is trusted/nonmalicious, not cryptographically verified. Dataset `datasets/journal-cases.json` contains self-contained synthetic commands/accounts/context and independent expected subsets from Stage 0. Compare complete Python/TS JSON; pure tests are not DB race tests.

Trusted adjustment reason trimming uses Python str.strip whitespace explicitly in TypeScript: U+0009–000D, U+001C–0020, U+0085, U+00A0, U+1680, U+2000–200A, U+2028–2029, U+202F, U+205F, U+3000. JavaScript default trim differs and is not used.
