progmune-runtime 2.0.4 → 2.1.1

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