Files
wikideck/docs/custody-sale-rehearsal.md
T

14 KiB
Executable File

Signed sale → PostgreSQL accounting rehearsal

Status: disposable, synthetic evidence only; no live settlement or consensus activation. Evidence: 2026-10-20. Related: sale-lock candidate, financial reconciliation, production plan.

What is integrated

The replay bridge validates trader-signed candidate terms against an actual sandbox purchase reservation. The PostgreSQL custody implementation can consume this evidence and atomically resolve its reserved balance. This exercises the real restricted runtime role, financial writer lock, journals, balance projection, listing/order/outbox updates and business audit—not an in-memory imitation of SQL accounting.

observeSaleRehearsal() is callable only on an instance explicitly opened with disposableSaleRehearsal: true, with WIKIDECK_PG_DISPOSABLE_TEST=1, a configured database named custody_test, and a matching actual connected database name. There is no HTTP route, production environment enable switch, host signer, Monero adapter or public checkout integration. These guards prevent accidental use; a database name/environment flag does not independently prove infrastructure isolation. The supplied test Compose deployment is disposable/internal/tmpfs, and must never contain funds or valuable assets.

The evidence supplies one synthetic seller-owned NFT seed and up to 128 candidate blocks, each containing at most 64 sale transactions. Replay uses the existing candidate state machine, signatures and depth rules. JSON traversal has collection/depth/node limits and a 256 KiB evidence ceiling. Seed, policy, host identity, signed windows and exact economics are bound on the first successful observation. Changing that binding is rejected; a different block history under the same binding can be observed to test reorg handling.

This seed is caller-supplied, not discovered in the live node. Blocks have no RandomX proof, parent hashes, fork choice, issuance verification, peer validation or canonical event provenance. Signed terms authorize the simulated transitions; signatures do not prove the chain is canonical or that real Monero backs the reservation.

Exact authorization and financial behavior

  • Both seller and buyer sign exact order/custodian/card/sequence, trading identities, host authority, candidate policy/domain, gross XMR amount, fee policy, recipient and admission/expiry heights. Existing sandbox reservation signatures alone are not candidate sale authorization.
  • The signed hosting rate must equal the stored listing rate, even where two rates round to identical commissions on a tiny sale. The allocation and host recipient must match the reserved order/listing. All amounts remain canonical atomic-unit strings and bigint; there is no float or USD conversion.
  • Optional hosting recipient terms use a tagged signature extension. Omission retains the earlier candidate message bytes. New drafts preserve the order's snapshotted recipient; changing current host policy does not redirect revenue. The candidate only checks address shape; actual custody policy/business audits additionally check Monero network and checksum.
  • The reservation commitment binds the order's exact buyer/seller, card/sequence, gross and split amounts, author and hosting recipient, custodian and rehearsal binding. Completion must contain that commitment and the bound host's valid signature. There is no private key or automatic signature generation in the accounting implementation.
  • Before maturity, gross remains in the buyer's reserved account. Once authorization has been bound, ordinary buyer cancel-reservation is denied, including through another ordinary custody instance. Synthetic mature terminal/non-admission evidence is required instead.
  • Mature completion debits reserved gross and credits the seller's available balance, fixed author liability and snapshotted hosting revenue in one balanced transaction. Zero commission legs are omitted, not rounded up; a one-atomic sale still credits one atomic unit to the seller.
  • Mature cancellation/expiry or the safe never-admitted boundary debits the reservation and returns gross to the buyer. There is no seller/fee credit or Monero transfer on this branch.
  • Deterministic journal IDs plus the shared database writer lock prevent duplicate terminal postings across pools, replay and restart. An identical outcome returns none rather than posting again. Failure while storing terminal evidence rolls the entire financial mutation back.

Evidence, audit and review

Namespaced insert-only metadata records retain each order's binding, first signed observation, terminal observation, per-order terminal index and (when there is an explicit terminal candidate transaction) candidate-tx-to-order index. Runtime has INSERT/SELECT but no UPDATE/DELETE privilege on these metadata records. Repeated observations do not create an unbounded transcript archive. Business audits replay signed retained evidence, check the exact listing rate and binding/state commitment, and compare terminal outcome, candidate transaction ID and indexes with the stored order and financial lifecycle.

The existing sandbox mock-sale-settlement journal and fixture-complete/fixture:<state-root> outbox format are reused deliberately. They remain fixture evidence, not a new assertion of canonical delivery. No schema migration or production evidence format is implied. Existing imported SQLite fixture settlements remain supported without being relabeled as signed rehearsals.

A submitted history that conflicts with, removes or makes immature an already posted outcome triggers sale-rehearsal-outcome-reorg. Business changes roll back, and a redacted durable integrity incident commits while retaining the shared writer lock. All custody instances then reject new mutations; private reads and original signed receipts remain available. The original seller credit/refund stays recorded: no automatic compensation, reversal or second payout. Re-submitting the old mature history cannot clear the incident. Existing schema-owner incident review procedures are not a substitute for resolving chain risk; production resolution requires retained canonical evidence and approved loss policy.

Local canonical-event inbox foundation

The same explicitly disposable PostgreSQL instance now has observeCanonicalEvents() and observeLedgerCanonicalEvents(ledger). The ledger adapter requires an actual in-process Ledger object, checks its network/chain identity, and fetches one page at a time from its locally validated canonical event API—no caller/HTTP/peer page can enter through that adapter. The inbox is append-only and bounded. It checks stream identity, page limits, predecessor anchors, event hashes, sequence continuity, connect parent/tip and tip-first disconnect rules against a reconstructed canonical hash stack. It accepts exact retries and overlapping pages from concurrent consumers only after matching their persisted overlap; stale retries report the durable cursor/head, not the stale page head. The startup/periodic financial audit replays and verifies the stored inbox; a changed stream, gap or malformed/tampered event creates the normal durable integrity hold. Inbox records use namespaced custody_meta keys and the existing runtime role; there is no schema migration or mutable cursor.

