@saulwade/swl-ses 2.6.1 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/CLAUDE.md +14 -2
  2. package/README.md +65 -18
  3. package/agentes/_intent-spec.md +73 -73
  4. package/agentes/_propose-step.md +90 -90
  5. package/bin/swl-ses.js +10 -0
  6. package/comandos/swl/brainstorm.md +1 -0
  7. package/comandos/swl/briefing.md +119 -119
  8. package/comandos/swl/contribuir.md +233 -233
  9. package/comandos/swl/deuda-codigo.md +97 -97
  10. package/comandos/swl/mcp-status.md +1 -0
  11. package/gateway/lib/event-channel.js +191 -191
  12. package/habilidades/agent-deep-links/SKILL.md +148 -148
  13. package/habilidades/backend-async-postgres-testing/SKILL.md +216 -216
  14. package/habilidades/backend-error-design/SKILL.md +221 -221
  15. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  16. package/habilidades/calidad-anti-patrones-universales/SKILL.md +105 -1
  17. package/habilidades/calidad-contract-testing/SKILL.md +165 -165
  18. package/habilidades/calidad-mutation-testing/SKILL.md +25 -1
  19. package/habilidades/checklist-seguridad/recursos/stride-cobertura.md +60 -60
  20. package/habilidades/ci-cd-pipelines/SKILL.md +5 -1
  21. package/habilidades/css-moderno/SKILL.md +7 -1
  22. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  23. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  24. package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +57 -57
  25. package/habilidades/extractor-de-aprendizajes/SKILL.md +5 -1
  26. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
  27. package/habilidades/harness-claude-code/SKILL.md +3 -2
  28. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  29. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  30. package/habilidades/perfil-usuario/SKILL.md +200 -200
  31. package/habilidades/prevencion-sobreingenieria/recursos/EXAMPLES.md +580 -580
  32. package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -166
  33. package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -85
  34. package/habilidades/proceso-ddia-streaming/SKILL.md +231 -231
  35. package/habilidades/proceso-discovery-machote/SKILL.md +157 -157
  36. package/habilidades/proceso-dynamic-workflows/SKILL.md +60 -0
  37. package/habilidades/proceso-dynamic-workflows/recursos/template-adversarial-verify.js +65 -65
  38. package/habilidades/proceso-dynamic-workflows/recursos/template-triage.js +65 -65
  39. package/habilidades/proceso-intent-engineering/SKILL.md +269 -269
  40. package/habilidades/proceso-modular-split/SKILL.md +256 -256
  41. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
  42. package/habilidades/swl-claudemd/recursos/contrato-aprender.md +83 -83
  43. package/habilidades/swl-claudemd/recursos/duplicacion-reglas-globales.md +85 -85
  44. package/habilidades/swl-claudemd/recursos/plantillas-init.md +94 -94
  45. package/habilidades/tdd-workflow/recursos/gherkin-bdd.md +111 -111
  46. package/hooks/calidad-pre-commit.js +159 -10
  47. package/hooks/ciclo-evolucion-subagente.js +26 -26
  48. package/hooks/ciclo-evolucion.js +26 -26
  49. package/hooks/contexto-subagente.js +68 -68
  50. package/hooks/lib/auto-consolidator.js +335 -335
  51. package/hooks/lib/ciclo-evolucion.js +47 -47
  52. package/hooks/lib/deep-links.js +185 -185
  53. package/hooks/lib/error-classifier.js +308 -308
  54. package/hooks/lib/notificacion-formato.js +45 -11
  55. package/hooks/lib/provenance-tracker.js +191 -191
  56. package/hooks/lib/raiz-proyecto.js +35 -4
  57. package/hooks/lib/resource-quota.js +122 -122
  58. package/hooks/lib/retry-jitter.js +165 -165
  59. package/hooks/lib/security-net.js +201 -201
  60. package/hooks/lib/skill-auditor.js +588 -588
  61. package/hooks/lib/sync-status.js +228 -228
  62. package/hooks/lib/taint-tracker.js +107 -107
  63. package/hooks/lib/text-similarity.js +241 -241
  64. package/hooks/lib/toon-compressor.js +245 -245
  65. package/hooks/notificacion-telegram.js +5 -11
  66. package/hooks/session-briefing.js +12 -4
  67. package/instintos/autonomia.yaml +27 -27
  68. package/instintos/prompt-appendices.yaml +57 -57
  69. package/llms.txt +1 -1
  70. package/manifiestos/agent-output-schemas.json +57 -57
  71. package/manifiestos/canonical-hashes.json +662 -0
  72. package/manifiestos/harness-ir.json +47536 -0
  73. package/manifiestos/hooks-config.json +469 -469
  74. package/manifiestos/invariantes-criticos.json +30 -30
  75. package/manifiestos/policy-bundle.json +2065 -0
  76. package/manifiestos/policy-corpus-w2.json +3926 -0
  77. package/manifiestos/runtime-adapters-core3.json +208 -0
  78. package/manifiestos/runtime-conformance.json +139 -0
  79. package/manifiestos/skills-lock.json +43 -43
  80. package/package.json +2 -2
  81. package/plantillas/auditor-veto-template.md +105 -105
  82. package/plantillas/github-workflows/release-please.yml +44 -44
  83. package/plantillas/github-workflows/swl-ci.yml +107 -107
  84. package/plantillas/github-workflows/swl-security.yml +51 -51
  85. package/plugin.json +2 -2
  86. package/reglas/accesibilidad.md +10 -10
  87. package/reglas/auditorias-documentales-estructurales.md +7 -7
  88. package/reglas/cloud-infra.md +8 -8
  89. package/reglas/consultar-vault-primero.md +195 -195
  90. package/reglas/git-workflow.md +1 -0
  91. package/reglas/hooks.md +6 -6
  92. package/reglas/intent-engineering.md +218 -218
  93. package/reglas/markitdown.md +8 -8
  94. package/reglas/monitor-ci.md +12 -0
  95. package/reglas/patrones.md +6 -6
  96. package/reglas/testing.md +7 -7
  97. package/reglas/tests-cleanup.md +224 -224
  98. package/schemas/agent-message.schema.json +73 -73
  99. package/schemas/agent-output-implementacion.schema.json +114 -114
  100. package/schemas/agent-output-planificacion.schema.json +150 -150
  101. package/schemas/agent-output-review.schema.json +98 -98
  102. package/schemas/diary-entry.schema.json +112 -112
  103. package/schemas/gate-state.schema.json +76 -0
  104. package/schemas/harness-ir.schema.json +369 -0
  105. package/schemas/hook-profiles.schema.json +54 -54
  106. package/schemas/hooks-config.schema.json +89 -89
  107. package/schemas/legacy-gates.schema.json +45 -0
  108. package/schemas/modulos.schema.json +38 -38
  109. package/schemas/perfiles.schema.json +36 -36
  110. package/schemas/plugin.schema.json +77 -77
  111. package/schemas/policy-bundle.schema.json +140 -0
  112. package/schemas/policy-enforcement.schema.json +117 -0
  113. package/schemas/policy-operation.schema.json +261 -0
  114. package/schemas/runtime-adapter.schema.json +176 -0
  115. package/schemas/runtime-build-attestation.schema.json +100 -0
  116. package/schemas/runtime-conformance.schema.json +239 -0
  117. package/schemas/runtime-diagnostic.schema.json +395 -0
  118. package/schemas/skill-evals.schema.json +119 -119
  119. package/schemas/skill-frontmatter.schema.json +245 -245
  120. package/schemas/w4-certification-request.schema.json +72 -0
  121. package/schemas/w4-certification-verdict.schema.json +224 -0
  122. package/schemas/w4-corpus.schema.json +172 -0
  123. package/schemas/w4-mutation-report.schema.json +116 -0
  124. package/schemas/w4-replay-result.schema.json +164 -0
  125. package/schemas/w4-scoring-report.schema.json +89 -0
  126. package/scripts/audit-tools/audit-history.js +330 -330
  127. package/scripts/audit-tools/bundle-tracker.js +290 -290
  128. package/scripts/audit-tools/canary-monitor.js +352 -352
  129. package/scripts/audit-tools/code-profiler.js +605 -605
  130. package/scripts/audit-tools/dep-doctor.js +320 -320
  131. package/scripts/audit-tools/env-validator.js +206 -206
  132. package/scripts/audit-tools/lib/fs-walk.js +48 -48
  133. package/scripts/audit-tools/lib/output.js +23 -23
  134. package/scripts/audit-tools/migration-checker.js +392 -392
  135. package/scripts/audit-tools/pentest-scanner.js +1436 -1436
  136. package/scripts/bootstrap-instintos.js +3 -0
  137. package/scripts/cli/aprobar-plan.js +73 -73
  138. package/scripts/cli/briefing.js +23 -23
  139. package/scripts/cli/ciclo-evolucion.js +26 -26
  140. package/scripts/cli/derivar-feature-list.js +25 -25
  141. package/scripts/cli/detectar-host.js +27 -27
  142. package/scripts/cli/diary-entry.js +69 -69
  143. package/scripts/cli/execution-state.js +18 -18
  144. package/scripts/cli/gateway-notify.js +41 -41
  145. package/scripts/cli/liberar-fase.js +42 -42
  146. package/scripts/cli/mark-evolved.js +56 -56
  147. package/scripts/cli/metricas-dora.js +26 -26
  148. package/scripts/cli/near-duplicate.js +55 -55
  149. package/scripts/cli/notificaciones.js +123 -123
  150. package/scripts/cli/propose-step.js +29 -29
  151. package/scripts/cli/schedule-parse.js +19 -19
  152. package/scripts/cli/sugerir-modelo.js +20 -20
  153. package/scripts/cli/verificar-plan.js +36 -36
  154. package/scripts/cli/verificar-trazabilidad.js +35 -35
  155. package/scripts/comandos/install-asistido.js +8 -7
  156. package/scripts/configurar-branch-protection.js +418 -418
  157. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  158. package/scripts/doctor.js +61 -36
  159. package/scripts/generar-checklists-consolidados.js +273 -273
  160. package/scripts/generar-claims-runtime.js +1342 -0
  161. package/scripts/generar-harness-ir.js +257 -0
  162. package/scripts/generar-inventario.js +52 -54
  163. package/scripts/generar-policy-bundle.js +202 -0
  164. package/scripts/instalador.js +26 -7
  165. package/scripts/lib/approval-receipts.js +190 -0
  166. package/scripts/lib/artefactos-python.js +43 -43
  167. package/scripts/lib/benchmark-metrics.js +160 -160
  168. package/scripts/lib/budget-enforcer.js +252 -252
  169. package/scripts/lib/certificacion-loop-state.js +421 -0
  170. package/scripts/lib/ci-reader.js +193 -193
  171. package/scripts/lib/ciclo-autonomo/yaml-instintos.js +56 -0
  172. package/scripts/lib/clasificar-directorio.js +92 -0
  173. package/scripts/lib/contadores-inventario.js +217 -217
  174. package/scripts/lib/detectar-host-swl.js +175 -175
  175. package/scripts/lib/detectar-runtime.js +29 -20
  176. package/scripts/lib/detectar-stack-detallado.js +307 -307
  177. package/scripts/lib/detector-autoduplicacion-intra-archivo.js +234 -234
  178. package/scripts/lib/detector-reglas-duplicadas.js +220 -220
  179. package/scripts/lib/eval-metrics-store.js +218 -218
  180. package/scripts/lib/eval-quality.js +171 -171
  181. package/scripts/lib/eval-schemas.js +144 -144
  182. package/scripts/lib/eval-self-correct.js +106 -106
  183. package/scripts/lib/eval-validator.js +185 -185
  184. package/scripts/lib/evidence-verifier.js +192 -0
  185. package/scripts/lib/evidencia-release.js +322 -322
  186. package/scripts/lib/frontmatter-canonico.js +509 -0
  187. package/scripts/lib/gate-engine.js +871 -0
  188. package/scripts/lib/gate-hooks-requires.js +249 -249
  189. package/scripts/lib/gate-licencias.js +212 -212
  190. package/scripts/lib/git-config-preflight.js +48 -0
  191. package/scripts/lib/git-metricas.js +257 -257
  192. package/scripts/lib/harness-ir.js +778 -0
  193. package/scripts/lib/harness-source-snapshot.js +309 -0
  194. package/scripts/lib/integrity-ledger.js +1147 -0
  195. package/scripts/lib/jaccard-similarity.js +98 -98
  196. package/scripts/lib/legacy-gate-migration.js +324 -0
  197. package/scripts/lib/limpiar-basura-global.js +45 -2
  198. package/scripts/lib/longmemeval-runner.js +125 -125
  199. package/scripts/lib/metricas-dora.js +204 -204
  200. package/scripts/lib/notificaciones-telegram.js +1 -0
  201. package/scripts/lib/npm-version.js +1 -0
  202. package/scripts/lib/paquetes-conocidos.js +50 -50
  203. package/scripts/lib/plan-lock.js +61 -13
  204. package/scripts/lib/policy-broker.js +338 -0
  205. package/scripts/lib/policy-bundle.js +342 -0
  206. package/scripts/lib/policy-context-provider.js +310 -0
  207. package/scripts/lib/policy-contract.js +479 -0
  208. package/scripts/lib/policy-verifier-utils.js +65 -0
  209. package/scripts/lib/pr-analyzer.js +399 -399
  210. package/scripts/lib/principal-verifier.js +178 -0
  211. package/scripts/lib/prompt-builder.js +264 -264
  212. package/scripts/lib/resolver-plan-fase.js +37 -37
  213. package/scripts/lib/rrf-fusion.js +175 -175
  214. package/scripts/lib/runtime-adapter-contract.js +267 -0
  215. package/scripts/lib/runtime-artifact-verifier.js +426 -0
  216. package/scripts/lib/runtime-build-attestation.js +127 -0
  217. package/scripts/lib/runtime-bundle-installer.js +586 -0
  218. package/scripts/lib/runtime-compiler.js +327 -0
  219. package/scripts/lib/runtime-conformance.js +202 -0
  220. package/scripts/lib/runtime-doctor-core3.js +567 -0
  221. package/scripts/lib/runtime-doctor-input.js +59 -0
  222. package/scripts/lib/runtime-operation-adapter.js +267 -0
  223. package/scripts/lib/schema-version.js +164 -164
  224. package/scripts/lib/semantic-search.js +252 -252
  225. package/scripts/lib/signed-envelope.js +545 -0
  226. package/scripts/lib/single-use-store.js +359 -0
  227. package/scripts/lib/skills-externas.js +31 -0
  228. package/scripts/lib/transformadores/codex.js +15 -8
  229. package/scripts/lib/transformadores/gemini.js +79 -5
  230. package/scripts/lib/w4-attestation-adapter.js +158 -0
  231. package/scripts/lib/w4-canario.js +337 -0
  232. package/scripts/lib/w4-claims.js +182 -0
  233. package/scripts/lib/w4-corpus-generador.js +542 -0
  234. package/scripts/lib/w4-gate-c5.js +115 -0
  235. package/scripts/lib/w4-harness-bajo-prueba.js +155 -0
  236. package/scripts/lib/w4-matriz-combos.js +55 -0
  237. package/scripts/lib/w4-motor-mutacion.js +1348 -0
  238. package/scripts/lib/w4-motor-replay.js +735 -0
  239. package/scripts/lib/w4-pin-origen.js +54 -0
  240. package/scripts/lib/w4-publicar-request.js +132 -0
  241. package/scripts/lib/w4-revocacion.js +62 -0
  242. package/scripts/lib/w4-runtimes-core3.js +38 -0
  243. package/scripts/lib/w4-scorer-certificacion.js +692 -0
  244. package/scripts/lib/w4-superficie-candidato.js +49 -0
  245. package/scripts/lib/w4-veredicto.js +452 -0
  246. package/scripts/lib/w4-verificar-veredicto.js +302 -0
  247. package/scripts/limpiar-artefactos-python.js +131 -131
  248. package/scripts/migrar-csv-a-array.js +168 -168
  249. package/scripts/migrar-fase-dominio.js +200 -200
  250. package/scripts/migrar-gates-legacy.js +108 -0
  251. package/scripts/publicar-certification-request.js +115 -0
  252. package/scripts/runtime-doctor.js +107 -0
  253. package/scripts/tui/componentes/selector-multi.js +189 -189
  254. package/scripts/tui/componentes/selector-unico.js +158 -158
  255. package/scripts/tui/ejecutores.js +375 -375
  256. package/scripts/tui/lib/colores.js +129 -129
  257. package/scripts/tui/lib/render.js +264 -264
  258. package/scripts/tui/lib/teclas.js +113 -113
  259. package/scripts/tui/pantallas/install-wizard.js +12 -7
  260. package/scripts/tui/pantallas/menu-principal.js +52 -52
  261. package/scripts/tui/pantallas/progreso.js +274 -274
  262. package/scripts/tui/pantallas/resumen.js +132 -132
  263. package/scripts/validar-userland-vacio.js +110 -110
  264. package/scripts/verificar-aislamiento-swl-eval.js +87 -0
  265. package/scripts/verificar-empaquetado-downstream.js +375 -0
  266. package/scripts/verificar-loop-constructor.js +215 -0
  267. package/scripts/verificar-trazabilidad.js +13 -6
  268. package/scripts/verificar-veredicto-real.js +84 -0
