# Frozen FX definition and exact data contract

D30-F04-A04 FX Revaluation Posting. All financial choices and the14 independent math cases were ratified before this exact schema. Root ratified this exact API before implementation. This file defines the JSON/API contract; the host adapter later binds its trusted extracted receipt to actual Trial-Balance output and frozen journal head. The receipt is a trusted input, not cryptographic proof.

## Pure API and common scalars

Python `propose_fx_revaluation(request, position_snapshot, rate_history, context)` / TypeScript `proposeFxRevaluation(request, positionSnapshot, rateHistory, context)`. No I/O, state mutation, sibling imports, current-rate fetch, journal write or durable operation registry. All listed key sets are exact. Unknown/missing keys reject. JSON bool never substitutes for an integer/string.

Identifier: ASCII `[A-Za-z0-9][A-Za-z0-9._:-]{0,63}`. Period: real YYYY-MM, year0001..9999. Timestamp: genuine UTC Gregorian `YYYY-MM-DDTHH:mm:ss.sssZ`, millisecond precision, no leap-second/offset substitution. Integers are canonical decimal strings, zero only as `0`, no plus/leading zero/negative zero. Signed raw values additionally allow a leading minus before a positive magnitude. q/rate parts have at most30 digits; current carrying/raw magnitude at most60 digits. Rate fractions are positive and reduced. Output fractions are reduced, with signed numerator and positive denominator strings; zero is0/1.

Reporting measurement_at is the last millisecond of the requested UTC calendar month (`YYYY-MM-lastdayT23:59:59.999Z`). This is the synthetic profile's declared close convention, not a universal market fixing time. K must be at least measurement_at.

## Request exact fields

`schema_version,revaluation_id,ledger_id,entity_id,period,knowledge_cutoff,profile,rounding,account_map`.

- schema_version `d30-fx-revaluation-v1`.
- revaluation_id is a calculation/proposal identity, not proof that a Stage1 business operation committed.
- profile `ias21-ordinary-eur-usd-v1`.
- rounding `half-even-target-v1`.
- account_map exactly `{valuation_account_id,fx_gain_account_id,fx_loss_account_id}`; three distinct strict IDs.

## Trusted extracted position receipt exact fields

The common close-source base is exactly `schema_version,evidence_id,ledger_id,entity_id,period,currency,basis,knowledge_cutoff,observed_at,head_sequence,receipt_ref`, extended here by exactly `position_id,position_version_ref,valuation_account_id,native_currency,foreign_quantity_minor,carrying_minor,raw_balance_minor,nature,classification,exchangeable,hedge_accounting,net_investment,hyperinflation,available_at,position_basis_ref`.

Base schema `d30-close-source-v1`; currency USD; basis `functional-usd`; scope/period/K equal request; observed_at≥K; head_sequence canonical nonnegative≤30digits; evidence/receipt IDs strict. Position IDs/basisref strict. native_currency EUR; foreign_quantity_minor positive≤30digits. carrying_minor nonnegative≤60digits. nature asset/liability; raw_balance_minor must equal +carrying for asset, −carrying for liability (zero remains0). valuation account matches request map. available_at≤K≤observed_at, with available_at≥measurement_at: this receipt supplies the close extraction, not an unverified earlier opening balance. position_version_ref changes with a changed extracted position; root's host adapter must establish current-as-of-K/frozen-head identity and exact native quantity/carrying/raw equality.

classification must be monetary, exchangeable exact booltrue; hedge_accounting/net_investment/hyperinflation exact boolfalse. Legitimate false/true exceptions route unsupported, not ordinary profit/loss. Nonbool flags reject malformed. These facts are trusted accounting assertions; the calculator does not determine exchangeability or classification. q/C must describe the same position/basis, not gross notional versus an unrelated net allowance or fair-value amount. EUR quantity and USD valuation are parallel views, not currencies to sum.

## Context and account integrity

Context exactly `{accounts,rate_head}`. accounts is≤256 seven-field Stage1 account records exactly `{account_id,ledger_id,entity_id,currency,account_type,normal_side,status}`; unique account IDs, USD/EUR supported, account_type asset/liability/equity/income/expense, normal sides debit for asset/expense and credit otherwise, status active/closed. Mapped three accounts must be active, same requested ledger/entity, USD. Valuation account type agrees with nature; gain income/credit; loss expense/debit. A native `cash_eur` account cannot stand in for the USD valuation account.

