@neuroverseos/nv-sim 0.1.4 → 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 (44) hide show
  1. package/README.md +94 -0
  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/{reportEngine-BfteK4MN.js → reportEngine-BVdQ2_nW.js} +1 -1
  7. package/dist/components/ConstraintsPanel.js +11 -0
  8. package/dist/components/StakeholderBuilder.js +32 -0
  9. package/dist/components/ui/badge.js +24 -0
  10. package/dist/components/ui/button.js +70 -0
  11. package/dist/components/ui/card.js +57 -0
  12. package/dist/components/ui/input.js +44 -0
  13. package/dist/components/ui/label.js +45 -0
  14. package/dist/components/ui/select.js +70 -0
  15. package/dist/engine/aiProvider.js +427 -2
  16. package/dist/engine/auditTrace.js +352 -0
  17. package/dist/engine/behavioralAnalysis.js +605 -0
  18. package/dist/engine/cli.js +1087 -13
  19. package/dist/engine/dynamicsGovernance.js +588 -0
  20. package/dist/engine/fullGovernedLoop.js +367 -0
  21. package/dist/engine/governedSimulation.js +77 -6
  22. package/dist/engine/index.js +41 -1
  23. package/dist/engine/liveVisualizer.js +1381 -175
  24. package/dist/engine/metrics/science.metrics.js +335 -0
  25. package/dist/engine/policyEnforcement.js +1611 -0
  26. package/dist/engine/policyEngine.js +799 -0
  27. package/dist/engine/primeRadiant.js +540 -0
  28. package/dist/engine/scenarioComparison.js +463 -0
  29. package/dist/engine/swarmSimulation.js +54 -1
  30. package/dist/engine/worldComparison.js +164 -0
  31. package/dist/engine/worldStorage.js +232 -0
  32. package/dist/index.html +2 -2
  33. package/dist/lib/reasoningEngine.js +290 -0
  34. package/dist/lib/simulationAdapter.js +686 -0
  35. package/dist/lib/swarmParser.js +291 -0
  36. package/dist/lib/types.js +2 -0
  37. package/dist/lib/utils.js +8 -0
  38. package/dist/runtime/govern.js +473 -0
  39. package/dist/runtime/index.js +75 -0
  40. package/dist/runtime/types.js +11 -0
  41. package/package.json +5 -2
  42. package/dist/assets/index-DHKd4rcV.js +0 -338
  43. package/dist/assets/index-SyyA3z3U.css +0 -1
  44. package/dist/assets/swarmSimulation-DHDqjfMa.js +0 -1
package/README.md CHANGED
@@ -291,10 +291,104 @@ npx @neuroverseos/nv-sim chaos --runs 500
291
291
  npx @neuroverseos/nv-sim serve
