progmune-runtime 2.0.2 → 2.1.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 (107) hide show
  1. package/.mcp.json +11 -0
  2. package/.progmune_allowlist +50 -0
  3. package/.test_report/test_report.md +87 -0
  4. package/Dockerfile +2 -10
  5. package/FAQ.md +167 -0
  6. package/demo-project/auth.ts +55 -0
  7. package/demo-project/tsconfig.json +8 -0
  8. package/dist/ab-stats.js +11 -0
  9. package/dist/acl-breakdown.js +13 -0
  10. package/dist/all-sessions.js +11 -0
  11. package/dist/antibody-stats.js +11 -0
  12. package/dist/audit.js +218 -0
  13. package/dist/benchmark-count.js +7 -0
  14. package/dist/benchmark-full.js +17 -0
  15. package/dist/benchmark-pass-rate.js +54 -0
  16. package/dist/benchmark-report.js +67 -0
  17. package/dist/benchmark-save.js +62 -0
  18. package/dist/benchmark-status.js +15 -0
  19. package/dist/branch-ledger.js +365 -0
  20. package/dist/branch-tree-count.js +14 -0
  21. package/dist/check.js +506 -0
  22. package/dist/common-fixpath.js +12 -0
  23. package/dist/constraint-types.js +12 -0
  24. package/dist/deterministic-replay.js +277 -0
  25. package/dist/emitter.js +112 -12
  26. package/dist/exec-metrics.js +11 -0
  27. package/dist/execute.js +242 -0
  28. package/dist/extract-ir.js +550 -5
  29. package/dist/failure-collector.js +143 -0
  30. package/dist/failure-corpus.js +481 -35
  31. package/dist/failure-report.js +11 -0
  32. package/dist/failures.js +11 -0
  33. package/dist/fast-path-hits.js +13 -0
  34. package/dist/feedback.js +6 -3
  35. package/dist/file-lock.js +82 -0
  36. package/dist/find-session.js +10 -0
  37. package/dist/fingerprint-list.js +15 -0
  38. package/dist/gen-history-log.js +13 -0
  39. package/dist/generate.js +2 -1
  40. package/dist/generate_500.js +4 -2
  41. package/dist/genome.js +11 -0
  42. package/dist/heatmap-data.js +11 -0
  43. package/dist/heatmap.js +11 -0
  44. package/dist/immune-reporter.js +137 -0
  45. package/dist/learned.js +11 -0
  46. package/dist/ledger-registry.js +241 -0
  47. package/dist/llm.js +43 -2
  48. package/dist/load-benchmarks.js +47 -0
  49. package/dist/main.js +2 -1
  50. package/dist/mcp-server.mjs +446 -34
  51. package/dist/memory-layer.js +51 -13
  52. package/dist/metrics.js +11 -0
  53. package/dist/obs-web.js +561 -0
  54. package/dist/p0_ssg_demo.js +255 -40
  55. package/dist/planner.js +933 -65
  56. package/dist/protocol-registry.js +105 -0
  57. package/dist/recent-session.js +12 -0
  58. package/dist/repair-proposal.js +353 -0
  59. package/dist/report.js +32 -0
  60. package/dist/runtime-invariants.js +161 -0
  61. package/dist/runtime-types.js +117 -0
  62. package/dist/search-planner.js +38 -9
  63. package/dist/semantic-snapshot.js +155 -0
  64. package/dist/semantic-trace.js +1497 -0
  65. package/dist/semantic-validator.js +3 -2
  66. package/dist/semantic_guard_test.js +2 -1
  67. package/dist/sessions.js +11 -0
  68. package/dist/ssg-validator.js +658 -20
  69. package/dist/svl-distribution.js +11 -0
  70. package/dist/terminal-status.js +11 -0
  71. package/dist/test_failure_corpus.js +3 -1
  72. package/dist/token-savings.js +11 -0
  73. package/dist/total-repairs.js +12 -0
  74. package/dist/unresolved-count.js +12 -0
  75. package/dist/valid-fingerprints.js +13 -0
  76. package/dist/validator.js +116 -60
  77. package/dist/verify-fps.js +11 -0
  78. package/dist/verify-ledgers.js +11 -0
  79. package/docs/whitepaper-style.css +77 -0
  80. package/docs/whitepaper-v2.1.md +609 -0
  81. package/docs/whitepaper-v2.2.md +1064 -0
  82. package/docs/whitepaper-v2.2.pdf +0 -0
  83. package/fly.toml +1 -1
  84. package/package.json +13 -4
  85. package/protocols.json +136 -0
  86. package/public/dashboard.html +119 -0
  87. package/readme.md +829 -0
  88. package/server/hub.js +84 -12
  89. package/test/replay-golden/sess_1780063202050_mgeld.json +9 -0
  90. package/test/replay-golden/sess_1780064032560_gocld.json +354 -0
  91. package/test/replay-golden/sess_1780064413331_s2709.json +606 -0
  92. package/test/replay-golden/sess_1780064792710_y3avo.json +614 -0
  93. package/test/replay-golden.ts +84 -0
  94. package/test_benchmark.js +165 -0
  95. package/test_comprehensive.mjs +638 -0
  96. package/test_concurrency.js +129 -0
  97. package/test_ir_robustness.js +85 -0
  98. package/test_semantic_contracts.js +269 -0
  99. package/test_ssg_stress.js +156 -0
  100. package/test_svl3.js +58 -0
  101. package/tsconfig.json +1 -1
  102. package/.env.example +0 -3
  103. package/.progmune_memory/fingerprints.json +0 -7
  104. package/.progmune_memory/opt_in.json +0 -4
  105. package/README.md +0 -118
  106. package/dist/mcp-server.js +0 -55
  107. package/immune_hub_data/2026-05-14.json +0 -50
