@neuroverseos/governance 0.4.3 → 0.5.1

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 (68) hide show
  1. package/README.md +189 -0
  2. package/dist/adapters/autoresearch.js +2 -2
  3. package/dist/adapters/deep-agents.js +2 -2
  4. package/dist/adapters/express.js +2 -2
  5. package/dist/adapters/github.js +2 -2
  6. package/dist/adapters/index.js +23 -21
  7. package/dist/adapters/langchain.js +2 -2
  8. package/dist/adapters/mentraos.js +8 -6
  9. package/dist/adapters/openai.js +2 -2
  10. package/dist/adapters/openclaw.js +2 -2
  11. package/dist/{add-XSANI3FK.js → add-JP7TC2K3.js} +1 -1
  12. package/dist/admin/index.cjs +2214 -0
  13. package/dist/admin/index.d.cts +362 -0
  14. package/dist/admin/index.d.ts +362 -0
  15. package/dist/admin/index.js +703 -0
  16. package/dist/{build-EGBGZFIJ.js → build-THUEYMVT.js} +5 -5
  17. package/dist/{chunk-YJ34R5NB.js → chunk-5RAQ5DZW.js} +3 -3
  18. package/dist/{chunk-RDA7ISWC.js → chunk-6UPEUMJ2.js} +3 -3
  19. package/dist/chunk-7UU7V3AD.js +447 -0
  20. package/dist/{chunk-ZEIT2QLM.js → chunk-EK77AJAH.js} +22 -4
  21. package/dist/{chunk-3S5AD4AB.js → chunk-FGOSKQDE.js} +3 -3
  22. package/dist/{chunk-GTPV2XGO.js → chunk-GJ6LM4JZ.js} +1 -441
  23. package/dist/chunk-H3REGQRI.js +107 -0
  24. package/dist/{chunk-J2IZBHXJ.js → chunk-LAKUB76X.js} +3 -3
  25. package/dist/{chunk-FVOGUCB6.js → chunk-R23T5SZG.js} +3 -3
  26. package/dist/{chunk-A7SHG75T.js → chunk-RF2L5SYG.js} +3 -3
  27. package/dist/{chunk-QMVQ6KPL.js → chunk-TL4DLMMW.js} +3 -3
  28. package/dist/{chunk-AV7XJJWK.js → chunk-TZBERHFM.js} +3 -3
  29. package/dist/{chunk-3AYKQHYI.js → chunk-UZBW44KD.js} +3 -3
  30. package/dist/{chunk-FS2UUJJO.js → chunk-XPMZB46F.js} +3 -3
  31. package/dist/cli/neuroverse.cjs +962 -284
  32. package/dist/cli/neuroverse.js +46 -22
  33. package/dist/cli/plan.js +1 -1
  34. package/dist/cli/run.cjs +242 -139
  35. package/dist/cli/run.js +23 -3
  36. package/dist/{demo-6OQYWRR6.js → demo-N5K4VXJW.js} +3 -3
  37. package/dist/{derive-7Y7YWVLU.js → derive-5LOMN7GO.js} +4 -4
  38. package/dist/{equity-penalties-NVBAB5WL.js → equity-penalties-PYCJ3Q4U.js} +6 -6
  39. package/dist/{explain-HDFN4ION.js → explain-42TVC3QD.js} +1 -1
  40. package/dist/{guard-6KSCWT2W.js → guard-TPYDFG6V.js} +16 -4
  41. package/dist/{improve-2PWGGO5B.js → improve-HLZGJ54Z.js} +3 -3
  42. package/dist/index.cjs +19 -1
  43. package/dist/index.d.cts +2 -0
  44. package/dist/index.d.ts +2 -0
  45. package/dist/index.js +27 -27
  46. package/dist/keygen-BSZH3NM2.js +77 -0
  47. package/dist/{lens-MHMUDCMQ.js → lens-NFGZHD76.js} +1 -1
  48. package/dist/{mcp-server-TNIWZ7B5.js → mcp-server-5XXNG6VC.js} +2 -2
  49. package/dist/migrate-NH5PVMX4.js +221 -0
  50. package/dist/{playground-3FLDGBET.js → playground-2EU5CFIH.js} +4 -4
  51. package/dist/{redteam-HV6LMKEH.js → redteam-VK6OVHAE.js} +3 -3
  52. package/dist/{session-XZP2754M.js → session-NGA4DUPL.js} +2 -2
  53. package/dist/sign-RRELHKWM.js +11 -0
  54. package/dist/{simulate-VT437EEL.js → simulate-4YNOBMES.js} +1 -1
  55. package/dist/{test-4WTX6RKQ.js → test-HDBPMQTG.js} +3 -3
  56. package/dist/{validate-M52DX22Y.js → validate-6MFQZ2EG.js} +1 -1
  57. package/dist/verify-6AVTWX75.js +151 -0
  58. package/dist/{world-O4HTQPDP.js → world-H5WVURKU.js} +1 -1
  59. package/dist/{world-loader-YTYFOP7D.js → world-loader-J47PCPDZ.js} +1 -1
  60. package/package.json +22 -10
  61. package/dist/{behavioral-SLW7ALEK.js → behavioral-SPWPGYXL.js} +3 -3
  62. package/dist/{bootstrap-2OW5ZLBL.js → bootstrap-IP5QMC3Q.js} +3 -3
  63. package/dist/{chunk-I4RTIMLX.js → chunk-EQUAWNXW.js} +0 -0
  64. package/dist/{chunk-DA5MHFRR.js → chunk-NTHXZAW4.js} +3 -3
  65. package/dist/{chunk-FHXXD2TI.js → chunk-QZ666FCV.js} +6 -6
  66. package/dist/{configure-ai-LL3VAPQW.js → configure-ai-5MP5DWTT.js} +3 -3
  67. package/dist/{decision-flow-3K4D72G4.js → decision-flow-IJPNMVQK.js} +3 -3
  68. /package/dist/{doctor-EC5OYTI3.js → doctor-Q5APJOTS.js} +0 -0
