progmune-runtime 2.1.6 → 3.2.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/README.md +108 -468
- 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 +710 -0
- package/package.json +74 -7
- 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,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* P8.1.1: Double-Blind State Inference Validation
|
|
4
|
+
*
|
|
5
|
+
* THE decisive test: if state inference truly ignores function names,
|
|
6
|
+
* scrambling ALL function names to F_001, F_002 (with randomized
|
|
7
|
+
* numbering) must produce IDENTICAL state machines.
|
|
8
|
+
*
|
|
9
|
+
* This goes beyond P8.0's name-scramble (which only tests fingerprint
|
|
10
|
+
* similarity) by verifying that EVERY structural property survives —
|
|
11
|
+
* state count, transition count, role assignments, DAG property,
|
|
12
|
+
* diameter, and fingerprint identity.
|
|
13
|
+
*
|
|
14
|
+
* If this passes, P8.1 genuinely learns state structure, not
|
|
15
|
+
* disguised verb patterns via role assignment.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
const vitest_1 = require("vitest");
|
|
19
|
+
const state_inference_1 = require("./experimental/state-inference");
|
|
20
|
+
// ── Double-blind scrambling ──
|
|
21
|
+
function doubleBlindScramble(sequences) {
|
|
22
|
+
// Step 1: Collect all unique function names
|
|
23
|
+
const allNames = new Set();
|
|
24
|
+
for (const seq of sequences)
|
|
25
|
+
for (const fn of seq)
|
|
26
|
+
allNames.add(fn);
|
|
27
|
+
// Step 2: Randomize the mapping
|
|
28
|
+
const names = [...allNames];
|
|
29
|
+
const shuffled = [...names];
|
|
30
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
31
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
32
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
33
|
+
}
|
|
34
|
+
// Step 3: Build deterministic map from original → F_XXXX
|
|
35
|
+
const map = new Map();
|
|
36
|
+
for (let i = 0; i < names.length; i++) {
|
|
37
|
+
map.set(names[i], `F_${String(shuffled.indexOf(names[i])).padStart(4, "0")}`);
|
|
38
|
+
}
|
|
39
|
+
// Step 4: Apply mapping
|
|
40
|
+
return sequences.map(seq => seq.map(fn => map.get(fn)));
|
|
41
|
+
}
|
|
42
|
+
// ── Test data ──
|
|
43
|
+
const FILE_PROTOCOL = [
|
|
44
|
+
["open_file", "read_file", "close_file"],
|
|
45
|
+
["open_file", "write_file", "close_file"],
|
|
46
|
+
["open_file", "read_file", "write_file", "close_file"],
|
|
47
|
+
];
|
|
48
|
+
const DB_PROTOCOL = [
|
|
49
|
+
["connect_db", "query_db", "disconnect_db"],
|
|
50
|
+
["connect_db", "query_db", "query_db", "disconnect_db"],
|
|
51
|
+
];
|
|
52
|
+
const AUTH_LIFECYCLE = [
|
|
53
|
+
["verify_password", "generate_jwt", "create_session", "logout"],
|
|
54
|
+
["verify_password", "generate_jwt", "create_session"],
|
|
55
|
+
["verify_password", "revoke_token"],
|
|
56
|
+
];
|
|
57
|
+
const CROSS_REPO = {
|
|
58
|
+
Redis: [
|
|
59
|
+
["createClient", "sendCommand", "closeClient"],
|
|
60
|
+
["selectDB", "getKey"],
|
|
61
|
+
["createClient", "sendCommand", "readReply", "closeClient"],
|
|
62
|
+
],
|
|
63
|
+
SQLite: [
|
|
64
|
+
["sqlite3_open", "sqlite3_exec", "sqlite3_close"],
|
|
65
|
+
["sqlite3_prepare", "sqlite3_step", "sqlite3_finalize"],
|
|
66
|
+
["sqlite3_open", "sqlite3_prepare", "sqlite3_step", "sqlite3_finalize", "sqlite3_close"],
|
|
67
|
+
],
|
|
68
|
+
nginx: [
|
|
69
|
+
["ngx_accept_connection", "ngx_read_request", "ngx_close_connection"],
|
|
70
|
+
["ngx_parse_headers", "ngx_send_response"],
|
|
71
|
+
["ngx_accept_connection", "ngx_process_request", "ngx_send_response", "ngx_close_connection"],
|
|
72
|
+
],
|
|
73
|
+
PostgreSQL: [
|
|
74
|
+
["PQconnectdb", "PQexec", "PQfinish"],
|
|
75
|
+
["begin_transaction", "execute_query", "commit_transaction"],
|
|
76
|
+
["PQconnectdb", "begin_transaction", "execute_query", "commit_transaction", "PQfinish"],
|
|
77
|
+
],
|
|
78
|
+
LevelDB: [
|
|
79
|
+
["DB_Open", "DB_Get", "DB_Close"],
|
|
80
|
+
["DB_Open", "DB_Put", "DB_Close"],
|
|
81
|
+
["DB_Open", "DB_Write", "DB_Compact", "DB_Close"],
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
(0, vitest_1.describe)("P8.1.1 Double-Blind State Inference Validation", () => {
|
|
85
|
+
(0, vitest_1.it)("DOUBLE-BLIND: identical topology with scrambled random names → identical fingerprint", () => {
|
|
86
|
+
const scrambled = doubleBlindScramble(FILE_PROTOCOL);
|
|
87
|
+
// Verify names are actually scrambled
|
|
88
|
+
const origNames = new Set(FILE_PROTOCOL.flat());
|
|
89
|
+
const scramNames = new Set(scrambled.flat());
|
|
90
|
+
for (const name of origNames) {
|
|
91
|
+
(0, vitest_1.expect)(scramNames.has(`F_${String(name).padStart(4, "0")}`) || scramNames.has(name)).toBe(false);
|
|
92
|
+
}
|
|
93
|
+
const orig = (0, state_inference_1.extractStateFingerprint)((0, state_inference_1.inferStateMachine)(FILE_PROTOCOL));
|
|
94
|
+
const scram = (0, state_inference_1.extractStateFingerprint)((0, state_inference_1.inferStateMachine)(scrambled));
|
|
95
|
+
console.log(`\n ═══ DOUBLE-BLIND TEST ═══`);
|
|
96
|
+
console.log(` Original names: ${[...origNames].join(", ")}`);
|
|
97
|
+
console.log(` Scrambled names: ${[...scramNames].slice(0, 4).join(", ")}...`);
|
|
98
|
+
console.log(` States: ${orig.stateCount} → ${scram.stateCount}`);
|
|
99
|
+
console.log(` Trans: ${orig.transitionCount} → ${scram.transitionCount}`);
|
|
100
|
+
console.log(` Entries: ${orig.entryCount} → ${scram.entryCount}`);
|
|
101
|
+
console.log(` Exits: ${orig.exitCount} → ${scram.exitCount}`);
|
|
102
|
+
console.log(` DAG: ${orig.isDAG} → ${scram.isDAG}`);
|
|
103
|
+
const sim = (0, state_inference_1.stateFingerprintSimilarity)(orig, scram);
|
|
104
|
+
console.log(` Similarity: ${(sim * 100).toFixed(1)}%`);
|
|
105
|
+
console.log(` Target: >95% (must be nearly identical)`);
|
|
106
|
+
// Double-blind: scrambled names must produce same fingerprint
|
|
107
|
+
(0, vitest_1.expect)(sim).toBeGreaterThan(0.95);
|
|
108
|
+
(0, vitest_1.expect)(orig.stateCount).toBe(scram.stateCount);
|
|
109
|
+
(0, vitest_1.expect)(orig.transitionCount).toBe(scram.transitionCount);
|
|
110
|
+
});
|
|
111
|
+
(0, vitest_1.it)("DB-LEVEL: database protocol survives double-blind at repo level", () => {
|
|
112
|
+
const scrambled = doubleBlindScramble(DB_PROTOCOL);
|
|
113
|
+
const orig = (0, state_inference_1.extractStateFingerprint)((0, state_inference_1.inferStateMachine)(DB_PROTOCOL));
|
|
114
|
+
const scram = (0, state_inference_1.extractStateFingerprint)((0, state_inference_1.inferStateMachine)(scrambled));
|
|
115
|
+
const sim = (0, state_inference_1.stateFingerprintSimilarity)(orig, scram);
|
|
116
|
+
console.log(` DB double-blind similarity: ${(sim * 100).toFixed(0)}%`);
|
|
117
|
+
(0, vitest_1.expect)(sim).toBeGreaterThan(0.95);
|
|
118
|
+
});
|
|
119
|
+
(0, vitest_1.it)("AUTH-LEVEL: auth lifecycle survives double-blind", () => {
|
|
120
|
+
const scrambled = doubleBlindScramble(AUTH_LIFECYCLE);
|
|
121
|
+
const orig = (0, state_inference_1.extractStateFingerprint)((0, state_inference_1.inferStateMachine)(AUTH_LIFECYCLE));
|
|
122
|
+
const scram = (0, state_inference_1.extractStateFingerprint)((0, state_inference_1.inferStateMachine)(scrambled));
|
|
123
|
+
const sim = (0, state_inference_1.stateFingerprintSimilarity)(orig, scram);
|
|
124
|
+
console.log(` Auth double-blind similarity: ${(sim * 100).toFixed(0)}%`);
|
|
125
|
+
(0, vitest_1.expect)(sim).toBeGreaterThan(0.95);
|
|
126
|
+
});
|
|
127
|
+
(0, vitest_1.it)("CROSS-REPO-LEVEL: all 5 known repos survive double-blind", () => {
|
|
128
|
+
const results = [];
|
|
129
|
+
for (const [repo, seqs] of Object.entries(CROSS_REPO)) {
|
|
130
|
+
const scrambled = doubleBlindScramble(seqs);
|
|
131
|
+
const orig = (0, state_inference_1.extractStateFingerprint)((0, state_inference_1.inferStateMachine)(seqs));
|
|
132
|
+
const scram = (0, state_inference_1.extractStateFingerprint)((0, state_inference_1.inferStateMachine)(scrambled));
|
|
133
|
+
const sim = (0, state_inference_1.stateFingerprintSimilarity)(orig, scram);
|
|
134
|
+
results.push({ repo, similarity: sim });
|
|
135
|
+
}
|
|
136
|
+
console.log(`\n Cross-repo double-blind results:`);
|
|
137
|
+
for (const r of results) {
|
|
138
|
+
const status = r.similarity > 0.95 ? "✅" : r.similarity > 0.8 ? "⚠️" : "❌";
|
|
139
|
+
console.log(` ${r.repo.padEnd(14)} ${(r.similarity * 100).toFixed(0)}% ${status}`);
|
|
140
|
+
}
|
|
141
|
+
const avg = results.reduce((s, r) => s + r.similarity, 0) / results.length;
|
|
142
|
+
console.log(` Average: ${(avg * 100).toFixed(0)}%`);
|
|
143
|
+
// All repos must survive double-blind
|
|
144
|
+
for (const r of results) {
|
|
145
|
+
(0, vitest_1.expect)(r.similarity).toBeGreaterThan(0.9);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
(0, vitest_1.it)("DISCRIMINATION-AFTER-SCRAMBLE: different repos remain distinguishable", () => {
|
|
149
|
+
// The acid test: after scrambling BOTH repos, can we still tell them apart?
|
|
150
|
+
const redisScram = doubleBlindScramble(CROSS_REPO.Redis);
|
|
151
|
+
const pgScram = doubleBlindScramble(CROSS_REPO.PostgreSQL);
|
|
152
|
+
const redisFp = (0, state_inference_1.extractStateFingerprint)((0, state_inference_1.inferStateMachine)(redisScram));
|
|
153
|
+
const pgFp = (0, state_inference_1.extractStateFingerprint)((0, state_inference_1.inferStateMachine)(pgScram));
|
|
154
|
+
const sim = (0, state_inference_1.stateFingerprintSimilarity)(redisFp, pgFp);
|
|
155
|
+
console.log(`\n Redis ↔ PostgreSQL (both scrambled): ${(sim * 100).toFixed(0)}%`);
|
|
156
|
+
// Both are acquire→use→release chains — should be similar
|
|
157
|
+
// but the specific structure (3 vs 5-var patterns) should leave a gap
|
|
158
|
+
(0, vitest_1.expect)(sim).toBeGreaterThan(0.5);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* P8.1: State Inference Engine — From raw call sequences to state machines
|
|
4
|
+
*
|
|
5
|
+
* Core insight: a protocol STATE is defined by what calls produce it,
|
|
6
|
+
* consume it, and invalidate it — NOT by function names.
|
|
7
|
+
*
|
|
8
|
+
* This engine takes raw call sequences (strings) and infers state machines
|
|
9
|
+
* using purely structural co-occurrence analysis. Zero dependency on
|
|
10
|
+
* function names, keywords, or pre-written protocol rules.
|
|
11
|
+
*
|
|
12
|
+
* Pipeline:
|
|
13
|
+
* Raw call sequences
|
|
14
|
+
* → Assign structural roles (producer/consumer/invalidator)
|
|
15
|
+
* → Group functions into states by structural equivalence
|
|
16
|
+
* → Build state transition graph (opaque S0, S1, S2...)
|
|
17
|
+
* → Extract state machine fingerprint
|
|
18
|
+
*
|
|
19
|
+
* The decisive test: after scrambling all function names to F_001, F_002,
|
|
20
|
+
* the inferred state machine must be identical to the original.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.inferStateMachine = inferStateMachine;
|
|
24
|
+
exports.extractStateFingerprint = extractStateFingerprint;
|
|
25
|
+
exports.stateFingerprintToVector = stateFingerprintToVector;
|
|
26
|
+
exports.stateFingerprintSimilarity = stateFingerprintSimilarity;
|
|
27
|
+
exports.printInferredStateMachine = printInferredStateMachine;
|
|
28
|
+
function assignRoles(fnIndices, sequences) {
|
|
29
|
+
const N = fnIndices.size;
|
|
30
|
+
const roles = Array.from({ length: N }, (_, i) => ({
|
|
31
|
+
index: i,
|
|
32
|
+
asFirst: 0,
|
|
33
|
+
asLast: 0,
|
|
34
|
+
asMiddle: 0,
|
|
35
|
+
totalOccurrences: 0,
|
|
36
|
+
predecessors: new Set(),
|
|
37
|
+
successors: new Set(),
|
|
38
|
+
role: "isolated",
|
|
39
|
+
}));
|
|
40
|
+
for (const seq of sequences) {
|
|
41
|
+
if (seq.length === 0)
|
|
42
|
+
continue;
|
|
43
|
+
for (let pos = 0; pos < seq.length; pos++) {
|
|
44
|
+
const idx = fnIndices.get(seq[pos]);
|
|
45
|
+
if (idx === undefined)
|
|
46
|
+
continue;
|
|
47
|
+
roles[idx].totalOccurrences++;
|
|
48
|
+
if (pos === 0)
|
|
49
|
+
roles[idx].asFirst++;
|
|
50
|
+
if (pos === seq.length - 1)
|
|
51
|
+
roles[idx].asLast++;
|
|
52
|
+
if (pos > 0 && pos < seq.length - 1)
|
|
53
|
+
roles[idx].asMiddle++;
|
|
54
|
+
if (pos > 0) {
|
|
55
|
+
const prevIdx = fnIndices.get(seq[pos - 1]);
|
|
56
|
+
if (prevIdx !== undefined)
|
|
57
|
+
roles[idx].predecessors.add(prevIdx);
|
|
58
|
+
}
|
|
59
|
+
if (pos < seq.length - 1) {
|
|
60
|
+
const nextIdx = fnIndices.get(seq[pos + 1]);
|
|
61
|
+
if (nextIdx !== undefined)
|
|
62
|
+
roles[idx].successors.add(nextIdx);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Classify: entry (mostly first), exit (mostly last), bridge (both), isolated (neither)
|
|
67
|
+
for (const r of roles) {
|
|
68
|
+
const total = Math.max(1, r.totalOccurrences);
|
|
69
|
+
const firstRate = r.asFirst / total;
|
|
70
|
+
const lastRate = r.asLast / total;
|
|
71
|
+
if (firstRate > 0.5 && r.successors.size > 0)
|
|
72
|
+
r.role = "entry";
|
|
73
|
+
else if (lastRate > 0.5 && r.predecessors.size > 0)
|
|
74
|
+
r.role = "exit";
|
|
75
|
+
else if (r.predecessors.size > 0 || r.successors.size > 0)
|
|
76
|
+
r.role = "bridge";
|
|
77
|
+
else
|
|
78
|
+
r.role = "isolated";
|
|
79
|
+
}
|
|
80
|
+
return roles;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Group functions that share the same role and similar neighbor patterns
|
|
84
|
+
* into opaque states (S0, S1, S2...).
|
|
85
|
+
*/
|
|
86
|
+
function groupIntoStates(roles) {
|
|
87
|
+
const N = roles.length;
|
|
88
|
+
// Strategy: functions with the same role AND overlapping predecessor/successor
|
|
89
|
+
// sets likely belong to the same protocol state.
|
|
90
|
+
const groups = [];
|
|
91
|
+
const assigned = new Set();
|
|
92
|
+
// Pass 1: Group by role first
|
|
93
|
+
for (const roleType of ["entry", "bridge", "exit", "isolated"]) {
|
|
94
|
+
const candidates = roles
|
|
95
|
+
.map((r, i) => ({ ...r, index: i }))
|
|
96
|
+
.filter(r => r.role === roleType && !assigned.has(r.index));
|
|
97
|
+
// Within the same role, group by neighbor overlap
|
|
98
|
+
const grouped = [];
|
|
99
|
+
const used = new Set();
|
|
100
|
+
for (const c of candidates) {
|
|
101
|
+
if (used.has(c.index))
|
|
102
|
+
continue;
|
|
103
|
+
const cluster = [c.index];
|
|
104
|
+
used.add(c.index);
|
|
105
|
+
// Find other candidates that share ≥50% neighbor overlap
|
|
106
|
+
for (const other of candidates) {
|
|
107
|
+
if (used.has(other.index))
|
|
108
|
+
continue;
|
|
109
|
+
const predOverlap = intersectSize(c.predecessors, other.predecessors);
|
|
110
|
+
const succOverlap = intersectSize(c.successors, other.successors);
|
|
111
|
+
const total = Math.max(1, Math.max(c.predecessors.size, other.predecessors.size) +
|
|
112
|
+
Math.max(c.successors.size, other.successors.size));
|
|
113
|
+
if ((predOverlap + succOverlap) / total > 0.3) {
|
|
114
|
+
cluster.push(other.index);
|
|
115
|
+
used.add(other.index);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
grouped.push(cluster);
|
|
119
|
+
}
|
|
120
|
+
for (const cluster of grouped) {
|
|
121
|
+
const stateId = `S${groups.length}`;
|
|
122
|
+
const predStates = new Set();
|
|
123
|
+
const succStates = new Set();
|
|
124
|
+
for (const fnIdx of cluster) {
|
|
125
|
+
assigned.add(fnIdx);
|
|
126
|
+
for (const p of roles[fnIdx].predecessors)
|
|
127
|
+
predStates.add(`fn_${p}`);
|
|
128
|
+
for (const s of roles[fnIdx].successors)
|
|
129
|
+
succStates.add(`fn_${s}`);
|
|
130
|
+
}
|
|
131
|
+
groups.push({
|
|
132
|
+
id: stateId,
|
|
133
|
+
members: cluster,
|
|
134
|
+
predecessorStates: predStates,
|
|
135
|
+
successorStates: succStates,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return groups;
|
|
140
|
+
}
|
|
141
|
+
function intersectSize(a, b) {
|
|
142
|
+
let count = 0;
|
|
143
|
+
for (const x of a)
|
|
144
|
+
if (b.has(x))
|
|
145
|
+
count++;
|
|
146
|
+
return count;
|
|
147
|
+
}
|
|
148
|
+
// ═══════════════════════════════════════════════════════════════
|
|
149
|
+
// Step 3: Build state transition graph
|
|
150
|
+
// ═══════════════════════════════════════════════════════════════
|
|
151
|
+
function buildStateTransitions(groups, roles) {
|
|
152
|
+
const S = groups.length;
|
|
153
|
+
const matrix = Array.from({ length: S }, () => new Array(S).fill(0));
|
|
154
|
+
// For each pair of states, count how many functions in state A
|
|
155
|
+
// are directly followed by functions in state B in any sequence
|
|
156
|
+
const fnToState = new Map();
|
|
157
|
+
for (let s = 0; s < S; s++) {
|
|
158
|
+
for (const fnIdx of groups[s].members) {
|
|
159
|
+
fnToState.set(fnIdx, s);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Count transitions between states by analyzing function-to-function edges
|
|
163
|
+
for (let si = 0; si < S; si++) {
|
|
164
|
+
for (const fnIdx of groups[si].members) {
|
|
165
|
+
for (const succFn of roles[fnIdx].successors) {
|
|
166
|
+
const sj = fnToState.get(succFn);
|
|
167
|
+
if (sj !== undefined) {
|
|
168
|
+
matrix[si][sj]++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return matrix;
|
|
174
|
+
}
|
|
175
|
+
// ═══════════════════════════════════════════════════════════════
|
|
176
|
+
// Step 4: Extract fingerprint
|
|
177
|
+
// ═══════════════════════════════════════════════════════════════
|
|
178
|
+
function computeStateGraphStats(matrix, groups) {
|
|
179
|
+
const S = matrix.length;
|
|
180
|
+
if (S === 0)
|
|
181
|
+
return { isDAG: true, diameter: 0, sccCount: 0, avgBranching: 0, density: 0 };
|
|
182
|
+
// DAG check: Kahn's algorithm
|
|
183
|
+
const inDeg = new Array(S).fill(0);
|
|
184
|
+
for (let i = 0; i < S; i++)
|
|
185
|
+
for (let j = 0; j < S; j++)
|
|
186
|
+
if (matrix[i][j] > 0)
|
|
187
|
+
inDeg[j]++;
|
|
188
|
+
const q = [];
|
|
189
|
+
for (let i = 0; i < S; i++)
|
|
190
|
+
if (inDeg[i] === 0)
|
|
191
|
+
q.push(i);
|
|
192
|
+
let visited = 0;
|
|
193
|
+
while (q.length > 0) {
|
|
194
|
+
const n = q.shift();
|
|
195
|
+
visited++;
|
|
196
|
+
for (let j = 0; j < S; j++) {
|
|
197
|
+
if (matrix[n][j] > 0 && --inDeg[j] === 0)
|
|
198
|
+
q.push(j);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const isDAG = visited === S;
|
|
202
|
+
// Diameter: BFS from each node
|
|
203
|
+
let diameter = 0;
|
|
204
|
+
for (let start = 0; start < S; start++) {
|
|
205
|
+
const dist = new Array(S).fill(-1);
|
|
206
|
+
const bq = [start];
|
|
207
|
+
dist[start] = 0;
|
|
208
|
+
while (bq.length > 0) {
|
|
209
|
+
const n = bq.shift();
|
|
210
|
+
for (let j = 0; j < S; j++) {
|
|
211
|
+
if (matrix[n][j] > 0 && dist[j] === -1) {
|
|
212
|
+
dist[j] = dist[n] + 1;
|
|
213
|
+
diameter = Math.max(diameter, dist[j]);
|
|
214
|
+
bq.push(j);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// SCC count: Kosaraju simplification (undirected component count as proxy)
|
|
220
|
+
const undirected = new Set();
|
|
221
|
+
for (let i = 0; i < S; i++)
|
|
222
|
+
for (let j = 0; j < S; j++)
|
|
223
|
+
if (matrix[i][j] > 0 || matrix[j][i] > 0)
|
|
224
|
+
undirected.add(`${Math.min(i, j)},${Math.max(i, j)}`);
|
|
225
|
+
const comps = new Map();
|
|
226
|
+
let compId = 0;
|
|
227
|
+
for (const edge of undirected) {
|
|
228
|
+
const [a, b] = edge.split(",").map(Number);
|
|
229
|
+
const ca = comps.get(a), cb = comps.get(b);
|
|
230
|
+
if (ca === undefined && cb === undefined) {
|
|
231
|
+
comps.set(a, compId);
|
|
232
|
+
comps.set(b, compId);
|
|
233
|
+
compId++;
|
|
234
|
+
}
|
|
235
|
+
else if (ca !== undefined && cb === undefined) {
|
|
236
|
+
comps.set(b, ca);
|
|
237
|
+
}
|
|
238
|
+
else if (cb !== undefined && ca === undefined) {
|
|
239
|
+
comps.set(a, cb);
|
|
240
|
+
}
|
|
241
|
+
// both defined: merge would be needed but keep simple
|
|
242
|
+
}
|
|
243
|
+
const sccCount = compId > 0 ? compId : S;
|
|
244
|
+
// Branching factor
|
|
245
|
+
let totalOut = 0, nonZeroOut = 0;
|
|
246
|
+
for (let i = 0; i < S; i++) {
|
|
247
|
+
let out = 0;
|
|
248
|
+
for (let j = 0; j < S; j++)
|
|
249
|
+
if (matrix[i][j] > 0)
|
|
250
|
+
out++;
|
|
251
|
+
totalOut += out;
|
|
252
|
+
if (out > 0)
|
|
253
|
+
nonZeroOut++;
|
|
254
|
+
}
|
|
255
|
+
const avgBranching = nonZeroOut > 0 ? totalOut / nonZeroOut : 0;
|
|
256
|
+
// Density
|
|
257
|
+
const maxEdges = S * (S - 1);
|
|
258
|
+
const edgeCount = undirected.size;
|
|
259
|
+
const density = maxEdges > 0 ? edgeCount / maxEdges : 0;
|
|
260
|
+
return { isDAG, diameter, sccCount, avgBranching, density };
|
|
261
|
+
}
|
|
262
|
+
// ═══════════════════════════════════════════════════════════════
|
|
263
|
+
// Public API
|
|
264
|
+
// ═══════════════════════════════════════════════════════════════
|
|
265
|
+
/**
|
|
266
|
+
* Infer a state machine from raw call sequences.
|
|
267
|
+
*
|
|
268
|
+
* ZERO dependency on function names. The same sequences with scrambled
|
|
269
|
+
* names produce structurally identical state machines.
|
|
270
|
+
*
|
|
271
|
+
* @param sequences Array of call sequences (e.g., [["open","read","close"],...])
|
|
272
|
+
* @returns InferredStateMachine with opaque state nodes
|
|
273
|
+
*/
|
|
274
|
+
function inferStateMachine(sequences) {
|
|
275
|
+
if (sequences.length === 0) {
|
|
276
|
+
return {
|
|
277
|
+
fnCount: 0, stateCount: 0, states: [],
|
|
278
|
+
stateTransitions: [], isDAG: true, diameter: 0, sccCount: 0, avgBranching: 0,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
// Build function index
|
|
282
|
+
const fnIndex = new Map();
|
|
283
|
+
for (const seq of sequences) {
|
|
284
|
+
for (const fn of seq) {
|
|
285
|
+
if (!fnIndex.has(fn))
|
|
286
|
+
fnIndex.set(fn, fnIndex.size);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Step 1: Role assignment
|
|
290
|
+
const roles = assignRoles(fnIndex, sequences);
|
|
291
|
+
// Step 2: Group into states
|
|
292
|
+
const groups = groupIntoStates(roles);
|
|
293
|
+
// If no clear groups found, create a simple linear state machine
|
|
294
|
+
if (groups.length === 0) {
|
|
295
|
+
return buildFallbackStateMachine(fnIndex, sequences);
|
|
296
|
+
}
|
|
297
|
+
// Step 3: Build state transition matrix
|
|
298
|
+
const stateTransitions = buildStateTransitions(groups, roles);
|
|
299
|
+
// Step 4: Extract stats
|
|
300
|
+
const stats = computeStateGraphStats(stateTransitions, groups);
|
|
301
|
+
// Build InferredState objects
|
|
302
|
+
const states = groups.map((g, i) => {
|
|
303
|
+
let inDeg = 0, outDeg = 0;
|
|
304
|
+
for (let j = 0; j < stateTransitions.length; j++) {
|
|
305
|
+
if (stateTransitions[j][i] > 0)
|
|
306
|
+
inDeg++;
|
|
307
|
+
if (stateTransitions[i][j] > 0)
|
|
308
|
+
outDeg++;
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
id: g.id,
|
|
312
|
+
members: g.members,
|
|
313
|
+
role: inDeg === 0 && outDeg > 0 ? "entry"
|
|
314
|
+
: outDeg === 0 && inDeg > 0 ? "exit"
|
|
315
|
+
: inDeg > 0 && outDeg > 0 ? "bridge"
|
|
316
|
+
: "isolated",
|
|
317
|
+
inDegree: inDeg,
|
|
318
|
+
outDegree: outDeg,
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
return {
|
|
322
|
+
fnCount: fnIndex.size,
|
|
323
|
+
stateCount: states.length,
|
|
324
|
+
states,
|
|
325
|
+
stateTransitions,
|
|
326
|
+
...stats,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
/** Fallback: build a simple linear chain when grouping fails. */
|
|
330
|
+
function buildFallbackStateMachine(fnIndex, sequences) {
|
|
331
|
+
// Find the longest sequence and use it as a template
|
|
332
|
+
const longest = sequences.reduce((a, b) => a.length >= b.length ? a : b, sequences[0] || []);
|
|
333
|
+
const S = Math.min(longest.length, 10);
|
|
334
|
+
const states = [];
|
|
335
|
+
const fnToState = new Map();
|
|
336
|
+
for (let i = 0; i < S; i++) {
|
|
337
|
+
const fnIdx = fnIndex.get(longest[i]);
|
|
338
|
+
fnToState.set(fnIdx, i);
|
|
339
|
+
states.push({
|
|
340
|
+
id: `F${i}`,
|
|
341
|
+
members: [fnIdx],
|
|
342
|
+
role: i === 0 ? "entry" : i === S - 1 ? "exit" : "bridge",
|
|
343
|
+
inDegree: i > 0 ? 1 : 0,
|
|
344
|
+
outDegree: i < S - 1 ? 1 : 0,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
const matrix = Array.from({ length: S }, () => new Array(S).fill(0));
|
|
348
|
+
for (let i = 0; i < S - 1; i++)
|
|
349
|
+
matrix[i][i + 1] = 1;
|
|
350
|
+
return {
|
|
351
|
+
fnCount: fnIndex.size,
|
|
352
|
+
stateCount: S,
|
|
353
|
+
states,
|
|
354
|
+
stateTransitions: matrix,
|
|
355
|
+
isDAG: true,
|
|
356
|
+
diameter: S - 1,
|
|
357
|
+
sccCount: S,
|
|
358
|
+
avgBranching: S > 0 ? (S - 1) / S : 0,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Extract a fixed-size fingerprint vector from an inferred state machine.
|
|
363
|
+
* All features are in [0,1] where possible.
|
|
364
|
+
*/
|
|
365
|
+
function extractStateFingerprint(sm) {
|
|
366
|
+
const S = sm.stateCount;
|
|
367
|
+
const entryCount = sm.states.filter(s => s.role === "entry").length;
|
|
368
|
+
const exitCount = sm.states.filter(s => s.role === "exit").length;
|
|
369
|
+
const bridgeCount = sm.states.filter(s => s.role === "bridge").length;
|
|
370
|
+
const isolatedCount = sm.states.filter(s => s.role === "isolated").length;
|
|
371
|
+
let totalTransitions = 0;
|
|
372
|
+
let selfLoopCount = 0;
|
|
373
|
+
for (let i = 0; i < S; i++) {
|
|
374
|
+
for (let j = 0; j < S; j++) {
|
|
375
|
+
if (sm.stateTransitions[i]?.[j] > 0) {
|
|
376
|
+
totalTransitions++;
|
|
377
|
+
if (i === j)
|
|
378
|
+
selfLoopCount++;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Degree entropy: Shannon entropy of in/out degree distributions
|
|
383
|
+
const inDegs = new Array(S).fill(0);
|
|
384
|
+
const outDegs = new Array(S).fill(0);
|
|
385
|
+
for (let i = 0; i < S; i++) {
|
|
386
|
+
for (let j = 0; j < S; j++) {
|
|
387
|
+
if (sm.stateTransitions[i]?.[j] > 0) {
|
|
388
|
+
outDegs[i]++;
|
|
389
|
+
inDegs[j]++;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const entropy = (counts) => {
|
|
394
|
+
const total = counts.reduce((a, b) => a + b, 0);
|
|
395
|
+
if (total === 0)
|
|
396
|
+
return 0;
|
|
397
|
+
return -counts
|
|
398
|
+
.filter(c => c > 0)
|
|
399
|
+
.map(c => c / total)
|
|
400
|
+
.reduce((s, p) => s - p * Math.log2(p), 0);
|
|
401
|
+
};
|
|
402
|
+
// Path length variance: BFS from all entry points
|
|
403
|
+
const pathLengths = [];
|
|
404
|
+
for (const entry of sm.states.filter(s => s.role === "entry")) {
|
|
405
|
+
const entryIdx = sm.states.indexOf(entry);
|
|
406
|
+
const dist = new Array(S).fill(-1);
|
|
407
|
+
const q = [entryIdx];
|
|
408
|
+
dist[entryIdx] = 0;
|
|
409
|
+
while (q.length > 0) {
|
|
410
|
+
const n = q.shift();
|
|
411
|
+
for (let j = 0; j < S; j++) {
|
|
412
|
+
if (sm.stateTransitions[n]?.[j] > 0 && dist[j] === -1) {
|
|
413
|
+
dist[j] = dist[n] + 1;
|
|
414
|
+
q.push(j);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
for (const d of dist)
|
|
419
|
+
if (d > 0)
|
|
420
|
+
pathLengths.push(d);
|
|
421
|
+
}
|
|
422
|
+
const avgPath = pathLengths.length > 0
|
|
423
|
+
? pathLengths.reduce((a, b) => a + b, 0) / pathLengths.length : 0;
|
|
424
|
+
const pathVariance = pathLengths.length > 1
|
|
425
|
+
? pathLengths.reduce((s, p) => s + (p - avgPath) ** 2, 0) / pathLengths.length : 0;
|
|
426
|
+
return {
|
|
427
|
+
stateCount: S,
|
|
428
|
+
fnCount: sm.fnCount,
|
|
429
|
+
transitionCount: totalTransitions,
|
|
430
|
+
entryCount,
|
|
431
|
+
exitCount,
|
|
432
|
+
bridgeCount,
|
|
433
|
+
isolatedCount,
|
|
434
|
+
entryRatio: S > 0 ? entryCount / S : 0,
|
|
435
|
+
exitRatio: S > 0 ? exitCount / S : 0,
|
|
436
|
+
isDAG: sm.isDAG,
|
|
437
|
+
diameter: sm.diameter,
|
|
438
|
+
sccCount: sm.sccCount,
|
|
439
|
+
avgBranching: Math.round(sm.avgBranching * 100) / 100,
|
|
440
|
+
density: Math.round((S > 1 ? totalTransitions / (S * (S - 1)) : 0) * 1000) / 1000,
|
|
441
|
+
selfLoopCount,
|
|
442
|
+
inDegreeEntropy: Math.round(entropy(inDegs) * 1000) / 1000,
|
|
443
|
+
outDegreeEntropy: Math.round(entropy(outDegs) * 1000) / 1000,
|
|
444
|
+
pathVariance: Math.round(pathVariance * 1000) / 1000,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Convert fingerprint to a 14-dim numeric vector for similarity comparison.
|
|
449
|
+
*/
|
|
450
|
+
function stateFingerprintToVector(fp) {
|
|
451
|
+
return [
|
|
452
|
+
fp.stateCount,
|
|
453
|
+
fp.fnCount,
|
|
454
|
+
fp.transitionCount,
|
|
455
|
+
fp.entryCount,
|
|
456
|
+
fp.exitCount,
|
|
457
|
+
fp.bridgeCount,
|
|
458
|
+
fp.isolatedCount,
|
|
459
|
+
fp.entryRatio,
|
|
460
|
+
fp.exitRatio,
|
|
461
|
+
fp.isDAG ? 1 : 0,
|
|
462
|
+
fp.diameter,
|
|
463
|
+
fp.sccCount,
|
|
464
|
+
fp.avgBranching,
|
|
465
|
+
fp.density,
|
|
466
|
+
fp.selfLoopCount,
|
|
467
|
+
fp.inDegreeEntropy,
|
|
468
|
+
fp.outDegreeEntropy,
|
|
469
|
+
fp.pathVariance,
|
|
470
|
+
];
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Cosine similarity between two state fingerprints.
|
|
474
|
+
*/
|
|
475
|
+
function stateFingerprintSimilarity(a, b) {
|
|
476
|
+
const va = stateFingerprintToVector(a);
|
|
477
|
+
const vb = stateFingerprintToVector(b);
|
|
478
|
+
let dot = 0, normA = 0, normB = 0;
|
|
479
|
+
for (let i = 0; i < va.length; i++) {
|
|
480
|
+
dot += va[i] * vb[i];
|
|
481
|
+
normA += va[i] * va[i];
|
|
482
|
+
normB += vb[i] * vb[i];
|
|
483
|
+
}
|
|
484
|
+
if (normA === 0 && normB === 0)
|
|
485
|
+
return 1;
|
|
486
|
+
if (normA === 0 || normB === 0)
|
|
487
|
+
return 0;
|
|
488
|
+
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
489
|
+
}
|
|
490
|
+
// ═══════════════════════════════════════════════════════════════
|
|
491
|
+
// Reporting
|
|
492
|
+
// ═══════════════════════════════════════════════════════════════
|
|
493
|
+
function printInferredStateMachine(sm) {
|
|
494
|
+
console.log(`\n─── Inferred State Machine ───`);
|
|
495
|
+
console.log(` Functions: ${sm.fnCount}`);
|
|
496
|
+
console.log(` Inferred states: ${sm.stateCount}`);
|
|
497
|
+
console.log(` DAG: ${sm.isDAG}`);
|
|
498
|
+
console.log(` Diameter: ${sm.diameter}`);
|
|
499
|
+
console.log(` SCC count: ${sm.sccCount}`);
|
|
500
|
+
if (sm.states.length > 0) {
|
|
501
|
+
console.log(`\n States:`);
|
|
502
|
+
for (const s of sm.states) {
|
|
503
|
+
console.log(` ${s.id} (${s.role}): ${s.members.length} functions, in=${s.inDegree}, out=${s.outDegree}`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (sm.stateTransitions.length > 0 && sm.stateTransitions.length <= 10) {
|
|
507
|
+
console.log(`\n State transitions:`);
|
|
508
|
+
for (let i = 0; i < sm.stateTransitions.length; i++) {
|
|
509
|
+
for (let j = 0; j < sm.stateTransitions[i].length; j++) {
|
|
510
|
+
if (sm.stateTransitions[i][j] > 0) {
|
|
511
|
+
console.log(` S${i} → S${j} (${sm.stateTransitions[i][j]} edges)`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|