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…
harkaq: Q2 RESUELTA — el runtime base son 5 paths, y aparece una 3ª categoría
1#!/bin/sh
2# Q2 — ¿qué es el "runtime base" implícito? (SDD 16 §8)
3#
4# La pregunta de diseño que decide si la métrica de la Fase 2 mide deuda real de de-Alpinización
5# o mide ruido. Ninguna receta declara /bin/sh, la libc o /opt/zig, y ninguna debería. Pero si el
6# runtime base se define de más, la política concede Alpine entero y la evidencia no vale nada.
7#
8# Método: NO adivinarlo. Correr un build real con `política = clausura declarada` y dejar que
9# las denegaciones lo nombren. Lo que aparezca es, por definición, lo que todo build necesita y
10# nadie declara.
11#
12# Replica el sandbox real (`Sandbox::bwrap_args`): overlay del rootfs Alpine + las deps apiladas,
13# `--tmp-overlay /`, /proc, /dev, /tmp, zig en /opt/zig, fuentes en /src, salida en /out.
14set -eu
15cd "$(dirname "$0")/../.."
16
17BIN="${HARKAQ_BIN:-$HOME/.cache/harkaq}"
18ROOTFS=.dev-fs/alpine
19ZIG=.dev-fs/tools/zig
20
21# MODE=base → build SIN ninguna dep declarada. Por definición, TODA denegación es runtime base:
22# no hay clausura que pueda explicarla. Aísla la respuesta a Q2 sin juicio de valor.
23# MODE=zlib → el mismo build contra la dep zlib del store. Lo que sobre por encima del runtime
24# base ES la deuda de de-Alpinización, sin ambigüedad.
25MODE="${MODE:-zlib}"
26
27[ -d "$ROOTFS" ] || { echo "falta $ROOTFS"; exit 1; }
28[ -d "$ZIG" ] || { echo "falta $ZIG"; exit 1; }
29
30WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT
31mkdir -p "$WORK/src" "$WORK/out"
32
33if [ "$MODE" = base ]; then
34 DEPS=""
35 LDFLAGS=""
36 cat > "$WORK/src/hello.c" <<'EOF'
37#include <stdio.h>
38int main(void) { printf("hola\n"); return 0; }
39EOF
40else
41 DEPS=$(ls -d store/*-zlib | head -1)
42 LDFLAGS="-lz"
43 cat > "$WORK/src/hello.c" <<'EOF'
44#include <zlib.h>
45#include <stdio.h>
46int main(void) { printf("zlib %s\n", zlibVersion()); return 0; }
47EOF
48fi
49
50# La clausura DECLARADA (D1) + las superficies que el sandbox define por contrato, no la receta:
51# /src (fuentes), /out (DESTDIR), /tmp (HOME). Nada de Alpine.
52POLICY="$WORK/policy"
53# Sin deps (MODE=base) la clausura declarada es VACÍA: la política son sólo las
54# superficies de contrato del sandbox. Todo lo demás que el build toque, se ve.
55: > "$POLICY"
56[ -n "$DEPS" ] && scripts/harkaq/harkaq-policy.sh $DEPS > "$POLICY"
57{
58 echo "rw /src"
59 echo "rw /out"
60 echo "rw /tmp"
61 echo "ro /opt/zig"
62 # BASE_EXTRA: lo que las corridas previas demostraron que hace falta. Empieza vacío y se
63 # llena SÓLO con lo que el kernel denunció — nunca por corazonada. Cada línea de acá es una
64 # respuesta medida a Q2, no una conjetura.
65 [ -n "${BASE_EXTRA:-}" ] && printf '%s\n' "$BASE_EXTRA"
66} >> "$POLICY"
67
68echo "── MODE=$MODE deps=[${DEPS:-ninguna}]" >&2
69echo "── política: $(grep -c '^ro ' "$POLICY") ro, $(grep -c '^list ' "$POLICY") list, $(grep -c '^rw ' "$POLICY") rw" >&2
70
71NONCE="q2-$$"
72# El canario va en la RAÍZ, no en /tmp: la política concede `rw /tmp` (es el HOME del build), así
73# que un canario ahí caería DENTRO de la clausura, se leería sin problema y no generaría ninguna
74# denegación ⇒ SinEvidencia eterno. El canario tiene que vivir donde la política NO alcanza, por
75# construcción. En el sandbox real la raíz es escribible (`--tmp-overlay /`) y bwrap puede crear
76# ahí el punto de montaje. (Lo cazó el propio D9: el lector se negó a certificar.)
77CANARY="/harkaq-canary-$NONCE"
78CSRC=$(mktemp); echo canario > "$CSRC"
79FIFO=$(mktemp -u); mkfifo "$FIFO"
80VERDICT=$(mktemp)
81trap 'rm -rf "$WORK" "$CSRC" "$FIFO" "$VERDICT"' EXIT
82
83"$BIN/harkaq-audit" --canary "$CANARY" --timeout "${HARKAQ_TIMEOUT:-60}" --ready-fd 3 \
84 3>"$FIFO" >"$VERDICT" &
85AUDIT_PID=$!
86read -r _ < "$FIFO" || true
87
88# El probe del canario usa SÓLO builtins del shell: `read` + redirección. Con `cat` dependía de
89# /bin/cat (otro applet de busybox) y, si no estaba en la clausura, el probe moría sin probar
90# nada — el canario tiene que ser lo más barato e independiente del sistema.
91CMD="read _ < $CANARY || true; cd /src && zig cc -mcpu=baseline hello.c $LDFLAGS -o /out/hello && echo BUILD-OK"
92
93set +e
94bwrap --overlay-src "$ROOTFS" $(for d in $DEPS; do printf -- "--overlay-src %s " "$d"; done) --tmp-overlay / \
95 --proc /proc --dev /dev --tmpfs /tmp \
96 --ro-bind "$ZIG" /opt/zig \
97 --bind "$WORK/src" /src --bind "$WORK/out" /out \
98 --ro-bind "$CSRC" "$CANARY" \
99 --ro-bind "$BIN/harkaq-exec" /tmp/harkaq-exec \
100 --ro-bind "$POLICY" /tmp/harkaq.policy \
101 --chdir /src --unshare-all --die-with-parent \
102 --setenv PATH /opt/zig:/usr/bin:/bin \
103 --setenv HOME /tmp \
104 /tmp/harkaq-exec --policy /tmp/harkaq.policy -- /bin/sh -c "$CMD"
105RC=$?
106set -e
107
108sleep 2
109kill -TERM "$AUDIT_PID" 2>/dev/null || true
110wait "$AUDIT_PID" 2>/dev/null || true
111
112echo "── build rc=$RC" >&2
113echo "── denegaciones (= candidatos a runtime base):" >&2
114python3 -c '
115import json,sys
116v=json.load(open(sys.argv[1]))
117print(" estado:", v["estado"], "| canario:", v["canario_visto"], "| contador kernel:", v["contador_kernel"])
118for d in v["denials"]:
119 print(" ", d["blockers"], d["path"])
120if not v["denials"]: print(" (ninguna)")
121' "$VERDICT"