Business document lifecycle contract v1
The Exchange library validates structured lifecycle snapshots. It reuses the
order contract
and its exact account-independent envelope, canonical UUIDs, frozen parties,
Core address snapshots, timestamps, revision rules, units, decimal precision,
half-up currency-increment rounding, and 64 KiB total JSON limit.
Decode wire JSON with Transport parse_json, then call
validate_document_envelope to validate and obtain its SHA-256 digest.
The validators do not authenticate senders or implement native delivery.
Common snapshot fields
Snapshots have exactly exchange_uuid, document_uuid, revision,
previous_digest, kind, source_state, issued_at, document_date, seller,
and buyer, plus the document-specific fields below. Invoice dates are seller
invoice dates; picking dates identify completed shipment/return dates. Receiver
accounting dates are determined independently by receiver settings and open periods.
The original complete envelope digest is required for every subsequent revision.
A document reference is exactly
{origin_instance_uuid, document_uuid, revision, digest}. A line/move reference
adds line_uuid. Dependencies must belong to one of the two peers, never a third
instance or the current document. Lists contain at most 200 unique references.
Header and line dependency references must agree, including revision and digest.
Unresolved declared references remain pending review; absence of an order reference
must be explicit rather than inferred by dropping an unresolved link.
Invoices and credit notes
Capabilities are invoice.snapshot and credit_note.snapshot. The seller sends
kind: invoice or kind: credit_note, with source_state: posted. Extra fields are:
currency:{code, rounding}, unchanged quotation currency.lines: 1–200 unique commercial lines.untaxed_amount,tax_amount,total_amount: signed decimal strings.order_references,delivery_references: exact document dependencies, or[]for genuinely standalone invoices.original_reference:nullfor invoices; required original seller invoice dependency for credit notes.replaces_reference:nullor the exact replaced seller document dependency; a replacement always has a distinct document identity.attachments: frozen metadata including at least one invoice PDF.
Invoice lines have exactly line_uuid, product_uuid, description, quantity,
unit, pricing, source_evidence, order_line_references, and
delivery_move_references. Product is a stable variant UUID or null. Pricing is
{unit_price, discount, untaxed_amount}; source evidence is
{unit_price, discount, untaxed_amount, total_amount, taxes} with tax entries
{label, amount, price_included}. Source evidence is required for accounting lines.
Descriptions, units, and tax evidence have the order contract's bounds.
Accounting quantity is nonzero and can be negative. Prices, source monetary evidence, tax amounts, and untaxed amounts can be signed to retain correction lines. Discounts remain between 0 and 100; negative zero is rejected. Each commercial line must satisfy the declared rounded quantity/price/discount amount; header untaxed amount is the exact sum of those line amounts. Header total equals untaxed plus source tax amount. Tax labels and amounts are evidence, never local tax instructions.
Native document kind determines invoice/refund polarity: a positive seller credit note line becomes a positive vendor refund line, not a negative ordinary bill. Signed correction lines keep their signs within that kind. Local tax differences are informational, while unexplained price/quantity/currency/untaxed-total differences require acknowledgment before receiver posting. Receiver posting, payments, and reconciliation remain manual.
Deliveries and customer returns
Capability picking.snapshot uses kind: picking, source_state: done, and a
seller-to-buyer direction. Capability return.snapshot uses kind: return,
source_state: done, and a buyer-to-seller direction for a customer return of the
original shipment. Extra fields are order_references, original_reference,
backorder_reference, lines, and attachments.
Each delivery has at least one exact order dependency and 1–200 unique moves.
Each move has exactly line_uuid, product_uuid, description, quantity,
unit, order_line_references, and original_move_reference. Product UUID and
at least one order-line link are required. Quantities are positive actual completed
quantities, not ordered quantities; direction supplies shipment/return polarity.
original_reference and each move's original_move_reference are null on ordinary
deliveries. Customer returns require them; every original move must identify the
exact original delivery revision/digest declared in the header.
backorder_reference is null or an exact earlier picking dependency.
Partial shipments have distinct document and move identities against the same order.
The receiver reuses eligible native order-generated receipts and never validates
them automatically. Returns never automatically create credit notes or refunds.
Cancellation and replacement notices
Capability document.cancel uses kind: cancellation, source_state: cancel,
and the sender's original document ownership direction. Extra fields are
original_reference and replacement_reference. The former is required; the
latter is null or an exact sender-owned replacement dependency. Notice, original,
and replacement document identities must be distinct. Missing replacement
dependencies stay pending review. A notice never cancels or overwrites native
orders, completed stock moves, or posted accounting records.
Attachments
Attachment metadata has exactly
{attachment_uuid, name, mimetype, size, digest, purpose}. UUID and SHA-256 bind
frozen content; names are bounded to 256 UTF-8 bytes. There are at most 10 distinct
attachment UUIDs, 5 MiB per file, and 10 MiB combined. Allowed MIME types are PDF,
PNG, and JPEG. Purpose is supporting or, on invoices/refunds only, invoice_pdf;
invoice PDFs must use application/pdf.
This contract carries metadata, not inline binary data, private URLs, or native attachment IDs. The transactional delivery adapter must provide authenticated, company-scoped transfer, verify actual type/size/digest, enforce attachment read access, automatically freeze invoice PDFs, and require explicit sender selection for other files. No bulk chatter attachment copying is implied.
Acknowledgments
Capability document.status has actor_identity_uuid: null for receiver service
attribution. Its payload is exactly
{exchange_uuid, subject, operation_uuid, state, code, issued_at}.
subject is the original sender's exact document reference, including complete
envelope digest. Payload operation_uuid identifies that original operation;
the outer envelope has its own acknowledgment operation UUID. Status travels in
the reverse peer direction. It includes no native record IDs, human-readable
business errors, prices, document changes, or native confirmation/posting state.
| State | Permitted codes |
|---|---|
received |
accepted |
pending_review |
mapping_required, dependency_required, revision_required, verification_required, conflict |
applied |
accepted |
rejected |
denied, invalid_document, conflict |
Applied means the operation was applied, not that a receiver confirmed or posted its counterpart. Adapters must verify subject correspondence before returning a status and retain monotonic evidence without echoing status as a new business exchange.
Native commercial calculation service
pmis.federation.exchange.pricing is an abstract model with private methods:
_extract_pricing(company, currency, taxes, quantity, unit_price, discount, ...)returns commercialpricingand originalsource_evidencefrom Odoo's native tax computation. Numeric inputs and outputs are contract decimal strings. Native source calculations use the explicit company and currency; currency-rounded source untaxed amount is preserved when deriving a discounted tax-exclusive price._receiver_unit_price(company, currency, taxes, quantity, pricing, ...)returns a native unit price under receiver-selected taxes. Odoo'stotal_excludedtax mode supplies the included-tax delta, followed by native recomputation to prove the quoted untaxed amount. Unrepresentable combinations require review._convert_units(company, currency, quantity, unit_price, source_unit, target_unit, approved=False)uses native same-category conversion without quantity rounding, changes price and quantity together, and verifies the line amount. Different units require explicit adapter approval derived from an approved scoped mapping. Packaging stays source evidence; it cannot itself approve a conversion.
These helpers require explicit accessible company scope, active currency, and same-company tax/product/partner records. They do not choose a fiscal position, map products, activate currencies, convert currency, or grant approval. Private methods are unavailable as generic Odoo RPC actions. Reviewed Exchange channels advertise immutable journal delivery through Transport. Native application remains receiver-owned and requires an installed native adapter.
Immutable incoming journal
The private pmis.federation.exchange.snapshot._receive_envelope method records
validated incoming document envelopes against caller-authenticated peers and capability.
It resolves reviewed routing and sender authority, then persists canonical envelope
bytes, SHA-256, exact operation/document/exchange/revision references, and current
verification evidence with append-only receipts. The caller owns the commit.
Public snapshot and receipt creation, mutation, and deletion are blocked.
Replay uses the original operation and digest. The instance pair owns both the operation UUID namespace and each document revision; sender claims cannot move an established document into another company or exchange. Instance-row locks serialize operation and revision reservations. A snapshot savepoint includes initial receipt recording, so a receipt failure cannot leave a partial operation.
Verification evidence retains the original route, company/counterparty/partner mapping bindings, sender correspondence and membership, and reviewed native address snapshots. It also freezes exact commercial approval IDs and native targets. Current approval changes invalidate receiver review without altering original journal evidence. Unresolved addresses are retained without approval. Later address edits, retirement, reviewed route replacement, or changed verification require receiver review and never overwrite the frozen envelope or follow a successor automatically.
Known immediate predecessors are checked against exact digest and party ownership; valid revisions still require explicit receiver review. Missing dependencies remain pending. Dependency lookup is batched and requires exact applied snapshot and line evidence in the same company, environment, and peer pair. It does not establish native counterpart/order/picking/invoice correspondence. Attachment metadata and file bytes are frozen. Authenticated binary transfer verifies declared ownership, MIME signature, length, and digest. Missing declared bytes block application.
pmis.federation.exchange.receipt._record appends received, pending-review, or terminal
outcomes. Identical observations reuse their receipt; terminal outcomes are permanent.
Application requires a native adapter's _authorize_application implementation in the
same transaction as its native effects. The default implementation rejects application.
Receiver administrators can record explicit verification approval through
Federation > Incoming Documents > Review Verification. Each approval is a
separate immutable pmis.federation.exchange.review record attributed to the
acting administrator, with bounded review evidence and the current verification
JSON. Original snapshots, route bindings, and receipt history remain unchanged.
The private _approve workflow requires an exact applied predecessor for revisions,
resolved declared dependencies, and verified attachment content. It never follows
a replacement route or applies a native document. Terminal outcomes block approval.
Native adapters call _check_application_review before their reviewed application
effects. It checks that the newest approval still matches current verification and
rechecks all dependencies. Verification changes invalidate approval and require a
new review, without overwriting the original evidence or earlier approvals.
_acknowledgment constructs the exact bounded reverse-direction document.status
envelope. The authenticated document connection returns these acknowledgments from
delivery and read-only receipt lookup. Their construction changes no native state.
Durable outgoing journal and native adapter inputs
pmis.federation.exchange.outgoing._enqueue_envelope(connection, envelope, source,
selected_attachments=None) records one explicit sender-owned native-source revision.
The sender must have an approved Core identity mapping and local membership. Native
read access, company, supported source model/state, outgoing route, and exact party
claims are checked. Invoice/refund PDFs are rendered automatically; readable binary
supporting attachments require explicit selection on that same source record.
Envelopes and files are frozen atomically. No source IDs enter the wire contract.
Dispatch uses Transport's pinned HTTPS, scoped capability discovery and runtime
bindings. Uncertain delivery retries the original operation, bytes, and digest at
most eight times. File transfers use the original operation and attachment UUID.
Reconciliation retrieves receipts without replaying business effects. Immutable
pmis.federation.exchange.observation records validate exact reverse direction,
operation, exchange, subject revision/digest, allowed states, and terminal history.
Changed route, sender membership, or native address verification requires review;
dispatch never changes frozen source ownership or follows successor routes.
pmis.federation.exchange.adapter._source_pricing accepts native sale, purchase,
and accounting lines. _receiver_lines(snapshot, side) prepares order inputs using
the exact active currency, approved product/unit/packaging mappings, native fiscal
positions, and local taxes. Unknown or incompatible conversions preserve original
priced productless inputs and review reasons. Buyer quotation claims require native
seller-line correspondence and never become authoritative prices from a header
dependency alone. These services do not create orders. Native adapters own stable
document/line correspondence, revision acceptance, and the same-transaction
application hook. Stock/accounting adapters add scoped native warehouse/picking-type/
location/journal/account mappings with their workflows.