@neuroverseos/governance 0.1.0 → 0.1.2

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 (94) hide show
  1. package/LICENSE.md +190 -0
  2. package/README.md +472 -115
  3. package/dist/adapters/express.cjs +874 -0
  4. package/dist/adapters/express.d.cts +66 -0
  5. package/dist/adapters/express.d.ts +66 -0
  6. package/dist/adapters/express.js +10 -0
  7. package/dist/adapters/index.cjs +1166 -0
  8. package/dist/adapters/index.d.cts +5 -0
  9. package/dist/adapters/index.d.ts +5 -0
  10. package/dist/adapters/index.js +40 -0
  11. package/dist/adapters/langchain.cjs +861 -0
  12. package/dist/adapters/langchain.d.cts +82 -0
  13. package/dist/adapters/langchain.d.ts +82 -0
  14. package/dist/adapters/langchain.js +14 -0
  15. package/dist/adapters/openai.cjs +891 -0
  16. package/dist/adapters/openai.d.cts +92 -0
  17. package/dist/adapters/openai.d.ts +92 -0
  18. package/dist/adapters/openai.js +14 -0
  19. package/dist/adapters/openclaw.cjs +883 -0
  20. package/dist/adapters/openclaw.d.cts +92 -0
  21. package/dist/adapters/openclaw.d.ts +92 -0
  22. package/dist/adapters/openclaw.js +14 -0
  23. package/dist/bootstrap-H4HHKQ5G.js +113 -0
  24. package/dist/build-73KAVHEY.js +308 -0
  25. package/dist/chunk-4NGDRRQH.js +10 -0
  26. package/dist/chunk-B4NF3OLW.js +673 -0
  27. package/dist/chunk-BUWWN2NX.js +192 -0
  28. package/dist/chunk-CROPZ75A.js +107 -0
  29. package/dist/chunk-D7BGWV2J.js +620 -0
  30. package/dist/chunk-EIUHJXBB.js +340 -0
  31. package/dist/chunk-EQXFOKH2.js +85 -0
  32. package/dist/chunk-FYPYZFV5.js +187 -0
  33. package/dist/chunk-FYS2CBUW.js +304 -0
  34. package/dist/chunk-FZQCRGUU.js +115 -0
  35. package/dist/chunk-I3RRAYK2.js +11 -0
  36. package/dist/chunk-ITJ3LCPG.js +225 -0
  37. package/dist/chunk-M3TZFGHO.js +79 -0
  38. package/dist/chunk-MWDQ4MJB.js +11 -0
  39. package/dist/chunk-O5OMJMIE.js +590 -0
  40. package/dist/chunk-T4X42QXC.js +121 -0
  41. package/dist/chunk-XPDMYECO.js +642 -0
  42. package/dist/chunk-Z2S2HIV5.js +100 -0
  43. package/dist/cli/neuroverse.cjs +5864 -0
  44. package/dist/cli/neuroverse.d.cts +1 -0
  45. package/dist/cli/neuroverse.d.ts +1 -0
  46. package/dist/cli/neuroverse.js +128 -0
  47. package/dist/configure-ai-46JVG56I.js +129 -0
  48. package/dist/derive-6NAEWLM5.js +150 -0
  49. package/dist/explain-3B3VB6TL.js +69 -0
  50. package/dist/guard-67Y66P3I.js +82 -0
  51. package/dist/guard-contract-D_RQz9kt.d.cts +529 -0
  52. package/dist/guard-contract-D_RQz9kt.d.ts +529 -0
  53. package/dist/impact-CHERK3O6.js +57 -0
  54. package/dist/improve-YG6I6ERG.js +84 -0
  55. package/dist/index.cjs +4227 -0
  56. package/dist/index.d.cts +1147 -0
  57. package/dist/index.d.ts +1139 -25
  58. package/dist/index.js +318 -1159
  59. package/dist/init-Z66T6TDI.js +142 -0
  60. package/dist/simulate-ETHHINZ4.js +107 -0
  61. package/dist/trace-3YODSSIP.js +164 -0
  62. package/dist/validate-UVE6GKQU.js +72 -0
  63. package/dist/validate-engine-657D75OG.js +6 -0
  64. package/dist/world-WLNHL5XC.js +298 -0
  65. package/dist/worlds/derivation-world.nv-world.md +278 -0
  66. package/package.json +74 -26
  67. package/LICENSE +0 -21
  68. package/dist/audit-logger.d.ts +0 -38
  69. package/dist/audit-logger.d.ts.map +0 -1
  70. package/dist/audit-logger.js +0 -100
  71. package/dist/audit-logger.js.map +0 -1
  72. package/dist/condition-engine.d.ts +0 -16
  73. package/dist/condition-engine.d.ts.map +0 -1
  74. package/dist/condition-engine.js +0 -186
  75. package/dist/condition-engine.js.map +0 -1
  76. package/dist/drift-monitor.d.ts +0 -81
  77. package/dist/drift-monitor.d.ts.map +0 -1
  78. package/dist/drift-monitor.js +0 -228
  79. package/dist/drift-monitor.js.map +0 -1
  80. package/dist/governance-engine.d.ts +0 -131
  81. package/dist/governance-engine.d.ts.map +0 -1
  82. package/dist/governance-engine.js +0 -660
  83. package/dist/governance-engine.js.map +0 -1
  84. package/dist/index.d.ts.map +0 -1
  85. package/dist/index.js.map +0 -1
  86. package/dist/types.d.ts +0 -196
  87. package/dist/types.d.ts.map +0 -1
  88. package/dist/types.js +0 -9
  89. package/dist/types.js.map +0 -1
  90. package/dist/world-bootstrap.d.ts +0 -31
  91. package/dist/world-bootstrap.d.ts.map +0 -1
  92. package/dist/world-bootstrap.js +0 -415
  93. package/dist/world-bootstrap.js.map +0 -1
  94. package/openclaw.plugin.json +0 -59
