Paid-sale workflow

This commit is contained in:
2026-10-20 17:38:34 -04:00
parent 621611ecd9
commit ca8ff48628
11 changed files with 414 additions and 13 deletions
+51
View File
@@ -0,0 +1,51 @@
# Signed sale → PostgreSQL accounting rehearsal
Status: **disposable, synthetic evidence only; no live settlement or consensus activation**. Evidence: 2026-10-20. Related: [sale-lock candidate](sale-lock-candidate.md), [financial reconciliation](financial-reconciliation.md), [production plan](production-plan.md).
## What is integrated
[The replay bridge](../server/market/sale-rehearsal.ts) validates trader-signed candidate terms against an actual sandbox purchase reservation. [The PostgreSQL custody implementation](../server/market/postgres-custody.ts) 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
Three namespaced metadata records per observed order retain its binding, **first** signed observation, and terminal observation when resolved. 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 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.
This rehearsal is **not a durable connect/disconnect observer**. It retains first/terminal evidence and the redacted incident reason, not every submitted fork transcript. It cannot detect an intermediate disconnect that is never submitted, even if an outcome later reappears. The caller must retain conflicting evidence for investigation. Production must integrate a replay-safe canonical event inbox/cursor, history anchors, disconnect-before-reconnect handling and persistent per-order review. No monotonic canonical-height guarantee is claimed for this caller-driven seam.
## Validation and remaining gates
TypeScript/Vite Docker build passed; **172 offline regressions and 22 PostgreSQL integration tests passed, zero failures/skips**. [Candidate tests](../tests/sale-candidate.test.ts) cover signature compatibility/recipient tampering and candidate transition rules. [PostgreSQL tests](../tests/postgres/custody.test.ts) 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;
- 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. 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.
+2 -2
View File
@@ -30,6 +30,6 @@ New native-XMR listings and custody listings snapshot the configured host fee ad
Existing signature bytes remain valid: omission retains the original message; explicitly supplied recipients add a domain-tagged fee tuple. Old USD/native-XMR records are not rewritten. Changing the environment changes **new listings**, not existing listings/orders or accumulated revenue. New checkout against an old listing follows its original recipient. For a new recipient, cancel/relist with fresh seller authorization. Hosting rate and the fixed 1% author policy remain separate; defaulting both recipients to the same wallet does not merge the rates.
The custody SQLite fixture records newly settled simulated host revenue under its snapshotted recipient. PostgreSQL production settlement/host withdrawals are still unimplemented: configuring an address does not create automatic transfers. Historical unallocated host revenue remains historical; it is not silently reassigned. The offline sale-lock candidate currently refuses custom-recipient drafts until its separate reviewed terms extension is specified, rather than dropping that authorization.
The custody SQLite fixture records newly settled simulated host revenue under its snapshotted recipient. PostgreSQL production settlement/host withdrawals are still unimplemented: configuring an address does not create automatic transfers. Historical unallocated host revenue remains historical; it is not silently reassigned. The offline sale-lock candidate now preserves the hosting recipient in a tagged signed-terms extension. A [disposable PostgreSQL accounting rehearsal](custody-sale-rehearsal.md) verifies that the signed snapshot receives simulated revenue even after current host policy changes; this is not active-chain delivery or a live host withdrawal service.
Validation: TypeScript/Vite build, 168 regression tests and 11 PostgreSQL integration tests passed. The full 168-test regression run was repeated after the candidate fail-closed guard. Tests cover strict flag/address validation, paused/disabled behavior, continuing existing-order delivery, preserved balances/cancellations, signed-recipient tampering and recipient snapshots across config changes. Real XMR remains disabled.
Validation: current TypeScript/Vite build, 172 regression tests and 22 PostgreSQL integration tests passed. Tests cover strict flag/address validation, paused/disabled behavior, continuing existing-order delivery, preserved balances/cancellations, signed-recipient tampering and recipient snapshots across config changes, including the isolated accounting rehearsal. Real XMR remains disabled.
+2
View File
@@ -86,6 +86,8 @@ Blocked on P0 ADR; use P1 amount types and P2 durable events. Consensus work is
**Candidate increment, 2026-10-20:** [ADR 004 — sale locks](sale-lock-candidate.md) now specifies and implements an offline deterministic signed lock/completion/cancel/expiry model, exact height boundaries, native transfer/trade lock guards, replay-resistant sequences, state commitments and a non-posting financial observation policy. A pure bridge checks terms drafted from actual sandbox reservations. Final build and 162 offline tests passed. Active v6 consensus still rejects the candidate and no host signer or balance-posting integration is enabled. Independent review, policy approval, versioned network activation, actual PoW/peer/index integration and PostgreSQL terminal workflow remain open; M3 is not accepted.
**Accounting rehearsal increment, 2026-10-20:** [signed candidate → PostgreSQL reservation resolution](custody-sale-rehearsal.md) now exercises actual restricted-role SQL terminal postings from bounded synthetic transcripts, behind explicit disposable database/instance guards and without an HTTP route. Exact terms/hosting recipients and reservation commitments are checked; mature completion credits seller/fees once, cancellation/non-admission releases buyer funds, and conflicting post-settlement evidence durably holds mutations rather than repaying. Insert-only first/terminal evidence is audited. Final build, 172 offline tests and 22 PostgreSQL integration tests passed. This supersedes the earlier absence of any balance-posting test integration, **not** the production/activation boundary: caller-supplied seeds/blocks are not canonical evidence, intermediate unseen disconnects cannot be detected, and no signer, canonical inbox, live deposit/withdrawal or current-chain paid sale is enabled. M3 remains open.
- If a reviewed trustless design is selected: implement its actual cross-chain validity/evidence rules, compatible cryptographic primitives, both-party failure/refund paths and commission semantics. Run maintained reference vectors, negative tests, fuzz/property tests and specialist audit before claiming atomicity.
- If a custodial/attested fallback is explicitly approved: define chain-enforced NFT lock, order identity, authorized releaser, cancellation/refund/expiry and stale-signature rules. Separate host control keys from seller wallet and processor payout keys. Reserve/refund procedures must not permit both buyer refund and later seller delivery/release. Describe who can steal, censor or lock assets and how recovery is authorized.
- Completed-sale inclusion must require the approved evidence that payment is sufficiently confirmed. A peer must not insert a completed sale merely because its local database says paid. Define threshold/policy version, proof/attestation authorization and deterministic validation; no external HTTP calls in consensus validation.
+5 -3
View File
@@ -6,7 +6,7 @@ Status: **offline executable candidate; NOT activated or independently reviewed*
Specify the missing asset-control rules before enabling real custodial purchases. An ordinary signed NFT transfer can survive a website refund deadline; balance reservations alone cannot prevent that late delivery. This candidate couples sale authorization to explicit canonical locks and terminal outcomes.
[Signed schemas/messages](../shared/sale-candidate.ts) and [deterministic state machine](../server/testnet/sale-candidate.ts) are deliberately separate from active consensus. The existing transaction schema rejects all candidate transactions. The candidate domain derives from the existing chain identity, explicit policy and candidate version; it is an offline signature domain, **not a reviewed network manifest/genesis**. No current chain identity, miner, peer endpoint, database balance or custody signing capability is changed.
[Signed schemas/messages](../shared/sale-candidate.ts) and [deterministic state machine](../server/testnet/sale-candidate.ts) are deliberately separate from active consensus. The existing transaction schema rejects all candidate transactions. The candidate domain derives from the existing chain identity, explicit policy and candidate version; it is an offline signature domain, **not a reviewed network manifest/genesis**. No current chain identity, miner, peer endpoint or custody signing capability is changed. A separately gated [disposable PostgreSQL accounting rehearsal](custody-sale-rehearsal.md) now changes fictitious reservations using signed synthetic transcripts; it is not mined-chain integration.
The pure `termsForCustodyOrder()` bridge drafts terms from an actual awaiting-lock reservation and checks exact seller/author/host allocations. It does not sign, persist, relay or debit anything. Tests exercise this bridge against the current custody engine. Public custody orders continue stopping at `awaiting-lock`.
@@ -14,6 +14,8 @@ The pure `termsForCustodyOrder()` bridge drafts terms from an actual awaiting-lo
Terms bind candidate protocol, network, chain/policy digest, custodian/order IDs, exact card/sequence, seller, buyer, host signing identity, native-XMR gross, hosting rate, fixed author policy, lock-admission deadline and final expiry height. Independent downward commission rounding remains unchanged. No production threshold is invented: maturity/depth/window limits are explicit policy inputs committed by the candidate digest.
Optional `hostFeeAddress` appends a tagged hosting-recipient tuple, preserving historical candidate signature bytes when omitted. Drafting preserves the reserved order's snapshot; the PostgreSQL rehearsal additionally checks the exact listing rate, not just rounded allocation equality. The candidate performs address-shape validation only, with checksum/network validation supplied by custody admission and business audits. This extension is still unactivated and requires the same protocol review as the candidate.
- **Lock:** both seller and buyer sign the complete terms. Host signing identity must differ from both trading identities. Distinct keys are role separation, not proof of independent humans/operators.
- **Completion:** the terms' exact host signs the sale ID plus a reservation digest, attesting that sufficiently confirmed/unlocked funds were reserved under the approved policy. Host cannot select another card/recipient/price. Validators would verify the host's authorization, **not Monero truth**. The reservation digest is an accounting commitment, not a cryptographic proof of backing or solvency.
- **Cancellation:** host and buyer sign the sale ID. Neither can unilaterally cancel early. Seller already consented to this branch in signed lock terms; seller retains card ownership on cancellation.
@@ -64,13 +66,13 @@ An already mature terminal record remains usable evidence if the NFT is later gi
## Evidence and next release gates
TypeScript/Vite Docker build passed. **162 offline tests passed, zero failures/skips**, including 13 candidate tests in [tests/sale-candidate.test.ts](../tests/sale-candidate.test.ts). Coverage includes current-consensus rejection, field/signature tampering, exact height boundaries, sequence exhaustion, native transfer/trade lock bypass, competing-market/reused-order locks, atomic conflicting-outcome rejection, reorg financial decisions, deterministic snapshot restore, state commitments, actual custody-order drafting and enumerated adversarial histories through expiry. One initial enumeration-count expectation was corrected by extending its horizon through the expiry boundary; no protocol guarantee was inferred from an arbitrary test count.
Initial candidate evidence: 162 offline tests passed, including 13 candidate tests. Current TypeScript/Vite Docker build and **172 offline tests plus 22 PostgreSQL integration tests passed, zero failures/skips**, including 14 candidate tests in [tests/sale-candidate.test.ts](../tests/sale-candidate.test.ts). Coverage includes current-consensus rejection, field/signature and hosting-recipient tampering, exact height boundaries, sequence exhaustion, native transfer/trade lock bypass, competing-market/reused-order locks, atomic conflicting-outcome rejection, reorg financial decisions, deterministic snapshot restore, state commitments, actual custody-order drafting and enumerated adversarial histories through expiry. The [accounting rehearsal](custody-sale-rehearsal.md) records the additional real-PostgreSQL/fictional-funds evidence and its synthetic-chain limitations. No protocol guarantee is inferred from a test count.
**This does not close M3.** Required next steps:
1. Independent protocol/consensus review and operator approval of authority, cancellation and numeric policy parameters; final specification and test vectors.
2. Versioned network/manifest and explicit migration/activation strategy. No enabling the candidate under the existing v6 chain identity.
3. Integrate new transaction encodings/state commitments with mint/transfer/trade validation, canonical SQL undo/indexes/events, actual RandomX blocks, peer sync, restart and multi-host fork/partition tests. The current model supplies no PoW, issuance or fork-choice implementation.
4. Durable PostgreSQL sale witness/terms storage, financially reserved host-signing workflow, outbox fencing, canonical observer/inbox, atomic terminal postings and reorg holds. Existing sandbox signatures do not authorize the new sale protocol. No signing before final exact order consent.
4. Production PostgreSQL sale witness/terms storage, financially reserved host-signing workflow, outbox fencing, canonical observer/inbox, atomic terminal postings and reorg holds. The disposable signed-transcript rehearsal exercises terminal postings and durable review, but supplies none of the canonical observer/host-signing integration. Existing sandbox signatures do not authorize the new sale protocol. No signing before final exact order consent.
5. Publication/review UI, key management/rotation, privacy and public-price disclosure, window/griefing/storage limits and operational recovery. Public terms would reveal sale linkage and amounts; privacy acceptance is a product decision.
6. Monero confirmation/unlock observation, real treasury controls, coordinated financial/node recovery and independently reviewed production release gates. No live wallet, funds or credentials were used in this increment.