SDD 26: la unidad 5 está hecha — el host, y por qué la extensión no puede hablarle al testigo

Cierra la fila 5 del plan con lo que hay: `puriy-costura` + `shared/foreign-webext` en tawasuyu
(`ebe41e96`, 14 tests) y `recipes/puriy-costura.toml` acá (`b3:3b633431`, verificado como artefacto:
contesta un marco de 4 bytes y hace la promesa del §6.1 completa).

El §7.quater deja tres cosas que no estaban escritas:

· POR QUÉ HACÍA FALTA el host y no se podía atajar: el §6.1 decía que la extensión «consulta al
  testigo», y el cable del testigo es un POST con postcard, no JSON — un JS no puede ser su cliente.
  Reimplementar el TOFU en la extensión sería el sustituto paralelo que la regla 10 de tawasuyu
  prohíbe, además de duplicar la única pieza ya certificada.

· CÓMO SE ELIGIÓ EL NOMBRE, con el grep de la regla 10 hecho ANTES: ningún término del dominio
  significaba esto, y los que suenan a mensajero están ocupados por dominios ajenos y grandes
  (chasqui = type broker, chaka = puente a COBOL, paloma = correo, tampu = común de objetos). El
  nombre salió del título de este propio §7: la costura.

· LOS DOS LÍMITES, que van en el código y en los LEEME y no en una nota de sesión: se hashea el texto
  ya decodificado (vale para detectar, no es comparable con el hash de un tercero ni con el de la v2),
  y la semilla del log sale del directorio de estado ⇒ a prueba de reescritura, no de suplantación.

