> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sesameterminal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 0011. Audit-first order path

# 0011. Audit-first order path

* **Status:** Proposed
* **Date:** 2026-09-30

## Context

Phase 3 places real orders with one click. The failures that cost money are an order
sent twice (a double click, a replayed request, a retry after a timeout), an order sent
without passing the limits, and an order whose fate is unknown after a lost response.
The owner must also be able to show what the app asked for and what the venue answered,
and tell the app's orders from anyone else's. The Phase 0 security review asked for an
idempotency key per intent (F6) and an append-only audit log that cannot be edited
quietly (F15). `CLAUDE.md` forbids retrying a non-idempotent venue request. The Phase 3
recheck found that signing and sending in one call lost the order hash when the process
stopped before the response row (P3R-06).

## Decision

Every order goes through `engine/orders`; there is no other way to submit one.

1. The browser mints a UUIDv7 `client_order_id` per user intent, at pointer-down. It is
   the idempotency key; a repeat press within 350 ms reuses it.
2. `engine/orders` writes an **intent** row to `order_audit`. The key is unique: a
   repeated `client_order_id` returns the first result and sends nothing.
3. `engine/risk` checks the intent; `engine/orders` writes a **decision** row (approved,
   or the reject code).
4. Only an approved intent is signed: `core.Trading.Sign` signs it and sends nothing. A
   signing failure is final as rejected (`no_credentials`); nothing was sent.
5. `engine/orders` writes a **signed** row with the venue order id (Polymarket: the order
   hash) and the payload hash (SHA-256 of the exact request body). If the row cannot be
   written, nothing is sent.
6. `core.Trading.Send` posts that exact body once.
7. `engine/orders` writes a **response** row: the order id and status, the venue reject,
   or `unknown`. A send error or a missing result is `unknown`, with the signed order id.

The table:

* Rows are separate and keyed by `client_order_id` and kind, never one row updated. Only
  `store.Writer` inserts; `BEFORE UPDATE` and `BEFORE DELETE` triggers abort any change.
* Kinds: `intent`, `decision`, `signed` and `response` as above; `reconcile` for what
  reconciliation found; `ended` when the order leaves the book (matched, cancelled, or no
  longer open), so it no longer explains later account activity (P3R-03); `cancel` before
  a cancel is sent and `cancel_result` after; `baseline` once, when the first start
  adopts the orders already open (P3R-04).
* Each row carries a hash over its contents and the previous row's hash. `task
  audit-verify` recomputes the chain and reports the first row that does not match.
* Rows hold a hash of the signed payload, never the signature, L2 headers or a key.

**No automatic re-send.** An `unknown` placement is never sent again. It is
**reconciled by its signed order id** and nothing else: the open orders holding that id, a
user-feed fill of it, or a lookup of it at the venue. Lookups run 2 to 30 s after the
unknown outcome, then every 60 s while the venue does not answer; meanwhile the
placement holds its reservation and counts in `unresolved_placements` (P3R-07). An id
the venue does not know after the last attempt is final as rejected. At start, every
approved placement without a final row is queued with its stored order id. Account
activity not explained by `order_audit` (trades, outgoing transfers, splits, merges,
conversions) raises an alert and switches to SAFE (security F5, P3R-02).

## Consequences

* A double click, a replayed request or a browser retry places at most one order.
* A crash after the signed row is reconciled at the next start by its order id. A crash
  before it leaves nothing on the book, because nothing was sent.
* The owner can compare the app's orders with Polymarket's history row by row, and the
  chain shows whether rows were changed. It does not stop someone with write access from
  rewriting the whole chain; the comparison with the venue's records covers that.
* The intent, decision and signed inserts sit on the latency path of every click. SQLite
  runs in WAL mode; one placement timeout covers signing, the signed row and the send.
* An `unknown` order leaves the user unsure whether to click again. The UI shows it as
  `Checking…`, and the incident runbook says to wait.

## Alternatives considered

* **One row per order, updated as it progresses.** Rejected: updates lose history and
  make an edit look like a normal state change.
* **Retry a placement once after a timeout.** Rejected: if the first request reached the
  venue, the retry places a second order. The venue has no idempotency key for orders.
* **Sign and send in one adapter call.** Rejected: a crash after the send and before the
  response row loses the order hash, so the order can only be reported as foreign (P3R-06).
* **Write the audit row after the venue call.** Rejected: a crash after sending would
  leave an order on the book with no record.
* **Keep the audit trail in the application log.** Rejected: logs rotate and are not
  append-only.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.