tawasuyu

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 Definitions

⎇ main AVAILABILITY.md Markdown · 56 lines · 2.8 KB History raw

T10b: rebalanceo sin ruta + fail-safe (Unreachable, no hang) 1be92f63c · Sergio ·

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

El límite, en números

Cuenta de 100, saldo ≥ 0, reparto [50, 50]:

GrafoRéplica 0 pideResultadoDisponibilidad efectiva de 0
0—1 (conectada)60Spent (50 local + 10 de 1)hasta 100 (todo el slack, si 1 cede)
∅ (particionada)60Rejected { spendable: 50 }exactamente 50 (su presupuesto local)
∅ (particionada)50Spent50

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:

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.