progmune-runtime 2.1.6 → 3.2.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/README.md +119 -461
- package/dist/ablation-study.js +144 -0
- package/dist/ablation-study.test.js +18 -0
- package/dist/action-runtime.js +3 -1
- package/dist/active-learning.js +211 -0
- package/dist/analytics.js +139 -0
- package/dist/asset-factory.js +309 -0
- package/dist/asset-growth.js +244 -0
- package/dist/asset-promotion.js +382 -0
- package/dist/asset-quality.js +550 -0
- package/dist/audit/business-translator.js +285 -0
- package/dist/audit/cli.js +66 -0
- package/dist/audit/formatters/html.js +379 -0
- package/dist/audit/formatters/json.js +11 -0
- package/dist/audit/formatters/markdown.js +192 -0
- package/dist/audit/formatters/terminal.js +189 -0
- package/dist/audit/index.js +25 -0
- package/dist/audit/report-builder.js +318 -0
- package/dist/audit/types.js +8 -0
- package/dist/audit.js +3 -3
- package/dist/auto-benchmark-generator.js +137 -0
- package/dist/auto-benchmark-generator.test.js +45 -0
- package/dist/auto-protocol-synthesizer.js +362 -0
- package/dist/auto-protocol-synthesizer.test.js +82 -0
- package/dist/autonomous-patch.js +175 -0
- package/dist/autonomous-patch.test.js +128 -0
- package/dist/badge/badge-server.js +98 -0
- package/dist/behavior-miner.js +442 -0
- package/dist/belief-layer.js +475 -0
- package/dist/benchmark-count.js +5 -0
- package/dist/benchmark-generator.js +211 -0
- package/dist/benchmark-harness.js +201 -0
- package/dist/benchmark-pass-rate.js +7 -0
- package/dist/benchmark-report.js +8 -3
- package/dist/benchmark-save.js +14 -1
- package/dist/bootstrap-validation.js +197 -0
- package/dist/bootstrap-validation.test.js +51 -0
- package/dist/branch-ledger.js +1 -1
- package/dist/capability-gap.js +130 -0
- package/dist/certify-html.js +351 -0
- package/dist/certify.js +326 -0
- package/dist/check.js +4 -4
- package/dist/compliance-miner.js +447 -0
- package/dist/continuous-benchmark.js +194 -0
- package/dist/continuous-benchmark.test.js +116 -0
- package/dist/corpus-stats.js +173 -0
- package/dist/counterfactual-engine.js +288 -0
- package/dist/coverage-dashboard.js +109 -0
- package/dist/coverage-system.test.js +205 -0
- package/dist/cross-repo-precision.js +352 -0
- package/dist/cve-benchmark.js +180 -0
- package/dist/cve-benchmark.test.js +28 -0
- package/dist/cve-collector.js +73 -0
- package/dist/data-quality.js +141 -0
- package/dist/decision-engine.js +388 -0
- package/dist/derive-metadata.js +250 -0
- package/dist/difficulty-active.test.js +198 -0
- package/dist/difficulty-map.js +244 -0
- package/dist/discovery-analytics.js +125 -0
- package/dist/discovery-model.js +149 -0
- package/dist/discovery-optimize.test.js +199 -0
- package/dist/discovery-trace.js +276 -0
- package/dist/discovery-trace.test.js +97 -0
- package/dist/emitter.js +83 -1
- package/dist/enterprise-dashboard.js +405 -0
- package/dist/eval-hardening.js +297 -0
- package/dist/eval-hardening.test.js +85 -0
- package/dist/evaluation-campaign.js +359 -0
- package/dist/evaluation-campaign.test.js +181 -0
- package/dist/evidence-growth.js +143 -0
- package/dist/evidence-repository.js +209 -0
- package/dist/evidence-system.js +441 -0
- package/dist/execute.js +15 -7
- package/dist/experimental/software-physics.js +291 -0
- package/dist/experimental/state-inference.js +516 -0
- package/dist/experimental/unsupervised-physics.js +230 -0
- package/dist/extract-ir-python.js +54 -7
- package/dist/extract-ir.js +376 -12
- package/dist/failure-collector.js +2 -2
- package/dist/failure-corpus.js +322 -9
- package/dist/feedback.js +16 -5
- package/dist/feedback.test.js +49 -0
- package/dist/file-lock.js +1 -1
- package/dist/flywheel-batch.js +292 -0
- package/dist/frameworks/express-cli.js +237 -0
- package/dist/frameworks/express-detector.js +445 -0
- package/dist/frameworks/express-detector.test.js +206 -0
- package/dist/frameworks/index.js +30 -0
- package/dist/frameworks/nestjs-detector.js +302 -0
- package/dist/frameworks/trpc-detector.js +161 -0
- package/dist/frameworks/version-awareness.js +179 -0
- package/dist/function-synonyms.js +164 -0
- package/dist/function-synonyms.test.js +68 -0
- package/dist/generalization.test.js +352 -0
- package/dist/goal-annotator.js +113 -0
- package/dist/goal-planner.js +563 -0
- package/dist/gold-cve.js +164 -0
- package/dist/gold-cve.test.js +104 -0
- package/dist/gold-quality.js +206 -0
- package/dist/gold-tiers.js +241 -0
- package/dist/governance-dashboard.js +327 -0
- package/dist/graph-viz.js +240 -0
- package/dist/guided-frontier.js +195 -0
- package/dist/hierarchical-planner.js +148 -0
- package/dist/identifier-parser.js +260 -0
- package/dist/immune-metrics.js +93 -0
- package/dist/immune-receiver.js +158 -0
- package/dist/immune-reporter.js +1 -1
- package/dist/improvement-orchestrator.js +206 -0
- package/dist/inject-p0-vocabulary.js +300 -0
- package/dist/intent-parser.js +218 -0
- package/dist/invariant-algebra.js +476 -0
- package/dist/invariant-calculus.js +533 -0
- package/dist/ir-utils.js +70 -0
- package/dist/ir-utils.test.js +50 -0
- package/dist/knowledge-api.js +312 -0
- package/dist/knowledge-evolution.js +452 -0
- package/dist/knowledge-explorer.js +506 -0
- package/dist/knowledge-flywheel.js +274 -0
- package/dist/knowledge-governance.js +338 -0
- package/dist/knowledge-governance.test.js +150 -0
- package/dist/knowledge-graph.js +181 -0
- package/dist/knowledge-guided-synth.js +246 -0
- package/dist/knowledge-loop.test.js +77 -0
- package/dist/knowledge-object.js +316 -0
- package/dist/knowledge-package.js +98 -0
- package/dist/kpi-dashboard.js +561 -0
- package/dist/l3-cross-function.js +280 -0
- package/dist/learning-ranker.js +148 -0
- package/dist/learning-ranker.test.js +291 -0
- package/dist/ledger/accountability.js +322 -0
- package/dist/ledger/chain-builder.js +185 -0
- package/dist/ledger/cli.js +222 -0
- package/dist/ledger/index.js +13 -0
- package/dist/ledger/signatures.js +193 -0
- package/dist/ledger/types.js +9 -0
- package/dist/llm.js +74 -3
- package/dist/load-benchmarks.js +8 -3
- package/dist/logger.js +66 -0
- package/dist/logger.test.js +37 -0
- package/dist/logistic-reward.js +339 -0
- package/dist/logistic-reward.test.js +180 -0
- package/dist/macro-graph.js +193 -0
- package/dist/macro-repair.js +183 -0
- package/dist/mcp-server.mjs +1202 -483
- package/dist/memory-layer.js +42 -5
- package/dist/multi-repo-precision.js +422 -0
- package/dist/name-free-protocol.js +425 -0
- package/dist/name-free-protocol.test.js +170 -0
- package/dist/name-scrambling.js +138 -0
- package/dist/name-scrambling.test.js +16 -0
- package/dist/p3-observability.test.js +281 -0
- package/dist/p5-orchestrator.test.js +225 -0
- package/dist/pairwise-preference.js +294 -0
- package/dist/pairwise-preference.test.js +140 -0
- package/dist/planner-constraints.js +104 -0
- package/dist/planner-prompts.js +155 -0
- package/dist/planner-telemetry.js +415 -0
- package/dist/planner-trace.js +214 -0
- package/dist/planner.js +162 -167
- package/dist/plsb/artifact.js +116 -0
- package/dist/plsb/cli.js +71 -0
- package/dist/plsb/index.js +19 -0
- package/dist/plsb/leaderboard.js +249 -0
- package/dist/plsb/report-md.js +156 -0
- package/dist/plsb/schema.js +179 -0
- package/dist/plsb-benchmark.js +284 -0
- package/dist/plsb-benchmark.test.js +119 -0
- package/dist/policy/cli.js +134 -0
- package/dist/policy/engine.js +333 -0
- package/dist/policy/index.js +12 -0
- package/dist/policy/types.js +59 -0
- package/dist/policy-miner.js +505 -0
- package/dist/precision-analyze.js +229 -0
- package/dist/precision-benchmark.js +147 -0
- package/dist/precision-label-c.js +134 -0
- package/dist/precision-label.js +193 -0
- package/dist/precision-report-c.js +149 -0
- package/dist/precision-report.js +246 -0
- package/dist/progmune-status.js +108 -0
- package/dist/proof-engine.js +479 -0
- package/dist/proof-provenance.js +315 -0
- package/dist/protocol-coverage.js +294 -0
- package/dist/protocol-detector.js +1189 -0
- package/dist/protocol-embedding-expanded.js +297 -0
- package/dist/protocol-embedding-expanded.test.js +97 -0
- package/dist/protocol-embedding.js +195 -0
- package/dist/protocol-embedding.test.js +82 -0
- package/dist/protocol-extractor-v2.js +354 -0
- package/dist/protocol-extractor-v2.test.js +140 -0
- package/dist/protocol-extractor.js +310 -0
- package/dist/protocol-extractor.test.js +113 -0
- package/dist/protocol-foundation.js +322 -0
- package/dist/protocol-foundation.test.js +163 -0
- package/dist/protocol-frontier.js +243 -0
- package/dist/protocol-frontier.test.js +92 -0
- package/dist/protocol-gap-analyzer.js +228 -0
- package/dist/protocol-gap-analyzer.test.js +49 -0
- package/dist/protocol-invariants.js +276 -0
- package/dist/protocol-invariants.test.js +111 -0
- package/dist/protocol-knowledge.js +464 -0
- package/dist/protocol-miner.js +343 -0
- package/dist/protocol-mining.js +207 -0
- package/dist/protocol-mining.test.js +37 -0
- package/dist/protocol-registry.js +1 -1
- package/dist/protocol-security-benchmark.js +222 -0
- package/dist/protocol-vulnerability.js +257 -0
- package/dist/protocol-vulnerability.test.js +60 -0
- package/dist/python-benchmark.js +120 -0
- package/dist/python-emitter.js +163 -45
- package/dist/python-protocol-extractor.js +187 -0
- package/dist/python-protocol-extractor.test.js +116 -0
- package/dist/realworld-benchmark.js +646 -0
- package/dist/realworld-benchmark.test.js +36 -0
- package/dist/repair-arch.test.js +411 -0
- package/dist/repair-evolution.test.js +454 -0
- package/dist/repair-executor.js +719 -0
- package/dist/repair-proposal.js +4 -4
- package/dist/repair-ranker.js +141 -0
- package/dist/repair-strategies.js +419 -0
- package/dist/repair-taxonomy.js +234 -0
- package/dist/repair-types.js +12 -0
- package/dist/repo-evaluator.js +250 -0
- package/dist/repo-evaluator.test.js +128 -0
- package/dist/resource-abstraction.js +242 -0
- package/dist/resource-detector.js +211 -0
- package/dist/result.test.js +43 -0
- package/dist/reward-system.js +411 -0
- package/dist/reward-system.test.js +175 -0
- package/dist/risk-model.js +215 -0
- package/dist/rule-miner.js +234 -7
- package/dist/rule-specificity.js +254 -0
- package/dist/runtime-types.js +27 -0
- package/dist/scaffold.js +208 -0
- package/dist/scale-collector.test.js +101 -0
- package/dist/scale-trajectory-collector.js +128 -0
- package/dist/sdk.js +250 -0
- package/dist/search-planner.js +4 -41
- package/dist/semantic-snapshot.js +1 -1
- package/dist/semantic-topology.js +121 -0
- package/dist/semantic-trace.js +310 -317
- package/dist/sequence-extractor.js +343 -0
- package/dist/skill-library.js +245 -0
- package/dist/skill-planner.test.js +189 -0
- package/dist/software-physics.js +291 -0
- package/dist/software-physics.test.js +81 -0
- package/dist/ssg-precision.js +478 -0
- package/dist/ssg-validator.js +71 -21
- package/dist/state-inference-doubleblind.test.js +160 -0
- package/dist/state-inference.js +516 -0
- package/dist/state-inference.test.js +115 -0
- package/dist/state-machine-fingerprint.js +345 -0
- package/dist/state-machine-fingerprint.test.js +120 -0
- package/dist/state-miner.js +386 -0
- package/dist/state-name-inference.js +213 -0
- package/dist/state-name-inference.test.js +69 -0
- package/dist/strategy-planner.js +262 -96
- package/dist/strategy-planner.test.js +135 -0
- package/dist/telemetry-analytics.test.js +402 -0
- package/dist/terminal-format.js +68 -0
- package/dist/terminal-format.test.js +83 -0
- package/dist/topology-factory.js +196 -0
- package/dist/topology-representation.js +242 -0
- package/dist/topology-representation.test.js +27 -0
- package/dist/trajectory-augmentation.js +254 -0
- package/dist/trajectory-augmentation.test.js +63 -0
- package/dist/trajectory-corpus.js +440 -0
- package/dist/trajectory-corpus.test.js +32 -0
- package/dist/trajectory-feedback.test.js +116 -0
- package/dist/transition-synthesizer.js +286 -0
- package/dist/transition-synthesizer.test.js +123 -0
- package/dist/trust/api-semantic-mapper.js +809 -0
- package/dist/trust/call-graph-propagator.js +225 -0
- package/dist/trust/cli.js +122 -0
- package/dist/trust/compliance-scorer.js +283 -0
- package/dist/trust/confidence-calculator.js +261 -0
- package/dist/trust/engine.js +1145 -0
- package/dist/trust/explainability.js +85 -0
- package/dist/trust/formatters/ci.js +42 -0
- package/dist/trust/formatters/json.js +11 -0
- package/dist/trust/formatters/terminal.js +152 -0
- package/dist/trust/index.js +39 -0
- package/dist/trust/phase1-verify.js +171 -0
- package/dist/trust/protocol-domain-validator.js +697 -0
- package/dist/trust/score-calculator.js +282 -0
- package/dist/trust/ssg-bridge.js +641 -0
- package/dist/trust/ssg-bridge.test.js +269 -0
- package/dist/trust/types.js +67 -0
- package/dist/trust/violation-trace.js +335 -0
- package/dist/trust-api.js +179 -0
- package/dist/trust-calibration.js +279 -0
- package/dist/unknown-protocol-discovery.js +339 -0
- package/dist/unknown-protocol-discovery.test.js +102 -0
- package/dist/unsupervised-physics.js +230 -0
- package/dist/unsupervised-physics.test.js +95 -0
- package/dist/utils.test.js +37 -0
- package/dist/validator.js +187 -10
- package/dist/verification-intelligence.js +475 -0
- package/dist/verify-api.js +432 -0
- package/dist/vi-impact-report.js +293 -0
- package/dist/wl-fingerprint.js +162 -0
- package/dist/wl-fingerprint.test.js +130 -0
- package/dist/zeroshot-strategy.js +139 -0
- package/docs/Progmune_/346/212/225/350/265/204/344/272/272/347/231/275/347/232/256/344/271/246_v2.0.html +576 -0
- package/docs/Progmune_/351/241/271/347/233/256/345/205/250/350/247/243.html +721 -0
- package/package.json +73 -6
- package/protocols.json +1956 -50
- package/.dockerignore +0 -14
- package/.mcp.json +0 -11
- package/.progmune_allowlist +0 -50
- package/.test_report/test_report.md +0 -87
- package/Dockerfile +0 -9
- package/FAQ.md +0 -167
- package/WHITEPAPER.md +0 -540
- package/demo-project/auth.ts +0 -55
- package/demo-project/tsconfig.json +0 -8
- package/dist/acl-breakdown.js +0 -13
- package/dist/all-sessions.js +0 -11
- package/dist/antibody-stats.js +0 -11
- package/dist/branch-tree-count.js +0 -14
- package/dist/common-fixpath.js +0 -12
- package/dist/constraint-types.js +0 -12
- package/dist/exec-metrics.js +0 -11
- package/dist/failure-report.js +0 -11
- package/dist/fast-path-hits.js +0 -13
- package/dist/fingerprint-list.js +0 -15
- package/dist/gen-history-log.js +0 -13
- package/dist/heatmap-data.js +0 -11
- package/dist/recent-session.js +0 -12
- package/dist/svl-distribution.js +0 -11
- package/dist/terminal-status.js +0 -11
- package/dist/token-savings.js +0 -11
- package/dist/total-repairs.js +0 -12
- package/dist/unresolved-count.js +0 -12
- package/dist/valid-fingerprints.js +0 -13
- package/dist/verify-ledgers.js +0 -11
- package/docs/whitepaper-style.css +0 -77
- package/docs/whitepaper-v2.1.md +0 -609
- package/docs/whitepaper-v2.2.md +0 -1064
- package/docs/whitepaper-v2.2.pdf +0 -0
- package/fly.toml +0 -31
- package/public/dashboard.html +0 -119
- package/server/hub.js +0 -116
- package/test/replay-golden/sess_1780063202050_mgeld.json +0 -9
- package/test/replay-golden/sess_1780064032560_gocld.json +0 -354
- package/test/replay-golden/sess_1780064413331_s2709.json +0 -606
- package/test/replay-golden/sess_1780064792710_y3avo.json +0 -614
- package/test/replay-golden.ts +0 -84
- package/test_benchmark.js +0 -165
- package/test_comprehensive.mjs +0 -638
- package/test_concurrency.js +0 -129
- package/test_ir_robustness.js +0 -85
- package/test_semantic_contracts.js +0 -269
- package/test_ssg_stress.js +0 -156
- package/test_svl3.js +0 -58
- package/tsconfig.json +0 -17
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SSG Bridge — connects the SSG state machine (protocols.json) to the
|
|
4
|
+
* trust engine's semantic validation pipeline.
|
|
5
|
+
*
|
|
6
|
+
* The bridge solves the abstraction gap: protocol rules use abstract function
|
|
7
|
+
* names (verify_password, begin_tx, open_file), while real code uses concrete
|
|
8
|
+
* library APIs (bcrypt.compare, db.query, fs.openSync).
|
|
9
|
+
*
|
|
10
|
+
* Strategy (multi-pass, best-effort):
|
|
11
|
+
* 1. Direct match — normalized call name == rule name
|
|
12
|
+
* 2. Substring match — call name contains rule name or vice versa
|
|
13
|
+
* 3. Domain-guided — semantic domain maps to namespace, keywords infer rule
|
|
14
|
+
*
|
|
15
|
+
* Architecture:
|
|
16
|
+
* SemanticStep[] → inferRuleMatches() → SSG validateTransition() chain
|
|
17
|
+
* → SSGViolation[] (with BFS fix paths from findFixPathStatic)
|
|
18
|
+
*/
|
|
19
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
22
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
23
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
24
|
+
}
|
|
25
|
+
Object.defineProperty(o, k2, desc);
|
|
26
|
+
}) : (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
o[k2] = m[k];
|
|
29
|
+
}));
|
|
30
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
31
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
32
|
+
}) : function(o, v) {
|
|
33
|
+
o["default"] = v;
|
|
34
|
+
});
|
|
35
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
36
|
+
var ownKeys = function(o) {
|
|
37
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
38
|
+
var ar = [];
|
|
39
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
40
|
+
return ar;
|
|
41
|
+
};
|
|
42
|
+
return ownKeys(o);
|
|
43
|
+
};
|
|
44
|
+
return function (mod) {
|
|
45
|
+
if (mod && mod.__esModule) return mod;
|
|
46
|
+
var result = {};
|
|
47
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
48
|
+
__setModuleDefault(result, mod);
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
})();
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.validateSequenceWithSSG = validateSequenceWithSSG;
|
|
54
|
+
exports.ssgViolationsToTrustViolations = ssgViolationsToTrustViolations;
|
|
55
|
+
exports.loadProjectAliases = loadProjectAliases;
|
|
56
|
+
exports.loadProtocolRules = loadProtocolRules;
|
|
57
|
+
exports.summarizeSSGCoverage = summarizeSSGCoverage;
|
|
58
|
+
const fs = __importStar(require("fs"));
|
|
59
|
+
const path = __importStar(require("path"));
|
|
60
|
+
const ssg_validator_1 = require("../ssg-validator");
|
|
61
|
+
// ═══════════════════════════════════════════════════════════════
|
|
62
|
+
// Domain → Namespace Mapping
|
|
63
|
+
// ═══════════════════════════════════════════════════════════════
|
|
64
|
+
/**
|
|
65
|
+
* Maps semantic protocol domains to SSG state machine namespaces.
|
|
66
|
+
* Multiple domains can map to the same namespace (e.g., tls_config,
|
|
67
|
+
* tls_handshake, tls_cert all map to "tls").
|
|
68
|
+
*/
|
|
69
|
+
const DOMAIN_TO_NAMESPACE = {
|
|
70
|
+
// TLS/SSL
|
|
71
|
+
tls_config: "tls",
|
|
72
|
+
tls_handshake: "tls",
|
|
73
|
+
tls_cert: "tls",
|
|
74
|
+
tls_session: "tls",
|
|
75
|
+
tls_alpn: "tls",
|
|
76
|
+
// Auth
|
|
77
|
+
auth_cred: "auth",
|
|
78
|
+
auth_mech: "auth",
|
|
79
|
+
auth_hash: "auth",
|
|
80
|
+
auth_spn: "auth",
|
|
81
|
+
auth_gssapi: "auth",
|
|
82
|
+
// Connection management → resource lifecycle
|
|
83
|
+
conn_mgmt: "resource",
|
|
84
|
+
conn_poll: "resource",
|
|
85
|
+
// HTTP
|
|
86
|
+
http_ops: "tls", // HTTP server → TLS namespace
|
|
87
|
+
http2_ops: "tls",
|
|
88
|
+
// Protocol operations → stateless (no state machine, always pass)
|
|
89
|
+
ldap_ops: "stateless",
|
|
90
|
+
ftp_ops: "stateless",
|
|
91
|
+
smtp_ops: "stateless",
|
|
92
|
+
imap_ops: "stateless",
|
|
93
|
+
mqtt_ops: "stateless",
|
|
94
|
+
smb_ops: "stateless",
|
|
95
|
+
ssh_ops: "stateless",
|
|
96
|
+
telnet_ops: "stateless",
|
|
97
|
+
dns_ops: "stateless",
|
|
98
|
+
// Memory → stateless (tracked by safeguard rules, not state machine)
|
|
99
|
+
mem_alloc: "stateless",
|
|
100
|
+
mem_free: "stateless",
|
|
101
|
+
mem_util: "stateless",
|
|
102
|
+
// Utilities → stateless
|
|
103
|
+
str_util: "stateless",
|
|
104
|
+
str_format: "stateless",
|
|
105
|
+
buf_util: "stateless",
|
|
106
|
+
net_util: "stateless",
|
|
107
|
+
platform_util: "stateless",
|
|
108
|
+
debug_trace: "stateless",
|
|
109
|
+
error_handle: "stateless",
|
|
110
|
+
util: "stateless",
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Domains that map to "auth" namespace with their position in the auth flow.
|
|
114
|
+
* Used to infer which protocol rule a call matches based on domain + position.
|
|
115
|
+
*/
|
|
116
|
+
const AUTH_DOMAIN_FLOW = [
|
|
117
|
+
"auth_hash", // password hashing/verification
|
|
118
|
+
"auth_mech", // token generation, SASL negotiation
|
|
119
|
+
"auth_cred", // credential retrieval
|
|
120
|
+
"auth_spn", // Kerberos SPN
|
|
121
|
+
"auth_gssapi", // GSSAPI wrap/unwrap
|
|
122
|
+
];
|
|
123
|
+
const NAMESPACE_RULE_HINTS = {
|
|
124
|
+
auth: [
|
|
125
|
+
{ keyword: "hash_password", ruleName: "hash_password" },
|
|
126
|
+
{ keyword: "argon2.hash", ruleName: "hash_password" },
|
|
127
|
+
{ keyword: "scrypt.hash", ruleName: "hash_password" },
|
|
128
|
+
{ keyword: "verify_hash", ruleName: "verify_hash" },
|
|
129
|
+
{ keyword: "verify_password", ruleName: "verify_password" },
|
|
130
|
+
{ keyword: "generate_jwt", ruleName: "generate_jwt" },
|
|
131
|
+
{ keyword: "create_session", ruleName: "create_session" },
|
|
132
|
+
{ keyword: "revoke_token", ruleName: "revoke_token" },
|
|
133
|
+
{ keyword: "verify_token", ruleName: "verify_token" },
|
|
134
|
+
{ keyword: "validate_token", ruleName: "verify_token" },
|
|
135
|
+
{ keyword: "check_owner", ruleName: "check_owner" },
|
|
136
|
+
],
|
|
137
|
+
tls: [
|
|
138
|
+
{ keyword: "load_tls_config", ruleName: "load_tls_config" },
|
|
139
|
+
{ keyword: "tls_config", ruleName: "load_tls_config" },
|
|
140
|
+
{ keyword: "ssl_config", ruleName: "load_tls_config" },
|
|
141
|
+
],
|
|
142
|
+
resource: [
|
|
143
|
+
{ keyword: "open_file", ruleName: "open_file" },
|
|
144
|
+
{ keyword: "read_file", ruleName: "read_file" },
|
|
145
|
+
{ keyword: "write_file", ruleName: "write_file" },
|
|
146
|
+
{ keyword: "close_file", ruleName: "close_file" },
|
|
147
|
+
{ keyword: "connect_db", ruleName: "connect_db" },
|
|
148
|
+
{ keyword: "query_db", ruleName: "query_db" },
|
|
149
|
+
{ keyword: "disconnect_db", ruleName: "disconnect_db" },
|
|
150
|
+
{ keyword: "sanitize", ruleName: "sanitize" },
|
|
151
|
+
{ keyword: "validate_type", ruleName: "validate_type" },
|
|
152
|
+
{ keyword: "validate_range", ruleName: "validate_range" },
|
|
153
|
+
],
|
|
154
|
+
payment: [
|
|
155
|
+
{ keyword: "initiate_payment", ruleName: "initiate_payment" },
|
|
156
|
+
{ keyword: "create_payment", ruleName: "initiate_payment" },
|
|
157
|
+
{ keyword: "stripe", ruleName: "initiate_payment" },
|
|
158
|
+
{ keyword: "webhook", ruleName: "receive_payment_callback" },
|
|
159
|
+
{ keyword: "confirm_payment", ruleName: "confirm_payment" },
|
|
160
|
+
{ keyword: "verify_signature", ruleName: "verify_payment_signature" },
|
|
161
|
+
{ keyword: "refund_payment", ruleName: "refund_payment" },
|
|
162
|
+
{ keyword: "cancel_payment", ruleName: "cancel_payment" },
|
|
163
|
+
],
|
|
164
|
+
transaction: [
|
|
165
|
+
{ keyword: "begin_tx", ruleName: "begin_tx" },
|
|
166
|
+
{ keyword: "commit_tx", ruleName: "commit_tx" },
|
|
167
|
+
{ keyword: "rollback_tx", ruleName: "rollback_tx" },
|
|
168
|
+
{ keyword: "insert_record", ruleName: "insert_record" },
|
|
169
|
+
{ keyword: "update_record", ruleName: "update_record" },
|
|
170
|
+
{ keyword: "delete_record", ruleName: "delete_record" },
|
|
171
|
+
],
|
|
172
|
+
file_upload: [
|
|
173
|
+
{ keyword: "multer", ruleName: "receive_upload" },
|
|
174
|
+
{ keyword: "validate_file", ruleName: "validate_file" },
|
|
175
|
+
{ keyword: "store_file", ruleName: "store_file" },
|
|
176
|
+
],
|
|
177
|
+
session_mgmt: [
|
|
178
|
+
{ keyword: "create_user_session", ruleName: "create_user_session" },
|
|
179
|
+
{ keyword: "refresh_session", ruleName: "refresh_session" },
|
|
180
|
+
{ keyword: "rotate_session_token", ruleName: "rotate_session_token" },
|
|
181
|
+
{ keyword: "revoke_session", ruleName: "revoke_session" },
|
|
182
|
+
{ keyword: "timeout_session", ruleName: "timeout_session" },
|
|
183
|
+
{ keyword: "validate_session", ruleName: "validate_session" },
|
|
184
|
+
],
|
|
185
|
+
registration: [
|
|
186
|
+
{ keyword: "register_user", ruleName: "register_user" },
|
|
187
|
+
{ keyword: "send_verification_code", ruleName: "send_verification_code" },
|
|
188
|
+
{ keyword: "send_verification", ruleName: "send_verification_code" },
|
|
189
|
+
{ keyword: "verify_code", ruleName: "verify_code" },
|
|
190
|
+
{ keyword: "activate_account", ruleName: "activate_account" },
|
|
191
|
+
],
|
|
192
|
+
missing_validation: [
|
|
193
|
+
{ keyword: "validate", ruleName: "validate_request" },
|
|
194
|
+
{ keyword: "sanitize", ruleName: "validate_request" },
|
|
195
|
+
{ keyword: "safeParse", ruleName: "validate_request" },
|
|
196
|
+
{ keyword: "parseAsync", ruleName: "validate_request" },
|
|
197
|
+
{ keyword: "checkSchema", ruleName: "validate_request" },
|
|
198
|
+
{ keyword: "zod", ruleName: "validate_request" },
|
|
199
|
+
],
|
|
200
|
+
};
|
|
201
|
+
// ═══════════════════════════════════════════════════════════════
|
|
202
|
+
// Call → Rule Inference
|
|
203
|
+
// ═══════════════════════════════════════════════════════════════
|
|
204
|
+
/**
|
|
205
|
+
* Normalize a function name for matching:
|
|
206
|
+
* "bcrypt.hashPassword" → "hash_password"
|
|
207
|
+
* "SSL_CTX_new" → "ssl_ctx_new"
|
|
208
|
+
*/
|
|
209
|
+
function normalizeName(name) {
|
|
210
|
+
// Split on dots, take the last segment
|
|
211
|
+
const segments = name.split(".");
|
|
212
|
+
const last = segments[segments.length - 1];
|
|
213
|
+
// CamelCase → snake_case
|
|
214
|
+
const snake = last
|
|
215
|
+
.replace(/([A-Z])/g, "_$1")
|
|
216
|
+
.toLowerCase()
|
|
217
|
+
.replace(/^_/, "")
|
|
218
|
+
.replace(/__+/g, "_");
|
|
219
|
+
return snake;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Try to match a call (real API name + semantic domain) to a protocol rule name.
|
|
223
|
+
* Returns the rule name if matched, or null if no match.
|
|
224
|
+
*
|
|
225
|
+
* Multi-strategy, in priority order:
|
|
226
|
+
* 1. Exact normalized match against all rule names
|
|
227
|
+
* 2. Substring match (call contains rule name or vice versa)
|
|
228
|
+
* 3. Domain-guided keyword match
|
|
229
|
+
*/
|
|
230
|
+
function inferRuleName(apiName, domain, description, ruleNames, namespace, aliases, wildcardAliases) {
|
|
231
|
+
const normalized = normalizeName(apiName);
|
|
232
|
+
const lowerApi = apiName.toLowerCase();
|
|
233
|
+
const lowerDesc = description.toLowerCase();
|
|
234
|
+
// Strategy 0a: Wildcard alias match — e.g., "db.*" matches "db.getUserByOpenId"
|
|
235
|
+
// Wildcard aliases are stored with their prefix (without "*") as key, ruleName as value.
|
|
236
|
+
// They're kept in a separate map: wildcardAliases: Map<prefix, ruleName>
|
|
237
|
+
for (const [prefix, ruleName] of wildcardAliases) {
|
|
238
|
+
if (lowerApi.startsWith(prefix))
|
|
239
|
+
return ruleName;
|
|
240
|
+
}
|
|
241
|
+
// Strategy 0b: Alias exact match (highest priority after wildcard)
|
|
242
|
+
// Try full API name, normalized form, and function-only form
|
|
243
|
+
const aliasKeys = [lowerApi, normalized];
|
|
244
|
+
const dotIdx = lowerApi.lastIndexOf(".");
|
|
245
|
+
if (dotIdx >= 0) {
|
|
246
|
+
aliasKeys.push(lowerApi.slice(dotIdx + 1));
|
|
247
|
+
}
|
|
248
|
+
for (const key of aliasKeys) {
|
|
249
|
+
const ruleName = aliases.get(key);
|
|
250
|
+
if (ruleName)
|
|
251
|
+
return ruleName;
|
|
252
|
+
}
|
|
253
|
+
// Strategy 1: Exact normalized match against rule names
|
|
254
|
+
for (const ruleName of ruleNames) {
|
|
255
|
+
if (normalized === ruleName || lowerApi === ruleName) {
|
|
256
|
+
return ruleName;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Strategy 2: Word-segment match — only match on complete word boundaries.
|
|
260
|
+
// Split both the rule name and normalized call name into word segments
|
|
261
|
+
// (separated by _). Match if every word in the rule name appears as a
|
|
262
|
+
// word segment in the normalized call name. This prevents "status" from
|
|
263
|
+
// matching "poll_status" via raw substring, since "status" must appear
|
|
264
|
+
// as a complete _-delimited segment.
|
|
265
|
+
for (const ruleName of ruleNames) {
|
|
266
|
+
const ruleWords = ruleName.split("_");
|
|
267
|
+
const callWords = normalized.split("_");
|
|
268
|
+
// Rule must have at least 2 words for this strategy (single-word rules
|
|
269
|
+
// are too prone to false positives via substring)
|
|
270
|
+
if (ruleWords.length < 2)
|
|
271
|
+
continue;
|
|
272
|
+
// Every rule word must appear as a complete call word segment
|
|
273
|
+
const allWordsMatch = ruleWords.every((rw) => callWords.includes(rw));
|
|
274
|
+
if (allWordsMatch) {
|
|
275
|
+
return ruleName;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Strategy 3: Domain-guided keyword match
|
|
279
|
+
const hints = NAMESPACE_RULE_HINTS[namespace];
|
|
280
|
+
if (hints) {
|
|
281
|
+
for (const hint of hints) {
|
|
282
|
+
const kw = hint.keyword.toLowerCase();
|
|
283
|
+
// Check call name, normalized name, and semantic description
|
|
284
|
+
if (lowerApi.includes(kw) || normalized.includes(kw) || lowerDesc.includes(kw)) {
|
|
285
|
+
return hint.ruleName;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
// ═══════════════════════════════════════════════════════════════
|
|
292
|
+
// SSG Validation
|
|
293
|
+
// ═══════════════════════════════════════════════════════════════
|
|
294
|
+
/**
|
|
295
|
+
* Validate a sequence of semantic steps against the SSG state machine.
|
|
296
|
+
*
|
|
297
|
+
* For each call, attempts to match it to a protocol rule. If matched,
|
|
298
|
+
* validates the state transition. Tracks state per-namespace.
|
|
299
|
+
*
|
|
300
|
+
* @param steps — semantic steps from the mapper
|
|
301
|
+
* @param rules — Map<functionName, StateAnnotation> from protocols.json
|
|
302
|
+
* @param namespaceInitialStates — initial states per namespace
|
|
303
|
+
* @param file — source file path (for violation reporting)
|
|
304
|
+
* @returns SSG validation result with violations and trace
|
|
305
|
+
*/
|
|
306
|
+
function validateSequenceWithSSG(steps, rules, namespaceInitialStates, file, aliasIndex, wildcardAliases) {
|
|
307
|
+
const allRuleNames = Array.from(rules.keys());
|
|
308
|
+
const aliases = aliasIndex || new Map();
|
|
309
|
+
const wildcards = wildcardAliases || new Map();
|
|
310
|
+
// Build namespace→initialState map
|
|
311
|
+
const nsInitMap = new Map();
|
|
312
|
+
for (const [ns, state] of Object.entries(namespaceInitialStates)) {
|
|
313
|
+
nsInitMap.set(ns, state);
|
|
314
|
+
}
|
|
315
|
+
if (!nsInitMap.has("_global"))
|
|
316
|
+
nsInitMap.set("_global", "INIT");
|
|
317
|
+
if (!nsInitMap.has("stateless"))
|
|
318
|
+
nsInitMap.set("stateless", "IDLE");
|
|
319
|
+
// Per-namespace validation contexts
|
|
320
|
+
const contexts = new Map(); // namespace → ValidationContext
|
|
321
|
+
const ensureContext = (ns) => {
|
|
322
|
+
if (!contexts.has(ns)) {
|
|
323
|
+
const initState = nsInitMap.get(ns) || "IDLE";
|
|
324
|
+
contexts.set(ns, {
|
|
325
|
+
ledger: [],
|
|
326
|
+
currentState: { [ns]: [initState] },
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return contexts.get(ns);
|
|
330
|
+
};
|
|
331
|
+
const trace = [];
|
|
332
|
+
const violations = [];
|
|
333
|
+
let matchedCalls = 0;
|
|
334
|
+
let validatedCalls = 0;
|
|
335
|
+
let violatedCalls = 0;
|
|
336
|
+
// Pre-compute rule hashes for integrity
|
|
337
|
+
const ruleHash = (() => {
|
|
338
|
+
try {
|
|
339
|
+
return (0, ssg_validator_1.hashRules)(rules);
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
})();
|
|
345
|
+
for (let i = 0; i < steps.length; i++) {
|
|
346
|
+
const step = steps[i];
|
|
347
|
+
const namespace = DOMAIN_TO_NAMESPACE[step.domain] || "stateless";
|
|
348
|
+
// Try to match call to a protocol rule FIRST (before namespace check).
|
|
349
|
+
// Even if the semantic domain maps to "stateless", an alias may still
|
|
350
|
+
// map this call to a specific protocol rule with its own namespace.
|
|
351
|
+
// Priority: wildcard alias → exact alias → name match → word match → keyword
|
|
352
|
+
const ruleName = inferRuleName(step.api, step.domain, step.description, allRuleNames, namespace, aliases, wildcards);
|
|
353
|
+
// Use matched rule's namespace, falling back to domain-inferred namespace
|
|
354
|
+
const effectiveNamespace = ruleName
|
|
355
|
+
? (rules.get(ruleName)?.namespace || namespace)
|
|
356
|
+
: namespace;
|
|
357
|
+
// Skip stateless — no state machine validation needed (unless alias matched)
|
|
358
|
+
if (effectiveNamespace === "stateless" && !ruleName) {
|
|
359
|
+
trace.push({
|
|
360
|
+
call: step.api,
|
|
361
|
+
namespace: effectiveNamespace,
|
|
362
|
+
matchedRule: null,
|
|
363
|
+
valid: true,
|
|
364
|
+
});
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (!ruleName) {
|
|
368
|
+
trace.push({
|
|
369
|
+
call: step.api,
|
|
370
|
+
namespace,
|
|
371
|
+
matchedRule: null,
|
|
372
|
+
valid: true, // unmatched calls pass through
|
|
373
|
+
});
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
matchedCalls++;
|
|
377
|
+
// Use the matched rule's actual namespace, not the domain-inferred one.
|
|
378
|
+
const ruleNamespace = effectiveNamespace;
|
|
379
|
+
// Validate transition
|
|
380
|
+
try {
|
|
381
|
+
const ctx = ensureContext(ruleNamespace);
|
|
382
|
+
const result = (0, ssg_validator_1.validateTransition)(ctx, ruleName, i, rules, nsInitMap, ruleHash);
|
|
383
|
+
validatedCalls++;
|
|
384
|
+
if (result.valid) {
|
|
385
|
+
// Advance state
|
|
386
|
+
ctx.ledger.push(result.transition);
|
|
387
|
+
ctx.currentState = result.transition.statesAfter;
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
violatedCalls++;
|
|
391
|
+
const rejection = result.rejection;
|
|
392
|
+
// Compute BFS fix path
|
|
393
|
+
let fixPath = [];
|
|
394
|
+
try {
|
|
395
|
+
fixPath = (0, ssg_validator_1.findFixPathStatic)(rules, ruleNamespace, rejection.currentState, rejection.requiredState);
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
fixPath = rejection.missingFunctions || [];
|
|
399
|
+
}
|
|
400
|
+
violations.push({
|
|
401
|
+
callName: step.api,
|
|
402
|
+
namespace: ruleNamespace,
|
|
403
|
+
currentState: rejection.currentState,
|
|
404
|
+
requiredState: rejection.requiredState,
|
|
405
|
+
fixPath,
|
|
406
|
+
matchedRule: ruleName,
|
|
407
|
+
explanation: `SSG state violation: "${step.api}" (mapped to "${ruleName}") ` +
|
|
408
|
+
`requires states [${rejection.requiredState.join(", ")}] ` +
|
|
409
|
+
`but current ${ruleNamespace} state is [${rejection.currentState.join(", ")}]. ` +
|
|
410
|
+
(fixPath.length > 0
|
|
411
|
+
? `Fix path: ${fixPath.join(" → ")}`
|
|
412
|
+
: `No fix path found — protocol gap.`),
|
|
413
|
+
});
|
|
414
|
+
// Still advance state on rejection (best-effort: apply transition anyway
|
|
415
|
+
// so subsequent calls can be validated)
|
|
416
|
+
ctx.ledger.push(result.transition);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
// Validation threw — record as unmatched (graceful degradation)
|
|
421
|
+
trace.push({
|
|
422
|
+
call: step.api,
|
|
423
|
+
namespace,
|
|
424
|
+
matchedRule: ruleName,
|
|
425
|
+
valid: false,
|
|
426
|
+
rejection: {
|
|
427
|
+
blocked: ruleName,
|
|
428
|
+
currentState: [],
|
|
429
|
+
requiredState: [],
|
|
430
|
+
missingFunctions: [],
|
|
431
|
+
fixPath: [],
|
|
432
|
+
namespace,
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
trace.push({
|
|
437
|
+
call: step.api,
|
|
438
|
+
namespace,
|
|
439
|
+
matchedRule: ruleName,
|
|
440
|
+
valid: true,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
passed: violations.length === 0,
|
|
445
|
+
trace,
|
|
446
|
+
violations,
|
|
447
|
+
stats: {
|
|
448
|
+
totalCalls: steps.length,
|
|
449
|
+
matchedCalls,
|
|
450
|
+
unmatchedCalls: steps.length - matchedCalls,
|
|
451
|
+
validatedCalls,
|
|
452
|
+
violatedCalls,
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Convert SSG violations to TrustViolation format for the trust engine.
|
|
458
|
+
*/
|
|
459
|
+
function ssgViolationsToTrustViolations(ssgResult, file, funcName) {
|
|
460
|
+
return ssgResult.violations.map((v) => ({
|
|
461
|
+
severity: "medium",
|
|
462
|
+
rule_id: `SSG_${v.namespace.toUpperCase()}_STATE_VIOLATION`,
|
|
463
|
+
file,
|
|
464
|
+
function: funcName,
|
|
465
|
+
message: v.explanation,
|
|
466
|
+
evidence: `Call: ${v.callName} | Rule: ${v.matchedRule || "unknown"} | ` +
|
|
467
|
+
`Required: [${v.requiredState.join(", ")}] | ` +
|
|
468
|
+
`Current: [${v.currentState.join(", ")}]`,
|
|
469
|
+
why: `Protocol state machine violation in namespace "${v.namespace}": ` +
|
|
470
|
+
`function "${v.callName}" cannot be called in current state ` +
|
|
471
|
+
`[${v.currentState.join(", ")}]. Required pre-states: [${v.requiredState.join(", ")}].`,
|
|
472
|
+
fix: v.fixPath.length > 0
|
|
473
|
+
? `Insert before the violating call: ${v.fixPath.join(" → ")}`
|
|
474
|
+
: `Review protocol documentation for namespace "${v.namespace}" to understand required state transitions.`,
|
|
475
|
+
policy_ref: `protocol-safety.ssg.${v.namespace}`,
|
|
476
|
+
}));
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Load protocol rules from protocols.json.
|
|
480
|
+
* Returns { rules, namespaceInitialStates } or null if unavailable.
|
|
481
|
+
*/
|
|
482
|
+
/**
|
|
483
|
+
* Load project-level aliases from .progmune_aliases.json.
|
|
484
|
+
*
|
|
485
|
+
* Format: { "aliases": { "createSessionToken": "create_user_session", ... } }
|
|
486
|
+
*
|
|
487
|
+
* Project aliases are supplemental — they never override built-in global aliases.
|
|
488
|
+
* Each alias is validated: the target rule must exist in the rule set.
|
|
489
|
+
*
|
|
490
|
+
* @returns { aliases, warnings } or null if no config file exists
|
|
491
|
+
*/
|
|
492
|
+
function loadProjectAliases(projectPath, ruleNames) {
|
|
493
|
+
try {
|
|
494
|
+
const aliasPath = path.join(projectPath, ".progmune_aliases.json");
|
|
495
|
+
if (!fs.existsSync(aliasPath))
|
|
496
|
+
return null;
|
|
497
|
+
const raw = JSON.parse(fs.readFileSync(aliasPath, "utf-8"));
|
|
498
|
+
if (!raw.aliases || typeof raw.aliases !== "object") {
|
|
499
|
+
return { aliases: {}, warnings: [".progmune_aliases.json: missing or invalid 'aliases' key"] };
|
|
500
|
+
}
|
|
501
|
+
const aliases = {};
|
|
502
|
+
const warnings = [];
|
|
503
|
+
for (const [callName, ruleName] of Object.entries(raw.aliases)) {
|
|
504
|
+
if (typeof ruleName !== "string") {
|
|
505
|
+
warnings.push(`.progmune_aliases.json: alias "${callName}" value must be a string, skipping`);
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
const normalizedCall = callName.toLowerCase().trim();
|
|
509
|
+
const normalizedRule = ruleName.trim();
|
|
510
|
+
if (!normalizedCall || !normalizedRule)
|
|
511
|
+
continue;
|
|
512
|
+
// Validate: target rule must exist
|
|
513
|
+
if (!ruleNames.has(normalizedRule)) {
|
|
514
|
+
warnings.push(`.progmune_aliases.json: "${callName}" → "${ruleName}" — ` +
|
|
515
|
+
`rule "${ruleName}" not found in protocol rules, skipping`);
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
aliases[normalizedCall] = normalizedRule;
|
|
519
|
+
}
|
|
520
|
+
return { aliases, warnings };
|
|
521
|
+
}
|
|
522
|
+
catch (e) {
|
|
523
|
+
return { aliases: {}, warnings: [`Failed to load .progmune_aliases.json: ${e.message}`] };
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function loadProtocolRules(projectPath) {
|
|
527
|
+
try {
|
|
528
|
+
// Resolve protocols.json using multiple strategies:
|
|
529
|
+
// 1. Project-local (if projectPath provided)
|
|
530
|
+
// 2. Relative to cwd (works in vitest/dev)
|
|
531
|
+
// 3. Relative to this source file (works with __dirname in CJS)
|
|
532
|
+
const searchPaths = [];
|
|
533
|
+
if (projectPath) {
|
|
534
|
+
searchPaths.push(path.join(projectPath, "protocols.json"), path.join(projectPath, ".progmune_protocols.json"));
|
|
535
|
+
}
|
|
536
|
+
// cwd-based resolution (most reliable cross-environment)
|
|
537
|
+
searchPaths.push(path.resolve(process.cwd(), "protocols.json"), path.resolve(process.cwd(), "..", "protocols.json"), path.resolve(process.cwd(), "progmune-runtime", "protocols.json"));
|
|
538
|
+
// __dirname-based (works in CJS context)
|
|
539
|
+
try {
|
|
540
|
+
searchPaths.push(path.resolve(__dirname, "../../protocols.json"));
|
|
541
|
+
searchPaths.push(path.resolve(__dirname, "../protocols.json"));
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
// __dirname not available (ESM context)
|
|
545
|
+
}
|
|
546
|
+
let protoDef = null;
|
|
547
|
+
for (const p of searchPaths) {
|
|
548
|
+
try {
|
|
549
|
+
if (fs.existsSync(p)) {
|
|
550
|
+
const parsed = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
551
|
+
// Only accept if it has the "rules" structure (some project-local
|
|
552
|
+
// .progmune_protocols.json files have a different format)
|
|
553
|
+
if (parsed.rules && typeof parsed.rules === "object") {
|
|
554
|
+
protoDef = parsed;
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
// Otherwise continue searching — this file exists but isn't a rules definition
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (!protoDef || !protoDef.rules)
|
|
565
|
+
return null;
|
|
566
|
+
const protocols = (0, ssg_validator_1.parseProtocolsFromJSON)(protoDef);
|
|
567
|
+
const rules = new Map();
|
|
568
|
+
const aliasIndex = new Map();
|
|
569
|
+
const wildcardAliases = new Map();
|
|
570
|
+
for (const p of protocols) {
|
|
571
|
+
rules.set(p.function, p.protocol);
|
|
572
|
+
// Build alias → ruleName reverse index
|
|
573
|
+
if (p.protocol.aliases) {
|
|
574
|
+
for (const alias of p.protocol.aliases) {
|
|
575
|
+
const normalized = alias.toLowerCase().trim();
|
|
576
|
+
if (!normalized)
|
|
577
|
+
continue;
|
|
578
|
+
// Wildcard aliases end with ".*" — match any function with that prefix
|
|
579
|
+
if (normalized.endsWith(".*")) {
|
|
580
|
+
const prefix = normalized.slice(0, -2); // remove ".*"
|
|
581
|
+
if (prefix && !wildcardAliases.has(prefix)) {
|
|
582
|
+
wildcardAliases.set(prefix, p.function);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
// First alias wins (no overwrite — prevents ambiguous mappings)
|
|
587
|
+
if (!aliasIndex.has(normalized)) {
|
|
588
|
+
aliasIndex.set(normalized, p.function);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const nsInit = {};
|
|
595
|
+
if (protoDef.namespaceInitialStates) {
|
|
596
|
+
Object.assign(nsInit, protoDef.namespaceInitialStates);
|
|
597
|
+
}
|
|
598
|
+
// Ensure essential namespaces have defaults
|
|
599
|
+
if (!nsInit._global)
|
|
600
|
+
nsInit._global = "INIT";
|
|
601
|
+
if (!nsInit.stateless)
|
|
602
|
+
nsInit.stateless = "IDLE";
|
|
603
|
+
// ── Load project-level aliases (supplemental, never override global) ──
|
|
604
|
+
let projectAliases;
|
|
605
|
+
let aliasWarnings;
|
|
606
|
+
if (projectPath) {
|
|
607
|
+
const ruleNameSet = new Set(rules.keys());
|
|
608
|
+
const pa = loadProjectAliases(projectPath, ruleNameSet);
|
|
609
|
+
if (pa) {
|
|
610
|
+
projectAliases = pa.aliases;
|
|
611
|
+
aliasWarnings = pa.warnings;
|
|
612
|
+
// Merge into exact-match alias index (project aliases DON'T override global)
|
|
613
|
+
for (const [callName, ruleName] of Object.entries(pa.aliases)) {
|
|
614
|
+
if (!aliasIndex.has(callName)) {
|
|
615
|
+
aliasIndex.set(callName, ruleName);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return { rules, namespaceInitialStates: nsInit, aliasIndex, wildcardAliases, projectAliases, aliasWarnings };
|
|
621
|
+
}
|
|
622
|
+
catch {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Generate a human-readable summary of SSG validation coverage.
|
|
628
|
+
*/
|
|
629
|
+
function summarizeSSGCoverage(results) {
|
|
630
|
+
const totalCalls = results.reduce((s, r) => s + r.stats.totalCalls, 0);
|
|
631
|
+
const totalMatched = results.reduce((s, r) => s + r.stats.matchedCalls, 0);
|
|
632
|
+
const totalViolations = results.reduce((s, r) => s + r.violations.length, 0);
|
|
633
|
+
if (totalCalls === 0)
|
|
634
|
+
return "No calls validated by SSG state machine.";
|
|
635
|
+
const matchRate = ((totalMatched / totalCalls) * 100).toFixed(1);
|
|
636
|
+
return [
|
|
637
|
+
`SSG State Machine: ${totalCalls} calls scanned, `,
|
|
638
|
+
`${totalMatched} matched (${matchRate}%), `,
|
|
639
|
+
`${totalViolations} state violation(s) found.`,
|
|
640
|
+
].join("");
|
|
641
|
+
}
|