tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1373Shell 116Rust 74Markdown 641915 Definitions

SDD 24 v2: correcciones incorporadas al cuerpo; §8 queda como registro de evidencia

El diseño entrante v2 absorbe las cuatro correcciones de la verificación:
R1 (inmutabilidad por mount, modos upstream), IF-1/IF-2 (blake3_arbol en vez de
EROFS y sin fanotify), la carrera B nombrada en §0 con la Fase 1b como entregable
de F3, y Q1 cerrada por precedente. NG-4/Q3 resuelta: se cita el hash, nunca una
ruta, y la cita sobrevive a la poda porque el mirror la regenera.

§8 deja de ser la lista de objeciones y pasa a ser el registro reproducible de las
mediciones, para que nadie tenga que volver a descubrirlas.

Añade una medición nueva (§8.2): la composición REAL de los dos grupos de overlay
—el del rootfs con --tmp-overlay / seguido del de la fuente sellada sobre /src—
funciona en una sola corrida. El acumulador de --overlay-src se consume por
operación y no filtra, y con modos upstream escribir en un SUBDIRECTORIO de /src
funciona: justo el caso que el chmod a-w rompía. El gate de mecanismo de F0 queda
verde; sólo resta IF-5 bit-a-bit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4

c31c00da09843a712b1d50cc8238e0a112ab3008 · by Sergio · (1 month ago) · parent dcd0ba97e · browse the tree at this commit

Semantic delta

No definition changed: this change is text, documentation or data.

1 files · +199 −235

diff --git a/docs/24-arbol-de-fuentes.md b/docs/24-arbol-de-fuentes.md
index d0d229d5..67e06bac 100644
--- a/docs/24-arbol-de-fuentes.md
+++ b/docs/24-arbol-de-fuentes.md
@@ -1,26 +1,40 @@
-# SDD 24 — El árbol de fuentes: caché sellada + workspace efímero
+# SDD 24 — El árbol de fuentes: caché sellada + workspace efímero (v2)
 
 > **Resuelve ADR 0012.** Estado propuesto: ACEPTADO al cerrar F2.5.
-> **Fecha:** 2026-08-28.
+> **v2, 2026-08-28:** incorpora la verificación contra el código (§8): R1 e IF-1/IF-2 corregidos
+> por medición, segunda carrera nombrada, Q1 cerrada por precedente, tensión NG-4/Q3 resuelta.
 
 ---
 
-## 0. El problema, medido
+## 0. El problema, medido — son DOS carreras
 
-`work/sources/<nombre>-<sha16>` cumple dos papeles que se contradicen bajo concurrencia:
+`work/sources/…` cumple dos papeles que se contradicen bajo concurrencia:
 
 1. **Caché direccionada por contenido**: dos builds de la misma dep resuelven al MISMO directorio.
-2. **Workspace mutable de build**: patches, `cargo vendor`, `go mod vendor` (minutos, decenas de GB),
-   y el wrapper `.zwrap/cc` que la receta deja EN EL ÁRBOL.
-
-Fallo medido a escala: al invalidar `libdrm`, ~93/205 recetas KDE murieron con
-`/src/.zwrap/cc is not a full path…` — el wrapper barrido por el fetch concurrente de otra receta.
-Mitigación vigente: `flock` global + `JOBS=1` ⇒ **la granja entera corre serializada**. Un lock sólo
-en la extracción NO alcanza: un segundo proceso puede borrar un árbol que un `bwrap` está compilando.
-
-**La tesis de este SDD:** los dos papeles no se reconcilian — se **separan por capa**, con el corte
-puesto para que lo caro (vendor) caiga del lado inmutable. Es la misma jugada que el sandbox ya usa
-para las build-deps (`--overlay-src` RO apiladas): extenderla a `/src`.
+   (El nombre es `<nombre>-<sha16>` para tarballs y `<nombre>-<commit-completo>` para git —
+   `fetch.rs:234` / `fetch.rs:73`.)
+2. **Workspace mutable de build**: patches, `cargo vendor`, `go mod vendor` (minutos, decenas de
+   GB), y los residuos que las recetas dejan EN EL ÁRBOL — `.zwrap/cc` lo escriben **131 recetas**
+   *(medido)*, casi todas de la cola KDE.
+
+Las dos carreras concretas:
+
+- **Carrera A — fetch vs build**: un `remove_dir_all` de re-materialización barre un árbol que un
+  `bwrap` está compilando. Medida a escala: al invalidar `libdrm`, ~93/205 recetas KDE murieron con
+  `/src/.zwrap/cc is not a full path…`.
+- **Carrera B — build vs build**: dos recetas DISTINTAS que comparten una dep transitiva no sellada
+  la construyen en el MISMO `sources/<dep>/output` ⇒ `Some other Meson process is already using
+  this build directory`. Documentada en `build-farm.sh:82-99`; hoy se concilia con un **reintento
+  serial** (Fase 1b) que existe sólo por esto. Un lock por árbol (opción A del ADR) NO la resuelve
+  sin serializar.
+
+Mitigación vigente: `flock` global + `JOBS=1` ⇒ la granja entera corre serializada.
+
+**La tesis:** los dos papeles se **separan por capa**, con el corte puesto para que lo caro
+(vendor) caiga del lado inmutable. Es el mismo mecanismo que el sandbox ya usa para las build-deps
+(`--overlay-src` RO apiladas), extendido a `/src`. Mata las dos carreras: A porque lo sellado es
+inmutable y la re-materialización es imposible (mismo `fuente_id` ⇒ ya existe); B porque cada
+constructor tiene su propio `output/` en su upper privado.
 
 ---
 
