# Plan — qué tomar de FreeBSD

**Fecha:** 2026-07-16 · **Origen:** análisis de META MODE/filemon, Capsicum/casper y
libarchive contra el estado real de hammer (harkaq Fase 2, cierres §2/§3, catálogo Etapa G).

**Regla del análisis:** de otro kernel se toma *diseño*, no código. La única pieza de código
importable es libarchive (como receta del catálogo, igual que cualquier otra). Todo lo demás
es prior art que valida o corrige decisiones ya tomadas.

---

## 1. filemon / META MODE → la traza POSITIVA que a harkaq le falta

**Qué tiene FreeBSD.** `filemon(4)` registra en el kernel cada acceso a fichero durante un
build; `bmake` en META MODE escribe un `.meta` por target con *todo lo que el build tocó*,
normalizado. Con eso hacen auto-descubrimiento de dependencias y detección de builds impuros:
si la traza cambia, el target se reconstruye. Llevan ~12 años iterando qué normalizar (orden,
tmpdirs, accesos que no importan).

**Qué tiene hammer hoy.** harkaq mide sólo evidencia **negativa**: denegaciones Landlock bajo
política=clausura (`harkaq-audit.c` emite JSON `{path, motivo}`, `harkaq-verdict.py` clasifica
esperada/deuda). Lo que NO mide: **qué parte de la clausura concedida se usó de verdad**.

**La brecha que eso deja.** harkaq detecta *sub*-declaración (deuda) pero no
*sobre*-declaración: una dep declarada y jamás tocada es invisible, engorda la clausura, y esa
grasa se hereda directo a la política runtime derivada del cierre §3 (política más ancha de lo
necesario = evidencia más débil). La traza positiva es el dual exacto del veredicto actual.

**Tareas:**

- **T1.1 — sonda de uso positivo.** Un `harkaq-trace` que emita la lista de paths *abiertos*
  por fase dentro del sandbox. Mecanismo candidato: fanotify — que ya es frente delegado por
  arje (journal fanotify, ver handoff) ⇒ reutilizar ese trabajo, no abrir mecanismo nuevo.
  Alternativa si fanotify dentro del userns del sandbox se complica: eBPF sobre `openat`
  (BPF_SYSCALL ya es explícito en los kernels propios, commit 2b7c701).
- **T1.2 — normalización estilo `.meta`.** Orden estable, dedupe, paths relativos a la jaula,
  filtrar tmpdirs / `/out` / `/src`. Las reglas de ruido viven junto a la política — la misma
  fuente de verdad que los `# expect` (D3: no se calla, se clasifica; el ruido se declara,
  no se pierde). Aquí es donde la experiencia de META MODE ahorra iteraciones: readdir sin
  orden garantizado y accesos especulativos del compilador son las dos fuentes de ruido que
  ellos ya catalogaron.
- **T1.3 — sellar la traza.** El `b3:` de la traza normalizada viaja junto al `of_tree` del
  artefacto. Uso inmediato para **why-differs (cierre §2, top-2)**: dos builders que divergen
  en hash se comparan primero por traza — diff de dos listas de texto ANTES que diff
  estructural de binarios. El descenso por rangos de `reconcile`/`format` aplica igual.
- **T1.4 — `harkaq-suggest --poda`.** Con traza positiva, sugerir *remover* deps
  declaradas-no-usadas: el dual del suggest actual (que sólo sugiere añadir). Cierra el bucle
  en ambas direcciones: la clausura converge a exactamente-lo-usado.

**Riesgos.** Coste de fanotify en builds grandes (medir primero); falsos "no usados" por
accesos condicionales entre corridas (mitigación: la poda sugiere, nunca aplica sola — mismo
contrato que suggest). Piloto: 3 recetas ya instrumentadas (zlib, tar, brotli), en el worker
(el laptop no compila C).

**PILOTO MEDIDO (2026-07-17, worker, builds reales de zlib-ng y dwarves):**

1. **Ceguera-overlay — el hallazgo que corrige el diseño.** `FAN_MARK_FILESYSTEM` sobre el
   fs del host (donde vive el store) NO ve los reads que el build hace a través del
   `--tmp-overlay` del sandbox: el superbloque del overlay es OTRO, y overlayfs abre los
   lower por dentro sin fsnotify en el sb de abajo. Medido: build completo de zlib-ng con
   traza sobre el store = **0 eventos**; los binds sí aparecen (206 eventos de `/src` vía
   `work/sources`, mismo sb) e incluso los reads host-side de hammer (`recipes/*.toml` al
   resolver deps — la traza ya sirve para auditar el hub).
2. **La salida, validada**: marcar `--fs /proc/<pid-bwrap>/root` — el magic-link cruza al
   mount-ns y la marca cae sobre el SB DEL OVERLAY. Test sintético en el worker (overlay en
   mntns aparte, lector externo): la lectura del lower a través del overlay **aparece** en
   la traza. Bonus: los paths salen EN EL NAMESPACE DEL SANDBOX (`/usr/include/zlib.h`) —
   el mismo idioma de la política harkaq, sin traducción host↔jaula. Cero código nuevo:
   `harkaq-trace --fs /proc/$PID/root --prefix /usr`.
3. ~~Pendiente de orquestación~~ **HECHO en forma piloto**: `harkaq-trace-build.sh` envuelve
   un `hammer build`, engancha un tracer a cada bwrap que aparece (un bwrap por fase = una
   traza por fase, la granularidad del veredicto) y emite canónica + sello + tabla
   declaradas-vs-usadas. La integración sin carrera (lanzar el tracer ANTES del exec, no
   por polling) sigue siendo del harness — coordinar con Opus.

