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.

⎇ 0922cdd971854855cc551882e1515636f9badc8b T9.md Markdown · 230 lines · 13.4 KB History raw

T9a: invariante global + certificado de escrow 89c62aa71 · Sergio ·

1# SDD — Addendum: T9 · De detección a evitación (escrow topología-consciente)
2
3**Extiende:** `DESIGN.md` + `DESIGN_lattice.md` (T5–T7) + `DESIGN_T8.md` (T8).
4**Estado:** Diseño — habilitado por el desmontaje del "resultado fuerte" de T8c.
5**Precondición:** T8 cerrado. Existe `STRONG_RESULT.md` con el testigo `[10,10]/[60,60]`.
6**Última edición:** 2026-07-01
7
8---
9
10## A. Qué corrige T9 (los dos errores de T8, nombrados)
11
12**Error 1 — detección disfrazada de evitación.** El veredicto de T8 (`corre libre ⟺ punto_fijo ∈ V`)
13funde dos estados ya divergidos y comprueba el merge. Eso es *detección reactiva de conflicto por-estado*,
14no *evitación de coordinación*. I-confluence (Bailis) cuantifica sobre **todos los estados alcanzables**:
15"retirar con saldo ≥ 0" no es I-confluente porque *existe* un [60,60] que rompe `I` → coordina,
16categórico, igual que CALM (`I-confluente ⟺ monótono`, Prop. 3 de Complete CALM). El testigo `[10,10]`
17no es una cuña más fina; responde otra pregunta, y para un invariante duro llega tarde: si hubiera sido
18[60,60], ambos retiros ya se aceptaron y repararlos es compensación = coordinación.
19
20**Error 2 — invariante global tratado como edge-local.** "Saldo total ≥ 0" acopla a **todas** las
21réplicas que tocan el recurso, compartan arista o no. Codificado por aristas pares, quitar la arista
22(partición) borra la restricción del haz — justo cuando el acoplamiento sigue intacto. De ahí el
23falso-verde: `particionado → libre` es el caso *más* peligroso reportado como seguro, porque el haz está
24**ciego** a él, no porque sea seguro.
25
26T9 arregla ambos de raíz: el invariante pasa a ser una **restricción global** (una hiperarista / celda de
27recurso incidente a todas las réplicas), y el veredicto pasa de *fundir-y-comprobar* a
28**repartir-y-chequear-local** — el esquema de **escrow**.
29
30---
31
32## B. La cura canónica: escrow / Bounded Counter
33
34Prior art, no lo inventamos: método de escrow (O'Neil 1986) materializado como **Bounded Counter CRDT**
35(Balegas, Serra, Duarte, Ferreira, Shapiro, Rodrigues, Preguiça — SRDS 2015).
36
37Idea, para invariante `valor ≥ K`:
38
391. Define el *slack* global `S = valor − K` (lo que se puede gastar sin romper `I`).
402. **Reparte** `S` en presupuestos por réplica `b_i` con la condición dura `Σ b_i ≤ S`.
413. Mientras a `r_i` le quede presupuesto, **retira localmente sin coordinar con nadie** — y esto es
42   correcto **incluso particionada**, porque `b_i` es suyo.
434. **La única coordinación** aparece cuando `r_i` agota `b_i` y necesita **rebalancear**: pedir
44   presupuesto a un vecino que aún tenga. Local, entre dos réplicas, no global.
45
46**El certificado (esto es lo que el detector de T8 no podía dar).** Sea `spent_i ≤ b_i` en cada réplica.
47Entonces `valor = inicial − Σ spent_i ≥ inicial − Σ b_i ≥ inicial − S = K`. Se cumple **para toda
48ejecución, todo orden, toda partición** — no se descubre tarde, se garantiza por construcción. Eso es
49evitación de coordinación real, cuantificada sobre todos los estados alcanzables dentro de los
50presupuestos.
51
52**Honestidad (para no repetir el sobre-reclamo de T8).** Escrow **tampoco** vence a CALM. *Transforma*
53la operación: de "retirar sin límite" (no I-confluente → coordina) a "retirar dentro de presupuesto"
54(I-confluente / monótona → libre), al costo de un rebalanceo ocasional. No rompes el teorema; re-ingenieras
55la operación para que caiga del lado bueno. Dilo así en `STRONG_RESULT.md`.
56
57---
58
59## C. Dónde el haz por fin se gana el sueldo (y no es derivativo)
60
61Bounded Counter usa esquemas simples para repartir y rebalancear (preguntar a un peer). El candidato a
62**contribución real** de T9 es hacer el reparto y el rebalanceo como un **flujo sobre el grafo de
63comunicación**, con localización principiada:
64
65- **Presupuesto** = una 0-cocadena `b` sobre los vértices, con `Σ b_i ≤ S`.
66- **Rebalanceo** = un flujo (1-cocadena) que mueve presupuesto por las aristas, **conservando el total**
67  (`b_i -= δ`, `b_j += δ` ⟹ `Σ b` invariante ⟹ el certificado se preserva).
68- **El Laplaciano rutea.** La "presión" para rebalancear es el gradiente de presupuesto restante a través
69  de las aristas; un flujo Laplaciano lo suaviza proactivamente para que las réplicas rara vez lleguen a
70  cero. Cuando una se seca, el Laplaciano **localiza** el flujo mínimo que la repone desde vecinos con
71  excedente — el rebalanceo mínimo, edge-local, no un consenso global.
72
73Cierre de arco honesto: el Laplaciano lineal que en el MVP era un **detector con falso-positivo** (H¹=β₁)
74reaparece aquí como un **ruteador de presupuesto legítimo**. Misma máquina, ahora en el problema correcto:
75no detectar conflictos post-hoc, sino repartir derechos proactivamente. Es una aplicación genuina del
76Laplaciano de haces (Hansen–Ghrist) a consenso/flujo. Para un presupuesto escalar basta el Laplaciano de
77grafo ordinario; el Laplaciano de haces solo hace falta si los presupuestos son vectoriales (multi-recurso,
78§H reach).
79
80---
81
82## D. Modelo de tipos (bocetos)
83
84```rust
85/// Restricción global: NO edge-local. Incide sobre todas las réplicas que tocan el recurso.
86pub struct GlobalInvariant {
87    pub floor: i64,        // K en  valor ≥ K
88    pub initial: i64,      // valor inicial del recurso
89}
90impl GlobalInvariant {
91    pub fn slack(&self) -> i64 { self.initial - self.floor }   // S
92}
93
94/// Presupuesto repartido: la 0-cocadena b sobre las réplicas. Invariante: sum(b) ≤ slack.
95pub struct Budget { pub per_replica: Vec<i64> }
96impl Budget {
97    pub fn is_valid_split(&self, inv: &GlobalInvariant) -> bool {
98        self.per_replica.iter().sum::<i64>() <= inv.slack()
99            && self.per_replica.iter().all(|&b| b >= 0)
100    }
101    /// Gasto local: permitido sin coordinar sii hay presupuesto.
102    pub fn try_spend(&mut self, r: usize, amount: i64) -> SpendResult { /* ... */ }
103}
104
105pub enum SpendResult {
106    Local,                          // dentro de presupuesto: cero coordinación
107    NeedsRebalance { deficit: i64 } // presupuesto agotado: coordinación edge-local
108}
109
110/// Ruteo de presupuesto sobre el grafo (Laplaciano). Conserva el total.
111pub struct BudgetRouter<'a> { nerve: &'a Nerve, budget: &'a mut Budget }
112impl<'a> BudgetRouter<'a> {
113    /// Flujo mínimo que repone a `r` desde vecinos con excedente. Devuelve las aristas usadas.
114    pub fn rebalance_to(&mut self, r: usize, deficit: i64) -> Vec<(usize, usize, i64)> { /* ... */ }
115    /// Difusión proactiva: suaviza el presupuesto para minimizar futuros déficits.
116    pub fn diffuse(&mut self) { /* paso de Laplaciano, conserva sum(b) */ }
117}
118```
119
120---
121
122## E. Cambios de módulos
123
124```
125Nuevo  src/invariant.rs — GlobalInvariant (restricción global, no edge-local)
126Nuevo  src/escrow.rs    — Budget, try_spend, el certificado sum(b) ≤ slack
127Nuevo  src/router.rs    — BudgetRouter: rebalanceo y difusión por Laplaciano (reusa el grafo del nervio)
128Editar src/verdict.rs   — Verdict::via_escrow: Local | NeedsRebalance{edges}, NUNCA "libre global" ciego
129Editar STRONG_RESULT.md — corregir el reclamo de T8 (§G)
130Nuevo  COMPARISON.md    — detector (T8) vs escrow (T9) sobre los mismos escenarios, con el punto ciego
131```
132
133El detector de T8 **no se borra**: queda como capa de reconciliación/alarma honesta (para conflictos que
134el escrow no cubre, p.ej. invariantes sin estructura de recurso divisible). Pero deja de ser el consejero
135de coordinación.
136
137---
138
139## F. El certificado y la muerte del falso-verde
140
141Test central de T9, que **invierte** el punto ciego de T8:
142
143- **Certificado exhaustivo.** Para cualquier reparto válido (`Σ b_i ≤ S`) y **cualquier** combinación de
144  gastos con `spent_i ≤ b_i` — incluidas todas las réplicas gastando su máximo a la vez, incluidas
145  particiones arbitrarias — el invariante global `valor ≥ K` se preserva. `proptest` sobre repartos y
146  gastos aleatorios; la aserción es el certificado de §B, no un merge concreto.
147- **El falso-verde particionado desaparece.** El caso 60/60 de T8: con presupuestos `[50,50]` (reparto
148  válido de `S=100`), una réplica particionada que intenta retirar 60 recibe `NeedsRebalance{deficit:10}`
149  — correctamente **bloqueada/pendiente de coordinación**, no "libre". Contrasta explícitamente en
150  `COMPARISON.md` contra el detector de T8, que decía "particionado → libre".
151- **Rebalanceo conserva y localiza.** Tras cualquier `rebalance_to`, `Σ b` es invariante (certificado
152  intacto) y las aristas devueltas son edge-locales al nodo seco. `proptest`: nunca un rebalanceo toca una
153  réplica no adyacente al déficit.
154
155---
156
157## G. Corrección obligatoria de `STRONG_RESULT.md`
158
159T9 no está completo hasta reescribir el reclamo de T8 con lo que el testigo realmente probó:
160
161- **Quitar:** "versión fuerte probada / más fino que CALM / I-confluence sobre el grafo real."
162- **Poner:** "detección de conflicto por-estado con localización, topología-consciente. NO es una
163  separación respecto a I-confluence ni a CALM — ambos coinciden (Prop. 3). El caso particionado-60/60
164  queda documentado como **punto ciego**: el detector reporta 'libre' porque, sin arista, no ve el
165  acoplamiento del invariante global. Esto motivó T9."
166- **Añadir puntero** a `COMPARISON.md` y al certificado de escrow como la evitación real.
167
168Un error registrado con honestidad vale más que un resultado fuerte reclamado de más. Ese es el criterio.
169
170---
171
172## H. Riesgos y horizonte
173
174- **R-T9a (reparto inicial).** `Σ b_i ≤ S` deja libertad en *cómo* repartir. Un reparto malo (todo a una
175  réplica) causa rebalanceos constantes = mucha coordinación. La calidad del reparto es un problema de
176  optimización sobre el grafo — donde el Laplaciano/difusión aporta. No es corrección (el certificado
177  aguanta cualquier reparto válido), es *rendimiento*. Sepáralos en los tests.
178- **R-T9b (recurso no divisible).** Escrow sirve para invariantes sobre un *recurso conservado divisible*
179  (saldo, stock, cupo). Invariantes no divisibles (unicidad de ID, integridad referencial) NO se resuelven
180  con presupuestos numéricos — ahí el detector de T8 sigue siendo lo mejor que tienes, o se necesitan
181  reservas de otro tipo. Documenta la frontera; no vendas escrow como universal.
182- **R-T9c (honestidad de novedad).** Bounded Counter/escrow es prior art (Balegas 2015). Tu candidato a
183  novedad es el **ruteo/localización del presupuesto por el grafo real** y, como reach, **múltiples
184  invariantes acoplados** — que el propio paper de Balegas señala como no resuelto ("constraints over
185  multiple counters... only way is to store them in a single object"). Ese multi-invariante vía haces sí
186  sería nuevo. Reclámalo solo si lo construyes.
187- **Q-T9 (M4).** Con escrow, el adaptador a Tawasuyu/Hammer cablea un mecanismo libre de coordinación de
188  verdad: presupuestos por nodo soberano, gasto local sin sync, rebalanceo edge-local al secarse. Eso es
189  lo que justifica el aparato sobre una malla de gossip. El detector de T8, no.
190
191---
192
193## I. Tickets
194
195**T9a — Invariante global + certificado de escrow.**
196"Crea `src/invariant.rs` (`GlobalInvariant`, restricción global no edge-local) y `src/escrow.rs`
197(`Budget`, `try_spend`, `is_valid_split`). Implementa el veredicto `Verdict::via_escrow`:
198`Local` si hay presupuesto, `NeedsRebalance{deficit}` si no. Test del certificado (§F): `proptest` que,
199para todo reparto válido y toda combinación de gastos dentro de presupuesto —incluidas particiones y todos
200gastando el máximo—, verifica `valor ≥ K`. Reproduce el caso 60/60 de T8 con presupuestos [50,50] y
201verifica que la réplica particionada recibe `NeedsRebalance`, no `Local`."
202
203**T9b — Ruteo de presupuesto por Laplaciano.**
204"Crea `src/router.rs` (`BudgetRouter`) reusando el grafo del nervio. `rebalance_to(r, deficit)` calcula el
205flujo mínimo que repone a `r` desde vecinos con excedente y devuelve las aristas usadas; `diffuse` hace un
206paso de Laplaciano que suaviza el presupuesto conservando `Σ b`. Tests: (1) todo rebalanceo conserva
207`Σ b` (certificado intacto); (2) las aristas del rebalanceo son adyacentes al nodo seco (nunca toca una
208réplica no vecina); (3) tras `diffuse`, la varianza del presupuesto no aumenta. Para presupuesto escalar
209usa el Laplaciano de grafo ordinario; deja un `TODO` marcado para el Laplaciano de haces si se pasa a
210multi-recurso."
211
212**T9c — Comparación honesta y corrección del registro.**
213"Crea `COMPARISON.md`: corre el detector de T8 y el certificador de escrow de T9 sobre los mismos
214escenarios (conectado, particionado, 10/10, 60/60, repartos varios) y tabula sus veredictos, resaltando el
215falso-verde particionado del detector frente al bloqueo correcto del escrow. Reescribe `STRONG_RESULT.md`
216según §G (quitar el reclamo fuerte, documentar el punto ciego, apuntar a la evitación real). No declares
217T9 cerrado hasta que ambos archivos reflejen lo que los tests realmente prueban."
218
219---
220
221## J. Referencias añadidas
222
223- V. Balegas, D. Serra, S. Duarte, C. Ferreira, M. Shapiro, R. Rodrigues, N. Preguiça — *Extending
224  Eventually Consistent Cloud Databases for Enforcing Numeric Invariants*, IEEE SRDS 2015. (Bounded Counter
225  CRDT: la evitación por presupuestos.)
226- P. E. O'Neil — *The Escrow Transactional Method*, ACM TODS 11(4), 1986. (Origen del escrow.)
227- (Contexto) Milano & Myers — *Predictive Treaties* (predicados particionables enforced localmente, con
228  dependencia del tiempo): generalización del reparto local; útil si el reach multi-invariante avanza.
229- (De addenda previos) Bailis et al. 2014 (I-confluence); Ghrist–Riess 2022 (Tarski); Hansen–Ghrist 2019
230  (Laplaciano de haces, base del ruteo); Shapiro et al. 2011 (CRDTs); Hellerstein–Alvaro (CALM).

T9.md

SDD — Addendum: T9 · De detección a evitación (escrow topología-consciente)

Extiende: DESIGN.md + DESIGN_lattice.md (T5–T7) + DESIGN_T8.md (T8). Estado: Diseño — habilitado por el desmontaje del "resultado fuerte" de T8c. Precondición: T8 cerrado. Existe STRONG_RESULT.md con el testigo [10,10]/[60,60]. Última edición: 2026-07-01


A. Qué corrige T9 (los dos errores de T8, nombrados)

Error 1 — detección disfrazada de evitación. El veredicto de T8 (corre libre ⟺ punto_fijo ∈ V) funde dos estados ya divergidos y comprueba el merge. Eso es detección reactiva de conflicto por-estado, no evitación de coordinación. I-confluence (Bailis) cuantifica sobre todos los estados alcanzables: "retirar con saldo ≥ 0" no es I-confluente porque existe un [60,60] que rompe I → coordina, categórico, igual que CALM (I-confluente ⟺ monótono, Prop. 3 de Complete CALM). El testigo [10,10] no es una cuña más fina; responde otra pregunta, y para un invariante duro llega tarde: si hubiera sido [60,60], ambos retiros ya se aceptaron y repararlos es compensación = coordinación.

Error 2 — invariante global tratado como edge-local. "Saldo total ≥ 0" acopla a todas las réplicas que tocan el recurso, compartan arista o no. Codificado por aristas pares, quitar la arista (partición) borra la restricción del haz — justo cuando el acoplamiento sigue intacto. De ahí el falso-verde: particionado → libre es el caso más peligroso reportado como seguro, porque el haz está ciego a él, no porque sea seguro.

T9 arregla ambos de raíz: el invariante pasa a ser una restricción global (una hiperarista / celda de recurso incidente a todas las réplicas), y el veredicto pasa de fundir-y-comprobar a repartir-y-chequear-local — el esquema de escrow.


B. La cura canónica: escrow / Bounded Counter

Prior art, no lo inventamos: método de escrow (O'Neil 1986) materializado como Bounded Counter CRDT (Balegas, Serra, Duarte, Ferreira, Shapiro, Rodrigues, Preguiça — SRDS 2015).

Idea, para invariante valor ≥ K:

  1. Define el slack global S = valor − K (lo que se puede gastar sin romper I).
  2. Reparte S en presupuestos por réplica b_i con la condición dura Σ b_i ≤ S.
  3. Mientras a r_i le quede presupuesto, retira localmente sin coordinar con nadie — y esto es correcto incluso particionada, porque b_i es suyo.
  4. La única coordinación aparece cuando r_i agota b_i y necesita rebalancear: pedir presupuesto a un vecino que aún tenga. Local, entre dos réplicas, no global.

El certificado (esto es lo que el detector de T8 no podía dar). Sea spent_i ≤ b_i en cada réplica. Entonces valor = inicial − Σ spent_i ≥ inicial − Σ b_i ≥ inicial − S = K. Se cumple para toda ejecución, todo orden, toda partición — no se descubre tarde, se garantiza por construcción. Eso es evitación de coordinación real, cuantificada sobre todos los estados alcanzables dentro de los presupuestos.

Honestidad (para no repetir el sobre-reclamo de T8). Escrow tampoco vence a CALM. Transforma la operación: de "retirar sin límite" (no I-confluente → coordina) a "retirar dentro de presupuesto" (I-confluente / monótona → libre), al costo de un rebalanceo ocasional. No rompes el teorema; re-ingenieras la operación para que caiga del lado bueno. Dilo así en STRONG_RESULT.md.


C. Dónde el haz por fin se gana el sueldo (y no es derivativo)

Bounded Counter usa esquemas simples para repartir y rebalancear (preguntar a un peer). El candidato a contribución real de T9 es hacer el reparto y el rebalanceo como un flujo sobre el grafo de comunicación, con localización principiada:

Cierre de arco honesto: el Laplaciano lineal que en el MVP era un detector con falso-positivo (H¹=β₁) reaparece aquí como un ruteador de presupuesto legítimo. Misma máquina, ahora en el problema correcto: no detectar conflictos post-hoc, sino repartir derechos proactivamente. Es una aplicación genuina del Laplaciano de haces (Hansen–Ghrist) a consenso/flujo. Para un presupuesto escalar basta el Laplaciano de grafo ordinario; el Laplaciano de haces solo hace falta si los presupuestos son vectoriales (multi-recurso, §H reach).


D. Modelo de tipos (bocetos)

/// Restricción global: NO edge-local. Incide sobre todas las réplicas que tocan el recurso.
pub struct GlobalInvariant {
    pub floor: i64,        // K en  valor ≥ K
    pub initial: i64,      // valor inicial del recurso
}
impl GlobalInvariant {
    pub fn slack(&self) -> i64 { self.initial - self.floor }   // S
}

/// Presupuesto repartido: la 0-cocadena b sobre las réplicas. Invariante: sum(b) ≤ slack.
pub struct Budget { pub per_replica: Vec<i64> }
impl Budget {
    pub fn is_valid_split(&self, inv: &GlobalInvariant) -> bool {
        self.per_replica.iter().sum::<i64>() <= inv.slack()
            && self.per_replica.iter().all(|&b| b >= 0)
    }
    /// Gasto local: permitido sin coordinar sii hay presupuesto.
    pub fn try_spend(&mut self, r: usize, amount: i64) -> SpendResult { /* ... */ }
}

pub enum SpendResult {
    Local,                          // dentro de presupuesto: cero coordinación
    NeedsRebalance { deficit: i64 } // presupuesto agotado: coordinación edge-local
}

/// Ruteo de presupuesto sobre el grafo (Laplaciano). Conserva el total.
pub struct BudgetRouter<'a> { nerve: &'a Nerve, budget: &'a mut Budget }
impl<'a> BudgetRouter<'a> {
    /// Flujo mínimo que repone a `r` desde vecinos con excedente. Devuelve las aristas usadas.
    pub fn rebalance_to(&mut self, r: usize, deficit: i64) -> Vec<(usize, usize, i64)> { /* ... */ }
    /// Difusión proactiva: suaviza el presupuesto para minimizar futuros déficits.
    pub fn diffuse(&mut self) { /* paso de Laplaciano, conserva sum(b) */ }
}

E. Cambios de módulos

Nuevo  src/invariant.rs — GlobalInvariant (restricción global, no edge-local)
Nuevo  src/escrow.rs    — Budget, try_spend, el certificado sum(b) ≤ slack
Nuevo  src/router.rs    — BudgetRouter: rebalanceo y difusión por Laplaciano (reusa el grafo del nervio)
Editar src/verdict.rs   — Verdict::via_escrow: Local | NeedsRebalance{edges}, NUNCA "libre global" ciego
Editar STRONG_RESULT.md — corregir el reclamo de T8 (§G)
Nuevo  COMPARISON.md    — detector (T8) vs escrow (T9) sobre los mismos escenarios, con el punto ciego

El detector de T8 no se borra: queda como capa de reconciliación/alarma honesta (para conflictos que el escrow no cubre, p.ej. invariantes sin estructura de recurso divisible). Pero deja de ser el consejero de coordinación.


F. El certificado y la muerte del falso-verde

Test central de T9, que invierte el punto ciego de T8:


G. Corrección obligatoria de STRONG_RESULT.md

T9 no está completo hasta reescribir el reclamo de T8 con lo que el testigo realmente probó:

Un error registrado con honestidad vale más que un resultado fuerte reclamado de más. Ese es el criterio.


H. Riesgos y horizonte


I. Tickets

T9a — Invariante global + certificado de escrow. "Crea src/invariant.rs (GlobalInvariant, restricción global no edge-local) y src/escrow.rs (Budget, try_spend, is_valid_split). Implementa el veredicto Verdict::via_escrow: Local si hay presupuesto, NeedsRebalance{deficit} si no. Test del certificado (§F): proptest que, para todo reparto válido y toda combinación de gastos dentro de presupuesto —incluidas particiones y todos gastando el máximo—, verifica valor ≥ K. Reproduce el caso 60/60 de T8 con presupuestos [50,50] y verifica que la réplica particionada recibe NeedsRebalance, no Local."

T9b — Ruteo de presupuesto por Laplaciano. "Crea src/router.rs (BudgetRouter) reusando el grafo del nervio. rebalance_to(r, deficit) calcula el flujo mínimo que repone a r desde vecinos con excedente y devuelve las aristas usadas; diffuse hace un paso de Laplaciano que suaviza el presupuesto conservando Σ b. Tests: (1) todo rebalanceo conserva Σ b (certificado intacto); (2) las aristas del rebalanceo son adyacentes al nodo seco (nunca toca una réplica no vecina); (3) tras diffuse, la varianza del presupuesto no aumenta. Para presupuesto escalar usa el Laplaciano de grafo ordinario; deja un TODO marcado para el Laplaciano de haces si se pasa a multi-recurso."

T9c — Comparación honesta y corrección del registro. "Crea COMPARISON.md: corre el detector de T8 y el certificador de escrow de T9 sobre los mismos escenarios (conectado, particionado, 10/10, 60/60, repartos varios) y tabula sus veredictos, resaltando el falso-verde particionado del detector frente al bloqueo correcto del escrow. Reescribe STRONG_RESULT.md según §G (quitar el reclamo fuerte, documentar el punto ciego, apuntar a la evitación real). No declares T9 cerrado hasta que ambos archivos reflejen lo que los tests realmente prueban."


J. Referencias añadidas