@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,49 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Superficie del CANDIDATO bajo prueba (F32/T-01, escindida de
5
+ * `w4-harness-bajo-prueba.js` por ADR-0056:100-106 y
6
+ * `DT-MOTOR-REPLAY-SRP-MUTACIONES-PINEADAS`).
7
+ *
8
+ * Este módulo contiene EXCLUSIVAMENTE la superficie de decisión de `swl-ses`
9
+ * (`runHarnessBajoPrueba`, que envuelve `verifyOperation()`). Se separó del
10
+ * validador del request del juez (`validateCertificationRequest`, que se queda
11
+ * en `w4-harness-bajo-prueba.js`) para que el juez de `swl-eval` pueda resolver
12
+ * la superficie DESDE EL TARBALL bajo prueba sin arrastrar maquinaria del juez
13
+ * — cerrando la mezcla de responsabilidades que la revisión adversarial de T-09
14
+ * señaló (el juez certifica el código que se envía, no una copia pineada).
15
+ *
16
+ * Es la superficie que el motor de replay ejercita contra cada caso del corpus
17
+ * a través del adaptador de candidato: en modo dev la envuelve
18
+ * `crearCandidatoSinteticoDev`; en el tramo real la resolvería
19
+ * `crearCandidatoReal` desde `CANDIDATO_ROOT`.
20
+ */
21
+
22
+ const { verifyOperation } = require('./policy-contract');
23
+
24
+ /**
25
+ * Superficie pura que el juez de `swl-eval` invoca sobre el candidato de
26
+ * `swl-ses`: envuelve `verifyOperation()` (ya puro) sin duplicar su
27
+ * validación ni construir un contrato nuevo (REQ-32-02). Nunca lanza —
28
+ * devuelve un resultado discriminado y sanitizado para que un proceso
29
+ * remoto no reciba un Error nativo con detalle interno de la pila.
30
+ */
31
+ function runHarnessBajoPrueba(rawOperationBytes) {
32
+ try {
33
+ const operation = verifyOperation(rawOperationBytes);
34
+ return Object.freeze({
35
+ ok: true,
36
+ operationDigest: operation.operationDigest,
37
+ capability: operation.capability,
38
+ effectKind: operation.effectKind,
39
+ });
40
+ } catch (error) {
41
+ // verifyOperation() garantiza .codigo en todo error que lanza (crearError()
42
+ // lo fija siempre); no se agrega un fallback defensivo no verificable.
43
+ return Object.freeze({ ok: false, codigo: error.codigo });
44
+ }
45
+ }
46
+
47
+ module.exports = {
48
+ runHarnessBajoPrueba,
49
+ };
@@ -0,0 +1,452 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Emisor del `CertificationVerdict` (ADR-0053 § Contrato swl-ses <-> swl-eval,
5
+ * F32/T-06, REQ-32-06).
6
+ *
7
+ * `emitirVeredicto(scoringReport, certificationRequest, insumos)` mapea un
8
+ * `ScoringReport` (T-05, `scripts/lib/w4-scorer-certificacion.js`) y un
9
+ * `CertificationRequest` (T-01, `scripts/lib/w4-harness-bajo-prueba.js`) a un
10
+ * `CertificationVerdict` sanitizado (`schemas/w4-certification-verdict.schema.json`):
11
+ * nunca el payload crudo del corpus, PII ni tokens — sólo SLO/medido/pass por
12
+ * dimensión, metadatos escalares y conteos agregados. `certifica` se copia
13
+ * LITERAL del `ScoringReport` (nunca se recomputa ni se "eleva" a partir de
14
+ * las dimensiones sanitizadas — esa es la única fuente de verdad de
15
+ * `certificarDimensiones()` en T-05).
16
+ *
17
+ * Puro: cero `fs`, cero red, cero reloj de pared. `emitidoEn` se recibe ya
18
+ * validado como argumento (`insumos.emitidoEn`) — este módulo nunca consulta
19
+ * `Date.now()`.
20
+ *
21
+ * C5 (sanitización enforzada también por el schema, no sólo aquí): el
22
+ * `medido` crudo de un `DimensionScore` puede traer booleanos
23
+ * (`paridadContractual`), objetos anidados (`ciclosConsecutivosPorRuntime`) o
24
+ * arreglos de categorías (`categoriasSinEvidenciaDeDefensaReal`). `aplanarMedido()`
25
+ * lo reduce a `null` o a un mapa plano de una sola profundidad cuyas claves
26
+ * son rutas con punto y cuyos valores son SIEMPRE `number` — nunca strings,
27
+ * booleanos ni arreglos crudos. Los booleanos se codifican 1/0; los arreglos
28
+ * se reducen a su longitud (un conteo, nunca los nombres de categoría
29
+ * crudos — esos, cuando importan, viajan reducidos en `failures[]`).
30
+ *
31
+ * C6: `firma.dev.nota` es un literal FIJO (`NOTA_DEV`) que este módulo nunca
32
+ * construye dinámicamente — el schema lo fija como `const` y el verificador
33
+ * (`w4-verificar-veredicto.js`) rechaza cualquier otro valor delegando en
34
+ * `validateCertificationVerdict()`.
35
+ */
36
+
37
+ const { validateScoringReport, NOMBRES_DIMENSIONES } = require('./w4-scorer-certificacion');
38
+ const { validateCertificationRequest } = require('./w4-harness-bajo-prueba');
39
+ const { CATEGORIAS } = require('./w4-corpus-generador');
40
+ const { validarTimestampIso } = require('./signed-envelope');
41
+
42
+ const SCHEMA_VERSION = '1.0.0';
43
+ const NOMBRE_META_SLO = 'meta-SLO';
44
+
45
+ // C6: literal fijo — DEBE ser idéntico, carácter por carácter, al `const` de
46
+ // schemas/w4-certification-verdict.schema.json#/$defs/firmaDev/properties/nota.
47
+ const NOTA_DEV = 'verificador dev, adaptador de attestation simulado — este veredicto nunca certifica ni implica origen verificado (ADR-0053 C6)';
48
+
49
+ const SHA256 = /^[a-f0-9]{64}$/u;
50
+ const IDENTIFICADOR = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
51
+ const CASO_ID = /^(?:mutation|injection)-[a-z][a-z-]*-[0-9]{6}$/u;
52
+ const CATEGORIA_SET = new Set(CATEGORIAS);
53
+ const NOMBRE_DIMENSION_SET = new Set(NOMBRES_DIMENSIONES);
54
+
55
+ const INSUMOS_KEYS_DEV = ['runId', 'emitidoEn', 'corpusAgregados', 'failures', 'modo'];
56
+ const INSUMOS_KEYS_REAL = [...INSUMOS_KEYS_DEV, 'bundleDigest'];
57
+ const CORPUS_AGREGADOS_KEYS = [
58
+ 'totalCasos', 'selladosHumano', 'mutationsTotal', 'promptInjectionsTotal', 'pass', 'fail', 'porCategoria',
59
+ ];
60
+ const CATEGORIA_AGG_KEYS = ['total', 'fail'];
61
+ const FAILURE_KEYS = ['casoId', 'categoria', 'dimension'];
62
+
63
+ const VERDICT_KEYS = [
64
+ 'kind', 'schemaVersion', 'runId', 'request', 'certifica', 'dimensiones',
65
+ 'metaSlo', 'metadata', 'corpusAgregados', 'failures', 'firma', 'emitidoEn',
66
+ ];
67
+ const REQUEST_KEYS = ['commit', 'tarballDigest', 'matrix'];
68
+ const MATRIX_ENTRY_KEYS = ['runtime', 'os', 'model'];
69
+ const DIMENSION_VERDICT_KEYS = ['dimension', 'slo', 'medido', 'pass'];
70
+ const METADATA_KEYS = ['cobertura', 'mutation', 'cve', 'rollback'];
71
+ const RUNTIMES_CORE3 = new Set(['claude-code', 'codex-cli', 'gemini-cli']);
72
+ const OS_CORE3 = new Set(['win32', 'linux', 'darwin']);
73
+
74
+ function crearError(codigo, mensaje) {
75
+ const error = new Error(mensaje);
76
+ error.codigo = codigo;
77
+ return error;
78
+ }
79
+
80
+ function fallar(codigo, mensaje) {
81
+ throw crearError(codigo, mensaje);
82
+ }
83
+
84
+ function esObjetoPlano(valor) {
85
+ return Boolean(valor) && typeof valor === 'object' && !Array.isArray(valor)
86
+ && Object.getPrototypeOf(valor) === Object.prototype
87
+ && Object.getOwnPropertySymbols(valor).length === 0;
88
+ }
89
+
90
+ function validarClaves(valor, esperadas, etiqueta, codigo = 'VERDICT_EMISION_INVALIDA') {
91
+ if (!esObjetoPlano(valor)) fallar(codigo, `${etiqueta} inválido`);
92
+ const actuales = Object.keys(valor).sort();
93
+ const objetivo = [...esperadas].sort();
94
+ if (JSON.stringify(actuales) !== JSON.stringify(objetivo)) fallar(codigo, `${etiqueta} inválido`);
95
+ }
96
+
97
+ function congelarProfundo(valor) {
98
+ if (valor && typeof valor === 'object' && !Object.isFrozen(valor)) {
99
+ Object.values(valor).forEach(congelarProfundo);
100
+ Object.freeze(valor);
101
+ }
102
+ return valor;
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // C5 — sanitización de 'medido': siempre número, null, u objeto plano de números.
107
+ // ---------------------------------------------------------------------------
108
+
109
+ function aplanarNumero(valor) {
110
+ if (typeof valor === 'number' && Number.isFinite(valor)) return valor;
111
+ if (typeof valor === 'boolean') return valor ? 1 : 0;
112
+ if (Array.isArray(valor)) return valor.length;
113
+ return 0;
114
+ }
115
+
116
+ function aplanarMedido(valor, prefijo = '') {
117
+ if (valor === null || valor === undefined) return prefijo ? {} : null;
118
+ if (typeof valor === 'number' || typeof valor === 'boolean' || Array.isArray(valor)) {
119
+ return { [prefijo || 'valor']: aplanarNumero(valor) };
120
+ }
121
+ if (esObjetoPlano(valor)) {
122
+ const acumulado = {};
123
+ for (const [clave, sub] of Object.entries(valor)) {
124
+ Object.assign(acumulado, aplanarMedido(sub, prefijo ? `${prefijo}.${clave}` : clave));
125
+ }
126
+ return acumulado;
127
+ }
128
+ return prefijo ? {} : null;
129
+ }
130
+
131
+ function sanitizarMedidoTope(valor) {
132
+ const aplanado = aplanarMedido(valor);
133
+ if (aplanado === null) return null;
134
+ return Object.keys(aplanado).length === 0 ? null : congelarProfundo(aplanado);
135
+ }
136
+
137
+ function crearDimensionVerdict(score) {
138
+ return congelarProfundo({
139
+ dimension: score.dimension,
140
+ slo: score.slo,
141
+ medido: sanitizarMedidoTope(score.medido),
142
+ pass: score.pass,
143
+ });
144
+ }
145
+
146
+ function extraerScalarMap(valor) {
147
+ if (!esObjetoPlano(valor)) return null;
148
+ const salida = {};
149
+ for (const [clave, v] of Object.entries(valor)) {
150
+ if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'string') salida[clave] = v;
151
+ }
152
+ return salida;
153
+ }
154
+
155
+ function construirMetadata(metaSlo) {
156
+ const medido = esObjetoPlano(metaSlo.medido) ? metaSlo.medido : {};
157
+ return congelarProfundo({
158
+ cobertura: extraerScalarMap(medido.cobertura),
159
+ mutation: extraerScalarMap(medido.mutation),
160
+ cve: extraerScalarMap(medido.cve),
161
+ rollback: extraerScalarMap(medido.rollback),
162
+ });
163
+ }
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // Validación de insumos del emisor — nunca confía en la forma del caller.
167
+ // ---------------------------------------------------------------------------
168
+
169
+ function validarEnteroNoNegativo(valor, etiqueta) {
170
+ if (!Number.isInteger(valor) || valor < 0) fallar('VERDICT_EMISION_INVALIDA', `${etiqueta} inválido`);
171
+ }
172
+
173
+ function validarCorpusAgregados(valor) {
174
+ validarClaves(valor, CORPUS_AGREGADOS_KEYS, 'corpusAgregados');
175
+ ['totalCasos', 'selladosHumano', 'mutationsTotal', 'promptInjectionsTotal', 'pass', 'fail']
176
+ .forEach((clave) => validarEnteroNoNegativo(valor[clave], `corpusAgregados.${clave}`));
177
+ if (valor.pass + valor.fail !== valor.totalCasos) {
178
+ fallar('VERDICT_EMISION_INVALIDA', 'corpusAgregados.pass + fail debe igualar totalCasos');
179
+ }
180
+ if (!esObjetoPlano(valor.porCategoria)) fallar('VERDICT_EMISION_INVALIDA', 'corpusAgregados.porCategoria inválido');
181
+ const porCategoria = {};
182
+ for (const [categoria, agregado] of Object.entries(valor.porCategoria)) {
183
+ if (!CATEGORIA_SET.has(categoria)) {
184
+ fallar('VERDICT_EMISION_INVALIDA', `corpusAgregados.porCategoria con categoría desconocida: ${categoria}`);
185
+ }
186
+ validarClaves(agregado, CATEGORIA_AGG_KEYS, `corpusAgregados.porCategoria.${categoria}`);
187
+ validarEnteroNoNegativo(agregado.total, `corpusAgregados.porCategoria.${categoria}.total`);
188
+ validarEnteroNoNegativo(agregado.fail, `corpusAgregados.porCategoria.${categoria}.fail`);
189
+ if (agregado.fail > agregado.total) {
190
+ fallar('VERDICT_EMISION_INVALIDA', `corpusAgregados.porCategoria.${categoria}.fail excede total`);
191
+ }
192
+ porCategoria[categoria] = { total: agregado.total, fail: agregado.fail };
193
+ }
194
+ return congelarProfundo({
195
+ totalCasos: valor.totalCasos,
196
+ selladosHumano: valor.selladosHumano,
197
+ mutationsTotal: valor.mutationsTotal,
198
+ promptInjectionsTotal: valor.promptInjectionsTotal,
199
+ pass: valor.pass,
200
+ fail: valor.fail,
201
+ porCategoria,
202
+ });
203
+ }
204
+
205
+ function validarFailures(lista) {
206
+ if (!Array.isArray(lista)) fallar('VERDICT_EMISION_INVALIDA', 'failures inválido');
207
+ return congelarProfundo(lista.map((entrada, indice) => {
208
+ validarClaves(entrada, FAILURE_KEYS, `failures[${indice}]`);
209
+ if (typeof entrada.casoId !== 'string' || !CASO_ID.test(entrada.casoId)) {
210
+ fallar('VERDICT_EMISION_INVALIDA', `failures[${indice}].casoId inválido`);
211
+ }
212
+ if (!CATEGORIA_SET.has(entrada.categoria)) {
213
+ fallar('VERDICT_EMISION_INVALIDA', `failures[${indice}].categoria inválido`);
214
+ }
215
+ if (!NOMBRE_DIMENSION_SET.has(entrada.dimension)) {
216
+ fallar('VERDICT_EMISION_INVALIDA', `failures[${indice}].dimension inválido`);
217
+ }
218
+ return { casoId: entrada.casoId, categoria: entrada.categoria, dimension: entrada.dimension };
219
+ }));
220
+ }
221
+
222
+ // Compara la matriz DECLARADA en el request contra la matriz efectivamente
223
+ // scoreada (matrixEntries del ScoringReport, derivada de replayReports) como
224
+ // MULTICONJUNTO por runtime|os|model (reusa `claveMatrixEntry`, definido abajo).
225
+ // Cierra el hueco de que el badge anuncie una matriz distinta a la evaluada: el
226
+ // candidato declara la matriz en su request, pero la fuente de verdad de lo
227
+ // certificado es lo que realmente se replayó/scoreó, no lo que el candidato dice.
228
+ function mismasMatrices(declarada, evaluada) {
229
+ const a = declarada.map(claveMatrixEntry).sort();
230
+ const b = evaluada.map(claveMatrixEntry).sort();
231
+ return a.length === b.length && a.every((clave, indice) => clave === b[indice]);
232
+ }
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // emitirVeredicto — ScoringReport + CertificationRequest + insumos -> Verdict
236
+ // ---------------------------------------------------------------------------
237
+
238
+ function emitirVeredicto(scoringReport, certificationRequest, insumos) {
239
+ // Re-validación: el emisor no confía en la forma de lo que recibe, aunque
240
+ // venga de módulos hermanos ya validados en su propio boundary.
241
+ const reporte = validateScoringReport(scoringReport);
242
+ const solicitud = validateCertificationRequest(certificationRequest);
243
+
244
+ if (!esObjetoPlano(insumos) || (insumos.modo !== 'dev' && insumos.modo !== 'real')) {
245
+ fallar('VERDICT_EMISION_INVALIDA', 'insumos.modo debe ser "dev" o "real"');
246
+ }
247
+ const clavesEsperadas = insumos.modo === 'real' ? INSUMOS_KEYS_REAL : INSUMOS_KEYS_DEV;
248
+ validarClaves(insumos, clavesEsperadas, 'insumos');
249
+
250
+ if (typeof insumos.runId !== 'string' || !IDENTIFICADOR.test(insumos.runId)) {
251
+ fallar('VERDICT_EMISION_INVALIDA', 'insumos.runId inválido');
252
+ }
253
+ validarTimestampIso(insumos.emitidoEn, 'insumos.emitidoEn');
254
+
255
+ const corpusAgregados = validarCorpusAgregados(insumos.corpusAgregados);
256
+ const failures = validarFailures(insumos.failures);
257
+
258
+ // Binding matriz declarada ↔ evaluada: la matriz que el badge certificará
259
+ // (request.matrix) debe coincidir exactamente con la que realmente se scoreó
260
+ // (reporte.matrixEntries). Sin esto, un candidato podría declarar una matriz y
261
+ // hacer certificar otra. Fail-closed antes de emitir.
262
+ if (!mismasMatrices(solicitud.matrix, reporte.matrixEntries)) {
263
+ fallar('VERDICT_EMISION_INVALIDA', 'request.matrix no coincide con la matriz efectivamente scoreada (matrixEntries)');
264
+ }
265
+
266
+ const dimensiones = reporte.dimensiones.map(crearDimensionVerdict);
267
+ const metaSlo = crearDimensionVerdict(reporte.metaSlo);
268
+ const metadata = construirMetadata(reporte.metaSlo);
269
+
270
+ let firma;
271
+ if (insumos.modo === 'dev') {
272
+ firma = Object.freeze({ modo: 'dev', tipo: 'no-certificante', nota: NOTA_DEV });
273
+ } else {
274
+ if (typeof insumos.bundleDigest !== 'string' || !SHA256.test(insumos.bundleDigest)) {
275
+ fallar('VERDICT_EMISION_INVALIDA', 'insumos.bundleDigest inválido');
276
+ }
277
+ firma = Object.freeze({ modo: 'real', tipo: 'certificante', bundleDigest: insumos.bundleDigest });
278
+ }
279
+
280
+ const verdict = congelarProfundo({
281
+ kind: 'swl-certification-verdict',
282
+ schemaVersion: SCHEMA_VERSION,
283
+ runId: insumos.runId,
284
+ request: {
285
+ commit: solicitud.candidate.commit,
286
+ tarballDigest: solicitud.candidate.tarballDigest,
287
+ matrix: solicitud.matrix.map((entrada) => ({ ...entrada })),
288
+ },
289
+ certifica: reporte.certifica, // COPIA LITERAL — nunca se recomputa ni se eleva.
290
+ dimensiones,
291
+ metaSlo,
292
+ metadata,
293
+ corpusAgregados,
294
+ failures,
295
+ firma,
296
+ emitidoEn: insumos.emitidoEn,
297
+ });
298
+
299
+ return validateCertificationVerdict(verdict);
300
+ }
301
+
302
+ // ---------------------------------------------------------------------------
303
+ // validateCertificationVerdict — validador oficial sin efectos.
304
+ // ---------------------------------------------------------------------------
305
+
306
+ function validarMatrixEntry(entry, etiqueta) {
307
+ validarClaves(entry, MATRIX_ENTRY_KEYS, etiqueta, 'CERTIFICATION_VERDICT_INVALID');
308
+ if (typeof entry.runtime !== 'string' || !RUNTIMES_CORE3.has(entry.runtime)) {
309
+ fallar('CERTIFICATION_VERDICT_INVALID', `${etiqueta}.runtime inválido`);
310
+ }
311
+ if (typeof entry.os !== 'string' || !OS_CORE3.has(entry.os)) {
312
+ fallar('CERTIFICATION_VERDICT_INVALID', `${etiqueta}.os inválido`);
313
+ }
314
+ if (typeof entry.model !== 'string' || !IDENTIFICADOR.test(entry.model)) {
315
+ fallar('CERTIFICATION_VERDICT_INVALID', `${etiqueta}.model inválido`);
316
+ }
317
+ }
318
+
319
+ // Mismo criterio de identidad que claveMatrixEntry() de
320
+ // w4-verificar-veredicto.js (sin NFKC aquí: en este boundary runtime/os/model
321
+ // ya pasaron por validarMatrixEntry — enum + IDENTIFICADOR ASCII — así que la
322
+ // normalización Unicode no aporta nada adicional).
323
+ function claveMatrixEntry(entrada) {
324
+ return `${entrada.runtime}|${entrada.os}|${entrada.model}`;
325
+ }
326
+
327
+ function validarRequestVerdict(request) {
328
+ validarClaves(request, REQUEST_KEYS, 'request', 'CERTIFICATION_VERDICT_INVALID');
329
+ if (typeof request.commit !== 'string' || !/^[a-f0-9]{40}$/u.test(request.commit)) {
330
+ fallar('CERTIFICATION_VERDICT_INVALID', 'request.commit inválido');
331
+ }
332
+ if (typeof request.tarballDigest !== 'string' || !SHA256.test(request.tarballDigest)) {
333
+ fallar('CERTIFICATION_VERDICT_INVALID', 'request.tarballDigest inválido');
334
+ }
335
+ if (!Array.isArray(request.matrix) || request.matrix.length === 0) {
336
+ fallar('CERTIFICATION_VERDICT_INVALID', 'request.matrix inválido');
337
+ }
338
+ request.matrix.forEach((entry, i) => validarMatrixEntry(entry, `request.matrix[${i}]`));
339
+
340
+ // Drift schema-vs-validador: el schema declara `request.matrix` con
341
+ // `uniqueItems: true` — se enforza también aquí (mismo criterio de clave
342
+ // que el verificador).
343
+ const clavesVistas = new Set();
344
+ for (const entry of request.matrix) {
345
+ const clave = claveMatrixEntry(entry);
346
+ if (clavesVistas.has(clave)) {
347
+ fallar('CERTIFICATION_VERDICT_INVALID', 'request.matrix con entradas duplicadas');
348
+ }
349
+ clavesVistas.add(clave);
350
+ }
351
+ }
352
+
353
+ function esEscalarValido(valor) {
354
+ // Un número escalar del veredicto debe ser FINITO: NaN/Infinity se serializan a
355
+ // `null` en I-JSON y corromperían silenciosamente el dato (fail-open de C5).
356
+ if (typeof valor === 'number') return Number.isFinite(valor);
357
+ return typeof valor === 'boolean' || typeof valor === 'string';
358
+ }
359
+
360
+ function esMedidoValido(valor) {
361
+ if (valor === null) return true;
362
+ if (typeof valor === 'number') return Number.isFinite(valor);
363
+ if (!esObjetoPlano(valor)) return false;
364
+ return Object.values(valor).every((v) => typeof v === 'number' && Number.isFinite(v));
365
+ }
366
+
367
+ function validarDimensionVerdict(valor, etiqueta, nombreEsperado) {
368
+ validarClaves(valor, DIMENSION_VERDICT_KEYS, etiqueta, 'CERTIFICATION_VERDICT_INVALID');
369
+ if (nombreEsperado ? valor.dimension !== nombreEsperado : !NOMBRE_DIMENSION_SET.has(valor.dimension)) {
370
+ fallar('CERTIFICATION_VERDICT_INVALID', `${etiqueta}.dimension inválido`);
371
+ }
372
+ if (typeof valor.slo !== 'string' || valor.slo.length === 0) {
373
+ fallar('CERTIFICATION_VERDICT_INVALID', `${etiqueta}.slo inválido`);
374
+ }
375
+ if (!esMedidoValido(valor.medido)) fallar('CERTIFICATION_VERDICT_INVALID', `${etiqueta}.medido inválido`);
376
+ if (typeof valor.pass !== 'boolean') fallar('CERTIFICATION_VERDICT_INVALID', `${etiqueta}.pass inválido`);
377
+ }
378
+
379
+ function validarMetadataVerdict(metadata) {
380
+ validarClaves(metadata, METADATA_KEYS, 'metadata', 'CERTIFICATION_VERDICT_INVALID');
381
+ for (const clave of METADATA_KEYS) {
382
+ const bloque = metadata[clave];
383
+ if (bloque === null) continue;
384
+ if (!esObjetoPlano(bloque)) fallar('CERTIFICATION_VERDICT_INVALID', `metadata.${clave} inválido`);
385
+ for (const valor of Object.values(bloque)) {
386
+ if (!esEscalarValido(valor)) {
387
+ fallar('CERTIFICATION_VERDICT_INVALID', `metadata.${clave} contiene un valor no escalar o no finito`);
388
+ }
389
+ }
390
+ }
391
+ }
392
+
393
+ function validarFirmaVerdict(firma) {
394
+ if (!esObjetoPlano(firma) || (firma.modo !== 'dev' && firma.modo !== 'real')) {
395
+ fallar('CERTIFICATION_VERDICT_INVALID', 'firma.modo inválido');
396
+ }
397
+ if (firma.modo === 'dev') {
398
+ validarClaves(firma, ['modo', 'tipo', 'nota'], 'firma', 'CERTIFICATION_VERDICT_INVALID');
399
+ if (firma.tipo !== 'no-certificante') fallar('CERTIFICATION_VERDICT_INVALID', 'firma.tipo inválido');
400
+ if (firma.nota !== NOTA_DEV) fallar('CERTIFICATION_VERDICT_INVALID', 'firma.nota inválido');
401
+ return;
402
+ }
403
+ validarClaves(firma, ['modo', 'tipo', 'bundleDigest'], 'firma', 'CERTIFICATION_VERDICT_INVALID');
404
+ if (firma.tipo !== 'certificante') fallar('CERTIFICATION_VERDICT_INVALID', 'firma.tipo inválido');
405
+ if (typeof firma.bundleDigest !== 'string' || !SHA256.test(firma.bundleDigest)) {
406
+ fallar('CERTIFICATION_VERDICT_INVALID', 'firma.bundleDigest inválido');
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Valida un `CertificationVerdict` (ADR-0053, REQ-32-06) sin efectos: no lee
412
+ * disco, no hace red, no consulta el reloj. Lanza `CERTIFICATION_VERDICT_INVALID`
413
+ * si el valor no cumple `schemas/w4-certification-verdict.schema.json` o sus
414
+ * `runtimeOnlyChecks`; retorna un valor deeply frozen equivalente si es válido.
415
+ */
416
+ function validateCertificationVerdict(value) {
417
+ validarClaves(value, VERDICT_KEYS, 'certificationVerdict', 'CERTIFICATION_VERDICT_INVALID');
418
+ if (value.kind !== 'swl-certification-verdict') fallar('CERTIFICATION_VERDICT_INVALID', 'kind inválido');
419
+ if (value.schemaVersion !== SCHEMA_VERSION) fallar('CERTIFICATION_VERDICT_INVALID', 'schemaVersion inválida');
420
+ if (typeof value.runId !== 'string' || !IDENTIFICADOR.test(value.runId)) {
421
+ fallar('CERTIFICATION_VERDICT_INVALID', 'runId inválido');
422
+ }
423
+
424
+ validarRequestVerdict(value.request);
425
+
426
+ if (typeof value.certifica !== 'boolean') fallar('CERTIFICATION_VERDICT_INVALID', 'certifica inválido');
427
+
428
+ if (!Array.isArray(value.dimensiones) || value.dimensiones.length !== 6) {
429
+ fallar('CERTIFICATION_VERDICT_INVALID', 'dimensiones inválido');
430
+ }
431
+ value.dimensiones.forEach((d, i) => validarDimensionVerdict(d, `dimensiones[${i}]`));
432
+ const nombresRecibidos = [...value.dimensiones.map((d) => d.dimension)].sort();
433
+ const nombresEsperados = [...NOMBRES_DIMENSIONES].sort();
434
+ if (JSON.stringify(nombresRecibidos) !== JSON.stringify(nombresEsperados)) {
435
+ fallar('CERTIFICATION_VERDICT_INVALID', 'dimensiones no cubre exactamente las 6 de ADR-0050 sin duplicados ni faltantes');
436
+ }
437
+
438
+ validarDimensionVerdict(value.metaSlo, 'metaSlo', NOMBRE_META_SLO);
439
+ validarMetadataVerdict(value.metadata);
440
+ validarCorpusAgregados(value.corpusAgregados);
441
+ validarFailures(value.failures);
442
+ validarFirmaVerdict(value.firma);
443
+ validarTimestampIso(value.emitidoEn, 'emitidoEn');
444
+
445
+ return congelarProfundo(JSON.parse(JSON.stringify(value)));
446
+ }
447
+
448
+ module.exports = {
449
+ NOTA_DEV,
450
+ emitirVeredicto,
451
+ validateCertificationVerdict,
452
+ };