**PILOTO END-TO-END (2026-07-17, worker, `hammer build` real de zlib en store desechable
con deps hardlinkeadas — cero contaminación):**

- **El build trazado SELLÓ y el hash REPRODUJO bit a bit un artefacto ya sellado del store
  principal** (`b3:2623a403…`): la traza no perturba el build y un store fresco reproduce —
  mini-consenso de regalo.
- **La señal de poda (T1.4) existe y es filosa**: de la clausura concedida, el build usó
  `make` **1 de 8** ficheros y `busybox` **1 de 2**. Por-fichero, no por-paquete — la misma
  granularidad que la política.
- **Taxonomía del ruido medida** (3520 paths únicos crudos): 84 de `/cache` (cache zig
  interna = estado derivado del build, regla nueva en el normalizador), ~3.4k del stdlib de
  zig (`/lib/compiler_rt/*.zig` — toolchain, no clausura de receta) y los productos del
  propio build (`example.o`, tmpfiles borrados del upper). Tras las reglas, la clausura
  real de zlib son un puñado de paths exactos. META MODE otra vez: **las reglas de
  normalización SON el producto**.
- Caveats anotados: ventana ciega del polling (~decenas de ms por fase), helpers de bwrap
  duplican marcas (la dedupe lo absorbe), y el sello del piloto es sha256 PROVISIONAL (el
  real debe ser b3, espacio de nombres del store). Gotcha operativo: `--store` ajeno a la
  raíz del repo rompe la resolución del rootfs (`<padre-del-store>/.dev-fs`).

---

## 2. Capsicum / casper → la taxonomía del cierre §3 (política runtime)

**La lección negativa.** Capsicum es más puro que Landlock (capabilities sobre fds,
`cap_enter()`), y aun así FreeBSD terminó necesitando `casper`: demonios auxiliares porque
hay una clase de necesidades — DNS, certificados TLS, syslog, random temprano, locale — que
son *servicios del mundo*, no paths, y no caben en ninguna jaula por-fichero.

**Dónde muerde en hammer.** En build: en ninguna parte — los builds son offline por
`--unshare-all`, la clase casper es vacía por construcción. En **runtime** (cierre §3: derivar
la política Landlock del binario instalado desde la clausura medida en build): la clausura de
build **no contiene** esa clase ⇒ derivar a ciegas produce binarios que revientan en el primer
`getaddrinfo`. Saber esto *antes* de arrancar Fase 3 es exactamente el valor del prior art.

**Tareas (papel, cero código hoy):**

- **T2.1 — sección "servicios del mundo" en SDD 16 (Fase 3).** La política runtime = clausura
  de build **+ clases de servicio declaradas** (`dns`, `tls-certs`, `random`, `locale`,
  `syslog`). Se declara por clase con nombre, nunca se ensancha en silencio — D3 aplica en
  runtime igual que en build.
- **T2.2 — mapa clase→paths para musl.** Ventaja estructural de hammer: musl no tiene
  nsswitch ni dlopen de módulos NSS — `dns` = `/etc/resolv.conf` + socket UDP; `tls-certs` =
  `/etc/ssl/certs`; `random` = `getrandom(2)` (ni path necesita). El mapa completo cabe en
  media página; en glibc esto era intratable y es la mitad de por qué casper existe.
- **T2.3 — la clase viaja en la ConcesionCapacidad.** `(blake3(binario), permisos)` gana un
  campo de clases de servicio; `arje-absorb --attest-from` ya integra concesiones (PLAN-ATESTACION
  §B.3) ⇒ sin formato nuevo.

---

## 3. libarchive / bsdtar → receta + extractor de referencia

El único export de código real de FreeBSD que todo el mundo usa.

- **T3.1 — `recipes/libarchive.toml`.** Receta C ⇒ worker. Nombre nuevo en el catálogo, sin
  riesgo de colisión homónima (el gotcha de promote no aplica). Deps mínimas: zlib (xz/zstd
  opcionales según features que se activen).
- **T3.2 — verificación cruzada de formato.** Donde hammer serializa árboles (pack/export),
  extraer con bsdtar y comparar el árbol resultante contra el extractor propio = **consenso de
  implementación aplicado al FORMATO** — mismo espíritu que el consenso de reconstrucción
  (cierre §1) aplicado a los bytes del contenedor, no al build. Barato: un script de barrido
  sobre N artefactos sellados.
- **T3.3 (opcional) — bsdtar como dep de build** para tarballs exóticos donde busybox tar se
  queda corto (hoy: ninguno conocido; tener la receta lo vuelve una línea en `[deps]`).

---

## Lo que NO se toma (para no re-litigarlo)

- **Ports como corpus de recetas** — patches y Makefiles asumen BSD userland; Alpine+nixpkgs
  (Etapa G) dominan estrictamente para musl-Linux.
- **FreeBSD como builder de consenso multi-kernel** — sin namespaces/bwrap/Landlock allá; el
  consenso multi-kernel barato ya existe (kernel propio vs genérico).
- **bectl / ZFS boot environments** — `hammer boot menu` + `activate --from-select` ya cubren
  el concepto; como mucho, robar UX de nombres/rollback más adelante.
- **bmake** — el mundo Linux necesita gmake; no reduce la deuda declarable de `/usr/bin/make`.

## Orden propuesto

1. **T2.1–T2.2** — papel, una tarde, desbloquea el diseño de Fase 3 antes de escribir código.
2. **T1.1–T1.3** — piloto en 3 recetas en el worker; decide si fanotify o eBPF con datos.
3. **T3.1** — receta a la próxima tanda del worker.
4. **T1.4 y T3.2** — sólo después de validar los pilotos (la poda sin traza confiable es ruido).
