Files
takana/docs/15-frontier-ai-native.md
T
sergioandClaude Opus 4.8 3e520d4477 H4e: requires OBSERVADOS — dep de runtime divergida = incompatible (SDD 15 §H4)
Cierra la simetría de la vía observada: además de observar lo que un paquete escribe (H4c),
observa de qué depende A UNA VERSIÓN. Fuente observable sin declaración: el paquete se
construyó contra la versión de sus deps que hay en el repo (deps.runtime del .swm +
expected_hash de cada dep en el índice); si el usuario tiene esa dep instalada a OTRO hash,
la divergió -> rechazo duro. Es el caso wayland DERIVADO (el que H4b captura cuando el autor
declara requires, ahora leído del cierre).

- compat::observed_requires(swm, index) + version_conflicts(db, req) — reusan deps del .swm,
  expected_hash del índice e InstalledDb.hash (cero declaración nueva).
- Cableado en install (rechazo duro, no lo salva --force-slots) y en `hammer compat`.
- Verificado e2e real: `hammer compat` marca app INCOMPATIBLE por su dep wayland-protocol
  instalada a un hash divergido del repo (read-only, ve el source_patch sin construirlo).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 06:29:22 -04:00

450 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SDD 15 — Frontera AI-nativa: código por contenido, evidencia sobre confianza, cómputo como dato
> Estado: propuesta (2026-07-02). Nace de una revisión externa de las 4 piezas del stack
> (wawa · hammer/BLAKE3 · wasm determinista · hifas) que propuso seis puentes. Tres caen del
> lado de hammer/wawa y se recogen aquí; los otros tres (fuente monótona en el kernel,
> fork-consistency, OS-como-CRDT) viven en `tawasuyu/03_ukupacha/PLAN-OS-CRDT.md`.
>
> **Este doc NO reemplaza nada cerrado.** Fases 06 + bootstrap Stage 02 + los 6 swaps
> reproducibles siguen siendo la base (ver `docs/10-roadmap.md`). Esto es *frontera*: apuestas
> ordenadas de "extensión natural de lo que ya hacemos" a "otro modelo de datos, design-doc".
---
## 0. El principio que ya tenemos y hay que empujar
El modelo de confianza de hammer (SDD 09) es **"verificar, no confiar"**: el binario ajeno
nunca viaja, viaja la receta, y `hammer apply` **reproduce y compara** el `expected_hash`. La
integración de IA (SDD 08 §4) es exactamente la arquitectura de frontera correcta: *la IA
propone `.swm`; el sistema reproduce; el humano hace commit; la IA nunca promueve sola.*
La revisión externa nombra esto bien: **la frontera de confianza no está en quien genera sino
en quien verifica.** Las tres ideas de abajo *empujan esa misma frontera* a territorio nuevo,
sin moverla de sitio. Ninguna pide confiar más en el modelo; todas piden **verificar más**.
Nota de higiene (de la propia revisión, adoptada): nada aquí es "matemáticamente invulnerable".
Es "verificado bajo supuestos nombrados" — reproducibilidad bajo toolchain fijado, determinismo
del sandbox, el checker es pequeño y auditable. seL4 se formula así; nosotros también.
---
## H1 — Proof-carrying recipes: de "reproduce" a "trae evidencia que pasa" (la idea 4)
**Qué ya hacemos.** El bucle agéntico (SDD 08 §2) es PLAN → BUILD → TRY → **VERIFY** → PROPOSE
→ humano. Hoy VERIFY = "corre el test harness, escucha `BUILD_FAILED`/`CRASHED`". La garantía
dura es **reproducibilidad**: dos máquinas ⇒ mismo `artifact_hash`. Eso certifica *identidad*,
no *comportamiento*.
**El salto.** Que la IA entregue, junto al `.swm`, **evidencia de comportamiento**: property
tests, contratos, y —cuando el crate lo permita— pruebas tipo Kani/Creusot. hammer admite la
mutación sólo si un **checker pequeño y confiable** confirma que la evidencia pasa. Proof-carrying
code (Necula 1997), pero **el generador es un modelo**: el sistema mejora solo sin poder
corromperse solo, porque la confianza vive en el checker, no en el generador.
**Por qué encaja aquí y no en otro lado.** hammer ya tiene el sandbox hermético (bwrap) donde
correr esa evidencia de forma reproducible, ya tiene el `Orchestrator` con paso VERIFY, y ya
tiene procedencia (`.hammer/recipe.toml` sellado en el artefacto). La atestación por-hash de
arje (concesiones matcheadas por hash de binario; ver `tawasuyu` `arje/SDD`) es el mismo patrón
del lado del init: **el permiso deriva de una propiedad verificada del artefacto, no de quién lo
firmó.** H1 es esa idea, subida del boot al build.
**Tickets:**
- **H1a** ✅ — Extender el `.swm` (SDD 06) con un bloque `evidence`: lista de `{kind, cmd, expected}`
donde `kind ∈ {proptest, contract, kani, cmd-exit}`. Serialización YAML estable + `verify_schema`.
- **H1b** ✅ — El checker: `hammer swm-verify --evidence` corre cada ítem **dentro del sandbox
reproducible** y exige `expected`. Pequeño y auditable a propósito (no un framework: un runner
de comandos con hash del output). Falla ⇒ no se propone. Reusa `Sandbox::run` + el `log_tail`
ya existente (Fase 5).
- **H1c** ✅ — Cablear al `Orchestrator`: VERIFY consume `evidence`; el `Proposal` lleva el
**veredicto de evidencia** además del diff. El humano ve "reproduce + estas N pruebas pasan",
no sólo "reproduce". **Frontera:** el checker confía en que las pruebas *cubren* lo que
importa — eso lo juzga el humano; H1 garantiza que *lo declarado pasa*, no que *lo declarado
basta*. Se dice así.
**Implementación:** la evidencia corre del lado del BUILD (no del agente — `hammer-agent` no
depende de `hammer-build`, separación PROPONE/CONSTRUYE). `proto::RecipeInline` lleva `evidence`
y `Event::BuildReady` lleva `verdict: Option<EvidenceVerdict>`; `hammerd::bus` la ejecuta tras
sellar el artefacto (`run_evidence` vía `swm_bridge::recipe_from_source_patch`) y adjunta el
veredicto; el `Orchestrator` lee el veredicto y, si `all_passed=false`, empuja `VerifyCheck::fail`
y **aborta antes de hidratar** (el artefacto queda sellado pero no toca el sistema).
**Frontera honesta.** Property tests no son prueba; Kani/Creusot sí pero sólo cubren crates que
se dejan. H1 **estratifica** la confianza (`cmd-exit` < `proptest` < `contract` < `kani`) y la
reporta; no finge que todo es prueba formal.
---
## H2 — Cómputo como dato: memoización de ejecución sobre wasm determinista (la idea 5)
**La observación.** Si wawa ejecuta wasm **determinista**, entonces
`blake3(módulo) + blake3(entrada) → blake3(salida)` es una **función pura**. Nix memoiza
*builds* por hash de entrada; esto memoiza **ejecución en runtime**. La caché de resultados es
content-addressed y **compartible por la malla** vía la capa CRDT de
`tawasuyu/shared/crdt` (es un `GSet`/`LwwMap` de `(módulo,entrada) → salida`, monótono ⇒ viaja
por anti-entropy sin coordinar).
**Los tres regalos gratis** que la revisión nombra bien:
1. **Caché global de cómputo** — la malla comparte resultados; un cómputo caro se hace una vez.
2. **Replay perfecto** — depuración con viaje en el tiempo: un proceso *es* su módulo + su log
de entradas (gancho directo con el `MonotonicLog` de PLAN-OS-CRDT E1).
3. **Migración de procesos como transferencia de datos** — mover un proceso = copiar `(módulo,
log de entradas)` y re-ejecutar; el estado se reconstruye determinísticamente.
**El gancho con hammer.** La caché es un **recurso acotado ruteable**: `hifas::router`
(`BudgetRouter`) reparte "créditos de cómputo" por la malla (PLAN-OS-CRDT E3c). Y la
verificación de un resultado memoizado es *reproducir la ejecución* — el mismo "verificar, no
confiar" de SDD 09, ahora sobre runtime en vez de build.
**Precondición dura (la apuesta).** Requiere **determinismo total de wasm en wawa**: sin
`float` no determinista, sin fuentes de entropía ocultas, orden de scheduling irrelevante para
la salida. `wasmi` es determinista para el core, pero hay que **auditar y sellar** que ninguna
capacidad expuesta (tiempo, random, I/O) filtre no-determinismo a la salida hasheada. Eso es un
spike de wawa, no de hammer.
**Tickets (spike, no fase):**
- **H2a** ✅ — Auditar determinismo del runtime wasm de wawa: enumerar toda fuente de no-determinismo
alcanzable por una app (`wawa-kernel/src/wasm/`), y definir el subconjunto "puro" (módulo +
entrada explícita, sin syscalls no deterministas). Salida: un doc `wawa/SDD` §determinismo con
el veredicto honesto (qué es puro, qué no).
**Hecho (2026-07-03):** `tawasuyu/03_ukupacha/wawa/SDD-determinismo.md`. Veredicto: el subconjunto
puro **ya existe** en el código (`ejecutar_dinamico`/`_v2`, `wasm/mod.rs:335,430` — linker VACÍO +
reloj virtual + fuel/memoria fijos + wasmi 1.0 sin simd). H2a no era construirlo sino **sellarlo**:
contrato de clave `blake3(módulo)⊕blake3(entrada)⊕E` con `E` = hash-de-entorno (pin wasmi/features/
fuel/mem/ABI); único vector residual acotado = payload de NaN vía `reinterpret` (sólo cross-build,
irrelevante para H2b local). La superficie de capacidades (`env/*.rs`) reintroduce no-determinismo
o efectos ⇒ NO memoizable; sólo la vía linker-vacío lo es.
- **H2b** ✅ — Prototipo de `blake3(módulo)+blake3(entrada) → blake3(salida)` como caché local
(un `LwwMap` de `shared/crdt`), sin malla todavía. Test: misma entrada ⇒ hit; recompute ⇒
byte-idéntico (o el determinismo está roto y H2a mintió).
**Hecho (2026-07-03):** crate `tawasuyu/03_ukupacha/wawa/wawa-memo` (host/std, standalone).
Reproduce fiel la vía pura del kernel (`ejecutar_dinamico_v2`: linker vacío + fuel 500k + 1 MiB
+ wasmi sin simd + despacho polimórfico) y monta la clave del contrato §4 (`blake3(bytecode) ‖
blake3(entrada_le) ‖ E`, con `E` = hash de entorno que pinnea wasmi/features/fuel/mem/abi). El
test de §5 **da verde**: 9 módulos × 8 entradas ejecutados dos veces ⇒ `blake3(salida)`
byte-idéntico (H2a no mintió); Miss→Hit sirve valor idéntico al recomputado; las fallas
(`Trampa`/`SinCombustible`/`Carga`) se cachean (§4 regla 4); `E` distinta ⇒ clave distinta (§4
regla 2); y dos réplicas convergen al fundir el `LwwMap` (anti-entropy, preview de H2c). Como la
clave es content-addressed y la función pura, el LWW nunca tiene conflicto real ⇒ el mapa es
monótono y compartible sin coordinar. **Verde ⇒ H2c desbloqueado** (compartir por la malla).
- **H2c** — (si H2b pasa) compartir la caché por la malla minga/agora. Frontera: envenenamiento
de caché por un peer ⇒ se **verifica reproduciendo** en el primer uso dudoso (no se confía en
el resultado ajeno; se confía en poder recomputarlo). Es el modelo de confianza de SDD 09
aplicado a runtime.
**Núcleo de confianza hecho (2026-07-03, en `wawa-memo`):** dos piezas que hacen *seguro* el
compartir, independientes del transporte. (1) **Sellado de portabilidad** (`sellado.rs`, §4
regla 3a): un escáner conservador del bytecode clasifica cada módulo `Portable` (enteros puros
⇒ sin vector NaN ⇒ idéntico cross-arch) o `SoloLocal` (toca floats / no decodifica). Decodifica
los inmediatos correctamente (no confunde un `i32.const 0xBC` con el opcode `reinterpret_f32`).
(2) **Modelo de confianza** (`malla.rs`): `ingerir_ajeno` **verifica reproduciendo** una entrada
ofrecida por un peer y **rechaza** la envenenada (afirmar `Ok(99)` donde `cuadrado(6)=36`, o `Ok`
donde hay `Trampa`) — nunca entra a la caché local; el valor correcto sigue recomputable. Política
`ConfiarSiPortable` = optimización que confía sin recomputar sólo módulos `Portable` (descarta el
vector NaN, no la deshonestidad del peer — dicho explícito). **Diferido (es del otro agente):** el
transporte real (anti-entropy sobre minga/agora) vive en `PLAN-OS-CRDT.md` (E3). Tests: `h2c.rs`.
**Frontera honesta.** Sin determinismo total (H2a), H2 no existe — es la apuesta. No prometer
la caché de malla antes de que H2a dé verde. Y "cómputo puro" excluye lo interesante-con-I/O:
la memoización cubre la parte funcional, no el proceso entero con efectos.
---
## H3 — Código direccionado por contenido estilo Unison (la idea 3) — DESIGN-DOC, no sprint
**La visión.** Hoy hammer direcciona por hash el **artefacto** (binario) y la **receta**. wawa
direcciona el **módulo wasm** y el **almacenamiento**. La idea 3 empuja al límite: direccionar
**funciones por el hash de su AST** (Unison). La IA no editaría archivos —insertaría
definiciones en una base content-addressed donde **nada se rompe por renombrar o actualizar**—
y el store de hammer, los módulos wasm de wawa y el código fuente **colapsarían en un solo
espacio de nombres: el hash**. Paquete, función y proceso = el mismo tipo de objeto.
**Por qué es design-doc y no ticket.** Es **otro modelo de datos**, no una extensión. hammer y
Unison content-addressan *cosas distintas*: hammer el artefacto compilado (grano grueso,
lenguaje-agnóstico, encaja con "recetas sobre fuente upstream"); Unison el AST de cada función
(grano fino, requiere un lenguaje y toolchain propios). Colapsarlos pide reescribir la unidad de
compilación entera. El valor es real (fin del dependency-hell para la IA) pero el costo es un
proyecto, no una fase.
**Qué SÍ hacer ahora (el puente barato hacia la visión):**
- **H3a** ✅ — Doc de diseño `docs/adr/0009-content-addressed-code.md` (2026-07-03): registra la
visión Unison y decide **no reescribir la unidad de compilación**. Puente barato = procedencia
por-símbolo como **metadata** (extender `parse_elf_needed`, `query.rs:298`, para emitir exports de
`.dynsym` además de `DT_NEEDED`), fuera de `hash_inputs` (misma disciplina que la evidencia de H1).
El grano fino real (función-por-hash) se difiere a H3b sobre **wasm**, gateado por H2.
- **H3b** ✅ — (exploratorio) Base content-addressed de funciones **wasm** (grano: un `.wasm` por
función pura de H2), verificando si "insertar definición, nunca romper" se sostiene sobre el
runtime de wawa. Es el subconjunto de Unison que el stack *ya* soporta (wasm + BLAKE3 +
determinismo H2), sin un lenguaje nuevo.
**Hecho (2026-07-04, en `wawa-memo/src/base.rs` + `tests/h3b.rs`):** el módulo `base` monta la
`Base` content-addressed sobre el grano de H2. Cuatro propiedades Unison, todas verificadas
(8 tests verdes): **(1) identidad = `blake3(bytecode)`** — `definir` es idempotente y deduplica
(structural sharing); **(2) los nombres humanos son metadata separada** (`nombre → Id`) — nombrar
jamás toca `defs`; **(3) actualizar no rompe** (el corazón) — una referencia es un `Termino::Ref(Id)`,
un hash; "actualizar f" = insertar def nueva (hash nuevo) + rebindear el nombre, y todo lo que
apuntaba al hash viejo **evalúa idéntico** (v1 y v2 coexisten, ninguna se sobreescribe); **(4)
composición por hash** — un programa es un árbol de hashes (`Termino::Componer`), su identidad
`blake3(árbol)` es estable e **independiente del mapa de nombres**. Extras: una referencia
colgante **falla con `Carga` sin mentir** (el hash no se resuelve en silencio a otra cosa), las
fallas deterministas de H2 se propagan por la composición, y **evaluar reusa el `Memo`** (cada
hoja `Ref` se memoiza) — código direccionado por contenido + cómputo direccionado por contenido
= el mismo hash, la tesis de §H3 mostrada en pequeño. **Frontera:** NO es Unison entero (sin
grano intra-función, sin typechecking sobre la base, sin lenguaje); es el *subconjunto* que
wasm + BLAKE3 + H2 sostienen. Demuestra que el modelo **se sostiene**, no que hayamos colapsado
paquete = función = proceso (eso sigue siendo el design-doc de H3a).
**Puente H3b↔H2c (2026-07-04, `wawa-memo/src/programa.rs` + `tests/h3b_malla.rs`, 8 tests):**
cierra la tesis de §H3 uniendo el código-por-contenido de H3b con el modelo de confianza de H2c.
Un programa entero viaja por la malla como un `Paquete { defs, termino }` **seguro por
construcción**, en dos capas que tapan vectores distintos: **(1) integridad del código = el
hash** (nueva) — las defs no llevan hash declarado, su identidad se **deriva** al recibirlas;
una `Ref(H)` sólo se satisface con los bytes exactos que hashean a `H`, así que alterar o retener
una def deja su referencia colgante y **rechaza el paquete** sin firmar nada (el hash ES la
prueba de integridad; la base local queda intacta); **(2) verdad del resultado = reproducción**
(H2c sobre un programa entero) — que el código sea íntegro no dice qué computa, así que una
salida afirmada se **recomputa y compara** (`ingerir_paquete_verificando`), rechazando la
envenenada. `verificar_paquete` es la primitiva pura. Compartir *código* es tamper-evident por el
hash; compartir *resultados* es seguro por reproducción — juntos, la foto completa de «el hash es
el espacio de nombres común».
- **H3c** ✅ — (exploratorio) **El grano intra-función: el linker de contenido** (2026-07-05,
`wawa-memo/src/enlace.rs` + `tests/h3c.rs`, 10 tests; 40 verdes en el crate). H3b dejó las
definiciones *opacas*: una función wasm no podía llamar a otra — la composición vivía sólo en el
árbol (`Termino::Componer`), fuera del bytecode. H3c mete la dependencia **dentro**: una
definición importa a otra **por hash** (`import "base" "h:<blake3-hex>"`), y el linker la
satisface únicamente con los bytes de la `Base` que hashean exacto a ese `Id` — el contrato de
`Termino::Ref`, ahora intra-bytecode. Consecuencias verificadas:
**(1) el Merkle-DAG de código** — el hash del callee está embebido en el bytecode del caller ⇒
la identidad del caller pinnea transitivamente el DAG entero; la dependencia ENTRA a la
identidad; **(2) actualizar no rompe, transitivamente** — «actualizar g» = def nueva + rebind;
toda f que embebía `h(g_v1)` sigue llamando los bytes exactos de siempre (v1/v2 coexisten);
**(3) los ciclos son imposibles por criptografía**, no por disciplina — un ciclo exigiría un
hash que depende del hash que lo contiene (invertir BLAKE3); el DAG es DAG por construcción,
idéntico a Unison; **(4) la memoización subsume el DAG** — la clave del compuesto
(`blake3(bytecode) ⊕ entrada ⊕ E`) ya cubre sus deps; un cierre INCOMPLETO jamás se cachea (el
colgante es un hecho de la base local, no del bytecode — cachearlo envenenaría réplicas);
`E` distingue el linker (`vacio` vs `base-v1`) para que los espacios de claves no colisionen;
**(5) los paquetes de malla llevan el cierre transitivo** — retener/alterar una dep profunda
(a dos aristas del árbol) tumba la ingesta con `RefColgante`, y la portabilidad del compuesto
hereda el veredicto más conservador de su DAG (`clasificar_cierre`). Política honesta: cada
subllamada corre en instancia fresca con fuel completo (pin en `E`); el presupuesto compuesto
no está acotado globalmente — suficiente para el *modelo*, no para un sandbox de producción.
Con H3c, de las dos ambiciones abiertas de H3 queda demostrado el **grano intra-función** (en
su forma wasm: función llama función por hash); lo aún abierto es el **colapso total**
paquete = función = proceso (el lado "proceso" = replay del MonotonicLog, plan OS-CRDT).
**Formato de viaje + checker standalone (2026-07-05, `wawa-memo` `formato.rs` +
`bin/wawa-verifica`, 5 tests; 45 en el crate):** el `Paquete` deja de ser sólo-memoria.
Serialización **canónica** versionada (`WAWA-PAQ1`): biyección bytes↔paquete (defs ordenadas
por Id, término con la misma serialización estructural que define su identidad, parseo
estricto — toda corrupción es un error determinista, nunca "mejor esfuerzo") ⇒
`blake3(bytes) = id_paquete` estable, la identidad del paquete entero en el mismo espacio de
nombres. Disciplina intacta: los bytes NO llevan hashes declarados — **el formato transporta,
el hash prueba** (la ingesta deriva identidades y rechaza el byte flipeado con `RefColgante`).
`wawa-verifica <paquete.wawa> <entrada> <esperado>` es el checker pequeño y auditable
(estilo H1) de la afirmación «este paquete con esta entrada produce este resultado»: exit
0/1/2 — ejecutable como evidencia `cmd-exit` en el sandbox de hammer (un `.swm` cuyo payload
es un programa wasm verificable = el puente concreto paquete↔función) o como gate de ingesta
de un peer. El transporte real (anti-entropy) sigue siendo del plan OS-CRDT.
**Frontera honesta.** No prometer "una distro donde paquete = función = proceso": H3b+H3c
demostraron el núcleo del modelo (definir por hash, nombres como metadata, actualizar sin romper
— ahora también transitivo —, componer por hash *fuera y dentro* del bytecode) sobre wasm, no el
colapso total ni el grano sub-función (el AST interno de una función sigue siendo opaco: el átomo
es la función-módulo, no la expresión). Es la idea más disruptiva; su núcleo ya está verde, su
ambición máxima sigue abierta.
---
## H4 — Configuraciones compartidas: compatibles, completas y seguras (la aplicación)
**De dónde sale.** No de la revisión externa: del **uso**. Un usuario modifica su
sistema; cada configuración suya es un *conjunto de modificaciones*. Quiere compartirlas
por la malla y, al **buscar** una ajena, saber si puede adoptarla — que sea **compatible,
completa y segura**. H4 aterriza toda la maquinaria de H2/H3 sobre ese caso concreto.
**La observación que lo hace tratable.** "Compatible" parece semántica difusa, pero el
usuario lo definió con dos ejemplos que el modelo trata **mecánicamente distinto**:
- *Logo.* Ya cambié el logo; bajo una config que **también** cambia el logo. No es un
error: ambas **escriben la misma superficie** con contenido distinto. La resolución es
una **elección**, no un rechazo.
- *Wayland.* Modifiqué el protocolo de Wayland; bajo algo que **depende de** el Wayland
stock. Su dependencia **no resuelve** contra mi versión ⇒ incompatible.
Entonces "compatible" = **colisión sobre la misma superficie**, y son dos cosas: colisión
de *escritura* (elección) y dependencia *insatisfecha* (rechazo). El eje nuevo que H4
agrega es un **espacio de nombres de slots** que cada modificación **reclama** (escribe,
`slot → Id`) y **requiere** (depende, `slot → Id` a una versión exacta). Con esos dos
conjuntos, compatible queda **definido de forma dura**, y sus dos mitades **reusan
primitivas ya verdes**:
| propiedad del usuario | qué la garantiza | dónde (ya construido) |
|---|---|---|
| **segura** | verificar reproduciendo — rechaza el resultado envenenado | `verificar_paquete` (H2c) |
| **completa** | cierre transitivo presente — sin referencia colgante | `ingerir_paquete` (H3c) |
| **compatible** | requisitos resueltos + reclamos disjuntos | **H4, nuevo** |
La definición dura de compatibilidad de una config `C` contra mi estado `E`:
> (1) **Requisitos** (caso wayland): `∀ (slot, id) ∈ C.requiere : E[slot] = id`. Un slot que
> resuelve a otra versión (o falta) es **incompatible** — el análogo de sistema a la
> `RefColgante` de H3c: una superficie requerida que resuelve a otros bytes.
> (2) **Reclamos** (caso logo): `∀ slot ∈ C.reclama : E[slot]` ausente **o** igual en `Id`.
> Un slot ya ocupado por otro contenido es **colisión = elección**, no error.
**Tickets:**
- **H4a** ✅ — (prototipo, `wawa-memo/src/compat.rs` + `tests/h4.rs`, 7 tests; **52 verdes**
en el crate) El modelo de slots corriendo. `Config { reclama, requiere }` sobre el mismo
espacio de hashes que H3; `evaluar(estado, config) → Veredicto ∈ {Compatible, Colision,
Incompatible}` es el chequeo **local y reproducible** ("verificar, no confiar" de SDD 09,
ahora sobre la topología de superficies). Verificado: el caso **logo** da `Colision` (y la
vía por defecto no pisa sin elección explícita); el caso **wayland** da `Incompatible`
(requisito que no resuelve, o ausente); la compatibilidad **entre dos configs**
(`conflicto`) es simétrica y ve tanto reclamos que se pisan como requisitos cruzados; y
`filtrar` **particiona una búsqueda** de la malla en `{compatibles, elegibles,
incompatibles}` — la respuesta concreta a "cuando busco, saber cuáles puedo adoptar". El
test de composición muestra que *compatible no basta*: la misma config puede pasar el
chequeo de slots y ser inservible por **incompleta** (paquete manipulado ⇒ `RefColgante`)
o **insegura** (resultado afirmado falso ⇒ recomputar lo rechaza) — las tres propiedades,
un solo hash.
- **H4b** ✅ — El modelo de slots **subido a la receta real de hammer** (no ya un prototipo
host). Cambios:
- `Recipe` (y el `source_patch` del `.swm`) llevan un bloque **`slots`** con `claims`/
`requires` (`slot → b3:…`), **fuera de `hash_inputs`** — misma disciplina que la evidencia
de H1: declara topología, no identidad ⇒ no mueve el `artifact_hash` (test que lo ancla).
Viaja intacto por los dos sentidos del puente (`Recipe → .swm → Recipe`, test de round-trip).
- `InstalledDb` (Etapa F) registra los `claims` por paquete y expone `system_state() →
slot → hash`: el `Estado` que el gate consulta.
- `hammer-core::compat::evaluar(estado, slots) → Veredicto` (el álgebra probada en `wawa-memo`,
ahora sobre tipos de hammer).
- **El gate en `hammer install`**: antes de reconstruir o hidratar nada, evalúa el paquete
entrante contra el estado instalado. **Incompatible** (requisito sin resolver, caso wayland)
⇒ aborta con un mensaje claro; **Colisión** (caso logo) ⇒ aborta pidiendo elección, salvo
`--force-slots`; **Compatible** ⇒ procede y registra los `claims` (para que la PRÓXIMA
instalación detecte la colisión). `hammer install --help` expone `--force-slots`.
Frontera que queda: definir el **espacio de slots** del sistema (qué es una "superficie": un
fichero, un módulo wasm de wawa, un componente) — ahí está el diseño real, no en el álgebra
(que ya está).
- **H4c** ✅ — **Superficies OBSERVADAS: "no prometas, observá".** H4b confía en que el AUTOR
declare `slots`; la misma subida que hizo H1 (de *prometer* a *verificar*) aplica acá: la
superficie más común y observable de un paquete es **el conjunto de paths que escribe**, y
esos paths YA están declarados en el `.swm` (`target_bin` de cada source_patch, `path` de
cada file_drop) — se conocen **antes de hidratar**, así que el gate aborta sin tocar nada.
- `compat::output_paths(swm)` lee esos paths; `compat::path_collisions(db, name, paths)`
detecta cuáles ya posee **otro** paquete instalado (reusa `InstalledDb.files` + el nuevo
`owner_of` — cero declaración nueva en la receta). Reinstalar el mismo paquete sobre sus
propios paths NO colisiona (es upgrade).
- `hammer install` corre este chequeo **junto** al declarado: pisar el fichero de otro
paquete es el caso *logo* a nivel de fichero (elección) ⇒ aborta salvo `--force-slots`.
- **Verificado e2e real** (`tests/compat_gate.rs`, shell-ea al binario `hammer`): dos paquetes
escriben `/share/logo.png`; el segundo aborta con "COLISIÓN de fichero" **y no escribe nada**;
con `--force-slots` la elección se respeta y el fichero se escribe.
Con H4c, un paquete **sin declarar slots** ya participa del gate por lo que de verdad toca.
- **H4d** ✅ — **La BÚSQUEDA: `hammer compat <repo>`.** El `filtrar` del prototipo `wawa-memo`,
ahora sobre el repo real y **read-only** (no construye ni toca nada). Responde la pregunta que
arrancó §H4 — *"cuando busco, ¿cuáles puedo adoptar?"*: evalúa **cada** paquete del repo contra
el estado instalado y lo particiona en **{compatibles, requieren-elección, incompatibles}**,
combinando la vía declarada (slots, H4b) con la observada (paths, H4c) — incompatible domina,
colisión (de slot o de fichero) ⇒ elección, si no compatible. **Verificado e2e real**
(`tests/compat_gate.rs`): un repo de dos paquetes se parte correctamente (uno choca de fichero
con lo instalado ⇒ elección, otro limpio ⇒ compatible).
- **H4e** ✅ — **`requires` OBSERVADOS: la versión contra la que el paquete se construyó.** Cierra
la simetría de H4c: además de observar lo que un paquete *escribe*, observamos de qué *depende a
una versión*. La fuente observable, sin que el autor prometa nada: un paquete se construyó contra
la versión de sus deps que hay **en el repo** (`deps.runtime` del `.swm` + `expected_hash` de cada
dep en el índice); si el usuario tiene esa dep instalada a **otro** hash, la divergió ⇒ rechazo
duro. Es el **caso wayland derivado** — el mismo que H4b captura cuando el autor declara
`requires`, ahora leído del cierre.
- `compat::observed_requires(swm, index)` + `compat::version_conflicts(db, req)` (reusan `deps`
del `.swm`, `expected_hash` del índice y `InstalledDb.hash` — cero declaración nueva).
- Cableado en `install` (rechazo duro, no lo salva `--force-slots`: es un requisito, no una
elección) y en `hammer compat` (bucket incompatibles).
- **Verificado e2e real** (`tests/compat_gate.rs`): `hammer compat` marca `app` INCOMPATIBLE
porque su dep `wayland-protocol` está instalada a un hash divergido del repo — read-only, ve
el `source_patch` sin construirlo.
Con H4e, la vía observada es simétrica: **lo que el paquete escribe** (H4c) *y* **de qué depende**
(H4e), ambas sin declaración. Lo que queda abierto: subir el grano del path-slot a superficies
más ricas (un módulo wasm de wawa, un componente) y el **colapso** con el lado proceso.
**Frontera honesta.** H4a demuestra el *álgebra* sobre hashes abstractos; lo que **no**
resuelve es la **granularidad de los slots** — dos configs pueden no colisionar en el slot
`wayland-protocol` pero sí en un byte compartido más fino si el slot está mal cortado. El
modelo es correcto para cualquier corte; **elegir el corte** (grano de superficie) es H4b y
es donde vive el juicio. Y la compatibilidad es **estructural** (¿resuelven los hashes?), no
**semántica** (¿las dos modificaciones *tienen sentido* juntas?): esto último sigue fuera
del alcance, como debe.
---
## La ambición, hasta el final: config = paquete = función = proceso
H4 no es un anexo: es la **prueba de que la tesis de §H3 sirve para lo que un usuario hace
de verdad.** Y empuja el colapso que H3 dejó abierto un paso más. Recapitulando lo que ya
está verde y unificado bajo **un solo tipo de objeto — el hash**:
- una **función** es `blake3(bytecode)` (H3b),
- un **programa** es `blake3(árbol de hashes)` (H3b) con dependencias por hash *dentro* del
bytecode (H3c: el Merkle-DAG),
- un **paquete** viaja como bytes canónicos con `blake3(bytes) = id` y se verifica standalone
(`wawa-verifica`, H3c),
- y ahora una **configuración** es un conjunto de `(slot → hash)`: reclamos y requisitos que
se resuelven exactamente como las `Ref` de H3c (una dependencia insatisfecha *es* una
referencia colgante, sólo que a nivel de sistema).
Es decir: **una config y un programa son el mismo objeto visto por dos lentes.** Un programa
compone funciones por hash; una config compone superficies del sistema por hash. `RefColgante`
gobierna la completitud de ambos; reproducir gobierna la seguridad de ambos. El colapso
*paquete = función* ya no es aspiración — H4 lo mostró incluyendo la config como caso.
**Lo que sigue abierto es el último lado: el proceso.** Un sistema configurado *corriendo* es
un proceso, y un proceso —en la visión H2— *es* su módulo más su log de entradas
(`MonotonicLog`, PLAN-OS-CRDT E1): mover o reconstruir un proceso = copiar `(módulo, log)` y
re-ejecutar determinísticamente (H2, regalo 3). Si un **estado del sistema** es un
`Estado = slot → Id` (H4) y su **historia** es un log monótono de configs aplicadas, entonces
un sistema-en-ejecución colapsa al mismo objeto: **un hash del código + un log de entradas**,
reproducible, migrable, memoizable. Ahí `config = paquete = función = proceso` deja de ser
lema y es *el mismo tipo direccionado por contenido en las cuatro lentes*.
Eso **no está construido** y no lo promete este doc: el lado "proceso" necesita el replay del
`MonotonicLog` sobre estado real (efectos, I/O), que es exactamente lo que H2 excluye de "puro"
y lo que vive en el plan OS-CRDT del otro agente. Pero la forma es clara y las tres primeras
lentes ya coinciden en el hash. La ambición máxima —**una distro donde instalar una config,
compilar una función, empaquetar y correr un proceso son la misma operación sobre el mismo
espacio de nombres**— sigue siendo ambición; su núcleo, cada vez menos.
---
## Orden y dependencias
```
H1 (proof-carrying) ── extensión natural del VERIFY actual ──► arrancable ya, alto valor
H2a (auditar determinismo wasm) ──► gate de todo H2 (spike wawa)
└► H2b (caché local) ──► H2c (caché de malla) [sólo si H2a da verde]
H3a (design-doc) ──► registrar la visión, barato
└► H3b ✅ (experimento wasm-por-función) [sobre H2 puro: núcleo Unison verde]
└► H3c ✅ (linker de contenido: imports por hash, Merkle-DAG intra-función)
└► H4a ✅ (config = conjunto de slots por hash: compatible/completa/segura)
└► H4b ✅ (slots en la receta/.swm real + gate en `hammer install`)
└► H4c ✅ (superficies OBSERVADAS: colisión de fichero, sin declarar slots)
└► H4d ✅ (`hammer compat <repo>`: la búsqueda que particiona un repo)
└► H4e ✅ (requires OBSERVADOS: dep divergida = incompatible)
└► [proceso] replay del MonotonicLog ──► plan OS-CRDT (otro agente)
```
Recomendación: **H1 primero** (empuja la frontera que ya tenemos, sin apuestas). **H2a** en
paralelo como spike de wawa (decide si H2/H3b existen). **H3a** cuando haya un rato: es escribir,
no construir. H2c y H3b son futuro condicionado a verdes previos.
## Qué NO promete este doc
- **No mueve la frontera de confianza.** Sigue en el verificador (checker H1, reproducción H2c);
la IA nunca se auto-promueve — SDD 08 §4 intacto.
- **No reclama verificación formal del sistema.** H1 estratifica evidencia y la reporta;
"verificado bajo supuestos", no "invulnerable".
- **No promete memoización de malla sin determinismo probado** (H2a es el gate).
- **No promete el colapso Unison.** H3b (✅) demostró el *núcleo* (definir por hash, nombres
como metadata, actualizar sin romper, componer por hash), H3c (✅) el *grano intra-función*
(función llama función por hash: el Merkle-DAG de código) y H4a (✅) que una *config* es el
mismo objeto (superficies por hash; una dependencia insatisfecha = referencia colgante de
sistema). El colapso total (config = paquete = función = **proceso**, grano sub-función)
sigue siendo ambición, no promesa: falta el lado proceso (replay del `MonotonicLog`).
- **No promete compatibilidad semántica.** H4 decide compatibilidad **estructural** (¿resuelven
los hashes de las superficies?), no si dos modificaciones *tienen sentido* juntas. Y el
**grano de los slots** (qué es una "superficie") es diseño abierto (H4b), no resuelto.