# Posting pipeline (operational → accounting)

**Stance:** Posting is a **reflection pipeline**. It runs **after** operational commits, preserves **idempotency**, and fails **without** corrupting operational state.

## High-level flow

1. Operational module commits movement/receipt (system of record).
2. **After-commit** hook emits domain event or enqueues **posting command** with idempotency material.
3. Posting worker builds journal lines from **mapping** (`posting-mapping.md`).
4. Persist journal atomically with dedupe; acknowledge queue / complete saga step.

---

## After-commit trigger

**Rule:** No financial posting inside the **same database transaction** as inventory/receipt mutation unless product explicitly requires synchronous certification (rare). Default:

- **Emit after successful commit** so rolls never orphan ledger rows and ledger retries never roll back stock.

If synchronous reporting is required for UX, return **pending** finance status until async posting completes; operational success remains authoritative.

---

## Async vs synchronous

| Mode | When | Trade-off |
| --- | --- | --- |
| **Async (default)** | High volume, queue-available deployments | Latency between operation and GL; compensates via status + reconciliation |
| **Sync** | Regulatory snapshot, single-node demos, explicit SLA | Stronger immediacy; must still use **idem key + short transaction** for journal insert only |

**Guideline:** Even “sync” posting should call the **same** idempotent posting service the async worker uses—one code path, two transports.

---

## Failure handling

| Failure class | Handling |
| --- | --- |
| Transient (DB, queue, lock) | Bounded retries with backoff; **same** idempotency key |
| Permanent validation (mapping gap, missing account) | **Fail posting**; mark operational row or posting queue row **`awaiting_manual`** with structured error; **do not** change operations |
| Poison (bug) | Stop auto-retry; fix code; **admin replay** with governance |

Operational records remain **valid**; finance catches up via retry or manual intervention.

---

## Retry rules

Align with `retry-and-failure-management.md`:

- **Single owner** for backoff on posting consumers (worker middleware).
- Per-tenant / per-key rate limits to avoid storms when operations burst.
- Retries are safe because **idempotency** prevents duplicate journals (`posting-idempotency.md`).

**Exhaustion:** Posting job moves to **failed** or **manual queue**; alerting fires; reconciliation jobs detect **unposted** operational facts (`operational-financial-reconciliation.md`).

---

## Ordering

- **Within** a single stock movement: single key—ordering trivial.
- **Across** related documents (PO receipt + invoice): use **correlation ID** and explicit **dependency** in posting order if credit side requires prior accrual; avoid fragile timestamp ordering.

---

## Phase completion checklist (operational → accounting bridge)

- Posting triggers defined (`operational-posting-triggers.md`)
- Mapping rules defined (`posting-mapping.md`)
- Idempotency enforced (`posting-idempotency.md`)
- Pipeline defined (this document)
- Reconciliation rules defined (`operational-financial-reconciliation.md`)

**Critical stance:** Accounting reflects reality; it does not create it. **Operational truth > financial abstraction.** **Traceability > convenience.**