@@ -29,175 +43,175 @@ para las build-deps (`--overlay-src` RO apiladas): extenderla a `/src`.
 ### 1.1 Las dos capas
 
 ```
-MATERIALIZACIÓN (host, una vez por fuente_id, con lock corto por-hash)
-  fetch (red) → patch → vendor (cargo/go)
-  → sellar árbol RO en  work/sources-cas/<fuente_id>-<nombre>/
+MATERIALIZACIÓN (host, una vez por fuente_id, lock corto por-hash)
+  fetch (red) → patch → vendor (cargo/go)      ← YA ocurre todo en host hoy (lib.rs:224,248,258);
+  → sellar árbol en  work/sources-cas/<fuente_id>-<nombre>/     este SDD lo nombra como capa
        fuente_id = blake3( source_id ⧺ Σ blake3(patch_i) ⧺ marca_vendor )
+       source_id cubre ambos modos: "git:<commit>" XOR "tarball:<sha256>"
 
 BUILD (sandbox, N en paralelo, cero locks)
-  bwrap monta /src = overlay( lower: fuente sellada RO, upper: work/ws/<uuid>/ privado )
-  configure → compile → install → seal artefacto → destruir upper
+  bwrap monta /src = overlay( lower: fuente sellada, upper+work: work/ws/<uuid>/ )
+  configure → compile → install → seal artefacto → destruir ws/<uuid>
 ```
 
