@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.
@@ -0,0 +1,1299 @@
1
+ import {
2
+ EdictumConfigError,
3
+ EdictumDenied,
4
+ EdictumToolError,
5
+ SideEffect,
6
+ createEnvelope
7
+ } from "./chunk-CRPQFRYJ.mjs";
8
+
9
+ // src/approval.ts
10
+ import { randomUUID } from "crypto";
11
+ import * as readline from "readline";
12
+
13
+ // src/redaction.ts
14
+ var RedactionPolicy = class _RedactionPolicy {
15
+ static DEFAULT_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
16
+ "password",
17
+ "secret",
18
+ "token",
19
+ "api_key",
20
+ "apikey",
21
+ "api-key",
22
+ "authorization",
23
+ "auth",
24
+ "credentials",
25
+ "private_key",
26
+ "privatekey",
27
+ "access_token",
28
+ "refresh_token",
29
+ "client_secret",
30
+ "connection_string",
31
+ "database_url",
32
+ "db_password",
33
+ "ssh_key",
34
+ "passphrase"
35
+ ]);
36
+ static BASH_REDACTION_PATTERNS = [
37
+ [
38
+ String.raw`(export\s+\w*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)\w*=)\S+`,
39
+ "$1[REDACTED]"
40
+ ],
41
+ [String.raw`(-p\s*|--password[= ])\S+`, "$1[REDACTED]"],
42
+ [String.raw`(://\w+:)\S+(@)`, "$1[REDACTED]$2"]
43
+ ];
44
+ static SECRET_VALUE_PATTERNS = [
45
+ String.raw`^(sk-[a-zA-Z0-9]{20,})`,
46
+ String.raw`^(AKIA[A-Z0-9]{16})`,
47
+ String.raw`^(eyJ[a-zA-Z0-9_-]{20,}\.)`,
48
+ String.raw`^(ghp_[a-zA-Z0-9]{36})`,
49
+ String.raw`^(xox[bpas]-[a-zA-Z0-9-]{10,})`
50
+ ];
51
+ static MAX_PAYLOAD_SIZE = 32768;
52
+ static MAX_REGEX_INPUT = 1e4;
53
+ static MAX_PATTERN_LENGTH = 1e4;
54
+ _keys;
55
+ _patterns;
56
+ _compiledPatterns;
57
+ _compiledSecretPatterns;
58
+ _detectValues;
59
+ constructor(sensitiveKeys, customPatterns, detectSecretValues = true) {
60
+ const baseKeys = sensitiveKeys ? /* @__PURE__ */ new Set([..._RedactionPolicy.DEFAULT_SENSITIVE_KEYS, ...sensitiveKeys]) : new Set(_RedactionPolicy.DEFAULT_SENSITIVE_KEYS);
61
+ this._keys = new Set([...baseKeys].map((k) => k.toLowerCase()));
62
+ if (customPatterns) {
63
+ for (const [pattern] of customPatterns) {
64
+ if (pattern.length > _RedactionPolicy.MAX_PATTERN_LENGTH) {
65
+ throw new EdictumConfigError(
66
+ `Custom redaction pattern exceeds ${_RedactionPolicy.MAX_PATTERN_LENGTH} characters`
67
+ );
68
+ }
69
+ }
70
+ }
71
+ this._patterns = [
72
+ ...customPatterns ?? [],
73
+ ..._RedactionPolicy.BASH_REDACTION_PATTERNS
74
+ ];
75
+ this._compiledPatterns = this._patterns.map(
76
+ ([pattern, replacement]) => [new RegExp(pattern, "g"), replacement]
77
+ );
78
+ this._compiledSecretPatterns = _RedactionPolicy.SECRET_VALUE_PATTERNS.map(
79
+ (p) => new RegExp(p)
80
+ );
81
+ this._detectValues = detectSecretValues;
82
+ }
83
+ /** Recursively redact sensitive data from tool arguments. */
84
+ redactArgs(args) {
85
+ if (args !== null && typeof args === "object" && !Array.isArray(args)) {
86
+ const result = {};
87
+ for (const [key, value] of Object.entries(
88
+ args
89
+ )) {
90
+ result[key] = this._isSensitiveKey(key) ? "[REDACTED]" : this.redactArgs(value);
91
+ }
92
+ return result;
93
+ }
94
+ if (Array.isArray(args)) {
95
+ return args.map((item) => this.redactArgs(item));
96
+ }
97
+ if (typeof args === "string") {
98
+ if (this._detectValues && this._looksLikeSecret(args)) {
99
+ return "[REDACTED]";
100
+ }
101
+ if (args.length > 1e3) {
102
+ return args.slice(0, 997) + "...";
103
+ }
104
+ return args;
105
+ }
106
+ return args;
107
+ }
108
+ /** Check if a key name indicates sensitive data. */
109
+ _isSensitiveKey(key) {
110
+ const k = key.toLowerCase();
111
+ if (this._keys.has(k)) return true;
112
+ const normalized = key.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
113
+ if (normalized !== k && this._keys.has(normalized)) return true;
114
+ const parts = normalized.split(/[_\-]/);
115
+ return parts.some(
116
+ (part) => part === "token" || part === "key" || part === "secret" || part === "password" || part === "credential"
117
+ );
118
+ }
119
+ /** Check if a string value looks like a known secret format. */
120
+ _looksLikeSecret(value) {
121
+ const capped = value.length > _RedactionPolicy.MAX_REGEX_INPUT ? value.slice(0, _RedactionPolicy.MAX_REGEX_INPUT) : value;
122
+ for (const regex of this._compiledSecretPatterns) {
123
+ if (regex.test(capped)) {
124
+ return true;
125
+ }
126
+ }
127
+ return false;
128
+ }
129
+ /** Apply redaction patterns to a bash command string. */
130
+ redactBashCommand(command) {
131
+ const capped = command.length > _RedactionPolicy.MAX_REGEX_INPUT ? command.slice(0, _RedactionPolicy.MAX_REGEX_INPUT) : command;
132
+ let result = capped;
133
+ for (const [regex, replacement] of this._compiledPatterns) {
134
+ regex.lastIndex = 0;
135
+ result = result.replace(regex, replacement);
136
+ }
137
+ return result;
138
+ }
139
+ /** Apply redaction patterns and truncate a result string. */
140
+ redactResult(result, maxLength = 500) {
141
+ const capped = result.length > _RedactionPolicy.MAX_REGEX_INPUT ? result.slice(0, _RedactionPolicy.MAX_REGEX_INPUT) : result;
142
+ let redacted = capped;
143
+ for (const [regex, replacement] of this._compiledPatterns) {
144
+ regex.lastIndex = 0;
145
+ redacted = redacted.replace(regex, replacement);
146
+ }
147
+ if (redacted.length > maxLength) {
148
+ redacted = redacted.slice(0, maxLength - 3) + "...";
149
+ }
150
+ return redacted;
151
+ }
152
+ /** Cap total serialized size of audit payload. Returns a new object if truncated. */
153
+ capPayload(data) {
154
+ const serialized = JSON.stringify(data);
155
+ if (serialized.length > _RedactionPolicy.MAX_PAYLOAD_SIZE) {
156
+ const { resultSummary: _rs, toolArgs: _ta, ...rest } = data;
157
+ void _rs;
158
+ void _ta;
159
+ return {
160
+ ...rest,
161
+ _truncated: true,
162
+ toolArgs: { _redacted: "payload exceeded 32KB" }
163
+ };
164
+ }
165
+ return data;
166
+ }
167
+ };
168
+
169
+ // src/approval.ts
170
+ function sanitizeForTerminal(s) {
171
+ return s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/[\x00-\x1f\x7f]/g, "");
172
+ }
173
+ var ApprovalStatus = {
174
+ PENDING: "pending",
175
+ APPROVED: "approved",
176
+ DENIED: "denied",
177
+ TIMEOUT: "timeout"
178
+ };
179
+ function createApprovalRequest(fields) {
180
+ const request = {
181
+ approvalId: fields.approvalId,
182
+ toolName: fields.toolName,
183
+ toolArgs: Object.freeze({ ...fields.toolArgs }),
184
+ message: fields.message,
185
+ timeout: fields.timeout,
186
+ timeoutEffect: fields.timeoutEffect,
187
+ principal: fields.principal !== null ? Object.freeze({ ...fields.principal }) : null,
188
+ metadata: Object.freeze({ ...fields.metadata }),
189
+ createdAt: fields.createdAt ?? /* @__PURE__ */ new Date()
190
+ };
191
+ return Object.freeze(request);
192
+ }
193
+ function createApprovalDecision(fields) {
194
+ const decision = {
195
+ approved: fields.approved,
196
+ approver: fields.approver ?? null,
197
+ reason: fields.reason ?? null,
198
+ status: fields.status ?? ApprovalStatus.PENDING,
199
+ timestamp: fields.timestamp ?? /* @__PURE__ */ new Date()
200
+ };
201
+ return Object.freeze(decision);
202
+ }
203
+ var LocalApprovalBackend = class {
204
+ _pending = /* @__PURE__ */ new Map();
205
+ async requestApproval(toolName, toolArgs, message, options) {
206
+ const approvalId = randomUUID();
207
+ const request = createApprovalRequest({
208
+ approvalId,
209
+ toolName,
210
+ toolArgs,
211
+ message,
212
+ timeout: options?.timeout ?? 300,
213
+ timeoutEffect: options?.timeoutEffect ?? "deny",
214
+ principal: options?.principal ?? null,
215
+ metadata: options?.metadata ?? {}
216
+ });
217
+ this._pending.set(approvalId, request);
218
+ const redaction = new RedactionPolicy();
219
+ const safeArgs = redaction.redactArgs(toolArgs);
220
+ process.stdout.write(`[APPROVAL REQUIRED] ${sanitizeForTerminal(message)}
221
+ `);
222
+ process.stdout.write(` Tool: ${sanitizeForTerminal(toolName)}
223
+ `);
224
+ process.stdout.write(` Args: ${sanitizeForTerminal(JSON.stringify(safeArgs))}
225
+ `);
226
+ process.stdout.write(` ID: ${approvalId}
227
+ `);
228
+ return request;
229
+ }
230
+ async waitForDecision(approvalId, timeout) {
231
+ const request = this._pending.get(approvalId);
232
+ const effectiveTimeout = timeout ?? (request ? request.timeout : 300);
233
+ try {
234
+ const response = await this._readStdin(approvalId, effectiveTimeout);
235
+ const approved = ["y", "yes", "approve"].includes(
236
+ response.trim().toLowerCase()
237
+ );
238
+ const status = approved ? ApprovalStatus.APPROVED : ApprovalStatus.DENIED;
239
+ return createApprovalDecision({
240
+ approved,
241
+ approver: "local",
242
+ status
243
+ });
244
+ } catch {
245
+ const timeoutEffect = request ? request.timeoutEffect : "deny";
246
+ const approved = timeoutEffect === "allow";
247
+ return createApprovalDecision({
248
+ approved,
249
+ status: ApprovalStatus.TIMEOUT
250
+ });
251
+ }
252
+ }
253
+ /** Read a single line from stdin with a timeout. */
254
+ _readStdin(approvalId, timeoutSeconds) {
255
+ return new Promise((resolve, reject) => {
256
+ const rl = readline.createInterface({
257
+ input: process.stdin,
258
+ output: process.stdout
259
+ });
260
+ const timer = setTimeout(() => {
261
+ rl.close();
262
+ reject(new Error("Approval timed out"));
263
+ }, timeoutSeconds * 1e3);
264
+ rl.question(`Approve? [y/N] (id: ${approvalId}): `, (answer) => {
265
+ clearTimeout(timer);
266
+ rl.close();
267
+ resolve(answer);
268
+ });
269
+ });
270
+ }
271
+ };
272
+
273
+ // src/audit.ts
274
+ import { appendFile } from "fs/promises";
275
+ var AuditAction = {
276
+ CALL_DENIED: "call_denied",
277
+ CALL_WOULD_DENY: "call_would_deny",
278
+ CALL_ALLOWED: "call_allowed",
279
+ CALL_EXECUTED: "call_executed",
280
+ CALL_FAILED: "call_failed",
281
+ POSTCONDITION_WARNING: "postcondition_warning",
282
+ CALL_APPROVAL_REQUESTED: "call_approval_requested",
283
+ CALL_APPROVAL_GRANTED: "call_approval_granted",
284
+ CALL_APPROVAL_DENIED: "call_approval_denied",
285
+ CALL_APPROVAL_TIMEOUT: "call_approval_timeout"
286
+ };
287
+ function createAuditEvent(f = {}) {
288
+ return {
289
+ schemaVersion: f.schemaVersion ?? "0.3.0",
290
+ timestamp: f.timestamp ?? /* @__PURE__ */ new Date(),
291
+ runId: f.runId ?? "",
292
+ callId: f.callId ?? "",
293
+ callIndex: f.callIndex ?? 0,
294
+ parentCallId: f.parentCallId ?? null,
295
+ toolName: f.toolName ?? "",
296
+ toolArgs: f.toolArgs ?? {},
297
+ sideEffect: f.sideEffect ?? "",
298
+ environment: f.environment ?? "",
299
+ principal: f.principal ?? null,
300
+ action: f.action ?? AuditAction.CALL_DENIED,
301
+ decisionSource: f.decisionSource ?? null,
302
+ decisionName: f.decisionName ?? null,
303
+ reason: f.reason ?? null,
304
+ hooksEvaluated: f.hooksEvaluated ?? [],
305
+ contractsEvaluated: f.contractsEvaluated ?? [],
306
+ toolSuccess: f.toolSuccess ?? null,
307
+ postconditionsPassed: f.postconditionsPassed ?? null,
308
+ durationMs: f.durationMs ?? 0,
309
+ error: f.error ?? null,
310
+ resultSummary: f.resultSummary ?? null,
311
+ sessionAttemptCount: f.sessionAttemptCount ?? 0,
312
+ sessionExecutionCount: f.sessionExecutionCount ?? 0,
313
+ mode: f.mode ?? "enforce",
314
+ policyVersion: f.policyVersion ?? null,
315
+ policyError: f.policyError ?? false
316
+ };
317
+ }
318
+ var MarkEvictedError = class extends Error {
319
+ constructor(message) {
320
+ super(message);
321
+ this.name = "MarkEvictedError";
322
+ }
323
+ };
324
+ var CompositeSink = class {
325
+ _sinks;
326
+ constructor(sinks) {
327
+ if (sinks.length === 0) throw new Error("CompositeSink requires at least one sink");
328
+ this._sinks = [...sinks];
329
+ }
330
+ get sinks() {
331
+ return [...this._sinks];
332
+ }
333
+ async emit(event) {
334
+ const errors = [];
335
+ for (const sink of this._sinks) {
336
+ try {
337
+ await sink.emit(event);
338
+ } catch (err) {
339
+ errors.push(err instanceof Error ? err : new Error(String(err)));
340
+ }
341
+ }
342
+ if (errors.length > 0) {
343
+ throw new AggregateError(errors, "CompositeSink: one or more sinks failed");
344
+ }
345
+ }
346
+ };
347
+ function _toPlain(event) {
348
+ const { timestamp, ...rest } = event;
349
+ return { ...rest, timestamp: timestamp.toISOString() };
350
+ }
351
+ var StdoutAuditSink = class {
352
+ _redaction;
353
+ constructor(redaction) {
354
+ this._redaction = redaction ?? new RedactionPolicy();
355
+ }
356
+ async emit(event) {
357
+ process.stdout.write(JSON.stringify(this._redaction.capPayload(_toPlain(event))) + "\n");
358
+ }
359
+ };
360
+ var FileAuditSink = class {
361
+ _path;
362
+ _redaction;
363
+ constructor(path, redaction) {
364
+ this._path = path;
365
+ this._redaction = redaction ?? new RedactionPolicy();
366
+ }
367
+ async emit(event) {
368
+ const data = this._redaction.capPayload(_toPlain(event));
369
+ await appendFile(this._path, JSON.stringify(data) + "\n", "utf-8");
370
+ }
371
+ };
372
+ var CollectingAuditSink = class {
373
+ _events = [];
374
+ _maxEvents;
375
+ _totalEmitted = 0;
376
+ constructor(maxEvents = 5e4) {
377
+ if (maxEvents < 1) throw new Error(`max_events must be >= 1, got ${maxEvents}`);
378
+ this._maxEvents = maxEvents;
379
+ }
380
+ async emit(event) {
381
+ this._events.push(event);
382
+ this._totalEmitted += 1;
383
+ if (this._events.length > this._maxEvents) this._events = this._events.slice(-this._maxEvents);
384
+ }
385
+ get events() {
386
+ return [...this._events];
387
+ }
388
+ mark() {
389
+ return this._totalEmitted;
390
+ }
391
+ sinceMark(m) {
392
+ if (m > this._totalEmitted) {
393
+ throw new Error(`Mark ${m} is ahead of total emitted (${this._totalEmitted})`);
394
+ }
395
+ const evictedCount = this._totalEmitted - this._events.length;
396
+ if (m < evictedCount) {
397
+ throw new MarkEvictedError(
398
+ `Mark ${m} references evicted events (buffer starts at ${evictedCount}, max_events=${this._maxEvents})`
399
+ );
400
+ }
401
+ return [...this._events.slice(m - evictedCount)];
402
+ }
403
+ last() {
404
+ if (this._events.length === 0) throw new Error("No events collected");
405
+ const last = this._events[this._events.length - 1];
406
+ if (!last) throw new Error("No events collected");
407
+ return last;
408
+ }
409
+ filter(action) {
410
+ return this._events.filter((e) => e.action === action);
411
+ }
412
+ clear() {
413
+ this._events = [];
414
+ }
415
+ };
416
+
417
+ // src/contracts.ts
418
+ var Verdict = {
419
+ /**
420
+ * Contract passed — tool call is acceptable.
421
+ */
422
+ pass_() {
423
+ return Object.freeze({ passed: true, message: null, metadata: Object.freeze({}) });
424
+ },
425
+ /**
426
+ * Contract failed with an actionable message (truncated to 500 chars).
427
+ *
428
+ * Make it SPECIFIC and INSTRUCTIVE — the agent uses it to self-correct.
429
+ */
430
+ fail(message, metadata = {}) {
431
+ const truncated = message.length > 500 ? message.slice(0, 497) + "..." : message;
432
+ return Object.freeze({
433
+ passed: false,
434
+ message: truncated,
435
+ metadata: Object.freeze({ ...metadata })
436
+ });
437
+ }
438
+ };
439
+
440
+ // src/hooks.ts
441
+ var HookResult = {
442
+ ALLOW: "allow",
443
+ DENY: "deny"
444
+ };
445
+ var HookDecision = {
446
+ allow() {
447
+ return Object.freeze({ result: HookResult.ALLOW, reason: null });
448
+ },
449
+ deny(reason) {
450
+ const truncated = reason.length > 500 ? reason.slice(0, 497) + "..." : reason;
451
+ return Object.freeze({ result: HookResult.DENY, reason: truncated });
452
+ }
453
+ };
454
+
455
+ // src/pipeline.ts
456
+ function createPreDecision(partial) {
457
+ return {
458
+ action: partial.action,
459
+ reason: partial.reason ?? null,
460
+ decisionSource: partial.decisionSource ?? null,
461
+ decisionName: partial.decisionName ?? null,
462
+ hooksEvaluated: partial.hooksEvaluated ?? [],
463
+ contractsEvaluated: partial.contractsEvaluated ?? [],
464
+ observed: partial.observed ?? false,
465
+ policyError: partial.policyError ?? false,
466
+ observeResults: partial.observeResults ?? [],
467
+ approvalTimeout: partial.approvalTimeout ?? 300,
468
+ approvalTimeoutEffect: partial.approvalTimeoutEffect ?? "deny",
469
+ approvalMessage: partial.approvalMessage ?? null
470
+ };
471
+ }
472
+ function createPostDecision(partial) {
473
+ return {
474
+ toolSuccess: partial.toolSuccess,
475
+ postconditionsPassed: partial.postconditionsPassed ?? true,
476
+ warnings: partial.warnings ?? [],
477
+ contractsEvaluated: partial.contractsEvaluated ?? [],
478
+ policyError: partial.policyError ?? false,
479
+ redactedResponse: partial.redactedResponse ?? null,
480
+ outputSuppressed: partial.outputSuppressed ?? false
481
+ };
482
+ }
483
+ function hasPolicyError(contractsEvaluated) {
484
+ return contractsEvaluated.some((c) => {
485
+ const meta = c["metadata"];
486
+ return meta?.["policy_error"] === true;
487
+ });
488
+ }
489
+ var GovernancePipeline = class {
490
+ _guard;
491
+ constructor(guard) {
492
+ this._guard = guard;
493
+ }
494
+ async preExecute(envelope, session) {
495
+ const hooksEvaluated = [];
496
+ const contractsEvaluated = [];
497
+ let hasObservedDeny = false;
498
+ let toolNameForBatch;
499
+ if (envelope.toolName in this._guard.limits.maxCallsPerTool) {
500
+ toolNameForBatch = envelope.toolName;
501
+ }
502
+ const counters = await session.batchGetCounters({
503
+ includeTool: toolNameForBatch
504
+ });
505
+ const attemptCount = counters["attempts"] ?? 0;
506
+ if (attemptCount >= this._guard.limits.maxAttempts) {
507
+ return createPreDecision({
508
+ action: "deny",
509
+ reason: `Attempt limit reached (${this._guard.limits.maxAttempts}). Agent may be stuck in a retry loop. Stop and reassess.`,
510
+ decisionSource: "attempt_limit",
511
+ decisionName: "max_attempts",
512
+ hooksEvaluated,
513
+ contractsEvaluated
514
+ });
515
+ }
516
+ for (const hookReg of this._guard.getHooks("before", envelope)) {
517
+ if (hookReg.when && !hookReg.when(envelope)) {
518
+ continue;
519
+ }
520
+ let decision;
521
+ try {
522
+ decision = await hookReg.callback(envelope);
523
+ } catch (exc) {
524
+ decision = HookDecision.deny(`Hook error: ${exc}`);
525
+ }
526
+ const hookRecord = {
527
+ name: hookReg.callback.name || "anonymous",
528
+ result: decision.result,
529
+ reason: decision.reason
530
+ };
531
+ hooksEvaluated.push(hookRecord);
532
+ if (decision.result === HookResult.DENY) {
533
+ return createPreDecision({
534
+ action: "deny",
535
+ reason: decision.reason,
536
+ decisionSource: "hook",
537
+ decisionName: hookRecord["name"],
538
+ hooksEvaluated,
539
+ contractsEvaluated,
540
+ policyError: (decision.reason ?? "").includes("Hook error:")
541
+ });
542
+ }
543
+ }
544
+ for (const contract of this._guard.getPreconditions(envelope)) {
545
+ let verdict;
546
+ try {
547
+ verdict = await contract.check(envelope);
548
+ } catch (exc) {
549
+ verdict = Verdict.fail(`Precondition error: ${exc}`, {
550
+ policy_error: true
551
+ });
552
+ }
553
+ const contractRecord = {
554
+ name: contract.name,
555
+ type: "precondition",
556
+ passed: verdict.passed,
557
+ message: verdict.message
558
+ };
559
+ if (verdict.metadata && Object.keys(verdict.metadata).length > 0) {
560
+ contractRecord["metadata"] = verdict.metadata;
561
+ }
562
+ contractsEvaluated.push(contractRecord);
563
+ if (!verdict.passed) {
564
+ if (contract.mode === "observe") {
565
+ contractRecord["observed"] = true;
566
+ hasObservedDeny = true;
567
+ continue;
568
+ }
569
+ const source = contract.source ?? "precondition";
570
+ const pe2 = hasPolicyError(contractsEvaluated);
571
+ const effect = contract.effect ?? "deny";
572
+ if (effect === "approve") {
573
+ return createPreDecision({
574
+ action: "pending_approval",
575
+ reason: verdict.message,
576
+ decisionSource: source,
577
+ decisionName: contract.name,
578
+ hooksEvaluated,
579
+ contractsEvaluated,
580
+ policyError: pe2,
581
+ approvalTimeout: contract.timeout ?? 300,
582
+ approvalTimeoutEffect: contract.timeoutEffect ?? "deny",
583
+ approvalMessage: verdict.message
584
+ });
585
+ }
586
+ return createPreDecision({
587
+ action: "deny",
588
+ reason: verdict.message,
589
+ decisionSource: source,
590
+ decisionName: contract.name,
591
+ hooksEvaluated,
592
+ contractsEvaluated,
593
+ policyError: pe2
594
+ });
595
+ }
596
+ }
597
+ for (const contract of this._guard.getSandboxContracts(envelope)) {
598
+ let verdict;
599
+ try {
600
+ verdict = await contract.check(envelope);
601
+ } catch (exc) {
602
+ verdict = Verdict.fail(`Sandbox contract error: ${exc}`, {
603
+ policy_error: true
604
+ });
605
+ }
606
+ const contractRecord = {
607
+ name: contract.name,
608
+ type: "sandbox",
609
+ passed: verdict.passed,
610
+ message: verdict.message
611
+ };
612
+ if (verdict.metadata && Object.keys(verdict.metadata).length > 0) {
613
+ contractRecord["metadata"] = verdict.metadata;
614
+ }
615
+ contractsEvaluated.push(contractRecord);
616
+ if (!verdict.passed) {
617
+ if (contract.mode === "observe") {
618
+ contractRecord["observed"] = true;
619
+ hasObservedDeny = true;
620
+ continue;
621
+ }
622
+ const source = contract.source ?? "yaml_sandbox";
623
+ const pe2 = hasPolicyError(contractsEvaluated);
624
+ const effect = contract.effect ?? "deny";
625
+ if (effect === "approve") {
626
+ return createPreDecision({
627
+ action: "pending_approval",
628
+ reason: verdict.message,
629
+ decisionSource: source,
630
+ decisionName: contract.name,
631
+ hooksEvaluated,
632
+ contractsEvaluated,
633
+ policyError: pe2,
634
+ approvalTimeout: contract.timeout ?? 300,
635
+ approvalTimeoutEffect: contract.timeoutEffect ?? "deny",
636
+ approvalMessage: verdict.message
637
+ });
638
+ }
639
+ return createPreDecision({
640
+ action: "deny",
641
+ reason: verdict.message,
642
+ decisionSource: source,
643
+ decisionName: contract.name,
644
+ hooksEvaluated,
645
+ contractsEvaluated,
646
+ policyError: pe2
647
+ });
648
+ }
649
+ }
650
+ for (const contract of this._guard.getSessionContracts()) {
651
+ let verdict;
652
+ try {
653
+ verdict = await contract.check(session);
654
+ } catch (exc) {
655
+ verdict = Verdict.fail(`Session contract error: ${exc}`, {
656
+ policy_error: true
657
+ });
658
+ }
659
+ const contractRecord = {
660
+ name: contract.name,
661
+ type: "session_contract",
662
+ passed: verdict.passed,
663
+ message: verdict.message
664
+ };
665
+ if (verdict.metadata && Object.keys(verdict.metadata).length > 0) {
666
+ contractRecord["metadata"] = verdict.metadata;
667
+ }
668
+ contractsEvaluated.push(contractRecord);
669
+ if (!verdict.passed) {
670
+ const source = contract.source ?? "session_contract";
671
+ const pe2 = hasPolicyError(contractsEvaluated);
672
+ return createPreDecision({
673
+ action: "deny",
674
+ reason: verdict.message,
675
+ decisionSource: source,
676
+ decisionName: contract.name,
677
+ hooksEvaluated,
678
+ contractsEvaluated,
679
+ policyError: pe2
680
+ });
681
+ }
682
+ }
683
+ const execCount = counters["execs"] ?? 0;
684
+ if (execCount >= this._guard.limits.maxToolCalls) {
685
+ return createPreDecision({
686
+ action: "deny",
687
+ reason: `Execution limit reached (${this._guard.limits.maxToolCalls} calls). Summarize progress and stop.`,
688
+ decisionSource: "operation_limit",
689
+ decisionName: "max_tool_calls",
690
+ hooksEvaluated,
691
+ contractsEvaluated
692
+ });
693
+ }
694
+ if (envelope.toolName in this._guard.limits.maxCallsPerTool) {
695
+ const toolKey = `tool:${envelope.toolName}`;
696
+ const toolCount = counters[toolKey] ?? 0;
697
+ const toolLimit = this._guard.limits.maxCallsPerTool[envelope.toolName] ?? 0;
698
+ if (toolCount >= toolLimit) {
699
+ return createPreDecision({
700
+ action: "deny",
701
+ reason: `Per-tool limit: ${envelope.toolName} called ${toolCount} times (limit: ${toolLimit}).`,
702
+ decisionSource: "operation_limit",
703
+ decisionName: `max_calls_per_tool:${envelope.toolName}`,
704
+ hooksEvaluated,
705
+ contractsEvaluated
706
+ });
707
+ }
708
+ }
709
+ const pe = hasPolicyError(contractsEvaluated);
710
+ const observeResults = await this._evaluateObserveContracts(
711
+ envelope,
712
+ session
713
+ );
714
+ return createPreDecision({
715
+ action: "allow",
716
+ hooksEvaluated,
717
+ contractsEvaluated,
718
+ observed: hasObservedDeny,
719
+ policyError: pe,
720
+ observeResults
721
+ });
722
+ }
723
+ async postExecute(envelope, toolResponse, toolSuccess) {
724
+ const warnings = [];
725
+ const contractsEvaluated = [];
726
+ let redactedResponse = null;
727
+ let outputSuppressed = false;
728
+ for (const contract of this._guard.getPostconditions(envelope)) {
729
+ let verdict;
730
+ try {
731
+ verdict = await contract.check(envelope, toolResponse);
732
+ } catch (exc) {
733
+ verdict = Verdict.fail(`Postcondition error: ${exc}`, {
734
+ policy_error: true
735
+ });
736
+ }
737
+ const contractRecord = {
738
+ name: contract.name,
739
+ type: "postcondition",
740
+ passed: verdict.passed,
741
+ message: verdict.message
742
+ };
743
+ if (verdict.metadata && Object.keys(verdict.metadata).length > 0) {
744
+ contractRecord["metadata"] = verdict.metadata;
745
+ }
746
+ contractsEvaluated.push(contractRecord);
747
+ if (!verdict.passed) {
748
+ const effect = contract.effect ?? "warn";
749
+ const contractMode = contract.mode;
750
+ const isSafe = envelope.sideEffect === SideEffect.PURE || envelope.sideEffect === SideEffect.READ;
751
+ if (contractMode === "observe") {
752
+ contractRecord["observed"] = true;
753
+ warnings.push(`\u26A0\uFE0F [observe] ${verdict.message}`);
754
+ } else if (effect === "redact" && isSafe) {
755
+ const patterns = contract.redactPatterns ?? [];
756
+ const source = redactedResponse !== null ? redactedResponse : toolResponse;
757
+ let text = source != null ? String(source) : "";
758
+ if (patterns.length > 0) {
759
+ for (const pat of patterns) {
760
+ const globalPat = pat.global ? pat : new RegExp(pat.source, pat.flags + "g");
761
+ text = text.replace(globalPat, "[REDACTED]");
762
+ }
763
+ } else {
764
+ const policy = new RedactionPolicy();
765
+ text = policy.redactResult(text, text.length + 100);
766
+ }
767
+ redactedResponse = text;
768
+ warnings.push(
769
+ `\u26A0\uFE0F Content redacted by ${contract.name}.`
770
+ );
771
+ } else if (effect === "deny" && isSafe) {
772
+ redactedResponse = `[OUTPUT SUPPRESSED] ${verdict.message}`;
773
+ outputSuppressed = true;
774
+ warnings.push(
775
+ `\u26A0\uFE0F Output suppressed by ${contract.name}.`
776
+ );
777
+ } else if ((effect === "redact" || effect === "deny") && !isSafe) {
778
+ warnings.push(
779
+ `\u26A0\uFE0F ${verdict.message} Tool already executed \u2014 assess before proceeding.`
780
+ );
781
+ } else if (isSafe) {
782
+ warnings.push(
783
+ `\u26A0\uFE0F ${verdict.message} Consider retrying.`
784
+ );
785
+ } else {
786
+ warnings.push(
787
+ `\u26A0\uFE0F ${verdict.message} Tool already executed \u2014 assess before proceeding.`
788
+ );
789
+ }
790
+ }
791
+ }
792
+ for (const hookReg of this._guard.getHooks("after", envelope)) {
793
+ if (hookReg.when && !hookReg.when(envelope)) {
794
+ continue;
795
+ }
796
+ try {
797
+ await hookReg.callback(envelope, toolResponse);
798
+ } catch {
799
+ }
800
+ }
801
+ for (const contract of this._guard.getObservePostconditions(envelope)) {
802
+ let verdict;
803
+ try {
804
+ verdict = await contract.check(envelope, toolResponse);
805
+ } catch (exc) {
806
+ verdict = Verdict.fail(
807
+ `Observe-mode postcondition error: ${exc}`,
808
+ { policy_error: true }
809
+ );
810
+ }
811
+ const record = {
812
+ name: contract.name,
813
+ type: "postcondition",
814
+ passed: verdict.passed,
815
+ message: verdict.message,
816
+ observed: true,
817
+ source: contract.source ?? "yaml_postcondition"
818
+ };
819
+ if (verdict.metadata && Object.keys(verdict.metadata).length > 0) {
820
+ record["metadata"] = verdict.metadata;
821
+ }
822
+ contractsEvaluated.push(record);
823
+ if (!verdict.passed) {
824
+ warnings.push(`\u26A0\uFE0F [observe] ${verdict.message}`);
825
+ }
826
+ }
827
+ const postconditionsPassed = contractsEvaluated.length > 0 ? contractsEvaluated.every(
828
+ (c) => c["passed"] === true || c["observed"] === true
829
+ ) : true;
830
+ const pe = hasPolicyError(contractsEvaluated);
831
+ return createPostDecision({
832
+ toolSuccess,
833
+ postconditionsPassed,
834
+ warnings,
835
+ contractsEvaluated,
836
+ policyError: pe,
837
+ redactedResponse,
838
+ outputSuppressed
839
+ });
840
+ }
841
+ /**
842
+ * Evaluate observe-mode contracts without affecting the real decision.
843
+ *
844
+ * Observe-mode contracts are identified by mode === "observe" on the
845
+ * internal contract. Results are returned as dicts for audit emission
846
+ * but never block calls.
847
+ */
848
+ async _evaluateObserveContracts(envelope, session) {
849
+ const results = [];
850
+ for (const contract of this._guard.getObservePreconditions(
851
+ envelope
852
+ )) {
853
+ let verdict;
854
+ try {
855
+ verdict = await contract.check(envelope);
856
+ } catch (exc) {
857
+ verdict = Verdict.fail(
858
+ `Observe-mode precondition error: ${exc}`,
859
+ { policy_error: true }
860
+ );
861
+ }
862
+ results.push({
863
+ name: contract.name,
864
+ type: "precondition",
865
+ passed: verdict.passed,
866
+ message: verdict.message,
867
+ source: contract.source ?? "yaml_precondition"
868
+ });
869
+ }
870
+ for (const contract of this._guard.getObserveSandboxContracts(
871
+ envelope
872
+ )) {
873
+ let verdict;
874
+ try {
875
+ verdict = await contract.check(envelope);
876
+ } catch (exc) {
877
+ verdict = Verdict.fail(
878
+ `Observe-mode sandbox error: ${exc}`,
879
+ { policy_error: true }
880
+ );
881
+ }
882
+ results.push({
883
+ name: contract.name,
884
+ type: "sandbox",
885
+ passed: verdict.passed,
886
+ message: verdict.message,
887
+ source: contract.source ?? "yaml_sandbox"
888
+ });
889
+ }
890
+ for (const contract of this._guard.getObserveSessionContracts()) {
891
+ let verdict;
892
+ try {
893
+ verdict = await contract.check(session);
894
+ } catch (exc) {
895
+ verdict = Verdict.fail(
896
+ `Observe-mode session contract error: ${exc}`,
897
+ { policy_error: true }
898
+ );
899
+ }
900
+ results.push({
901
+ name: contract.name,
902
+ type: "session_contract",
903
+ passed: verdict.passed,
904
+ message: verdict.message,
905
+ source: contract.source ?? "yaml_session"
906
+ });
907
+ }
908
+ return results;
909
+ }
910
+ };
911
+
912
+ // src/session.ts
913
+ function hasBatchGet(backend) {
914
+ return "batchGet" in backend;
915
+ }
916
+ var MAX_ID_LENGTH = 1e4;
917
+ function _validateStorageKeyComponent(value, label) {
918
+ if (!value) {
919
+ throw new EdictumConfigError(`Invalid ${label}: ${JSON.stringify(value)}`);
920
+ }
921
+ if (value.length > MAX_ID_LENGTH) {
922
+ throw new EdictumConfigError(`Invalid ${label}: exceeds ${MAX_ID_LENGTH} characters`);
923
+ }
924
+ for (let i = 0; i < value.length; i++) {
925
+ const code = value.charCodeAt(i);
926
+ if (code < 32 || code === 127) {
927
+ throw new EdictumConfigError(`Invalid ${label}: ${JSON.stringify(value)}`);
928
+ }
929
+ }
930
+ }
931
+ var Session = class {
932
+ _sid;
933
+ _backend;
934
+ constructor(sessionId, backend) {
935
+ _validateStorageKeyComponent(sessionId, "session_id");
936
+ this._sid = sessionId;
937
+ this._backend = backend;
938
+ }
939
+ get sessionId() {
940
+ return this._sid;
941
+ }
942
+ /** Increment attempt counter. Called in PreToolUse (before governance). */
943
+ async incrementAttempts() {
944
+ return await this._backend.increment(`s:${this._sid}:attempts`);
945
+ }
946
+ async attemptCount() {
947
+ return Number(await this._backend.get(`s:${this._sid}:attempts`) ?? 0);
948
+ }
949
+ /** Record a tool execution. Called in PostToolUse. */
950
+ async recordExecution(toolName, success) {
951
+ _validateStorageKeyComponent(toolName, "tool_name");
952
+ await this._backend.increment(`s:${this._sid}:execs`);
953
+ await this._backend.increment(`s:${this._sid}:tool:${toolName}`);
954
+ if (success) {
955
+ await this._backend.delete(`s:${this._sid}:consec_fail`);
956
+ } else {
957
+ await this._backend.increment(`s:${this._sid}:consec_fail`);
958
+ }
959
+ }
960
+ async executionCount() {
961
+ return Number(await this._backend.get(`s:${this._sid}:execs`) ?? 0);
962
+ }
963
+ async toolExecutionCount(tool) {
964
+ _validateStorageKeyComponent(tool, "tool_name");
965
+ return Number(
966
+ await this._backend.get(`s:${this._sid}:tool:${tool}`) ?? 0
967
+ );
968
+ }
969
+ async consecutiveFailures() {
970
+ return Number(
971
+ await this._backend.get(`s:${this._sid}:consec_fail`) ?? 0
972
+ );
973
+ }
974
+ /**
975
+ * Pre-fetch multiple session counters in a single backend call.
976
+ *
977
+ * Returns a record with keys: "attempts", "execs", and optionally
978
+ * "tool:{name}" if includeTool is provided.
979
+ *
980
+ * Uses batchGet() on the backend when available (single HTTP round
981
+ * trip for ServerBackend). Falls back to individual get() calls for
982
+ * backends without batchGet support.
983
+ */
984
+ async batchGetCounters(options) {
985
+ const keys = [
986
+ `s:${this._sid}:attempts`,
987
+ `s:${this._sid}:execs`
988
+ ];
989
+ const keyLabels = ["attempts", "execs"];
990
+ if (options?.includeTool != null) {
991
+ _validateStorageKeyComponent(options.includeTool, "tool_name");
992
+ keys.push(`s:${this._sid}:tool:${options.includeTool}`);
993
+ keyLabels.push(`tool:${options.includeTool}`);
994
+ }
995
+ let raw;
996
+ if (hasBatchGet(this._backend)) {
997
+ raw = await this._backend.batchGet(keys);
998
+ } else {
999
+ raw = {};
1000
+ for (const key of keys) {
1001
+ raw[key] = await this._backend.get(key);
1002
+ }
1003
+ }
1004
+ const result = {};
1005
+ for (let i = 0; i < keys.length; i++) {
1006
+ const label = keyLabels[i] ?? "";
1007
+ const key = keys[i] ?? "";
1008
+ result[label] = Number(raw[key] ?? 0);
1009
+ }
1010
+ return result;
1011
+ }
1012
+ };
1013
+
1014
+ // src/runner.ts
1015
+ function defaultSuccessCheck(_toolName, result) {
1016
+ if (result == null) {
1017
+ return true;
1018
+ }
1019
+ if (typeof result === "object" && !Array.isArray(result)) {
1020
+ const dict = result;
1021
+ if (dict["is_error"]) {
1022
+ return false;
1023
+ }
1024
+ }
1025
+ if (typeof result === "string") {
1026
+ const lower = result.slice(0, 7).toLowerCase();
1027
+ if (lower.startsWith("error:") || lower.startsWith("fatal:")) {
1028
+ return false;
1029
+ }
1030
+ }
1031
+ return true;
1032
+ }
1033
+ async function _emitRunPreAudit(guard, envelope, session, action, pre) {
1034
+ const event = createAuditEvent({
1035
+ action,
1036
+ runId: envelope.runId,
1037
+ callId: envelope.callId,
1038
+ toolName: envelope.toolName,
1039
+ toolArgs: guard.redaction.redactArgs(envelope.args),
1040
+ sideEffect: envelope.sideEffect,
1041
+ environment: envelope.environment,
1042
+ principal: envelope.principal ? { ...envelope.principal } : null,
1043
+ decisionSource: pre.decisionSource,
1044
+ decisionName: pre.decisionName,
1045
+ reason: pre.reason,
1046
+ hooksEvaluated: pre.hooksEvaluated,
1047
+ contractsEvaluated: pre.contractsEvaluated,
1048
+ sessionAttemptCount: await session.attemptCount(),
1049
+ sessionExecutionCount: await session.executionCount(),
1050
+ mode: guard.mode,
1051
+ policyVersion: guard.policyVersion,
1052
+ policyError: pre.policyError
1053
+ });
1054
+ await guard.auditSink.emit(event);
1055
+ }
1056
+ async function run(guard, toolName, args, toolCallable, options) {
1057
+ const sessionId = options?.sessionId ?? guard.sessionId;
1058
+ const session = new Session(sessionId, guard.backend);
1059
+ const pipeline = new GovernancePipeline(guard);
1060
+ const env = options?.environment ?? guard.environment;
1061
+ let principal = options?.principal ?? void 0;
1062
+ if (principal === void 0) {
1063
+ const resolved = guard._resolvePrincipal(toolName, args);
1064
+ if (resolved != null) {
1065
+ principal = resolved;
1066
+ }
1067
+ }
1068
+ const envelope = createEnvelope(toolName, args, {
1069
+ runId: sessionId,
1070
+ environment: env,
1071
+ registry: guard.toolRegistry,
1072
+ principal: principal ?? null
1073
+ });
1074
+ await session.incrementAttempts();
1075
+ try {
1076
+ const pre = await pipeline.preExecute(envelope, session);
1077
+ if (pre.action === "pending_approval") {
1078
+ if (guard._approvalBackend == null) {
1079
+ throw new EdictumDenied(
1080
+ `Approval required but no approval backend configured: ${pre.reason}`,
1081
+ pre.decisionSource,
1082
+ pre.decisionName
1083
+ );
1084
+ }
1085
+ const principalDict = envelope.principal ? { ...envelope.principal } : null;
1086
+ const approvalRequest = await guard._approvalBackend.requestApproval(
1087
+ envelope.toolName,
1088
+ envelope.args,
1089
+ pre.approvalMessage ?? pre.reason ?? "",
1090
+ {
1091
+ timeout: pre.approvalTimeout,
1092
+ timeoutEffect: pre.approvalTimeoutEffect,
1093
+ principal: principalDict
1094
+ }
1095
+ );
1096
+ await _emitRunPreAudit(
1097
+ guard,
1098
+ envelope,
1099
+ session,
1100
+ AuditAction.CALL_APPROVAL_REQUESTED,
1101
+ pre
1102
+ );
1103
+ const decision = await guard._approvalBackend.waitForDecision(
1104
+ approvalRequest.approvalId,
1105
+ pre.approvalTimeout
1106
+ );
1107
+ let approved = false;
1108
+ if (decision.status === ApprovalStatus.TIMEOUT) {
1109
+ await _emitRunPreAudit(
1110
+ guard,
1111
+ envelope,
1112
+ session,
1113
+ AuditAction.CALL_APPROVAL_TIMEOUT,
1114
+ pre
1115
+ );
1116
+ if (pre.approvalTimeoutEffect === "allow") {
1117
+ approved = true;
1118
+ }
1119
+ } else if (!decision.approved) {
1120
+ await _emitRunPreAudit(
1121
+ guard,
1122
+ envelope,
1123
+ session,
1124
+ AuditAction.CALL_APPROVAL_DENIED,
1125
+ pre
1126
+ );
1127
+ } else {
1128
+ approved = true;
1129
+ await _emitRunPreAudit(
1130
+ guard,
1131
+ envelope,
1132
+ session,
1133
+ AuditAction.CALL_APPROVAL_GRANTED,
1134
+ pre
1135
+ );
1136
+ }
1137
+ if (approved) {
1138
+ if (guard._onAllow) {
1139
+ try {
1140
+ guard._onAllow(envelope);
1141
+ } catch {
1142
+ }
1143
+ }
1144
+ } else {
1145
+ const denyReason = decision.reason ?? pre.reason ?? "";
1146
+ if (guard._onDeny) {
1147
+ try {
1148
+ guard._onDeny(envelope, denyReason, pre.decisionName);
1149
+ } catch {
1150
+ }
1151
+ }
1152
+ throw new EdictumDenied(
1153
+ decision.reason ?? pre.reason ?? "denied",
1154
+ pre.decisionSource,
1155
+ pre.decisionName
1156
+ );
1157
+ }
1158
+ }
1159
+ const realDeny = pre.action === "deny" && !pre.observed;
1160
+ if (pre.action === "pending_approval") {
1161
+ } else if (realDeny) {
1162
+ const auditAction = guard.mode === "observe" ? AuditAction.CALL_WOULD_DENY : AuditAction.CALL_DENIED;
1163
+ await _emitRunPreAudit(guard, envelope, session, auditAction, pre);
1164
+ if (guard.mode === "enforce") {
1165
+ if (guard._onDeny) {
1166
+ try {
1167
+ guard._onDeny(envelope, pre.reason ?? "", pre.decisionName);
1168
+ } catch {
1169
+ }
1170
+ }
1171
+ throw new EdictumDenied(
1172
+ pre.reason ?? "denied",
1173
+ pre.decisionSource,
1174
+ pre.decisionName
1175
+ );
1176
+ }
1177
+ } else {
1178
+ for (const cr of pre.contractsEvaluated) {
1179
+ if (cr["observed"] && !cr["passed"]) {
1180
+ const observedEvent = createAuditEvent({
1181
+ action: AuditAction.CALL_WOULD_DENY,
1182
+ runId: envelope.runId,
1183
+ callId: envelope.callId,
1184
+ toolName: envelope.toolName,
1185
+ toolArgs: guard.redaction.redactArgs(envelope.args),
1186
+ sideEffect: envelope.sideEffect,
1187
+ environment: envelope.environment,
1188
+ principal: envelope.principal ? { ...envelope.principal } : null,
1189
+ decisionSource: "precondition",
1190
+ decisionName: cr["name"],
1191
+ reason: cr["message"],
1192
+ mode: "observe",
1193
+ policyVersion: guard.policyVersion,
1194
+ policyError: pre.policyError
1195
+ });
1196
+ await guard.auditSink.emit(observedEvent);
1197
+ }
1198
+ }
1199
+ await _emitRunPreAudit(
1200
+ guard,
1201
+ envelope,
1202
+ session,
1203
+ AuditAction.CALL_ALLOWED,
1204
+ pre
1205
+ );
1206
+ if (guard._onAllow) {
1207
+ try {
1208
+ guard._onAllow(envelope);
1209
+ } catch {
1210
+ }
1211
+ }
1212
+ }
1213
+ for (const sr of pre.observeResults) {
1214
+ const observeAction = sr["passed"] ? AuditAction.CALL_ALLOWED : AuditAction.CALL_WOULD_DENY;
1215
+ const observeEvent = createAuditEvent({
1216
+ action: observeAction,
1217
+ runId: envelope.runId,
1218
+ callId: envelope.callId,
1219
+ toolName: envelope.toolName,
1220
+ toolArgs: guard.redaction.redactArgs(envelope.args),
1221
+ sideEffect: envelope.sideEffect,
1222
+ environment: envelope.environment,
1223
+ principal: envelope.principal ? { ...envelope.principal } : null,
1224
+ decisionSource: sr["source"],
1225
+ decisionName: sr["name"],
1226
+ reason: sr["message"],
1227
+ mode: "observe",
1228
+ policyVersion: guard.policyVersion
1229
+ });
1230
+ await guard.auditSink.emit(observeEvent);
1231
+ }
1232
+ let result;
1233
+ let toolSuccess;
1234
+ try {
1235
+ result = toolCallable(envelope.args);
1236
+ if (result != null && typeof result === "object" && typeof result.then === "function") {
1237
+ result = await result;
1238
+ }
1239
+ if (guard._successCheck) {
1240
+ toolSuccess = guard._successCheck(toolName, result);
1241
+ } else {
1242
+ toolSuccess = defaultSuccessCheck(toolName, result);
1243
+ }
1244
+ } catch (e) {
1245
+ result = String(e);
1246
+ toolSuccess = false;
1247
+ }
1248
+ const post = await pipeline.postExecute(envelope, result, toolSuccess);
1249
+ await session.recordExecution(toolName, toolSuccess);
1250
+ const postAction = toolSuccess ? AuditAction.CALL_EXECUTED : AuditAction.CALL_FAILED;
1251
+ const postEvent = createAuditEvent({
1252
+ action: postAction,
1253
+ runId: envelope.runId,
1254
+ callId: envelope.callId,
1255
+ toolName: envelope.toolName,
1256
+ toolArgs: guard.redaction.redactArgs(envelope.args),
1257
+ sideEffect: envelope.sideEffect,
1258
+ environment: envelope.environment,
1259
+ principal: envelope.principal ? { ...envelope.principal } : null,
1260
+ toolSuccess,
1261
+ postconditionsPassed: post.postconditionsPassed,
1262
+ contractsEvaluated: post.contractsEvaluated,
1263
+ sessionAttemptCount: await session.attemptCount(),
1264
+ sessionExecutionCount: await session.executionCount(),
1265
+ mode: guard.mode,
1266
+ policyVersion: guard.policyVersion,
1267
+ policyError: post.policyError
1268
+ });
1269
+ await guard.auditSink.emit(postEvent);
1270
+ if (!toolSuccess) {
1271
+ throw new EdictumToolError(String(result));
1272
+ }
1273
+ return post.redactedResponse != null ? post.redactedResponse : result;
1274
+ } finally {
1275
+ }
1276
+ }
1277
+
1278
+ export {
1279
+ Verdict,
1280
+ HookResult,
1281
+ HookDecision,
1282
+ Session,
1283
+ RedactionPolicy,
1284
+ ApprovalStatus,
1285
+ LocalApprovalBackend,
1286
+ AuditAction,
1287
+ createAuditEvent,
1288
+ MarkEvictedError,
1289
+ CompositeSink,
1290
+ StdoutAuditSink,
1291
+ FileAuditSink,
1292
+ CollectingAuditSink,
1293
+ createPreDecision,
1294
+ createPostDecision,
1295
+ GovernancePipeline,
1296
+ defaultSuccessCheck,
1297
+ run
1298
+ };
1299
+ //# sourceMappingURL=chunk-X5E2YY35.mjs.map