@neuroverseos/nv-sim 0.1.2 → 0.1.6

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 (52) hide show
  1. package/README.md +376 -66
  2. package/dist/adapters/mirofish.js +461 -0
  3. package/dist/adapters/scienceclaw.js +750 -0
  4. package/dist/assets/index-CHmUN8s0.js +532 -0
  5. package/dist/assets/index-DWgMnB7I.css +1 -0
  6. package/dist/assets/mirotir-logo-DUexumBH.svg +185 -0
  7. package/dist/assets/reportEngine-BVdQ2_nW.js +1 -0
  8. package/dist/components/ConstraintsPanel.js +11 -0
  9. package/dist/components/StakeholderBuilder.js +32 -0
  10. package/dist/components/ui/badge.js +24 -0
  11. package/dist/components/ui/button.js +70 -0
  12. package/dist/components/ui/card.js +57 -0
  13. package/dist/components/ui/input.js +44 -0
  14. package/dist/components/ui/label.js +45 -0
  15. package/dist/components/ui/select.js +70 -0
  16. package/dist/engine/aiProvider.js +681 -0
  17. package/dist/engine/auditTrace.js +352 -0
  18. package/dist/engine/behavioralAnalysis.js +605 -0
  19. package/dist/engine/cli.js +1408 -299
  20. package/dist/engine/dynamicsGovernance.js +588 -0
  21. package/dist/engine/fullGovernedLoop.js +367 -0
  22. package/dist/engine/governance.js +8 -3
  23. package/dist/engine/governedSimulation.js +114 -17
  24. package/dist/engine/index.js +56 -1
  25. package/dist/engine/liveAdapter.js +342 -0
  26. package/dist/engine/liveVisualizer.js +3063 -0
  27. package/dist/engine/metrics/science.metrics.js +335 -0
  28. package/dist/engine/narrativeInjection.js +305 -0
  29. package/dist/engine/policyEnforcement.js +1611 -0
  30. package/dist/engine/policyEngine.js +799 -0
  31. package/dist/engine/primeRadiant.js +540 -0
  32. package/dist/engine/reasoningEngine.js +57 -3
  33. package/dist/engine/reportEngine.js +97 -0
  34. package/dist/engine/scenarioComparison.js +463 -0
  35. package/dist/engine/scenarioLibrary.js +231 -0
  36. package/dist/engine/swarmSimulation.js +54 -1
  37. package/dist/engine/worldComparison.js +358 -0
  38. package/dist/engine/worldStorage.js +232 -0
  39. package/dist/favicon.ico +0 -0
  40. package/dist/index.html +23 -0
  41. package/dist/lib/reasoningEngine.js +290 -0
  42. package/dist/lib/simulationAdapter.js +686 -0
  43. package/dist/lib/swarmParser.js +291 -0
  44. package/dist/lib/types.js +2 -0
  45. package/dist/lib/utils.js +8 -0
  46. package/dist/placeholder.svg +1 -0
  47. package/dist/robots.txt +14 -0
  48. package/dist/runtime/govern.js +473 -0
  49. package/dist/runtime/index.js +75 -0
  50. package/dist/runtime/types.js +11 -0
  51. package/package.json +17 -12
  52. package/variants/.gitkeep +0 -0