@@ -0,0 +1,304 @@
1
+ // src/engine/simulate-engine.ts
2
+ function simulateWorld(world, options = {}) {
3
+ const steps = Math.max(1, Math.min(options.steps ?? 1, 50));
4
+ const profileName = options.profile ?? world.world.default_assumption_profile;
5
+ const state = buildInitialState(world.stateSchema, options.stateOverrides);
6
+ const assumptions = resolveAssumptions(world.assumptions, profileName);
7
+ const initialState = { ...state };
8
+ const simulationSteps = [];
9
+ let collapsed = false;
10
+ let collapseStep;
11
+ let collapseRule;
12
+ const sortedRules = [...world.rules].sort((a, b) => a.order - b.order);
13
+ for (let stepNum = 1; stepNum <= steps; stepNum++) {
14
+ if (collapsed) break;
15
+ const stepResult = evaluateStep(
16
+ stepNum,
17
+ sortedRules,
18
+ state,
19
+ assumptions,
20
+ world
21
+ );
22
+ simulationSteps.push(stepResult);
23
+ if (stepResult.collapsed) {
24
+ collapsed = true;
25
+ collapseStep = stepNum;
26
+ collapseRule = stepResult.rulesEvaluated.find((r) => r.collapsed)?.ruleId;
27
+ }
28
+ }
29
+ const finalViability = classifyViability(state, world);
30
+ return {
31
+ worldId: world.world.world_id,
32
+ worldName: world.world.name,
33
+ profile: profileName,
34
+ initialState,
35
+ steps: simulationSteps,
36
+ finalState: { ...state },
37
+ finalViability,
38
+ collapsed,
39
+ collapseStep,
40
+ collapseRule
41
+ };
42
+ }
43
+ function evaluateStep(stepNum, rules, state, assumptions, world) {
44
+ const evaluations = [];
45
+ let rulesFired = 0;
46
+ let collapsed = false;
47
+ const firedRuleIds = /* @__PURE__ */ new Set();
48
+ for (const rule of rules) {
49
+ if (collapsed) {
50
+ evaluations.push({
51
+ ruleId: rule.id,
52
+ label: rule.label,
53
+ triggered: false,
54
+ excluded: true,
55
+ effects: [],
56
+ collapsed: false
57
+ });
58
+ continue;
59
+ }
60
+ const excluded = rule.exclusive_with ? firedRuleIds.has(rule.exclusive_with) : false;
61
+ if (excluded) {
62
+ evaluations.push({
63
+ ruleId: rule.id,
64
+ label: rule.label,
65
+ triggered: false,
66
+ excluded: true,
67
+ effects: [],
68
+ collapsed: false
69
+ });
70
+ continue;
71
+ }
72
+ const triggered = evaluateTriggers(rule.triggers, state, assumptions);
73
+ if (!triggered) {
74
+ evaluations.push({
75
+ ruleId: rule.id,
76
+ label: rule.label,
77
+ triggered: false,
78
+ excluded: false,
79
+ effects: [],
80
+ collapsed: false
81
+ });
82
+ continue;
83
+ }
84
+ firedRuleIds.add(rule.id);
85
+ rulesFired++;
86
+ const appliedEffects = [];
87
+ for (const effect of rule.effects ?? []) {
88
+ const applied = applyEffect(effect, state);
89
+ if (applied) appliedEffects.push(applied);
90
+ }
91
+ for (const ce of rule.effects_conditional ?? []) {
92
+ const conditionMet = evaluateSingleTrigger(ce.condition, state, assumptions);
93
+ const andMet = ce.and ? evaluateSingleTrigger(ce.and, state, assumptions) : true;
94
+ const orMet = ce.or ? evaluateSingleTrigger(ce.or, state, assumptions) : false;
95
+ const anyMet = ce.condition_any ? ce.condition_any.some((c) => evaluateSingleTrigger(c, state, assumptions)) : false;
96
+ const shouldApply = conditionMet && andMet || ce.or && orMet || ce.condition_any && anyMet;
97
+ if (shouldApply) {
98
+ for (const effect of ce.effects) {
99
+ const applied = applyEffect(effect, state);
100
+ if (applied) appliedEffects.push(applied);
101
+ }
102
+ }
103
+ }
104
+ let ruleCollapsed = false;
105
+ if (rule.collapse_check) {
106
+ const fieldVal = typeof state[rule.collapse_check.field] === "number" ? state[rule.collapse_check.field] : 0;
107
+ if (evaluateOperator(fieldVal, rule.collapse_check.operator, rule.collapse_check.value)) {
108
+ ruleCollapsed = true;
109
+ collapsed = true;
110
+ }
111
+ }
112
+ if (!ruleCollapsed && rule.secondary_check) {
113
+ const fieldVal = typeof state[rule.secondary_check.field] === "number" ? state[rule.secondary_check.field] : 0;
114
+ if (evaluateOperator(fieldVal, rule.secondary_check.operator, rule.secondary_check.value)) {
115
+ ruleCollapsed = true;
116
+ collapsed = true;
117
+ }
118
+ }
119
+ evaluations.push({
120
+ ruleId: rule.id,
121
+ label: rule.label,
122
+ triggered: true,
123
+ excluded: false,
124
+ effects: appliedEffects,
125
+ collapsed: ruleCollapsed,
126
+ collapseField: ruleCollapsed ? rule.collapse_check?.field ?? rule.secondary_check?.field : void 0
127
+ });
128
+ }
129
+ const viability = classifyViability(state, world);
130
+ return {
131
+ step: stepNum,
132
+ rulesEvaluated: evaluations,
133
+ rulesFired,
134
+ stateAfter: { ...state },
135
+ viability,
136
+ collapsed
137
+ };
138
+ }
139
+ function evaluateTriggers(triggers, state, assumptions) {
140
+ if (!triggers || triggers.length === 0) return true;
141
+ return triggers.every((t) => evaluateSingleTrigger(t, state, assumptions));
142
+ }
143
+ function evaluateSingleTrigger(trigger, state, assumptions) {
144
+ const source = trigger.source === "assumption" ? assumptions : state;
145
+ const fieldValue = source[trigger.field];
146
+ if (fieldValue === void 0) return false;
147
+ return evaluateOperator(fieldValue, trigger.operator, trigger.value);
148
+ }
149
+ function evaluateOperator(fieldValue, operator, conditionValue) {
150
+ switch (operator) {
151
+ case "==":
152
+ return fieldValue === conditionValue;
153
+ case "!=":
154
+ return fieldValue !== conditionValue;
155
+ case ">":
156
+ return typeof fieldValue === "number" && typeof conditionValue === "number" && fieldValue > conditionValue;
157
+ case "<":
158
+ return typeof fieldValue === "number" && typeof conditionValue === "number" && fieldValue < conditionValue;
159
+ case ">=":
160
+ return typeof fieldValue === "number" && typeof conditionValue === "number" && fieldValue >= conditionValue;
161
+ case "<=":
162
+ return typeof fieldValue === "number" && typeof conditionValue === "number" && fieldValue <= conditionValue;
163
+ case "in":
164
+ return Array.isArray(conditionValue) && conditionValue.includes(String(fieldValue));
165
+ default:
166
+ return false;
167
+ }
168
+ }
169
+ function applyEffect(effect, state) {
170
+ const before = state[effect.target];
171
+ let after;
172
+ switch (effect.operation) {
173
+ case "multiply":
174
+ case "multiply_dynamic": {
175
+ const current = typeof before === "number" ? before : 0;
176
+ const factor = typeof effect.value === "number" ? effect.value : 1;
177
+ after = current * factor;
178
+ break;
179
+ }
180
+ case "add":
181
+ case "add_dynamic": {
182
+ const current = typeof before === "number" ? before : 0;
183
+ const addend = typeof effect.value === "number" ? effect.value : 0;
184
+ after = current + addend;
185
+ break;
186
+ }
187
+ case "subtract":
188
+ case "subtract_dynamic": {
189
+ const current = typeof before === "number" ? before : 0;
190
+ const subtrahend = typeof effect.value === "number" ? effect.value : 0;
191
+ after = current - subtrahend;
192
+ break;
193
+ }
194
+ case "set":
195
+ case "set_dynamic":
196
+ after = effect.value;
197
+ break;
198
+ case "set_boolean":
199
+ after = !!effect.value;
200
+ break;
201
+ default:
202
+ return null;
203
+ }
204
+ state[effect.target] = after;
205
+ return {
206
+ target: effect.target,
207
+ operation: effect.operation,
208
+ value: effect.value,
209
+ before: before ?? 0,
210
+ after
211
+ };
212
+ }
213
+ function buildInitialState(schema, overrides) {
214
+ const state = {};
215
+ for (const [name, variable] of Object.entries(schema.variables ?? {})) {
216
+ state[name] = variable.default;
217
+ }
218
+ if (overrides) {
219
+ for (const [key, value] of Object.entries(overrides)) {
220
+ state[key] = value;
221
+ }
222
+ }
223
+ return state;
224
+ }
225
+ function resolveAssumptions(config, profileName) {
226
+ const profile = config.profiles?.[profileName];
227
+ return profile?.parameters ?? {};
228
+ }
229
+ function classifyViability(state, world) {
230
+ const gates = world.gates?.viability_classification ?? [];
231
+ for (const gate of gates) {
232
+ const fieldValue = state[gate.field];
233
+ if (typeof fieldValue !== "number") continue;
234
+ if (evaluateOperator(fieldValue, gate.operator, gate.value)) {
235
+ return gate.status;
236
+ }
237
+ }
238
+ return "MODEL_COLLAPSES";
239
+ }
240
+ function renderSimulateText(result) {
241
+ const lines = [];
242
+ lines.push(`SIMULATION: ${result.worldName}`);
243
+ lines.push(`Profile: ${result.profile}`);
244
+ lines.push(`Steps: ${result.steps.length}`);
245
+ lines.push("");
246
+ lines.push("INITIAL STATE");
247
+ for (const [key, value] of Object.entries(result.initialState)) {
248
+ lines.push(` ${key}: ${value}`);
249
+ }
250
+ lines.push("");
251
+ for (const step of result.steps) {
252
+ lines.push(`STEP ${step.step}`);
253
+ const fired = step.rulesEvaluated.filter((r) => r.triggered);
254
+ const skipped = step.rulesEvaluated.filter((r) => !r.triggered && !r.excluded);
255
+ const excluded = step.rulesEvaluated.filter((r) => r.excluded);
256
+ if (fired.length === 0) {
257
+ lines.push(" No rules fired (state unchanged)");
258
+ } else {
259
+ for (const rule of fired) {
260
+ lines.push(` FIRED: ${rule.label}`);
261
+ for (const effect of rule.effects) {
262
+ const beforeStr = formatValue(effect.before);
263
+ const afterStr = formatValue(effect.after);
264
+ lines.push(` ${effect.target}: ${beforeStr} -> ${afterStr}`);
265
+ }
266
+ if (rule.collapsed) {
267
+ lines.push(` COLLAPSE on ${rule.collapseField}`);
268
+ }
269
+ }
270
+ }
271
+ if (excluded.length > 0) {
272
+ lines.push(` Excluded: ${excluded.map((r) => r.label).join(", ")}`);
273
+ }
274
+ lines.push(` Viability: ${step.viability}`);
275
+ if (step.collapsed) {
276
+ lines.push(" ** MODEL COLLAPSED **");
277
+ }
278
+ lines.push("");
279
+ }
280
+ lines.push("FINAL STATE");
281
+ for (const [key, value] of Object.entries(result.finalState)) {
282
+ const initial = result.initialState[key];
283
+ const changed = initial !== value;
284
+ const marker = changed ? " (changed)" : "";
285
+ lines.push(` ${key}: ${formatValue(value)}${marker}`);
286
+ }
287
+ lines.push("");
288
+ lines.push(`VIABILITY: ${result.finalViability}`);
289
+ if (result.collapsed) {
290
+ lines.push(`COLLAPSED at step ${result.collapseStep} (rule: ${result.collapseRule})`);
291
+ }
292
+ return lines.join("\n");
293
+ }
294
+ function formatValue(v) {
295
+ if (typeof v === "number") {
296
+ return Number.isInteger(v) ? String(v) : v.toFixed(2);
297
+ }
298
+ return String(v);
299
+ }
300
+
301
+ export {
302
+ simulateWorld,
303
+ renderSimulateText
304
+ };
@@ -0,0 +1,115 @@
1
+ import {
2
+ evaluateGuard
3
+ } from "./chunk-B4NF3OLW.js";
4
+ import {
5
+ loadWorld
6
+ } from "./chunk-M3TZFGHO.js";
7
+
8
+ // src/adapters/openai.ts
9
+ var GovernanceBlockedError = class extends Error {
10
+ verdict;
11
+ toolCallId;
12
+ constructor(verdict, toolCallId) {
13
+ super(`[NeuroVerse] BLOCKED: ${verdict.reason ?? verdict.ruleId ?? "governance rule"}`);
14
+ this.name = "GovernanceBlockedError";
15
+ this.verdict = verdict;
16
+ this.toolCallId = toolCallId;
17
+ }
18
+ };
19
+ function defaultMapFunctionCall(name, args) {
20
+ return {
21
+ intent: name,
22
+ tool: name,
23
+ scope: typeof args.path === "string" ? args.path : typeof args.url === "string" ? args.url : void 0,
24
+ args,
25
+ direction: "input"
26
+ };
27
+ }
28
+ function defaultBlockMessage(verdict) {
29
+ return `Action blocked by governance policy: ${verdict.reason ?? "rule violation"}. Rule: ${verdict.ruleId ?? "unknown"}.`;
30
+ }
31
+ var GovernedToolExecutor = class {
32
+ world;
33
+ options;
34
+ engineOptions;
35
+ mapFn;
36
+ blockMsg;
37
+ constructor(world, options = {}) {
38
+ this.world = world;
39
+ this.options = options;
40
+ this.engineOptions = {
41
+ trace: options.trace ?? false,
42
+ level: options.level
43
+ };
44
+ this.mapFn = options.mapFunctionCall ?? defaultMapFunctionCall;
45
+ this.blockMsg = options.blockMessage ?? defaultBlockMessage;
46
+ }
47
+ /**
48
+ * Evaluate a single tool call against governance rules.
49
+ * Returns the verdict without executing the tool.
50
+ */
51
+ evaluate(toolCall) {
52
+ let args;
53
+ try {
54
+ args = JSON.parse(toolCall.function.arguments);
55
+ } catch {
56
+ args = { raw: toolCall.function.arguments };
57
+ }
58
+ const event = this.mapFn(toolCall.function.name, args);
59
+ const verdict = evaluateGuard(event, this.world, this.engineOptions);
60
+ this.options.onEvaluate?.(verdict, event);
61
+ return verdict;
62
+ }
63
+ /**
64
+ * Execute a tool call with governance enforcement.
65
+ *
66
+ * If ALLOW: runs the tool and returns the result.
67
+ * If BLOCK: returns a blocked message (no execution).
68
+ * If PAUSE: throws — caller must handle approval flow.
69
+ *
70
+ * @param toolCall - The OpenAI tool call to evaluate
71
+ * @param runner - The actual tool execution function
72
+ * @returns A tool result message ready for the OpenAI API
73
+ */
74
+ async execute(toolCall, runner) {
75
+ const verdict = this.evaluate(toolCall);
76
+ if (verdict.status === "BLOCK") {
77
+ return {
78
+ tool_call_id: toolCall.id,
79
+ role: "tool",
80
+ content: this.blockMsg(verdict),
81
+ _verdict: verdict
82
+ };
83
+ }
84
+ if (verdict.status === "PAUSE") {
85
+ throw new GovernanceBlockedError(verdict, toolCall.id);
86
+ }
87
+ let args;
88
+ try {
89
+ args = JSON.parse(toolCall.function.arguments);
90
+ } catch {
91
+ args = { raw: toolCall.function.arguments };
92
+ }
93
+ const content = await runner(toolCall.function.name, args);
94
+ return {
95
+ tool_call_id: toolCall.id,
96
+ role: "tool",
97
+ content,
98
+ _verdict: verdict
99
+ };
100
+ }
101
+ };
102
+ async function createGovernedToolExecutor(worldPath, options) {
103
+ const world = await loadWorld(worldPath);
104
+ return new GovernedToolExecutor(world, options);
105
+ }
106
+ function createGovernedToolExecutorFromWorld(world, options) {
107
+ return new GovernedToolExecutor(world, options);
108
+ }
109
+
110
+ export {
111
+ GovernanceBlockedError,
112
+ GovernedToolExecutor,
113
+ createGovernedToolExecutor,
114
+ createGovernedToolExecutorFromWorld
115
+ };
@@ -0,0 +1,11 @@
1
+ // src/contracts/validate-contract.ts
2
+ var VALIDATE_EXIT_CODES = {
3
+ PASS: 0,
4
+ FAIL: 1,
5
+ WARN: 2,
6
+ ERROR: 3
7
+ };
8
+
9
+ export {
10
+ VALIDATE_EXIT_CODES
11
+ };
@@ -0,0 +1,225 @@
1
+ import {
2
+ readAuditLog
3
+ } from "./chunk-FYPYZFV5.js";
4
+
5
+ // src/engine/impact-report.ts
6
+ function generateImpactReport(events) {
7
+ if (events.length === 0) {
8
+ return emptyReport();
9
+ }
10
+ const blocked = events.filter((e) => e.decision === "BLOCK");
11
+ const paused = events.filter((e) => e.decision === "PAUSE");
12
+ const prevented = [...blocked, ...paused];
13
+ const categoryMap = /* @__PURE__ */ new Map();
14
+ for (const e of prevented) {
15
+ const cat = classifyPreventionCategory(e);
16
+ if (!categoryMap.has(cat)) categoryMap.set(cat, /* @__PURE__ */ new Set());
17
+ categoryMap.get(cat).add(e.intent);
18
+ }
19
+ const preventedByCategory = [...categoryMap.entries()].map(([category, intents]) => ({
20
+ category,
21
+ count: prevented.filter((e) => classifyPreventionCategory(e) === category).length,
22
+ examples: [...intents].slice(0, 5)
23
+ })).sort((a, b) => b.count - a.count);
24
+ const intentMap = /* @__PURE__ */ new Map();
25
+ for (const e of prevented) {
26
+ const entry = intentMap.get(e.intent) ?? { count: 0, rules: /* @__PURE__ */ new Map() };
27
+ entry.count++;
28
+ if (e.ruleId) {
29
+ entry.rules.set(e.ruleId, (entry.rules.get(e.ruleId) ?? 0) + 1);
30
+ }
31
+ for (const g of e.guardsMatched) {
32
+ entry.rules.set(g, (entry.rules.get(g) ?? 0) + 1);
33
+ }
34
+ intentMap.set(e.intent, entry);
35
+ }
36
+ const topPreventedIntents = [...intentMap.entries()].map(([intent, data]) => {
37
+ let topRule = "";
38
+ let topCount = 0;
39
+ for (const [rule, count] of data.rules) {
40
+ if (count > topCount) {
41
+ topRule = rule;
42
+ topCount = count;
43
+ }
44
+ }
45
+ return { intent, count: data.count, topRule };
46
+ }).sort((a, b) => b.count - a.count).slice(0, 15);
47
+ const actorMap = /* @__PURE__ */ new Map();
48
+ for (const e of events) {
49
+ const actor = e.actor ?? "unknown";
50
+ const entry = actorMap.get(actor) ?? { blocked: 0, paused: 0, total: 0 };
51
+ entry.total++;
52
+ if (e.decision === "BLOCK") entry.blocked++;
53
+ if (e.decision === "PAUSE") entry.paused++;
54
+ actorMap.set(actor, entry);
55
+ }
56
+ const hotActors = [...actorMap.entries()].filter(([, data]) => data.blocked > 0 || data.paused > 0).map(([actor, data]) => ({ actor, ...data })).sort((a, b) => b.blocked + b.paused - (a.blocked + a.paused)).slice(0, 10);
57
+ const ruleMap = /* @__PURE__ */ new Map();
58
+ for (const e of prevented) {
59
+ const ruleIds = [e.ruleId, ...e.guardsMatched, ...e.rulesMatched].filter(Boolean);
60
+ for (const rId of new Set(ruleIds)) {
61
+ const entry = ruleMap.get(rId) ?? { blockCount: 0, pauseCount: 0 };
62
+ if (e.decision === "BLOCK") entry.blockCount++;
63
+ if (e.decision === "PAUSE") entry.pauseCount++;
64
+ ruleMap.set(rId, entry);
65
+ }
66
+ }
67
+ const mostActiveRules = [...ruleMap.entries()].map(([ruleId, data]) => ({ ruleId, ...data })).sort((a, b) => b.blockCount + b.pauseCount - (a.blockCount + a.pauseCount)).slice(0, 10);
68
+ const hourMap = /* @__PURE__ */ new Map();
69
+ for (const e of events) {
70
+ const hour = new Date(e.timestamp).getHours();
71
+ const entry = hourMap.get(hour) ?? { total: 0, blocked: 0 };
72
+ entry.total++;
73
+ if (e.decision === "BLOCK") entry.blocked++;
74
+ hourMap.set(hour, entry);
75
+ }
76
+ const hourlyDistribution = Array.from({ length: 24 }, (_, hour) => ({
77
+ hour,
78
+ total: hourMap.get(hour)?.total ?? 0,
79
+ blocked: hourMap.get(hour)?.blocked ?? 0
80
+ }));
81
+ const violationKey = (e) => `${e.actor ?? "unknown"}::${e.intent}`;
82
+ const violationMap = /* @__PURE__ */ new Map();
83
+ for (const e of blocked) {
84
+ const key = violationKey(e);
85
+ const entry = violationMap.get(key) ?? {
86
+ intent: e.intent,
87
+ actor: e.actor ?? "unknown",
88
+ attempts: 0,
89
+ firstSeen: e.timestamp,
90
+ lastSeen: e.timestamp
91
+ };
92
+ entry.attempts++;
93
+ entry.lastSeen = e.timestamp;
94
+ violationMap.set(key, entry);
95
+ }
96
+ const repeatViolations = [...violationMap.values()].filter((v) => v.attempts > 1).sort((a, b) => b.attempts - a.attempts).slice(0, 10);
97
+ return {
98
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
99
+ periodStart: events[0].timestamp,
100
+ periodEnd: events[events.length - 1].timestamp,
101
+ worldName: events[0].worldName ?? "unknown",
102
+ totalEvaluations: events.length,
103
+ totalBlocked: blocked.length,
104
+ totalPaused: paused.length,
105
+ totalAllowed: events.length - blocked.length - paused.length,
106
+ preventionRate: prevented.length / events.length,
107
+ preventedByCategory,
108
+ topPreventedIntents,
109
+ hotActors,
110
+ mostActiveRules,
111
+ hourlyDistribution,
112
+ repeatViolations
113
+ };
114
+ }
115
+ function classifyPreventionCategory(event) {
116
+ const intent = event.intent.toLowerCase();
117
+ const rule = (event.ruleId ?? "").toLowerCase();
118
+ const combined = `${intent} ${rule}`;
119
+ if (combined.match(/inject|prompt|jailbreak|bypass/)) return "Prompt Injection Prevention";
120
+ if (combined.match(/scope|escape|traversal|path/)) return "Scope Escape Prevention";
121
+ if (combined.match(/delete|drop|destroy|remove|purge/)) return "Destructive Action Prevention";
122
+ if (combined.match(/trade|margin|position|leverage/)) return "Financial Risk Prevention";
123
+ if (combined.match(/withdraw|transfer|payment|fund/)) return "Unauthorized Transfer Prevention";
124
+ if (combined.match(/credential|secret|key|password|token/)) return "Credential Access Prevention";
125
+ if (combined.match(/shell|exec|command|script/)) return "Command Execution Prevention";
126
+ if (combined.match(/network|http|api|external/)) return "Network Access Prevention";
127
+ if (combined.match(/write|modify|update|alter/)) return "Unauthorized Modification Prevention";
128
+ if (combined.match(/approval|review|confirm/)) return "Approval Gate";
129
+ return "Policy Violation Prevention";
130
+ }
131
+ function renderImpactReport(report) {
132
+ const lines = [];
133
+ lines.push("GOVERNANCE IMPACT REPORT");
134
+ lines.push("\u2550".repeat(50));
135
+ lines.push("");
136
+ lines.push(` World: ${report.worldName}`);
137
+ lines.push(` Period: ${report.periodStart.split("T")[0]} \u2192 ${report.periodEnd.split("T")[0]}`);
138
+ lines.push(` Generated: ${report.generatedAt}`);
139
+ lines.push("");
140
+ lines.push("SUMMARY");
141
+ lines.push("\u2500".repeat(50));
142
+ lines.push(` Total evaluations: ${report.totalEvaluations}`);
143
+ lines.push(` Allowed: ${report.totalAllowed}`);
144
+ lines.push(` Blocked: ${report.totalBlocked}`);
145
+ lines.push(` Paused: ${report.totalPaused}`);
146
+ lines.push(` Prevention rate: ${(report.preventionRate * 100).toFixed(1)}%`);
147
+ lines.push("");
148
+ if (report.totalBlocked > 0 || report.totalPaused > 0) {
149
+ lines.push("WITHOUT GOVERNANCE");
150
+ lines.push("\u2500".repeat(50));
151
+ lines.push(` ${report.totalBlocked + report.totalPaused} actions would have executed unchecked:`);
152
+ for (const cat of report.preventedByCategory) {
153
+ lines.push(` ${cat.category.padEnd(38)} ${String(cat.count).padStart(5)}`);
154
+ if (cat.examples.length > 0) {
155
+ lines.push(` e.g. ${cat.examples.slice(0, 3).join(", ")}`);
156
+ }
157
+ }
158
+ lines.push("");
159
+ }
160
+ if (report.topPreventedIntents.length > 0) {
161
+ lines.push("TOP PREVENTED ACTIONS");
162
+ lines.push("\u2500".repeat(50));
163
+ for (const entry of report.topPreventedIntents.slice(0, 10)) {
164
+ lines.push(` ${entry.intent.padEnd(30)} ${String(entry.count).padStart(5)} (rule: ${entry.topRule || "\u2014"})`);
165
+ }
166
+ lines.push("");
167
+ }
168
+ if (report.hotActors.length > 0) {
169
+ lines.push("ACTORS WITH MOST VIOLATIONS");
170
+ lines.push("\u2500".repeat(50));
171
+ for (const actor of report.hotActors) {
172
+ const violations = actor.blocked + actor.paused;
173
+ const rate = (violations / actor.total * 100).toFixed(0);
174
+ lines.push(` ${actor.actor.padEnd(25)} ${String(violations).padStart(5)} violations / ${actor.total} total (${rate}%)`);
175
+ }
176
+ lines.push("");
177
+ }
178
+ if (report.mostActiveRules.length > 0) {
179
+ lines.push("MOST ACTIVE RULES");
180
+ lines.push("\u2500".repeat(50));
181
+ for (const rule of report.mostActiveRules) {
182
+ lines.push(` ${rule.ruleId.padEnd(30)} ${String(rule.blockCount).padStart(5)} blocked ${String(rule.pauseCount).padStart(5)} paused`);
183
+ }
184
+ lines.push("");
185
+ }
186
+ if (report.repeatViolations.length > 0) {
187
+ lines.push("REPEAT VIOLATIONS");
188
+ lines.push("\u2500".repeat(50));
189
+ lines.push(" Actions attempted multiple times after being blocked:");
190
+ for (const v of report.repeatViolations) {
191
+ lines.push(` ${v.actor.padEnd(20)} ${v.intent.padEnd(25)} ${v.attempts}x (${v.firstSeen.split("T")[0]} \u2192 ${v.lastSeen.split("T")[0]})`);
192
+ }
193
+ lines.push("");
194
+ }
195
+ return lines.join("\n");
196
+ }
197
+ async function generateImpactReportFromFile(logPath) {
198
+ const events = await readAuditLog(logPath);
199
+ return generateImpactReport(events);
200
+ }
201
+ function emptyReport() {
202
+ return {
203
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
204
+ periodStart: "",
205
+ periodEnd: "",
206
+ worldName: "unknown",
207
+ totalEvaluations: 0,
208
+ totalBlocked: 0,
209
+ totalPaused: 0,
210
+ totalAllowed: 0,
211
+ preventionRate: 0,
212
+ preventedByCategory: [],
213
+ topPreventedIntents: [],
214
+ hotActors: [],
215
+ mostActiveRules: [],
216
+ hourlyDistribution: [],
217
+ repeatViolations: []
218
+ };
219
+ }
220
+
221
+ export {
222
+ generateImpactReport,
223
+ renderImpactReport,
224
+ generateImpactReportFromFile
225
+ };