tane/docs/design/open-decisions.md
vjrj ee2fdc1e93 docs(trust): record the ego-centric pivot and ratings v1
network-trust.md §2 rewritten (ego-centric model, misuse resistance,
honest losses, unburnt Duniter bridge) + new §2.1 for ratings.
open-decisions.md: 2026-07-10 WoT-parameters decision superseded;
two new dated entries (ego-centric trust, ratings v1 with its soft
conversation anchor and the strong-anchor question kept open).
CLAUDE.md Block 2 paragraph updated to match.
2026-07-11 13:17:32 +02:00

14 KiB
Raw Blame History

Tanemaki — 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) 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
  • 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
  • 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, 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 §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 §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 §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
  • 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 15 + 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
  • 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 12 (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
  • 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

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 Tanemaki 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

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.