Buying together · technical page · July 9, 2026 · for people who build and break software

Two archives split one case of olive oil.

Zero copies of anyone's life. The co-purchase protocol: a local replenishment miner over each person's own receipts, plus an utterance-only exchange between paired archives (proposal, agreement, receipt lines, settlement) with a transitive field allowlist standing between the archive and the wire. Built July 9, 2026 as a working prototype; 143 tests green; synthetic data only. The plain-words version is for everyone else.

The shape

Each person's archive learns locally and speaks minimally. Nothing reads the other archive. Envelopes cross between them, and each side files what it agreed to in its own home. Same doctrine as everything else here: the archive is canonical; the gate is how others read you; message transport is how you speak.

ARCHIVE A (shopper) ARCHIVE B   Receipts/ → extraction files Receipts/ → extraction files │ pna_replenish.mine() — local, C1 │ same, on B's machine ▼ ▼ shopping-list-vN.yaml (cadence, qty, shopping-list-vN.yaml │ solo unit-price baseline — STAYS HOME) │ ▼ approve_intent() — the consent moment ▼ minimized intent ────────────────╮ ╭──────── minimized intent ▼ ▼ ┌─────────────────────┐ │ TRANSPORT (shared │ envelopes only; unknown │ directory in v0) │ senders quarantined unopened └─────────────────────┘   verb thread: PROPOSE → ACCEPT | COUNTER | DECLINE → CONFIRM ⇄ CONFIRM → RECEIPT-SHARE → SETTLE (CANCEL: organizer only, tombstone) DONE is local-only — outcome, vouch, savings never cross

Envelopes follow the sharing rail's shape — env · thread · verb · from · to · sent_utc · body · sealing · auth — authenticated with HMAC-SHA256 under a pair secret established by a human pairing ceremony, verified with a constant-time compare. The thread of signed envelopes is canonical; current state is a pure replay of it (delete the state file, rebuild byte-identical — tested).

What is allowed to cross

The intent body is built from a positive allowlist, enforced transitively: every key at every depth must be on the list, and 19 named keys (the deny list) fail the mint outright. Both the miner and the exchange engine carry the same constants; a parity test fails if they ever drift.