@@ -0,0 +1,799 @@
1
+ "use strict";
2
+ /**
3
+ * Policy Enforcement Engine
4
+ *
5
+ * The full pipeline: Upload → Parse → Validate → Simulate → Compare
6
+ *
7
+ * "Drop in your policy. We'll show you what happens.
8
+ * Before it actually happens."
9
+ *
10
+ * This engine doesn't just parse policy text — it pressure-tests it.
11
+ * It detects conflicts, weak rules, over-constrained systems,
12
+ * and shows you baseline vs. policy outcomes with real metrics.
13
+ *
14
+ * Diagnostic model inspired by VS Code / GitHub:
15
+ * - Errors (red): Conflicts, structural issues that block simulation
16
+ * - Warnings (yellow): Overlapping rules, potential issues
17
+ * - Info (blue): Advisory-only rules, suggestions
18
+ * - Hints (gray): Optimization opportunities
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.parseRulesFromText = parseRulesFromText;
22
+ exports.validatePolicy = validatePolicy;
23
+ exports.applyQuickFix = applyQuickFix;
24
+ exports.policyToWorld = policyToWorld;
25
+ exports.runPolicyPipeline = runPolicyPipeline;
26
+ const governedSimulation_1 = require("./governedSimulation");
27
+ // ============================================
28
+ // RULE PARSING ENGINE
29
+ // ============================================
30
+ /** Intent patterns for classifying rules */
31
+ const INTENT_PATTERNS = [
32
+ {
33
+ category: "prohibit",
34
+ patterns: [
35
+ /\b(block|prohibit|forbid|prevent|disallow|ban|never|must\s+not|shall\s+not|cannot)\b/i,
36
+ ],
37
+ enforcement: "structural",
38
+ },
39
+ {
40
+ category: "limit",
41
+ patterns: [
42
+ /\b(limit|cap|restrict|maximum|max|ceiling|no\s+more\s+than|not\s+exceed|threshold)\b/i,
43
+ ],
44
+ enforcement: "structural",
45
+ },
46
+ {
47
+ category: "require",
48
+ patterns: [
49
+ /\b(require|mandate|must|shall|obligat|enforce|compulsory|necessary)\b/i,
50
+ ],
51
+ enforcement: "structural",
52
+ },
53
+ {
54
+ category: "circuit_breaker",
55
+ patterns: [
56
+ /\b(circuit\s*breaker|halt|pause|freeze|stop|suspend|emergency|shutdown)\b/i,
57
+ ],
58
+ enforcement: "structural",
59
+ },
60
+ {
61
+ category: "monitor",
62
+ patterns: [
63
+ /\b(monitor|track|report|audit|log|observe|surveil|watch|alert)\b/i,
64
+ ],
65
+ enforcement: "structural",
66
+ },
67
+ {
68
+ category: "encourage",
69
+ patterns: [
70
+ /\b(encourage|promote|suggest|recommend|prefer|advise|should|guideline|best\s+practice)\b/i,
71
+ ],
72
+ enforcement: "advisory",
73
+ },
74
+ ];
75
+ /** Keywords for variable detection */
76
+ const VARIABLE_KEYWORDS = {
77
+ liquidity: ["liquidity", "liquid", "cash", "capital", "reserve"],
78
+ leverage: ["leverage", "leveraged", "margin", "borrowed", "debt"],
79
+ volatility: ["volatility", "volatile", "vix", "fluctuation", "swing", "spike"],
80
+ contagion: ["contagion", "spread", "cascade", "chain", "domino", "systemic"],
81
+ trade: ["trade", "trading", "transaction", "order", "position", "buy", "sell"],
82
+ price: ["price", "cost", "valuation", "rate", "tariff"],
83
+ risk: ["risk", "exposure", "hazard", "danger", "threat"],
84
+ stability: ["stability", "stable", "equilibrium", "balance"],
85
+ intervention: ["intervention", "intervene", "central bank", "regulator", "authority"],
86
+ panic: ["panic", "fear", "sentiment", "confidence", "trust"],
87
+ };
88
+ /**
89
+ * Parse raw policy text into structured rules.
90
+ *
91
+ * Handles .txt and .md formats.
92
+ * Each line or bullet point is treated as a potential rule.
93
+ */
94
+ function parseRulesFromText(text) {
95
+ const lines = text.split("\n").filter((l) => l.trim().length > 0);
96
+ const rules = [];
97
+ let ruleIndex = 0;
98
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
99
+ const raw = lines[lineIdx].trim();
100
+ // Skip headers, comments, empty lines
101
+ if (raw.startsWith("#") && raw.length < 80 && !raw.includes("must") && !raw.includes("shall"))
102
+ continue;
103
+ if (raw.startsWith("//") || raw.startsWith("<!--"))
104
+ continue;
105
+ if (raw.length < 10)
106
+ continue;
107
+ // Strip markdown bullets and numbering
108
+ const cleaned = raw
109
+ .replace(/^[-*+]\s+/, "")
110
+ .replace(/^\d+[.)]\s+/, "")
111
+ .replace(/^>\s+/, "")
112
+ .replace(/\*\*/g, "")
113
+ .replace(/\*/g, "")
114
+ .trim();
115
+ if (cleaned.length < 10)
116
+ continue;
117
+ // Classify intent
118
+ let matchedCategory = "encourage";
119
+ let matchedEnforcement = "advisory";
120
+ let bestConfidence = 0.3;
121
+ for (const pattern of INTENT_PATTERNS) {
122
+ for (const regex of pattern.patterns) {
123
+ if (regex.test(cleaned)) {
124
+ matchedCategory = pattern.category;
125
+ matchedEnforcement = pattern.enforcement;
126
+ bestConfidence = Math.max(bestConfidence, 0.7 + Math.random() * 0.25);
127
+ break;
128
+ }
129
+ }
130
+ if (bestConfidence > 0.7)
131
+ break;
132
+ }
133
+ // Detect affected variables
134
+ const affectedVars = [];
135
+ const keywords = [];
136
+ const cleanedLower = cleaned.toLowerCase();
137
+ for (const [varName, kws] of Object.entries(VARIABLE_KEYWORDS)) {
138
+ for (const kw of kws) {
139
+ if (cleanedLower.includes(kw)) {
140
+ if (!affectedVars.includes(varName))
141
+ affectedVars.push(varName);
142
+ if (!keywords.includes(kw))
143
+ keywords.push(kw);
144
+ }
145
+ }
146
+ }
147
+ ruleIndex++;
148
+ rules.push({
149
+ id: `RULE-${String(ruleIndex).padStart(3, "0")}`,
150
+ originalText: raw,
151
+ description: cleaned,
152
+ enforcement: matchedEnforcement,
153
+ category: matchedCategory,
154
+ confidence: Number(bestConfidence.toFixed(2)),
155
+ affectedVariables: affectedVars,
156
+ keywords,
157
+ sourceLine: lineIdx + 1,
158
+ });
159
+ }
160
+ return {
161
+ rules,
162
+ summary: {
163
+ total: rules.length,
164
+ enforced: rules.filter((r) => r.enforcement === "structural").length,
165
+ advisory: rules.filter((r) => r.enforcement === "advisory").length,
166
+ },
167
+ sourceText: text,
168
+ };
169
+ }
170
+ /**
171
+ * Detect conflicts and overlaps between parsed rules.
172
+ */
173
+ function detectConflicts(rules) {
174
+ const conflicts = [];
175
+ for (let i = 0; i < rules.length; i++) {
176
+ for (let j = i + 1; j < rules.length; j++) {
177
+ const a = rules[i];
178
+ const b = rules[j];
179
+ // Check for shared variables
180
+ const sharedVars = a.affectedVariables.filter((v) => b.affectedVariables.includes(v));
181
+ if (sharedVars.length === 0)
182
+ continue;
183
+ // Direct conflict: one blocks, other allows the same thing
184
+ if ((a.category === "prohibit" && (b.category === "encourage" || b.category === "require")) ||
185
+ (b.category === "prohibit" && (a.category === "encourage" || a.category === "require"))) {
186
+ conflicts.push({
187
+ ruleA: a,
188
+ ruleB: b,
189
+ type: "direct_conflict",
190
+ description: `${a.id} ${a.category}s ${sharedVars.join(", ")} while ${b.id} ${b.category}s it`,
191
+ });
192
+ continue;
193
+ }
194
+ // Overlap: both rules constrain the same variable in different ways
195
+ if (a.category === b.category &&
196
+ sharedVars.length > 0 &&
197
+ a.description !== b.description) {
198
+ conflicts.push({
199
+ ruleA: a,
200
+ ruleB: b,
201
+ type: "overlap",
202
+ description: `Both ${a.id} and ${b.id} ${a.category} on "${sharedVars.join(", ")}" — may cause redundancy or conflict`,
203
+ });
204
+ continue;
205
+ }
206
+ // Subsumption: one rule is broader than the other
207
+ if (a.affectedVariables.length > b.affectedVariables.length &&
208
+ b.affectedVariables.every((v) => a.affectedVariables.includes(v))) {
209
+ conflicts.push({
210
+ ruleA: a,
211
+ ruleB: b,
212
+ type: "subsumption",
213
+ description: `${a.id} may already cover what ${b.id} specifies — ${b.id} may be redundant`,
214
+ });
215
+ }
216
+ }
217
+ }
218
+ return conflicts;
219
+ }
220
+ // ============================================
221
+ // POLICY VALIDATION (HEALTH CHECK)
222
+ // ============================================
223
+ /**
224
+ * Run a full policy health check.
225
+ *
226
+ * Produces VS Code/GitHub-style diagnostics:
227
+ * - Errors: Conflicts that block simulation
228
+ * - Warnings: Overlaps, weak enforcement
229
+ * - Info: Advisory rules, suggestions
230
+ * - Hints: Optimization opportunities
231
+ */
232
+ function validatePolicy(parsed) {
233
+ const diagnostics = [];
234
+ let diagIndex = 0;
235
+ const nextId = () => `POL-${String(++diagIndex).padStart(3, "0")}`;
236
+ // --- Conflict detection ---
237
+ const conflicts = detectConflicts(parsed.rules);
238
+ for (const conflict of conflicts) {
239
+ if (conflict.type === "direct_conflict") {
240
+ // Build merged replacement: keep the more specific rule, add condition
241
+ const sharedVars = conflict.ruleA.affectedVariables.filter((v) => conflict.ruleB.affectedVariables.includes(v));
242
+ const mergedText = `Limit ${sharedVars.join(" and ")} during volatility spikes with strict thresholds`;
243
+ diagnostics.push({
244
+ id: nextId(),
245
+ severity: "error",
246
+ message: `Conflicting rules: ${conflict.ruleA.id} vs ${conflict.ruleB.id}`,
247
+ detail: conflict.description,
248
+ relatedRules: [conflict.ruleA.id, conflict.ruleB.id],
249
+ fix: `Resolve conflict: remove one rule, or add a condition (e.g., "${conflict.ruleB.id} applies only during volatility")`,
250
+ source: {
251
+ startLine: conflict.ruleA.sourceLine,
252
+ endLine: conflict.ruleB.sourceLine,
253
+ },
254
+ category: "conflict",
255
+ quickFix: {
256
+ label: "Merge into single rule",
257
+ operation: "replace",
258
+ text: mergedText,
259
+ targetLines: [conflict.ruleA.sourceLine, conflict.ruleB.sourceLine],
260
+ },
261
+ });
262
+ }
263
+ else if (conflict.type === "overlap") {
264
+ const sharedVars = conflict.ruleA.affectedVariables.filter((v) => conflict.ruleB.affectedVariables.includes(v));
265
+ const mergedText = `Limit ${sharedVars.join(" and ")} with unified thresholds across all conditions`;
266
+ diagnostics.push({
267
+ id: nextId(),
268
+ severity: "warning",
269
+ message: `Overlapping enforcement: ${conflict.ruleA.id} and ${conflict.ruleB.id}`,
270
+ detail: conflict.description,
271
+ relatedRules: [conflict.ruleA.id, conflict.ruleB.id],
272
+ fix: "Consider merging into a single rule or adding distinct conditions",
273
+ source: {
274
+ startLine: conflict.ruleA.sourceLine,
275
+ endLine: conflict.ruleB.sourceLine,
276
+ },
277
+ category: "conflict",
278
+ quickFix: {
279
+ label: "Merge rules",
280
+ operation: "replace",
281
+ text: mergedText,
282
+ targetLines: [conflict.ruleA.sourceLine, conflict.ruleB.sourceLine],
283
+ },
284
+ });
285
+ }
286
+ else {
287
+ diagnostics.push({
288
+ id: nextId(),
289
+ severity: "hint",
290
+ message: `${conflict.ruleB.id} may be redundant (subsumed by ${conflict.ruleA.id})`,
291
+ detail: conflict.description,
292
+ relatedRules: [conflict.ruleA.id, conflict.ruleB.id],
293
+ category: "structure",
294
+ quickFix: {
295
+ label: "Remove redundant rule",
296
+ operation: "remove",
297
+ text: "",
298
+ targetLines: [conflict.ruleB.sourceLine],
299
+ },
300
+ });
301
+ }
302
+ }
303
+ // --- Weak policy detection ---
304
+ if (parsed.summary.enforced === 0 && parsed.rules.length > 0) {
305
+ // Build strengthened versions of the first few advisory rules
306
+ const strengthened = parsed.rules.slice(0, 3).map((r) => {
307
+ const desc = r.description.toLowerCase();
308
+ if (desc.includes("encourage") || desc.includes("promote"))
309
+ return r.description.replace(/\b(encourage|promote)\b/i, "Require");
310
+ if (desc.includes("should"))
311
+ return r.description.replace(/\bshould\b/i, "Must");
312
+ return `Limit ${r.affectedVariables[0] ?? "system behavior"} to prevent instability`;
313
+ });
314
+ diagnostics.push({
315
+ id: nextId(),
316
+ severity: "error",
317
+ message: "Advisory only — no enforceable constraints detected",
318
+ detail: "This policy contains only advisory guidelines with no structural enforcement. " +
319
+ "The simulation will run but the policy will have no measurable effect on outcomes.",
320
+ relatedRules: parsed.rules.map((r) => r.id),
321
+ fix: 'Add enforceable rules using action verbs: "must", "shall", "limit", "block", "require"',
322
+ category: "strength",
323
+ quickFix: {
324
+ label: "Strengthen enforcement",
325
+ operation: "insert",
326
+ text: strengthened.join("\n"),
327
+ },
328
+ });
329
+ }
330
+ else if (parsed.summary.enforced < parsed.summary.total * 0.3 && parsed.rules.length > 2) {
331
+ // Upgrade the first advisory rule as an example
332
+ const firstAdvisory = parsed.rules.find((r) => r.enforcement === "advisory");
333
+ const upgraded = firstAdvisory
334
+ ? firstAdvisory.description.replace(/\b(encourage|promote|suggest|recommend|should)\b/i, "Require")
335
+ : "Require compliance with all governance constraints";
336
+ diagnostics.push({
337
+ id: nextId(),
338
+ severity: "warning",
339
+ message: `Weak enforcement: only ${parsed.summary.enforced}/${parsed.summary.total} rules are enforceable`,
340
+ detail: "Most rules in this policy are advisory. Simulations may show limited governance effect.",
341
+ relatedRules: parsed.rules.filter((r) => r.enforcement === "advisory").map((r) => r.id),
342
+ fix: "Strengthen advisory rules with specific thresholds and action verbs",
343
+ category: "strength",
344
+ quickFix: firstAdvisory ? {
345
+ label: "Strengthen weakest rule",
346
+ operation: "replace",
347
+ text: upgraded,
348
+ targetLines: [firstAdvisory.sourceLine],
349
+ } : {
350
+ label: "Add enforced rule",
351
+ operation: "insert",
352
+ text: upgraded,
353
+ },
354
+ });
355
+ }
356
+ // --- Over-constrained detection ---
357
+ const prohibitCount = parsed.rules.filter((r) => r.category === "prohibit").length;
358
+ const limitCount = parsed.rules.filter((r) => r.category === "limit").length;
359
+ const restrictiveRatio = (prohibitCount + limitCount) / Math.max(parsed.rules.length, 1);
360
+ if (restrictiveRatio > 0.8 && parsed.rules.length > 3) {
361
+ diagnostics.push({
362
+ id: nextId(),
363
+ severity: "warning",
364
+ message: "Policy may over-restrict system behavior",
365
+ detail: `${Math.round(restrictiveRatio * 100)}% of rules are prohibitive/restrictive. ` +
366
+ "This may cause system freeze where most actions are blocked, reducing the system's ability to respond to crises.",
367
+ relatedRules: parsed.rules
368
+ .filter((r) => r.category === "prohibit" || r.category === "limit")
369
+ .map((r) => r.id),
370
+ fix: "Balance restrictive rules with permissive ones: add monitoring, encourage, or conditional allowance rules",
371
+ category: "constraint",
372
+ quickFix: {
373
+ label: "Add flexibility rules",
374
+ operation: "insert",
375
+ text: "Allow normal trading activity under standard market conditions\nMonitor and report unusual activity patterns before restricting",
376
+ },
377
+ });
378
+ }
379
+ // --- Coverage analysis ---
380
+ const coveredVariables = new Set(parsed.rules.flatMap((r) => r.affectedVariables));
381
+ const criticalVariables = ["liquidity", "leverage", "volatility", "risk", "stability"];
382
+ const uncovered = criticalVariables.filter((v) => !coveredVariables.has(v));
383
+ if (uncovered.length > 0 && parsed.rules.length > 0) {
384
+ // Generate a rule for each uncovered variable
385
+ const coverageRules = uncovered.map((v) => {
386
+ switch (v) {
387
+ case "liquidity": return "Maintain minimum liquidity floor — no agent may drain liquidity below 15% of baseline";
388
+ case "leverage": return "Limit maximum leverage to 5x during elevated volatility conditions";
389
+ case "volatility": return "Halt automated trading when volatility index exceeds critical threshold";
390
+ case "risk": return "Require risk assessment reporting for all positions exceeding normal thresholds";
391
+ case "stability": return "Require rebalancing when system stability drops below acceptable levels";
392
+ default: return `Monitor and limit ${v} to prevent systemic issues`;
393
+ }
394
+ });
395
+ diagnostics.push({
396
+ id: nextId(),
397
+ severity: "info",
398
+ message: `Uncovered critical areas: ${uncovered.join(", ")}`,
399
+ detail: `The policy does not address: ${uncovered.join(", ")}. ` +
400
+ "These are common governance targets in financial/systemic scenarios.",
401
+ relatedRules: [],
402
+ fix: `Consider adding rules for: ${uncovered.map((v) => v.charAt(0).toUpperCase() + v.slice(1)).join(", ")}`,
403
+ category: "coverage",
404
+ quickFix: {
405
+ label: `Add ${uncovered.length} coverage rule${uncovered.length > 1 ? "s" : ""}`,
406
+ operation: "insert",
407
+ text: coverageRules.join("\n"),
408
+ },
409
+ });
410
+ }
411
+ // --- Low confidence rules ---
412
+ const lowConfRules = parsed.rules.filter((r) => r.confidence < 0.5);
413
+ if (lowConfRules.length > 0) {
414
+ // Rephrase the first low-confidence rule
415
+ const first = lowConfRules[0];
416
+ const rephrased = `Require ${first.affectedVariables[0] ?? "compliance"} controls to prevent ${first.keywords[0] ?? "violations"}`;
417
+ diagnostics.push({
418
+ id: nextId(),
419
+ severity: "hint",
420
+ message: `${lowConfRules.length} rule(s) parsed with low confidence`,
421
+ detail: "Some lines were difficult to classify. They may be descriptions rather than actionable rules.",
422
+ relatedRules: lowConfRules.map((r) => r.id),
423
+ fix: "Rephrase ambiguous rules with clear action verbs and specific thresholds",
424
+ category: "structure",
425
+ quickFix: {
426
+ label: "Rephrase ambiguous rule",
427
+ operation: "replace",
428
+ text: rephrased,
429
+ targetLines: [first.sourceLine],
430
+ },
431
+ });
432
+ }
433
+ // --- No rules at all ---
434
+ if (parsed.rules.length === 0) {
435
+ diagnostics.push({
436
+ id: nextId(),
437
+ severity: "error",
438
+ message: "No rules detected in policy document",
439
+ detail: "The uploaded document did not contain any recognizable policy rules.",
440
+ relatedRules: [],
441
+ fix: "Add policy rules as bullet points or numbered items with clear action verbs",
442
+ category: "structure",
443
+ quickFix: {
444
+ label: "Add starter policy",
445
+ operation: "insert",
446
+ text: "Block all large trades during extreme volatility\nLimit maximum leverage to 5x\nHalt automated trading when volatility exceeds critical threshold\nMaintain minimum liquidity floor at 15% of baseline\nMonitor and report all correlated position clusters",
447
+ },
448
+ });
449
+ }
450
+ // --- Circuit breaker check ---
451
+ const hasCircuitBreaker = parsed.rules.some((r) => r.category === "circuit_breaker");
452
+ if (!hasCircuitBreaker && parsed.rules.length > 2) {
453
+ diagnostics.push({
454
+ id: nextId(),
455
+ severity: "info",
456
+ message: "No circuit breaker / emergency halt rule detected",
457
+ detail: "The policy has no emergency shutdown mechanism. In extreme scenarios, the system has no last-resort protection.",
458
+ relatedRules: [],
459
+ fix: 'Add an emergency rule: "Halt all trading when volatility exceeds critical threshold"',
460
+ category: "coverage",
461
+ quickFix: {
462
+ label: "Add circuit breaker",
463
+ operation: "insert",
464
+ text: "Halt all trading when volatility exceeds critical threshold",
465
+ },
466
+ });
467
+ }
468
+ // Compute summary
469
+ const counts = {
470
+ errors: diagnostics.filter((d) => d.severity === "error").length,
471
+ warnings: diagnostics.filter((d) => d.severity === "warning").length,
472
+ info: diagnostics.filter((d) => d.severity === "info").length,
473
+ hints: diagnostics.filter((d) => d.severity === "hint").length,
474
+ };
475
+ const hasBlockingErrors = counts.errors > 0 && parsed.rules.length === 0;
476
+ const healthScore = Math.max(0, 100 - counts.errors * 25 - counts.warnings * 10 - counts.info * 3 - counts.hints * 1);
477
+ let status;
478
+ if (counts.errors > 0 && hasBlockingErrors)
479
+ status = "critical";
480
+ else if (counts.errors > 0)
481
+ status = "errors";
482
+ else if (counts.warnings > 0)
483
+ status = "warnings";
484
+ else
485
+ status = "healthy";
486
+ return {
487
+ status,
488
+ diagnostics,
489
+ counts,
490
+ canSimulate: parsed.rules.length > 0,
491
+ healthScore,
492
+ };
493
+ }
494
+ // ============================================
495
+ // QUICK FIX APPLICATION
496
+ // ============================================
497
+ /**
498
+ * Apply a quick fix action to the policy text.
499
+ * Returns the new text after the fix is applied.
500
+ */
501
+ function applyQuickFix(policyText, fix) {
502
+ const lines = policyText.split("\n");
503
+ switch (fix.operation) {
504
+ case "insert":
505
+ // Append new lines at the end
506
+ return policyText.trimEnd() + "\n" + fix.text;
507
+ case "replace": {
508
+ if (!fix.targetLines || fix.targetLines.length === 0)
509
+ return policyText;
510
+ // Replace the first target line with the new text, remove the rest
511
+ const sortedTargets = [...fix.targetLines].sort((a, b) => a - b);
512
+ const result = [];
513
+ let replaced = false;
514
+ for (let i = 0; i < lines.length; i++) {
515
+ const lineNum = i + 1;
516
+ if (sortedTargets.includes(lineNum)) {
517
+ if (!replaced) {
518
+ // Replace the first occurrence with the fix text
519
+ result.push(fix.text);
520
+ replaced = true;
521
+ }
522
+ // Skip subsequent target lines (they're merged into one)
523
+ }
524
+ else {
525
+ result.push(lines[i]);
526
+ }
527
+ }
528
+ return result.join("\n");
529
+ }
530
+ case "remove": {
531
+ if (!fix.targetLines || fix.targetLines.length === 0)
532
+ return policyText;
533
+ return lines
534
+ .filter((_, i) => !fix.targetLines.includes(i + 1))
535
+ .join("\n");
536
+ }
537
+ default:
538
+ return policyText;
539
+ }
540
+ }
541
+ // ============================================
542
+ // POLICY → WORLD CONVERSION
543
+ // ============================================
544
+ /**
545
+ * Convert parsed policy rules into a WorldDefinitionLite for simulation.
546
+ */
547
+ function policyToWorld(parsed) {
548
+ const invariants = parsed.rules.map((rule) => ({
549
+ id: rule.id.replace("RULE", "INV"),
550
+ description: rule.description,
551
+ enforceable: rule.enforcement === "structural",
552
+ }));
553
+ const gates = [];
554
+ // Generate gates from circuit breakers and prohibitions
555
+ for (const rule of parsed.rules) {
556
+ if (rule.category === "circuit_breaker") {
557
+ gates.push({
558
+ id: `GATE-${rule.id}`,
559
+ label: `Circuit Breaker: ${rule.description.slice(0, 60)}`,
560
+ condition: buildGateCondition(rule),
561
+ severity: "critical",
562
+ });
563
+ }
564
+ else if (rule.category === "prohibit") {
565
+ gates.push({
566
+ id: `GATE-${rule.id}`,
567
+ label: `Block: ${rule.description.slice(0, 60)}`,
568
+ condition: buildGateCondition(rule),
569
+ severity: "critical",
570
+ });
571
+ }
572
+ else if (rule.category === "limit") {
573
+ gates.push({
574
+ id: `GATE-${rule.id}`,
575
+ label: `Limit: ${rule.description.slice(0, 60)}`,
576
+ condition: buildGateCondition(rule),
577
+ severity: "warning",
578
+ });
579
+ }
580
+ }
581
+ // Infer thesis from the policy
582
+ const enforced = parsed.rules.filter((r) => r.enforcement === "structural");
583
+ const thesis = enforced.length > 0
584
+ ? `Policy enforces ${enforced.length} structural constraints covering ${[...new Set(enforced.flatMap((r) => r.affectedVariables))].join(", ") || "system behavior"}`
585
+ : "Advisory policy providing guidelines without structural enforcement";
586
+ // Build state variables from affected variables
587
+ const allVars = [...new Set(parsed.rules.flatMap((r) => r.affectedVariables))];
588
+ const stateVars = allVars.map((varName) => ({
589
+ id: `${varName}_index`,
590
+ label: varName.charAt(0).toUpperCase() + varName.slice(1).replace(/_/g, " ") + " Index",
591
+ type: "number",
592
+ range: { min: 0, max: 100 },
593
+ default_value: 50,
594
+ }));
595
+ return {
596
+ thesis,
597
+ state_variables: stateVars,
598
+ invariants,
599
+ gates,
600
+ };
601
+ }
602
+ function buildGateCondition(rule) {
603
+ const vars = rule.affectedVariables;
604
+ if (vars.length === 0)
605
+ return "system_stress > 80";
606
+ return vars
607
+ .map((v) => {
608
+ if (v === "volatility")
609
+ return "volatility_index > 80";
610
+ if (v === "liquidity")
611
+ return "liquidity_index < 15";
612
+ if (v === "leverage")
613
+ return "leverage_ratio > 8";
614
+ if (v === "risk")
615
+ return "risk_index > 75";
616
+ if (v === "contagion")
617
+ return "contagion_spread == systemic";
618
+ return `${v}_index > 80`;
619
+ })
620
+ .join(" || ");
621
+ }
622
+ // ============================================
623
+ // FULL PIPELINE: Upload → Parse → Validate → Simulate → Compare
624
+ // ============================================
625
+ /**
626
+ * Build default stakeholders for policy simulation.
627
+ */
628
+ function buildDefaultStakeholders() {
629
+ return [
630
+ { id: "Policy Enforcer", disposition: "supportive", priorities: ["compliance", "stability", "risk reduction"] },
631
+ { id: "Market Participants", disposition: "neutral", priorities: ["profit", "liquidity", "freedom"] },
632
+ { id: "Regulators", disposition: "supportive", priorities: ["systemic stability", "transparency", "enforcement"] },
633
+ { id: "Risk Managers", disposition: "neutral", priorities: ["exposure limits", "portfolio protection", "hedging"] },
634
+ ];
635
+ }
636
+ /**
637
+ * Build default reasoning paths for policy simulation.
638
+ */
639
+ function buildDefaultPaths() {
640
+ return [
641
+ {
642
+ id: "path_compliant",
643
+ label: "Full Compliance",
644
+ description: "All actors comply with policy rules — system operates within constraints",
645
+ projected_outcome: "Reduced volatility, slower but stable growth, fewer extreme events",
646
+ probability: 0.55,
647
+ risk: "low",
648
+ tradeoffs: ["Stability vs. opportunity cost", "Compliance cost vs. systemic safety"],
649
+ benefits_stakeholders: ["Regulators", "Risk Managers"],
650
+ harms_stakeholders: [],
651
+ },
652
+ {
653
+ id: "path_stress_test",
654
+ label: "Stress Test",
655
+ description: "External shock tests policy resilience — cascade events trigger policy rules",
656
+ projected_outcome: "Policy constraints activate, dampening cascade but limiting response flexibility",
657
+ probability: 0.65,
658
+ risk: "high",
659
+ tradeoffs: ["Protection vs. agility", "Circuit breakers vs. market freeze"],
660
+ benefits_stakeholders: ["Policy Enforcer"],
661
+ harms_stakeholders: ["Market Participants"],
662
+ },
663
+ {
664
+ id: "path_evasion",
665
+ label: "Policy Evasion",
666
+ description: "Some actors find loopholes or work around constraints",
667
+ projected_outcome: "Partial effectiveness — compliant actors stabilized, non-compliant create new risks",
668
+ probability: 0.45,
669
+ risk: "moderate",
670
+ tradeoffs: ["Enforcement strictness vs. innovation", "Monitoring cost vs. coverage"],
671
+ benefits_stakeholders: [],
672
+ harms_stakeholders: ["Policy Enforcer", "Regulators"],
673
+ },
674
+ ];
675
+ }
676
+ /**
677
+ * Run the complete policy enforcement pipeline.
678
+ *
679
+ * 1. Parse policy text into rules
680
+ * 2. Validate logic (conflicts, coverage, strength)
681
+ * 3. Convert to world definition
682
+ * 4. Run baseline vs. policy simulation
683
+ * 5. Generate impact report with explanation
684
+ *
685
+ * Returns everything needed for the UI in one call.
686
+ */
687
+ async function runPolicyPipeline(policyText, options = {}) {
688
+ // Step 1: Parse
689
+ const parsed = parseRulesFromText(policyText);
690
+ // Step 2: Validate
691
+ const healthCheck = validatePolicy(parsed);
692
+ // Step 3: Convert to world
693
+ const worldLite = policyToWorld(parsed);
694
+ // Step 4: Build scenario request
695
+ const scenarioText = options.scenarioText ??
696
+ "A systemic stress event triggers cascading failures across interconnected markets. " +
697
+ "Volatility spikes, liquidity evaporates, and contagion threatens to spread. " +
698
+ "The policy framework is the only structural defense.";
699
+ const stakeholders = (options.stakeholders ?? buildDefaultStakeholders());
700
+ const paths = buildDefaultPaths();
701
+ const request = {
702
+ scenario: scenarioText,
703
+ stakeholders,
704
+ assumptions: {
705
+ severity: "critical",
706
+ environmental_hostility: "high",
707
+ time_pressure: "immediate",
708
+ regulatory_climate: "strict",
709
+ },
710
+ constraints: {
711
+ time_horizon: "72 hours",
712
+ risk_tolerance: "conservative",
713
+ },
714
+ depth: "full",
715
+ swarm: {
716
+ enabled: true,
717
+ rounds: options.rounds ?? 5,
718
+ reaction_model: "mixed",
719
+ },
720
+ };
721
+ // Step 5: Run governed comparison
722
+ const comparison = await (0, governedSimulation_1.runGovernedComparison)(request, worldLite, paths);
723
+ // Step 6: Compute policy-specific impact
724
+ const impact = computePolicyImpact(comparison, parsed);
725
+ // Step 7: Generate explanation
726
+ const explanation = generatePolicyExplanation(comparison, parsed, impact);
727
+ return {
728
+ policy: parsed,
729
+ healthCheck,
730
+ comparison,
731
+ impact,
732
+ explanation,
733
+ };
734
+ }
735
+ // ============================================
736
+ // IMPACT COMPUTATION
737
+ // ============================================
738
+ function computePolicyImpact(comparison, parsed) {
739
+ const baseline = comparison.baseline.metrics;
740
+ const governed = comparison.governed.metrics;
741
+ const stats = comparison.governanceStats;
742
+ const stabilityChange = (governed.stabilityScore - baseline.stabilityScore) * 100;
743
+ const cascadeRiskChange = (governed.collapseProbability - baseline.collapseProbability) * -100;
744
+ const totalActions = comparison.governed.swarm.rounds.reduce((sum, r) => sum + r.reactions.length, 0);
745
+ const actionsBlocked = stats.verdicts.block;
746
+ const blockedPct = totalActions > 0 ? (actionsBlocked / totalActions) * 100 : 0;
747
+ // Over-constraint: if >70% actions blocked, policy is too restrictive
748
+ const overConstraintScore = Math.min(1, blockedPct / 100);
749
+ return {
750
+ stabilityChange: Number(stabilityChange.toFixed(1)),
751
+ cascadeRiskChange: Number(cascadeRiskChange.toFixed(1)),
752
+ governanceActions: stats.totalEvaluations,
753
+ actionsBlocked: Number(blockedPct.toFixed(1)),
754
+ preventedCollapse: baseline.collapseProbability > 0.5 && governed.collapseProbability < 0.3,
755
+ overConstraintScore: Number(overConstraintScore.toFixed(2)),
756
+ };
757
+ }
758
+ // ============================================
759
+ // EXPLANATION GENERATION
760
+ // ============================================
761
+ function generatePolicyExplanation(comparison, parsed, impact) {
762
+ const parts = [];
763
+ // Opening
764
+ if (impact.preventedCollapse) {
765
+ parts.push("The policy prevented system collapse by enforcing structural constraints during the stress test.");
766
+ }
767
+ else if (impact.stabilityChange > 5) {
768
+ parts.push("The policy measurably improved system stability during the simulated stress event.");
769
+ }
770
+ else if (impact.stabilityChange < -5) {
771
+ parts.push("The policy may have reduced system flexibility, leading to lower stability under stress.");
772
+ }
773
+ else {
774
+ parts.push("The policy had a moderate effect on system dynamics during the stress test.");
775
+ }
776
+ // Mechanism
777
+ const circuitBreakers = parsed.rules.filter((r) => r.category === "circuit_breaker");
778
+ const limits = parsed.rules.filter((r) => r.category === "limit");
779
+ const prohibitions = parsed.rules.filter((r) => r.category === "prohibit");
780
+ if (circuitBreakers.length > 0) {
781
+ parts.push(`Circuit breaker rules (${circuitBreakers.map((r) => r.id).join(", ")}) activated during peak volatility, halting cascading sell-offs.`);
782
+ }
783
+ if (limits.length > 0 && impact.cascadeRiskChange > 10) {
784
+ parts.push(`Limit rules reduced cascade risk by constraining extreme positions and enforcing thresholds.`);
785
+ }
786
+ if (prohibitions.length > 0) {
787
+ parts.push(`Prohibition rules blocked ${impact.actionsBlocked.toFixed(0)}% of actions that violated policy constraints.`);
788
+ }
789
+ // Over-constraint warning
790
+ if (impact.overConstraintScore > 0.7) {
791
+ parts.push(`WARNING: The policy appears over-constrained — ${impact.actionsBlocked.toFixed(0)}% of actions were blocked, ` +
792
+ "risking system freeze where legitimate responses are also suppressed.");
793
+ }
794
+ // Summary
795
+ parts.push(`Net effect: ${impact.stabilityChange > 0 ? "+" : ""}${impact.stabilityChange.toFixed(0)}% stability, ` +
796
+ `${impact.cascadeRiskChange > 0 ? "-" : "+"}${Math.abs(impact.cascadeRiskChange).toFixed(0)}% cascade risk, ` +
797
+ `${impact.governanceActions} governance actions.`);
798
+ return parts.join(" ");
799
+ }