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 1430Shell 145Markdown 87Rust 852973 Sutichaykuna

go/cgo: GOTMPDIR fuera del módulo — era la única causa del no-determinismo de gocryptfs

`gocryptfs` era el último no-determinismo vigente del corpus. Ya REPRODUCE.

── La medición, que primero hice mal ──────────────────────────────────────────────────
Comparé los dos ficheros de `store/.divergen/` y salían 1.874.136 bytes distintos, con
`.text` entre ellos y funciones de OpenSSL cambiando de AVX2 a AVX-512. Era el par
EQUIVOCADO: `.divergen/<D>` es el artefacto VIEJO archivado, no una reconstrucción. El
par que define el veredicto es `<D>.r1` (1ª reconstrucción) contra el que queda en el
store (2ª).

Comparado bien, el no-determinismo son **7 bytes, todos en `.debug_str`**: los dígitos
de `go-build598798091` contra `go-build270591055`.

(Lo de AVX no era ruido: era DERIVA real contra el artefacto viejo, sellado antes de que
467a87cd pusiera `-mcpu=baseline` en el CC de cgo. Se trata aparte, abajo.)

── La causa ───────────────────────────────────────────────────────────────────────────
`-trimpath` reescribe rutas, pero cuál gana depende de dónde esté el work dir de Go:
dentro del módulo gana la reescritura del módulo y el componente ALEATORIO sobrevive;
fuera, Go reescribe el work dir entero a `/tmp/go-build` y el número desaparece. El
driver ponía `GOTMPDIR=/src/.gotmp`, que es justo dentro del módulo.

Con CGO off da igual —no hay fuentes C generadas, ninguna ruta del work dir entra en el
DWARF— y por eso las 15 recetas Go puras verificadas reproducen. Con cgo sí entra. Por
eso el cambio va acotado a `cgo = true`: las otras 358 no lo necesitan.

── Medido en los dos sentidos, con un módulo cgo de juguete y cachés limpias ─────────
  · GOTMPDIR dentro  ⇒ `go-build3806217352` en el binario; dos builds DIFIEREN
  · GOTMPDIR fuera   ⇒ ninguna cadena `go-build`; dos builds IDÉNTICOS
Y sobre gocryptfs de verdad: DERIVA primero (las dos reconstrucciones ya coincidían
entre sí y el guardado era el viejo), REPRODUCE en la pasada siguiente. En el binario
sólo queda `/tmp/go-build`, el marcador constante de trimpath.

── Va a `/cache` y no a `/tmp`, y se COMPRUEBA que exista ─────────────────────────────
El sandbox monta `--tmpfs /tmp` (~½ RAM) y un proyecto Go grande lo desborda: ésa era la
razón de poner esto en `/src`. `/cache` es un bind RW a disco (el de la caché de zig),
así que conserva el disco y queda fuera del módulo.

Y no se da por hecho que esté: con `--tmp-overlay /` la raíz es escribible, así que un
`mkdir -p /cache/gotmp` a ciegas TRIUNFARÍA creando el directorio en la capa tmpfs —en
RAM, el desbordamiento que se quería evitar, y en silencio. Si el bind falta, el build
muere diciéndolo.

1728a77a3ae6e102ec913322325c55892da95bc9 · ruwaq Sergio · (1 killa ñawpaq) · tayta 229ecff90 · kay commitpi sach'ata qhaway

Semántico sayk'uy · 1

crates/takana-build/src/lib.rs

2 qillqakuna · +39 −2

diff --git a/crates/takana-build/src/lib.rs b/crates/takana-build/src/lib.rs
index 8802c535..87326911 100644
--- a/crates/takana-build/src/lib.rs
+++ b/crates/takana-build/src/lib.rs
@@ -984,10 +984,44 @@ fn resolve_phases(recipe: &Recipe, src: &Path) -> takana_core::Result<Phases> {
                 } else {
                     ("CGO_ENABLED=0", "-ldflags=-buildid=")
                 };
