65 lines
16 KiB
Markdown
65 lines
16 KiB
Markdown
# Tane — Decisiones abiertas y "qué chirría"
|
||
|
||
*Revisión de conjunto antes de seguir construyendo. Reúne los cabos sueltos repartidos por los docs de diseño, ordenados por cuándo hay que decidirlos, y nombra las tensiones del plan que conviene mirar de frente.*
|
||
|
||
> **Actualización (2026-07-10) — se abre la ronda social (Block 2).** El spike de de-risking ([spike-block2-findings.md](spike-block2-findings.md)) validó el happy-path completo de la capa social: derivación de la clave Nostr desde la semilla Ğ1, `OfferTransport`/NIP-99, mensajería NIP-17 (privada y con metadatos ocultos) y red de confianza estilo Duniter, todo sobre **una `NostrConnection` compartida con tres interfaces**. Con eso, la puerta de "no empezar Block 2" queda **levantada**. Orden de construcción dentro de Block 2: **(1) base de transporte en `commons_core`** (sobre librerías contrastadas — `nostr`, Dart puro, LGPL) → (2) UI de ofertas → (3) endurecer mensajería (entrega offline, vectores NIP-44, multidispositivo) → (4) parámetros WoT + arranque en frío. Los ítems de §B siguen siendo las decisiones a cerrar; §D.3 y §D.7 quedan **resueltos** por el spike.
|
||
|
||
## A) Decidir antes de escribir código de la Fase 1 (inventario)
|
||
|
||
Estas fijan `schemaVersion = 1` y el arranque técnico:
|
||
|
||
- **Formato de exportación/copia → ✅** un **archivo único cifrado**, abierto, documentado y versionado (volcado SQLite/CRDT + fotos + semilla envuelta). → [backup-and-recovery.md](backup-and-recovery.md)
|
||
- **Cifrado en reposo (el "cómo" técnico):** SQLCipher + llave aleatoria en el almacén del sistema + QR de recuperación. Conceptualmente decidido; falta bajar el mecanismo. → [security-privacy.md](security-privacy.md)
|
||
- **Identidad → ✅ una sola clave** raíz (Duniter/Ğ1), de la que se deriva la secp256k1 para Nostr. La clave simétrica que cifra la BD en reposo es un **detalle interno aparte** (no es una identidad), así que no contradice el "una sola identidad". → [g1-integration.md](g1-integration.md), [security-privacy.md](security-privacy.md)
|
||
- **Detalles de modelo que fijan el esquema → ✅** `Quantity` como **tipo compartido** (Lot y Movement); `offer_status` **solo en Lot**; `category` **texto libre** prerrellenado desde `Species.family`. → [data-model.md](data-model.md) §6
|
||
- **Forma del lote (`LotType`) y envase (`Presentation`) → ✅** (schema v4). `LotType` pasa de `{seed, plant}` a **6 formas** append-only: `seed, seedling(plantón), plant, tree, bulb, cutting` — tras repasar el vocabulario de viveros/tiendas ("plantel" era mal nombre: es el semillero, no una unidad). El **envase** (`pot/tray/plug/bareRoot/rootBall`) es un **atributo opcional aparte**, no una cantidad, para no mezclar "cuántas" con "en qué formato". Germinación sigue siendo solo-`seed`. → [data-model.md](data-model.md) §2.3.2
|
||
- **Licencia** del código → ✅ **AGPL-3.0** (GPLv3 + cláusula de red; compatible con GPLv3 y con Duniter/Ğ1nkgo). Falta añadir el fichero `LICENSE` al abrir el repo.
|
||
- **Varilla v1 → ✅ viable** con datos abiertos: nombres/identificación (Wikidata+GBIF, fácil), **conservación desde Kew SID** (dominio público, ~52k taxones), cuidados enlazados de Permapeople. Falta: confirmar acceso/descarga de Kew SID y el subconjunto curado a empaquetar. → [data-notes.md](data-notes.md) §3-ter
|
||
- **Estructura del workspace** (`commons_core` + `app_seeds`, pub workspaces) — decidido; falta materializarlo.
|
||
|
||
## B) Decidir antes de la capa de compartir (Fase 3) — muy interdependientes
|
||
|
||
- **Transporte de la capa social → Nostr (confirmado).** No se renuncia a Nostr: **Duniter no tiene mensajería**, así que Nostr es la vía para mensajes y ofertas. Se mantiene "una sola identidad" derivando la clave **secp256k1** (Nostr) de la semilla raíz Ğ1. Falta: confirmar madurez de los NIPs y la ruta de derivación. (Datapods de Duniter descartados: no están en servicio.) → [g1-integration.md](g1-integration.md)
|
||
- **Estrategia de relays:** app-as-relay oportunista + proximidad física + relays comunitarios. → network-trust §3
|
||
- **Confianza egocéntrica sustituye a la WoT global — DECIDIDO/IMPLEMENTADO (2026-07-11), SUPERSEDE la decisión del 2026-07-10.** La membresía Duniter completa (referentes curados + `WotParams` sigQty/stepMax, pantalla de raíces npub y steppers) se retiró de la app: resolvía identidad sybil-proof para una RBU — problema que Tane no tiene —, exigía curar referentes por comunidad/país (inviable internacionalmente; un set de referentes malo es peor que ninguno) y su pantalla violaba "palabras humanas, nunca jerga". **Queda solo la vista egocéntrica**: tú avalas ("Conozco a esta persona", en el chat) / tu círculo (distancia ≤2 desde ti, umbral 1) / avalado por N / desconocido. Cero bootstrap, cero parámetros de usuario; los avales caducan (365 días) y se renuevan. Un racimo sybil queda desconectado de la vista de cada observador — el filtro antispam se conserva sin veredicto global. **Se mantiene sin cambios** el primitivo (`Certification`, kind Nostr 30777, una cert viva por par) y el motor puro `WebOfTrust` en `commons_core` (usado para el círculo; capacidad latente para una futura "confianza por comunidad" o importación opcional de la WoT Ğ1 — sin migración de eventos). **UI:** badge por `TrustTier` (en tu círculo > avalado > desconocido) en el chat; pantalla "Tu gente" (a quién avalas —revocable— / quién te avala) desde el perfil, sustituyendo a "Red de confianza". Eliminados `TrustReferents`, `WotSettings`, `TrustNetworkScreen` y el asset de referentes. Certificación por colectivo sigue en sharing-model §6. → network-trust §2
|
||
- **Valoraciones tipo Wallapop v1 — DECIDIDO/IMPLEMENTADO (2026-07-11).** Estrellas 1–5 + comentario corto, públicas y firmadas (`Rating`, kind Nostr **30778** direccionable, `d` = sujeto): **una valoración viva por par** — re-valorar edita, no acumula; retirable. **Anclaje blando v1:** solo puedes valorar a alguien con quien tienes conversación (el anclaje fuerte a trato cerrado firmado sigue abierto, ver "Reputación" abajo). Se muestran en el detalle de oferta y en el chat **ponderadas por tu círculo** ("N de gente que conoces", mismo cálculo egocéntrico) — inflar reseñas con claves desconocidas no destaca nada; sin valoraciones no se muestra nada (la ausencia nunca es un reproche). Quinto transporte (`RatingTransport`) sobre el canal compartido. → network-trust §2.1
|
||
- **Mensajería:** alcance v1 (1:1 atada a oferta), apoyo en NIP-17. → network-trust §4
|
||
- **Almacenamiento del historial de chat — RESUELTO/IMPLEMENTADO (2026-07-11).** El historial 1:1 se guarda en una **BD Drift/SQLCipher aparte** (`tane_chat.sqlite` / `ChatDatabase`), no en el keystore (era un blob JSON por conversación con read-modify-write O(n²), tope silencioso de 200 e índice manual). BD separada a propósito: el chat es **caché de red efímera por dispositivo**, no inventario — sin metadatos CRDT, fuera de la serie de migraciones del inventario, fuera del sync/backup. `append` = un insert indexado (dedup por clave única), historial sin tope. Pre-release: no se migran los blobs viejos del keystore. Mismo antipatrón (a escala trivial) en `OfferOutbox`/`SocialSettings` → keystore aceptable ahí; solo el chat crece con el uso. → [chat-storage.md](chat-storage.md)
|
||
- **Reputación: anclaje fuerte** a un trato/oferta cerrada (evitar reseñas falsas del todo) — la v1 con anclaje blando por conversación ya está (ver arriba); el anclaje fuerte espera al formulario bilateral firmado. → sharing-model §6
|
||
- **Precio:** ¿monedas comunitarias / de tiempo además de dinero? → sharing-model §6
|
||
- **Integración Ğ1 (moneda libre):** niveles 1–2 (precio en Ğ1 + enlace a cartera Ğecko/Cesium²/Ğ1nkgo) en la capa social; nivel 3 (reusar la WoT de Ğ1 como fuente de confianza) como estudio aparte. → [g1-integration.md](g1-integration.md)
|
||
- **Ofertas de banco colectivo** (publica la entidad, no la persona). → sharing-model §6
|
||
- **Caducidad/revocación** de ofertas ya replicadas en relays. → sharing-model §6
|
||
- **Cambiar de identidad social — RESUELTO/IMPLEMENTADO (2026-07-10).** Se eligió una refinación de la vía (A) que **mantiene "backup UNA semilla"**: identidades seudónimas **derivadas de la misma semilla raíz con un índice de cuenta** (`NostrKeyDerivation.deriveFromSeed(seed, {account})`, HKDF one-way → no vinculables a Ğ1; **cuenta 0 = la identidad actual, byte-for-byte, sin rotar a nadie**). La cuenta activa se persiste en el keystore (`SocialAccountStore`), los stores por-identidad (chats/perfil/nombres) se **namespacian por cuenta** (cuenta 0 = claves legacy, sin migración), y `switchSocialAccount()` re-registra la porción social del DI y reinicia el inbox; la UI (perfil → "Tus identidades", crear/cambiar) aplica el cambio con un `RestartWidget` que reconstruye el árbol leyendo los singletons nuevos. La app NO toca la DB/inventario. Descartada la vía (B) reset total (perdía identidad+recuperación). → [[project-block2-spike]]
|
||
|
||
- **Paquete legal + moderación mínima — DECIDIDO/IMPLEMENTADO (2026-07-13).** Se escribe la capa legal completa en [`docs/legal/`](../legal/README.md): política de privacidad, condiciones de uso, normas de la comunidad y aviso sobre legalidad de semillas (masters en inglés + espejo es), más docs internos de cumplimiento (Play Data Safety/UGC, notas Apple, memoria jurídica con calendario de revisión — el PRM europeo sigue en trílogos). Decisiones que fija: **(a)** consentimiento único **al entrar al mercado por primera vez** (hoja modal con las normas; el inventario local no pide nada — progressive disclosure), con la misma bandera guardando también publicar-oferta y primer DM; **(b)** **bloqueo local** de claves (keystore, sin migración de esquema): filtra ofertas, chats y DMs entrantes; **(c)** **denuncias estándar NIP-56** (kind 1984) publicadas a los relays + ocultación local; en `relay.comunes.org` (nuestro) se actúa sobre ellas — esa es la historia de "moderación con respuesta" para la revisión de Play; **(d)** se reafirma **cero comisiones** sobre semillas y **Ğ1 solo como etiqueta de precio** (sin flujos de pago in-app, precedente Damus/Apple); **(e)** borrado honesto: NIP-09 es best-effort y así se cuenta al usuario. Metadata de tienda en `fastlane/metadata/android/` (la reutiliza F-Droid).
|
||
|
||
## C) Puede esperar (no bloquea nada ahora)
|
||
|
||
- Negación plausible / bóveda señuelo; modo discreto. → security-privacy
|
||
- Reconocimiento por foto en la varilla.
|
||
- Respaldo social distribuido entre contactos.
|
||
- Ampliar avisos legales más allá de Europa (empezar por jurisdicción común europea). → PLAN §6
|
||
- Segundo producto (biblioteca de cosas) y extracción de `Item/Holding` al core. → core-domain-boundary
|
||
- Bloqueo de app: ¿activado por defecto o sugerido? → security-privacy
|
||
- **Puente opcional con portales abiertos de biodiversidad (Darwin Core / GBIF / ALA).** Export *opt-in* de agrobiodiversidad (variedades cultivadas, taxonomía folk, nombres vernáculos) como Darwin Core Archive hacia un atlas ALA/GBIF — justo los datos que esos portales tienen más flojos. **No es fase ni código ahora:** solo se apunta como intención. Lo único que exige del diseño actual ya lo hacemos por otros motivos: anclar especies a GBIF key + Wikidata QID (data-model §2.2) y mantener el formato de exportación abierto/versionado (requisito de backup, Fase 1). **Guardas innegociables** para que no choque con el modelo de privacidad: opt-in por ítem, a nivel de banco colectivo (no persona), ubicación difuminada, solo material no sensible, y en un solo sentido (app → portal; nunca escribir de vuelta en un inventario privado). Argumento de interoperabilidad útil para NLnet/NGI. → data-notes §3-bis
|
||
|
||
## D) Qué chirría (tensiones a mirar de frente)
|
||
|
||
1. **La capa social es indivisible y grande.** "MVP = inventario + compartición local" subestima que "compartir" de verdad = **ofertas + mensajería + relays + red de confianza**, todo junto e interdependiente. Propuesta: replantear las fases como **(1) inventario** —entregable, sólido, en solitario— y **(2) el salto social** —un bloque grande, no un incremento—. Ser honestos con esto en el plan y en la financiación.
|
||
|
||
2. **Casi toda la dificultad vive en `commons_core`.** Identidad, sync CRDT, ofertas, pledge, red de confianza, **mensajería**, relays… `app_seeds` es, en comparación, ligero. En el fondo Tane es **infraestructura de procomún** con las semillas como primera aplicación. Es buena noticia para NLnet/NGI Zero (financian infraestructura), pero hay que asumir que lo "sencillo" (semillas) es la punta del iceberg y comunicar el proyecto en consecuencia.
|
||
|
||
3. **Apuesta fuerte por Nostr.** Ofertas, mensajes y confianza dependerían de NIPs en evolución (99 / 17 / 85). La abstracción `OfferTransport` amortigua las ofertas, pero mensajería y confianza también se apoyan en Nostr → más acoplamiento del que sugería el plan. Revisar si un solo protocolo lo cubre bien o si conviene aislar también mensajería y confianza tras interfaces.
|
||
|
||
4. **Cifrado en reposo + sync CRDT + multidispositivo — EN CURSO (base landed 2026-07-10).** El modelo ya está listo para CRDT (HLC, tombstones, LWW, Movement append-only) y el reconciliador de import prueba el merge; faltaba el **transporte de sync**. Primera rebanada en `commons_core`: `SyncTransport` (mueve snapshots opacos y cifrados entre los dispositivos de UNA identidad) + `NostrSyncTransport` (NIP-78 kind 30078 addressable/reemplazable por dispositivo, contenido **NIP-44 cifrado a sí mismo** → el relay solo ve cifrado; filtrado a tu propia clave de autor). Tests con MiniRelay: replica al otro dispositivo, el cable solo ve cifrado, otra identidad no recibe ni podría descifrar, re-push reemplaza. **Confirma la garantía de §4**: no filtra llave ni datos en claro. PENDIENTE (siguiente rebanada, app): `SyncService` que serialice el inventario (reusa `ExportImportService`), empuje en mutaciones, y en recepción corra `importInventory` (LWW idempotente ya existente); id de dispositivo estable por instalación (keystore, NO el nodeId derivado de la semilla, que es común a los dispositivos); integrar `sync` en `SocialSession` (5ª interfaz sobre la conexión compartida); UI de conflictos (raro).
|
||
|
||
5. **La "varilla" y los datos de conservación** necesitan una fuente y una curación que **aún no tenemos**. Puede ser más trabajo del que parece (y es, a la vez, de lo que más valor humano aporta).
|
||
|
||
6. **Alcance vs. sostenibilidad de un mantenedor.** El conjunto (inventario + cripto + P2P + confianza + mensajería + relays, en varias plataformas) es **mucho** para "proyecto personal + IA". Refuerza la necesidad de: fases honestas, comunidad temprana, y financiación (NLnet) para la parte social. La Fase 1 sí es abordable en solitario.
|
||
|
||
7. **Identidad — RESUELTO: una sola identidad, dos claves derivadas.** Una **semilla raíz** estilo Duniter/Ğ1 (perfil, certificaciones, Ğ1) de la que se **deriva** determinísticamente la clave **secp256k1** para Nostr (mensajes, ofertas). Para la persona es *una sola identidad y una sola copia* (el QR de la semilla). **Nostr no se descarta** — es imprescindible porque **Duniter carece de mensajería**. No hay contradicción con "una sola clave": son subclaves de una raíz. → [g1-integration.md](g1-integration.md)
|
||
|
||
## Recomendación de orden
|
||
|
||
Cerrar el bloque **(A)** — son pocas decisiones y desbloquean construir el inventario, que es valioso por sí solo y de bajo riesgo. Dejar **(B)** para una ronda de diseño específica de la capa social (con su propia financiación si llega). Y asumir explícitamente el punto **(D.1)**: replantear las fases para que "lo social" sea un hito grande y no una coletilla del MVP.
|