-- **El vendoreo se paga una vez** y queda del lado inmutable: `marca_vendor` deriva de los lockfiles
-  (`Cargo.lock` / `go.sum`) que ya viven en el `source_id`, así que la fuente sellada es
-  determinista dado el pin.
-- **`.zwrap/cc` y todo residuo de build viven en el upper privado.** El fallo medido queda
-  estructuralmente imposible: no hay inode compartido escribible.
-- **El diamante deja de serializar**: N builds montan la misma fuente sellada como lower RO
-  simultáneamente. Es exactamente el mecanismo ya probado con las build-deps.
-- **La fase `patch` migra a la materialización** (fuera del sandbox). Inerte para el hash: el
-  contenido del patch ya está en `hash_inputs`; sólo cambia dónde se aplica.
+- **El vendoreo se paga una vez** y queda del lado inmutable: `marca_vendor` deriva de los
+  lockfiles que ya viven en el `source_id` ⇒ la fuente sellada es determinista dado el pin.
+- **`.zwrap` y todo residuo viven en el upper privado.** Las 131 recetas quedan estructuralmente
+  a salvo.
+- **La fase `patch` NO migra: ya corre en host** desde el día 1. Este SDD nombra como contrato lo
+  que hoy es de facto. La tabla de fases del **SDD 02 §4 está desactualizada** (dice "patch en
+  sandbox") y corregirla es entregable de F1.
+
+### 1.2 Gotchas de bwrap verificados (citar al implementar)
 
-### 1.2 Por qué no las otras opciones del ADR
+- `--overlay` exige ≥1 `--overlay-src` previo, y el acumulador **se consume por operación**. Como
+  `sandbox.rs` ya emite `--overlay-src <rootfs> [deps…] --tmp-overlay /`, el grupo de `/src` va
+  **después** y no debe filtrar srcs al overlay del root. Orden de argumentos, no diseño.
+  **Verificado end-to-end** (§8.2): la composición real de los dos grupos funciona y no filtra.
+- El límite de una página del `lowerdir` (`sandbox.rs:347-357`, el pre-merge `cp -al`) **no
+  aplica**: `/src` es un mount aparte con su propia lowerdir.
+- `--overlay RWSRC WORKDIR DEST`: exige `upper` **y** `work` vacíos en el mismo filesystem.
 
-- **(A) lock por árbol durante todo el build**: correcto pero serializa el diamante qtbase — el
-  costo que este SDD elimina.
-- **(B) árbol privado por constructor**: paga el vendor (decenas de GB, minutos) en CADA build.
-- **(C) caché inmutable + copia**: paga la copia completa por build. Este diseño es (C) con
-  overlay en vez de copia y con el vendor movido al lado sellado.
+### 1.3 Por qué no las otras opciones del ADR
+
+- **(A)** lock por árbol durante todo el build: serializa el diamante qtbase y **no resuelve la
+  carrera B**.
+- **(B)** árbol privado por constructor: paga el vendor completo en cada build.
+- **(C)** caché inmutable + copia: paga la copia por build. Este diseño es (C) con overlay en vez
+  de copia y el vendor movido al lado sellado.
 
 ---
 
 ## 2. Reglas
 
 - **R1** — La fuente sellada es inmutable y direccionada: `fuente_id = blake3(source_id ⧺
-  Σ blake3(patch) ⧺ marca_vendor)`. Vive en `work/sources-cas/`, `chmod a-w` recursivo tras sellar.
+  Σ blake3(patch) ⧺ marca_vendor)`. Vive en `work/sources-cas/`. **La inmutabilidad la da el
+  mount, no los bits de modo**: a través del overlay es estructuralmente imposible escribir el
+  lower. Los modos del árbol se dejan **tal como los trae upstream** — sellarlos a `a-w` los
+  hereda el copy-up y **rompe el build** (medido: el sandbox corre uid 1001 sin
+  `CAP_DAC_OVERRIDE`; EACCES al reescribir un fichero existente y al crear dentro de un subdir;
+  el fallo es parcial-por-profundidad porque el modo del upper root gana en la raíz). Blindaje
+  contra procesos del host: proteger el **directorio contenedor** `sources-cas/`, nunca el árbol.
+  La detección de corrupción es `blake3_arbol` (R10).
 - **R2** — Sellado atómico: materializar en `tmp/`, escribir marker, `rename`. Jamás visible un
   árbol parcial bajo su nombre final.
-- **R3** — El marker `.sellada` contiene `{fuente_id, blake3_arbol, ts}`. Presencia del directorio
-  SIN marker = no sellado (lección §14.4.1: *un vacío es un cache-hit*; el marker exige contenido).
-- **R4** — Nada escribe en una fuente sellada. El build la recibe sólo como lower RO del overlay
-  de bwrap.
-- **R5** — Workspace efímero por constructor: `work/ws/<uuid>/`, destruido tras el seal del
-  artefacto. `--keep-ws` lo conserva para debug.
+- **R3** — Marker `.sellada` = `{fuente_id, blake3_arbol, ts}`. Directorio sin marker = no sellado
+  (lección del vacío-como-cache-hit: *un ausente falla ruidosamente; un vacío llega hasta el final
+  diciendo que todo fue bien* — CLAUDE.md regla 3).
+- **R4** — Nada escribe en una fuente sellada. El build la recibe sólo como lower del overlay.
+- **R5** — Workspace efímero por constructor: `work/ws/<uuid>/{upper,work}` (los dos directorios
+  que `--overlay` exige, mismo filesystem), destruido tras el seal. `--keep-ws` lo conserva.
 - **R6** — Lock **sólo** durante la materialización, por `fuente_id`
   (`flock work/locks/<fuente_id>`). El segundo llegador espera y encuentra sellado. Los builds no
-  toman ningún lock.
+  toman locks.
 - **R7** — Fallo o kill durante materialización ⇒ sólo queda `tmp/`; la corrida siguiente lo barre
   y re-materializa. Nunca se sella parcial.
-- **R8** — La adopción **no mueve ningún `artifact_hash`**: `fuente_id` no entra en `hash_inputs`
-  (sus componentes ya están). Verificación obligatoria pre-merge: `hammer hash --check` sobre el
-  corpus completo, cero movidos.
-- **R9** — GC de `sources-cas` por alcanzabilidad desde recetas activas; los no alcanzables son
-  podables gratis (reproducibles desde el mirror ADR 0013). Medir con `st_nlink`/contenido, nunca
-  con `du`.
-- **R10** — Re-materializar un `fuente_id` existente debe reproducir `blake3_arbol` del marker o
-  fallar ruidoso (detecta vendor no determinista o mirror envenenado).
-- **R11** — El `flock` global de CLAUDE.md regla 1 se retira **sólo** tras F2.5 verde, en el mismo
-  commit que sube `JOBS` del worker.
+- **R8** — La adopción **no mueve ningún `artifact_hash`**: `fuente_id` no entra en `hash_inputs`.
+  Verificación pre-merge: `hammer hash --check` sobre el corpus, cero movidos.
+- **R9** — GC de `sources-cas` por alcanzabilidad desde recetas activas; lo no alcanzable es
+  podable (regenerable desde el mirror ADR 0013). Medir por contenido, nunca con `du`.
+- **R10** — Re-materializar un `fuente_id` existente debe reproducir el `blake3_arbol` del marker
+  o fallar ruidoso (detecta vendor no determinista o mirror envenenado).
+- **R11** — El `flock` global (CLAUDE.md regla 1) y el `JOBS=1` se retiran **sólo** tras F2.5
+  verde, en el mismo commit.
 
 ## 3. Invariantes (mecánicamente verificables)
 
-- **IF-1** — N builds concurrentes que comparten fuente jamás mutan un inode de `sources-cas`.
-  Test: diamante real (dos recetas KDE sobre la misma qt-dep) con `fanotify` de escritura sobre
-  `sources-cas` — cero eventos.
-- **IF-2** — Canario deliberado (estilo D9 de harkaq): un build de test intenta `touch` en `/src`
-  fuera del upper ⇒ DEBE recibir `EROFS`. Si no lo recibe, el montaje está roto — no se cree, se gana.
+- **IF-1** — Tras N builds concurrentes sobre la misma fuente, el `blake3_arbol` del árbol sellado
+  casa con el del marker. Verificable **sin fanotify** (que exige `CAP_SYS_ADMIN` y complica la
+  granja): hashear antes/después es más barato y más fuerte.
+- **IF-2** — Canario deliberado (estilo D9 de harkaq): un build de test **escribe** en `/src`
+  (debe FUNCIONAR, vía copy-up al upper) y el árbol sellado **sigue idéntico** por `blake3_arbol`.
+  Si el hash se movió, alguien montó el lower como bind RW. No se cree: se gana.
 - **IF-3** — `hammer hash --check` corpus completo: hash vigente idéntico pre/post migración.
 - **IF-4** — `kill -9` en medio de la materialización ⇒ no existe marker; la corrida siguiente
   sella limpio y `blake3_arbol` casa con una materialización sin interrupción.
-- **IF-5** — Artefacto construido sobre overlay ≡ bit-a-bit al mismo artefacto construido con el
-  modelo viejo (por receta de cada clase: c, cargo, go, gui).
-- **IF-6** — Tras F3, `work/sources/` (viejo) no existe y ningún script lo referencia
-  (verificado reconstruyendo, no con `grep` — §14.4.4).
+- **IF-5** — Artefacto sobre overlay ≡ bit-a-bit al del modelo viejo, **una receta por clase y
+  por build-system**: autotools-en-raíz (zlib), **meson y cmake (escriben en subdirs — la clase
+  que un spike sólo-zlib dejaría sin cubrir)**, cargo, go.
+- **IF-6** — Tras F3, `work/sources/` no existe, la **Fase 1b de `build-farm.sh` está borrada**, y
+  ningún script referencia el esquema viejo — verificado reconstruyendo, no con `grep` (en este
+  repo `grep` sobre ficheros con comentarios densos no es una medición).
 
 ## 4. Fases
 
-- **F0 — spike** (gate del frente): sellar `zlib`, montar `/src` como overlay en bwrap, construir.
-  Verde ⇔ IF-5 para zlib + IF-2. Un día.
+- **F0 — gate residual**: el mecanismo overlay ya está verde (verificado 2026-08-28 con bwrap
+  0.11.2: lower legible, escritura al upper, lower intacto, y la composición con el
+  `--tmp-overlay /` del root — §8). Queda **sólo IF-5**, y con el corpus de clases completo — el
+  caso que muerde es meson/cmake en subdirs, no zlib.
 - **F1 — materialización**: `fetch.rs` produce fuentes selladas (R1–R3, R6–R7, R10). El build
-  sigue copiando del sellado al modelo viejo (puente temporal, sin overlay aún).
-- **F2 — build sobre overlay**: `/src` = lower sellado + upper efímero (R4–R5). `.zwrap` al upper.
-  Detrás de `SOURCES_CAS=1`, **inerte sin él** (mismo requisito que harkaq: 700+ sellados no
-  cambian por encender un mecanismo). IF-3 + IF-5 por clase.
-- **F2.5 — vertical slice**: un worker real de granja con `JOBS=4` drena una onda KDE que
-  atraviesa el diamante qtbase. Todo `✓ REPRODUCIBLE`, IF-1 con fanotify activo. **Gate del ADR:**
-  0012 pasa a ACEPTADO aquí o el diseño vuelve.
-- **F3 — migración**: borrar `work/sources/`, retirar flock global + `JOBS=1` (R11), GC (R9)
-  cableado al latido.
-- **F4 — cosecha**: medir speedup real de la campaña de reconstrucción del corpus (§13.3) con
-  JOBS=N. El número cierra el informe del ADR.
+  sigue en el modelo viejo (puente). **Incluye corregir la tabla de fases del SDD 02 §4.**
+- **F2 — build sobre overlay**: `/src` = lower sellado + `ws/<uuid>/{upper,work}` (R4–R5),
+  respetando §1.2. Detrás de `SOURCES_CAS=1`, **inerte sin él**. IF-3 + IF-5.
+- **F2.5 — vertical slice**: worker real con `JOBS=4` drena una onda KDE que atraviesa el diamante
+  qtbase. Todo `✓ REPRODUCIBLE`, IF-1 e IF-2 corridos. **Gate del ADR:** 0012 pasa a ACEPTADO aquí
+  o el diseño vuelve.
+- **F3 — migración**: borrar `work/sources/`; **borrar la Fase 1b de `build-farm.sh`**
+  (entregable medible: la carrera B ya no existe); **reescribir — no re-apuntar — el watchdog de
+  disco de `farm-worker-loop.sh:43`** (queda más simple: lo sellado es inmutable, cada `ws/<uuid>`
+  tiene un solo dueño); actualizar `reconstruir-corpus.sh:206`; retirar flock global + `JOBS=1`
+  (R11); GC (R9) al latido.
+- **F4 — cosecha**: medir el speedup real de la campaña de reconstrucción con JOBS=N. El número
+  cierra el informe del ADR.
 
 ## 5. No-objetivos
 
-- **NG-1** — No cambia el sandbox de build ni sus fases internas (bwrap sigue; sólo cambia cómo
-  llega `/src`).
-- **NG-2** — No toca `hash_inputs` ni re-hashea nada (R8 lo hace verificable, no aspiracional).
+- **NG-1** — No cambia el sandbox de build ni sus fases internas.
+- **NG-2** — No toca `hash_inputs` ni re-hashea nada (R8 lo verifica).
 - **NG-3** — No resuelve la red del vendor de Go en host (→ D1).
-- **NG-4** — `sources-cas` NO es el store ni procedencia: es caché podable. La procedencia de
-  fuentes es el mirror (ADR 0013).
-- **NG-5** — No introduce dependencia de filesystem con reflink: el mecanismo es el overlay de
-  bwrap que ya se usa para deps.
+- **NG-4** — `sources-cas` NO es procedencia ni parte del store: es caché **podable**. Q3 cita
+  `fuente_id` en el log de transparencia, y eso es compatible **porque la cita es a un hash cuya
+  materialización es regenerable desde el mirror (ADR 0013)**: podar no rompe la cita, la vuelve
+  un puntero frío. Lo que NUNCA se cita es una ruta de `sources-cas`.
+- **NG-5** — No introduce dependencia de reflink ni de overlayfs del host: el mecanismo es el
+  `--overlay` de bwrap ya en uso para deps.
 
 ## 6. Deudas nombradas
 
-- **D1** — `go mod vendor` sigue corriendo en host con red. Acotado a materialización; R10 lo hace
-  detectable. Cierre futuro: materialización dentro de un sandbox con red controlada.
-- **D2** — Verificar que los kernels de la granja (los propios, SDD 22) montan overlay dentro del
-  userns de bwrap con lower en `sources-cas`. Si algún worker no puede: fallback copia-completa
-  (correcto, lento, ruidoso en el log — nunca silencioso).
-- **D3** — `--keep-ws` acumula workspaces; podarlos en el mismo GC de R9.
-- **D4** — El snapshot golden de la granja debe hornear `sources-cas` poblado o los primeros
-  builds pagan materialización fría; decidir en F2.5 con el dato del slice.
-
-## 7. Preguntas abiertas
-
-- **Q1** — ¿La fase `patch` fuera del sandbox es aceptable como riesgo? Propuesta: sí (`git apply`
-  es inerte y el contenido está en el hash). Alternativa si no: materialización dentro de un bwrap
-  sin red salvo el paso fetch.
-- **Q2** — Política de retención de `sources-cas` con vendors grandes: ¿tope en GB con evicción
-  LRU, o sólo alcanzabilidad? Decide el operador con el dato de F4.
-- **Q3** — ¿`fuente_id` se registra en `bootstrap.json` como eslabón del log de transparencia?
-  Barato ahora, difícil retrofit. Propuesta: sí, campo opcional.
+- **D1** — `go mod vendor` sigue en host con red. Acotado a materialización; R10 lo hace
+  detectable. Cierre futuro: materialización en sandbox con red controlada.
+- **D2** — Confirmar en F2.5 que los kernels propios de granja (SDD 22) montan el `--overlay` de
+  bwrap con lower en `sources-cas`. Fallback: copia completa — correcta, lenta, **ruidosa en el
+  log**, nunca silenciosa.
+- **D3** — `--keep-ws` acumula workspaces; podarlos en el GC de R9.
+- **D4** — El snapshot golden debe hornear `sources-cas` o los primeros builds pagan
+  materialización fría. **Choca con el test de `hammer-bootstrap` (`lib.rs:2659`) que afirma que
+  `work/sources/` no viaja en el builder rootfs**: la decisión (¿el builder hereda `sources-cas`?)
+  se toma en F2.5 con el dato del slice, y si es sí, ese test se actualiza a propósito — no se
+  deja fallar por sorpresa.
+
+## 7. Preguntas — estado
+
+- **Q1 — CERRADA por precedente**: fetch+patch+vendor corren en host desde el día 1
+  (`lib.rs:224,248,258`). El riesgo ya se corre hoy; este SDD sólo lo pone bajo contrato (R10 lo
+  hace además detectable). No es una apuesta nueva.
+- **Q2 — abierta**: retención de `sources-cas` con vendors grandes: ¿tope GB con evicción LRU o
+  sólo alcanzabilidad? Decide el operador con el dato de F4.
+- **Q3 — DECIDIDA: sí** — `fuente_id` entra en `bootstrap.json` como campo opcional. Ver NG-4
+  para por qué no contradice la podabilidad.
 
 ---
 
-*Cruces: ADR 0012 (resuelve) · ADR 0013 (procedencia de fuentes) · SDD 02 §4.2 (overlay-src de
-deps: el mecanismo que este SDD reutiliza) · SDD 16 (patrón "inerte sin flag" y canario D9) ·
-§14.4.1 (marker contra vacíos) · CLAUDE.md regla 1 (retiro en R11).*
+*Cruces: ADR 0012 (resuelve) · ADR 0013 (regenerabilidad que sostiene NG-4/Q3) · SDD 02 §4
+(overlay-src de deps: el mecanismo reutilizado; tabla de fases a corregir en F1) · SDD 16 (patrón
+"inerte sin flag"; canario D9 → IF-2) · CLAUDE.md regla 3 (marker contra vacíos) · CLAUDE.md
+regla 1 (retiro del flock en R11) · `build-farm.sh` Fase 1b (carrera B; se borra en F3) ·
+`docs/BRIEFING-hammer.md` §14.4 (los modos de fallo estructurales que R3 e IF-6 citan).*
 
 ---
 
-# 8. Verificación contra el código (2026-08-28)
-
-> Escrito por el agente con acceso al repo, siguiendo el patrón del [SDD 22](22-configurador-kernel.md):
-> un diseño entrante se contesta con evidencia, y **si un dato de hammer lo contradice, manda hammer**.
-> El diseño **se sostiene** y su mecanismo central quedó **probado en un comando** (§8.1). Hay
-> **cuatro correcciones** que hay que aplicar antes de escribir código: dos invalidan una regla y un
-> invariante tal como están escritos, y dos son huecos de alcance.
-
-## 8.1 ✅ El mecanismo central FUNCIONA — spike F0 ya ejecutado
-
-`bwrap 0.11.2` (el del `.dev-fs`) soporta `--overlay RWSRC WORKDIR DEST`. Montado dentro de
-`--unshare-all`, con lower en disco y upper en disco:
-
-```
-lower legible: hola
-escritura al upper: cc          ← /mnt/.zwrap/cc fue al upper privado
-lower intacto: hola             ← el árbol sellado no se tocó
-upper: ovl/upper/.zwrap/cc      ← el residuo de build queda aislado
-```
+# 8. Registro de evidencia — mediciones contra el código (2026-08-28)
 
-⇒ **F0 no es «un día»: el gate de mecanismo ya está verde.** Lo que queda de F0 es IF-5 sobre
-`zlib` (bit-a-bit contra el modelo viejo), que sí exige construir.
+> Las correcciones que salieron de aquí **ya están incorporadas en el cuerpo de la v2**. Esta
+> sección queda como registro reproducible: qué se midió, con qué comando y con qué salida, para
+> que nadie tenga que volver a descubrirlo. Método del SDD 22: un diseño entrante se contesta con
+> evidencia, y si un dato de hammer lo contradice, manda hammer.
 
-Dos hechos del código que lo refuerzan y que el SDD no cita:
+## 8.1 El `chmod a-w` recursivo rompe el build (origen de R1)
 
-- **`--overlay` exige al menos un `--overlay-src` previo**, y el acumulador de `--overlay-src` se
-  **consume por operación de overlay**. Como `sandbox.rs` ya emite `--overlay-src <rootfs> [deps…]
-  --tmp-overlay /`, el grupo de `/src` debe ir **después** y no debe filtrar srcs al del root.
-  Es un gotcha de orden de argumentos, no de diseño.
-- **El límite de página del `lowerdir`** que `sandbox.rs:347-357` documenta (el kernel concatena
-  todas las lowerdir en un string de una página; por eso hay pre-merge con `cp -al` cuando hay
-  muchas deps) **no aplica aquí**: `/src` es un *mount aparte* con su propia lowerdir. El diseño no
-  agrava ese límite.
-
-## 8.2 🔴 R1 está mal: `chmod a-w` recursivo ROMPE el build (medido)
-
-El sandbox corre como **uid 1001, sin `CAP_DAC_OVERRIDE`** (`id` dentro de bwrap lo confirma), y
+El sandbox corre como **uid 1001 sin `CAP_DAC_OVERRIDE`** (`id` dentro de bwrap lo confirma) y
 **overlayfs preserva el modo del lower al hacer copy-up**. Con el árbol sellado a `a-w`
-(dirs 0555, ficheros 0444), medido sobre patrones reales de fase:
+(dirs 0555, ficheros 0444), sobre patrones reales de fase:
 
 | patrón de build | resultado |
 |---|---|
@@ -206,100 +220,50 @@ El sandbox corre como **uid 1001, sin `CAP_DAC_OVERRIDE`** (`id` dentro de bwrap
 | `sed -i` en la **raíz** del overlay | OK |
 | `mkdir /mnt/output` (patrón meson) en la **raíz** | OK |
 
-Y lo peor es **por qué** las dos últimas pasan: el upper root ya existe con modo 0755 y su modo gana
-sobre el del lower root. O sea que el fallo es **parcial y por profundidad** — funciona arriba y
-falla adentro.
-
-⇒ **Un spike sobre `zlib` (autotools que configura en la raíz) pasaría F0 en verde y el diseño
-reventaría después, en meson/cmake/cargo, que escriben en subdirectorios.** Es exactamente la forma
-del fallo *sellado ≠ arranca*: verde en el grafo, roto en la realidad.
-
-**Corrección propuesta.** La inmutabilidad la da el **mount**, no los bits de modo: a través de un
-overlay **es estructuralmente imposible** escribir el lower — R4 ya se cumple sin ayuda de `chmod`.
-Si además se quiere blindaje contra un proceso del *host*, protéjase el **directorio contenedor**
-(`work/sources-cas/`, para que nada pueda crear/renombrar/borrar dentro) y déjense los modos del
-árbol **tal como los trae upstream**. La detección de corrupción ya la cubre `blake3_arbol` (R10).
-
-## 8.3 🔴 IF-2 es inimplementable tal como está escrito
-
-«Un `touch` en `/src` **fuera del upper** ⇒ debe recibir `EROFS`» no es una noción coherente bajo
-overlayfs: *todo* lo escribible pasa por el upper vía copy-up. Un build que recibiera `EROFS` en
-`/src` sería un build roto, no un invariante que pasa. El canario D9 de harkaq es legítimo, pero el
-hecho negativo que hay que ganarse aquí es otro:
-
-> **IF-2 (corregido)** — Tras N builds concurrentes sobre la misma fuente, `blake3_arbol` del árbol
-> sellado sigue casando con el del marker. El canario deliberado: un build de test **escribe** en
-> `/src` (debe funcionar, vía copy-up) y el árbol sellado **sigue idéntico**. Si el hash se movió, el
-> montaje está mal (alguien montó el lower como bind RW). No se cree: se gana.
-
-Esto además hace IF-1 verificable **sin `fanotify`** (que exige `CAP_SYS_ADMIN` y complica el test en
-la granja): comparar el `blake3_arbol` antes y después es más barato y más fuerte.
-
-## 8.4 🟡 Falta la SEGUNDA carrera — y es el mejor argumento del diseño
-
-El SDD sólo nombra la carrera de `fetch` (uno borra mientras otro extrae). Hay una segunda, ya
-documentada en `scripts/build-farm.sh:82-99`, que el SDD no menciona:
-
-> *«dos recetas DISTINTAS que comparten una dep transitiva NO sellada (p.ej. gtk4 bajo libadwaita y
-> gtksourceview) la construyen en el MISMO `sources/<dep>-<sha>/output` ⇒ chocan»* —
-> `Some other Meson process is already using this build directory`, `Directory not empty`.
-
-Hoy se concilia con un **reintento serial** (Fase 1b de `build-farm.sh`) que existe *sólo* por esto.
-El diseño de este SDD **la mata también** (cada constructor tiene su propio `output/` en su upper), y
-conviene decirlo porque:
-
-1. Es la carrera que un lock por-árbol (opción A del ADR) **no** resuelve sin serializar.
-2. Permite **borrar la Fase 1b entera** en F3 — un entregable medible que hoy no está en el plan.
-
-Además, `.zwrap` no es un caso aislado de `perl`: **131 recetas lo escriben en el árbol de
-fuentes** *(medido)*, la mayoría de la cola KDE. El daño potencial es mayor de lo que sugiere el §0.
-
-## 8.5 🟡 Q1 ya está contestada por el código: la fase `patch` NUNCA estuvo en el sandbox
-
-`hammer-build/src/lib.rs` llama `fetch::apply_patches`, `ensure_cargo_workspace_isolation` y
-`fetch::vendor_cargo_deps` **en el host, antes de entrar a bwrap** (líneas 224, 248, 258). O sea:
-
-- §1.1 dice «la fase `patch` **migra** a la materialización». No migra: **ya está ahí**. Lo que este
-  SDD hace es *nombrar* como capa lo que hoy ocurre de facto y sin contrato.
-- **Q1 es discutible pero no es una decisión nueva** — es el statu quo desde el día 1, y el riesgo
-  que plantea ya se está corriendo hoy.
-- **Efecto colateral que sí hay que registrar:** la tabla de fases del [SDD 02 §4](02-build-lab.md)
-  dice que `patch` corre **en sandbox**. Está **desactualizada** y este SDD la propaga. Corregirla es
-  parte de F1.
-
-## 8.6 Precisiones menores
-
-- **§0, el nombre del árbol.** `<nombre>-<sha16>` sólo vale para tarballs
-  (`&sha256[..16]`, `fetch.rs:234`); las fuentes **git** usan el **commit completo**
-  (`fetch.rs:73`). Sin efecto en el diseño, pero el `fuente_id` debe cubrir los dos modos.
-- **R5 está incompleto.** `--overlay` exige `RWSRC` **y** `WORKDIR`, «un directorio vacío en el mismo
-  filesystem que RWSRC». El workspace es `work/ws/<uuid>/{upper,work}`, no un solo directorio.
-- **F3 tiene un consumidor no listado.** El watchdog de disco de `farm-worker-loop.sh:43` protege
-  árboles haciendo `pgrep -af bwrap | grep -oE 'work/sources/[^ ]+'` — está **acoplado al esquema de
-  rutas actual**. Con el modelo nuevo hay que reescribirlo (queda más simple y más seguro: lo sellado
-  es inmutable y cada `ws/<uuid>` tiene un solo dueño), no re-apuntarlo. Otros consumidores:
-  `reconstruir-corpus.sh:206` (purga) y `build-farm.sh` (Fase 1b, §8.4).
-- **D4 choca con un test existente.** `hammer-bootstrap` afirma explícitamente que `work/sources/`
-  **no debe viajar** en el builder rootfs («regenerable y pesado», `lib.rs:2659`). Si `sources-cas` se
-  hornea en la golden, hay que decidir si el builder lo hereda o no — y hay un test que fallará si
-  alguien lo incluye sin pensarlo.
-- **NG-4 y Q3 están en tensión.** Si `fuente_id` entra en `bootstrap.json` (Q3, «propuesta: sí»),
-  `sources-cas` deja de ser «caché podable, no procedencia» (NG-4) para volverse un eslabón citado del
-  log de transparencia. No es contradictorio —se puede citar un hash cuya materialización sea
-  regenerable— pero conviene decirlo, o la primera poda agresiva romperá una cita.
-
-## 8.7 Veredicto
-
-**El diseño es correcto y el mecanismo está probado.** Separar caché sellada de workspace efímero es
-la opción (C) del ADR 0012 con overlay en vez de copia, y resuelve las **dos** carreras — no sólo la
-que el §0 nombra. Antes de abrir F1:
-
-1. **Reescribir R1** (§8.2) — es la que rompe builds reales, y de forma parcial y silenciosa.
-2. **Reescribir IF-2** (§8.3) — hoy no se puede implementar y además hace innecesario el `fanotify`
-   de IF-1.
-3. **Añadir la carrera del directorio de build al §0** y **borrar la Fase 1b** como entregable de F3
-   (§8.4).
-4. **Corregir la tabla de fases del SDD 02** y cerrar Q1 como statu quo, no como decisión nueva
-   (§8.5).
-
-Con eso, el gate de F0 pasa a ser sólo IF-5 sobre `zlib`, y F2.5 sigue siendo el gate real del ADR.
+Las dos últimas pasan porque el upper root ya existe con modo 0755 y su modo gana sobre el del
+lower root ⇒ **el fallo es parcial y por profundidad**: funciona arriba y falla adentro. Un spike
+sobre `zlib` (autotools que configura en la raíz) habría pasado en verde y el diseño habría
+reventado después en meson/cmake/cargo. Es la forma del fallo *sellado ≠ arranca*. De ahí R1
+(inmutabilidad por mount, modos upstream) e IF-5 (una receta por build-system, no sólo zlib).
+
+## 8.2 La composición real con el sandbox actual funciona (origen de §1.2 y del gate de F0)
+
+Reproduciendo el orden de argumentos que `sandbox.rs` ya emite, más el grupo de `/src`:
+
+```sh
+bwrap --unshare-all \
+  --overlay-src .dev-fs/alpine        --tmp-overlay /            \
+  --overlay-src $S/lower  --overlay $S/upper $S/work  /src       \
+  --proc /proc --dev /dev -- /bin/sh -c '…'
+```
+```
+root = rootfs alpine: bin dev etc home lib media mnt musl256 opt proc root run sbi
+/src = fuente sellada: SOY-LA-FUENTE-SELLADA
+escritura en SUBDIR de /src: OK          ← /src/sub/output/build.ninja (el caso meson)
+raiz sigue escribible (tmp-overlay): OK
+lower intacto: marca.txt, sub/f.c        ← el árbol sellado no se tocó
+upper del /src: sub/output/build.ninja   ← el residuo quedó aislado
+```
+
+Cuatro hechos de una sola corrida: (1) el acumulador de `--overlay-src` **se consume por operación
+y no filtra** — el root sigue siendo el rootfs Alpine y `/src` la fuente sellada; (2) el
+`--tmp-overlay /` del root sigue funcionando junto al `/src`; (3) con modos upstream, **escribir en
+un subdirectorio de `/src` funciona** — exactamente el caso que §8.1 rompía; (4) el lower queda
+byte-intacto y el residuo aislado en el upper.
+
+⇒ El gate de *mecanismo* de F0 está verde. Lo que queda de F0 es IF-5 (bit-a-bit real).
+
+## 8.3 Referencias de código verificadas
+
+| Afirmación del SDD | Dónde | Estado |
+|---|---|---|
+| `<nombre>-<sha16>` (tarball) / `<nombre>-<commit>` (git) | `fetch.rs:234` / `fetch.rs:73` | ✅ |
+| fetch+patch+vendor corren en host, antes de bwrap | `lib.rs:224,248,258` | ✅ (cierra Q1) |
+| Límite de página del `lowerdir` y pre-merge `cp -al` | `sandbox.rs:347-357` | ✅ |
+| Carrera B y su reintento serial (Fase 1b) | `build-farm.sh:82-99` | ✅ |
+| Watchdog acoplado al esquema de rutas | `farm-worker-loop.sh:43` | ✅ |
+| Purga del árbol viejo | `reconstruir-corpus.sh:206` | ✅ |
+| `work/sources/` no viaja en el builder rootfs (test) | `hammer-bootstrap/src/lib.rs:2659` | ✅ (origen de D4) |
+| `.zwrap` escrito en el árbol de fuentes | 131 recetas *(medido)* | ✅ |
+| `--overlay RWSRC WORKDIR DEST` disponible | bubblewrap 0.11.2 | ✅ |
+| La tabla de fases dice "patch en sandbox" | `docs/02-build-lab.md` §4 | ⚠️ desactualizada — F1 |