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.
Files changed (356) hide show
  1. package/README.md +108 -468
  2. package/dist/ablation-study.js +144 -0
  3. package/dist/ablation-study.test.js +18 -0
  4. package/dist/action-runtime.js +3 -1
  5. package/dist/active-learning.js +211 -0
  6. package/dist/analytics.js +139 -0
  7. package/dist/asset-factory.js +309 -0
  8. package/dist/asset-growth.js +244 -0
  9. package/dist/asset-promotion.js +382 -0
  10. package/dist/asset-quality.js +550 -0
  11. package/dist/audit/business-translator.js +285 -0
  12. package/dist/audit/cli.js +66 -0
  13. package/dist/audit/formatters/html.js +379 -0
  14. package/dist/audit/formatters/json.js +11 -0
  15. package/dist/audit/formatters/markdown.js +192 -0
  16. package/dist/audit/formatters/terminal.js +189 -0
  17. package/dist/audit/index.js +25 -0
  18. package/dist/audit/report-builder.js +318 -0
  19. package/dist/audit/types.js +8 -0
  20. package/dist/audit.js +3 -3
  21. package/dist/auto-benchmark-generator.js +137 -0
  22. package/dist/auto-benchmark-generator.test.js +45 -0
  23. package/dist/auto-protocol-synthesizer.js +362 -0
  24. package/dist/auto-protocol-synthesizer.test.js +82 -0
  25. package/dist/autonomous-patch.js +175 -0
  26. package/dist/autonomous-patch.test.js +128 -0
  27. package/dist/badge/badge-server.js +98 -0
  28. package/dist/behavior-miner.js +442 -0
  29. package/dist/belief-layer.js +475 -0
  30. package/dist/benchmark-count.js +5 -0
  31. package/dist/benchmark-generator.js +211 -0
  32. package/dist/benchmark-harness.js +201 -0
  33. package/dist/benchmark-pass-rate.js +7 -0
  34. package/dist/benchmark-report.js +8 -3
  35. package/dist/benchmark-save.js +14 -1
  36. package/dist/bootstrap-validation.js +197 -0
  37. package/dist/bootstrap-validation.test.js +51 -0
  38. package/dist/branch-ledger.js +1 -1
  39. package/dist/capability-gap.js +130 -0
  40. package/dist/certify-html.js +351 -0
  41. package/dist/certify.js +326 -0
  42. package/dist/check.js +4 -4
  43. package/dist/compliance-miner.js +447 -0
  44. package/dist/continuous-benchmark.js +194 -0
  45. package/dist/continuous-benchmark.test.js +116 -0
  46. package/dist/corpus-stats.js +173 -0
  47. package/dist/counterfactual-engine.js +288 -0
  48. package/dist/coverage-dashboard.js +109 -0
  49. package/dist/coverage-system.test.js +205 -0
  50. package/dist/cross-repo-precision.js +352 -0
  51. package/dist/cve-benchmark.js +180 -0
  52. package/dist/cve-benchmark.test.js +28 -0
  53. package/dist/cve-collector.js +73 -0
  54. package/dist/data-quality.js +141 -0
  55. package/dist/decision-engine.js +388 -0
  56. package/dist/derive-metadata.js +250 -0
  57. package/dist/difficulty-active.test.js +198 -0
  58. package/dist/difficulty-map.js +244 -0
  59. package/dist/discovery-analytics.js +125 -0
  60. package/dist/discovery-model.js +149 -0
  61. package/dist/discovery-optimize.test.js +199 -0
  62. package/dist/discovery-trace.js +276 -0
  63. package/dist/discovery-trace.test.js +97 -0
  64. package/dist/emitter.js +83 -1
  65. package/dist/enterprise-dashboard.js +405 -0
  66. package/dist/eval-hardening.js +297 -0
  67. package/dist/eval-hardening.test.js +85 -0
  68. package/dist/evaluation-campaign.js +359 -0
  69. package/dist/evaluation-campaign.test.js +181 -0
  70. package/dist/evidence-growth.js +143 -0
  71. package/dist/evidence-repository.js +209 -0
  72. package/dist/evidence-system.js +441 -0
  73. package/dist/execute.js +15 -7
  74. package/dist/experimental/software-physics.js +291 -0
  75. package/dist/experimental/state-inference.js +516 -0
  76. package/dist/experimental/unsupervised-physics.js +230 -0
  77. package/dist/extract-ir-python.js +54 -7
  78. package/dist/extract-ir.js +376 -12
  79. package/dist/failure-collector.js +2 -2
  80. package/dist/failure-corpus.js +322 -9
  81. package/dist/feedback.js +16 -5
  82. package/dist/feedback.test.js +49 -0
  83. package/dist/file-lock.js +1 -1
  84. package/dist/flywheel-batch.js +292 -0
  85. package/dist/frameworks/express-cli.js +237 -0
  86. package/dist/frameworks/express-detector.js +445 -0
  87. package/dist/frameworks/express-detector.test.js +206 -0
  88. package/dist/frameworks/index.js +30 -0
  89. package/dist/frameworks/nestjs-detector.js +302 -0
  90. package/dist/frameworks/trpc-detector.js +161 -0
  91. package/dist/frameworks/version-awareness.js +179 -0
  92. package/dist/function-synonyms.js +164 -0
  93. package/dist/function-synonyms.test.js +68 -0
  94. package/dist/generalization.test.js +352 -0
  95. package/dist/goal-annotator.js +113 -0
  96. package/dist/goal-planner.js +563 -0
  97. package/dist/gold-cve.js +164 -0
  98. package/dist/gold-cve.test.js +104 -0
  99. package/dist/gold-quality.js +206 -0
  100. package/dist/gold-tiers.js +241 -0
  101. package/dist/governance-dashboard.js +327 -0
  102. package/dist/graph-viz.js +240 -0
  103. package/dist/guided-frontier.js +195 -0
  104. package/dist/hierarchical-planner.js +148 -0
  105. package/dist/identifier-parser.js +260 -0
  106. package/dist/immune-metrics.js +93 -0
  107. package/dist/immune-receiver.js +158 -0
  108. package/dist/immune-reporter.js +1 -1
  109. package/dist/improvement-orchestrator.js +206 -0
  110. package/dist/inject-p0-vocabulary.js +300 -0
  111. package/dist/intent-parser.js +218 -0
  112. package/dist/invariant-algebra.js +476 -0
  113. package/dist/invariant-calculus.js +533 -0
  114. package/dist/ir-utils.js +70 -0
  115. package/dist/ir-utils.test.js +50 -0
  116. package/dist/knowledge-api.js +312 -0
  117. package/dist/knowledge-evolution.js +452 -0
  118. package/dist/knowledge-explorer.js +506 -0
  119. package/dist/knowledge-flywheel.js +274 -0
  120. package/dist/knowledge-governance.js +338 -0
  121. package/dist/knowledge-governance.test.js +150 -0
  122. package/dist/knowledge-graph.js +181 -0
  123. package/dist/knowledge-guided-synth.js +246 -0
  124. package/dist/knowledge-loop.test.js +77 -0
  125. package/dist/knowledge-object.js +316 -0
  126. package/dist/knowledge-package.js +98 -0
  127. package/dist/kpi-dashboard.js +561 -0
  128. package/dist/l3-cross-function.js +280 -0
  129. package/dist/learning-ranker.js +148 -0
  130. package/dist/learning-ranker.test.js +291 -0
  131. package/dist/ledger/accountability.js +322 -0
  132. package/dist/ledger/chain-builder.js +185 -0
  133. package/dist/ledger/cli.js +222 -0
  134. package/dist/ledger/index.js +13 -0
  135. package/dist/ledger/signatures.js +193 -0
  136. package/dist/ledger/types.js +9 -0
  137. package/dist/llm.js +74 -3
  138. package/dist/load-benchmarks.js +8 -3
  139. package/dist/logger.js +66 -0
  140. package/dist/logger.test.js +37 -0
  141. package/dist/logistic-reward.js +339 -0
  142. package/dist/logistic-reward.test.js +180 -0
  143. package/dist/macro-graph.js +193 -0
  144. package/dist/macro-repair.js +183 -0
  145. package/dist/mcp-server.mjs +1202 -483
  146. package/dist/memory-layer.js +42 -5
  147. package/dist/multi-repo-precision.js +422 -0
  148. package/dist/name-free-protocol.js +425 -0
  149. package/dist/name-free-protocol.test.js +170 -0
  150. package/dist/name-scrambling.js +138 -0
  151. package/dist/name-scrambling.test.js +16 -0
  152. package/dist/p3-observability.test.js +281 -0
  153. package/dist/p5-orchestrator.test.js +225 -0
  154. package/dist/pairwise-preference.js +294 -0
  155. package/dist/pairwise-preference.test.js +140 -0
  156. package/dist/planner-constraints.js +104 -0
  157. package/dist/planner-prompts.js +155 -0
  158. package/dist/planner-telemetry.js +415 -0
  159. package/dist/planner-trace.js +214 -0
  160. package/dist/planner.js +162 -167
  161. package/dist/plsb/artifact.js +116 -0
  162. package/dist/plsb/cli.js +71 -0
  163. package/dist/plsb/index.js +19 -0
  164. package/dist/plsb/leaderboard.js +249 -0
  165. package/dist/plsb/report-md.js +156 -0
  166. package/dist/plsb/schema.js +179 -0
  167. package/dist/plsb-benchmark.js +284 -0
  168. package/dist/plsb-benchmark.test.js +119 -0
  169. package/dist/policy/cli.js +134 -0
  170. package/dist/policy/engine.js +333 -0
  171. package/dist/policy/index.js +12 -0
  172. package/dist/policy/types.js +59 -0
  173. package/dist/policy-miner.js +505 -0
  174. package/dist/precision-analyze.js +229 -0
  175. package/dist/precision-benchmark.js +147 -0
  176. package/dist/precision-label-c.js +134 -0
  177. package/dist/precision-label.js +193 -0
  178. package/dist/precision-report-c.js +149 -0
  179. package/dist/precision-report.js +246 -0
  180. package/dist/progmune-status.js +108 -0
  181. package/dist/proof-engine.js +479 -0
  182. package/dist/proof-provenance.js +315 -0
  183. package/dist/protocol-coverage.js +294 -0
  184. package/dist/protocol-detector.js +1189 -0
  185. package/dist/protocol-embedding-expanded.js +297 -0
  186. package/dist/protocol-embedding-expanded.test.js +97 -0
  187. package/dist/protocol-embedding.js +195 -0
  188. package/dist/protocol-embedding.test.js +82 -0
  189. package/dist/protocol-extractor-v2.js +354 -0
  190. package/dist/protocol-extractor-v2.test.js +140 -0
  191. package/dist/protocol-extractor.js +310 -0
  192. package/dist/protocol-extractor.test.js +113 -0
  193. package/dist/protocol-foundation.js +322 -0
  194. package/dist/protocol-foundation.test.js +163 -0
  195. package/dist/protocol-frontier.js +243 -0
  196. package/dist/protocol-frontier.test.js +92 -0
  197. package/dist/protocol-gap-analyzer.js +228 -0
  198. package/dist/protocol-gap-analyzer.test.js +49 -0
  199. package/dist/protocol-invariants.js +276 -0
  200. package/dist/protocol-invariants.test.js +111 -0
  201. package/dist/protocol-knowledge.js +464 -0
  202. package/dist/protocol-miner.js +343 -0
  203. package/dist/protocol-mining.js +207 -0
  204. package/dist/protocol-mining.test.js +37 -0
  205. package/dist/protocol-registry.js +1 -1
  206. package/dist/protocol-security-benchmark.js +222 -0
  207. package/dist/protocol-vulnerability.js +257 -0
  208. package/dist/protocol-vulnerability.test.js +60 -0
  209. package/dist/python-benchmark.js +120 -0
  210. package/dist/python-emitter.js +163 -45
  211. package/dist/python-protocol-extractor.js +187 -0
  212. package/dist/python-protocol-extractor.test.js +116 -0
  213. package/dist/realworld-benchmark.js +646 -0
  214. package/dist/realworld-benchmark.test.js +36 -0
  215. package/dist/repair-arch.test.js +411 -0
  216. package/dist/repair-evolution.test.js +454 -0
  217. package/dist/repair-executor.js +719 -0
  218. package/dist/repair-proposal.js +4 -4
  219. package/dist/repair-ranker.js +141 -0
  220. package/dist/repair-strategies.js +419 -0
  221. package/dist/repair-taxonomy.js +234 -0
  222. package/dist/repair-types.js +12 -0
  223. package/dist/repo-evaluator.js +250 -0
  224. package/dist/repo-evaluator.test.js +128 -0
  225. package/dist/resource-abstraction.js +242 -0
  226. package/dist/resource-detector.js +211 -0
  227. package/dist/result.test.js +43 -0
  228. package/dist/reward-system.js +411 -0
  229. package/dist/reward-system.test.js +175 -0
  230. package/dist/risk-model.js +215 -0
  231. package/dist/rule-miner.js +234 -7
  232. package/dist/rule-specificity.js +254 -0
  233. package/dist/runtime-types.js +27 -0
  234. package/dist/scaffold.js +208 -0
  235. package/dist/scale-collector.test.js +101 -0
  236. package/dist/scale-trajectory-collector.js +128 -0
  237. package/dist/sdk.js +250 -0
  238. package/dist/search-planner.js +4 -41
  239. package/dist/semantic-snapshot.js +1 -1
  240. package/dist/semantic-topology.js +121 -0
  241. package/dist/semantic-trace.js +310 -317
  242. package/dist/sequence-extractor.js +343 -0
  243. package/dist/skill-library.js +245 -0
  244. package/dist/skill-planner.test.js +189 -0
  245. package/dist/software-physics.js +291 -0
  246. package/dist/software-physics.test.js +81 -0
  247. package/dist/ssg-precision.js +478 -0
  248. package/dist/ssg-validator.js +71 -21
  249. package/dist/state-inference-doubleblind.test.js +160 -0
  250. package/dist/state-inference.js +516 -0
  251. package/dist/state-inference.test.js +115 -0
  252. package/dist/state-machine-fingerprint.js +345 -0
  253. package/dist/state-machine-fingerprint.test.js +120 -0
  254. package/dist/state-miner.js +386 -0
  255. package/dist/state-name-inference.js +213 -0
  256. package/dist/state-name-inference.test.js +69 -0
  257. package/dist/strategy-planner.js +262 -96
  258. package/dist/strategy-planner.test.js +135 -0
  259. package/dist/telemetry-analytics.test.js +402 -0
  260. package/dist/terminal-format.js +68 -0
  261. package/dist/terminal-format.test.js +83 -0
  262. package/dist/topology-factory.js +196 -0
  263. package/dist/topology-representation.js +242 -0
  264. package/dist/topology-representation.test.js +27 -0
  265. package/dist/trajectory-augmentation.js +254 -0
  266. package/dist/trajectory-augmentation.test.js +63 -0
  267. package/dist/trajectory-corpus.js +440 -0
  268. package/dist/trajectory-corpus.test.js +32 -0
  269. package/dist/trajectory-feedback.test.js +116 -0
  270. package/dist/transition-synthesizer.js +286 -0
  271. package/dist/transition-synthesizer.test.js +123 -0
  272. package/dist/trust/api-semantic-mapper.js +809 -0
  273. package/dist/trust/call-graph-propagator.js +225 -0
  274. package/dist/trust/cli.js +122 -0
  275. package/dist/trust/compliance-scorer.js +283 -0
  276. package/dist/trust/confidence-calculator.js +261 -0
  277. package/dist/trust/engine.js +1145 -0
  278. package/dist/trust/explainability.js +85 -0
  279. package/dist/trust/formatters/ci.js +42 -0
  280. package/dist/trust/formatters/json.js +11 -0
  281. package/dist/trust/formatters/terminal.js +152 -0
  282. package/dist/trust/index.js +39 -0
  283. package/dist/trust/phase1-verify.js +171 -0
  284. package/dist/trust/protocol-domain-validator.js +697 -0
  285. package/dist/trust/score-calculator.js +282 -0
  286. package/dist/trust/ssg-bridge.js +641 -0
  287. package/dist/trust/ssg-bridge.test.js +269 -0
  288. package/dist/trust/types.js +67 -0
  289. package/dist/trust/violation-trace.js +335 -0
  290. package/dist/trust-api.js +179 -0
  291. package/dist/trust-calibration.js +279 -0
  292. package/dist/unknown-protocol-discovery.js +339 -0
  293. package/dist/unknown-protocol-discovery.test.js +102 -0
  294. package/dist/unsupervised-physics.js +230 -0
  295. package/dist/unsupervised-physics.test.js +95 -0
  296. package/dist/utils.test.js +37 -0
  297. package/dist/validator.js +187 -10
  298. package/dist/verification-intelligence.js +475 -0
  299. package/dist/verify-api.js +432 -0
  300. package/dist/vi-impact-report.js +293 -0
  301. package/dist/wl-fingerprint.js +162 -0
  302. package/dist/wl-fingerprint.test.js +130 -0
  303. package/dist/zeroshot-strategy.js +139 -0
  304. 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
  305. package/docs/Progmune_/351/241/271/347/233/256/345/205/250/350/247/243.html +710 -0
  306. package/package.json +74 -7
  307. package/protocols.json +1956 -50
  308. package/.dockerignore +0 -14
  309. package/.mcp.json +0 -11
  310. package/.progmune_allowlist +0 -50
  311. package/.test_report/test_report.md +0 -87
  312. package/Dockerfile +0 -9
  313. package/FAQ.md +0 -167
  314. package/WHITEPAPER.md +0 -540
  315. package/demo-project/auth.ts +0 -55
  316. package/demo-project/tsconfig.json +0 -8
  317. package/dist/acl-breakdown.js +0 -13
  318. package/dist/all-sessions.js +0 -11
  319. package/dist/antibody-stats.js +0 -11
  320. package/dist/branch-tree-count.js +0 -14
  321. package/dist/common-fixpath.js +0 -12
  322. package/dist/constraint-types.js +0 -12
  323. package/dist/exec-metrics.js +0 -11
  324. package/dist/failure-report.js +0 -11
  325. package/dist/fast-path-hits.js +0 -13
  326. package/dist/fingerprint-list.js +0 -15
  327. package/dist/gen-history-log.js +0 -13
  328. package/dist/heatmap-data.js +0 -11
  329. package/dist/recent-session.js +0 -12
  330. package/dist/svl-distribution.js +0 -11
  331. package/dist/terminal-status.js +0 -11
  332. package/dist/token-savings.js +0 -11
  333. package/dist/total-repairs.js +0 -12
  334. package/dist/unresolved-count.js +0 -12
  335. package/dist/valid-fingerprints.js +0 -13
  336. package/dist/verify-ledgers.js +0 -11
  337. package/docs/whitepaper-style.css +0 -77
  338. package/docs/whitepaper-v2.1.md +0 -609
  339. package/docs/whitepaper-v2.2.md +0 -1064
  340. package/docs/whitepaper-v2.2.pdf +0 -0
  341. package/fly.toml +0 -31
  342. package/public/dashboard.html +0 -119
  343. package/server/hub.js +0 -116
  344. package/test/replay-golden/sess_1780063202050_mgeld.json +0 -9
  345. package/test/replay-golden/sess_1780064032560_gocld.json +0 -354
  346. package/test/replay-golden/sess_1780064413331_s2709.json +0 -606
  347. package/test/replay-golden/sess_1780064792710_y3avo.json +0 -614
  348. package/test/replay-golden.ts +0 -84
  349. package/test_benchmark.js +0 -165
  350. package/test_comprehensive.mjs +0 -638
  351. package/test_concurrency.js +0 -129
  352. package/test_ir_robustness.js +0 -85
  353. package/test_semantic_contracts.js +0 -269
  354. package/test_ssg_stress.js +0 -156
  355. package/test_svl3.js +0 -58
  356. 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
+ }