Status: 0.1-draft SKELETON — curated before implementation, on purpose. This is the artifact to argue with: every section below is a claim about how SMB purchasing should work under governed agents, written down so a design partner can tell us where it is wrong before a line of the module exists. Open questions are marked ⚑.
o2c's mirror image: o2c is money coming in for things we sell; p2p is money going out for things we buy. Same doctrine — one definition per act, agent and UI on the same guards, one audit trail, refusals that name their numbers.
Same band as o2c: 5–200 people, one legal entity, one currency, one warehouse. Businesses that BUY stocked goods on terms — distributors, wholesalers, light manufacturers — plus the service bills every business has. Between a toy and SAP MM: no requisition chains, no RFQ tournaments, no consignment; a real match gate, real receipt discipline, real AP.
In: vendors, purchase orders, goods receipts, vendor bills, the 3-way match, AP aging, payment RECORDING, debit notes, duplicate-bill guard, GRNI visibility, item cost from receipts.
Out (see §9): payment EXECUTION (no rails, ever — INV-22 family), landed cost, multi-currency, FIFO/specific-identification costing, requisitions and approval chains, drop-ship, consignment, recurring bills.
Writes (~15): create_po, issue_po, amend_po, cancel_po, close_po_short, receive_goods, record_vendor_bill, dispute_bill, approve_bill, void_bill, record_bill_payment, apply_bill_payment, record_debit_note, apply_debit_note, return_to_vendor.
Reads (~6): po, vendor_position, bill (the match report rides on it), ap_aging, unbilled_receipts (the GRNI list), payment_worklist (approved bills by due date).
Environment acts (core): create_vendor, update_vendor (banking fields human-only), set_match_tolerance.
⚑ Open: does approve_bill need its own permission tag (ap.approve) or does o2c's credit.authority pattern generalize to a shared "money authority" tag?
⚑ Open: is GRNI accrual right for the smallest books, or should sub-N-employee mode expense-on-receipt? (Lean: GRNI always — it is derivable, and hiding it is how month-end surprises happen. A design partner may prove us wrong.)
vendor_position (open POs, unbilled receipts, open AP, next payments due), ap_aging (mirror of AR aging), unbilled_receipts, payment_worklist, and the bill read carrying its full match evidence — the artifact you hand an auditor.
Contract: the match gate at approval; receipt-only stock entry; human-only banking; derived cost; payment recording only. Freedom: tolerance values, PO numbering, whether service bills 2-way match (no receipt) — ⚑ likely contract as "2-way for unstocked lines," to be argued.
01 happy path PO→receipt→bill→approve→pay · 02 the match gate refuses (price high, qty over, then tolerance raised with reason — the credit-gate story, mirrored) · 03 partial receipts and GRNI · 04 dispute → resolve → pay · 05 duplicate bill guard · 06 debit note and return-to-vendor · 07 the agent tries to change vendor banking (refused, logged) · 08 costing: two receipts at different costs → moving average → COGS on next o2c shipment.
| Item | Why deferred, not rejected |
|---|---|
| Payment execution / bank files | The system records; it never moves money. Anything else changes what Saybooks is. |
| Landed cost | Real for importers; drags freight allocation design. Own pass. |
| Multi-currency | Same whole-area concern as o2c §9. |
| FIFO / specific identification | Moving average first; method choice needs a design partner with a reason. |
| Requisitions & approval chains | SMB calibration: one approval act with a permission tag. Chains are enterprise theater until someone proves otherwise. |
| Drop-ship / consignment | Stock-ownership semantics beyond one warehouse. |
| Recurring bills | Subscription lifecycle, different vertical. |
---
Change log: 0.1-draft skeleton (2026-08-24) — curated before implementation; written for design partners to mark up. No module claims this spec yet; the conformance machinery ignores it until one does.
| Act | Kind | Required |
|---|---|---|
create_po | write | vendor lines |
issue_po | write | po |
amend_po | write | po |
cancel_po | write | po reason |
close_po_short | write | po reason |
receive_goods | write | po lines |
record_vendor_bill | write | vendor bill_number lines |
dispute_bill | write | bill reason |
approve_bill | write | bill |
void_bill | write | bill reason |
record_bill_payment | write | vendor amount |
apply_bill_payment | write | payment bill |
record_debit_note | write | vendor amount kind reason |
apply_debit_note | write | debit_note bill |
return_to_vendor | write | po lines reason |
po | read | po |
vendor_position | read | vendor |
bill | read | bill |
ap_aging | read | — |
unbilled_receipts | read | — |
payment_worklist | read | — |
create_vendor | environment | name |
update_vendor | environment | vendor |
set_match_tolerance | environment | reason |
| Id | Invariant |
|---|---|
P2P-1 | Money is integer cents, everywhere. |
P2P-2 | Nothing is paid without an approved bill; nothing approves without a match on the record. |
P2P-3 | The 3-way match names its numbers; tolerance is config, breach is a refusal. |
P2P-4 | Stock enters only through a receipt; receipts are immutable facts. |
P2P-5 | Item cost is derived from receipts, never entered. |
P2P-6 | Vendor bank details are human-only; an agent can record a bill, never redirect money. |
P2P-7 | A disputed bill cannot be paid; dispute and resolution carry reasons. |
P2P-8 | PO prices freeze at issue; variances are shown, never averaged away. |
P2P-9 | Payments are recorded, never executed — no outbound rails. |
P2P-10 | The duplicate-bill guard surfaces warnings; it never auto-resolves them. |
P2P-11 | Over-receipt and over-billing are refusals naming the quantities. |
P2P-12 | Every write is a logged command with an actor; refusals are logged too. |
P2P-13 | Journal entries derive balanced: receipt to GRNI, approval to AP, payment to cash; COGS arrives with cost. |
P2P-14 | Goods received not invoiced is always visible — a real liability, not a residue. |