@@ -0,0 +1,302 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Verificador del `CertificationVerdict` (ADR-0053 § Decisión pieza 5, F32/T-06,
5
+ * REQ-32-06). Núcleo PURO con el puerto de attestation INYECTADO
6
+ * (`attestationVerifier`) — este módulo nunca invoca `gh`, nunca hace red ni
7
+ * filesystem por su cuenta; el adaptador real de `gh attestation verify`
8
+ * origin-pinned es T-09 (fuera del alcance de esta tarea).
9
+ *
10
+ * C3a — honestidad del modo dev (ADR-0053 § Honestidad del red-team, C6): en
11
+ * F32/T-06 este verificador SÓLO puede ejercitarse contra un
12
+ * `attestationVerifier` SIMULADO (un stub de test, nunca `gh` real). Ningún
13
+ * resultado `{ok:true}` producido con un stub constituye "origen verificado"
14
+ * en el sentido de ADR-0053 — esa garantía exige el adaptador real de T-09,
15
+ * el environment protegido `certificacion` y el resto del provisioning
16
+ * humano (H-3/H-4/H-6/H-7/H-10). El propio veredicto lo declara: en modo
17
+ * dev, `firma.dev.nota` (constante fija, validada por
18
+ * `w4-veredicto.js#validateCertificationVerdict`) dice explícitamente
19
+ * "verificador dev, adaptador de attestation simulado" — y este verificador
20
+ * NUNCA certifica un veredicto dev (`VERDICT_DEV_NO_CERTIFICANTE`),
21
+ * cualquiera sea el resultado del puerto.
22
+ *
23
+ * Solo lectura: nunca muta `verdict`. Nunca lanza — todo camino de error
24
+ * retorna `{ ok:false, codigo }` (fail-closed en cada borde, incluyendo
25
+ * cualquier excepción del puerto inyectado — C4).
26
+ */
27
+
28
+ const crypto = require('node:crypto');
29
+
30
+ const { validateCertificationVerdict } = require('./w4-veredicto');
31
+ const { canonicalizarIJson } = require('./signed-envelope');
32
+
33
+ const MAX_BUNDLE_BYTES = 8 * 1024 * 1024;
34
+
35
+ const CODIGOS = Object.freeze({
36
+ MALFORMED: 'VERDICT_MALFORMED',
37
+ REQUEST_REQUIRED: 'VERDICT_REQUEST_REQUIRED',
38
+ REQUEST_MISMATCH: 'VERDICT_REQUEST_MISMATCH',
39
+ DEV_NO_CERTIFICANTE: 'VERDICT_DEV_NO_CERTIFICANTE',
40
+ SIGNATURE_INVALID: 'VERDICT_SIGNATURE_INVALID',
41
+ ORIGIN_MISMATCH: 'VERDICT_ORIGIN_MISMATCH',
42
+ });
43
+
44
+ function esObjetoPlano(valor) {
45
+ return Boolean(valor) && typeof valor === 'object' && !Array.isArray(valor)
46
+ && Object.getPrototypeOf(valor) === Object.prototype
47
+ && Object.getOwnPropertySymbols(valor).length === 0;
48
+ }
49
+
50
+ function congelarProfundo(valor) {
51
+ if (valor && typeof valor === 'object' && !Object.isFrozen(valor)) {
52
+ Object.values(valor).forEach(congelarProfundo);
53
+ Object.freeze(valor);
54
+ }
55
+ return valor;
56
+ }
57
+
58
+ function rechazo(codigo) {
59
+ return Object.freeze({ ok: false, codigo });
60
+ }
61
+
62
+ function clavesExactas(valor, esperadas) {
63
+ return esObjetoPlano(valor)
64
+ && JSON.stringify(Object.keys(valor).sort()) === JSON.stringify([...esperadas].sort());
65
+ }
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // C2 — anti-replay: requestEsperado es obligatorio, comparación exacta.
69
+ // ---------------------------------------------------------------------------
70
+
71
+ // Defensa en profundidad deliberadamente redundante: `runtime`/`os`/`model`
72
+ // (vía w4-veredicto.js#validarMatrixEntry, enum + `IDENTIFICADOR` ASCII) y
73
+ // `commit`/`tarballDigest` (hex ASCII) ya restringen estos campos a ASCII
74
+ // antes de llegar aquí, así que hoy ningún valor real requiere normalización
75
+ // NFKC. Se conserva porque es barata (una llamada nativa por campo) y actúa
76
+ // como anti-regresión: si el validador upstream (`validarRequestEsperadoShape`
77
+ // / `validateCertificationVerdict`) alguna vez se relaja a Unicode más amplio,
78
+ // esta normalización sigue cerrando el vector de confusión NFKC sin requerir
79
+ // un cambio coordinado en este módulo.
80
+ function normalizarNFKC(valor) {
81
+ return typeof valor === 'string' ? valor.normalize('NFKC') : valor;
82
+ }
83
+
84
+ function validarRequestEsperadoShape(valor) {
85
+ if (!clavesExactas(valor, ['commit', 'tarballDigest', 'matrix'])) return false;
86
+ if (typeof valor.commit !== 'string' || typeof valor.tarballDigest !== 'string') return false;
87
+ if (!Array.isArray(valor.matrix) || valor.matrix.length === 0) return false;
88
+ return valor.matrix.every((entrada) => (
89
+ clavesExactas(entrada, ['runtime', 'os', 'model'])
90
+ && typeof entrada.runtime === 'string' && typeof entrada.os === 'string' && typeof entrada.model === 'string'
91
+ ));
92
+ }
93
+
94
+ function claveMatrixEntry(entrada) {
95
+ return `${normalizarNFKC(entrada.runtime)}|${normalizarNFKC(entrada.os)}|${normalizarNFKC(entrada.model)}`;
96
+ }
97
+
98
+ /**
99
+ * Comparación EXACTA (C2) de `verdict.request` contra `requestEsperado`, con
100
+ * normalización NFKC por campo — el arreglo `matrix` se compara como
101
+ * multiconjunto (el orden de las entradas nunca es información de
102
+ * seguridad); cualquier entrada distinta, faltante o sobrante rechaza.
103
+ */
104
+ function coincideRequest(verdictRequest, requestEsperado) {
105
+ if (normalizarNFKC(verdictRequest.commit) !== normalizarNFKC(requestEsperado.commit)) return false;
106
+ if (normalizarNFKC(verdictRequest.tarballDigest) !== normalizarNFKC(requestEsperado.tarballDigest)) return false;
107
+ if (verdictRequest.matrix.length !== requestEsperado.matrix.length) return false;
108
+ const propias = verdictRequest.matrix.map(claveMatrixEntry).sort();
109
+ const esperadas = requestEsperado.matrix.map(claveMatrixEntry).sort();
110
+ return JSON.stringify(propias) === JSON.stringify(esperadas);
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // C1 — binding firma <-> payload: el núcleo calcula el digest esperado.
115
+ // ---------------------------------------------------------------------------
116
+
117
+ /**
118
+ * `sha256(canonicalizarIJson(verdict SIN el bloque 'firma')))` — puro,
119
+ * independientemente testeable (C1: "la comparación vive en el core
120
+ * testeable"). Excluir `firma` es deliberado: el digest identifica el
121
+ * CONTENIDO atestiguado, no la firma misma (que se añade después de
122
+ * calcular el digest, en el flujo real de `gh attestation sign`).
123
+ */
124
+ function sha256Utf8(texto) {
125
+ return crypto.createHash('sha256').update(texto, 'utf8').digest('hex');
126
+ }
127
+
128
+ function bytesVerdictSinFirma(verdict) {
129
+ const { firma, ...sinFirma } = verdict;
130
+ void firma;
131
+ return canonicalizarIJson(sinFirma);
132
+ }
133
+
134
+ function digestVerdictSinFirma(verdict) {
135
+ return sha256Utf8(bytesVerdictSinFirma(verdict));
136
+ }
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // C3/C4 — contrato documentado del adaptador real + fail-closed en su salida.
140
+ // ---------------------------------------------------------------------------
141
+
142
+ /**
143
+ * Contrato documentado del adaptador REAL de `gh attestation verify`
144
+ * (T-09, NO ejecutado en F32/T-06 — este módulo sólo recibe el puerto
145
+ * inyectado). Entrada, tres argumentos (ADR-0056):
146
+ * `(bundle: Buffer, expectedArtifactDigest: string sha256 hex,
147
+ * artifactBytes: string)`
148
+ * donde `artifactBytes` son los bytes canónicos I-JSON del veredicto SIN el
149
+ * bloque `firma` (los mismos cuyo sha256 es `expectedArtifactDigest`).
150
+ * Salida esperada, `exactKeys` estricto (C3c/C4): SIN campos adicionales,
151
+ * SIN tipos laxos —
152
+ *
153
+ * { identidadOIDC: { repo: string, workflow: string, ref: string },
154
+ * firmaValida: boolean }
155
+ *
156
+ * El `gh` real (v2.89.0, verificado en T-09) EXIGE el artefacto en disco
157
+ * (`gh attestation verify <file-path> --owner … --signer-workflow …`) y NO
158
+ * soporta un modo `--digest` de digest-desnudo; por eso el núcleo pasa los
159
+ * `artifactBytes`, que el adaptador escribe a un archivo temporal 0600 y
160
+ * entrega a `gh`. `gh` computa el sha256 del archivo y lo compara contra el
161
+ * sujeto atestiguado por Sigstore — así se materializa la comparación C1 en
162
+ * el mundo real. `expectedArtifactDigest` se pasa además como cross-check
163
+ * barato (el adaptador puede aseverar `sha256(artifactBytes) === expected`
164
+ * antes de invocar `gh`). Este verificador nunca parsea el bundle Sigstore
165
+ * ni toca disco/red por sí mismo (el efecto vive en el adaptador de T-09).
166
+ */
167
+ function validarFormaSalidaAttestation(valor) {
168
+ if (!clavesExactas(valor, ['identidadOIDC', 'firmaValida'])) return false;
169
+ if (typeof valor.firmaValida !== 'boolean') return false;
170
+ const identidad = valor.identidadOIDC;
171
+ if (!clavesExactas(identidad, ['repo', 'workflow', 'ref'])) return false;
172
+ return typeof identidad.repo === 'string' && identidad.repo.length > 0
173
+ && typeof identidad.workflow === 'string' && identidad.workflow.length > 0
174
+ && typeof identidad.ref === 'string' && identidad.ref.length > 0;
175
+ }
176
+
177
+ /**
178
+ * Contrato de ENTRADA del adaptador real: `bundle` son bytes crudos
179
+ * acotados (mismo patrón fail-closed que
180
+ * `scripts/lib/runtime-build-attestation.js#validateRawBytes` — prototipo
181
+ * `Buffer`/`Uint8Array`, sin propiedades intrusas, tamaño en
182
+ * `(0, MAX_BUNDLE_BYTES]`). Este módulo NO parsea el bundle como I-JSON
183
+ * (ese formato lo define Sigstore, no ADR-0053) — sólo acota su forma antes
184
+ * de calcular su digest (C7) y de pasarlo al puerto.
185
+ */
186
+ function validarBundleBytes(bundle) {
187
+ if (!bundle || typeof bundle !== 'object') return false;
188
+ const prototipo = Object.getPrototypeOf(bundle);
189
+ if (prototipo !== Buffer.prototype && prototipo !== Uint8Array.prototype) return false;
190
+ if (['buffer', 'byteOffset', 'byteLength'].some((propiedad) => Object.hasOwn(bundle, propiedad))) return false;
191
+ return bundle.byteLength > 0 && bundle.byteLength <= MAX_BUNDLE_BYTES;
192
+ }
193
+
194
+ function validarPinOrigen(valor) {
195
+ return clavesExactas(valor, ['repo', 'workflow', 'ref'])
196
+ && typeof valor.repo === 'string' && valor.repo.length > 0
197
+ && typeof valor.workflow === 'string' && valor.workflow.length > 0
198
+ && typeof valor.ref === 'string' && valor.ref.length > 0;
199
+ }
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // verificarVeredicto — orquestador fail-closed.
203
+ // ---------------------------------------------------------------------------
204
+
205
+ /**
206
+ * Verifica un `CertificationVerdict` contra el mecanismo de aislamiento de
207
+ * ADR-0053. Nunca lanza: retorna `{ok:false, codigo}` en cualquier borde de
208
+ * fallo, o `{ok:true, verdict, identidadOIDC}` (deep-frozen) sólo cuando
209
+ * TODAS las condiciones se cumplen — estructura válida, request coincidente
210
+ * (C2), modo `real` con firma protegida verificada con origen pineado
211
+ * (C1/C3/C4/C7). Un veredicto `modo:'dev'` NUNCA certifica, aunque esté
212
+ * perfectamente bien formado (C3a).
213
+ *
214
+ * @param {unknown} verdict
215
+ * @param {{pinOrigen: unknown, requestEsperado: unknown,
216
+ * attestationVerifier: (bundle: Buffer, expectedArtifactDigest: string,
217
+ * artifactBytes: string) => Promise<unknown> | unknown, bundle: unknown}} opciones
218
+ */
219
+ async function verificarVeredicto(verdict, opciones) {
220
+ let sano;
221
+ try {
222
+ sano = validateCertificationVerdict(verdict);
223
+ } catch {
224
+ return rechazo(CODIGOS.MALFORMED);
225
+ }
226
+
227
+ const opcionesSanas = esObjetoPlano(opciones) ? opciones : {};
228
+ const { pinOrigen, requestEsperado, attestationVerifier, bundle } = opcionesSanas;
229
+
230
+ // C2: "nunca acepta sin request" — precondición incondicional, antes de
231
+ // ramificar por modo dev/real.
232
+ if (!validarRequestEsperadoShape(requestEsperado)) return rechazo(CODIGOS.REQUEST_REQUIRED);
233
+ if (!coincideRequest(sano.request, requestEsperado)) return rechazo(CODIGOS.REQUEST_MISMATCH);
234
+
235
+ // C6 (delegado a validateCertificationVerdict, ejecutado arriba): un
236
+ // veredicto dev con `firma.dev.nota` distinta del literal fijo ya fue
237
+ // rechazado como VERDICT_MALFORMED. Llegar aquí con modo:'dev' significa
238
+ // que la nota es exactamente la esperada — igual NUNCA certifica (C3a).
239
+ if (sano.firma.modo === 'dev') return rechazo(CODIGOS.DEV_NO_CERTIFICANTE);
240
+
241
+ // ---- Camino 'real' — bloque sigstore completo, C1/C3/C4/C7. --------------
242
+
243
+ if (typeof attestationVerifier !== 'function') return rechazo(CODIGOS.SIGNATURE_INVALID);
244
+ if (!validarBundleBytes(bundle)) return rechazo(CODIGOS.SIGNATURE_INVALID);
245
+
246
+ // C7: el bundle presentado en esta verificación debe ser el mismo que el
247
+ // veredicto declaró al emitirse — nunca invocar el puerto con un bundle
248
+ // sustituido después de los hechos.
249
+ const bundleDigest = crypto.createHash('sha256').update(bundle).digest('hex');
250
+ if (bundleDigest !== sano.firma.bundleDigest) return rechazo(CODIGOS.SIGNATURE_INVALID);
251
+
252
+ if (!validarPinOrigen(pinOrigen)) return rechazo(CODIGOS.SIGNATURE_INVALID);
253
+
254
+ // C1 (ADR-0056): el núcleo puro deriva del CONTENIDO del propio veredicto
255
+ // tanto los bytes canónicos como su digest — nunca de un valor que el caller
256
+ // pudiera inyectar — y ambos se pasan al puerto: el adaptador real necesita
257
+ // los bytes para presentarle a `gh attestation verify` el artefacto en disco
258
+ // (no existe modo digest-desnudo), y el digest queda como cross-check.
259
+ const artifactBytes = bytesVerdictSinFirma(sano);
260
+ const expectedArtifactDigest = sha256Utf8(artifactBytes);
261
+
262
+ let salidaCruda;
263
+ try {
264
+ salidaCruda = await attestationVerifier(bundle, expectedArtifactDigest, artifactBytes);
265
+ } catch {
266
+ // C4: cualquier excepción del puerto -> rechazo, nunca propagar.
267
+ return rechazo(CODIGOS.SIGNATURE_INVALID);
268
+ }
269
+
270
+ // C3/C4: forma exacta y tipos estrictos antes de confiar en nada de la salida.
271
+ if (!validarFormaSalidaAttestation(salidaCruda)) return rechazo(CODIGOS.SIGNATURE_INVALID);
272
+ if (salidaCruda.firmaValida !== true) return rechazo(CODIGOS.SIGNATURE_INVALID);
273
+
274
+ // Origen exacto: repo, workflow Y ref, los tres, igualdad estricta de string.
275
+ const { identidadOIDC } = salidaCruda;
276
+ if (identidadOIDC.repo !== pinOrigen.repo || identidadOIDC.workflow !== pinOrigen.workflow
277
+ || identidadOIDC.ref !== pinOrigen.ref) {
278
+ return rechazo(CODIGOS.ORIGIN_MISMATCH);
279
+ }
280
+
281
+ // El veredicto es AUTÉNTICO y de ORIGEN correcto. Pero `ok:true` NO autoriza
282
+ // por sí solo un badge: un veredicto real de un candidato que falló los SLO
283
+ // trae `certifica:false`. Se expone `certifica` al tope del resultado para que
284
+ // el generador de claims (T-10) NUNCA pueda leer `ok` aislado — el badge exige
285
+ // `ok === true && certifica === true`. Ver gate W4-H (32-PLAN.md).
286
+ return congelarProfundo({
287
+ ok: true,
288
+ certifica: sano.certifica === true,
289
+ verdict: sano,
290
+ identidadOIDC: { ...identidadOIDC },
291
+ });
292
+ }
293
+
294
+ module.exports = {
295
+ CODIGOS,
296
+ MAX_BUNDLE_BYTES,
297
+ bytesVerdictSinFirma,
298
+ digestVerdictSinFirma,
299
+ validarBundleBytes,
300
+ validarFormaSalidaAttestation,
301
+ verificarVeredicto,
302
+ };
@@ -1,131 +1,131 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * limpiar-artefactos-python.js — limpia caché Python antes de empaquetar.
6
- *
7
- * Se ejecuta como `prepack` antes de `npm pack` y `npm publish` para evitar
8
- * que artefactos locales (.pyc, __pycache__/) contaminen el tarball publicado.
9
- *
10
- * Reglas de seguridad (defensa en profundidad):
11
- * 1. Aborta si cwd no coincide con la raíz del package.json del repo.
12
- * 2. Profundidad máxima de recursión: 3 niveles desde la raíz.
13
- * 3. Allowlist explícita de directorios a EXCLUIR de la búsqueda
14
- * (node_modules, .git, temp, .planning, respositorios-git, _userland).
15
- * 4. Solo elimina directorios cuyo nombre coincide exactamente con el
16
- * conjunto cerrado: __pycache__, .pytest_cache, .mypy_cache, .ruff_cache.
17
- * 5. Solo elimina archivos sueltos con extensiones .pyc, .pyo.
18
- * 6. En CI no-interactivo respeta el flag SWL_PREPACK_DRY=1 (no borra,
19
- * solo lista).
20
- *
21
- * Exit codes:
22
- * 0 — OK (limpieza ejecutada o nada que limpiar)
23
- * 1 — error de invariante (cwd incorrecto, package.json no encontrado)
24
- */
25
-
26
- const fs = require('fs');
27
- const path = require('path');
28
-
29
- const { NOMBRES_VALIDOS } = require('./lib/paquetes-conocidos');
30
- const {
31
- DIRS_ARTEFACTOS_PYTHON: DIRS_A_LIMPIAR,
32
- EXTS_ARTEFACTOS_PYTHON: EXTS_A_LIMPIAR,
33
- } = require('./lib/artefactos-python');
34
-
35
- const PROFUNDIDAD_MAX = 3;
36
- const DIRS_EXCLUIDOS = new Set([
37
- 'node_modules', '.git', 'temp', '.planning', 'respositorios-git',
38
- '_userland', 'tests', '.github',
39
- ]);
40
-
41
- const dryRun = process.env.SWL_PREPACK_DRY === '1';
42
-
43
- function log(msg) { process.stdout.write(`[prepack] ${msg}\n`); }
44
- function err(msg) { process.stderr.write(`[prepack] ERROR: ${msg}\n`); }
45
-
46
- /**
47
- * Verifica que el cwd actual contiene el package.json del repo swl-ses.
48
- * Esto evita que el script borre directorios en la máquina del usuario si
49
- * algún día se invocara desde un cwd incorrecto (ej. dentro de un tarball
50
- * extraído por npm en .npm/_cacache).
51
- */
52
- function verificarRaiz() {
53
- const cwd = process.cwd();
54
- const pkgPath = path.join(cwd, 'package.json');
55
- if (!fs.existsSync(pkgPath)) {
56
- err(`no existe package.json en cwd: ${cwd}`);
57
- process.exit(1);
58
- }
59
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
60
- if (!NOMBRES_VALIDOS.includes(pkg.name)) {
61
- err(`package.json en ${cwd} no corresponde a swl-ses (name: ${pkg.name}). Abortando por seguridad.`);
62
- process.exit(1);
63
- }
64
- return cwd;
65
- }
66
-
67
- /**
68
- * Recorre el árbol desde root con profundidad limitada y elimina los
69
- * artefactos Python detectados. No sigue symlinks.
70
- */
71
- function limpiar(root, profundidad = 0) {
72
- if (profundidad > PROFUNDIDAD_MAX) return { dirs: 0, files: 0 };
73
-
74
- let dirsEliminados = 0;
75
- let filesEliminados = 0;
76
-
77
- let entries;
78
- try {
79
- entries = fs.readdirSync(root, { withFileTypes: true });
80
- } catch (e) {
81
- err(`no se pudo leer ${root}: ${e.message}`);
82
- return { dirs: 0, files: 0 };
83
- }
84
-
85
- for (const entry of entries) {
86
- if (entry.isSymbolicLink()) continue;
87
- const fullPath = path.join(root, entry.name);
88
-
89
- if (entry.isDirectory()) {
90
- if (DIRS_A_LIMPIAR.has(entry.name)) {
91
- if (dryRun) {
92
- log(`[dry-run] borraría dir: ${path.relative(process.cwd(), fullPath)}`);
93
- } else {
94
- fs.rmSync(fullPath, { recursive: true, force: true });
95
- log(`borrado dir: ${path.relative(process.cwd(), fullPath)}`);
96
- }
97
- dirsEliminados++;
98
- } else if (!DIRS_EXCLUIDOS.has(entry.name) && !entry.name.startsWith('.')) {
99
- const sub = limpiar(fullPath, profundidad + 1);
100
- dirsEliminados += sub.dirs;
101
- filesEliminados += sub.files;
102
- }
103
- } else if (entry.isFile()) {
104
- const ext = path.extname(entry.name).toLowerCase();
105
- if (EXTS_A_LIMPIAR.has(ext)) {
106
- if (dryRun) {
107
- log(`[dry-run] borraría archivo: ${path.relative(process.cwd(), fullPath)}`);
108
- } else {
109
- fs.rmSync(fullPath, { force: true });
110
- }
111
- filesEliminados++;
112
- }
113
- }
114
- }
115
-
116
- return { dirs: dirsEliminados, files: filesEliminados };
117
- }
118
-
119
- function main() {
120
- const root = verificarRaiz();
121
- const result = limpiar(root);
122
-
123
- if (result.dirs === 0 && result.files === 0) {
124
- log('sin artefactos Python que limpiar.');
125
- } else {
126
- const accion = dryRun ? 'detectados' : 'eliminados';
127
- log(`${accion}: ${result.dirs} directorios + ${result.files} archivos.`);
128
- }
129
- }
130
-
131
- main();
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * limpiar-artefactos-python.js — limpia caché Python antes de empaquetar.
6
+ *
7
+ * Se ejecuta como `prepack` antes de `npm pack` y `npm publish` para evitar
8
+ * que artefactos locales (.pyc, __pycache__/) contaminen el tarball publicado.
9
+ *
10
+ * Reglas de seguridad (defensa en profundidad):
11
+ * 1. Aborta si cwd no coincide con la raíz del package.json del repo.
12
+ * 2. Profundidad máxima de recursión: 3 niveles desde la raíz.
13
+ * 3. Allowlist explícita de directorios a EXCLUIR de la búsqueda
14
+ * (node_modules, .git, temp, .planning, respositorios-git, _userland).
15
+ * 4. Solo elimina directorios cuyo nombre coincide exactamente con el
16
+ * conjunto cerrado: __pycache__, .pytest_cache, .mypy_cache, .ruff_cache.
17
+ * 5. Solo elimina archivos sueltos con extensiones .pyc, .pyo.
18
+ * 6. En CI no-interactivo respeta el flag SWL_PREPACK_DRY=1 (no borra,
19
+ * solo lista).
20
+ *
21
+ * Exit codes:
22
+ * 0 — OK (limpieza ejecutada o nada que limpiar)
23
+ * 1 — error de invariante (cwd incorrecto, package.json no encontrado)
24
+ */
25
+
26
+ const fs = require('fs');
27
+ const path = require('path');
28
+
29
+ const { NOMBRES_VALIDOS } = require('./lib/paquetes-conocidos');
30
+ const {
31
+ DIRS_ARTEFACTOS_PYTHON: DIRS_A_LIMPIAR,
32
+ EXTS_ARTEFACTOS_PYTHON: EXTS_A_LIMPIAR,
33
+ } = require('./lib/artefactos-python');
34
+
35
+ const PROFUNDIDAD_MAX = 3;
36
+ const DIRS_EXCLUIDOS = new Set([
37
+ 'node_modules', '.git', 'temp', '.planning', 'respositorios-git',
38
+ '_userland', 'tests', '.github',
39
+ ]);
40
+
41
+ const dryRun = process.env.SWL_PREPACK_DRY === '1';
42
+
43
+ function log(msg) { process.stdout.write(`[prepack] ${msg}\n`); }
44
+ function err(msg) { process.stderr.write(`[prepack] ERROR: ${msg}\n`); }
45
+
46
+ /**
47
+ * Verifica que el cwd actual contiene el package.json del repo swl-ses.
48
+ * Esto evita que el script borre directorios en la máquina del usuario si
49
+ * algún día se invocara desde un cwd incorrecto (ej. dentro de un tarball
50
+ * extraído por npm en .npm/_cacache).
51
+ */
52
+ function verificarRaiz() {
53
+ const cwd = process.cwd();
54
+ const pkgPath = path.join(cwd, 'package.json');
55
+ if (!fs.existsSync(pkgPath)) {
56
+ err(`no existe package.json en cwd: ${cwd}`);
57
+ process.exit(1);
58
+ }
59
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
60
+ if (!NOMBRES_VALIDOS.includes(pkg.name)) {
61
+ err(`package.json en ${cwd} no corresponde a swl-ses (name: ${pkg.name}). Abortando por seguridad.`);
62
+ process.exit(1);
63
+ }
64
+ return cwd;
65
+ }
66
+
67
+ /**
68
+ * Recorre el árbol desde root con profundidad limitada y elimina los
69
+ * artefactos Python detectados. No sigue symlinks.
70
+ */
71
+ function limpiar(root, profundidad = 0) {
72
+ if (profundidad > PROFUNDIDAD_MAX) return { dirs: 0, files: 0 };
73
+
74
+ let dirsEliminados = 0;
75
+ let filesEliminados = 0;
76
+
77
+ let entries;
78
+ try {
79
+ entries = fs.readdirSync(root, { withFileTypes: true });
80
+ } catch (e) {
81
+ err(`no se pudo leer ${root}: ${e.message}`);
82
+ return { dirs: 0, files: 0 };
83
+ }
84
+
85
+ for (const entry of entries) {
86
+ if (entry.isSymbolicLink()) continue;
87
+ const fullPath = path.join(root, entry.name);
88
+
89
+ if (entry.isDirectory()) {
90
+ if (DIRS_A_LIMPIAR.has(entry.name)) {
91
+ if (dryRun) {
92
+ log(`[dry-run] borraría dir: ${path.relative(process.cwd(), fullPath)}`);
93
+ } else {
94
+ fs.rmSync(fullPath, { recursive: true, force: true });
95
+ log(`borrado dir: ${path.relative(process.cwd(), fullPath)}`);
96
+ }
97
+ dirsEliminados++;
98
+ } else if (!DIRS_EXCLUIDOS.has(entry.name) && !entry.name.startsWith('.')) {
99
+ const sub = limpiar(fullPath, profundidad + 1);
100
+ dirsEliminados += sub.dirs;
101
+ filesEliminados += sub.files;
102
+ }
103
+ } else if (entry.isFile()) {
104
+ const ext = path.extname(entry.name).toLowerCase();
105
+ if (EXTS_A_LIMPIAR.has(ext)) {
106
+ if (dryRun) {
107
+ log(`[dry-run] borraría archivo: ${path.relative(process.cwd(), fullPath)}`);
108
+ } else {
109
+ fs.rmSync(fullPath, { force: true });
110
+ }
111
+ filesEliminados++;
112
+ }
113
+ }
114
+ }
115
+
116
+ return { dirs: dirsEliminados, files: filesEliminados };
117
+ }
118
+
119
+ function main() {
120
+ const root = verificarRaiz();
121
+ const result = limpiar(root);
122
+
123
+ if (result.dirs === 0 && result.files === 0) {
124
+ log('sin artefactos Python que limpiar.');
125
+ } else {
126
+ const accion = dryRun ? 'detectados' : 'eliminados';
127
+ log(`${accion}: ${result.dirs} directorios + ${result.files} archivos.`);
128
+ }
129
+ }
130
+
131
+ main();