Rate head exactly `{schema_version,source_id,measurement_at,observed_at,head_revision,receipt_ref}`. Schema `d30-fx-rate-head-v1`, source/receipt IDs strict, measurement matches requested close, observed_at≥K, head_revision canonical nonnegative≤30digits. It is independent trusted coverage, not derived from supplied array length.

## Complete rate history

rate_history is an array of≤512 versions. Each version exactly `{schema_version,source_id,rate_version_id,revision,supersedes_revision,source_version_ref,measurement_at,available_at,base_currency,quote_currency,rate}`; schema `d30-fx-rate-version-v1`, source matches head, measurement equals request/head. rate exactly `{numerator,denominator}`, both positive canonical≤30digits/reduced. Quote pair must be EUR/USD or USD/EUR. Normalize the latter by exact inversion. No silent provider ticker inference, bid/ask choice or missing-rate invention.

Validate every supplied version before K selection, including future-unselected facts. rate_version_id/source_version_ref strict IDs. Revision is canonical positive≤30digits; revisions cover1..head_revision. First supersedes_revision:null; later exactly previous revision string. Source-version refs are unique across this normalized history. Exact duplicate version ID/content→DUPLICATE_RATE_VERSION; changed content under same ID→RATE_VERSION_ID_CONFLICT; repeated revision with different IDs→REVISION_ID_CONFLICT. Missing interior revision→RATE_SEQUENCE_GAP; missing tail→INCOMPLETE_RATE_HISTORY; provided beyond head→HEAD_REVISION_MISMATCH. Head0 requires empty array and leads MISSING_CLOSING_RATE, not invented rate.

Canonical revision order is numeric, independent of delivery order. available_at≥measurement_at and≤head.observed_at; receipts nondecreasing by revision, ties allowed. For K choose the highest revision whose available_at≤K; equality inclusive and a tied receipt exposes all tied revisions, choosing their highest. K beyond observed head→KNOWLEDGE_BEYOND_RATE_SOURCE. No visible version→MISSING_CLOSING_RATE. Versions are normalized observer evidence, not a claim about native provider numbering. Future versions may be represented in coverage but never in selected-rate features or calculation.

## Arithmetic and posting proposal

Normalize r in USD major/EUR major. With both minor exponents2, exact targetUSDminor=q×r. Round target once half-even to T; keep residual exact_target−T. Delta=T−current_carrying. Do not round delta instead: q3×1/2=1.5→T2; from current 1 delta 1, whereas rounding0.5 would wrongly produce0.

Gain/loss classification: zero→none; positive assetdelta or negative liabilitydelta→gain; opposite→loss. Two legs debit first then credit, exactly `{account_id,side,amount_minor}`. Asset increase Drvaluation/Crgain; decrease Drloss/Crvaluation. Liability increase Drloss/Crvaluation; decrease Drvaluation/Crgain. Amount abs(delta). Zero emits legs[]; absdelta>10^30−1 emits blocked proposal with legs[], retaining exact calculation. A large target alone is not blocked and is not an account-balance cap. Target≤60digits fits continuation carrying profile. Foreign quantity remains unchanged.

Proposal effective_at=measurement_at, booking_period=request.period, source_available_at=max(position.available_at,selected_rate.available_at). It still requires actual authorization, open/closing period policy, accepted posting and business identity handling. After accepted posting is reflected in a new current receipt C=T, a recalculation yields zero; that is not durable replay semantics. A nonzero ready calculation is not a passed close prerequisite.

## Exact output

Always exactly `{status,code,scope,position_snapshot,selected_rate,coverage,calculation,proposal}`. On rejected/indeterminate: all six non-status/code fields null. No partial values from invalid evidence. On success statusok/code FX_REVALUATION_CALCULATED.

scope exactly `{revaluation_id,ledger_id,entity_id,period,currency,basis,measurement_at,knowledge_cutoff}` with USD/functional-usd. position_snapshot is a detached exact copy of supplied receipt. selected_rate is a detached exact copy of chosen version. coverage exactly `{source_id,receipt_ref,observed_at,head_revision,validated_through_revision,visible_through_revision}`; revisions canonical strings, validated=head on successful calculation.