An opt-in CanonicalObserver now schedules one bounded poll cycle on the server runtime, resumes from its durable cursor after restart, and stops/drains before PostgreSQL and the ledger close. Each cycle processes at most four pages of 64 events; lag is exposed on the private operations listener. Poll failures or sale-review events stop the loop fail-closed; it does not retry a failed observer automatically. The runtime opt-in requires WIKIDECK_CUSTODY_CANONICAL_REHEARSAL=1, PostgreSQL custody and WIKIDECK_PG_DISPOSABLE_TEST=1; the configured and actual database must also be custody_test, and NODE_ENV=production is explicitly rejected. Invalid/missing combinations fail closed, including direct construction of the polling controller. Shutdown stops scheduling immediately and drains the current page before custody/ledger close. This is not enabled in the persistent sandbox or any regular deployment and has no public HTTP control endpoint.

PostgreSQL tests include synthetic event pages for malformed/retry cases, local Ledger blocks configured with a test-only fake PoW engine for equal-work side-branch/reorg transitions, and one separately mined and independently revalidated block using the native RandomX engine before its canonical event is consumed. This proves the real PoW validation → local event API → PostgreSQL inbox path for a single block; it does not establish production multi-node reliability or sale-candidate transaction support. Hash-anchor consistency is not proof of honest observation; the stream ID and unkeyed hashes are stored beside the events, so a schema owner able to coherently rewrite the complete history can also recompute the hashes. The included corruption test detects a partial edit, not a fully rewritten valid history. Production must anchor custody checkpoints outside the database's mutable trust domain. The disposable inbox is capped at 4,096 total events, stores the append-only event history and fully rescans it during audits. That is intentionally a small-fixture design, not production storage/cursor architecture or a scale claim.

The synthetic PostgreSQL event-page seam now links an explicitly terminal candidate transaction ID to its posted order. A later submitted disconnect event containing that exact ID commits both the canonical event page and a durable per-order sale-reorg-review record plus global posted-sale-terminal-disconnected mutation hold in one transaction; the original journal is not reversed or paid again. This remains true if the transaction reappears later in the same replacement-branch page: review does not auto-clear on apparent re-inclusion. Generic incident resolution re-audits the retained review record and therefore cannot clear it. This exercises order-to-event linkage and atomic review behavior. The hold only detects disconnects actually delivered to this seam; it cannot detect an unseen intermediate fork.

The local-ledger adapter is not linked to these terminal candidate IDs: active v6 blocks cannot contain candidate sale transactions, and local free transfers are not accepted as sale completion evidence. The adapter's integration test demonstrates canonical event ingestion/reorg only, not sale rollback handling. The terminal-link test supplies a synthetic canonical page under disposable-only guards. Production still requires the candidate transaction format in a separately reviewed/activated network, an actual local canonical feed containing those IDs, and per-order integration with durable node history. No production sale-observation guarantee is claimed.

Validation and remaining gates

TypeScript/Vite Docker build passed; 172 offline regressions and 30 PostgreSQL integration tests passed, zero failures/skips. Candidate tests cover signature compatibility/recipient tampering and candidate transition rules. PostgreSQL tests add:

  • explicit instance/database consent and exact signed reservation matching;
  • seed-binding changes, malformed/cyclic/oversized collections, and denial of ordinary cancellation after binding;
  • immature completion hold, concurrent exactly-once seller/author/host credit, replay and reconnect;
  • mature cancellation and never-admitted-lock refund boundaries;
  • identical-rounded-allocation but different fee-rate rejection;
  • one-atomic settlement and signed custom-recipient revenue across current-policy changes;
  • conflicting outcome → durable cross-instance/restart hold without a second credit;
  • anchored canonical-event connect/disconnect paging, overlapping/stale consumer retries, reconnect, partial history mutation audit, forged disconnect-ancestor rejection and tamper-triggered hold; actual native-RandomX block validation and event ingestion plus test-only fake-PoW excluded branch/reorg fixture (no active-chain candidate sale link);
  • opt-in HTTP runtime startup, initial observer checkpoint, 64-event bounded catch-up, clean shutdown, durable restart resume and invalid-flag rejection;
  • synthetic exact candidate-terminal transaction disconnect/reappearance in one page → committed event history plus order-linked durable hold, generic incident resolution denied, one preserved seller credit and no second payout;
  • injected terminal-evidence failure → complete rollback and safe retry; corrupted evidence → audit hold.

A first bounded-checkpoint implementation attempted metadata UPDATE and was rejected by the real runtime grants. It was corrected to retain insert-only first/terminal evidence; no privilege was widened. Test evidence is for this implementation boundary, not production approval.

M3 remains open. Next work is independently reviewed protocol/policy and explicit new-network activation design, then actual PoW/peer/index/undo integration and durable canonical observation tied to financially fenced host-signing intent. The order-link detector is only exercised with synthetic page events until an activated candidate network can produce candidate IDs through the real local ledger. Monero deposit confirmation/unlock observation, treasury/signer separation, real withdrawal controls, coordinated recovery, capacity and legal/operational release gates remain separate. The active v6 chain still rejects every candidate sale transaction. Persistent local custody deployments were not switched or restarted by this rehearsal.