@neuroverseos/nv-sim 0.1.0 → 0.1.4
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 +324 -44
- package/dist/assets/index-DHKd4rcV.js +338 -0
- package/dist/assets/index-SyyA3z3U.css +1 -0
- package/dist/assets/mirotir-logo-DUexumBH.svg +185 -0
- package/dist/assets/reportEngine-BfteK4MN.js +1 -0
- package/dist/assets/swarmSimulation-DHDqjfMa.js +1 -0
- package/dist/engine/aiProvider.js +256 -0
- package/dist/engine/cli.js +334 -299
- package/dist/engine/governance.js +8 -3
- package/dist/engine/governedSimulation.js +38 -12
- package/dist/engine/index.js +16 -1
- package/dist/engine/liveAdapter.js +342 -0
- package/dist/engine/liveVisualizer.js +1857 -0
- package/dist/engine/narrativeInjection.js +305 -0
- package/dist/engine/reasoningEngine.js +57 -3
- package/dist/engine/reportEngine.js +97 -0
- package/dist/engine/scenarioLibrary.js +231 -0
- package/dist/engine/worldComparison.js +194 -0
- package/dist/favicon.ico +0 -0
- package/dist/index.html +23 -0
- package/dist/placeholder.svg +1 -0
- package/dist/robots.txt +14 -0
- package/package.json +13 -12
- package/variants/.gitkeep +0 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Narrative Injection — Information Shocks for Agent Simulations
|
|
4
|
+
*
|
|
5
|
+
* The third knob: agents + world rules + narrative events.
|
|
6
|
+
*
|
|
7
|
+
* Narrative injection models how information shocks propagate through
|
|
8
|
+
* agent networks. Instead of just changing rules, you inject events
|
|
9
|
+
* that shift beliefs, trigger reactions, and reorganize clusters.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* nv-sim compare --inject tanker_explosion@5
|
|
13
|
+
* nv-sim worlds trading strait_of_hormuz --inject rate_cut@3,sanctions@6
|
|
14
|
+
* nv-sim visualize --inject "China begins military exercises"@4
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.NARRATIVE_PRESETS = void 0;
|
|
18
|
+
exports.injectNarrative = injectNarrative;
|
|
19
|
+
exports.parseInjectArgs = parseInjectArgs;
|
|
20
|
+
exports.getEventsForRound = getEventsForRound;
|
|
21
|
+
// ============================================
|
|
22
|
+
// SEVERITY → MAGNITUDE MAPPING
|
|
23
|
+
// ============================================
|
|
24
|
+
const SEVERITY_MAGNITUDE = {
|
|
25
|
+
minor: 0.1,
|
|
26
|
+
moderate: 0.25,
|
|
27
|
+
major: 0.45,
|
|
28
|
+
extreme: 0.7,
|
|
29
|
+
};
|
|
30
|
+
const PROPAGATION_MULTIPLIER = {
|
|
31
|
+
slow: 0.6,
|
|
32
|
+
normal: 1.0,
|
|
33
|
+
viral: 1.4,
|
|
34
|
+
};
|
|
35
|
+
// ============================================
|
|
36
|
+
// NARRATIVE INJECTION ENGINE
|
|
37
|
+
// ============================================
|
|
38
|
+
/**
|
|
39
|
+
* Apply a narrative event to a set of agent reactions.
|
|
40
|
+
*
|
|
41
|
+
* This models how an information shock propagates through the network:
|
|
42
|
+
* - Targeted agents get the full impact
|
|
43
|
+
* - Related agents get partial impact (network effect)
|
|
44
|
+
* - Unrelated agents get minimal impact (background noise)
|
|
45
|
+
* - Confidence shifts based on event clarity
|
|
46
|
+
*/
|
|
47
|
+
function injectNarrative(reactions, event, stakeholders) {
|
|
48
|
+
const magnitude = SEVERITY_MAGNITUDE[event.severity];
|
|
49
|
+
const propagation = PROPAGATION_MULTIPLIER[event.propagation];
|
|
50
|
+
const direction = event.direction === "positive" ? 1 : event.direction === "negative" ? -1 : 0;
|
|
51
|
+
const agentShifts = [];
|
|
52
|
+
const systemEffects = [];
|
|
53
|
+
systemEffects.push(`EVENT INJECTED: "${event.headline}"`);
|
|
54
|
+
systemEffects.push(`Severity: ${event.severity} | Propagation: ${event.propagation}`);
|
|
55
|
+
for (const reaction of reactions) {
|
|
56
|
+
const stakeholder = stakeholders.find(s => s.id === reaction.stakeholder_id);
|
|
57
|
+
const isTargeted = event.targets.some(t => reaction.stakeholder_id.toLowerCase().includes(t.toLowerCase()) ||
|
|
58
|
+
t.toLowerCase().includes(reaction.stakeholder_id.toLowerCase()));
|
|
59
|
+
// Determine how much this agent is affected
|
|
60
|
+
let sensitivity;
|
|
61
|
+
let reason;
|
|
62
|
+
if (isTargeted) {
|
|
63
|
+
// Directly targeted — full impact
|
|
64
|
+
sensitivity = 1.0;
|
|
65
|
+
reason = `directly targeted by "${event.headline}"`;
|
|
66
|
+
}
|
|
67
|
+
else if (stakeholder?.disposition === "hostile") {
|
|
68
|
+
// Hostile agents are more reactive to shocks
|
|
69
|
+
sensitivity = 0.6;
|
|
70
|
+
reason = `hostile disposition amplifies reaction to "${event.headline}"`;
|
|
71
|
+
}
|
|
72
|
+
else if (stakeholder?.disposition === "neutral") {
|
|
73
|
+
// Neutral agents react moderately
|
|
74
|
+
sensitivity = 0.35;
|
|
75
|
+
reason = `neutral stance — moderate reaction to "${event.headline}"`;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Supportive or unknown — less reactive to negative shocks
|
|
79
|
+
sensitivity = 0.2;
|
|
80
|
+
reason = `indirect exposure to "${event.headline}"`;
|
|
81
|
+
}
|
|
82
|
+
// Calculate belief shift
|
|
83
|
+
let impactShift;
|
|
84
|
+
if (direction === 0) {
|
|
85
|
+
// Mixed direction — push toward extremes based on existing position
|
|
86
|
+
impactShift = reaction.impact > 0
|
|
87
|
+
? magnitude * sensitivity * propagation * 0.5
|
|
88
|
+
: -magnitude * sensitivity * propagation * 0.5;
|
|
89
|
+
reason += " (mixed signal — amplifies existing position)";
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
impactShift = direction * magnitude * sensitivity * propagation;
|
|
93
|
+
}
|
|
94
|
+
// Confidence shift — shocks generally reduce confidence for non-targeted
|
|
95
|
+
const confidenceShift = isTargeted
|
|
96
|
+
? 0.1 * propagation // targeted agents gain conviction
|
|
97
|
+
: -0.05 * magnitude * propagation; // others become less certain
|
|
98
|
+
// Apply shifts
|
|
99
|
+
const newImpact = Math.max(-1, Math.min(1, reaction.impact + impactShift));
|
|
100
|
+
const newConfidence = Math.max(0.05, Math.min(1, reaction.confidence + confidenceShift));
|
|
101
|
+
reaction.impact = Number(newImpact.toFixed(3));
|
|
102
|
+
reaction.confidence = Number(newConfidence.toFixed(3));
|
|
103
|
+
agentShifts.push({
|
|
104
|
+
stakeholder_id: reaction.stakeholder_id,
|
|
105
|
+
impactShift: Number(impactShift.toFixed(3)),
|
|
106
|
+
confidenceShift: Number(confidenceShift.toFixed(3)),
|
|
107
|
+
reason,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// Detect system-level effects
|
|
111
|
+
const totalShift = agentShifts.reduce((s, a) => s + Math.abs(a.impactShift), 0);
|
|
112
|
+
if (totalShift > reactions.length * 0.3) {
|
|
113
|
+
systemEffects.push("NARRATIVE CASCADE: event caused widespread belief shifts");
|
|
114
|
+
}
|
|
115
|
+
const polarization = reactions.filter(r => r.impact > 0.3).length > 0 &&
|
|
116
|
+
reactions.filter(r => r.impact < -0.3).length > 0;
|
|
117
|
+
if (polarization) {
|
|
118
|
+
systemEffects.push("POLARIZATION: event split agent consensus");
|
|
119
|
+
}
|
|
120
|
+
const consensus = Math.abs(reactions.reduce((s, r) => s + r.impact, 0) / reactions.length);
|
|
121
|
+
if (consensus > 0.5) {
|
|
122
|
+
systemEffects.push(`CONSENSUS SHIFT: agents aligned around ${consensus > 0 ? "positive" : "negative"} narrative`);
|
|
123
|
+
}
|
|
124
|
+
return { event, agentShifts, systemEffects };
|
|
125
|
+
}
|
|
126
|
+
// ============================================
|
|
127
|
+
// PRESET NARRATIVE EVENTS
|
|
128
|
+
// ============================================
|
|
129
|
+
exports.NARRATIVE_PRESETS = {
|
|
130
|
+
// Trading / Financial
|
|
131
|
+
tanker_explosion: {
|
|
132
|
+
id: "tanker_explosion",
|
|
133
|
+
headline: "Oil tanker hit by missile in Strait of Hormuz",
|
|
134
|
+
severity: "extreme",
|
|
135
|
+
targets: ["Energy", "Traders", "Financial", "Algorithmic"],
|
|
136
|
+
direction: "negative",
|
|
137
|
+
propagation: "viral",
|
|
138
|
+
category: "geopolitical",
|
|
139
|
+
},
|
|
140
|
+
rate_cut: {
|
|
141
|
+
id: "rate_cut",
|
|
142
|
+
headline: "Federal Reserve announces emergency rate cut",
|
|
143
|
+
severity: "major",
|
|
144
|
+
targets: ["Central Banks", "Institutional", "Financial", "Market"],
|
|
145
|
+
direction: "positive",
|
|
146
|
+
propagation: "viral",
|
|
147
|
+
category: "monetary",
|
|
148
|
+
},
|
|
149
|
+
bank_collapse: {
|
|
150
|
+
id: "bank_collapse",
|
|
151
|
+
headline: "Major bank reports insolvency — contagion fears",
|
|
152
|
+
severity: "extreme",
|
|
153
|
+
targets: ["Institutional", "Retail", "Financial", "Market Makers"],
|
|
154
|
+
direction: "negative",
|
|
155
|
+
propagation: "viral",
|
|
156
|
+
category: "financial",
|
|
157
|
+
},
|
|
158
|
+
flash_crash_rumor: {
|
|
159
|
+
id: "flash_crash_rumor",
|
|
160
|
+
headline: "Rumors of algorithmic trading malfunction spread",
|
|
161
|
+
severity: "moderate",
|
|
162
|
+
targets: ["Algorithmic", "Traders", "Retail"],
|
|
163
|
+
direction: "negative",
|
|
164
|
+
propagation: "normal",
|
|
165
|
+
category: "market",
|
|
166
|
+
},
|
|
167
|
+
sanctions: {
|
|
168
|
+
id: "sanctions",
|
|
169
|
+
headline: "New economic sanctions imposed on major oil producer",
|
|
170
|
+
severity: "major",
|
|
171
|
+
targets: ["Energy", "Government", "OPEC", "Oil"],
|
|
172
|
+
direction: "negative",
|
|
173
|
+
propagation: "normal",
|
|
174
|
+
category: "geopolitical",
|
|
175
|
+
},
|
|
176
|
+
// Geopolitical
|
|
177
|
+
taiwan_exercises: {
|
|
178
|
+
id: "taiwan_exercises",
|
|
179
|
+
headline: "China begins military exercises near Taiwan",
|
|
180
|
+
severity: "major",
|
|
181
|
+
targets: ["Military", "Government", "Financial", "Traders"],
|
|
182
|
+
direction: "negative",
|
|
183
|
+
propagation: "viral",
|
|
184
|
+
category: "geopolitical",
|
|
185
|
+
},
|
|
186
|
+
diplomatic_breakthrough: {
|
|
187
|
+
id: "diplomatic_breakthrough",
|
|
188
|
+
headline: "Surprise diplomatic agreement reached — tensions ease",
|
|
189
|
+
severity: "major",
|
|
190
|
+
targets: ["Government", "Military", "Financial"],
|
|
191
|
+
direction: "positive",
|
|
192
|
+
propagation: "normal",
|
|
193
|
+
category: "geopolitical",
|
|
194
|
+
},
|
|
195
|
+
ceasefire: {
|
|
196
|
+
id: "ceasefire",
|
|
197
|
+
headline: "Ceasefire announced — shipping lanes reopening",
|
|
198
|
+
severity: "moderate",
|
|
199
|
+
targets: ["Energy", "Military", "Government", "Consumers"],
|
|
200
|
+
direction: "positive",
|
|
201
|
+
propagation: "normal",
|
|
202
|
+
category: "geopolitical",
|
|
203
|
+
},
|
|
204
|
+
// Political / Regulatory
|
|
205
|
+
candidate_indicted: {
|
|
206
|
+
id: "candidate_indicted",
|
|
207
|
+
headline: "Leading candidate indicted on corruption charges",
|
|
208
|
+
severity: "major",
|
|
209
|
+
targets: ["Politicians", "Consumers", "Media"],
|
|
210
|
+
direction: "mixed",
|
|
211
|
+
propagation: "viral",
|
|
212
|
+
category: "political",
|
|
213
|
+
},
|
|
214
|
+
regulation_shock: {
|
|
215
|
+
id: "regulation_shock",
|
|
216
|
+
headline: "Unexpected strict AI regulation passed overnight",
|
|
217
|
+
severity: "major",
|
|
218
|
+
targets: ["Regulators", "EV", "Grid", "AI"],
|
|
219
|
+
direction: "negative",
|
|
220
|
+
propagation: "normal",
|
|
221
|
+
category: "regulatory",
|
|
222
|
+
},
|
|
223
|
+
stimulus_package: {
|
|
224
|
+
id: "stimulus_package",
|
|
225
|
+
headline: "Emergency fiscal stimulus package announced",
|
|
226
|
+
severity: "moderate",
|
|
227
|
+
targets: ["Government", "Consumers", "Financial"],
|
|
228
|
+
direction: "positive",
|
|
229
|
+
propagation: "normal",
|
|
230
|
+
category: "economic",
|
|
231
|
+
},
|
|
232
|
+
// Energy
|
|
233
|
+
grid_failure: {
|
|
234
|
+
id: "grid_failure",
|
|
235
|
+
headline: "Regional power grid failure — rolling blackouts",
|
|
236
|
+
severity: "major",
|
|
237
|
+
targets: ["Grid", "Consumers", "EV", "Energy"],
|
|
238
|
+
direction: "negative",
|
|
239
|
+
propagation: "normal",
|
|
240
|
+
category: "infrastructure",
|
|
241
|
+
},
|
|
242
|
+
oil_discovery: {
|
|
243
|
+
id: "oil_discovery",
|
|
244
|
+
headline: "Major new oil field discovered — supply outlook shifts",
|
|
245
|
+
severity: "moderate",
|
|
246
|
+
targets: ["Energy", "Oil", "OPEC", "Consumers"],
|
|
247
|
+
direction: "positive",
|
|
248
|
+
propagation: "slow",
|
|
249
|
+
category: "energy",
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
// ============================================
|
|
253
|
+
// CLI ARGUMENT PARSING
|
|
254
|
+
// ============================================
|
|
255
|
+
/**
|
|
256
|
+
* Parse --inject arguments into NarrativeEvent arrays.
|
|
257
|
+
*
|
|
258
|
+
* Format: --inject event_id@round[,event_id@round,...]
|
|
259
|
+
* Or: --inject "Custom headline"@round
|
|
260
|
+
*
|
|
261
|
+
* Examples:
|
|
262
|
+
* --inject tanker_explosion@5
|
|
263
|
+
* --inject rate_cut@3,sanctions@6
|
|
264
|
+
* --inject "Bank collapses"@8
|
|
265
|
+
*/
|
|
266
|
+
function parseInjectArgs(args) {
|
|
267
|
+
const injectIdx = args.indexOf("--inject");
|
|
268
|
+
if (injectIdx === -1 || !args[injectIdx + 1])
|
|
269
|
+
return [];
|
|
270
|
+
const injectStr = args[injectIdx + 1];
|
|
271
|
+
const events = [];
|
|
272
|
+
for (const part of injectStr.split(",")) {
|
|
273
|
+
const atIdx = part.lastIndexOf("@");
|
|
274
|
+
if (atIdx === -1)
|
|
275
|
+
continue;
|
|
276
|
+
const eventId = part.slice(0, atIdx).trim();
|
|
277
|
+
const round = parseInt(part.slice(atIdx + 1), 10);
|
|
278
|
+
if (isNaN(round))
|
|
279
|
+
continue;
|
|
280
|
+
const preset = exports.NARRATIVE_PRESETS[eventId];
|
|
281
|
+
if (preset) {
|
|
282
|
+
events.push({ ...preset, round });
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
// Custom headline
|
|
286
|
+
events.push({
|
|
287
|
+
id: `custom_${round}`,
|
|
288
|
+
headline: eventId.replace(/^["']|["']$/g, ""),
|
|
289
|
+
round,
|
|
290
|
+
severity: "major",
|
|
291
|
+
targets: [],
|
|
292
|
+
direction: "mixed",
|
|
293
|
+
propagation: "normal",
|
|
294
|
+
category: "custom",
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return events;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Get events scheduled for a specific round.
|
|
302
|
+
*/
|
|
303
|
+
function getEventsForRound(events, round) {
|
|
304
|
+
return events.filter(e => e.round === round);
|
|
305
|
+
}
|
|
@@ -444,13 +444,67 @@ async function processReasonRequest(request) {
|
|
|
444
444
|
...governanceResult.enforcedConstraints,
|
|
445
445
|
];
|
|
446
446
|
// Build NeuroverseOS governance summary
|
|
447
|
+
// Compute world_health_score from actual simulation dynamics, not static validation
|
|
448
|
+
let dynamicWorldHealth;
|
|
449
|
+
if (swarmResult) {
|
|
450
|
+
const allReactions = swarmResult.rounds.flatMap((r) => r.reactions ?? []);
|
|
451
|
+
if (allReactions.length > 0) {
|
|
452
|
+
const allImpacts = allReactions.map((r) => r.impact ?? 0);
|
|
453
|
+
const avgImpact = allImpacts.reduce((s, i) => s + i, 0) / allImpacts.length;
|
|
454
|
+
const maxVolatility = Math.max(...allImpacts.map((i) => Math.abs(i)));
|
|
455
|
+
const impactVariance = allImpacts.reduce((s, i) => s + (i - avgImpact) ** 2, 0) / allImpacts.length;
|
|
456
|
+
// Stability = f(variance, volatility, trajectory, negative sentiment)
|
|
457
|
+
let stability = 1 - Math.min(1, impactVariance * 3 + Math.max(0, -avgImpact));
|
|
458
|
+
if (swarmResult.trajectory === "converging" || swarmResult.trajectory === "stabilizing") {
|
|
459
|
+
stability = Math.min(1, stability + 0.15);
|
|
460
|
+
}
|
|
461
|
+
if (swarmResult.trajectory === "escalating") {
|
|
462
|
+
stability = Math.max(0, stability - 0.2);
|
|
463
|
+
}
|
|
464
|
+
if (swarmResult.trajectory === "diverging") {
|
|
465
|
+
stability = Math.max(0, stability - 0.15);
|
|
466
|
+
}
|
|
467
|
+
// Factor in governance interventions
|
|
468
|
+
const enforcedCount = enforcedConstraints.length;
|
|
469
|
+
stability = Math.min(1, stability + enforcedCount * 0.02);
|
|
470
|
+
dynamicWorldHealth = Math.round(Math.max(0, Math.min(100, stability * 100)));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
// Compute dynamic collapse risk from simulation
|
|
474
|
+
let dynamicCollapseRisk;
|
|
475
|
+
if (swarmResult) {
|
|
476
|
+
const allReactions = swarmResult.rounds.flatMap((r) => r.reactions ?? []);
|
|
477
|
+
if (allReactions.length > 0) {
|
|
478
|
+
const allImpacts = allReactions.map((r) => r.impact ?? 0);
|
|
479
|
+
const avgImpact = allImpacts.reduce((s, i) => s + i, 0) / allImpacts.length;
|
|
480
|
+
const maxVol = Math.max(...allImpacts.map((i) => Math.abs(i)));
|
|
481
|
+
let risk = 0;
|
|
482
|
+
if (swarmResult.trajectory === "escalating")
|
|
483
|
+
risk += 0.35;
|
|
484
|
+
if (swarmResult.trajectory === "diverging")
|
|
485
|
+
risk += 0.25;
|
|
486
|
+
if (avgImpact < -0.3)
|
|
487
|
+
risk += 0.25;
|
|
488
|
+
if (maxVol > 0.7)
|
|
489
|
+
risk += 0.15;
|
|
490
|
+
risk = Math.min(0.95, risk);
|
|
491
|
+
if (risk > 0.5)
|
|
492
|
+
dynamicCollapseRisk = "high";
|
|
493
|
+
else if (risk > 0.3)
|
|
494
|
+
dynamicCollapseRisk = "moderate";
|
|
495
|
+
else if (risk > 0.1)
|
|
496
|
+
dynamicCollapseRisk = "low";
|
|
497
|
+
else
|
|
498
|
+
dynamicCollapseRisk = "none";
|
|
499
|
+
}
|
|
500
|
+
}
|
|
447
501
|
const neuroverseTrace = governanceResult.guardVerdict ? {
|
|
448
502
|
guard_status: governanceResult.guardVerdict.status,
|
|
449
503
|
world_viability: governanceResult.governanceSignals?.viability,
|
|
450
504
|
world_collapsed: governanceResult.governanceSignals?.collapsed,
|
|
451
|
-
collapse_risk: governanceResult.governanceSignals?.collapseRisk,
|
|
452
|
-
rules_fired: governanceResult.governanceSignals?.rulesFired,
|
|
453
|
-
world_health_score: governanceResult.validationReport?.summary.completenessScore,
|
|
505
|
+
collapse_risk: dynamicCollapseRisk ?? governanceResult.governanceSignals?.collapseRisk,
|
|
506
|
+
rules_fired: governanceResult.governanceSignals?.rulesFired ?? enforcedConstraints.length,
|
|
507
|
+
world_health_score: dynamicWorldHealth ?? governanceResult.validationReport?.summary.completenessScore,
|
|
454
508
|
invariant_coverage: governanceResult.validationReport?.summary.invariantCoverage,
|
|
455
509
|
} : undefined;
|
|
456
510
|
const governance = {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Governed Report Engine
|
|
4
|
+
*
|
|
5
|
+
* Generates structured reports from simulation traces.
|
|
6
|
+
* ALL report generation goes through /world evaluate — even AI-generated reports.
|
|
7
|
+
*
|
|
8
|
+
* "Even the AI has to follow the rules."
|
|
9
|
+
*
|
|
10
|
+
* Pipeline:
|
|
11
|
+
* Trace (ground truth)
|
|
12
|
+
* → /world evaluate (ai_analyst actor)
|
|
13
|
+
* → governed constraints applied
|
|
14
|
+
* → structured report output
|
|
15
|
+
* → trace recorded
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.extractTrace = extractTrace;
|
|
19
|
+
exports.generateGovernedReport = generateGovernedReport;
|
|
20
|
+
const aiProvider_1 = require("./aiProvider");
|
|
21
|
+
// ============================================
|
|
22
|
+
// TRACE EXTRACTION
|
|
23
|
+
// ============================================
|
|
24
|
+
/**
|
|
25
|
+
* Extract a SimulationTraceInput from a GovernedComparisonResult.
|
|
26
|
+
* This is the "ground truth" that the report must reference.
|
|
27
|
+
*/
|
|
28
|
+
function extractTrace(comparison) {
|
|
29
|
+
const governed = comparison.governed;
|
|
30
|
+
const stats = comparison.governanceStats;
|
|
31
|
+
const rounds = governed.swarm.rounds.map((r, i) => ({
|
|
32
|
+
round: r.round,
|
|
33
|
+
reactions: r.reactions.map(rx => ({
|
|
34
|
+
agent: rx.stakeholder_id,
|
|
35
|
+
action: rx.reaction,
|
|
36
|
+
impact: rx.impact,
|
|
37
|
+
verdict: undefined,
|
|
38
|
+
})),
|
|
39
|
+
interventions: r.emergent_dynamics
|
|
40
|
+
?.filter(d => d.includes("[BLOCK]") || d.includes("[PAUSE]") || d.includes("CIRCUIT BREAKER") || d.includes("intervention"))
|
|
41
|
+
?? [],
|
|
42
|
+
}));
|
|
43
|
+
// Collect all interventions from emergent dynamics
|
|
44
|
+
const allInterventions = governed.swarm.rounds.flatMap(r => (r.emergent_dynamics ?? []).filter(d => d.includes("[BLOCK]") || d.includes("[PAUSE]") || d.includes("GATE") || d.includes("Rebalanced") || d.includes("Capped")));
|
|
45
|
+
const metrics = {
|
|
46
|
+
avgImpact: governed.metrics.avgImpact,
|
|
47
|
+
collapseProbability: governed.metrics.collapseProbability,
|
|
48
|
+
stabilityScore: governed.metrics.stabilityScore,
|
|
49
|
+
maxVolatility: governed.metrics.maxVolatility,
|
|
50
|
+
peakNegativeSentiment: governed.metrics.peakNegativeSentiment,
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
scenario: comparison.scenario,
|
|
54
|
+
rounds,
|
|
55
|
+
metrics,
|
|
56
|
+
interventions: allInterventions,
|
|
57
|
+
governanceStats: {
|
|
58
|
+
totalEvaluations: stats.totalEvaluations,
|
|
59
|
+
blocks: stats.verdicts.block,
|
|
60
|
+
pauses: stats.verdicts.pause,
|
|
61
|
+
allows: stats.verdicts.allow,
|
|
62
|
+
rulesFired: stats.rulesFired,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Generate a governed report from a simulation comparison.
|
|
68
|
+
*
|
|
69
|
+
* This is the main entry point. It:
|
|
70
|
+
* 1. Extracts the trace (ground truth)
|
|
71
|
+
* 2. Routes through /world evaluate (ai_analyst actor)
|
|
72
|
+
* 3. Validates governance constraints
|
|
73
|
+
* 4. Returns structured report with governance trace
|
|
74
|
+
*/
|
|
75
|
+
async function generateGovernedReport(comparison, options = {}) {
|
|
76
|
+
const trace = extractTrace(comparison);
|
|
77
|
+
const aiEnabled = options.aiEnabled ?? false;
|
|
78
|
+
const providerName = aiEnabled ? (options.provider ?? "deterministic") : "deterministic";
|
|
79
|
+
const provider = (0, aiProvider_1.getAIProvider)(providerName);
|
|
80
|
+
// Find the ai_analyst role
|
|
81
|
+
const analystRole = aiProvider_1.AI_ROLES.find(r => r.id === "ai_analyst");
|
|
82
|
+
// Route through governance: /world evaluate
|
|
83
|
+
const governance = await (0, aiProvider_1.evaluateAIAction)(analystRole, "generate_report", async () => {
|
|
84
|
+
if (aiEnabled && providerName !== "deterministic") {
|
|
85
|
+
// AI provider generates report — still governed
|
|
86
|
+
return provider.summarize(trace);
|
|
87
|
+
}
|
|
88
|
+
// Deterministic: generate from trace data only
|
|
89
|
+
return (0, aiProvider_1.generateDeterministicReport)(trace);
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
report: governance.result,
|
|
93
|
+
governance,
|
|
94
|
+
aiUsed: aiEnabled && providerName !== "deterministic",
|
|
95
|
+
provider: providerName,
|
|
96
|
+
};
|
|
97
|
+
}
|