+                // ── GOTMPDIR: DENTRO del módulo con cgo apagado, FUERA con cgo encendido ──────
+                // `-trimpath` reescribe rutas, pero cuál gana depende de dónde esté el work dir:
+                //   · si cae DENTRO del módulo, gana la reescritura del módulo y el componente
+                //     ALEATORIO `go-build<N>` SOBREVIVE;
+                //   · si cae FUERA, Go reescribe el work dir entero a `/tmp/go-build` y el número
+                //     desaparece.
+                // Con CGO off da igual: no hay fuentes C generadas, así que ninguna ruta del work
+                // dir entra en el DWARF — las 15 recetas Go puras verificadas REPRODUCEN. Con cgo
+                // sí entra, y ésa era la ÚNICA causa del no-determinismo de `gocryptfs`: dos
+                // reconstrucciones difieren en SIETE bytes, todos en `.debug_str`, y son los
+                // dígitos de `go-build598798091` contra `go-build270591055`.
+                //
+                // Medido con un módulo cgo de juguete, cachés limpias y en los dos sentidos
+                // (2026-09-14):
+                //   · GOTMPDIR dentro  ⇒ `go-build3806217352` aparece en el binario; dos builds DIFIEREN
+                //   · GOTMPDIR fuera   ⇒ ninguna cadena `go-build`; dos builds IDÉNTICOS
+                //
+                // ⚠ Va a `/cache`, NO a `/tmp`: el sandbox monta `--tmpfs /tmp` (~½ RAM) y un
+                // proyecto Go grande lo desborda con sus objetos — es la razón por la que esto
+                // estaba en `/src` de entrada. `/cache` es un bind RW a disco (el mismo que usa la
+                // caché de zig), así que conserva la propiedad de estar en disco Y queda fuera del
+                // módulo.
+                //
+                // ⚠ Y se COMPRUEBA que `/cache` exista en vez de dar por hecho que está: con
+                // `--tmp-overlay /` la raíz es escribible, así que un `mkdir -p /cache/gotmp` a
+                // ciegas TRIUNFARÍA creando el directorio en la capa tmpfs — o sea en RAM, que es
+                // exactamente el desbordamiento que se quería evitar, y en silencio. Si no está,
+                // el build muere diciendo por qué.
+                let gotmp_setup = if recipe.build.cgo {
+                    "[ -d /cache ] || { echo '!! falta el bind /cache: GOTMPDIR caería en la capa                      tmpfs (RAM) y un proyecto Go grande la desborda' >&2; exit 1; };                      mkdir -p /cache/gotmp"
+                } else {
+                    "mkdir -p /src/.gotmp"
+                };
+                let gotmpdir = if recipe.build.cgo { "/cache/gotmp" } else { "/src/.gotmp" };
                 Some(format!(
-                    "{cd_prefix}export GOCACHE=/src/.gocache GOTMPDIR=/src/.gotmp GOPATH=/tmp/gopath \
+                    "{cd_prefix}export GOCACHE=/src/.gocache GOTMPDIR={gotmpdir} GOPATH=/tmp/gopath \
                      GOTOOLCHAIN=local {cgo_env} GOFLAGS=-mod=vendor GOPROXY=off \
-                     GOBIN=/out/usr/bin; mkdir -p /src/.gotmp; \
+                     GOBIN=/out/usr/bin; {gotmp_setup}; \
                      go install -trimpath {ldflags} {pkg}"
                 ))
             }
diff --git a/docs/state/repro-verificado.tsv b/docs/state/repro-verificado.tsv
index 188f9b19..1dcbd0ab 100644
--- a/docs/state/repro-verificado.tsv
+++ b/docs/state/repro-verificado.tsv
@@ -236,3 +236,6 @@ libplasma	73ec5fb279ea44e6bcfdf4c58d8703ed1307ae7985e56ce55a5da078ea098b7e	2026-
 syntax-highlighting	3db73c19bdbddcb046a80d11ec112bc7dc60134eeec75b26e907cc756d25d081	2026-09-14	reproduce
 powerdevil	70ea4b373af5ac3c22cf6adde9ca45153925724ef523000bc1e0e2338e282d15	2026-09-14	reproduce
 ktexteditor	f13baeb75249c7f768fdd1b4584b5077779e6806ffcb295a6554f50d3f31d0ec	2026-09-14	reproduce
+gocryptfs	0caf96352eae21ed51b32be9ead8d5525bb07e8aa124eb33c4b9588f1a02421d	2026-09-14	NO-DETERMINISMO
+gocryptfs	0caf96352eae21ed51b32be9ead8d5525bb07e8aa124eb33c4b9588f1a02421d	2026-09-14	reproduce (tras poner al día un artefacto derivado)
+gocryptfs	0caf96352eae21ed51b32be9ead8d5525bb07e8aa124eb33c4b9588f1a02421d	2026-09-14	reproduce