1# AVAILABILITY — el precio honesto del escrow (T10b, §D/§F.6)
2
3El escrow cambia el **falso-verde** del detector (T8: "particionado → libre") por
4un **límite de disponibilidad real y seguro**. Este archivo lo nombra, en vez de
5fingir disponibilidad ilimitada. Un consejero de coordinación soberano debe poder
6decir *"bajo esta partición, este nodo puede gastar hasta X y no más"*.
7
8Respaldado por `router::tests::particion_sin_ruta_es_fail_safe` y
9`gasto_con_ruta_se_cubre_por_rebalanceo`.
10
11## La regla
12
13- `try_spend`/`spend_with_rebalance` **nunca** sobregiran y **nunca** cuelgan.
14- Si el gasto excede el presupuesto local y **hay ruta** a excedente en las
15 vecinas, se cubre por rebalanceo edge-local → `Spent`.
16- Si **no hay ruta** (partición), `rebalance_to` devuelve `Unreachable` (sin mover
17 nada) y el gasto se **rechaza** con `Rejected { spendable }`: la réplica puede
18 gastar como mucho su presupuesto local, garantizando `valor ≥ K`.
19
20## El límite, en números
21
22Cuenta de 100, `saldo ≥ 0`, reparto `[50, 50]`:
23
24| Grafo | Réplica 0 pide | Resultado | Disponibilidad efectiva de 0 |
25|---|---|---|---|
26| `0—1` (conectada) | 60 | `Spent` (50 local + 10 de 1) | hasta 100 (todo el slack, si 1 cede) |
27| `∅` (particionada) | 60 | `Rejected { spendable: 50 }` | **exactamente 50** (su presupuesto local) |
28| `∅` (particionada) | 50 | `Spent` | 50 |
29
30Bajo partición, la réplica está **limitada a su presupuesto local**. No es un bug:
31es el precio correcto de no sobregirar jamás. El detector de T8 "resolvía" esto
32mintiendo (decía libre y sobregiraba); el escrow lo resuelve diciendo la verdad.
33
34## Qué mitiga `diffuse` (y qué no)
35
36`BudgetRouter::diffuse` suaviza el presupuesto **antes** de que llegue la
37partición: reparte el slack para que ninguna réplica quede con presupuesto muy por
38debajo de su demanda esperada. Reduce la probabilidad y la severidad de un
39`Rejected` — pero **no lo elimina**:
40
41- Si una réplica particionada demanda más que **todo** el slack que le tocó, ni el
42 mejor reparto la salva: no hay derechos que gastar sin coordinar.
43- `diffuse` conserva `Σ b` (no crea disponibilidad, solo la redistribuye).
44
45**Ningún mecanismo seguro puede eliminar el límite.** Es el teorema CAP en su
46forma cotidiana: bajo partición, o sacrificas consistencia (sobregiras, el bug de
47T8) o sacrificas disponibilidad (te limitas a tu presupuesto, el escrow). El
48escrow elige lo segundo, explícitamente.
49
50## Sin promesas de liveness
51
52Este documento **no** promete que un `Rejected` se convierta en `Spent` en un
53tiempo acotado: eso depende de que la partición sane y de que haya excedente que
54rutear, cosas fuera del control del mecanismo. Lo que sí se garantiza es
55**safety**: nunca un sobregiro, nunca un cuelgue. Liveness es best-effort vía
56`diffuse` proactivo y rebalanceo al reconectar.
AVAILABILITY.md
AVAILABILITY — el precio honesto del escrow (T10b, §D/§F.6)
El escrow cambia el falso-verde del detector (T8: "particionado → libre") por
un límite de disponibilidad real y seguro. Este archivo lo nombra, en vez de
fingir disponibilidad ilimitada. Un consejero de coordinación soberano debe poder
decir "bajo esta partición, este nodo puede gastar hasta X y no más".
Respaldado por router::tests::particion_sin_ruta_es_fail_safe y
gasto_con_ruta_se_cubre_por_rebalanceo.
La regla
try_spend/spend_with_rebalance nunca sobregiran y nunca cuelgan.
- Si el gasto excede el presupuesto local y hay ruta a excedente en las
vecinas, se cubre por rebalanceo edge-local →
Spent.
- Si no hay ruta (partición),
rebalance_to devuelve Unreachable (sin mover
nada) y el gasto se rechaza con Rejected { spendable }: la réplica puede
gastar como mucho su presupuesto local, garantizando valor ≥ K.
El límite, en números
Cuenta de 100, saldo ≥ 0, reparto [50, 50]:
| Grafo | Réplica 0 pide | Resultado | Disponibilidad efectiva de 0 |
0—1 (conectada) | 60 | Spent (50 local + 10 de 1) | hasta 100 (todo el slack, si 1 cede) |
∅ (particionada) | 60 | Rejected { spendable: 50 } | exactamente 50 (su presupuesto local) |
∅ (particionada) | 50 | Spent | 50 |
Bajo partición, la réplica está limitada a su presupuesto local. No es un bug:
es el precio correcto de no sobregirar jamás. El detector de T8 "resolvía" esto
mintiendo (decía libre y sobregiraba); el escrow lo resuelve diciendo la verdad.
Qué mitiga diffuse (y qué no)
BudgetRouter::diffuse suaviza el presupuesto antes de que llegue la
partición: reparte el slack para que ninguna réplica quede con presupuesto muy por
debajo de su demanda esperada. Reduce la probabilidad y la severidad de un
Rejected — pero no lo elimina:
- Si una réplica particionada demanda más que todo el slack que le tocó, ni el
mejor reparto la salva: no hay derechos que gastar sin coordinar.
diffuse conserva Σ b (no crea disponibilidad, solo la redistribuye).
Ningún mecanismo seguro puede eliminar el límite. Es el teorema CAP en su
forma cotidiana: bajo partición, o sacrificas consistencia (sobregiras, el bug de
T8) o sacrificas disponibilidad (te limitas a tu presupuesto, el escrow). El
escrow elige lo segundo, explícitamente.
Sin promesas de liveness
Este documento no promete que un Rejected se convierta en Spent en un
tiempo acotado: eso depende de que la partición sane y de que haya excedente que
rutear, cosas fuera del control del mecanismo. Lo que sí se garantiza es
safety: nunca un sobregiro, nunca un cuelgue. Liveness es best-effort vía
diffuse proactivo y rebalanceo al reconectar.