@saulwade/swl-ses 2.3.0 → 2.3.1
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.
- package/CLAUDE.md +47 -6
- package/README.md +1 -1
- package/agentes/accesibilidad-wcag-swl.md +690 -690
- package/agentes/arquitecto-swl.md +267 -267
- package/agentes/auto-evolucion-swl.md +908 -908
- package/agentes/backend-api-swl.md +472 -472
- package/agentes/backend-csharp-swl.md +420 -420
- package/agentes/backend-go-swl.md +390 -390
- package/agentes/backend-java-swl.md +281 -281
- package/agentes/backend-node-swl.md +479 -479
- package/agentes/backend-python-swl.md +605 -605
- package/agentes/backend-rust-swl.md +364 -364
- package/agentes/backend-workers-swl.md +482 -482
- package/agentes/cloud-infra-swl.md +509 -509
- package/agentes/consolidador-swl.md +541 -541
- package/agentes/datos-swl.md +605 -605
- package/agentes/depurador-swl.md +352 -352
- package/agentes/devops-ci-swl.md +400 -400
- package/agentes/disenador-ui-swl.md +569 -569
- package/agentes/documentador-swl.md +345 -345
- package/agentes/frontend-angular-swl.md +621 -621
- package/agentes/frontend-css-swl.md +716 -716
- package/agentes/frontend-react-swl.md +692 -692
- package/agentes/frontend-swl.md +496 -496
- package/agentes/frontend-tailwind-swl.md +826 -826
- package/agentes/gh-fix-ci-swl.md +2 -2
- package/agentes/implementador-swl.md +328 -328
- package/agentes/investigador-swl.md +432 -432
- package/agentes/investigador-ux-swl.md +505 -505
- package/agentes/llm-apps-swl.md +278 -278
- package/agentes/migrador-swl.md +442 -442
- package/agentes/mobile-android-swl.md +511 -511
- package/agentes/mobile-cross-swl.md +541 -541
- package/agentes/mobile-ios-swl.md +502 -502
- package/agentes/mobile-testing-swl.md +302 -302
- package/agentes/nemesis-auditor-swl.md +285 -285
- package/agentes/notificador-swl.md +918 -918
- package/agentes/observabilidad-swl.md +438 -438
- package/agentes/orquestador-swl.md +1026 -1026
- package/agentes/pagos-swl.md +310 -310
- package/agentes/perfilador-usuario-swl.md +321 -321
- package/agentes/planificador-swl.md +399 -399
- package/agentes/producto-prd-swl.md +589 -589
- package/agentes/red-team-swl.md +1 -1
- package/agentes/release-manager-swl.md +590 -590
- package/agentes/rendimiento-swl.md +713 -713
- package/agentes/resolutor-build-swl.md +245 -245
- package/agentes/revisor-angular-swl.md +278 -278
- package/agentes/revisor-codigo-swl.md +505 -505
- package/agentes/revisor-csharp-swl.md +264 -264
- package/agentes/revisor-go-swl.md +259 -259
- package/agentes/revisor-java-swl.md +257 -257
- package/agentes/revisor-kotlin-swl.md +273 -273
- package/agentes/revisor-nextjs-swl.md +281 -281
- package/agentes/revisor-php-swl.md +271 -271
- package/agentes/revisor-react-swl.md +278 -278
- package/agentes/revisor-rust-swl.md +346 -346
- package/agentes/revisor-seguridad-swl.md +399 -399
- package/agentes/revisor-swift-swl.md +268 -268
- package/agentes/revisor-typescript-swl.md +346 -346
- package/agentes/sre-swl.md +291 -291
- package/agentes/tdd-qa-swl.md +393 -393
- package/comandos/swl/briefing.md +119 -119
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/predecir.md +139 -139
- package/comandos/swl/sesiones.md +1 -1
- package/gateway/agent-executor.js +1 -1
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/agent-deep-links/SKILL.md +148 -148
- package/habilidades/agentes-como-servicio/SKILL.md +2 -2
- package/habilidades/angular-moderno/SKILL.md +20 -1
- package/habilidades/auto-evolucion-protocolo/SKILL.md +276 -276
- package/habilidades/backend-async-postgres-testing/SKILL.md +215 -215
- package/habilidades/backend-error-design/SKILL.md +221 -221
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/calidad-anti-patrones-universales/SKILL.md +368 -368
- package/habilidades/calidad-contract-testing/SKILL.md +165 -165
- package/habilidades/calidad-mutation-testing/SKILL.md +170 -170
- package/habilidades/changelog-generator/scripts/parse-commits.js +367 -367
- package/habilidades/checklist-seguridad/recursos/stride-cobertura.md +60 -60
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/drift-detection/SKILL.md +179 -179
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +1 -1
- package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +57 -57
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
- package/habilidades/harness-claude-code/recursos/disciplina-harness-regla.md +252 -252
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/perfil-usuario/SKILL.md +200 -200
- package/habilidades/prevencion-sobreingenieria/recursos/EXAMPLES.md +580 -580
- package/habilidades/proceso-ddia-fundamentos/SKILL.md +255 -255
- package/habilidades/proceso-ddia-streaming/SKILL.md +231 -231
- package/habilidades/proceso-debate-adversarial/recursos/personas.md +105 -105
- package/habilidades/proceso-discovery-machote/SKILL.md +157 -157
- package/habilidades/proceso-dynamic-workflows/SKILL.md +138 -138
- package/habilidades/proceso-dynamic-workflows/recursos/template-adversarial-verify.js +65 -65
- package/habilidades/proceso-dynamic-workflows/recursos/template-triage.js +65 -65
- package/habilidades/proceso-intent-engineering/SKILL.md +269 -269
- package/habilidades/proceso-modular-split/SKILL.md +256 -256
- package/habilidades/prompt-engineering/recursos/patrones-avanzados.md +2 -2
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
- package/habilidades/structured-outputs/SKILL.md +2 -2
- package/habilidades/swl-claudemd/recursos/contrato-aprender.md +83 -83
- package/habilidades/swl-claudemd/recursos/duplicacion-reglas-globales.md +85 -85
- package/habilidades/swl-claudemd/recursos/plantillas-init.md +94 -94
- package/habilidades/swl-dashboard/SKILL.md +2 -2
- package/habilidades/tdd-workflow/recursos/gherkin-bdd.md +111 -111
- package/habilidades/validacion-ci-sistema/recursos/validadores-completos.md +1 -1
- package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +1 -1
- package/hooks/ciclo-evolucion-subagente.js +26 -26
- package/hooks/ciclo-evolucion.js +26 -26
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/claudemd-duplicacion-detector.js +170 -170
- package/hooks/contexto-iteracion.js +144 -144
- package/hooks/guardrail-modelo.js +1 -1
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/autonomia.js +208 -208
- package/hooks/lib/ciclo-evolucion.js +47 -47
- package/hooks/lib/deep-links.js +185 -185
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/etapa-auto-evolucion.js +701 -701
- package/hooks/lib/etapa-metricas.js +388 -388
- package/hooks/lib/etapa-perfil-usuario.js +376 -376
- package/hooks/lib/loop-telemetry.js +321 -321
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/model-router.js +2 -2
- package/hooks/lib/propose-step.js +358 -358
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/rate-limit-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/security-net.js +201 -201
- package/hooks/lib/skill-auditor.js +588 -588
- package/hooks/lib/sync-status.js +228 -228
- package/hooks/lib/taint-tracker.js +107 -107
- package/hooks/lib/text-similarity.js +241 -241
- package/hooks/lib/token-estimator.js +1 -0
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/lib/usage-model.js +1 -1
- package/hooks/linea-estado.js +2 -0
- package/hooks/registro-turnos.js +209 -209
- package/hooks/session-briefing.js +98 -98
- package/hooks/spec-gate.js +211 -211
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/tdd-gate.js +241 -241
- package/hooks/telemetria-skill-routing.js +100 -100
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-intent-spec.js +242 -242
- package/hooks/validar-memoria-hook.js +218 -218
- package/hooks/validar-planning-paths.js +134 -134
- package/instintos/autonomia.yaml +27 -27
- package/instintos/prompt-appendices.yaml +57 -57
- package/llms.txt +29 -29
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/canonical-hashes.json +2291 -1964
- package/manifiestos/planning-paths.json +44 -44
- package/manifiestos/skills-lock.json +1282 -1282
- package/package.json +1 -1
- package/plantillas/auditor-veto-template.md +105 -105
- package/plantillas/github-workflows/README.md +47 -47
- package/plantillas/github-workflows/release-please.yml +44 -44
- package/plantillas/github-workflows/swl-ci.yml +107 -107
- package/plantillas/github-workflows/swl-security.yml +51 -51
- package/plugin.json +1 -1
- package/reglas/accesibilidad.md +10 -10
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/api-diseno.md +9 -9
- package/reglas/arreglar-al-detectar.md +240 -240
- package/reglas/auditorias-documentales-estructurales.md +7 -7
- package/reglas/cloud-infra.md +8 -8
- package/reglas/consultar-vault-primero.md +195 -195
- package/reglas/debatir-antes-de-aceptar.md +158 -158
- package/reglas/fragmentos-compartidos.md +183 -183
- package/reglas/hooks.md +6 -6
- package/reglas/intent-engineering.md +218 -218
- package/reglas/markitdown.md +8 -8
- package/reglas/monitor-ci.md +309 -309
- package/reglas/patrones.md +6 -6
- package/reglas/sesiones-paralelas.md +180 -180
- package/reglas/sin-duplicacion-reglas-globales.md +182 -182
- package/reglas/skills-estandar.md +6 -6
- package/reglas/testing.md +7 -7
- package/reglas/tests-cleanup.md +224 -224
- package/reglas/usar-code-review-graph.md +155 -155
- package/reglas/usar-context7.md +226 -226
- package/reglas/verificar-citas-normativas.md +548 -548
- package/schemas/agent-frontmatter.schema.json +3 -3
- package/schemas/agent-message.schema.json +73 -73
- package/schemas/agent-output-implementacion.schema.json +114 -114
- package/schemas/agent-output-planificacion.schema.json +150 -150
- package/schemas/agent-output-review.schema.json +98 -98
- package/schemas/diary-entry.schema.json +112 -112
- package/schemas/hook-profiles.schema.json +54 -54
- package/schemas/hooks-config.schema.json +89 -89
- package/schemas/modulos.schema.json +38 -38
- package/schemas/perfiles.schema.json +36 -36
- package/schemas/plugin.schema.json +77 -77
- package/schemas/skill-evals.schema.json +119 -119
- package/schemas/skill-frontmatter.schema.json +245 -245
- package/scripts/audit-tools/audit-history.js +330 -330
- package/scripts/audit-tools/bundle-tracker.js +290 -290
- package/scripts/audit-tools/canary-monitor.js +352 -352
- package/scripts/audit-tools/code-profiler.js +605 -605
- package/scripts/audit-tools/dep-doctor.js +320 -320
- package/scripts/audit-tools/env-validator.js +206 -206
- package/scripts/audit-tools/lib/fs-walk.js +48 -48
- package/scripts/audit-tools/lib/output.js +23 -23
- package/scripts/audit-tools/migration-checker.js +392 -392
- package/scripts/audit-tools/pentest-scanner.js +1436 -1436
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/cli/aprobar-plan.js +73 -73
- package/scripts/cli/briefing.js +23 -23
- package/scripts/cli/ciclo-evolucion.js +26 -26
- package/scripts/cli/configurar-ci.js +40 -40
- package/scripts/cli/derivar-feature-list.js +25 -25
- package/scripts/cli/detectar-host.js +27 -27
- package/scripts/cli/diary-entry.js +69 -69
- package/scripts/cli/execution-state.js +18 -18
- package/scripts/cli/gateway-notify.js +41 -41
- package/scripts/cli/liberar-fase.js +42 -42
- package/scripts/cli/loop-telemetry.js +125 -125
- package/scripts/cli/mark-evolved.js +56 -56
- package/scripts/cli/metricas-dora.js +26 -26
- package/scripts/cli/near-duplicate.js +55 -55
- package/scripts/cli/notificaciones.js +123 -123
- package/scripts/cli/propose-step.js +29 -29
- package/scripts/cli/schedule-parse.js +19 -19
- package/scripts/cli/sugerir-modelo.js +20 -20
- package/scripts/cli/verificar-plan.js +36 -36
- package/scripts/cli/verificar-trazabilidad.js +35 -35
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/auditar-invocaciones-comandos.js +104 -104
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/ci-reader.js +193 -193
- package/scripts/lib/claude-sessions.js +1 -0
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-host-swl.js +175 -175
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/detector-autoduplicacion-intra-archivo.js +234 -234
- package/scripts/lib/detector-reglas-duplicadas.js +220 -220
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/eval-metrics-store.js +218 -218
- package/scripts/lib/eval-quality.js +171 -171
- package/scripts/lib/eval-schemas.js +144 -144
- package/scripts/lib/eval-self-correct.js +106 -106
- package/scripts/lib/eval-validator.js +185 -185
- package/scripts/lib/evidencia-release.js +322 -322
- package/scripts/lib/expandir-targets.js +71 -71
- package/scripts/lib/gate-hooks-requires.js +249 -249
- package/scripts/lib/gate-licencias.js +212 -212
- package/scripts/lib/git-metricas.js +257 -257
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/metricas-dora.js +204 -204
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/plan-lock.js +275 -275
- package/scripts/lib/pr-analyzer.js +399 -399
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/reglas-globales-conocidas.json +180 -180
- package/scripts/lib/resolver-plan-fase.js +37 -37
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/schema-version.js +164 -164
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/lib/toml-merge.js +204 -204
- package/scripts/lib/tool-cost-analyzer.js +1 -0
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +200 -200
- package/scripts/publicar.js +497 -497
- package/scripts/run-eval.js +141 -141
- package/scripts/tui/componentes/selector-multi.js +189 -189
- package/scripts/tui/componentes/selector-unico.js +158 -158
- package/scripts/tui/ejecutores.js +375 -375
- package/scripts/tui/index.js +162 -162
- package/scripts/tui/lib/colores.js +129 -129
- package/scripts/tui/lib/render.js +264 -264
- package/scripts/tui/lib/teclas.js +113 -113
- package/scripts/tui/pantallas/inspect.js +173 -173
- package/scripts/tui/pantallas/install-wizard.js +334 -334
- package/scripts/tui/pantallas/menu-principal.js +52 -52
- package/scripts/tui/pantallas/progreso.js +274 -274
- package/scripts/tui/pantallas/resumen.js +132 -132
- package/scripts/tui/pantallas/uninstall-wizard.js +208 -208
- package/scripts/tui/pantallas/update-wizard.js +232 -232
- package/scripts/tui/pantallas/welcome.js +187 -187
- package/scripts/validar-userland-vacio.js +110 -110
package/scripts/lib/plan-lock.js
CHANGED
|
@@ -1,275 +1,275 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* plan-lock.js — Firma y verificación de integridad de PLAN.md (Gate G1).
|
|
5
|
-
*
|
|
6
|
-
* Materializa el gate G1 del enforcement SDD (Fase 09, ADR de la Revisión
|
|
7
|
-
* Evolutiva 03): un PLAN aprobado vía `/swl:aprobar-plan` queda firmado con
|
|
8
|
-
* un SHA256 escrito en `.planning/locks/<basename>.lock`. `ejecutar-fase`
|
|
9
|
-
* recomputa ese hash al arrancar; un plan mutado tras la aprobación detiene
|
|
10
|
-
* la ejecución. Cierra el hueco "el plan puede mutar tras aprobación" que
|
|
11
|
-
* dejaba SDD en ~60%.
|
|
12
|
-
*
|
|
13
|
-
* Cláusula de gracia D-05 (compatibilidad legacy): un PLAN con
|
|
14
|
-
* `estado: aprobado` en su frontmatter pero SIN `.lock` correspondiente
|
|
15
|
-
* (los planes creados antes de la Fase 09) se considera `modo: "legacy"` y
|
|
16
|
-
* se ejecuta con advertencia, sin bloqueo. Solo los planes firmados vía
|
|
17
|
-
* `/swl:aprobar-plan` (post-Fase 09) exigen coincidencia de hash.
|
|
18
|
-
*
|
|
19
|
-
* MODELO DE AMENAZA (gate G1): el lock detecta MUTACIÓN ACCIDENTAL o no
|
|
20
|
-
* aprobada del PLAN tras su firma, y planes alterados por un agente que
|
|
21
|
-
* confabula. NO es un control criptográfico contra un adversario con acceso
|
|
22
|
-
* de escritura al repo — quien pueda commitear puede recalcular el SHA256 y
|
|
23
|
-
* re-firmar. La evidencia real de aprobación es `aprobadoPor:` + el commit del
|
|
24
|
-
* lock por `/swl:aprobar-plan` en el audit trail de git. El SHA256 solo detecta
|
|
25
|
-
* drift entre firma y verificación. (Verificación Fase 09, hallazgo S-6.)
|
|
26
|
-
*
|
|
27
|
-
* API:
|
|
28
|
-
* firmarPlan(planPath, opciones?) -> { ok, sha256, lockPath, archivo }
|
|
29
|
-
* verificarPlan(planPath) -> { ok, modo, motivo, ... }
|
|
30
|
-
* resolverLockPath(planPath) -> string (ruta del .lock esperado)
|
|
31
|
-
*
|
|
32
|
-
* Zero-dependencies. Compatible Windows (hash sobre buffer crudo). Node 18+.
|
|
33
|
-
*
|
|
34
|
-
* Origen: Fase 09 — enforcement SDD/TDD vNext H1, Slice 1.
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
const fs = require('fs');
|
|
38
|
-
const path = require('path');
|
|
39
|
-
const crypto = require('crypto');
|
|
40
|
-
|
|
41
|
-
// Escritura atómica obligatoria (regla CLAUDE.md). Fallback defensivo idéntico
|
|
42
|
-
// al de scripts/generar-skills-lock.js para no acoplar a un layout fijo.
|
|
43
|
-
let atomicWriteSync;
|
|
44
|
-
try {
|
|
45
|
-
({ atomicWriteSync } = require(path.join(__dirname, '..', '..', 'hooks', 'lib', 'atomic-write.js')));
|
|
46
|
-
} catch {
|
|
47
|
-
atomicWriteSync = (p, c, e) => {
|
|
48
|
-
const dir = path.dirname(p);
|
|
49
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
50
|
-
fs.writeFileSync(p, c, e);
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const LOCK_VERSION = 1;
|
|
55
|
-
|
|
56
|
-
// Basename válido de un PLAN de fase: `PLAN.md` o `0N-PLAN.md`. Evita que
|
|
57
|
-
// `firmarPlan` produzca locks de archivos arbitrarios (falsearía el audit
|
|
58
|
-
// trail de aprobación). Verificación Fase 09, hallazgo S-2.
|
|
59
|
-
const PLAN_BASENAME_RE = /^(\d{2}-)?PLAN\.md$/;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* SHA256 de un buffer. Idéntico a scripts/generar-skills-lock.js:42-44.
|
|
63
|
-
* Se hashea el buffer crudo (no string utf8) para que el hash sea byte-exacto
|
|
64
|
-
* y detecte incluso cambios de fin de línea introducidos por un editor.
|
|
65
|
-
*
|
|
66
|
-
* @param {Buffer|string} data
|
|
67
|
-
* @returns {string} hex digest
|
|
68
|
-
*/
|
|
69
|
-
function sha256(data) {
|
|
70
|
-
return crypto.createHash('sha256').update(data).digest('hex');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Extrae el campo `estado:` del frontmatter YAML del PLAN.
|
|
75
|
-
* Reutiliza el patrón CRLF-safe de scripts/generar-skills-lock.js:46-55.
|
|
76
|
-
*
|
|
77
|
-
* @param {string} contenido
|
|
78
|
-
* @returns {string|null} valor de `estado` o null si no hay frontmatter/campo
|
|
79
|
-
*/
|
|
80
|
-
function leerEstado(contenido) {
|
|
81
|
-
const m = contenido.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
82
|
-
if (!m) return null;
|
|
83
|
-
for (const linea of m[1].split(/\r?\n/)) {
|
|
84
|
-
const kv = linea.match(/^estado:\s*(.*)$/);
|
|
85
|
-
if (kv) return kv[1].trim();
|
|
86
|
-
}
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Resuelve el directorio `.planning/locks/` ascendiendo desde el PLAN hasta
|
|
92
|
-
* encontrar el directorio `.planning` ancestro. Robusto ante planes en
|
|
93
|
-
* `.planning/fases/0N-PLAN.md` o `.planning/PLAN.md`.
|
|
94
|
-
*
|
|
95
|
-
* Hardening S-1 (verificación Fase 09):
|
|
96
|
-
* - Resuelve symlinks con `fs.realpathSync` antes de ascender, para que un
|
|
97
|
-
* symlink plantado (`temp/.planning` → `/etc/.planning`) no permita escapar
|
|
98
|
-
* del árbol real del proyecto.
|
|
99
|
-
* - SIN fallback ciego: si no hay `.planning` ancestro, retorna `null` (antes
|
|
100
|
-
* escribía `locks/` al lado del PLAN, en ubicación arbitraria). El caller
|
|
101
|
-
* trata `null` como error explícito.
|
|
102
|
-
*
|
|
103
|
-
* @param {string} planPath
|
|
104
|
-
* @returns {string|null} ruta absoluta del directorio de locks, o null si no
|
|
105
|
-
* hay `.planning` ancestro.
|
|
106
|
-
*/
|
|
107
|
-
function resolverLocksDir(planPath) {
|
|
108
|
-
let dir;
|
|
109
|
-
try {
|
|
110
|
-
dir = path.dirname(fs.realpathSync(path.resolve(planPath)));
|
|
111
|
-
} catch {
|
|
112
|
-
dir = path.dirname(path.resolve(planPath));
|
|
113
|
-
}
|
|
114
|
-
while (dir !== path.dirname(dir)) {
|
|
115
|
-
if (path.basename(dir) === '.planning') {
|
|
116
|
-
return path.join(dir, 'locks');
|
|
117
|
-
}
|
|
118
|
-
dir = path.dirname(dir);
|
|
119
|
-
}
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Ruta del `.lock` esperado para un PLAN dado.
|
|
125
|
-
*
|
|
126
|
-
* @param {string} planPath
|
|
127
|
-
* @returns {string|null} ruta absoluta del archivo `.lock`, o null si el PLAN
|
|
128
|
-
* no tiene un `.planning` ancestro.
|
|
129
|
-
*/
|
|
130
|
-
function resolverLockPath(planPath) {
|
|
131
|
-
const dir = resolverLocksDir(planPath);
|
|
132
|
-
return dir ? path.join(dir, `${path.basename(planPath)}.lock`) : null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Firma un PLAN: computa su SHA256 y escribe el `.lock` correspondiente.
|
|
137
|
-
* Idempotente — re-firmar sobreescribe el lock (caso re-aprobación).
|
|
138
|
-
*
|
|
139
|
-
* @param {string} planPath
|
|
140
|
-
* @param {object} [opciones]
|
|
141
|
-
* @param {string} [opciones.firmadoPor='swl:aprobar-plan']
|
|
142
|
-
* @param {string} [opciones.firmadoEn] ISO timestamp; default new Date()
|
|
143
|
-
* @returns {{ok: boolean, sha256?: string, lockPath?: string, archivo?: string, motivo?: string}}
|
|
144
|
-
*/
|
|
145
|
-
function firmarPlan(planPath, opciones = {}) {
|
|
146
|
-
if (!fs.existsSync(planPath)) {
|
|
147
|
-
return { ok: false, motivo: `PLAN no existe: ${planPath}` };
|
|
148
|
-
}
|
|
149
|
-
const archivo = path.basename(planPath);
|
|
150
|
-
if (!PLAN_BASENAME_RE.test(archivo)) {
|
|
151
|
-
return {
|
|
152
|
-
ok: false,
|
|
153
|
-
motivo: `basename inválido para firma: "${archivo}". Solo se firman PLAN.md o 0N-PLAN.md de fases.`,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
const lockPath = resolverLockPath(planPath);
|
|
157
|
-
if (!lockPath) {
|
|
158
|
-
return {
|
|
159
|
-
ok: false,
|
|
160
|
-
motivo: `el PLAN no tiene un directorio .planning ancestro: ${planPath}`,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
const buffer = fs.readFileSync(planPath);
|
|
164
|
-
const hash = sha256(buffer);
|
|
165
|
-
|
|
166
|
-
const lock = {
|
|
167
|
-
version: LOCK_VERSION,
|
|
168
|
-
archivo,
|
|
169
|
-
sha256: hash,
|
|
170
|
-
firmadoEn: opciones.firmadoEn || new Date().toISOString(),
|
|
171
|
-
firmadoPor: opciones.firmadoPor || 'swl:aprobar-plan',
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
atomicWriteSync(lockPath, `${JSON.stringify(lock, null, 2)}\n`, 'utf8');
|
|
175
|
-
return { ok: true, sha256: hash, lockPath, archivo };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Verifica la integridad de un PLAN contra su `.lock`.
|
|
180
|
-
*
|
|
181
|
-
* Modos de retorno:
|
|
182
|
-
* - `firmado` : existe lock y el hash coincide -> ok: true
|
|
183
|
-
* - `legacy` : NO existe lock pero estado:aprobado (gracia D-05) -> ok: true
|
|
184
|
-
* - `mutado` : existe lock y el hash NO coincide -> ok: false
|
|
185
|
-
* - `lock-corrupto`: el lock existe pero no es JSON válido -> ok: false
|
|
186
|
-
* - `sin-firmar` : NO existe lock y estado != aprobado -> ok: false
|
|
187
|
-
* - `sin-planning`: el PLAN no tiene `.planning` ancestro -> ok: false
|
|
188
|
-
* - `no-existe` : el PLAN no existe en disco -> ok: false
|
|
189
|
-
*
|
|
190
|
-
* @param {string} planPath
|
|
191
|
-
* @returns {{ok: boolean, modo: string, motivo?: string, hashEsperado?: string, hashActual?: string, lockPath?: string}}
|
|
192
|
-
*/
|
|
193
|
-
function verificarPlan(planPath) {
|
|
194
|
-
if (!fs.existsSync(planPath)) {
|
|
195
|
-
return { ok: false, modo: 'no-existe', motivo: `PLAN no existe: ${planPath}` };
|
|
196
|
-
}
|
|
197
|
-
const lockPath = resolverLockPath(planPath);
|
|
198
|
-
if (!lockPath) {
|
|
199
|
-
return {
|
|
200
|
-
ok: false,
|
|
201
|
-
modo: 'sin-planning',
|
|
202
|
-
motivo: `el PLAN no tiene un directorio .planning ancestro: ${planPath}`,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
const buffer = fs.readFileSync(planPath);
|
|
206
|
-
const hashActual = sha256(buffer);
|
|
207
|
-
|
|
208
|
-
if (!fs.existsSync(lockPath)) {
|
|
209
|
-
// Cláusula de gracia D-05: plan aprobado antes de que existiera el lock.
|
|
210
|
-
const estado = leerEstado(buffer.toString('utf8'));
|
|
211
|
-
if (estado === 'aprobado') {
|
|
212
|
-
return {
|
|
213
|
-
ok: true,
|
|
214
|
-
modo: 'legacy',
|
|
215
|
-
motivo: 'plan aprobado pre-Fase09 (sin lock); ejecutar con advertencia',
|
|
216
|
-
hashActual,
|
|
217
|
-
lockPath,
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
return {
|
|
221
|
-
ok: false,
|
|
222
|
-
modo: 'sin-firmar',
|
|
223
|
-
motivo: `plan sin aprobar y sin lock (estado: ${estado || 'ausente'})`,
|
|
224
|
-
hashActual,
|
|
225
|
-
lockPath,
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
let lock;
|
|
230
|
-
try {
|
|
231
|
-
lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
232
|
-
} catch (err) {
|
|
233
|
-
return {
|
|
234
|
-
ok: false,
|
|
235
|
-
modo: 'lock-corrupto',
|
|
236
|
-
motivo: `lock malformado en ${lockPath}: ${err.message}`,
|
|
237
|
-
hashActual,
|
|
238
|
-
lockPath,
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const hashEsperado = lock && typeof lock.sha256 === 'string' ? lock.sha256 : null;
|
|
243
|
-
if (!hashEsperado) {
|
|
244
|
-
return {
|
|
245
|
-
ok: false,
|
|
246
|
-
modo: 'lock-corrupto',
|
|
247
|
-
motivo: `lock sin campo sha256 en ${lockPath}`,
|
|
248
|
-
hashActual,
|
|
249
|
-
lockPath,
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (hashEsperado === hashActual) {
|
|
254
|
-
return { ok: true, modo: 'firmado', hashEsperado, hashActual, lockPath };
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return {
|
|
258
|
-
ok: false,
|
|
259
|
-
modo: 'mutado',
|
|
260
|
-
motivo: 'plan mutado tras aprobación (el hash no coincide con el lock)',
|
|
261
|
-
hashEsperado,
|
|
262
|
-
hashActual,
|
|
263
|
-
lockPath,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
module.exports = {
|
|
268
|
-
firmarPlan,
|
|
269
|
-
verificarPlan,
|
|
270
|
-
resolverLockPath,
|
|
271
|
-
resolverLocksDir,
|
|
272
|
-
sha256,
|
|
273
|
-
leerEstado,
|
|
274
|
-
LOCK_VERSION,
|
|
275
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* plan-lock.js — Firma y verificación de integridad de PLAN.md (Gate G1).
|
|
5
|
+
*
|
|
6
|
+
* Materializa el gate G1 del enforcement SDD (Fase 09, ADR de la Revisión
|
|
7
|
+
* Evolutiva 03): un PLAN aprobado vía `/swl:aprobar-plan` queda firmado con
|
|
8
|
+
* un SHA256 escrito en `.planning/locks/<basename>.lock`. `ejecutar-fase`
|
|
9
|
+
* recomputa ese hash al arrancar; un plan mutado tras la aprobación detiene
|
|
10
|
+
* la ejecución. Cierra el hueco "el plan puede mutar tras aprobación" que
|
|
11
|
+
* dejaba SDD en ~60%.
|
|
12
|
+
*
|
|
13
|
+
* Cláusula de gracia D-05 (compatibilidad legacy): un PLAN con
|
|
14
|
+
* `estado: aprobado` en su frontmatter pero SIN `.lock` correspondiente
|
|
15
|
+
* (los planes creados antes de la Fase 09) se considera `modo: "legacy"` y
|
|
16
|
+
* se ejecuta con advertencia, sin bloqueo. Solo los planes firmados vía
|
|
17
|
+
* `/swl:aprobar-plan` (post-Fase 09) exigen coincidencia de hash.
|
|
18
|
+
*
|
|
19
|
+
* MODELO DE AMENAZA (gate G1): el lock detecta MUTACIÓN ACCIDENTAL o no
|
|
20
|
+
* aprobada del PLAN tras su firma, y planes alterados por un agente que
|
|
21
|
+
* confabula. NO es un control criptográfico contra un adversario con acceso
|
|
22
|
+
* de escritura al repo — quien pueda commitear puede recalcular el SHA256 y
|
|
23
|
+
* re-firmar. La evidencia real de aprobación es `aprobadoPor:` + el commit del
|
|
24
|
+
* lock por `/swl:aprobar-plan` en el audit trail de git. El SHA256 solo detecta
|
|
25
|
+
* drift entre firma y verificación. (Verificación Fase 09, hallazgo S-6.)
|
|
26
|
+
*
|
|
27
|
+
* API:
|
|
28
|
+
* firmarPlan(planPath, opciones?) -> { ok, sha256, lockPath, archivo }
|
|
29
|
+
* verificarPlan(planPath) -> { ok, modo, motivo, ... }
|
|
30
|
+
* resolverLockPath(planPath) -> string (ruta del .lock esperado)
|
|
31
|
+
*
|
|
32
|
+
* Zero-dependencies. Compatible Windows (hash sobre buffer crudo). Node 18+.
|
|
33
|
+
*
|
|
34
|
+
* Origen: Fase 09 — enforcement SDD/TDD vNext H1, Slice 1.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const fs = require('fs');
|
|
38
|
+
const path = require('path');
|
|
39
|
+
const crypto = require('crypto');
|
|
40
|
+
|
|
41
|
+
// Escritura atómica obligatoria (regla CLAUDE.md). Fallback defensivo idéntico
|
|
42
|
+
// al de scripts/generar-skills-lock.js para no acoplar a un layout fijo.
|
|
43
|
+
let atomicWriteSync;
|
|
44
|
+
try {
|
|
45
|
+
({ atomicWriteSync } = require(path.join(__dirname, '..', '..', 'hooks', 'lib', 'atomic-write.js')));
|
|
46
|
+
} catch {
|
|
47
|
+
atomicWriteSync = (p, c, e) => {
|
|
48
|
+
const dir = path.dirname(p);
|
|
49
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
50
|
+
fs.writeFileSync(p, c, e);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const LOCK_VERSION = 1;
|
|
55
|
+
|
|
56
|
+
// Basename válido de un PLAN de fase: `PLAN.md` o `0N-PLAN.md`. Evita que
|
|
57
|
+
// `firmarPlan` produzca locks de archivos arbitrarios (falsearía el audit
|
|
58
|
+
// trail de aprobación). Verificación Fase 09, hallazgo S-2.
|
|
59
|
+
const PLAN_BASENAME_RE = /^(\d{2}-)?PLAN\.md$/;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* SHA256 de un buffer. Idéntico a scripts/generar-skills-lock.js:42-44.
|
|
63
|
+
* Se hashea el buffer crudo (no string utf8) para que el hash sea byte-exacto
|
|
64
|
+
* y detecte incluso cambios de fin de línea introducidos por un editor.
|
|
65
|
+
*
|
|
66
|
+
* @param {Buffer|string} data
|
|
67
|
+
* @returns {string} hex digest
|
|
68
|
+
*/
|
|
69
|
+
function sha256(data) {
|
|
70
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extrae el campo `estado:` del frontmatter YAML del PLAN.
|
|
75
|
+
* Reutiliza el patrón CRLF-safe de scripts/generar-skills-lock.js:46-55.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} contenido
|
|
78
|
+
* @returns {string|null} valor de `estado` o null si no hay frontmatter/campo
|
|
79
|
+
*/
|
|
80
|
+
function leerEstado(contenido) {
|
|
81
|
+
const m = contenido.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
82
|
+
if (!m) return null;
|
|
83
|
+
for (const linea of m[1].split(/\r?\n/)) {
|
|
84
|
+
const kv = linea.match(/^estado:\s*(.*)$/);
|
|
85
|
+
if (kv) return kv[1].trim();
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Resuelve el directorio `.planning/locks/` ascendiendo desde el PLAN hasta
|
|
92
|
+
* encontrar el directorio `.planning` ancestro. Robusto ante planes en
|
|
93
|
+
* `.planning/fases/0N-PLAN.md` o `.planning/PLAN.md`.
|
|
94
|
+
*
|
|
95
|
+
* Hardening S-1 (verificación Fase 09):
|
|
96
|
+
* - Resuelve symlinks con `fs.realpathSync` antes de ascender, para que un
|
|
97
|
+
* symlink plantado (`temp/.planning` → `/etc/.planning`) no permita escapar
|
|
98
|
+
* del árbol real del proyecto.
|
|
99
|
+
* - SIN fallback ciego: si no hay `.planning` ancestro, retorna `null` (antes
|
|
100
|
+
* escribía `locks/` al lado del PLAN, en ubicación arbitraria). El caller
|
|
101
|
+
* trata `null` como error explícito.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} planPath
|
|
104
|
+
* @returns {string|null} ruta absoluta del directorio de locks, o null si no
|
|
105
|
+
* hay `.planning` ancestro.
|
|
106
|
+
*/
|
|
107
|
+
function resolverLocksDir(planPath) {
|
|
108
|
+
let dir;
|
|
109
|
+
try {
|
|
110
|
+
dir = path.dirname(fs.realpathSync(path.resolve(planPath)));
|
|
111
|
+
} catch {
|
|
112
|
+
dir = path.dirname(path.resolve(planPath));
|
|
113
|
+
}
|
|
114
|
+
while (dir !== path.dirname(dir)) {
|
|
115
|
+
if (path.basename(dir) === '.planning') {
|
|
116
|
+
return path.join(dir, 'locks');
|
|
117
|
+
}
|
|
118
|
+
dir = path.dirname(dir);
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Ruta del `.lock` esperado para un PLAN dado.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} planPath
|
|
127
|
+
* @returns {string|null} ruta absoluta del archivo `.lock`, o null si el PLAN
|
|
128
|
+
* no tiene un `.planning` ancestro.
|
|
129
|
+
*/
|
|
130
|
+
function resolverLockPath(planPath) {
|
|
131
|
+
const dir = resolverLocksDir(planPath);
|
|
132
|
+
return dir ? path.join(dir, `${path.basename(planPath)}.lock`) : null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Firma un PLAN: computa su SHA256 y escribe el `.lock` correspondiente.
|
|
137
|
+
* Idempotente — re-firmar sobreescribe el lock (caso re-aprobación).
|
|
138
|
+
*
|
|
139
|
+
* @param {string} planPath
|
|
140
|
+
* @param {object} [opciones]
|
|
141
|
+
* @param {string} [opciones.firmadoPor='swl:aprobar-plan']
|
|
142
|
+
* @param {string} [opciones.firmadoEn] ISO timestamp; default new Date()
|
|
143
|
+
* @returns {{ok: boolean, sha256?: string, lockPath?: string, archivo?: string, motivo?: string}}
|
|
144
|
+
*/
|
|
145
|
+
function firmarPlan(planPath, opciones = {}) {
|
|
146
|
+
if (!fs.existsSync(planPath)) {
|
|
147
|
+
return { ok: false, motivo: `PLAN no existe: ${planPath}` };
|
|
148
|
+
}
|
|
149
|
+
const archivo = path.basename(planPath);
|
|
150
|
+
if (!PLAN_BASENAME_RE.test(archivo)) {
|
|
151
|
+
return {
|
|
152
|
+
ok: false,
|
|
153
|
+
motivo: `basename inválido para firma: "${archivo}". Solo se firman PLAN.md o 0N-PLAN.md de fases.`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const lockPath = resolverLockPath(planPath);
|
|
157
|
+
if (!lockPath) {
|
|
158
|
+
return {
|
|
159
|
+
ok: false,
|
|
160
|
+
motivo: `el PLAN no tiene un directorio .planning ancestro: ${planPath}`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const buffer = fs.readFileSync(planPath);
|
|
164
|
+
const hash = sha256(buffer);
|
|
165
|
+
|
|
166
|
+
const lock = {
|
|
167
|
+
version: LOCK_VERSION,
|
|
168
|
+
archivo,
|
|
169
|
+
sha256: hash,
|
|
170
|
+
firmadoEn: opciones.firmadoEn || new Date().toISOString(),
|
|
171
|
+
firmadoPor: opciones.firmadoPor || 'swl:aprobar-plan',
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
atomicWriteSync(lockPath, `${JSON.stringify(lock, null, 2)}\n`, 'utf8');
|
|
175
|
+
return { ok: true, sha256: hash, lockPath, archivo };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Verifica la integridad de un PLAN contra su `.lock`.
|
|
180
|
+
*
|
|
181
|
+
* Modos de retorno:
|
|
182
|
+
* - `firmado` : existe lock y el hash coincide -> ok: true
|
|
183
|
+
* - `legacy` : NO existe lock pero estado:aprobado (gracia D-05) -> ok: true
|
|
184
|
+
* - `mutado` : existe lock y el hash NO coincide -> ok: false
|
|
185
|
+
* - `lock-corrupto`: el lock existe pero no es JSON válido -> ok: false
|
|
186
|
+
* - `sin-firmar` : NO existe lock y estado != aprobado -> ok: false
|
|
187
|
+
* - `sin-planning`: el PLAN no tiene `.planning` ancestro -> ok: false
|
|
188
|
+
* - `no-existe` : el PLAN no existe en disco -> ok: false
|
|
189
|
+
*
|
|
190
|
+
* @param {string} planPath
|
|
191
|
+
* @returns {{ok: boolean, modo: string, motivo?: string, hashEsperado?: string, hashActual?: string, lockPath?: string}}
|
|
192
|
+
*/
|
|
193
|
+
function verificarPlan(planPath) {
|
|
194
|
+
if (!fs.existsSync(planPath)) {
|
|
195
|
+
return { ok: false, modo: 'no-existe', motivo: `PLAN no existe: ${planPath}` };
|
|
196
|
+
}
|
|
197
|
+
const lockPath = resolverLockPath(planPath);
|
|
198
|
+
if (!lockPath) {
|
|
199
|
+
return {
|
|
200
|
+
ok: false,
|
|
201
|
+
modo: 'sin-planning',
|
|
202
|
+
motivo: `el PLAN no tiene un directorio .planning ancestro: ${planPath}`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const buffer = fs.readFileSync(planPath);
|
|
206
|
+
const hashActual = sha256(buffer);
|
|
207
|
+
|
|
208
|
+
if (!fs.existsSync(lockPath)) {
|
|
209
|
+
// Cláusula de gracia D-05: plan aprobado antes de que existiera el lock.
|
|
210
|
+
const estado = leerEstado(buffer.toString('utf8'));
|
|
211
|
+
if (estado === 'aprobado') {
|
|
212
|
+
return {
|
|
213
|
+
ok: true,
|
|
214
|
+
modo: 'legacy',
|
|
215
|
+
motivo: 'plan aprobado pre-Fase09 (sin lock); ejecutar con advertencia',
|
|
216
|
+
hashActual,
|
|
217
|
+
lockPath,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
ok: false,
|
|
222
|
+
modo: 'sin-firmar',
|
|
223
|
+
motivo: `plan sin aprobar y sin lock (estado: ${estado || 'ausente'})`,
|
|
224
|
+
hashActual,
|
|
225
|
+
lockPath,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let lock;
|
|
230
|
+
try {
|
|
231
|
+
lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
232
|
+
} catch (err) {
|
|
233
|
+
return {
|
|
234
|
+
ok: false,
|
|
235
|
+
modo: 'lock-corrupto',
|
|
236
|
+
motivo: `lock malformado en ${lockPath}: ${err.message}`,
|
|
237
|
+
hashActual,
|
|
238
|
+
lockPath,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const hashEsperado = lock && typeof lock.sha256 === 'string' ? lock.sha256 : null;
|
|
243
|
+
if (!hashEsperado) {
|
|
244
|
+
return {
|
|
245
|
+
ok: false,
|
|
246
|
+
modo: 'lock-corrupto',
|
|
247
|
+
motivo: `lock sin campo sha256 en ${lockPath}`,
|
|
248
|
+
hashActual,
|
|
249
|
+
lockPath,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (hashEsperado === hashActual) {
|
|
254
|
+
return { ok: true, modo: 'firmado', hashEsperado, hashActual, lockPath };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
ok: false,
|
|
259
|
+
modo: 'mutado',
|
|
260
|
+
motivo: 'plan mutado tras aprobación (el hash no coincide con el lock)',
|
|
261
|
+
hashEsperado,
|
|
262
|
+
hashActual,
|
|
263
|
+
lockPath,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = {
|
|
268
|
+
firmarPlan,
|
|
269
|
+
verificarPlan,
|
|
270
|
+
resolverLockPath,
|
|
271
|
+
resolverLocksDir,
|
|
272
|
+
sha256,
|
|
273
|
+
leerEstado,
|
|
274
|
+
LOCK_VERSION,
|
|
275
|
+
};
|