calculation exactly `{foreign_quantity_minor,carrying_before_minor,normalized_usd_per_eur,exact_target_minor,rounded_target_minor,rounding_residual_minor,adjustment_minor,gain_loss,foreign_quantity_after_minor}`. Fraction fields are normalized rate/exact target/residual. All integer money fields strings; signed adjustment; gain_loss gain/loss/none. Foreign before/after equal and do not imply any accepted state change.

proposal exactly `{status,code,currency,booking_period,effective_at,source_available_at,amount_minor,legs}`. Status ready/zero/blocked; code POSTING_PROPOSAL/NO_ADJUSTMENT/AMOUNT_LIMIT respectively; currencyUSD, amountabsdelta (can exceed a valid leg wire only in blocked result), legs as above or[]. No returned accepted carrying_after and no journal_id/business_operation_id generated invisibly. All mutable output views detached from inputs and from each other.

## Error precedence and typed states

Order: strict request/schema/IDs/period/K/profile/rounding/map; strict context/accounts/rate-head intrinsic fields; strict position receipt/scope/amounts/rawsign/flags/clocks/current K; mapped-account roles; complete rate-version shape/IDs/values/duplicates; canonical chain/clocks/head coverage; selected version atK; calculation/proposal.

Indeterminate only: RATE_SEQUENCE_GAP,INCOMPLETE_RATE_HISTORY,KNOWLEDGE_BEYOND_RATE_SOURCE,MISSING_CLOSING_RATE,POSITION_NOT_AVAILABLE (available_at>K). Other errors rejected. Main local codes INVALID_SCHEMA,UNSUPPORTED_SCHEMA,INVALID_IDENTIFIER,INVALID_PERIOD,INVALID_TIMESTAMP,KNOWLEDGE_BEFORE_CLOSE,UNSUPPORTED_PROFILE,UNSUPPORTED_ROUNDING,INVALID_ACCOUNT_MAP,INVALID_CONTEXT,INVALID_ACCOUNTS,INVALID_RATE_HEAD,INVALID_POSITION,POSITION_SCOPE_MISMATCH,INVALID_AMOUNT,INVALID_RAW_BALANCE,UNSUPPORTED_POSITION_CURRENCY,UNSUPPORTED_MONETARY_PROFILE,NONEXCHANGEABLE_UNSUPPORTED,UNSUPPORTED_EXCEPTION_PROFILE,POSITION_CLOCK_VIOLATION,SOURCE_ACCOUNT_MISMATCH,ACCOUNT_ROLE_MISMATCH,INVALID_RATE_HISTORY,INVALID_RATE_VERSION,INVALID_RATE,UNSUPPORTED_QUOTE_PAIR,RATE_SCOPE_MISMATCH,DUPLICATE_RATE_VERSION,RATE_VERSION_ID_CONFLICT,DUPLICATE_SOURCE_VERSION,REVISION_ID_CONFLICT,INVALID_REVISION_CHAIN,RATE_CLOCK_VIOLATION,HEAD_REVISION_MISMATCH. Exact code precedence in colliding invalid fields is an implementation contract, not accounting advice.

## Pre-code evidence and acceptance

FX-INDEPENDENT-MATH.json preserves 14 explicitly specified target/delta/sign/rounding/limit cases and two revision-cutoff expectations before code. The completed full-output suite includes strict key/type/currency/scope/clock errors, missing future/interior versions, permutation equality, zero/blocked/ready legs, detached output views and known-rate revision equality. A 24-position lab keeps all observations and expose exact target bridge, rate direction, current carrying, asset/liability signs and pending accepted-posting boundary. Source/date and current-amendment evidence stays primary and paragraph-specific. Root freeze preceded implementation and derivatives.


## Root freshness clarification before release

The extracted receipt head_sequence denotes the visible whole-group journal position p at K. Independent source coverage may validate a later head h without making h visible. A historical p<h cannot pass a current close: the trusted close adapter must require p equals the frozen current close head and bind the exact carrying/native quantity/raw balance to actual Trial-Balance/position output. The calculator does not authenticate that binding.
