docs(plantare): mark bilateral signed record shipped

Flip plantare-bilateral.md from "design, not yet built" to "v1 shipped",
summarize what landed (transport / schema v12 / service / UI), resolve
the open questions inline with the decisions made, and list the honest
follow-ups (durable outbox, accept-after-restart, rating anchor,
provenance DAG, paper/group/forgiveness sync).
This commit is contained in:
vjrj 2026-07-14 11:19:55 +02:00
parent e78656bc07
commit 4ab146b157

View file

@ -1,8 +1,29 @@
# Plantaré — the bilateral signed record (design, not yet built)
# Plantaré — the bilateral signed record
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:
Status: **v1 shipped (2026-07-14).** The two-sided signed Plantaré — the "formulario
bilateral firmado" other docs waited on — is now built end-to-end: transport in
`commons_core`, schema v12 + repo, `PlantareService`, and UI (propose from a chat,
accept/decline on the ledger). What's left is noted under "Deferred to a follow-up"
below. This doc keeps the original design for reference; the "Open questions" are now
resolved inline.
Built (commit series `feat(plantare): …`, 2026-07-14):
- **Transport**`PlantarePledge` (canonical, byte-stable core + two BIP-340 Schnorr
stubs), `PlantareCrypto` (sign/verify over SHA-256 of the core), `PlantareTransport` +
`NostrPlantareTransport` (propose→accept→counter-sign / decline over NIP-17 gift wrap
carrying a tagged JSON payload; the message inbox skips these and vice-versa).
- **Schema v12** — the deferred columns on `Plantares` (`pledgeId`, `debtorKey`,
`creditorKey`, `debtorSignature`, `creditorSignature`, `movementId`, `remoteState`,
`returnKind`, `workHours`), all nullable/defaulted so v1 local rows are untouched;
guarded migration + schema dump + migration test.
- **`PlantareService`** — signs my stub, sends, and reconciles incoming moves into the
ledger (verifies stubs before storing; pending proposals held in memory keyed by
pledge id, relay redelivers on reconnect).
- **UI** — propose sheet reached from the chat overflow menu; accept/decline + handshake
badges (awaiting signature / signed by both / declined) on the Plantares screen.
This doc originally specified the two-sided Plantaré so a later session could 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
@ -147,14 +168,34 @@ An `accepted` Plantaré is the **strong anchor** for a rating (both parties prov
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)
## Resolved in the v1 build (2026-07-14)
- **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.
- **Conflict / divergence:** RESOLVED as anticipated — the two signatures cover the
canonical core (everything except the signatures), and the `pledgeId` is part of that
core. Any edit changes the signing hash, so an accept always closes the *exact* payload
proposed; a changed pledge is a different id and must be re-proposed. `PlantareCrypto`
enforces this; the "editing after signing invalidates the stub" test pins it.
- **Revocation / forgiveness sync:** kept **one-sided** for v1 — `status`
(`open`/`returned`/`forgiven`) stays each party's own view, distinct from the shared
`remoteState` handshake. Propagating a forgive to the counterparty is a follow-up (it'd
need a signed "released" move).
- **Expiry:** `dueBy` passing is a **UI nudge only** (overdue styling), as in Phase 1 — it
does not change `remoteState`.
- **Paper ingest:** the schema already supports it (nullable keys/signatures + free-text
`counterparty`); no UI path yet — deferred.
- **Group/collective party:** deferred — still tied to the collective-identity question in
`sharing-model.md` §6.
## Deferred to a follow-up
- **Durable send outbox.** Propose/accept send best-effort over the current session; if
offline the local row is recorded but the move isn't queued for retry. A durable outbox
(like `offer_outbox`) should back it so a proposal made offline goes out on reconnect.
- **Accept after restart before redelivery.** Pending proposals live in memory (refilled by
NIP-17 redelivery on reconnect); accepting one the relay hasn't redelivered yet shows a
gentle "offline / reconnecting" nudge rather than reconstructing the pledge from the row.
- **Rating anchor.** An `accepted` Plantaré is now a provable closed deal, but the rating
flow isn't yet wired to require/prefer it (the "strong anchor" upgrade).
- **Provenance DAG.** `movementId` is stored; building the distributed procedence graph
from accepted pledges is not done.
- **Forgiveness/paper/group** as above.