@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.
- package/CLAUDE.md +14 -2
- package/README.md +65 -18
- package/agentes/_intent-spec.md +73 -73
- package/agentes/_propose-step.md +90 -90
- package/bin/swl-ses.js +10 -0
- package/comandos/swl/brainstorm.md +1 -0
- package/comandos/swl/briefing.md +119 -119
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/deuda-codigo.md +97 -97
- package/comandos/swl/mcp-status.md +1 -0
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/agent-deep-links/SKILL.md +148 -148
- package/habilidades/backend-async-postgres-testing/SKILL.md +216 -216
- package/habilidades/backend-error-design/SKILL.md +221 -221
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/calidad-anti-patrones-universales/SKILL.md +105 -1
- package/habilidades/calidad-contract-testing/SKILL.md +165 -165
- package/habilidades/calidad-mutation-testing/SKILL.md +25 -1
- package/habilidades/checklist-seguridad/recursos/stride-cobertura.md +60 -60
- package/habilidades/ci-cd-pipelines/SKILL.md +5 -1
- package/habilidades/css-moderno/SKILL.md +7 -1
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +57 -57
- package/habilidades/extractor-de-aprendizajes/SKILL.md +5 -1
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
- package/habilidades/harness-claude-code/SKILL.md +3 -2
- 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/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -166
- package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -85
- package/habilidades/proceso-ddia-streaming/SKILL.md +231 -231
- package/habilidades/proceso-discovery-machote/SKILL.md +157 -157
- package/habilidades/proceso-dynamic-workflows/SKILL.md +60 -0
- 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/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
- 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/tdd-workflow/recursos/gherkin-bdd.md +111 -111
- package/hooks/calidad-pre-commit.js +159 -10
- package/hooks/ciclo-evolucion-subagente.js +26 -26
- package/hooks/ciclo-evolucion.js +26 -26
- package/hooks/contexto-subagente.js +68 -68
- package/hooks/lib/auto-consolidator.js +335 -335
- 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/notificacion-formato.js +45 -11
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/raiz-proyecto.js +35 -4
- 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/toon-compressor.js +245 -245
- package/hooks/notificacion-telegram.js +5 -11
- package/hooks/session-briefing.js +12 -4
- package/instintos/autonomia.yaml +27 -27
- package/instintos/prompt-appendices.yaml +57 -57
- package/llms.txt +1 -1
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/canonical-hashes.json +662 -0
- package/manifiestos/harness-ir.json +47536 -0
- package/manifiestos/hooks-config.json +469 -469
- package/manifiestos/invariantes-criticos.json +30 -30
- package/manifiestos/policy-bundle.json +2065 -0
- package/manifiestos/policy-corpus-w2.json +3926 -0
- package/manifiestos/runtime-adapters-core3.json +208 -0
- package/manifiestos/runtime-conformance.json +139 -0
- package/manifiestos/skills-lock.json +43 -43
- package/package.json +2 -2
- package/plantillas/auditor-veto-template.md +105 -105
- 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 +2 -2
- package/reglas/accesibilidad.md +10 -10
- 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/git-workflow.md +1 -0
- package/reglas/hooks.md +6 -6
- package/reglas/intent-engineering.md +218 -218
- package/reglas/markitdown.md +8 -8
- package/reglas/monitor-ci.md +12 -0
- package/reglas/patrones.md +6 -6
- package/reglas/testing.md +7 -7
- package/reglas/tests-cleanup.md +224 -224
- 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/gate-state.schema.json +76 -0
- package/schemas/harness-ir.schema.json +369 -0
- package/schemas/hook-profiles.schema.json +54 -54
- package/schemas/hooks-config.schema.json +89 -89
- package/schemas/legacy-gates.schema.json +45 -0
- package/schemas/modulos.schema.json +38 -38
- package/schemas/perfiles.schema.json +36 -36
- package/schemas/plugin.schema.json +77 -77
- package/schemas/policy-bundle.schema.json +140 -0
- package/schemas/policy-enforcement.schema.json +117 -0
- package/schemas/policy-operation.schema.json +261 -0
- package/schemas/runtime-adapter.schema.json +176 -0
- package/schemas/runtime-build-attestation.schema.json +100 -0
- package/schemas/runtime-conformance.schema.json +239 -0
- package/schemas/runtime-diagnostic.schema.json +395 -0
- package/schemas/skill-evals.schema.json +119 -119
- package/schemas/skill-frontmatter.schema.json +245 -245
- package/schemas/w4-certification-request.schema.json +72 -0
- package/schemas/w4-certification-verdict.schema.json +224 -0
- package/schemas/w4-corpus.schema.json +172 -0
- package/schemas/w4-mutation-report.schema.json +116 -0
- package/schemas/w4-replay-result.schema.json +164 -0
- package/schemas/w4-scoring-report.schema.json +89 -0
- 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/bootstrap-instintos.js +3 -0
- 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/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/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/comandos/install-asistido.js +8 -7
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/doctor.js +61 -36
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-claims-runtime.js +1342 -0
- package/scripts/generar-harness-ir.js +257 -0
- package/scripts/generar-inventario.js +52 -54
- package/scripts/generar-policy-bundle.js +202 -0
- package/scripts/instalador.js +26 -7
- package/scripts/lib/approval-receipts.js +190 -0
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/certificacion-loop-state.js +421 -0
- package/scripts/lib/ci-reader.js +193 -193
- package/scripts/lib/ciclo-autonomo/yaml-instintos.js +56 -0
- package/scripts/lib/clasificar-directorio.js +92 -0
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-host-swl.js +175 -175
- package/scripts/lib/detectar-runtime.js +29 -20
- 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/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/evidence-verifier.js +192 -0
- package/scripts/lib/evidencia-release.js +322 -322
- package/scripts/lib/frontmatter-canonico.js +509 -0
- package/scripts/lib/gate-engine.js +871 -0
- package/scripts/lib/gate-hooks-requires.js +249 -249
- package/scripts/lib/gate-licencias.js +212 -212
- package/scripts/lib/git-config-preflight.js +48 -0
- package/scripts/lib/git-metricas.js +257 -257
- package/scripts/lib/harness-ir.js +778 -0
- package/scripts/lib/harness-source-snapshot.js +309 -0
- package/scripts/lib/integrity-ledger.js +1147 -0
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/legacy-gate-migration.js +324 -0
- package/scripts/lib/limpiar-basura-global.js +45 -2
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/metricas-dora.js +204 -204
- package/scripts/lib/notificaciones-telegram.js +1 -0
- package/scripts/lib/npm-version.js +1 -0
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/plan-lock.js +61 -13
- package/scripts/lib/policy-broker.js +338 -0
- package/scripts/lib/policy-bundle.js +342 -0
- package/scripts/lib/policy-context-provider.js +310 -0
- package/scripts/lib/policy-contract.js +479 -0
- package/scripts/lib/policy-verifier-utils.js +65 -0
- package/scripts/lib/pr-analyzer.js +399 -399
- package/scripts/lib/principal-verifier.js +178 -0
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/resolver-plan-fase.js +37 -37
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/runtime-adapter-contract.js +267 -0
- package/scripts/lib/runtime-artifact-verifier.js +426 -0
- package/scripts/lib/runtime-build-attestation.js +127 -0
- package/scripts/lib/runtime-bundle-installer.js +586 -0
- package/scripts/lib/runtime-compiler.js +327 -0
- package/scripts/lib/runtime-conformance.js +202 -0
- package/scripts/lib/runtime-doctor-core3.js +567 -0
- package/scripts/lib/runtime-doctor-input.js +59 -0
- package/scripts/lib/runtime-operation-adapter.js +267 -0
- package/scripts/lib/schema-version.js +164 -164
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/lib/signed-envelope.js +545 -0
- package/scripts/lib/single-use-store.js +359 -0
- package/scripts/lib/skills-externas.js +31 -0
- package/scripts/lib/transformadores/codex.js +15 -8
- package/scripts/lib/transformadores/gemini.js +79 -5
- package/scripts/lib/w4-attestation-adapter.js +158 -0
- package/scripts/lib/w4-canario.js +337 -0
- package/scripts/lib/w4-claims.js +182 -0
- package/scripts/lib/w4-corpus-generador.js +542 -0
- package/scripts/lib/w4-gate-c5.js +115 -0
- package/scripts/lib/w4-harness-bajo-prueba.js +155 -0
- package/scripts/lib/w4-matriz-combos.js +55 -0
- package/scripts/lib/w4-motor-mutacion.js +1348 -0
- package/scripts/lib/w4-motor-replay.js +735 -0
- package/scripts/lib/w4-pin-origen.js +54 -0
- package/scripts/lib/w4-publicar-request.js +132 -0
- package/scripts/lib/w4-revocacion.js +62 -0
- package/scripts/lib/w4-runtimes-core3.js +38 -0
- package/scripts/lib/w4-scorer-certificacion.js +692 -0
- package/scripts/lib/w4-superficie-candidato.js +49 -0
- package/scripts/lib/w4-veredicto.js +452 -0
- package/scripts/lib/w4-verificar-veredicto.js +302 -0
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +200 -200
- package/scripts/migrar-gates-legacy.js +108 -0
- package/scripts/publicar-certification-request.js +115 -0
- package/scripts/runtime-doctor.js +107 -0
- 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/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/install-wizard.js +12 -7
- 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/validar-userland-vacio.js +110 -110
- package/scripts/verificar-aislamiento-swl-eval.js +87 -0
- package/scripts/verificar-empaquetado-downstream.js +375 -0
- package/scripts/verificar-loop-constructor.js +215 -0
- package/scripts/verificar-trazabilidad.js +13 -6
- package/scripts/verificar-veredicto-real.js +84 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/** Diagnóstico estático y probes acotados de runtimes Core-3. */
|
|
4
|
+
|
|
5
|
+
const { spawn } = require('node:child_process');
|
|
6
|
+
const fs = require('node:fs');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const util = require('node:util');
|
|
9
|
+
|
|
10
|
+
const { cargarContratoAdapters } = require('./runtime-adapter-contract');
|
|
11
|
+
const { verificarLocator } = require('./runtime-artifact-verifier');
|
|
12
|
+
const {
|
|
13
|
+
canonicalizarIJson,
|
|
14
|
+
validarTimestampIso,
|
|
15
|
+
} = require('./signed-envelope');
|
|
16
|
+
|
|
17
|
+
const CONTRACT = cargarContratoAdapters();
|
|
18
|
+
const RUNTIME_IDS = Object.freeze(CONTRACT.runtimes.map(({ id }) => id));
|
|
19
|
+
const CANARY_IDS = Object.freeze([
|
|
20
|
+
'instructions-load',
|
|
21
|
+
'swl-command',
|
|
22
|
+
'forbidden-mutation-block',
|
|
23
|
+
'verified-context',
|
|
24
|
+
'single-use-grant',
|
|
25
|
+
]);
|
|
26
|
+
const OPTION_KEYS = Object.freeze(['now', 'pathEntries', 'bundles', 'buildEvents']);
|
|
27
|
+
const DIAGNOSTIC_KEYS = Object.freeze([
|
|
28
|
+
'schemaVersion', 'kind', 'generatedAt', 'effectiveMode', 'certification', 'runtimes',
|
|
29
|
+
]);
|
|
30
|
+
const RUNTIME_KEYS = Object.freeze([
|
|
31
|
+
'runtimeId', 'installerTarget', 'availability', 'bundle', 'buildAttestation',
|
|
32
|
+
'canaries', 'effectiveMode', 'certification', 'reasons',
|
|
33
|
+
]);
|
|
34
|
+
const AVAILABILITY_KEYS = Object.freeze([
|
|
35
|
+
'status', 'binary', 'version', 'exitCode', 'reasonCode',
|
|
36
|
+
]);
|
|
37
|
+
const STATIC_KEYS = Object.freeze(['status', 'reasonCode']);
|
|
38
|
+
const CANARY_KEYS = Object.freeze(['id', 'status', 'reasonCode']);
|
|
39
|
+
const AVAILABILITY_STATES = new Set([
|
|
40
|
+
'available', 'missing', 'blocked', 'timeout', 'failed', 'unverified',
|
|
41
|
+
]);
|
|
42
|
+
const AVAILABILITY_REASONS = new Set([
|
|
43
|
+
'BINARY_MISSING', 'RUNTIME_AVAILABLE', 'SPAWN_BLOCKED', 'SPAWN_TIMEOUT',
|
|
44
|
+
'SPAWN_FAILED', 'OUTPUT_LIMIT_EXCEEDED', 'EXIT_NOT_ALLOWED', 'VERSION_UNPARSEABLE',
|
|
45
|
+
]);
|
|
46
|
+
const AVAILABILITY_REASON_BY_STATUS = Object.freeze({
|
|
47
|
+
available: new Set(['RUNTIME_AVAILABLE']),
|
|
48
|
+
missing: new Set(['BINARY_MISSING']),
|
|
49
|
+
blocked: new Set(['SPAWN_BLOCKED']),
|
|
50
|
+
timeout: new Set(['SPAWN_TIMEOUT']),
|
|
51
|
+
failed: new Set(['SPAWN_FAILED', 'OUTPUT_LIMIT_EXCEEDED', 'EXIT_NOT_ALLOWED']),
|
|
52
|
+
unverified: new Set(['VERSION_UNPARSEABLE']),
|
|
53
|
+
});
|
|
54
|
+
const BUNDLE_REASON_BY_STATUS = Object.freeze({
|
|
55
|
+
verified: 'BUNDLE_VERIFIED', missing: 'BUNDLE_MISSING', invalid: 'BUNDLE_INVALID',
|
|
56
|
+
});
|
|
57
|
+
const BUILD_REASON_BY_STATUS = Object.freeze({
|
|
58
|
+
missing: 'BUILD_ATTESTATION_MISSING',
|
|
59
|
+
invalid: 'BUILD_ATTESTATION_INVALID',
|
|
60
|
+
unverified: 'BUILD_ATTESTATION_UNVERIFIED',
|
|
61
|
+
});
|
|
62
|
+
const SAFE_ENV = Object.freeze({
|
|
63
|
+
NO_COLOR: '1',
|
|
64
|
+
PATH: path.dirname(process.execPath),
|
|
65
|
+
TERM: 'dumb',
|
|
66
|
+
});
|
|
67
|
+
const PROBE_CWD = path.parse(process.execPath).root;
|
|
68
|
+
const TERMINATION_GRACE_MS = 250;
|
|
69
|
+
const SNAPSHOT_MAX_DEPTH = 32;
|
|
70
|
+
const SNAPSHOT_MAX_NODES = 10000;
|
|
71
|
+
const DIAGNOSTICS = new WeakSet();
|
|
72
|
+
|
|
73
|
+
function invalidInput() {
|
|
74
|
+
throw new TypeError('entrada de diagnóstico inválida');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isDataObject(value) {
|
|
78
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
79
|
+
|| util.types.isProxy(value)) return false;
|
|
80
|
+
const prototype = Object.getPrototypeOf(value);
|
|
81
|
+
if (prototype !== Object.prototype && prototype !== null) return false;
|
|
82
|
+
if (Object.getOwnPropertySymbols(value).length > 0) return false;
|
|
83
|
+
return Object.values(Object.getOwnPropertyDescriptors(value))
|
|
84
|
+
.every((descriptor) => descriptor.enumerable && 'value' in descriptor);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function exactKeys(value, keys) {
|
|
88
|
+
return isDataObject(value)
|
|
89
|
+
&& JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...keys].sort());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isDataArray(value) {
|
|
93
|
+
if (!Array.isArray(value) || util.types.isProxy(value)
|
|
94
|
+
|| Object.getOwnPropertySymbols(value).length > 0) return false;
|
|
95
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
96
|
+
const entries = Object.entries(descriptors).filter(([key]) => key !== 'length');
|
|
97
|
+
return entries.length === value.length && entries.every(([key, descriptor]) => (
|
|
98
|
+
/^(?:0|[1-9]\d*)$/u.test(key) && descriptor.enumerable && 'value' in descriptor
|
|
99
|
+
));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function validatePathEntries(entries) {
|
|
103
|
+
if (!isDataArray(entries) || entries.length > 64) invalidInput();
|
|
104
|
+
const valid = entries.every((entry) => (
|
|
105
|
+
typeof entry === 'string' && entry.length > 0 && entry.length <= 4096
|
|
106
|
+
&& path.isAbsolute(entry) && entry.normalize('NFKC') === entry && !entry.includes('\0')
|
|
107
|
+
));
|
|
108
|
+
if (!valid || new Set(entries).size !== entries.length) invalidInput();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function validateInputMap(value) {
|
|
112
|
+
if (!exactKeys(value, RUNTIME_IDS)) invalidInput();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function validateOptions(options) {
|
|
116
|
+
if (!exactKeys(options, OPTION_KEYS)) invalidInput();
|
|
117
|
+
try {
|
|
118
|
+
validarTimestampIso(options.now, 'diagnostic now');
|
|
119
|
+
} catch {
|
|
120
|
+
invalidInput();
|
|
121
|
+
}
|
|
122
|
+
validatePathEntries(options.pathEntries);
|
|
123
|
+
validateInputMap(options.bundles);
|
|
124
|
+
validateInputMap(options.buildEvents);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function snapshotPrimitive(value) {
|
|
128
|
+
const type = typeof value;
|
|
129
|
+
if (value === null || type === 'string' || type === 'boolean') return value;
|
|
130
|
+
if (type === 'number' && Number.isFinite(value)) return value;
|
|
131
|
+
invalidInput();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function snapshotData(value, context, depth = 0) {
|
|
135
|
+
if (!value || typeof value !== 'object') return snapshotPrimitive(value);
|
|
136
|
+
context.nodes += 1;
|
|
137
|
+
if (depth > SNAPSHOT_MAX_DEPTH || context.nodes > SNAPSHOT_MAX_NODES
|
|
138
|
+
|| context.seen.has(value)) invalidInput();
|
|
139
|
+
context.seen.add(value);
|
|
140
|
+
if (isDataArray(value)) {
|
|
141
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
142
|
+
return value.map((unused, index) => (
|
|
143
|
+
snapshotData(descriptors[String(index)].value, context, depth + 1)
|
|
144
|
+
));
|
|
145
|
+
}
|
|
146
|
+
if (!isDataObject(value)) invalidInput();
|
|
147
|
+
const clone = {};
|
|
148
|
+
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
|
|
149
|
+
clone[key] = snapshotData(descriptor.value, context, depth + 1);
|
|
150
|
+
}
|
|
151
|
+
return clone;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function snapshotOptions(options) {
|
|
155
|
+
const context = { nodes: 0, seen: new WeakSet() };
|
|
156
|
+
return freezeDeep(snapshotData(options, context));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function isRealDirectory(entry) {
|
|
160
|
+
try {
|
|
161
|
+
const stats = fs.lstatSync(entry);
|
|
162
|
+
return stats.isDirectory() && !stats.isSymbolicLink()
|
|
163
|
+
&& path.resolve(fs.realpathSync(entry)) === path.resolve(entry);
|
|
164
|
+
} catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function resolveBinary(runtime, pathEntries) {
|
|
170
|
+
for (const entry of pathEntries) {
|
|
171
|
+
if (!isRealDirectory(entry)) continue;
|
|
172
|
+
for (const candidate of orderedCandidates(runtime.binaries)) {
|
|
173
|
+
const target = path.join(entry, candidate);
|
|
174
|
+
try {
|
|
175
|
+
const stats = fs.lstatSync(target);
|
|
176
|
+
if (stats.isFile() && !stats.isSymbolicLink()) {
|
|
177
|
+
return Object.freeze({ path: target, candidate });
|
|
178
|
+
}
|
|
179
|
+
} catch { /* un candidato ausente no invalida los demás */ }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function orderedCandidates(candidates) {
|
|
186
|
+
if (process.platform !== 'win32') {
|
|
187
|
+
return candidates.filter((candidate) => !/\.(?:cmd|exe)$/iu.test(candidate));
|
|
188
|
+
}
|
|
189
|
+
return [...candidates].sort((left, right) => candidateRank(left) - candidateRank(right));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function candidateRank(candidate) {
|
|
193
|
+
if (candidate.endsWith('.exe')) return 0;
|
|
194
|
+
if (candidate.endsWith('.cmd')) return 1;
|
|
195
|
+
return 2;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function availability(status, binary, version, exitCode, reasonCode) {
|
|
199
|
+
return Object.freeze({ status, binary, version, exitCode, reasonCode });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function classifySpawnError(error, binary) {
|
|
203
|
+
if (error && ['EACCES', 'EPERM'].includes(error.code)) {
|
|
204
|
+
return availability('blocked', binary, null, null, 'SPAWN_BLOCKED');
|
|
205
|
+
}
|
|
206
|
+
if (error && error.code === 'ENOENT') {
|
|
207
|
+
return availability('missing', null, null, null, 'BINARY_MISSING');
|
|
208
|
+
}
|
|
209
|
+
return availability('failed', binary, null, null, 'SPAWN_FAILED');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function stopChild(child) {
|
|
213
|
+
try { return child.kill('SIGKILL') !== false; } catch { return false; }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function abandonChild(child) {
|
|
217
|
+
for (const stream of [child.stdout, child.stderr]) {
|
|
218
|
+
try { if (typeof stream.destroy === 'function') stream.destroy(); } catch { /* best effort */ }
|
|
219
|
+
}
|
|
220
|
+
try { if (typeof child.unref === 'function') child.unref(); } catch { /* best effort */ }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function consumeOutput(state, chunk, child, control, capture) {
|
|
224
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
225
|
+
state.total += bytes.length;
|
|
226
|
+
if (state.total > state.limit) {
|
|
227
|
+
control.terminate(child, availability(
|
|
228
|
+
'failed', state.binary, null, null, 'OUTPUT_LIMIT_EXCEEDED'
|
|
229
|
+
), state.binary);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (capture) state.stdout.push(bytes);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function resultFromClose(state, code, pattern) {
|
|
236
|
+
if (!Number.isInteger(code)) {
|
|
237
|
+
return availability('failed', state.binary, null, null, 'SPAWN_FAILED');
|
|
238
|
+
}
|
|
239
|
+
if (code !== 0) {
|
|
240
|
+
return availability('failed', state.binary, null, code, 'EXIT_NOT_ALLOWED');
|
|
241
|
+
}
|
|
242
|
+
const match = Buffer.concat(state.stdout).toString('utf8').trim().match(pattern);
|
|
243
|
+
if (!match || match[1].length > 64) {
|
|
244
|
+
return availability('unverified', state.binary, null, code, 'VERSION_UNPARSEABLE');
|
|
245
|
+
}
|
|
246
|
+
return availability('available', state.binary, match[1], code, 'RUNTIME_AVAILABLE');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function scheduleTerminationGrace(child, binary, settle) {
|
|
250
|
+
const timer = setTimeout(() => {
|
|
251
|
+
abandonChild(child);
|
|
252
|
+
settle(availability('failed', binary, null, null, 'SPAWN_FAILED'));
|
|
253
|
+
}, TERMINATION_GRACE_MS);
|
|
254
|
+
timer.unref();
|
|
255
|
+
return timer;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function clearTimers(...timers) {
|
|
259
|
+
for (const timer of timers) {
|
|
260
|
+
if (timer) clearTimeout(timer);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function createSettlement(resolve) {
|
|
265
|
+
let settled = false;
|
|
266
|
+
let timer;
|
|
267
|
+
let graceTimer;
|
|
268
|
+
let cleanup = () => {};
|
|
269
|
+
let pendingTermination = null;
|
|
270
|
+
function settle(result) {
|
|
271
|
+
if (settled) return;
|
|
272
|
+
settled = true;
|
|
273
|
+
clearTimers(timer, graceTimer);
|
|
274
|
+
cleanup();
|
|
275
|
+
resolve(result);
|
|
276
|
+
}
|
|
277
|
+
return Object.freeze({
|
|
278
|
+
complete(result) { settle(pendingTermination || result); },
|
|
279
|
+
settle,
|
|
280
|
+
terminate(child, result, binary) {
|
|
281
|
+
if (settled || pendingTermination) return;
|
|
282
|
+
pendingTermination = result;
|
|
283
|
+
if (!stopChild(child)) {
|
|
284
|
+
abandonChild(child);
|
|
285
|
+
settle(availability('failed', binary, null, null, 'SPAWN_FAILED'));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
graceTimer = scheduleTerminationGrace(child, binary, settle);
|
|
289
|
+
},
|
|
290
|
+
setCleanup(value) { cleanup = value; },
|
|
291
|
+
setTimer(value) { timer = value; },
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function invalidCmdPath(target) {
|
|
296
|
+
return /[%!&|<>^"\r\n]/u.test(target);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function cmdExecutable() {
|
|
300
|
+
const systemRoot = process.env.SystemRoot;
|
|
301
|
+
if (typeof systemRoot !== 'string' || !path.isAbsolute(systemRoot)) {
|
|
302
|
+
throw Object.assign(new Error('cmd no disponible'), { code: 'EACCES' });
|
|
303
|
+
}
|
|
304
|
+
return path.join(systemRoot, 'System32', 'cmd.exe');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function spawnSpecification(resolved) {
|
|
308
|
+
if (!resolved.candidate.endsWith('.cmd')) {
|
|
309
|
+
return {
|
|
310
|
+
executable: resolved.path,
|
|
311
|
+
args: ['--version'],
|
|
312
|
+
windowsVerbatimArguments: false,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (invalidCmdPath(resolved.path)) {
|
|
316
|
+
throw Object.assign(new Error('ruta cmd inválida'), { code: 'EACCES' });
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
executable: cmdExecutable(),
|
|
320
|
+
args: ['/d', '/s', '/v:off', '/c', `""${resolved.path}" --version"`],
|
|
321
|
+
windowsVerbatimArguments: true,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function spawnProbe(resolved) {
|
|
326
|
+
const spec = spawnSpecification(resolved);
|
|
327
|
+
return spawn(spec.executable, spec.args, {
|
|
328
|
+
cwd: PROBE_CWD, env: SAFE_ENV, shell: false,
|
|
329
|
+
stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true,
|
|
330
|
+
windowsVerbatimArguments: spec.windowsVerbatimArguments,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function bindProbe(child, state, runtime, control) {
|
|
335
|
+
const onStdout = (chunk) => consumeOutput(state, chunk, child, control, true);
|
|
336
|
+
const onStderr = (chunk) => consumeOutput(state, chunk, child, control, false);
|
|
337
|
+
const onError = (error) => control.complete(classifySpawnError(error, state.binary));
|
|
338
|
+
const onClose = (code) => control.complete(resultFromClose(
|
|
339
|
+
state, code, new RegExp(runtime.versionProbe.parsePattern, 'u')
|
|
340
|
+
));
|
|
341
|
+
child.stdout.on('data', onStdout);
|
|
342
|
+
child.stderr.on('data', onStderr);
|
|
343
|
+
child.once('error', onError);
|
|
344
|
+
child.once('close', onClose);
|
|
345
|
+
control.setCleanup(() => {
|
|
346
|
+
child.stdout.removeListener('data', onStdout);
|
|
347
|
+
child.stderr.removeListener('data', onStderr);
|
|
348
|
+
child.removeListener('error', onError);
|
|
349
|
+
child.removeListener('close', onClose);
|
|
350
|
+
});
|
|
351
|
+
const timer = setTimeout(() => {
|
|
352
|
+
control.terminate(child, availability(
|
|
353
|
+
'timeout', state.binary, null, null, 'SPAWN_TIMEOUT'
|
|
354
|
+
), state.binary);
|
|
355
|
+
}, runtime.versionProbe.timeoutMs);
|
|
356
|
+
timer.unref();
|
|
357
|
+
control.setTimer(timer);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function runProbe(runtime, resolved) {
|
|
361
|
+
const state = {
|
|
362
|
+
total: 0, stdout: [], limit: runtime.versionProbe.maxOutputBytes,
|
|
363
|
+
binary: resolved.candidate,
|
|
364
|
+
};
|
|
365
|
+
return new Promise((resolve) => {
|
|
366
|
+
const control = createSettlement(resolve);
|
|
367
|
+
let child;
|
|
368
|
+
try { child = spawnProbe(resolved); } catch (error) {
|
|
369
|
+
control.settle(classifySpawnError(error, resolved.candidate));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
bindProbe(child, state, runtime, control);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function diagnoseAvailability(runtime, pathEntries) {
|
|
377
|
+
const resolved = resolveBinary(runtime, pathEntries);
|
|
378
|
+
if (!resolved) return availability('missing', null, null, null, 'BINARY_MISSING');
|
|
379
|
+
return runProbe(runtime, resolved);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function staticStatus(status, reasonCode) {
|
|
383
|
+
return Object.freeze({ status, reasonCode });
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function diagnoseBundle(bundle) {
|
|
387
|
+
if (bundle === null) return staticStatus('missing', 'BUNDLE_MISSING');
|
|
388
|
+
if (!exactKeys(bundle, ['root', 'locator'])) {
|
|
389
|
+
return staticStatus('invalid', 'BUNDLE_INVALID');
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
verificarLocator(bundle.root, bundle.locator);
|
|
393
|
+
return staticStatus('verified', 'BUNDLE_VERIFIED');
|
|
394
|
+
} catch {
|
|
395
|
+
return staticStatus('invalid', 'BUNDLE_INVALID');
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function plausibleBuildEvent(event, runtimeId) {
|
|
400
|
+
return exactKeys(event, ['eventType', 'payload', 'signature'])
|
|
401
|
+
&& event.eventType === 'runtime-build'
|
|
402
|
+
&& exactKeys(event.payload, ['runtimeId', 'effectiveMode'])
|
|
403
|
+
&& event.payload.runtimeId === runtimeId
|
|
404
|
+
&& event.payload.effectiveMode === 'proposal_only'
|
|
405
|
+
&& typeof event.signature === 'string' && event.signature.length > 0
|
|
406
|
+
&& event.signature.length <= 4096;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function diagnoseBuild(event, runtimeId) {
|
|
410
|
+
if (event === null) return staticStatus('missing', 'BUILD_ATTESTATION_MISSING');
|
|
411
|
+
if (!plausibleBuildEvent(event, runtimeId)) {
|
|
412
|
+
return staticStatus('invalid', 'BUILD_ATTESTATION_INVALID');
|
|
413
|
+
}
|
|
414
|
+
return staticStatus('unverified', 'BUILD_ATTESTATION_UNVERIFIED');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function createCanaries() {
|
|
418
|
+
return Object.freeze(CANARY_IDS.map((id) => Object.freeze({
|
|
419
|
+
id, status: 'not_evaluated', reasonCode: 'W4_REQUIRED',
|
|
420
|
+
})));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function createReasons(availabilityResult, bundle, buildAttestation) {
|
|
424
|
+
return Object.freeze([...new Set([
|
|
425
|
+
availabilityResult.reasonCode,
|
|
426
|
+
bundle.reasonCode,
|
|
427
|
+
buildAttestation.reasonCode,
|
|
428
|
+
'BEHAVIOR_NOT_EVALUATED',
|
|
429
|
+
'CERTIFICATION_UNVERIFIED',
|
|
430
|
+
])].sort());
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function diagnoseRuntime(runtime, options) {
|
|
434
|
+
const availabilityResult = await diagnoseAvailability(runtime, options.pathEntries);
|
|
435
|
+
const bundle = diagnoseBundle(options.bundles[runtime.id]);
|
|
436
|
+
const buildAttestation = diagnoseBuild(options.buildEvents[runtime.id], runtime.id);
|
|
437
|
+
return Object.freeze({
|
|
438
|
+
runtimeId: runtime.id,
|
|
439
|
+
installerTarget: runtime.installerTarget,
|
|
440
|
+
availability: availabilityResult,
|
|
441
|
+
bundle,
|
|
442
|
+
buildAttestation,
|
|
443
|
+
canaries: createCanaries(),
|
|
444
|
+
effectiveMode: 'proposal_only',
|
|
445
|
+
certification: 'unverified',
|
|
446
|
+
reasons: createReasons(availabilityResult, bundle, buildAttestation),
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function freezeDeep(value) {
|
|
451
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
452
|
+
Object.values(value).forEach(freezeDeep);
|
|
453
|
+
return Object.freeze(value);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function assertOutput(condition) {
|
|
457
|
+
if (!condition) throw new TypeError('diagnóstico interno viola su contrato');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function validateAvailability(value) {
|
|
461
|
+
assertOutput(exactKeys(value, AVAILABILITY_KEYS)
|
|
462
|
+
&& AVAILABILITY_STATES.has(value.status)
|
|
463
|
+
&& AVAILABILITY_REASONS.has(value.reasonCode)
|
|
464
|
+
&& AVAILABILITY_REASON_BY_STATUS[value.status].has(value.reasonCode));
|
|
465
|
+
const binaryValid = value.status === 'missing'
|
|
466
|
+
? value.binary === null
|
|
467
|
+
: typeof value.binary === 'string' && /^[A-Za-z0-9._-]{1,64}$/u.test(value.binary);
|
|
468
|
+
const versionValid = value.status === 'available'
|
|
469
|
+
? typeof value.version === 'string' && /^[0-9]+\.[0-9]+\.[0-9]+$/u.test(value.version)
|
|
470
|
+
&& value.version.length <= 64
|
|
471
|
+
: value.version === null;
|
|
472
|
+
assertOutput(binaryValid && versionValid && validExitCode(value));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function validExitCode(value) {
|
|
476
|
+
if (['available', 'unverified'].includes(value.status)) return value.exitCode === 0;
|
|
477
|
+
if (value.reasonCode === 'EXIT_NOT_ALLOWED') {
|
|
478
|
+
return Number.isSafeInteger(value.exitCode) && value.exitCode !== 0;
|
|
479
|
+
}
|
|
480
|
+
return value.exitCode === null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function validateStaticStatus(value, reasons) {
|
|
484
|
+
assertOutput(exactKeys(value, STATIC_KEYS)
|
|
485
|
+
&& Object.hasOwn(reasons, value.status)
|
|
486
|
+
&& reasons[value.status] === value.reasonCode);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function validateCanaries(canaries) {
|
|
490
|
+
assertOutput(isDataArray(canaries) && canaries.length === CANARY_IDS.length);
|
|
491
|
+
canaries.forEach((canary, index) => assertOutput(
|
|
492
|
+
exactKeys(canary, CANARY_KEYS) && canary.id === CANARY_IDS[index]
|
|
493
|
+
&& canary.status === 'not_evaluated' && canary.reasonCode === 'W4_REQUIRED'
|
|
494
|
+
));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function validateRuntimeOutput(value, runtime) {
|
|
498
|
+
assertOutput(exactKeys(value, RUNTIME_KEYS));
|
|
499
|
+
assertOutput(value.runtimeId === runtime.id && value.installerTarget === runtime.installerTarget);
|
|
500
|
+
validateAvailability(value.availability);
|
|
501
|
+
validateStaticStatus(value.bundle, BUNDLE_REASON_BY_STATUS);
|
|
502
|
+
validateStaticStatus(value.buildAttestation, BUILD_REASON_BY_STATUS);
|
|
503
|
+
validateCanaries(value.canaries);
|
|
504
|
+
assertOutput(value.effectiveMode === 'proposal_only' && value.certification === 'unverified');
|
|
505
|
+
const expectedReasons = createReasons(value.availability, value.bundle, value.buildAttestation);
|
|
506
|
+
assertOutput(isDataArray(value.reasons)
|
|
507
|
+
&& JSON.stringify(value.reasons) === JSON.stringify(expectedReasons));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function validateDiagnosticOutput(value) {
|
|
511
|
+
assertOutput(exactKeys(value, DIAGNOSTIC_KEYS));
|
|
512
|
+
assertOutput(value.schemaVersion === '1.0.0'
|
|
513
|
+
&& value.kind === 'swl-runtime-diagnostic-core3'
|
|
514
|
+
&& value.effectiveMode === 'proposal_only' && value.certification === 'unverified');
|
|
515
|
+
validarTimestampIso(value.generatedAt, 'diagnostic generatedAt');
|
|
516
|
+
assertOutput(isDataArray(value.runtimes) && value.runtimes.length === CONTRACT.runtimes.length);
|
|
517
|
+
value.runtimes.forEach((runtime, index) => (
|
|
518
|
+
validateRuntimeOutput(runtime, CONTRACT.runtimes[index], index)
|
|
519
|
+
));
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Exige exactamente los tres runtimes Core-3 (`validateDiagnosticOutput`).
|
|
524
|
+
* Una vista parcial (p. ej. filtrada por `--target` en `scripts/runtime-
|
|
525
|
+
* doctor.js`) es una selección de conveniencia para el consumidor, no un
|
|
526
|
+
* diagnóstico Core-3 completo — rechazarla aquí es correcto, no un bug.
|
|
527
|
+
*/
|
|
528
|
+
function validarDiagnosticoCore3(value) {
|
|
529
|
+
validateDiagnosticOutput(value);
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function diagnosticarCore3(options) {
|
|
534
|
+
validateOptions(options);
|
|
535
|
+
const snapshot = snapshotOptions(options);
|
|
536
|
+
const runtimes = [];
|
|
537
|
+
for (const runtime of CONTRACT.runtimes) {
|
|
538
|
+
runtimes.push(await diagnoseRuntime(runtime, snapshot));
|
|
539
|
+
}
|
|
540
|
+
const result = freezeDeep({
|
|
541
|
+
schemaVersion: '1.0.0', kind: 'swl-runtime-diagnostic-core3',
|
|
542
|
+
generatedAt: snapshot.now, effectiveMode: 'proposal_only',
|
|
543
|
+
certification: 'unverified', runtimes,
|
|
544
|
+
});
|
|
545
|
+
validateDiagnosticOutput(result);
|
|
546
|
+
DIAGNOSTICS.add(result);
|
|
547
|
+
return result;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Identidad exacta contra el WeakSet interno — una vista filtrada por target nunca coincide. */
|
|
551
|
+
function esDiagnosticoCore3(value) {
|
|
552
|
+
return DIAGNOSTICS.has(value);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** Igual que esDiagnosticoCore3(): solo serializa el diagnóstico completo emitido por diagnosticarCore3(). */
|
|
556
|
+
function serializarDiagnosticoCore3(value) {
|
|
557
|
+
if (!DIAGNOSTICS.has(value)) throw new TypeError('se requiere diagnóstico verificado');
|
|
558
|
+
validateDiagnosticOutput(value);
|
|
559
|
+
return canonicalizarIJson(value);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
module.exports = {
|
|
563
|
+
diagnosticarCore3,
|
|
564
|
+
esDiagnosticoCore3,
|
|
565
|
+
serializarDiagnosticoCore3,
|
|
566
|
+
validarDiagnosticoCore3,
|
|
567
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Construye el input read-only que consume diagnosticarCore3() a partir del
|
|
5
|
+
* entorno del proceso: PATH normalizado y mapas vacíos de bundle/build-event
|
|
6
|
+
* (el CLI y `doctor` nunca tienen contexto de instalación real que ofrecer).
|
|
7
|
+
*
|
|
8
|
+
* Compartido entre scripts/runtime-doctor.js y la integración de scripts/doctor.js
|
|
9
|
+
* para no duplicar las reglas de saneamiento de PATH (DRY).
|
|
10
|
+
*
|
|
11
|
+
* @module scripts/lib/runtime-doctor-input
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const path = require('node:path');
|
|
15
|
+
|
|
16
|
+
const { CORE_RUNTIME_IDS } = require('./runtime-adapter-contract');
|
|
17
|
+
|
|
18
|
+
const MAX_PATH_ENTRIES = 64;
|
|
19
|
+
const MAX_PATH_LENGTH = 4096;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Normaliza `process.env.PATH` a un array apto para diagnosticarCore3():
|
|
23
|
+
* absoluto, NFKC, sin byte nulo, sin duplicados, acotado a 64 entradas.
|
|
24
|
+
* Entradas que no cumplen el formato se descartan silenciosamente — el
|
|
25
|
+
* validador aguas abajo falla cerrado ante cualquier entrada residual mal
|
|
26
|
+
* formada, así que aquí basta con producir un array ya conforme.
|
|
27
|
+
*
|
|
28
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
29
|
+
* @returns {string[]}
|
|
30
|
+
*/
|
|
31
|
+
function pathEntriesFromEnv(env = process.env) {
|
|
32
|
+
const crudo = String(env.PATH || env.Path || '').split(path.delimiter);
|
|
33
|
+
const vistos = new Set();
|
|
34
|
+
const entradas = [];
|
|
35
|
+
for (const segmento of crudo) {
|
|
36
|
+
if (!segmento) continue;
|
|
37
|
+
const entrada = normalizarEntrada(segmento);
|
|
38
|
+
if (!entrada || vistos.has(entrada)) continue;
|
|
39
|
+
vistos.add(entrada);
|
|
40
|
+
entradas.push(entrada);
|
|
41
|
+
if (entradas.length >= MAX_PATH_ENTRIES) break;
|
|
42
|
+
}
|
|
43
|
+
return entradas;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizarEntrada(segmento) {
|
|
47
|
+
const absoluta = path.resolve(segmento);
|
|
48
|
+
if (absoluta.length === 0 || absoluta.length > MAX_PATH_LENGTH) return null;
|
|
49
|
+
if (!path.isAbsolute(absoluta) || absoluta.includes('\0')) return null;
|
|
50
|
+
if (absoluta.normalize('NFKC') !== absoluta) return null;
|
|
51
|
+
return absoluta;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Mapa `{ [runtimeId]: null }` para las tres claves Core-3 exigidas por diagnosticarCore3(). */
|
|
55
|
+
function emptyRuntimeMap() {
|
|
56
|
+
return Object.fromEntries(CORE_RUNTIME_IDS.map((id) => [id, null]));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { pathEntriesFromEnv, emptyRuntimeMap, MAX_PATH_ENTRIES };
|