@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,367 @@
1
+ "use strict";
2
+ /**
3
+ * Full Governed Simulation Loop — govern() + governDynamics()
4
+ *
5
+ * The complete simulation architecture:
6
+ *
7
+ * for each round:
8
+ * for each agent:
9
+ * action → govern(action) → commit ← Layer A (action governance)
10
+ * updateState()
11
+ * governDynamics(state, trajectory, history) ← Layer B (dynamics governance)
12
+ *
13
+ * "We don't just control what agents do.
14
+ * We control how their actions shape the system over time."
15
+ *
16
+ * This module combines the existing govern() with the new governDynamics()
17
+ * into a unified simulation runner that produces rich, traceable output.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.runFullGovernedSimulation = runFullGovernedSimulation;
21
+ const swarmSimulation_1 = require("./swarmSimulation");
22
+ const govern_1 = require("../runtime/govern");
23
+ const dynamicsGovernance_1 = require("./dynamicsGovernance");
24
+ const narrativeInjection_1 = require("./narrativeInjection");
25
+ // ============================================
26
+ // AGENT REACTION → ACTION ADAPTER
27
+ // ============================================
28
+ /**
29
+ * Convert a SwarmAgentReaction into an AgentAction for govern().
30
+ *
31
+ * This is the bridge between MiroFish/Swarm output and NeuroVerse governance.
32
+ * It infers action type, magnitude, and description from the reaction content.
33
+ */
34
+ function reactionToAction(reaction) {
35
+ const desc = reaction.reaction.toLowerCase();
36
+ // Infer action type from reaction content
37
+ let type = "communication";
38
+ if (desc.includes("sell") || desc.includes("buy") || desc.includes("trade"))
39
+ type = "trade";
40
+ if (desc.includes("post") || desc.includes("share") || desc.includes("publish"))
41
+ type = "post";
42
+ if (desc.includes("comment") || desc.includes("reply") || desc.includes("respond"))
43
+ type = "comment";
44
+ if (desc.includes("follow") || desc.includes("join") || desc.includes("coalition"))
45
+ type = "coalition";
46
+ if (desc.includes("withdraw") || desc.includes("exit") || desc.includes("leave"))
47
+ type = "withdrawal";
48
+ if (desc.includes("intervene") || desc.includes("inject") || desc.includes("regulate"))
49
+ type = "intervention";
50
+ if (desc.includes("amplif") || desc.includes("boost") || desc.includes("promote"))
51
+ type = "amplification";
52
+ if (desc.includes("attack") || desc.includes("accuse") || desc.includes("denounce"))
53
+ type = "hostile";
54
+ // Magnitude is the absolute impact
55
+ const magnitude = Math.abs(reaction.impact);
56
+ return {
57
+ agentId: reaction.stakeholder_id,
58
+ type,
59
+ description: reaction.reaction,
60
+ magnitude: Number(magnitude.toFixed(3)),
61
+ context: {
62
+ originalImpact: reaction.impact,
63
+ confidence: reaction.confidence,
64
+ trigger: reaction.trigger,
65
+ },
66
+ };
67
+ }
68
+ /**
69
+ * Apply a governance verdict back to a reaction.
70
+ */
71
+ function applyVerdictToReaction(reaction, verdict) {
72
+ if (verdict.status === "BLOCK") {
73
+ return {
74
+ ...reaction,
75
+ impact: Number((reaction.impact * 0.1).toFixed(3)),
76
+ confidence: Number((Math.max(0.05, reaction.confidence * 0.3)).toFixed(3)),
77
+ };
78
+ }
79
+ if (verdict.status === "PAUSE") {
80
+ return {
81
+ ...reaction,
82
+ impact: Number((reaction.impact * 0.5).toFixed(3)),
83
+ confidence: Number((Math.max(0.1, reaction.confidence * 0.6)).toFixed(3)),
84
+ };
85
+ }
86
+ if (verdict.status === "MODIFY" && verdict.action) {
87
+ const ratio = verdict.action.magnitude / Math.max(0.001, Math.abs(reaction.impact));
88
+ return {
89
+ ...reaction,
90
+ impact: Number((reaction.impact * ratio).toFixed(3)),
91
+ confidence: Number(reaction.confidence.toFixed(3)),
92
+ };
93
+ }
94
+ // ALLOW — no change
95
+ return reaction;
96
+ }
97
+ // ============================================
98
+ // DETECT EMERGENT DYNAMICS
99
+ // ============================================
100
+ function detectEmergentDynamics(reactions, dynamicsInterventions, systemState) {
101
+ const dynamics = [];
102
+ const avgImpact = reactions.reduce((s, r) => s + r.impact, 0) / reactions.length;
103
+ if (Math.abs(avgImpact) > 0.5) {
104
+ dynamics.push(avgImpact > 0
105
+ ? "Strong positive consensus — governance channeled energy constructively"
106
+ : "Negative consensus persists despite governance intervention");
107
+ }
108
+ else if (Math.abs(avgImpact) < 0.12) {
109
+ dynamics.push("Near-equilibrium achieved — agent positions stabilized by governance");
110
+ }
111
+ const positive = reactions.filter((r) => r.impact > 0.2).length;
112
+ const negative = reactions.filter((r) => r.impact < -0.2).length;
113
+ if (positive > 0 && negative > 0 && Math.abs(positive - negative) <= 1) {
114
+ dynamics.push("Polarization detected but contained by dynamics governance");
115
+ }
116
+ if (systemState.coolingPeriod > 0) {
117
+ dynamics.push(`Cooling period active — reactive posting suppressed for ${systemState.coolingPeriod} more round(s)`);
118
+ }
119
+ if (systemState.cascadeRisk > 0.7) {
120
+ dynamics.push(`CASCADE WARNING: risk at ${(systemState.cascadeRisk * 100).toFixed(0)}% — circuit breakers engaged`);
121
+ }
122
+ if (systemState.trust < 0.3) {
123
+ dynamics.push(`TRUST CRISIS: institutional trust at ${(systemState.trust * 100).toFixed(0)}% — visibility shift active`);
124
+ }
125
+ if (dynamicsInterventions.length > 0) {
126
+ dynamics.push(`${dynamicsInterventions.length} dynamics intervention(s) applied this round`);
127
+ }
128
+ return dynamics;
129
+ }
130
+ // ============================================
131
+ // MAIN: runFullGovernedSimulation()
132
+ // ============================================
133
+ /**
134
+ * Run a simulation with BOTH governance layers:
135
+ * Layer A: govern() — per-action, per-agent
136
+ * Layer B: governDynamics() — per-round, system-level
137
+ *
138
+ * This is the complete Prime Radiant simulation loop.
139
+ */
140
+ async function runFullGovernedSimulation(config) {
141
+ // --- Initialize both governors ---
142
+ const actionGovernor = (0, govern_1.createGovernor)({
143
+ policyText: config.policyText,
144
+ sensitivity: 0.5,
145
+ });
146
+ const dynamicsGovernor = (0, dynamicsGovernance_1.createDynamicsGovernor)(config.policyText, config.initialSystemState);
147
+ // Build policy text from world definition invariants + explicit policy
148
+ const fullPolicyText = [
149
+ config.policyText,
150
+ ...config.worldDef.invariants.map((inv) => inv.description),
151
+ ].join("\n");
152
+ // Re-create action governor with full policy
153
+ const fullActionGovernor = (0, govern_1.createGovernor)({
154
+ policyText: fullPolicyText,
155
+ sensitivity: 0.5,
156
+ });
157
+ // --- Run raw simulation ---
158
+ const rawResult = await (0, swarmSimulation_1.runSwarmSimulation)(config.scenario, config.stakeholders, config.paths, config.swarmConfig);
159
+ // --- Process each round through both governance layers ---
160
+ const rounds = [];
161
+ const allDynamicsInterventions = [];
162
+ const allNarrativeImpacts = [];
163
+ const actionStats = {
164
+ totalEvaluations: 0,
165
+ allowed: 0,
166
+ blocked: 0,
167
+ modified: 0,
168
+ paused: 0,
169
+ totalReduction: 0,
170
+ rulesFired: 0,
171
+ };
172
+ for (const rawRound of rawResult.rounds) {
173
+ // === LAYER A: Action Governance ===
174
+ const actionVerdicts = [];
175
+ const actionGovernedReactions = [];
176
+ for (const reaction of rawRound.reactions) {
177
+ // Convert reaction to action
178
+ const action = reactionToAction(reaction);
179
+ // Build world state from current dynamics state
180
+ const currentState = dynamicsGovernor.state;
181
+ const worldState = {
182
+ volatility: currentState.outrage * 100,
183
+ liquidity: currentState.trust * 100,
184
+ polarization: currentState.polarization * 100,
185
+ cascade_risk: currentState.cascadeRisk * 100,
186
+ };
187
+ // Evaluate through action governor
188
+ const verdict = fullActionGovernor.evaluate(action, worldState);
189
+ actionStats.totalEvaluations++;
190
+ switch (verdict.status) {
191
+ case "ALLOW":
192
+ actionStats.allowed++;
193
+ break;
194
+ case "BLOCK":
195
+ actionStats.blocked++;
196
+ break;
197
+ case "MODIFY":
198
+ actionStats.modified++;
199
+ break;
200
+ case "PAUSE":
201
+ actionStats.paused++;
202
+ break;
203
+ }
204
+ actionStats.rulesFired += verdict.rulesFired.filter((r) => r.effect !== "monitored").length;
205
+ if (verdict.action) {
206
+ const reduction = 1 - (verdict.action.magnitude / Math.max(0.001, action.magnitude));
207
+ actionStats.totalReduction += reduction;
208
+ }
209
+ else {
210
+ actionStats.totalReduction += 1;
211
+ }
212
+ // Apply verdict to reaction
213
+ const governedReaction = applyVerdictToReaction(reaction, verdict);
214
+ actionGovernedReactions.push(governedReaction);
215
+ actionVerdicts.push({
216
+ agentId: reaction.stakeholder_id,
217
+ status: verdict.status,
218
+ originalMagnitude: action.magnitude,
219
+ governedMagnitude: verdict.action?.magnitude ?? null,
220
+ rulesFired: verdict.rulesFired.length,
221
+ reason: verdict.reason,
222
+ });
223
+ }
224
+ // === NARRATIVE INJECTION (between Layer A and Layer B) ===
225
+ if (config.narrativeEvents && config.narrativeEvents.length > 0) {
226
+ const roundEvents = (0, narrativeInjection_1.getEventsForRound)(config.narrativeEvents, rawRound.round);
227
+ for (const event of roundEvents) {
228
+ const impact = (0, narrativeInjection_1.injectNarrative)(actionGovernedReactions, event, config.stakeholders);
229
+ allNarrativeImpacts.push(impact);
230
+ }
231
+ }
232
+ // === LAYER B: Dynamics Governance ===
233
+ const dynamicsResult = dynamicsGovernor.governRound(actionGovernedReactions, rawRound.round, config.agentTypes);
234
+ allDynamicsInterventions.push(...dynamicsResult.interventions);
235
+ // Detect emergent dynamics
236
+ const emergentDynamics = detectEmergentDynamics(dynamicsResult.reactions, dynamicsResult.interventions, dynamicsResult.systemState);
237
+ rounds.push({
238
+ round: rawRound.round,
239
+ rawReactions: rawRound.reactions,
240
+ actionGovernedReactions,
241
+ finalReactions: dynamicsResult.reactions,
242
+ actionVerdicts,
243
+ dynamicsInterventions: dynamicsResult.interventions,
244
+ dynamicsTrajectory: dynamicsResult.trajectory,
245
+ systemState: dynamicsResult.systemState,
246
+ emergentDynamics,
247
+ predictedRisk: dynamicsResult.predictedRisk,
248
+ });
249
+ }
250
+ // --- Compute aggregate metrics ---
251
+ const finalState = dynamicsGovernor.state;
252
+ const stateTimeline = finalState.history;
253
+ // Overall trajectory
254
+ const trajectory = computeOverallTrajectory(rounds);
255
+ // System health score (0-100)
256
+ const systemHealthScore = computeSystemHealth(finalState, actionStats, dynamicsGovernor.stats);
257
+ // Inflection points
258
+ const inflectionPoints = findInflectionPoints(rounds);
259
+ const avgReduction = actionStats.totalEvaluations > 0
260
+ ? actionStats.totalReduction / actionStats.totalEvaluations
261
+ : 0;
262
+ return {
263
+ scenario: config.scenario,
264
+ policyText: config.policyText,
265
+ worldRules: {
266
+ thesis: config.worldDef.thesis,
267
+ invariantCount: config.worldDef.invariants.length,
268
+ gateCount: config.worldDef.gates?.length ?? 0,
269
+ },
270
+ rounds,
271
+ stateTimeline,
272
+ actionGovernance: {
273
+ totalEvaluations: actionStats.totalEvaluations,
274
+ allowed: actionStats.allowed,
275
+ blocked: actionStats.blocked,
276
+ modified: actionStats.modified,
277
+ paused: actionStats.paused,
278
+ avgReduction: Number(avgReduction.toFixed(3)),
279
+ rulesFired: actionStats.rulesFired,
280
+ },
281
+ dynamicsGovernance: dynamicsGovernor.stats,
282
+ allDynamicsInterventions,
283
+ narrativeImpacts: allNarrativeImpacts,
284
+ trajectory,
285
+ systemHealthScore,
286
+ inflectionPoints,
287
+ finalState,
288
+ };
289
+ }
290
+ // ============================================
291
+ // HELPERS
292
+ // ============================================
293
+ function computeOverallTrajectory(rounds) {
294
+ if (rounds.length < 2)
295
+ return "stabilizing";
296
+ const avgImpacts = rounds.map((r) => r.finalReactions.reduce((s, rx) => s + rx.impact, 0) / r.finalReactions.length);
297
+ const trend = avgImpacts[avgImpacts.length - 1] - avgImpacts[0];
298
+ const mean = avgImpacts.reduce((s, x) => s + x, 0) / avgImpacts.length;
299
+ const variance = avgImpacts.reduce((s, v) => s + (v - mean) ** 2, 0) / avgImpacts.length;
300
+ if (variance > 0.15)
301
+ return "diverging";
302
+ if (Math.abs(trend) < 0.1 && variance < 0.05)
303
+ return "converging";
304
+ if (trend < -0.2)
305
+ return "escalating";
306
+ return "stabilizing";
307
+ }
308
+ function computeSystemHealth(finalState, actionStats, dynamicsStats) {
309
+ let score = 50; // baseline
310
+ // Trust contribution (+/- 20)
311
+ score += (finalState.trust - 0.5) * 40;
312
+ // Low outrage is good (+/- 15)
313
+ score += (0.5 - finalState.outrage) * 30;
314
+ // Low polarization is good (+/- 10)
315
+ score += (0.5 - finalState.polarization) * 20;
316
+ // Low cascade risk is good (+/- 10)
317
+ score += (0.5 - finalState.cascadeRisk) * 20;
318
+ // Governance activity is neutral-positive (shows the system is working)
319
+ if (dynamicsStats.totalInterventions > 0) {
320
+ score += 5; // System was active
321
+ }
322
+ // Over-blocking is bad
323
+ const blockRate = actionStats.totalEvaluations > 0
324
+ ? actionStats.blocked / actionStats.totalEvaluations
325
+ : 0;
326
+ if (blockRate > 0.7)
327
+ score -= 15; // Over-constrained
328
+ return Math.max(0, Math.min(100, Math.round(score)));
329
+ }
330
+ function findInflectionPoints(rounds) {
331
+ const points = [];
332
+ for (let i = 1; i < rounds.length; i++) {
333
+ const prev = rounds[i - 1];
334
+ const curr = rounds[i];
335
+ // System state transitions
336
+ if (prev.dynamicsTrajectory !== curr.dynamicsTrajectory) {
337
+ points.push(`Round ${i}: Trajectory shifted ${prev.dynamicsTrajectory} → ${curr.dynamicsTrajectory}`);
338
+ }
339
+ // Cascade breaker activation
340
+ const cascadeBreaker = curr.dynamicsInterventions.find((d) => d.type === "CASCADE_BREAKER");
341
+ if (cascadeBreaker) {
342
+ points.push(`Round ${i}: CASCADE CIRCUIT BREAKER activated — ${cascadeBreaker.description}`);
343
+ }
344
+ // Cooling period start
345
+ if (prev.systemState.coolingPeriod === 0 && curr.systemState.coolingPeriod > 0) {
346
+ points.push(`Round ${i}: Cooling period triggered — system entering dampened state`);
347
+ }
348
+ // Trust crisis
349
+ if (prev.systemState.trust >= 0.3 && curr.systemState.trust < 0.3) {
350
+ points.push(`Round ${i}: TRUST CRISIS — institutional trust dropped below 30%`);
351
+ }
352
+ // Trust recovery
353
+ if (prev.systemState.trust < 0.3 && curr.systemState.trust >= 0.3) {
354
+ points.push(`Round ${i}: Trust recovery — institutional trust climbed back above 30%`);
355
+ }
356
+ // Major sentiment shift
357
+ const prevAvg = prev.finalReactions.reduce((s, r) => s + r.impact, 0) / prev.finalReactions.length;
358
+ const currAvg = curr.finalReactions.reduce((s, r) => s + r.impact, 0) / curr.finalReactions.length;
359
+ if (Math.abs(currAvg - prevAvg) > 0.3) {
360
+ points.push(`Round ${i}: Major sentiment shift (${(Math.abs(currAvg - prevAvg) * 100).toFixed(0)}% swing)`);
361
+ }
362
+ }
363
+ if (points.length === 0) {
364
+ points.push("Governance prevented major inflection points — smooth trajectory maintained");
365
+ }
366
+ return points;
367
+ }
@@ -110,10 +110,15 @@ function runFullGovernanceChecks(request, worldLite) {
110
110
  // Convert verdict to constitutional checks
111
111
  const nvChecks = (0, worldBridge_1.verdictToConstitutionalChecks)(guardVerdict);
112
112
  results.push(...nvChecks);
113
- // If guard blocks, stop reasoning
113
+ // If guard blocks, note the intervention but continue in advisory mode
114
+ // (Reasoning continues so user can see what governance changed)
114
115
  if (guardVerdict.status === "BLOCK") {
115
- allowed = false;
116
- blockReason = `NeuroverseOS governance: ${guardVerdict.reason ?? "Action blocked by guard"}`;
116
+ results.push({
117
+ rule_id: "NV-BLOCK",
118
+ rule: "NeuroverseOS guard issued BLOCK — governance intervention active",
119
+ passed: false,
120
+ detail: `Guard blocked: ${guardVerdict.reason ?? "Action blocked by guard"}. Continuing in governed advisory mode.`,
121
+ });
117
122
  }
118
123
  // If guard pauses, note it but continue (reasoning is advisory)
119
124
  if (guardVerdict.status === "PAUSE") {
@@ -17,6 +17,8 @@ exports.TRADING_DEMO = void 0;
17
17
  exports.runGovernedComparison = runGovernedComparison;
18
18
  const swarmSimulation_1 = require("./swarmSimulation");
19
19
  const worldBridge_1 = require("./worldBridge");
20
+ const narrativeInjection_1 = require("./narrativeInjection");
21
+ const behavioralAnalysis_1 = require("./behavioralAnalysis");
20
22
  // ============================================
21
23
  // GOVERNED REACTION MODEL
22
24
  // ============================================
@@ -30,20 +32,79 @@ const worldBridge_1 = require("./worldBridge");
30
32
  */
31
33
  function applyGovernanceToReactions(reactions, invariants, gates, roundIndex, cumulativeAvgImpact) {
32
34
  const interventions = [];
35
+ const enforcementLog = [];
33
36
  const governed = reactions.map((r) => ({ ...r }));
34
37
  // --- Invariant enforcement ---
35
- // Enforceable invariants act as behavioral constraints
36
- const enforceableInvariants = invariants.filter((inv) => inv.enforceable);
37
- for (const inv of enforceableInvariants) {
38
+ for (const inv of invariants) {
38
39
  const desc = inv.description.toLowerCase();
40
+ let fired = false;
41
+ if (!inv.enforceable) {
42
+ // Advisory invariants: log but don't reshape
43
+ enforcementLog.push({ id: inv.id, description: inv.description, level: "advisory", fired: false });
44
+ if (roundIndex === 0) {
45
+ interventions.push(`[ADVISORY] ${inv.description} — monitored, not enforced`);
46
+ }
47
+ continue;
48
+ }
49
+ // === Block/prohibit patterns (user language: "Block panic selling") ===
50
+ // Hard block: cap matching actions to near-zero impact
51
+ if (desc.includes("block") || desc.includes("prohibit") || desc.includes("prevent") || desc.includes("forbid") || desc.includes("ban") || desc.includes("stop")) {
52
+ for (const agent of governed) {
53
+ // Match the blocked action pattern against agent reactions
54
+ const reactionLower = agent.reaction?.toLowerCase() ?? "";
55
+ const actionTerms = desc.replace(/\b(block|prohibit|prevent|forbid|ban|stop)\b/g, "").trim().split(/\s+/).filter(t => t.length > 2);
56
+ const matches = actionTerms.some(term => reactionLower.includes(term)) || agent.impact < -0.3;
57
+ if (matches) {
58
+ const original = agent.impact;
59
+ agent.impact = agent.impact * 0.15; // Hard suppression
60
+ agent.confidence = Math.max(0.1, agent.confidence * 0.4);
61
+ if (Math.abs(original - agent.impact) > 0.01) {
62
+ fired = true;
63
+ interventions.push(`[BLOCK] ${agent.stakeholder_id}: "${inv.description}" — impact crushed ${original.toFixed(2)} → ${agent.impact.toFixed(2)}`);
64
+ }
65
+ }
66
+ }
67
+ }
68
+ // === Panic / cascade / contagion patterns ===
69
+ // Circuit breaker: dampen all agents when panic conditions detected
70
+ if (desc.includes("panic") || desc.includes("cascade") || desc.includes("contagion") || desc.includes("runaway") || desc.includes("crash")) {
71
+ const negativeCount = governed.filter(a => a.impact < -0.2).length;
72
+ const panicThreshold = Math.max(1, Math.floor(governed.length * 0.4));
73
+ if (negativeCount >= panicThreshold || cumulativeAvgImpact < -0.2) {
74
+ const dampingFactor = 0.3;
75
+ for (const agent of governed) {
76
+ if (agent.impact < -0.15) {
77
+ const original = agent.impact;
78
+ agent.impact = agent.impact * dampingFactor;
79
+ fired = true;
80
+ }
81
+ }
82
+ interventions.push(`[BLOCK] PANIC CIRCUIT BREAKER: "${inv.description}" — ${negativeCount} agents in panic, negative impacts crushed by ${((1 - dampingFactor) * 100).toFixed(0)}%`);
83
+ }
84
+ }
85
+ // === Slow / cool / gradual patterns ===
86
+ // Dampening: reduce all extreme movements
87
+ if (desc.includes("slow") || desc.includes("cool") || desc.includes("gradual") || desc.includes("dampen") || desc.includes("reduce volatility")) {
88
+ for (const agent of governed) {
89
+ if (Math.abs(agent.impact) > 0.3) {
90
+ const original = agent.impact;
91
+ agent.impact = agent.impact * 0.65;
92
+ if (Math.abs(original - agent.impact) > 0.01) {
93
+ fired = true;
94
+ interventions.push(`[PAUSE] ${agent.stakeholder_id}: "${inv.description}" — dampened ${original.toFixed(2)} → ${agent.impact.toFixed(2)}`);
95
+ }
96
+ }
97
+ }
98
+ }
39
99
  // Cap extreme negative impacts (e.g., "maintain liquidity floor")
40
100
  if (desc.includes("floor") || desc.includes("maintain") || desc.includes("minimum")) {
41
101
  for (const agent of governed) {
42
- if (agent.impact < -0.6) {
102
+ if (agent.impact < -0.35) {
43
103
  const original = agent.impact;
44
- agent.impact = Math.max(agent.impact, -0.5);
104
+ agent.impact = Math.max(agent.impact, -0.3);
45
105
  if (original !== agent.impact) {
46
- interventions.push(`[${inv.id}] Capped ${agent.stakeholder_id} negative impact: ${original.toFixed(2)} → ${agent.impact.toFixed(2)} (${inv.description})`);
106
+ fired = true;
107
+ interventions.push(`[BLOCK] ${agent.stakeholder_id}: Liquidity floor violated — impact capped ${original.toFixed(2)} → ${agent.impact.toFixed(2)} (${inv.description})`);
47
108
  }
48
109
  }
49
110
  }
@@ -51,11 +112,12 @@ function applyGovernanceToReactions(reactions, invariants, gates, roundIndex, cu
51
112
  // Limit excessive positive speculation (e.g., "no leverage > 5x")
52
113
  if (desc.includes("leverage") || desc.includes("limit") || desc.includes("cap") || desc.includes("restrict")) {
53
114
  for (const agent of governed) {
54
- if (agent.impact > 0.7) {
115
+ if (agent.impact > 0.45) {
55
116
  const original = agent.impact;
56
- agent.impact = Math.min(agent.impact, 0.6);
117
+ agent.impact = Math.min(agent.impact, 0.4);
57
118
  if (original !== agent.impact) {
58
- interventions.push(`[${inv.id}] Capped ${agent.stakeholder_id} excessive position: ${original.toFixed(2)} → ${agent.impact.toFixed(2)} (${inv.description})`);
119
+ fired = true;
120
+ interventions.push(`[BLOCK] ${agent.stakeholder_id}: Leverage limit exceeded — position capped ${original.toFixed(2)} → ${agent.impact.toFixed(2)} (${inv.description})`);
59
121
  }
60
122
  }
61
123
  }
@@ -65,10 +127,11 @@ function applyGovernanceToReactions(reactions, invariants, gates, roundIndex, cu
65
127
  // Pull extreme reactions toward the mean
66
128
  const avgImpact = governed.reduce((s, a) => s + a.impact, 0) / governed.length;
67
129
  for (const agent of governed) {
68
- if (Math.abs(agent.impact - avgImpact) > 0.4) {
130
+ if (Math.abs(agent.impact - avgImpact) > 0.25) {
69
131
  const original = agent.impact;
70
132
  agent.impact = agent.impact * 0.7 + avgImpact * 0.3;
71
- interventions.push(`[${inv.id}] Rebalanced ${agent.stakeholder_id}: ${original.toFixed(2)} → ${agent.impact.toFixed(2)} (${inv.description})`);
133
+ fired = true;
134
+ interventions.push(`[PAUSE] Rebalanced ${agent.stakeholder_id}: drift ${original.toFixed(2)} → ${agent.impact.toFixed(2)} (${inv.description})`);
72
135
  }
73
136
  }
74
137
  }
@@ -79,11 +142,13 @@ function applyGovernanceToReactions(reactions, invariants, gates, roundIndex, cu
79
142
  const original = agent.impact;
80
143
  agent.impact = agent.impact * 0.8; // 20% reduction in hostility
81
144
  if (Math.abs(original - agent.impact) > 0.01) {
145
+ fired = true;
82
146
  interventions.push(`[${inv.id}] Trust gate dampened ${agent.stakeholder_id} hostility: ${original.toFixed(2)} → ${agent.impact.toFixed(2)}`);
83
147
  }
84
148
  }
85
149
  }
86
150
  }
151
+ enforcementLog.push({ id: inv.id, description: inv.description, level: "full", fired });
87
152
  }
88
153
  // --- Gate enforcement ---
89
154
  // Gates trigger systemic interventions when thresholds are crossed
@@ -92,7 +157,7 @@ function applyGovernanceToReactions(reactions, invariants, gates, roundIndex, cu
92
157
  if (gate.severity === "critical") {
93
158
  // Critical gates act as circuit breakers — compress all impacts toward zero
94
159
  const avgAbsImpact = governed.reduce((s, a) => s + Math.abs(a.impact), 0) / governed.length;
95
- if (avgAbsImpact > 0.5 || cumulativeAvgImpact < -0.3) {
160
+ if (avgAbsImpact > 0.3 || cumulativeAvgImpact < -0.15) {
96
161
  const dampingFactor = 0.6;
97
162
  for (const agent of governed) {
98
163
  const original = agent.impact;
@@ -120,7 +185,7 @@ function applyGovernanceToReactions(reactions, invariants, gates, roundIndex, cu
120
185
  agent.impact = Number(agent.impact.toFixed(2));
121
186
  agent.confidence = Number(agent.confidence.toFixed(2));
122
187
  }
123
- return { reactions: governed, interventions };
188
+ return { reactions: governed, interventions, enforcementLog };
124
189
  }
125
190
  // ============================================
126
191
  // GOVERNED SWARM SIMULATION
@@ -131,13 +196,15 @@ function applyGovernanceToReactions(reactions, invariants, gates, roundIndex, cu
131
196
  * Same agents, same scenario, same reaction model —
132
197
  * but world rules reshape the outcomes.
133
198
  */
134
- async function runGovernedSwarmSimulation(scenario, stakeholders, paths, config, worldDef, nvWorld, request) {
199
+ async function runGovernedSwarmSimulation(scenario, stakeholders, paths, config, worldDef, nvWorld, request, narrativeEvents) {
135
200
  // First run the unmodified simulation to get raw reactions
136
201
  const rawResult = await (0, swarmSimulation_1.runSwarmSimulation)(scenario, stakeholders, paths, config);
137
202
  // Now re-run applying governance to each round
138
203
  const governedRounds = [];
139
204
  const allInterventions = [];
140
205
  const allGuardVerdicts = [];
206
+ const allNarrativeImpacts = [];
207
+ let latestEnforcementLog = [];
141
208
  let cumulativeAvgImpact = 0;
142
209
  for (const round of rawResult.rounds) {
143
210
  // --- Real governance engine: evaluate each agent's action ---
@@ -159,8 +226,17 @@ async function runGovernedSwarmSimulation(scenario, stakeholders, paths, config,
159
226
  }
160
227
  }
161
228
  }
229
+ // --- Narrative injection: information shocks ---
230
+ if (narrativeEvents && narrativeEvents.length > 0) {
231
+ const roundEvents = (0, narrativeInjection_1.getEventsForRound)(narrativeEvents, round.round);
232
+ for (const event of roundEvents) {
233
+ const impact = (0, narrativeInjection_1.injectNarrative)(round.reactions, event, stakeholders);
234
+ allNarrativeImpacts.push(impact);
235
+ }
236
+ }
162
237
  // --- Heuristic governance: invariant/gate enforcement (always applied) ---
163
- const { reactions: governedReactions, interventions } = applyGovernanceToReactions(round.reactions, worldDef.invariants, worldDef.gates ?? [], round.round, cumulativeAvgImpact);
238
+ const { reactions: governedReactions, interventions, enforcementLog } = applyGovernanceToReactions(round.reactions, worldDef.invariants, worldDef.gates ?? [], round.round, cumulativeAvgImpact);
239
+ latestEnforcementLog = [...enforcementLog];
164
240
  // Recompute emergent dynamics with governed reactions
165
241
  const emergentDynamics = detectGovernedDynamics(governedReactions, interventions);
166
242
  governedRounds.push({
@@ -184,6 +260,8 @@ async function runGovernedSwarmSimulation(scenario, stakeholders, paths, config,
184
260
  },
185
261
  allInterventions,
186
262
  guardVerdicts: allGuardVerdicts,
263
+ narrativeImpacts: allNarrativeImpacts,
264
+ enforcementLog: latestEnforcementLog,
187
265
  };
188
266
  }
189
267
  function detectGovernedDynamics(reactions, interventions) {
@@ -323,7 +401,7 @@ function buildComparisonNarrative(baseline, governed, worldThesis) {
323
401
  * 3. Compare metrics
324
402
  * 4. Generate narrative
325
403
  */
326
- async function runGovernedComparison(request, worldLite, paths) {
404
+ async function runGovernedComparison(request, worldLite, paths, narrativeEvents) {
327
405
  const stakeholders = (request.stakeholders ?? []).map((s) => typeof s === "string" ? { id: s, disposition: "unknown" } : s);
328
406
  const swarmConfig = request.swarm ?? {
329
407
  enabled: true,
@@ -336,7 +414,7 @@ async function runGovernedComparison(request, worldLite, paths) {
336
414
  const baselineSwarm = await (0, swarmSimulation_1.runSwarmSimulation)(request.scenario, stakeholders, paths, swarmConfig);
337
415
  const baselineMetrics = computeMetrics(baselineSwarm);
338
416
  // Step 2: Governed — same scenario, world rules applied (real engine + heuristics)
339
- const { swarm: governedSwarm, guardVerdicts } = await runGovernedSwarmSimulation(request.scenario, stakeholders, paths, swarmConfig, worldLite, nvWorld, request);
417
+ const { swarm: governedSwarm, guardVerdicts, narrativeImpacts: collectedNarrativeImpacts, enforcementLog: enforcementClassification } = await runGovernedSwarmSimulation(request.scenario, stakeholders, paths, swarmConfig, worldLite, nvWorld, request, narrativeEvents);
340
418
  const governedMetrics = computeMetrics(governedSwarm);
341
419
  // Step 3: Build world simulation (deterministic state evolution via real engine)
342
420
  let worldSimulation;
@@ -392,6 +470,8 @@ async function runGovernedComparison(request, worldLite, paths) {
392
470
  invariantsChecked,
393
471
  triggeredGuards: [...triggeredGuardIds],
394
472
  };
473
+ // Step 6: Behavioral analysis — what agents did differently
474
+ const behavioralAnalysis = (0, behavioralAnalysis_1.analyzeBehavior)(baselineSwarm.rounds, governedSwarm.rounds);
395
475
  return {
396
476
  scenario: request.scenario,
397
477
  worldRules: {
@@ -417,6 +497,9 @@ async function runGovernedComparison(request, worldLite, paths) {
417
497
  narrative,
418
498
  },
419
499
  governanceStats,
500
+ enforcementClassification: enforcementClassification.length > 0 ? enforcementClassification : undefined,
501
+ narrativeImpacts: collectedNarrativeImpacts.length > 0 ? collectedNarrativeImpacts : undefined,
502
+ behavioralAnalysis,
420
503
  };
421
504
  }
422
505
  // ============================================
@@ -478,6 +561,20 @@ exports.TRADING_DEMO = {
478
561
  { id: "GATE-002", label: "Contagion Warning", condition: "contagion_spread == systemic", severity: "critical" },
479
562
  { id: "GATE-003", label: "Leverage Warning", condition: "leverage_ratio > 8", severity: "warning" },
480
563
  ],
564
+ ai_roles: [
565
+ {
566
+ id: "ai_translator",
567
+ type: "ai",
568
+ permissions: ["translate_input"],
569
+ constraints: ["must_output_valid_schema", "no_invention_of_events", "confidence_must_be_provided"],
570
+ },
571
+ {
572
+ id: "ai_analyst",
573
+ type: "ai",
574
+ permissions: ["generate_report", "summarize_trace"],
575
+ constraints: ["must_reference_trace", "must_include_blocked_actions", "must_include_metrics", "no_unverifiable_claims"],
576
+ },
577
+ ],
481
578
  },
482
579
  paths: [
483
580
  {