@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.
- package/README.md +376 -66
- package/dist/adapters/mirofish.js +461 -0
- package/dist/adapters/scienceclaw.js +750 -0
- package/dist/assets/index-CHmUN8s0.js +532 -0
- package/dist/assets/index-DWgMnB7I.css +1 -0
- package/dist/assets/mirotir-logo-DUexumBH.svg +185 -0
- package/dist/assets/reportEngine-BVdQ2_nW.js +1 -0
- package/dist/components/ConstraintsPanel.js +11 -0
- package/dist/components/StakeholderBuilder.js +32 -0
- package/dist/components/ui/badge.js +24 -0
- package/dist/components/ui/button.js +70 -0
- package/dist/components/ui/card.js +57 -0
- package/dist/components/ui/input.js +44 -0
- package/dist/components/ui/label.js +45 -0
- package/dist/components/ui/select.js +70 -0
- package/dist/engine/aiProvider.js +681 -0
- package/dist/engine/auditTrace.js +352 -0
- package/dist/engine/behavioralAnalysis.js +605 -0
- package/dist/engine/cli.js +1408 -299
- package/dist/engine/dynamicsGovernance.js +588 -0
- package/dist/engine/fullGovernedLoop.js +367 -0
- package/dist/engine/governance.js +8 -3
- package/dist/engine/governedSimulation.js +114 -17
- package/dist/engine/index.js +56 -1
- package/dist/engine/liveAdapter.js +342 -0
- package/dist/engine/liveVisualizer.js +3063 -0
- package/dist/engine/metrics/science.metrics.js +335 -0
- package/dist/engine/narrativeInjection.js +305 -0
- package/dist/engine/policyEnforcement.js +1611 -0
- package/dist/engine/policyEngine.js +799 -0
- package/dist/engine/primeRadiant.js +540 -0
- package/dist/engine/reasoningEngine.js +57 -3
- package/dist/engine/reportEngine.js +97 -0
- package/dist/engine/scenarioComparison.js +463 -0
- package/dist/engine/scenarioLibrary.js +231 -0
- package/dist/engine/swarmSimulation.js +54 -1
- package/dist/engine/worldComparison.js +358 -0
- package/dist/engine/worldStorage.js +232 -0
- package/dist/favicon.ico +0 -0
- package/dist/index.html +23 -0
- package/dist/lib/reasoningEngine.js +290 -0
- package/dist/lib/simulationAdapter.js +686 -0
- package/dist/lib/swarmParser.js +291 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/utils.js +8 -0
- package/dist/placeholder.svg +1 -0
- package/dist/robots.txt +14 -0
- package/dist/runtime/govern.js +473 -0
- package/dist/runtime/index.js +75 -0
- package/dist/runtime/types.js +11 -0
- package/package.json +17 -12
- 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
|
+
}
|