@neuroverseos/nv-sim 0.1.0
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/LICENSE +201 -0
- package/README.md +73 -0
- package/dist/engine/analyzer.js +651 -0
- package/dist/engine/api.js +208 -0
- package/dist/engine/chaosEngine.js +292 -0
- package/dist/engine/cli.js +803 -0
- package/dist/engine/goalEngine.js +559 -0
- package/dist/engine/governance.js +210 -0
- package/dist/engine/governedSimulation.js +529 -0
- package/dist/engine/index.js +82 -0
- package/dist/engine/mirofish.js +295 -0
- package/dist/engine/reasoningEngine.js +548 -0
- package/dist/engine/scenarioCapsule.js +351 -0
- package/dist/engine/swarmSimulation.js +244 -0
- package/dist/engine/types.js +15 -0
- package/dist/engine/worldBridge.js +481 -0
- package/dist/package.json +1 -0
- package/package.json +110 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* POST /reason API Route Handler
|
|
4
|
+
*
|
|
5
|
+
* The HTTP interface for the Echelon reasoning engine.
|
|
6
|
+
* This is the "Stripe for reasoning" endpoint that agents and
|
|
7
|
+
* applications call for structured decision analysis.
|
|
8
|
+
*
|
|
9
|
+
* Endpoints:
|
|
10
|
+
* POST /api/v1/reason — Run structured reasoning on a scenario
|
|
11
|
+
* POST /api/v1/reason/capsule — Create a shareable scenario capsule
|
|
12
|
+
* GET /api/v1/reason/health — Engine health check
|
|
13
|
+
* GET /api/v1/reason/presets — List preset scenario templates
|
|
14
|
+
*
|
|
15
|
+
* Design:
|
|
16
|
+
* - Stateless: no data persisted after response
|
|
17
|
+
* - BYOK: caller provides their own API key (future)
|
|
18
|
+
* - Governance traces in every response (open governance layer)
|
|
19
|
+
* - Rate limiting per API key (future)
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.parseReasonRequestBody = parseReasonRequestBody;
|
|
23
|
+
exports.handleReasonRequest = handleReasonRequest;
|
|
24
|
+
exports.handleReasonFromCapsule = handleReasonFromCapsule;
|
|
25
|
+
exports.handleCreateCapsule = handleCreateCapsule;
|
|
26
|
+
exports.handleHealthCheck = handleHealthCheck;
|
|
27
|
+
exports.handleListPresets = handleListPresets;
|
|
28
|
+
exports.handleGetPreset = handleGetPreset;
|
|
29
|
+
exports.handleRunPreset = handleRunPreset;
|
|
30
|
+
const reasoningEngine_1 = require("./reasoningEngine");
|
|
31
|
+
const scenarioCapsule_1 = require("./scenarioCapsule");
|
|
32
|
+
// ============================================
|
|
33
|
+
// REQUEST PARSING
|
|
34
|
+
// ============================================
|
|
35
|
+
/**
|
|
36
|
+
* Parse and validate a POST /reason request body.
|
|
37
|
+
*/
|
|
38
|
+
function parseReasonRequestBody(body) {
|
|
39
|
+
if (!body || typeof body !== "object") {
|
|
40
|
+
return {
|
|
41
|
+
error: {
|
|
42
|
+
status: "error",
|
|
43
|
+
error: {
|
|
44
|
+
code: "INVALID_REQUEST",
|
|
45
|
+
message: "Request body must be a JSON object",
|
|
46
|
+
},
|
|
47
|
+
reasoning_id: `rsn_${Date.now().toString(36)}_err`,
|
|
48
|
+
timestamp: new Date().toISOString(),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const b = body;
|
|
53
|
+
if (!b.scenario || typeof b.scenario !== "string") {
|
|
54
|
+
return {
|
|
55
|
+
error: {
|
|
56
|
+
status: "error",
|
|
57
|
+
error: {
|
|
58
|
+
code: "INVALID_REQUEST",
|
|
59
|
+
message: "\"scenario\" field is required and must be a string",
|
|
60
|
+
},
|
|
61
|
+
reasoning_id: `rsn_${Date.now().toString(36)}_err`,
|
|
62
|
+
timestamp: new Date().toISOString(),
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Build the request — the engine validates the rest
|
|
67
|
+
const request = {
|
|
68
|
+
scenario: b.scenario,
|
|
69
|
+
stakeholders: b.stakeholders,
|
|
70
|
+
assumptions: b.assumptions,
|
|
71
|
+
constraints: b.constraints,
|
|
72
|
+
depth: b.depth,
|
|
73
|
+
questions: b.questions,
|
|
74
|
+
perspective: b.perspective,
|
|
75
|
+
swarm: b.swarm,
|
|
76
|
+
};
|
|
77
|
+
return { request };
|
|
78
|
+
}
|
|
79
|
+
// ============================================
|
|
80
|
+
// ROUTE HANDLERS
|
|
81
|
+
// ============================================
|
|
82
|
+
/**
|
|
83
|
+
* POST /api/v1/reason
|
|
84
|
+
*
|
|
85
|
+
* Main reasoning endpoint. Accepts a scenario and returns
|
|
86
|
+
* structured analysis with governance traces.
|
|
87
|
+
*
|
|
88
|
+
* This is the endpoint that autonomous agents call.
|
|
89
|
+
*/
|
|
90
|
+
async function handleReasonRequest(body) {
|
|
91
|
+
const parsed = parseReasonRequestBody(body);
|
|
92
|
+
if ("error" in parsed) {
|
|
93
|
+
return parsed.error;
|
|
94
|
+
}
|
|
95
|
+
return (0, reasoningEngine_1.processReasonRequest)(parsed.request);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* POST /api/v1/reason (from capsule)
|
|
99
|
+
*
|
|
100
|
+
* Run reasoning from a scenario capsule.
|
|
101
|
+
* The capsule can come from a URL, a file, or another agent.
|
|
102
|
+
*/
|
|
103
|
+
async function handleReasonFromCapsule(capsule) {
|
|
104
|
+
const request = (0, scenarioCapsule_1.capsuleToReasonRequest)(capsule);
|
|
105
|
+
return (0, reasoningEngine_1.processReasonRequest)(request);
|
|
106
|
+
}
|
|
107
|
+
function handleCreateCapsule(body) {
|
|
108
|
+
const capsule = (0, scenarioCapsule_1.createCapsule)(body.title, body.request, {
|
|
109
|
+
signals: body.signals,
|
|
110
|
+
tags: body.tags,
|
|
111
|
+
author: body.author,
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
capsule,
|
|
115
|
+
encoded: (0, scenarioCapsule_1.encodeCapsule)(capsule),
|
|
116
|
+
shareable_url: (0, scenarioCapsule_1.buildShareableUrl)(capsule),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function handleHealthCheck() {
|
|
120
|
+
return {
|
|
121
|
+
status: "healthy",
|
|
122
|
+
engine_version: "0.1.0",
|
|
123
|
+
governance_layers: [
|
|
124
|
+
"echelon-constitutional (proprietary)",
|
|
125
|
+
"neuroverse-os-governance (open source)",
|
|
126
|
+
],
|
|
127
|
+
capabilities: [
|
|
128
|
+
"multi-path reasoning",
|
|
129
|
+
"assumption challenging",
|
|
130
|
+
"outcome projection",
|
|
131
|
+
"swarm simulation",
|
|
132
|
+
"scenario capsules",
|
|
133
|
+
"governance tracing",
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function handleListPresets() {
|
|
138
|
+
return {
|
|
139
|
+
presets: Object.entries(scenarioCapsule_1.SCENARIO_TEMPLATES).map(([id, template]) => ({
|
|
140
|
+
id,
|
|
141
|
+
title: template.title,
|
|
142
|
+
tags: template.tags ?? [],
|
|
143
|
+
stakeholder_count: template.stakeholders.length,
|
|
144
|
+
})),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* GET /api/v1/reason/presets/:id
|
|
149
|
+
*
|
|
150
|
+
* Get a specific preset scenario as a capsule, ready to run.
|
|
151
|
+
*/
|
|
152
|
+
function handleGetPreset(presetId) {
|
|
153
|
+
try {
|
|
154
|
+
const capsule = (0, scenarioCapsule_1.getPresetCapsule)(presetId);
|
|
155
|
+
return {
|
|
156
|
+
capsule,
|
|
157
|
+
shareable_url: (0, scenarioCapsule_1.buildShareableUrl)(capsule),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return {
|
|
162
|
+
status: "error",
|
|
163
|
+
error: {
|
|
164
|
+
code: "INVALID_REQUEST",
|
|
165
|
+
message: `Unknown preset: "${presetId}". Use GET /api/v1/reason/presets for available presets.`,
|
|
166
|
+
},
|
|
167
|
+
reasoning_id: `rsn_${Date.now().toString(36)}_err`,
|
|
168
|
+
timestamp: new Date().toISOString(),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* POST /api/v1/reason/preset/:id
|
|
174
|
+
*
|
|
175
|
+
* Run reasoning on a preset scenario.
|
|
176
|
+
* Convenience endpoint — loads the preset and runs it.
|
|
177
|
+
*/
|
|
178
|
+
async function handleRunPreset(presetId, overrides) {
|
|
179
|
+
try {
|
|
180
|
+
const capsule = (0, scenarioCapsule_1.getPresetCapsule)(presetId);
|
|
181
|
+
const baseRequest = (0, scenarioCapsule_1.capsuleToReasonRequest)(capsule);
|
|
182
|
+
// Apply any overrides
|
|
183
|
+
const request = {
|
|
184
|
+
...baseRequest,
|
|
185
|
+
...overrides,
|
|
186
|
+
assumptions: {
|
|
187
|
+
...baseRequest.assumptions,
|
|
188
|
+
...overrides?.assumptions,
|
|
189
|
+
},
|
|
190
|
+
constraints: {
|
|
191
|
+
...baseRequest.constraints,
|
|
192
|
+
...overrides?.constraints,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
return (0, reasoningEngine_1.processReasonRequest)(request);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return {
|
|
199
|
+
status: "error",
|
|
200
|
+
error: {
|
|
201
|
+
code: "INVALID_REQUEST",
|
|
202
|
+
message: `Unknown preset: "${presetId}"`,
|
|
203
|
+
},
|
|
204
|
+
reasoning_id: `rsn_${Date.now().toString(36)}_err`,
|
|
205
|
+
timestamp: new Date().toISOString(),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Chaos Engine — Automated Stress Testing for Governed Systems
|
|
4
|
+
*
|
|
5
|
+
* "Under what conditions does this system break?"
|
|
6
|
+
*
|
|
7
|
+
* Runs hundreds of randomized simulations to find:
|
|
8
|
+
* - System collapse probability (baseline vs governed)
|
|
9
|
+
* - Worst-case volatility
|
|
10
|
+
* - Most triggered governance rules
|
|
11
|
+
* - Breaking points and fragile assumptions
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* npx nv-sim chaos # Stress test the trading demo
|
|
15
|
+
* npx nv-sim chaos --runs 500 # More iterations
|
|
16
|
+
* npx nv-sim chaos strait_of_hormuz # Test a preset
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.runChaosTest = runChaosTest;
|
|
20
|
+
const scenarioCapsule_1 = require("./scenarioCapsule");
|
|
21
|
+
const governedSimulation_1 = require("./governedSimulation");
|
|
22
|
+
// ============================================
|
|
23
|
+
// SCENARIO PERTURBATION
|
|
24
|
+
// ============================================
|
|
25
|
+
/** Simple seeded PRNG for reproducible chaos */
|
|
26
|
+
function createRng(seed) {
|
|
27
|
+
let s = seed;
|
|
28
|
+
return () => {
|
|
29
|
+
s = (s * 1664525 + 1013904223) & 0xffffffff;
|
|
30
|
+
return (s >>> 0) / 0xffffffff;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const DISPOSITIONS = ["hostile", "neutral", "supportive"];
|
|
34
|
+
function perturbStakeholders(stakeholders, rng, intensity) {
|
|
35
|
+
return stakeholders.map((s) => {
|
|
36
|
+
// Randomly shift dispositions based on intensity
|
|
37
|
+
if (rng() < intensity * 0.4) {
|
|
38
|
+
const newDisp = DISPOSITIONS[Math.floor(rng() * DISPOSITIONS.length)];
|
|
39
|
+
return { ...s, disposition: newDisp };
|
|
40
|
+
}
|
|
41
|
+
return { ...s };
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function perturbScenario(scenario, rng) {
|
|
45
|
+
// Add random shock modifiers to the scenario text
|
|
46
|
+
const shocks = [
|
|
47
|
+
"A sudden liquidity crisis amplifies all pressures.",
|
|
48
|
+
"An unexpected regulatory announcement creates panic.",
|
|
49
|
+
"A major player unexpectedly exits the market.",
|
|
50
|
+
"Coordinated action by hostile actors intensifies pressure.",
|
|
51
|
+
"Communication systems experience partial failure.",
|
|
52
|
+
"Public sentiment shifts dramatically against incumbents.",
|
|
53
|
+
"A previously hidden risk factor suddenly materializes.",
|
|
54
|
+
"Cross-market contagion accelerates beyond models.",
|
|
55
|
+
"A trusted institution's credibility collapses.",
|
|
56
|
+
"External geopolitical shock compounds the crisis.",
|
|
57
|
+
];
|
|
58
|
+
const numShocks = Math.floor(rng() * 3) + 1;
|
|
59
|
+
const selected = [];
|
|
60
|
+
for (let i = 0; i < numShocks; i++) {
|
|
61
|
+
const idx = Math.floor(rng() * shocks.length);
|
|
62
|
+
if (!selected.includes(shocks[idx])) {
|
|
63
|
+
selected.push(shocks[idx]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return scenario + " " + selected.join(" ");
|
|
67
|
+
}
|
|
68
|
+
function perturbPaths(paths, rng, intensity) {
|
|
69
|
+
return paths.map((p) => ({
|
|
70
|
+
...p,
|
|
71
|
+
probability: Math.max(0.05, Math.min(0.95, p.probability + (rng() - 0.5) * intensity * 0.4)),
|
|
72
|
+
risk: rng() < intensity * 0.3
|
|
73
|
+
? ["moderate", "high", "critical"][Math.floor(rng() * 3)]
|
|
74
|
+
: p.risk,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
async function runChaosTest(presetId, options = {}) {
|
|
78
|
+
const { runs = 100, intensity = 0.6, seed = Date.now(), onProgress, } = options;
|
|
79
|
+
// Resolve scenario
|
|
80
|
+
const { request, world, paths } = resolvePreset(presetId);
|
|
81
|
+
const startTime = Date.now();
|
|
82
|
+
const rng = createRng(seed);
|
|
83
|
+
// Collect per-run results
|
|
84
|
+
const results = [];
|
|
85
|
+
for (let i = 0; i < runs; i++) {
|
|
86
|
+
// Perturb the scenario for this run
|
|
87
|
+
const perturbedRequest = {
|
|
88
|
+
...request,
|
|
89
|
+
scenario: perturbScenario(request.scenario, rng),
|
|
90
|
+
stakeholders: perturbStakeholders(request.stakeholders, rng, intensity),
|
|
91
|
+
};
|
|
92
|
+
const perturbedPaths = perturbPaths(paths, rng, intensity);
|
|
93
|
+
const result = await (0, governedSimulation_1.runGovernedComparison)(perturbedRequest, world, perturbedPaths);
|
|
94
|
+
results.push(result);
|
|
95
|
+
if (onProgress)
|
|
96
|
+
onProgress(i + 1, runs);
|
|
97
|
+
}
|
|
98
|
+
const durationMs = Date.now() - startTime;
|
|
99
|
+
// Aggregate
|
|
100
|
+
return aggregateResults(presetId, results, durationMs);
|
|
101
|
+
}
|
|
102
|
+
function resolvePreset(presetId) {
|
|
103
|
+
if (presetId === "trading" || presetId === "flash_crash") {
|
|
104
|
+
return {
|
|
105
|
+
request: governedSimulation_1.TRADING_DEMO.scenario,
|
|
106
|
+
world: governedSimulation_1.TRADING_DEMO.world,
|
|
107
|
+
paths: governedSimulation_1.TRADING_DEMO.paths,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const template = scenarioCapsule_1.SCENARIO_TEMPLATES[presetId];
|
|
111
|
+
if (!template) {
|
|
112
|
+
throw new Error(`Unknown preset: ${presetId}. Available: trading, ${Object.keys(scenarioCapsule_1.SCENARIO_TEMPLATES).join(", ")}`);
|
|
113
|
+
}
|
|
114
|
+
if (!template.world?.inline_definition) {
|
|
115
|
+
throw new Error(`Preset "${presetId}" has no world definition for chaos testing.`);
|
|
116
|
+
}
|
|
117
|
+
const request = {
|
|
118
|
+
scenario: template.scenario,
|
|
119
|
+
stakeholders: template.stakeholders,
|
|
120
|
+
assumptions: template.assumptions,
|
|
121
|
+
constraints: template.constraints,
|
|
122
|
+
depth: template.depth,
|
|
123
|
+
swarm: template.swarm,
|
|
124
|
+
};
|
|
125
|
+
const defaultPaths = [
|
|
126
|
+
{
|
|
127
|
+
id: "path_primary",
|
|
128
|
+
label: "Primary Response",
|
|
129
|
+
description: "Most likely course of action",
|
|
130
|
+
projected_outcome: "Moderate disruption with cascading effects",
|
|
131
|
+
probability: 0.6,
|
|
132
|
+
risk: "high",
|
|
133
|
+
tradeoffs: ["Speed vs. thoroughness"],
|
|
134
|
+
benefits_stakeholders: template.stakeholders.filter(s => s.disposition === "supportive").map(s => s.id),
|
|
135
|
+
harms_stakeholders: template.stakeholders.filter(s => s.disposition === "hostile").map(s => s.id),
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "path_defensive",
|
|
139
|
+
label: "Defensive Posture",
|
|
140
|
+
description: "Conservative approach prioritizing stability",
|
|
141
|
+
projected_outcome: "Reduced damage but slower recovery",
|
|
142
|
+
probability: 0.7,
|
|
143
|
+
risk: "moderate",
|
|
144
|
+
tradeoffs: ["Safety vs. opportunity cost"],
|
|
145
|
+
benefits_stakeholders: template.stakeholders.filter(s => s.disposition !== "hostile").map(s => s.id),
|
|
146
|
+
harms_stakeholders: [],
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
return { request, world: template.world.inline_definition, paths: defaultPaths };
|
|
150
|
+
}
|
|
151
|
+
// ============================================
|
|
152
|
+
// RESULT AGGREGATION
|
|
153
|
+
// ============================================
|
|
154
|
+
function aggregateResults(scenarioId, results, durationMs) {
|
|
155
|
+
const n = results.length;
|
|
156
|
+
// Collapse threshold: collapse probability >= 0.5
|
|
157
|
+
const COLLAPSE_THRESHOLD = 0.5;
|
|
158
|
+
// Baseline aggregates
|
|
159
|
+
const baselineCollapses = results.filter(r => r.baseline.metrics.collapseProbability >= COLLAPSE_THRESHOLD).length;
|
|
160
|
+
const baselineTrajectories = {};
|
|
161
|
+
let baselineTotalStability = 0;
|
|
162
|
+
let baselineMinStability = 1;
|
|
163
|
+
let baselineTotalVolatility = 0;
|
|
164
|
+
let baselineMaxVolatility = 0;
|
|
165
|
+
let baselineTotalCoalitionRisks = 0;
|
|
166
|
+
for (const r of results) {
|
|
167
|
+
const m = r.baseline.metrics;
|
|
168
|
+
baselineTotalStability += m.stabilityScore;
|
|
169
|
+
baselineMinStability = Math.min(baselineMinStability, m.stabilityScore);
|
|
170
|
+
baselineTotalVolatility += m.maxVolatility;
|
|
171
|
+
baselineMaxVolatility = Math.max(baselineMaxVolatility, m.maxVolatility);
|
|
172
|
+
baselineTotalCoalitionRisks += m.coalitionRisks;
|
|
173
|
+
const traj = r.baseline.swarm.trajectory;
|
|
174
|
+
baselineTrajectories[traj] = (baselineTrajectories[traj] ?? 0) + 1;
|
|
175
|
+
}
|
|
176
|
+
// Governed aggregates
|
|
177
|
+
const governedCollapses = results.filter(r => r.governed.metrics.collapseProbability >= COLLAPSE_THRESHOLD).length;
|
|
178
|
+
const governedTrajectories = {};
|
|
179
|
+
let governedTotalStability = 0;
|
|
180
|
+
let governedMinStability = 1;
|
|
181
|
+
let governedTotalVolatility = 0;
|
|
182
|
+
let governedMaxVolatility = 0;
|
|
183
|
+
let governedTotalCoalitionRisks = 0;
|
|
184
|
+
for (const r of results) {
|
|
185
|
+
const m = r.governed.metrics;
|
|
186
|
+
governedTotalStability += m.stabilityScore;
|
|
187
|
+
governedMinStability = Math.min(governedMinStability, m.stabilityScore);
|
|
188
|
+
governedTotalVolatility += m.maxVolatility;
|
|
189
|
+
governedMaxVolatility = Math.max(governedMaxVolatility, m.maxVolatility);
|
|
190
|
+
governedTotalCoalitionRisks += m.coalitionRisks;
|
|
191
|
+
const traj = r.governed.swarm.trajectory;
|
|
192
|
+
governedTrajectories[traj] = (governedTrajectories[traj] ?? 0) + 1;
|
|
193
|
+
}
|
|
194
|
+
// Impact aggregates
|
|
195
|
+
let totalEffectiveness = 0;
|
|
196
|
+
let improvementCount = 0;
|
|
197
|
+
let totalStabilityImprovement = 0;
|
|
198
|
+
let totalVolatilityReduction = 0;
|
|
199
|
+
for (const r of results) {
|
|
200
|
+
totalEffectiveness += r.comparison.governanceEffectiveness;
|
|
201
|
+
totalStabilityImprovement += r.comparison.stabilityImprovement;
|
|
202
|
+
totalVolatilityReduction += r.comparison.volatilityReduction;
|
|
203
|
+
if (r.comparison.governanceEffectiveness > 0)
|
|
204
|
+
improvementCount++;
|
|
205
|
+
}
|
|
206
|
+
// Most triggered rules — count gate triggers across all runs
|
|
207
|
+
const gateTriggerMap = new Map();
|
|
208
|
+
for (const r of results) {
|
|
209
|
+
for (const gate of r.worldRules.gates) {
|
|
210
|
+
const existing = gateTriggerMap.get(gate.id) ?? { label: gate.label, count: 0, severity: gate.severity };
|
|
211
|
+
existing.count++;
|
|
212
|
+
gateTriggerMap.set(gate.id, existing);
|
|
213
|
+
}
|
|
214
|
+
// Count from governance stats
|
|
215
|
+
for (const guardId of r.governanceStats.triggeredGuards) {
|
|
216
|
+
const existing = gateTriggerMap.get(guardId) ?? { label: guardId, count: 0, severity: "guard" };
|
|
217
|
+
existing.count++;
|
|
218
|
+
gateTriggerMap.set(guardId, existing);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const topRules = [...gateTriggerMap.entries()]
|
|
222
|
+
.map(([id, data]) => ({ ruleId: id, label: data.label, triggerCount: data.count, severity: data.severity }))
|
|
223
|
+
.sort((a, b) => b.triggerCount - a.triggerCount)
|
|
224
|
+
.slice(0, 5);
|
|
225
|
+
// Worst cases — runs with highest baseline collapse + lowest governed stability
|
|
226
|
+
const worstCases = results
|
|
227
|
+
.map((r, i) => ({
|
|
228
|
+
runIndex: i,
|
|
229
|
+
baselineCollapse: r.baseline.metrics.collapseProbability,
|
|
230
|
+
governedCollapse: r.governed.metrics.collapseProbability,
|
|
231
|
+
baselineStability: r.baseline.metrics.stabilityScore,
|
|
232
|
+
governedStability: r.governed.metrics.stabilityScore,
|
|
233
|
+
volatility: r.baseline.metrics.maxVolatility,
|
|
234
|
+
}))
|
|
235
|
+
.sort((a, b) => b.baselineCollapse - a.baselineCollapse)
|
|
236
|
+
.slice(0, 3);
|
|
237
|
+
// Engine stats
|
|
238
|
+
let totalGuardEvals = 0;
|
|
239
|
+
const totalVerdicts = { allow: 0, block: 0, pause: 0 };
|
|
240
|
+
let totalRulesFired = 0;
|
|
241
|
+
let engineLoaded = false;
|
|
242
|
+
for (const r of results) {
|
|
243
|
+
const gs = r.governanceStats;
|
|
244
|
+
if (gs.engineLoaded)
|
|
245
|
+
engineLoaded = true;
|
|
246
|
+
totalGuardEvals += gs.totalEvaluations;
|
|
247
|
+
totalVerdicts.allow += gs.verdicts.allow;
|
|
248
|
+
totalVerdicts.block += gs.verdicts.block;
|
|
249
|
+
totalVerdicts.pause += gs.verdicts.pause;
|
|
250
|
+
totalRulesFired += gs.rulesFired;
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
scenarioId,
|
|
254
|
+
runsCompleted: n,
|
|
255
|
+
durationMs,
|
|
256
|
+
baseline: {
|
|
257
|
+
collapseCount: baselineCollapses,
|
|
258
|
+
collapseProbability: baselineCollapses / n,
|
|
259
|
+
avgStability: baselineTotalStability / n,
|
|
260
|
+
minStability: baselineMinStability,
|
|
261
|
+
avgVolatility: baselineTotalVolatility / n,
|
|
262
|
+
maxVolatility: baselineMaxVolatility,
|
|
263
|
+
avgCoalitionRisks: baselineTotalCoalitionRisks / n,
|
|
264
|
+
trajectories: baselineTrajectories,
|
|
265
|
+
},
|
|
266
|
+
governed: {
|
|
267
|
+
collapseCount: governedCollapses,
|
|
268
|
+
collapseProbability: governedCollapses / n,
|
|
269
|
+
avgStability: governedTotalStability / n,
|
|
270
|
+
minStability: governedMinStability,
|
|
271
|
+
avgVolatility: governedTotalVolatility / n,
|
|
272
|
+
maxVolatility: governedMaxVolatility,
|
|
273
|
+
avgCoalitionRisks: governedTotalCoalitionRisks / n,
|
|
274
|
+
trajectories: governedTrajectories,
|
|
275
|
+
},
|
|
276
|
+
impact: {
|
|
277
|
+
collapseReduction: (baselineCollapses - governedCollapses) / n * 100,
|
|
278
|
+
avgStabilityImprovement: totalStabilityImprovement / n,
|
|
279
|
+
avgVolatilityReduction: totalVolatilityReduction / n,
|
|
280
|
+
avgEffectiveness: totalEffectiveness / n,
|
|
281
|
+
improvementRate: improvementCount / n,
|
|
282
|
+
},
|
|
283
|
+
topRules,
|
|
284
|
+
worstCases,
|
|
285
|
+
engineStats: {
|
|
286
|
+
engineLoaded,
|
|
287
|
+
totalGuardEvals,
|
|
288
|
+
totalVerdicts,
|
|
289
|
+
totalRulesFired,
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|