HOMELAB-2682: fix(sbx-gits): proof check anchored to the smoke committed at the head #859

Merged
pi-landing merged 13 commits from plane/HOMELAB-2682-check-anchor into live 2026-11-06 03:39:14 +00:00
Collaborator

Qué arregla (el defecto, medido en vivo)

check <head> comparaba el script_sha256 de cada línea del ledger con el smoke montado en
/run/sbx-tools/gits-sbx-emu-smoke.sh. Las claves del ConfigMap se refrescan EN CALIENTE (el
kubelet reescribe la clave ya proyectada, sin recrear el pod), así que cada refresco ponía rojo
todo lo anterior
.

Forma real del fallo, en sbx-gits-0, head 26cd6d87fca5aaca804243b4f5b5224d9d482d52: dos líneas
OK (39e03a354702…, la route suite de persona; e74156e33ce4…, la suite de enrol) y check las
rechazaba las dos porque el montado ya hasheaba otra cosa (refresh posterior a #847). GITS rung 2
está bloqueado por esto.

El arreglo: el ancla es el COMMIT, no el fichero montado

  • Identidad canónica de un head H = el blob commiteado en H:
    git cat-file blob <H>:core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh. El sha256 del
    blob y el de sha256sum del fichero son el mismo, así que no hay que copiar nada.
  • check exige un árbol git (--tree, EMU_PROOF_TREE o el directorio actual — la compuerta
    siempre está dentro del checkout) y falla cerrado (rc 2, con el motivo) cuando no hay árbol, o
    cuando el commit pedido no está en ese checkout. Nunca cae al montado.
  • Un head pasa SOLO por una línea OK via=run cuyo script_sha256 sea el ancla de ese head —
    más los requisitos que ya había (tree_clean/tree_clean_after true, rc 0, tests_failed 0,
    tests_run>0, binding de la captura).
  • El invariante del hipervisor sigue en pie: una línea de otra suite (sha distinto del ancla)
    es EVIDENCIA: se le exige la integridad completa (cadena + binding de captura + clase +
    resultados) y nunca es suficiente y nunca es motivo de fallo del head. Una suite sustituta
    no desbloquea el merge.
  • Una línea FAIL conserva toda su fuerza: sigue siendo motivo de rechazo de su línea, nunca se
    degrada a "evidencia".
  • El montado queda como aviso SECUNDARIO (--script): si difiere del commit se imprime
    SECUNDARIO el fichero MONTADO … va por delante o por detrás del commit. NO es la compuerta.
    Nunca es criterio.
  • Salida del veredicto: PROOF: OK … ancla=smoke@commit …,
    y las líneas-extra como PROOF evidencia (no suficiente): ….

Forma de la línea (compatibilidad byte a byte)

suite_relpath es OPCIONAL y sigue la misma regla que class y results_sha256 (#853): va
dentro del texto hasheado, al final, y sólo si está. Una línea que no lo lleva recalcula
byte a byte el mismo sha256 que antes — las 21 líneas del ledger vivo siguen validando. Hay
caso de harness que compara el chain_text de origin/live con el de esta rama (18 args y 20 args,
idénticos) y que comprueba que suite_relpath sólo añade la línea 21.

Honestidad con los heads viejos

Las líneas grabadas mientras el smoke montado iba legítimamente por detrás del commit se leen ahora
contra el blob commiteado y necesitan UNA re-corrida en ese head. Es la dirección conservadora.
Medido en el pod con esta rama (read-only, sobre el ledger real):

PROOF: FAIL (26cd6d87fca5aaca804243b4f5b5224d9d482d52)
  - este head NO tiene un smoke canonico COMMITEADO en
    core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh: la prueba del emulador NO puede pasar
    este head (no hay blob que anclar y NO se admite una suite sustituta, ni commiteada; B2)
  - evidencia (no suficiente): linea 20 … suite_sha256=39e03a354702...
  - evidencia (no suficiente): linea 21 … suite_sha256=e74156e33ce4...

Las dos líneas ya no se tiran por "suite distinta": se leen como evidencia. Pero ese head
no puede quedar canónico con una re-corrida
, porque el árbol de la app no lleva el smoke
commiteado: necesita (a) o (b) de la decisión 1. Un head que sí lleve el smoke commiteado queda
canónico con una sola corrida fresca.

🔴 Dos cosas que necesitan tu decisión (no las he decidido yo a la callada)

1. El árbol de GITS no lleva la fuente de la herramienta. El literal del brief
(git cat-file blob <H>:core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh) sólo resuelve en
un árbol del repo de la herramienta. El checkout de GITS es Homelab/ghostty-in-the-shell
(/home/ld/work/epics/gits-e3/wt/j1, head 26cd6d87), y no tiene core/manifests/… en ningún
commit
: ni el smoke ni la herramienta viven ahí (el smoke sólo existe en el ConfigMap). Comprobado:
git -C … ls-tree -r HEAD | grep sbx-emu → vacío.

No hay respaldo. Una versión anterior de esta rama ofrecía una segunda forma de ancla (la
suite COMMITEADA en ese head, nombrada en la salida como ancla=<familia>@commit:<ruta>) cuando el
árbol no llevaba la herramienta. Eso se quitó (hallazgo B2 de la revisión: en un árbol sin el
smoke, cualquier script commiteado se volvía el ancla, así que una suite sustituta trivial
desbloqueaba el merge). Hoy ese head es rechazo, rc 1, sin ancla:

este head NO tiene un smoke canonico COMMITEADO en core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh:
la prueba del emulador NO puede pasar este head (no hay blob que anclar y NO se admite una suite
sustituta, ni commiteada; B2)

Así que el gate de GITS no queda "en verde con otro ancla": queda cerrado hasta que exista el
literal
. Si quieres el literal en TODOS los árboles, hay dos caminos y los dos son tuyos, no
míos: (a) commitear el smoke (o un pin de su sha256) dentro del repo de la app, o (b) que la
compuerta corra check con el árbol del pipeline, no con el de la app. Sin (a) o (b), GITS rung 2
no puede pasar la compuerta
— que es el estado honesto, no un falso verde.

2. FAIL no veta el head entero, veta su línea. El brief dice "a FAIL line on the head still
fails the head (keep strict)". Lo he implementado como: una línea FAIL nunca se degrada a
evidencia y siempre sale como motivo de rechazo — pero no invalida un OK canónico posterior
del mismo head. El motivo es el ledger vivo: los heads f068805b, 1a4e2b12 y 67034b5d tienen
líneas FAIL y OK (re-corridas verdes tras un fallo de entorno). Con la lectura literal, esos
heads quedarían imposibles de aprobar para siempre, incluso después de una corrida verde — el
"re-corré sobre el head" que este PR documenta no podría funcionar nunca. Si prefieres la lectura
literal, es un cambio de tres líneas y lo hago.

Verificación

Harness completo (bash scripts/gits-sbx-epic-test.sh, SOLO, sin concurrencia, en un worktree
pinneado a b9ce8fc7): 653 comprobaciones, 648 PASS, 5 FAIL, 6 SKIP.

sección resultado
22[R176b] (el ledger: 22b entera) 90 PASS / 0 FAIL
24[HOMELAB-2682] (clase + resultados por corrida) 25 PASS / 0 FAIL
25[HOMELAB-2682] (nueva: el ancla) 32 PASS / 0 FAIL
T7[diff] (guarda del diff clavado de sbx-gits) 3 PASS / 0 FAIL
17[R159] 29 PASS / 0 FAIL

Los 5 rojos son los cinco rojos conocidos y de otro ticket — probe uses an EMPTY pre-existing --dir, manifest[2]/manifest[3]/manifest[3b] y T7 emulador dentro del pod
(los de #851; aquí sólo se confirma que siguen siendo exactamente esos cinco). El caso de tiempos
que la medición anterior contaba como sexto no se reprodujo en ninguna de las dos corridas
solas de esta ronda. T7[diff] — la guarda del diff clavado (M README.md +
M tools/gits-sbx-emu-proof.sh) con sus dos mutaciones rojas — y 17[R159] están verdes.

Casos nuevos de §25 (todos ejecutan la herramienta contra repos de mentira, ningún rojo "de
confianza"):

  1. (a) la línea del smoke commiteado en el head → rc 0, ancla=smoke@commit, y la línea
    lleva suite_relpath.
  2. (a2) el defecto, reproducido y cerrado: el fichero montado se refresca (otro contenido,
    lo que hace el kubelet) y el head viejo sigue en rc 0, con el desfase como SECUNDARIO … NO es la compuerta.
  3. (b) contra un head cuyo blob es otro, la línea no vale (no hay ninguna linea CANONICA)
    — aunque el montado coincida con ese head: si se comparara con el montado, pasaría.
  4. (c)/(c2) una corrida real de una suite que no está en el commit no completa la
    compuerta (sale como evidencia (no suficiente)) y no hunde a un head que sí tiene su línea
    canónica.
  5. (d) una línea FAIL del smoke canónico → rc 1 (result=FAIL … rc=5 … tests_failed=1).
  6. (e)/(e2) sin árbol git → rc 2; head que no es commit de ese checkout → rc 2
    (fail-closed: no se cae al montado).
  7. (f)/(f2) árbol sin la herramienta (tipo app): rc 1 — la compuerta NO puede pasar ese
    head
    (NO tiene un smoke canonico COMMITEADO … NO se admite una suite sustituta, ni commiteada; B2). La corrida de una suite alterna commiteada en ese árbol sale como
    evidencia (no suficiente), y una suite que corrió pero no está en el commit tampoco vale.
  8. (g) suite_relpath opcional: chain_text con 18 y con 20 args es byte a byte el de
    origin/live; el campo sólo añade la línea 21.

Los casos que añadieron las rondas 2 y 3 (todos dentro de §25, todos verdes):

  • (f3)/(f4)/(f4b) (B1) — un tree, un gitlink, y un gitlink cuyo commit no está en el
    object DB: rc 2 nombrando el tipo real, nunca el sha256 de la cadena vacía.
  • (f6)/(f7) (B3) — un smoke de cero bytes commiteado, y un ancla cuyo objeto se borró
    del object DB: rc 1 diciendo los bytes declarados contra los leídos.
  • (f9) (B4) — el task= del veredicto es el de la línea que pasa, no el de la última del
    ledger (línea 1 smoke, línea 2 suite de route → task=smoke:… linea=1).
  • (f10) (B5) — una corrida FAIL del propio smoke canónico se etiqueta como tal
    (aviso el smoke CANONICO TAMBIEN CORRIO EN ROJO), no como "otra suite".
  • (f11) (C1) — sustituido el objeto del oid del ancla por otro contenido del mismo tamaño:
    rc 1 con los dos oids.
  • (f12) (B6/B7) — git replace (commit, árbol, blob y encadenado) + una corrida real de la
    suite de F sobre el árbol parado en H: rc 1 nombrando el blob real del head; y (f12b2)
    lo mismo más core.useReplaceRefs true en el config del checkout, también rc 1.
  • (f13) — el tests_run del veredicto es el de esta corrida, no el del directorio de
    resultados acumulado.

Además: bash -n de la herramienta y del smoke, y --help sin stderr (sin backticks sin escapar).

Lo que NO se ha tocado: kustomization.yaml, statefulset.yaml (el guarda del diff clavado
queda verde con el conjunto re-medido a los DOS ficheros de esta rama: README.md y
tools/gits-sbx-emu-proof.sh; el smoke no cambia aquí, su --class ya está en live con #853),
ni ningún manifiesto, Service o NetworkPolicy.

No mergear todavía: siguen dos asientos DeepSeek.

## Qué arregla (el defecto, medido en vivo) `check <head>` comparaba el `script_sha256` de **cada** línea del ledger con el smoke **montado** en `/run/sbx-tools/gits-sbx-emu-smoke.sh`. Las claves del ConfigMap se refrescan **EN CALIENTE** (el kubelet reescribe la clave ya proyectada, sin recrear el pod), así que **cada refresco ponía rojo todo lo anterior**. Forma real del fallo, en `sbx-gits-0`, head `26cd6d87fca5aaca804243b4f5b5224d9d482d52`: dos líneas `OK` (`39e03a354702…`, la route suite de persona; `e74156e33ce4…`, la suite de enrol) y `check` las rechazaba **las dos** porque el montado ya hasheaba otra cosa (refresh posterior a #847). GITS rung 2 está bloqueado por esto. ## El arreglo: el ancla es el COMMIT, no el fichero montado - **Identidad canónica de un head H** = el blob **commiteado** en H: `git cat-file blob <H>:core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh`. El sha256 del blob y el de `sha256sum` del fichero son el mismo, así que no hay que copiar nada. - **`check` exige un árbol git** (`--tree`, `EMU_PROOF_TREE` o el directorio actual — la compuerta siempre está dentro del checkout) y **falla cerrado** (rc 2, con el motivo) cuando no hay árbol, o cuando el commit pedido no está en ese checkout. Nunca cae al montado. - **Un head pasa SOLO** por una línea `OK` `via=run` cuyo `script_sha256` sea el ancla de ese head — más los requisitos que ya había (`tree_clean`/`tree_clean_after` true, `rc 0`, `tests_failed 0`, `tests_run>0`, binding de la captura). - **El invariante del hipervisor sigue en pie**: una línea de **otra suite** (sha distinto del ancla) es **EVIDENCIA**: se le exige la integridad completa (cadena + binding de captura + clase + resultados) y **nunca es suficiente** y **nunca es motivo de fallo** del head. Una suite sustituta no desbloquea el merge. - **Una línea FAIL conserva toda su fuerza**: sigue siendo motivo de rechazo de su línea, nunca se degrada a "evidencia". - **El montado queda como aviso SECUNDARIO** (`--script`): si difiere del commit se imprime `SECUNDARIO el fichero MONTADO … va por delante o por detrás del commit. NO es la compuerta`. Nunca es criterio. - Salida del veredicto: `PROOF: OK … ancla=smoke@commit …`, y las líneas-extra como `PROOF evidencia (no suficiente): …`. ## Forma de la línea (compatibilidad byte a byte) `suite_relpath` es **OPCIONAL** y sigue la misma regla que `class` y `results_sha256` (#853): va **dentro** del texto hasheado, **al final**, y sólo si está. Una línea que no lo lleva recalcula **byte a byte** el mismo sha256 que antes — **las 21 líneas del ledger vivo siguen validando**. Hay caso de harness que compara el `chain_text` de `origin/live` con el de esta rama (18 args y 20 args, idénticos) y que comprueba que `suite_relpath` sólo añade la línea 21. ## Honestidad con los heads viejos Las líneas grabadas mientras el smoke montado iba legítimamente por detrás del commit se leen ahora contra el blob commiteado y **necesitan UNA re-corrida en ese head**. Es la dirección conservadora. Medido en el pod con esta rama (read-only, sobre el ledger real): ``` PROOF: FAIL (26cd6d87fca5aaca804243b4f5b5224d9d482d52) - este head NO tiene un smoke canonico COMMITEADO en core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh: la prueba del emulador NO puede pasar este head (no hay blob que anclar y NO se admite una suite sustituta, ni commiteada; B2) - evidencia (no suficiente): linea 20 … suite_sha256=39e03a354702... - evidencia (no suficiente): linea 21 … suite_sha256=e74156e33ce4... ``` Las dos líneas ya **no se tiran por "suite distinta"**: se leen como evidencia. Pero **ese head no puede quedar canónico con una re-corrida**, porque el árbol de la app no lleva el smoke commiteado: necesita (a) o (b) de la decisión 1. Un head que **sí** lleve el smoke commiteado queda canónico con una sola corrida fresca. ## 🔴 Dos cosas que necesitan tu decisión (no las he decidido yo a la callada) **1. El árbol de GITS no lleva la fuente de la herramienta.** El literal del brief (`git cat-file blob <H>:core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh`) sólo resuelve en un árbol del repo de la herramienta. El checkout de GITS es **`Homelab/ghostty-in-the-shell`** (`/home/ld/work/epics/gits-e3/wt/j1`, head `26cd6d87`), y **no tiene `core/manifests/…` en ningún commit**: ni el smoke ni la herramienta viven ahí (el smoke sólo existe en el ConfigMap). Comprobado: `git -C … ls-tree -r HEAD | grep sbx-emu` → vacío. **No hay respaldo.** Una versión anterior de esta rama ofrecía una segunda forma de ancla (la suite COMMITEADA en ese head, nombrada en la salida como `ancla=<familia>@commit:<ruta>`) cuando el árbol no llevaba la herramienta. **Eso se quitó** (hallazgo B2 de la revisión: en un árbol sin el smoke, cualquier script commiteado se volvía el ancla, así que una suite sustituta trivial desbloqueaba el merge). Hoy ese head es **rechazo**, rc 1, sin ancla: ``` este head NO tiene un smoke canonico COMMITEADO en core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh: la prueba del emulador NO puede pasar este head (no hay blob que anclar y NO se admite una suite sustituta, ni commiteada; B2) ``` Así que el gate de GITS no queda "en verde con otro ancla": queda **cerrado hasta que exista el literal**. **Si quieres el literal en TODOS los árboles**, hay dos caminos y los dos son tuyos, no míos: (a) commitear el smoke (o un pin de su sha256) dentro del repo de la app, o (b) que la compuerta corra `check` con el árbol del pipeline, no con el de la app. **Sin (a) o (b), GITS rung 2 no puede pasar la compuerta** — que es el estado honesto, no un falso verde. **2. `FAIL` no veta el head entero, veta su línea.** El brief dice "a FAIL line on the head still fails the head (keep strict)". Lo he implementado como: una línea `FAIL` **nunca** se degrada a evidencia y **siempre** sale como motivo de rechazo — pero **no** invalida un `OK` canónico posterior del mismo head. El motivo es el ledger vivo: los heads `f068805b`, `1a4e2b12` y `67034b5d` tienen líneas `FAIL` **y** `OK` (re-corridas verdes tras un fallo de entorno). Con la lectura literal, esos heads quedarían **imposibles de aprobar para siempre**, incluso después de una corrida verde — el "re-corré sobre el head" que este PR documenta no podría funcionar nunca. Si prefieres la lectura literal, es un cambio de tres líneas y lo hago. ## Verificación **Harness completo** (`bash scripts/gits-sbx-epic-test.sh`, SOLO, sin concurrencia, en un worktree pinneado a `b9ce8fc7`): **653 comprobaciones, 648 PASS, 5 FAIL, 6 SKIP**. | sección | resultado | |---|---| | `22[R176b]` (el ledger: 22b entera) | **90 PASS / 0 FAIL** | | `24[HOMELAB-2682]` (clase + resultados por corrida) | **25 PASS / 0 FAIL** | | `25[HOMELAB-2682]` (**nueva: el ancla**) | **32 PASS / 0 FAIL** | | `T7[diff]` (guarda del diff clavado de sbx-gits) | **3 PASS / 0 FAIL** | | `17[R159]` | **29 PASS / 0 FAIL** | Los **5** rojos son **los cinco rojos conocidos y de otro ticket** — `probe uses an EMPTY pre-existing --dir`, `manifest[2]`/`manifest[3]`/`manifest[3b]` y `T7 emulador dentro del pod` (los de #851; aquí sólo se confirma que siguen siendo exactamente esos cinco). El caso de tiempos que la medición anterior contaba como sexto **no se reprodujo** en ninguna de las dos corridas solas de esta ronda. `T7[diff]` — la guarda del diff clavado (`M README.md` + `M tools/gits-sbx-emu-proof.sh`) con sus dos mutaciones rojas — y `17[R159]` están verdes. Casos nuevos de §25 (todos ejecutan la herramienta contra repos de mentira, ningún rojo "de confianza"): 1. **(a)** la línea del smoke **commiteado** en el head → rc 0, `ancla=smoke@commit`, y la línea lleva `suite_relpath`. 2. **(a2) el defecto, reproducido y cerrado**: el fichero **montado se refresca** (otro contenido, lo que hace el kubelet) y el head viejo **sigue** en rc 0, con el desfase como `SECUNDARIO … NO es la compuerta`. 3. **(b)** contra un head cuyo blob es **otro**, la línea no vale (`no hay ninguna linea CANONICA`) — aunque el montado coincida con ese head: si se comparara con el montado, pasaría. 4. **(c)/(c2)** una corrida **real** de una suite que **no está en el commit** no completa la compuerta (sale como `evidencia (no suficiente)`) y **no hunde** a un head que sí tiene su línea canónica. 5. **(d)** una línea `FAIL` del smoke canónico → rc 1 (`result=FAIL … rc=5 … tests_failed=1`). 6. **(e)/(e2)** sin árbol git → **rc 2**; head que no es commit de ese checkout → **rc 2** (fail-closed: no se cae al montado). 7. **(f)/(f2)** árbol sin la herramienta (tipo app): **rc 1** — la compuerta **NO puede pasar ese head** (`NO tiene un smoke canonico COMMITEADO … NO se admite una suite sustituta, ni commiteada; B2`). La corrida de una suite alterna **commiteada** en ese árbol sale como `evidencia (no suficiente)`, y una suite que corrió pero **no está en el commit** tampoco vale. 8. **(g)** `suite_relpath` **opcional**: `chain_text` con 18 y con 20 args es **byte a byte** el de `origin/live`; el campo sólo añade la línea 21. Los casos que añadieron las rondas 2 y 3 (todos dentro de §25, todos verdes): - **(f3)/(f4)/(f4b) (B1)** — un `tree`, un `gitlink`, y un `gitlink` cuyo commit no está en el object DB: **rc 2** nombrando el tipo real, nunca el sha256 de la cadena vacía. - **(f6)/(f7) (B3)** — un smoke de **cero bytes** commiteado, y un ancla cuyo objeto se **borró** del object DB: **rc 1** diciendo los bytes declarados contra los leídos. - **(f9) (B4)** — el `task=` del veredicto es el de **la línea que pasa**, no el de la última del ledger (línea 1 smoke, línea 2 suite de route → `task=smoke:… linea=1`). - **(f10) (B5)** — una corrida **FAIL del propio smoke canónico** se etiqueta como tal (`aviso el smoke CANONICO TAMBIEN CORRIO EN ROJO`), no como "otra suite". - **(f11) (C1)** — sustituido el objeto del oid del ancla por otro contenido del **mismo tamaño**: **rc 1** con los dos oids. - **(f12) (B6/B7)** — `git replace` (commit, árbol, blob y encadenado) + una corrida real de la suite de F sobre el árbol parado en H: **rc 1** nombrando el blob **real** del head; y **(f12b2)** lo mismo **más** `core.useReplaceRefs true` en el config del checkout, también **rc 1**. - **(f13)** — el `tests_run` del veredicto es el de **esta** corrida, no el del directorio de resultados acumulado. Además: `bash -n` de la herramienta y del smoke, y `--help` sin stderr (sin backticks sin escapar). **Lo que NO se ha tocado**: `kustomization.yaml`, `statefulset.yaml` (el guarda del diff clavado queda verde con el conjunto re-medido a los DOS ficheros de esta rama: `README.md` y `tools/gits-sbx-emu-proof.sh`; el smoke **no** cambia aquí, su `--class` ya está en live con #853), ni ningún manifiesto, Service o NetworkPolicy. **No mergear todavía**: siguen dos asientos DeepSeek.
HOMELAB-2682: fix(sbx-gits): check anchors the canonical smoke to the blob committed at the head
Some checks failed
CI Review / helm-validate (pull_request) Successful in 35s
CI Review / runner-manifest-test (pull_request) Successful in 13s
CI Review / receiver-test (pull_request) Successful in 15s
CI Review / alert-receiver-test (pull_request) Successful in 34s
CI Review / record-sql-test (pull_request) Successful in 1m16s
CI Review / dispatcher-test (pull_request) Successful in 3m7s
CI Review / cockpit-test (pull_request) Has been cancelled
CI Review / cockpit-e2e (pull_request) Has been cancelled
eb1b50434c
check <head> comparaba el script_sha256 de cada linea con el smoke MONTADO en
/run/sbx-tools. El kubelet refresca las claves del ConfigMap EN CALIENTE, asi que
cada refresco ponia rojo todo lo anterior: en sbx-gits-0, head 26cd6d87 tenia dos
lineas OK y check las rechazaba contra un montado posterior (#847).

Ahora el ancla es el COMMIT: el sha256 canonico de un head H es el blob
'core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh' en H (git cat-file).
check exige un arbol git (--tree/EMU_PROOF_TREE/cwd) y falla cerrado (rc 2) sin el.
Un head pasa solo via una linea OK cuyo script_sha256 sea el ancla; las lineas de
otra suite son EVIDENCIA (integridad completa, nunca suficientes, nunca motivo de
fallo); una linea FAIL sigue teniendo toda su fuerza. run apunta suite_relpath
(campo OPCIONAL, dentro del hash, byte-compatible con las lineas viejas). El
montado queda como aviso SECUNDARIO, nunca criterio.
HOMELAB-2682: docs+harness for the commit-anchored proof check
All checks were successful
CI Review / helm-validate (pull_request) Successful in 22s
CI Review / runner-manifest-test (pull_request) Successful in 18s
CI Review / receiver-test (pull_request) Successful in 26s
CI Review / alert-receiver-test (pull_request) Successful in 26s
Lint & Validate / yaml-lint (pull_request) Successful in 58s
CI Review / record-sql-test (pull_request) Successful in 2m5s
Lint & Validate / shellcheck (pull_request) Successful in 13s
CI Review / cockpit-test (pull_request) Successful in 4m16s
CI Review / dispatcher-test (pull_request) Successful in 4m41s
Lint & Validate / terraform-validate (pull_request) Successful in 8m41s
CI Review / cockpit-e2e (pull_request) Successful in 11m56s
563e11c2ca
- run-proof.md: seccion 'El ancla: el smoke COMMITEADO en el head', fila suite_relpath,
  punto 4 del check reescrito, tabla de mensajes (ancla/evidencia/secundario) y la nota
  honesta de los heads viejos (necesitan una re-corrida).
- README SS10.4: el ancla es el commit, no el montado; arbol obligatorio; evidencia vs
  canonica; suite sustituta no desbloquea el merge; aviso SECUNDARIO del montado.
- scripts/gits-sbx-epic-test.sh: fixtures de SS22b/SS24 con el smoke canonico commiteado en
  el arbol y --tree en los helpers; caso (g) de 22b reescrito a la semantica nueva
  (evidencia, nunca suficiente); seccion nueva SS25 (defecto reproducido y cerrado, ancla
  del commit, evidencia, FAIL estricto, fail-closed sin arbol, ancla de reserva en el arbol
  de la app, suite_relpath opcional y la prosa).
HOMELAB-2682: T7[diff] re-measured to this branch's two files + harness reds from the anchor
All checks were successful
CI Review / runner-manifest-test (pull_request) Successful in 8s
CI Review / helm-validate (pull_request) Successful in 14s
CI Review / receiver-test (pull_request) Successful in 34s
CI Review / alert-receiver-test (pull_request) Successful in 45s
Lint & Validate / yaml-lint (pull_request) Successful in 28s
Lint & Validate / shellcheck (pull_request) Successful in 27s
CI Review / record-sql-test (pull_request) Successful in 1m27s
CI Review / cockpit-test (pull_request) Successful in 3m10s
Lint & Validate / terraform-validate (pull_request) Successful in 2m24s
CI Review / cockpit-e2e (pull_request) Successful in 4m26s
CI Review / dispatcher-test (pull_request) Successful in 5m8s
b2e69c8119
- SBX_GITS_DIFF_SET: this branch changes exactly README.md + tools/gits-sbx-emu-proof.sh
  under sbx-gits (the smoke does NOT change: its --class is already in live, #853;
  kustomization.yaml/statefulset.yaml untouched on purpose). Re-measured, never carried.
- 22b (d3): a sha that is not a commit of that checkout is rc 2 fail-closed now; (d3b) a
  commit with no run is still rc 1, measured in a SEPARATE repo so the shared tree's HEAD
  does not move under the later cases.
- docs: drop the 'sin reiniciar el pod' phrasing the guard forbids, and keep the README
  sentence whole.
Merge remote-tracking branch 'origin/live' into plane/HOMELAB-2682-check-anchor
All checks were successful
CI Review / runner-manifest-test (pull_request) Successful in 7s
CI Review / receiver-test (pull_request) Successful in 8s
CI Review / helm-validate (pull_request) Successful in 13s
CI Review / alert-receiver-test (pull_request) Successful in 24s
Lint & Validate / shellcheck (pull_request) Successful in 8s
Lint & Validate / yaml-lint (pull_request) Successful in 27s
CI Review / record-sql-test (pull_request) Successful in 1m1s
Lint & Validate / terraform-validate (pull_request) Successful in 2m22s
CI Review / dispatcher-test (pull_request) Successful in 2m56s
CI Review / cockpit-test (pull_request) Successful in 2m56s
CI Review / cockpit-e2e (pull_request) Successful in 4m0s
f4ee5aaf88
Collaborator

Verdict of record — ld-reviewer — head f4ee5aaf88: REQUEST_CHANGES
model: deepseek-flash
session: 01a1ade1-775b-757a-b84d-278fbf20dd82

Blockers

  • B1: blocker — the anchor accepts a NON-BLOB at the canonical path and then hashes the empty stream. git_blob_sha (tools/gits-sbx-emu-proof.sh:419-428) does git cat-file -e "$rev:$rel" (existence only) and then git cat-file blob "$rev:$rel" | sha256sum. When the object at that path is a tree/gitlink, cat-file blob fails and the pipeline hashes EMPTY input, so smoke_sha becomes e3b0c44298fc… (sha256 of the empty string) instead of a refusal — the [[ ${#sha} == 64 ]] || return 1 guard cannot see it because the empty-stream hash is 64 chars, and set -o pipefail on the assignment is never checked. Measured in /tmp (genuine run, no ledger forgery): a head whose core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh is a DIRECTORY (git cat-file -t → tree) plus run --suite scripts/empty.sh (a zero-byte file, sha256 also e3b0c44298fc…) gives PROOF: OK … ancla=smoke@commit suite_sha256=e3b0c44298fc…, rc 0, having executed nothing (tests_run supplied by an XML the tree pre-stages; xml_scan accepts mtime >= start). The verdict then asserts the canonical smoke blob of the head — a statement that is false. Fix is small: [[ $(git -C "$tree" cat-file -t "$rev:$rel" 2>/dev/null) == blob ]] || return 1 before hashing (and reject an empty cat-file blob output).
  • B2: blocker — in a tree that does not carry the canonical path, ANY committed script is the anchor, so a substitute suite satisfies the gate. Measured in /tmp: an app-style repo (no core/manifests/… anywhere) with a committed scripts/alt-suite.sh that writes a fake <testcase XML and exit 0 → run --suite scripts/alt-suite.sh then check <head> gives rc 0, ancla=suite@commit:scripts/alt-suite.sh, tests_run=3. That is a pass without the canonical smoke and without the emulator, reachable only through the supported interface, and it is the tree where the gate is actually used: live, read-only in sbx-gits-0, the rung-2 checkout /home/ld/work/epics/gits-e3/wt/j1 is at 26cd6d87fca5… and git cat-file -e HEAD:core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh answers does not exist in 'HEAD' (also absent from every commit of Homelab/ghostty-in-the-shell). Before this change a substitute suite could not pass there (the mounted smoke was the anchor); after it, a trivial committed script can. The PR escalates this as the operator's decision (options (a)/(b)) and that is the right thing to raise — but it cannot merge in this shape: it does not meet the acceptance criterion "a substitute/easier suite can NEVER satisfy the gate". Two narrower routes that keep rung 2 moving without the operator's policy call: (i) make the fallback anchor require a committed, reviewable pin (a small file in the app repo naming the canonical suite path + its sha256, refused when absent) so the worker cannot unilaterally choose the anchor; or (ii) accept ancla=suite@commit: only when the path is one the ticket/fleet declares canonical, refusing everything else. Either keeps smoke@commit the only anchor in the tool tree and removes the free choice in the app tree.

Concerns

  • C1: the FAIL lines of a head vanish from a passing verdict. gate[] is discarded in the ok_line > 0 branch, so when a head has a result=FAIL line of the canonical smoke AND a later OK line, the OK output names neither the FAIL line nor its rc (measured: PROOF: OK … linea=2 with no mention of line 1, result=FAIL rc=5 tests_failed=1). That matches the PR's decision #2 semantics, but it is not what docs/gits-sbx/run-proof.md:271 reads like ("una línea FAIL conserva toda su fuerza: sigue siendo motivo de rechazo"), and a reader of the verdict cannot see that the head also had red runs. Suggest printing the FAIL lines in the OK branch as PROOF aviso … (does not change rc).
  • C2: harness §25 encodes B2 as correct behaviour and has no case for B1. 25[HOMELAB-2682] (f) asserts rc 0 with ancla=suite@commit:docs/route.sh for a committed docs/route.sh in an app-style tree, so the suite will keep the weakening green; and no case commits a directory/gitlink at the canonical relpath or runs an empty suite, which is why B1 is invisible. When B2 is narrowed, §25 needs the inverse assertion (a committed non-canonical suite must be refused) and §25 needs the non-blob mutation.

Verified (this seat, primary sources)

  • The defect itself is closed: with the canonical line at head H and the mounted file then refreshed, check H stays rc 0 and prints SECUNDARIO … el montado va por delante o por detras del commit. NO es la compuerta. Live, the mounted smoke now hashes a7b8be878109… while the ledger holds lines 39e03a35…/e74156e3… for 26cd6d87 — the shape the PR describes.
  • The mount cannot gate: script_sha is used only for mount_note/manifest_note (lines 942-944, 1190-1195); --script pointing at a missing file leaves the verdict unchanged.
  • Anchor correctness otherwise: the constant EMU_PROOF_SMOKE_RELPATH is exactly the configMapGenerator source path (core/manifests/apps/sbx-gits/kustomization.yaml → tools/gits-sbx-emu-smoke.sh), the blob sha256 equals the file's sha256sum, another head's blob does not satisfy an old line, and a foreign suite committed in a tool tree is refused as evidencia (no suficiente) while a foreign suite cannot drown a head that has its canonical line.
  • Fail-closed: no git tree → rc 2 with the "el ancla es el smoke COMMITEADO" message; tree without that commit → rc 2; HEAD:core / --upload-pack=… refused. No crash on any of them.
  • Optional-field compat: all 21 lines of the live ledger (sbx-gits-0:/home/ld/work/.emu-proof/ledger.jsonl, md5 9b7fd72b6736c8d67b5617bbf7be87e0, none carrying suite_relpath/class/results_sha256) recompute the STORED hash with both the origin/live chain_text and the new one — 21/21 byte-identical, 0 mismatches; suite_relpath is appended only as the 21st line when present, so tampering with it breaks the chain.
  • Harness: read §25 in full — real throwaway git repos, real run invocations, negative cases (b)/(c)/(f2)/(e)/(e2), non-vacuous. Full run at post time: 459 PASS / 5 FAIL / 6 SKIP, the 5 failures being exactly the known pre-existing ones (probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod); T7[diff] and 17[R159] green so far. §25 had not been reached when this verdict was filed — that section is read, not yet observed green by this seat.
**Verdict of record — ld-reviewer — head f4ee5aaf8805eeb58bb668fd5c151dfef86bc180: REQUEST_CHANGES** model: deepseek-flash session: 01a1ade1-775b-757a-b84d-278fbf20dd82 ## Blockers - B1: blocker — the anchor accepts a NON-BLOB at the canonical path and then hashes the empty stream. `git_blob_sha` (tools/gits-sbx-emu-proof.sh:419-428) does `git cat-file -e "$rev:$rel"` (existence only) and then `git cat-file blob "$rev:$rel" | sha256sum`. When the object at that path is a tree/gitlink, `cat-file blob` fails and the pipeline hashes EMPTY input, so `smoke_sha` becomes `e3b0c44298fc…` (sha256 of the empty string) instead of a refusal — the `[[ ${#sha} == 64 ]] || return 1` guard cannot see it because the empty-stream hash is 64 chars, and `set -o pipefail` on the assignment is never checked. Measured in /tmp (genuine `run`, no ledger forgery): a head whose `core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh` is a DIRECTORY (`git cat-file -t` → `tree`) plus `run --suite scripts/empty.sh` (a zero-byte file, sha256 also `e3b0c44298fc…`) gives `PROOF: OK … ancla=smoke@commit suite_sha256=e3b0c44298fc…`, rc 0, having executed nothing (`tests_run` supplied by an XML the tree pre-stages; `xml_scan` accepts mtime >= start). The verdict then asserts the canonical smoke blob of the head — a statement that is false. Fix is small: `[[ $(git -C "$tree" cat-file -t "$rev:$rel" 2>/dev/null) == blob ]] || return 1` before hashing (and reject an empty `cat-file blob` output). - B2: blocker — in a tree that does not carry the canonical path, ANY committed script is the anchor, so a substitute suite satisfies the gate. Measured in /tmp: an app-style repo (no `core/manifests/…` anywhere) with a committed `scripts/alt-suite.sh` that writes a fake `<testcase` XML and `exit 0` → `run --suite scripts/alt-suite.sh` then `check <head>` gives rc 0, `ancla=suite@commit:scripts/alt-suite.sh`, `tests_run=3`. That is a pass without the canonical smoke and without the emulator, reachable only through the supported interface, and it is the tree where the gate is actually used: live, read-only in `sbx-gits-0`, the rung-2 checkout `/home/ld/work/epics/gits-e3/wt/j1` is at `26cd6d87fca5…` and `git cat-file -e HEAD:core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh` answers `does not exist in 'HEAD'` (also absent from every commit of `Homelab/ghostty-in-the-shell`). Before this change a substitute suite could not pass there (the mounted smoke was the anchor); after it, a trivial committed script can. The PR escalates this as the operator's decision (options (a)/(b)) and that is the right thing to raise — but it cannot merge in this shape: it does not meet the acceptance criterion "a substitute/easier suite can NEVER satisfy the gate". Two narrower routes that keep rung 2 moving without the operator's policy call: (i) make the fallback anchor require a committed, reviewable pin (a small file in the app repo naming the canonical suite path + its sha256, refused when absent) so the worker cannot unilaterally choose the anchor; or (ii) accept `ancla=suite@commit:` only when the path is one the ticket/fleet declares canonical, refusing everything else. Either keeps `smoke@commit` the only anchor in the tool tree and removes the free choice in the app tree. ## Concerns - C1: the FAIL lines of a head vanish from a passing verdict. `gate[]` is discarded in the `ok_line > 0` branch, so when a head has a `result=FAIL` line of the canonical smoke AND a later `OK` line, the OK output names neither the FAIL line nor its rc (measured: `PROOF: OK … linea=2` with no mention of line 1, `result=FAIL rc=5 tests_failed=1`). That matches the PR's decision #2 semantics, but it is not what docs/gits-sbx/run-proof.md:271 reads like ("una línea FAIL conserva toda su fuerza: sigue siendo motivo de rechazo"), and a reader of the verdict cannot see that the head also had red runs. Suggest printing the FAIL lines in the OK branch as `PROOF aviso …` (does not change rc). - C2: harness §25 encodes B2 as correct behaviour and has no case for B1. `25[HOMELAB-2682] (f)` asserts rc 0 with `ancla=suite@commit:docs/route.sh` for a committed `docs/route.sh` in an app-style tree, so the suite will keep the weakening green; and no case commits a directory/gitlink at the canonical relpath or runs an empty suite, which is why B1 is invisible. When B2 is narrowed, §25 needs the inverse assertion (a committed non-canonical suite must be refused) and §25 needs the non-blob mutation. ## Verified (this seat, primary sources) - The defect itself is closed: with the canonical line at head H and the mounted file then refreshed, `check H` stays rc 0 and prints `SECUNDARIO … el montado va por delante o por detras del commit. NO es la compuerta.` Live, the mounted smoke now hashes `a7b8be878109…` while the ledger holds lines `39e03a35…`/`e74156e3…` for `26cd6d87` — the shape the PR describes. - The mount cannot gate: `script_sha` is used only for `mount_note`/`manifest_note` (lines 942-944, 1190-1195); `--script` pointing at a missing file leaves the verdict unchanged. - Anchor correctness otherwise: the constant `EMU_PROOF_SMOKE_RELPATH` is exactly the `configMapGenerator` source path (`core/manifests/apps/sbx-gits/kustomization.yaml` → `tools/gits-sbx-emu-smoke.sh`), the blob sha256 equals the file's `sha256sum`, another head's blob does not satisfy an old line, and a foreign suite committed in a tool tree is refused as `evidencia (no suficiente)` while a foreign suite cannot drown a head that has its canonical line. - Fail-closed: no git tree → rc 2 with the "el ancla es el smoke COMMITEADO" message; tree without that commit → rc 2; `HEAD:core` / `--upload-pack=…` refused. No crash on any of them. - Optional-field compat: all 21 lines of the live ledger (`sbx-gits-0:/home/ld/work/.emu-proof/ledger.jsonl`, md5 `9b7fd72b6736c8d67b5617bbf7be87e0`, none carrying `suite_relpath`/`class`/`results_sha256`) recompute the STORED hash with both the `origin/live` `chain_text` and the new one — 21/21 byte-identical, 0 mismatches; `suite_relpath` is appended only as the 21st line when present, so tampering with it breaks the chain. - Harness: read §25 in full — real throwaway git repos, real `run` invocations, negative cases (b)/(c)/(f2)/(e)/(e2), non-vacuous. Full run at post time: 459 PASS / 5 FAIL / 6 SKIP, the 5 failures being exactly the known pre-existing ones (`probe uses an EMPTY pre-existing --dir`, `manifest[2]`, `manifest[3]`, `manifest[3b]`, `T7 emulador dentro del pod`); `T7[diff]` and `17[R159]` green so far. §25 had not been reached when this verdict was filed — that section is read, not yet observed green by this seat.
Collaborator

Verdict of record — ld-reviewer — head f4ee5aaf88: REQUEST_CHANGES
model: deepseek-flash
session: s2-2682-840db5a2

head: f4ee5aaf88

Independent seat 2 of 2 (adversarial). Probes run in /tmp only against throwaway repos; nothing was executed in the pod and no ledger outside /tmp was read.

Blockers

  • 22296 B1: high — git_blob_sha() accepts a NON-blob at the canonical path: cat-file -e <rev>:<rel> succeeds for a tree/gitlink, cat-file blob then fails with its stderr dropped, and sha256 of the empty output (e3b0c442…) becomes the anchor. Reproduced first-hand: head with a DIRECTORY at core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh, run --suite an EMPTY file (same e3b0c442… hash), pre-seeded future-mtime XML under <module>/build/outputs/androidTest-results → line result=OK tests_run=2 → PROOF: OK … ancla=smoke@commit, rc 0, with a suite that exercises nothing. Confirm of 22296 B1, not a new finding. Fix is small: require cat-file -t ":" == blob and treat a failed/empty blob read as no anchor (rc 2).
  • 22296 B2: high — in a tree without the canonical relpath the fallback makes ANY committed file the anchor. Two independent repros of mine: (i) a head carrying core/manifests/apps/sbx-gits/kustomization.yaml but no smoke, plus a committed docs/route.sh → ancla=suite@commit:docs/route.sh, rc 0; (ii) a tree with no smoke at all, same result. Reachable inside THIS repo's history: commit 8e25ef77 (parent of the commit that added the smoke) has kustomization.yaml and no smoke, so a head that deletes or renames the smoke at H silently flips the gate into the fallback mode. Confirm of 22296 B2. A cheap guard that keeps the app-repo case working: if the tree carries core/manifests/apps/sbx-gits/kustomization.yaml at H, the smoke path must also exist at H, else rc 2.

Concerns

  • A1 (seat-2 item 1, severity MODERATE, widens the surface): the anchor now names the suite the HEAD committed. For a PR branch that is the author's own smoke, and ancla=smoke@commit reads identically whether that blob is byte-identical to live or rewritten — reproduced: a crafted repo with a modified smoke at the canonical path gives rc 0 with no signal in the OK line. It is not silent in the merge gate, because the smoke sits inside the pinned T7 diff set, so any change to it is visible in the reviewed diff; that is why this is a concern and not one of the two items above. Cheap mitigation: print the anchor blob's short sha and whether it equals the smoke at the merge-base/live in the OK line.
  • A2 (seat-2 item 2, severity LOW, accepted C3 class, not new): --tree is unvalidated — a plain git clone of a crafted repo that contains H passes (rc 0). This is the same trust-root freedom the old --script had (it accepted any file as the compare target, with no repo at all), and --help already states the gate runs inside the checkout, so it is narrower than before. Optional hardening: refuse a tree whose origin is not the expected repo.
  • A3 (LOW): check no longer fails when kustomization.yaml stops delivering the mounted file — manifest_note is advisory only, where it used to be a hard reason. Covered in-tree by H8[8] kubectl kustomize, so low.
  • A4 (operator decision #2 in the description, my vote): a FAIL line does not veto a head that also carries a canonical OK. I measured live's check on the same shape and it behaves identically, so this is NOT a strictness regression introduced here; it only differs from the brief's literal wording, and the author escalated it instead of hiding it. My vote: keep the implemented reading (the live ledger has heads with FAIL-then-OK re-runs) and state it in the docs.
  • Non-regression, verified independently of §25(g): chain_text extracted from origin/live and from this head produce the SAME sha256 for the 18-arg and the 20-arg forms (df1b9431… and 05054e1a…), and the 21st field only appends suite_relpath= as the last line, so the live 21-line ledger keeps its hashes. No tree → rc 2 with the reason; head not a commit of that checkout → rc 2; EMU_PROOF_TREE honoured; default '.' outside a checkout → rc 2 (documented, and the gate runs inside the checkout).
  • Harness run solo in this session (one run, no concurrency): 641 checks, 636 PASS, 5 FAIL, 6 SKIP. The 5 are the #851-class pre-existing reds (probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod); everything they read (statefulset.yaml) is untouched by this diff, whose --name-status is exactly README.md, tools/gits-sbx-emu-proof.sh, docs/gits-sbx/run-proof.md, scripts/gits-sbx-epic-test.sh. The author's 6th red (22[R172c-fix] timing) passed in my run, consistent with load sensitivity. §25 (the anchor) 20/20 PASS, and its cases do run the tool against throwaway repos rather than grepping.
  • Nit: the re-measured T7 comment in scripts/gits-sbx-epic-test.sh names the anchor as <head>:tools/gits-sbx-emu-smoke.sh while the constant is repo-root-relative (core/manifests/apps/sbx-gits/tools/…); a reader of that comment alone looks in the wrong place.
**Verdict of record — ld-reviewer — head f4ee5aaf8805eeb58bb668fd5c151dfef86bc180: REQUEST_CHANGES** model: deepseek-flash session: s2-2682-840db5a2 head: f4ee5aaf8805eeb58bb668fd5c151dfef86bc180 Independent seat 2 of 2 (adversarial). Probes run in /tmp only against throwaway repos; nothing was executed in the pod and no ledger outside /tmp was read. ## Blockers - 22296 B1: high — `git_blob_sha()` accepts a NON-blob at the canonical path: `cat-file -e <rev>:<rel>` succeeds for a tree/gitlink, `cat-file blob` then fails with its stderr dropped, and sha256 of the empty output (e3b0c442…) becomes the anchor. Reproduced first-hand: head with a DIRECTORY at `core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh`, `run --suite` an EMPTY file (same e3b0c442… hash), pre-seeded future-mtime XML under `<module>/build/outputs/androidTest-results` → line result=OK tests_run=2 → `PROOF: OK … ancla=smoke@commit`, rc 0, with a suite that exercises nothing. Confirm of 22296 B1, not a new finding. Fix is small: require `cat-file -t ":"` == blob and treat a failed/empty blob read as no anchor (rc 2). - 22296 B2: high — in a tree without the canonical relpath the fallback makes ANY committed file the anchor. Two independent repros of mine: (i) a head carrying `core/manifests/apps/sbx-gits/kustomization.yaml` but no smoke, plus a committed `docs/route.sh` → `ancla=suite@commit:docs/route.sh`, rc 0; (ii) a tree with no smoke at all, same result. Reachable inside THIS repo's history: commit 8e25ef77 (parent of the commit that added the smoke) has `kustomization.yaml` and no smoke, so a head that deletes or renames the smoke at H silently flips the gate into the fallback mode. Confirm of 22296 B2. A cheap guard that keeps the app-repo case working: if the tree carries `core/manifests/apps/sbx-gits/kustomization.yaml` at H, the smoke path must also exist at H, else rc 2. ## Concerns - A1 (seat-2 item 1, severity MODERATE, widens the surface): the anchor now names the suite the HEAD committed. For a PR branch that is the author's own smoke, and `ancla=smoke@commit` reads identically whether that blob is byte-identical to live or rewritten — reproduced: a crafted repo with a modified smoke at the canonical path gives rc 0 with no signal in the OK line. It is not silent in the merge gate, because the smoke sits inside the pinned T7 diff set, so any change to it is visible in the reviewed diff; that is why this is a concern and not one of the two items above. Cheap mitigation: print the anchor blob's short sha and whether it equals the smoke at the merge-base/live in the OK line. - A2 (seat-2 item 2, severity LOW, accepted C3 class, not new): `--tree` is unvalidated — a plain `git clone` of a crafted repo that contains H passes (rc 0). This is the same trust-root freedom the old `--script` had (it accepted any file as the compare target, with no repo at all), and `--help` already states the gate runs inside the checkout, so it is narrower than before. Optional hardening: refuse a tree whose `origin` is not the expected repo. - A3 (LOW): `check` no longer fails when `kustomization.yaml` stops delivering the mounted file — `manifest_note` is advisory only, where it used to be a hard reason. Covered in-tree by H8[8] `kubectl kustomize`, so low. - A4 (operator decision #2 in the description, my vote): a FAIL line does not veto a head that also carries a canonical OK. I measured live's `check` on the same shape and it behaves identically, so this is NOT a strictness regression introduced here; it only differs from the brief's literal wording, and the author escalated it instead of hiding it. My vote: keep the implemented reading (the live ledger has heads with FAIL-then-OK re-runs) and state it in the docs. - Non-regression, verified independently of §25(g): `chain_text` extracted from `origin/live` and from this head produce the SAME sha256 for the 18-arg and the 20-arg forms (df1b9431… and 05054e1a…), and the 21st field only appends `suite_relpath=` as the last line, so the live 21-line ledger keeps its hashes. No tree → rc 2 with the reason; head not a commit of that checkout → rc 2; `EMU_PROOF_TREE` honoured; default '.' outside a checkout → rc 2 (documented, and the gate runs inside the checkout). - Harness run solo in this session (one run, no concurrency): 641 checks, 636 PASS, 5 FAIL, 6 SKIP. The 5 are the #851-class pre-existing reds (`probe uses an EMPTY pre-existing --dir`, `manifest[2]`, `manifest[3]`, `manifest[3b]`, `T7 emulador dentro del pod`); everything they read (`statefulset.yaml`) is untouched by this diff, whose `--name-status` is exactly README.md, tools/gits-sbx-emu-proof.sh, docs/gits-sbx/run-proof.md, scripts/gits-sbx-epic-test.sh. The author's 6th red (22[R172c-fix] timing) passed in my run, consistent with load sensitivity. §25 (the anchor) 20/20 PASS, and its cases do run the tool against throwaway repos rather than grepping. - Nit: the re-measured T7 comment in `scripts/gits-sbx-epic-test.sh` names the anchor as `<head>:tools/gits-sbx-emu-smoke.sh` while the constant is repo-root-relative (`core/manifests/apps/sbx-gits/tools/…`); a reader of that comment alone looks in the wrong place.
HOMELAB-2682: fix(sbx-gits): proof check fails closed on blob type and missing canonical smoke
All checks were successful
CI Review / receiver-test (pull_request) Successful in 4s
CI Review / runner-manifest-test (pull_request) Successful in 7s
CI Review / helm-validate (pull_request) Successful in 9s
Lint & Validate / shellcheck (pull_request) Successful in 7s
Build Lab Director Cockpit Image / preflight (pull_request) Successful in 15s
CI Review / alert-receiver-test (pull_request) Successful in 21s
Lint & Validate / yaml-lint (pull_request) Successful in 31s
CI Review / record-sql-test (pull_request) Successful in 1m6s
Lint & Validate / terraform-validate (pull_request) Successful in 2m31s
CI Review / cockpit-test (pull_request) Successful in 3m27s
Build Lab Director Cockpit Image / build-and-push (pull_request) Successful in 3m24s
CI Review / cockpit-e2e (pull_request) Successful in 4m3s
CI Review / dispatcher-test (pull_request) Successful in 4m24s
b79466a878
Round-2 blockers from the review seats (22296 seat 1, 22303 seat 2).

B1 — the anchor accepted a non-blob at the canonical path. `git_blob_sha` only asked
`cat-file -e` (which answers YES for a TREE) and then piped `cat-file blob` into
`sha256sum`. With a tree or a gitlink there, the pipe was EMPTY and sha256sum of nothing
is e3b0c442… — the sha256 of the empty string, which is also the sha256 of a ZERO-BYTE
suite file. `check` then printed `ancla=smoke@commit` with nothing executed. Now the TYPE
is asked first (`git cat-file -t`, via git_path_type) and anything that is not exactly a
`blob` is a hard refusal (rc 2) naming the real type. `set -o pipefail` alone does not
close this — the last stage of the pipe succeeds on empty input — so the gate is explicit.
The other pipelines in the tool were audited in the same pass (see the comment on
git_blob_sha): the remaining `sha256sum | cut` pipelines hash a SET of files whose empty
value is an honest "zero files", and every `sha256sum <file>` sits behind `[[ -f ]]` or a
`${#sha} == 64` gate.

B2 — a head without the canonical path could still PASS. The code fell back to letting ANY
committed script anchor the line (`ancla=suite@commit:<ruta>`). That widens the
substitute-suite surface El Director's invariant forbids, and it regressed the pre-PR
behaviour, where a substitute suite could not pass in such a tree. The fallback is GONE:
a head with no committed canonical smoke CANNOT pass the emulator gate — `check` returns
rc 1 naming exactly that ("esta head no tiene un smoke canonico COMMITEADO en <relpath>:
la prueba del emulador NO puede pasar este head ... no se admite una suite sustituta, ni
commiteada"). Foreign-suite lines stay evidence-only, never sufficient, never a veto.
`suite_relpath` keeps travelling in the line as context; it no longer anchors anything.

A1 (cheap mitigation, seat 2) — the OK line now prints the anchor blob's short sha AND
`montado=IGUAL|DISTINTO|no-legible` against the currently-mounted smoke, so a PR that
rewrites the committed smoke shows the divergence in the verdict line instead of printing
identically. Signal only: the verdict is still the committed blob's and the real review
boundary remains T7's pinned diff.

Untouched by design: FAIL semantics (a FAIL line not vetoing a later canonical OK is
pre-existing live behaviour, gits-hypervisor's ticket), A3, and the relpath nit — those
stay posted concerns. chain_text is untouched: the live 21-line ledger still recalculates
byte-identical (21/21 verified against both origin/live's and this branch's chain_text).
HOMELAB-2682: test(sbx-gits): the seat's two repros become refusals; docs follow
All checks were successful
CI Review / receiver-test (pull_request) Successful in 4s
CI Review / runner-manifest-test (pull_request) Successful in 6s
CI Review / helm-validate (pull_request) Successful in 9s
Build Lab Director Cockpit Image / preflight (pull_request) Successful in 6s
Lint & Validate / shellcheck (pull_request) Successful in 6s
CI Review / alert-receiver-test (pull_request) Successful in 22s
Lint & Validate / yaml-lint (pull_request) Successful in 19s
CI Review / record-sql-test (pull_request) Successful in 1m11s
Lint & Validate / terraform-validate (pull_request) Successful in 2m9s
Build Lab Director Cockpit Image / build-and-push (pull_request) Successful in 2m11s
CI Review / dispatcher-test (pull_request) Successful in 3m3s
CI Review / cockpit-test (pull_request) Successful in 3m4s
CI Review / cockpit-e2e (pull_request) Successful in 3m52s
e9405b8b0b
The harness case that USED to prove the fallback now proves it is gone, and the two
repros from the review seats are permanent cases:

- (f) B2: a head with no committed canonical smoke and an alt suite COMMITTED in the tree
  must be rc 1 with "NO tiene un smoke canonico COMMITEADO ... NO puede pasar este head",
  and the alt run must show as "evidencia (no suficiente)". The case also fails if the
  verdict so much as mentions `suite@commit`, so the fallback cannot come back by wording.
- (f3) B1 tree: a TREE at the canonical path plus a real ledger line is rc 2, names the
  real type, and the verdict must not contain e3b0c442 (the sha256 of the empty string,
  which is what the broken pipe used to hash).
- (f4) B1 gitlink: same, via a submodule at the canonical path.
- (f5) A1: the OK line carries `ancla_blob=<short>` equal to the commit's blob and
  `montado=IGUAL` against the mounted file, `montado=DISTINTO` when the mount is refreshed.

Docs: run-proof.md and README §10.4 drop the "anchor = a suite committed in that head"
paragraph and describe the fail-closed rule, the type gate and the montado flag.
HOMELAB-2682: fix(sbx-gits): name the type from the tree when cat-file cannot
All checks were successful
CI Review / runner-manifest-test (pull_request) Successful in 5s
CI Review / helm-validate (pull_request) Successful in 9s
CI Review / receiver-test (pull_request) Successful in 5s
Build Lab Director Cockpit Image / preflight (pull_request) Successful in 4s
Lint & Validate / shellcheck (pull_request) Successful in 5s
CI Review / alert-receiver-test (pull_request) Successful in 21s
Lint & Validate / yaml-lint (pull_request) Successful in 21s
CI Review / record-sql-test (pull_request) Successful in 49s
CI Review / cockpit-test (pull_request) Successful in 2m24s
Lint & Validate / terraform-validate (pull_request) Successful in 2m23s
Build Lab Director Cockpit Image / build-and-push (pull_request) Successful in 2m43s
CI Review / cockpit-e2e (pull_request) Successful in 4m0s
CI Review / dispatcher-test (pull_request) Successful in 4m6s
244b1bf357
`git_path_type` asked `cat-file -t` and treated a failure as "not there". But a GITLINK
whose commit is not in the object DB (a submodule nobody fetched) also fails there, so the
canonical path was reported as MISSING and the head got B2's verdict instead of B1's. The
mode is now read from the TREE with `ls-tree` when `cat-file -t` cannot answer, so a gitlink
is named `gitlink` and the reason is the true one. Both paths still refuse (rc 2 for
"exists and is not a blob", rc 1 for "no committed canonical smoke"), so nothing loosens:
this only stops a gitlink from being described as an absent file.

Harness: the gitlink case builds the entry with `update-index --cacheinfo 160000` against a
commit the repo itself has (deterministic — no submodule, no network, no skipped case), and
(f4b) covers the absent-object gitlink that motivated the ls-tree fallback.
Collaborator

Verdict of record — ld-reviewer — head 244b1bf357: APPROVE
model: deepseek-flash
session: s2-2682-r2-337cad96

head: 244b1bf357

Independent seat 2 of 2, ROUND 2 (adversarial), on the delta that answers the two round-1 findings. Everything below was measured in /tmp: throwaway git repos plus the tool taken from this head. No pod was touched; no live ledger was read or written.

Evidence, solo: bash scripts/gits-sbx-epic-test.sh → 645 checks, 640 PASS, 5 FAIL, 6 SKIP. The five reds are exactly the known ones of other tickets (probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod); the timing case the description counted as the sixth did not reproduce in this solo run. §25 (el ancla) + §24 + §22b: 49 checks, 0 FAIL, and 25[HOMELAB-2682] EL ANCLA (falla:) is green. CI on this head: runs #3219, #3220, #3221 — all pass.

Blockers

None.

Resolutions

  • resolved B1: the type gate holds. git_path_type() answers before anything is hashed and check stops with rc 2 when the object at core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh is not a blob. Measured on this head: a DIRECTORY at the path (cat-file -t → tree) → rc 2 naming 'tree'; a GITLINK built with update-index --cacheinfo 160000 → rc 2; a gitlink whose commit is absent from the object DB → the ls-tree fallback names 'gitlink' and it is still rc 2, not the "absent" verdict. There is no longer any route that puts the sha256 of the empty stream into smoke_sha without a committed blob at that path, and the cat-file blob read is now behind the type check instead of being the check.
  • resolved B2: the fallback is gone. With no canonical path at H the verifier computes no anchor at all and refuses with rc 1, "este head NO tiene un smoke canonico COMMITEADO …". My round-1 repro re-run on this head (committed docs/route.sh plus suite_relpath pointing at it, no smoke anywhere in the tree) gives rc 1 with that sentence and lists the run as evidencia (no suficiente); the verdict no longer contains any suite@commit form. The neighbouring shape is also closed: a head that DOES carry the smoke, plus a second committed suite that actually ran, gives rc 1 and the substitute is evidence only. Forging the field is not available either — suite_relpath sits inside the hashed text (chain recalculated from zero) and is now read only for evidence prose; no blob lookup hangs off it.

Attack results, beyond the two findings:

  • The accepted class, exactly: a candidate line for the requested head (string equality with the argument, chain recomputed from genesis) with via=run, tree_clean=true, tree_clean_after=true, result=OK, rc=0, tests_failed=0, integer tests_run>0, output capture present and matching in sha256, size and first line (head=, suite_sha256=, plus class when declared), result copies matching when declared, and script_sha256 equal to the blob of the constant relpath at H. ok_line is assigned in exactly one place, under [[ -n $anchor ]]; nothing else returns 0. Nothing I could construct inside the unit surface passes outside that class.
  • Env: EMU_PROOF_SMOKE_RELPATH is a plain assignment (not ${VAR:-}) and is never re-assigned. EMU_PROOF_SMOKE_RELPATH=evil/smoke.sh at check time changes nothing: the verdict still names the constant path and still gives rc 1 for a head that lacks it.
  • Rev syntax: HEAD:…, :…, :/nm, HEAD^{tree}, HEAD: are all refused (rc 2 through the ^{commit} validation). The relpath spliced into $rev:$rel is a constant literal, so there is nothing to inject there; a Unicode-variant path in the tree is simply a different path and can never become the anchor.
  • The mounted-file signal, both directions: canonical line plus --script on a different file → rc 0 with montado=DISTINTO; head with no line plus --script equal to the commit's blob → rc 1; --script on a nonexistent path → verdict unchanged. Prose only, exactly as documented.

Concerns

  • S2-C1 (MODERATE, hardening — not a reopening of B1): a SYMLINK at the canonical path anchors the gate. git_path_type() maps mode 120000 to blob and cat-file -t answers blob for a symlink, so a head can commit …/gits-sbx-emu-smoke.sh as a symlink whose link text is a runnable one-liner; a run of a /tmp file holding those exact bytes then gives PROOF: OK … ancla=smoke@commit ancla_blob=61c1e244f154…, rc 0, tests_run=2. It is byte-honest — the bytes executed ARE the bytes committed — so no different suite gets through, and the shape is visible in the reviewed diff; that is why I am not filing it against this delta. Still: a checkout of that head holds a dangling symlink, not a suite. One-line hardening: gate on the ls-tree mode being 100644/100755 rather than on cat-file -t.
  • S2-C2 (LOW, mostly pre-existing): sha256('') is still an acceptable anchor VALUE where the head genuinely commits a zero-byte blob at the constant path — measured rc 0 with ancla_blob=e3b0c44298fc…, a zero-byte suite, and tests_run supplied. The supply is the pre-existing part: xml_scan counts any XML under <module>/build/… with mtime >= start, which anything with write access to the gitignored build dir can stage — the residual limit the tool documents. So the empty-hash path is closed where it mattered (non-blob objects) and what remains is the honest state of an empty blob, loud in the diff.
  • S2-C3 (nit, diagnostic): when a line carries the anchor sha but fails another requirement, the added summary reason still reads "ninguna linea OK lleva ESE script_sha256", which reads oddly next to the per-line failure that names the real cause (seen in the zero-byte probe).
  • A4 (round-1 operator decision; my vote unchanged): a FAIL line does not veto a head that also carries a later canonical OK — re-confirmed here (rc 0, linea=2). Live behaves the same way, so this delta introduces no strictness regression and the decision belongs to the description; worth stating in docs/gits-sbx/run-proof.md as round-1 A4 asked. The passing branch also still drops the FAIL lines from the verdict text, so a reader cannot see that the head had red runs.
  • Expected, not a finding: heads already recorded in the live ledger (e.g. 26cd6d87…) read red until re-run against the new anchor. Fail-closed, and it is stated in the doc and in the description.
**Verdict of record — ld-reviewer — head 244b1bf3576e2f15dddd88b04424ee527ab8a551: APPROVE** model: deepseek-flash session: s2-2682-r2-337cad96 head: 244b1bf3576e2f15dddd88b04424ee527ab8a551 Independent seat 2 of 2, ROUND 2 (adversarial), on the delta that answers the two round-1 findings. Everything below was measured in /tmp: throwaway git repos plus the tool taken from this head. No pod was touched; no live ledger was read or written. Evidence, solo: `bash scripts/gits-sbx-epic-test.sh` → **645 checks, 640 PASS, 5 FAIL, 6 SKIP**. The five reds are exactly the known ones of other tickets (`probe uses an EMPTY pre-existing --dir`, `manifest[2]`, `manifest[3]`, `manifest[3b]`, `T7 emulador dentro del pod`); the timing case the description counted as the sixth did not reproduce in this solo run. §25 (el ancla) + §24 + §22b: 49 checks, 0 FAIL, and `25[HOMELAB-2682] EL ANCLA (falla:)` is green. CI on this head: runs #3219, #3220, #3221 — all pass. ## Blockers None. ## Resolutions - resolved B1: the type gate holds. `git_path_type()` answers before anything is hashed and `check` stops with rc 2 when the object at `core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh` is not a blob. Measured on this head: a DIRECTORY at the path (`cat-file -t` → `tree`) → rc 2 naming 'tree'; a GITLINK built with `update-index --cacheinfo 160000` → rc 2; a gitlink whose commit is absent from the object DB → the `ls-tree` fallback names 'gitlink' and it is still rc 2, not the "absent" verdict. There is no longer any route that puts the sha256 of the empty stream into `smoke_sha` without a committed blob at that path, and the `cat-file blob` read is now behind the type check instead of being the check. - resolved B2: the fallback is gone. With no canonical path at H the verifier computes no anchor at all and refuses with rc 1, "este head NO tiene un smoke canonico COMMITEADO …". My round-1 repro re-run on this head (committed `docs/route.sh` plus `suite_relpath` pointing at it, no smoke anywhere in the tree) gives rc 1 with that sentence and lists the run as `evidencia (no suficiente)`; the verdict no longer contains any `suite@commit` form. The neighbouring shape is also closed: a head that DOES carry the smoke, plus a second committed suite that actually ran, gives rc 1 and the substitute is evidence only. Forging the field is not available either — `suite_relpath` sits inside the hashed text (chain recalculated from zero) and is now read only for evidence prose; no blob lookup hangs off it. Attack results, beyond the two findings: - The accepted class, exactly: a candidate line for the requested head (string equality with the argument, chain recomputed from genesis) with `via=run`, `tree_clean=true`, `tree_clean_after=true`, `result=OK`, `rc=0`, `tests_failed=0`, integer `tests_run>0`, output capture present and matching in sha256, size and first line (`head=`, `suite_sha256=`, plus `class` when declared), result copies matching when declared, **and** `script_sha256` equal to the blob of the constant relpath at H. `ok_line` is assigned in exactly one place, under `[[ -n $anchor ]]`; nothing else returns 0. Nothing I could construct inside the unit surface passes outside that class. - Env: `EMU_PROOF_SMOKE_RELPATH` is a plain assignment (not `${VAR:-}`) and is never re-assigned. `EMU_PROOF_SMOKE_RELPATH=evil/smoke.sh` at check time changes nothing: the verdict still names the constant path and still gives rc 1 for a head that lacks it. - Rev syntax: `HEAD:…`, `:…`, `:/nm`, `HEAD^{tree}`, `HEAD:` are all refused (rc 2 through the `^{commit}` validation). The relpath spliced into `$rev:$rel` is a constant literal, so there is nothing to inject there; a Unicode-variant path in the tree is simply a different path and can never become the anchor. - The mounted-file signal, both directions: canonical line plus `--script` on a different file → rc 0 with `montado=DISTINTO`; head with no line plus `--script` equal to the commit's blob → rc 1; `--script` on a nonexistent path → verdict unchanged. Prose only, exactly as documented. ## Concerns - S2-C1 (MODERATE, hardening — not a reopening of B1): a SYMLINK at the canonical path anchors the gate. `git_path_type()` maps mode 120000 to `blob` and `cat-file -t` answers `blob` for a symlink, so a head can commit `…/gits-sbx-emu-smoke.sh` as a symlink whose link text is a runnable one-liner; a `run` of a /tmp file holding those exact bytes then gives `PROOF: OK … ancla=smoke@commit ancla_blob=61c1e244f154…`, rc 0, `tests_run=2`. It is byte-honest — the bytes executed ARE the bytes committed — so no different suite gets through, and the shape is visible in the reviewed diff; that is why I am not filing it against this delta. Still: a checkout of that head holds a dangling symlink, not a suite. One-line hardening: gate on the `ls-tree` mode being 100644/100755 rather than on `cat-file -t`. - S2-C2 (LOW, mostly pre-existing): `sha256('')` is still an acceptable anchor VALUE where the head genuinely commits a zero-byte blob at the constant path — measured rc 0 with `ancla_blob=e3b0c44298fc…`, a zero-byte suite, and `tests_run` supplied. The supply is the pre-existing part: `xml_scan` counts any XML under `<module>/build/…` with mtime >= start, which anything with write access to the gitignored build dir can stage — the residual limit the tool documents. So the empty-hash path is closed where it mattered (non-blob objects) and what remains is the honest state of an empty blob, loud in the diff. - S2-C3 (nit, diagnostic): when a line carries the anchor sha but fails another requirement, the added summary reason still reads "ninguna linea OK lleva ESE script_sha256", which reads oddly next to the per-line failure that names the real cause (seen in the zero-byte probe). - A4 (round-1 operator decision; my vote unchanged): a FAIL line does not veto a head that also carries a later canonical OK — re-confirmed here (rc 0, `linea=2`). Live behaves the same way, so this delta introduces no strictness regression and the decision belongs to the description; worth stating in docs/gits-sbx/run-proof.md as round-1 A4 asked. The passing branch also still drops the FAIL lines from the verdict text, so a reader cannot see that the head had red runs. - Expected, not a finding: heads already recorded in the live ledger (e.g. `26cd6d87…`) read red until re-run against the new anchor. Fail-closed, and it is stated in the doc and in the description.
Collaborator

Verdict of record — ld-reviewer — head 244b1bf357: REQUEST_CHANGES
model: deepseek-flash
session: s1-2682-r2-11490272

head: 244b1bf357

Seat 1 of 2, round 2. Every probe below ran in /tmp against throwaway repos with the tool taken from f4ee5aaf and from this head; nothing was executed in the pod and no PR was touched (the live ledger was only read, read-only).

Resolutions

  • resolved B1: verified — seat-1's repro (a DIRECTORY standing at core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh in the head, run --suite a zero-byte file, pre-staged future-mtime XML) now gives rc 2: "el smoke canonico '...' EXISTE en <head> pero es un 'tree', NO un blob ... No compruebo nada (fail-closed)". Control on f4ee5aaf with the same repo and ledger gave rc 0 with ancla=smoke@commit and the empty-stream sha256. git_path_type answers tree for that path and a mode-160000 entry answers gitlink; git_blob_sha returns 3 in both (no hash printed).
  • resolved B2: verified — app-style head (no core/manifests/... in any commit) with a committed docs/alt-suite.sh that writes a fake testcase XML and exits 0: on f4ee5aaf rc 0 "ancla=suite@commit:docs/alt-suite.sh tests_run=3"; on this head rc 1 "este head NO tiene un smoke canonico COMMITEADO en ... NO puede pasar este head ... NO se admite una suite sustituta, ni commiteada; B2", with that line demoted to "evidencia (no suficiente)". The suite@commit branch is gone from the code and I could not reach a pass through any committed script.

Blockers

  • B3: high — B1's root cause is only half closed: the sha256 of the EMPTY stream is still accepted as the anchor whenever cat-file blob yields no bytes, so the gate still prints ancla=smoke@commit with an anchor of e3b0c44298fc... and nothing was executed. git_blob_sha (tools/gits-sbx-emu-proof.sh:448-458) now requires the TYPE to be blob, but git_path_type also answers blob by INFERENCE from the ls-tree mode when cat-file -t cannot read the object, and the only post-hash guard is the 64-character length test — which the empty-stream hash passes. Two reachable shapes, both measured at THIS head, rc 0 both times: (a) no object-store tampering at all — a head that commits a ZERO-BYTE file at the canonical path (cat-file -s = 0) plus run --suite a zero-byte script gives "PROOF: OK ... ancla=smoke@commit ancla_blob=e3b0c44298fc... montado=no-legible tests_run=2" rc 0, a pass whose anchor is the sha256 of the empty string, which is exactly the value round-1 B1 named; (b) delete the loose object of the canonical blob in the worker's own checkout (rm .git/objects//; cat-file -t then says could not get object info while ls-tree still lists 100644 blob), git_path_type answers blob, the pipe is empty and check prints "PROOF: OK ... ancla=smoke@commit ancla_blob=e3b0c44298fc... montado=no-legible" rc 0 while the real smoke blob hashes 2d1401d2b553... and was never run. Case (b) is the one the function's own comment worries about ("la entrada EXISTE en el arbol pero su objeto no esta en el object DB") and the smoke_rc != 0 die branch at cmd_check was written for; that branch is dead code, because git_blob_sha returns 0 with a 64-character hash. Net effect: a head can still make the gate assert that a suite it never ran is its canonical suite, which is the invariant this ticket exists to enforce. Suggested close (it fixes (a) and (b) together and makes the existing die branch reachable): reject an empty cat-file blob stream and a zero-byte anchor blob — for instance compare git cat-file -s ":" with the byte count actually piped, or refuse a hash equal to e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 — returning rc 1 so cmd_check fails closed. Harness coverage today has only the tree and gitlink instances (25[HOMELAB-2682] (f3)/(f4)); a case for the empty and unreadable anchor belongs beside them so the proof can fail.

Concerns

  • C1: after this change the GITS rung-2 checkout cannot pass at all — /home/ld/work/epics/gits-e3/wt/j1 carries no core/manifests/... in any commit, so check now answers rc 1 on every head there. That is the right posture and the description escalates it as operator decision (a)/(b), but it means this PR does not unblock rung 2 by itself: the fresh run the description promises gets refused for the same reason until (a) or (b) lands.
  • C2: git_path_type names a blob from the tree MODE when the object cannot be read, so the type is right about the entry and silent about readability, and the caller treats blob as hashable. Letting the caller tell "type verified from the object" apart from "type inferred from the mode" (or naming it, e.g. blob-sin-objeto) would keep the true reason in the message, which is what the function's comment sets out to do.
  • C3: git_path_type returns empty (rc 1, i.e. the B2 verdict) for a trailing-slash path, an empty rev and a rev that is not a commit. All three fold into rc 1 instead of the hard rc 2, but none is reachable from cmd_check (the commit is validated first and the canonical relpath is a constant without a trailing slash), so this is a note, not a defect.
  • C4: the A1 signal prints, verified as asked. The OK line carries ancla_blob=... montado=IGUAL|DISTINTO|no-legible. The same head with the mounted key refreshed (what the kubelet does) stays rc 0 and prints "SECUNDARIO ... NO es la compuerta"; a head that rewrote the committed smoke shows a different ancla_blob and montado=DISTINTO. The live defect this ticket is about is fixed on that evidence.

Non-regression (independent of 25(g))

  • chain_text in the old and the new tool is byte-identical (empty diff of the extracted function).
  • The LIVE ledger, read read-only from sbx-gits-0 (/home/ld/work/.emu-proof/ledger.jsonl, 26 lines — it has grown past the 21 the description names): all 26 recompute their stored hash under the new chain_text, 0 mismatches; 4 lines carry results_sha256, 0 carry suite_relpath, 0 carry class.
  • Real rung-2 shape: a head WITH the canonical path plus a committed route suite. run twice (route suite, then the mounted smoke byte-identical to the blob), then check: rc 0, "ancla=smoke@commit ancla_blob=5e8a4e100422... montado=IGUAL", and the route line prints as "evidencia (no suficiente)". A line of another suite neither completes nor sinks the head.
  • Harness solo (bash scripts/gits-sbx-epic-test.sh, no concurrency): 645 checks, 640 PASS, 5 FAIL, 6 SKIP. The 5 are the known pre-existing reds (probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod); the timing case the description flags passed in this run. T7[diff] 5 PASS / 0 FAIL (both red mutations bite), 17[R159] 29 PASS / 0 FAIL, 25[HOMELAB-2682] 24 PASS / 0 FAIL, 22[R176b] 90 PASS / 0 FAIL.
  • FAIL semantics: not re-litigated here (out of scope, agreed by both round-1 seats).
**Verdict of record — ld-reviewer — head 244b1bf3576e2f15dddd88b04424ee527ab8a551: REQUEST_CHANGES** model: deepseek-flash session: s1-2682-r2-11490272 head: 244b1bf3576e2f15dddd88b04424ee527ab8a551 Seat 1 of 2, round 2. Every probe below ran in /tmp against throwaway repos with the tool taken from f4ee5aaf and from this head; nothing was executed in the pod and no PR was touched (the live ledger was only read, read-only). ## Resolutions - resolved B1: verified — seat-1's repro (a DIRECTORY standing at core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh in the head, run --suite a zero-byte file, pre-staged future-mtime XML) now gives rc 2: "el smoke canonico '...' EXISTE en <head> pero es un 'tree', NO un blob ... No compruebo nada (fail-closed)". Control on f4ee5aaf with the same repo and ledger gave rc 0 with ancla=smoke@commit and the empty-stream sha256. git_path_type answers tree for that path and a mode-160000 entry answers gitlink; git_blob_sha returns 3 in both (no hash printed). - resolved B2: verified — app-style head (no core/manifests/... in any commit) with a committed docs/alt-suite.sh that writes a fake testcase XML and exits 0: on f4ee5aaf rc 0 "ancla=suite@commit:docs/alt-suite.sh tests_run=3"; on this head rc 1 "este head NO tiene un smoke canonico COMMITEADO en ... NO puede pasar este head ... NO se admite una suite sustituta, ni commiteada; B2", with that line demoted to "evidencia (no suficiente)". The suite@commit branch is gone from the code and I could not reach a pass through any committed script. ## Blockers - B3: high — B1's root cause is only half closed: the sha256 of the EMPTY stream is still accepted as the anchor whenever cat-file blob yields no bytes, so the gate still prints ancla=smoke@commit with an anchor of e3b0c44298fc... and nothing was executed. git_blob_sha (tools/gits-sbx-emu-proof.sh:448-458) now requires the TYPE to be blob, but git_path_type also answers blob by INFERENCE from the ls-tree mode when cat-file -t cannot read the object, and the only post-hash guard is the 64-character length test — which the empty-stream hash passes. Two reachable shapes, both measured at THIS head, rc 0 both times: (a) no object-store tampering at all — a head that commits a ZERO-BYTE file at the canonical path (cat-file -s = 0) plus run --suite a zero-byte script gives "PROOF: OK ... ancla=smoke@commit ancla_blob=e3b0c44298fc... montado=no-legible tests_run=2" rc 0, a pass whose anchor is the sha256 of the empty string, which is exactly the value round-1 B1 named; (b) delete the loose object of the canonical blob in the worker's own checkout (rm .git/objects/<xx>/<rest>; cat-file -t then says could not get object info while ls-tree still lists 100644 blob), git_path_type answers blob, the pipe is empty and check prints "PROOF: OK ... ancla=smoke@commit ancla_blob=e3b0c44298fc... montado=no-legible" rc 0 while the real smoke blob hashes 2d1401d2b553... and was never run. Case (b) is the one the function's own comment worries about ("la entrada EXISTE en el arbol pero su objeto no esta en el object DB") and the smoke_rc != 0 die branch at cmd_check was written for; that branch is dead code, because git_blob_sha returns 0 with a 64-character hash. Net effect: a head can still make the gate assert that a suite it never ran is its canonical suite, which is the invariant this ticket exists to enforce. Suggested close (it fixes (a) and (b) together and makes the existing die branch reachable): reject an empty cat-file blob stream and a zero-byte anchor blob — for instance compare git cat-file -s "<rev>:<rel>" with the byte count actually piped, or refuse a hash equal to e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 — returning rc 1 so cmd_check fails closed. Harness coverage today has only the tree and gitlink instances (25[HOMELAB-2682] (f3)/(f4)); a case for the empty and unreadable anchor belongs beside them so the proof can fail. ## Concerns - C1: after this change the GITS rung-2 checkout cannot pass at all — /home/ld/work/epics/gits-e3/wt/j1 carries no core/manifests/... in any commit, so check now answers rc 1 on every head there. That is the right posture and the description escalates it as operator decision (a)/(b), but it means this PR does not unblock rung 2 by itself: the fresh run the description promises gets refused for the same reason until (a) or (b) lands. - C2: git_path_type names a blob from the tree MODE when the object cannot be read, so the type is right about the entry and silent about readability, and the caller treats blob as hashable. Letting the caller tell "type verified from the object" apart from "type inferred from the mode" (or naming it, e.g. blob-sin-objeto) would keep the true reason in the message, which is what the function's comment sets out to do. - C3: git_path_type returns empty (rc 1, i.e. the B2 verdict) for a trailing-slash path, an empty rev and a rev that is not a commit. All three fold into rc 1 instead of the hard rc 2, but none is reachable from cmd_check (the commit is validated first and the canonical relpath is a constant without a trailing slash), so this is a note, not a defect. - C4: the A1 signal prints, verified as asked. The OK line carries ancla_blob=<short sha>... montado=IGUAL|DISTINTO|no-legible. The same head with the mounted key refreshed (what the kubelet does) stays rc 0 and prints "SECUNDARIO ... NO es la compuerta"; a head that rewrote the committed smoke shows a different ancla_blob and montado=DISTINTO. The live defect this ticket is about is fixed on that evidence. ## Non-regression (independent of 25(g)) - chain_text in the old and the new tool is byte-identical (empty diff of the extracted function). - The LIVE ledger, read read-only from sbx-gits-0 (/home/ld/work/.emu-proof/ledger.jsonl, 26 lines — it has grown past the 21 the description names): all 26 recompute their stored hash under the new chain_text, 0 mismatches; 4 lines carry results_sha256, 0 carry suite_relpath, 0 carry class. - Real rung-2 shape: a head WITH the canonical path plus a committed route suite. run twice (route suite, then the mounted smoke byte-identical to the blob), then check: rc 0, "ancla=smoke@commit ancla_blob=5e8a4e100422... montado=IGUAL", and the route line prints as "evidencia (no suficiente)". A line of another suite neither completes nor sinks the head. - Harness solo (bash scripts/gits-sbx-epic-test.sh, no concurrency): 645 checks, 640 PASS, 5 FAIL, 6 SKIP. The 5 are the known pre-existing reds (probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod); the timing case the description flags passed in this run. T7[diff] 5 PASS / 0 FAIL (both red mutations bite), 17[R159] 29 PASS / 0 FAIL, 25[HOMELAB-2682] 24 PASS / 0 FAIL, 22[R176b] 90 PASS / 0 FAIL. - FAIL semantics: not re-litigated here (out of scope, agreed by both round-1 seats).
HOMELAB-2682: fix(sbx-gits): proof check rejects empty/unreadable anchor blobs
All checks were successful
CI Review / runner-manifest-test (pull_request) Successful in 4s
CI Review / receiver-test (pull_request) Successful in 5s
Build Lab Director Cockpit Image / preflight (pull_request) Successful in 4s
CI Review / helm-validate (pull_request) Successful in 11s
Lint & Validate / shellcheck (pull_request) Successful in 6s
CI Review / alert-receiver-test (pull_request) Successful in 21s
Lint & Validate / yaml-lint (pull_request) Successful in 19s
CI Review / record-sql-test (pull_request) Successful in 1m2s
Lint & Validate / terraform-validate (pull_request) Successful in 1m55s
Build Lab Director Cockpit Image / build-and-push (pull_request) Successful in 1m58s
CI Review / dispatcher-test (pull_request) Successful in 2m38s
CI Review / cockpit-test (pull_request) Successful in 2m38s
CI Review / cockpit-e2e (pull_request) Successful in 3m15s
37202e75ad
Close the empty-stream sha256 (e3b0c442..., which is also the sha256 of a
zero-byte suite) as the anchor of `check`, round-2A blocker B3. Two reachable
rc-0 shapes at 244b1bf3, both reproduced first in /tmp:

  (a) a head committing a ZERO-BYTE file at the canonical smoke path. The type
      gate answers blob legitimately, `cat-file blob` prints zero bytes by
      right, and the line of a zero-byte `run --suite` carries that very hash,
      so the check printed `ancla=smoke@commit` with tests_run=2 having executed
      nothing;
  (b) the anchor object MISSING from the checkout's object DB (loose object
      removed, half clone): `cat-file -t` fails, git_path_type answers 'blob' by
      INFERENCE from the ls-tree mode, `cat-file blob` leaves the pipe empty and
      the empty hash anchors a pass while the real smoke was never run. This is
      the shape the `smoke_rc != 0` branch was written for: it was dead code,
      because git_blob_sha returned 0 with a 64-character hash.

The fix is byte accounting in git_blob_sha: the object size declared by
`git cat-file -s` is compared with the bytes actually read; zero size or a
mismatch is rc 1 (fail-closed), the empty-string sha256 is refused by name as a
sentinel, and `check` stops with rc 1 naming the anchor path, the declared size
and the bytes read (die1: a verdict on the head, unlike die's rc 2 "cannot even
look"). The blob is still hashed straight through the pipe so a file ending in a
newline keeps its exact sha256. B1 (tree/gitlink -> rc 2) and B2 (no canonical
smoke -> rc 1, no substitute anchor, evidence-only foreign suites) are
unchanged and still bite.

Harness: §25 gains (f6) zero-byte committed smoke -> rc 1, and (f7) loose object
removed after the run -> rc 1 (with a fixture assertion that the deletion really
left the object unreadable, so the case cannot pass vacuously). Both fixtures
were rc 0 before this commit. §25 green, T7[diff] and 17[R159] green.
HOMELAB-2682: fix(sbx-gits): the verdict says WHICH suite passed — smoke vs other suites
All checks were successful
CI Review / runner-manifest-test (pull_request) Successful in 5s
CI Review / helm-validate (pull_request) Successful in 10s
CI Review / receiver-test (pull_request) Successful in 5s
Build Lab Director Cockpit Image / preflight (pull_request) Successful in 4s
Lint & Validate / shellcheck (pull_request) Successful in 5s
CI Review / alert-receiver-test (pull_request) Successful in 20s
Lint & Validate / yaml-lint (pull_request) Successful in 18s
CI Review / record-sql-test (pull_request) Successful in 47s
Build Lab Director Cockpit Image / build-and-push (pull_request) Successful in 1m56s
Lint & Validate / terraform-validate (pull_request) Successful in 2m12s
CI Review / cockpit-test (pull_request) Successful in 2m54s
CI Review / dispatcher-test (pull_request) Successful in 3m26s
CI Review / cockpit-e2e (pull_request) Successful in 3m55s
0d928e8848
GITS is moving the route test out of the default instrumentation into its own
suite (the smoke cannot pass it: test-server details), so a verdict that only
says "OK" lets the two reads blur. This pins the wording end to end, no
semantics changed:

- the pass line names the suite that passed: `suite=smoke
  smoke_rel=<canonical path> suite_relpath=<where the executed file lived|
  fuera-del-arbol> task=<task> class=<class|->` (task and class are the fields
  recorded on the line, #853; `class=-` for lines older than #853), and a second
  line states the verdict is the CANONICAL SMOKE's run and that no other suite —
  the route suite included — can pass the head or be read as if the smoke ran;
- a line executed from a COPY of the smoke elsewhere in the tree says so: it is
  the smoke BY CONTENT (same sha256), not another suite;
- other runs of the same head are listed explicitly as `otras corridas de este
  head en el ledger (NO son el smoke canonico, NO lo prueban)`, so a reader
  never has to guess whether a suite_sha256 is the smoke in red or the route
  suite;
- every non-smoke OK line is labelled `OTRA SUITE (NO es el smoke canonico) …
  el ancla es SOLO el smoke canonico commiteado` and every broken non-smoke line
  `suite: NO es el smoke canonico (…) — EVIDENCIA, no prueba de la compuerta`.

`--help` and docs/gits-sbx/run-proof.md carry the same wording (new section "El
SMOKE y las OTRAS suites", plus the B3 refusal in the message table). Harness
§25 gains (f8)/(f8b) pinning both halves: the pass line names the smoke with
task/class and lists the other run as not-the-smoke, and the refusal labels the
substitute as OTRA SUITE + evidence.

Harness SOLO on this head: 648 checks, 643 PASS, 5 FAIL, 6 SKIP — the five reds
are the known pre-existing #851 ones; §25 27 PASS/0 FAIL, §24 25, §22[R176b]
113, T7[diff] 3, 17[R159] 29, all green.
Collaborator

Verdict of record — ld-reviewer — head 0d928e8848: REQUEST_CHANGES
model: deepseek-flash
session: s2-2682-r3-033a7215

head: 0d928e8848

Independent seat 2 of 2, ROUND 3 (adversarial), on the two commits of this round (37202e7 byte accounting, 0d928e8 the smoke-vs-suite wording). Everything below was measured in /tmp against throwaway git repos with the tool taken from this head; no pod was touched, no real ledger was read or written. The two findings are in the NEW output of 0d928e8 — B3 itself is closed, and I could not reopen B1/B2.

Harness, solo, no concurrency: bash scripts/gits-sbx-epic-test.sh → 648 checks, 643 PASS, 5 FAIL, 6 SKIP. The 5 reds are exactly the known ones of other tickets (probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod); §25[HOMELAB-2682] is 27/27 PASS, §24 green. CI on this head: runs #3225, #3226, #3227 — all pass.

Blockers

  • B4: high — the task= on the verdict line is NOT the task of the line that passed: it is the task of the LAST line in the ledger. task is read once per ledger line in the chain loop (gits-sbx-emu-proof.sh:1109) and is never re-read inside the candidate loop; the OK branch prints ... suite=smoke ... task=$task ... (line 1340) with whatever the last line left behind, so ok_line's own task is never used (unlike ok_class/ok_rel, which do come from the OK candidate). Measured, honest runs, no forgery: one head carrying the canonical smoke plus a committed route suite; line 1 = run --suite <smoke> --task smoke:connectedDebugAndroidTest, line 2 = run --suite docs/route-suite.sh --task route:connectedAndroidTest; check <head> prints ... suite=smoke smoke_rel=<canonical> suite_relpath=<canonical> task=route:connectedAndroidTest ... linea=1. The verdict therefore asserts, in the words of this very delta, that the smoke passed AND that the run that passed was the ROUTE task — the exact reading the hypervisor split must forbid. It is not confined to one head: with the last line belonging to a DIFFERENT head the same field leaks across heads (check H1 on a two-line ledger printed task=route:app, the task of line 2, which is a line for H2). The reverse order hides it (when the smoke is the last line the field looks right), which is why the harness does not catch it. docs/gits-sbx/run-proof.md now states the opposite ("task y class … son los campos grabados en la línea"), so the doc is wrong too. Fix: capture ok_task=$(jstr "$oc" task) next to ok_class/ok_rel and print that in both the OK line and the PROOF suites: line; add a harness case with a foreign-suite line AFTER the canonical one on the same head and assert task= is the smoke line's.
  • B5: moderate — the "otras corridas" block labels a FAIL line OF THE CANONICAL SMOKE as "another suite". The new loop prints every candidate except ok_line as PROOF otras corridas de este head en el ledger (NO son el smoke canonico, NO lo prueban): linea N suite_sha256=… result=… via=… without ever comparing script_sha256 to the anchor, so a red run of the canonical smoke is asserted to be NOT the canonical smoke. Measured: one head, canonical blob that exits 5 unless an env var is set; line 1 FAIL, line 2 OK; both carry script_sha256=7c75c5358506… and the anchor blob hashes 7c75c5358506…; the verdict prints otras corridas … (NO son el smoke canonico, NO lo prueban): linea 1 suite_sha256=7c75c5358506… result=FAIL. The row's sha is literally the ancla_blob printed three lines above, so the verdict contradicts itself and a reader (or the hypervisor) is told the red run was some other suite when it was the canonical smoke failing. This is the C1 of round 1 coming back with a wrong label: the FAIL row must say it is the canonical smoke, and it is RED (PROOF aviso el smoke CANONICO tambien corrio en ROJO en este head: linea N … result=FAIL rc=…, does not change rc), and only a row whose sha differs from the anchor may be called "otra suite".

Resolutions

  • resolved B3: verified closed, first-hand, on this head. (a) a ZERO-BYTE file committed at the canonical path, plus run --suite a zero-byte script → ERROR … el objeto DECLARA 0 bytes y la lectura devolvio 0 bytes (rc=1), rc 1, no ancla= line at all; (b) the loose object of the anchor blob removed from the checkout's object DB (cat-file -t then answers could not get object info while ls-tree still says 100644 blob) → ERROR … DECLARA ? bytes y la lectura devolvio 0 bytes, rc 1. Both were rc 0 at 244b1bf. The accounting is the thing that closes them: cat-file -s is compared with the bytes actually piped, zero or a mismatch is rc 1, and the e3b0c442… sentinel is a third, independent net.

Concerns

  • C1: the byte accounting binds the anchor's SIZE to the commit, not its CONTENT, because the hash is a third, separate cat-file blob read. A blob of the SAME declared size swapped in between the count and the hash read passes every check. Measured deterministically (no shim: run --suite a real fake that differs from the committed smoke by one byte in a comment, same byte length, so cat-file -s is unchanged; then the loose object at the committed blob's oid is rewritten in place, chmod 444, with the fake's content at the same size): check <head> prints PROOF: OK … ancla=smoke@commit ancla_blob=8b4c4f2af5ea... suite_relpath=fuera-del-arbol ... rc 0, while the tree still points at oid 2a61e7cb31eb… whose committed content hashes 3d191bf7c4e5… — the fake was never committed and did not run under any path of the tree; the same ledger without the swap is rc 1 (control). This is NOT B3's shape (it needs a writer inside the checkout's object DB, the same trust-root freedom as the accepted A2), but it defeats the one mitigation A1 named (the smoke is inside the pinned T7 diff set, so an honest review sees it): the reviewed diff shows the real smoke and the gate anchors on something else. It is also cheap to close and I would close it now: bind the content to the tree, [[ $(git -C "$tree" rev-parse "$rev:$rel") == $(git -C "$tree" cat-file blob "$rev:$rel" | git -C "$tree" hash-object -t blob --stdin) ]], or compare against the oid that ls-tree already returns in git_path_type.
  • C2: the -s and count reads are independent, so cat-file -s failing AFTER the type gate is answered by the || return 1 (rc 1, measured: DECLARA ? bytes y la lectura devolvio 351 bytes, fail-closed, no crash), a declared size in the 20-digit range is refused by the arithmetic (DECLARA 99999999999999999999 bytes, rc 1, no (( )) abort), truncating the COUNT read is refused (bytes<size) and truncating the HASH read is refused further down (the sha stops being 64 chars / stops matching the line). A partial pipe read cannot pass unnoticed: wc -c cannot undercount a stream that git produced in full, and undercounting refuses. Honest residue: none of these are reachable in a quiescent object store, and they are all fail-closed.
  • C3: a concurrent git gc --prune=now + git repack -adq loop hammering the object store while check ran 40 times (content immutable, storage moves): 40/40 rc 0 with the correct anchor, 0 false passes, 0 spurious refusals. gc does not touch content, so the accounting survives it; the only lossy case is a content swap (C1), not a repack.
  • C4: tests_run on the smoke line can include another suite's XML. Pre-existing, not introduced here, but it matters for this split: with the route suite having run in the same second in the same worktree, the smoke's own run counted 9 tests (7 route + 2 smoke) because the find -newermt @since filter has one-second granularity, and check faithfully printed tests_run=9. Nothing false is printed (it is the line's number), but a reader takes 9 as the smoke's test count. Worth a harness case with a sleep or a -newermt on nanosecond mtimes.
  • C5: cosmetic — the added comment block has two comment lines joined by a swallowed newline (# OTRAS CORRIDAS … El # veredicto no depende …, line 1355), so one line of the explanation is glued mid-sentence. Harmless to bash; ugly in a file whose comments are the contract.

Attack results, for the record (beyond B3)

  • B1 stays closed on both shapes: a DIRECTORY at the canonical path → rc 2 … EXISTE en … pero es un 'tree', NO un blob …; a GITLINK built with update-index --cacheinfo 160000 to a commit absent from the object DB → rc 2 … pero es un 'gitlink' …. Neither reaches the byte accounting.
  • B2 stays closed: app-style head (no canonical path in any commit) with a committed docs/alt-suite.sh that writes fake XML and exits 0 → rc 1 este head NO tiene un smoke canonico COMMITEADO … NO se admite una suite sustituta, and that run is listed as OTRA SUITE (NO es el smoke canonico) … evidencia. No suite@commit form survives in the code or in the output.
  • Distinction, the good half, verified: a foreign suite whose FILENAME contains smoke (docs/tools-smoke-route.sh), run and committed, is always printed as OTRA SUITE (NO es el smoke canonico) … esta corrida NO prueba el head … el ancla es SOLO el smoke canonico commiteado — the wording holds against the name trick. A byte-identical COPY of the smoke at another path is correctly reported as la suite EJECUTADA vivio en <otra ruta> y es el smoke POR CONTENIDO (mismo sha256 que <ruta canonica>): NO es otra suite (ni la de route). A canonical smoke launched with --task route:… prints suite=smoke … task=route:…, which is honest (that is what was declared) and reads as the smoke because suite=smoke/suite_relpath name it; that case is fine — B4 is the one where the field is simply another line's.
  • The mounted file never becomes a criterion: --script on a different file with a canonical line → rc 0 with montado=DISTINTO and the SECUNDARIO … NO es la compuerta note; --script equal to the commit's blob with no canonical line → rc 1.
  • Env: EMU_PROOF_SMOKE_RELPATH is a literal assignment, and its value is spliced into $rev:$rel — a head that lacks the constant path refuses (B2) regardless of what the env says, so nothing there is injectable.

Nothing I could construct passes the gate outside the accepted class (a line for the requested head, chain recomputed from genesis, via=run, tree_clean/tree_clean_after true, result=OK, rc=0, tests_failed=0, integer tests_run>0, capture and results binding intact, script_sha256 equal to the blob of the constant path at H). B4 and B5 do not let a wrong head pass — they make the verdict say untrue things about WHICH run passed, which is the whole point of 0d928e8 and is why this round is red rather than green. Both are a handful of lines in the same block.

**Verdict of record — ld-reviewer — head 0d928e8848b41c7329ee78552e531aa5bbebe0dd: REQUEST_CHANGES** model: deepseek-flash session: s2-2682-r3-033a7215 head: 0d928e8848b41c7329ee78552e531aa5bbebe0dd Independent seat 2 of 2, ROUND 3 (adversarial), on the two commits of this round (37202e7 byte accounting, 0d928e8 the smoke-vs-suite wording). Everything below was measured in /tmp against throwaway git repos with the tool taken from this head; no pod was touched, no real ledger was read or written. The two findings are in the NEW output of 0d928e8 — B3 itself is closed, and I could not reopen B1/B2. Harness, solo, no concurrency: `bash scripts/gits-sbx-epic-test.sh` → **648 checks, 643 PASS, 5 FAIL, 6 SKIP**. The 5 reds are exactly the known ones of other tickets (`probe uses an EMPTY pre-existing --dir`, `manifest[2]`, `manifest[3]`, `manifest[3b]`, `T7 emulador dentro del pod`); §25[HOMELAB-2682] is 27/27 PASS, §24 green. CI on this head: runs #3225, #3226, #3227 — all pass. ## Blockers - B4: high — the `task=` on the verdict line is NOT the task of the line that passed: it is the task of the LAST line in the ledger. `task` is read once per ledger line in the chain loop (gits-sbx-emu-proof.sh:1109) and is never re-read inside the candidate loop; the OK branch prints `... suite=smoke ... task=$task ...` (line 1340) with whatever the last line left behind, so `ok_line`'s own task is never used (unlike `ok_class`/`ok_rel`, which do come from the OK candidate). Measured, honest runs, no forgery: one head carrying the canonical smoke plus a committed route suite; line 1 = `run --suite <smoke> --task smoke:connectedDebugAndroidTest`, line 2 = `run --suite docs/route-suite.sh --task route:connectedAndroidTest`; `check <head>` prints `... suite=smoke smoke_rel=<canonical> suite_relpath=<canonical> task=route:connectedAndroidTest ... linea=1`. The verdict therefore asserts, in the words of this very delta, that the smoke passed AND that the run that passed was the ROUTE task — the exact reading the hypervisor split must forbid. It is not confined to one head: with the last line belonging to a DIFFERENT head the same field leaks across heads (`check H1` on a two-line ledger printed `task=route:app`, the task of line 2, which is a line for H2). The reverse order hides it (when the smoke is the last line the field looks right), which is why the harness does not catch it. `docs/gits-sbx/run-proof.md` now states the opposite ("`task` y `class` … son los campos **grabados en la línea**"), so the doc is wrong too. Fix: capture `ok_task=$(jstr "$oc" task)` next to `ok_class`/`ok_rel` and print that in both the OK line and the `PROOF suites:` line; add a harness case with a foreign-suite line AFTER the canonical one on the same head and assert `task=` is the smoke line's. - B5: moderate — the "otras corridas" block labels a FAIL line OF THE CANONICAL SMOKE as "another suite". The new loop prints every candidate except `ok_line` as `PROOF otras corridas de este head en el ledger (NO son el smoke canonico, NO lo prueban): linea N suite_sha256=… result=… via=…` without ever comparing `script_sha256` to the anchor, so a red run of the canonical smoke is asserted to be NOT the canonical smoke. Measured: one head, canonical blob that exits 5 unless an env var is set; line 1 FAIL, line 2 OK; both carry `script_sha256=7c75c5358506…` and the anchor blob hashes `7c75c5358506…`; the verdict prints `otras corridas … (NO son el smoke canonico, NO lo prueban): linea 1 suite_sha256=7c75c5358506… result=FAIL`. The row's sha is literally the `ancla_blob` printed three lines above, so the verdict contradicts itself and a reader (or the hypervisor) is told the red run was some other suite when it was the canonical smoke failing. This is the C1 of round 1 coming back with a wrong label: the FAIL row must say **it is the canonical smoke, and it is RED** (`PROOF aviso el smoke CANONICO tambien corrio en ROJO en este head: linea N … result=FAIL rc=…`, does not change rc), and only a row whose sha differs from the anchor may be called "otra suite". ## Resolutions - resolved B3: verified closed, first-hand, on this head. (a) a ZERO-BYTE file committed at the canonical path, plus `run --suite` a zero-byte script → `ERROR … el objeto DECLARA 0 bytes y la lectura devolvio 0 bytes (rc=1)`, rc 1, no `ancla=` line at all; (b) the loose object of the anchor blob removed from the checkout's object DB (`cat-file -t` then answers `could not get object info` while `ls-tree` still says `100644 blob`) → `ERROR … DECLARA ? bytes y la lectura devolvio 0 bytes`, rc 1. Both were rc 0 at 244b1bf. The accounting is the thing that closes them: `cat-file -s` is compared with the bytes actually piped, zero or a mismatch is rc 1, and the `e3b0c442…` sentinel is a third, independent net. ## Concerns - C1: the byte accounting binds the anchor's SIZE to the commit, not its CONTENT, because the hash is a third, separate `cat-file blob` read. A blob of the SAME declared size swapped in between the count and the hash read passes every check. Measured deterministically (no shim: `run --suite` a real fake that differs from the committed smoke by one byte in a comment, same byte length, so `cat-file -s` is unchanged; then the loose object at the committed blob's oid is rewritten in place, chmod 444, with the fake's content at the same size): `check <head>` prints `PROOF: OK … ancla=smoke@commit ancla_blob=8b4c4f2af5ea... suite_relpath=fuera-del-arbol ...` rc 0, while the tree still points at oid 2a61e7cb31eb… whose committed content hashes `3d191bf7c4e5…` — the fake was never committed and did not run under any path of the tree; the same ledger without the swap is rc 1 (control). This is NOT B3's shape (it needs a writer inside the checkout's object DB, the same trust-root freedom as the accepted A2), but it defeats the one mitigation A1 named (the smoke is inside the pinned T7 diff set, so an honest review sees it): the reviewed diff shows the real smoke and the gate anchors on something else. It is also cheap to close and I would close it now: bind the content to the tree, `[[ $(git -C "$tree" rev-parse "$rev:$rel") == $(git -C "$tree" cat-file blob "$rev:$rel" | git -C "$tree" hash-object -t blob --stdin) ]]`, or compare against the oid that `ls-tree` already returns in `git_path_type`. - C2: the `-s` and count reads are independent, so `cat-file -s` failing AFTER the type gate is answered by the `|| return 1` (rc 1, measured: `DECLARA ? bytes y la lectura devolvio 351 bytes`, fail-closed, no crash), a declared size in the 20-digit range is refused by the arithmetic (`DECLARA 99999999999999999999 bytes`, rc 1, no `(( ))` abort), truncating the COUNT read is refused (bytes<size) and truncating the HASH read is refused further down (the sha stops being 64 chars / stops matching the line). A partial pipe read cannot pass unnoticed: `wc -c` cannot undercount a stream that git produced in full, and undercounting refuses. Honest residue: none of these are reachable in a quiescent object store, and they are all fail-closed. - C3: a concurrent `git gc --prune=now` + `git repack -adq` loop hammering the object store while `check` ran 40 times (content immutable, storage moves): **40/40 rc 0 with the correct anchor, 0 false passes, 0 spurious refusals**. gc does not touch content, so the accounting survives it; the only lossy case is a content swap (C1), not a repack. - C4: `tests_run` on the smoke line can include another suite's XML. Pre-existing, not introduced here, but it matters for this split: with the route suite having run in the same second in the same worktree, the smoke's own `run` counted **9** tests (7 route + 2 smoke) because the `find -newermt @since` filter has one-second granularity, and `check` faithfully printed `tests_run=9`. Nothing false is printed (it is the line's number), but a reader takes 9 as the smoke's test count. Worth a harness case with a sleep or a `-newermt` on nanosecond mtimes. - C5: cosmetic — the added comment block has two comment lines joined by a swallowed newline (`# OTRAS CORRIDAS … El # veredicto no depende …`, line 1355), so one line of the explanation is glued mid-sentence. Harmless to bash; ugly in a file whose comments are the contract. ## Attack results, for the record (beyond B3) - **B1** stays closed on both shapes: a DIRECTORY at the canonical path → rc 2 `… EXISTE en … pero es un 'tree', NO un blob …`; a GITLINK built with `update-index --cacheinfo 160000` to a commit absent from the object DB → rc 2 `… pero es un 'gitlink' …`. Neither reaches the byte accounting. - **B2** stays closed: app-style head (no canonical path in any commit) with a committed `docs/alt-suite.sh` that writes fake XML and exits 0 → rc 1 `este head NO tiene un smoke canonico COMMITEADO … NO se admite una suite sustituta`, and that run is listed as `OTRA SUITE (NO es el smoke canonico) … evidencia`. No `suite@commit` form survives in the code or in the output. - **Distinction, the good half, verified**: a foreign suite whose FILENAME contains `smoke` (`docs/tools-smoke-route.sh`), run and committed, is always printed as `OTRA SUITE (NO es el smoke canonico) … esta corrida NO prueba el head … el ancla es SOLO el smoke canonico commiteado` — the wording holds against the name trick. A byte-identical COPY of the smoke at another path is correctly reported as `la suite EJECUTADA vivio en <otra ruta> y es el smoke POR CONTENIDO (mismo sha256 que <ruta canonica>): NO es otra suite (ni la de route)`. A canonical smoke launched with `--task route:…` prints `suite=smoke … task=route:…`, which is honest (that is what was declared) and reads as the smoke because `suite=smoke`/`suite_relpath` name it; that case is fine — B4 is the one where the field is simply another line's. - The mounted file never becomes a criterion: `--script` on a different file with a canonical line → rc 0 with `montado=DISTINTO` and the `SECUNDARIO … NO es la compuerta` note; `--script` equal to the commit's blob with no canonical line → rc 1. - Env: `EMU_PROOF_SMOKE_RELPATH` is a literal assignment, and its value is spliced into `$rev:$rel` — a head that lacks the constant path refuses (B2) regardless of what the env says, so nothing there is injectable. Nothing I could construct passes the gate outside the accepted class (a line for the requested head, chain recomputed from genesis, `via=run`, `tree_clean`/`tree_clean_after` true, `result=OK`, `rc=0`, `tests_failed=0`, integer `tests_run>0`, capture and results binding intact, `script_sha256` equal to the blob of the constant path at H). B4 and B5 do not let a wrong head pass — they make the verdict say untrue things about WHICH run passed, which is the whole point of 0d928e8 and is why this round is red rather than green. Both are a handful of lines in the same block.
Collaborator

Verdict of record — ld-reviewer — head 0d928e8848: REQUEST_CHANGES
model: deepseek-flash
session: s1-2682-r3-8f31c7a4

head: 0d928e8848

Seat 1 of 2, ROUND 3 delta (37202e75 + 0d928e88). Every probe below ran in /tmp against throwaway git repos and the tool taken from this head and from 244b1bf3; nothing was executed in a pod, no real PR was touched and no live ledger was written.

Blockers

  • B4: high — the OK line's task= is not the OK line's task: it comes from the LAST line of the ledger. task is assigned only in the chain-parse loop (tools/gits-sbx-emu-proof.sh:1109), the candidate loop that picks the verdict line never re-reads it, and unlike class/ancla_blob/suite_relpath (saved into ok_class/ok_ssha/ok_rel at :1290-1296) there is no ok_task; the new say at :1341 prints the stale global. Measured in /tmp: one ledger, line 1 = the canonical smoke on head H with task=app:connectedDevDebugAndroidTest, line 2 = a later run on the same head with task=app:connectedRouteAndroidTest; check H gives rc 0 with linea=1 … suite=smoke … task=app:connectedRouteAndroidTest and the follow-up line repeats task=app:connectedRouteAndroidTest. So the verdict names the ROUTE task as the task that passed the head — the exact smoke-vs-other-suite confusion this delta was written to remove, now printed by the line that was supposed to end it. Independent confirmation of seat 2's B4 (filed at 22369), from a separate fixture. Fix is one saved field: ok_task=$task next to ok_class, and print that.
  • B5: high — confirming seat 2's B5 (22369) from an independent repro, so I am not filing it twice: a FAIL line OF THE CANONICAL SMOKE is labelled as another suite. Same fixture, one head: BREAK=1 run of the committed smoke (line 1, script_sha256=27babd5906e3… = the anchor blob, result=FAIL rc=5 tests_run=2), then a green run of the SAME suite (line 2) → rc 0 verdict printing PROOF otras corridas de este head en el ledger (NO son el smoke canonico, NO lo prueban): linea 1 suite_sha256=27babd5906e3... result=FAIL via=run. The line says the smoke is not the smoke; a reader cannot tell a red run of the canonical smoke from the route suite, and the live ledger has exactly that shape (heads with FAIL-then-OK re-runs). The sha is in hand (script_sha256 vs the anchor) and the label should branch on it.

Resolutions

  • resolved B3: verified closed, both repros, with numbers. (a) a head committing a ZERO-BYTE file at core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh (cat-file -s = 0) plus run --suite a zero-byte script: at 244b1bf3 the same repos compute the anchor as e3b0c442… (printed as "su blob hashea e3b0c44298fc...") — the empty-stream value the finding named; at this head check stops at the anchor, rc 1, "el objeto DECLARA 0 bytes y la lectura devolvio 0 bytes (rc=1) … CERO BYTES … B3". (b) the same repo with the canonical blob's loose object removed (cat-file -t → "could not get object info", ls-tree still lists a 100644 blob, real smoke blob 27babd5906e3… never read): rc 1, "el objeto DECLARA ? bytes y la lectura devolvio 0 bytes", naming the object DB and the canonical path. No new fallthrough: a git shim that truncates the cat-file blob stream to 10 bytes and still exits 0 gives rc 1, "el objeto DECLARA 420 bytes y la lectura devolvio 10 bytes"; a shim that makes cat-file -s lie by +3 gives rc 1, "DECLARA 423 … 420 …"; the real-git control on the same ledger gives rc 0. The empty-sha sentinel is in the code as a belt-and-braces second gate and the old dead smoke_rc != 0 branch is now reachable through die1 (rc 1, correctly a verdict about the head, not a die rc 2).

Concerns

  • C4: the harness in my clone first reported 628 checks / 8 FAIL / 16 SKIP, which is not the shape the description reports: five are the known pre-existing reds of other tickets (probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod), a sixth is 20[phase1] mutaciones (mutacion-sbx-common-editado-sigue-verde mutacion-sendrast-editado-sigue-verde), and 24[HOMELAB-2682] (4) and (8) are fixture artifacts of a checkout without origin/live: both read $T2682E/ct-new.sh, which is only written inside the origin/live branch of case (3), so the fixture hashes the empty string and every such case dies with escrito e3b0c442…. With origin/live fetched the affected §24 cases and the T7[diff]/17[R159] comparisons are no longer SKIPped; I re-ran the suite and report its result on the ticket rather than claim a green I did not see. §25 itself was green in both runs: 27 PASS, 0 FAIL, including the new (f6), (f7) and (f8)/(f8b) — so the two B3 cases and the wording case are pinned as the brief asks.
  • C5: byte-compat verified independently of §25(g)/24(3): chain_text extracted from origin/live and from this head produce the same sha256 for the 18-argument and the 20-argument forms (2590bf79…, 493454fd…), and the 21-argument form only appends suite_relpath=… after class=/results_sha256=. B1 and B2 refusals did not regress: a TREE at the canonical path still gives rc 2 ("es un 'tree', NO un blob"), a head without the canonical smoke still gives rc 1 with the B2 sentence and no suite@commit, and a substitute suite that ran but is not the smoke still lands as OTRA SUITE (NO es el smoke canonico) … evidencia (no suficiente).
  • C6: the rest of the distinction is sound and reachable: the OK line carries suite=smoke smoke_rel=<canonical> suite_relpath=<executed|fuera-del-arbol> task=… class=- (plus class=<fqcn> when the line declares it), a PROOF suites: … SMOKE CANONICO … line follows, and the OK of a byte-identical copy in another path gets its own sentence. The one thing missing is that the sentence covering "the copy is the smoke by content" is only printed when suite_relpath != smoke_rel, while task/class in the same three lines are not bound to the line they claim to describe (B4).
**Verdict of record — ld-reviewer — head 0d928e8848b41c7329ee78552e531aa5bbebe0dd: REQUEST_CHANGES** model: deepseek-flash session: s1-2682-r3-8f31c7a4 head: 0d928e8848b41c7329ee78552e531aa5bbebe0dd Seat 1 of 2, ROUND 3 delta (37202e75 + 0d928e88). Every probe below ran in /tmp against throwaway git repos and the tool taken from this head and from 244b1bf3; nothing was executed in a pod, no real PR was touched and no live ledger was written. ## Blockers - B4: high — the OK line's `task=` is not the OK line's task: it comes from the LAST line of the ledger. `task` is assigned only in the chain-parse loop (`tools/gits-sbx-emu-proof.sh:1109`), the candidate loop that picks the verdict line never re-reads it, and unlike `class`/`ancla_blob`/`suite_relpath` (saved into `ok_class`/`ok_ssha`/`ok_rel` at :1290-1296) there is no `ok_task`; the new `say` at :1341 prints the stale global. Measured in /tmp: one ledger, line 1 = the canonical smoke on head H with `task=app:connectedDevDebugAndroidTest`, line 2 = a later run on the same head with `task=app:connectedRouteAndroidTest`; `check H` gives rc 0 with `linea=1 … suite=smoke … task=app:connectedRouteAndroidTest` and the follow-up line repeats `task=app:connectedRouteAndroidTest`. So the verdict names the ROUTE task as the task that passed the head — the exact smoke-vs-other-suite confusion this delta was written to remove, now printed by the line that was supposed to end it. Independent confirmation of seat 2's B4 (filed at 22369), from a separate fixture. Fix is one saved field: `ok_task=$task` next to `ok_class`, and print that. - B5: high — confirming seat 2's B5 (22369) from an independent repro, so I am not filing it twice: a FAIL line OF THE CANONICAL SMOKE is labelled as another suite. Same fixture, one head: `BREAK=1` run of the committed smoke (line 1, `script_sha256=27babd5906e3…` = the anchor blob, `result=FAIL rc=5 tests_run=2`), then a green run of the SAME suite (line 2) → rc 0 verdict printing `PROOF otras corridas de este head en el ledger (NO son el smoke canonico, NO lo prueban): linea 1 suite_sha256=27babd5906e3... result=FAIL via=run`. The line says the smoke is not the smoke; a reader cannot tell a red run of the canonical smoke from the route suite, and the live ledger has exactly that shape (heads with FAIL-then-OK re-runs). The sha is in hand (`script_sha256` vs the anchor) and the label should branch on it. ## Resolutions - resolved B3: verified closed, both repros, with numbers. (a) a head committing a ZERO-BYTE file at `core/manifests/apps/sbx-gits/tools/gits-sbx-emu-smoke.sh` (`cat-file -s` = 0) plus `run --suite` a zero-byte script: at 244b1bf3 the same repos compute the anchor as `e3b0c442…` (printed as "su blob hashea e3b0c44298fc...") — the empty-stream value the finding named; at this head `check` stops at the anchor, rc 1, "el objeto DECLARA 0 bytes y la lectura devolvio 0 bytes (rc=1) … CERO BYTES … B3". (b) the same repo with the canonical blob's loose object removed (`cat-file -t` → "could not get object info", `ls-tree` still lists a 100644 blob, real smoke blob `27babd5906e3…` never read): rc 1, "el objeto DECLARA ? bytes y la lectura devolvio 0 bytes", naming the object DB and the canonical path. No new fallthrough: a `git` shim that truncates the `cat-file blob` stream to 10 bytes and still exits 0 gives rc 1, "el objeto DECLARA 420 bytes y la lectura devolvio 10 bytes"; a shim that makes `cat-file -s` lie by +3 gives rc 1, "DECLARA 423 … 420 …"; the real-git control on the same ledger gives rc 0. The empty-sha sentinel is in the code as a belt-and-braces second gate and the old dead `smoke_rc != 0` branch is now reachable through `die1` (rc 1, correctly a verdict about the head, not a `die` rc 2). ## Concerns - C4: the harness in my clone first reported 628 checks / 8 FAIL / 16 SKIP, which is not the shape the description reports: five are the known pre-existing reds of other tickets (`probe uses an EMPTY pre-existing --dir`, `manifest[2]`, `manifest[3]`, `manifest[3b]`, `T7 emulador dentro del pod`), a sixth is `20[phase1] mutaciones (mutacion-sbx-common-editado-sigue-verde mutacion-sendrast-editado-sigue-verde)`, and `24[HOMELAB-2682] (4)` and `(8)` are fixture artifacts of a checkout without `origin/live`: both read `$T2682E/ct-new.sh`, which is only written inside the `origin/live` branch of case (3), so the fixture hashes the empty string and every such case dies with `escrito e3b0c442…`. With `origin/live` fetched the affected §24 cases and the `T7[diff]`/`17[R159]` comparisons are no longer SKIPped; I re-ran the suite and report its result on the ticket rather than claim a green I did not see. §25 itself was green in both runs: 27 PASS, 0 FAIL, including the new `(f6)`, `(f7)` and `(f8)/(f8b)` — so the two B3 cases and the wording case are pinned as the brief asks. - C5: byte-compat verified independently of §25(g)/24(3): `chain_text` extracted from `origin/live` and from this head produce the same sha256 for the 18-argument and the 20-argument forms (`2590bf79…`, `493454fd…`), and the 21-argument form only appends `suite_relpath=…` after `class=`/`results_sha256=`. B1 and B2 refusals did not regress: a TREE at the canonical path still gives rc 2 ("es un 'tree', NO un blob"), a head without the canonical smoke still gives rc 1 with the B2 sentence and no `suite@commit`, and a substitute suite that ran but is not the smoke still lands as `OTRA SUITE (NO es el smoke canonico) … evidencia (no suficiente)`. - C6: the rest of the distinction is sound and reachable: the OK line carries `suite=smoke smoke_rel=<canonical> suite_relpath=<executed|fuera-del-arbol> task=… class=-` (plus `class=<fqcn>` when the line declares it), a `PROOF suites: … SMOKE CANONICO …` line follows, and the OK of a byte-identical copy in another path gets its own sentence. The one thing missing is that the sentence covering "the copy is the smoke by content" is only printed when `suite_relpath != smoke_rel`, while `task`/`class` in the same three lines are not bound to the line they claim to describe (B4).
HOMELAB-2682: fix(sbx-gits): verdict task from the OK line, evidence labels vs anchor, content bound to oid
All checks were successful
CI Review / runner-manifest-test (pull_request) Successful in 6s
CI Review / receiver-test (pull_request) Successful in 5s
CI Review / helm-validate (pull_request) Successful in 10s
CI Review / alert-receiver-test (pull_request) Successful in 23s
Lint & Validate / shellcheck (pull_request) Successful in 8s
Lint & Validate / yaml-lint (pull_request) Successful in 23s
CI Review / record-sql-test (pull_request) Successful in 1m9s
Lint & Validate / terraform-validate (pull_request) Successful in 2m31s
CI Review / dispatcher-test (pull_request) Successful in 3m29s
CI Review / cockpit-e2e (pull_request) Successful in 3m22s
CI Review / cockpit-test (pull_request) Successful in 3m26s
4ce1268241
Round 3 left two blockers in this round's own output plus two concerns.

B4 (high): `task` is read once per ledger line in the chain-parse loop and the OK
branch printed that global, i.e. the task of the LAST line. A clean smoke in line 1
with the route suite in line 2 printed `suite=smoke ... task=route:...`: the verdict
asserted that the route task was what passed the head. Now `ok_task` is saved from
the passing line, like `ok_class`/`ancla_blob`/`suite_relpath`, and both OK lines and
the `PROOF  suites:` prose print that.

B5 (moderate): the "otras corridas" block labelled EVERY line but the verdict's as
"NO son el smoke canonico" without ever comparing `script_sha256` to the anchor, so a
red run of the canonical smoke was asserted not to be the smoke. The label now
branches on the sha: equal -> `PROOF  aviso el smoke CANONICO TAMBIEN CORRIO EN ROJO
en este head: linea N result=FAIL rc=...` (naming the same `ancla_blob` printed three
lines above and that the verdict is the clean line); different -> other suite, said
with its own sha next to the anchor's.

C1: count (`cat-file -s`) and hash were SEPARATE reads, so a same-size blob swapped in
the object DB between them passed. The object is now read ONCE to a temp file and that
content is bound to the OID the tree declares: `git rev-parse <H>:<rel>` must equal
`git hash-object -t blob` of the bytes read (rc 5, a distinct reason), refusing with
both oids in the message. Byte count, oid binding and sha256 all look at the same
bytes; the empty-stream sentinel of B3 stays.

C4: the §24 (4)/(8) fixtures read `ct-new.sh`, which was only written inside the
origin/live comparison branch, so a clone without that ref false-red them. The
extraction is now unconditional; the live comparison stays conditional.

Harness: §25 (f9) B4 - smoke line 1, route line 2, the verdict must carry the smoke
task and `linea=1`; (f10) B5 - canonical smoke red then green, the red line must be
named as the canonical smoke in red and never as another suite; (f11) C1 - the
compressed object of a same-size different content copied over the declared oid, the
check must refuse naming the oid. docs/gits-sbx/run-proof.md follows: the anchor is
bound to the oid, a FAIL line's force is stated as implemented (the red canonical run
is named, the verdict is the clean run), and `task`/`class` are the fields of the line
that passes.
Collaborator

Verdict of record — ld-reviewer — head 4ce1268241: REQUEST_CHANGES
model: deepseek-flash
session: seat2-ds-20261106T0221Z-7a3c4e

Asiento independiente 2 de 2, ronda fresca 1 (gate tooling). Todo lo de abajo lo medí yo contra repos de mentira en /tmp — ningún PR, pod ni ledger real tocado. El delta de esta ronda (B4/B5/C1/C4) hace lo que dice en los cuatro puntos; el bloqueo es el límite que C1 afirma y no alcanza.

Blockers

  • B6: high — el binding de C1 ata el ancla a una vista reemplazable del commit, así que git replace forja ancla=smoke@commit: con git replace <H> <F> (F un commit con OTRO smoke) + una corrida REAL de esa suite falsa sobre el head H, check imprime PROOF: OK head=H … ancla=smoke@commit ancla_blob=<sha de la suite de F> — y el GIT_NO_REPLACE_OBJECTS=1 git cat-file blob H:<ruta> de ese mismo head hashea otra cosa. rev-parse <H>:<ruta> y cat-file blob <H>:<ruta> coinciden porque las dos lecturas siguen el replace del COMMIT: el oid contra el que se ata el contenido es el de la vista reemplazada, no el del commit H. Cuesta un comando local, no deja rastro en el PR (los refs/replace no se empujan) y rompe el invariante que este PR dice sostener ("una suite sustituta no desbloquea el merge", y la doc: ancla <ruta> … NO es configurable). No es una regresión de C1 (el agujero ya estaba antes: también comparaba con cat-file), pero C1 es el commit que escribe la promesa "el ancla se ata al OID del árbol" y es donde la promesa tiene que ser verdad.
    • Medido end-to-end con la herramienta de este head (/tmp/seat2rev/replace-probe.sh): repo de mentira con el smoke v1 commiteado en core/manifests/…/gits-sbx-emu-smoke.sh (b35c7933…); commit F con otro smoke (538d59b9…); run de la suite de F sobre el árbol parado en H (línea honesta: cadena, tree_clean*, captura, results_sha256); check H sin replace → PROOF: FAIL … no hay ninguna linea CANONICA … su blob hashea b35c7933… (correcto). Y con un git replace H F → PROOF: OK head=3f925169ae64 … ancla=smoke@commit ancla_blob=538d59b9381e… montado=DISTINTO (rc=0). El montado incluso avisaba DISTINTO, es decir: el veredicto certificaba como "smoke commiteado" un contenido que el commit no lleva.
    • Dirección contraria, ya cerrada (bien): un replace sobre el blob SÍ diverge — rev-parse da el oid del árbol (d41e2380…) y cat-file blob devuelve los bytes del reemplazo (5a76ce1d… de hash) → rc 1, mensaje C1 con los dos oids. Ese caso falla cerrado; el que no, es el del commit/árbol.
    • Arreglo (una línea, verificado): export GIT_NO_REPLACE_OBJECTS=1 en la herramienta (o git --no-replace-objects en las lecturas de objeto). Con el mismo estado forjado, GIT_NO_REPLACE_OBJECTS=1 check H → PROOF: FAIL … no hay ninguna linea CANONICA … b35c7933…, rc 1. De paso desaparece la falsa negativa del blob-replace. Un caso nuevo en §25 lo clava: git replace <H> <F> + run de la suite de F → check debe salir rojo nombrando el sha del commit.
    • Nota de alcance: git replace puede sustituir commit, árbol o blob; los grafts (.git/info/grafts) NO son vector aquí (sólo reescriben padres: con H→F en grafts, rev-parse H:<ruta> sigue dando el oid real, medido).

Resolutions

  • B4 — cerrado, verificado ejecutando (/tmp/seat2rev/b45-probe.sh, sin usar el harness): línea 1 = smoke canónico (task=smoke:canon, 2 tests), línea 2 = suite de route (task=route:connectedAndroidTest, 7 tests) → PROOF: OK … task=smoke:canon … tests_run=2 … linea=1. Orden inverso con la canónica en la línea 2 de 3 → task=smoke:canon … tests_run=3 … linea=2. El task= sale de LA línea que pasa; tests_run/output_sha256 también (7 no aparece en ningún veredicto). El caso "OK no es la última línea" está cubierto: el bucle para en la canónica limpia y ok_task se asigna ahí. ok_task no puede salir vacío: task está en la lista de missing de la cadena, así que una línea sin task nunca llega al veredicto (comprobado en el código y por rechazo de una línea con task vacío).
  • B5 — cerrado, verificado ejecutando: canónico en rojo (rc 5, mismo script_sha256 que el ancla) + canónico verde → veredicto OK de la línea limpia y PROOF aviso el smoke CANONICO TAMBIEN CORRIO EN ROJO en este head: linea 1 result=FAIL rc=5 … = el ancla_blob de arriba. Orden inverso (verde primero, rojo después) → idéntico aviso desde el bloque de "otras corridas". Una suite distinta sigue diciéndose su sha256 NO es el ancla_blob (…)…: es OTRA suite. El caso "vieja línea sin script_sha256" no puede llegar a ese bloque: script_sha256 es campo requerido de la cadena (missing) y el check entero se corta con le faltan campos — no hay crash, hay rechazo del ledger.
  • C1 — cerrado en su ámbito, verificado ejecutando: objeto de otro contenido (mismo tamaño o no) copiado sobre el oid declarado → rc 1 y mensaje con los dos oids (el arbol declara el oid … y los bytes que se leen de ese oid hashean como …); verificado también con la vía "objeto borrado del object DB" → rc 1, el objeto DECLARA ? bytes y la lectura devolvio 0 bytes (B3 intacto, motivo verdadero). Con GIT_NO_REPLACE_OBJECTS=1 la promesa pasa a ser literalmente cierta (ver B6).
  • C4 — cerrado por inspección: la extracción de chain_text a ct-new.sh es ahora incondicional (justo después de crear $T2682E, fuera del if de la comparación con origin/live); la comparación sigue condicional. Un clon sin la ref origin/live ya no puede false-rojo (4)/(8).
  • Regresiones previas, re-medidas a mano (no sólo con el harness): B1 árbol en la ruta canónica → rc 2, pero es un 'tree', NO un blob (no hashea tubería vacía); B2 head sin el smoke → rc 1, este head NO tiene un smoke canonico COMMITEADO … y ninguna mención de ancla=suite@commit; una suite commiteada en ese árbol (docs/route.sh) sale como evidencia (no suficiente) (rc 1); B3 vacío/borrado → rc 1 con los números; byte-compat: chain_text extraído de origin/live y de este head da el MISMO sha256 con 18 args y con 20 (clase + results_sha256); A1: ancla_blob= y montado=IGUAL|DISTINTO en la línea del veredicto (visto en los dos estados); wording de distinción (OTRA SUITE (NO es el smoke canonico), evidencia (no suficiente), TAMBIEN CORRIO EN ROJO) presente.
  • Harness: bash scripts/gits-sbx-epic-test.sh corriendo SOLO (worktree limpio en /tmp/seat2rev/wt, head 4ce1268). Al cerrar esta revisión iba por §18 de 25 con 388 PASS / 5 FAIL, y los 5 son exactamente los rojos conocidos y de otro ticket que declara el PR (probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod). Ningún rojo nuevo en lo corrido; §22/§24/§25 seguían en vuelo y su substancia (§24 (4)/(8), §25 (a)–(g), (f9), (f10), (f11)) la reproduje yo de forma independiente con las pruebas de arriba, no sólo leyendo su PASS.

Concerns

  • El cuerpo del PR describe una forma de ancla que el head ya no tiene. Dice que hay "una segunda forma de ancla … la suite COMMITEADA en ese head … ancla=suite@commit:<ruta>" y lista §25 (f)/(f2) como "la suite commiteada es el ancla". En el head no existe tal camino (sólo queda un comentario que explica por qué se quitó), la doc de run-proof.md ya dice lo contrario, y el harness (f) ahora falla si el veredicto nombra suite@commit. Consecuencia práctica, medida: en un árbol sin el smoke (el caso de GITS, Homelab/ghostty-in-the-shell) la compuerta no puede pasar ese head — es la decisión (a)/(b) que el cuerpo pide al operador, y hoy está tomada a favor del rechazo, no "resuelta con un respaldo". El cuerpo hay que reescribirlo antes de que alguien lo lea como "GITS ya puede pasar rung 2 tras una re-corrida".
  • Comentario de cabecera de §25 en el propio harness: sigue diciendo "(f) en un arbol que NO lleva la fuente de la herramienta … el ancla es la suite COMMITEADA en ese head (suite_relpath)" mientras el caso (f) comprueba lo contrario (B2). Drift de contrato dentro del test; una línea.
  • git hash-object -t blob -- "$tf" corre en el repo del cwd, no con -C "$tree": si el cwd de la compuerta fuese un repo con otro formato de objeto (--object-format=sha256), el oid calculado (64 hex) nunca puede igualar el del árbol (40 hex) → rc 5 con un mensaje que acusa al object DB. Fail-closed, pero motivo falso; git -C "$tree" hash-object … lo hace verdad. Medido: en un repo sha256 hash-object devuelve 64 hex aunque el archivo sea el mismo.
  • tests_run del veredicto suma el directorio de resultados ACUMULADO del árbol: en mi caso 2 la suite canónica declaraba tests="2" y el veredicto imprimió tests_run=3, porque el XML de la corrida anterior de la suite de route seguía en <tree>/app/build/…. El results_sha256 por corrida sí ata las copias, pero el número del veredicto no es "de esta suite". Pre-existente y fuera del delta; anotado por si el número se usa para leer "cuántas pruebas pasaron".
**Verdict of record — ld-reviewer — head 4ce1268241ba020a0390dfb7af0b7ab99b2faaa7: REQUEST_CHANGES** model: deepseek-flash session: seat2-ds-20261106T0221Z-7a3c4e Asiento independiente 2 de 2, ronda fresca 1 (gate tooling). Todo lo de abajo lo medí yo contra repos de mentira en `/tmp` — ningún PR, pod ni ledger real tocado. El delta de esta ronda (B4/B5/C1/C4) hace lo que dice en los cuatro puntos; el bloqueo es el límite que C1 afirma y no alcanza. ## Blockers - B6: high — el binding de C1 ata el ancla a una **vista reemplazable** del commit, así que `git replace` forja `ancla=smoke@commit`: con `git replace <H> <F>` (F un commit con OTRO smoke) + una corrida REAL de esa suite falsa sobre el head H, `check` imprime `PROOF: OK head=H … ancla=smoke@commit ancla_blob=<sha de la suite de F>` — y el `GIT_NO_REPLACE_OBJECTS=1 git cat-file blob H:<ruta>` de ese mismo head hashea otra cosa. `rev-parse <H>:<ruta>` y `cat-file blob <H>:<ruta>` coinciden porque **las dos lecturas siguen el replace del COMMIT**: el oid contra el que se ata el contenido es el de la vista reemplazada, no el del commit H. Cuesta un comando local, no deja rastro en el PR (los `refs/replace` no se empujan) y rompe el invariante que este PR dice sostener ("una suite sustituta no desbloquea el merge", y la doc: `ancla <ruta> … NO es configurable`). No es una regresión de C1 (el agujero ya estaba antes: también comparaba con `cat-file`), pero C1 es el commit que **escribe** la promesa "el ancla se ata al OID del árbol" y es donde la promesa tiene que ser verdad. - **Medido end-to-end con la herramienta de este head** (`/tmp/seat2rev/replace-probe.sh`): repo de mentira con el smoke v1 commiteado en `core/manifests/…/gits-sbx-emu-smoke.sh` (`b35c7933…`); commit F con otro smoke (`538d59b9…`); `run` de la suite de F sobre el árbol parado en H (línea honesta: cadena, `tree_clean*`, captura, `results_sha256`); `check H` sin replace → `PROOF: FAIL … no hay ninguna linea CANONICA … su blob hashea b35c7933…` (correcto). Y con **un** `git replace H F` → `PROOF: OK head=3f925169ae64 … ancla=smoke@commit ancla_blob=538d59b9381e… montado=DISTINTO` (`rc=0`). El montado incluso avisaba `DISTINTO`, es decir: el veredicto certificaba como "smoke commiteado" un contenido que el commit no lleva. - **Dirección contraria, ya cerrada (bien)**: un replace sobre el **blob** SÍ diverge — `rev-parse` da el oid del árbol (`d41e2380…`) y `cat-file blob` devuelve los bytes del reemplazo (`5a76ce1d…` de hash) → `rc 1`, mensaje C1 con los dos oids. Ese caso falla cerrado; el que no, es el del commit/árbol. - **Arreglo (una línea, verificado)**: `export GIT_NO_REPLACE_OBJECTS=1` en la herramienta (o `git --no-replace-objects` en las lecturas de objeto). Con el mismo estado forjado, `GIT_NO_REPLACE_OBJECTS=1 check H` → `PROOF: FAIL … no hay ninguna linea CANONICA … b35c7933…`, `rc 1`. De paso desaparece la falsa negativa del blob-replace. Un caso nuevo en §25 lo clava: `git replace <H> <F>` + `run` de la suite de F → `check` debe salir rojo nombrando el sha del commit. - Nota de alcance: `git replace` puede sustituir commit, árbol o blob; los **grafts** (`.git/info/grafts`) NO son vector aquí (sólo reescriben padres: con `H→F` en grafts, `rev-parse H:<ruta>` sigue dando el oid real, medido). ## Resolutions - **B4 — cerrado, verificado ejecutando** (`/tmp/seat2rev/b45-probe.sh`, sin usar el harness): línea 1 = smoke canónico (`task=smoke:canon`, 2 tests), línea 2 = suite de route (`task=route:connectedAndroidTest`, 7 tests) → `PROOF: OK … task=smoke:canon … tests_run=2 … linea=1`. Orden inverso con la canónica en la línea 2 de 3 → `task=smoke:canon … tests_run=3 … linea=2`. El `task=` sale de LA línea que pasa; `tests_run`/`output_sha256` también (7 no aparece en ningún veredicto). El caso "OK no es la última línea" está cubierto: el bucle para en la canónica limpia y `ok_task` se asigna ahí. `ok_task` no puede salir vacío: `task` está en la lista de `missing` de la cadena, así que una línea sin `task` nunca llega al veredicto (comprobado en el código y por rechazo de una línea con `task` vacío). - **B5 — cerrado, verificado ejecutando**: canónico en rojo (`rc 5`, mismo `script_sha256` que el ancla) + canónico verde → veredicto OK de la línea limpia **y** `PROOF aviso el smoke CANONICO TAMBIEN CORRIO EN ROJO en este head: linea 1 result=FAIL rc=5 … = el ancla_blob de arriba`. Orden inverso (verde primero, rojo después) → idéntico aviso desde el bloque de "otras corridas". Una suite distinta sigue diciéndose `su sha256 NO es el ancla_blob (…)…: es OTRA suite`. El caso "vieja línea sin `script_sha256`" no puede llegar a ese bloque: `script_sha256` es campo requerido de la cadena (`missing`) y el check entero se corta con `le faltan campos` — no hay crash, hay rechazo del ledger. - **C1 — cerrado en su ámbito, verificado ejecutando**: objeto de otro contenido (mismo tamaño o no) copiado sobre el oid declarado → `rc 1` y mensaje con los dos oids (`el arbol declara el oid … y los bytes que se leen de ese oid hashean como …`); verificado también con la vía "objeto borrado del object DB" → `rc 1`, `el objeto DECLARA ? bytes y la lectura devolvio 0 bytes` (B3 intacto, motivo verdadero). Con `GIT_NO_REPLACE_OBJECTS=1` la promesa pasa a ser literalmente cierta (ver B6). - **C4 — cerrado por inspección**: la extracción de `chain_text` a `ct-new.sh` es ahora incondicional (justo después de crear `$T2682E`, fuera del `if` de la comparación con `origin/live`); la comparación sigue condicional. Un clon sin la ref `origin/live` ya no puede false-rojo (4)/(8). - **Regresiones previas, re-medidas a mano (no sólo con el harness)**: B1 árbol en la ruta canónica → `rc 2`, `pero es un 'tree', NO un blob` (no hashea tubería vacía); B2 head sin el smoke → `rc 1`, `este head NO tiene un smoke canonico COMMITEADO …` y **ninguna** mención de `ancla=suite@commit`; una suite commiteada en ese árbol (`docs/route.sh`) sale como `evidencia (no suficiente)` (`rc 1`); B3 vacío/borrado → `rc 1` con los números; **byte-compat**: `chain_text` extraído de `origin/live` y de este head da el MISMO sha256 con 18 args y con 20 (clase + `results_sha256`); A1: `ancla_blob=` y `montado=IGUAL|DISTINTO` en la línea del veredicto (visto en los dos estados); wording de distinción (`OTRA SUITE (NO es el smoke canonico)`, `evidencia (no suficiente)`, `TAMBIEN CORRIO EN ROJO`) presente. - **Harness**: `bash scripts/gits-sbx-epic-test.sh` corriendo SOLO (worktree limpio en `/tmp/seat2rev/wt`, head 4ce1268). Al cerrar esta revisión iba por §18 de 25 con **388 PASS / 5 FAIL**, y los 5 son exactamente los rojos conocidos y de otro ticket que declara el PR (`probe uses an EMPTY pre-existing --dir`, `manifest[2]`, `manifest[3]`, `manifest[3b]`, `T7 emulador dentro del pod`). Ningún rojo nuevo en lo corrido; §22/§24/§25 seguían en vuelo y su substancia (§24 (4)/(8), §25 (a)–(g), (f9), (f10), (f11)) la reproduje yo de forma independiente con las pruebas de arriba, no sólo leyendo su PASS. ## Concerns - **El cuerpo del PR describe una forma de ancla que el head ya no tiene.** Dice que hay "una segunda forma de ancla … la suite COMMITEADA en ese head … `ancla=suite@commit:<ruta>`" y lista §25 (f)/(f2) como "la suite commiteada es el ancla". En el head no existe tal camino (sólo queda un comentario que explica por qué se quitó), la doc de `run-proof.md` ya dice lo contrario, y el harness (f) ahora **falla** si el veredicto nombra `suite@commit`. Consecuencia práctica, medida: en un árbol sin el smoke (el caso de GITS, `Homelab/ghostty-in-the-shell`) la compuerta **no puede** pasar ese head — es la decisión (a)/(b) que el cuerpo pide al operador, y hoy está tomada a favor del rechazo, no "resuelta con un respaldo". El cuerpo hay que reescribirlo antes de que alguien lo lea como "GITS ya puede pasar rung 2 tras una re-corrida". - **Comentario de cabecera de §25 en el propio harness**: sigue diciendo "(f) en un arbol que NO lleva la fuente de la herramienta … el ancla es la suite COMMITEADA en ese head (`suite_relpath`)" mientras el caso (f) comprueba lo contrario (B2). Drift de contrato dentro del test; una línea. - **`git hash-object -t blob -- "$tf"` corre en el repo del cwd, no con `-C "$tree"`**: si el cwd de la compuerta fuese un repo con otro formato de objeto (`--object-format=sha256`), el oid calculado (64 hex) nunca puede igualar el del árbol (40 hex) → `rc 5` con un mensaje que acusa al object DB. Fail-closed, pero motivo falso; `git -C "$tree" hash-object …` lo hace verdad. Medido: en un repo sha256 `hash-object` devuelve 64 hex aunque el archivo sea el mismo. - **`tests_run` del veredicto suma el directorio de resultados ACUMULADO del árbol**: en mi caso 2 la suite canónica declaraba `tests="2"` y el veredicto imprimió `tests_run=3`, porque el XML de la corrida anterior de la suite de route seguía en `<tree>/app/build/…`. El `results_sha256` por corrida sí ata las copias, pero el número del veredicto no es "de esta suite". Pre-existente y fuera del delta; anotado por si el número se usa para leer "cuántas pruebas pasaron".
Collaborator

Verdict of record — ld-reviewer — head 4ce1268241: REQUEST_CHANGES

model: deepseek-flash
session: 880adf70-41e1-45a4-953b-30a564801445

Independent review seat 1 of 2, fresh round 1 (gate tooling), scoped to the delta 0d928e88..4ce12682 over core/manifests/apps/sbx-gits/tools/gits-sbx-emu-proof.sh, docs/gits-sbx/run-proof.md, scripts/gits-sbx-epic-test.sh. Read from primary sources only: the diff at that head, the tool as committed at that head, and a solo run of the repo harness in a worktree pinned to that head. Nothing was written to the repo, the cluster or any live ledger.

Blockers

  • 22405: high — anchor identity follows refs/replace, so the commit the gate proves is not the commit it names. I reproduced it independently at this head: in a scratch repo, commit A carries the canonical smoke with content v1, commit B carries v2-FORGED; after git replace B A, rev-parse B:<rel> and cat-file blob B:<rel> BOTH resolve through the replacement (626799f0…, content v1), so the C1 binding (rev-parse oid == hash-object -t blob of the read content) agrees with itself and a line recorded against A's blob passes the gate while claiming head B. With GIT_NO_REPLACE_OBJECTS=1 the real blob is 4fcd3429…. So the fix direction seat 2 verified (GIT_NO_REPLACE_OBJECTS=1, and/or refusing a checkout that carries refs/replace/*) is needed: the anchor must be the immutable object graph of the head, not a rewritten view of it. Filed by seat 2 as 22405; recorded here because it is open at this head and it defeats the property this PR exists to establish.

Resolutions

  • B4: resolved — the verdict prints the task of the line that passes. Read at the head: ok_task is a new local in cmd_check, assigned ok_task=$(jstr "$c" task) inside the branch that sets ok_line/ok_class/ok_anchor, immediately before break, and both OK lines (PROOF: OK … task=$ok_task) and the PROOF suites: sentence use it; the chain-loop local task is no longer reachable from the OK output. Reproduced by hand outside the fixtures: ledger with the canonical smoke run as task=smoke:connectedDebugAndroidTest on line 1 and a committed route suite as task=route:connectedAndroidTest on line 2, same head — the verdict line reads suite=smoke … task=smoke:connectedDebugAndroidTest … linea=1 and the route run is only reported as an additional run. The doc assertion matches (run-proof.md: task/class are the fields recorded in the line that passes the head, not the last ledger line).
  • B5: resolved — other-run lines are now compared against the anchor before being labelled. Read at the head: the per-line block compares script_sha256 with $smoke_sha and branches into PROOF aviso el smoke CANONICO … (OK and red variants) or the foreign-suite line whose text now also carries the anchor it was compared against (su sha256 NO es el ancla_blob (…)). Reproduced by hand: a red run of the canonical smoke (same blob) followed by a green one on the same head prints PROOF aviso el smoke CANONICO TAMBIEN CORRIO EN ROJO en este head: linea 1 result=FAIL rc=5 via=run suite_sha256=1f942e02… = el ancla_blob de arriba, and no otras corridas … result=FAIL line is emitted. A genuinely different suite still reads as OTRA SUITE (NO es el smoke canonico) … evidencia.
  • C1: resolved — the anchor content is bound to the oid the tree declares. Read at the head: git_blob_sha resolves oid with rev-parse --verify "$rev:$rel", reads the object ONCE into a mktemp file, counts those same bytes, then binds them with git hash-object -t blob -- "$tf" and returns rc 5 on mismatch, which cmd_check turns into a named refusal quoting both oids. Reproduced by hand: after copying a same-size (321 B, size preserved after the swap) foreign blob over the object named by the tree entry, check exits rc 1 with apunta a un OBJETO que NO es su contenido … el arbol declara el oid 52aa64bb… y los bytes que se leen de ese oid hashean como e6b43a79… and prints no verdict line.
  • C4: resolved — the round-3 fixture fragility is closed: the chain_text fixture for this branch is extracted unconditionally at fixture setup, outside the origin/live conditional, so the two cases that read it no longer depend on a git ref being present; the live comparison itself stays conditional and still passes here (18-arg and 20-arg texts byte-identical to origin/live, the new field only adding the 21st line).

Prior closures still bite, re-measured at this head: the blob-type refusal, the no-canonical-path fail-closed and the empty/corrupt-anchor byte accounting are all exercised and green in the §25 cases; a substitute suite still never passes; the live 21-line ledger byte compatibility is green (the whole ledger section, 90 checks, 0 failures); the mount-divergence signal prints.

Harness, run solo in a worktree at this exact head: 651 checks, 646 PASS, 5 FAIL, 6 SKIP. The 5 reds are the known pre-existing ones from other tickets (the empty pre-existing --dir probe, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod — all resource/image expectations of other work, none touching this diff). Green here: §25 (30/0, including the three round-3 cases f9/f10/f11), §24 (25/0), T7[diff] including both mutation cases, and 17[R159] (29/0). No timing red this run; the six skips are absent sandbox/prod refs, not this change.

Concerns

  • C1-adjacent, low: git hash-object is invoked without -C "$tree", so the object format comes from the process CWD rather than from the checkout being checked. If check ever runs with a CWD inside a SHA-256 repository, the computed oid would be 64 hex while the tree's oid is 40, and every anchor check would refuse rather than pass — fail-closed, but a false red with a misleading message. git -C "$tree" hash-object -t blob -- "$tf" would bind the same way with the tree's own format.
  • C4 residual, low: the same fixture-extraction pattern is repeated inside §25 (ct-new.sh written under the same [[ -f … ]] && guard at fixture time, which is fine), so the two sections now differ in shape; worth one helper if a third section needs it.
  • Evidence honesty note, informational: §25's f10 case asserts the red-then-green reading, which means the ledger's verdict semantics are "one clean canonical run suffices, earlier reds are reported and not hidden". That is consistent with the live ledger and with run-proof.md, and it is stated in the output text — recording it here only so the landing note says it out loud.
**Verdict of record — ld-reviewer — head 4ce1268241ba020a0390dfb7af0b7ab99b2faaa7: REQUEST_CHANGES** model: deepseek-flash session: 880adf70-41e1-45a4-953b-30a564801445 Independent review seat 1 of 2, fresh round 1 (gate tooling), scoped to the delta `0d928e88..4ce12682` over `core/manifests/apps/sbx-gits/tools/gits-sbx-emu-proof.sh`, `docs/gits-sbx/run-proof.md`, `scripts/gits-sbx-epic-test.sh`. Read from primary sources only: the diff at that head, the tool as committed at that head, and a solo run of the repo harness in a worktree pinned to that head. Nothing was written to the repo, the cluster or any live ledger. ## Blockers - 22405: high — anchor identity follows `refs/replace`, so the commit the gate proves is not the commit it names. I reproduced it independently at this head: in a scratch repo, commit A carries the canonical smoke with content `v1`, commit B carries `v2-FORGED`; after `git replace B A`, `rev-parse B:<rel>` and `cat-file blob B:<rel>` BOTH resolve through the replacement (`626799f0…`, content `v1`), so the C1 binding (`rev-parse` oid == `hash-object -t blob` of the read content) agrees with itself and a line recorded against A's blob passes the gate while claiming head B. With `GIT_NO_REPLACE_OBJECTS=1` the real blob is `4fcd3429…`. So the fix direction seat 2 verified (`GIT_NO_REPLACE_OBJECTS=1`, and/or refusing a checkout that carries `refs/replace/*`) is needed: the anchor must be the immutable object graph of the head, not a rewritten view of it. Filed by seat 2 as 22405; recorded here because it is open at this head and it defeats the property this PR exists to establish. ## Resolutions - B4: resolved — the verdict prints the task of the line that passes. Read at the head: `ok_task` is a new local in `cmd_check`, assigned `ok_task=$(jstr "$c" task)` inside the branch that sets `ok_line`/`ok_class`/`ok_anchor`, immediately before `break`, and both OK lines (`PROOF: OK … task=$ok_task`) and the `PROOF suites:` sentence use it; the chain-loop local `task` is no longer reachable from the OK output. Reproduced by hand outside the fixtures: ledger with the canonical smoke run as `task=smoke:connectedDebugAndroidTest` on line 1 and a committed route suite as `task=route:connectedAndroidTest` on line 2, same head — the verdict line reads `suite=smoke … task=smoke:connectedDebugAndroidTest … linea=1` and the route run is only reported as an additional run. The doc assertion matches (`run-proof.md`: task/class are the fields recorded in the line that passes the head, not the last ledger line). - B5: resolved — other-run lines are now compared against the anchor before being labelled. Read at the head: the per-line block compares `script_sha256` with `$smoke_sha` and branches into `PROOF aviso el smoke CANONICO …` (OK and red variants) or the foreign-suite line whose text now also carries the anchor it was compared against (`su sha256 NO es el ancla_blob (…)`). Reproduced by hand: a red run of the canonical smoke (same blob) followed by a green one on the same head prints `PROOF aviso el smoke CANONICO TAMBIEN CORRIO EN ROJO en este head: linea 1 result=FAIL rc=5 via=run suite_sha256=1f942e02… = el ancla_blob de arriba`, and no `otras corridas … result=FAIL` line is emitted. A genuinely different suite still reads as `OTRA SUITE (NO es el smoke canonico) … evidencia`. - C1: resolved — the anchor content is bound to the oid the tree declares. Read at the head: `git_blob_sha` resolves `oid` with `rev-parse --verify "$rev:$rel"`, reads the object ONCE into a `mktemp` file, counts those same bytes, then binds them with `git hash-object -t blob -- "$tf"` and returns rc 5 on mismatch, which `cmd_check` turns into a named refusal quoting both oids. Reproduced by hand: after copying a same-size (321 B, size preserved after the swap) foreign blob over the object named by the tree entry, `check` exits rc 1 with `apunta a un OBJETO que NO es su contenido … el arbol declara el oid 52aa64bb… y los bytes que se leen de ese oid hashean como e6b43a79…` and prints no verdict line. - C4: resolved — the round-3 fixture fragility is closed: the `chain_text` fixture for this branch is extracted unconditionally at fixture setup, outside the `origin/live` conditional, so the two cases that read it no longer depend on a git ref being present; the live comparison itself stays conditional and still passes here (18-arg and 20-arg texts byte-identical to `origin/live`, the new field only adding the 21st line). Prior closures still bite, re-measured at this head: the blob-type refusal, the no-canonical-path fail-closed and the empty/corrupt-anchor byte accounting are all exercised and green in the §25 cases; a substitute suite still never passes; the live 21-line ledger byte compatibility is green (the whole ledger section, 90 checks, 0 failures); the mount-divergence signal prints. Harness, run solo in a worktree at this exact head: 651 checks, 646 PASS, 5 FAIL, 6 SKIP. The 5 reds are the known pre-existing ones from other tickets (the empty pre-existing `--dir` probe, `manifest[2]`, `manifest[3]`, `manifest[3b]`, `T7 emulador dentro del pod` — all resource/image expectations of other work, none touching this diff). Green here: §25 (30/0, including the three round-3 cases f9/f10/f11), §24 (25/0), `T7[diff]` including both mutation cases, and 17[R159] (29/0). No timing red this run; the six skips are absent sandbox/prod refs, not this change. ## Concerns - C1-adjacent, low: `git hash-object` is invoked without `-C "$tree"`, so the object format comes from the process CWD rather than from the checkout being checked. If `check` ever runs with a CWD inside a SHA-256 repository, the computed oid would be 64 hex while the tree's oid is 40, and every anchor check would refuse rather than pass — fail-closed, but a false red with a misleading message. `git -C "$tree" hash-object -t blob -- "$tf"` would bind the same way with the tree's own format. - C4 residual, low: the same fixture-extraction pattern is repeated inside §25 (`ct-new.sh` written under the same `[[ -f … ]] &&` guard at fixture time, which is fine), so the two sections now differ in shape; worth one helper if a third section needs it. - Evidence honesty note, informational: §25's f10 case asserts the red-then-green reading, which means the ledger's verdict semantics are "one clean canonical run suffices, earlier reds are reported and not hidden". That is consistent with the live ledger and with `run-proof.md`, and it is stated in the output text — recording it here only so the landing note says it out loud.
HOMELAB-2682: fix(sbx-gits): anchor reads ignore replace refs + verdict fields from the OK line
All checks were successful
CI Review / runner-manifest-test (pull_request) Successful in 5s
CI Review / helm-validate (pull_request) Successful in 10s
CI Review / receiver-test (pull_request) Successful in 5s
CI Review / alert-receiver-test (pull_request) Successful in 21s
CI Review / record-sql-test (pull_request) Successful in 49s
Lint & Validate / shellcheck (pull_request) Successful in 7s
Lint & Validate / yaml-lint (pull_request) Successful in 19s
CI Review / cockpit-test (pull_request) Successful in 2m27s
Lint & Validate / terraform-validate (pull_request) Successful in 2m11s
CI Review / cockpit-e2e (pull_request) Successful in 2m47s
CI Review / dispatcher-test (pull_request) Successful in 3m23s
6d44b962d8
B6 (seat 2, round-1 fresh): `git replace <H> <F>` substitutes the whole
commit, and both anchor reads followed the replacement, so `check H` printed
`ancla=smoke@commit ancla_blob=<blob de F>` (rc 0) for a head that does not
carry that suite — and a replace leaves no trace in the PR (refs/replace are
not pushed). Every object-store read on the anchor path (rev-parse
<rev>:<rel>, cat-file -t/-s/blob, ls-tree, hash-object) now runs with
GIT_NO_REPLACE_OBJECTS=1 in its environment (git_obj), and the variable is
also exported for the whole tool as the outer belt. The forged state now
fails closed naming the REAL blob; a checkout that has refs/replace but a
genuine run of the smoke still passes (the substitution is ignored, not the
checkout rejected), and the check says so with a PROOF AVISO.

Also from the same seat, fixed while in the file:
- `git hash-object -t blob` ran in the CWD repo instead of the checked tree
  (a sha256-named CWD gave a 64-hex oid and the wrong refusal reason): now
  `git -C "$tree" hash-object`.
- the verdict's tests_run is now taken from the OK candidate (ok_tests_run,
  same class of misattribution as B4) and `run` no longer sums the
  ACCUMULATED results dir: xml_scan takes a pre-scan of the XML that already
  existed before the suite was launched, so a stale route XML (mtime in the
  same second) no longer turns a 2-case suite into tests_run=3 and no longer
  leaks into that run's results_sha256 copy.
- stale contract lines: the §25 header described (f) as "the committed suite
  is the anchor" (the fallback B2 removed) and run-proof.md's error table
  still offered "o la suite commiteada en ese head". Both corrected, and the
  doc gains the B6 rule plus the refs/replace AVISO row.

Harness: new §25 (f12) for the replace forgery (forged state red, same
ledger without the replace red, genuine run with the replace in place green)
and (f13) for tests_run being this run's.
Collaborator

head: 6d44b962d8

Independent seat 2 of 2, ROUND 2 of the fresh round (adversarial), on the B6 delta 4ce12682..6d44b962 plus the two riders (git hash-object -C "$tree", ok_tests_run). Everything below was measured in /tmp against throwaway git repos with the tool taken from this head; no pod, no PR, no real ledger was touched, and nothing was written outside /tmp.

Sweep, first-hand: the plain replace is closed, and closed properly. With a repo whose head H commits the smoke v1 (e28b9496…, sha256 9a3f7c6c88ff…) and a commit F carrying another smoke, plus a genuine run of F's suite on the tree parked at H, check H gives rc 0 with ancla_blob=<F's suite> on the tool at 4ce12682 (the original defect reproduced) and rc 1 naming H's real blob on this head — for all four shapes I built: commit-level replace git replace H F, tree-level git replace <H^{tree}> <F^{tree}>, blob-level git replace <blob_H> <blob_F> (caught twice, by the flag and by the C1 oid binding), and chained H→F→F2 (third content). No anchor-path read is left unprefixed: every one goes through git_obj, the four plain git calls that remain (status, rev-parse --abbrev-ref, remote get-url, rev-parse --git-dir) are inherited from the export and none of them feeds the anchor, and the file contains no env -i/sudo/sh -c/xargs/eval that could strip the environment. The mid-check race for replace refs is genuinely closed, not papered over: the flag is fixed per process at startup, so a replace created before, during or after the check changes no read. What is not closed is one line of the checkout's own config — see B7.

Harness, solo, no concurrency, in a worktree pinned to this head: still in flight at the moment of posting (§20 of 25) — 479 PASS, 5 FAIL, 0 unexpected, the five reds being exactly the known ones of other tickets (probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod). I am not claiming the final total; the sections that matter for this delta (24, 25) had not been reached yet.

Blockers

  • B7: high — GIT_NO_REPLACE_OBJECTS=1 is not authoritative, so B6 is still reachable with one more local command, and in that state the verdict forges the anchor and prints a false statement about what it read. core.useReplaceRefs is read from config after git sets the flag from the environment, so the checkout can turn replacements back on. Measured on git 2.39.5: with git replace H F in place, GIT_NO_REPLACE_OBJECTS=1 git rev-parse H:<rel> answers H's real blob e28b9496…; add git config core.useReplaceRefs true and the same command answers F's blob 5c9a1805…. git --no-replace-objects (the alternative offered in round 1) is defeated identically; only git -c core.useReplaceRefs=false wins, because it is applied after the file config.
    • End-to-end with the tool of THIS head: repo with the smoke v1 committed at the canonical path, commit F with another smoke, an honest run of F's suite on the tree parked at H (script_sha256=0d037b77a093…, chain intact, capture bound, tree_clean=true both sides). Then git replace H F + git config core.useReplaceRefs true → check H prints PROOF: OK head=H … ancla=smoke@commit ancla_blob=0d037b77a093… tests_run=2, rc 0, i.e. the head's canonical smoke certified as a suite H does not carry — and in the same output the new line asserts "el checkout tiene refs/replace (git replace): el check las IGNORA (GIT_NO_REPLACE_OBJECTS=1) y lee el object store REAL". The same state without the config line is rc 1 (b35c7933-style refusal naming H's real blob 9a3f7c6c88ff…), and the tool at 4ce12682 is rc 0 in both states — so the delta closes the named case and the config line reopens it verbatim. That false aviso is worse than silence: it tells the reader the forgery is impossible.
    • Cost is the same as the original B6: writing .git/config is the same privilege as writing refs/replace (both local, both unpushed, no trace in the PR), git status --porcelain does not see it, and check never re-measures the tree — it reads the fields recorded in the line — so planting it after an honest run leaves tree_clean/tree_clean_after true. run additionally EXECUTES tree-controlled code, so the plant can even arrive without a separate command.
    • Remedy, verified against the same forged state: git_obj() { GIT_NO_REPLACE_OBJECTS=1 git -c core.useReplaceRefs=false "$@"; } → back to H's real blob e28b9496…. Keep the export as the wide net and add the -c as the belt; the AVISO should then be true as written. It costs one clause. The check's git version in the pod should be recorded when it lands (core.useReplaceRefs has existed since git 2.30; I measured 2.39.5 on the mgmt VM).
    • Harness gap that makes this urgent: §25 (f12) never writes that config line, so the regression suite stays green over a gate that a one-liner forges. (f12b) should set core.useReplaceRefs true on the fixture repo and still require rc 1.

Resolutions

  • resolved B6: verified closed for every replace shape the finding named — commit-level (git replace H F), tree-level (<H^{tree}> → <F^{tree}>) and chained (H→F→F2), each with a genuine run of the foreign suite and each rc 1 on this head naming H's real blob, against rc 0 on 4ce12682 with the same fixtures; blob-level replace was already caught by the C1 binding and now fails at the flag first. NOT resolved for the core.useReplaceRefs variant — that is B7 above.

Concerns

  • C-A: --no-replace-objects must not be used as the remedy: it is defeated by the same config exactly like the environment variable, so a fix written that way would look green in §25 and stay forgeable. The -c core.useReplaceRefs=false form is the one that holds (measured both ways).
  • C-B: if B7 is fixed, the AVISO sentence is correct as written; until then it is a false claim in a verdict, and a reader has no way to tell the two states apart — the forged and the honest run print byte-identical PROOF: OK lines.
  • C-C: git hash-object -C "$tree" — verified correct, and it was a real defect: with the CWD being a --object-format=sha256 repo, the tool at 4ce12682 refuses this same honest state rc 1 with the FALSE C1 sentence ("apunta a un OBJETO que NO es su contenido", 64-hex coid vs the 40-hex tree oid, bee1a78c…), and this head gives rc 0 from that CWD and from a non-repo CWD. No regression.
  • C-D: ok_tests_run — verified against three multi-line ledgers on the same head: canonical(2) then foreign(3) → task= and tests_run=2 from linea=1; foreign(3) then canonical(2) → linea=2, tests_run=2; canonical on H1(2) then a line for ANOTHER head H2(5) → linea=1, tests_run=2, no cross-head leak. The run-side pre-scan also holds outside the harness: consecutive runs in the same tree report 2 and 3 cases for 2- and 3-case suites, not the accumulated directory.
  • C-E: B1–B5 and C1 re-measured on this head, all still closed: tree at the canonical path → rc 2 naming tree; a head without the named smoke → rc 1 "NO tiene un smoke canonico COMMITEADO"; zero-byte committed blob → rc 1 with the byte count; loose object deleted → rc 1 with the byte count; same-size object swap in the object DB → rc 1 naming the oid; a red run of the canonical smoke followed by a green one → rc 0 whose verdict says, in those words, that line 1 IS the canonical smoke and is RED. No regression from this delta.
head: 6d44b962d886ebf9cdedee6e97784e8a546a22ef Independent seat 2 of 2, ROUND 2 of the fresh round (adversarial), on the B6 delta `4ce12682..6d44b962` plus the two riders (`git hash-object -C "$tree"`, `ok_tests_run`). Everything below was measured in `/tmp` against throwaway git repos with the tool taken from this head; no pod, no PR, no real ledger was touched, and nothing was written outside `/tmp`. Sweep, first-hand: the plain replace is closed, and closed properly. With a repo whose head H commits the smoke `v1` (`e28b9496…`, sha256 `9a3f7c6c88ff…`) and a commit F carrying another smoke, plus a genuine `run` of F's suite on the tree parked at H, `check H` gives rc 0 with `ancla_blob=<F's suite>` on the tool at `4ce12682` (the original defect reproduced) and rc 1 naming H's real blob on this head — for all four shapes I built: **commit-level** replace `git replace H F`, **tree-level** `git replace <H^{tree}> <F^{tree}>`, **blob-level** `git replace <blob_H> <blob_F>` (caught twice, by the flag and by the C1 oid binding), and **chained** `H→F→F2` (third content). No anchor-path read is left unprefixed: every one goes through `git_obj`, the four plain `git` calls that remain (`status`, `rev-parse --abbrev-ref`, `remote get-url`, `rev-parse --git-dir`) are inherited from the export and none of them feeds the anchor, and the file contains no `env -i`/`sudo`/`sh -c`/`xargs`/`eval` that could strip the environment. The mid-check race for replace refs is genuinely closed, not papered over: the flag is fixed per process at startup, so a replace created before, during or after the check changes no read. What is *not* closed is one line of the checkout's own config — see B7. Harness, solo, no concurrency, in a worktree pinned to this head: still in flight at the moment of posting (§20 of 25) — **479 PASS, 5 FAIL, 0 unexpected**, the five reds being exactly the known ones of other tickets (`probe uses an EMPTY pre-existing --dir`, `manifest[2]`, `manifest[3]`, `manifest[3b]`, `T7 emulador dentro del pod`). I am not claiming the final total; the sections that matter for this delta (24, 25) had not been reached yet. ## Blockers - B7: high — `GIT_NO_REPLACE_OBJECTS=1` is not authoritative, so B6 is still reachable with one more local command, and in that state the verdict forges the anchor **and** prints a false statement about what it read. `core.useReplaceRefs` is read from config *after* git sets the flag from the environment, so the checkout can turn replacements back on. Measured on git 2.39.5: with `git replace H F` in place, `GIT_NO_REPLACE_OBJECTS=1 git rev-parse H:<rel>` answers H's real blob `e28b9496…`; add `git config core.useReplaceRefs true` and the same command answers F's blob `5c9a1805…`. `git --no-replace-objects` (the alternative offered in round 1) is defeated identically; only `git -c core.useReplaceRefs=false` wins, because it is applied after the file config. - End-to-end with the tool of THIS head: repo with the smoke `v1` committed at the canonical path, commit F with another smoke, an honest `run` of F's suite on the tree parked at H (`script_sha256=0d037b77a093…`, chain intact, capture bound, `tree_clean=true` both sides). Then `git replace H F` + `git config core.useReplaceRefs true` → `check H` prints `PROOF: OK head=H … ancla=smoke@commit ancla_blob=0d037b77a093… tests_run=2`, **rc 0**, i.e. the head's canonical smoke certified as a suite H does not carry — and in the same output the new line asserts *"el checkout tiene refs/replace (git replace): el check las IGNORA (GIT_NO_REPLACE_OBJECTS=1) y lee el object store REAL"*. The same state without the config line is rc 1 (`b35c7933`-style refusal naming H's real blob `9a3f7c6c88ff…`), and the tool at `4ce12682` is rc 0 in both states — so the delta closes the named case and the config line reopens it verbatim. That false aviso is worse than silence: it tells the reader the forgery is impossible. - Cost is the same as the original B6: writing `.git/config` is the same privilege as writing `refs/replace` (both local, both unpushed, no trace in the PR), `git status --porcelain` does not see it, and `check` never re-measures the tree — it reads the fields recorded in the line — so planting it after an honest run leaves `tree_clean`/`tree_clean_after` true. `run` additionally EXECUTES tree-controlled code, so the plant can even arrive without a separate command. - Remedy, verified against the same forged state: `git_obj() { GIT_NO_REPLACE_OBJECTS=1 git -c core.useReplaceRefs=false "$@"; }` → back to H's real blob `e28b9496…`. Keep the export as the wide net and add the `-c` as the belt; the AVISO should then be true as written. It costs one clause. The check's git version in the pod should be recorded when it lands (`core.useReplaceRefs` has existed since git 2.30; I measured 2.39.5 on the mgmt VM). - Harness gap that makes this urgent: §25 (f12) never writes that config line, so the regression suite stays green over a gate that a one-liner forges. (f12b) should set `core.useReplaceRefs true` on the fixture repo and still require rc 1. ## Resolutions - resolved B6: verified closed for every replace shape the finding named — commit-level (`git replace H F`), tree-level (`<H^{tree}> → <F^{tree}>`) and chained (`H→F→F2`), each with a genuine `run` of the foreign suite and each rc 1 on this head naming H's real blob, against rc 0 on `4ce12682` with the same fixtures; blob-level replace was already caught by the C1 binding and now fails at the flag first. NOT resolved for the `core.useReplaceRefs` variant — that is B7 above. ## Concerns - C-A: `--no-replace-objects` must not be used as the remedy: it is defeated by the same config exactly like the environment variable, so a fix written that way would look green in §25 and stay forgeable. The `-c core.useReplaceRefs=false` form is the one that holds (measured both ways). - C-B: if B7 is fixed, the AVISO sentence is correct as written; until then it is a false claim in a verdict, and a reader has no way to tell the two states apart — the forged and the honest run print byte-identical `PROOF: OK` lines. - C-C: `git hash-object -C "$tree"` — verified correct, and it was a real defect: with the CWD being a `--object-format=sha256` repo, the tool at `4ce12682` refuses this same honest state rc 1 with the FALSE C1 sentence ("apunta a un OBJETO que NO es su contenido", 64-hex `coid` vs the 40-hex tree oid, `bee1a78c…`), and this head gives rc 0 from that CWD and from a non-repo CWD. No regression. - C-D: `ok_tests_run` — verified against three multi-line ledgers on the same head: canonical(2) then foreign(3) → `task=` and `tests_run=2` from `linea=1`; foreign(3) then canonical(2) → `linea=2`, `tests_run=2`; canonical on H1(2) then a line for ANOTHER head H2(5) → `linea=1`, `tests_run=2`, no cross-head leak. The `run`-side pre-scan also holds outside the harness: consecutive runs in the same tree report 2 and 3 cases for 2- and 3-case suites, not the accumulated directory. - C-E: B1–B5 and C1 re-measured on this head, all still closed: tree at the canonical path → rc 2 naming `tree`; a head without the named smoke → rc 1 "NO tiene un smoke canonico COMMITEADO"; zero-byte committed blob → rc 1 with the byte count; loose object deleted → rc 1 with the byte count; same-size object swap in the object DB → rc 1 naming the oid; a red run of the canonical smoke followed by a green one → rc 0 whose verdict says, in those words, that line 1 IS the canonical smoke and is RED. No regression from this delta.
Collaborator

Verdict of record — ld-reviewer — head 6d44b962d8: REQUEST_CHANGES
model: deepseek-flash
session: seat2-ds-20261106T0250Z-b7f3a1

head: 6d44b962d8

Independent seat 2 of 2, ROUND 2 of the fresh round (adversarial), on the B6 delta 4ce12682..6d44b962 plus the two riders (git hash-object -C "$tree", ok_tests_run). Everything below was measured in /tmp against throwaway git repos with the tool taken from this head; no pod, no PR, no real ledger was touched, and nothing was written outside /tmp.

Sweep, first-hand: the plain replace is closed, and closed properly. With a repo whose head H commits the smoke v1 (e28b9496…, sha256 9a3f7c6c88ff…) and a commit F carrying another smoke, plus a genuine run of F's suite on the tree parked at H, check H gives rc 0 with ancla_blob=<F's suite> on the tool at 4ce12682 (the original defect reproduced) and rc 1 naming H's real blob on this head — for all four shapes I built: commit-level replace git replace H F, tree-level git replace <H^{tree}> <F^{tree}>, blob-level git replace <blob_H> <blob_F> (caught twice, by the flag and by the C1 oid binding), and chained H→F→F2 (third content). No anchor-path read is left unprefixed: every one goes through git_obj, the four plain git calls that remain (status, rev-parse --abbrev-ref, remote get-url, rev-parse --git-dir) are inherited from the export and none of them feeds the anchor, and the file contains no env -i/sudo/sh -c/xargs/eval that could strip the environment. The mid-check race for replace refs is genuinely closed, not papered over: the flag is fixed per process at startup, so a replace created before, during or after the check changes no read. What is not closed is one line of the checkout's own config — see B7.

Harness, solo, no concurrency, in a worktree pinned to this head: still in flight at the moment of posting (§20 of 25) — 479 PASS, 5 FAIL, 0 unexpected, the five reds being exactly the known ones of other tickets (probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod). I am not claiming the final total; the sections that matter for this delta (24, 25) had not been reached yet.

Blockers

  • B7: high — GIT_NO_REPLACE_OBJECTS=1 is not authoritative, so B6 is still reachable with one more local command, and in that state the verdict forges the anchor and prints a false statement about what it read. core.useReplaceRefs is read from config after git sets the flag from the environment, so the checkout can turn replacements back on. Measured on git 2.39.5: with git replace H F in place, GIT_NO_REPLACE_OBJECTS=1 git rev-parse H:<rel> answers H's real blob e28b9496…; add git config core.useReplaceRefs true and the same command answers F's blob 5c9a1805…. git --no-replace-objects (the alternative offered in round 1) is defeated identically; only git -c core.useReplaceRefs=false wins, because it is applied after the file config.
    • End-to-end with the tool of THIS head: repo with the smoke v1 committed at the canonical path, commit F with another smoke, an honest run of F's suite on the tree parked at H (script_sha256=0d037b77a093…, chain intact, capture bound, tree_clean=true both sides). Then git replace H F + git config core.useReplaceRefs true → check H prints PROOF: OK head=H … ancla=smoke@commit ancla_blob=0d037b77a093… tests_run=2, rc 0, i.e. the head's canonical smoke certified as a suite H does not carry — and in the same output the new line asserts "el checkout tiene refs/replace (git replace): el check las IGNORA (GIT_NO_REPLACE_OBJECTS=1) y lee el object store REAL". The same state without the config line is rc 1 (b35c7933-style refusal naming H's real blob 9a3f7c6c88ff…), and the tool at 4ce12682 is rc 0 in both states — so the delta closes the named case and the config line reopens it verbatim. That false aviso is worse than silence: it tells the reader the forgery is impossible.
    • Cost is the same as the original B6: writing .git/config is the same privilege as writing refs/replace (both local, both unpushed, no trace in the PR), git status --porcelain does not see it, and check never re-measures the tree — it reads the fields recorded in the line — so planting it after an honest run leaves tree_clean/tree_clean_after true. run additionally EXECUTES tree-controlled code, so the plant can even arrive without a separate command.
    • Remedy, verified against the same forged state: git_obj() { GIT_NO_REPLACE_OBJECTS=1 git -c core.useReplaceRefs=false "$@"; } → back to H's real blob e28b9496…. Keep the export as the wide net and add the -c as the belt; the AVISO should then be true as written. It costs one clause. The check's git version in the pod should be recorded when it lands (core.useReplaceRefs has existed since git 2.30; I measured 2.39.5 on the mgmt VM).
    • Harness gap that makes this urgent: §25 (f12) never writes that config line, so the regression suite stays green over a gate that a one-liner forges. (f12b) should set core.useReplaceRefs true on the fixture repo and still require rc 1.

Resolutions

  • resolved B6: verified closed for every replace shape the finding named — commit-level (git replace H F), tree-level (<H^{tree}> → <F^{tree}>) and chained (H→F→F2), each with a genuine run of the foreign suite and each rc 1 on this head naming H's real blob, against rc 0 on 4ce12682 with the same fixtures; blob-level replace was already caught by the C1 binding and now fails at the flag first. NOT resolved for the core.useReplaceRefs variant — that is B7 above.

Concerns

  • C-A: --no-replace-objects must not be used as the remedy: it is defeated by the same config exactly like the environment variable, so a fix written that way would look green in §25 and stay forgeable. The -c core.useReplaceRefs=false form is the one that holds (measured both ways).
  • C-B: if B7 is fixed, the AVISO sentence is correct as written; until then it is a false claim in a verdict, and a reader has no way to tell the two states apart — the forged and the honest run print byte-identical PROOF: OK lines.
  • C-C: git hash-object -C "$tree" — verified correct, and it was a real defect: with the CWD being a --object-format=sha256 repo, the tool at 4ce12682 refuses this same honest state rc 1 with the FALSE C1 sentence ("apunta a un OBJETO que NO es su contenido", 64-hex coid vs the 40-hex tree oid, bee1a78c…), and this head gives rc 0 from that CWD and from a non-repo CWD. No regression.
  • C-D: ok_tests_run — verified against three multi-line ledgers on the same head: canonical(2) then foreign(3) → task= and tests_run=2 from linea=1; foreign(3) then canonical(2) → linea=2, tests_run=2; canonical on H1(2) then a line for ANOTHER head H2(5) → linea=1, tests_run=2, no cross-head leak. The run-side pre-scan also holds outside the harness: consecutive runs in the same tree report 2 and 3 cases for 2- and 3-case suites, not the accumulated directory.
  • C-E: B1–B5 and C1 re-measured on this head, all still closed: tree at the canonical path → rc 2 naming tree; a head without the named smoke → rc 1 "NO tiene un smoke canonico COMMITEADO"; zero-byte committed blob → rc 1 with the byte count; loose object deleted → rc 1 with the byte count; same-size object swap in the object DB → rc 1 naming the oid; a red run of the canonical smoke followed by a green one → rc 0 whose verdict says, in those words, that line 1 IS the canonical smoke and is RED. No regression from this delta.

(Format note, mine: comment 22419 carries the same findings with the verdict header missing — I posted it seconds earlier without the first block the lock reads. This comment is the complete verdict of record and supersedes it; 22419 is left in place, not edited and not deleted.)

**Verdict of record — ld-reviewer — head 6d44b962d886ebf9cdedee6e97784e8a546a22ef: REQUEST_CHANGES** model: deepseek-flash session: seat2-ds-20261106T0250Z-b7f3a1 head: 6d44b962d886ebf9cdedee6e97784e8a546a22ef Independent seat 2 of 2, ROUND 2 of the fresh round (adversarial), on the B6 delta `4ce12682..6d44b962` plus the two riders (`git hash-object -C "$tree"`, `ok_tests_run`). Everything below was measured in `/tmp` against throwaway git repos with the tool taken from this head; no pod, no PR, no real ledger was touched, and nothing was written outside `/tmp`. Sweep, first-hand: the plain replace is closed, and closed properly. With a repo whose head H commits the smoke `v1` (`e28b9496…`, sha256 `9a3f7c6c88ff…`) and a commit F carrying another smoke, plus a genuine `run` of F's suite on the tree parked at H, `check H` gives rc 0 with `ancla_blob=<F's suite>` on the tool at `4ce12682` (the original defect reproduced) and rc 1 naming H's real blob on this head — for all four shapes I built: **commit-level** replace `git replace H F`, **tree-level** `git replace <H^{tree}> <F^{tree}>`, **blob-level** `git replace <blob_H> <blob_F>` (caught twice, by the flag and by the C1 oid binding), and **chained** `H→F→F2` (third content). No anchor-path read is left unprefixed: every one goes through `git_obj`, the four plain `git` calls that remain (`status`, `rev-parse --abbrev-ref`, `remote get-url`, `rev-parse --git-dir`) are inherited from the export and none of them feeds the anchor, and the file contains no `env -i`/`sudo`/`sh -c`/`xargs`/`eval` that could strip the environment. The mid-check race for replace refs is genuinely closed, not papered over: the flag is fixed per process at startup, so a replace created before, during or after the check changes no read. What is *not* closed is one line of the checkout's own config — see B7. Harness, solo, no concurrency, in a worktree pinned to this head: still in flight at the moment of posting (§20 of 25) — **479 PASS, 5 FAIL, 0 unexpected**, the five reds being exactly the known ones of other tickets (`probe uses an EMPTY pre-existing --dir`, `manifest[2]`, `manifest[3]`, `manifest[3b]`, `T7 emulador dentro del pod`). I am not claiming the final total; the sections that matter for this delta (24, 25) had not been reached yet. ## Blockers - B7: high — `GIT_NO_REPLACE_OBJECTS=1` is not authoritative, so B6 is still reachable with one more local command, and in that state the verdict forges the anchor **and** prints a false statement about what it read. `core.useReplaceRefs` is read from config *after* git sets the flag from the environment, so the checkout can turn replacements back on. Measured on git 2.39.5: with `git replace H F` in place, `GIT_NO_REPLACE_OBJECTS=1 git rev-parse H:<rel>` answers H's real blob `e28b9496…`; add `git config core.useReplaceRefs true` and the same command answers F's blob `5c9a1805…`. `git --no-replace-objects` (the alternative offered in round 1) is defeated identically; only `git -c core.useReplaceRefs=false` wins, because it is applied after the file config. - End-to-end with the tool of THIS head: repo with the smoke `v1` committed at the canonical path, commit F with another smoke, an honest `run` of F's suite on the tree parked at H (`script_sha256=0d037b77a093…`, chain intact, capture bound, `tree_clean=true` both sides). Then `git replace H F` + `git config core.useReplaceRefs true` → `check H` prints `PROOF: OK head=H … ancla=smoke@commit ancla_blob=0d037b77a093… tests_run=2`, **rc 0**, i.e. the head's canonical smoke certified as a suite H does not carry — and in the same output the new line asserts *"el checkout tiene refs/replace (git replace): el check las IGNORA (GIT_NO_REPLACE_OBJECTS=1) y lee el object store REAL"*. The same state without the config line is rc 1 (`b35c7933`-style refusal naming H's real blob `9a3f7c6c88ff…`), and the tool at `4ce12682` is rc 0 in both states — so the delta closes the named case and the config line reopens it verbatim. That false aviso is worse than silence: it tells the reader the forgery is impossible. - Cost is the same as the original B6: writing `.git/config` is the same privilege as writing `refs/replace` (both local, both unpushed, no trace in the PR), `git status --porcelain` does not see it, and `check` never re-measures the tree — it reads the fields recorded in the line — so planting it after an honest run leaves `tree_clean`/`tree_clean_after` true. `run` additionally EXECUTES tree-controlled code, so the plant can even arrive without a separate command. - Remedy, verified against the same forged state: `git_obj() { GIT_NO_REPLACE_OBJECTS=1 git -c core.useReplaceRefs=false "$@"; }` → back to H's real blob `e28b9496…`. Keep the export as the wide net and add the `-c` as the belt; the AVISO should then be true as written. It costs one clause. The check's git version in the pod should be recorded when it lands (`core.useReplaceRefs` has existed since git 2.30; I measured 2.39.5 on the mgmt VM). - Harness gap that makes this urgent: §25 (f12) never writes that config line, so the regression suite stays green over a gate that a one-liner forges. (f12b) should set `core.useReplaceRefs true` on the fixture repo and still require rc 1. ## Resolutions - resolved B6: verified closed for every replace shape the finding named — commit-level (`git replace H F`), tree-level (`<H^{tree}> → <F^{tree}>`) and chained (`H→F→F2`), each with a genuine `run` of the foreign suite and each rc 1 on this head naming H's real blob, against rc 0 on `4ce12682` with the same fixtures; blob-level replace was already caught by the C1 binding and now fails at the flag first. NOT resolved for the `core.useReplaceRefs` variant — that is B7 above. ## Concerns - C-A: `--no-replace-objects` must not be used as the remedy: it is defeated by the same config exactly like the environment variable, so a fix written that way would look green in §25 and stay forgeable. The `-c core.useReplaceRefs=false` form is the one that holds (measured both ways). - C-B: if B7 is fixed, the AVISO sentence is correct as written; until then it is a false claim in a verdict, and a reader has no way to tell the two states apart — the forged and the honest run print byte-identical `PROOF: OK` lines. - C-C: `git hash-object -C "$tree"` — verified correct, and it was a real defect: with the CWD being a `--object-format=sha256` repo, the tool at `4ce12682` refuses this same honest state rc 1 with the FALSE C1 sentence ("apunta a un OBJETO que NO es su contenido", 64-hex `coid` vs the 40-hex tree oid, `bee1a78c…`), and this head gives rc 0 from that CWD and from a non-repo CWD. No regression. - C-D: `ok_tests_run` — verified against three multi-line ledgers on the same head: canonical(2) then foreign(3) → `task=` and `tests_run=2` from `linea=1`; foreign(3) then canonical(2) → `linea=2`, `tests_run=2`; canonical on H1(2) then a line for ANOTHER head H2(5) → `linea=1`, `tests_run=2`, no cross-head leak. The `run`-side pre-scan also holds outside the harness: consecutive runs in the same tree report 2 and 3 cases for 2- and 3-case suites, not the accumulated directory. - C-E: B1–B5 and C1 re-measured on this head, all still closed: tree at the canonical path → rc 2 naming `tree`; a head without the named smoke → rc 1 "NO tiene un smoke canonico COMMITEADO"; zero-byte committed blob → rc 1 with the byte count; loose object deleted → rc 1 with the byte count; same-size object swap in the object DB → rc 1 naming the oid; a red run of the canonical smoke followed by a green one → rc 0 whose verdict says, in those words, that line 1 IS the canonical smoke and is RED. No regression from this delta. (Format note, mine: comment 22419 carries the same findings with the verdict header missing — I posted it seconds earlier without the first block the lock reads. This comment is the complete verdict of record and supersedes it; 22419 is left in place, not edited and not deleted.)
Collaborator

Verdict of record — ld-reviewer — head 6d44b962d8: REQUEST_CHANGES
model: deepseek-flash
session: s1-2682-r2d-20261106T0305Z-9c41ab

head: 6d44b962d8

Independent seat 1 of 2, ROUND 2 delta (gate tooling), scoped to 4ce12682..6d44b962 over core/manifests/apps/sbx-gits/tools/gits-sbx-emu-proof.sh, docs/gits-sbx/run-proof.md and scripts/gits-sbx-epic-test.sh. Everything below was measured in /tmp against throwaway git repos, with the tool taken from this head and from 4ce12682; nothing was executed in a pod, no live ledger was read or written, and no PR was touched.

B6 is closed — I reproduced the forgery at the old head and it now fails closed, with the honest control still passing. What keeps this at changes-requested is one new harness case that can never pass, which also falsifies the harness table in the PR description; the gate code itself is sound on every other dimension I attacked.

Blockers

  • B7: moderate — the new harness case 25[HOMELAB-2682] (f13) can never pass, and the solo run therefore reports a red that is NOT one of the known pre-existing ones. t2682a_field reads QUOTED values only (sed -n "s/.*\"$2\":\"\([^\"]*\)\".*/\1/p", scripts/gits-sbx-epic-test.sh:13021) while tests_run is a JSON NUMBER, so F13_RUN comes back EMPTY and the assertion [[ $F13_RUN != 2 ]] fires unconditionally. Measured, solo, no concurrency, in a worktree pinned to this head: bash scripts/gits-sbx-epic-test.sh → RESULT: FAIL (6 of 653 checks failed, 6 skipped, 0 pending), and the sixth red is FAIL 25[HOMELAB-2682] (f13) la LINEA de la corrida cuenta casos y la suite deja 2: … — note the empty field in the message. The five others are the known pre-existing reds of other tickets (probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod); the timing case did not reproduce. §25 is 20 PASS / 1 FAIL, not 0 FAIL, and §24 is green (56 PASS across §24+§25).
    • The behaviour it is trying to test is correct — I verified it independently of the harness. Pre-seeding an accumulated XML of 5 cases with a future mtime in <tree>/app/build/outputs/androidTest-results/… and then running the canonical smoke: at this head the LINE says "tests_run":2; at 4ce12682, same repos, the line says "tests_run":7. So the pre-scan does what the comment claims. Only the assertion is broken.
    • Fix is one line, plus re-measuring: give the numeric fields their own extractor (e.g. sed -n 's/.*"tests_run":\([0-9]*\).*/\1/p', or make t2682a_field fall back to the unquoted form), then re-run solo and correct the harness table in the PR body — it still says 641 comprobaciones, 635 PASS, 6 FAIL, 6 SKIP and §25 … 20 PASS / 0 FAIL, and neither matches this head. A permanently-red assertion in the epic harness costs the next reader exactly the signal that distinguishes a known pre-existing red from a new one, which is why I am not filing this under Concerns.
    • CI on this head is green (runs #3239, #3240 — all jobs pass); this is the manual epic harness, not CI.

Resolutions

  • resolved B6: verified closed, both directions, by executing the exact forged state and its controls. Fixture: a scratch repo carrying the canonical smoke v1 (1647e2cf…) committed at H=a2edc4e6…; a second commit F=0f25bec7… carrying a DIFFERENT smoke v2 (d77c37f0…); the tree parked back at H; a GENUINE run --suite <F's smoke> on that tree (chain, tree_clean*, capture, results_sha256 all real, line script_sha256=d77c37f0…). At 4ce12682: check H WITHOUT the replace → rc 1 (no hay ninguna linea CANONICA … su blob hashea 1647e2cf…), and with ONE git replace H F → rc 0, PROOF: OK … ancla=smoke@commit ancla_blob=d77c37f080db… montado=DISTINTO — the forgery, reproduced first-hand. At this head, same repo, replace still installed → rc 1, PROOF: FAIL (a2edc4e6…), no hay ninguna linea CANONICA … 1647e2cf… (the REAL blob of H), plus PROOF AVISO el checkout '…' tiene refs/replace (git replace): el check las IGNORA.
    • Coverage of the wrapper, read at the head: git_obj() { GIT_NO_REPLACE_OBJECTS=1 git "$@"; } (:435) plus export GIT_NO_REPLACE_OBJECTS=1 (:172). Every read the anchor is built from goes through it — git_head (:438), git_path_type's cat-file -t and ls-tree fallback (:456-475), and in git_blob_sha the rev-parse --verify, cat-file -s, the single cat-file blob read into the temp file and the hash-object -t blob binding (:531-580) — as do the C1 and B3 diagnostics (:1201-1218), the cat-file -e "$headarg^{commit}" gate (:1146) and the new for-each-ref refs/replace probe. The only plain git calls left are status --porcelain (tree_clean), rev-parse --abbrev-ref HEAD, remote get-url (banner) and rev-parse --git-dir (existence) — none of them feeds smoke_sha, and replace refs do not move any of them. I found no bypass left in the anchor path.
    • I attacked the other two substitutable object kinds too. A TREE-level replace (git replace <H^{tree}> <F^{tree}>) forges identically at the old head (rc 0, ancla=smoke@commit) and is rc 1 at this head. A BLOB-level replace is ignored at this head (rc 0 with the real 1647e2cf…) where the old head gave the false negative rc 1 that B6's note described — so the fix removes a wrong refusal as well as the forgery.
    • No false negative introduced: the same honest ledger (a real run of H's own smoke) gives rc 0 both with a stray git replace H F installed and without it — the substitution is IGNORED, the checkout is not rejected, which is the property the docs now state.
  • B1, B2, B3, B4, B5, C1 still bite at this head (all re-measured, in /tmp, after the git_obj change): B1 a DIRECTORY at the canonical path → rc 2 naming 'tree'; B2 an app-style tree with a committed scripts/alt-suite.sh that ran → rc 1 este head NO tiene un smoke canonico COMMITEADO … NO se admite una suite sustituta, ni commiteada; B2, the run demoted to evidencia (no suficiente), and no suite@commit form anywhere; B3a a zero-byte file at the canonical path + a zero-byte suite → rc 1 DECLARA 0 bytes …; B3b the canonical blob's loose object removed → rc 1 DECLARA ? bytes y la lectura devolvio 0 bytes; B4 canonical smoke on line 1 (task=smoke:connectedDebugAndroidTest) and a foreign route suite on line 2 → rc 0 with task=smoke:connectedDebugAndroidTest … tests_run=2 … linea=1 and line 2 reported as otras corridas … su sha256 NO es el ancla_blob … es OTRA suite; B5 a RED run of the canonical smoke followed by a green one → rc 0 with PROOF aviso el smoke CANONICO TAMBIEN CORRIO EN ROJO en este head: linea 1 result=FAIL rc=5 … = el ancla_blob de arriba; C1 a same-size (405 B) foreign blob copied over the oid the tree declares → rc 1 quoting both oids.
  • Byte compatibility: chain_text extracted from origin/live and from this head differ only in comments and the optional 21st field. Numerically, the 18-arg form hashes 4e7bb549… and the 20-arg form fca31765… on BOTH (identical), while the 21st argument only appends suite_relpath=… (fe09fd95…). The 21 live ledger lines keep validating.
  • A1 signal: with --script pointing at a different file the OK line carries montado=DISTINTO and the PROOF SECUNDARIO el fichero MONTADO … NO es la compuerta line, rc unchanged — verified on a head whose canonical line passes.
  • hash-object -C "$tree" (concern from round 1): the motive is real and the fix works. With the CWD inside a scratch repo created with --object-format=sha256 and the honest ledger for a sha1 tree, the old tool exits rc 1 with the FALSE reason (… apunta a un OBJETO que NO es su contenido … hashean como e0ef462c… — a 64-hex oid from the CWD repo), and this head exits rc 0. When the content genuinely differs, the -C "$tree" form still names the true mismatch (C1 above).
  • §25 header wording and the in-tree docs are corrected: the (f)/(f2) header no longer promises an suite@commit fallback, run-proof.md says the anchor is the smoke COMMITTED in that head with "no hay respaldo, B2", documents the GIT_NO_REPLACE_OBJECTS reads and the refs/replace aviso, and the tests_run row now excludes the accumulated directory.

Concerns

  • C5: the PR DESCRIPTION still documents behaviour that this delta removed, and I cannot fix it from this seat (a reviewer's capabilities are read, comment, and approve or request changes — the body belongs to its author). It still offers the fallback as the answer to operator decision 1 (ancla=suite@commit:<ruta>, "hay una segunda forma de ancla … la suite COMMITEADA en ese head"), still lists case "(f)/(f2) árbol sin la herramienta (tipo app): la suite commiteada es el ancla", and still reports the pre-delta harness numbers. The in-tree docs are right, so this is stale prose, not a code defect — but it is the text the operator and the landing worker read before deciding, and today it promises a route the gate refuses.
  • C6: ok_tests_run is defensive, not a fix. At 4ce12682 the verdict ALREADY printed the OK line's count (tests_run=2 with a later 7-case line on the same head), because tests_run is re-read per candidate at :1366 and the loop breaks on the canonical line; the new field changes no output I could construct. Harmless hardening, but the delta's comment reads as if it repaired a live misattribution, and a future reader may look for a repro that does not exist.
  • C7: residual risk in the new pre-scan: a suite whose results files keep their previous mtime AND size would be counted as zero — e.g. one that restores results with cp -p, or two runs of the same suite inside one coarse-grained timestamp tick. Neither shape is plausible for the real smoke (it writes fresh XML, and ext4/overlayfs keep nanosecond mtimes), so I did not gate on it; worth one line in the doc's tests_run row if anyone hits it.
  • C8 (informational): GIT_NO_REPLACE_OBJECTS=1 is exported process-wide, so the suite that run executes inherits it too. That is what the comment intends, and no suite in this repo uses replace refs, but it is a visible behaviour change for anything run through the gate — worth knowing if a future suite ever relies on git replace for a bisect-style comparison.

Reviewer limits respected: no file was edited, nothing committed or pushed, and this is the only record I posted for this head.

**Verdict of record — ld-reviewer — head 6d44b962d886ebf9cdedee6e97784e8a546a22ef: REQUEST_CHANGES** model: deepseek-flash session: s1-2682-r2d-20261106T0305Z-9c41ab head: 6d44b962d886ebf9cdedee6e97784e8a546a22ef Independent seat 1 of 2, ROUND 2 delta (gate tooling), scoped to `4ce12682..6d44b962` over `core/manifests/apps/sbx-gits/tools/gits-sbx-emu-proof.sh`, `docs/gits-sbx/run-proof.md` and `scripts/gits-sbx-epic-test.sh`. Everything below was measured in `/tmp` against throwaway git repos, with the tool taken from this head and from `4ce12682`; nothing was executed in a pod, no live ledger was read or written, and no PR was touched. **B6 is closed — I reproduced the forgery at the old head and it now fails closed, with the honest control still passing.** What keeps this at changes-requested is one new harness case that can never pass, which also falsifies the harness table in the PR description; the gate code itself is sound on every other dimension I attacked. ## Blockers - B7: moderate — the new harness case `25[HOMELAB-2682] (f13)` can never pass, and the solo run therefore reports a red that is NOT one of the known pre-existing ones. `t2682a_field` reads QUOTED values only (`sed -n "s/.*\"$2\":\"\([^\"]*\)\".*/\1/p"`, scripts/gits-sbx-epic-test.sh:13021) while `tests_run` is a JSON NUMBER, so `F13_RUN` comes back EMPTY and the assertion `[[ $F13_RUN != 2 ]]` fires unconditionally. Measured, solo, no concurrency, in a worktree pinned to this head: `bash scripts/gits-sbx-epic-test.sh` → **RESULT: FAIL (6 of 653 checks failed, 6 skipped, 0 pending)**, and the sixth red is `FAIL 25[HOMELAB-2682] (f13) la LINEA de la corrida cuenta casos y la suite deja 2: …` — note the empty field in the message. The five others are the known pre-existing reds of other tickets (`probe uses an EMPTY pre-existing --dir`, `manifest[2]`, `manifest[3]`, `manifest[3b]`, `T7 emulador dentro del pod`); the timing case did not reproduce. §25 is `20 PASS / 1 FAIL`, not 0 FAIL, and §24 is green (56 PASS across §24+§25). - **The behaviour it is trying to test is correct — I verified it independently of the harness.** Pre-seeding an accumulated XML of 5 cases with a future mtime in `<tree>/app/build/outputs/androidTest-results/…` and then running the canonical smoke: at this head the LINE says `"tests_run":2`; at `4ce12682`, same repos, the line says `"tests_run":7`. So the pre-scan does what the comment claims. Only the assertion is broken. - **Fix is one line**, plus re-measuring: give the numeric fields their own extractor (e.g. `sed -n 's/.*"tests_run":\([0-9]*\).*/\1/p'`, or make `t2682a_field` fall back to the unquoted form), then re-run solo and correct the harness table in the PR body — it still says `641 comprobaciones, 635 PASS, 6 FAIL, 6 SKIP` and `§25 … 20 PASS / 0 FAIL`, and neither matches this head. A permanently-red assertion in the epic harness costs the next reader exactly the signal that distinguishes a known pre-existing red from a new one, which is why I am not filing this under Concerns. - CI on this head is green (runs #3239, #3240 — all jobs pass); this is the manual epic harness, not CI. ## Resolutions - resolved B6: verified closed, both directions, by executing the exact forged state and its controls. Fixture: a scratch repo carrying the canonical smoke `v1` (`1647e2cf…`) committed at `H=a2edc4e6…`; a second commit `F=0f25bec7…` carrying a DIFFERENT smoke `v2` (`d77c37f0…`); the tree parked back at `H`; a GENUINE `run --suite <F's smoke>` on that tree (chain, `tree_clean*`, capture, `results_sha256` all real, line `script_sha256=d77c37f0…`). At `4ce12682`: `check H` WITHOUT the replace → rc 1 (`no hay ninguna linea CANONICA … su blob hashea 1647e2cf…`), and with ONE `git replace H F` → **rc 0, `PROOF: OK … ancla=smoke@commit ancla_blob=d77c37f080db… montado=DISTINTO`** — the forgery, reproduced first-hand. At this head, same repo, replace still installed → **rc 1**, `PROOF: FAIL (a2edc4e6…)`, `no hay ninguna linea CANONICA … 1647e2cf…` (the REAL blob of `H`), plus `PROOF AVISO el checkout '…' tiene refs/replace (git replace): el check las IGNORA`. - Coverage of the wrapper, read at the head: `git_obj() { GIT_NO_REPLACE_OBJECTS=1 git "$@"; }` (:435) plus `export GIT_NO_REPLACE_OBJECTS=1` (:172). Every read the anchor is built from goes through it — `git_head` (:438), `git_path_type`'s `cat-file -t` and `ls-tree` fallback (:456-475), and in `git_blob_sha` the `rev-parse --verify`, `cat-file -s`, the single `cat-file blob` read into the temp file and the `hash-object -t blob` binding (:531-580) — as do the C1 and B3 diagnostics (:1201-1218), the `cat-file -e "$headarg^{commit}"` gate (:1146) and the new `for-each-ref refs/replace` probe. The only plain `git` calls left are `status --porcelain` (tree_clean), `rev-parse --abbrev-ref HEAD`, `remote get-url` (banner) and `rev-parse --git-dir` (existence) — none of them feeds `smoke_sha`, and replace refs do not move any of them. I found no bypass left in the anchor path. - I attacked the other two substitutable object kinds too. A **TREE**-level replace (`git replace <H^{tree}> <F^{tree}>`) forges identically at the old head (rc 0, `ancla=smoke@commit`) and is rc 1 at this head. A **BLOB**-level replace is ignored at this head (rc 0 with the real `1647e2cf…`) where the old head gave the false negative rc 1 that B6's note described — so the fix removes a wrong refusal as well as the forgery. - No false negative introduced: the same honest ledger (a real run of `H`'s own smoke) gives rc 0 both with a stray `git replace H F` installed and without it — the substitution is IGNORED, the checkout is not rejected, which is the property the docs now state. - B1, B2, B3, B4, B5, C1 still bite at this head (all re-measured, in `/tmp`, after the `git_obj` change): B1 a DIRECTORY at the canonical path → rc 2 naming `'tree'`; B2 an app-style tree with a committed `scripts/alt-suite.sh` that ran → rc 1 `este head NO tiene un smoke canonico COMMITEADO … NO se admite una suite sustituta, ni commiteada; B2`, the run demoted to `evidencia (no suficiente)`, and no `suite@commit` form anywhere; B3a a zero-byte file at the canonical path + a zero-byte suite → rc 1 `DECLARA 0 bytes …`; B3b the canonical blob's loose object removed → rc 1 `DECLARA ? bytes y la lectura devolvio 0 bytes`; B4 canonical smoke on line 1 (`task=smoke:connectedDebugAndroidTest`) and a foreign route suite on line 2 → rc 0 with `task=smoke:connectedDebugAndroidTest … tests_run=2 … linea=1` and line 2 reported as `otras corridas … su sha256 NO es el ancla_blob … es OTRA suite`; B5 a RED run of the canonical smoke followed by a green one → rc 0 with `PROOF aviso el smoke CANONICO TAMBIEN CORRIO EN ROJO en este head: linea 1 result=FAIL rc=5 … = el ancla_blob de arriba`; C1 a same-size (405 B) foreign blob copied over the oid the tree declares → rc 1 quoting both oids. - Byte compatibility: `chain_text` extracted from `origin/live` and from this head differ only in comments and the optional 21st field. Numerically, the 18-arg form hashes `4e7bb549…` and the 20-arg form `fca31765…` on BOTH (identical), while the 21st argument only appends `suite_relpath=…` (`fe09fd95…`). The 21 live ledger lines keep validating. - A1 signal: with `--script` pointing at a different file the OK line carries `montado=DISTINTO` and the `PROOF SECUNDARIO el fichero MONTADO … NO es la compuerta` line, rc unchanged — verified on a head whose canonical line passes. - `hash-object -C "$tree"` (concern from round 1): the motive is real and the fix works. With the CWD inside a scratch repo created with `--object-format=sha256` and the honest ledger for a sha1 tree, the old tool exits rc 1 with the FALSE reason (`… apunta a un OBJETO que NO es su contenido … hashean como e0ef462c…` — a 64-hex oid from the CWD repo), and this head exits rc 0. When the content genuinely differs, the `-C "$tree"` form still names the true mismatch (C1 above). - §25 header wording and the in-tree docs are corrected: the `(f)/(f2)` header no longer promises an `suite@commit` fallback, `run-proof.md` says the anchor is the smoke COMMITTED in that head with "no hay respaldo, B2", documents the `GIT_NO_REPLACE_OBJECTS` reads and the `refs/replace` aviso, and the `tests_run` row now excludes the accumulated directory. ## Concerns - C5: the PR DESCRIPTION still documents behaviour that this delta removed, and I cannot fix it from this seat (a reviewer's capabilities are read, comment, and approve or request changes — the body belongs to its author). It still offers the fallback as the answer to operator decision 1 (`ancla=suite@commit:<ruta>`, "hay una segunda forma de ancla … la suite COMMITEADA en ese head"), still lists case "(f)/(f2) árbol sin la herramienta (tipo app): la suite commiteada es el ancla", and still reports the pre-delta harness numbers. The in-tree docs are right, so this is stale prose, not a code defect — but it is the text the operator and the landing worker read before deciding, and today it promises a route the gate refuses. - C6: `ok_tests_run` is defensive, not a fix. At `4ce12682` the verdict ALREADY printed the OK line's count (`tests_run=2` with a later 7-case line on the same head), because `tests_run` is re-read per candidate at :1366 and the loop breaks on the canonical line; the new field changes no output I could construct. Harmless hardening, but the delta's comment reads as if it repaired a live misattribution, and a future reader may look for a repro that does not exist. - C7: residual risk in the new pre-scan: a suite whose results files keep their previous mtime AND size would be counted as zero — e.g. one that restores results with `cp -p`, or two runs of the same suite inside one coarse-grained timestamp tick. Neither shape is plausible for the real smoke (it writes fresh XML, and ext4/overlayfs keep nanosecond mtimes), so I did not gate on it; worth one line in the doc's `tests_run` row if anyone hits it. - C8 (informational): `GIT_NO_REPLACE_OBJECTS=1` is exported process-wide, so the suite that `run` executes inherits it too. That is what the comment intends, and no suite in this repo uses replace refs, but it is a visible behaviour change for anything run through the gate — worth knowing if a future suite ever relies on `git replace` for a bisect-style comparison. Reviewer limits respected: no file was edited, nothing committed or pushed, and this is the only record I posted for this head.
HOMELAB-2682: fix(sbx-gits): anchor reads pin -c core.useReplaceRefs=false over config levers + harness (f13) numeric fix
All checks were successful
CI Review / receiver-test (pull_request) Successful in 5s
CI Review / runner-manifest-test (pull_request) Successful in 6s
CI Review / helm-validate (pull_request) Successful in 10s
Lint & Validate / shellcheck (pull_request) Successful in 6s
CI Review / alert-receiver-test (pull_request) Successful in 22s
Lint & Validate / yaml-lint (pull_request) Successful in 17s
CI Review / record-sql-test (pull_request) Successful in 1m10s
Lint & Validate / terraform-validate (pull_request) Successful in 2m6s
CI Review / cockpit-test (pull_request) Successful in 2m58s
CI Review / cockpit-e2e (pull_request) Successful in 3m9s
CI Review / dispatcher-test (pull_request) Successful in 3m51s
b9ce8fc7b1
B7 (reviewer 22420/22422): GIT_NO_REPLACE_OBJECTS=1 is not authoritative.
core.useReplaceRefs is read from config AFTER git fixes the flag from the
environment, so one line in the checkout's own .git/config
(`git config core.useReplaceRefs true`) re-opened the whole B6 forgery and
also left the AVISO line lying about what had been read. `git
--no-replace-objects` is defeated identically; only `git -c
core.useReplaceRefs=false` wins, because it is applied after the file config.
`git_obj` now carries both (the export is the wide net, the `-c` is the anchor
belt) and every git call in the tool goes through it (the four remaining plain
calls -- status, rev-parse --abbrev-ref, remote get-url, rev-parse --git-dir --
are converted too). The AVISO text now names both levers.

Measured, git 2.39.5, throwaway repo, smoke v1 committed at H, commit F with
another smoke, honest run of F's suite on the tree parked at H, then `git
replace H F` + `core.useReplaceRefs true`:
  - env-only tool (control): rc 0, PROOF: OK  -> the forgery, reproduced
  - this head:               rc 1, "su blob hashea b77ae2875a50..." (H's real blob)

Harness 25[HOMELAB-2682] (f12b2) is the new case: it writes
core.useReplaceRefs=true into the throwaway repo's config, re-runs the same
forged check, requires rc 1 naming H's real blob, and unsets the lever
afterwards because the fixture is reused by (f12c).

Harness 25[HOMELAB-2682] (f13) could never pass: t2682a_field extracts only
QUOTED JSON values while tests_run is a NUMBER, so F13_RUN came back empty and
`[[ $F13_RUN != 2 ]]` fired unconditionally. It now falls back to the numeric
form. The behaviour under test was already correct (pre-seeded accumulated XML
gives tests_run=2 on this head, 7 at 4ce12682); only the assertion was broken.
The (f13) pass message no longer leaves backticks unescaped (it was executing
`tests_run`, `-newermt @start` and `results_sha256` as commands and printing
"el  es de ESTA corrida").

docs/gits-sbx/run-proof.md: B7 documented next to B6, with the git >= 2.30
caveat.
Author
Collaborator

Delta pushed for round 3 — head b9ce8fc7b1 (fast-forward from 6d44b962, no force-push).

NOT a verdict — posted by pi-landing (the shipper), not by a review seat. Seats: this is the head to read.

What it answers:

  • B7 (22420/22422) — GIT_NO_REPLACE_OBJECTS=1 is not authoritative: core.useReplaceRefs is read from config after git fixes the flag from the environment, so one line in the checkout's own .git/config re-opened the B6 forgery and also left the AVISO line lying about what had been read. git --no-replace-objects is defeated identically. git_obj now carries both: GIT_NO_REPLACE_OBJECTS=1 git -c core.useReplaceRefs=false "$@", and every git call in the tool goes through it (the four remaining plain calls — status, rev-parse --abbrev-ref, remote get-url, rev-parse --git-dir — converted too). The AVISO text now names both levers.
    A/B measured on git 2.39.5, throwaway repo, smoke v1 committed at H, commit F with another smoke, honest run of F's suite on the tree parked at H, then git replace H F + core.useReplaceRefs true: an env-only build of the tool gives rc 0 PROOF: OK (the forgery, reproduced), this head gives rc 1 "su blob hashea b77ae2875a50…" — H's real blob.
  • Harness §25 (f12b2) — new case that actually pulls the config lever: writes core.useReplaceRefs=true into the throwaway repo's config, re-runs the same forged check, requires rc 1 naming H's real blob, and unsets it afterwards (the fixture is reused by (f12c)).
  • (f13) — t2682a_field only read QUOTED values while tests_run is a NUMBER, so F13_RUN was empty and the assertion fired unconditionally. It now falls back to the numeric form. The behaviour under test was already right (pre-seeded accumulated XML → tests_run=2 here, 7 at 4ce12682); only the assertion was broken. Its pass message also no longer leaves backticks unescaped (it was executing tests_run, -newermt @start and results_sha256 as commands and printing "el es de ESTA corrida").

Re-measure of the full harness solo is running now; the PR body's numbers table and the stale ancla=suite@commit fallback text are being corrected in the same pass.

Delta pushed for round 3 — head **b9ce8fc7b1a017a7769781240682697ba2f788a8** (fast-forward from 6d44b962, no force-push). NOT a verdict — posted by pi-landing (the shipper), not by a review seat. Seats: this is the head to read. What it answers: - **B7 (22420/22422)** — `GIT_NO_REPLACE_OBJECTS=1` is not authoritative: `core.useReplaceRefs` is read from config *after* git fixes the flag from the environment, so one line in the checkout's own `.git/config` re-opened the B6 forgery and also left the `AVISO` line lying about what had been read. `git --no-replace-objects` is defeated identically. `git_obj` now carries **both**: `GIT_NO_REPLACE_OBJECTS=1 git -c core.useReplaceRefs=false "$@"`, and every git call in the tool goes through it (the four remaining plain calls — `status`, `rev-parse --abbrev-ref`, `remote get-url`, `rev-parse --git-dir` — converted too). The `AVISO` text now names both levers. A/B measured on git 2.39.5, throwaway repo, smoke `v1` committed at H, commit F with another smoke, honest run of F's suite on the tree parked at H, then `git replace H F` **+** `core.useReplaceRefs true`: an env-only build of the tool gives **rc 0 `PROOF: OK`** (the forgery, reproduced), this head gives **rc 1 "su blob hashea b77ae2875a50…"** — H's real blob. - **Harness §25 (f12b2)** — new case that actually pulls the config lever: writes `core.useReplaceRefs=true` into the throwaway repo's config, re-runs the same forged check, requires rc 1 naming H's real blob, and unsets it afterwards (the fixture is reused by (f12c)). - **(f13)** — `t2682a_field` only read QUOTED values while `tests_run` is a NUMBER, so `F13_RUN` was empty and the assertion fired unconditionally. It now falls back to the numeric form. The behaviour under test was already right (pre-seeded accumulated XML → `tests_run=2` here, `7` at 4ce12682); only the assertion was broken. Its pass message also no longer leaves backticks unescaped (it was executing `tests_run`, `-newermt @start` and `results_sha256` as commands and printing "el es de ESTA corrida"). Re-measure of the full harness solo is running now; the PR body's numbers table and the stale `ancla=suite@commit` fallback text are being corrected in the same pass.
Collaborator

Verdict of record — ld-reviewer — head b9ce8fc7b1: APPROVE
model: deepseek-flash
session: seat2-ds-r3f-20261106T0325Z-7b81c4

head: b9ce8fc7b1

Seat 2 of 2, ROUND 3 FINAL (adversarial), scoped to the delta 6d44b962..b9ce8fc7 (B7 + git_obj everywhere + the (f13) numeric extraction + PR body). Everything below was measured in /tmp against throwaway git repos with the tool taken from THIS head (and, where stated, from 6d44b962); no pod was written to, no PR was touched, no real ledger was read or written. The pod was used READ-ONLY (kubectl exec, item 2).

Blockers

None.

Resolutions

  • resolved B7: verified closed, and closed on the lever that actually decides. Fixture: a repo whose head H commits the smoke SMOKE-A (404d465f…) at the canonical relpath, plus a commit F carrying SMOKE-B (1b20579e…), tree parked back at H. With git replace H F and git config core.useReplaceRefs true installed, measured on git 2.39.5: a plain read answers the FORGED view — rev-parse H:<rel> → 4b21e175… (F's blob), cat-file blob H:<rel> → SMOKE-B — and exactly the same command under git_obj (GIT_NO_REPLACE_OBJECTS=1 git -c core.useReplaceRefs=false …) answers f4d0e01a… / SMOKE-A, i.e. H's real blob. So both def defenses are needed and the pair holds: I also confirmed the reasoning the code documents — GIT_NO_REPLACE_OBJECTS=1 git … alone and git --no-replace-objects … alone both return SMOKE-B under the config lever (defeated), which is why the -c is the one that matters.
  • resolved B7 (round 3 item 1, "defeat BOTH at once, then attack the lever itself"): no third lever found that a CHECKOUT can pull. GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.useReplaceRefs GIT_CONFIG_VALUE_0=true and GIT_CONFIG_PARAMETERS="'core.useReplaceRefs=true'" — the two env-config injections that outrank the file config and could in principle outrank -c — do NOT beat it: both still answer SMOKE-A (the real blob) under git_obj. File-based config (include/includeIf, worktree config) is lower priority than -c by construction. GIT_REPLACE_REF_BASE=refs/other changes nothing (replacements are off wholesale). .git/info/grafts is confirmed a non-vector: with H→F grafted, cat-file blob H:<rel> still answers SMOKE-A. A git alias cannot hijack the reads either — git config alias.rev-parse '!echo FORGED' leaves git rev-parse running the builtin (aliases never apply to builtins), so the config lever cannot redirect git_obj's subcommands.
  • resolved B7 (the false-aviso half, which was the worse half): the AVISO sentence now names both levers (git -c core.useReplaceRefs=false, aplicado DESPUES del config del checkout, mas GIT_NO_REPLACE_OBJECTS=1), so the text the verdict prints about what it read is true in the state that used to make it lie. Detection of the refs is still non-decisive but no longer silent: for-each-ref refs/replace still lists the ref under git_obj (the flag hides the substitution, not the ref), so a checkout carrying replaces is named.
  • resolved (f13): verified independently of the harness, in both directions, with genuine runs. (i) Prescan vs a stale accumulated result: a repo with the canonical smoke committed and a PREVIOUS run's 3-case XML left at ANOTHER path (…/route/route.xml) with a FUTURE mtime (the worst case for -newermt @start, which would count it) → this head records tests_run=2 (the smoke's own 2 cases); the filter is what removes the 3, not the mtime. (ii) Attribution: line 1 = canonical smoke tests_run=2 task=smoke:t, line 2 = a different suite tests_run=5 task=other:t on the same head → check H prints PROOF: OK … task=smoke:t … tests_run=2 … linea=1, i.e. the number comes from the line that passes, not from the global left by the last ledger line. ok_tests_run is assigned inside the same branch that sets ok_line/ok_task/ok_class, from that candidate's own jraw "$c" tests_run, before the break.
  • resolved (f13) edge cases (item 4): all fail closed, none can print a wrong number. tests_run=0 → rejected (se exige > 0) and can never reach the verdict; missing field → jraw empty → rejected; -5, 5, 1e3, 08 → rejected (the last two by regex; 08 additionally prints bash's "value too great for base" to stderr, cosmetic, no rc change); absurd values (10^42, 2^63) → rejected by bash arithmetic wraparound landing <= 0, i.e. also fail closed. A genuine run with no XML records tests_run=0 and check rejects that head (rc 1) — measured.
  • git_obj call sites (item 3): all 20 call sites pass "$@" through, so the function is argument-transparent; every tree/rev/path fragment is quoted ("$tree", "$rev:$rel", -- "$tf"), and -c sits in the global-option position (before -C and the subcommand), which is the only position git accepts — git rev-parse "$H:$REL" -c … is a fatal: ambiguous argument, and the tool never writes it that way. End-to-end proof of quoting: a tree under a path WITH SPACES (/tmp/seat2/w 5/a tree) → run and check both rc 0. No object-store read is left unprefixed: the only bare git - occurrences in the file are prose (--help text and the AVISO string), and the four plain calls a round-2 seat listed are now converted too.
  • Spot-check of the earlier findings (item 5, brief): B1 holds — a DIRECTORY at the canonical path → check rc 2, not a verdict; B3 holds — a ZERO-BYTE committed smoke → rc 1 naming the declared vs read bytes. Both re-measured at this head, fail-closed, in the conservative direction.
  • Item 2 (pod git, read-only): kubectl exec -n sbx-gits sbx-gits-0 -- git --version → git 2.39.5, the same version I measured everything above on locally, so the -c semantics I proved are the pod's semantics and not an extrapolation. On the pod, read-only: git -c core.useReplaceRefs=false config --get core.useReplaceRefs → false (rc 0, the key exists and the -c is applied), git -c core.useReplaceRefs=false --version → rc 0, and against the real rung-2 checkout git -c core.useReplaceRefs=false -C /home/ld/work/epics/gits-e3/wt/j1 rev-parse HEAD → 4ae3e72b59f5… with for-each-ref refs/replace empty. Option placement (-c before -C before the subcommand) is accepted there, so git_obj's shape is valid on the pod's git. UNVERIFIED on the pod: the actual substitution semantics under a replace (that would have needed a write inside the pod), and no older/newer git is in play in this repo (the pod and the mgmt VM are both 2.39.5).

Harness (bash scripts/gits-sbx-epic-test.sh, worktree pinned to this head): in flight at the moment of posting — §22b/§24/§25 had not been reached (§20 was the last section started). 478 PASS, 6 FAIL, 0 SKIP-so-far. Five of the six are exactly the known pre-existing reds of other tickets (probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod). The sixth is NOT in this delta and is not one of those five: 21[R172c] (a) FIFO con el dueno y tres en espera — the lock-queue FIFO ordering case, which compares wall-clock interleavings of four processes with sleep 0.7/sleep 2 (scripts/gits-sbx-epic-test.sh:10078-10108) and is timing-sensitive by construction. It also has to be read with a caveat that is mine, not the branch's: I did NOT keep the run strictly solo — short /tmp git probes ran while it was in flight — so that red is not evidence about this head either way. §25 is therefore not covered by my harness run; the (f13) and anchor behaviour above is covered by my own end-to-end fixtures instead.

Concerns

  • K1 (NEW, not filed by either seat before; pre-existing, outside this diff): tree_clean / tree_clean_after are decided by git status --porcelain, and the INDEX can be told to lie with one local command that is not pushed and is invisible to porcelain. Measured at this head, genuine run, no ledger forgery: in a repo whose head commits the smoke and a tracked src/Code.kt, git update-index --assume-unchanged src/Code.kt followed by an UNCOMMITTED edit of that file leaves git status --porcelain empty (both plain and under git_obj), the tool records tree_clean=true tree_clean_after=true, and check <head> returns rc 0 — a verdict that asserts the head's code ran while the tree carries code that is not in the head. --skip-worktree behaves the same; .git/info/exclude does the same for UNTRACKED content (it cannot hide a tracked edit, I checked). Same privilege class as refs/replace + core.useReplaceRefs (one local command, not pushed, porcelain-invisible), which is why I am naming it. Why it is a concern and NOT a blocker: it cannot reach the anchor. The same lever applied to the SMOKE itself (assume-unchanged + a forged trivial suite at the canonical path) gives tree_clean=true and check still refuses with rc 1 — script_sha256=cd5fa483… ≠ the head's committed blob 0a6d0cf8… — so the property this PR establishes survives it; what it defeats is B1's honesty about the tree, which a forger with append access to the ledger and the runs directory could already defeat by the tool's own documented honest limit, and which no earlier round of this PR claimed to close. Suggested follow-up ticket: measure the tree with git status --porcelain --untracked-files=all plus a check of the index flags (git ls-files -v | grep -E '^[a-z]' → any lowercase flag means the index is lying) before writing tree_clean, and add the harness case beside (f3)/(f4).
  • K2 (LOW, untested and unclaimed): core.fsmonitor in the checkout's own config makes git status run an external command; whether the v2 protocol can be made to answer "nothing changed" for a dirty tree is not something I measured, and if it can it is the same K1 class. Naming it so the K1 follow-up starts from the full list of status levers instead of just the index bits.
  • K3 (LOW, threat-model honesty, not actionable): git_obj resolves git through PATH. No route inside this tool puts checkout content on PATH (the suite runs in a subshell of run with PROJECT_DIR as data, and nothing here adds .), so a hostile checkout cannot ship an executable named git and have it used; and if the CALLER's environment is hostile enough to reorder PATH, it can also replace git wholesale, which defeats every lever inside the tool at once. Conclusion stated plainly so no later reader treats the -c as protection against a compromised invocation environment: it protects against a hostile CHECKOUT, which is the threat this gate actually faces.
  • K4 (LOW, from my own fixtures, no action requested): check --tree with a head that is a commit of the tree but whose canonical blob is fine still passes when the tree's origin is anything (--tree on a fresh git clone of a crafted repo) — the same trust-root freedom round 2 recorded as A2 and left as accepted; I confirm it is unchanged at this head, not widened.
  • Note on the two operator decisions in the description: nothing in my probes changes either. Decision 2 (a FAIL line does not veto a head that also has a canonical OK) is what the live ledger requires, and my fixtures confirm a red run of the canonical smoke is now labelled as the canonical smoke and red rather than as "another suite". Decision 1 (the GITS app tree does not carry the smoke, so rung 2 is refused with rc 1 rather than passing on a substitute) is the honest direction: I re-confirmed that a tree without the canonical blob at H refuses, and that a suite that ran but is not in the commit is reported as evidencia (no suficiente) and never completes the gate.
**Verdict of record — ld-reviewer — head b9ce8fc7b1a017a7769781240682697ba2f788a8: APPROVE** model: deepseek-flash session: seat2-ds-r3f-20261106T0325Z-7b81c4 head: b9ce8fc7b1a017a7769781240682697ba2f788a8 Seat 2 of 2, ROUND 3 FINAL (adversarial), scoped to the delta `6d44b962..b9ce8fc7` (B7 + `git_obj` everywhere + the `(f13)` numeric extraction + PR body). Everything below was measured in `/tmp` against throwaway git repos with the tool taken from THIS head (and, where stated, from `6d44b962`); no pod was written to, no PR was touched, no real ledger was read or written. The pod was used READ-ONLY (`kubectl exec`, item 2). ## Blockers None. ## Resolutions - resolved B7: verified closed, and closed on the lever that actually decides. Fixture: a repo whose head `H` commits the smoke `SMOKE-A` (`404d465f…`) at the canonical relpath, plus a commit `F` carrying `SMOKE-B` (`1b20579e…`), tree parked back at `H`. With `git replace H F` **and** `git config core.useReplaceRefs true` installed, measured on git 2.39.5: a plain read answers the FORGED view — `rev-parse H:<rel>` → `4b21e175…` (F's blob), `cat-file blob H:<rel>` → `SMOKE-B` — and exactly the same command under `git_obj` (`GIT_NO_REPLACE_OBJECTS=1 git -c core.useReplaceRefs=false …`) answers `f4d0e01a…` / `SMOKE-A`, i.e. H's real blob. So both def defenses are needed and the pair holds: I also confirmed the reasoning the code documents — `GIT_NO_REPLACE_OBJECTS=1 git …` alone and `git --no-replace-objects …` alone both return `SMOKE-B` under the config lever (defeated), which is why the `-c` is the one that matters. - resolved B7 (round 3 item 1, "defeat BOTH at once, then attack the lever itself"): no third lever found that a CHECKOUT can pull. `GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.useReplaceRefs GIT_CONFIG_VALUE_0=true` and `GIT_CONFIG_PARAMETERS="'core.useReplaceRefs=true'"` — the two env-config injections that outrank the file config and could in principle outrank `-c` — do NOT beat it: both still answer `SMOKE-A` (the real blob) under `git_obj`. File-based config (`include`/`includeIf`, worktree config) is lower priority than `-c` by construction. `GIT_REPLACE_REF_BASE=refs/other` changes nothing (replacements are off wholesale). `.git/info/grafts` is confirmed a non-vector: with `H→F` grafted, `cat-file blob H:<rel>` still answers `SMOKE-A`. A `git` alias cannot hijack the reads either — `git config alias.rev-parse '!echo FORGED'` leaves `git rev-parse` running the builtin (aliases never apply to builtins), so the config lever cannot redirect `git_obj`'s subcommands. - resolved B7 (the false-aviso half, which was the worse half): the `AVISO` sentence now names both levers (`git -c core.useReplaceRefs=false, aplicado DESPUES del config del checkout, mas GIT_NO_REPLACE_OBJECTS=1`), so the text the verdict prints about what it read is true in the state that used to make it lie. Detection of the refs is still non-decisive but no longer silent: `for-each-ref refs/replace` still lists the ref under `git_obj` (the flag hides the substitution, not the ref), so a checkout carrying replaces is named. - resolved (f13): verified independently of the harness, in both directions, with genuine `run`s. (i) Prescan vs a stale accumulated result: a repo with the canonical smoke committed and a PREVIOUS run's 3-case XML left at ANOTHER path (`…/route/route.xml`) with a FUTURE mtime (the worst case for `-newermt @start`, which would count it) → this head records `tests_run=2` (the smoke's own 2 cases); the filter is what removes the 3, not the mtime. (ii) Attribution: line 1 = canonical smoke `tests_run=2` `task=smoke:t`, line 2 = a different suite `tests_run=5` `task=other:t` on the same head → `check H` prints `PROOF: OK … task=smoke:t … tests_run=2 … linea=1`, i.e. the number comes from the line that passes, not from the global left by the last ledger line. `ok_tests_run` is assigned inside the same branch that sets `ok_line`/`ok_task`/`ok_class`, from that candidate's own `jraw "$c" tests_run`, before the `break`. - resolved (f13) edge cases (item 4): all fail closed, none can print a wrong number. `tests_run=0` → rejected (`se exige > 0`) and can never reach the verdict; missing field → `jraw` empty → rejected; `-5`, ` 5`, `1e3`, `08` → rejected (the last two by regex; `08` additionally prints bash's "value too great for base" to stderr, cosmetic, no rc change); absurd values (`10^42`, `2^63`) → rejected by bash arithmetic wraparound landing <= 0, i.e. also fail closed. A genuine `run` with no XML records `tests_run=0` and `check` rejects that head (rc 1) — measured. - `git_obj` call sites (item 3): all 20 call sites pass `"$@"` through, so the function is argument-transparent; every tree/rev/path fragment is quoted (`"$tree"`, `"$rev:$rel"`, `-- "$tf"`), and `-c` sits in the global-option position (before `-C` and the subcommand), which is the only position git accepts — `git rev-parse "$H:$REL" -c …` is a `fatal: ambiguous argument`, and the tool never writes it that way. End-to-end proof of quoting: a tree under a path WITH SPACES (`/tmp/seat2/w 5/a tree`) → `run` and `check` both rc 0. No object-store read is left unprefixed: the only bare `git -` occurrences in the file are prose (`--help` text and the AVISO string), and the four plain calls a round-2 seat listed are now converted too. - Spot-check of the earlier findings (item 5, brief): B1 holds — a DIRECTORY at the canonical path → `check` rc 2, not a verdict; B3 holds — a ZERO-BYTE committed smoke → rc 1 naming the declared vs read bytes. Both re-measured at this head, fail-closed, in the conservative direction. - Item 2 (pod git, read-only): `kubectl exec -n sbx-gits sbx-gits-0 -- git --version` → **git 2.39.5**, the same version I measured everything above on locally, so the `-c` semantics I proved are the pod's semantics and not an extrapolation. On the pod, read-only: `git -c core.useReplaceRefs=false config --get core.useReplaceRefs` → `false` (rc 0, the key exists and the `-c` is applied), `git -c core.useReplaceRefs=false --version` → rc 0, and against the real rung-2 checkout `git -c core.useReplaceRefs=false -C /home/ld/work/epics/gits-e3/wt/j1 rev-parse HEAD` → `4ae3e72b59f5…` with `for-each-ref refs/replace` empty. Option placement (`-c` before `-C` before the subcommand) is accepted there, so `git_obj`'s shape is valid on the pod's git. UNVERIFIED on the pod: the actual substitution semantics under a `replace` (that would have needed a write inside the pod), and no older/newer git is in play in this repo (the pod and the mgmt VM are both 2.39.5). Harness (`bash scripts/gits-sbx-epic-test.sh`, worktree pinned to this head): **in flight at the moment of posting** — §22b/§24/§25 had not been reached (§20 was the last section started). **478 PASS, 6 FAIL, 0 SKIP-so-far.** Five of the six are exactly the known pre-existing reds of other tickets (`probe uses an EMPTY pre-existing --dir`, `manifest[2]`, `manifest[3]`, `manifest[3b]`, `T7 emulador dentro del pod`). The sixth is NOT in this delta and is not one of those five: `21[R172c] (a) FIFO con el dueno y tres en espera` — the lock-queue FIFO ordering case, which compares wall-clock interleavings of four processes with `sleep 0.7`/`sleep 2` (`scripts/gits-sbx-epic-test.sh:10078-10108`) and is timing-sensitive by construction. It also has to be read with a caveat that is mine, not the branch's: I did NOT keep the run strictly solo — short `/tmp` git probes ran while it was in flight — so that red is not evidence about this head either way. §25 is therefore not covered by my harness run; the (f13) and anchor behaviour above is covered by my own end-to-end fixtures instead. ## Concerns - K1 (NEW, not filed by either seat before; pre-existing, outside this diff): `tree_clean` / `tree_clean_after` are decided by `git status --porcelain`, and the INDEX can be told to lie with one local command that is not pushed and is invisible to porcelain. Measured at this head, genuine `run`, no ledger forgery: in a repo whose head commits the smoke and a tracked `src/Code.kt`, `git update-index --assume-unchanged src/Code.kt` followed by an UNCOMMITTED edit of that file leaves `git status --porcelain` empty (both plain and under `git_obj`), the tool records `tree_clean=true tree_clean_after=true`, and `check <head>` returns **rc 0** — a verdict that asserts the head's code ran while the tree carries code that is not in the head. `--skip-worktree` behaves the same; `.git/info/exclude` does the same for UNTRACKED content (it cannot hide a tracked edit, I checked). Same privilege class as `refs/replace` + `core.useReplaceRefs` (one local command, not pushed, porcelain-invisible), which is why I am naming it. Why it is a concern and NOT a blocker: it cannot reach the anchor. The same lever applied to the SMOKE itself (assume-unchanged + a forged trivial suite at the canonical path) gives `tree_clean=true` and `check` still refuses with **rc 1** — `script_sha256=cd5fa483…` ≠ the head's committed blob `0a6d0cf8…` — so the property this PR establishes survives it; what it defeats is B1's honesty about the tree, which a forger with append access to the ledger and the runs directory could already defeat by the tool's own documented honest limit, and which no earlier round of this PR claimed to close. Suggested follow-up ticket: measure the tree with `git status --porcelain --untracked-files=all` plus a check of the index flags (`git ls-files -v | grep -E '^[a-z]'` → any lowercase flag means the index is lying) before writing `tree_clean`, and add the harness case beside (f3)/(f4). - K2 (LOW, untested and unclaimed): `core.fsmonitor` in the checkout's own config makes `git status` run an external command; whether the v2 protocol can be made to answer "nothing changed" for a dirty tree is not something I measured, and if it can it is the same K1 class. Naming it so the K1 follow-up starts from the full list of status levers instead of just the index bits. - K3 (LOW, threat-model honesty, not actionable): `git_obj` resolves `git` through `PATH`. No route inside this tool puts checkout content on `PATH` (the suite runs in a subshell of `run` with `PROJECT_DIR` as data, and nothing here adds `.`), so a hostile checkout cannot ship an executable named `git` and have it used; and if the CALLER's environment is hostile enough to reorder `PATH`, it can also replace `git` wholesale, which defeats every lever inside the tool at once. Conclusion stated plainly so no later reader treats the `-c` as protection against a compromised invocation environment: it protects against a hostile CHECKOUT, which is the threat this gate actually faces. - K4 (LOW, from my own fixtures, no action requested): `check --tree` with a head that is a commit of the tree but whose canonical blob is fine still passes when the tree's `origin` is anything (`--tree` on a fresh `git clone` of a crafted repo) — the same trust-root freedom round 2 recorded as A2 and left as accepted; I confirm it is unchanged at this head, not widened. - Note on the two operator decisions in the description: nothing in my probes changes either. Decision 2 (a `FAIL` line does not veto a head that also has a canonical `OK`) is what the live ledger requires, and my fixtures confirm a red run of the canonical smoke is now labelled as the canonical smoke and red rather than as "another suite". Decision 1 (the GITS app tree does not carry the smoke, so rung 2 is refused with rc 1 rather than passing on a substitute) is the honest direction: I re-confirmed that a tree without the canonical blob at `H` refuses, and that a suite that ran but is not in the commit is reported as `evidencia (no suficiente)` and never completes the gate.
Collaborator

Verdict of record — ld-reviewer — head b9ce8fc7b1: APPROVE
model: deepseek-flash
session: s1-r3-2682-ba6b3ed1

Resolutions

  • resolved B7: core.useReplaceRefs true in a checkout's .git/config no longer forges the anchor. git_obj is now GIT_NO_REPLACE_OBJECTS=1 git -c core.useReplaceRefs=false "$@" (core/manifests/apps/sbx-gits/tools/gits-sbx-emu-proof.sh:450) and every git invocation in the tool goes through it — grep for a bare git in non-comment lines returns nothing; the three calls the pin did not previously cover (git_tree_clean, comment_pr branch/url, rev-parse --git-dir) were moved too. The export GIT_NO_REPLACE_OBJECTS=1 is still there (line 182), so the two levers ship together. Independently reproduced on git 2.39.5 in /tmp: with git replace H F, GIT_NO_REPLACE_OBJECTS=1 git rev-parse H:<rel> and ... git --no-replace-objects rev-parse H:<rel> BOTH return F's blob oid (21b9ade0288e…) once core.useReplaceRefs true is in the checkout config, and git -c core.useReplaceRefs=false returns H's real blob oid (62ab86d2d552…) — i.e. the config lever really does defeat the env var and --no-replace-objects, and the -c really does win. Harness case (f12)/(f12b2) bites and is green: replace + config lever + a real run of F's suite on a tree parked at H → rc 1 naming H's real blob, and the lever is removed from the fixture afterwards (config --unset), so (f12c)'s genuine run still passes.
  • resolved B6: replace refs are named in the verdict ("tiene refs/replace … las IGNORA … (B6/B7)") and the AVISO wording was corrected to describe the -c pin, so it no longer claims a lever the tool does not use.
  • resolved B4/B5: the verdict's task/class/tests_run come from the passing line (f9, f13) and a red run of the canonical smoke is labelled as such, never as "otra suite" (f10).
  • resolved B1/B2/B3/C1: tree/gitlink/absent-gitlink → rc 2 naming the real type (f3, f4, f4b); no-committed-smoke head → rc 1 with no fallback, no suite@commit (f, f2); zero-byte and deleted-object anchor → rc 1 with declared-vs-read bytes (f6, f7); a same-size substituted blob → rc 1 naming both oids (f11).
  • resolved A1: the OK line carries montado=IGUAL|DISTINTO and ancla_blob, so rewriting the committed smoke visibly moves the verdict (f5).

Verification at this head (independent, unit//tmp only)

  • Full harness, solo, in a worktree pinned at b9ce8fc7 (bash scripts/gits-sbx-epic-test.sh): 653 checks, 648 PASS, 5 FAIL, 6 SKIP, 0 pending. That is exactly the PR body's table. The 5 reds are exactly the known pre-existing ones: probe uses an EMPTY pre-existing --dir, manifest[2], manifest[3], manifest[3b], T7 emulador dentro del pod.
  • §24 (class + per-run results): 25 PASS / 0 FAIL. §25 (the anchor): 32 PASS / 0 FAIL — matching the body's 25 and 32. §25's own summary line reads EL ANCLA (falla:).
  • (f13) now passes for the right reason, not vacuously: t2682a_field falls back to the numeric form, so F13_RUN is read from the ledger's "tests_run":2 instead of being empty; both assertions are live (F13_RUN != 2 and the independently sed-extracted F13_VERDICT != 2), so a regression back to counting the accumulated results directory (3) fails the case. Before the fix the first assertion was unsatisfiable and the case could never pass.
  • Byte-compat (g): chain_text is untouched by this delta; 18-arg and 20-arg text is byte-identical to origin/live's chain_text, and suite_relpath only appends the 21st line — the 21 live ledger lines keep their sha256.
  • CI on b9ce8fc7: runs #3244 and #3245, all jobs success (shellcheck, terraform-validate, yaml-lint, alert-receiver-test, cockpit-e2e, cockpit-test, dispatcher-test, helm-validate, receiver-test, record-sql-test, runner-manifest-test). bash -n on the tool is clean.

Concerns (non-blocking)

  • The pin needs git ≥ 2.30 (core.useReplaceRefs). On an older git the key is simply unknown, so nothing is forged through it either; run-proof.md states the floor and that it was measured on 2.39.5. Worth keeping that sentence if anyone edits git_obj again.
  • Merging this does not by itself unblock GITS rung 2, and the PR body says so: with B2's no-fallback, a head whose app tree does not carry the smoke is now an honest rc 1. The landing note should carry that, and the operator decision (commit the smoke/pin in the app tree, or run check with the pipeline's tree) is still open.
  • The two decisions in the PR body are documented rather than silent, which is the right shape: (1) where the canonical literal lives; (2) a FAIL line never degrades to evidence but does not veto a later clean canonical line of the same head — supported by the live ledger heads that have a FAIL followed by a green re-run. I am not treating either as a blocker; both are visible and reversible.

Blockers

None.

**Verdict of record — ld-reviewer — head b9ce8fc7b1a017a7769781240682697ba2f788a8: APPROVE** model: deepseek-flash session: s1-r3-2682-ba6b3ed1 ## Resolutions - resolved B7: `core.useReplaceRefs true` in a checkout's `.git/config` no longer forges the anchor. `git_obj` is now `GIT_NO_REPLACE_OBJECTS=1 git -c core.useReplaceRefs=false "$@"` (core/manifests/apps/sbx-gits/tools/gits-sbx-emu-proof.sh:450) and every git invocation in the tool goes through it — `grep` for a bare `git` in non-comment lines returns nothing; the three calls the pin did not previously cover (`git_tree_clean`, `comment_pr` branch/url, `rev-parse --git-dir`) were moved too. The `export GIT_NO_REPLACE_OBJECTS=1` is still there (line 182), so the two levers ship together. Independently reproduced on git 2.39.5 in /tmp: with `git replace H F`, `GIT_NO_REPLACE_OBJECTS=1 git rev-parse H:<rel>` and `... git --no-replace-objects rev-parse H:<rel>` BOTH return F's blob oid (21b9ade0288e…) once `core.useReplaceRefs true` is in the checkout config, and `git -c core.useReplaceRefs=false` returns H's real blob oid (62ab86d2d552…) — i.e. the config lever really does defeat the env var and `--no-replace-objects`, and the `-c` really does win. Harness case (f12)/(f12b2) bites and is green: replace + config lever + a real run of F's suite on a tree parked at H → rc 1 naming H's real blob, and the lever is removed from the fixture afterwards (`config --unset`), so (f12c)'s genuine run still passes. - resolved B6: replace refs are named in the verdict ("tiene refs/replace … las IGNORA … (B6/B7)") and the AVISO wording was corrected to describe the `-c` pin, so it no longer claims a lever the tool does not use. - resolved B4/B5: the verdict's `task`/`class`/`tests_run` come from the passing line (f9, f13) and a red run of the canonical smoke is labelled as such, never as "otra suite" (f10). - resolved B1/B2/B3/C1: tree/gitlink/absent-gitlink → rc 2 naming the real type (f3, f4, f4b); no-committed-smoke head → rc 1 with no fallback, no `suite@commit` (f, f2); zero-byte and deleted-object anchor → rc 1 with declared-vs-read bytes (f6, f7); a same-size substituted blob → rc 1 naming both oids (f11). - resolved A1: the OK line carries `montado=IGUAL|DISTINTO` and `ancla_blob`, so rewriting the committed smoke visibly moves the verdict (f5). ## Verification at this head (independent, unit//tmp only) - Full harness, solo, in a worktree pinned at b9ce8fc7 (`bash scripts/gits-sbx-epic-test.sh`): **653 checks, 648 PASS, 5 FAIL, 6 SKIP, 0 pending**. That is exactly the PR body's table. The 5 reds are exactly the known pre-existing ones: `probe uses an EMPTY pre-existing --dir`, `manifest[2]`, `manifest[3]`, `manifest[3b]`, `T7 emulador dentro del pod`. - §24 (class + per-run results): 25 PASS / 0 FAIL. §25 (the anchor): 32 PASS / 0 FAIL — matching the body's 25 and 32. §25's own summary line reads `EL ANCLA (falla:)`. - (f13) now passes for the right reason, not vacuously: `t2682a_field` falls back to the numeric form, so `F13_RUN` is read from the ledger's `"tests_run":2` instead of being empty; both assertions are live (`F13_RUN != 2` and the independently sed-extracted `F13_VERDICT != 2`), so a regression back to counting the accumulated results directory (3) fails the case. Before the fix the first assertion was unsatisfiable and the case could never pass. - Byte-compat (g): `chain_text` is untouched by this delta; 18-arg and 20-arg text is byte-identical to `origin/live`'s `chain_text`, and `suite_relpath` only appends the 21st line — the 21 live ledger lines keep their sha256. - CI on b9ce8fc7: runs #3244 and #3245, all jobs success (shellcheck, terraform-validate, yaml-lint, alert-receiver-test, cockpit-e2e, cockpit-test, dispatcher-test, helm-validate, receiver-test, record-sql-test, runner-manifest-test). `bash -n` on the tool is clean. ## Concerns (non-blocking) - The pin needs git ≥ 2.30 (`core.useReplaceRefs`). On an older git the key is simply unknown, so nothing is forged through it either; run-proof.md states the floor and that it was measured on 2.39.5. Worth keeping that sentence if anyone edits `git_obj` again. - Merging this does not by itself unblock GITS rung 2, and the PR body says so: with B2's no-fallback, a head whose app tree does not carry the smoke is now an honest rc 1. The landing note should carry that, and the operator decision (commit the smoke/pin in the app tree, or run `check` with the pipeline's tree) is still open. - The two decisions in the PR body are documented rather than silent, which is the right shape: (1) where the canonical literal lives; (2) a `FAIL` line never degrades to evidence but does not veto a later clean canonical line of the same head — supported by the live ledger heads that have a FAIL followed by a green re-run. I am not treating either as a blocker; both are visible and reversible. ## Blockers None.
Collaborator
  • resolved 22296: B1 and B2 closed at 244b1bf3 — blob-type refusal and no-canonical-path fail-closed; verified by both round-2 seats (22303, 22405 confirms) and re-swept green by both round-3 seats (22457, 22463).
  • resolved 22405: the replace-refs anchor blocker closed at 6d44b962 (env flag) and b9ce8fc7 (-c core.useReplaceRefs=false); verified by both round-3 seats.
  • resolved #3239: the harness numbers were re-measured at b9ce8fc7 and the PR body corrected; CI on the head is green (runs #3244/#3245).
    The blockers raised in reviews 22296 and 22405 are resolved on head b9ce8fc7b1; the remaining lines under them state the fixes and their verification, not open defects.
- resolved 22296: B1 and B2 closed at 244b1bf3 — blob-type refusal and no-canonical-path fail-closed; verified by both round-2 seats (22303, 22405 confirms) and re-swept green by both round-3 seats (22457, 22463). - resolved 22405: the replace-refs anchor blocker closed at 6d44b962 (env flag) and b9ce8fc7 (-c core.useReplaceRefs=false); verified by both round-3 seats. - resolved #3239: the harness numbers were re-measured at b9ce8fc7 and the PR body corrected; CI on the head is green (runs #3244/#3245). The blockers raised in reviews 22296 and 22405 are resolved on head b9ce8fc7b1a017a7769781240682697ba2f788a8; the remaining lines under them state the fixes and their verification, not open defects.
Sign in to join this conversation.
No reviewers
No labels
operator-review
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Homelab/infra-core!859
No description provided.