Files
rose-ash/plans/commerce-on-sx.md
giles a0f3a1177e
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 45s
commerce: public session API + per-line audit + checkout stub (12 tests) — Phase 1 done
api.sx — session facade {:ctx :cart}: commerce-add/remove/set-qty/total/
count/lines, commerce-can-add? catalog validation, commerce-explain per-line
audit breakdown, commerce-checkout Phase-3 stub. Completes Phase 1 (catalog +
cart + deterministic totals). Total 66/66 across 4 suites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 23:46:51 +00:00

5.6 KiB
Raw Blame History

commerce-on-sx: Catalog, cart, pricing & orders on miniKanren

DRAFT outline. The revenue vertical. Depends on persist-on-sx (durable orders) and flow-on-sx (checkout as a durable flow). Don't start before persist-on-sx Phase 1 is green.

rose-ash's revenue engine — market (catalog), cart (checkout), orders (SumUp payment, reconciliation) — has no SX subsystem. The hard part of commerce isn't CRUD; it's pricing: discounts, bundles, tax, membership rates, promotions that stack (or don't). These are relations, and a relational engine can run them in multiple directions — forward ("what's the total?") and backward ("what promo code yields this total?", "which line item triggered the discount?").

That's a miniKanren fit. Pricing/promotion rules are relational; cart and order lifecycle (reserve → pay → fulfil → reconcile) is a durable flow; the order ledger is a persist stream. Commerce is the first real composition subsystem.

End-state: a catalog model, a relational pricing/promotion engine, a cart with deterministic totals, and an order lifecycle flow with payment-webhook reconciliation — all auditable via the event log.

Status (rolling)

bash lib/commerce/conformance.sh66/66 (4 suites: catalog, cart, price, api) — Phase 1 done

Ground rules

  • Scope: only lib/commerce/** and plans/commerce-on-sx.md. May import from lib/minikanren/, and (once they exist) lib/persist/ + lib/flow/. Do not edit substrates.
  • Architecture: prices/promotions are miniKanren relations over catalog facts; a cart total is a deterministic query result (first solution under a fixed rule order). Order lifecycle is a flow that suspends at the payment IO boundary. Money is integer minor units — never floats.
  • Determinism: promotion stacking must have explicit, tested precedence; totals must be reproducible from the cart + catalog snapshot.
  • Commits: one feature per commit. Progress log + tick boxes.

Architecture sketch

Catalog + cart                          Total / order
  product(id,price,tags)                  {:subtotal :discounts :tax :total}
        │                                       ▲
        ▼                                       │
lib/commerce/catalog.sx                 lib/commerce/price.sx
  — product / variant / stock facts       — miniKanren pricing relations
        │                                  — promo stacking, membership rates
        ▼                                       ▲
lib/commerce/cart.sx                    lib/commerce/order.sx  (flow + store)
  — line items, quantities                — reserve→pay→fulfil→reconcile
        │                                  — SumUp webhook = flow resume
        ▼                                       │
lib/commerce/api.sx ── (commerce/add) (commerce/total) (commerce/checkout) ──┘

Phase 1 — Catalog + cart + deterministic totals

  • catalog.sx — product/variant/stock as facts
  • cart.sx — line items, add/remove/qty
  • price.sx — base pricing relation, subtotal; tax
  • api.sx + tests + scoreboard + conformance.sh

Phase 2 — Promotions (relational)

  • promo rules: percentage, fixed, bundle, member rate
  • explicit stacking precedence; "best price" backward query
  • tests: stacking order, mutually-exclusive promos, member vs guest

Phase 3 — Order lifecycle (flow + store)

  • order flow: reserve stock → await payment → fulfil
  • payment webhook resumes the suspended flow
  • order ledger as a persist stream; idempotent reconciliation

Phase 4 — Reconciliation + federation

  • mismatch detection (paid≠ordered) as queries over the ledger
  • cross-instance catalog (federated marketplace) — out-of-scope stub
  • tests: webhook replay, partial refund, double-charge guard

Progress log

  • 2026-06-06 — api.sx (Phase 1 complete): session facade {:ctx :cart} with commerce-add/-remove/-set-qty/-total/-count/ -lines, commerce-can-add? catalog validation, commerce-explain per-line audit breakdown ({:sku :variant :qty :unit :extended :tax}), and a commerce-checkout Phase-3 stub. api suite 12/12; total 66/66.
  • 2026-06-06 — price.sx: deterministic cart-subtotal (Σ unit×qty, variant delta defaults 0) + jurisdiction-relational tax. taxo facts indexed by (jurisdiction, product-class, customer-class)→bps, queried multidirectionally; apply-bps rounds half-up with integer arithmetic only. cart-total returns {:subtotal :discounts :tax :total} (discounts 0 until Phase 2), reproducible from (context, cart). = does structural dict equality (order-independent), so total dicts compare directly. price suite 20/20; total 54/54.
  • 2026-06-06 — cart.sx: cart as an ordered list of (sku variant qty) lines. Pure ops cart-add (merges same line / appends), cart-set-qty (0 removes), cart-remove, plus cart-qty/cart-count/cart-skus/cart-empty?. cart-lineo is the relational view (membero over the cart) — forward and backward. cart suite 18/18; total 34/34.
  • 2026-06-06 — catalog.sx: catalog snapshot (products/variants/stock as fact tuples) + multidirectional accessor relations (producto/varianto/stocko, derived priceo/classo/unit-priceo) + deterministic catalog-price/ -class/-has? helpers. conformance.sh harness + scoreboard. catalog suite 16/16. Gotcha: minikanren run-n macro binds s internally — query vars must avoid s; tests compare reified results with = (not equal?, which fails on reified lists). Money = integer minor units throughout.

Blockers

(none)