# 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//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 (`/.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).