@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/dist/index.cjs CHANGED
@@ -81,7 +81,7 @@ function _validateToolName(toolName) {
81
81
  for (let i = 0; i < toolName.length; i++) {
82
82
  const code = toolName.charCodeAt(i);
83
83
  const ch = toolName[i];
84
- if (code < 32 || code === 127 || ch === "/" || ch === "\\") {
84
+ if (code < 32 || code >= 127 && code <= 159 || code === 8232 || code === 8233 || ch === "/" || ch === "\\") {
85
85
  throw new EdictumConfigError(`Invalid tool_name: ${JSON.stringify(toolName)}`);
86
86
  }
87
87
  }
@@ -323,7 +323,7 @@ function _validateStorageKeyComponent(value, label) {
323
323
  }
324
324
  for (let i = 0; i < value.length; i++) {
325
325
  const code = value.charCodeAt(i);
326
- if (code < 32 || code === 127) {
326
+ if (code < 32 || code >= 127 && code <= 159 || code === 8232 || code === 8233) {
327
327
  throw new EdictumConfigError(`Invalid ${label}: ${JSON.stringify(value)}`);
328
328
  }
329
329
  }
@@ -368,14 +368,10 @@ var init_session = __esm({
368
368
  }
369
369
  async toolExecutionCount(tool) {
370
370
  _validateStorageKeyComponent(tool, "tool_name");
371
- return Number(
372
- await this._backend.get(`s:${this._sid}:tool:${tool}`) ?? 0
373
- );
371
+ return Number(await this._backend.get(`s:${this._sid}:tool:${tool}`) ?? 0);
374
372
  }
375
373
  async consecutiveFailures() {
376
- return Number(
377
- await this._backend.get(`s:${this._sid}:consec_fail`) ?? 0
378
- );
374
+ return Number(await this._backend.get(`s:${this._sid}:consec_fail`) ?? 0);
379
375
  }
380
376
  /**
381
377
  * Pre-fetch multiple session counters in a single backend call.
@@ -388,10 +384,7 @@ var init_session = __esm({
388
384
  * backends without batchGet support.
389
385
  */
390
386
  async batchGetCounters(options) {
391
- const keys = [
392
- `s:${this._sid}:attempts`,
393
- `s:${this._sid}:execs`
394
- ];
387
+ const keys = [`s:${this._sid}:attempts`, `s:${this._sid}:execs`];
395
388
  const keyLabels = ["attempts", "execs"];
396
389
  if (options?.includeTool != null) {
397
390
  _validateStorageKeyComponent(options.includeTool, "tool_name");
@@ -448,11 +441,8 @@ var init_redaction = __esm({
448
441
  "passphrase"
449
442
  ]);
450
443
  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]"],
444
+ [String.raw`(export\s+\w*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)\w*=)\S+`, "$1[REDACTED]"],
445
+ [String.raw`((?:^|\s)-p\s*|--password[= ])\S+`, "$1[REDACTED]"],
456
446
  [String.raw`(://\w+:)\S+(@)`, "$1[REDACTED]$2"]
457
447
  ];
458
448
  static SECRET_VALUE_PATTERNS = [
@@ -482,25 +472,18 @@ var init_redaction = __esm({
482
472
  }
483
473
  }
484
474
  }
485
- this._patterns = [
486
- ...customPatterns ?? [],
487
- ..._RedactionPolicy.BASH_REDACTION_PATTERNS
488
- ];
475
+ this._patterns = [...customPatterns ?? [], ..._RedactionPolicy.BASH_REDACTION_PATTERNS];
489
476
  this._compiledPatterns = this._patterns.map(
490
477
  ([pattern, replacement]) => [new RegExp(pattern, "g"), replacement]
491
478
  );
492
- this._compiledSecretPatterns = _RedactionPolicy.SECRET_VALUE_PATTERNS.map(
493
- (p) => new RegExp(p)
494
- );
479
+ this._compiledSecretPatterns = _RedactionPolicy.SECRET_VALUE_PATTERNS.map((p) => new RegExp(p));
495
480
  this._detectValues = detectSecretValues;
496
481
  }
497
482
  /** Recursively redact sensitive data from tool arguments. */
498
483
  redactArgs(args) {
499
484
  if (args !== null && typeof args === "object" && !Array.isArray(args)) {
500
485
  const result = {};
501
- for (const [key, value] of Object.entries(
502
- args
503
- )) {
486
+ for (const [key, value] of Object.entries(args)) {
504
487
  result[key] = this._isSensitiveKey(key) ? "[REDACTED]" : this.redactArgs(value);
505
488
  }
506
489
  return result;
@@ -512,6 +495,18 @@ var init_redaction = __esm({
512
495
  if (this._detectValues && this._looksLikeSecret(args)) {
513
496
  return "[REDACTED]";
514
497
  }
498
+ if (this._detectValues) {
499
+ const capped = args.length > _RedactionPolicy.MAX_REGEX_INPUT ? args.slice(0, _RedactionPolicy.MAX_REGEX_INPUT) : args;
500
+ let redacted = capped;
501
+ for (const [pattern, replacement] of this._compiledPatterns) {
502
+ pattern.lastIndex = 0;
503
+ redacted = redacted.replace(pattern, replacement);
504
+ }
505
+ if (redacted.length > 1e3) {
506
+ return redacted.slice(0, 997) + "...";
507
+ }
508
+ return redacted;
509
+ }
515
510
  if (args.length > 1e3) {
516
511
  return args.slice(0, 997) + "...";
517
512
  }
@@ -584,7 +579,7 @@ var init_redaction = __esm({
584
579
 
585
580
  // src/approval.ts
586
581
  function sanitizeForTerminal(s) {
587
- return s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/[\x00-\x1f\x7f]/g, "");
582
+ return s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/[\x00-\x1f\x7f-\x9f\u2028\u2029]/g, "");
588
583
  }
589
584
  function createApprovalRequest(fields) {
590
585
  const request = {
@@ -655,9 +650,7 @@ var init_approval = __esm({
655
650
  const effectiveTimeout = timeout ?? (request ? request.timeout : 300);
656
651
  try {
657
652
  const response = await this._readStdin(approvalId, effectiveTimeout);
658
- const approved = ["y", "yes", "approve"].includes(
659
- response.trim().toLowerCase()
660
- );
653
+ const approved = ["y", "yes", "approve"].includes(response.trim().toLowerCase());
661
654
  const status = approved ? ApprovalStatus.APPROVED : ApprovalStatus.DENIED;
662
655
  return createApprovalDecision({
663
656
  approved,
@@ -1139,10 +1132,7 @@ var init_pipeline = __esm({
1139
1132
  }
1140
1133
  }
1141
1134
  const pe = hasPolicyError(contractsEvaluated);
1142
- const observeResults = await this._evaluateObserveContracts(
1143
- envelope,
1144
- session
1145
- );
1135
+ const observeResults = await this._evaluateObserveContracts(envelope, session);
1146
1136
  return createPreDecision({
1147
1137
  action: "allow",
1148
1138
  hooksEvaluated,
@@ -1197,23 +1187,17 @@ var init_pipeline = __esm({
1197
1187
  text = policy.redactResult(text, text.length + 100);
1198
1188
  }
1199
1189
  redactedResponse = text;
1200
- warnings.push(
1201
- `\u26A0\uFE0F Content redacted by ${contract.name}.`
1202
- );
1190
+ warnings.push(`\u26A0\uFE0F Content redacted by ${contract.name}.`);
1203
1191
  } else if (effect === "deny" && isSafe) {
1204
1192
  redactedResponse = `[OUTPUT SUPPRESSED] ${verdict.message}`;
1205
1193
  outputSuppressed = true;
1206
- warnings.push(
1207
- `\u26A0\uFE0F Output suppressed by ${contract.name}.`
1208
- );
1194
+ warnings.push(`\u26A0\uFE0F Output suppressed by ${contract.name}.`);
1209
1195
  } else if ((effect === "redact" || effect === "deny") && !isSafe) {
1210
1196
  warnings.push(
1211
1197
  `\u26A0\uFE0F ${verdict.message} Tool already executed \u2014 assess before proceeding.`
1212
1198
  );
1213
1199
  } else if (isSafe) {
1214
- warnings.push(
1215
- `\u26A0\uFE0F ${verdict.message} Consider retrying.`
1216
- );
1200
+ warnings.push(`\u26A0\uFE0F ${verdict.message} Consider retrying.`);
1217
1201
  } else {
1218
1202
  warnings.push(
1219
1203
  `\u26A0\uFE0F ${verdict.message} Tool already executed \u2014 assess before proceeding.`
@@ -1235,10 +1219,7 @@ var init_pipeline = __esm({
1235
1219
  try {
1236
1220
  verdict = await contract.check(envelope, toolResponse);
1237
1221
  } catch (exc) {
1238
- verdict = Verdict.fail(
1239
- `Observe-mode postcondition error: ${exc}`,
1240
- { policy_error: true }
1241
- );
1222
+ verdict = Verdict.fail(`Observe-mode postcondition error: ${exc}`, { policy_error: true });
1242
1223
  }
1243
1224
  const record = {
1244
1225
  name: contract.name,
@@ -1256,9 +1237,7 @@ var init_pipeline = __esm({
1256
1237
  warnings.push(`\u26A0\uFE0F [observe] ${verdict.message}`);
1257
1238
  }
1258
1239
  }
1259
- const postconditionsPassed = contractsEvaluated.length > 0 ? contractsEvaluated.every(
1260
- (c) => c["passed"] === true || c["observed"] === true
1261
- ) : true;
1240
+ const postconditionsPassed = contractsEvaluated.length > 0 ? contractsEvaluated.every((c) => c["passed"] === true || c["observed"] === true) : true;
1262
1241
  const pe = hasPolicyError(contractsEvaluated);
1263
1242
  return createPostDecision({
1264
1243
  toolSuccess,
@@ -1279,17 +1258,12 @@ var init_pipeline = __esm({
1279
1258
  */
1280
1259
  async _evaluateObserveContracts(envelope, session) {
1281
1260
  const results = [];
1282
- for (const contract of this._guard.getObservePreconditions(
1283
- envelope
1284
- )) {
1261
+ for (const contract of this._guard.getObservePreconditions(envelope)) {
1285
1262
  let verdict;
1286
1263
  try {
1287
1264
  verdict = await contract.check(envelope);
1288
1265
  } catch (exc) {
1289
- verdict = Verdict.fail(
1290
- `Observe-mode precondition error: ${exc}`,
1291
- { policy_error: true }
1292
- );
1266
+ verdict = Verdict.fail(`Observe-mode precondition error: ${exc}`, { policy_error: true });
1293
1267
  }
1294
1268
  results.push({
1295
1269
  name: contract.name,
@@ -1299,17 +1273,12 @@ var init_pipeline = __esm({
1299
1273
  source: contract.source ?? "yaml_precondition"
1300
1274
  });
1301
1275
  }
1302
- for (const contract of this._guard.getObserveSandboxContracts(
1303
- envelope
1304
- )) {
1276
+ for (const contract of this._guard.getObserveSandboxContracts(envelope)) {
1305
1277
  let verdict;
1306
1278
  try {
1307
1279
  verdict = await contract.check(envelope);
1308
1280
  } catch (exc) {
1309
- verdict = Verdict.fail(
1310
- `Observe-mode sandbox error: ${exc}`,
1311
- { policy_error: true }
1312
- );
1281
+ verdict = Verdict.fail(`Observe-mode sandbox error: ${exc}`, { policy_error: true });
1313
1282
  }
1314
1283
  results.push({
1315
1284
  name: contract.name,
@@ -1324,10 +1293,9 @@ var init_pipeline = __esm({
1324
1293
  try {
1325
1294
  verdict = await contract.check(session);
1326
1295
  } catch (exc) {
1327
- verdict = Verdict.fail(
1328
- `Observe-mode session contract error: ${exc}`,
1329
- { policy_error: true }
1330
- );
1296
+ verdict = Verdict.fail(`Observe-mode session contract error: ${exc}`, {
1297
+ policy_error: true
1298
+ });
1331
1299
  }
1332
1300
  results.push({
1333
1301
  name: contract.name,
@@ -1430,46 +1398,22 @@ async function run(guard, toolName, args, toolCallable, options) {
1430
1398
  principal: principalDict
1431
1399
  }
1432
1400
  );
1433
- await _emitRunPreAudit(
1434
- guard,
1435
- envelope,
1436
- session,
1437
- AuditAction.CALL_APPROVAL_REQUESTED,
1438
- pre
1439
- );
1401
+ await _emitRunPreAudit(guard, envelope, session, AuditAction.CALL_APPROVAL_REQUESTED, pre);
1440
1402
  const decision = await guard._approvalBackend.waitForDecision(
1441
1403
  approvalRequest.approvalId,
1442
1404
  pre.approvalTimeout
1443
1405
  );
1444
1406
  let approved = false;
1445
1407
  if (decision.status === ApprovalStatus.TIMEOUT) {
1446
- await _emitRunPreAudit(
1447
- guard,
1448
- envelope,
1449
- session,
1450
- AuditAction.CALL_APPROVAL_TIMEOUT,
1451
- pre
1452
- );
1408
+ await _emitRunPreAudit(guard, envelope, session, AuditAction.CALL_APPROVAL_TIMEOUT, pre);
1453
1409
  if (pre.approvalTimeoutEffect === "allow") {
1454
1410
  approved = true;
1455
1411
  }
1456
1412
  } else if (!decision.approved) {
1457
- await _emitRunPreAudit(
1458
- guard,
1459
- envelope,
1460
- session,
1461
- AuditAction.CALL_APPROVAL_DENIED,
1462
- pre
1463
- );
1413
+ await _emitRunPreAudit(guard, envelope, session, AuditAction.CALL_APPROVAL_DENIED, pre);
1464
1414
  } else {
1465
1415
  approved = true;
1466
- await _emitRunPreAudit(
1467
- guard,
1468
- envelope,
1469
- session,
1470
- AuditAction.CALL_APPROVAL_GRANTED,
1471
- pre
1472
- );
1416
+ await _emitRunPreAudit(guard, envelope, session, AuditAction.CALL_APPROVAL_GRANTED, pre);
1473
1417
  }
1474
1418
  if (approved) {
1475
1419
  if (guard._onAllow) {
@@ -1505,11 +1449,7 @@ async function run(guard, toolName, args, toolCallable, options) {
1505
1449
  } catch {
1506
1450
  }
1507
1451
  }
1508
- throw new EdictumDenied(
1509
- pre.reason ?? "denied",
1510
- pre.decisionSource,
1511
- pre.decisionName
1512
- );
1452
+ throw new EdictumDenied(pre.reason ?? "denied", pre.decisionSource, pre.decisionName);
1513
1453
  }
1514
1454
  } else {
1515
1455
  for (const cr of pre.contractsEvaluated) {
@@ -1533,13 +1473,7 @@ async function run(guard, toolName, args, toolCallable, options) {
1533
1473
  await guard.auditSink.emit(observedEvent);
1534
1474
  }
1535
1475
  }
1536
- await _emitRunPreAudit(
1537
- guard,
1538
- envelope,
1539
- session,
1540
- AuditAction.CALL_ALLOWED,
1541
- pre
1542
- );
1476
+ await _emitRunPreAudit(guard, envelope, session, AuditAction.CALL_ALLOWED, pre);
1543
1477
  if (guard._onAllow) {
1544
1478
  try {
1545
1479
  guard._onAllow(envelope);
@@ -1966,21 +1900,15 @@ function classifyFinding(contractId, verdictMessage) {
1966
1900
  const contractLower = contractId.toLowerCase();
1967
1901
  const messageLower = (verdictMessage || "").toLowerCase();
1968
1902
  const piiTerms = ["pii", "ssn", "patient", "name", "dob"];
1969
- if (piiTerms.some(
1970
- (term) => contractLower.includes(term) || messageLower.includes(term)
1971
- )) {
1903
+ if (piiTerms.some((term) => contractLower.includes(term) || messageLower.includes(term))) {
1972
1904
  return "pii_detected";
1973
1905
  }
1974
1906
  const secretTerms = ["secret", "token", "key", "credential", "password"];
1975
- if (secretTerms.some(
1976
- (term) => contractLower.includes(term) || messageLower.includes(term)
1977
- )) {
1907
+ if (secretTerms.some((term) => contractLower.includes(term) || messageLower.includes(term))) {
1978
1908
  return "secret_detected";
1979
1909
  }
1980
1910
  const limitTerms = ["session", "limit", "max_calls", "budget"];
1981
- if (limitTerms.some(
1982
- (term) => contractLower.includes(term) || messageLower.includes(term)
1983
- )) {
1911
+ if (limitTerms.some((term) => contractLower.includes(term) || messageLower.includes(term))) {
1984
1912
  return "limit_exceeded";
1985
1913
  }
1986
1914
  return "policy_violation";
@@ -2127,9 +2055,7 @@ function mergeObserveAlongside(merged, layer, label, contractSources, observes)
2127
2055
  for (const contract of layer.contracts ?? []) {
2128
2056
  const cid = contract.id;
2129
2057
  const observeId = `${cid}:candidate`;
2130
- const existingIds = new Set(
2131
- mc.map((c) => c.id)
2132
- );
2058
+ const existingIds = new Set(mc.map((c) => c.id));
2133
2059
  if (existingIds.has(observeId)) {
2134
2060
  throw new EdictumConfigError(
2135
2061
  `observe_alongside collision: generated ID "${observeId}" already exists in the bundle. Rename the conflicting contract or use a different ID for "${cid}".`
@@ -2276,10 +2202,7 @@ function resolveSelector(selector, envelope, outputText, customSelectors) {
2276
2202
  if (rest === "role") return envelope.principal.role;
2277
2203
  if (rest === "ticket_ref") return envelope.principal.ticketRef;
2278
2204
  if (rest.startsWith("claims.")) {
2279
- return resolveNested(
2280
- rest.slice(7),
2281
- envelope.principal.claims
2282
- );
2205
+ return resolveNested(rest.slice(7), envelope.principal.claims);
2283
2206
  }
2284
2207
  return _MISSING;
2285
2208
  }
@@ -2293,10 +2216,7 @@ function resolveSelector(selector, envelope, outputText, customSelectors) {
2293
2216
  return coerceEnvValue(raw);
2294
2217
  }
2295
2218
  if (selector.startsWith("metadata.")) {
2296
- return resolveNested(
2297
- selector.slice(9),
2298
- envelope.metadata
2299
- );
2219
+ return resolveNested(selector.slice(9), envelope.metadata);
2300
2220
  }
2301
2221
  if (customSelectors) {
2302
2222
  const dotPos = selector.indexOf(".");
@@ -2345,13 +2265,31 @@ function evaluateExpression(expr, envelope, outputText, options) {
2345
2265
  const customOps = options?.customOperators ?? null;
2346
2266
  const customSels = options?.customSelectors ?? null;
2347
2267
  if ("all" in expr) {
2348
- return _evalAll(expr.all, envelope, outputText, customOps, customSels);
2268
+ return _evalAll(
2269
+ expr.all,
2270
+ envelope,
2271
+ outputText,
2272
+ customOps,
2273
+ customSels
2274
+ );
2349
2275
  }
2350
2276
  if ("any" in expr) {
2351
- return _evalAny(expr.any, envelope, outputText, customOps, customSels);
2277
+ return _evalAny(
2278
+ expr.any,
2279
+ envelope,
2280
+ outputText,
2281
+ customOps,
2282
+ customSels
2283
+ );
2352
2284
  }
2353
2285
  if ("not" in expr) {
2354
- return _evalNot(expr.not, envelope, outputText, customOps, customSels);
2286
+ return _evalNot(
2287
+ expr.not,
2288
+ envelope,
2289
+ outputText,
2290
+ customOps,
2291
+ customSels
2292
+ );
2355
2293
  }
2356
2294
  const leafKeys = Object.keys(expr);
2357
2295
  if (leafKeys.length !== 1) {
@@ -2406,7 +2344,8 @@ function _applyOperator(op, fieldValue, opValue, selector, customOperators) {
2406
2344
  }
2407
2345
  if (fieldValue === _MISSING || fieldValue == null) return false;
2408
2346
  try {
2409
- if (Object.hasOwn(OPERATORS, op)) return OPERATORS[op](fieldValue, opValue);
2347
+ if (Object.hasOwn(OPERATORS, op))
2348
+ return OPERATORS[op](fieldValue, opValue);
2410
2349
  if (customOperators && Object.hasOwn(customOperators, op)) {
2411
2350
  return Boolean(customOperators[op](fieldValue, opValue));
2412
2351
  }
@@ -2433,10 +2372,7 @@ function expandMessage(template, envelope, outputText, customSelectors) {
2433
2372
  });
2434
2373
  }
2435
2374
  function validateOperators(bundle, customOperators) {
2436
- const known = /* @__PURE__ */ new Set([
2437
- ...BUILTIN_OPERATOR_NAMES,
2438
- ...Object.keys(customOperators ?? {})
2439
- ]);
2375
+ const known = /* @__PURE__ */ new Set([...BUILTIN_OPERATOR_NAMES, ...Object.keys(customOperators ?? {})]);
2440
2376
  const contracts = bundle.contracts ?? [];
2441
2377
  for (const contract of contracts) {
2442
2378
  const when = contract.when;
@@ -2468,9 +2404,7 @@ function _validateExpressionOperators(expr, known, contractId) {
2468
2404
  if (operator != null && typeof operator === "object") {
2469
2405
  for (const opName of Object.keys(operator)) {
2470
2406
  if (!known.has(opName)) {
2471
- throw new EdictumConfigError(
2472
- `Contract '${contractId}': unknown operator '${opName}'`
2473
- );
2407
+ throw new EdictumConfigError(`Contract '${contractId}': unknown operator '${opName}'`);
2474
2408
  }
2475
2409
  }
2476
2410
  }
@@ -2607,7 +2541,16 @@ function compilePost(contract, mode, customOps, customSels) {
2607
2541
  const meta = then.metadata ?? {};
2608
2542
  const check = (envelope, response) => {
2609
2543
  const outputText = response != null ? String(response) : void 0;
2610
- return _evalAndVerdict(whenExpr, envelope, outputText, msgTpl, tags, meta, customOps, customSels);
2544
+ return _evalAndVerdict(
2545
+ whenExpr,
2546
+ envelope,
2547
+ outputText,
2548
+ msgTpl,
2549
+ tags,
2550
+ meta,
2551
+ customOps,
2552
+ customSels
2553
+ );
2611
2554
  };
2612
2555
  const effectValue = then.effect ?? "warn";
2613
2556
  const result = {
@@ -2672,14 +2615,18 @@ function mergeSessionLimits(contract, existing) {
2672
2615
  if ("max_tool_calls" in sessionLimits) {
2673
2616
  const raw = sessionLimits.max_tool_calls;
2674
2617
  if (typeof raw !== "number" || !Number.isFinite(raw)) {
2675
- throw new EdictumConfigError(`Session limit max_tool_calls must be a finite number, got: ${String(raw)}`);
2618
+ throw new EdictumConfigError(
2619
+ `Session limit max_tool_calls must be a finite number, got: ${String(raw)}`
2620
+ );
2676
2621
  }
2677
2622
  maxToolCalls = Math.min(maxToolCalls, raw);
2678
2623
  }
2679
2624
  if ("max_attempts" in sessionLimits) {
2680
2625
  const raw = sessionLimits.max_attempts;
2681
2626
  if (typeof raw !== "number" || !Number.isFinite(raw)) {
2682
- throw new EdictumConfigError(`Session limit max_attempts must be a finite number, got: ${String(raw)}`);
2627
+ throw new EdictumConfigError(
2628
+ `Session limit max_attempts must be a finite number, got: ${String(raw)}`
2629
+ );
2683
2630
  }
2684
2631
  maxAttempts = Math.min(maxAttempts, raw);
2685
2632
  }
@@ -2704,10 +2651,6 @@ function mergeSessionLimits(contract, existing) {
2704
2651
  // src/yaml-engine/sandbox-compile-fn.ts
2705
2652
  init_contracts();
2706
2653
 
2707
- // src/yaml-engine/sandbox-compiler.ts
2708
- var import_node_fs = require("fs");
2709
- var import_node_path = require("path");
2710
-
2711
2654
  // src/fnmatch.ts
2712
2655
  function fnmatch(name, pattern) {
2713
2656
  if (pattern === "*") return true;
@@ -2732,6 +2675,36 @@ function fnmatch(name, pattern) {
2732
2675
  return new RegExp("^" + regex + "$").test(safeName);
2733
2676
  }
2734
2677
 
2678
+ // src/yaml-engine/resolve-path.ts
2679
+ var import_node_fs = require("fs");
2680
+ var import_node_path = require("path");
2681
+ function resolvePath(p) {
2682
+ const cleaned = p.replace(/\0/g, "");
2683
+ const resolved = (0, import_node_path.resolve)(cleaned);
2684
+ try {
2685
+ return (0, import_node_fs.realpathSync)(resolved);
2686
+ } catch (err) {
2687
+ if (err.code !== "ENOENT") {
2688
+ return resolved;
2689
+ }
2690
+ const parts = resolved.split(import_node_path.sep);
2691
+ for (let i = parts.length - 1; i > 0; i--) {
2692
+ const prefix = parts.slice(0, i).join(import_node_path.sep) || "/";
2693
+ try {
2694
+ const realPrefix = (0, import_node_fs.realpathSync)(prefix);
2695
+ const rest = parts.slice(i).join(import_node_path.sep);
2696
+ return (0, import_node_path.join)(realPrefix, rest);
2697
+ } catch (innerErr) {
2698
+ if (innerErr.code !== "ENOENT") {
2699
+ return resolved;
2700
+ }
2701
+ continue;
2702
+ }
2703
+ }
2704
+ return resolved;
2705
+ }
2706
+ }
2707
+
2735
2708
  // src/yaml-engine/sandbox-compiler.ts
2736
2709
  var _REDIRECT_PREFIX_RE = /^(?:\d*>>|>>|\d*>|>|<<|<)/;
2737
2710
  var _SHELL_SEPARATOR_RE = /[;|&\n\r`]|\$\(|\$\{|<\(/;
@@ -2807,21 +2780,25 @@ var _PATH_ARG_KEYS = /* @__PURE__ */ new Set([
2807
2780
  "destination",
2808
2781
  "source",
2809
2782
  "src",
2810
- "dst"
2783
+ "dst",
2784
+ // Common path-like arg names from AI framework tool calls.
2785
+ // IMPORTANT: Only include keys that are unambiguously path-related.
2786
+ // Generic keys like 'name', 'input', 'output', 'from', 'to' are excluded
2787
+ // because they frequently hold non-path values (e.g., { from: "English" }),
2788
+ // and resolvePath() would produce false positives. The heuristic loop below
2789
+ // catches path-like VALUES in any key (containing '..', '~', or '/').
2790
+ "filename",
2791
+ "file",
2792
+ "filepath",
2793
+ "read_path",
2794
+ "write_path"
2811
2795
  ]);
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
2796
  function extractPaths(envelope) {
2820
2797
  const paths = [];
2821
2798
  const seen = /* @__PURE__ */ new Set();
2822
2799
  function add(p) {
2823
2800
  if (!p) return;
2824
- const resolved = _realpath(p);
2801
+ const resolved = resolvePath(p);
2825
2802
  if (!seen.has(resolved)) {
2826
2803
  seen.add(resolved);
2827
2804
  paths.push(resolved);
@@ -2835,6 +2812,17 @@ function extractPaths(envelope) {
2835
2812
  for (const [key, value] of Object.entries(args)) {
2836
2813
  if (typeof value === "string" && value.startsWith("/") && !_PATH_ARG_KEYS.has(key)) add(value);
2837
2814
  }
2815
+ for (const [key, value] of Object.entries(args)) {
2816
+ if (typeof value === "string" && !_PATH_ARG_KEYS.has(key)) {
2817
+ const isUrl = value.includes("://");
2818
+ if (!isUrl && value.includes("../") || // embedded traversal: foo/../etc/passwd
2819
+ value === ".." || // bare parent reference
2820
+ value.startsWith("../") || // parent traversal prefix: ../../etc/passwd
2821
+ value.startsWith("~") || value.startsWith("./") && !isUrl) {
2822
+ add(value);
2823
+ }
2824
+ }
2825
+ }
2838
2826
  const cmd = envelope.bashCommand ?? args.command ?? "";
2839
2827
  if (cmd) {
2840
2828
  for (const token of tokenizeCommand(cmd)) {
@@ -2887,23 +2875,14 @@ function domainMatches(hostname, patterns) {
2887
2875
  }
2888
2876
 
2889
2877
  // 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
2878
  function _pathWithin(filePath, prefix) {
2900
2879
  return filePath === prefix || filePath.startsWith(prefix.replace(/\/+$/, "") + "/");
2901
2880
  }
2902
2881
  function compileSandbox(contract, mode) {
2903
2882
  const contractId = contract.id;
2904
2883
  const toolPatterns = "tools" in contract ? contract.tools : [contract.tool];
2905
- const within = (contract.within ?? []).map(_realpath2);
2906
- const notWithin = (contract.not_within ?? []).map(_realpath2);
2884
+ const within = (contract.within ?? []).map(resolvePath);
2885
+ const notWithin = (contract.not_within ?? []).map(resolvePath);
2907
2886
  const allows = contract.allows ?? {};
2908
2887
  const notAllows = contract.not_allows ?? {};
2909
2888
  const allowedCommands = allows.commands ?? [];
@@ -2916,6 +2895,11 @@ function compileSandbox(contract, mode) {
2916
2895
  const check = (envelope) => {
2917
2896
  if (within.length > 0 || notWithin.length > 0) {
2918
2897
  const paths = extractPaths(envelope);
2898
+ if (paths.length === 0 && within.length > 0) {
2899
+ return Verdict.fail(
2900
+ expandMessage(messageTemplate, envelope) + " (no extractable paths \u2014 sandbox cannot verify boundary compliance)"
2901
+ );
2902
+ }
2919
2903
  if (paths.length > 0) {
2920
2904
  for (const p of paths) {
2921
2905
  for (const excluded of notWithin) {
@@ -2982,9 +2966,7 @@ function compileContracts(bundle, options = {}) {
2982
2966
  const customSels = options?.customSelectors ?? null;
2983
2967
  validateOperators(bundle, customOps);
2984
2968
  if (bundle.defaults == null || typeof bundle.defaults !== "object") {
2985
- throw new EdictumConfigError(
2986
- "Bundle missing required 'defaults' section with 'mode' field"
2987
- );
2969
+ throw new EdictumConfigError("Bundle missing required 'defaults' section with 'mode' field");
2988
2970
  }
2989
2971
  const defaults = bundle.defaults;
2990
2972
  const defaultMode = defaults.mode;
@@ -3030,7 +3012,8 @@ function compileContracts(bundle, options = {}) {
3030
3012
 
3031
3013
  // src/yaml-engine/loader.ts
3032
3014
  var import_node_crypto3 = require("crypto");
3033
- var import_node_fs3 = require("fs");
3015
+ var import_node_fs2 = require("fs");
3016
+ var import_js_yaml = __toESM(require("js-yaml"), 1);
3034
3017
  init_errors();
3035
3018
 
3036
3019
  // src/yaml-engine/loader-validators.ts
@@ -3053,13 +3036,22 @@ function validateSchema(data) {
3053
3036
  throw new EdictumConfigError("Schema validation failed: contracts must be an array");
3054
3037
  }
3055
3038
  }
3056
- var CONTROL_CHAR_RE = /[\x00-\x1f\x7f-\x9f]/;
3039
+ var CONTROL_CHAR_RE = /[\x00-\x1f\x7f-\x9f\u2028\u2029]/;
3040
+ var CONTRACT_ID_RE = /^[a-z0-9][a-z0-9_-]*$/;
3057
3041
  function validateContractId(contractId) {
3042
+ if (contractId.length > 1e4) {
3043
+ throw new EdictumConfigError("Contract id exceeds maximum length");
3044
+ }
3058
3045
  if (CONTROL_CHAR_RE.test(contractId)) {
3059
3046
  throw new EdictumConfigError(
3060
3047
  `Contract id contains control characters: '${contractId.replace(CONTROL_CHAR_RE, "\\x??")}'`
3061
3048
  );
3062
3049
  }
3050
+ if (!CONTRACT_ID_RE.test(contractId)) {
3051
+ throw new EdictumConfigError(
3052
+ `Contract id '${contractId}' must match pattern ^[a-z0-9][a-z0-9_-]*$`
3053
+ );
3054
+ }
3063
3055
  }
3064
3056
  function validateUniqueIds(data) {
3065
3057
  const ids = /* @__PURE__ */ new Set();
@@ -3160,26 +3152,231 @@ function validateSandboxContracts(data) {
3160
3152
  }
3161
3153
  }
3162
3154
 
3155
+ // src/yaml-engine/loader-field-validators.ts
3156
+ init_errors();
3157
+ var VALID_CONTRACT_TYPES = /* @__PURE__ */ new Set(["pre", "post", "session", "sandbox"]);
3158
+ var PRE_EFFECTS = /* @__PURE__ */ new Set(["deny", "approve"]);
3159
+ var POST_EFFECTS = /* @__PURE__ */ new Set(["warn", "redact", "deny"]);
3160
+ var VALID_MODES = /* @__PURE__ */ new Set(["enforce", "observe"]);
3161
+ var VALID_SIDE_EFFECTS = /* @__PURE__ */ new Set(["pure", "read", "write", "irreversible"]);
3162
+ var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
3163
+ "apiVersion",
3164
+ "kind",
3165
+ "metadata",
3166
+ "defaults",
3167
+ "contracts",
3168
+ "tools",
3169
+ "observability",
3170
+ "observe_alongside"
3171
+ ]);
3172
+ var METADATA_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/;
3173
+ var MAX_MESSAGE_LENGTH = 500;
3174
+ function fail(msg) {
3175
+ throw new EdictumConfigError(`Schema validation failed: ${msg}`);
3176
+ }
3177
+ function validateContractFields(data) {
3178
+ for (const key of Object.keys(data)) {
3179
+ if (!KNOWN_TOP_LEVEL.has(key)) fail(`unknown top-level field '${key}'`);
3180
+ }
3181
+ if (data.metadata == null || typeof data.metadata !== "object" || Array.isArray(data.metadata)) {
3182
+ fail("'metadata' is required and must be an object");
3183
+ }
3184
+ const meta = data.metadata;
3185
+ if (meta.name == null || typeof meta.name !== "string") fail("metadata.name is required");
3186
+ const metaName = meta.name;
3187
+ if (metaName.length > 1e4) fail("metadata.name exceeds maximum length");
3188
+ if (!METADATA_NAME_RE.test(metaName)) {
3189
+ fail(
3190
+ `metadata.name must be a lowercase slug (^[a-z0-9][a-z0-9._-]*$), got '${metaName.slice(0, 100)}'`
3191
+ );
3192
+ }
3193
+ if (data.defaults == null || typeof data.defaults !== "object" || Array.isArray(data.defaults)) {
3194
+ fail("'defaults' is required and must be an object");
3195
+ }
3196
+ const mode = data.defaults.mode;
3197
+ if (!VALID_MODES.has(mode)) {
3198
+ fail(`defaults.mode must be 'enforce' or 'observe', got '${String(mode)}'`);
3199
+ }
3200
+ const contracts = data.contracts;
3201
+ if (contracts.length === 0) fail("contracts must contain at least 1 item");
3202
+ if (data.tools != null) {
3203
+ if (typeof data.tools !== "object" || Array.isArray(data.tools)) {
3204
+ fail("'tools' must be a mapping of tool names to descriptors, not an array");
3205
+ }
3206
+ for (const [tn, td] of Object.entries(data.tools)) {
3207
+ if (td == null || typeof td !== "object" || Array.isArray(td)) {
3208
+ fail(`tools.${tn} must be an object with a 'side_effect' field`);
3209
+ }
3210
+ const se = td.side_effect;
3211
+ if (!VALID_SIDE_EFFECTS.has(se)) {
3212
+ fail(
3213
+ `tools.${tn}.side_effect must be 'pure', 'read', 'write', or 'irreversible', got '${String(se)}'`
3214
+ );
3215
+ }
3216
+ }
3217
+ }
3218
+ for (const c of contracts) {
3219
+ if (c == null || typeof c !== "object" || Array.isArray(c)) {
3220
+ fail("every contract must be an object (got null or non-object array element)");
3221
+ }
3222
+ if (c.id == null || typeof c.id !== "string" || c.id.length === 0) {
3223
+ fail("every contract requires a non-empty 'id' string");
3224
+ }
3225
+ const cid = c.id;
3226
+ if (!VALID_CONTRACT_TYPES.has(c.type)) {
3227
+ fail(`contract '${cid}': invalid type '${String(c.type)}'`);
3228
+ }
3229
+ const t = c.type;
3230
+ if (t === "pre" || t === "post") validatePrePost(c, t, cid);
3231
+ else if (t === "session") validateSession(c, cid);
3232
+ else if (t === "sandbox") validateSandboxStructure(c, cid);
3233
+ }
3234
+ }
3235
+ function validatePrePost(c, t, cid) {
3236
+ if (c.tool == null || typeof c.tool !== "string") {
3237
+ fail(`${t} contract '${cid}' requires 'tool' to be a string`);
3238
+ }
3239
+ if (c.when == null || typeof c.when !== "object" || Array.isArray(c.when)) {
3240
+ fail(
3241
+ `${t} contract '${cid}' requires 'when' to be a mapping (got ${Array.isArray(c.when) ? "array" : typeof c.when})`
3242
+ );
3243
+ }
3244
+ if (c.then == null || typeof c.then !== "object" || Array.isArray(c.then)) {
3245
+ fail(`${t} contract '${cid}' requires 'then' to be a mapping`);
3246
+ }
3247
+ const then = c.then;
3248
+ if (then.effect == null) fail(`${t} contract '${cid}' requires 'then.effect'`);
3249
+ if (then.message == null) fail(`${t} contract '${cid}' requires 'then.message'`);
3250
+ validateMessageLength(then.message, `${t} contract '${cid}'`);
3251
+ const effect = then.effect;
3252
+ if (t === "pre" && !PRE_EFFECTS.has(effect)) {
3253
+ fail(`pre contract '${cid}': effect must be 'deny' or 'approve', got '${effect}'`);
3254
+ }
3255
+ if (t === "post" && !POST_EFFECTS.has(effect)) {
3256
+ fail(`post contract '${cid}': effect must be 'warn', 'redact', or 'deny', got '${effect}'`);
3257
+ }
3258
+ }
3259
+ function validateSession(c, cid) {
3260
+ if (c.limits == null || typeof c.limits !== "object" || Array.isArray(c.limits)) {
3261
+ fail(`session contract '${cid}' requires 'limits' to be a mapping`);
3262
+ }
3263
+ const lim = c.limits;
3264
+ if (!("max_tool_calls" in lim) && !("max_attempts" in lim) && !("max_calls_per_tool" in lim)) {
3265
+ fail(
3266
+ `session contract '${cid}': limits must have max_tool_calls, max_attempts, or max_calls_per_tool`
3267
+ );
3268
+ }
3269
+ if (c.then == null || typeof c.then !== "object" || Array.isArray(c.then)) {
3270
+ fail(`session contract '${cid}' requires 'then' to be a mapping`);
3271
+ }
3272
+ const then = c.then;
3273
+ if (then.effect !== "deny") {
3274
+ fail(`session contract '${cid}': effect must be 'deny', got '${String(then.effect)}'`);
3275
+ }
3276
+ if (then.message == null) fail(`session contract '${cid}' requires 'then.message'`);
3277
+ validateMessageLength(then.message, `session contract '${cid}'`);
3278
+ }
3279
+ function validateSandboxStructure(c, cid) {
3280
+ if (c.tool == null && c.tools == null) {
3281
+ fail(`sandbox contract '${cid}' requires either 'tool' or 'tools'`);
3282
+ }
3283
+ if (c.tool != null && typeof c.tool !== "string") {
3284
+ fail(`sandbox contract '${cid}': 'tool' must be a string`);
3285
+ }
3286
+ if (c.tools != null && (!Array.isArray(c.tools) || c.tools.length === 0)) {
3287
+ fail(`sandbox contract '${cid}': 'tools' must be a non-empty array`);
3288
+ }
3289
+ if (c.within == null && c.allows == null) {
3290
+ fail(`sandbox contract '${cid}' requires either 'within' or 'allows'`);
3291
+ }
3292
+ if (c.within != null && (!Array.isArray(c.within) || c.within.length === 0)) {
3293
+ fail(`sandbox contract '${cid}': 'within' must be a non-empty array`);
3294
+ }
3295
+ if (c.message == null) fail(`sandbox contract '${cid}' requires 'message'`);
3296
+ validateMessageLength(c.message, `sandbox contract '${cid}'`);
3297
+ }
3298
+ function validateMessageLength(msg, context) {
3299
+ if (typeof msg !== "string") {
3300
+ fail(`${context}: message must be a string`);
3301
+ }
3302
+ if (msg.length > MAX_MESSAGE_LENGTH) {
3303
+ fail(`${context}: message exceeds ${MAX_MESSAGE_LENGTH} characters`);
3304
+ }
3305
+ }
3306
+
3307
+ // src/yaml-engine/loader-expression-validators.ts
3308
+ init_errors();
3309
+ var NUMERIC_OPS = /* @__PURE__ */ new Set(["gt", "gte", "lt", "lte"]);
3310
+ var STRING_OPS = /* @__PURE__ */ new Set(["contains", "starts_with", "ends_with"]);
3311
+ var ARRAY_MIN1_OPS = /* @__PURE__ */ new Set(["in", "not_in", "contains_any", "matches_any"]);
3312
+ function fail2(msg) {
3313
+ throw new EdictumConfigError(`Schema validation failed: ${msg}`);
3314
+ }
3315
+ function validateExpressionShapes(data) {
3316
+ for (const c of data.contracts ?? []) {
3317
+ if (c == null || typeof c !== "object") continue;
3318
+ const contract = c;
3319
+ if (contract.when != null) checkExprShape(contract.when, contract.id ?? "?");
3320
+ }
3321
+ }
3322
+ var MAX_EXPR_DEPTH = 50;
3323
+ function checkExprShape(expr, cid, depth = 0) {
3324
+ if (depth > MAX_EXPR_DEPTH)
3325
+ fail2(`contract '${cid}': expression nesting exceeds maximum depth (${MAX_EXPR_DEPTH})`);
3326
+ if (expr == null || typeof expr !== "object") return;
3327
+ const e = expr;
3328
+ if ("all" in e) {
3329
+ const a = e.all;
3330
+ if (!Array.isArray(a) || a.length === 0)
3331
+ fail2(`contract '${cid}': 'all' requires a non-empty array`);
3332
+ for (const s of a) checkExprShape(s, cid, depth + 1);
3333
+ return;
3334
+ }
3335
+ if ("any" in e) {
3336
+ const a = e.any;
3337
+ if (!Array.isArray(a) || a.length === 0)
3338
+ fail2(`contract '${cid}': 'any' requires a non-empty array`);
3339
+ for (const s of a) checkExprShape(s, cid, depth + 1);
3340
+ return;
3341
+ }
3342
+ if ("not" in e) {
3343
+ if (e.not == null || typeof e.not !== "object" || Array.isArray(e.not)) {
3344
+ fail2(
3345
+ `contract '${cid}': 'not' requires an expression mapping (got ${e.not === null ? "null" : Array.isArray(e.not) ? "array" : typeof e.not})`
3346
+ );
3347
+ }
3348
+ checkExprShape(e.not, cid, depth + 1);
3349
+ return;
3350
+ }
3351
+ for (const v of Object.values(e)) {
3352
+ if (v == null || typeof v !== "object") continue;
3353
+ if (Array.isArray(v)) {
3354
+ fail2(`contract '${cid}': selector value must be an operator mapping, not an array`);
3355
+ }
3356
+ const op = v;
3357
+ for (const [name, val] of Object.entries(op)) {
3358
+ if (NUMERIC_OPS.has(name) && typeof val !== "number") {
3359
+ fail2(`contract '${cid}': operator '${name}' requires a number, got ${typeof val}`);
3360
+ }
3361
+ if (STRING_OPS.has(name) && typeof val !== "string") {
3362
+ fail2(`contract '${cid}': operator '${name}' requires a string, got ${typeof val}`);
3363
+ }
3364
+ if (ARRAY_MIN1_OPS.has(name) && (!Array.isArray(val) || val.length === 0)) {
3365
+ fail2(`contract '${cid}': operator '${name}' requires a non-empty array`);
3366
+ }
3367
+ }
3368
+ }
3369
+ }
3370
+
3163
3371
  // src/yaml-engine/loader.ts
3164
3372
  var MAX_BUNDLE_SIZE = 1048576;
3165
3373
  function computeHash(rawBytes) {
3166
3374
  return { hex: (0, import_node_crypto3.createHash)("sha256").update(rawBytes).digest("hex") };
3167
3375
  }
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
3376
  function parseYaml(content) {
3179
- const yaml = requireYaml();
3180
3377
  let data;
3181
3378
  try {
3182
- data = yaml.load(content);
3379
+ data = import_js_yaml.default.load(content, { schema: import_js_yaml.default.CORE_SCHEMA });
3183
3380
  } catch (e) {
3184
3381
  throw new EdictumConfigError(`YAML parse error: ${String(e)}`);
3185
3382
  }
@@ -3190,20 +3387,22 @@ function parseYaml(content) {
3190
3387
  }
3191
3388
  function validateBundle(data) {
3192
3389
  validateSchema(data);
3390
+ validateContractFields(data);
3193
3391
  validateUniqueIds(data);
3392
+ validateExpressionShapes(data);
3194
3393
  validateRegexes(data);
3195
3394
  validatePreSelectors(data);
3196
3395
  validateSandboxContracts(data);
3197
3396
  }
3198
3397
  function loadBundle(source) {
3199
- const resolved = (0, import_node_fs3.realpathSync)(source);
3200
- const fileSize = (0, import_node_fs3.statSync)(resolved).size;
3398
+ const resolved = (0, import_node_fs2.realpathSync)(source);
3399
+ const fileSize = (0, import_node_fs2.statSync)(resolved).size;
3201
3400
  if (fileSize > MAX_BUNDLE_SIZE) {
3202
3401
  throw new EdictumConfigError(
3203
3402
  `Bundle file too large (${fileSize} bytes, max ${MAX_BUNDLE_SIZE})`
3204
3403
  );
3205
3404
  }
3206
- const rawBytes = (0, import_node_fs3.readFileSync)(resolved);
3405
+ const rawBytes = (0, import_node_fs2.readFileSync)(resolved);
3207
3406
  const bundleHash = computeHash(rawBytes);
3208
3407
  const data = parseYaml(rawBytes.toString("utf-8"));
3209
3408
  validateBundle(data);
@@ -3251,9 +3450,10 @@ function fromYaml(...args) {
3251
3450
  policyVersion = entry[1].hex;
3252
3451
  report = { overriddenContracts: [], observeContracts: [] };
3253
3452
  } else {
3254
- const bundleTuples = loaded.map(
3255
- ([data], i) => [data, paths[i]]
3256
- );
3453
+ const bundleTuples = loaded.map(([data], i) => [
3454
+ data,
3455
+ paths[i]
3456
+ ]);
3257
3457
  const composed = composeBundles(...bundleTuples);
3258
3458
  bundleData = composed.bundle;
3259
3459
  report = composed.report;
@@ -3378,15 +3578,9 @@ var Edictum = class _Edictum {
3378
3578
  this._approvalBackend = options.approvalBackend ?? null;
3379
3579
  this._localSink = new CollectingAuditSink();
3380
3580
  if (Array.isArray(options.auditSink)) {
3381
- this.auditSink = new CompositeSink([
3382
- this._localSink,
3383
- ...options.auditSink
3384
- ]);
3581
+ this.auditSink = new CompositeSink([this._localSink, ...options.auditSink]);
3385
3582
  } else if (options.auditSink != null) {
3386
- this.auditSink = new CompositeSink([
3387
- this._localSink,
3388
- options.auditSink
3389
- ]);
3583
+ this.auditSink = new CompositeSink([this._localSink, options.auditSink]);
3390
3584
  } else {
3391
3585
  this.auditSink = this._localSink;
3392
3586
  }
@@ -3484,24 +3678,16 @@ var Edictum = class _Edictum {
3484
3678
  }
3485
3679
  getHooks(phase, envelope) {
3486
3680
  const hooks = phase === "before" ? this._beforeHooks : this._afterHooks;
3487
- return hooks.filter(
3488
- (h) => h.tool === "*" || fnmatch(envelope.toolName, h.tool)
3489
- );
3681
+ return hooks.filter((h) => h.tool === "*" || fnmatch(envelope.toolName, h.tool));
3490
3682
  }
3491
3683
  // -----------------------------------------------------------------------
3492
3684
  // Contract accessors -- enforce mode
3493
3685
  // -----------------------------------------------------------------------
3494
3686
  getPreconditions(envelope) {
3495
- return _Edictum._filterByTool(
3496
- this._state.preconditions,
3497
- envelope
3498
- );
3687
+ return _Edictum._filterByTool(this._state.preconditions, envelope);
3499
3688
  }
3500
3689
  getPostconditions(envelope) {
3501
- return _Edictum._filterByTool(
3502
- this._state.postconditions,
3503
- envelope
3504
- );
3690
+ return _Edictum._filterByTool(this._state.postconditions, envelope);
3505
3691
  }
3506
3692
  getSessionContracts() {
3507
3693
  return [...this._state.sessionContracts];
@@ -3561,12 +3747,16 @@ var Edictum = class _Edictum {
3561
3747
  const edictumType = raw._edictum_type;
3562
3748
  const isObserve = raw._edictum_observe ?? raw._edictum_shadow ?? false;
3563
3749
  if (edictumType != null) {
3564
- _Edictum._classifyInternal(
3565
- raw,
3566
- edictumType,
3567
- isObserve,
3568
- { pre, post, session, sandbox, oPre, oPost, oSession, oSandbox }
3569
- );
3750
+ _Edictum._classifyInternal(raw, edictumType, isObserve, {
3751
+ pre,
3752
+ post,
3753
+ session,
3754
+ sandbox,
3755
+ oPre,
3756
+ oPost,
3757
+ oSession,
3758
+ oSandbox
3759
+ });
3570
3760
  } else if (isSessionContract(item)) {
3571
3761
  const name = raw.name ?? "anonymous";
3572
3762
  session.push({
@@ -3624,9 +3814,12 @@ var Edictum = class _Edictum {
3624
3814
  static _classifyInternal(raw, edictumType, isObserve, lists) {
3625
3815
  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
3816
  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);
3817
+ else if (edictumType === "postcondition")
3818
+ target.post.push(raw);
3819
+ else if (edictumType === "session_contract")
3820
+ target.session.push(raw);
3821
+ else if (edictumType === "sandbox")
3822
+ target.sandbox.push(raw);
3630
3823
  else {
3631
3824
  throw new EdictumConfigError(
3632
3825
  `Unknown _edictum_type "${edictumType}". Expected "precondition", "postcondition", "session_contract", or "sandbox".`
@@ -3678,15 +3871,11 @@ var Edictum = class _Edictum {
3678
3871
  * Session contracts are skipped.
3679
3872
  */
3680
3873
  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
- );
3874
+ return Promise.resolve().then(() => (init_dry_run(), dry_run_exports)).then(({ evaluate: evaluate2 }) => evaluate2(this, toolName, args, options));
3684
3875
  }
3685
3876
  /** Evaluate a batch of tool calls. Thin wrapper over evaluate(). */
3686
3877
  evaluateBatch(calls) {
3687
- return Promise.resolve().then(() => (init_dry_run(), dry_run_exports)).then(
3688
- ({ evaluateBatch: evaluateBatch2 }) => evaluateBatch2(this, calls)
3689
- );
3878
+ return Promise.resolve().then(() => (init_dry_run(), dry_run_exports)).then(({ evaluateBatch: evaluateBatch2 }) => evaluateBatch2(this, calls));
3690
3879
  }
3691
3880
  static fromYaml(...args) {
3692
3881
  return fromYaml(...args);