@edictum/core 0.1.0 → 0.2.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/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @edictum/core
2
+
3
+ Runtime contract enforcement for AI agent tool calls. One runtime dep ([js-yaml](https://github.com/nodeca/js-yaml)).
4
+
5
+ Part of [Edictum](https://github.com/edictum-ai/edictum-ts) -- runtime contract enforcement for AI agent tool calls.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @edictum/core
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import { readFile } from 'node:fs/promises'
17
+ import { Edictum, EdictumDenied } from '@edictum/core'
18
+
19
+ const guard = Edictum.fromYaml('contracts.yaml')
20
+
21
+ const governedReadFile = (args: Record<string, unknown>) => readFile(args.path as string, 'utf8')
22
+
23
+ try {
24
+ await guard.run('readFile', { path: '.env' }, governedReadFile)
25
+ } catch (e) {
26
+ if (e instanceof EdictumDenied) console.log(e.reason)
27
+ }
28
+ ```
29
+
30
+ ## Key Exports
31
+
32
+ - `Edictum` -- main guard class (`fromYaml`, `fromYamlString`, `run`, `evaluate`)
33
+ - `EdictumDenied`, `EdictumConfigError`, `EdictumToolError` -- error types
34
+ - `GovernancePipeline` -- governance pipeline (used by adapters)
35
+ - `Verdict` -- contract result builder (`pass()`, `fail(reason)`)
36
+ - `Session`, `MemoryBackend` -- session tracking and storage
37
+ - `RedactionPolicy` -- sensitive field redaction for audit events
38
+ - `CollectingAuditSink`, `StdoutAuditSink`, `FileAuditSink`, `CompositeSink` -- audit sinks
39
+ - `createEnvelope`, `BashClassifier`, `SideEffect` -- envelope construction
40
+ - `composeBundles`, `loadBundle`, `compileContracts` -- YAML engine
41
+
42
+ ## Links
43
+
44
+ - [Full documentation](https://docs.edictum.ai)
45
+ - [GitHub](https://github.com/edictum-ai/edictum-ts)
46
+ - [All packages](https://github.com/edictum-ai/edictum-ts#packages)
@@ -27,4 +27,4 @@ export {
27
27
  createContractResult,
28
28
  createEvaluationResult
29
29
  };
30
- //# sourceMappingURL=chunk-IXMXZGJG.mjs.map
30
+ //# sourceMappingURL=chunk-23XIQZR5.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/evaluation.ts"],"sourcesContent":["/** Evaluation result types for dry-run contract evaluation. */\n\n// ---------------------------------------------------------------------------\n// ContractResult\n// ---------------------------------------------------------------------------\n\n/** Result of evaluating a single contract. */\nexport interface ContractResult {\n readonly contractId: string\n readonly contractType: string // \"precondition\" | \"postcondition\" | \"sandbox\"\n readonly passed: boolean\n readonly message: string | null\n readonly tags: readonly string[]\n readonly observed: boolean\n readonly effect: string\n readonly policyError: boolean\n}\n\n/** Create a frozen ContractResult with defaults matching the Python dataclass. */\nexport function createContractResult(\n fields: Pick<ContractResult, 'contractId' | 'contractType' | 'passed'> &\n Partial<Omit<ContractResult, 'contractId' | 'contractType' | 'passed'>>,\n): ContractResult {\n return Object.freeze({\n contractId: fields.contractId,\n contractType: fields.contractType,\n passed: fields.passed,\n message: fields.message ?? null,\n tags: Object.freeze([...(fields.tags ?? [])]),\n observed: fields.observed ?? false,\n effect: fields.effect ?? 'warn',\n policyError: fields.policyError ?? false,\n })\n}\n\n// ---------------------------------------------------------------------------\n// EvaluationResult\n// ---------------------------------------------------------------------------\n\n/** Result of dry-run evaluation of a tool call against contracts. */\nexport interface EvaluationResult {\n readonly verdict: string // \"allow\" | \"deny\" | \"warn\"\n readonly toolName: string\n readonly contracts: readonly ContractResult[]\n readonly denyReasons: readonly string[]\n readonly warnReasons: readonly string[]\n readonly contractsEvaluated: number\n readonly policyError: boolean\n}\n\n/** Create a frozen EvaluationResult with defaults matching the Python dataclass. */\nexport function createEvaluationResult(\n fields: Pick<EvaluationResult, 'verdict' | 'toolName'> &\n Partial<Omit<EvaluationResult, 'verdict' | 'toolName'>>,\n): EvaluationResult {\n return Object.freeze({\n verdict: fields.verdict,\n toolName: fields.toolName,\n contracts: Object.freeze([...(fields.contracts ?? [])]),\n denyReasons: Object.freeze([...(fields.denyReasons ?? [])]),\n warnReasons: Object.freeze([...(fields.warnReasons ?? [])]),\n contractsEvaluated: fields.contractsEvaluated ?? 0,\n policyError: fields.policyError ?? false,\n })\n}\n"],"mappings":";AAmBO,SAAS,qBACd,QAEgB;AAChB,SAAO,OAAO,OAAO;AAAA,IACnB,YAAY,OAAO;AAAA,IACnB,cAAc,OAAO;AAAA,IACrB,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO,WAAW;AAAA,IAC3B,MAAM,OAAO,OAAO,CAAC,GAAI,OAAO,QAAQ,CAAC,CAAE,CAAC;AAAA,IAC5C,UAAU,OAAO,YAAY;AAAA,IAC7B,QAAQ,OAAO,UAAU;AAAA,IACzB,aAAa,OAAO,eAAe;AAAA,EACrC,CAAC;AACH;AAkBO,SAAS,uBACd,QAEkB;AAClB,SAAO,OAAO,OAAO;AAAA,IACnB,SAAS,OAAO;AAAA,IAChB,UAAU,OAAO;AAAA,IACjB,WAAW,OAAO,OAAO,CAAC,GAAI,OAAO,aAAa,CAAC,CAAE,CAAC;AAAA,IACtD,aAAa,OAAO,OAAO,CAAC,GAAI,OAAO,eAAe,CAAC,CAAE,CAAC;AAAA,IAC1D,aAAa,OAAO,OAAO,CAAC,GAAI,OAAO,eAAe,CAAC,CAAE,CAAC;AAAA,IAC1D,oBAAoB,OAAO,sBAAsB;AAAA,IACjD,aAAa,OAAO,eAAe;AAAA,EACrC,CAAC;AACH;","names":[]}
@@ -1,10 +1,3 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
1
  // src/errors.ts
9
2
  var EdictumDenied = class extends Error {
10
3
  reason;
@@ -57,7 +50,7 @@ function _validateToolName(toolName) {
57
50
  for (let i = 0; i < toolName.length; i++) {
58
51
  const code = toolName.charCodeAt(i);
59
52
  const ch = toolName[i];
60
- if (code < 32 || code === 127 || ch === "/" || ch === "\\") {
53
+ if (code < 32 || code >= 127 && code <= 159 || code === 8232 || code === 8233 || ch === "/" || ch === "\\") {
61
54
  throw new EdictumConfigError(`Invalid tool_name: ${JSON.stringify(toolName)}`);
62
55
  }
63
56
  }
@@ -223,7 +216,6 @@ function createEnvelope(toolName, toolInput, options = {}) {
223
216
  }
224
217
 
225
218
  export {
226
- __require,
227
219
  EdictumDenied,
228
220
  EdictumConfigError,
229
221
  EdictumToolError,
@@ -235,4 +227,4 @@ export {
235
227
  BashClassifier,
236
228
  createEnvelope
237
229
  };
238
- //# sourceMappingURL=chunk-CRPQFRYJ.mjs.map
230
+ //# sourceMappingURL=chunk-2YSBMUK5.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/envelope.ts"],"sourcesContent":["/** Exception classes for Edictum. */\n\n/** Raised when guard.run() denies a tool call in enforce mode. */\nexport class EdictumDenied extends Error {\n readonly reason: string\n readonly decisionSource: string | null\n readonly decisionName: string | null\n\n constructor(\n reason: string,\n decisionSource: string | null = null,\n decisionName: string | null = null,\n ) {\n super(reason)\n this.name = 'EdictumDenied'\n this.reason = reason\n this.decisionSource = decisionSource\n this.decisionName = decisionName\n }\n}\n\n/** Raised for configuration/load-time errors (invalid YAML, schema failures, etc.). */\nexport class EdictumConfigError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'EdictumConfigError'\n }\n}\n\n/** Raised when the governed tool itself fails. */\nexport class EdictumToolError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'EdictumToolError'\n }\n}\n","/** Tool Invocation Envelope — immutable snapshot of a tool call. */\n\nimport { randomUUID } from 'node:crypto'\n\nimport { EdictumConfigError } from './errors.js'\nimport type { ToolConfig } from './types.js'\n\n// ---------------------------------------------------------------------------\n// SideEffect\n// ---------------------------------------------------------------------------\n\n/**\n * Classification of tool side effects.\n *\n * Determines postcondition behavior and retry safety.\n *\n * DEFAULTS:\n * - Unregistered tools -> IRREVERSIBLE (conservative)\n * - Bash -> IRREVERSIBLE unless strict allowlist match\n * - Classification errors always err toward MORE restrictive\n */\nexport const SideEffect = {\n PURE: 'pure',\n READ: 'read',\n WRITE: 'write',\n IRREVERSIBLE: 'irreversible',\n} as const\n\nexport type SideEffect = (typeof SideEffect)[keyof typeof SideEffect]\n\n// ---------------------------------------------------------------------------\n// Principal\n// ---------------------------------------------------------------------------\n\n/**\n * Identity context for audit attribution.\n *\n * NOTE: `claims` is a plain object. The Principal itself is frozen via\n * `Object.freeze()`, making the reference immutable. Callers should treat\n * claims as read-only after construction.\n */\nexport interface Principal {\n readonly userId: string | null\n readonly serviceId: string | null\n readonly orgId: string | null\n readonly role: string | null\n readonly ticketRef: string | null\n readonly claims: Readonly<Record<string, unknown>>\n}\n\n/** Create a frozen Principal with defaults for omitted fields. */\nexport function createPrincipal(partial: Partial<Principal> = {}): Readonly<Principal> {\n const p: Principal = {\n userId: partial.userId ?? null,\n serviceId: partial.serviceId ?? null,\n orgId: partial.orgId ?? null,\n role: partial.role ?? null,\n ticketRef: partial.ticketRef ?? null,\n claims: partial.claims ?? {},\n }\n return deepFreeze(p)\n}\n\n// ---------------------------------------------------------------------------\n// _validateToolName\n// ---------------------------------------------------------------------------\n\n/**\n * Validate tool_name: reject empty, control chars, path separators.\n *\n * Throws EdictumConfigError for:\n * - Empty string\n * - Any C0 control character (code < 0x20), DEL/C1 (U+007F-U+009F)\n * - Unicode line/paragraph separators (U+2028, U+2029)\n * - Forward slash `/`\n * - Backslash `\\`\n */\nexport function _validateToolName(toolName: string): void {\n if (!toolName) {\n throw new EdictumConfigError(`Invalid tool_name: ${JSON.stringify(toolName)}`)\n }\n for (let i = 0; i < toolName.length; i++) {\n const code = toolName.charCodeAt(i)\n const ch = toolName[i]\n if (\n code < 0x20 ||\n (code >= 0x7f && code <= 0x9f) ||\n code === 0x2028 ||\n code === 0x2029 ||\n ch === '/' ||\n ch === '\\\\'\n ) {\n throw new EdictumConfigError(`Invalid tool_name: ${JSON.stringify(toolName)}`)\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// ToolEnvelope\n// ---------------------------------------------------------------------------\n\n/**\n * Immutable snapshot of a tool invocation.\n *\n * Prefer `createEnvelope()` factory for deep-copy guarantees.\n * Direct construction validates tool_name but does NOT deep-copy args.\n */\nexport interface ToolEnvelope {\n // Identity\n readonly toolName: string\n readonly args: Readonly<Record<string, unknown>>\n readonly callId: string\n readonly runId: string\n readonly callIndex: number\n readonly parentCallId: string | null\n\n // Classification\n readonly sideEffect: SideEffect\n readonly idempotent: boolean\n\n // Context\n readonly environment: string\n readonly timestamp: Date\n readonly caller: string\n readonly toolUseId: string | null\n\n // Principal\n readonly principal: Readonly<Principal> | null\n\n // Extracted convenience fields\n readonly bashCommand: string | null\n readonly filePath: string | null\n\n // Extensible\n readonly metadata: Readonly<Record<string, unknown>>\n}\n\n// ---------------------------------------------------------------------------\n// deepFreeze\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively freeze an object and all nested objects.\n *\n * Date objects are skipped — Object.freeze() cannot prevent mutation\n * via Date prototype methods (setFullYear, setTime, etc.) because Date\n * stores state in internal slots, not own properties.\n */\nexport function deepFreeze<T>(obj: T): T {\n if (obj === null || obj === undefined || typeof obj !== 'object') {\n return obj\n }\n // Date internal slots are not freezable — skip to avoid false sense of safety\n if (obj instanceof Date) {\n return obj\n }\n // RegExp with global/sticky flags stores mutable lastIndex in internal state.\n // Freezing prevents String.replace() from updating lastIndex → TypeError.\n if (obj instanceof RegExp) {\n return obj\n }\n Object.freeze(obj)\n for (const value of Object.values(obj as Record<string, unknown>)) {\n if (\n value !== null &&\n value !== undefined &&\n typeof value === 'object' &&\n !Object.isFrozen(value)\n ) {\n deepFreeze(value)\n }\n }\n return obj\n}\n\n// ---------------------------------------------------------------------------\n// ToolRegistry\n// ---------------------------------------------------------------------------\n\n/** Maps tool names to governance properties. Unregistered tools default to IRREVERSIBLE. */\nexport class ToolRegistry {\n private readonly _tools: Map<string, ToolConfig> = new Map()\n\n register(\n name: string,\n sideEffect: SideEffect = SideEffect.WRITE,\n idempotent: boolean = false,\n ): void {\n this._tools.set(name, { name, sideEffect, idempotent })\n }\n\n classify(toolName: string, _args: Record<string, unknown>): [SideEffect, boolean] {\n const cfg = this._tools.get(toolName)\n if (cfg) {\n return [cfg.sideEffect as SideEffect, cfg.idempotent]\n }\n return [SideEffect.IRREVERSIBLE, false]\n }\n}\n\n// ---------------------------------------------------------------------------\n// BashClassifier\n// ---------------------------------------------------------------------------\n\n/**\n * Classify bash commands by side-effect level.\n *\n * Default is IRREVERSIBLE. Only downgraded to READ via strict\n * allowlist AND absence of shell operators.\n *\n * This is a heuristic, not a security boundary.\n */\nexport const BashClassifier = {\n READ_ALLOWLIST: [\n 'ls',\n 'cat',\n 'head',\n 'tail',\n 'wc',\n 'find',\n 'grep',\n 'rg',\n 'git status',\n 'git log',\n 'git diff',\n 'git show',\n 'git branch',\n 'git remote',\n 'git tag',\n 'echo',\n 'pwd',\n 'whoami',\n 'date',\n 'which',\n 'file',\n 'stat',\n 'du',\n 'df',\n 'tree',\n 'less',\n 'more',\n ] as const,\n\n SHELL_OPERATORS: [\n '\\n',\n '\\r',\n '<(',\n '<<',\n '$',\n '${',\n '>',\n '>>',\n '|',\n ';',\n '&&',\n '||',\n '$(',\n '`',\n '#{',\n ] as const,\n\n classify(command: string): SideEffect {\n const stripped = command.trim()\n if (!stripped) {\n return SideEffect.READ\n }\n\n for (const op of BashClassifier.SHELL_OPERATORS) {\n if (stripped.includes(op)) {\n return SideEffect.IRREVERSIBLE\n }\n }\n\n for (const allowed of BashClassifier.READ_ALLOWLIST) {\n if (stripped === allowed || stripped.startsWith(allowed + ' ')) {\n return SideEffect.READ\n }\n }\n\n return SideEffect.IRREVERSIBLE\n },\n} as const\n\n// ---------------------------------------------------------------------------\n// safeDeepCopy — structuredClone with JSON roundtrip fallback\n// ---------------------------------------------------------------------------\n\nfunction safeDeepCopy<T>(value: T): T {\n try {\n return structuredClone(value)\n } catch {\n return JSON.parse(JSON.stringify(value)) as T\n }\n}\n\n// ---------------------------------------------------------------------------\n// createEnvelope\n// ---------------------------------------------------------------------------\n\n/** Options for `createEnvelope()` beyond the required positional args. */\nexport interface CreateEnvelopeOptions {\n readonly runId?: string\n readonly callIndex?: number\n readonly callId?: string\n readonly parentCallId?: string | null\n readonly sideEffect?: SideEffect\n readonly idempotent?: boolean\n readonly environment?: string\n readonly timestamp?: Date\n readonly caller?: string\n readonly toolUseId?: string | null\n readonly principal?: Principal | null\n readonly metadata?: Record<string, unknown>\n readonly registry?: ToolRegistry | null\n}\n\n/**\n * Factory that enforces immutability guarantees.\n *\n * Prefer this factory over direct construction — it deep-copies args\n * and metadata to ensure the envelope is a true immutable snapshot.\n */\nexport function createEnvelope(\n toolName: string,\n toolInput: Record<string, unknown>,\n options: CreateEnvelopeOptions = {},\n): Readonly<ToolEnvelope> {\n _validateToolName(toolName)\n\n // Deep-copy for immutability\n const safeArgs = safeDeepCopy(toolInput)\n\n // Deep-copy metadata\n const safeMetadata = options.metadata ? safeDeepCopy(options.metadata) : {}\n\n // Deep-copy Principal to protect claims dict\n let safePrincipal: Readonly<Principal> | null = null\n if (options.principal != null) {\n const p = options.principal\n safePrincipal = createPrincipal({\n userId: p.userId,\n serviceId: p.serviceId,\n orgId: p.orgId,\n role: p.role,\n ticketRef: p.ticketRef,\n claims: p.claims ? safeDeepCopy(p.claims as Record<string, unknown>) : {},\n })\n }\n\n // Classification: explicit options > registry > defaults\n const registry = options.registry ?? null\n let sideEffect: SideEffect = options.sideEffect ?? SideEffect.IRREVERSIBLE\n let idempotent = options.idempotent ?? false\n let bashCommand: string | null = null\n let filePath: string | null = null\n\n // Registry overrides defaults but not explicit options\n if (registry) {\n const [regEffect, regIdempotent] = registry.classify(toolName, safeArgs)\n if (options.sideEffect == null) sideEffect = regEffect\n if (options.idempotent == null) idempotent = regIdempotent\n }\n\n // Extract convenience fields (handle both snake_case and camelCase keys)\n if (toolName === 'Bash') {\n bashCommand = (safeArgs.command as string) ?? ''\n // BashClassifier wins over registry but NOT over explicit caller options\n if (options.sideEffect == null) {\n sideEffect = BashClassifier.classify(bashCommand)\n }\n } else if (toolName === 'Read' || toolName === 'Glob' || toolName === 'Grep') {\n filePath =\n (safeArgs.file_path as string) ??\n (safeArgs.filePath as string) ??\n (safeArgs.path as string) ??\n null\n } else if (toolName === 'Write' || toolName === 'Edit') {\n filePath =\n (safeArgs.file_path as string) ??\n (safeArgs.filePath as string) ??\n (safeArgs.path as string) ??\n null\n }\n\n const envelope: ToolEnvelope = {\n toolName,\n args: safeArgs,\n callId: options.callId ?? randomUUID(),\n runId: options.runId ?? '',\n callIndex: options.callIndex ?? 0,\n parentCallId: options.parentCallId ?? null,\n sideEffect,\n idempotent,\n environment: options.environment ?? 'production',\n timestamp: options.timestamp ?? new Date(),\n caller: options.caller ?? '',\n toolUseId: options.toolUseId ?? null,\n principal: safePrincipal,\n bashCommand,\n filePath,\n metadata: safeMetadata,\n }\n\n return deepFreeze(envelope)\n}\n"],"mappings":"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","names":[]}
@@ -4,7 +4,7 @@ import {
4
4
  EdictumToolError,
5
5
  SideEffect,
6
6
  createEnvelope
7
- } from "./chunk-CRPQFRYJ.mjs";
7
+ } from "./chunk-2YSBMUK5.mjs";
8
8
 
9
9
  // src/approval.ts
10
10
  import { randomUUID } from "crypto";
@@ -34,11 +34,8 @@ var RedactionPolicy = class _RedactionPolicy {
34
34
  "passphrase"
35
35
  ]);
36
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]"],
37
+ [String.raw`(export\s+\w*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)\w*=)\S+`, "$1[REDACTED]"],
38
+ [String.raw`((?:^|\s)-p\s*|--password[= ])\S+`, "$1[REDACTED]"],
42
39
  [String.raw`(://\w+:)\S+(@)`, "$1[REDACTED]$2"]
