#!/usr/bin/env python3 """harkaq-trace-norm — canoniza la traza cruda de harkaq-trace (plan-freebsd T1.2). harkaq-trace-norm.py [--store ] [--drop ]... [--resumen] Las reglas de normalización son la parte que FreeBSD tardó años en iterar (los `.meta` de META MODE): el ruido no se CALLA, se clasifica con reglas explícitas — D3 aplica acá igual que en el verdict. Reglas: 1. DROP por prefijo: /proc /sys /dev /tmp /out /src (el build escribe /out y lee /src por contrato; lo interesante es lo demás). Ampliable con --drop. 2. DEDUPE + ORDEN ESTABLE: la traza cruda trae un evento por open(); el mismo path N veces no es señal (el orden de llegada tampoco: readdir no garantiza orden — la lección número uno de META MODE). Salida: paths únicos, ordenados. 3. ATRIBUCIÓN por dep (--store): un path store/-/resto se atribuye a la dep . Con --resumen emite el conteo por dep — la señal de PODA (T1.4): una dep declarada con 0 toques es grasa de la clausura. El sellado (T1.3) NO es de este script: la salida canónica es determinista a propósito (orden estable, sin timestamps) para que `b3sum` del worker la selle tal cual y viaje junto al of_tree. Un script canoniza, el store sella — cada pieza tonta en lo suyo. """ import argparse import re import sys from collections import Counter from pathlib import Path # /cache: la cache content-addressed de zig DENTRO de la jaula — estado derivado del propio # build (84 entradas en el piloto zlib), no entrada de la clausura. La lección META MODE # repite: las reglas de ruido SON el producto; cada una entra medida, no adivinada. DROP_DEFAULT = ["/proc", "/sys", "/dev", "/tmp", "/out", "/src", "/run", "/cache"] def main(): ap = argparse.ArgumentParser() ap.add_argument("traza") ap.add_argument("--store", default=None, help="raíz del store para atribuir paths a deps (store/-/…)") ap.add_argument("--drop", action="append", default=[], help="prefijo adicional a descartar (repetible)") ap.add_argument("--resumen", action="store_true", help="emite conteo por dep en vez de la lista canónica") args = ap.parse_args() drops = DROP_DEFAULT + args.drop store = str(Path(args.store).resolve()) if args.store else None # store/-/… — el nombre puede traer guiones; el hash no. re_dep = re.compile(re.escape(store) + r"/[0-9a-f]{16,}-([^/]+)/") if store else None unicos = set() por_dep = Counter() with open(args.traza) as f: for linea in f: p = linea.strip() if not p or any(p == d or p.startswith(d + "/") for d in drops): continue if p not in unicos and re_dep: m = re_dep.match(p) if m: por_dep[m.group(1)] += 1 unicos.add(p) if args.resumen: for dep, n in sorted(por_dep.items()): print(f"{n:6d} {dep}") else: for p in sorted(unicos): print(p) return 0 if __name__ == "__main__": sys.exit(main())