292
292
  ```
293
293
 
294
+ ## Policy Enforcement — The Product Loop
295
+
296
+ Write rules in plain English. Run the same scenario. See what changes. Adjust and repeat.
297
+
298
+ ### Step 1: See it work (zero config)
299
+
300
+ ```bash
301
+ npx nv-sim enforce
302
+ ```
303
+
304
+ Runs three iterations automatically: no rules → light rules → full rules. You see divergence immediately.
305
+
306
+ ### Step 2: Write your own rules
307
+
308
+ Create a text file. That's it.
309
+
310
+ ```
311
+ # my-rules.txt
312
+
313
+ Block panic selling during high volatility
314
+ Limit leverage to 5x
315
+ Maintain minimum liquidity floor
316
+ Slow down algorithmic trading when contagion spreads
317
+ ```
318
+
319
+ ### Step 3: Run it
320
+
321
+ ```bash
322
+ npx nv-sim enforce trading my-rules.txt
323
+ ```
324
+
325
+ The engine parses your plain English into governance rules, runs the scenario, and shows what changed.
326
+
327
+ ### Step 4: Change a rule. Run again.
328
+
329
+ Remove "Limit leverage to 5x". Run again. Did stability drop? That rule was load-bearing.
330
+
331
+ Add "Require transparency for all large trades". Run again. Did effectiveness improve?
332
+
333
+ The report tracks every change:
334
+
335
+ ```
336
+ RULE CHANGES
337
+ Run 2:
338
+ + Block panic selling during high volatility
339
+ + Slow down algorithmic trading when contagion spreads
340
+ - Limit leverage to 5x
341
+
342
+ DIVERGENCE ANALYSIS
343
+ Stability trend: 79% → 98%
344
+ Effectiveness trend: 11% → 32%
345
+
346
+ KEY INSIGHT
347
+ Enforcement gates are the key differentiator. Rules alone: 11%. Rules + gates: 32%.
348
+
349
+ TRY THIS EXPERIMENT
350
+ Remove this rule and see what happens:
351
+ Remove "Block panic selling during high volatility" from your rules file, then run again.
352
+ If stability drops, that rule was load-bearing. If nothing changes, it was noise.
353
+ ```
354
+
355
+ ### Step 5: Compare two rule sets side by side
356
+
357
+ ```bash
358
+ npx nv-sim enforce trading light-rules.txt strict-rules.txt
359
+ ```
360
+
361
+ ### Rule patterns
362
+
363
+ The engine understands these patterns in plain English:
364
+
365
+ | Pattern | What it does | Example |
366
+ |---------|-------------|---------|
367
+ | `Block X` | Hard suppression of matching actions | `Block panic selling` |
368
+ | `Limit X` / `Cap X` | Caps extreme positions | `Limit leverage to 5x` |
369
+ | `Slow X` / `Dampen X` | Reduces large movements | `Slow down algorithmic trading` |
370
+ | `Maintain X` / `Floor X` | Enforces minimum thresholds | `Maintain minimum liquidity` |
371
+ | `Rebalance X` | Pulls extremes toward equilibrium | `Rebalance correlated positions` |
372
+ | `Require X` | Enforceable structural constraint | `Require transparency for large trades` |
373
+ | `Monitor X` | Generates a circuit breaker gate | `Monitor contagion spread` |
374
+
375
+ ### Other scenarios
376
+
377
+ ```bash
378
+ npx nv-sim enforce strait_of_hormuz my-rules.txt # Same rules, different scenario
379
+ npx nv-sim enforce ai_regulation_crisis # Default progressive run
380
+ npx nv-sim enforce trading --output=report.json # Save as JSON
381
+ ```
382
+
383
+ ### Advanced: JSON world files
384
+
385
+ For full control over gates, state variables, and thesis, use JSON world files. See `examples/worlds/` for templates. Enforce accepts both `.txt` and `.json` — mix and match.
386
+
294
387
  ## Commands
295
388
 
296
389
  | Command | What It Does |
297
390
  |---------|-------------|
391
+ | `nv-sim enforce [preset]` | Policy enforcement lab — iterative rule testing |
298
392
  | `nv-sim visualize` | Interactive control platform |
299
393
  | `nv-sim compare [preset]` | Baseline vs governed simulation |
300
394
  | `nv-sim compare --inject event@round,...` | With narrative shocks |
@@ -0,0 +1,461 @@
1
+ "use strict";
2
+ /**
3
+ * MiroFish Adapter — Governance Wrapper for MiroFish Simulations
4
+ *
5
+ * "MiroFish shows what could happen. NeuroVerse ensures what happens makes sense."
6
+ *
7
+ * This adapter wraps MiroFish's simulation loop with dual-layer governance.
8
+ * It does NOT fork or modify MiroFish — it intercepts inputs, actions, and outputs.
9
+ *
10
+ * Architecture:
11
+ * MiroFish runs as-is
12
+ * NeuroVerse wraps:
13
+ * - agent actions → govern(action) [Layer A]
14
+ * - system state → governDynamics(state) [Layer B]
15
+ * - outputs → science metrics + verdicts
16
+ *
17
+ * Hook points (where NeuroVerse intercepts):
18
+ * 1. Agent execution: action = agent.act() → govern(action) → commit
19
+ * 2. Round completion: state = step(actions) → governDynamics(state)
20
+ * 3. Artifact creation: artifact = produce() → governArtifact(artifact)
21
+ *
22
+ * Usage:
23
+ * const wrapper = createMiroFishWrapper({
24
+ * policyText: SCIENCE_POLICY_TEXT,
25
+ * onIntervention: (event) => emitToUI(event),
26
+ * })
27
+ *
28
+ * // In MiroFish's loop:
29
+ * wrapper.interceptAction(agentId, action)
30
+ * wrapper.completeCycle(cycleState)
31
+ * const metrics = wrapper.getMetrics()
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.createMiroFishWrapper = createMiroFishWrapper;
35
+ exports.generateDemoSimulation = generateDemoSimulation;
36
+ exports.runMiroFishComparison = runMiroFishComparison;
37
+ const govern_1 = require("../runtime/govern");
38
+ const dynamicsGovernance_1 = require("../engine/dynamicsGovernance");
39
+ const science_metrics_1 = require("../engine/metrics/science.metrics");
40
+ // ============================================
41
+ // ACTION CONVERSION
42
+ // ============================================
43
+ function simulatorActionToAgentAction(action) {
44
+ return {
45
+ agentId: action.agentId,
46
+ type: action.type,
47
+ description: action.description,
48
+ magnitude: action.magnitude,
49
+ context: {
50
+ originalImpact: action.impact,
51
+ confidence: action.confidence,
52
+ trigger: action.trigger,
53
+ ...action.metadata,
54
+ },
55
+ };
56
+ }
57
+ function simulatorActionsToReactions(actions) {
58
+ return actions.map((action) => ({
59
+ stakeholder_id: action.agentId,
60
+ reaction: action.description,
61
+ confidence: action.confidence,
62
+ impact: action.impact,
63
+ trigger: action.trigger ?? "simulation_cycle",
64
+ }));
65
+ }
66
+ // ============================================
67
+ // FACTORY: createMiroFishWrapper()
68
+ // ============================================
69
+ function createMiroFishWrapper(config = {}) {
70
+ const policyText = config.policyText ?? science_metrics_1.SCIENCE_POLICY_TEXT;
71
+ let _enabled = config.enabled ?? true;
72
+ const actionGov = (0, govern_1.createGovernor)({
73
+ policyText,
74
+ sensitivity: 0.5,
75
+ });
76
+ const dynamicsGov = (0, dynamicsGovernance_1.createDynamicsGovernor)(policyText, { ...science_metrics_1.SCIENCE_INITIAL_STATE, ...config.initialState });
77
+ let cycleCount = 0;
78
+ const actionStats = { total: 0, allowed: 0, blocked: 0, modified: 0, paused: 0 };
79
+ const recentInterventions = [];
80
+ // --- Intercept single action (Layer A) ---
81
+ function interceptAction(action) {
82
+ actionStats.total++;
83
+ if (!_enabled) {
84
+ actionStats.allowed++;
85
+ return {
86
+ original: action,
87
+ verdict: { status: "ALLOW", reason: "Governance disabled", action: simulatorActionToAgentAction(action), rulesFired: [], confidence: 1, timestamp: Date.now() },
88
+ governed: action,
89
+ wasModified: false,
90
+ wasBlocked: false,
91
+ };
92
+ }
93
+ const agentAction = simulatorActionToAgentAction(action);
94
+ const currentState = dynamicsGov.state;
95
+ const worldState = {
96
+ volatility: currentState.outrage * 100,
97
+ liquidity: currentState.trust * 100,
98
+ polarization: currentState.polarization * 100,
99
+ cascade_risk: currentState.cascadeRisk * 100,
100
+ };
101
+ const verdict = actionGov.evaluate(agentAction, worldState);
102
+ switch (verdict.status) {
103
+ case "ALLOW":
104
+ actionStats.allowed++;
105
+ break;
106
+ case "BLOCK":
107
+ actionStats.blocked++;
108
+ break;
109
+ case "MODIFY":
110
+ actionStats.modified++;
111
+ break;
112
+ case "PAUSE":
113
+ actionStats.paused++;
114
+ break;
115
+ }
116
+ let governed;
117
+ let wasModified = false;
118
+ let wasBlocked = false;
119
+ if (verdict.status === "BLOCK") {
120
+ governed = null;
121
+ wasBlocked = true;
122
+ recentInterventions.unshift({
123
+ cycle: cycleCount,
124
+ type: "block",
125
+ agentId: action.agentId,
126
+ description: `Blocked: ${action.type} from ${action.agentId} — ${verdict.reason}`,
127
+ magnitude: action.magnitude,
128
+ metric: "action",
129
+ before: action.magnitude,
130
+ after: 0,
131
+ });
132
+ }
133
+ else if (verdict.status === "MODIFY" && verdict.action) {
134
+ const ratio = verdict.action.magnitude / Math.max(0.001, action.magnitude);
135
+ governed = {
136
+ ...action,
137
+ magnitude: verdict.action.magnitude,
138
+ impact: action.impact * ratio,
139
+ };
140
+ wasModified = true;
141
+ recentInterventions.unshift({
142
+ cycle: cycleCount,
143
+ type: "modify",
144
+ agentId: action.agentId,
145
+ description: `Modified: ${action.type} from ${action.agentId} — magnitude reduced`,
146
+ magnitude: verdict.action.magnitude,
147
+ metric: "magnitude",
148
+ before: action.magnitude,
149
+ after: verdict.action.magnitude,
150
+ });
151
+ }
152
+ else if (verdict.status === "PAUSE") {
153
+ governed = {
154
+ ...action,
155
+ magnitude: action.magnitude * 0.5,
156
+ impact: action.impact * 0.5,
157
+ };
158
+ wasModified = true;
159
+ }
160
+ else {
161
+ governed = action;
162
+ }
163
+ // Keep only recent interventions
164
+ if (recentInterventions.length > 50) {
165
+ recentInterventions.length = 50;
166
+ }
167
+ const result = {
168
+ original: action,
169
+ verdict,
170
+ governed,
171
+ wasModified,
172
+ wasBlocked,
173
+ };
174
+ config.onAction?.(result);
175
+ return result;
176
+ }
177
+ // --- Complete cycle (Layer B) ---
178
+ function completeCycle(state) {
179
+ cycleCount = state.cycle;
180
+ if (!_enabled) {
181
+ // Still track state but don't intervene
182
+ const reactions = simulatorActionsToReactions(state.actions);
183
+ const result = dynamicsGov.governRound(reactions, state.cycle);
184
+ // Without governance, system degrades naturally
185
+ dynamicsGov.mutateState((st) => {
186
+ st.trust = Math.max(0.05, st.trust - 0.04);
187
+ st.cascadeRisk = Math.min(1, st.cascadeRisk + 0.05);
188
+ st.polarization = Math.min(1, st.polarization + 0.03);
189
+ st.outrage = Math.min(1, st.outrage + 0.02);
190
+ });
191
+ return result;
192
+ }
193
+ const reactions = simulatorActionsToReactions(state.actions);
194
+ // Apply simulator-reported metrics if available
195
+ if (state.systemMetrics) {
196
+ dynamicsGov.mutateState((st) => {
197
+ if (state.systemMetrics.trust !== undefined) {
198
+ st.trust = st.trust * 0.7 + state.systemMetrics.trust * 0.3;
199
+ }
200
+ if (state.systemMetrics.polarization !== undefined) {
201
+ st.polarization = st.polarization * 0.7 + state.systemMetrics.polarization * 0.3;
202
+ }
203
+ });
204
+ }
205
+ const result = dynamicsGov.governRound(reactions, state.cycle);
206
+ // Feed governance activity into state (blocked actions improve system health)
207
+ const blockFraction = actionStats.blocked / Math.max(1, actionStats.total);
208
+ if (blockFraction > 0.1) {
209
+ dynamicsGov.mutateState((st) => {
210
+ st.trust = Math.min(1, st.trust + blockFraction * 0.06);
211
+ st.cascadeRisk = Math.max(0, st.cascadeRisk - blockFraction * 0.08);
212
+ });
213
+ }
214
+ // Map dynamics interventions to wrapper events
215
+ for (const intervention of result.interventions) {
216
+ const typeMap = {
217
+ PROPAGATION_LIMIT: "slow",
218
+ AMPLIFICATION_DAMPEN: "slow",
219
+ CASCADE_BREAKER: "break",
220
+ COOLING_PERIOD: "cool",
221
+ TRUST_BOOST: "boost",
222
+ VISIBILITY_SHIFT: "boost",
223
+ FEEDBACK_DAMPEN: "slow",
224
+ };
225
+ recentInterventions.unshift({
226
+ cycle: state.cycle,
227
+ type: typeMap[intervention.type] ?? "slow",
228
+ agentId: "system",
229
+ description: intervention.description.length > 120
230
+ ? intervention.description.slice(0, 120) + "..."
231
+ : intervention.description,
232
+ magnitude: intervention.magnitude,
233
+ metric: intervention.effect.metric,
234
+ before: intervention.effect.before,
235
+ after: intervention.effect.after,
236
+ });
237
+ }
238
+ if (recentInterventions.length > 50) {
239
+ recentInterventions.length = 50;
240
+ }
241
+ // Notify UI
242
+ config.onStateChange?.(result.systemState);
243
+ for (const intervention of result.interventions) {
244
+ const typeMap = {
245
+ PROPAGATION_LIMIT: "slow",
246
+ AMPLIFICATION_DAMPEN: "slow",
247
+ CASCADE_BREAKER: "break",
248
+ COOLING_PERIOD: "cool",
249
+ TRUST_BOOST: "boost",
250
+ VISIBILITY_SHIFT: "boost",
251
+ FEEDBACK_DAMPEN: "slow",
252
+ };
253
+ config.onIntervention?.({
254
+ cycle: state.cycle,
255
+ type: typeMap[intervention.type] ?? "slow",
256
+ agentId: "system",
257
+ description: intervention.description,
258
+ magnitude: intervention.magnitude,
259
+ metric: intervention.effect.metric,
260
+ before: intervention.effect.before,
261
+ after: intervention.effect.after,
262
+ });
263
+ }
264
+ const metrics = getMetrics();
265
+ config.onCycleComplete?.(metrics);
266
+ return result;
267
+ }
268
+ // --- Get current metrics ---
269
+ function getMetrics() {
270
+ const state = dynamicsGov.state;
271
+ const sciState = (0, science_metrics_1.interpretScienceState)(state);
272
+ const stats = dynamicsGov.stats;
273
+ return {
274
+ cycle: cycleCount,
275
+ enabled: _enabled,
276
+ systemState: state,
277
+ scienceState: sciState,
278
+ actionStats: { ...actionStats },
279
+ dynamicsStats: stats,
280
+ recentInterventions: [...recentInterventions],
281
+ trajectory: stats.totalRounds > 0
282
+ ? (state.cascadeRisk > 0.7 ? "critical"
283
+ : state.outrage > 0.5 ? "escalating"
284
+ : state.trust > 0.5 ? "stabilizing"
285
+ : "at-risk")
286
+ : "initializing",
287
+ assessment: sciState.overallAssessment,
288
+ };
289
+ }
290
+ function setEnabled(enabled) {
291
+ _enabled = enabled;
292
+ }
293
+ function reset() {
294
+ dynamicsGov.reset();
295
+ cycleCount = 0;
296
+ actionStats.total = 0;
297
+ actionStats.allowed = 0;
298
+ actionStats.blocked = 0;
299
+ actionStats.modified = 0;
300
+ actionStats.paused = 0;
301
+ recentInterventions.length = 0;
302
+ }
303
+ return {
304
+ interceptAction,
305
+ completeCycle,
306
+ getMetrics,
307
+ setEnabled,
308
+ get enabled() { return _enabled; },
309
+ get state() { return dynamicsGov.state; },
310
+ get scienceState() { return (0, science_metrics_1.interpretScienceState)(dynamicsGov.state); },
311
+ reset,
312
+ };
313
+ }
314
+ // ============================================
315
+ // DEMO: generateDemoSimulation()
316
+ // ============================================
317
+ /**
318
+ * Generate a realistic multi-agent simulation scenario.
319
+ *
320
+ * Models a market stress event where agents react over 10 rounds:
321
+ * - Rounds 1-2: Normal trading, low volatility
322
+ * - Rounds 3-4: Shock event, panic selling begins
323
+ * - Rounds 5-6: Cascade risk as agents amplify each other
324
+ * - Rounds 7-8: With governance: dampened. Without: spiral.
325
+ * - Rounds 9-10: Recovery (governed) or collapse (ungoverned)
326
+ */
327
+ function generateDemoSimulation() {
328
+ const agents = [
329
+ { id: "momentum_trader_1", role: "momentum_trader", volatility: 0.7 },
330
+ { id: "momentum_trader_2", role: "momentum_trader", volatility: 0.6 },
331
+ { id: "market_maker_1", role: "market_maker", volatility: 0.2 },
332
+ { id: "hedge_fund_1", role: "hedge_fund", volatility: 0.5 },
333
+ { id: "hedge_fund_2", role: "hedge_fund", volatility: 0.4 },
334
+ { id: "retail_investor_1", role: "retail_investor", volatility: 0.8 },
335
+ { id: "retail_investor_2", role: "retail_investor", volatility: 0.9 },
336
+ { id: "algo_trader_1", role: "algorithmic_trader", volatility: 0.65 },
337
+ ];
338
+ const rounds = [];
339
+ // Seeded pseudo-random for deterministic output
340
+ let seed = 42;
341
+ const rand = () => { seed = (seed * 16807 + 0) % 2147483647; return seed / 2147483647; };
342
+ for (let round = 1; round <= 10; round++) {
343
+ const actions = [];
344
+ // Market pressure curve: peaks at round 5-6
345
+ const pressure = round <= 2 ? 0.1 + round * 0.05
346
+ : round <= 4 ? 0.2 + (round - 2) * 0.2
347
+ : round <= 6 ? 0.6 + (round - 4) * 0.15
348
+ : round <= 8 ? 0.9 - (round - 6) * 0.1
349
+ : 0.7 - (round - 8) * 0.2;
350
+ for (const agent of agents) {
351
+ const agentPanic = pressure * agent.volatility;
352
+ const noise = (rand() - 0.5) * 0.3;
353
+ let type;
354
+ let description;
355
+ let magnitude;
356
+ let impact;
357
+ if (agentPanic + noise > 0.7) {
358
+ // Destabilizing action
359
+ const destabilizing = ["panic_sell", "aggressive_buy", "increase_leverage"];
360
+ type = destabilizing[Math.floor(rand() * destabilizing.length)];
361
+ magnitude = 0.6 + rand() * 0.4;
362
+ impact = -(0.3 + rand() * 0.5);
363
+ description = `${agent.role} executes ${type} under market stress (pressure: ${(agentPanic * 100).toFixed(0)}%)`;
364
+ }
365
+ else if (agentPanic + noise > 0.4) {
366
+ // Neutral action
367
+ const neutral = ["buy", "sell", "short", "cover"];
368
+ type = neutral[Math.floor(rand() * neutral.length)];
369
+ magnitude = 0.3 + rand() * 0.4;
370
+ impact = (rand() - 0.5) * 0.4;
371
+ description = `${agent.role} takes ${type} position amid uncertainty`;
372
+ }
373
+ else {
374
+ // Stabilizing action
375
+ const stabilizing = ["hold", "hedge", "reduce_exposure"];
376
+ type = stabilizing[Math.floor(rand() * stabilizing.length)];
377
+ magnitude = 0.1 + rand() * 0.3;
378
+ impact = 0.1 + rand() * 0.3;
379
+ description = `${agent.role} ${type}s to manage risk`;
380
+ }
381
+ actions.push({
382
+ agentId: agent.id,
383
+ type,
384
+ description,
385
+ magnitude: Number(magnitude.toFixed(3)),
386
+ impact: Number(impact.toFixed(3)),
387
+ confidence: Number((0.4 + rand() * 0.5).toFixed(3)),
388
+ trigger: round <= 2 ? "normal_market" : round <= 4 ? "volatility_spike" : "cascade_pressure",
389
+ });
390
+ }
391
+ rounds.push(actions);
392
+ }
393
+ return {
394
+ rounds,
395
+ scenario: "Market stress cascade — 8 agents, 10 rounds, shock at round 3",
396
+ };
397
+ }
398
+ /**
399
+ * Run a full governed vs ungoverned comparison using the MiroFish wrapper.
400
+ *
401
+ * This is the self-contained demo: no external dependencies,
402
+ * no Python, no MiroFish instance needed.
403
+ */
404
+ function runMiroFishComparison() {
405
+ const demo = generateDemoSimulation();
406
+ // --- Governed run ---
407
+ const govWrapper = createMiroFishWrapper({ enabled: true });
408
+ const govTimeline = [];
409
+ for (let i = 0; i < demo.rounds.length; i++) {
410
+ const round = demo.rounds[i];
411
+ const approvedActions = [];
412
+ for (const action of round) {
413
+ const result = govWrapper.interceptAction(action);
414
+ if (!result.wasBlocked) {
415
+ approvedActions.push(result.governed ?? action);
416
+ }
417
+ }
418
+ govWrapper.completeCycle({
419
+ cycle: i + 1,
420
+ actions: approvedActions,
421
+ });
422
+ govTimeline.push(govWrapper.getMetrics());
423
+ }
424
+ const govMetrics = govWrapper.getMetrics();
425
+ const govState = govMetrics.systemState;
426
+ const govVerdict = govState.trust > 0.5 && govState.cascadeRisk < 0.3
427
+ ? "stabilized" : govState.trust < 0.2 ? "collapsed" : "at-risk";
428
+ // --- Ungoverned run ---
429
+ const ungovWrapper = createMiroFishWrapper({ enabled: false });
430
+ const ungovTimeline = [];
431
+ for (let i = 0; i < demo.rounds.length; i++) {
432
+ const round = demo.rounds[i];
433
+ for (const action of round) {
434
+ ungovWrapper.interceptAction(action);
435
+ }
436
+ ungovWrapper.completeCycle({
437
+ cycle: i + 1,
438
+ actions: round,
439
+ });
440
+ ungovTimeline.push(ungovWrapper.getMetrics());
441
+ }
442
+ const ungovMetrics = ungovWrapper.getMetrics();
443
+ const ungovState = ungovMetrics.systemState;
444
+ const ungovVerdict = ungovState.trust > 0.5 && ungovState.cascadeRisk < 0.3
445
+ ? "stabilized" : ungovState.trust < 0.2 ? "collapsed" : "at-risk";
446
+ return {
447
+ governed: {
448
+ metrics: govMetrics,
449
+ timeline: govTimeline,
450
+ totalBlocked: govMetrics.actionStats.blocked,
451
+ totalModified: govMetrics.actionStats.modified,
452
+ verdict: govVerdict,
453
+ },
454
+ ungoverned: {
455
+ metrics: ungovMetrics,
456
+ timeline: ungovTimeline,
457
+ verdict: ungovVerdict,
458
+ },
459
+ scenario: demo.scenario,
460
+ };
461
+ }