43
40
  ];
44
41
  static SECRET_VALUE_PATTERNS = [
@@ -68,25 +65,18 @@ var RedactionPolicy = class _RedactionPolicy {
68
65
  }
69
66
  }
70
67
  }
71
- this._patterns = [
72
- ...customPatterns ?? [],
73
- ..._RedactionPolicy.BASH_REDACTION_PATTERNS
74
- ];
68
+ this._patterns = [...customPatterns ?? [], ..._RedactionPolicy.BASH_REDACTION_PATTERNS];
75
69
  this._compiledPatterns = this._patterns.map(
76
70
  ([pattern, replacement]) => [new RegExp(pattern, "g"), replacement]
77
71
  );
78
- this._compiledSecretPatterns = _RedactionPolicy.SECRET_VALUE_PATTERNS.map(
79
- (p) => new RegExp(p)
80
- );
72
+ this._compiledSecretPatterns = _RedactionPolicy.SECRET_VALUE_PATTERNS.map((p) => new RegExp(p));
81
73
  this._detectValues = detectSecretValues;
82
74
  }
83
75
  /** Recursively redact sensitive data from tool arguments. */
84
76
  redactArgs(args) {
85
77
  if (args !== null && typeof args === "object" && !Array.isArray(args)) {
86
78
  const result = {};
87
- for (const [key, value] of Object.entries(
88
- args
89
- )) {
79
+ for (const [key, value] of Object.entries(args)) {
90
80
  result[key] = this._isSensitiveKey(key) ? "[REDACTED]" : this.redactArgs(value);
91
81
  }
92
82
  return result;
@@ -98,6 +88,18 @@ var RedactionPolicy = class _RedactionPolicy {
98
88
  if (this._detectValues && this._looksLikeSecret(args)) {
99
89
  return "[REDACTED]";
100
90
  }
91
+ if (this._detectValues) {
92
+ const capped = args.length > _RedactionPolicy.MAX_REGEX_INPUT ? args.slice(0, _RedactionPolicy.MAX_REGEX_INPUT) : args;
93
+ let redacted = capped;
94
+ for (const [pattern, replacement] of this._compiledPatterns) {
95
+ pattern.lastIndex = 0;
96
+ redacted = redacted.replace(pattern, replacement);
97
+ }
98
+ if (redacted.length > 1e3) {
99
+ return redacted.slice(0, 997) + "...";
100
+ }
101
+ return redacted;
102
+ }
101
103
  if (args.length > 1e3) {
102
104
  return args.slice(0, 997) + "...";
103
105
  }
@@ -168,7 +170,7 @@ var RedactionPolicy = class _RedactionPolicy {
168
170
 
169
171
  // src/approval.ts
170
172
  function sanitizeForTerminal(s) {
171
- return s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/[\x00-\x1f\x7f]/g, "");
173
+ return s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/[\x00-\x1f\x7f-\x9f\u2028\u2029]/g, "");
172
174
  }
173
175
  var ApprovalStatus = {
174
176
  PENDING: "pending",
@@ -232,9 +234,7 @@ var LocalApprovalBackend = class {
232
234
  const effectiveTimeout = timeout ?? (request ? request.timeout : 300);
233
235
  try {
234
236
  const response = await this._readStdin(approvalId, effectiveTimeout);
235
- const approved = ["y", "yes", "approve"].includes(
236
- response.trim().toLowerCase()
237
- );
237
+ const approved = ["y", "yes", "approve"].includes(response.trim().toLowerCase());
238
238
  const status = approved ? ApprovalStatus.APPROVED : ApprovalStatus.DENIED;
239
239
  return createApprovalDecision({
240
240
  approved,
@@ -707,10 +707,7 @@ var GovernancePipeline = class {
707
707
  }
708
708
  }
709
709
  const pe = hasPolicyError(contractsEvaluated);
710
- const observeResults = await this._evaluateObserveContracts(
711
- envelope,
712
- session
713
- );
710
+ const observeResults = await this._evaluateObserveContracts(envelope, session);
714
711
  return createPreDecision({
715
712
  action: "allow",
716
713
  hooksEvaluated,
@@ -765,23 +762,17 @@ var GovernancePipeline = class {
765
762
  text = policy.redactResult(text, text.length + 100);
766
763
  }
767
764
  redactedResponse = text;
768
- warnings.push(
769
- `\u26A0\uFE0F Content redacted by ${contract.name}.`
770
- );
765
+ warnings.push(`\u26A0\uFE0F Content redacted by ${contract.name}.`);
771
766
  } else if (effect === "deny" && isSafe) {
772
767
  redactedResponse = `[OUTPUT SUPPRESSED] ${verdict.message}`;
773
768
  outputSuppressed = true;
774
- warnings.push(
775
- `\u26A0\uFE0F Output suppressed by ${contract.name}.`
776
- );
769
+ warnings.push(`\u26A0\uFE0F Output suppressed by ${contract.name}.`);
777
770
  } else if ((effect === "redact" || effect === "deny") && !isSafe) {
778
771
  warnings.push(
779
772
  `\u26A0\uFE0F ${verdict.message} Tool already executed \u2014 assess before proceeding.`
780
773
  );
781
774
  } else if (isSafe) {
782
- warnings.push(
783
- `\u26A0\uFE0F ${verdict.message} Consider retrying.`
784
- );
775
+ warnings.push(`\u26A0\uFE0F ${verdict.message} Consider retrying.`);
785
776
  } else {
786
777
  warnings.push(
787
778
  `\u26A0\uFE0F ${verdict.message} Tool already executed \u2014 assess before proceeding.`
@@ -803,10 +794,7 @@ var GovernancePipeline = class {
803
794
  try {
804
795
  verdict = await contract.check(envelope, toolResponse);
805
796
  } catch (exc) {
806
- verdict = Verdict.fail(
807
- `Observe-mode postcondition error: ${exc}`,
808
- { policy_error: true }
809
- );
797
+ verdict = Verdict.fail(`Observe-mode postcondition error: ${exc}`, { policy_error: true });
810
798
  }
811
799
  const record = {
812
800
  name: contract.name,
@@ -824,9 +812,7 @@ var GovernancePipeline = class {
824
812
  warnings.push(`\u26A0\uFE0F [observe] ${verdict.message}`);
825
813
  }
826
814
  }
827
- const postconditionsPassed = contractsEvaluated.length > 0 ? contractsEvaluated.every(
828
- (c) => c["passed"] === true || c["observed"] === true
829
- ) : true;
815
+ const postconditionsPassed = contractsEvaluated.length > 0 ? contractsEvaluated.every((c) => c["passed"] === true || c["observed"] === true) : true;
830
816
  const pe = hasPolicyError(contractsEvaluated);
831
817
  return createPostDecision({
832
818
  toolSuccess,
@@ -847,17 +833,12 @@ var GovernancePipeline = class {
847
833
  */
848
834
  async _evaluateObserveContracts(envelope, session) {
849
835
  const results = [];
850
- for (const contract of this._guard.getObservePreconditions(
851
- envelope
852
- )) {
836
+ for (const contract of this._guard.getObservePreconditions(envelope)) {
853
837
  let verdict;
854
838
  try {
855
839
  verdict = await contract.check(envelope);
856
840
  } catch (exc) {
857
- verdict = Verdict.fail(
858
- `Observe-mode precondition error: ${exc}`,
859
- { policy_error: true }
860
- );
841
+ verdict = Verdict.fail(`Observe-mode precondition error: ${exc}`, { policy_error: true });
861
842
  }
862
843
  results.push({
863
844
  name: contract.name,
@@ -867,17 +848,12 @@ var GovernancePipeline = class {
867
848
  source: contract.source ?? "yaml_precondition"
868
849
  });
869
850
  }
870
- for (const contract of this._guard.getObserveSandboxContracts(
871
- envelope
872
- )) {
851
+ for (const contract of this._guard.getObserveSandboxContracts(envelope)) {
873
852
  let verdict;
874
853
  try {
875
854
  verdict = await contract.check(envelope);
876
855
  } catch (exc) {
877
- verdict = Verdict.fail(
878
- `Observe-mode sandbox error: ${exc}`,
879
- { policy_error: true }
880
- );
856
+ verdict = Verdict.fail(`Observe-mode sandbox error: ${exc}`, { policy_error: true });
881
857
  }
882
858
  results.push({
883
859
  name: contract.name,
@@ -892,10 +868,9 @@ var GovernancePipeline = class {
892
868
  try {
893
869
  verdict = await contract.check(session);
894
870
  } catch (exc) {
895
- verdict = Verdict.fail(
896
- `Observe-mode session contract error: ${exc}`,
897
- { policy_error: true }
898
- );
871
+ verdict = Verdict.fail(`Observe-mode session contract error: ${exc}`, {
872
+ policy_error: true
873
+ });
899
874
  }
900
875
  results.push({
901
876
  name: contract.name,
@@ -923,7 +898,7 @@ function _validateStorageKeyComponent(value, label) {
923
898
  }
924
899
  for (let i = 0; i < value.length; i++) {
925
900
  const code = value.charCodeAt(i);
926
- if (code < 32 || code === 127) {
901
+ if (code < 32 || code >= 127 && code <= 159 || code === 8232 || code === 8233) {
927
902
  throw new EdictumConfigError(`Invalid ${label}: ${JSON.stringify(value)}`);
928
903
  }
929
904
  }
@@ -962,14 +937,10 @@ var Session = class {
962
937
  }
963
938
  async toolExecutionCount(tool) {
964
939
  _validateStorageKeyComponent(tool, "tool_name");
965
- return Number(
966
- await this._backend.get(`s:${this._sid}:tool:${tool}`) ?? 0
967
- );
940
+ return Number(await this._backend.get(`s:${this._sid}:tool:${tool}`) ?? 0);
968
941
  }
969
942
  async consecutiveFailures() {
970
- return Number(
971
- await this._backend.get(`s:${this._sid}:consec_fail`) ?? 0
972
- );
943
+ return Number(await this._backend.get(`s:${this._sid}:consec_fail`) ?? 0);
973
944
  }
974
945
  /**
975
946
  * Pre-fetch multiple session counters in a single backend call.
@@ -982,10 +953,7 @@ var Session = class {
982
953
  * backends without batchGet support.
983
954
  */
984
955
  async batchGetCounters(options) {
985
- const keys = [
986
- `s:${this._sid}:attempts`,
987
- `s:${this._sid}:execs`
988
- ];
956
+ const keys = [`s:${this._sid}:attempts`, `s:${this._sid}:execs`];
989
957
  const keyLabels = ["attempts", "execs"];
990
958
  if (options?.includeTool != null) {
991
959
  _validateStorageKeyComponent(options.includeTool, "tool_name");
@@ -1093,46 +1061,22 @@ async function run(guard, toolName, args, toolCallable, options) {
1093
1061
  principal: principalDict
1094
1062
  }
1095
1063
  );
1096
- await _emitRunPreAudit(
1097
- guard,
1098
- envelope,
1099
- session,
1100
- AuditAction.CALL_APPROVAL_REQUESTED,
1101
- pre
1102
- );
1064
+ await _emitRunPreAudit(guard, envelope, session, AuditAction.CALL_APPROVAL_REQUESTED, pre);
1103
1065
  const decision = await guard._approvalBackend.waitForDecision(
1104
1066
  approvalRequest.approvalId,
1105
1067
  pre.approvalTimeout
1106
1068
  );
1107
1069
  let approved = false;
1108
1070
  if (decision.status === ApprovalStatus.TIMEOUT) {
1109
- await _emitRunPreAudit(
1110
- guard,
1111
- envelope,
1112
- session,
1113
- AuditAction.CALL_APPROVAL_TIMEOUT,
1114
- pre
1115
- );
1071
+ await _emitRunPreAudit(guard, envelope, session, AuditAction.CALL_APPROVAL_TIMEOUT, pre);
1116
1072
  if (pre.approvalTimeoutEffect === "allow") {
1117
1073
  approved = true;
1118
1074
  }
1119
1075
  } else if (!decision.approved) {
1120
- await _emitRunPreAudit(
1121
- guard,
1122
- envelope,
1123
- session,
1124
- AuditAction.CALL_APPROVAL_DENIED,
1125
- pre
1126
- );
1076
+ await _emitRunPreAudit(guard, envelope, session, AuditAction.CALL_APPROVAL_DENIED, pre);
1127
1077
  } else {
1128
1078
  approved = true;
1129
- await _emitRunPreAudit(
1130
- guard,
1131
- envelope,
1132
- session,
1133
- AuditAction.CALL_APPROVAL_GRANTED,
1134
- pre
1135
- );
1079
+ await _emitRunPreAudit(guard, envelope, session, AuditAction.CALL_APPROVAL_GRANTED, pre);
1136
1080
  }
1137
1081
  if (approved) {
1138
1082
  if (guard._onAllow) {
@@ -1168,11 +1112,7 @@ async function run(guard, toolName, args, toolCallable, options) {
1168
1112
  } catch {
1169
1113
  }
1170
1114
  }
1171
- throw new EdictumDenied(
1172
- pre.reason ?? "denied",
1173
- pre.decisionSource,
1174
- pre.decisionName
1175
- );
1115
+ throw new EdictumDenied(pre.reason ?? "denied", pre.decisionSource, pre.decisionName);
1176
1116
  }
1177
1117
  } else {
1178
1118
  for (const cr of pre.contractsEvaluated) {
@@ -1196,13 +1136,7 @@ async function run(guard, toolName, args, toolCallable, options) {
1196
1136
  await guard.auditSink.emit(observedEvent);
1197
1137
  }
1198
1138
  }
1199
- await _emitRunPreAudit(
1200
- guard,
1201
- envelope,
1202
- session,
1203
- AuditAction.CALL_ALLOWED,
1204
- pre
1205
- );
1139
+ await _emitRunPreAudit(guard, envelope, session, AuditAction.CALL_ALLOWED, pre);
1206
1140
  if (guard._onAllow) {
1207
1141
  try {
1208
1142
  guard._onAllow(envelope);
@@ -1296,4 +1230,4 @@ export {
1296
1230
  defaultSuccessCheck,
1297
1231
  run
1298
1232
  };
1299
- //# sourceMappingURL=chunk-X5E2YY35.mjs.map
1233
+ //# sourceMappingURL=chunk-JOBPRXVE.mjs.map