gioser

tawasuyu/hifas

Motor en Rust para decidir, con demostración y no a ojo, cuándo un conjunto de réplicas local-first puede operar sin coordinación y cuándo no — y, cuando la respuesta es "sí, dentro de límites", proveer el mecanismo que lo garantiza.

Rust 24Markdown 15TOML 1358 Definiciones

T11: precisar el oráculo del tally y que CloneDetected es alarma, no recuperación

Dos precisiones para que el registro diga exactamente lo que el código hace:
- durable: documentar que `real` es un oráculo INDEPENDIENTE del desembolso
  FÍSICO al usuario — se incrementa en el ack (punto de entrega externo e
  irreversible), una vez por retiro entregado, y NUNCA lee la contabilidad
  interna (value/committed). Cuenta eventos de entrega, no lo que el sistema
  cree que gastó. El guard usa la contabilidad para decidir; si se corrompe
  (rollback) se entrega de más y `real` lo atrapa. Ahí vive detección vs
  prevención.
- BOUNDARY.md: CloneDetected es una ALARMA, no una recuperación. El daño de
  una celda ya ocurrió en el mundo (dinero real entregado); aislar evita que
  el doble conteo se propague, pero no recupera nada. La compensación vive
  fuera del sistema, es coordinación, y puede fracasar. Acotado y delatado,
  sí; reversible, no necesariamente. Para que M4 no lo lea como auto-reparación.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

100d09cc730498b3b3c104e360166e921f024b14 · por Sergio · (hace 3 meses) · padre 8b7400422 · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

2 archivos · +21 −5

diff --git a/BOUNDARY.md b/BOUNDARY.md
index 8853aec..7c60e00 100644
--- a/BOUNDARY.md
+++ b/BOUNDARY.md
@@ -42,8 +42,16 @@ enforcement vive en el `merge` y la garantía es más débil y honesta:
 - **Lo acota**: cada clon gasta ≤ su presupuesto de celda ⟹ el sobregiro es ≤ una
   celda (`FencedCell::overspend() ≤ budget`, verificado por `proptest`).
 - **Lo delata**: `CloneDetected` en el merge. El linaje vivo (época mayor) es lo
-  único que se propaga como legítimo; los gastos del clon obsoleto quedan aislados
-  y señalados para que la capa de aplicación los **compense** (clawback, alarma).
+  único que se propaga como legítimo; los gastos del clon obsoleto quedan aislados.
+
+**`CloneDetected` es una ALARMA, no una recuperación.** El daño de una celda **ya
+ocurrió en el mundo**: el clon obsoleto entregó dinero real que no se puede
+des-entregar desde aquí. Aislar sus gastos evita que el doble conteo se propague
+como presupuesto legítimo por la malla, pero **no recupera nada**. La compensación
+(clawback, reverso, alarma humana) es un problema de la **capa de aplicación**,
+vive **fuera** de este sistema, es **coordinación**, y **puede fracasar** (el
+dinero se fue). Acotado y delatado, sí; **reversible, no necesariamente**. M4 no
+debe leer esto como "el sistema se auto-repara".
 
 Convierte "silencioso e ilimitado" en "acotado y delatado". Eso es todo lo que un
 sistema coordination-free puede ofrecer aquí — y decirlo así es el resultado.
diff --git a/src/durable.rs b/src/durable.rs
index b5b1b2b..faa762b 100644
--- a/src/durable.rs
+++ b/src/durable.rs
@@ -141,12 +141,20 @@ mod tests {
     /// CRUDO, bypass del WAL), y se re-gasta.
     fn valor_real_tras_rollback(con_durabilidad: bool) -> i64 {
         let (initial, budget) = (50i64, 50i64);
+        // `real` = oráculo INDEPENDIENTE del desembolso FÍSICO al usuario (efecto
+        // externo e irreversible). Se incrementa en el punto de entrega (el ack,
+        // justo tras `spend`), UNA vez por retiro efectivamente entregado. Nunca
+        // lee la contabilidad interna del sistema (`cell.value()`/`committed()`):
+        // cuenta eventos de entrega, no lo que el sistema cree que gastó. Ahí vive
+        // la diferencia detección/prevención — mide el dinero que salió al mundo.
         let mut real = 0;
         let mut cell = DurableSpent::new();
 
+        // El guard usa la contabilidad del sistema para DECIDIR si entregar; si esa
+        // contabilidad se corrompe (rollback), se entrega de más y `real` lo atrapa.
         if budget - cell.value() >= 50 {
-            cell.spend(50); // ack durable del primer gasto
-            real += 50;
+            cell.spend(50); // commit durable → ack → 50 entregados al usuario
+            real += 50; // ← desembolso físico (externo), no la variable `spent`
         }
         // Crash + rollback crudo: la memoria vuelve a un estado viejo (spent = 0).
         cell.live = 0;
@@ -156,7 +164,7 @@ mod tests {
         // Re-gasto, guardado por el `spent` visible en este momento.
         if budget - cell.value() >= 50 {
             cell.spend(50);
-            real += 50;
+            real += 50; // ← segunda entrega física si el guard dejó pasar (doble gasto)
         }
         initial - real
     }