@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
package/dist/index.js CHANGED
@@ -1,1170 +1,329 @@
1
- "use strict";
2
- /**
3
- * NeuroVerseOS Governance Plugin for OpenClaw
4
- *
5
- * A governance layer that intercepts tool calls and enforces
6
- * ALLOW / PAUSE / BLOCK verdicts against structured world rules.
7
- *
8
- * Performance: Deterministic, local, no model calls. Sub-millisecond
9
- * per evaluation. Condition engine against world.json rules.
10
- *
11
- * Logging: Quiet by default. Only BLOCK and PAUSE print to terminal.
12
- * Set "verbose": true to see all verdicts including ALLOW.
13
- *
14
- * Audit: Every verdict logged to .neuroverseos/audit.jsonl.
15
- *
16
- * Install:
17
- * openclaw plugins install neuroverseos-governance
18
- *
19
- * Commands:
20
- * /world — Quick status dashboard
21
- * /world summary — One-line protection status
22
- * /world status — Full governance metrics (drift, audit, friction)
23
- * /world laws — Display the full constitution
24
- * /world bootstrap — Generate world.json from your .md files
25
- * /world bind — Bind agent identity to governance role
26
- * /world unbind — Remove agent role binding
27
- * /world bindings — Show all agent-to-role bindings
28
- * /world propose — Agent recommends governance amendments
29
- * /world approve <id> — Human approves a proposed amendment
30
- * /world export — Export for use in other tools
31
- * /world help — Command reference
32
- */
33
- Object.defineProperty(exports, "__esModule", { value: true });
34
- exports.default = register;
35
- const fs_1 = require("fs");
36
- const readline_1 = require("readline");
37
- const path_1 = require("path");
38
- const world_bootstrap_1 = require("./world-bootstrap");
39
- const governance_engine_1 = require("./governance-engine");
40
- const drift_monitor_1 = require("./drift-monitor");
41
- const audit_logger_1 = require("./audit-logger");
42
- /**
43
- * Prompt the developer in the terminal for a pause decision.
44
- * Blocks until they respond. Returns their choice.
45
- *
46
- * Display format — full agent context:
47
- * [governance] PAUSE
48
- * agent: coder-agent-01 (role: executor)
49
- * action: file_write → /etc/passwd
50
- * guard: guard-sensitive-paths
51
- * "Blocks writes to system directories"
52
- * evidence: path matches /etc/*
53
- * Allow? [y]es once / [a]lways / [n]o: _
54
- */
55
- function promptForDecision(ctx) {
56
- return new Promise((resolvePrompt) => {
57
- const rl = (0, readline_1.createInterface)({
58
- input: process.stdin,
59
- output: process.stderr,
60
- });
61
- const isGovernance = ctx.severity === 'governance';
62
- const isCritical = ctx.severity === 'critical';
63
- // ── Governance-level pauses get visually distinct treatment ──
64
- if (isGovernance) {
65
- process.stderr.write(`\n[governance] 🔒 GOVERNANCE PAUSE\n`);
66
- process.stderr.write(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
67
- }
68
- else if (isCritical) {
69
- process.stderr.write(`\n[governance] ⚠️ PAUSE [CRITICAL]\n`);
70
- }
71
- else {
72
- process.stderr.write(`\n[governance] PAUSE\n`);
73
- }
74
- if (ctx.agentId) {
75
- const roleSuffix = ctx.role ? ` (role: ${ctx.role})` : ' (unbound)';
76
- process.stderr.write(` agent: ${ctx.agentId}${roleSuffix}\n`);
77
- }
78
- const actionTarget = ctx.scope ? ` → ${ctx.scope}` : '';
79
- process.stderr.write(` action: ${ctx.tool}${actionTarget}\n`);
80
- process.stderr.write(` guard: ${ctx.guardLabel}\n`);
81
- if (ctx.guardDescription) {
82
- process.stderr.write(` "${ctx.guardDescription}"\n`);
83
- }
84
- if (ctx.evidence) {
85
- process.stderr.write(` evidence: ${ctx.evidence}\n`);
86
- }
87
- // Governance-level pauses get explicit denial guidance
88
- if (isGovernance) {
89
- process.stderr.write(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
90
- process.stderr.write(` 🚫 DENY this if you did not request this change.\n`);
91
- }
92
- process.stderr.write(` Allow? [y]es once / [a]lways / [n]o: `);
93
- rl.on('line', (answer) => {
94
- rl.close();
95
- const a = answer.trim().toLowerCase();
96
- if (a === 'a' || a === 'always') {
97
- resolvePrompt('allow-always');
98
- }
99
- else if (a === 'n' || a === 'no') {
100
- resolvePrompt('deny');
101
- }
102
- else {
103
- resolvePrompt('allow-once');
104
- }
105
- });
106
- rl.on('close', () => {
107
- resolvePrompt('deny');
108
- });
109
- });
1
+ import {
2
+ generateImpactReport,
3
+ generateImpactReportFromFile,
4
+ renderImpactReport
5
+ } from "./chunk-ITJ3LCPG.js";
6
+ import {
7
+ CompositeAuditLogger,
8
+ ConsoleAuditLogger,
9
+ FileAuditLogger,
10
+ createGovernanceEngine,
11
+ readAuditLog,
12
+ summarizeAuditEvents,
13
+ verdictToAuditEvent
14
+ } from "./chunk-FYPYZFV5.js";
15
+ import {
16
+ deriveWorld,
17
+ extractWorldMarkdown,
18
+ normalizeWorldMarkdown
19
+ } from "./chunk-D7BGWV2J.js";
20
+ import {
21
+ CONFIGURE_AI_EXIT_CODES,
22
+ DERIVE_EXIT_CODES
23
+ } from "./chunk-T4X42QXC.js";
24
+ import {
25
+ explainWorld,
26
+ renderExplainText
27
+ } from "./chunk-BUWWN2NX.js";
28
+ import {
29
+ improveWorld,
30
+ renderImproveText
31
+ } from "./chunk-EIUHJXBB.js";
32
+ import {
33
+ renderSimulateText,
34
+ simulateWorld
35
+ } from "./chunk-FYS2CBUW.js";
36
+ import {
37
+ BOOTSTRAP_EXIT_CODES
38
+ } from "./chunk-4NGDRRQH.js";
39
+ import {
40
+ emitWorldDefinition,
41
+ parseWorldMarkdown
42
+ } from "./chunk-XPDMYECO.js";
43
+ import {
44
+ VALIDATE_EXIT_CODES
45
+ } from "./chunk-I3RRAYK2.js";
46
+ import {
47
+ GUARD_EXIT_CODES
48
+ } from "./chunk-MWDQ4MJB.js";
49
+ import {
50
+ evaluateGuard,
51
+ eventToAllowlistKey
52
+ } from "./chunk-B4NF3OLW.js";
53
+ import {
54
+ loadWorld,
55
+ loadWorldFromDirectory
56
+ } from "./chunk-M3TZFGHO.js";
57
+ import {
58
+ validateWorld
59
+ } from "./chunk-O5OMJMIE.js";
60
+
61
+ // src/engine/verdict-formatter.ts
62
+ var ANSI = {
63
+ red: "\x1B[31m",
64
+ green: "\x1B[32m",
65
+ yellow: "\x1B[33m",
66
+ bold: "\x1B[1m",
67
+ dim: "\x1B[2m",
68
+ reset: "\x1B[0m"
69
+ };
70
+ function c(text, ...codes) {
71
+ return codes.join("") + text + ANSI.reset;
110
72
  }
111
- // ────────────────────────────────────────────────────────────────────────
112
- // Session Allowlist — Keyed by guard ID, not tool name (spec §6)
113
- // ────────────────────────────────────────────────────────────────────────
114
- class SessionAllowlist {
115
- constructor() {
116
- this.guardIds = new Set();
117
- }
118
- isAllowed(guardId) {
119
- return this.guardIds.has(guardId);
120
- }
121
- add(guardId) {
122
- this.guardIds.add(guardId);
123
- }
124
- getOverrides() {
125
- return Array.from(this.guardIds);
73
+ function formatVerdict(verdict, options = {}) {
74
+ const {
75
+ showRuleId = true,
76
+ showEvidence = false,
77
+ showWarning = true,
78
+ color = false,
79
+ compact = false
80
+ } = options;
81
+ if (compact) {
82
+ return formatCompact(verdict, { showRuleId, showWarning, color });
83
+ }
84
+ return formatFull(verdict, { showRuleId, showEvidence, showWarning, color });
85
+ }
86
+ function formatCompact(verdict, opts) {
87
+ const status = formatStatus(verdict.status, opts.color);
88
+ const parts = [status];
89
+ if (opts.showRuleId && verdict.ruleId) {
90
+ parts.push(verdict.ruleId);
91
+ }
92
+ if (verdict.reason) {
93
+ parts.push(verdict.reason);
94
+ }
95
+ if (opts.showWarning && verdict.warning) {
96
+ parts.push(`warning: ${verdict.warning}`);
97
+ }
98
+ return parts.join(" \u2014 ");
99
+ }
100
+ function formatFull(verdict, opts) {
101
+ const lines = [];
102
+ lines.push(formatStatus(verdict.status, opts.color));
103
+ if (opts.showRuleId && verdict.ruleId) {
104
+ lines.push(` Rule: ${verdict.ruleId}`);
105
+ }
106
+ if (verdict.reason) {
107
+ lines.push(` Reason: ${verdict.reason}`);
108
+ }
109
+ if (opts.showWarning && verdict.warning) {
110
+ const label = opts.color ? c("Warning:", ANSI.yellow) : "Warning:";
111
+ lines.push(` ${label} ${verdict.warning}`);
112
+ }
113
+ if (opts.showEvidence) {
114
+ const ev = verdict.evidence;
115
+ lines.push(` World: ${ev.worldName} v${ev.worldVersion}`);
116
+ lines.push(` Invariants: ${ev.invariantsSatisfied}/${ev.invariantsTotal} covered`);
117
+ if (ev.guardsMatched.length > 0) {
118
+ lines.push(` Guards matched: ${ev.guardsMatched.join(", ")}`);
126
119
  }
127
- clear() {
128
- this.guardIds.clear();
120
+ if (ev.rulesMatched.length > 0) {
121
+ lines.push(` Rules matched: ${ev.rulesMatched.join(", ")}`);
129
122
  }
123
+ lines.push(` Enforcement: ${ev.enforcementLevel}`);
124
+ }
125
+ return lines.join("\n");
130
126
  }
131
- // ────────────────────────────────────────────────────────────────────────
132
- // Event Construction — Normalize OpenClaw hook event to internal shape
133
- // ────────────────────────────────────────────────────────────────────────
134
- /**
135
- * Derive scope from tool call params.
136
- */
137
- function deriveScope(tool, params) {
138
- if (params.file_path && typeof params.file_path === 'string')
139
- return params.file_path;
140
- if (params.command && typeof params.command === 'string')
141
- return params.command;
142
- if (params.url && typeof params.url === 'string')
143
- return params.url;
144
- if (params.path && typeof params.path === 'string')
145
- return params.path;
146
- return undefined;
127
+ function formatStatus(status, color) {
128
+ switch (status) {
129
+ case "ALLOW":
130
+ return color ? c("ALLOWED", ANSI.green, ANSI.bold) : "ALLOWED";
131
+ case "BLOCK":
132
+ return color ? c("BLOCKED", ANSI.red, ANSI.bold) : "BLOCKED";
133
+ case "PAUSE":
134
+ return color ? c("PAUSED", ANSI.yellow, ANSI.bold) : "PAUSED";
135
+ default:
136
+ return status;
137
+ }
147
138
  }
148
- /**
149
- * Build a normalized ToolCallEvent from OpenClaw's before_tool_call event + context.
150
- *
151
- * OpenClaw provides:
152
- * event: { toolName: string; params: Record<string, unknown> }
153
- * ctx: { agentId?: string; sessionKey?: string; toolName: string }
154
- */
155
- function buildEvent(toolName, params, agentId, allowlist, roleMap, environment) {
156
- return {
157
- type: 'tool_call',
158
- tool: toolName,
159
- intent: `${toolName}: ${JSON.stringify(params).slice(0, 100)}`,
160
- args: params,
161
- scope: deriveScope(toolName, params),
162
- role: agentId ? roleMap.get(agentId) : undefined,
163
- environment,
164
- sessionOverrides: allowlist.getOverrides(),
165
- };
139
+ function formatVerdictOneLine(verdict) {
140
+ const parts = [verdict.status];
141
+ if (verdict.ruleId) {
142
+ parts[0] += `: ${verdict.ruleId}`;
143
+ }
144
+ if (verdict.reason) {
145
+ parts.push(verdict.reason);
146
+ }
147
+ return parts.join(" \u2014 ");
166
148
  }
167
- // ────────────────────────────────────────────────────────────────────────
168
- // Shared: Read .md files from workspace
169
- // ────────────────────────────────────────────────────────────────────────
170
- function readMdFiles(dir) {
171
- const mdFiles = {};
172
- try {
173
- const entries = (0, fs_1.readdirSync)(dir);
174
- for (const file of entries) {
175
- if (file.toLowerCase().endsWith('.md')) {
176
- const fullPath = (0, path_1.join)(dir, file);
177
- mdFiles[file] = (0, fs_1.readFileSync)(fullPath, 'utf-8');
178
- }
179
- }
180
- }
181
- catch {
182
- // Directory unreadable — return empty
183
- }
184
- return mdFiles;
149
+
150
+ // src/engine/condition-engine.ts
151
+ function getFieldValue(event, field) {
152
+ if (field.startsWith("args.")) {
153
+ const key = field.slice(5);
154
+ return event.args?.[key];
155
+ }
156
+ return event[field];
185
157
  }
186
- // ────────────────────────────────────────────────────────────────────────
187
- // Plugin Registration
188
- // ────────────────────────────────────────────────────────────────────────
189
- /**
190
- * Resolve the persistent storage root.
191
- * Priority: explicit env var → /data if present (Docker) → cwd fallback.
192
- *
193
- * In OpenClaw Docker containers /data is always the mounted persistent volume.
194
- * We detect its *presence* (existsSync), not its permissions (accessSync),
195
- * because the node process may not yet have write access at register time
196
- * on certain hosts (e.g. Hostinger), causing a false fallback to process.cwd().
197
- */
198
- function getStorageRoot() {
199
- // 1. Explicit override — always wins
200
- if (process.env.OPENCLAW_DATA_DIR) {
201
- return process.env.OPENCLAW_DATA_DIR;
202
- }
203
- // 2. /data exists — standard OpenClaw container volume
204
- if ((0, fs_1.existsSync)('/data')) {
205
- return '/data';
206
- }
207
- // 3. Local dev fallback
208
- return process.cwd();
158
+ function toString(value) {
159
+ if (value === null || value === void 0) return "";
160
+ if (typeof value === "string") return value;
161
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
162
+ return JSON.stringify(value);
209
163
  }
210
- function register(api) {
211
- // Config: OpenClaw provides plugin-specific config via api.pluginConfig
212
- const config = (api.pluginConfig ?? {});
213
- const verbose = config.verbose ?? false;
214
- const autoAllow = config.autoAllow ?? false;
215
- const allowlist = new SessionAllowlist();
216
- const roleMap = new Map();
217
- const environment = config.environment ?? process.env.NODE_ENV;
218
- // Track .md files modified by agents during this session (for bootstrap warning)
219
- const agentModifiedMdFiles = new Map();
220
- // Storage root: where .neuroverse/ persistent data lives
221
- const storageRoot = getStorageRoot();
222
- // Workspace content: where .md files live (for bootstrap & drift detection)
223
- // OpenClaw workspace is at <dataRoot>/.openclaw/workspace NOT the container CWD.
224
- // api.resolvePath('.') returns the container root (e.g. /data), which has no .md files.
225
- const workspaceDir = (0, path_1.resolve)(storageRoot, '.openclaw', 'workspace');
226
- const worldDir = (0, path_1.resolve)(storageRoot, '.neuroverseos');
227
- const worldPath = config.worldPath
228
- ? (0, path_1.resolve)(storageRoot, config.worldPath)
229
- : (0, path_1.join)(worldDir, 'world.json');
230
- const auditPath = (0, path_1.join)(worldDir, 'audit.jsonl');
231
- const statePath = (0, path_1.join)(worldDir, 'state.json');
232
- const proposalsDir = (0, path_1.join)(worldDir, 'proposals');
233
- // Ensure directories
234
- if (!(0, fs_1.existsSync)(worldDir))
235
- (0, fs_1.mkdirSync)(worldDir, { recursive: true });
236
- if (!(0, fs_1.existsSync)(proposalsDir))
237
- (0, fs_1.mkdirSync)(proposalsDir, { recursive: true });
238
- // ── Core Services ──────────────────────────────────────────────
239
- const engine = new governance_engine_1.GovernanceEngine({
240
- worldPath,
241
- enforcement: config.enforcement ?? 'standard',
242
- observeOnly: config.observeFirst ?? false,
243
- });
244
- // Set workspace dir for runtime md drift detection (spec §8)
245
- engine.setWorkspaceDir(workspaceDir);
246
- const audit = new audit_logger_1.AuditLogger(auditPath);
247
- let monitor = null;
248
- if (config.driftTracking !== false) {
249
- monitor = new drift_monitor_1.DriftMonitor({ statePath, auditPath, engine });
250
- }
251
- // ── Tool Interception Hook (spec §3 pipeline) ─────────────────
252
- // OpenClaw before_tool_call hook signature (from src/plugins/types.ts):
253
- // event: { toolName: string; params: Record<string, unknown> }
254
- // ctx: { agentId?: string; sessionKey?: string; toolName: string }
255
- // return: { block?: boolean; blockReason?: string; params?: Record<string, unknown> } | void
256
- api.on('before_tool_call', async (event, ctx) => {
257
- // 1. Build normalized event (spec §1)
258
- const govEvent = buildEvent(event.toolName, event.params, ctx.agentId, allowlist, roleMap, environment);
259
- // 2. Run decision pipeline (spec §3)
260
- const verdict = engine.evaluate(govEvent);
261
- // 3. Audit log every verdict (spec §7)
262
- const eventId = audit.logVerdict(event.toolName, govEvent.intent, verdict, ctx.agentId);
263
- // 4. Track in drift monitor
264
- monitor?.recordAction(event.toolName, verdict.status, verdict.ruleId ?? undefined);
265
- // 4.5 Surface proactive alerts (spec §8)
266
- if (verdict.alerts && verdict.alerts.length > 0) {
267
- for (const alert of verdict.alerts) {
268
- const prefix = alert.level === 'critical' ? '!!!'
269
- : alert.level === 'warning' ? '!'
270
- : 'i';
271
- api.logger.warn(`[governance] [${prefix}] ${alert.message}`);
272
- if (alert.details) {
273
- api.logger.warn(` ${alert.details}`);
274
- }
275
- api.logger.warn(` → ${alert.action}`);
276
- }
277
- }
278
- // ── BLOCK ──────────────────────────────────────────────
279
- if (verdict.status === 'BLOCK') {
280
- api.logger.warn(`[governance] BLOCK`);
281
- if (ctx.agentId) {
282
- const roleSuffix = govEvent.role ? ` (role: ${govEvent.role})` : ' (unbound)';
283
- api.logger.warn(` agent: ${ctx.agentId}${roleSuffix}`);
284
- }
285
- const actionTarget = govEvent.scope ? ` → ${govEvent.scope}` : '';
286
- api.logger.warn(` action: ${event.toolName}${actionTarget}`);
287
- api.logger.warn(` rule: ${verdict.ruleId ?? 'default'}`);
288
- api.logger.warn(` reason: ${verdict.reason}`);
289
- if (verdict.evidence) {
290
- api.logger.warn(` evidence: ${verdict.evidence}`);
291
- }
292
- return { block: true, blockReason: verdict.reason };
293
- }
294
- // ── PAUSE ──────────────────────────────────────────────
295
- if (verdict.status === 'PAUSE') {
296
- const guardId = verdict.ruleId ?? '';
297
- // Check session allowlist first (spec §6 step 1)
298
- if (guardId && allowlist.isAllowed(guardId)) {
299
- if (verbose) {
300
- api.logger.debug?.(`[governance] ALLOW ${govEvent.intent} (session allowlist: ${guardId})`);
301
- }
302
- audit.logDecision(eventId, 'allow-always');
303
- monitor?.recordAction(event.toolName, 'ALLOW', verdict.ruleId ?? undefined);
304
- return; // allow — return void
305
- }
306
- // Auto-allow mode for CI/headless (spec §6 step 2)
307
- // NEVER auto-allow governance-level pauses — these require human decision
308
- if (autoAllow && verdict.severity !== 'governance') {
309
- api.logger.info(`[governance] PAUSE ${govEvent.intent} (auto-allowed)`);
310
- api.logger.info(` ${verdict.guard ?? verdict.ruleId}`);
311
- audit.logDecision(eventId, 'allow-once');
312
- return; // allow — return void
313
- }
314
- // Interactive prompt with full context (spec §6 step 3)
315
- const guardLabel = verdict.guard ?? verdict.ruleId ?? 'governance rule';
316
- const decision = await promptForDecision({
317
- agentId: ctx.agentId,
318
- role: govEvent.role,
319
- tool: event.toolName,
320
- scope: govEvent.scope,
321
- intent: govEvent.intent,
322
- guardLabel,
323
- guardDescription: verdict.reason !== guardLabel ? verdict.reason : undefined,
324
- evidence: verdict.evidence,
325
- severity: verdict.severity,
326
- });
327
- // Log the decision (spec §7 — every PAUSE paired with a decision)
328
- audit.logDecision(eventId, decision);
329
- if (decision === 'allow-always') {
330
- if (guardId)
331
- allowlist.add(guardId);
332
- process.stderr.write(`[governance] Added ${guardId} to session allowlist\n\n`);
333
- monitor?.recordOverride();
334
- // Track agent-approved .md modifications for bootstrap warning
335
- if (verdict.severity === 'governance' && govEvent.scope?.toLowerCase().endsWith('.md')) {
336
- agentModifiedMdFiles.set(govEvent.scope, { agentId: ctx.agentId ?? 'unknown', at: Date.now() });
337
- }
338
- return; // allow — return void
339
- }
340
- if (decision === 'allow-once') {
341
- monitor?.recordOverride();
342
- // Track agent-approved .md modifications for bootstrap warning
343
- if (verdict.severity === 'governance' && govEvent.scope?.toLowerCase().endsWith('.md')) {
344
- agentModifiedMdFiles.set(govEvent.scope, { agentId: ctx.agentId ?? 'unknown', at: Date.now() });
345
- }
346
- return; // allow — return void
347
- }
348
- // deny
349
- process.stderr.write(`[governance] Denied\n\n`);
350
- return { block: true, blockReason: verdict.reason };
351
- }
352
- // ── ALLOW ──────────────────────────────────────────────
353
- if (verbose) {
354
- api.logger.debug?.(`[governance] ALLOW ${govEvent.intent}`);
355
- }
356
- // allow — return void
357
- });
358
- // ── Session Reset Hook — Drift detection on /new ────────────────
359
- // OpenClaw before_reset hook (from src/plugins/types.ts):
360
- // event: { sessionFile?: string; messages?: unknown[]; reason?: string }
361
- // ctx: { agentId?: string; sessionKey?: string }
362
- // return: void
363
- //
364
- // On /new or /reset, check if .md files have changed since last bootstrap.
365
- // If drifted, prompt the user: "Regenerate constitution? [y/n]"
366
- // If yes, re-bootstrap from current .md files before the new session starts.
367
- api.on('before_reset', async () => {
368
- // Only check if we have a world with hash baseline
369
- const world = engine.getWorld();
370
- const storedHashes = world?.metadata?.mdHashes;
371
- if (!storedHashes)
372
- return;
373
- const drift = (0, world_bootstrap_1.checkMdDrift)(workspaceDir, storedHashes);
374
- const hasDrift = drift.changed.length > 0 || drift.added.length > 0 || drift.removed.length > 0;
375
- if (!hasDrift) {
376
- // Clear session allowlist on reset (fresh session = fresh allowlist)
377
- allowlist.clear();
378
- return;
379
- }
380
- // Report what changed
381
- const changedSummary = [];
382
- if (drift.changed.length > 0)
383
- changedSummary.push(`modified: ${drift.changed.join(', ')}`);
384
- if (drift.added.length > 0)
385
- changedSummary.push(`new: ${drift.added.join(', ')}`);
386
- if (drift.removed.length > 0)
387
- changedSummary.push(`removed: ${drift.removed.join(', ')}`);
388
- process.stderr.write(`\n[NeuroVerseOS] Markdown files changed since last bootstrap.\n`);
389
- process.stderr.write(` ${changedSummary.join('; ')}\n`);
390
- // Prompt: regenerate?
391
- const answer = await new Promise((res) => {
392
- const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stderr });
393
- process.stderr.write(` Regenerate constitution? [y/n]: `);
394
- rl.on('line', (line) => { rl.close(); res(line.trim().toLowerCase()); });
395
- rl.on('close', () => res('n'));
396
- });
397
- if (answer === 'y' || answer === 'yes') {
398
- const mdFiles = readMdFiles(workspaceDir);
399
- if (Object.keys(mdFiles).length > 0) {
400
- const newWorld = (0, world_bootstrap_1.bootstrapWorldFromMarkdown)(mdFiles);
401
- engine.setWorld(newWorld);
402
- engine.saveWorld();
403
- process.stderr.write(`[NeuroVerseOS] Constitution regenerated. ${newWorld.invariants.length} invariants, ${newWorld.guards.length} guards, ${newWorld.rules.length} rules.\n\n`);
404
- }
405
- else {
406
- process.stderr.write(`[NeuroVerseOS] No .md files found. Keeping current constitution.\n\n`);
407
- }
408
- }
409
- else {
410
- process.stderr.write(`[NeuroVerseOS] Keeping current constitution.\n\n`);
411
- }
412
- // Always clear session allowlist and alert history on reset
413
- allowlist.clear();
414
- engine.clearAlertHistory();
415
- });
416
- // ── Background Drift Service ───────────────────────────────────
417
- if (monitor) {
418
- api.registerService({
419
- id: 'neuroverse-drift-monitor',
420
- start: () => {
421
- monitor.start();
422
- api.logger.info('[NeuroVerseOS] Drift monitor started');
423
- },
424
- stop: () => {
425
- monitor.stop();
426
- api.logger.info('[NeuroVerseOS] Drift monitor stopped');
427
- },
428
- });
429
- }
430
- // ── Agent Tool: governance_status ──────────────────────────────
431
- // Lets agents answer "am I protected?", "any issues?", "what's enforced?"
432
- // by querying structured kernel state. No guessing — deterministic output.
433
- api.registerTool({
434
- name: 'governance_status',
435
- description: 'Query the governance kernel state. Use when the user asks about protection status, pending changes, integrity, role bindings, or audit trail.',
436
- parameters: {
437
- type: 'object',
438
- properties: {
439
- query: {
440
- type: 'string',
441
- enum: ['summary', 'status', 'bindings', 'audit', 'integrity'],
442
- description: 'What aspect of governance to query',
443
- },
444
- },
445
- required: ['query'],
446
- },
447
- handler: async (args) => {
448
- const world = engine.getWorld();
449
- const stats = engine.getStatus();
450
- const auditCounts = audit.getCounts();
451
- const bindings = engine.getRoleBindings();
452
- const meta = engine.getMetaRecord();
453
- if (!world) {
454
- return { text: 'No governance world loaded. The user should run /world bootstrap to create one from their .md files.' };
455
- }
456
- switch (args.query) {
457
- case 'summary': {
458
- const ruleCount = stats.invariantCount + stats.guardCount + stats.ruleCount;
459
- const issues = [];
460
- const storedHashes = world.metadata?.mdHashes;
461
- if (storedHashes) {
462
- const drift = (0, world_bootstrap_1.checkMdDrift)(workspaceDir, storedHashes);
463
- if (drift.changed.length > 0 || drift.added.length > 0 || drift.removed.length > 0) {
464
- issues.push('source files have drifted from the active world');
465
- }
466
- }
467
- if ((0, fs_1.existsSync)((0, path_1.join)(worldDir, 'world.pending.json'))) {
468
- issues.push('pending governance changes await approval');
469
- }
470
- if (bindings.length === 0 && stats.roleCount > 0) {
471
- issues.push('roles are defined but no agents are bound to them');
472
- }
473
- return {
474
- text: JSON.stringify({
475
- worldActive: true,
476
- worldName: world.metadata.name,
477
- enforcement: stats.enforcement,
478
- ruleCount,
479
- roleCount: stats.roleCount,
480
- bindingCount: bindings.length,
481
- verdicts: auditCounts,
482
- issues,
483
- }),
484
- };
485
- }
486
- case 'bindings': {
487
- const bindingList = bindings.map(b => {
488
- const role = world.roles.find(r => r.id === b.roleId);
489
- return {
490
- agentId: b.agentId,
491
- roleId: b.roleId,
492
- roleName: role?.name ?? '(unknown)',
493
- boundBy: b.boundBy,
494
- };
495
- });
496
- return {
497
- text: JSON.stringify({
498
- totalBindings: bindings.length,
499
- totalRoles: stats.roleCount,
500
- availableRoles: world.roles.map(r => ({ id: r.id, name: r.name })),
501
- bindings: bindingList,
502
- }),
503
- };
504
- }
505
- case 'audit': {
506
- return {
507
- text: JSON.stringify({
508
- totalVerdicts: auditCounts.total,
509
- allowed: auditCounts.allow,
510
- paused: auditCounts.pause,
511
- blocked: auditCounts.block,
512
- driftStats: monitor?.getStats() ?? null,
513
- }),
514
- };
515
- }
516
- case 'integrity': {
517
- let driftStatus = 'unknown';
518
- const storedHashes = world.metadata?.mdHashes;
519
- if (storedHashes) {
520
- const drift = (0, world_bootstrap_1.checkMdDrift)(workspaceDir, storedHashes);
521
- const hasDrift = drift.changed.length > 0 || drift.added.length > 0 || drift.removed.length > 0;
522
- driftStatus = hasDrift ? 'drifted' : 'in_sync';
523
- }
524
- return {
525
- text: JSON.stringify({
526
- worldHash: meta?.hash?.slice(0, 16) ?? null,
527
- version: meta?.version ?? null,
528
- activatedAt: meta?.activatedAt ?? null,
529
- sourceDrift: driftStatus,
530
- pendingChanges: (0, fs_1.existsSync)((0, path_1.join)(worldDir, 'world.pending.json')),
531
- }),
532
- };
533
- }
534
- default:
535
- return { text: 'Unknown query. Use: summary, status, bindings, audit, or integrity.' };
536
- }
537
- },
538
- });
539
- // ── Agent Tool: world_proposal.create ──────────────────────────
540
- api.registerTool({
541
- name: 'world_proposal_create',
542
- description: 'Propose a governance amendment. The human must approve it with /world approve.',
543
- parameters: {
544
- type: 'object',
545
- properties: {
546
- type: { type: 'string', enum: ['add_guard', 'modify_threshold', 'add_role', 'add_invariant'] },
547
- reason: { type: 'string', description: 'Why this amendment is needed' },
548
- suggestion: { type: 'string', description: 'The specific change to make' },
549
- evidence: { type: 'string', description: 'What behavior triggered this recommendation' },
550
- },
551
- required: ['type', 'reason', 'suggestion'],
552
- },
553
- handler: async (args) => {
554
- const id = Date.now().toString(36);
555
- const proposal = { ...args, id, createdAt: Date.now(), source: 'agent' };
556
- const proposalPath = (0, path_1.join)(proposalsDir, `${id}.json`);
557
- (0, fs_1.writeFileSync)(proposalPath, JSON.stringify(proposal, null, 2));
558
- return {
559
- text: `Governance amendment proposed (ID: ${id}).\nType: ${args.type}\nReason: ${args.reason}\nThe human must run /world approve ${id} to apply this change.`,
560
- };
561
- },
562
- });
563
- // ── CLI Commands (gateway CLI: `openclaw world ...`) ───────────
564
- api.registerCli(({ program, logger }) => {
565
- const worldCmd = program
566
- .command('world')
567
- .description('NeuroVerse governance commands');
568
- // openclaw world bootstrap
569
- worldCmd
570
- .command('bootstrap')
571
- .description('Generate world.json from your .md files')
572
- .action(async () => {
573
- logger.info('[NeuroVerseOS] Reading .md files from workspace...');
574
- const mdFiles = readMdFiles(workspaceDir);
575
- for (const file of Object.keys(mdFiles)) {
576
- logger.info(` Found ${file}`);
577
- }
578
- if (Object.keys(mdFiles).length === 0) {
579
- logger.info(' No .md files found in workspace.');
580
- logger.info(' Add any .md files to define your governance rules.');
581
- return;
582
- }
583
- logger.info(`[NeuroVerseOS] Bootstrapping world from ${Object.keys(mdFiles).length} files...`);
584
- const world = (0, world_bootstrap_1.bootstrapWorldFromMarkdown)(mdFiles);
585
- engine.setWorld(world);
586
- engine.saveWorld();
587
- logger.info(`World file created: ${worldPath}`);
588
- logger.info(` Kernel: ${world.kernel.enforcementMode} mode`);
589
- logger.info(` Invariants: ${world.invariants.length}`);
590
- logger.info(` Guards: ${world.guards.length}`);
591
- logger.info(` Rules: ${world.rules.length}`);
592
- logger.info(` Roles: ${world.roles.length}`);
593
- });
594
- // openclaw world status
595
- worldCmd
596
- .command('status')
597
- .description('Show governance status')
598
- .action(() => {
599
- if (!(0, fs_1.existsSync)(worldPath)) {
600
- logger.info('[NeuroVerseOS] No world file found. Run /world bootstrap first.');
601
- return;
602
- }
603
- const stats = engine.getStatus();
604
- const auditCounts = audit.getCounts();
605
- logger.info(`World: ${worldPath}`);
606
- logger.info(`Enforcement: ${config.enforcement ?? 'standard'}`);
607
- logger.info(`Invariants: ${stats.invariantCount}, Guards: ${stats.guardCount}, Rules: ${stats.ruleCount}, Roles: ${stats.roleCount}`);
608
- logger.info(`Verdicts — ALLOW: ${auditCounts.allow}, PAUSE: ${auditCounts.pause}, BLOCK: ${auditCounts.block}`);
609
- });
610
- // openclaw world laws
611
- worldCmd
612
- .command('laws')
613
- .description('Display the full constitution')
614
- .action(() => {
615
- if (!(0, fs_1.existsSync)(worldPath)) {
616
- logger.info('[NeuroVerseOS] No world file found. Run /world bootstrap first.');
617
- return;
618
- }
619
- const world = engine.getWorld();
620
- if (!world) {
621
- logger.info('[NeuroVerseOS] World file could not be loaded.');
622
- return;
623
- }
624
- logger.info(`\n=== THE LAWS OF ${(world.metadata.name ?? 'NeuroVerseOS').toUpperCase()} ===\n`);
625
- if (world.metadata.description)
626
- logger.info(world.metadata.description);
627
- logger.info(`Enforcement: ${world.kernel.enforcementMode} | Overrides: ${world.kernel.sessionOverridesAllowed ? 'allowed' : 'disabled'}\n`);
628
- logger.info(`--- INVARIANTS (${world.invariants.length}) ---`);
629
- for (const inv of world.invariants) {
630
- logger.info(` [${inv.id}] ${inv.description}`);
631
- logger.info(` When: ${inv.condition.field} ${inv.condition.operator} ${JSON.stringify(inv.condition.value)}`);
632
- }
633
- logger.info(`\n--- GUARDS (${world.guards.length}) ---`);
634
- for (const guard of world.guards) {
635
- logger.info(` [${guard.id}] → ${guard.enforcement.toUpperCase()}${guard.description ? ': ' + guard.description : ''}`);
636
- logger.info(` When: ${guard.condition.field} ${guard.condition.operator} ${JSON.stringify(guard.condition.value)}`);
637
- if (guard.appliesTo.length > 0)
638
- logger.info(` Applies to: ${guard.appliesTo.join(', ')}`);
639
- }
640
- logger.info(`\n--- RULES (${world.rules.length}) ---`);
641
- for (const rule of world.rules) {
642
- logger.info(` [${rule.id}] → ${rule.effect.verdict.toUpperCase()}${rule.description ? ': ' + rule.description : ''}`);
643
- logger.info(` Trigger: ${rule.trigger.field} ${rule.trigger.operator} ${JSON.stringify(rule.trigger.value)}`);
644
- }
645
- logger.info(`\n--- ROLES (${world.roles.length}) ---`);
646
- for (const role of world.roles) {
647
- logger.info(` [${role.id}] ${role.name}`);
648
- if (role.canDo.length > 0)
649
- logger.info(` Can do: ${role.canDo.join(', ')}`);
650
- if (role.cannotDo.length > 0)
651
- logger.info(` Cannot do: ${role.cannotDo.join(', ')}`);
652
- logger.info(` Requires approval: ${role.requiresApproval ? 'yes' : 'no'}`);
653
- }
654
- });
655
- }, { commands: ['world'] });
656
- // ── Chat Command: /world ───────────────────────────────────────
657
- // Registers as a slash command in the agent conversation.
658
- // Bypasses the LLM — processed directly by the plugin.
659
- api.registerCommand({
660
- name: 'world',
661
- description: 'NeuroVerse governance commands (bootstrap, status, laws, propose, approve, export)',
662
- acceptsArgs: true,
663
- requireAuth: true,
664
- handler: async (ctx) => {
665
- const cmdArgs = (ctx.args ?? '').trim();
666
- const [subcommand, ...rest] = cmdArgs.split(/\s+/);
667
- // ── /world (no args) — Quick status dashboard ──
668
- if (!subcommand) {
669
- if (!(0, fs_1.existsSync)(worldPath)) {
670
- return {
671
- text: [
672
- '**NeuroVerse Governance**',
673
- '',
674
- 'No world file found. Run `/world bootstrap` to create one from your .md files.',
675
- 'Type `/world help` for all commands.',
676
- ].join('\n'),
677
- };
678
- }
679
- const world = engine.getWorld();
680
- const stats = engine.getStatus();
681
- const auditCounts = audit.getCounts();
682
- const bindings = engine.getRoleBindings();
683
- const meta = engine.getMetaRecord();
684
- const worldName = world?.metadata?.name ?? 'NeuroVerseOS';
685
- // Integrity check
686
- let integrityStatus = 'Verified';
687
- if (!meta) {
688
- integrityStatus = 'No baseline (run /world bootstrap)';
689
- }
690
- // Drift check
691
- let driftStatus = 'In sync';
692
- const storedHashes = world?.metadata?.mdHashes;
693
- if (storedHashes) {
694
- const drift = (0, world_bootstrap_1.checkMdDrift)(workspaceDir, storedHashes);
695
- const hasDrift = drift.changed.length > 0 || drift.added.length > 0 || drift.removed.length > 0;
696
- if (hasDrift) {
697
- const parts = [];
698
- if (drift.changed.length > 0)
699
- parts.push(`${drift.changed.length} modified`);
700
- if (drift.added.length > 0)
701
- parts.push(`${drift.added.length} new`);
702
- if (drift.removed.length > 0)
703
- parts.push(`${drift.removed.length} removed`);
704
- driftStatus = parts.join(', ') + ' — run /world bootstrap';
705
- }
706
- }
707
- else {
708
- driftStatus = 'No baseline';
709
- }
710
- // Pending check
711
- const pendingPath = (0, path_1.join)(worldDir, 'world.pending.json');
712
- const hasPending = (0, fs_1.existsSync)(pendingPath);
713
- const lines = [
714
- `**${worldName}** — NeuroVerse Governance`,
715
- '',
716
- `World: ACTIVE`,
717
- `Integrity: ${integrityStatus}`,
718
- `Enforcement: ${stats.enforcement}`,
719
- `Source drift: ${driftStatus}`,
720
- `Pending updates: ${hasPending ? 'Yes — run /world approve' : 'None'}`,
721
- '',
722
- `Invariants: ${stats.invariantCount} Guards: ${stats.guardCount} Rules: ${stats.ruleCount} Roles: ${stats.roleCount}`,
723
- `Role bindings: ${bindings.length > 0 ? `${bindings.length} active` : 'None — run /world bind'}`,
724
- '',
725
- `Verdicts this session: ${auditCounts.allow} allowed, ${auditCounts.pause} paused, ${auditCounts.block} blocked`,
726
- ];
727
- return { text: lines.join('\n') };
728
- }
729
- // ── /world help ──
730
- if (subcommand === 'help') {
731
- return {
732
- text: [
733
- '**NeuroVerse Governance Commands**',
734
- '',
735
- '`/world` — Quick status dashboard',
736
- '`/world summary` — One-line protection status',
737
- '`/world status` — Full metrics (drift, audit, friction)',
738
- '`/world laws` — Read the full constitution',
739
- '`/world bootstrap` — Generate world.json from your .md files',
740
- '',
741
- '**Role bindings:**',
742
- '`/world bind <agentId> <roleId>` — Bind an agent to a role',
743
- '`/world unbind <agentId>` — Remove a binding',
744
- '`/world bindings` — Show all bindings',
745
- '',
746
- '**Amendments:**',
747
- '`/world propose` — Agent recommends amendments',
748
- '`/world approve <id>` — Approve amendment',
749
- '`/world export` — Export world file',
750
- '',
751
- 'You can also ask naturally:',
752
- ' "Am I protected?" "Any pending changes?" "What changed?"',
753
- ].join('\n'),
754
- };
755
- }
756
- // ── /world summary ── One-line conversational status
757
- if (subcommand === 'summary') {
758
- if (!(0, fs_1.existsSync)(worldPath)) {
759
- return { text: 'No governance world loaded. Run `/world bootstrap` to create one.' };
760
- }
761
- const stats = engine.getStatus();
762
- const bindings = engine.getRoleBindings();
763
- const auditCounts = audit.getCounts();
764
- // Check for issues
765
- const issues = [];
766
- const storedHashes = engine.getWorld()?.metadata?.mdHashes;
767
- if (storedHashes) {
768
- const drift = (0, world_bootstrap_1.checkMdDrift)(workspaceDir, storedHashes);
769
- if (drift.changed.length > 0 || drift.added.length > 0 || drift.removed.length > 0) {
770
- issues.push('source drift detected');
771
- }
772
- }
773
- const pendingPath = (0, path_1.join)(worldDir, 'world.pending.json');
774
- if ((0, fs_1.existsSync)(pendingPath)) {
775
- issues.push('pending updates await approval');
776
- }
777
- if (bindings.length === 0 && stats.roleCount > 0) {
778
- issues.push('roles defined but no agents bound');
779
- }
780
- const enforcement = stats.enforcement;
781
- const ruleCount = stats.invariantCount + stats.guardCount + stats.ruleCount;
782
- if (issues.length === 0) {
783
- return {
784
- text: `Governance active. ${ruleCount} rules enforcing in ${enforcement} mode. ${bindings.length} agent(s) bound. ${auditCounts.total} verdicts logged. No issues.`,
785
- };
786
- }
787
- return {
788
- text: `Governance active. ${ruleCount} rules enforcing in ${enforcement} mode. ${bindings.length} agent(s) bound. ${auditCounts.total} verdicts logged. Attention: ${issues.join('; ')}.`,
789
- };
790
- }
791
- // ── /world bootstrap ──
792
- if (subcommand === 'bootstrap') {
793
- const lines = [];
794
- // Warn if agents modified .md files during this session
795
- if (agentModifiedMdFiles.size > 0) {
796
- lines.push('🔒 GOVERNANCE WARNING');
797
- lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
798
- lines.push('The following .md files were modified by agents this session:');
799
- for (const [file, { agentId }] of agentModifiedMdFiles) {
800
- lines.push(` ⚠️ ${file} (by ${agentId})`);
801
- }
802
- lines.push('');
803
- lines.push('Bootstrapping will compile these changes into your World File.');
804
- lines.push('Review each file before proceeding. If any changes look');
805
- lines.push('unexpected, revert them before bootstrapping.');
806
- lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
807
- lines.push('');
808
- }
809
- lines.push('[NeuroVerseOS] Reading .md files from workspace...');
810
- const mdFiles = readMdFiles(workspaceDir);
811
- for (const file of Object.keys(mdFiles)) {
812
- lines.push(` Found ${file}`);
813
- }
814
- if (Object.keys(mdFiles).length === 0) {
815
- lines.push(' No .md files found in workspace.');
816
- lines.push(' Add any .md files to define your governance rules.');
817
- return { text: lines.join('\n') };
818
- }
819
- lines.push(`\n[NeuroVerseOS] Bootstrapping world from ${Object.keys(mdFiles).length} files...`);
820
- const world = (0, world_bootstrap_1.bootstrapWorldFromMarkdown)(mdFiles);
821
- engine.setWorld(world);
822
- engine.saveWorld();
823
- lines.push(`\nWorld file created: ${worldPath}`);
824
- lines.push(` Kernel: ${world.kernel.enforcementMode} mode`);
825
- lines.push(` Session overrides: ${world.kernel.sessionOverridesAllowed ? 'allowed' : 'disabled'}`);
826
- lines.push(` Invariants: ${world.invariants.length}`);
827
- lines.push(` Guards: ${world.guards.length}`);
828
- lines.push(` Rules: ${world.rules.length}`);
829
- lines.push(` Roles: ${world.roles.length}`);
830
- lines.push(`\n Run '/world status' to see governance overview.`);
831
- return { text: lines.join('\n') };
832
- }
833
- // ── /world status ──
834
- if (subcommand === 'status') {
835
- if (!(0, fs_1.existsSync)(worldPath)) {
836
- return { text: '[NeuroVerseOS] No world file found. Run /world bootstrap first.' };
837
- }
838
- const stats = engine.getStatus();
839
- const driftStats = monitor?.getStats();
840
- const auditCounts = audit.getCounts();
841
- const lines = [
842
- '\n=== NeuroVerse Governance Status ===\n',
843
- `World: ${worldPath}`,
844
- `Enforcement: ${config.enforcement ?? 'standard'}`,
845
- `Mode: ${config.observeFirst ? 'OBSERVE (recording only)' : 'ENFORCE (active governance)'}`,
846
- `\nInvariants: ${stats.invariantCount}`,
847
- `Guards: ${stats.guardCount}`,
848
- `Rules: ${stats.ruleCount}`,
849
- `Roles: ${stats.roleCount}`,
850
- `Role bindings: ${engine.getRoleBindings().length}`,
851
- `\n--- Audit Log ---`,
852
- `Total verdicts: ${auditCounts.total}`,
853
- ` ALLOW: ${auditCounts.allow}`,
854
- ` PAUSE: ${auditCounts.pause}`,
855
- ` BLOCK: ${auditCounts.block}`,
856
- ];
857
- if (driftStats) {
858
- lines.push(`\n--- Drift Metrics ---`, `Total actions: ${driftStats.totalActions}`, `Blocks: ${driftStats.blockCount} (${(driftStats.blockRate * 100).toFixed(1)}%)`, `Pauses: ${driftStats.pauseCount} (${(driftStats.pauseRate * 100).toFixed(1)}%)`, `Allows: ${driftStats.allowCount}`, `Override rate: ${(driftStats.overrideRate * 100).toFixed(1)}%`);
859
- if (driftStats.driftSignals.length > 0) {
860
- lines.push(`\nDrift signals:`);
861
- for (const signal of driftStats.driftSignals) {
862
- lines.push(` - ${signal}`);
863
- }
864
- }
865
- }
866
- // ── Markdown Drift Detection ──
867
- const world = engine.getWorld();
868
- const storedHashes = world?.metadata?.mdHashes;
869
- if (storedHashes) {
870
- const drift = (0, world_bootstrap_1.checkMdDrift)(workspaceDir, storedHashes);
871
- const hasDrift = drift.changed.length > 0 || drift.added.length > 0 || drift.removed.length > 0;
872
- if (hasDrift) {
873
- lines.push(`\n--- Constitution Drift ---`);
874
- lines.push(`Markdown files changed since last bootstrap.`);
875
- if (drift.changed.length > 0)
876
- lines.push(` Modified: ${drift.changed.join(', ')}`);
877
- if (drift.added.length > 0)
878
- lines.push(` New: ${drift.added.join(', ')}`);
879
- if (drift.removed.length > 0)
880
- lines.push(` Removed: ${drift.removed.join(', ')}`);
881
- lines.push(`Governance may be out of sync. Run /world bootstrap to regenerate.`);
882
- }
883
- else {
884
- lines.push(`\nConstitution: in sync with .md files`);
885
- }
886
- }
887
- else {
888
- lines.push(`\nConstitution: no hash baseline (bootstrapped before drift detection was added)`);
889
- lines.push(`Run /world bootstrap to establish baseline.`);
890
- }
891
- return { text: lines.join('\n') };
892
- }
893
- // ── /world laws ──
894
- if (subcommand === 'laws') {
895
- if (!(0, fs_1.existsSync)(worldPath)) {
896
- return { text: '[NeuroVerseOS] No world file found. Run /world bootstrap first.' };
897
- }
898
- const world = engine.getWorld();
899
- if (!world) {
900
- return { text: '[NeuroVerseOS] World file could not be loaded.' };
901
- }
902
- const lines = [
903
- '\n========================================',
904
- ` THE LAWS OF ${(world.metadata.name ?? 'NeuroVerseOS').toUpperCase()}`,
905
- '========================================\n',
906
- ];
907
- if (world.metadata.description) {
908
- lines.push(`${world.metadata.description}\n`);
909
- }
910
- lines.push(`Bootstrapped from: ${world.metadata.bootstrappedFrom.join(', ')}`);
911
- lines.push(`Enforcement mode: ${world.kernel.enforcementMode}`);
912
- lines.push(`Session overrides: ${world.kernel.sessionOverridesAllowed ? 'allowed' : 'disabled'}`);
913
- // ── Invariants ──
914
- lines.push(`\n--- INVARIANTS (${world.invariants.length}) ---`);
915
- lines.push(`Hard blocks. Cannot be overridden. Ever.\n`);
916
- if (world.invariants.length === 0) {
917
- lines.push(' (none)');
918
- }
919
- for (const inv of world.invariants) {
920
- lines.push(` [${inv.id}]`);
921
- lines.push(` ${inv.description}`);
922
- lines.push(` When: ${inv.condition.field} ${inv.condition.operator} ${JSON.stringify(inv.condition.value)}`);
923
- if (inv.scope.length > 0)
924
- lines.push(` Scope: ${inv.scope.join(', ')}`);
925
- lines.push('');
926
- }
927
- // ── Guards ──
928
- lines.push(`--- GUARDS (${world.guards.length}) ---`);
929
- lines.push(`Pause or block. Can be session-overridden if allowed.\n`);
930
- if (world.guards.length === 0) {
931
- lines.push(' (none)');
932
- }
933
- for (const guard of world.guards) {
934
- lines.push(` [${guard.id}] → ${guard.enforcement.toUpperCase()}`);
935
- if (guard.description)
936
- lines.push(` ${guard.description}`);
937
- lines.push(` When: ${guard.condition.field} ${guard.condition.operator} ${JSON.stringify(guard.condition.value)}`);
938
- if (guard.appliesTo.length > 0)
939
- lines.push(` Applies to: ${guard.appliesTo.join(', ')}`);
940
- if (guard.scope.length > 0)
941
- lines.push(` Scope: ${guard.scope.join(', ')}`);
942
- if (guard.requiresApproval)
943
- lines.push(` Requires approval: yes`);
944
- lines.push('');
945
- }
946
- // ── Rules ──
947
- lines.push(`--- RULES (${world.rules.length}) ---`);
948
- lines.push(`Contextual triggers with verdict effects.\n`);
949
- if (world.rules.length === 0) {
950
- lines.push(' (none)');
951
- }
952
- for (const rule of world.rules) {
953
- lines.push(` [${rule.id}] → ${rule.effect.verdict.toUpperCase()}`);
954
- if (rule.description)
955
- lines.push(` ${rule.description}`);
956
- lines.push(` Trigger: ${rule.trigger.field} ${rule.trigger.operator} ${JSON.stringify(rule.trigger.value)}`);
957
- lines.push('');
958
- }
959
- // ── Roles ──
960
- lines.push(`--- ROLES (${world.roles.length}) ---`);
961
- lines.push(`Identity-based constraints.\n`);
962
- if (world.roles.length === 0) {
963
- lines.push(' (none)');
964
- }
965
- for (const role of world.roles) {
966
- lines.push(` [${role.id}] ${role.name}`);
967
- if (role.canDo.length > 0)
968
- lines.push(` Can do: ${role.canDo.join(', ')}`);
969
- if (role.cannotDo.length > 0)
970
- lines.push(` Cannot do: ${role.cannotDo.join(', ')}`);
971
- lines.push(` Requires approval: ${role.requiresApproval ? 'yes' : 'no'}`);
972
- lines.push('');
973
- }
974
- // ── Role Bindings ──
975
- const bindings = engine.getRoleBindings();
976
- lines.push(`--- ROLE BINDINGS (${bindings.length}) ---`);
977
- lines.push(`Agent identity → governance role. Environment-specific.\n`);
978
- if (bindings.length === 0) {
979
- lines.push(' (none — roles are defined but no agents are bound)');
980
- lines.push(' Run /world bind <agentId> <roleId> to assign.');
981
- }
982
- for (const binding of bindings) {
983
- const role = world.roles.find(r => r.id === binding.roleId);
984
- const roleName = role ? role.name : '(unknown role)';
985
- lines.push(` ${binding.agentId} → [${binding.roleId}] ${roleName}`);
986
- lines.push(` Bound by: ${binding.boundBy}`);
987
- }
988
- lines.push('');
989
- lines.push('========================================');
990
- return { text: lines.join('\n') };
991
- }
992
- // ── /world bind <agentId> <roleId> ──
993
- if (subcommand === 'bind') {
994
- const [agentId, roleId] = rest;
995
- if (!agentId || !roleId) {
996
- return { text: 'Usage: `/world bind <agentId> <roleId>`\n\nExample: `/world bind agent-coder-01 executor`' };
997
- }
998
- // Validate roleId exists in the world
999
- const world = engine.getWorld();
1000
- if (!world) {
1001
- return { text: '[NeuroVerseOS] No world loaded. Run /world bootstrap first.' };
1002
- }
1003
- const role = world.roles.find(r => r.id === roleId);
1004
- if (!role) {
1005
- const available = world.roles.map(r => `\`${r.id}\` (${r.name})`).join(', ');
1006
- return { text: `[NeuroVerseOS] Role "${roleId}" not found.\n\nAvailable roles: ${available}` };
1007
- }
1008
- const meta = engine.getMetaRecord();
1009
- if (!meta) {
1010
- return { text: '[NeuroVerseOS] No meta record. Run /world bootstrap first.' };
1011
- }
1012
- // Classify severity and apply
1013
- const severity = engine.setRoleBinding(agentId, roleId, 'human');
1014
- engine.saveMeta();
1015
- engine.hydrateRoleMap(roleMap);
1016
- const severityLabel = {
1017
- new_binding: 'LOW — new binding',
1018
- reassignment: 'HIGH — role reassignment',
1019
- escalation: 'CRITICAL — privilege escalation',
1020
- de_escalation: 'LOW — privilege de-escalation',
1021
- removal: 'HIGH — binding removed',
1022
- };
1023
- const lines = [
1024
- `[NeuroVerseOS] Role binding updated.`,
1025
- ` Agent: ${agentId}`,
1026
- ` Role: ${role.id} (${role.name})`,
1027
- ` Severity: ${severityLabel[severity] ?? severity}`,
1028
- ` Can do: ${role.canDo.join(', ') || '(none)'}`,
1029
- ` Cannot do: ${role.cannotDo.join(', ') || '(none)'}`,
1030
- ` Requires approval: ${role.requiresApproval ? 'yes' : 'no'}`,
1031
- ];
1032
- if (severity === 'escalation') {
1033
- lines.push('');
1034
- lines.push(' ⚠ This is a privilege escalation. The agent now has broader permissions.');
1035
- }
1036
- return { text: lines.join('\n') };
1037
- }
1038
- // ── /world unbind <agentId> ──
1039
- if (subcommand === 'unbind') {
1040
- const [agentId] = rest;
1041
- if (!agentId) {
1042
- return { text: 'Usage: `/world unbind <agentId>`' };
1043
- }
1044
- const removed = engine.removeRoleBinding(agentId);
1045
- if (!removed) {
1046
- return { text: `[NeuroVerseOS] No binding found for agent "${agentId}".` };
1047
- }
1048
- engine.saveMeta();
1049
- engine.hydrateRoleMap(roleMap);
1050
- return {
1051
- text: [
1052
- `[NeuroVerseOS] Role binding removed.`,
1053
- ` Agent: ${agentId}`,
1054
- ` Status: unbound (deny-by-default — role constraints will not apply)`,
1055
- ].join('\n'),
1056
- };
1057
- }
1058
- // ── /world bindings ──
1059
- if (subcommand === 'bindings') {
1060
- const bindings = engine.getRoleBindings();
1061
- if (bindings.length === 0) {
1062
- return {
1063
- text: [
1064
- '[NeuroVerseOS] No role bindings configured.',
1065
- '',
1066
- 'Agents are unbound. Role constraints are inert.',
1067
- 'Run `/world bind <agentId> <roleId>` to assign roles.',
1068
- '',
1069
- 'Available roles:',
1070
- ...(engine.getWorld()?.roles.map(r => ` \`${r.id}\` — ${r.name}`) ?? [' (no world loaded)']),
1071
- ].join('\n'),
1072
- };
1073
- }
1074
- const lines = [
1075
- '**Agent Role Bindings**',
1076
- '',
1077
- ];
1078
- for (const binding of bindings) {
1079
- const role = engine.getWorld()?.roles.find(r => r.id === binding.roleId);
1080
- const roleName = role ? ` (${role.name})` : ' (role not found in world)';
1081
- const boundDate = new Date(binding.boundAt).toISOString().slice(0, 19);
1082
- lines.push(` \`${binding.agentId}\` → \`${binding.roleId}\`${roleName}`);
1083
- lines.push(` Bound by: ${binding.boundBy} at ${boundDate}`);
1084
- }
1085
- return { text: lines.join('\n') };
1086
- }
1087
- // ── /world propose ──
1088
- if (subcommand === 'propose') {
1089
- if (!monitor) {
1090
- return { text: '[NeuroVerseOS] Drift tracking is disabled. Enable it to generate proposals.' };
1091
- }
1092
- const proposals = monitor.generateProposals();
1093
- if (proposals.length === 0) {
1094
- return { text: '[NeuroVerseOS] No amendments to propose. Governance is stable.' };
1095
- }
1096
- const lines = [];
1097
- for (const proposal of proposals) {
1098
- const id = Date.now().toString(36);
1099
- const proposalPath = (0, path_1.join)(proposalsDir, `${id}.json`);
1100
- (0, fs_1.writeFileSync)(proposalPath, JSON.stringify({ ...proposal, id, createdAt: Date.now() }, null, 2));
1101
- lines.push(`\nProposal ${id}:`);
1102
- lines.push(` Type: ${proposal.type}`);
1103
- lines.push(` Reason: ${proposal.reason}`);
1104
- lines.push(` Suggestion: ${proposal.suggestion}`);
1105
- }
1106
- lines.push(`\nRun '/world approve <id>' to apply a proposal.`);
1107
- return { text: lines.join('\n') };
1108
- }
1109
- // ── /world approve <id> ──
1110
- if (subcommand === 'approve') {
1111
- const id = rest[0];
1112
- if (!id) {
1113
- return { text: 'Usage: /world approve <proposal-id>' };
1114
- }
1115
- const proposalPath = (0, path_1.join)(proposalsDir, `${id}.json`);
1116
- if (!(0, fs_1.existsSync)(proposalPath)) {
1117
- return { text: `[NeuroVerseOS] Proposal ${id} not found.` };
1118
- }
1119
- const proposal = JSON.parse((0, fs_1.readFileSync)(proposalPath, 'utf-8'));
1120
- engine.applyAmendment(proposal);
1121
- engine.saveWorld();
1122
- return { text: `Proposal ${id} approved and applied to world.\n World file updated: ${worldPath}` };
1123
- }
1124
- // ── /world export ──
1125
- if (subcommand === 'export') {
1126
- if (!(0, fs_1.existsSync)(worldPath)) {
1127
- return { text: '[NeuroVerseOS] No world file. Run /world bootstrap first.' };
1128
- }
1129
- return {
1130
- text: [
1131
- '[NeuroVerseOS] Export to .nv-world.zip:',
1132
- ' Use the NeuroVerse configurator at https://www.neuroverseos.com/build/configurator',
1133
- ` Or copy ${worldPath} to use with the Action Space runner.`,
1134
- ].join('\n'),
1135
- };
1136
- }
1137
- return { text: `Unknown subcommand: ${subcommand}. Run /world help for usage.` };
1138
- },
1139
- });
1140
- // ── Startup ────────────────────────────────────────────────────
1141
- api.logger.info('[NeuroVerseOS] Governance plugin loaded (v1.3.0)');
1142
- if ((0, fs_1.existsSync)(worldPath)) {
1143
- engine.loadWorld();
1144
- engine.loadMeta();
1145
- const status = engine.getStatus();
1146
- const ruleCount = status.invariantCount + status.guardCount + status.ruleCount;
1147
- api.logger.info(`[NeuroVerseOS] World active: ${ruleCount} rules, ${status.roleCount} roles, ${status.enforcement} enforcement`);
1148
- const meta = engine.getMetaRecord();
1149
- if (meta) {
1150
- api.logger.info(`[NeuroVerseOS] Integrity: verified (v${meta.version})`);
1151
- }
1152
- // Hydrate roleMap from meta bindings (agent identity → governance role)
1153
- engine.hydrateRoleMap(roleMap);
1154
- const bindingCount = engine.getRoleBindings().length;
1155
- if (bindingCount > 0) {
1156
- api.logger.info(`[NeuroVerseOS] ${bindingCount} agent(s) bound to roles`);
1157
- }
1158
- api.logger.info(`[NeuroVerseOS] Type /world for status, /world help for commands`);
164
+ function evaluateCondition(condition, event) {
165
+ const fieldValue = getFieldValue(event, condition.field);
166
+ if (fieldValue === void 0 && condition.operator !== "!=") {
167
+ return { matched: false, evidence: null };
168
+ }
169
+ switch (condition.operator) {
170
+ case "==":
171
+ return evaluateEquals(fieldValue, condition.value);
172
+ case "!=":
173
+ return evaluateNotEquals(fieldValue, condition.value);
174
+ case ">":
175
+ return evaluateComparison(fieldValue, condition.value, (a, b) => a > b);
176
+ case "<":
177
+ return evaluateComparison(fieldValue, condition.value, (a, b) => a < b);
178
+ case ">=":
179
+ return evaluateComparison(fieldValue, condition.value, (a, b) => a >= b);
180
+ case "<=":
181
+ return evaluateComparison(fieldValue, condition.value, (a, b) => a <= b);
182
+ case "in":
183
+ return evaluateIn(fieldValue, condition.value);
184
+ case "contains":
185
+ return evaluateContains(fieldValue, condition.value);
186
+ case "contains_any":
187
+ return evaluateContainsAny(fieldValue, condition.value);
188
+ case "matches_pattern":
189
+ return evaluateMatchesPattern(fieldValue, condition.value);
190
+ case "starts_with":
191
+ return evaluateStartsWith(fieldValue, condition.value);
192
+ case "ends_with":
193
+ return evaluateEndsWith(fieldValue, condition.value);
194
+ default:
195
+ return { matched: false, evidence: null };
196
+ }
197
+ }
198
+ function evaluateEquals(fieldValue, conditionValue) {
199
+ const fieldStr = toString(fieldValue);
200
+ const condStr = toString(conditionValue);
201
+ const matched = fieldStr === condStr;
202
+ return {
203
+ matched,
204
+ evidence: matched ? `${fieldStr} == ${condStr}` : null
205
+ };
206
+ }
207
+ function evaluateNotEquals(fieldValue, conditionValue) {
208
+ const fieldStr = toString(fieldValue);
209
+ const condStr = toString(conditionValue);
210
+ const matched = fieldStr !== condStr;
211
+ return {
212
+ matched,
213
+ evidence: matched ? `${fieldStr} != ${condStr}` : null
214
+ };
215
+ }
216
+ function evaluateComparison(fieldValue, conditionValue, comparator) {
217
+ const a = Number(fieldValue);
218
+ const b = Number(conditionValue);
219
+ if (isNaN(a) || isNaN(b)) return { matched: false, evidence: null };
220
+ const matched = comparator(a, b);
221
+ return {
222
+ matched,
223
+ evidence: matched ? `${a} compared to ${b}` : null
224
+ };
225
+ }
226
+ function evaluateIn(fieldValue, conditionValue) {
227
+ if (!Array.isArray(conditionValue)) return { matched: false, evidence: null };
228
+ const fieldStr = toString(fieldValue);
229
+ const matched = conditionValue.some((v) => toString(v) === fieldStr);
230
+ return {
231
+ matched,
232
+ evidence: matched ? `"${fieldStr}" found in [${conditionValue.join(", ")}]` : null
233
+ };
234
+ }
235
+ function evaluateContains(fieldValue, conditionValue) {
236
+ const fieldStr = toString(fieldValue).toLowerCase();
237
+ const searchStr = toString(conditionValue).toLowerCase();
238
+ const matched = fieldStr.includes(searchStr);
239
+ return {
240
+ matched,
241
+ evidence: matched ? `"${searchStr}" found in field` : null
242
+ };
243
+ }
244
+ function evaluateContainsAny(fieldValue, conditionValue) {
245
+ if (!Array.isArray(conditionValue)) return { matched: false, evidence: null };
246
+ const fieldStr = toString(fieldValue).toLowerCase();
247
+ for (const pattern of conditionValue) {
248
+ const patternLower = toString(pattern).toLowerCase();
249
+ if (fieldStr.includes(patternLower)) {
250
+ return {
251
+ matched: true,
252
+ evidence: `"${patternLower}" found in field`
253
+ };
1159
254
  }
1160
- else {
1161
- // First install clean onboarding
1162
- api.logger.info('[NeuroVerseOS] Welcome. No governance world found.');
1163
- api.logger.info('[NeuroVerseOS] To get started:');
1164
- api.logger.info('[NeuroVerseOS] 1. Add .md files defining your agent rules');
1165
- api.logger.info('[NeuroVerseOS] 2. Run /world bootstrap to compile them');
1166
- api.logger.info('[NeuroVerseOS] 3. Run /world bind <agentId> <roleId> to assign roles');
1167
- api.logger.info('[NeuroVerseOS] Type /world help for all commands');
255
+ }
256
+ return { matched: false, evidence: null };
257
+ }
258
+ function evaluateMatchesPattern(fieldValue, conditionValue) {
259
+ const fieldStr = toString(fieldValue);
260
+ const patterns = Array.isArray(conditionValue) ? conditionValue : [toString(conditionValue)];
261
+ for (const pattern of patterns) {
262
+ try {
263
+ const regex = new RegExp(toString(pattern), "i");
264
+ if (regex.test(fieldStr)) {
265
+ return {
266
+ matched: true,
267
+ evidence: `matched pattern /${pattern}/`
268
+ };
269
+ }
270
+ } catch {
271
+ continue;
1168
272
  }
273
+ }
274
+ return { matched: false, evidence: null };
275
+ }
276
+ function evaluateStartsWith(fieldValue, conditionValue) {
277
+ const fieldStr = toString(fieldValue).toLowerCase();
278
+ const prefix = toString(conditionValue).toLowerCase();
279
+ const matched = fieldStr.startsWith(prefix);
280
+ return {
281
+ matched,
282
+ evidence: matched ? `field starts with "${prefix}"` : null
283
+ };
284
+ }
285
+ function evaluateEndsWith(fieldValue, conditionValue) {
286
+ const fieldStr = toString(fieldValue).toLowerCase();
287
+ const suffix = toString(conditionValue).toLowerCase();
288
+ const matched = fieldStr.endsWith(suffix);
289
+ return {
290
+ matched,
291
+ evidence: matched ? `field ends with "${suffix}"` : null
292
+ };
1169
293
  }
1170
- //# sourceMappingURL=index.js.map
294
+ export {
295
+ BOOTSTRAP_EXIT_CODES,
296
+ CONFIGURE_AI_EXIT_CODES,
297
+ CompositeAuditLogger,
298
+ ConsoleAuditLogger,
299
+ DERIVE_EXIT_CODES,
300
+ FileAuditLogger,
301
+ GUARD_EXIT_CODES,
302
+ VALIDATE_EXIT_CODES,
303
+ createGovernanceEngine,
304
+ deriveWorld,
305
+ emitWorldDefinition,
306
+ evaluateCondition,
307
+ evaluateGuard,
308
+ eventToAllowlistKey,
309
+ explainWorld,
310
+ extractWorldMarkdown,
311
+ formatVerdict,
312
+ formatVerdictOneLine,
313
+ generateImpactReport,
314
+ generateImpactReportFromFile,
315
+ improveWorld,
316
+ loadWorld,
317
+ loadWorldFromDirectory,
318
+ normalizeWorldMarkdown,
319
+ parseWorldMarkdown,
320
+ readAuditLog,
321
+ renderExplainText,
322
+ renderImpactReport,
323
+ renderImproveText,
324
+ renderSimulateText,
325
+ simulateWorld,
326
+ summarizeAuditEvents,
327
+ validateWorld,
328
+ verdictToAuditEvent
329
+ };