@@ -0,0 +1,703 @@
1
+ import {
2
+ MENTRA_INTENT_TAXONOMY
3
+ } from "../chunk-GJ6LM4JZ.js";
4
+ import {
5
+ evaluateGuard
6
+ } from "../chunk-ZAF6JH23.js";
7
+ import "../chunk-QLPTHTVB.js";
8
+ import "../chunk-QWGCMQQD.js";
9
+
10
+ // src/admin/simulator.ts
11
+ function zonePolicyToVerdict(policy) {
12
+ switch (policy) {
13
+ case "allow":
14
+ return "allow";
15
+ case "block":
16
+ return "block";
17
+ case "confirm_each":
18
+ return "pause";
19
+ }
20
+ }
21
+ function intentMatchesPattern(intent, pattern) {
22
+ if (pattern.endsWith("*")) {
23
+ return intent.startsWith(pattern.slice(0, -1));
24
+ }
25
+ return intent === pattern;
26
+ }
27
+ function evaluateIntentAgainstZone(intentDef, zone) {
28
+ const rules = zone.rules;
29
+ const domain = intentDef.domain;
30
+ for (const custom of rules.customRules) {
31
+ if (intentMatchesPattern(intentDef.intent, custom.intentPattern)) {
32
+ return custom.action === "confirm" ? "pause" : custom.action;
33
+ }
34
+ }
35
+ if (domain === "camera") return zonePolicyToVerdict(rules.camera);
36
+ if (domain === "microphone") return zonePolicyToVerdict(rules.microphone);
37
+ if (domain === "ai_data" || domain === "ai_action") {
38
+ if (intentDef.intent.includes("auto_purchase") || intentDef.intent.includes("auto_respond")) {
39
+ return zonePolicyToVerdict(rules.aiActions);
40
+ }
41
+ if (intentDef.intent.includes("retain") || intentDef.intent.includes("share")) {
42
+ return zonePolicyToVerdict(rules.dataRetention);
43
+ }
44
+ return zonePolicyToVerdict(rules.aiDataSend);
45
+ }
46
+ if (domain === "location") return zonePolicyToVerdict(rules.locationSharing);
47
+ return "allow";
48
+ }
49
+ function evaluateIntentAgainstRole(intentDef, role) {
50
+ const def = role.definition;
51
+ for (const pattern of def.cannotDo) {
52
+ if (intentDef.intent.includes(pattern) || intentDef.description.toLowerCase().includes(pattern.toLowerCase()) || intentDef.domain === pattern) {
53
+ return "block";
54
+ }
55
+ }
56
+ if (def.requiresApproval) {
57
+ return "pause";
58
+ }
59
+ if (def.canDo.length > 0) {
60
+ const allowed = def.canDo.some(
61
+ (pattern) => intentDef.intent.includes(pattern) || intentDef.description.toLowerCase().includes(pattern.toLowerCase()) || intentDef.domain === pattern
62
+ );
63
+ if (!allowed) return "block";
64
+ }
65
+ return "allow";
66
+ }
67
+ function mergeVerdicts(...verdicts) {
68
+ if (verdicts.includes("block")) return "block";
69
+ if (verdicts.includes("pause")) return "pause";
70
+ if (verdicts.includes("modify")) return "modify";
71
+ return "allow";
72
+ }
73
+ function simulatePolicy(request, currentRoles, currentZones, platformWorld) {
74
+ const intentsToTest = request.intents ? MENTRA_INTENT_TAXONOMY.filter((i) => request.intents.includes(i.intent)) : MENTRA_INTENT_TAXONOMY;
75
+ const verdicts = [];
76
+ const conflicts = [];
77
+ for (const intentDef of intentsToTest) {
78
+ let currentVerdict;
79
+ if (request.type !== "full_matrix") {
80
+ const currentVerdictParts = [];
81
+ if (request.targetZoneId) {
82
+ const currentZone = currentZones.find((z) => z.id === request.targetZoneId);
83
+ if (currentZone) {
84
+ currentVerdictParts.push(evaluateIntentAgainstZone(intentDef, currentZone));
85
+ }
86
+ }
87
+ if (request.targetRoleId) {
88
+ const currentRole = currentRoles.find((r) => r.id === request.targetRoleId);
89
+ if (currentRole) {
90
+ currentVerdictParts.push(evaluateIntentAgainstRole(intentDef, currentRole));
91
+ }
92
+ }
93
+ currentVerdict = currentVerdictParts.length > 0 ? mergeVerdicts(...currentVerdictParts) : "allow";
94
+ }
95
+ const proposedParts = [];
96
+ if (request.proposed?.zone) {
97
+ proposedParts.push(evaluateIntentAgainstZone(intentDef, request.proposed.zone));
98
+ } else if (request.targetZoneId) {
99
+ const existingZone = currentZones.find((z) => z.id === request.targetZoneId);
100
+ if (existingZone) {
101
+ proposedParts.push(evaluateIntentAgainstZone(intentDef, existingZone));
102
+ }
103
+ }
104
+ if (request.proposed?.role) {
105
+ proposedParts.push(evaluateIntentAgainstRole(intentDef, request.proposed.role));
106
+ } else if (request.targetRoleId) {
107
+ const existingRole = currentRoles.find((r) => r.id === request.targetRoleId);
108
+ if (existingRole) {
109
+ proposedParts.push(evaluateIntentAgainstRole(intentDef, existingRole));
110
+ }
111
+ }
112
+ if (request.type === "full_matrix") {
113
+ for (const zone of currentZones) {
114
+ proposedParts.push(evaluateIntentAgainstZone(intentDef, zone));
115
+ }
116
+ for (const role of currentRoles) {
117
+ proposedParts.push(evaluateIntentAgainstRole(intentDef, role));
118
+ }
119
+ }
120
+ if (platformWorld) {
121
+ try {
122
+ const event = {
123
+ intent: intentDef.intent,
124
+ scope: intentDef.domain,
125
+ actionCategory: intentDef.action_category
126
+ };
127
+ const guardResult = evaluateGuard(event, platformWorld, {});
128
+ if (guardResult.status !== "ALLOW") {
129
+ proposedParts.push(
130
+ guardResult.status === "BLOCK" ? "block" : guardResult.status === "PAUSE" ? "pause" : guardResult.status === "MODIFY" ? "modify" : "allow"
131
+ );
132
+ }
133
+ } catch {
134
+ }
135
+ }
136
+ const proposedVerdict = proposedParts.length > 0 ? mergeVerdicts(...proposedParts) : "allow";
137
+ const changed = currentVerdict !== void 0 && currentVerdict !== proposedVerdict;
138
+ verdicts.push({
139
+ intent: intentDef.intent,
140
+ description: intentDef.description,
141
+ currentVerdict,
142
+ proposedVerdict,
143
+ changed,
144
+ reason: buildReason(intentDef, proposedVerdict, request)
145
+ });
146
+ if (changed && currentVerdict === "allow" && proposedVerdict === "block") {
147
+ conflicts.push({
148
+ description: `"${intentDef.description}" was allowed but would now be blocked`,
149
+ severity: intentDef.base_risk === "low" ? "warning" : "error",
150
+ intent: intentDef.intent,
151
+ suggestion: `Verify that blocking ${intentDef.intent} won't break apps that depend on it`
152
+ });
153
+ }
154
+ }
155
+ const summary = {
156
+ total: verdicts.length,
157
+ allowed: verdicts.filter((v) => v.proposedVerdict === "allow").length,
158
+ blocked: verdicts.filter((v) => v.proposedVerdict === "block").length,
159
+ paused: verdicts.filter((v) => v.proposedVerdict === "pause").length,
160
+ modified: verdicts.filter((v) => v.proposedVerdict === "modify").length
161
+ };
162
+ const diff = request.type !== "full_matrix" ? verdicts.filter((v) => v.changed).map((v) => ({
163
+ intent: v.intent,
164
+ from: v.currentVerdict,
165
+ to: v.proposedVerdict,
166
+ explanation: `${v.description}: ${v.currentVerdict} \u2192 ${v.proposedVerdict}`
167
+ })) : void 0;
168
+ return {
169
+ request,
170
+ timestamp: Date.now(),
171
+ verdicts,
172
+ summary,
173
+ conflicts,
174
+ diff
175
+ };
176
+ }
177
+ function simulateFullMatrix(roles, zones, platformWorld) {
178
+ const matrix = /* @__PURE__ */ new Map();
179
+ for (const role of roles) {
180
+ const roleResults = /* @__PURE__ */ new Map();
181
+ for (const zone of zones) {
182
+ const result = simulatePolicy(
183
+ {
184
+ type: "full_matrix",
185
+ proposed: { role, zone }
186
+ },
187
+ [],
188
+ [],
189
+ platformWorld
190
+ );
191
+ roleResults.set(zone.id, result.verdicts);
192
+ }
193
+ matrix.set(role.id, roleResults);
194
+ }
195
+ return matrix;
196
+ }
197
+ function buildReason(intentDef, verdict, request) {
198
+ if (verdict === "block") {
199
+ if (request.proposed?.zone) {
200
+ return `Blocked by zone "${request.proposed.zone.name}" policy`;
201
+ }
202
+ if (request.proposed?.role) {
203
+ return `Blocked by role "${request.proposed.role.name}" restrictions`;
204
+ }
205
+ return "Blocked by combined policy";
206
+ }
207
+ if (verdict === "pause") {
208
+ return "Requires user confirmation before proceeding";
209
+ }
210
+ if (verdict === "modify") {
211
+ return "Allowed with modifications";
212
+ }
213
+ return "Allowed by current policy";
214
+ }
215
+
216
+ // src/admin/manager.ts
217
+ var AUTHORITY_RANK = {
218
+ viewer: 0,
219
+ operator: 1,
220
+ supervisor: 2,
221
+ manager: 3,
222
+ admin: 4
223
+ };
224
+ function hasAuthority(actorLevel, requiredLevel) {
225
+ return AUTHORITY_RANK[actorLevel] >= AUTHORITY_RANK[requiredLevel];
226
+ }
227
+ var GovernanceAdmin = class {
228
+ storage;
229
+ platformWorld;
230
+ constructor(storage, platformWorld) {
231
+ this.storage = storage;
232
+ this.platformWorld = platformWorld;
233
+ }
234
+ // ── Authority Check ───────────────────────────────────────────────────
235
+ async getActorAuthority(actorRoleId) {
236
+ const chain = await this.storage.getAuthorityChain();
237
+ const grant = chain.grants.find((g) => g.roleId === actorRoleId);
238
+ return grant?.authorityLevel ?? "viewer";
239
+ }
240
+ async checkAuthority(actorRoleId, requiredLevel, locationId) {
241
+ const chain = await this.storage.getAuthorityChain();
242
+ const grant = chain.grants.find((g) => g.roleId === actorRoleId);
243
+ if (!grant || !hasAuthority(grant.authorityLevel, requiredLevel)) {
244
+ throw new GovernanceAdminError(
245
+ `Insufficient authority. Requires ${requiredLevel}, role "${actorRoleId}" has ${grant?.authorityLevel ?? "viewer"}`,
246
+ "INSUFFICIENT_AUTHORITY"
247
+ );
248
+ }
249
+ if (locationId && grant.locationScope && grant.locationScope.length > 0) {
250
+ if (!grant.locationScope.includes(locationId)) {
251
+ throw new GovernanceAdminError(
252
+ `Role "${actorRoleId}" does not have authority over location "${locationId}"`,
253
+ "LOCATION_SCOPE_DENIED"
254
+ );
255
+ }
256
+ }
257
+ }
258
+ async audit(action, actorId, actorRole, targetType, targetId, summary, changes) {
259
+ await this.storage.appendAudit({
260
+ id: `audit_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
261
+ action,
262
+ actorId,
263
+ actorRole,
264
+ timestamp: Date.now(),
265
+ targetType,
266
+ targetId,
267
+ changes,
268
+ summary
269
+ });
270
+ }
271
+ // ── Roles ─────────────────────────────────────────────────────────────
272
+ async createRole(role, actorId, actorRoleId) {
273
+ await this.checkAuthority(actorRoleId, "manager");
274
+ const existing = await this.storage.getRole(role.id);
275
+ if (existing) {
276
+ throw new GovernanceAdminError(
277
+ `Role "${role.id}" already exists`,
278
+ "ROLE_EXISTS"
279
+ );
280
+ }
281
+ const now = Date.now();
282
+ const newRole = {
283
+ ...role,
284
+ createdBy: actorId,
285
+ createdAt: now,
286
+ updatedAt: now,
287
+ active: true
288
+ };
289
+ await this.storage.saveRole(newRole);
290
+ await this.audit(
291
+ "role_created",
292
+ actorId,
293
+ actorRoleId,
294
+ "role",
295
+ role.id,
296
+ `Created role "${role.name}"`,
297
+ { after: newRole }
298
+ );
299
+ return newRole;
300
+ }
301
+ async updateRole(roleId, updates, actorId, actorRoleId) {
302
+ await this.checkAuthority(actorRoleId, "manager");
303
+ const existing = await this.storage.getRole(roleId);
304
+ if (!existing) {
305
+ throw new GovernanceAdminError(`Role "${roleId}" not found`, "ROLE_NOT_FOUND");
306
+ }
307
+ const updated = {
308
+ ...existing,
309
+ ...updates,
310
+ id: roleId,
311
+ // prevent ID mutation
312
+ updatedAt: Date.now()
313
+ };
314
+ await this.storage.saveRole(updated);
315
+ await this.audit(
316
+ "role_updated",
317
+ actorId,
318
+ actorRoleId,
319
+ "role",
320
+ roleId,
321
+ `Updated role "${roleId}"`,
322
+ {
323
+ before: existing,
324
+ after: updated
325
+ }
326
+ );
327
+ return updated;
328
+ }
329
+ async deleteRole(roleId, actorId, actorRoleId) {
330
+ await this.checkAuthority(actorRoleId, "admin");
331
+ const existing = await this.storage.getRole(roleId);
332
+ if (!existing) {
333
+ throw new GovernanceAdminError(`Role "${roleId}" not found`, "ROLE_NOT_FOUND");
334
+ }
335
+ const assignments = await this.storage.getAssignmentsByRole(roleId);
336
+ if (assignments.length > 0) {
337
+ throw new GovernanceAdminError(
338
+ `Cannot delete role "${roleId}" \u2014 ${assignments.length} device(s) still assigned. Unassign them first.`,
339
+ "ROLE_IN_USE"
340
+ );
341
+ }
342
+ await this.storage.deleteRole(roleId);
343
+ await this.audit(
344
+ "role_deleted",
345
+ actorId,
346
+ actorRoleId,
347
+ "role",
348
+ roleId,
349
+ `Deleted role "${existing.name}"`,
350
+ { before: existing }
351
+ );
352
+ }
353
+ async listRoles() {
354
+ return this.storage.getRoles();
355
+ }
356
+ async getRole(roleId) {
357
+ return this.storage.getRole(roleId);
358
+ }
359
+ // ── Assignments ───────────────────────────────────────────────────────
360
+ async assignRole(assignment, actorId, actorRoleId) {
361
+ await this.checkAuthority(actorRoleId, "operator");
362
+ const role = await this.storage.getRole(assignment.roleId);
363
+ if (!role) {
364
+ throw new GovernanceAdminError(
365
+ `Role "${assignment.roleId}" not found`,
366
+ "ROLE_NOT_FOUND"
367
+ );
368
+ }
369
+ const full = {
370
+ ...assignment,
371
+ id: `assign_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
372
+ assignedAt: Date.now()
373
+ };
374
+ await this.storage.saveAssignment(full);
375
+ await this.audit(
376
+ "role_assigned",
377
+ actorId,
378
+ actorRoleId,
379
+ "assignment",
380
+ full.id,
381
+ `Assigned role "${assignment.roleId}" to device "${assignment.deviceId}"` + (assignment.employeeName ? ` (${assignment.employeeName})` : "")
382
+ );
383
+ return full;
384
+ }
385
+ async unassignRole(assignmentId, actorId, actorRoleId) {
386
+ await this.checkAuthority(actorRoleId, "operator");
387
+ await this.storage.deleteAssignment(assignmentId);
388
+ await this.audit(
389
+ "role_unassigned",
390
+ actorId,
391
+ actorRoleId,
392
+ "assignment",
393
+ assignmentId,
394
+ `Removed role assignment "${assignmentId}"`
395
+ );
396
+ }
397
+ async getDeviceRole(deviceId) {
398
+ const assignments = await this.storage.getAssignmentsByDevice(deviceId);
399
+ const now = Date.now();
400
+ const active = assignments.filter(
401
+ (a) => !a.expiresAt || a.expiresAt > now
402
+ );
403
+ return active.sort((a, b) => b.assignedAt - a.assignedAt)[0] ?? null;
404
+ }
405
+ async listAssignments() {
406
+ return this.storage.getAssignments();
407
+ }
408
+ // ── Zones ─────────────────────────────────────────────────────────────
409
+ async createZone(zone, actorId, actorRoleId) {
410
+ await this.checkAuthority(actorRoleId, "manager", zone.locationId);
411
+ const existing = await this.storage.getZone(zone.id);
412
+ if (existing) {
413
+ throw new GovernanceAdminError(
414
+ `Zone "${zone.id}" already exists`,
415
+ "ZONE_EXISTS"
416
+ );
417
+ }
418
+ const now = Date.now();
419
+ const newZone = {
420
+ ...zone,
421
+ createdBy: actorId,
422
+ createdAt: now,
423
+ updatedAt: now,
424
+ active: true
425
+ };
426
+ await this.storage.saveZone(newZone);
427
+ await this.audit(
428
+ "zone_created",
429
+ actorId,
430
+ actorRoleId,
431
+ "zone",
432
+ zone.id,
433
+ `Created zone "${zone.name}" at location "${zone.locationId}"`,
434
+ { after: newZone }
435
+ );
436
+ return newZone;
437
+ }
438
+ async updateZone(zoneId, updates, actorId, actorRoleId) {
439
+ const existing = await this.storage.getZone(zoneId);
440
+ if (!existing) {
441
+ throw new GovernanceAdminError(`Zone "${zoneId}" not found`, "ZONE_NOT_FOUND");
442
+ }
443
+ await this.checkAuthority(actorRoleId, "manager", existing.locationId);
444
+ const updated = {
445
+ ...existing,
446
+ ...updates,
447
+ id: zoneId,
448
+ updatedAt: Date.now()
449
+ };
450
+ await this.storage.saveZone(updated);
451
+ await this.audit(
452
+ "zone_updated",
453
+ actorId,
454
+ actorRoleId,
455
+ "zone",
456
+ zoneId,
457
+ `Updated zone "${zoneId}"`,
458
+ {
459
+ before: existing,
460
+ after: updated
461
+ }
462
+ );
463
+ return updated;
464
+ }
465
+ async deleteZone(zoneId, actorId, actorRoleId) {
466
+ const existing = await this.storage.getZone(zoneId);
467
+ if (!existing) {
468
+ throw new GovernanceAdminError(`Zone "${zoneId}" not found`, "ZONE_NOT_FOUND");
469
+ }
470
+ await this.checkAuthority(actorRoleId, "admin", existing.locationId);
471
+ await this.storage.deleteZone(zoneId);
472
+ await this.audit(
473
+ "zone_deleted",
474
+ actorId,
475
+ actorRoleId,
476
+ "zone",
477
+ zoneId,
478
+ `Deleted zone "${existing.name}"`,
479
+ { before: existing }
480
+ );
481
+ }
482
+ async listZones(locationId) {
483
+ if (locationId) {
484
+ return this.storage.getZonesByLocation(locationId);
485
+ }
486
+ return this.storage.getZones();
487
+ }
488
+ async getZone(zoneId) {
489
+ return this.storage.getZone(zoneId);
490
+ }
491
+ // ── Authority ─────────────────────────────────────────────────────────
492
+ async updateAuthority(chain, actorId, actorRoleId) {
493
+ await this.checkAuthority(actorRoleId, "admin");
494
+ const previous = await this.storage.getAuthorityChain();
495
+ const enforced = {
496
+ ...chain,
497
+ emergencyOverrideAlwaysAllowed: true
498
+ };
499
+ await this.storage.saveAuthorityChain(enforced);
500
+ await this.audit(
501
+ "authority_updated",
502
+ actorId,
503
+ actorRoleId,
504
+ "authority",
505
+ "chain",
506
+ "Updated authority chain",
507
+ {
508
+ before: previous,
509
+ after: enforced
510
+ }
511
+ );
512
+ return enforced;
513
+ }
514
+ async getAuthority() {
515
+ return this.storage.getAuthorityChain();
516
+ }
517
+ // ── Simulation ────────────────────────────────────────────────────────
518
+ async simulate(request, actorId, actorRoleId) {
519
+ const roles = await this.storage.getRoles();
520
+ const zones = await this.storage.getZones();
521
+ const result = simulatePolicy(
522
+ request,
523
+ roles,
524
+ zones,
525
+ this.platformWorld
526
+ );
527
+ await this.audit(
528
+ "simulation_run",
529
+ actorId,
530
+ actorRoleId,
531
+ "simulation",
532
+ request.type,
533
+ `Ran ${request.type} simulation: ${result.summary.total} intents evaluated, ${result.summary.blocked} blocked, ${result.conflicts.length} conflicts`
534
+ );
535
+ return result;
536
+ }
537
+ async simulateMatrix(actorId, actorRoleId) {
538
+ const roles = await this.storage.getRoles();
539
+ const zones = await this.storage.getZones();
540
+ const result = simulateFullMatrix(roles, zones, this.platformWorld);
541
+ await this.audit(
542
+ "simulation_run",
543
+ actorId,
544
+ actorRoleId,
545
+ "simulation",
546
+ "full_matrix",
547
+ `Ran full matrix simulation: ${roles.length} roles \xD7 ${zones.length} zones`
548
+ );
549
+ return result;
550
+ }
551
+ /**
552
+ * Simulate a proposed role change and return only what would break.
553
+ * The "measure twice" before deploying.
554
+ */
555
+ async simulateRoleChange(roleId, proposedDefinitionUpdates, actorId, actorRoleId) {
556
+ const existing = await this.storage.getRole(roleId);
557
+ if (!existing) {
558
+ throw new GovernanceAdminError(`Role "${roleId}" not found`, "ROLE_NOT_FOUND");
559
+ }
560
+ const proposedRole = {
561
+ ...existing,
562
+ definition: { ...existing.definition, ...proposedDefinitionUpdates }
563
+ };
564
+ return this.simulate(
565
+ {
566
+ type: "role_change",
567
+ proposed: { role: proposedRole },
568
+ targetRoleId: roleId
569
+ },
570
+ actorId,
571
+ actorRoleId
572
+ );
573
+ }
574
+ /**
575
+ * Simulate a proposed zone rule change and return impact.
576
+ */
577
+ async simulateZoneChange(zoneId, proposedRuleUpdates, targetRoleId, actorId, actorRoleId) {
578
+ const existing = await this.storage.getZone(zoneId);
579
+ if (!existing) {
580
+ throw new GovernanceAdminError(`Zone "${zoneId}" not found`, "ZONE_NOT_FOUND");
581
+ }
582
+ const proposedZone = {
583
+ ...existing,
584
+ rules: {
585
+ ...existing.rules,
586
+ ...proposedRuleUpdates,
587
+ customRules: proposedRuleUpdates.customRules ?? existing.rules.customRules
588
+ }
589
+ };
590
+ return this.simulate(
591
+ {
592
+ type: "zone_change",
593
+ proposed: { zone: proposedZone },
594
+ targetRoleId,
595
+ targetZoneId: zoneId
596
+ },
597
+ actorId,
598
+ actorRoleId
599
+ );
600
+ }
601
+ // ── Audit Log ─────────────────────────────────────────────────────────
602
+ async getAuditLog(options) {
603
+ return this.storage.getAuditLog(options);
604
+ }
605
+ };
606
+ var GovernanceAdminError = class extends Error {
607
+ code;
608
+ constructor(message, code) {
609
+ super(message);
610
+ this.name = "GovernanceAdminError";
611
+ this.code = code;
612
+ }
613
+ };
614
+
615
+ // src/admin/storage.ts
616
+ var InMemoryStorage = class {
617
+ roles = /* @__PURE__ */ new Map();
618
+ assignments = /* @__PURE__ */ new Map();
619
+ zones = /* @__PURE__ */ new Map();
620
+ authority = {
621
+ grants: [],
622
+ emergencyOverrideAlwaysAllowed: true
623
+ };
624
+ audit = [];
625
+ // ── Roles ───────────────────────────────────────────────────────────────
626
+ async getRoles() {
627
+ return Array.from(this.roles.values());
628
+ }
629
+ async getRole(id) {
630
+ return this.roles.get(id) ?? null;
631
+ }
632
+ async saveRole(role) {
633
+ this.roles.set(role.id, role);
634
+ }
635
+ async deleteRole(id) {
636
+ this.roles.delete(id);
637
+ }
638
+ // ── Assignments ─────────────────────────────────────────────────────────
639
+ async getAssignments() {
640
+ return Array.from(this.assignments.values());
641
+ }
642
+ async getAssignmentsByDevice(deviceId) {
643
+ return Array.from(this.assignments.values()).filter(
644
+ (a) => a.deviceId === deviceId
645
+ );
646
+ }
647
+ async getAssignmentsByRole(roleId) {
648
+ return Array.from(this.assignments.values()).filter(
649
+ (a) => a.roleId === roleId
650
+ );
651
+ }
652
+ async saveAssignment(assignment) {
653
+ this.assignments.set(assignment.id, assignment);
654
+ }
655
+ async deleteAssignment(id) {
656
+ this.assignments.delete(id);
657
+ }
658
+ // ── Zones ───────────────────────────────────────────────────────────────
659
+ async getZones() {
660
+ return Array.from(this.zones.values());
661
+ }
662
+ async getZone(id) {
663
+ return this.zones.get(id) ?? null;
664
+ }
665
+ async getZonesByLocation(locationId) {
666
+ return Array.from(this.zones.values()).filter(
667
+ (z) => z.locationId === locationId
668
+ );
669
+ }
670
+ async saveZone(zone) {
671
+ this.zones.set(zone.id, zone);
672
+ }
673
+ async deleteZone(id) {
674
+ this.zones.delete(id);
675
+ }
676
+ // ── Authority ───────────────────────────────────────────────────────────
677
+ async getAuthorityChain() {
678
+ return this.authority;
679
+ }
680
+ async saveAuthorityChain(chain) {
681
+ this.authority = chain;
682
+ }
683
+ // ── Audit ───────────────────────────────────────────────────────────────
684
+ async getAuditLog(options) {
685
+ let entries = this.audit;
686
+ if (options?.action) {
687
+ entries = entries.filter((e) => e.action === options.action);
688
+ }
689
+ const offset = options?.offset ?? 0;
690
+ const limit = options?.limit ?? 100;
691
+ return entries.slice(offset, offset + limit);
692
+ }
693
+ async appendAudit(entry) {
694
+ this.audit.push(entry);
695
+ }
696
+ };
697
+ export {
698
+ GovernanceAdmin,
699
+ GovernanceAdminError,
700
+ InMemoryStorage,
701
+ simulateFullMatrix,
702
+ simulatePolicy
703
+ };