tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Sutichaykuna

⎇ 7c794568a963b18b68d747959e6bbd07cf2ebff7 scripts/harkaq/harkaq-verdict.py Python · 88 siq'ikuna · 3.6 KB Ñawpa willay Pi imata llamkarqan ch'uya

harkaq: Verdict clasificado (base|esperada|deuda) + el runtime base es por forma de build 77317a729 · sergio ·

1#!/usr/bin/env python3
2"""harkaq-verdict — clasifica las denegaciones crudas y emite el Verdict final (SDD 16 §4.2).
3
4    harkaq-verdict.py <verdict-crudo.json> <politica> [--human]
5
6Por qué es un proceso aparte y NO parte de harkaq-audit: el lector corre con CAP_AUDIT_READ, y
7clasificar es POLÍTICA, no privilegio. El componente privilegiado se mantiene lo más tonto
8posible — mismo argumento que Q1c (el privilegio en un helper chico, no en hammerd). Efecto
9lateral útil: se itera la clasificación sin recompilar el binario capabilitado (y sin perder el
10setcap en cada cambio).
11
12Las tres categorías salen de la medición de §4.2:
13
14  base      concedida por la política ⇒ nunca aparece como denegación. No se clasifica acá.
15  esperada  denegada A PROPÓSITO y previsible. `zig cc` sondea /usr/bin/gcc y el build sale rc=0
16            igual: concederlo lo dejaría usar el gcc de Alpine por detrás. Se declara en la
17            política con `# expect <path>` — la MISMA fuente de verdad que las reglas, y
18            harkaq-exec la ignora como comentario. D3: no se calla, se clasifica.
19  deuda     todo lo demás. ESTO es la de-Alpinización: lo que el build usó sin declarar.
20
21Y la consecuencia que obliga: `Hermetico` = **cero deuda**, no cero denegaciones. Con la
22definición estricta ningún build real lo alcanzaría jamás y el estado más importante del sistema
23quedaría vacío de uso.
24"""
25import json
26import sys
27
28
29def cargar_esperadas(politica):
30    """Lee los `# expect <path>` de la política. Un fichero, una fuente de verdad."""
31    esperadas = set()
32    with open(politica) as f:
33        for linea in f:
34            linea = linea.strip()
35            if linea.startswith("# expect "):
36                esperadas.add(linea[len("# expect "):].strip())
37    return esperadas
38
39
40def main():
41    if len(sys.argv) < 3:
42        print(__doc__.split("\n")[2].strip(), file=sys.stderr)
43        return 2
44    crudo = json.load(open(sys.argv[1]))
45    esperadas = cargar_esperadas(sys.argv[2])
46    humano = "--human" in sys.argv
47
48    # SinEvidencia manda sobre todo: si el lector no es confiable, no clasificamos nada. Un
49    # veredicto sin canario no es "limpio", es "no sabemos" (D9), y reinterpretarlo acá sería
50    # justo el falso Hermetico que el canario existe para impedir.
51    if crudo["estado"] == "SinEvidencia":
52        salida = dict(crudo, esperadas=[], deuda=[])
53        print(json.dumps(salida) if not humano else
54              f"SinEvidencia — {crudo.get('motivo', 'sin motivo')}")
55        return 3
56
57    vistas_esperadas, deuda = [], []
58    for d in crudo["denials"]:
59        (vistas_esperadas if d["path"] in esperadas else deuda).append(d)
60
61    estado = "Hermetico" if not deuda else "Impuro"
62    salida = {
63        "estado": estado,
64        "canario_visto": crudo["canario_visto"],
65        "domain": crudo["domain"],
66        "contador_kernel": crudo["contador_kernel"],
67        "esperadas": vistas_esperadas,
68        "deuda": deuda,
69    }
70
71    if not humano:
72        print(json.dumps(salida))
73    else:
74        print(f"  estado: {estado}  (canario visto, contador kernel={crudo['contador_kernel']})")
75        if vistas_esperadas:
76            uniq = sorted({d["path"] for d in vistas_esperadas})
77            print(f"  esperadas ({len(vistas_esperadas)}): " + ", ".join(uniq))
78        if deuda:
79            print(f"  DEUDA ({len(deuda)}) — usado sin declarar:")
80            for p in sorted({d["path"] for d in deuda}):
81                print(f"      {p}")
82        else:
83            print("  deuda: ninguna ✓")
84    return 0 if estado == "Hermetico" else 1
85
86
87if __name__ == "__main__":
88    sys.exit(main())