@@ -1,32 +1,670 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StateMachineValidator = void 0;
36
+ exports.StateMachineValidator = exports.InvariantViolationError = void 0;
37
+ exports.rebuildState = rebuildState;
38
+ exports.applyTransitionDelta = applyTransitionDelta;
39
+ exports.findFixPathStatic = findFixPathStatic;
40
+ exports.validateTransition = validateTransition;
41
+ exports.checkLedgerConsistency = checkLedgerConsistency;
42
+ exports.hashRules = hashRules;
43
+ exports.hashLedger = hashLedger;
44
+ exports.diffLedgers = diffLedgers;
45
+ exports.findProducer = findProducer;
46
+ exports.findConsumer = findConsumer;
47
+ exports.findViolations = findViolations;
48
+ exports.findTransition = findTransition;
49
+ exports.listAllStates = listAllStates;
50
+ exports.explainRejection = explainRejection;
51
+ exports.rejectionToJSON = rejectionToJSON;
52
+ exports.parseProtocolsFromJSON = parseProtocolsFromJSON;
53
+ const crypto = __importStar(require("crypto"));
54
+ const DEFAULT_NAMESPACE = "_global";
55
+ class InvariantViolationError extends Error {
56
+ constructor(message, detail) {
57
+ super(message);
58
+ this.name = "InvariantViolationError";
59
+ this.detail = detail;
60
+ }
61
+ }
62
+ exports.InvariantViolationError = InvariantViolationError;
63
+ // ═══════════════════════════════════════════════════════════════
64
+ // Phase 3: Pure Functions — Semantic Ledger Kernel
65
+ // ═══════════════════════════════════════════════════════════════
66
+ // ── Internal helpers ──
67
+ function toSnapshot(stateMap) {
68
+ const snap = {};
69
+ for (const [ns, states] of stateMap) {
70
+ snap[ns] = [...states].sort();
71
+ }
72
+ return snap;
73
+ }
74
+ function fromSnapshot(snap) {
75
+ const map = new Map();
76
+ for (const [ns, states] of Object.entries(snap)) {
77
+ map.set(ns, new Set(states));
78
+ }
79
+ return map;
80
+ }
81
+ function deepEqualSnapshots(a, b) {
82
+ const keysA = Object.keys(a).sort();
83
+ const keysB = Object.keys(b).sort();
84
+ if (keysA.length !== keysB.length)
85
+ return false;
86
+ for (let i = 0; i < keysA.length; i++) {
87
+ if (keysA[i] !== keysB[i])
88
+ return false;
89
+ const statesA = a[keysA[i]];
90
+ const statesB = b[keysA[i]];
91
+ if (statesA.length !== statesB.length)
92
+ return false;
93
+ for (let j = 0; j < statesA.length; j++) {
94
+ if (statesA[j] !== statesB[j])
95
+ return false;
96
+ }
97
+ }
98
+ return true;
99
+ }
100
+ // ── rebuildState: pure fold over ledger → per-namespace state snapshot ──
101
+ function rebuildState(ledger, namespaceInitialStates = new Map([["_global", "INIT"]])) {
102
+ const stateMap = fromSnapshot({});
103
+ for (const [ns, initState] of namespaceInitialStates) {
104
+ stateMap.set(ns, new Set([initState]));
105
+ }
106
+ if (!stateMap.has("_global"))
107
+ stateMap.set("_global", new Set(["INIT"]));
108
+ for (const t of ledger) {
109
+ if (!t.valid)
110
+ continue;
111
+ applyTransitionDelta(stateMap, t);
112
+ }
113
+ return toSnapshot(stateMap);
114
+ }
115
+ // ── applyTransitionDelta: incremental primitive for O(n) loops ──
116
+ function applyTransitionDelta(stateMap, transition) {
117
+ const ns = transition.namespace || DEFAULT_NAMESPACE;
118
+ const nsStates = stateMap.get(ns) || new Set();
119
+ for (const s of transition.invalidated)
120
+ nsStates.delete(s);
121
+ for (const s of transition.acquired)
122
+ nsStates.add(s);
123
+ stateMap.set(ns, nsStates);
124
+ }
125
+ // ── computeDelta: derive acquired/invalidated from before/after for one namespace ──
126
+ function computeDelta(beforeSnap, afterSnap, namespace) {
127
+ const before = beforeSnap[namespace] || [];
128
+ const after = afterSnap[namespace] || [];
129
+ const acquired = after.filter(s => !before.includes(s));
130
+ const invalidated = before.filter(s => !after.includes(s));
131
+ return { acquired, invalidated };
132
+ }
133
+ // ── findFixPathStatic: BFS state graph search (extracted from class) ──
134
+ function findFixPathStatic(rules, namespace, current, targetPreStates) {
135
+ const nsFuncs = [];
136
+ for (const [fn, rule] of rules) {
137
+ if ((rule.namespace || DEFAULT_NAMESPACE) === namespace) {
138
+ nsFuncs.push({ name: fn, rule });
139
+ }
140
+ }
141
+ // BFS
142
+ const startKey = [...new Set(current)].sort().join(",");
143
+ const visited = new Set();
144
+ const queue = [
145
+ { states: new Set(current), path: [] }
146
+ ];
147
+ visited.add(startKey);
148
+ while (queue.length > 0) {
149
+ const { states, path } = queue.shift();
150
+ if (targetPreStates.every(s => states.has(s)))
151
+ return path;
152
+ for (const { name, rule } of nsFuncs) {
153
+ if (!rule.pre_states.every(p => states.has(p)))
154
+ continue;
155
+ const nextStates = new Set(states);
156
+ if (rule.invalidate)
157
+ rule.invalidate.forEach(s => nextStates.delete(s));
158
+ rule.post_states.forEach(s => nextStates.add(s));
159
+ const nextKey = [...nextStates].sort().join(",");
160
+ if (visited.has(nextKey))
161
+ continue;
162
+ visited.add(nextKey);
163
+ if (visited.size > 1000)
164
+ break;
165
+ queue.push({ states: nextStates, path: [...path, name] });
166
+ }
167
+ }
168
+ // Fallback: single-hop greedy
169
+ const path = [];
170
+ const currentSet = new Set(current);
171
+ for (const target of targetPreStates) {
172
+ if (currentSet.has(target))
173
+ continue;
174
+ for (const { name, rule } of nsFuncs) {
175
+ if (rule.post_states.includes(target)) {
176
+ path.push(name);
177
+ if (rule.invalidate)
178
+ rule.invalidate.forEach(s => currentSet.delete(s));
179
+ rule.post_states.forEach(s => currentSet.add(s));
180
+ break;
181
+ }
182
+ }
183
+ }
184
+ return path;
185
+ }
186
+ // ── validateTransition: pure function, stateless ──
187
+ function validateTransition(ctx, candidateFunctionName, actionIndex, rules, namespaceInitialStates, ruleHash) {
188
+ const currentState = ctx.currentState;
189
+ const rule = rules.get(candidateFunctionName);
190
+ if (!rule) {
191
+ const transition = {
192
+ actionIndex,
193
+ function: candidateFunctionName,
194
+ namespace: DEFAULT_NAMESPACE,
195
+ acquired: [],
196
+ invalidated: [],
197
+ statesBefore: currentState,
198
+ statesAfter: currentState,
199
+ valid: true,
200
+ ruleHash,
201
+ };
202
+ // Invariant-1: delta consistency for no-rule transition (trivially satisfied)
203
+ return { valid: true, transition };
204
+ }
205
+ const ns = rule.namespace || DEFAULT_NAMESPACE;
206
+ const nsStates = new Set(currentState[ns] || []);
207
+ // Check pre-states
208
+ if (!rule.pre_states.every((s) => nsStates.has(s))) {
209
+ const fixPath = findFixPathStatic(rules, ns, [...nsStates], rule.pre_states);
210
+ const rejection = {
211
+ blocked: candidateFunctionName,
212
+ currentState: [...nsStates],
213
+ requiredState: rule.pre_states,
214
+ missingFunctions: fixPath,
215
+ fixPath,
216
+ namespace: ns,
217
+ };
218
+ const transition = {
219
+ actionIndex,
220
+ function: candidateFunctionName,
221
+ namespace: ns,
222
+ acquired: [],
223
+ invalidated: [],
224
+ statesBefore: currentState,
225
+ statesAfter: currentState, // no state change on rejection
226
+ valid: false,
227
+ ruleHash,
228
+ };
229
+ return { valid: false, transition, rejection };
230
+ }
231
+ // Valid — compute state change
232
+ const beforeNs = [...nsStates];
233
+ if (rule.invalidate)
234
+ rule.invalidate.forEach((s) => nsStates.delete(s));
235
+ rule.post_states.forEach((s) => nsStates.add(s));
236
+ // Build statesAfter: copy currentState, replace this namespace
237
+ const statesAfter = {};
238
+ for (const nsKey of Object.keys(currentState)) {
239
+ statesAfter[nsKey] = nsKey === ns
240
+ ? [...nsStates].sort()
241
+ : [...(currentState[nsKey] || [])];
242
+ }
243
+ if (!statesAfter[ns]) {
244
+ statesAfter[ns] = [...nsStates].sort();
245
+ }
246
+ const afterNs = statesAfter[ns] || [];
247
+ const acquired = afterNs.filter((s) => !beforeNs.includes(s));
248
+ const invalidated = beforeNs.filter((s) => !afterNs.includes(s));
249
+ const transition = {
250
+ actionIndex,
251
+ function: candidateFunctionName,
252
+ namespace: ns,
253
+ acquired,
254
+ invalidated,
255
+ statesBefore: currentState,
256
+ statesAfter,
257
+ valid: true,
258
+ ruleHash,
259
+ };
260
+ // Invariant-1: delta consistency
261
+ const deltaMap = fromSnapshot(currentState);
262
+ applyTransitionDelta(deltaMap, transition);
263
+ const computedAfter = toSnapshot(deltaMap);
264
+ if (!deepEqualSnapshots(computedAfter, statesAfter)) {
265
+ const detail = `[Invariant-1] Delta consistency violation in validateTransition for "${candidateFunctionName}":\n` +
266
+ ` acquired: [${acquired.join(", ")}] invalidated: [${invalidated.join(", ")}]\n` +
267
+ ` expected after: ${JSON.stringify(computedAfter)}\n` +
268
+ ` actual after: ${JSON.stringify(statesAfter)}`;
269
+ // P0 Strict Mode: fail fast on kernel corruption
270
+ if (process.env.PROGMUNE_STRICT !== "false") {
271
+ throw new InvariantViolationError(detail, {
272
+ invariant: "delta-consistency",
273
+ namespace: ns,
274
+ function: candidateFunctionName,
275
+ expected: computedAfter,
276
+ actual: statesAfter,
277
+ });
278
+ }
279
+ console.error(detail);
280
+ }
281
+ return { valid: true, transition };
282
+ }
283
+ // ── checkLedgerConsistency: Invariant-0 + Invariant-1 over full ledger ──
284
+ function checkLedgerConsistency(ledger, namespaceInitialStates = new Map([["_global", "INIT"]])) {
285
+ const violations = [];
286
+ const running = fromSnapshot({});
287
+ // Pre-scan: collect all namespaces referenced in any transition
288
+ const allNamespaces = new Set(namespaceInitialStates.keys());
289
+ for (const t of ledger) {
290
+ for (const ns of Object.keys(t.statesBefore))
291
+ allNamespaces.add(ns);
292
+ for (const ns of Object.keys(t.statesAfter))
293
+ allNamespaces.add(ns);
294
+ allNamespaces.add(t.namespace);
295
+ }
296
+ // Initialize running state: init from namespaceInitialStates, empty for others
297
+ for (const ns of allNamespaces) {
298
+ if (namespaceInitialStates.has(ns)) {
299
+ running.set(ns, new Set([namespaceInitialStates.get(ns)]));
300
+ }
301
+ else {
302
+ running.set(ns, new Set());
303
+ }
304
+ }
305
+ // Normalize a snapshot: ensure all known namespaces are present
306
+ function normalizeSnap(snap) {
307
+ const out = {};
308
+ for (const ns of allNamespaces) {
309
+ out[ns] = [...(snap[ns] || [])].sort();
310
+ }
311
+ return out;
312
+ }
313
+ for (let i = 0; i < ledger.length; i++) {
314
+ const t = ledger[i];
315
+ // Invariant-0: statesBefore must equal rebuildState(ledger[0..i-1])
316
+ const expectedBefore = normalizeSnap(toSnapshot(running));
317
+ const actualBefore = normalizeSnap(t.statesBefore);
318
+ if (!deepEqualSnapshots(expectedBefore, actualBefore)) {
319
+ violations.push({
320
+ index: i,
321
+ invariant: "before-consistency",
322
+ expected: expectedBefore,
323
+ actual: actualBefore,
324
+ detail: `Transition[${i}] "${t.function}": statesBefore does not match rebuilt state from previous ledger`,
325
+ });
326
+ }
327
+ // Invariant-1: statesAfter must equal applyDelta(statesBefore, acquired, invalidated)
328
+ if (t.valid) {
329
+ const beforeMap = fromSnapshot(t.statesBefore);
330
+ applyTransitionDelta(beforeMap, t);
331
+ const expectedAfter = normalizeSnap(toSnapshot(beforeMap));
332
+ const actualAfter = normalizeSnap(t.statesAfter);
333
+ if (!deepEqualSnapshots(expectedAfter, actualAfter)) {
334
+ violations.push({
335
+ index: i,
336
+ invariant: "delta-consistency",
337
+ expected: expectedAfter,
338
+ actual: actualAfter,
339
+ detail: `Transition[${i}] "${t.function}": statesAfter does not match applyDelta(statesBefore, acquired, invalidated)`,
340
+ });
341
+ }
342
+ }
343
+ // Advance running state
344
+ if (t.valid) {
345
+ applyTransitionDelta(running, t);
346
+ }
347
+ }
348
+ return { consistent: violations.length === 0, violations };
349
+ }
350
+ // ── hashRules: stable hash of rule set for constraint snapshot (P1) ──
351
+ function hashRules(rules) {
352
+ const sorted = [...rules.entries()]
353
+ .sort(([a], [b]) => a.localeCompare(b))
354
+ .map(([name, rule]) => ({
355
+ function: name,
356
+ pre_states: [...rule.pre_states].sort(),
357
+ post_states: [...rule.post_states].sort(),
358
+ invalidate: rule.invalidate ? [...rule.invalidate].sort() : undefined,
359
+ namespace: rule.namespace || DEFAULT_NAMESPACE,
360
+ }));
361
+ return crypto.createHash("sha256").update(JSON.stringify(sorted)).digest("hex").slice(0, 16);
362
+ }
363
+ /** Compute a deterministic SHA256 hash of an entire ledger (P1: Tamper-evident integrity). */
364
+ function hashLedger(ledger) {
365
+ const canonical = ledger.map(t => ({
366
+ actionIndex: t.actionIndex,
367
+ function: t.function,
368
+ namespace: t.namespace,
369
+ acquired: [...t.acquired].sort(),
370
+ invalidated: [...t.invalidated].sort(),
371
+ statesBefore: Object.fromEntries(Object.entries(t.statesBefore).map(([k, v]) => [k, [...v].sort()]).sort()),
372
+ statesAfter: Object.fromEntries(Object.entries(t.statesAfter).map(([k, v]) => [k, [...v].sort()]).sort()),
373
+ valid: t.valid,
374
+ ruleHash: t.ruleHash || "",
375
+ }));
376
+ return crypto.createHash("sha256").update(JSON.stringify(canonical)).digest("hex").slice(0, 16);
377
+ }
378
+ /** Compare two ledgers and identify structural differences. */
379
+ function diffLedgers(ledgerA, ledgerB) {
380
+ const hash = (t) => crypto.createHash("sha256").update(JSON.stringify({
381
+ i: t.actionIndex, f: t.function, n: t.namespace,
382
+ a: [...t.acquired].sort(), x: [...t.invalidated].sort(),
383
+ v: t.valid, r: t.ruleHash || "",
384
+ })).digest("hex").slice(0, 12);
385
+ const mapA = new Map();
386
+ const mapB = new Map();
387
+ for (const t of ledgerA)
388
+ mapA.set(t.actionIndex, { t, h: hash(t) });
389
+ for (const t of ledgerB)
390
+ mapB.set(t.actionIndex, { t, h: hash(t) });
391
+ const allIndices = new Set([...mapA.keys(), ...mapB.keys()]);
392
+ const unchanged = [];
393
+ const onlyInA = [];
394
+ const onlyInB = [];
395
+ const changed = [];
396
+ for (const idx of [...allIndices].sort((a, b) => a - b)) {
397
+ const a = mapA.get(idx);
398
+ const b = mapB.get(idx);
399
+ if (a && b) {
400
+ if (a.h === b.h) {
401
+ unchanged.push(idx);
402
+ }
403
+ else {
404
+ changed.push({ index: idx, function: a.t.function, hashA: a.h, hashB: b.h });
405
+ }
406
+ }
407
+ else if (a && !b) {
408
+ onlyInA.push({ index: idx, function: a.t.function, hashA: a.h });
409
+ }
410
+ else if (!a && b) {
411
+ onlyInB.push({ index: idx, function: b.t.function, hashB: b.h });
412
+ }
413
+ }
414
+ return {
415
+ unchanged: unchanged.length,
416
+ onlyInA,
417
+ onlyInB,
418
+ changed,
419
+ identical: onlyInA.length === 0 && onlyInB.length === 0 && changed.length === 0,
420
+ };
421
+ }
422
+ /** Find all transitions that acquire (produce) a given state in the ledger. */
423
+ function findProducer(state, ledger) {
424
+ return ledger
425
+ .map((t, i) => ({ transition: t, index: i, namespace: t.namespace }))
426
+ .filter(r => r.transition.acquired.includes(state));
427
+ }
428
+ /** Find all transitions that have a given state in their pre_states (consume it). */
429
+ function findConsumer(state, ledger) {
430
+ return ledger
431
+ .map((t, i) => ({ transition: t, index: i, namespace: t.namespace }))
432
+ .filter(r => (r.transition.statesBefore[r.namespace] || []).includes(state));
433
+ }
434
+ /** Find all invalid transitions in a ledger. */
435
+ function findViolations(ledger) {
436
+ return ledger
437
+ .map((t, i) => ({ transition: t, index: i, namespace: t.namespace }))
438
+ .filter(r => !r.transition.valid);
439
+ }
440
+ /** Find a transition by its action index. */
441
+ function findTransition(actionIndex, ledger) {
442
+ const idx = ledger.findIndex(t => t.actionIndex === actionIndex);
443
+ if (idx === -1)
444
+ return null;
445
+ return { transition: ledger[idx], index: idx, namespace: ledger[idx].namespace };
446
+ }
447
+ /** List all unique states present across all namespaces in a ledger. */
448
+ function listAllStates(ledger) {
449
+ const seen = new Set();
450
+ const result = [];
451
+ for (const t of ledger) {
452
+ const ns = t.namespace;
453
+ for (const s of t.acquired) {
454
+ const key = `${ns}:${s}`;
455
+ if (!seen.has(key)) {
456
+ seen.add(key);
457
+ result.push({ namespace: ns, state: s });
458
+ }
459
+ }
460
+ for (const s of t.invalidated) {
461
+ const key = `${ns}:${s}`;
462
+ if (!seen.has(key)) {
463
+ seen.add(key);
464
+ result.push({ namespace: ns, state: s });
465
+ }
466
+ }
467
+ for (const states of Object.values(t.statesBefore)) {
468
+ for (const s of states) {
469
+ const key = `${ns}:${s}`;
470
+ if (!seen.has(key)) {
471
+ seen.add(key);
472
+ result.push({ namespace: ns, state: s });
473
+ }
474
+ }
475
+ }
476
+ }
477
+ return result;
478
+ }
479
+ // ═══════════════════════════════════════════════════════════════
480
+ // StateMachineValidator — backward-compatible class wrapper
481
+ // Delegates to pure functions internally (Strangler Pattern)
482
+ // ═══════════════════════════════════════════════════════════════
4
483
  class StateMachineValidator {
5
- constructor(rules, initialState = 'INIT') {
6
- this.currentStates = new Set([initialState]);
484
+ constructor(rules, initialState = 'INIT', namespaceInitialStates) {
485
+ /** Internal ledger — the truth source. State is derived from this. */
486
+ this.ledger = [];
7
487
  this.rules = new Map();
8
- rules.forEach(r => this.rules.set(r.function, r.protocol));
9
- }
10
- apply(functionName) {
11
- const rule = this.rules.get(functionName);
12
- if (!rule) {
13
- return { valid: true, statesAfter: [...this.currentStates] };
14
- }
15
- const hasValidPreState = rule.pre_states.some(s => this.currentStates.has(s));
16
- if (!hasValidPreState) {
17
- return {
18
- valid: false,
19
- error: `非法调用:${functionName} 要求前置状态 [${rule.pre_states}],当前状态为 [${[...this.currentStates]}]`
488
+ rules.forEach(r => {
489
+ this.rules.set(r.function, r.protocol);
490
+ });
491
+ this.nsInitialStates = new Map(namespaceInitialStates);
492
+ if (!this.nsInitialStates.has("_global")) {
493
+ this.nsInitialStates.set("_global", initialState);
494
+ }
495
+ // Pre-compute rule hash for constraint snapshot determinism
496
+ this._ruleHash = hashRules(this.rules);
497
+ // Initialize ledger and context from namespace initial states
498
+ this.ctx = {
499
+ ledger: [],
500
+ currentState: rebuildState([], this.nsInitialStates),
501
+ };
502
+ }
503
+ /** @deprecated Use validateTransition(ctx, ...) for stateless validation */
504
+ setNamespaceInitialState(namespace, state) {
505
+ const ns = namespace || DEFAULT_NAMESPACE;
506
+ this.nsInitialStates.set(ns, state);
507
+ // Rebuild context to reflect new initial state
508
+ this.ctx = {
509
+ ledger: this.ledger,
510
+ currentState: rebuildState(this.ledger, this.nsInitialStates),
511
+ };
512
+ }
513
+ /**
514
+ * Validate a function call against current protocol state.
515
+ * Internally delegates to the pure validateTransition() function.
516
+ * @deprecated Prefer validateTransition() directly for stateless validation.
517
+ */
518
+ apply(functionName, actionIndex) {
519
+ const idx = actionIndex ?? this.ledger.length;
520
+ const { valid, transition, rejection } = validateTransition(this.ctx, functionName, idx, this.rules, this.nsInitialStates, this._ruleHash);
521
+ // Append to ledger (truth source)
522
+ this.ledger.push(transition);
523
+ // Update context incrementally (O(1) per step)
524
+ if (transition.valid) {
525
+ const stateMap = fromSnapshot(this.ctx.currentState);
526
+ applyTransitionDelta(stateMap, transition);
527
+ this.ctx = {
528
+ ledger: this.ledger,
529
+ currentState: toSnapshot(stateMap),
20
530
  };
21
531
  }
22
- if (rule.invalidate) {
23
- rule.invalidate.forEach(s => this.currentStates.delete(s));
532
+ else {
533
+ this.ctx = {
534
+ ledger: this.ledger,
535
+ currentState: this.ctx.currentState, // unchanged on rejection
536
+ };
24
537
  }
25
- rule.post_states.forEach(s => this.currentStates.add(s));
26
- return { valid: true, statesAfter: [...this.currentStates] };
538
+ return {
539
+ valid,
540
+ statesBefore: transition.statesBefore,
541
+ statesAfter: transition.statesAfter,
542
+ acquired: transition.acquired,
543
+ invalidated: transition.invalidated,
544
+ namespace: transition.namespace,
545
+ rejection,
546
+ };
547
+ }
548
+ applyWithTransition(functionName, actionIndex) {
549
+ const result = this.apply(functionName, actionIndex);
550
+ const transition = this.ledger[this.ledger.length - 1];
551
+ return { result, transition };
27
552
  }
28
553
  getCurrentStates() {
29
- return [...this.currentStates];
554
+ const effective = new Set();
555
+ for (const states of Object.values(this.ctx.currentState)) {
556
+ for (const s of states)
557
+ effective.add(s);
558
+ }
559
+ return [...effective];
560
+ }
561
+ snapshotNamespaceStates() {
562
+ return rebuildState(this.ledger, this.nsInitialStates);
563
+ }
564
+ getNamespaceStates(namespace) {
565
+ const ns = namespace || DEFAULT_NAMESPACE;
566
+ return [...(this.ctx.currentState[ns] || [])];
567
+ }
568
+ /** Returns trace reconstructed from the ledger (backward-compat) */
569
+ getTrace() {
570
+ // Replay the ledger to build trace nodes
571
+ const trace = [];
572
+ const runningState = fromSnapshot({});
573
+ for (const [ns, initState] of this.nsInitialStates) {
574
+ runningState.set(ns, new Set([initState]));
575
+ }
576
+ if (!runningState.has("_global"))
577
+ runningState.set("_global", new Set(["INIT"]));
578
+ for (const t of this.ledger) {
579
+ const node = {
580
+ function: t.function,
581
+ valid: t.valid,
582
+ statesBefore: t.statesBefore,
583
+ statesAfter: t.statesAfter,
584
+ acquired: t.acquired,
585
+ invalidated: t.invalidated,
586
+ namespace: t.namespace,
587
+ };
588
+ trace.push(node);
589
+ if (t.valid) {
590
+ applyTransitionDelta(runningState, t);
591
+ }
592
+ }
593
+ return trace;
594
+ }
595
+ /** Returns the internal ledger (Phase 3 API) */
596
+ getLedger() {
597
+ return [...this.ledger];
598
+ }
599
+ /** Returns the current ValidationContext (Phase 3 API) */
600
+ getContext() {
601
+ return { ...this.ctx, ledger: [...this.ctx.ledger] };
602
+ }
603
+ /** Returns the pre-computed rule hash (Phase 3 API) */
604
+ getRuleHash() {
605
+ return this._ruleHash;
606
+ }
607
+ // ── Static utilities (delegate to standalone functions) ──
608
+ static explainRejection(rejection) {
609
+ return explainRejection(rejection);
610
+ }
611
+ static rejectionToJSON(rejection) {
612
+ return rejectionToJSON(rejection);
30
613
  }
31
614
  }
32
615
  exports.StateMachineValidator = StateMachineValidator;
616
+ // ═══════════════════════════════════════════════════════════════
617
+ // Standalone presentation utilities (formerly static methods)
618
+ // ═══════════════════════════════════════════════════════════════
619
+ /** Format an SSG rejection as a human-readable multi-line string. */
620
+ function explainRejection(rejection) {
621
+ const nsLabel = rejection.namespace && rejection.namespace !== DEFAULT_NAMESPACE
622
+ ? ` [namespace: ${rejection.namespace}]` : '';
623
+ const lines = [
624
+ `🚫 SSG 协议拦截: ${rejection.blocked}${nsLabel}`,
625
+ ``,
626
+ ` 当前状态: ${rejection.currentState.join(', ') || '(无)'}`,
627
+ ` 所需状态: ${rejection.requiredState.join(', ')}`,
628
+ ``,
629
+ ];
630
+ if (rejection.missingFunctions.length > 0) {
631
+ lines.push(` 缺失步骤: ${rejection.missingFunctions.join(' → ')}`);
632
+ }
633
+ if (rejection.fixPath.length > 0) {
634
+ lines.push(` 修复路径: ${rejection.fixPath.join(' → ')}`);
635
+ }
636
+ return lines.join('\n');
637
+ }
638
+ /** Format an SSG rejection as a structured JSON object. */
639
+ function rejectionToJSON(rejection) {
640
+ return {
641
+ protocol_violation: {
642
+ blocked_function: rejection.blocked,
643
+ namespace: rejection.namespace,
644
+ current_state: rejection.currentState,
645
+ required_pre_states: rejection.requiredState,
646
+ },
647
+ diagnosis: {
648
+ missing_functions: rejection.missingFunctions,
649
+ fix_path: rejection.fixPath,
650
+ },
651
+ };
652
+ }
653
+ // ═══════════════════════════════════════════════════════════════
654
+ // parseProtocolsFromJSON (unchanged)
655
+ // ═══════════════════════════════════════════════════════════════
656
+ function parseProtocolsFromJSON(protocolDef) {
657
+ const protocols = [];
658
+ for (const [funcName, rule] of Object.entries(protocolDef.rules)) {
659
+ protocols.push({
660
+ function: funcName,
661
+ protocol: {
662
+ pre_states: rule.pre_states,
663
+ post_states: rule.post_states,
664
+ invalidate: rule.invalidate,
665
+ namespace: rule.namespace,
666
+ },
667
+ });
668
+ }
669
+ return protocols;
670
+ }