Y lo medido para la unidad 6 preguntándole al artefacto: `filterResponseData` y el permiso
`webRequestFilterResponse` SÍ están en nuestro omni.ja ⇒ la extensión podrá leer el cuerpo de un
script. Los inline NO se ven por esa vía, y para v1 está bien: el ataque que sct nombra es la
sustitución en el CDN, o sea el caso `<script src>`.
This commit is contained in:
Sergio
2026-09-10 00:07:10 +00:00
parent 60703e3975
commit 9f476ddd70
+50 -1
View File
@@ -1060,6 +1060,55 @@ hidratado el rootfs de `scripts/atuq-nested.sh`, así que **es una decisión con
`echo` más en el cron** — queda escrita acá y sin dar por hecho lo contrario. El día que se meta, el
sitio es el bloque de guardianes de `cosecha-cron.sh` (L341-352).
### 7.quater El host, escrito — y por qué la extensión NO puede hablarle al testigo (2026-09-10)
El §6.1 decía que `sct` v1 es «extensión con `webRequest` bloqueante que hashea cada respuesta de
script y **consulta al testigo** antes de dejarla pasar». La primera mitad es correcta; la segunda es
imposible tal como está escrita, y conviene decirlo porque parecía un atajo para saltarse esta
unidad: **el cable de `puriy-sct-testigo` es un POST con `postcard` en el body**, no JSON. Un JS no
puede ser su cliente. Y reimplementar el registro TOFU en la extensión sería fabricar un sustituto
paralelo del original —lo que la regla 10 de tawasuyu prohíbe— además de duplicar la única pieza que
ya estaba certificada. Entonces v1 pasa por el host, y el host es esta unidad.
Se decidió con el operador, con el `grep` de la regla 10 hecho primero: **ningún término del dominio
significaba «host de native messaging»**, y los que suenan a mensajero están todos ocupados por
dominios ajenos y grandes (`chasqui` es un type broker, `chaka` el puente a COBOL, `paloma` el cliente
de correo, `tampu` el común de objetos). El nombre salió del título de este propio §7: **la costura**.
| Pieza | Dónde | Por qué ahí |
|---|---|---|
| `puriy-costura` | `00_unanchay/puriy/` | regla 1: subcrate del dominio, no proliferación lateral. Precedentes: `puriy-sct-testigo`, `rimay/verbo-daemon`, `watuy-daemon` |
| `foreign-webext` | `shared/` | regla 4: el protocolo es AJENO. Lib pura sin binario, como los otros 37 puentes (medido: ninguno lleva `[[bin]]`) |
| `recipes/puriy-costura.toml` | takana | el artefacto: estático musl, pineado por commit, `--locked` |
Verbos v1: `ping`, `sct.observe`, `sct.state`. Claves del cable en inglés (regla 7 de allá y regla 4
de acá: se tipean). `phase` es `learning`/`stable`/`not-evaluable`, y un `events[]` vacío es lo normal
— sólo hay evento cuando un origen **ya estable** ejecuta un hash que nadie vio nunca.
**Los tres tests que hacen que el veredicto signifique algo** (de catorce): el mismo cambio de script
mientras el origen todavía aprende **no** alerta; rotar `?v=` con el mismo código **tampoco** (la
identidad del recurso es su contenido, y sin eso cada despliegue con cache-buster sería un aviso de
supply-chain: un detector que grita todos los días no se mira más); y el aprendizaje **sobrevive al
cierre del navegador**, sin lo cual cada arranque volvería a «aprendiendo» y ningún cambio sería nunca
un evento.
**Los dos límites, escritos en el código y en los dos LEEME, no en una nota de sesión:**
1. **Qué bytes se hashean.** La extensión manda el TEXTO ya decodificado; se hashea su UTF-8. La
detección vale —el TOFU compara lo mismo contra lo mismo—, pero nuestro hash **no es comparable**
con el de los bytes servidos que publique un tercero, ni con el de la v2 (el gancho en el script
loader). Un hash que uno cree comparable y no lo es sería peor que no tenerlo.
2. **La semilla del log es provisional**: se deriva del directorio de estado, así que es
determinística (el log se reanuda) pero **no es un secreto** ⇒ la bitácora es a prueba de
REESCRITURA, no de SUPLANTACIÓN. La de verdad sale del perfil cuando el almacén del perfil exista.
**Y lo que queda medido para la unidad 6, preguntándole al artefacto y no a la documentación de
Mozilla** (§2.sexies): `filterResponseData` y el permiso `webRequestFilterResponse` **están** en
nuestro build (`omni.ja`, `chrome/toolkit/content/extensions/schemas/web_request.json`), así que la
extensión va a poder leer el cuerpo de un script. Lo que NO va a poder es ver los scripts **inline**
por esa vía —`filterResponseData` entrega el documento, no cada `<script>`—, y eso está bien para v1:
el ataque que `sct` nombra es la sustitución en el CDN, que es exactamente el caso `<script src>`.
## 8. Plan, por unidades de trabajo
Cada una cierra sola, se commitea y se pushea. El orden no es preferencia: cada una destraba a la
@@ -1080,7 +1129,7 @@ siguiente.
| 4.c | `MOZ_BUILD_DATE` determinista ✅ — `BuildID=19700101000001`, con guardián en install y **`why-differs` 56/56 sobre dos builds reales** | el invariante de reproducibilidad | — |
| 4.g | **El chrome se programa desde `atuq.cfg`, sin abrir `omni.ja`** ✅ 2026-09-09 — y de paso: la **vista dividida ya la trae el motor y viene prendida** (fx 154), ejercitada de verdad con `gBrowser.addTabSplitView``activeSplitView` + 2 navegadores. Guardián `scripts/test-atuq-chrome.py` con control negativo del propio motor | retira dos pendientes que eran de upstream, y deja el camino del chrome que NO pelea con el `jarlog` | — |
| 4.h | **Quién instala las extensiones, medido** ✅ 2026-09-09 — tres escenarios en `scripts/test-atuq-instalacion.py`: instala el ESCANEO de `distribution/extensions/`, `install_url` con `file://` no instala nada. El README de la receta afirmaba lo contrario y quedó corregido | el mecanismo del que cuelga la extensión de `sct` deja de ser una nota al pie y pasa a estar vigilado | — |
| 5 | Host de native messaging (tawasuyu) — **camino COMPROBADO 2026-09-07** (§7.bis): sonda extensión↔proceso nativo con control negativo, y las tres trampas medidas (dónde va el manifiesto, qué instala de verdad, y que el host tiene que ser un proceso largo) | los verbos del §6 | 4 |
| 5 | **Host de native messaging — HECHO 2026-09-10.** `puriy-costura` en tawasuyu (`ebe41e96`) + el cable `shared/foreign-webext`, 14 tests sin navegador; y del lado de acá `recipes/puriy-costura.toml``b3:3b633431`, ELF estático de 952 K **verificado como artefacto**: contesta un marco de 4 bytes y hace la promesa del §6.1 completa (4 visitas aprendiendo → `stable` → un script cambiado = un evento). El camino ya estaba comprobado en el §7.bis con sonda y control negativo | los verbos del §6 | 4 |
| 6 | `sct` v1 (extensión + testigo) | el diferenciador que nadie tiene | 5 |
| 7 | Descargas al CAS | 6.2, y alimenta 6.9 | 5 |
| 8 | Archivo + RAG | 6.3 | 5, 7 |