Files
hifas/AVAILABILITY.md
T
SergioandClaude Opus 4.8 1be92f63c6 T10b: rebalanceo sin ruta + fail-safe (Unreachable, no hang)
T10 §D/§F.4: T9 no definía el eslabón cuando no hay ruta al presupuesto
(partición). Ahora:
- router::RebalanceResult { Routed{edges} | Unreachable }. rebalance_to es
  TRANSACCIONAL: si las vecinas directas cubren el déficit aplica el flujo
  (Routed); si no, no mueve nada y devuelve Unreachable. Nunca a medias, nunca
  bloquea.
- SpendOutcome + spend_with_rebalance: gasta local, o repone por rebalanceo, o
  ante Unreachable RECHAZA con spendable = presupuesto local (fail-safe). Nunca
  sobregira ni cuelga.

Tests: el 60/60 particionado con [50,50] → Unreachable → Rejected{spendable:50}
(gasta ≤50, valor≥0); con ruta se cubre por rebalanceo. Tests de T9b migrados a
la nueva firma.

AVAILABILITY.md (§F.6): el precio honesto del escrow — bajo partición la réplica
se limita a su presupuesto local (CAP: safety > availability); diffuse lo mitiga,
no lo elimina; sin promesas de liveness, solo safety. 61 tests, clippy limpio.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:09:59 +00:00

2.8 KiB

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.