@edictum/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,3774 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/errors.ts
34
+ var EdictumDenied, EdictumConfigError, EdictumToolError;
35
+ var init_errors = __esm({
36
+ "src/errors.ts"() {
37
+ "use strict";
38
+ EdictumDenied = class extends Error {
39
+ reason;
40
+ decisionSource;
41
+ decisionName;
42
+ constructor(reason, decisionSource = null, decisionName = null) {
43
+ super(reason);
44
+ this.name = "EdictumDenied";
45
+ this.reason = reason;
46
+ this.decisionSource = decisionSource;
47
+ this.decisionName = decisionName;
48
+ }
49
+ };
50
+ EdictumConfigError = class extends Error {
51
+ constructor(message) {
52
+ super(message);
53
+ this.name = "EdictumConfigError";
54
+ }
55
+ };
56
+ EdictumToolError = class extends Error {
57
+ constructor(message) {
58
+ super(message);
59
+ this.name = "EdictumToolError";
60
+ }
61
+ };
62
+ }
63
+ });
64
+
65
+ // src/envelope.ts
66
+ function createPrincipal(partial = {}) {
67
+ const p = {
68
+ userId: partial.userId ?? null,
69
+ serviceId: partial.serviceId ?? null,
70
+ orgId: partial.orgId ?? null,
71
+ role: partial.role ?? null,
72
+ ticketRef: partial.ticketRef ?? null,
73
+ claims: partial.claims ?? {}
74
+ };
75
+ return deepFreeze(p);
76
+ }
77
+ function _validateToolName(toolName) {
78
+ if (!toolName) {
79
+ throw new EdictumConfigError(`Invalid tool_name: ${JSON.stringify(toolName)}`);
80
+ }
81
+ for (let i = 0; i < toolName.length; i++) {
82
+ const code = toolName.charCodeAt(i);
83
+ const ch = toolName[i];
84
+ if (code < 32 || code === 127 || ch === "/" || ch === "\\") {
85
+ throw new EdictumConfigError(`Invalid tool_name: ${JSON.stringify(toolName)}`);
86
+ }
87
+ }
88
+ }
89
+ function deepFreeze(obj) {
90
+ if (obj === null || obj === void 0 || typeof obj !== "object") {
91
+ return obj;
92
+ }
93
+ if (obj instanceof Date) {
94
+ return obj;
95
+ }
96
+ if (obj instanceof RegExp) {
97
+ return obj;
98
+ }
99
+ Object.freeze(obj);
100
+ for (const value of Object.values(obj)) {
101
+ if (value !== null && value !== void 0 && typeof value === "object" && !Object.isFrozen(value)) {
102
+ deepFreeze(value);
103
+ }
104
+ }
105
+ return obj;
106
+ }
107
+ function safeDeepCopy(value) {
108
+ try {
109
+ return structuredClone(value);
110
+ } catch {
111
+ return JSON.parse(JSON.stringify(value));
112
+ }
113
+ }
114
+ function createEnvelope(toolName, toolInput, options = {}) {
115
+ _validateToolName(toolName);
116
+ const safeArgs = safeDeepCopy(toolInput);
117
+ const safeMetadata = options.metadata ? safeDeepCopy(options.metadata) : {};
118
+ let safePrincipal = null;
119
+ if (options.principal != null) {
120
+ const p = options.principal;
121
+ safePrincipal = createPrincipal({
122
+ userId: p.userId,
123
+ serviceId: p.serviceId,
124
+ orgId: p.orgId,
125
+ role: p.role,
126
+ ticketRef: p.ticketRef,
127
+ claims: p.claims ? safeDeepCopy(p.claims) : {}
128
+ });
129
+ }
130
+ const registry = options.registry ?? null;
131
+ let sideEffect = options.sideEffect ?? SideEffect.IRREVERSIBLE;
132
+ let idempotent = options.idempotent ?? false;
133
+ let bashCommand = null;
134
+ let filePath = null;
135
+ if (registry) {
136
+ const [regEffect, regIdempotent] = registry.classify(toolName, safeArgs);
137
+ if (options.sideEffect == null) sideEffect = regEffect;
138
+ if (options.idempotent == null) idempotent = regIdempotent;
139
+ }
140
+ if (toolName === "Bash") {
141
+ bashCommand = safeArgs.command ?? "";
142
+ if (options.sideEffect == null) {
143
+ sideEffect = BashClassifier.classify(bashCommand);
144
+ }
145
+ } else if (toolName === "Read" || toolName === "Glob" || toolName === "Grep") {
146
+ filePath = safeArgs.file_path ?? safeArgs.filePath ?? safeArgs.path ?? null;
147
+ } else if (toolName === "Write" || toolName === "Edit") {
148
+ filePath = safeArgs.file_path ?? safeArgs.filePath ?? safeArgs.path ?? null;
149
+ }
150
+ const envelope = {
151
+ toolName,
152
+ args: safeArgs,
153
+ callId: options.callId ?? (0, import_node_crypto.randomUUID)(),
154
+ runId: options.runId ?? "",
155
+ callIndex: options.callIndex ?? 0,
156
+ parentCallId: options.parentCallId ?? null,
157
+ sideEffect,
158
+ idempotent,
159
+ environment: options.environment ?? "production",
160
+ timestamp: options.timestamp ?? /* @__PURE__ */ new Date(),
161
+ caller: options.caller ?? "",
162
+ toolUseId: options.toolUseId ?? null,
163
+ principal: safePrincipal,
164
+ bashCommand,
165
+ filePath,
166
+ metadata: safeMetadata
167
+ };
168
+ return deepFreeze(envelope);
169
+ }
170
+ var import_node_crypto, SideEffect, ToolRegistry, BashClassifier;
171
+ var init_envelope = __esm({
172
+ "src/envelope.ts"() {
173
+ "use strict";
174
+ import_node_crypto = require("crypto");
175
+ init_errors();
176
+ SideEffect = {
177
+ PURE: "pure",
178
+ READ: "read",
179
+ WRITE: "write",
180
+ IRREVERSIBLE: "irreversible"
181
+ };
182
+ ToolRegistry = class {
183
+ _tools = /* @__PURE__ */ new Map();
184
+ register(name, sideEffect = SideEffect.WRITE, idempotent = false) {
185
+ this._tools.set(name, { name, sideEffect, idempotent });
186
+ }
187
+ classify(toolName, _args) {
188
+ const cfg = this._tools.get(toolName);
189
+ if (cfg) {
190
+ return [cfg.sideEffect, cfg.idempotent];
191
+ }
192
+ return [SideEffect.IRREVERSIBLE, false];
193
+ }
194
+ };
195
+ BashClassifier = {
196
+ READ_ALLOWLIST: [
197
+ "ls",
198
+ "cat",
199
+ "head",
200
+ "tail",
201
+ "wc",
202
+ "find",
203
+ "grep",
204
+ "rg",
205
+ "git status",
206
+ "git log",
207
+ "git diff",
208
+ "git show",
209
+ "git branch",
210
+ "git remote",
211
+ "git tag",
212
+ "echo",
213
+ "pwd",
214
+ "whoami",
215
+ "date",
216
+ "which",
217
+ "file",
218
+ "stat",
219
+ "du",
220
+ "df",
221
+ "tree",
222
+ "less",
223
+ "more"
224
+ ],
225
+ SHELL_OPERATORS: [
226
+ "\n",
227
+ "\r",
228
+ "<(",
229
+ "<<",
230
+ "$",
231
+ "${",
232
+ ">",
233
+ ">>",
234
+ "|",
235
+ ";",
236
+ "&&",
237
+ "||",
238
+ "$(",
239
+ "`",
240
+ "#{"
241
+ ],
242
+ classify(command) {
243
+ const stripped = command.trim();
244
+ if (!stripped) {
245
+ return SideEffect.READ;
246
+ }
247
+ for (const op of BashClassifier.SHELL_OPERATORS) {
248
+ if (stripped.includes(op)) {
249
+ return SideEffect.IRREVERSIBLE;
250
+ }
251
+ }
252
+ for (const allowed of BashClassifier.READ_ALLOWLIST) {
253
+ if (stripped === allowed || stripped.startsWith(allowed + " ")) {
254
+ return SideEffect.READ;
255
+ }
256
+ }
257
+ return SideEffect.IRREVERSIBLE;
258
+ }
259
+ };
260
+ }
261
+ });
262
+
263
+ // src/contracts.ts
264
+ var Verdict;
265
+ var init_contracts = __esm({
266
+ "src/contracts.ts"() {
267
+ "use strict";
268
+ Verdict = {
269
+ /**
270
+ * Contract passed — tool call is acceptable.
271
+ */
272
+ pass_() {
273
+ return Object.freeze({ passed: true, message: null, metadata: Object.freeze({}) });
274
+ },
275
+ /**
276
+ * Contract failed with an actionable message (truncated to 500 chars).
277
+ *
278
+ * Make it SPECIFIC and INSTRUCTIVE — the agent uses it to self-correct.
279
+ */
280
+ fail(message, metadata = {}) {
281
+ const truncated = message.length > 500 ? message.slice(0, 497) + "..." : message;
282
+ return Object.freeze({
283
+ passed: false,
284
+ message: truncated,
285
+ metadata: Object.freeze({ ...metadata })
286
+ });
287
+ }
288
+ };
289
+ }
290
+ });
291
+
292
+ // src/hooks.ts
293
+ var HookResult, HookDecision;
294
+ var init_hooks = __esm({
295
+ "src/hooks.ts"() {
296
+ "use strict";
297
+ HookResult = {
298
+ ALLOW: "allow",
299
+ DENY: "deny"
300
+ };
301
+ HookDecision = {
302
+ allow() {
303
+ return Object.freeze({ result: HookResult.ALLOW, reason: null });
304
+ },
305
+ deny(reason) {
306
+ const truncated = reason.length > 500 ? reason.slice(0, 497) + "..." : reason;
307
+ return Object.freeze({ result: HookResult.DENY, reason: truncated });
308
+ }
309
+ };
310
+ }
311
+ });
312
+
313
+ // src/session.ts
314
+ function hasBatchGet(backend) {
315
+ return "batchGet" in backend;
316
+ }
317
+ function _validateStorageKeyComponent(value, label) {
318
+ if (!value) {
319
+ throw new EdictumConfigError(`Invalid ${label}: ${JSON.stringify(value)}`);
320
+ }
321
+ if (value.length > MAX_ID_LENGTH) {
322
+ throw new EdictumConfigError(`Invalid ${label}: exceeds ${MAX_ID_LENGTH} characters`);
323
+ }
324
+ for (let i = 0; i < value.length; i++) {
325
+ const code = value.charCodeAt(i);
326
+ if (code < 32 || code === 127) {
327
+ throw new EdictumConfigError(`Invalid ${label}: ${JSON.stringify(value)}`);
328
+ }
329
+ }
330
+ }
331
+ var MAX_ID_LENGTH, Session;
332
+ var init_session = __esm({
333
+ "src/session.ts"() {
334
+ "use strict";
335
+ init_errors();
336
+ MAX_ID_LENGTH = 1e4;
337
+ Session = class {
338
+ _sid;
339
+ _backend;
340
+ constructor(sessionId, backend) {
341
+ _validateStorageKeyComponent(sessionId, "session_id");
342
+ this._sid = sessionId;
343
+ this._backend = backend;
344
+ }
345
+ get sessionId() {
346
+ return this._sid;
347
+ }
348
+ /** Increment attempt counter. Called in PreToolUse (before governance). */
349
+ async incrementAttempts() {
350
+ return await this._backend.increment(`s:${this._sid}:attempts`);
351
+ }
352
+ async attemptCount() {
353
+ return Number(await this._backend.get(`s:${this._sid}:attempts`) ?? 0);
354
+ }
355
+ /** Record a tool execution. Called in PostToolUse. */
356
+ async recordExecution(toolName, success) {
357
+ _validateStorageKeyComponent(toolName, "tool_name");
358
+ await this._backend.increment(`s:${this._sid}:execs`);
359
+ await this._backend.increment(`s:${this._sid}:tool:${toolName}`);
360
+ if (success) {
361
+ await this._backend.delete(`s:${this._sid}:consec_fail`);
362
+ } else {
363
+ await this._backend.increment(`s:${this._sid}:consec_fail`);
364
+ }
365
+ }
366
+ async executionCount() {
367
+ return Number(await this._backend.get(`s:${this._sid}:execs`) ?? 0);
368
+ }
369
+ async toolExecutionCount(tool) {
370
+ _validateStorageKeyComponent(tool, "tool_name");
371
+ return Number(
372
+ await this._backend.get(`s:${this._sid}:tool:${tool}`) ?? 0
373
+ );
374
+ }
375
+ async consecutiveFailures() {
376
+ return Number(
377
+ await this._backend.get(`s:${this._sid}:consec_fail`) ?? 0
378
+ );
379
+ }
380
+ /**
381
+ * Pre-fetch multiple session counters in a single backend call.
382
+ *
383
+ * Returns a record with keys: "attempts", "execs", and optionally
384
+ * "tool:{name}" if includeTool is provided.
385
+ *
386
+ * Uses batchGet() on the backend when available (single HTTP round
387
+ * trip for ServerBackend). Falls back to individual get() calls for
388
+ * backends without batchGet support.
389
+ */
390
+ async batchGetCounters(options) {
391
+ const keys = [
392
+ `s:${this._sid}:attempts`,
393
+ `s:${this._sid}:execs`
394
+ ];
395
+ const keyLabels = ["attempts", "execs"];
396
+ if (options?.includeTool != null) {
397
+ _validateStorageKeyComponent(options.includeTool, "tool_name");
398
+ keys.push(`s:${this._sid}:tool:${options.includeTool}`);
399
+ keyLabels.push(`tool:${options.includeTool}`);
400
+ }
401
+ let raw;
402
+ if (hasBatchGet(this._backend)) {
403
+ raw = await this._backend.batchGet(keys);
404
+ } else {
405
+ raw = {};
406
+ for (const key of keys) {
407
+ raw[key] = await this._backend.get(key);
408
+ }
409
+ }
410
+ const result = {};
411
+ for (let i = 0; i < keys.length; i++) {
412
+ const label = keyLabels[i] ?? "";
413
+ const key = keys[i] ?? "";
414
+ result[label] = Number(raw[key] ?? 0);
415
+ }
416
+ return result;
417
+ }
418
+ };
419
+ }
420
+ });
421
+
422
+ // src/redaction.ts
423
+ var RedactionPolicy;
424
+ var init_redaction = __esm({
425
+ "src/redaction.ts"() {
426
+ "use strict";
427
+ init_errors();
428
+ RedactionPolicy = class _RedactionPolicy {
429
+ static DEFAULT_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
430
+ "password",
431
+ "secret",
432
+ "token",
433
+ "api_key",
434
+ "apikey",
435
+ "api-key",
436
+ "authorization",
437
+ "auth",
438
+ "credentials",
439
+ "private_key",
440
+ "privatekey",
441
+ "access_token",
442
+ "refresh_token",
443
+ "client_secret",
444
+ "connection_string",
445
+ "database_url",
446
+ "db_password",
447
+ "ssh_key",
448
+ "passphrase"
449
+ ]);
450
+ static BASH_REDACTION_PATTERNS = [
451
+ [
452
+ String.raw`(export\s+\w*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)\w*=)\S+`,
453
+ "$1[REDACTED]"
454
+ ],
455
+ [String.raw`(-p\s*|--password[= ])\S+`, "$1[REDACTED]"],
456
+ [String.raw`(://\w+:)\S+(@)`, "$1[REDACTED]$2"]
457
+ ];
458
+ static SECRET_VALUE_PATTERNS = [
459
+ String.raw`^(sk-[a-zA-Z0-9]{20,})`,
460
+ String.raw`^(AKIA[A-Z0-9]{16})`,
461
+ String.raw`^(eyJ[a-zA-Z0-9_-]{20,}\.)`,
462
+ String.raw`^(ghp_[a-zA-Z0-9]{36})`,
463
+ String.raw`^(xox[bpas]-[a-zA-Z0-9-]{10,})`
464
+ ];
465
+ static MAX_PAYLOAD_SIZE = 32768;
466
+ static MAX_REGEX_INPUT = 1e4;
467
+ static MAX_PATTERN_LENGTH = 1e4;
468
+ _keys;
469
+ _patterns;
470
+ _compiledPatterns;
471
+ _compiledSecretPatterns;
472
+ _detectValues;
473
+ constructor(sensitiveKeys, customPatterns, detectSecretValues = true) {
474
+ const baseKeys = sensitiveKeys ? /* @__PURE__ */ new Set([..._RedactionPolicy.DEFAULT_SENSITIVE_KEYS, ...sensitiveKeys]) : new Set(_RedactionPolicy.DEFAULT_SENSITIVE_KEYS);
475
+ this._keys = new Set([...baseKeys].map((k) => k.toLowerCase()));
476
+ if (customPatterns) {
477
+ for (const [pattern] of customPatterns) {
478
+ if (pattern.length > _RedactionPolicy.MAX_PATTERN_LENGTH) {
479
+ throw new EdictumConfigError(
480
+ `Custom redaction pattern exceeds ${_RedactionPolicy.MAX_PATTERN_LENGTH} characters`
481
+ );
482
+ }
483
+ }
484
+ }
485
+ this._patterns = [
486
+ ...customPatterns ?? [],
487
+ ..._RedactionPolicy.BASH_REDACTION_PATTERNS
488
+ ];
489
+ this._compiledPatterns = this._patterns.map(
490
+ ([pattern, replacement]) => [new RegExp(pattern, "g"), replacement]
491
+ );
492
+ this._compiledSecretPatterns = _RedactionPolicy.SECRET_VALUE_PATTERNS.map(
493
+ (p) => new RegExp(p)
494
+ );
495
+ this._detectValues = detectSecretValues;
496
+ }
497
+ /** Recursively redact sensitive data from tool arguments. */
498
+ redactArgs(args) {
499
+ if (args !== null && typeof args === "object" && !Array.isArray(args)) {
500
+ const result = {};
501
+ for (const [key, value] of Object.entries(
502
+ args
503
+ )) {
504
+ result[key] = this._isSensitiveKey(key) ? "[REDACTED]" : this.redactArgs(value);
505
+ }
506
+ return result;
507
+ }
508
+ if (Array.isArray(args)) {
509
+ return args.map((item) => this.redactArgs(item));
510
+ }
511
+ if (typeof args === "string") {
512
+ if (this._detectValues && this._looksLikeSecret(args)) {
513
+ return "[REDACTED]";
514
+ }
515
+ if (args.length > 1e3) {
516
+ return args.slice(0, 997) + "...";
517
+ }
518
+ return args;
519
+ }
520
+ return args;
521
+ }
522
+ /** Check if a key name indicates sensitive data. */
523
+ _isSensitiveKey(key) {
524
+ const k = key.toLowerCase();
525
+ if (this._keys.has(k)) return true;
526
+ const normalized = key.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
527
+ if (normalized !== k && this._keys.has(normalized)) return true;
528
+ const parts = normalized.split(/[_\-]/);
529
+ return parts.some(
530
+ (part) => part === "token" || part === "key" || part === "secret" || part === "password" || part === "credential"
531
+ );
532
+ }
533
+ /** Check if a string value looks like a known secret format. */
534
+ _looksLikeSecret(value) {
535
+ const capped = value.length > _RedactionPolicy.MAX_REGEX_INPUT ? value.slice(0, _RedactionPolicy.MAX_REGEX_INPUT) : value;
536
+ for (const regex of this._compiledSecretPatterns) {
537
+ if (regex.test(capped)) {
538
+ return true;
539
+ }
540
+ }
541
+ return false;
542
+ }
543
+ /** Apply redaction patterns to a bash command string. */
544
+ redactBashCommand(command) {
545
+ const capped = command.length > _RedactionPolicy.MAX_REGEX_INPUT ? command.slice(0, _RedactionPolicy.MAX_REGEX_INPUT) : command;
546
+ let result = capped;
547
+ for (const [regex, replacement] of this._compiledPatterns) {
548
+ regex.lastIndex = 0;
549
+ result = result.replace(regex, replacement);
550
+ }
551
+ return result;
552
+ }
553
+ /** Apply redaction patterns and truncate a result string. */
554
+ redactResult(result, maxLength = 500) {
555
+ const capped = result.length > _RedactionPolicy.MAX_REGEX_INPUT ? result.slice(0, _RedactionPolicy.MAX_REGEX_INPUT) : result;
556
+ let redacted = capped;
557
+ for (const [regex, replacement] of this._compiledPatterns) {
558
+ regex.lastIndex = 0;
559
+ redacted = redacted.replace(regex, replacement);
560
+ }
561
+ if (redacted.length > maxLength) {
562
+ redacted = redacted.slice(0, maxLength - 3) + "...";
563
+ }
564
+ return redacted;
565
+ }
566
+ /** Cap total serialized size of audit payload. Returns a new object if truncated. */
567
+ capPayload(data) {
568
+ const serialized = JSON.stringify(data);
569
+ if (serialized.length > _RedactionPolicy.MAX_PAYLOAD_SIZE) {
570
+ const { resultSummary: _rs, toolArgs: _ta, ...rest } = data;
571
+ void _rs;
572
+ void _ta;
573
+ return {
574
+ ...rest,
575
+ _truncated: true,
576
+ toolArgs: { _redacted: "payload exceeded 32KB" }
577
+ };
578
+ }
579
+ return data;
580
+ }
581
+ };
582
+ }
583
+ });
584
+
585
+ // src/approval.ts
586
+ function sanitizeForTerminal(s) {
587
+ return s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/[\x00-\x1f\x7f]/g, "");
588
+ }
589
+ function createApprovalRequest(fields) {
590
+ const request = {
591
+ approvalId: fields.approvalId,
592
+ toolName: fields.toolName,
593
+ toolArgs: Object.freeze({ ...fields.toolArgs }),
594
+ message: fields.message,
595
+ timeout: fields.timeout,
596
+ timeoutEffect: fields.timeoutEffect,
597
+ principal: fields.principal !== null ? Object.freeze({ ...fields.principal }) : null,
598
+ metadata: Object.freeze({ ...fields.metadata }),
599
+ createdAt: fields.createdAt ?? /* @__PURE__ */ new Date()
600
+ };
601
+ return Object.freeze(request);
602
+ }
603
+ function createApprovalDecision(fields) {
604
+ const decision = {
605
+ approved: fields.approved,
606
+ approver: fields.approver ?? null,
607
+ reason: fields.reason ?? null,
608
+ status: fields.status ?? ApprovalStatus.PENDING,
609
+ timestamp: fields.timestamp ?? /* @__PURE__ */ new Date()
610
+ };
611
+ return Object.freeze(decision);
612
+ }
613
+ var import_node_crypto2, readline, ApprovalStatus, LocalApprovalBackend;
614
+ var init_approval = __esm({
615
+ "src/approval.ts"() {
616
+ "use strict";
617
+ import_node_crypto2 = require("crypto");
618
+ readline = __toESM(require("readline"), 1);
619
+ init_redaction();
620
+ ApprovalStatus = {
621
+ PENDING: "pending",
622
+ APPROVED: "approved",
623
+ DENIED: "denied",
624
+ TIMEOUT: "timeout"
625
+ };
626
+ LocalApprovalBackend = class {
627
+ _pending = /* @__PURE__ */ new Map();
628
+ async requestApproval(toolName, toolArgs, message, options) {
629
+ const approvalId = (0, import_node_crypto2.randomUUID)();
630
+ const request = createApprovalRequest({
631
+ approvalId,
632
+ toolName,
633
+ toolArgs,
634
+ message,
635
+ timeout: options?.timeout ?? 300,
636
+ timeoutEffect: options?.timeoutEffect ?? "deny",
637
+ principal: options?.principal ?? null,
638
+ metadata: options?.metadata ?? {}
639
+ });
640
+ this._pending.set(approvalId, request);
641
+ const redaction = new RedactionPolicy();
642
+ const safeArgs = redaction.redactArgs(toolArgs);
643
+ process.stdout.write(`[APPROVAL REQUIRED] ${sanitizeForTerminal(message)}
644
+ `);
645
+ process.stdout.write(` Tool: ${sanitizeForTerminal(toolName)}
646
+ `);
647
+ process.stdout.write(` Args: ${sanitizeForTerminal(JSON.stringify(safeArgs))}
648
+ `);
649
+ process.stdout.write(` ID: ${approvalId}
650
+ `);
651
+ return request;
652
+ }
653
+ async waitForDecision(approvalId, timeout) {
654
+ const request = this._pending.get(approvalId);
655
+ const effectiveTimeout = timeout ?? (request ? request.timeout : 300);
656
+ try {
657
+ const response = await this._readStdin(approvalId, effectiveTimeout);
658
+ const approved = ["y", "yes", "approve"].includes(
659
+ response.trim().toLowerCase()
660
+ );
661
+ const status = approved ? ApprovalStatus.APPROVED : ApprovalStatus.DENIED;
662
+ return createApprovalDecision({
663
+ approved,
664
+ approver: "local",
665
+ status
666
+ });
667
+ } catch {
668
+ const timeoutEffect = request ? request.timeoutEffect : "deny";
669
+ const approved = timeoutEffect === "allow";
670
+ return createApprovalDecision({
671
+ approved,
672
+ status: ApprovalStatus.TIMEOUT
673
+ });
674
+ }
675
+ }
676
+ /** Read a single line from stdin with a timeout. */
677
+ _readStdin(approvalId, timeoutSeconds) {
678
+ return new Promise((resolve, reject) => {
679
+ const rl = readline.createInterface({
680
+ input: process.stdin,
681
+ output: process.stdout
682
+ });
683
+ const timer = setTimeout(() => {
684
+ rl.close();
685
+ reject(new Error("Approval timed out"));
686
+ }, timeoutSeconds * 1e3);
687
+ rl.question(`Approve? [y/N] (id: ${approvalId}): `, (answer) => {
688
+ clearTimeout(timer);
689
+ rl.close();
690
+ resolve(answer);
691
+ });
692
+ });
693
+ }
694
+ };
695
+ }
696
+ });
697
+
698
+ // src/audit.ts
699
+ function createAuditEvent(f = {}) {
700
+ return {
701
+ schemaVersion: f.schemaVersion ?? "0.3.0",
702
+ timestamp: f.timestamp ?? /* @__PURE__ */ new Date(),
703
+ runId: f.runId ?? "",
704
+ callId: f.callId ?? "",
705
+ callIndex: f.callIndex ?? 0,
706
+ parentCallId: f.parentCallId ?? null,
707
+ toolName: f.toolName ?? "",
708
+ toolArgs: f.toolArgs ?? {},
709
+ sideEffect: f.sideEffect ?? "",
710
+ environment: f.environment ?? "",
711
+ principal: f.principal ?? null,
712
+ action: f.action ?? AuditAction.CALL_DENIED,
713
+ decisionSource: f.decisionSource ?? null,
714
+ decisionName: f.decisionName ?? null,
715
+ reason: f.reason ?? null,
716
+ hooksEvaluated: f.hooksEvaluated ?? [],
717
+ contractsEvaluated: f.contractsEvaluated ?? [],
718
+ toolSuccess: f.toolSuccess ?? null,
719
+ postconditionsPassed: f.postconditionsPassed ?? null,
720
+ durationMs: f.durationMs ?? 0,
721
+ error: f.error ?? null,
722
+ resultSummary: f.resultSummary ?? null,
723
+ sessionAttemptCount: f.sessionAttemptCount ?? 0,
724
+ sessionExecutionCount: f.sessionExecutionCount ?? 0,
725
+ mode: f.mode ?? "enforce",
726
+ policyVersion: f.policyVersion ?? null,
727
+ policyError: f.policyError ?? false
728
+ };
729
+ }
730
+ function _toPlain(event) {
731
+ const { timestamp, ...rest } = event;
732
+ return { ...rest, timestamp: timestamp.toISOString() };
733
+ }
734
+ var import_promises, AuditAction, MarkEvictedError, CompositeSink, StdoutAuditSink, FileAuditSink, CollectingAuditSink;
735
+ var init_audit = __esm({
736
+ "src/audit.ts"() {
737
+ "use strict";
738
+ import_promises = require("fs/promises");
739
+ init_redaction();
740
+ AuditAction = {
741
+ CALL_DENIED: "call_denied",
742
+ CALL_WOULD_DENY: "call_would_deny",
743
+ CALL_ALLOWED: "call_allowed",
744
+ CALL_EXECUTED: "call_executed",
745
+ CALL_FAILED: "call_failed",
746
+ POSTCONDITION_WARNING: "postcondition_warning",
747
+ CALL_APPROVAL_REQUESTED: "call_approval_requested",
748
+ CALL_APPROVAL_GRANTED: "call_approval_granted",
749
+ CALL_APPROVAL_DENIED: "call_approval_denied",
750
+ CALL_APPROVAL_TIMEOUT: "call_approval_timeout"
751
+ };
752
+ MarkEvictedError = class extends Error {
753
+ constructor(message) {
754
+ super(message);
755
+ this.name = "MarkEvictedError";
756
+ }
757
+ };
758
+ CompositeSink = class {
759
+ _sinks;
760
+ constructor(sinks) {
761
+ if (sinks.length === 0) throw new Error("CompositeSink requires at least one sink");
762
+ this._sinks = [...sinks];
763
+ }
764
+ get sinks() {
765
+ return [...this._sinks];
766
+ }
767
+ async emit(event) {
768
+ const errors = [];
769
+ for (const sink of this._sinks) {
770
+ try {
771
+ await sink.emit(event);
772
+ } catch (err) {
773
+ errors.push(err instanceof Error ? err : new Error(String(err)));
774
+ }
775
+ }
776
+ if (errors.length > 0) {
777
+ throw new AggregateError(errors, "CompositeSink: one or more sinks failed");
778
+ }
779
+ }
780
+ };
781
+ StdoutAuditSink = class {
782
+ _redaction;
783
+ constructor(redaction) {
784
+ this._redaction = redaction ?? new RedactionPolicy();
785
+ }
786
+ async emit(event) {
787
+ process.stdout.write(JSON.stringify(this._redaction.capPayload(_toPlain(event))) + "\n");
788
+ }
789
+ };
790
+ FileAuditSink = class {
791
+ _path;
792
+ _redaction;
793
+ constructor(path, redaction) {
794
+ this._path = path;
795
+ this._redaction = redaction ?? new RedactionPolicy();
796
+ }
797
+ async emit(event) {
798
+ const data = this._redaction.capPayload(_toPlain(event));
799
+ await (0, import_promises.appendFile)(this._path, JSON.stringify(data) + "\n", "utf-8");
800
+ }
801
+ };
802
+ CollectingAuditSink = class {
803
+ _events = [];
804
+ _maxEvents;
805
+ _totalEmitted = 0;
806
+ constructor(maxEvents = 5e4) {
807
+ if (maxEvents < 1) throw new Error(`max_events must be >= 1, got ${maxEvents}`);
808
+ this._maxEvents = maxEvents;
809
+ }
810
+ async emit(event) {
811
+ this._events.push(event);
812
+ this._totalEmitted += 1;
813
+ if (this._events.length > this._maxEvents) this._events = this._events.slice(-this._maxEvents);
814
+ }
815
+ get events() {
816
+ return [...this._events];
817
+ }
818
+ mark() {
819
+ return this._totalEmitted;
820
+ }
821
+ sinceMark(m) {
822
+ if (m > this._totalEmitted) {
823
+ throw new Error(`Mark ${m} is ahead of total emitted (${this._totalEmitted})`);
824
+ }
825
+ const evictedCount = this._totalEmitted - this._events.length;
826
+ if (m < evictedCount) {
827
+ throw new MarkEvictedError(
828
+ `Mark ${m} references evicted events (buffer starts at ${evictedCount}, max_events=${this._maxEvents})`
829
+ );
830
+ }
831
+ return [...this._events.slice(m - evictedCount)];
832
+ }
833
+ last() {
834
+ if (this._events.length === 0) throw new Error("No events collected");
835
+ const last = this._events[this._events.length - 1];
836
+ if (!last) throw new Error("No events collected");
837
+ return last;
838
+ }
839
+ filter(action) {
840
+ return this._events.filter((e) => e.action === action);
841
+ }
842
+ clear() {
843
+ this._events = [];
844
+ }
845
+ };
846
+ }
847
+ });
848
+
849
+ // src/evaluation.ts
850
+ function createContractResult(fields) {
851
+ return Object.freeze({
852
+ contractId: fields.contractId,
853
+ contractType: fields.contractType,
854
+ passed: fields.passed,
855
+ message: fields.message ?? null,
856
+ tags: Object.freeze([...fields.tags ?? []]),
857
+ observed: fields.observed ?? false,
858
+ effect: fields.effect ?? "warn",
859
+ policyError: fields.policyError ?? false
860
+ });
861
+ }
862
+ function createEvaluationResult(fields) {
863
+ return Object.freeze({
864
+ verdict: fields.verdict,
865
+ toolName: fields.toolName,
866
+ contracts: Object.freeze([...fields.contracts ?? []]),
867
+ denyReasons: Object.freeze([...fields.denyReasons ?? []]),
868
+ warnReasons: Object.freeze([...fields.warnReasons ?? []]),
869
+ contractsEvaluated: fields.contractsEvaluated ?? 0,
870
+ policyError: fields.policyError ?? false
871
+ });
872
+ }
873
+ var init_evaluation = __esm({
874
+ "src/evaluation.ts"() {
875
+ "use strict";
876
+ }
877
+ });
878
+
879
+ // src/pipeline.ts
880
+ function createPreDecision(partial) {
881
+ return {
882
+ action: partial.action,
883
+ reason: partial.reason ?? null,
884
+ decisionSource: partial.decisionSource ?? null,
885
+ decisionName: partial.decisionName ?? null,
886
+ hooksEvaluated: partial.hooksEvaluated ?? [],
887
+ contractsEvaluated: partial.contractsEvaluated ?? [],
888
+ observed: partial.observed ?? false,
889
+ policyError: partial.policyError ?? false,
890
+ observeResults: partial.observeResults ?? [],
891
+ approvalTimeout: partial.approvalTimeout ?? 300,
892
+ approvalTimeoutEffect: partial.approvalTimeoutEffect ?? "deny",
893
+ approvalMessage: partial.approvalMessage ?? null
894
+ };
895
+ }
896
+ function createPostDecision(partial) {
897
+ return {
898
+ toolSuccess: partial.toolSuccess,
899
+ postconditionsPassed: partial.postconditionsPassed ?? true,
900
+ warnings: partial.warnings ?? [],
901
+ contractsEvaluated: partial.contractsEvaluated ?? [],
902
+ policyError: partial.policyError ?? false,
903
+ redactedResponse: partial.redactedResponse ?? null,
904
+ outputSuppressed: partial.outputSuppressed ?? false
905
+ };
906
+ }
907
+ function hasPolicyError(contractsEvaluated) {
908
+ return contractsEvaluated.some((c) => {
909
+ const meta = c["metadata"];
910
+ return meta?.["policy_error"] === true;
911
+ });
912
+ }
913
+ var GovernancePipeline;
914
+ var init_pipeline = __esm({
915
+ "src/pipeline.ts"() {
916
+ "use strict";
917
+ init_contracts();
918
+ init_envelope();
919
+ init_hooks();
920
+ init_redaction();
921
+ GovernancePipeline = class {
922
+ _guard;
923
+ constructor(guard) {
924
+ this._guard = guard;
925
+ }
926
+ async preExecute(envelope, session) {
927
+ const hooksEvaluated = [];
928
+ const contractsEvaluated = [];
929
+ let hasObservedDeny = false;
930
+ let toolNameForBatch;
931
+ if (envelope.toolName in this._guard.limits.maxCallsPerTool) {
932
+ toolNameForBatch = envelope.toolName;
933
+ }
934
+ const counters = await session.batchGetCounters({
935
+ includeTool: toolNameForBatch
936
+ });
937
+ const attemptCount = counters["attempts"] ?? 0;
938
+ if (attemptCount >= this._guard.limits.maxAttempts) {
939
+ return createPreDecision({
940
+ action: "deny",
941
+ reason: `Attempt limit reached (${this._guard.limits.maxAttempts}). Agent may be stuck in a retry loop. Stop and reassess.`,
942
+ decisionSource: "attempt_limit",
943
+ decisionName: "max_attempts",
944
+ hooksEvaluated,
945
+ contractsEvaluated
946
+ });
947
+ }
948
+ for (const hookReg of this._guard.getHooks("before", envelope)) {
949
+ if (hookReg.when && !hookReg.when(envelope)) {
950
+ continue;
951
+ }
952
+ let decision;
953
+ try {
954
+ decision = await hookReg.callback(envelope);
955
+ } catch (exc) {
956
+ decision = HookDecision.deny(`Hook error: ${exc}`);
957
+ }
958
+ const hookRecord = {
959
+ name: hookReg.callback.name || "anonymous",
960
+ result: decision.result,
961
+ reason: decision.reason
962
+ };
963
+ hooksEvaluated.push(hookRecord);
964
+ if (decision.result === HookResult.DENY) {
965
+ return createPreDecision({
966
+ action: "deny",
967
+ reason: decision.reason,
968
+ decisionSource: "hook",
969
+ decisionName: hookRecord["name"],
970
+ hooksEvaluated,
971
+ contractsEvaluated,
972
+ policyError: (decision.reason ?? "").includes("Hook error:")
973
+ });
974
+ }
975
+ }
976
+ for (const contract of this._guard.getPreconditions(envelope)) {
977
+ let verdict;
978
+ try {
979
+ verdict = await contract.check(envelope);
980
+ } catch (exc) {
981
+ verdict = Verdict.fail(`Precondition error: ${exc}`, {
982
+ policy_error: true
983
+ });
984
+ }
985
+ const contractRecord = {
986
+ name: contract.name,
987
+ type: "precondition",
988
+ passed: verdict.passed,
989
+ message: verdict.message
990
+ };
991
+ if (verdict.metadata && Object.keys(verdict.metadata).length > 0) {
992
+ contractRecord["metadata"] = verdict.metadata;
993
+ }
994
+ contractsEvaluated.push(contractRecord);
995
+ if (!verdict.passed) {
996
+ if (contract.mode === "observe") {
997
+ contractRecord["observed"] = true;
998
+ hasObservedDeny = true;
999
+ continue;
1000
+ }
1001
+ const source = contract.source ?? "precondition";
1002
+ const pe2 = hasPolicyError(contractsEvaluated);
1003
+ const effect = contract.effect ?? "deny";
1004
+ if (effect === "approve") {
1005
+ return createPreDecision({
1006
+ action: "pending_approval",
1007
+ reason: verdict.message,
1008
+ decisionSource: source,
1009
+ decisionName: contract.name,
1010
+ hooksEvaluated,
1011
+ contractsEvaluated,
1012
+ policyError: pe2,
1013
+ approvalTimeout: contract.timeout ?? 300,
1014
+ approvalTimeoutEffect: contract.timeoutEffect ?? "deny",
1015
+ approvalMessage: verdict.message
1016
+ });
1017
+ }
1018
+ return createPreDecision({
1019
+ action: "deny",
1020
+ reason: verdict.message,
1021
+ decisionSource: source,
1022
+ decisionName: contract.name,
1023
+ hooksEvaluated,
1024
+ contractsEvaluated,
1025
+ policyError: pe2
1026
+ });
1027
+ }
1028
+ }
1029
+ for (const contract of this._guard.getSandboxContracts(envelope)) {
1030
+ let verdict;
1031
+ try {
1032
+ verdict = await contract.check(envelope);
1033
+ } catch (exc) {
1034
+ verdict = Verdict.fail(`Sandbox contract error: ${exc}`, {
1035
+ policy_error: true
1036
+ });
1037
+ }
1038
+ const contractRecord = {
1039
+ name: contract.name,
1040
+ type: "sandbox",
1041
+ passed: verdict.passed,
1042
+ message: verdict.message
1043
+ };
1044
+ if (verdict.metadata && Object.keys(verdict.metadata).length > 0) {
1045
+ contractRecord["metadata"] = verdict.metadata;
1046
+ }
1047
+ contractsEvaluated.push(contractRecord);
1048
+ if (!verdict.passed) {
1049
+ if (contract.mode === "observe") {
1050
+ contractRecord["observed"] = true;
1051
+ hasObservedDeny = true;
1052
+ continue;
1053
+ }
1054
+ const source = contract.source ?? "yaml_sandbox";
1055
+ const pe2 = hasPolicyError(contractsEvaluated);
1056
+ const effect = contract.effect ?? "deny";
1057
+ if (effect === "approve") {
1058
+ return createPreDecision({
1059
+ action: "pending_approval",
1060
+ reason: verdict.message,
1061
+ decisionSource: source,
1062
+ decisionName: contract.name,
1063
+ hooksEvaluated,
1064
+ contractsEvaluated,
1065
+ policyError: pe2,
1066
+ approvalTimeout: contract.timeout ?? 300,
1067
+ approvalTimeoutEffect: contract.timeoutEffect ?? "deny",
1068
+ approvalMessage: verdict.message
1069
+ });
1070
+ }
1071
+ return createPreDecision({
1072
+ action: "deny",
1073
+ reason: verdict.message,
1074
+ decisionSource: source,
1075
+ decisionName: contract.name,
1076
+ hooksEvaluated,
1077
+ contractsEvaluated,
1078
+ policyError: pe2
1079
+ });
1080
+ }
1081
+ }
1082
+ for (const contract of this._guard.getSessionContracts()) {
1083
+ let verdict;
1084
+ try {
1085
+ verdict = await contract.check(session);
1086
+ } catch (exc) {
1087
+ verdict = Verdict.fail(`Session contract error: ${exc}`, {
1088
+ policy_error: true
1089
+ });
1090
+ }
1091
+ const contractRecord = {
1092
+ name: contract.name,
1093
+ type: "session_contract",
1094
+ passed: verdict.passed,
1095
+ message: verdict.message
1096
+ };
1097
+ if (verdict.metadata && Object.keys(verdict.metadata).length > 0) {
1098
+ contractRecord["metadata"] = verdict.metadata;
1099
+ }
1100
+ contractsEvaluated.push(contractRecord);
1101
+ if (!verdict.passed) {
1102
+ const source = contract.source ?? "session_contract";
1103
+ const pe2 = hasPolicyError(contractsEvaluated);
1104
+ return createPreDecision({
1105
+ action: "deny",
1106
+ reason: verdict.message,
1107
+ decisionSource: source,
1108
+ decisionName: contract.name,
1109
+ hooksEvaluated,
1110
+ contractsEvaluated,
1111
+ policyError: pe2
1112
+ });
1113
+ }
1114
+ }
1115
+ const execCount = counters["execs"] ?? 0;
1116
+ if (execCount >= this._guard.limits.maxToolCalls) {
1117
+ return createPreDecision({
1118
+ action: "deny",
1119
+ reason: `Execution limit reached (${this._guard.limits.maxToolCalls} calls). Summarize progress and stop.`,
1120
+ decisionSource: "operation_limit",
1121
+ decisionName: "max_tool_calls",
1122
+ hooksEvaluated,
1123
+ contractsEvaluated
1124
+ });
1125
+ }
1126
+ if (envelope.toolName in this._guard.limits.maxCallsPerTool) {
1127
+ const toolKey = `tool:${envelope.toolName}`;
1128
+ const toolCount = counters[toolKey] ?? 0;
1129
+ const toolLimit = this._guard.limits.maxCallsPerTool[envelope.toolName] ?? 0;
1130
+ if (toolCount >= toolLimit) {
1131
+ return createPreDecision({
1132
+ action: "deny",
1133
+ reason: `Per-tool limit: ${envelope.toolName} called ${toolCount} times (limit: ${toolLimit}).`,
1134
+ decisionSource: "operation_limit",
1135
+ decisionName: `max_calls_per_tool:${envelope.toolName}`,
1136
+ hooksEvaluated,
1137
+ contractsEvaluated
1138
+ });
1139
+ }
1140
+ }
1141
+ const pe = hasPolicyError(contractsEvaluated);
1142
+ const observeResults = await this._evaluateObserveContracts(
1143
+ envelope,
1144
+ session
1145
+ );
1146
+ return createPreDecision({
1147
+ action: "allow",
1148
+ hooksEvaluated,
1149
+ contractsEvaluated,
1150
+ observed: hasObservedDeny,
1151
+ policyError: pe,
1152
+ observeResults
1153
+ });
1154
+ }
1155
+ async postExecute(envelope, toolResponse, toolSuccess) {
1156
+ const warnings = [];
1157
+ const contractsEvaluated = [];
1158
+ let redactedResponse = null;
1159
+ let outputSuppressed = false;
1160
+ for (const contract of this._guard.getPostconditions(envelope)) {
1161
+ let verdict;
1162
+ try {
1163
+ verdict = await contract.check(envelope, toolResponse);
1164
+ } catch (exc) {
1165
+ verdict = Verdict.fail(`Postcondition error: ${exc}`, {
1166
+ policy_error: true
1167
+ });
1168
+ }
1169
+ const contractRecord = {
1170
+ name: contract.name,
1171
+ type: "postcondition",
1172
+ passed: verdict.passed,
1173
+ message: verdict.message
1174
+ };
1175
+ if (verdict.metadata && Object.keys(verdict.metadata).length > 0) {
1176
+ contractRecord["metadata"] = verdict.metadata;
1177
+ }
1178
+ contractsEvaluated.push(contractRecord);
1179
+ if (!verdict.passed) {
1180
+ const effect = contract.effect ?? "warn";
1181
+ const contractMode = contract.mode;
1182
+ const isSafe = envelope.sideEffect === SideEffect.PURE || envelope.sideEffect === SideEffect.READ;
1183
+ if (contractMode === "observe") {
1184
+ contractRecord["observed"] = true;
1185
+ warnings.push(`\u26A0\uFE0F [observe] ${verdict.message}`);
1186
+ } else if (effect === "redact" && isSafe) {
1187
+ const patterns = contract.redactPatterns ?? [];
1188
+ const source = redactedResponse !== null ? redactedResponse : toolResponse;
1189
+ let text = source != null ? String(source) : "";
1190
+ if (patterns.length > 0) {
1191
+ for (const pat of patterns) {
1192
+ const globalPat = pat.global ? pat : new RegExp(pat.source, pat.flags + "g");
1193
+ text = text.replace(globalPat, "[REDACTED]");
1194
+ }
1195
+ } else {
1196
+ const policy = new RedactionPolicy();
1197
+ text = policy.redactResult(text, text.length + 100);
1198
+ }
1199
+ redactedResponse = text;
1200
+ warnings.push(
1201
+ `\u26A0\uFE0F Content redacted by ${contract.name}.`
1202
+ );
1203
+ } else if (effect === "deny" && isSafe) {
1204
+ redactedResponse = `[OUTPUT SUPPRESSED] ${verdict.message}`;
1205
+ outputSuppressed = true;
1206
+ warnings.push(
1207
+ `\u26A0\uFE0F Output suppressed by ${contract.name}.`
1208
+ );
1209
+ } else if ((effect === "redact" || effect === "deny") && !isSafe) {
1210
+ warnings.push(
1211
+ `\u26A0\uFE0F ${verdict.message} Tool already executed \u2014 assess before proceeding.`
1212
+ );
1213
+ } else if (isSafe) {
1214
+ warnings.push(
1215
+ `\u26A0\uFE0F ${verdict.message} Consider retrying.`
1216
+ );
1217
+ } else {
1218
+ warnings.push(
1219
+ `\u26A0\uFE0F ${verdict.message} Tool already executed \u2014 assess before proceeding.`
1220
+ );
1221
+ }
1222
+ }
1223
+ }
1224
+ for (const hookReg of this._guard.getHooks("after", envelope)) {
1225
+ if (hookReg.when && !hookReg.when(envelope)) {
1226
+ continue;
1227
+ }
1228
+ try {
1229
+ await hookReg.callback(envelope, toolResponse);
1230
+ } catch {
1231
+ }
1232
+ }
1233
+ for (const contract of this._guard.getObservePostconditions(envelope)) {
1234
+ let verdict;
1235
+ try {
1236
+ verdict = await contract.check(envelope, toolResponse);
1237
+ } catch (exc) {
1238
+ verdict = Verdict.fail(
1239
+ `Observe-mode postcondition error: ${exc}`,
1240
+ { policy_error: true }
1241
+ );
1242
+ }
1243
+ const record = {
1244
+ name: contract.name,
1245
+ type: "postcondition",
1246
+ passed: verdict.passed,
1247
+ message: verdict.message,
1248
+ observed: true,
1249
+ source: contract.source ?? "yaml_postcondition"
1250
+ };
1251
+ if (verdict.metadata && Object.keys(verdict.metadata).length > 0) {
1252
+ record["metadata"] = verdict.metadata;
1253
+ }
1254
+ contractsEvaluated.push(record);
1255
+ if (!verdict.passed) {
1256
+ warnings.push(`\u26A0\uFE0F [observe] ${verdict.message}`);
1257
+ }
1258
+ }
1259
+ const postconditionsPassed = contractsEvaluated.length > 0 ? contractsEvaluated.every(
1260
+ (c) => c["passed"] === true || c["observed"] === true
1261
+ ) : true;
1262
+ const pe = hasPolicyError(contractsEvaluated);
1263
+ return createPostDecision({
1264
+ toolSuccess,
1265
+ postconditionsPassed,
1266
+ warnings,
1267
+ contractsEvaluated,
1268
+ policyError: pe,
1269
+ redactedResponse,
1270
+ outputSuppressed
1271
+ });
1272
+ }
1273
+ /**
1274
+ * Evaluate observe-mode contracts without affecting the real decision.
1275
+ *
1276
+ * Observe-mode contracts are identified by mode === "observe" on the
1277
+ * internal contract. Results are returned as dicts for audit emission
1278
+ * but never block calls.
1279
+ */
1280
+ async _evaluateObserveContracts(envelope, session) {
1281
+ const results = [];
1282
+ for (const contract of this._guard.getObservePreconditions(
1283
+ envelope
1284
+ )) {
1285
+ let verdict;
1286
+ try {
1287
+ verdict = await contract.check(envelope);
1288
+ } catch (exc) {
1289
+ verdict = Verdict.fail(
1290
+ `Observe-mode precondition error: ${exc}`,
1291
+ { policy_error: true }
1292
+ );
1293
+ }
1294
+ results.push({
1295
+ name: contract.name,
1296
+ type: "precondition",
1297
+ passed: verdict.passed,
1298
+ message: verdict.message,
1299
+ source: contract.source ?? "yaml_precondition"
1300
+ });
1301
+ }
1302
+ for (const contract of this._guard.getObserveSandboxContracts(
1303
+ envelope
1304
+ )) {
1305
+ let verdict;
1306
+ try {
1307
+ verdict = await contract.check(envelope);
1308
+ } catch (exc) {
1309
+ verdict = Verdict.fail(
1310
+ `Observe-mode sandbox error: ${exc}`,
1311
+ { policy_error: true }
1312
+ );
1313
+ }
1314
+ results.push({
1315
+ name: contract.name,
1316
+ type: "sandbox",
1317
+ passed: verdict.passed,
1318
+ message: verdict.message,
1319
+ source: contract.source ?? "yaml_sandbox"
1320
+ });
1321
+ }
1322
+ for (const contract of this._guard.getObserveSessionContracts()) {
1323
+ let verdict;
1324
+ try {
1325
+ verdict = await contract.check(session);
1326
+ } catch (exc) {
1327
+ verdict = Verdict.fail(
1328
+ `Observe-mode session contract error: ${exc}`,
1329
+ { policy_error: true }
1330
+ );
1331
+ }
1332
+ results.push({
1333
+ name: contract.name,
1334
+ type: "session_contract",
1335
+ passed: verdict.passed,
1336
+ message: verdict.message,
1337
+ source: contract.source ?? "yaml_session"
1338
+ });
1339
+ }
1340
+ return results;
1341
+ }
1342
+ };
1343
+ }
1344
+ });
1345
+
1346
+ // src/runner.ts
1347
+ var runner_exports = {};
1348
+ __export(runner_exports, {
1349
+ defaultSuccessCheck: () => defaultSuccessCheck,
1350
+ run: () => run
1351
+ });
1352
+ function defaultSuccessCheck(_toolName, result) {
1353
+ if (result == null) {
1354
+ return true;
1355
+ }
1356
+ if (typeof result === "object" && !Array.isArray(result)) {
1357
+ const dict = result;
1358
+ if (dict["is_error"]) {
1359
+ return false;
1360
+ }
1361
+ }
1362
+ if (typeof result === "string") {
1363
+ const lower = result.slice(0, 7).toLowerCase();
1364
+ if (lower.startsWith("error:") || lower.startsWith("fatal:")) {
1365
+ return false;
1366
+ }
1367
+ }
1368
+ return true;
1369
+ }
1370
+ async function _emitRunPreAudit(guard, envelope, session, action, pre) {
1371
+ const event = createAuditEvent({
1372
+ action,
1373
+ runId: envelope.runId,
1374
+ callId: envelope.callId,
1375
+ toolName: envelope.toolName,
1376
+ toolArgs: guard.redaction.redactArgs(envelope.args),
1377
+ sideEffect: envelope.sideEffect,
1378
+ environment: envelope.environment,
1379
+ principal: envelope.principal ? { ...envelope.principal } : null,
1380
+ decisionSource: pre.decisionSource,
1381
+ decisionName: pre.decisionName,
1382
+ reason: pre.reason,
1383
+ hooksEvaluated: pre.hooksEvaluated,
1384
+ contractsEvaluated: pre.contractsEvaluated,
1385
+ sessionAttemptCount: await session.attemptCount(),
1386
+ sessionExecutionCount: await session.executionCount(),
1387
+ mode: guard.mode,
1388
+ policyVersion: guard.policyVersion,
1389
+ policyError: pre.policyError
1390
+ });
1391
+ await guard.auditSink.emit(event);
1392
+ }
1393
+ async function run(guard, toolName, args, toolCallable, options) {
1394
+ const sessionId = options?.sessionId ?? guard.sessionId;
1395
+ const session = new Session(sessionId, guard.backend);
1396
+ const pipeline = new GovernancePipeline(guard);
1397
+ const env = options?.environment ?? guard.environment;
1398
+ let principal = options?.principal ?? void 0;
1399
+ if (principal === void 0) {
1400
+ const resolved = guard._resolvePrincipal(toolName, args);
1401
+ if (resolved != null) {
1402
+ principal = resolved;
1403
+ }
1404
+ }
1405
+ const envelope = createEnvelope(toolName, args, {
1406
+ runId: sessionId,
1407
+ environment: env,
1408
+ registry: guard.toolRegistry,
1409
+ principal: principal ?? null
1410
+ });
1411
+ await session.incrementAttempts();
1412
+ try {
1413
+ const pre = await pipeline.preExecute(envelope, session);
1414
+ if (pre.action === "pending_approval") {
1415
+ if (guard._approvalBackend == null) {
1416
+ throw new EdictumDenied(
1417
+ `Approval required but no approval backend configured: ${pre.reason}`,
1418
+ pre.decisionSource,
1419
+ pre.decisionName
1420
+ );
1421
+ }
1422
+ const principalDict = envelope.principal ? { ...envelope.principal } : null;
1423
+ const approvalRequest = await guard._approvalBackend.requestApproval(
1424
+ envelope.toolName,
1425
+ envelope.args,
1426
+ pre.approvalMessage ?? pre.reason ?? "",
1427
+ {
1428
+ timeout: pre.approvalTimeout,
1429
+ timeoutEffect: pre.approvalTimeoutEffect,
1430
+ principal: principalDict
1431
+ }
1432
+ );
1433
+ await _emitRunPreAudit(
1434
+ guard,
1435
+ envelope,
1436
+ session,
1437
+ AuditAction.CALL_APPROVAL_REQUESTED,
1438
+ pre
1439
+ );
1440
+ const decision = await guard._approvalBackend.waitForDecision(
1441
+ approvalRequest.approvalId,
1442
+ pre.approvalTimeout
1443
+ );
1444
+ let approved = false;
1445
+ if (decision.status === ApprovalStatus.TIMEOUT) {
1446
+ await _emitRunPreAudit(
1447
+ guard,
1448
+ envelope,
1449
+ session,
1450
+ AuditAction.CALL_APPROVAL_TIMEOUT,
1451
+ pre
1452
+ );
1453
+ if (pre.approvalTimeoutEffect === "allow") {
1454
+ approved = true;
1455
+ }
1456
+ } else if (!decision.approved) {
1457
+ await _emitRunPreAudit(
1458
+ guard,
1459
+ envelope,
1460
+ session,
1461
+ AuditAction.CALL_APPROVAL_DENIED,
1462
+ pre
1463
+ );
1464
+ } else {
1465
+ approved = true;
1466
+ await _emitRunPreAudit(
1467
+ guard,
1468
+ envelope,
1469
+ session,
1470
+ AuditAction.CALL_APPROVAL_GRANTED,
1471
+ pre
1472
+ );
1473
+ }
1474
+ if (approved) {
1475
+ if (guard._onAllow) {
1476
+ try {
1477
+ guard._onAllow(envelope);
1478
+ } catch {
1479
+ }
1480
+ }
1481
+ } else {
1482
+ const denyReason = decision.reason ?? pre.reason ?? "";
1483
+ if (guard._onDeny) {
1484
+ try {
1485
+ guard._onDeny(envelope, denyReason, pre.decisionName);
1486
+ } catch {
1487
+ }
1488
+ }
1489
+ throw new EdictumDenied(
1490
+ decision.reason ?? pre.reason ?? "denied",
1491
+ pre.decisionSource,
1492
+ pre.decisionName
1493
+ );
1494
+ }
1495
+ }
1496
+ const realDeny = pre.action === "deny" && !pre.observed;
1497
+ if (pre.action === "pending_approval") {
1498
+ } else if (realDeny) {
1499
+ const auditAction = guard.mode === "observe" ? AuditAction.CALL_WOULD_DENY : AuditAction.CALL_DENIED;
1500
+ await _emitRunPreAudit(guard, envelope, session, auditAction, pre);
1501
+ if (guard.mode === "enforce") {
1502
+ if (guard._onDeny) {
1503
+ try {
1504
+ guard._onDeny(envelope, pre.reason ?? "", pre.decisionName);
1505
+ } catch {
1506
+ }
1507
+ }
1508
+ throw new EdictumDenied(
1509
+ pre.reason ?? "denied",
1510
+ pre.decisionSource,
1511
+ pre.decisionName
1512
+ );
1513
+ }
1514
+ } else {
1515
+ for (const cr of pre.contractsEvaluated) {
1516
+ if (cr["observed"] && !cr["passed"]) {
1517
+ const observedEvent = createAuditEvent({
1518
+ action: AuditAction.CALL_WOULD_DENY,
1519
+ runId: envelope.runId,
1520
+ callId: envelope.callId,
1521
+ toolName: envelope.toolName,
1522
+ toolArgs: guard.redaction.redactArgs(envelope.args),
1523
+ sideEffect: envelope.sideEffect,
1524
+ environment: envelope.environment,
1525
+ principal: envelope.principal ? { ...envelope.principal } : null,
1526
+ decisionSource: "precondition",
1527
+ decisionName: cr["name"],
1528
+ reason: cr["message"],
1529
+ mode: "observe",
1530
+ policyVersion: guard.policyVersion,
1531
+ policyError: pre.policyError
1532
+ });
1533
+ await guard.auditSink.emit(observedEvent);
1534
+ }
1535
+ }
1536
+ await _emitRunPreAudit(
1537
+ guard,
1538
+ envelope,
1539
+ session,
1540
+ AuditAction.CALL_ALLOWED,
1541
+ pre
1542
+ );
1543
+ if (guard._onAllow) {
1544
+ try {
1545
+ guard._onAllow(envelope);
1546
+ } catch {
1547
+ }
1548
+ }
1549
+ }
1550
+ for (const sr of pre.observeResults) {
1551
+ const observeAction = sr["passed"] ? AuditAction.CALL_ALLOWED : AuditAction.CALL_WOULD_DENY;
1552
+ const observeEvent = createAuditEvent({
1553
+ action: observeAction,
1554
+ runId: envelope.runId,
1555
+ callId: envelope.callId,
1556
+ toolName: envelope.toolName,
1557
+ toolArgs: guard.redaction.redactArgs(envelope.args),
1558
+ sideEffect: envelope.sideEffect,
1559
+ environment: envelope.environment,
1560
+ principal: envelope.principal ? { ...envelope.principal } : null,
1561
+ decisionSource: sr["source"],
1562
+ decisionName: sr["name"],
1563
+ reason: sr["message"],
1564
+ mode: "observe",
1565
+ policyVersion: guard.policyVersion
1566
+ });
1567
+ await guard.auditSink.emit(observeEvent);
1568
+ }
1569
+ let result;
1570
+ let toolSuccess;
1571
+ try {
1572
+ result = toolCallable(envelope.args);
1573
+ if (result != null && typeof result === "object" && typeof result.then === "function") {
1574
+ result = await result;
1575
+ }
1576
+ if (guard._successCheck) {
1577
+ toolSuccess = guard._successCheck(toolName, result);
1578
+ } else {
1579
+ toolSuccess = defaultSuccessCheck(toolName, result);
1580
+ }
1581
+ } catch (e) {
1582
+ result = String(e);
1583
+ toolSuccess = false;
1584
+ }
1585
+ const post = await pipeline.postExecute(envelope, result, toolSuccess);
1586
+ await session.recordExecution(toolName, toolSuccess);
1587
+ const postAction = toolSuccess ? AuditAction.CALL_EXECUTED : AuditAction.CALL_FAILED;
1588
+ const postEvent = createAuditEvent({
1589
+ action: postAction,
1590
+ runId: envelope.runId,
1591
+ callId: envelope.callId,
1592
+ toolName: envelope.toolName,
1593
+ toolArgs: guard.redaction.redactArgs(envelope.args),
1594
+ sideEffect: envelope.sideEffect,
1595
+ environment: envelope.environment,
1596
+ principal: envelope.principal ? { ...envelope.principal } : null,
1597
+ toolSuccess,
1598
+ postconditionsPassed: post.postconditionsPassed,
1599
+ contractsEvaluated: post.contractsEvaluated,
1600
+ sessionAttemptCount: await session.attemptCount(),
1601
+ sessionExecutionCount: await session.executionCount(),
1602
+ mode: guard.mode,
1603
+ policyVersion: guard.policyVersion,
1604
+ policyError: post.policyError
1605
+ });
1606
+ await guard.auditSink.emit(postEvent);
1607
+ if (!toolSuccess) {
1608
+ throw new EdictumToolError(String(result));
1609
+ }
1610
+ return post.redactedResponse != null ? post.redactedResponse : result;
1611
+ } finally {
1612
+ }
1613
+ }
1614
+ var init_runner = __esm({
1615
+ "src/runner.ts"() {
1616
+ "use strict";
1617
+ init_approval();
1618
+ init_audit();
1619
+ init_envelope();
1620
+ init_errors();
1621
+ init_pipeline();
1622
+ init_session();
1623
+ }
1624
+ });
1625
+
1626
+ // src/dry-run.ts
1627
+ var dry_run_exports = {};
1628
+ __export(dry_run_exports, {
1629
+ evaluate: () => evaluate,
1630
+ evaluateBatch: () => evaluateBatch
1631
+ });
1632
+ function safeTags(metadata) {
1633
+ if (!metadata) return [];
1634
+ const raw = metadata["tags"];
1635
+ if (!Array.isArray(raw)) return [];
1636
+ return raw.filter((t) => typeof t === "string");
1637
+ }
1638
+ async function evaluate(guard, toolName, args, options) {
1639
+ const env = options?.environment ?? guard.environment;
1640
+ const envelope = createEnvelope(toolName, args, {
1641
+ environment: env,
1642
+ principal: options?.principal ?? null,
1643
+ registry: guard.toolRegistry
1644
+ });
1645
+ const contracts = [];
1646
+ const denyReasons = [];
1647
+ const warnReasons = [];
1648
+ for (const contract of guard.getPreconditions(envelope)) {
1649
+ const contractId = contract.name ?? "unknown";
1650
+ let verdict;
1651
+ try {
1652
+ verdict = await contract.check(envelope);
1653
+ } catch (exc) {
1654
+ const contractResult2 = createContractResult({
1655
+ contractId,
1656
+ contractType: "precondition",
1657
+ passed: false,
1658
+ message: `Precondition error: ${exc}`,
1659
+ policyError: true
1660
+ });
1661
+ contracts.push(contractResult2);
1662
+ denyReasons.push(contractResult2.message ?? "");
1663
+ continue;
1664
+ }
1665
+ const tags = safeTags(verdict.metadata);
1666
+ const isObserved = contract.mode === "observe" && !verdict.passed;
1667
+ const pe = verdict.metadata ? verdict.metadata["policy_error"] ?? false : false;
1668
+ const contractResult = createContractResult({
1669
+ contractId,
1670
+ contractType: "precondition",
1671
+ passed: verdict.passed,
1672
+ message: verdict.message,
1673
+ tags,
1674
+ observed: isObserved,
1675
+ policyError: pe
1676
+ });
1677
+ contracts.push(contractResult);
1678
+ if (!verdict.passed && !isObserved) {
1679
+ denyReasons.push(verdict.message ?? "");
1680
+ }
1681
+ }
1682
+ for (const contract of guard.getSandboxContracts(envelope)) {
1683
+ const contractId = contract.name ?? "unknown";
1684
+ let verdict;
1685
+ try {
1686
+ verdict = await contract.check(envelope);
1687
+ } catch (exc) {
1688
+ const contractResult2 = createContractResult({
1689
+ contractId,
1690
+ contractType: "sandbox",
1691
+ passed: false,
1692
+ message: `Sandbox error: ${exc}`,
1693
+ policyError: true
1694
+ });
1695
+ contracts.push(contractResult2);
1696
+ denyReasons.push(contractResult2.message ?? "");
1697
+ continue;
1698
+ }
1699
+ const tags = safeTags(verdict.metadata);
1700
+ const isObserved = contract.mode === "observe" && !verdict.passed;
1701
+ const pe = verdict.metadata ? verdict.metadata["policy_error"] ?? false : false;
1702
+ const contractResult = createContractResult({
1703
+ contractId,
1704
+ contractType: "sandbox",
1705
+ passed: verdict.passed,
1706
+ message: verdict.message,
1707
+ tags,
1708
+ observed: isObserved,
1709
+ policyError: pe
1710
+ });
1711
+ contracts.push(contractResult);
1712
+ if (!verdict.passed && !isObserved) {
1713
+ denyReasons.push(verdict.message ?? "");
1714
+ }
1715
+ }
1716
+ if (options?.output != null) {
1717
+ for (const contract of guard.getPostconditions(envelope)) {
1718
+ const contractId = contract.name ?? "unknown";
1719
+ let verdict;
1720
+ try {
1721
+ verdict = await contract.check(envelope, options.output);
1722
+ } catch (exc) {
1723
+ const contractResult2 = createContractResult({
1724
+ contractId,
1725
+ contractType: "postcondition",
1726
+ passed: false,
1727
+ message: `Postcondition error: ${exc}`,
1728
+ policyError: true
1729
+ });
1730
+ contracts.push(contractResult2);
1731
+ const excEffect = contract.effect ?? "warn";
1732
+ if (excEffect === "deny") {
1733
+ denyReasons.push(contractResult2.message ?? "");
1734
+ } else {
1735
+ warnReasons.push(contractResult2.message ?? "");
1736
+ }
1737
+ continue;
1738
+ }
1739
+ const tags = safeTags(verdict.metadata);
1740
+ const isObserved = contract.mode === "observe" && !verdict.passed;
1741
+ const pe = verdict.metadata ? verdict.metadata["policy_error"] ?? false : false;
1742
+ const effect = contract.effect ?? "warn";
1743
+ const contractResult = createContractResult({
1744
+ contractId,
1745
+ contractType: "postcondition",
1746
+ passed: verdict.passed,
1747
+ message: verdict.message,
1748
+ tags,
1749
+ observed: isObserved,
1750
+ effect,
1751
+ policyError: pe
1752
+ });
1753
+ contracts.push(contractResult);
1754
+ if (!verdict.passed && !isObserved) {
1755
+ if (effect === "deny") {
1756
+ denyReasons.push(verdict.message ?? "");
1757
+ } else {
1758
+ warnReasons.push(verdict.message ?? "");
1759
+ }
1760
+ }
1761
+ }
1762
+ }
1763
+ let verdictStr;
1764
+ if (denyReasons.length > 0) {
1765
+ verdictStr = "deny";
1766
+ } else if (warnReasons.length > 0) {
1767
+ verdictStr = "warn";
1768
+ } else {
1769
+ verdictStr = "allow";
1770
+ }
1771
+ return createEvaluationResult({
1772
+ verdict: verdictStr,
1773
+ toolName,
1774
+ contracts,
1775
+ denyReasons,
1776
+ warnReasons,
1777
+ contractsEvaluated: contracts.length,
1778
+ policyError: contracts.some((r) => r.policyError)
1779
+ });
1780
+ }
1781
+ async function evaluateBatch(guard, calls) {
1782
+ const results = [];
1783
+ for (const call of calls) {
1784
+ const callArgs = call.args ?? {};
1785
+ let principal;
1786
+ if (call.principal != null && typeof call.principal === "object") {
1787
+ principal = createPrincipal({
1788
+ role: call.principal["role"] ?? void 0,
1789
+ userId: call.principal["userId"] ?? void 0,
1790
+ ticketRef: call.principal["ticketRef"] ?? void 0,
1791
+ claims: typeof call.principal["claims"] === "object" && call.principal["claims"] != null && !Array.isArray(call.principal["claims"]) ? call.principal["claims"] : {}
1792
+ });
1793
+ }
1794
+ let output;
1795
+ if (call.output != null) {
1796
+ if (typeof call.output === "object") {
1797
+ try {
1798
+ output = JSON.stringify(call.output);
1799
+ } catch {
1800
+ output = "[unserializable output]";
1801
+ }
1802
+ } else {
1803
+ output = call.output;
1804
+ }
1805
+ }
1806
+ results.push(
1807
+ await evaluate(guard, call.tool, callArgs, {
1808
+ principal,
1809
+ output,
1810
+ environment: call.environment
1811
+ })
1812
+ );
1813
+ }
1814
+ return results;
1815
+ }
1816
+ var init_dry_run = __esm({
1817
+ "src/dry-run.ts"() {
1818
+ "use strict";
1819
+ init_envelope();
1820
+ init_evaluation();
1821
+ }
1822
+ });
1823
+
1824
+ // src/index.ts
1825
+ var index_exports = {};
1826
+ __export(index_exports, {
1827
+ ApprovalStatus: () => ApprovalStatus,
1828
+ AuditAction: () => AuditAction,
1829
+ BUILTIN_OPERATOR_NAMES: () => BUILTIN_OPERATOR_NAMES,
1830
+ BUILTIN_SELECTOR_PREFIXES: () => BUILTIN_SELECTOR_PREFIXES,
1831
+ BashClassifier: () => BashClassifier,
1832
+ CollectingAuditSink: () => CollectingAuditSink,
1833
+ CompositeSink: () => CompositeSink,
1834
+ DEFAULT_LIMITS: () => DEFAULT_LIMITS,
1835
+ Edictum: () => Edictum,
1836
+ EdictumConfigError: () => EdictumConfigError,
1837
+ EdictumDenied: () => EdictumDenied,
1838
+ EdictumToolError: () => EdictumToolError,
1839
+ FileAuditSink: () => FileAuditSink,
1840
+ GovernancePipeline: () => GovernancePipeline,
1841
+ HookDecision: () => HookDecision,
1842
+ HookResult: () => HookResult,
1843
+ LocalApprovalBackend: () => LocalApprovalBackend,
1844
+ MAX_BUNDLE_SIZE: () => MAX_BUNDLE_SIZE,
1845
+ MAX_REGEX_INPUT: () => MAX_REGEX_INPUT,
1846
+ MarkEvictedError: () => MarkEvictedError,
1847
+ MemoryBackend: () => MemoryBackend,
1848
+ PolicyError: () => PolicyError,
1849
+ RedactionPolicy: () => RedactionPolicy,
1850
+ Session: () => Session,
1851
+ SideEffect: () => SideEffect,
1852
+ StdoutAuditSink: () => StdoutAuditSink,
1853
+ ToolRegistry: () => ToolRegistry,
1854
+ VERSION: () => VERSION,
1855
+ Verdict: () => Verdict,
1856
+ _validateToolName: () => _validateToolName,
1857
+ buildFindings: () => buildFindings,
1858
+ classifyFinding: () => classifyFinding,
1859
+ compileContracts: () => compileContracts,
1860
+ composeBundles: () => composeBundles,
1861
+ computeHash: () => computeHash,
1862
+ createAuditEvent: () => createAuditEvent,
1863
+ createCompiledState: () => createCompiledState,
1864
+ createContractResult: () => createContractResult,
1865
+ createEnvelope: () => createEnvelope,
1866
+ createEvaluationResult: () => createEvaluationResult,
1867
+ createFinding: () => createFinding,
1868
+ createPostCallResult: () => createPostCallResult,
1869
+ createPostDecision: () => createPostDecision,
1870
+ createPreDecision: () => createPreDecision,
1871
+ createPrincipal: () => createPrincipal,
1872
+ deepFreeze: () => deepFreeze,
1873
+ defaultSuccessCheck: () => defaultSuccessCheck,
1874
+ evaluateExpression: () => evaluateExpression,
1875
+ expandMessage: () => expandMessage,
1876
+ fnmatch: () => fnmatch,
1877
+ fromYaml: () => fromYaml,
1878
+ fromYamlString: () => fromYamlString,
1879
+ loadBundle: () => loadBundle,
1880
+ loadBundleString: () => loadBundleString,
1881
+ reload: () => reload,
1882
+ run: () => run,
1883
+ validateOperators: () => validateOperators
1884
+ });
1885
+ module.exports = __toCommonJS(index_exports);
1886
+ init_errors();
1887
+ init_envelope();
1888
+ init_contracts();
1889
+ init_hooks();
1890
+
1891
+ // src/limits.ts
1892
+ var DEFAULT_LIMITS = Object.freeze({
1893
+ maxAttempts: 500,
1894
+ maxToolCalls: 200,
1895
+ maxCallsPerTool: Object.freeze({})
1896
+ });
1897
+
1898
+ // src/storage.ts
1899
+ var MemoryBackend = class {
1900
+ _data = /* @__PURE__ */ new Map();
1901
+ _counters = /* @__PURE__ */ new Map();
1902
+ async get(key) {
1903
+ const strVal = this._data.get(key);
1904
+ if (strVal !== void 0) {
1905
+ return strVal;
1906
+ }
1907
+ const numVal = this._counters.get(key);
1908
+ if (numVal !== void 0) {
1909
+ return numVal === Math.trunc(numVal) ? String(Math.trunc(numVal)) : String(numVal);
1910
+ }
1911
+ return null;
1912
+ }
1913
+ async set(key, value) {
1914
+ this._data.set(key, value);
1915
+ }
1916
+ async delete(key) {
1917
+ this._data.delete(key);
1918
+ this._counters.delete(key);
1919
+ }
1920
+ async increment(key, amount = 1) {
1921
+ const current = this._counters.get(key) ?? 0;
1922
+ const next = current + amount;
1923
+ this._counters.set(key, next);
1924
+ return next;
1925
+ }
1926
+ /**
1927
+ * Retrieve multiple values in a single operation.
1928
+ *
1929
+ * In-memory implementation: multiple Map lookups, no network overhead.
1930
+ */
1931
+ async batchGet(keys) {
1932
+ const result = {};
1933
+ for (const key of keys) {
1934
+ result[key] = await this.get(key);
1935
+ }
1936
+ return result;
1937
+ }
1938
+ };
1939
+
1940
+ // src/index.ts
1941
+ init_session();
1942
+ init_approval();
1943
+ init_audit();
1944
+ init_redaction();
1945
+ init_evaluation();
1946
+
1947
+ // src/findings.ts
1948
+ function createFinding(fields) {
1949
+ return Object.freeze({
1950
+ type: fields.type,
1951
+ contractId: fields.contractId,
1952
+ field: fields.field,
1953
+ message: fields.message,
1954
+ metadata: Object.freeze({ ...fields.metadata ?? {} })
1955
+ });
1956
+ }
1957
+ function createPostCallResult(fields) {
1958
+ return Object.freeze({
1959
+ result: fields.result,
1960
+ postconditionsPassed: fields.postconditionsPassed ?? true,
1961
+ findings: Object.freeze([...fields.findings ?? []]),
1962
+ outputSuppressed: fields.outputSuppressed ?? false
1963
+ });
1964
+ }
1965
+ function classifyFinding(contractId, verdictMessage) {
1966
+ const contractLower = contractId.toLowerCase();
1967
+ const messageLower = (verdictMessage || "").toLowerCase();
1968
+ const piiTerms = ["pii", "ssn", "patient", "name", "dob"];
1969
+ if (piiTerms.some(
1970
+ (term) => contractLower.includes(term) || messageLower.includes(term)
1971
+ )) {
1972
+ return "pii_detected";
1973
+ }
1974
+ const secretTerms = ["secret", "token", "key", "credential", "password"];
1975
+ if (secretTerms.some(
1976
+ (term) => contractLower.includes(term) || messageLower.includes(term)
1977
+ )) {
1978
+ return "secret_detected";
1979
+ }
1980
+ const limitTerms = ["session", "limit", "max_calls", "budget"];
1981
+ if (limitTerms.some(
1982
+ (term) => contractLower.includes(term) || messageLower.includes(term)
1983
+ )) {
1984
+ return "limit_exceeded";
1985
+ }
1986
+ return "policy_violation";
1987
+ }
1988
+ function buildFindings(postDecision) {
1989
+ const findings = [];
1990
+ for (const cr of postDecision.contractsEvaluated) {
1991
+ if (!cr.passed) {
1992
+ const meta = cr.metadata ?? {};
1993
+ findings.push(
1994
+ createFinding({
1995
+ type: classifyFinding(cr.name, cr.message ?? ""),
1996
+ contractId: cr.name,
1997
+ field: meta.field ?? "output",
1998
+ message: cr.message ?? "",
1999
+ metadata: meta
2000
+ })
2001
+ );
2002
+ }
2003
+ }
2004
+ return findings;
2005
+ }
2006
+
2007
+ // src/compiled-state.ts
2008
+ init_envelope();
2009
+ function createCompiledState(partial = {}) {
2010
+ return deepFreeze({
2011
+ preconditions: partial.preconditions ?? [],
2012
+ postconditions: partial.postconditions ?? [],
2013
+ sessionContracts: partial.sessionContracts ?? [],
2014
+ sandboxContracts: partial.sandboxContracts ?? [],
2015
+ observePreconditions: partial.observePreconditions ?? [],
2016
+ observePostconditions: partial.observePostconditions ?? [],
2017
+ observeSessionContracts: partial.observeSessionContracts ?? [],
2018
+ observeSandboxContracts: partial.observeSandboxContracts ?? [],
2019
+ limits: partial.limits ?? DEFAULT_LIMITS,
2020
+ policyVersion: partial.policyVersion ?? null
2021
+ });
2022
+ }
2023
+
2024
+ // src/index.ts
2025
+ init_pipeline();
2026
+
2027
+ // src/guard.ts
2028
+ var import_node_crypto5 = require("crypto");
2029
+
2030
+ // src/factory.ts
2031
+ var import_node_crypto4 = require("crypto");
2032
+ init_errors();
2033
+
2034
+ // src/yaml-engine/composer.ts
2035
+ init_errors();
2036
+ function deepCopyBundle(data) {
2037
+ return structuredClone(data);
2038
+ }
2039
+ function composeBundles(...bundles) {
2040
+ if (bundles.length === 0) {
2041
+ throw new Error("composeBundles() requires at least one bundle");
2042
+ }
2043
+ if (bundles.length === 1) {
2044
+ const entry = bundles[0];
2045
+ return {
2046
+ bundle: deepCopyBundle(entry[0]),
2047
+ report: { overriddenContracts: [], observeContracts: [] }
2048
+ };
2049
+ }
2050
+ const overrides = [];
2051
+ const observes = [];
2052
+ const first = bundles[0];
2053
+ const merged = deepCopyBundle(first[0]);
2054
+ const firstLabel = first[1];
2055
+ const contractSources = /* @__PURE__ */ new Map();
2056
+ for (const c of merged.contracts ?? []) {
2057
+ contractSources.set(c.id, firstLabel);
2058
+ }
2059
+ for (let i = 1; i < bundles.length; i++) {
2060
+ const entry = bundles[i];
2061
+ const [data, label] = entry;
2062
+ const isObserveAlongside = Boolean(data.observe_alongside);
2063
+ if (isObserveAlongside) {
2064
+ mergeObserveAlongside(merged, data, label, contractSources, observes);
2065
+ } else {
2066
+ mergeStandard(merged, data, label, contractSources, overrides);
2067
+ }
2068
+ }
2069
+ return {
2070
+ bundle: merged,
2071
+ report: { overriddenContracts: overrides, observeContracts: observes }
2072
+ };
2073
+ }
2074
+ function mergeStandard(merged, layer, label, contractSources, overrides) {
2075
+ if ("defaults" in layer) {
2076
+ const ld = layer.defaults;
2077
+ const md = merged.defaults ?? {};
2078
+ if ("mode" in ld) md.mode = ld.mode;
2079
+ if ("environment" in ld) md.environment = ld.environment;
2080
+ merged.defaults = md;
2081
+ }
2082
+ if ("limits" in layer) merged.limits = deepCopyBundle(layer.limits);
2083
+ if ("tools" in layer) {
2084
+ const mt = merged.tools ?? {};
2085
+ for (const [name, cfg] of Object.entries(layer.tools)) {
2086
+ mt[name] = { ...cfg };
2087
+ }
2088
+ merged.tools = mt;
2089
+ }
2090
+ if ("metadata" in layer) {
2091
+ const mm = merged.metadata ?? {};
2092
+ for (const [k, v] of Object.entries(layer.metadata)) mm[k] = v;
2093
+ merged.metadata = mm;
2094
+ }
2095
+ if ("observability" in layer) {
2096
+ merged.observability = deepCopyBundle(layer.observability);
2097
+ }
2098
+ if ("contracts" in layer) {
2099
+ const existingById = /* @__PURE__ */ new Map();
2100
+ const mc = merged.contracts ?? [];
2101
+ for (let j = 0; j < mc.length; j++) {
2102
+ const c = mc[j];
2103
+ existingById.set(c.id, j);
2104
+ }
2105
+ for (const contract of layer.contracts ?? []) {
2106
+ const cid = contract.id;
2107
+ const newContract = deepCopyBundle(contract);
2108
+ if (existingById.has(cid)) {
2109
+ const idx = existingById.get(cid);
2110
+ overrides.push({
2111
+ contractId: cid,
2112
+ overriddenBy: label,
2113
+ originalSource: contractSources.get(cid) ?? "unknown"
2114
+ });
2115
+ mc[idx] = newContract;
2116
+ } else {
2117
+ mc.push(newContract);
2118
+ existingById.set(cid, mc.length - 1);
2119
+ }
2120
+ contractSources.set(cid, label);
2121
+ }
2122
+ merged.contracts = mc;
2123
+ }
2124
+ }
2125
+ function mergeObserveAlongside(merged, layer, label, contractSources, observes) {
2126
+ const mc = merged.contracts ?? [];
2127
+ for (const contract of layer.contracts ?? []) {
2128
+ const cid = contract.id;
2129
+ const observeId = `${cid}:candidate`;
2130
+ const existingIds = new Set(
2131
+ mc.map((c) => c.id)
2132
+ );
2133
+ if (existingIds.has(observeId)) {
2134
+ throw new EdictumConfigError(
2135
+ `observe_alongside collision: generated ID "${observeId}" already exists in the bundle. Rename the conflicting contract or use a different ID for "${cid}".`
2136
+ );
2137
+ }
2138
+ const observeContract = deepCopyBundle(contract);
2139
+ observeContract.id = observeId;
2140
+ observeContract.mode = "observe";
2141
+ observeContract._observe = true;
2142
+ mc.push(observeContract);
2143
+ observes.push({
2144
+ contractId: cid,
2145
+ enforcedSource: contractSources.get(cid) ?? "",
2146
+ observedSource: label
2147
+ });
2148
+ }
2149
+ merged.contracts = mc;
2150
+ if ("tools" in layer) {
2151
+ const mt = merged.tools ?? {};
2152
+ for (const [name, cfg] of Object.entries(layer.tools)) {
2153
+ mt[name] = { ...cfg };
2154
+ }
2155
+ merged.tools = mt;
2156
+ }
2157
+ if ("metadata" in layer) {
2158
+ const mm = merged.metadata ?? {};
2159
+ for (const [k, v] of Object.entries(layer.metadata)) mm[k] = v;
2160
+ merged.metadata = mm;
2161
+ }
2162
+ }
2163
+
2164
+ // src/yaml-engine/compiler.ts
2165
+ init_errors();
2166
+
2167
+ // src/yaml-engine/compiler-utils.ts
2168
+ init_errors();
2169
+ init_redaction();
2170
+
2171
+ // src/yaml-engine/operators.ts
2172
+ var MAX_REGEX_INPUT = 1e4;
2173
+ function opEquals(fieldValue, opValue) {
2174
+ return fieldValue === opValue;
2175
+ }
2176
+ function opNotEquals(fieldValue, opValue) {
2177
+ return fieldValue !== opValue;
2178
+ }
2179
+ function opIn(fieldValue, opValue) {
2180
+ return opValue.includes(fieldValue);
2181
+ }
2182
+ function opNotIn(fieldValue, opValue) {
2183
+ return !opValue.includes(fieldValue);
2184
+ }
2185
+ function opContains(fieldValue, opValue) {
2186
+ if (typeof fieldValue !== "string") throw new TypeError();
2187
+ return fieldValue.includes(opValue);
2188
+ }
2189
+ function opContainsAny(fieldValue, opValue) {
2190
+ if (typeof fieldValue !== "string") throw new TypeError();
2191
+ return opValue.some((v) => fieldValue.includes(v));
2192
+ }
2193
+ function opStartsWith(fieldValue, opValue) {
2194
+ if (typeof fieldValue !== "string") throw new TypeError();
2195
+ return fieldValue.startsWith(opValue);
2196
+ }
2197
+ function opEndsWith(fieldValue, opValue) {
2198
+ if (typeof fieldValue !== "string") throw new TypeError();
2199
+ return fieldValue.endsWith(opValue);
2200
+ }
2201
+ function opMatches(fieldValue, opValue) {
2202
+ if (typeof fieldValue !== "string") throw new TypeError();
2203
+ const truncated = fieldValue.slice(0, MAX_REGEX_INPUT);
2204
+ if (opValue instanceof RegExp) {
2205
+ return opValue.test(truncated);
2206
+ }
2207
+ return new RegExp(opValue).test(truncated);
2208
+ }
2209
+ function opMatchesAny(fieldValue, opValue) {
2210
+ if (typeof fieldValue !== "string") throw new TypeError();
2211
+ const truncated = fieldValue.slice(0, MAX_REGEX_INPUT);
2212
+ return opValue.some(
2213
+ (p) => p instanceof RegExp ? p.test(truncated) : new RegExp(p).test(truncated)
2214
+ );
2215
+ }
2216
+ function opGt(fieldValue, opValue) {
2217
+ if (typeof fieldValue !== "number") throw new TypeError();
2218
+ return fieldValue > opValue;
2219
+ }
2220
+ function opGte(fieldValue, opValue) {
2221
+ if (typeof fieldValue !== "number") throw new TypeError();
2222
+ return fieldValue >= opValue;
2223
+ }
2224
+ function opLt(fieldValue, opValue) {
2225
+ if (typeof fieldValue !== "number") throw new TypeError();
2226
+ return fieldValue < opValue;
2227
+ }
2228
+ function opLte(fieldValue, opValue) {
2229
+ if (typeof fieldValue !== "number") throw new TypeError();
2230
+ return fieldValue <= opValue;
2231
+ }
2232
+ var OPERATORS = {
2233
+ equals: opEquals,
2234
+ not_equals: opNotEquals,
2235
+ in: opIn,
2236
+ not_in: opNotIn,
2237
+ contains: opContains,
2238
+ contains_any: opContainsAny,
2239
+ starts_with: opStartsWith,
2240
+ ends_with: opEndsWith,
2241
+ matches: opMatches,
2242
+ matches_any: opMatchesAny,
2243
+ gt: opGt,
2244
+ gte: opGte,
2245
+ lt: opLt,
2246
+ lte: opLte
2247
+ };
2248
+ var BUILTIN_OPERATOR_NAMES = /* @__PURE__ */ new Set([
2249
+ ...Object.keys(OPERATORS),
2250
+ "exists"
2251
+ ]);
2252
+
2253
+ // src/yaml-engine/selectors.ts
2254
+ var _MISSING = /* @__PURE__ */ Symbol("MISSING");
2255
+ var BUILTIN_SELECTOR_PREFIXES = /* @__PURE__ */ new Set([
2256
+ "environment",
2257
+ "tool",
2258
+ "args",
2259
+ "principal",
2260
+ "output",
2261
+ "env",
2262
+ "metadata"
2263
+ ]);
2264
+ function resolveSelector(selector, envelope, outputText, customSelectors) {
2265
+ if (selector === "environment") return envelope.environment;
2266
+ if (selector === "tool.name") return envelope.toolName;
2267
+ if (selector.startsWith("args.")) {
2268
+ return resolveNested(selector.slice(5), envelope.args);
2269
+ }
2270
+ if (selector.startsWith("principal.")) {
2271
+ if (envelope.principal == null) return _MISSING;
2272
+ const rest = selector.slice(10);
2273
+ if (rest === "user_id") return envelope.principal.userId;
2274
+ if (rest === "service_id") return envelope.principal.serviceId;
2275
+ if (rest === "org_id") return envelope.principal.orgId;
2276
+ if (rest === "role") return envelope.principal.role;
2277
+ if (rest === "ticket_ref") return envelope.principal.ticketRef;
2278
+ if (rest.startsWith("claims.")) {
2279
+ return resolveNested(
2280
+ rest.slice(7),
2281
+ envelope.principal.claims
2282
+ );
2283
+ }
2284
+ return _MISSING;
2285
+ }
2286
+ if (selector === "output.text") {
2287
+ return outputText == null ? _MISSING : outputText;
2288
+ }
2289
+ if (selector.startsWith("env.")) {
2290
+ const varName = selector.slice(4);
2291
+ const raw = process.env[varName];
2292
+ if (raw == null) return _MISSING;
2293
+ return coerceEnvValue(raw);
2294
+ }
2295
+ if (selector.startsWith("metadata.")) {
2296
+ return resolveNested(
2297
+ selector.slice(9),
2298
+ envelope.metadata
2299
+ );
2300
+ }
2301
+ if (customSelectors) {
2302
+ const dotPos = selector.indexOf(".");
2303
+ if (dotPos > 0) {
2304
+ const prefix = selector.slice(0, dotPos);
2305
+ if (Object.hasOwn(customSelectors, prefix)) {
2306
+ const resolver = customSelectors[prefix];
2307
+ const data = resolver(envelope);
2308
+ const rest = selector.slice(dotPos + 1);
2309
+ return resolveNested(rest, data);
2310
+ }
2311
+ }
2312
+ }
2313
+ return _MISSING;
2314
+ }
2315
+ function resolveNested(path, data) {
2316
+ const parts = path.split(".");
2317
+ let current = data;
2318
+ for (const part of parts) {
2319
+ if (current == null || typeof current !== "object") return _MISSING;
2320
+ const obj = current;
2321
+ if (!Object.hasOwn(obj, part)) return _MISSING;
2322
+ current = obj[part];
2323
+ }
2324
+ return current;
2325
+ }
2326
+ function coerceEnvValue(raw) {
2327
+ const low = raw.toLowerCase();
2328
+ if (low === "true") return true;
2329
+ if (low === "false") return false;
2330
+ const asInt = parseInt(raw, 10);
2331
+ if (!isNaN(asInt) && String(asInt) === raw) return asInt;
2332
+ const asFloat = parseFloat(raw);
2333
+ if (!isNaN(asFloat) && String(asFloat) === raw) return asFloat;
2334
+ return raw;
2335
+ }
2336
+
2337
+ // src/yaml-engine/evaluator.ts
2338
+ var PolicyError = class {
2339
+ message;
2340
+ constructor(message) {
2341
+ this.message = message;
2342
+ }
2343
+ };
2344
+ function evaluateExpression(expr, envelope, outputText, options) {
2345
+ const customOps = options?.customOperators ?? null;
2346
+ const customSels = options?.customSelectors ?? null;
2347
+ if ("all" in expr) {
2348
+ return _evalAll(expr.all, envelope, outputText, customOps, customSels);
2349
+ }
2350
+ if ("any" in expr) {
2351
+ return _evalAny(expr.any, envelope, outputText, customOps, customSels);
2352
+ }
2353
+ if ("not" in expr) {
2354
+ return _evalNot(expr.not, envelope, outputText, customOps, customSels);
2355
+ }
2356
+ const leafKeys = Object.keys(expr);
2357
+ if (leafKeys.length !== 1) {
2358
+ return new PolicyError(
2359
+ `Leaf expression must have exactly one selector key, got ${leafKeys.length}: [${leafKeys.join(", ")}]`
2360
+ );
2361
+ }
2362
+ return _evalLeaf(expr, envelope, outputText, customOps, customSels);
2363
+ }
2364
+ function _evalAll(exprs, envelope, outputText, customOps, customSels) {
2365
+ for (const expr of exprs) {
2366
+ const result = evaluateExpression(expr, envelope, outputText, {
2367
+ customOperators: customOps,
2368
+ customSelectors: customSels
2369
+ });
2370
+ if (result instanceof PolicyError) return result;
2371
+ if (!result) return false;
2372
+ }
2373
+ return true;
2374
+ }
2375
+ function _evalAny(exprs, envelope, outputText, customOps, customSels) {
2376
+ for (const expr of exprs) {
2377
+ const result = evaluateExpression(expr, envelope, outputText, {
2378
+ customOperators: customOps,
2379
+ customSelectors: customSels
2380
+ });
2381
+ if (result instanceof PolicyError) return result;
2382
+ if (result) return true;
2383
+ }
2384
+ return false;
2385
+ }
2386
+ function _evalNot(expr, envelope, outputText, customOps, customSels) {
2387
+ const result = evaluateExpression(expr, envelope, outputText, {
2388
+ customOperators: customOps,
2389
+ customSelectors: customSels
2390
+ });
2391
+ if (result instanceof PolicyError) return result;
2392
+ return !result;
2393
+ }
2394
+ function _evalLeaf(leaf, envelope, outputText, customOps, customSels) {
2395
+ const selector = Object.keys(leaf)[0];
2396
+ const operatorBlock = leaf[selector];
2397
+ const value = resolveSelector(selector, envelope, outputText, customSels);
2398
+ const opName = Object.keys(operatorBlock)[0];
2399
+ const opValue = operatorBlock[opName];
2400
+ return _applyOperator(opName, value, opValue, selector, customOps);
2401
+ }
2402
+ function _applyOperator(op, fieldValue, opValue, selector, customOperators) {
2403
+ if (op === "exists") {
2404
+ const isPresent = fieldValue !== _MISSING && fieldValue != null;
2405
+ return isPresent === opValue;
2406
+ }
2407
+ if (fieldValue === _MISSING || fieldValue == null) return false;
2408
+ try {
2409
+ if (Object.hasOwn(OPERATORS, op)) return OPERATORS[op](fieldValue, opValue);
2410
+ if (customOperators && Object.hasOwn(customOperators, op)) {
2411
+ return Boolean(customOperators[op](fieldValue, opValue));
2412
+ }
2413
+ return new PolicyError(`Unknown operator: '${op}'`);
2414
+ } catch {
2415
+ return new PolicyError(
2416
+ `Type mismatch: operator '${op}' cannot be applied to selector '${selector}' value ${typeof fieldValue}`
2417
+ );
2418
+ }
2419
+ }
2420
+
2421
+ // src/yaml-engine/compiler-utils.ts
2422
+ var _PLACEHOLDER_RE = /\{([^}]+)\}/g;
2423
+ var _PLACEHOLDER_CAP = 200;
2424
+ function expandMessage(template, envelope, outputText, customSelectors) {
2425
+ const redaction = new RedactionPolicy();
2426
+ return template.replace(_PLACEHOLDER_RE, (match, selectorRaw) => {
2427
+ const value = resolveSelector(selectorRaw, envelope, outputText, customSelectors);
2428
+ if (value === _MISSING || value == null) return match;
2429
+ let text = String(value);
2430
+ if (redaction._looksLikeSecret(text)) text = "[REDACTED]";
2431
+ if (text.length > _PLACEHOLDER_CAP) text = text.slice(0, _PLACEHOLDER_CAP - 3) + "...";
2432
+ return text;
2433
+ });
2434
+ }
2435
+ function validateOperators(bundle, customOperators) {
2436
+ const known = /* @__PURE__ */ new Set([
2437
+ ...BUILTIN_OPERATOR_NAMES,
2438
+ ...Object.keys(customOperators ?? {})
2439
+ ]);
2440
+ const contracts = bundle.contracts ?? [];
2441
+ for (const contract of contracts) {
2442
+ const when = contract.when;
2443
+ if (when) {
2444
+ _validateExpressionOperators(when, known, contract.id);
2445
+ }
2446
+ }
2447
+ }
2448
+ function _validateExpressionOperators(expr, known, contractId) {
2449
+ if (expr == null || typeof expr !== "object") return;
2450
+ const e = expr;
2451
+ if ("all" in e) {
2452
+ for (const sub of e.all) {
2453
+ _validateExpressionOperators(sub, known, contractId);
2454
+ }
2455
+ return;
2456
+ }
2457
+ if ("any" in e) {
2458
+ for (const sub of e.any) {
2459
+ _validateExpressionOperators(sub, known, contractId);
2460
+ }
2461
+ return;
2462
+ }
2463
+ if ("not" in e) {
2464
+ _validateExpressionOperators(e.not, known, contractId);
2465
+ return;
2466
+ }
2467
+ for (const [, operator] of Object.entries(e)) {
2468
+ if (operator != null && typeof operator === "object") {
2469
+ for (const opName of Object.keys(operator)) {
2470
+ if (!known.has(opName)) {
2471
+ throw new EdictumConfigError(
2472
+ `Contract '${contractId}': unknown operator '${opName}'`
2473
+ );
2474
+ }
2475
+ }
2476
+ }
2477
+ }
2478
+ }
2479
+ function precompileRegexes(expr) {
2480
+ if (expr == null || typeof expr !== "object") return expr;
2481
+ const e = expr;
2482
+ if ("all" in e) {
2483
+ return { all: e.all.map(precompileRegexes) };
2484
+ }
2485
+ if ("any" in e) {
2486
+ return { any: e.any.map(precompileRegexes) };
2487
+ }
2488
+ if ("not" in e) {
2489
+ return { not: precompileRegexes(e.not) };
2490
+ }
2491
+ const compiled = {};
2492
+ for (const [selector, operator] of Object.entries(e)) {
2493
+ if (operator == null || typeof operator !== "object") {
2494
+ compiled[selector] = operator;
2495
+ continue;
2496
+ }
2497
+ const newOp = { ...operator };
2498
+ if ("matches" in newOp && typeof newOp.matches === "string") {
2499
+ newOp.matches = new RegExp(newOp.matches);
2500
+ }
2501
+ if ("matches_any" in newOp && Array.isArray(newOp.matches_any)) {
2502
+ newOp.matches_any = newOp.matches_any.map(
2503
+ (p) => typeof p === "string" ? new RegExp(p) : p
2504
+ );
2505
+ }
2506
+ compiled[selector] = newOp;
2507
+ }
2508
+ return compiled;
2509
+ }
2510
+ function extractOutputPatterns(expr) {
2511
+ if (expr == null || typeof expr !== "object") return [];
2512
+ const e = expr;
2513
+ if ("all" in e) {
2514
+ const patterns = [];
2515
+ for (const sub of e.all) {
2516
+ patterns.push(...extractOutputPatterns(sub));
2517
+ }
2518
+ return patterns;
2519
+ }
2520
+ if ("any" in e) {
2521
+ const patterns = [];
2522
+ for (const sub of e.any) {
2523
+ patterns.push(...extractOutputPatterns(sub));
2524
+ }
2525
+ return patterns;
2526
+ }
2527
+ if ("not" in e) {
2528
+ return extractOutputPatterns(e.not);
2529
+ }
2530
+ const collected = [];
2531
+ for (const [selector, operator] of Object.entries(e)) {
2532
+ if (selector !== "output.text" || operator == null || typeof operator !== "object") continue;
2533
+ const op = operator;
2534
+ if ("matches" in op && op.matches instanceof RegExp) {
2535
+ collected.push(op.matches);
2536
+ }
2537
+ if ("matches_any" in op && Array.isArray(op.matches_any)) {
2538
+ for (const p of op.matches_any) {
2539
+ if (p instanceof RegExp) collected.push(p);
2540
+ }
2541
+ }
2542
+ }
2543
+ return collected;
2544
+ }
2545
+
2546
+ // src/yaml-engine/compile-contracts.ts
2547
+ init_contracts();
2548
+ init_errors();
2549
+ function _evalAndVerdict(whenExpr, envelope, outputText, messageTemplate, tags, thenMetadata, customOps, customSels) {
2550
+ try {
2551
+ const result = evaluateExpression(whenExpr, envelope, outputText, {
2552
+ customOperators: customOps,
2553
+ customSelectors: customSels
2554
+ });
2555
+ if (result instanceof PolicyError) {
2556
+ const msg = expandMessage(messageTemplate, envelope, outputText, customSels);
2557
+ return Verdict.fail(msg, { tags, policyError: true, ...thenMetadata });
2558
+ }
2559
+ if (result) {
2560
+ const msg = expandMessage(messageTemplate, envelope, outputText, customSels);
2561
+ return Verdict.fail(msg, { tags, ...thenMetadata });
2562
+ }
2563
+ return Verdict.pass_();
2564
+ } catch (exc) {
2565
+ const msg = expandMessage(messageTemplate, envelope, outputText, customSels);
2566
+ return Verdict.fail(msg, { tags, policyError: true, errorDetail: String(exc), ...thenMetadata });
2567
+ }
2568
+ }
2569
+ function _maybeObserve(result, contract) {
2570
+ if (contract._observe === true || contract._shadow === true) result._edictum_observe = true;
2571
+ }
2572
+ function compilePre(contract, mode, customOps, customSels) {
2573
+ const contractId = contract.id;
2574
+ const tool = contract.tool;
2575
+ const whenExpr = precompileRegexes(contract.when);
2576
+ const then = contract.then;
2577
+ const msgTpl = then.message;
2578
+ const tags = then.tags ?? [];
2579
+ const meta = then.metadata ?? {};
2580
+ const check = (envelope) => _evalAndVerdict(whenExpr, envelope, void 0, msgTpl, tags, meta, customOps, customSels);
2581
+ const result = {
2582
+ check,
2583
+ name: contractId,
2584
+ tool,
2585
+ type: "precondition",
2586
+ mode,
2587
+ _edictum_type: "precondition",
2588
+ _edictum_tool: tool,
2589
+ _edictum_when: null,
2590
+ _edictum_mode: mode,
2591
+ _edictum_id: contractId,
2592
+ _edictum_source: "yaml_precondition",
2593
+ _edictum_effect: then.effect ?? "deny",
2594
+ _edictum_timeout: then.timeout ?? 300,
2595
+ _edictum_timeout_effect: then.timeout_effect ?? "deny"
2596
+ };
2597
+ _maybeObserve(result, contract);
2598
+ return result;
2599
+ }
2600
+ function compilePost(contract, mode, customOps, customSels) {
2601
+ const contractId = contract.id;
2602
+ const tool = contract.tool;
2603
+ const whenExpr = precompileRegexes(contract.when);
2604
+ const then = contract.then;
2605
+ const msgTpl = then.message;
2606
+ const tags = then.tags ?? [];
2607
+ const meta = then.metadata ?? {};
2608
+ const check = (envelope, response) => {
2609
+ const outputText = response != null ? String(response) : void 0;
2610
+ return _evalAndVerdict(whenExpr, envelope, outputText, msgTpl, tags, meta, customOps, customSels);
2611
+ };
2612
+ const effectValue = then.effect ?? "warn";
2613
+ const result = {
2614
+ check,
2615
+ name: contractId,
2616
+ tool,
2617
+ type: "postcondition",
2618
+ mode,
2619
+ effect: effectValue,
2620
+ _edictum_type: "postcondition",
2621
+ _edictum_tool: tool,
2622
+ _edictum_when: null,
2623
+ _edictum_mode: mode,
2624
+ _edictum_id: contractId,
2625
+ _edictum_source: "yaml_postcondition",
2626
+ _edictum_effect: effectValue,
2627
+ _edictum_redact_patterns: extractOutputPatterns(whenExpr)
2628
+ };
2629
+ _maybeObserve(result, contract);
2630
+ return result;
2631
+ }
2632
+ function compileSession(contract, mode, limits) {
2633
+ const contractId = contract.id;
2634
+ const then = contract.then;
2635
+ const messageTemplate = then.message;
2636
+ const tags = then.tags ?? [];
2637
+ const thenMetadata = then.metadata ?? {};
2638
+ const capturedLimits = { ...limits };
2639
+ const check = async (session) => {
2640
+ const execCount = await session.executionCount();
2641
+ if (execCount >= capturedLimits.maxToolCalls) {
2642
+ return Verdict.fail(messageTemplate, { tags, ...thenMetadata });
2643
+ }
2644
+ const attemptCount = await session.attemptCount();
2645
+ if (attemptCount >= capturedLimits.maxAttempts) {
2646
+ return Verdict.fail(messageTemplate, { tags, ...thenMetadata });
2647
+ }
2648
+ return Verdict.pass_();
2649
+ };
2650
+ const result = {
2651
+ check,
2652
+ name: contractId,
2653
+ type: "session_contract",
2654
+ _edictum_type: "session_contract",
2655
+ _edictum_mode: mode,
2656
+ _edictum_id: contractId,
2657
+ _edictum_message: messageTemplate,
2658
+ _edictum_tags: tags,
2659
+ _edictum_then_metadata: thenMetadata,
2660
+ _edictum_source: "yaml_session"
2661
+ };
2662
+ if (contract._observe === true || contract._shadow === true) {
2663
+ result._edictum_observe = true;
2664
+ }
2665
+ return result;
2666
+ }
2667
+ function mergeSessionLimits(contract, existing) {
2668
+ const sessionLimits = contract.limits;
2669
+ let maxToolCalls = existing.maxToolCalls;
2670
+ let maxAttempts = existing.maxAttempts;
2671
+ const maxCallsPerTool = { ...existing.maxCallsPerTool };
2672
+ if ("max_tool_calls" in sessionLimits) {
2673
+ const raw = sessionLimits.max_tool_calls;
2674
+ if (typeof raw !== "number" || !Number.isFinite(raw)) {
2675
+ throw new EdictumConfigError(`Session limit max_tool_calls must be a finite number, got: ${String(raw)}`);
2676
+ }
2677
+ maxToolCalls = Math.min(maxToolCalls, raw);
2678
+ }
2679
+ if ("max_attempts" in sessionLimits) {
2680
+ const raw = sessionLimits.max_attempts;
2681
+ if (typeof raw !== "number" || !Number.isFinite(raw)) {
2682
+ throw new EdictumConfigError(`Session limit max_attempts must be a finite number, got: ${String(raw)}`);
2683
+ }
2684
+ maxAttempts = Math.min(maxAttempts, raw);
2685
+ }
2686
+ if ("max_calls_per_tool" in sessionLimits) {
2687
+ const perTool = sessionLimits.max_calls_per_tool;
2688
+ for (const [tool, limit] of Object.entries(perTool)) {
2689
+ if (typeof limit !== "number" || !Number.isFinite(limit)) {
2690
+ throw new EdictumConfigError(
2691
+ `Session limit max_calls_per_tool['${tool}'] must be a finite number, got: ${String(limit)}`
2692
+ );
2693
+ }
2694
+ if (Object.hasOwn(maxCallsPerTool, tool)) {
2695
+ maxCallsPerTool[tool] = Math.min(maxCallsPerTool[tool], limit);
2696
+ } else {
2697
+ maxCallsPerTool[tool] = limit;
2698
+ }
2699
+ }
2700
+ }
2701
+ return { maxAttempts, maxToolCalls, maxCallsPerTool };
2702
+ }
2703
+
2704
+ // src/yaml-engine/sandbox-compile-fn.ts
2705
+ init_contracts();
2706
+
2707
+ // src/yaml-engine/sandbox-compiler.ts
2708
+ var import_node_fs = require("fs");
2709
+ var import_node_path = require("path");
2710
+
2711
+ // src/fnmatch.ts
2712
+ function fnmatch(name, pattern) {
2713
+ if (pattern === "*") return true;
2714
+ if (!pattern.includes("*") && !pattern.includes("?")) {
2715
+ return name === pattern;
2716
+ }
2717
+ const safeName = name.length > 1e4 ? name.slice(0, 1e4) : name;
2718
+ const safePattern = pattern.length > 1e4 ? pattern.slice(0, 1e4) : pattern;
2719
+ let regex = "";
2720
+ for (let i = 0; i < safePattern.length; i++) {
2721
+ const ch = safePattern[i] ?? "";
2722
+ if (ch === "*") {
2723
+ regex += ".*";
2724
+ } else if (ch === "?") {
2725
+ regex += ".";
2726
+ } else if (".+^${}()|[]\\".includes(ch)) {
2727
+ regex += "\\" + ch;
2728
+ } else {
2729
+ regex += ch;
2730
+ }
2731
+ }
2732
+ return new RegExp("^" + regex + "$").test(safeName);
2733
+ }
2734
+
2735
+ // src/yaml-engine/sandbox-compiler.ts
2736
+ var _REDIRECT_PREFIX_RE = /^(?:\d*>>|>>|\d*>|>|<<|<)/;
2737
+ var _SHELL_SEPARATOR_RE = /[;|&\n\r`]|\$\(|\$\{|<\(/;
2738
+ function tokenizeCommand(cmd) {
2739
+ const rawTokens = _shlexSplit(cmd);
2740
+ const tokens = [];
2741
+ for (const t of rawTokens) {
2742
+ const stripped = t.replace(_REDIRECT_PREFIX_RE, "");
2743
+ if (stripped) tokens.push(stripped);
2744
+ }
2745
+ return tokens;
2746
+ }
2747
+ function _shlexSplit(s) {
2748
+ const tokens = [];
2749
+ let current = "";
2750
+ let inSingle = false;
2751
+ let inDouble = false;
2752
+ let i = 0;
2753
+ try {
2754
+ while (i < s.length) {
2755
+ const ch = s.charAt(i);
2756
+ if (inSingle) {
2757
+ if (ch === "'") {
2758
+ inSingle = false;
2759
+ } else {
2760
+ current += ch;
2761
+ }
2762
+ } else if (inDouble) {
2763
+ if (ch === "\\" && i + 1 < s.length) {
2764
+ const next = s.charAt(i + 1);
2765
+ if (next === '"' || next === "\\" || next === "$" || next === "`" || next === "\n") {
2766
+ current += next;
2767
+ i++;
2768
+ } else {
2769
+ current += ch;
2770
+ }
2771
+ } else if (ch === '"') {
2772
+ inDouble = false;
2773
+ } else {
2774
+ current += ch;
2775
+ }
2776
+ } else if (ch === "'") {
2777
+ inSingle = true;
2778
+ } else if (ch === '"') {
2779
+ inDouble = true;
2780
+ } else if (ch === " " || ch === " ") {
2781
+ if (current) {
2782
+ tokens.push(current);
2783
+ current = "";
2784
+ }
2785
+ } else {
2786
+ current += ch;
2787
+ }
2788
+ i++;
2789
+ }
2790
+ if (inSingle || inDouble) {
2791
+ return s.split(/\s+/).filter(Boolean).map((t) => t.replace(/^['"]|['"]$/g, ""));
2792
+ }
2793
+ if (current) tokens.push(current);
2794
+ return tokens;
2795
+ } catch {
2796
+ return s.split(/\s+/).filter(Boolean).map((t) => t.replace(/^['"]|['"]$/g, ""));
2797
+ }
2798
+ }
2799
+ var _PATH_ARG_KEYS = /* @__PURE__ */ new Set([
2800
+ "path",
2801
+ "file_path",
2802
+ "filePath",
2803
+ "directory",
2804
+ "dir",
2805
+ "folder",
2806
+ "target",
2807
+ "destination",
2808
+ "source",
2809
+ "src",
2810
+ "dst"
2811
+ ]);
2812
+ function _realpath(p) {
2813
+ try {
2814
+ return (0, import_node_fs.realpathSync)(p);
2815
+ } catch {
2816
+ return (0, import_node_path.resolve)(p);
2817
+ }
2818
+ }
2819
+ function extractPaths(envelope) {
2820
+ const paths = [];
2821
+ const seen = /* @__PURE__ */ new Set();
2822
+ function add(p) {
2823
+ if (!p) return;
2824
+ const resolved = _realpath(p);
2825
+ if (!seen.has(resolved)) {
2826
+ seen.add(resolved);
2827
+ paths.push(resolved);
2828
+ }
2829
+ }
2830
+ if (envelope.filePath) add(envelope.filePath);
2831
+ const args = envelope.args;
2832
+ for (const [key, value] of Object.entries(args)) {
2833
+ if (typeof value === "string" && _PATH_ARG_KEYS.has(key)) add(value);
2834
+ }
2835
+ for (const [key, value] of Object.entries(args)) {
2836
+ if (typeof value === "string" && value.startsWith("/") && !_PATH_ARG_KEYS.has(key)) add(value);
2837
+ }
2838
+ const cmd = envelope.bashCommand ?? args.command ?? "";
2839
+ if (cmd) {
2840
+ for (const token of tokenizeCommand(cmd)) {
2841
+ if (token.startsWith("/")) add(token);
2842
+ }
2843
+ }
2844
+ return paths;
2845
+ }
2846
+ function extractCommand(envelope) {
2847
+ const cmd = envelope.bashCommand ?? envelope.args.command;
2848
+ if (!cmd || typeof cmd !== "string") return null;
2849
+ const stripped = cmd.trim();
2850
+ if (!stripped) return null;
2851
+ if (_SHELL_SEPARATOR_RE.test(stripped)) return "\0";
2852
+ const rawFirst = stripped.split(/\s/)[0] ?? "";
2853
+ if (_REDIRECT_PREFIX_RE.test(rawFirst)) return "\0";
2854
+ const tokens = tokenizeCommand(stripped);
2855
+ return tokens.length > 0 ? tokens[0] ?? null : null;
2856
+ }
2857
+ function extractUrls(envelope) {
2858
+ const urls = [];
2859
+ const seen = /* @__PURE__ */ new Set();
2860
+ function addUrl(u) {
2861
+ if (!seen.has(u)) {
2862
+ seen.add(u);
2863
+ urls.push(u);
2864
+ }
2865
+ }
2866
+ for (const value of Object.values(envelope.args)) {
2867
+ if (typeof value !== "string" || !value.includes("://")) continue;
2868
+ if (extractHostname(value) !== null) {
2869
+ addUrl(value);
2870
+ } else {
2871
+ for (const token of tokenizeCommand(value)) {
2872
+ if (token.includes("://") && extractHostname(token) !== null) addUrl(token);
2873
+ }
2874
+ }
2875
+ }
2876
+ return urls;
2877
+ }
2878
+ function extractHostname(url) {
2879
+ try {
2880
+ return new URL(url).hostname || null;
2881
+ } catch {
2882
+ return null;
2883
+ }
2884
+ }
2885
+ function domainMatches(hostname, patterns) {
2886
+ return patterns.some((p) => fnmatch(hostname, p));
2887
+ }
2888
+
2889
+ // src/yaml-engine/sandbox-compile-fn.ts
2890
+ var import_node_fs2 = require("fs");
2891
+ var import_node_path2 = require("path");
2892
+ function _realpath2(p) {
2893
+ try {
2894
+ return (0, import_node_fs2.realpathSync)(p);
2895
+ } catch {
2896
+ return (0, import_node_path2.resolve)(p);
2897
+ }
2898
+ }
2899
+ function _pathWithin(filePath, prefix) {
2900
+ return filePath === prefix || filePath.startsWith(prefix.replace(/\/+$/, "") + "/");
2901
+ }
2902
+ function compileSandbox(contract, mode) {
2903
+ const contractId = contract.id;
2904
+ const toolPatterns = "tools" in contract ? contract.tools : [contract.tool];
2905
+ const within = (contract.within ?? []).map(_realpath2);
2906
+ const notWithin = (contract.not_within ?? []).map(_realpath2);
2907
+ const allows = contract.allows ?? {};
2908
+ const notAllows = contract.not_allows ?? {};
2909
+ const allowedCommands = allows.commands ?? [];
2910
+ const allowedDomains = allows.domains ?? [];
2911
+ const blockedDomains = notAllows.domains ?? [];
2912
+ const outside = contract.outside ?? "deny";
2913
+ const messageTemplate = contract.message ?? "Tool call outside sandbox boundary.";
2914
+ const timeout = contract.timeout ?? 300;
2915
+ const timeoutEffect = contract.timeout_effect ?? "deny";
2916
+ const check = (envelope) => {
2917
+ if (within.length > 0 || notWithin.length > 0) {
2918
+ const paths = extractPaths(envelope);
2919
+ if (paths.length > 0) {
2920
+ for (const p of paths) {
2921
+ for (const excluded of notWithin) {
2922
+ if (_pathWithin(p, excluded)) {
2923
+ return Verdict.fail(expandMessage(messageTemplate, envelope));
2924
+ }
2925
+ }
2926
+ }
2927
+ if (within.length > 0) {
2928
+ for (const p of paths) {
2929
+ if (!within.some((allowed) => _pathWithin(p, allowed))) {
2930
+ return Verdict.fail(expandMessage(messageTemplate, envelope));
2931
+ }
2932
+ }
2933
+ }
2934
+ }
2935
+ }
2936
+ if (allowedCommands.length > 0) {
2937
+ const firstToken = extractCommand(envelope);
2938
+ if (firstToken !== null && !allowedCommands.includes(firstToken)) {
2939
+ return Verdict.fail(expandMessage(messageTemplate, envelope));
2940
+ }
2941
+ }
2942
+ const urls = extractUrls(envelope);
2943
+ if (urls.length > 0) {
2944
+ for (const url of urls) {
2945
+ const hostname = extractHostname(url);
2946
+ if (hostname) {
2947
+ if (blockedDomains.length > 0 && domainMatches(hostname, blockedDomains)) {
2948
+ return Verdict.fail(expandMessage(messageTemplate, envelope));
2949
+ }
2950
+ if (allowedDomains.length > 0 && !domainMatches(hostname, allowedDomains)) {
2951
+ return Verdict.fail(expandMessage(messageTemplate, envelope));
2952
+ }
2953
+ } else if (allowedDomains.length > 0) {
2954
+ return Verdict.fail(expandMessage(messageTemplate, envelope));
2955
+ }
2956
+ }
2957
+ }
2958
+ return Verdict.pass_();
2959
+ };
2960
+ const result = {
2961
+ check,
2962
+ name: contractId,
2963
+ tool: toolPatterns.length === 1 ? toolPatterns[0] : void 0,
2964
+ _edictum_type: "sandbox",
2965
+ _edictum_tools: toolPatterns,
2966
+ _edictum_mode: mode,
2967
+ _edictum_id: contractId,
2968
+ _edictum_source: "yaml_sandbox",
2969
+ _edictum_effect: outside,
2970
+ _edictum_timeout: timeout,
2971
+ _edictum_timeout_effect: timeoutEffect
2972
+ };
2973
+ if (contract._observe === true || contract._shadow === true) {
2974
+ result._edictum_observe = true;
2975
+ }
2976
+ return result;
2977
+ }
2978
+
2979
+ // src/yaml-engine/compiler.ts
2980
+ function compileContracts(bundle, options = {}) {
2981
+ const customOps = options?.customOperators ?? null;
2982
+ const customSels = options?.customSelectors ?? null;
2983
+ validateOperators(bundle, customOps);
2984
+ if (bundle.defaults == null || typeof bundle.defaults !== "object") {
2985
+ throw new EdictumConfigError(
2986
+ "Bundle missing required 'defaults' section with 'mode' field"
2987
+ );
2988
+ }
2989
+ const defaults = bundle.defaults;
2990
+ const defaultMode = defaults.mode;
2991
+ const preconditions = [];
2992
+ const postconditions = [];
2993
+ const sessionContracts = [];
2994
+ const sandboxContracts = [];
2995
+ let limits = { ...DEFAULT_LIMITS };
2996
+ const contracts = bundle.contracts ?? [];
2997
+ for (const contract of contracts) {
2998
+ if (contract.enabled === false) continue;
2999
+ const contractType = contract.type;
3000
+ const contractMode = contract.mode ?? defaultMode;
3001
+ if (contractType === "pre") {
3002
+ preconditions.push(compilePre(contract, contractMode, customOps, customSels));
3003
+ } else if (contractType === "post") {
3004
+ postconditions.push(compilePost(contract, contractMode, customOps, customSels));
3005
+ } else if (contractType === "session") {
3006
+ const isObserve = contract._observe ?? contract._shadow ?? false;
3007
+ if (!isObserve) {
3008
+ limits = mergeSessionLimits(contract, limits);
3009
+ }
3010
+ sessionContracts.push(compileSession(contract, contractMode, limits));
3011
+ } else if (contractType === "sandbox") {
3012
+ sandboxContracts.push(compileSandbox(contract, contractMode));
3013
+ } else {
3014
+ throw new EdictumConfigError(
3015
+ `Unknown contract type "${contractType}" in contract "${contract.id ?? "unknown"}". Expected "pre", "post", "session", or "sandbox".`
3016
+ );
3017
+ }
3018
+ }
3019
+ const tools = bundle.tools ?? {};
3020
+ return {
3021
+ preconditions,
3022
+ postconditions,
3023
+ sessionContracts,
3024
+ sandboxContracts,
3025
+ limits,
3026
+ defaultMode,
3027
+ tools
3028
+ };
3029
+ }
3030
+
3031
+ // src/yaml-engine/loader.ts
3032
+ var import_node_crypto3 = require("crypto");
3033
+ var import_node_fs3 = require("fs");
3034
+ init_errors();
3035
+
3036
+ // src/yaml-engine/loader-validators.ts
3037
+ init_errors();
3038
+ function validateSchema(data) {
3039
+ if (data.apiVersion !== "edictum/v1") {
3040
+ throw new EdictumConfigError(
3041
+ `Schema validation failed: apiVersion must be 'edictum/v1', got '${String(data.apiVersion)}'`
3042
+ );
3043
+ }
3044
+ if (data.kind !== "ContractBundle") {
3045
+ throw new EdictumConfigError(
3046
+ `Schema validation failed: kind must be 'ContractBundle', got '${String(data.kind)}'`
3047
+ );
3048
+ }
3049
+ if (data.metadata != null && typeof data.metadata !== "object") {
3050
+ throw new EdictumConfigError("Schema validation failed: metadata must be an object");
3051
+ }
3052
+ if (!Array.isArray(data.contracts)) {
3053
+ throw new EdictumConfigError("Schema validation failed: contracts must be an array");
3054
+ }
3055
+ }
3056
+ var CONTROL_CHAR_RE = /[\x00-\x1f\x7f-\x9f]/;
3057
+ function validateContractId(contractId) {
3058
+ if (CONTROL_CHAR_RE.test(contractId)) {
3059
+ throw new EdictumConfigError(
3060
+ `Contract id contains control characters: '${contractId.replace(CONTROL_CHAR_RE, "\\x??")}'`
3061
+ );
3062
+ }
3063
+ }
3064
+ function validateUniqueIds(data) {
3065
+ const ids = /* @__PURE__ */ new Set();
3066
+ const contracts = data.contracts ?? [];
3067
+ for (const contract of contracts) {
3068
+ const contractId = contract.id;
3069
+ if (contractId != null) {
3070
+ validateContractId(contractId);
3071
+ if (ids.has(contractId)) {
3072
+ throw new EdictumConfigError(`Duplicate contract id: '${contractId}'`);
3073
+ }
3074
+ ids.add(contractId);
3075
+ }
3076
+ }
3077
+ }
3078
+ function validateRegexes(data) {
3079
+ const contracts = data.contracts ?? [];
3080
+ for (const contract of contracts) {
3081
+ const when = contract.when;
3082
+ if (when != null) {
3083
+ validateExpressionRegexes(when);
3084
+ }
3085
+ }
3086
+ }
3087
+ function validateExpressionRegexes(expr) {
3088
+ if (expr == null || typeof expr !== "object") return;
3089
+ const e = expr;
3090
+ if ("all" in e) {
3091
+ for (const sub of e.all) validateExpressionRegexes(sub);
3092
+ return;
3093
+ }
3094
+ if ("any" in e) {
3095
+ for (const sub of e.any) validateExpressionRegexes(sub);
3096
+ return;
3097
+ }
3098
+ if ("not" in e) {
3099
+ validateExpressionRegexes(e.not);
3100
+ return;
3101
+ }
3102
+ for (const operator of Object.values(e)) {
3103
+ if (operator == null || typeof operator !== "object") continue;
3104
+ const op = operator;
3105
+ if ("matches" in op) tryCompileRegex(op.matches);
3106
+ if ("matches_any" in op) {
3107
+ for (const pattern of op.matches_any) tryCompileRegex(pattern);
3108
+ }
3109
+ }
3110
+ }
3111
+ function tryCompileRegex(pattern) {
3112
+ try {
3113
+ new RegExp(pattern);
3114
+ } catch (e) {
3115
+ throw new EdictumConfigError(`Invalid regex pattern '${pattern}': ${String(e)}`);
3116
+ }
3117
+ }
3118
+ function validatePreSelectors(data) {
3119
+ const contracts = data.contracts ?? [];
3120
+ for (const contract of contracts) {
3121
+ if (contract.type !== "pre") continue;
3122
+ const when = contract.when;
3123
+ if (when != null && expressionHasSelector(when, "output.text")) {
3124
+ throw new EdictumConfigError(
3125
+ `Contract '${contract.id ?? "?"}': output.text selector is not available in type: pre contracts`
3126
+ );
3127
+ }
3128
+ }
3129
+ }
3130
+ function expressionHasSelector(expr, target) {
3131
+ if (expr == null || typeof expr !== "object") return false;
3132
+ const e = expr;
3133
+ if ("all" in e) return e.all.some((sub) => expressionHasSelector(sub, target));
3134
+ if ("any" in e) return e.any.some((sub) => expressionHasSelector(sub, target));
3135
+ if ("not" in e) return expressionHasSelector(e.not, target);
3136
+ return target in e;
3137
+ }
3138
+ function validateSandboxContracts(data) {
3139
+ const contracts = data.contracts ?? [];
3140
+ for (const contract of contracts) {
3141
+ if (contract.type !== "sandbox") continue;
3142
+ const cid = contract.id ?? "?";
3143
+ if ("not_within" in contract && !("within" in contract)) {
3144
+ throw new EdictumConfigError(`Contract '${cid}': not_within requires within to also be set`);
3145
+ }
3146
+ if ("not_allows" in contract && !("allows" in contract)) {
3147
+ throw new EdictumConfigError(`Contract '${cid}': not_allows requires allows to also be set`);
3148
+ }
3149
+ if ("not_allows" in contract) {
3150
+ const notAllows = contract.not_allows ?? {};
3151
+ if ("domains" in notAllows) {
3152
+ const allows = contract.allows ?? {};
3153
+ if (!("domains" in allows)) {
3154
+ throw new EdictumConfigError(
3155
+ `Contract '${cid}': not_allows.domains requires allows.domains to also be set`
3156
+ );
3157
+ }
3158
+ }
3159
+ }
3160
+ }
3161
+ }
3162
+
3163
+ // src/yaml-engine/loader.ts
3164
+ var MAX_BUNDLE_SIZE = 1048576;
3165
+ function computeHash(rawBytes) {
3166
+ return { hex: (0, import_node_crypto3.createHash)("sha256").update(rawBytes).digest("hex") };
3167
+ }
3168
+ function requireYaml() {
3169
+ try {
3170
+ const yaml = require("js-yaml");
3171
+ return yaml;
3172
+ } catch {
3173
+ throw new EdictumConfigError(
3174
+ "The YAML engine requires js-yaml. Install it with: npm install js-yaml"
3175
+ );
3176
+ }
3177
+ }
3178
+ function parseYaml(content) {
3179
+ const yaml = requireYaml();
3180
+ let data;
3181
+ try {
3182
+ data = yaml.load(content);
3183
+ } catch (e) {
3184
+ throw new EdictumConfigError(`YAML parse error: ${String(e)}`);
3185
+ }
3186
+ if (data == null || typeof data !== "object" || Array.isArray(data)) {
3187
+ throw new EdictumConfigError("YAML document must be a mapping");
3188
+ }
3189
+ return data;
3190
+ }
3191
+ function validateBundle(data) {
3192
+ validateSchema(data);
3193
+ validateUniqueIds(data);
3194
+ validateRegexes(data);
3195
+ validatePreSelectors(data);
3196
+ validateSandboxContracts(data);
3197
+ }
3198
+ function loadBundle(source) {
3199
+ const resolved = (0, import_node_fs3.realpathSync)(source);
3200
+ const fileSize = (0, import_node_fs3.statSync)(resolved).size;
3201
+ if (fileSize > MAX_BUNDLE_SIZE) {
3202
+ throw new EdictumConfigError(
3203
+ `Bundle file too large (${fileSize} bytes, max ${MAX_BUNDLE_SIZE})`
3204
+ );
3205
+ }
3206
+ const rawBytes = (0, import_node_fs3.readFileSync)(resolved);
3207
+ const bundleHash = computeHash(rawBytes);
3208
+ const data = parseYaml(rawBytes.toString("utf-8"));
3209
+ validateBundle(data);
3210
+ return [data, bundleHash];
3211
+ }
3212
+ function loadBundleString(content) {
3213
+ const rawBytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
3214
+ if (rawBytes.length > MAX_BUNDLE_SIZE) {
3215
+ throw new EdictumConfigError(
3216
+ `Bundle content too large (${rawBytes.length} bytes, max ${MAX_BUNDLE_SIZE})`
3217
+ );
3218
+ }
3219
+ const bundleHash = computeHash(rawBytes);
3220
+ const text = typeof content === "string" ? content : new TextDecoder().decode(rawBytes);
3221
+ const data = parseYaml(text);
3222
+ validateBundle(data);
3223
+ return [data, bundleHash];
3224
+ }
3225
+
3226
+ // src/factory.ts
3227
+ function fromYaml(...args) {
3228
+ let paths;
3229
+ let options;
3230
+ const last = args[args.length - 1];
3231
+ if (typeof last === "object" && last !== null && !Array.isArray(last)) {
3232
+ paths = args.slice(0, -1);
3233
+ options = last;
3234
+ } else {
3235
+ paths = args;
3236
+ options = {};
3237
+ }
3238
+ if (paths.length === 0) {
3239
+ throw new EdictumConfigError("fromYaml() requires at least one path");
3240
+ }
3241
+ const loaded = [];
3242
+ for (const p of paths) {
3243
+ loaded.push(loadBundle(p));
3244
+ }
3245
+ let bundleData;
3246
+ let policyVersion;
3247
+ let report;
3248
+ if (loaded.length === 1) {
3249
+ const entry = loaded[0];
3250
+ bundleData = entry[0];
3251
+ policyVersion = entry[1].hex;
3252
+ report = { overriddenContracts: [], observeContracts: [] };
3253
+ } else {
3254
+ const bundleTuples = loaded.map(
3255
+ ([data], i) => [data, paths[i]]
3256
+ );
3257
+ const composed = composeBundles(...bundleTuples);
3258
+ bundleData = composed.bundle;
3259
+ report = composed.report;
3260
+ policyVersion = (0, import_node_crypto4.createHash)("sha256").update(loaded.map(([, h]) => h.hex).join(":")).digest("hex");
3261
+ }
3262
+ const compiled = compileContracts(bundleData, {
3263
+ customOperators: options.customOperators ?? null,
3264
+ customSelectors: options.customSelectors ?? null
3265
+ });
3266
+ const guard = _buildGuard(compiled, policyVersion, options);
3267
+ if (options.returnReport) {
3268
+ return [guard, report];
3269
+ }
3270
+ return guard;
3271
+ }
3272
+ function fromYamlString(content, options = {}) {
3273
+ const [bundleData, bundleHash] = loadBundleString(content);
3274
+ const policyVersion = bundleHash.hex;
3275
+ const compiled = compileContracts(bundleData, {
3276
+ customOperators: options.customOperators ?? null,
3277
+ customSelectors: options.customSelectors ?? null
3278
+ });
3279
+ return _buildGuard(compiled, policyVersion, options);
3280
+ }
3281
+ function reload(guard, yamlContent, options = {}) {
3282
+ const [bundleData, bundleHash] = loadBundleString(yamlContent);
3283
+ const compiled = compileContracts(bundleData, {
3284
+ customOperators: options.customOperators ?? null,
3285
+ customSelectors: options.customSelectors ?? null
3286
+ });
3287
+ const allContracts = [
3288
+ ...compiled.preconditions,
3289
+ ...compiled.postconditions,
3290
+ ...compiled.sessionContracts,
3291
+ ...compiled.sandboxContracts
3292
+ ];
3293
+ const temp = new Edictum({
3294
+ contracts: allContracts,
3295
+ limits: compiled.limits,
3296
+ policyVersion: bundleHash.hex
3297
+ });
3298
+ guard._replaceState(temp._getState());
3299
+ }
3300
+ function _buildGuard(compiled, policyVersion, options) {
3301
+ const effectiveMode = options.mode ?? compiled.defaultMode;
3302
+ const allContracts = [
3303
+ ...compiled.preconditions,
3304
+ ...compiled.postconditions,
3305
+ ...compiled.sessionContracts,
3306
+ ...compiled.sandboxContracts
3307
+ ];
3308
+ const mergedTools = {};
3309
+ for (const [name, cfg] of Object.entries(compiled.tools)) {
3310
+ mergedTools[name] = cfg;
3311
+ }
3312
+ if (options.tools) {
3313
+ for (const [name, cfg] of Object.entries(options.tools)) {
3314
+ mergedTools[name] = cfg;
3315
+ }
3316
+ }
3317
+ return new Edictum({
3318
+ environment: options.environment ?? "production",
3319
+ mode: effectiveMode,
3320
+ limits: compiled.limits,
3321
+ tools: Object.keys(mergedTools).length > 0 ? mergedTools : void 0,
3322
+ contracts: allContracts,
3323
+ auditSink: options.auditSink,
3324
+ redaction: options.redaction,
3325
+ backend: options.backend,
3326
+ policyVersion,
3327
+ onDeny: options.onDeny,
3328
+ onAllow: options.onAllow,
3329
+ successCheck: options.successCheck,
3330
+ principal: options.principal,
3331
+ principalResolver: options.principalResolver,
3332
+ approvalBackend: options.approvalBackend
3333
+ });
3334
+ }
3335
+
3336
+ // src/guard.ts
3337
+ init_audit();
3338
+ init_errors();
3339
+ init_envelope();
3340
+ init_redaction();
3341
+ function isSessionContract(c) {
3342
+ return !("tool" in c);
3343
+ }
3344
+ var Edictum = class _Edictum {
3345
+ environment;
3346
+ mode;
3347
+ backend;
3348
+ redaction;
3349
+ toolRegistry;
3350
+ auditSink;
3351
+ _localSink;
3352
+ _state;
3353
+ _beforeHooks;
3354
+ _afterHooks;
3355
+ _sessionId;
3356
+ // Callbacks and resolution — not private because _runner.ts needs access
3357
+ // (Python's _runner.py accesses self._on_deny etc. directly)
3358
+ /** @internal */
3359
+ _onDeny;
3360
+ /** @internal */
3361
+ _onAllow;
3362
+ /** @internal */
3363
+ _successCheck;
3364
+ _principal;
3365
+ _principalResolver;
3366
+ /** @internal */
3367
+ _approvalBackend;
3368
+ constructor(options = {}) {
3369
+ this.environment = options.environment ?? "production";
3370
+ this.mode = options.mode ?? "enforce";
3371
+ this.backend = options.backend ?? new MemoryBackend();
3372
+ this.redaction = options.redaction ?? new RedactionPolicy();
3373
+ this._onDeny = options.onDeny ?? null;
3374
+ this._onAllow = options.onAllow ?? null;
3375
+ this._successCheck = options.successCheck ?? null;
3376
+ this._principal = options.principal ?? null;
3377
+ this._principalResolver = options.principalResolver ?? null;
3378
+ this._approvalBackend = options.approvalBackend ?? null;
3379
+ this._localSink = new CollectingAuditSink();
3380
+ if (Array.isArray(options.auditSink)) {
3381
+ this.auditSink = new CompositeSink([
3382
+ this._localSink,
3383
+ ...options.auditSink
3384
+ ]);
3385
+ } else if (options.auditSink != null) {
3386
+ this.auditSink = new CompositeSink([
3387
+ this._localSink,
3388
+ options.auditSink
3389
+ ]);
3390
+ } else {
3391
+ this.auditSink = this._localSink;
3392
+ }
3393
+ this.toolRegistry = new ToolRegistry();
3394
+ if (options.tools) {
3395
+ for (const [name, config] of Object.entries(options.tools)) {
3396
+ this.toolRegistry.register(
3397
+ name,
3398
+ config.side_effect ?? SideEffect.IRREVERSIBLE,
3399
+ config.idempotent ?? false
3400
+ );
3401
+ }
3402
+ }
3403
+ this._state = _Edictum._classifyContracts(
3404
+ options.contracts ?? [],
3405
+ options.limits ?? DEFAULT_LIMITS,
3406
+ options.policyVersion ?? null
3407
+ );
3408
+ this._beforeHooks = [];
3409
+ this._afterHooks = [];
3410
+ for (const item of options.hooks ?? []) {
3411
+ this._registerHook(item);
3412
+ }
3413
+ this._sessionId = (0, import_node_crypto5.randomUUID)();
3414
+ }
3415
+ // -----------------------------------------------------------------------
3416
+ // Properties
3417
+ // -----------------------------------------------------------------------
3418
+ /** The local in-memory audit event collector. Always present. */
3419
+ get localSink() {
3420
+ return this._localSink;
3421
+ }
3422
+ /** Operation limits for the current contract set. */
3423
+ get limits() {
3424
+ return this._state.limits;
3425
+ }
3426
+ /** Update operation limits (replaces compiled state atomically). */
3427
+ set limits(value) {
3428
+ this._state = createCompiledState({ ...this._state, limits: value });
3429
+ }
3430
+ /** SHA256 hash identifying the active contract bundle. */
3431
+ get policyVersion() {
3432
+ return this._state.policyVersion;
3433
+ }
3434
+ /**
3435
+ * Replace the compiled state atomically.
3436
+ *
3437
+ * @internal — used by factory.ts reload(). Not part of the public API.
3438
+ */
3439
+ _replaceState(newState) {
3440
+ this._state = newState;
3441
+ }
3442
+ /**
3443
+ * Read the current compiled state.
3444
+ *
3445
+ * @internal — used by factory.ts reload(). Not part of the public API.
3446
+ */
3447
+ _getState() {
3448
+ return this._state;
3449
+ }
3450
+ /** Update policy version (replaces compiled state atomically). */
3451
+ set policyVersion(value) {
3452
+ this._state = createCompiledState({
3453
+ ...this._state,
3454
+ policyVersion: value
3455
+ });
3456
+ }
3457
+ /** The persistent session ID for this guard instance. */
3458
+ get sessionId() {
3459
+ return this._sessionId;
3460
+ }
3461
+ // -----------------------------------------------------------------------
3462
+ // Principal
3463
+ // -----------------------------------------------------------------------
3464
+ /** Update the principal used for subsequent tool calls. */
3465
+ setPrincipal(principal) {
3466
+ this._principal = principal;
3467
+ }
3468
+ /** Resolve the principal for a tool call. */
3469
+ _resolvePrincipal(toolName, toolInput) {
3470
+ if (this._principalResolver != null) {
3471
+ return this._principalResolver(toolName, toolInput);
3472
+ }
3473
+ return this._principal;
3474
+ }
3475
+ // -----------------------------------------------------------------------
3476
+ // Hooks
3477
+ // -----------------------------------------------------------------------
3478
+ _registerHook(item) {
3479
+ if (item.phase === "before") {
3480
+ this._beforeHooks.push(item);
3481
+ } else {
3482
+ this._afterHooks.push(item);
3483
+ }
3484
+ }
3485
+ getHooks(phase, envelope) {
3486
+ const hooks = phase === "before" ? this._beforeHooks : this._afterHooks;
3487
+ return hooks.filter(
3488
+ (h) => h.tool === "*" || fnmatch(envelope.toolName, h.tool)
3489
+ );
3490
+ }
3491
+ // -----------------------------------------------------------------------
3492
+ // Contract accessors -- enforce mode
3493
+ // -----------------------------------------------------------------------
3494
+ getPreconditions(envelope) {
3495
+ return _Edictum._filterByTool(
3496
+ this._state.preconditions,
3497
+ envelope
3498
+ );
3499
+ }
3500
+ getPostconditions(envelope) {
3501
+ return _Edictum._filterByTool(
3502
+ this._state.postconditions,
3503
+ envelope
3504
+ );
3505
+ }
3506
+ getSessionContracts() {
3507
+ return [...this._state.sessionContracts];
3508
+ }
3509
+ getSandboxContracts(envelope) {
3510
+ return _Edictum._filterSandbox(
3511
+ this._state.sandboxContracts,
3512
+ envelope
3513
+ );
3514
+ }
3515
+ // -----------------------------------------------------------------------
3516
+ // Contract accessors -- observe mode
3517
+ // -----------------------------------------------------------------------
3518
+ getObservePreconditions(envelope) {
3519
+ return _Edictum._filterByTool(
3520
+ this._state.observePreconditions,
3521
+ envelope
3522
+ );
3523
+ }
3524
+ getObservePostconditions(envelope) {
3525
+ return _Edictum._filterByTool(
3526
+ this._state.observePostconditions,
3527
+ envelope
3528
+ );
3529
+ }
3530
+ getObserveSessionContracts() {
3531
+ return [...this._state.observeSessionContracts];
3532
+ }
3533
+ getObserveSandboxContracts(envelope) {
3534
+ return _Edictum._filterSandbox(
3535
+ this._state.observeSandboxContracts,
3536
+ envelope
3537
+ );
3538
+ }
3539
+ // -----------------------------------------------------------------------
3540
+ // Private helpers
3541
+ // -----------------------------------------------------------------------
3542
+ /**
3543
+ * Classify user-facing and internal contracts into enforce/observe lists.
3544
+ *
3545
+ * User-facing contracts (Precondition, Postcondition, SessionContract)
3546
+ * are converted to internal representations. Internal contracts (from
3547
+ * YAML compiler) carry _edictum_* metadata and are classified by their
3548
+ * _edictum_observe flag (Python uses _edictum_shadow — wire-format parity).
3549
+ */
3550
+ static _classifyContracts(contracts, limits, policyVersion) {
3551
+ const pre = [];
3552
+ const post = [];
3553
+ const session = [];
3554
+ const sandbox = [];
3555
+ const oPre = [];
3556
+ const oPost = [];
3557
+ const oSession = [];
3558
+ const oSandbox = [];
3559
+ for (const item of contracts) {
3560
+ const raw = item;
3561
+ const edictumType = raw._edictum_type;
3562
+ const isObserve = raw._edictum_observe ?? raw._edictum_shadow ?? false;
3563
+ if (edictumType != null) {
3564
+ _Edictum._classifyInternal(
3565
+ raw,
3566
+ edictumType,
3567
+ isObserve,
3568
+ { pre, post, session, sandbox, oPre, oPost, oSession, oSandbox }
3569
+ );
3570
+ } else if (isSessionContract(item)) {
3571
+ const name = raw.name ?? "anonymous";
3572
+ session.push({
3573
+ type: "session_contract",
3574
+ name,
3575
+ check: item.check
3576
+ });
3577
+ } else if ("tool" in item && item.contractType === "post") {
3578
+ const postItem = item;
3579
+ const name = raw.name ?? "anonymous";
3580
+ post.push({
3581
+ type: "postcondition",
3582
+ name,
3583
+ tool: postItem.tool,
3584
+ check: postItem.check,
3585
+ when: postItem.when
3586
+ });
3587
+ } else if ("tool" in item) {
3588
+ const ct = raw.contractType;
3589
+ if (ct != null && ct !== "pre") {
3590
+ throw new EdictumConfigError(
3591
+ `Contract with tool "${item.tool}" has unknown contractType "${String(ct)}". Expected "pre" or omitted for Precondition, "post" for Postcondition.`
3592
+ );
3593
+ }
3594
+ if (ct == null && item.check.length >= 2) {
3595
+ throw new EdictumConfigError(
3596
+ `Contract with tool "${item.tool}" has a check function with ${item.check.length} parameters (looks like a Postcondition) but is missing contractType: "post". Add it to prevent misclassification.`
3597
+ );
3598
+ }
3599
+ const preItem = item;
3600
+ const name = raw.name ?? "anonymous";
3601
+ pre.push({
3602
+ type: "precondition",
3603
+ name,
3604
+ tool: preItem.tool,
3605
+ check: preItem.check,
3606
+ when: preItem.when
3607
+ });
3608
+ }
3609
+ }
3610
+ return createCompiledState({
3611
+ preconditions: pre,
3612
+ postconditions: post,
3613
+ sessionContracts: session,
3614
+ sandboxContracts: sandbox,
3615
+ observePreconditions: oPre,
3616
+ observePostconditions: oPost,
3617
+ observeSessionContracts: oSession,
3618
+ observeSandboxContracts: oSandbox,
3619
+ limits,
3620
+ policyVersion
3621
+ });
3622
+ }
3623
+ /** Route an internal contract to the appropriate enforce/observe list. */
3624
+ static _classifyInternal(raw, edictumType, isObserve, lists) {
3625
+ const target = isObserve ? { pre: lists.oPre, post: lists.oPost, session: lists.oSession, sandbox: lists.oSandbox } : { pre: lists.pre, post: lists.post, session: lists.session, sandbox: lists.sandbox };
3626
+ if (edictumType === "precondition") target.pre.push(raw);
3627
+ else if (edictumType === "postcondition") target.post.push(raw);
3628
+ else if (edictumType === "session_contract") target.session.push(raw);
3629
+ else if (edictumType === "sandbox") target.sandbox.push(raw);
3630
+ else {
3631
+ throw new EdictumConfigError(
3632
+ `Unknown _edictum_type "${edictumType}". Expected "precondition", "postcondition", "session_contract", or "sandbox".`
3633
+ );
3634
+ }
3635
+ }
3636
+ /** Filter contracts by tool pattern and optional `when` guard. */
3637
+ static _filterByTool(contracts, envelope) {
3638
+ const result = [];
3639
+ for (const p of contracts) {
3640
+ const tool = p.tool ?? "*";
3641
+ const when = p.when ?? null;
3642
+ if (tool !== "*" && !fnmatch(envelope.toolName, tool)) {
3643
+ continue;
3644
+ }
3645
+ if (when != null) {
3646
+ try {
3647
+ if (!when(envelope)) continue;
3648
+ } catch {
3649
+ }
3650
+ }
3651
+ result.push(p);
3652
+ }
3653
+ return result;
3654
+ }
3655
+ /** Filter sandbox contracts by tool patterns array. */
3656
+ static _filterSandbox(contracts, envelope) {
3657
+ const result = [];
3658
+ for (const s of contracts) {
3659
+ const tools = s.tools ?? ["*"];
3660
+ if (tools.some((p) => fnmatch(envelope.toolName, p))) {
3661
+ result.push(s);
3662
+ }
3663
+ }
3664
+ return result;
3665
+ }
3666
+ // -----------------------------------------------------------------------
3667
+ // Delegated methods — run, evaluate, evaluateBatch
3668
+ // -----------------------------------------------------------------------
3669
+ /** Execute a tool call with full governance pipeline. */
3670
+ async run(toolName, args, toolCallable, options) {
3671
+ const { run: run2 } = await Promise.resolve().then(() => (init_runner(), runner_exports));
3672
+ return run2(this, toolName, args, toolCallable, options);
3673
+ }
3674
+ /**
3675
+ * Dry-run evaluation of a tool call against all matching contracts.
3676
+ *
3677
+ * Never executes the tool. Evaluates exhaustively (no short-circuit).
3678
+ * Session contracts are skipped.
3679
+ */
3680
+ evaluate(toolName, args, options) {
3681
+ return Promise.resolve().then(() => (init_dry_run(), dry_run_exports)).then(
3682
+ ({ evaluate: evaluate2 }) => evaluate2(this, toolName, args, options)
3683
+ );
3684
+ }
3685
+ /** Evaluate a batch of tool calls. Thin wrapper over evaluate(). */
3686
+ evaluateBatch(calls) {
3687
+ return Promise.resolve().then(() => (init_dry_run(), dry_run_exports)).then(
3688
+ ({ evaluateBatch: evaluateBatch2 }) => evaluateBatch2(this, calls)
3689
+ );
3690
+ }
3691
+ static fromYaml(...args) {
3692
+ return fromYaml(...args);
3693
+ }
3694
+ /**
3695
+ * Create an Edictum instance from a YAML string or Uint8Array.
3696
+ */
3697
+ static fromYamlString(content, options) {
3698
+ return fromYamlString(content, options);
3699
+ }
3700
+ /**
3701
+ * Atomically replace this guard's contracts from a YAML string.
3702
+ *
3703
+ * Pass customOperators/customSelectors if the new YAML uses custom
3704
+ * operators or selectors that were passed to fromYaml/fromYamlString.
3705
+ */
3706
+ reload(yamlContent, options) {
3707
+ reload(this, yamlContent, options);
3708
+ }
3709
+ };
3710
+
3711
+ // src/index.ts
3712
+ init_runner();
3713
+ var VERSION = "0.1.0";
3714
+ // Annotate the CommonJS export names for ESM import in node:
3715
+ 0 && (module.exports = {
3716
+ ApprovalStatus,
3717
+ AuditAction,
3718
+ BUILTIN_OPERATOR_NAMES,
3719
+ BUILTIN_SELECTOR_PREFIXES,
3720
+ BashClassifier,
3721
+ CollectingAuditSink,
3722
+ CompositeSink,
3723
+ DEFAULT_LIMITS,
3724
+ Edictum,
3725
+ EdictumConfigError,
3726
+ EdictumDenied,
3727
+ EdictumToolError,
3728
+ FileAuditSink,
3729
+ GovernancePipeline,
3730
+ HookDecision,
3731
+ HookResult,
3732
+ LocalApprovalBackend,
3733
+ MAX_BUNDLE_SIZE,
3734
+ MAX_REGEX_INPUT,
3735
+ MarkEvictedError,
3736
+ MemoryBackend,
3737
+ PolicyError,
3738
+ RedactionPolicy,
3739
+ Session,
3740
+ SideEffect,
3741
+ StdoutAuditSink,
3742
+ ToolRegistry,
3743
+ VERSION,
3744
+ Verdict,
3745
+ _validateToolName,
3746
+ buildFindings,
3747
+ classifyFinding,
3748
+ compileContracts,
3749
+ composeBundles,
3750
+ computeHash,
3751
+ createAuditEvent,
3752
+ createCompiledState,
3753
+ createContractResult,
3754
+ createEnvelope,
3755
+ createEvaluationResult,
3756
+ createFinding,
3757
+ createPostCallResult,
3758
+ createPostDecision,
3759
+ createPreDecision,
3760
+ createPrincipal,
3761
+ deepFreeze,
3762
+ defaultSuccessCheck,
3763
+ evaluateExpression,
3764
+ expandMessage,
3765
+ fnmatch,
3766
+ fromYaml,
3767
+ fromYamlString,
3768
+ loadBundle,
3769
+ loadBundleString,
3770
+ reload,
3771
+ run,
3772
+ validateOperators
3773
+ });
3774
+ //# sourceMappingURL=index.cjs.map