@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.
- package/LICENSE.md +190 -0
- package/README.md +472 -115
- package/dist/adapters/express.cjs +874 -0
- package/dist/adapters/express.d.cts +66 -0
- package/dist/adapters/express.d.ts +66 -0
- package/dist/adapters/express.js +10 -0
- package/dist/adapters/index.cjs +1166 -0
- package/dist/adapters/index.d.cts +5 -0
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.js +40 -0
- package/dist/adapters/langchain.cjs +861 -0
- package/dist/adapters/langchain.d.cts +82 -0
- package/dist/adapters/langchain.d.ts +82 -0
- package/dist/adapters/langchain.js +14 -0
- package/dist/adapters/openai.cjs +891 -0
- package/dist/adapters/openai.d.cts +92 -0
- package/dist/adapters/openai.d.ts +92 -0
- package/dist/adapters/openai.js +14 -0
- package/dist/adapters/openclaw.cjs +883 -0
- package/dist/adapters/openclaw.d.cts +92 -0
- package/dist/adapters/openclaw.d.ts +92 -0
- package/dist/adapters/openclaw.js +14 -0
- package/dist/bootstrap-H4HHKQ5G.js +113 -0
- package/dist/build-73KAVHEY.js +308 -0
- package/dist/chunk-4NGDRRQH.js +10 -0
- package/dist/chunk-B4NF3OLW.js +673 -0
- package/dist/chunk-BUWWN2NX.js +192 -0
- package/dist/chunk-CROPZ75A.js +107 -0
- package/dist/chunk-D7BGWV2J.js +620 -0
- package/dist/chunk-EIUHJXBB.js +340 -0
- package/dist/chunk-EQXFOKH2.js +85 -0
- package/dist/chunk-FYPYZFV5.js +187 -0
- package/dist/chunk-FYS2CBUW.js +304 -0
- package/dist/chunk-FZQCRGUU.js +115 -0
- package/dist/chunk-I3RRAYK2.js +11 -0
- package/dist/chunk-ITJ3LCPG.js +225 -0
- package/dist/chunk-M3TZFGHO.js +79 -0
- package/dist/chunk-MWDQ4MJB.js +11 -0
- package/dist/chunk-O5OMJMIE.js +590 -0
- package/dist/chunk-T4X42QXC.js +121 -0
- package/dist/chunk-XPDMYECO.js +642 -0
- package/dist/chunk-Z2S2HIV5.js +100 -0
- package/dist/cli/neuroverse.cjs +5864 -0
- package/dist/cli/neuroverse.d.cts +1 -0
- package/dist/cli/neuroverse.d.ts +1 -0
- package/dist/cli/neuroverse.js +128 -0
- package/dist/configure-ai-46JVG56I.js +129 -0
- package/dist/derive-6NAEWLM5.js +150 -0
- package/dist/explain-3B3VB6TL.js +69 -0
- package/dist/guard-67Y66P3I.js +82 -0
- package/dist/guard-contract-D_RQz9kt.d.cts +529 -0
- package/dist/guard-contract-D_RQz9kt.d.ts +529 -0
- package/dist/impact-CHERK3O6.js +57 -0
- package/dist/improve-YG6I6ERG.js +84 -0
- package/dist/index.cjs +4227 -0
- package/dist/index.d.cts +1147 -0
- package/dist/index.d.ts +1139 -25
- package/dist/index.js +318 -1159
- package/dist/init-Z66T6TDI.js +142 -0
- package/dist/simulate-ETHHINZ4.js +107 -0
- package/dist/trace-3YODSSIP.js +164 -0
- package/dist/validate-UVE6GKQU.js +72 -0
- package/dist/validate-engine-657D75OG.js +6 -0
- package/dist/world-WLNHL5XC.js +298 -0
- package/dist/worlds/derivation-world.nv-world.md +278 -0
- package/package.json +74 -26
- package/LICENSE +0 -21
- package/dist/audit-logger.d.ts +0 -38
- package/dist/audit-logger.d.ts.map +0 -1
- package/dist/audit-logger.js +0 -100
- package/dist/audit-logger.js.map +0 -1
- package/dist/condition-engine.d.ts +0 -16
- package/dist/condition-engine.d.ts.map +0 -1
- package/dist/condition-engine.js +0 -186
- package/dist/condition-engine.js.map +0 -1
- package/dist/drift-monitor.d.ts +0 -81
- package/dist/drift-monitor.d.ts.map +0 -1
- package/dist/drift-monitor.js +0 -228
- package/dist/drift-monitor.js.map +0 -1
- package/dist/governance-engine.d.ts +0 -131
- package/dist/governance-engine.d.ts.map +0 -1
- package/dist/governance-engine.js +0 -660
- package/dist/governance-engine.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/types.d.ts +0 -196
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -9
- package/dist/types.js.map +0 -1
- package/dist/world-bootstrap.d.ts +0 -31
- package/dist/world-bootstrap.d.ts.map +0 -1
- package/dist/world-bootstrap.js +0 -415
- package/dist/world-bootstrap.js.map +0 -1
- 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,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
|
+
};
|