{ // kisop.copurchase.intent/v0.1 — everything an ask is allowed to say "commodity": "K-food.oil.olive", // coarse code only; brand, SKU, and store stay out "pack_options": [{"pack":"case-6x2L","unit":"case","splittable_at":"bottle"}], "share_fraction": 0.5, "quantity_wanted": {"amount":3,"unit":"each-2L"}, // forward ceiling only; past totals stay out "substitution_ok": true, "time_window": {"earliest":"2026-07-10","latest":"2026-07-24"}, "proximity": {"geohash_prefix":"9q9h","max_km":8,"band":"walk-or-short-drive"}, "pickup": {"handoff":"same-day-ok","cold_chain":false}, "issued":"…","expires":"…","consent_class":"C3","intent_id":"…","party_ref":"…","schema":"…" }   DENIED, at any depth (mint fails closed): unit_price_baseline_cents · cadence_days · label_as_printed · brand · store · merchant · prices_paid · receipts · diet · conditions · income · household_size · source_assertions · … (19 keys total)

The receipt-share is the exchange's one excerpt-shaped moment, and it is composed: only line items whose commodity is in the agreement's set may appear. The receipt itself is never copied across. A smuggled foreign line is refused before signing. The rest of the shopper's receipt is structurally invisible to the counterparty.

Enforced versus recorded

Same discipline as the builders page: a claim is ENFORCED only when a test refuses the violation today. RECORDED means written down and honored by convention; FUTURE means designed. The build has not started.

ClaimStandingWhere
Nothing but allowlisted fields crosses; deny-listed keys fail the mint closed, transitivelyENFORCEDX1, R6 — 8 tests incl. nested contamination
Purchase history, baselines, cadence, off-agreement receipt lines never appear in any crossing artifactENFORCEDX2 — full-lifecycle scan + smuggled-line refusal
One-bit tamper of body or metadata → HMAC refuse, nothing landsENFORCEDR2
Unknown sender → quarantined unopened, logged, never renderedENFORCEDR1
Exactly one courier-log line per send/receive/quarantine/reject; unwritable log ⇒ operation refusesENFORCEDR4 — send refuses; receive leaves envelope untouched
Replay & out-of-order verbs are no-ops/refusals; state is a pure replay of the signed threadENFORCEDR3, R5 — rebuild byte-identical
Settlement is integer-cent exact (largest-remainder), sums reconcile, no payment API existsENFORCEDX5 — incl. a source grep for payment calls
Savings claimed only against the person's own solo baseline; otherwise “unknown, not claimed”ENFORCEDX6
Tests can never write into a real archive at ~/PNAENFORCEDX7/M4 — write-guard with a non-vacuity meta-test
Intents carry consent class C3 and cross under a circle grant through the consent gateRECORDEDclass is stamped; the gate (pna-mcp) is not wired into this loop yet, so enforcement happens at the library level today
Vouch/reliability record informs circle trustRECORDEDlocal C1 ledger accrues; no cross-archive semantics yet
Stranger discovery (ring 2): blinded commodity rendezvous, k-anonymity, PSI upgrade, Solution-Center anchoringFUTUREspecified in CO-PURCHASING-SPEC §4
End-to-end sealing rung (age/X25519), asymmetric identity (Ed25519), live transports (Signal / shared repo)FUTUREv0 = HMAC pair secrets over shared-folder transport

Run it yourself

# from KISOP-SYSTEM/pna/cli — stdlib only, Python 3.9+, no installs python3 test_pna_replenish.py # 78 tests — miner, minimization, supersede-never-overwrite python3 test_pna_copurchase.py # 65 tests — rail, state machine, settlement, savings python3 demo_copurchase.py # two synthetic archives, full exchange, who-sees-what narrated

The demo builds two synthetic archives, runs the real miner on both, and walks the whole exchange with the transport logs printed: a 6×2L case at $41.94 + $3.36 tax splits 3/3 → $22.65 each; one party's savings is $35.25 against her own baseline, the other's is “unknown, not claimed” because no baseline exists. The run exercises both honesty paths in front of you.

Defects found while building — recorded, because that's the culture

Attack here

The adversarial pass (F4) inherits these targets; good-faith security research is welcome through the main site's Write to us → security door, promise written into the Terms.

Where it sits

This protocol rides the sharing rail's correspondence discipline (utterance-only envelopes between paired archives) as its second application, beside the Together calendar. Its receipt-share is the rail's first consented excerpt (the deferred v0.2 path whose consent mechanics the export-rail draft specifies). The consent gate pna-mcp (built, adversarially tested) is not yet wired into this loop; the gate-side export tool is the named enforcement seam. Working documents, in the repository: product/CO-PURCHASING-SPEC-v0.1.md · pna/SHARING-SPEC-v0.1.md · pna/SHARING-SPEC-v0.1-export-rail.md · pna/PNA-COPURCHASE-BUILD-REPORT.md.

The privacy story ends at the money seam, and we say so: the settlement statement is computed inside the system, but the reimbursement itself moves person-to-person (Venmo, Zelle, cash), and a payment handle is identity leaving the rail. Cash is the only zero-leak settlement. A tidier diagram would leave the money seam out. We left it in.

Where this stands

Prototype, built July 9, 2026, on synthetic data only. Two invented archives on one computer; a directory provides the transport; the founder's real archive was never touched (verified: its access log is unchanged).

Not yet: gate integration, live transports, the e2e sealing rung, more-than-two-party circles, stranger discovery. Each is specified with plain statuses in the documents above.

Open design calls (the founder's): whether a coarse “it happened” crosses at completion or stays local (built: local) · the coarse commodity vocabulary and its owner · warehouse-club membership terms are on the counsel pile before any pilot.