# Plantaré — the bilateral signed record (design, not yet built) Status: **design locked, implementation deferred.** This doc specifies the two-sided Plantaré so a later session can build it without re-deciding. It is the "formulario bilateral firmado" that other docs wait on: - [`data-model.md` §2.7](data-model.md) — the `Plantare` entity, whose `debtor_key`, `creditor_key`, `debtor_signature`/`creditor_signature` and `movement_id` columns are specified there but **deferred** in the shipped Drift table. - [`sharing-model.md` §1, §6](sharing-model.md) — the Plantaré as the *private bilateral close* of an exchange; and the open question of anchoring reputation to a closed deal. - [`PLAN.md` §5-bis](../../PLAN.md) — the paper original (BAH-Semillero, 2009): "partida doble repartida entre dos bolsillos", device-to-device, provenance DAG. - [`open-decisions.md`](open-decisions.md) — strong rating anchoring "espera al formulario bilateral firmado". ## What ships today (v1, local-only) A private local ledger only. `apps/app_seeds` `Plantares` table stores `varietyId`, `direction` (`iReturn` / `owedToMe`), `counterparty` (**free text**), `owedDescription`, `madeOn`, `dueBy`, `status`, `note`. No identity keys, no signatures, no transport — the other party gets **no record**. The UI now surfaces the linked variety and the dates (Phase 1), but it is still one-sided. ## The paper original (v0.4, transcribed) Source: BAH-Semillero, *Plantaré — moneda comunitaria de intercambio de semilla*, v4.0, © 2009–2010, CC-BY-SA 3.0 (`plantare.ourproject.org`). The printed form is **two matching stubs** ("partida doble") on one sheet: **Stub A — "CORTAR Y ENTREGAR A CAMBIO DE LAS SEMILLAS"** (cut off and handed over with the seeds; the **giver keeps it** as proof of what they're owed): - **Yo** ______ (your name) - **localizable en** ______ (*email, tel., etc.* — contact info) - **recibo** ______ (*cantidad de semillas o lo que sea que recibo*) - **y me comprometo a devolver antes de (mes/año):** __ / 20__ - return options (*choose those that apply*): - `[ ]` una **cantidad similar\*** de la recibida — *(\*) similar = no hibridada, no transgénica, cultivada en ecológico* - `[ ]` ó un **trabajo de ___ horas** equivalente - `[ ]` ó ______ (free text — anything else) - **Firmado,** ______ **el** __ / __ / 20__ (signature + date) **Stub B — "RESGUARDO QUE ME QUEDO"** (the receipt the **receiver keeps** to remember what they owe): person/collective owed, quantity received, and "lo devolveré antes de __/20__". What the paper captures that v1 does **not** yet: - **Contact** of the party ("localizable en") — v1 stores only a free-text name. Digitally this becomes the **Nostr pubkey** (reachable by DM); keep a free-text contact for the offline/paper case. - **Structured return options** instead of one free-text line: *similar quantity* (default), *equivalent work in N hours*, or *other*. The work-hours option is time-as-currency — it overlaps Tane's hand-over payment model, which already allows a time/`tiempo` currency ([project-sales]; €/Ğ1/time/none). - **"Similar" is a defined term** — open-pollinated, non-GMO, organically grown — which ties to Tane's existing organic/eco self-declaration flag. The default return preset should carry this meaning, not just the word. - **Return-by is month/year**, matched to cultivation cycles (v1 stores a full day; Phase 1's picker is day-precision — display can round to month/year). ## The goal Turn the one-sided note into the paper Plantaré's **two matching signed stubs**: when two people close an exchange, both devices end up holding the *same* commitment, each signed by both, so either can prove "recibí X, devolveré una cantidad similar antes de la fecha Y". Non-goals: interest, enforcement, debt framing (microcopy stays "promise, not debt"); a central server or global ledger (it stays device-to-device / relay-as-enrichment). ## Identity — counterparty becomes a real key Replace (or rather, *augment*) the free-text `counterparty` with the counterparty's **Nostr pubkey / `Party`**, reusing the identity already present in the social layer (offers, chat). The flow starts from an existing conversation or a closed offer, so the pubkey is in hand. Keep the free-text field as the offline/paper fallback (a Plantaré made face-to-face with someone who has no app, or ingested from a paper form / QR). ## Schema delta (versioned Drift migration) Add the deferred columns to `Plantares`, nullable so v1 rows coexist untouched: - `debtorKey` TEXT — pubkey of who received and owes a return. - `creditorKey` TEXT — pubkey of who gave. - `debtorSignature` / `creditorSignature` TEXT — signatures over the canonical payload (both "stubs of the paper"). - `movementId` TEXT → `Movement` — the shared `given` event this accompanies (the `Movement.plantareId` link already exists on the other side). - `remoteState` enum — `proposed` / `accepted` / `declined`, distinct from `status` (`open`/`returned`/`forgiven`), which tracks the *promise*, not the *handshake*. From the paper form: - ~~`contact` (the paper's "localizable en", email/phone)~~ — **decided NOT needed** (2026-07-13). It's a paper-era artifact: digitally the counterparty is a Nostr pubkey, reachable by DM, so a manual contact field is redundant. Offline/paper Plantarés keep it only inside the free-text `counterparty`. - `returnKind` enum — `similar` / `workHours` / `other`, mirroring the form's checkboxes (default `similar`, meaning open-pollinated · non-GMO · organically grown — surface that definition, not just the word). `workHours` REAL for the "trabajo de N horas" option; `owedDescription` still backs `other`. Note the work-hours option overlaps Tane's hand-over time/`tiempo` currency — reconcile the two rather than duplicating. Kept for the Phase 2 build; not added to v1. A v1 local row = both keys/signatures null, `remoteState` null. Migration test in CI per [`data-model.md` §5](data-model.md). ## Transport — `PlantareTransport` in `commons_core` A new interface alongside `OfferTransport` / `MessageTransport` / `TrustTransport` / `RatingTransport` / `SyncTransport` / `ReportTransport` (`packages/commons_core/lib/src/social/`), on the shared `NostrConnection`. Unlike ratings (public signed events), a Plantaré is **private to the two parties**, so it is *encrypted*, like DMs/sync. Sketch, mirroring the existing `*Transport` shape: ```dart abstract interface class PlantareTransport { /// Send a signed proposal (my signature only) to [counterpartyPubkey]. Future propose(PlantarePledge pledge); /// Counter-sign a received proposal and send the fully-signed copy back. Future accept(PlantarePledge pledge); /// Decline a received proposal. Future decline({required String pledgeId, String reason}); /// Incoming proposals / accepts / declines addressed to this identity. Stream incoming(); Future close(); } ``` **Handshake:** propose → accept → counter-sign. A proposes (signs, encrypts to B); B reviews, accepts (adds B's signature), sends the doubly-signed copy back; both persist the identical row (`remoteState = accepted`). Decline leaves A's row `declined`. **Channel:** prefer **NIP-17 encrypted DM** (already used for messaging) carrying a canonical JSON payload — least new surface, rides the existing connection. A dedicated event kind is an alternative if we want first-class filtering. **Offline QR/NFC** device-to-device (PLAN.md's favoured, most-decentralized path) is the same payload exchanged without a relay — the transport abstracts channel from payload. Relays are enrichment only; offline must fully work. ## Provenance DAG Each accepted Plantaré links parent→child (this lot came from that exchange), forming the variety's distributed **procedence DAG** ([PLAN.md §5-bis](../../PLAN.md)) — "un grafo de commits, pero de semillas". `movementId` + the counterparty's prior movement give the edge. ## Reputation anchor An `accepted` Plantaré is the **strong anchor** for a rating (both parties provably closed a real deal), upgrading the current soft, per-conversation anchoring — closing the [`open-decisions.md`](open-decisions.md) / [`network-trust.md`](network-trust.md) item. ## Open questions (for the build session) - **Conflict / divergence:** if A and B end up with mismatched payloads (edited after propose), which wins? Likely: accept is over an immutable hash; any change re-proposes. - **Revocation / forgiveness sync:** `forgiven` is one-sided today; should it propagate to the counterparty's copy, or stay each party's own view? - **Expiry:** does `dueBy` passing change `remoteState`, or only the UI nudge (as in Phase 1)? - **Paper ingest:** signature model for a Plantaré where one side has no key (photo/QR of a paper stub) — store as `creditorSignature` null, `counterparty` free text. - **Group/collective party:** a seed bank (not a person) as creditor — ties to the collective-identity question in `sharing-model.md` §6.