@edictum/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-CRPQFRYJ.mjs +238 -0
- package/dist/chunk-CRPQFRYJ.mjs.map +1 -0
- package/dist/chunk-IXMXZGJG.mjs +30 -0
- package/dist/chunk-IXMXZGJG.mjs.map +1 -0
- package/dist/chunk-X5E2YY35.mjs +1299 -0
- package/dist/chunk-X5E2YY35.mjs.map +1 -0
- package/dist/dry-run-54PYIM6T.mjs +199 -0
- package/dist/dry-run-54PYIM6T.mjs.map +1 -0
- package/dist/index.cjs +3774 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1201 -0
- package/dist/index.d.ts +1201 -0
- package/dist/index.mjs +1890 -0
- package/dist/index.mjs.map +1 -0
- package/dist/runner-ASI4JIW2.mjs +10 -0
- package/dist/runner-ASI4JIW2.mjs.map +1 -0
- package/package.json +58 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1890 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApprovalStatus,
|
|
3
|
+
AuditAction,
|
|
4
|
+
CollectingAuditSink,
|
|
5
|
+
CompositeSink,
|
|
6
|
+
FileAuditSink,
|
|
7
|
+
GovernancePipeline,
|
|
8
|
+
HookDecision,
|
|
9
|
+
HookResult,
|
|
10
|
+
LocalApprovalBackend,
|
|
11
|
+
MarkEvictedError,
|
|
12
|
+
RedactionPolicy,
|
|
13
|
+
Session,
|
|
14
|
+
StdoutAuditSink,
|
|
15
|
+
Verdict,
|
|
16
|
+
createAuditEvent,
|
|
17
|
+
createPostDecision,
|
|
18
|
+
createPreDecision,
|
|
19
|
+
defaultSuccessCheck,
|
|
20
|
+
run
|
|
21
|
+
} from "./chunk-X5E2YY35.mjs";
|
|
22
|
+
import {
|
|
23
|
+
createContractResult,
|
|
24
|
+
createEvaluationResult
|
|
25
|
+
} from "./chunk-IXMXZGJG.mjs";
|
|
26
|
+
import {
|
|
27
|
+
BashClassifier,
|
|
28
|
+
EdictumConfigError,
|
|
29
|
+
EdictumDenied,
|
|
30
|
+
EdictumToolError,
|
|
31
|
+
SideEffect,
|
|
32
|
+
ToolRegistry,
|
|
33
|
+
__require,
|
|
34
|
+
_validateToolName,
|
|
35
|
+
createEnvelope,
|
|
36
|
+
createPrincipal,
|
|
37
|
+
deepFreeze
|
|
38
|
+
} from "./chunk-CRPQFRYJ.mjs";
|
|
39
|
+
|
|
40
|
+
// src/limits.ts
|
|
41
|
+
var DEFAULT_LIMITS = Object.freeze({
|
|
42
|
+
maxAttempts: 500,
|
|
43
|
+
maxToolCalls: 200,
|
|
44
|
+
maxCallsPerTool: Object.freeze({})
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// src/storage.ts
|
|
48
|
+
var MemoryBackend = class {
|
|
49
|
+
_data = /* @__PURE__ */ new Map();
|
|
50
|
+
_counters = /* @__PURE__ */ new Map();
|
|
51
|
+
async get(key) {
|
|
52
|
+
const strVal = this._data.get(key);
|
|
53
|
+
if (strVal !== void 0) {
|
|
54
|
+
return strVal;
|
|
55
|
+
}
|
|
56
|
+
const numVal = this._counters.get(key);
|
|
57
|
+
if (numVal !== void 0) {
|
|
58
|
+
return numVal === Math.trunc(numVal) ? String(Math.trunc(numVal)) : String(numVal);
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
async set(key, value) {
|
|
63
|
+
this._data.set(key, value);
|
|
64
|
+
}
|
|
65
|
+
async delete(key) {
|
|
66
|
+
this._data.delete(key);
|
|
67
|
+
this._counters.delete(key);
|
|
68
|
+
}
|
|
69
|
+
async increment(key, amount = 1) {
|
|
70
|
+
const current = this._counters.get(key) ?? 0;
|
|
71
|
+
const next = current + amount;
|
|
72
|
+
this._counters.set(key, next);
|
|
73
|
+
return next;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Retrieve multiple values in a single operation.
|
|
77
|
+
*
|
|
78
|
+
* In-memory implementation: multiple Map lookups, no network overhead.
|
|
79
|
+
*/
|
|
80
|
+
async batchGet(keys) {
|
|
81
|
+
const result = {};
|
|
82
|
+
for (const key of keys) {
|
|
83
|
+
result[key] = await this.get(key);
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// src/findings.ts
|
|
90
|
+
function createFinding(fields) {
|
|
91
|
+
return Object.freeze({
|
|
92
|
+
type: fields.type,
|
|
93
|
+
contractId: fields.contractId,
|
|
94
|
+
field: fields.field,
|
|
95
|
+
message: fields.message,
|
|
96
|
+
metadata: Object.freeze({ ...fields.metadata ?? {} })
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function createPostCallResult(fields) {
|
|
100
|
+
return Object.freeze({
|
|
101
|
+
result: fields.result,
|
|
102
|
+
postconditionsPassed: fields.postconditionsPassed ?? true,
|
|
103
|
+
findings: Object.freeze([...fields.findings ?? []]),
|
|
104
|
+
outputSuppressed: fields.outputSuppressed ?? false
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function classifyFinding(contractId, verdictMessage) {
|
|
108
|
+
const contractLower = contractId.toLowerCase();
|
|
109
|
+
const messageLower = (verdictMessage || "").toLowerCase();
|
|
110
|
+
const piiTerms = ["pii", "ssn", "patient", "name", "dob"];
|
|
111
|
+
if (piiTerms.some(
|
|
112
|
+
(term) => contractLower.includes(term) || messageLower.includes(term)
|
|
113
|
+
)) {
|
|
114
|
+
return "pii_detected";
|
|
115
|
+
}
|
|
116
|
+
const secretTerms = ["secret", "token", "key", "credential", "password"];
|
|
117
|
+
if (secretTerms.some(
|
|
118
|
+
(term) => contractLower.includes(term) || messageLower.includes(term)
|
|
119
|
+
)) {
|
|
120
|
+
return "secret_detected";
|
|
121
|
+
}
|
|
122
|
+
const limitTerms = ["session", "limit", "max_calls", "budget"];
|
|
123
|
+
if (limitTerms.some(
|
|
124
|
+
(term) => contractLower.includes(term) || messageLower.includes(term)
|
|
125
|
+
)) {
|
|
126
|
+
return "limit_exceeded";
|
|
127
|
+
}
|
|
128
|
+
return "policy_violation";
|
|
129
|
+
}
|
|
130
|
+
function buildFindings(postDecision) {
|
|
131
|
+
const findings = [];
|
|
132
|
+
for (const cr of postDecision.contractsEvaluated) {
|
|
133
|
+
if (!cr.passed) {
|
|
134
|
+
const meta = cr.metadata ?? {};
|
|
135
|
+
findings.push(
|
|
136
|
+
createFinding({
|
|
137
|
+
type: classifyFinding(cr.name, cr.message ?? ""),
|
|
138
|
+
contractId: cr.name,
|
|
139
|
+
field: meta.field ?? "output",
|
|
140
|
+
message: cr.message ?? "",
|
|
141
|
+
metadata: meta
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return findings;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/compiled-state.ts
|
|
150
|
+
function createCompiledState(partial = {}) {
|
|
151
|
+
return deepFreeze({
|
|
152
|
+
preconditions: partial.preconditions ?? [],
|
|
153
|
+
postconditions: partial.postconditions ?? [],
|
|
154
|
+
sessionContracts: partial.sessionContracts ?? [],
|
|
155
|
+
sandboxContracts: partial.sandboxContracts ?? [],
|
|
156
|
+
observePreconditions: partial.observePreconditions ?? [],
|
|
157
|
+
observePostconditions: partial.observePostconditions ?? [],
|
|
158
|
+
observeSessionContracts: partial.observeSessionContracts ?? [],
|
|
159
|
+
observeSandboxContracts: partial.observeSandboxContracts ?? [],
|
|
160
|
+
limits: partial.limits ?? DEFAULT_LIMITS,
|
|
161
|
+
policyVersion: partial.policyVersion ?? null
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/guard.ts
|
|
166
|
+
import { randomUUID } from "crypto";
|
|
167
|
+
|
|
168
|
+
// src/factory.ts
|
|
169
|
+
import { createHash as createHash2 } from "crypto";
|
|
170
|
+
|
|
171
|
+
// src/yaml-engine/composer.ts
|
|
172
|
+
function deepCopyBundle(data) {
|
|
173
|
+
return structuredClone(data);
|
|
174
|
+
}
|
|
175
|
+
function composeBundles(...bundles) {
|
|
176
|
+
if (bundles.length === 0) {
|
|
177
|
+
throw new Error("composeBundles() requires at least one bundle");
|
|
178
|
+
}
|
|
179
|
+
if (bundles.length === 1) {
|
|
180
|
+
const entry = bundles[0];
|
|
181
|
+
return {
|
|
182
|
+
bundle: deepCopyBundle(entry[0]),
|
|
183
|
+
report: { overriddenContracts: [], observeContracts: [] }
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const overrides = [];
|
|
187
|
+
const observes = [];
|
|
188
|
+
const first = bundles[0];
|
|
189
|
+
const merged = deepCopyBundle(first[0]);
|
|
190
|
+
const firstLabel = first[1];
|
|
191
|
+
const contractSources = /* @__PURE__ */ new Map();
|
|
192
|
+
for (const c of merged.contracts ?? []) {
|
|
193
|
+
contractSources.set(c.id, firstLabel);
|
|
194
|
+
}
|
|
195
|
+
for (let i = 1; i < bundles.length; i++) {
|
|
196
|
+
const entry = bundles[i];
|
|
197
|
+
const [data, label] = entry;
|
|
198
|
+
const isObserveAlongside = Boolean(data.observe_alongside);
|
|
199
|
+
if (isObserveAlongside) {
|
|
200
|
+
mergeObserveAlongside(merged, data, label, contractSources, observes);
|
|
201
|
+
} else {
|
|
202
|
+
mergeStandard(merged, data, label, contractSources, overrides);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
bundle: merged,
|
|
207
|
+
report: { overriddenContracts: overrides, observeContracts: observes }
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function mergeStandard(merged, layer, label, contractSources, overrides) {
|
|
211
|
+
if ("defaults" in layer) {
|
|
212
|
+
const ld = layer.defaults;
|
|
213
|
+
const md = merged.defaults ?? {};
|
|
214
|
+
if ("mode" in ld) md.mode = ld.mode;
|
|
215
|
+
if ("environment" in ld) md.environment = ld.environment;
|
|
216
|
+
merged.defaults = md;
|
|
217
|
+
}
|
|
218
|
+
if ("limits" in layer) merged.limits = deepCopyBundle(layer.limits);
|
|
219
|
+
if ("tools" in layer) {
|
|
220
|
+
const mt = merged.tools ?? {};
|
|
221
|
+
for (const [name, cfg] of Object.entries(layer.tools)) {
|
|
222
|
+
mt[name] = { ...cfg };
|
|
223
|
+
}
|
|
224
|
+
merged.tools = mt;
|
|
225
|
+
}
|
|
226
|
+
if ("metadata" in layer) {
|
|
227
|
+
const mm = merged.metadata ?? {};
|
|
228
|
+
for (const [k, v] of Object.entries(layer.metadata)) mm[k] = v;
|
|
229
|
+
merged.metadata = mm;
|
|
230
|
+
}
|
|
231
|
+
if ("observability" in layer) {
|
|
232
|
+
merged.observability = deepCopyBundle(layer.observability);
|
|
233
|
+
}
|
|
234
|
+
if ("contracts" in layer) {
|
|
235
|
+
const existingById = /* @__PURE__ */ new Map();
|
|
236
|
+
const mc = merged.contracts ?? [];
|
|
237
|
+
for (let j = 0; j < mc.length; j++) {
|
|
238
|
+
const c = mc[j];
|
|
239
|
+
existingById.set(c.id, j);
|
|
240
|
+
}
|
|
241
|
+
for (const contract of layer.contracts ?? []) {
|
|
242
|
+
const cid = contract.id;
|
|
243
|
+
const newContract = deepCopyBundle(contract);
|
|
244
|
+
if (existingById.has(cid)) {
|
|
245
|
+
const idx = existingById.get(cid);
|
|
246
|
+
overrides.push({
|
|
247
|
+
contractId: cid,
|
|
248
|
+
overriddenBy: label,
|
|
249
|
+
originalSource: contractSources.get(cid) ?? "unknown"
|
|
250
|
+
});
|
|
251
|
+
mc[idx] = newContract;
|
|
252
|
+
} else {
|
|
253
|
+
mc.push(newContract);
|
|
254
|
+
existingById.set(cid, mc.length - 1);
|
|
255
|
+
}
|
|
256
|
+
contractSources.set(cid, label);
|
|
257
|
+
}
|
|
258
|
+
merged.contracts = mc;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function mergeObserveAlongside(merged, layer, label, contractSources, observes) {
|
|
262
|
+
const mc = merged.contracts ?? [];
|
|
263
|
+
for (const contract of layer.contracts ?? []) {
|
|
264
|
+
const cid = contract.id;
|
|
265
|
+
const observeId = `${cid}:candidate`;
|
|
266
|
+
const existingIds = new Set(
|
|
267
|
+
mc.map((c) => c.id)
|
|
268
|
+
);
|
|
269
|
+
if (existingIds.has(observeId)) {
|
|
270
|
+
throw new EdictumConfigError(
|
|
271
|
+
`observe_alongside collision: generated ID "${observeId}" already exists in the bundle. Rename the conflicting contract or use a different ID for "${cid}".`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
const observeContract = deepCopyBundle(contract);
|
|
275
|
+
observeContract.id = observeId;
|
|
276
|
+
observeContract.mode = "observe";
|
|
277
|
+
observeContract._observe = true;
|
|
278
|
+
mc.push(observeContract);
|
|
279
|
+
observes.push({
|
|
280
|
+
contractId: cid,
|
|
281
|
+
enforcedSource: contractSources.get(cid) ?? "",
|
|
282
|
+
observedSource: label
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
merged.contracts = mc;
|
|
286
|
+
if ("tools" in layer) {
|
|
287
|
+
const mt = merged.tools ?? {};
|
|
288
|
+
for (const [name, cfg] of Object.entries(layer.tools)) {
|
|
289
|
+
mt[name] = { ...cfg };
|
|
290
|
+
}
|
|
291
|
+
merged.tools = mt;
|
|
292
|
+
}
|
|
293
|
+
if ("metadata" in layer) {
|
|
294
|
+
const mm = merged.metadata ?? {};
|
|
295
|
+
for (const [k, v] of Object.entries(layer.metadata)) mm[k] = v;
|
|
296
|
+
merged.metadata = mm;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/yaml-engine/operators.ts
|
|
301
|
+
var MAX_REGEX_INPUT = 1e4;
|
|
302
|
+
function opEquals(fieldValue, opValue) {
|
|
303
|
+
return fieldValue === opValue;
|
|
304
|
+
}
|
|
305
|
+
function opNotEquals(fieldValue, opValue) {
|
|
306
|
+
return fieldValue !== opValue;
|
|
307
|
+
}
|
|
308
|
+
function opIn(fieldValue, opValue) {
|
|
309
|
+
return opValue.includes(fieldValue);
|
|
310
|
+
}
|
|
311
|
+
function opNotIn(fieldValue, opValue) {
|
|
312
|
+
return !opValue.includes(fieldValue);
|
|
313
|
+
}
|
|
314
|
+
function opContains(fieldValue, opValue) {
|
|
315
|
+
if (typeof fieldValue !== "string") throw new TypeError();
|
|
316
|
+
return fieldValue.includes(opValue);
|
|
317
|
+
}
|
|
318
|
+
function opContainsAny(fieldValue, opValue) {
|
|
319
|
+
if (typeof fieldValue !== "string") throw new TypeError();
|
|
320
|
+
return opValue.some((v) => fieldValue.includes(v));
|
|
321
|
+
}
|
|
322
|
+
function opStartsWith(fieldValue, opValue) {
|
|
323
|
+
if (typeof fieldValue !== "string") throw new TypeError();
|
|
324
|
+
return fieldValue.startsWith(opValue);
|
|
325
|
+
}
|
|
326
|
+
function opEndsWith(fieldValue, opValue) {
|
|
327
|
+
if (typeof fieldValue !== "string") throw new TypeError();
|
|
328
|
+
return fieldValue.endsWith(opValue);
|
|
329
|
+
}
|
|
330
|
+
function opMatches(fieldValue, opValue) {
|
|
331
|
+
if (typeof fieldValue !== "string") throw new TypeError();
|
|
332
|
+
const truncated = fieldValue.slice(0, MAX_REGEX_INPUT);
|
|
333
|
+
if (opValue instanceof RegExp) {
|
|
334
|
+
return opValue.test(truncated);
|
|
335
|
+
}
|
|
336
|
+
return new RegExp(opValue).test(truncated);
|
|
337
|
+
}
|
|
338
|
+
function opMatchesAny(fieldValue, opValue) {
|
|
339
|
+
if (typeof fieldValue !== "string") throw new TypeError();
|
|
340
|
+
const truncated = fieldValue.slice(0, MAX_REGEX_INPUT);
|
|
341
|
+
return opValue.some(
|
|
342
|
+
(p) => p instanceof RegExp ? p.test(truncated) : new RegExp(p).test(truncated)
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
function opGt(fieldValue, opValue) {
|
|
346
|
+
if (typeof fieldValue !== "number") throw new TypeError();
|
|
347
|
+
return fieldValue > opValue;
|
|
348
|
+
}
|
|
349
|
+
function opGte(fieldValue, opValue) {
|
|
350
|
+
if (typeof fieldValue !== "number") throw new TypeError();
|
|
351
|
+
return fieldValue >= opValue;
|
|
352
|
+
}
|
|
353
|
+
function opLt(fieldValue, opValue) {
|
|
354
|
+
if (typeof fieldValue !== "number") throw new TypeError();
|
|
355
|
+
return fieldValue < opValue;
|
|
356
|
+
}
|
|
357
|
+
function opLte(fieldValue, opValue) {
|
|
358
|
+
if (typeof fieldValue !== "number") throw new TypeError();
|
|
359
|
+
return fieldValue <= opValue;
|
|
360
|
+
}
|
|
361
|
+
var OPERATORS = {
|
|
362
|
+
equals: opEquals,
|
|
363
|
+
not_equals: opNotEquals,
|
|
364
|
+
in: opIn,
|
|
365
|
+
not_in: opNotIn,
|
|
366
|
+
contains: opContains,
|
|
367
|
+
contains_any: opContainsAny,
|
|
368
|
+
starts_with: opStartsWith,
|
|
369
|
+
ends_with: opEndsWith,
|
|
370
|
+
matches: opMatches,
|
|
371
|
+
matches_any: opMatchesAny,
|
|
372
|
+
gt: opGt,
|
|
373
|
+
gte: opGte,
|
|
374
|
+
lt: opLt,
|
|
375
|
+
lte: opLte
|
|
376
|
+
};
|
|
377
|
+
var BUILTIN_OPERATOR_NAMES = /* @__PURE__ */ new Set([
|
|
378
|
+
...Object.keys(OPERATORS),
|
|
379
|
+
"exists"
|
|
380
|
+
]);
|
|
381
|
+
|
|
382
|
+
// src/yaml-engine/selectors.ts
|
|
383
|
+
var _MISSING = /* @__PURE__ */ Symbol("MISSING");
|
|
384
|
+
var BUILTIN_SELECTOR_PREFIXES = /* @__PURE__ */ new Set([
|
|
385
|
+
"environment",
|
|
386
|
+
"tool",
|
|
387
|
+
"args",
|
|
388
|
+
"principal",
|
|
389
|
+
"output",
|
|
390
|
+
"env",
|
|
391
|
+
"metadata"
|
|
392
|
+
]);
|
|
393
|
+
function resolveSelector(selector, envelope, outputText, customSelectors) {
|
|
394
|
+
if (selector === "environment") return envelope.environment;
|
|
395
|
+
if (selector === "tool.name") return envelope.toolName;
|
|
396
|
+
if (selector.startsWith("args.")) {
|
|
397
|
+
return resolveNested(selector.slice(5), envelope.args);
|
|
398
|
+
}
|
|
399
|
+
if (selector.startsWith("principal.")) {
|
|
400
|
+
if (envelope.principal == null) return _MISSING;
|
|
401
|
+
const rest = selector.slice(10);
|
|
402
|
+
if (rest === "user_id") return envelope.principal.userId;
|
|
403
|
+
if (rest === "service_id") return envelope.principal.serviceId;
|
|
404
|
+
if (rest === "org_id") return envelope.principal.orgId;
|
|
405
|
+
if (rest === "role") return envelope.principal.role;
|
|
406
|
+
if (rest === "ticket_ref") return envelope.principal.ticketRef;
|
|
407
|
+
if (rest.startsWith("claims.")) {
|
|
408
|
+
return resolveNested(
|
|
409
|
+
rest.slice(7),
|
|
410
|
+
envelope.principal.claims
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
return _MISSING;
|
|
414
|
+
}
|
|
415
|
+
if (selector === "output.text") {
|
|
416
|
+
return outputText == null ? _MISSING : outputText;
|
|
417
|
+
}
|
|
418
|
+
if (selector.startsWith("env.")) {
|
|
419
|
+
const varName = selector.slice(4);
|
|
420
|
+
const raw = process.env[varName];
|
|
421
|
+
if (raw == null) return _MISSING;
|
|
422
|
+
return coerceEnvValue(raw);
|
|
423
|
+
}
|
|
424
|
+
if (selector.startsWith("metadata.")) {
|
|
425
|
+
return resolveNested(
|
|
426
|
+
selector.slice(9),
|
|
427
|
+
envelope.metadata
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
if (customSelectors) {
|
|
431
|
+
const dotPos = selector.indexOf(".");
|
|
432
|
+
if (dotPos > 0) {
|
|
433
|
+
const prefix = selector.slice(0, dotPos);
|
|
434
|
+
if (Object.hasOwn(customSelectors, prefix)) {
|
|
435
|
+
const resolver = customSelectors[prefix];
|
|
436
|
+
const data = resolver(envelope);
|
|
437
|
+
const rest = selector.slice(dotPos + 1);
|
|
438
|
+
return resolveNested(rest, data);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return _MISSING;
|
|
443
|
+
}
|
|
444
|
+
function resolveNested(path, data) {
|
|
445
|
+
const parts = path.split(".");
|
|
446
|
+
let current = data;
|
|
447
|
+
for (const part of parts) {
|
|
448
|
+
if (current == null || typeof current !== "object") return _MISSING;
|
|
449
|
+
const obj = current;
|
|
450
|
+
if (!Object.hasOwn(obj, part)) return _MISSING;
|
|
451
|
+
current = obj[part];
|
|
452
|
+
}
|
|
453
|
+
return current;
|
|
454
|
+
}
|
|
455
|
+
function coerceEnvValue(raw) {
|
|
456
|
+
const low = raw.toLowerCase();
|
|
457
|
+
if (low === "true") return true;
|
|
458
|
+
if (low === "false") return false;
|
|
459
|
+
const asInt = parseInt(raw, 10);
|
|
460
|
+
if (!isNaN(asInt) && String(asInt) === raw) return asInt;
|
|
461
|
+
const asFloat = parseFloat(raw);
|
|
462
|
+
if (!isNaN(asFloat) && String(asFloat) === raw) return asFloat;
|
|
463
|
+
return raw;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/yaml-engine/evaluator.ts
|
|
467
|
+
var PolicyError = class {
|
|
468
|
+
message;
|
|
469
|
+
constructor(message) {
|
|
470
|
+
this.message = message;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
function evaluateExpression(expr, envelope, outputText, options) {
|
|
474
|
+
const customOps = options?.customOperators ?? null;
|
|
475
|
+
const customSels = options?.customSelectors ?? null;
|
|
476
|
+
if ("all" in expr) {
|
|
477
|
+
return _evalAll(expr.all, envelope, outputText, customOps, customSels);
|
|
478
|
+
}
|
|
479
|
+
if ("any" in expr) {
|
|
480
|
+
return _evalAny(expr.any, envelope, outputText, customOps, customSels);
|
|
481
|
+
}
|
|
482
|
+
if ("not" in expr) {
|
|
483
|
+
return _evalNot(expr.not, envelope, outputText, customOps, customSels);
|
|
484
|
+
}
|
|
485
|
+
const leafKeys = Object.keys(expr);
|
|
486
|
+
if (leafKeys.length !== 1) {
|
|
487
|
+
return new PolicyError(
|
|
488
|
+
`Leaf expression must have exactly one selector key, got ${leafKeys.length}: [${leafKeys.join(", ")}]`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
return _evalLeaf(expr, envelope, outputText, customOps, customSels);
|
|
492
|
+
}
|
|
493
|
+
function _evalAll(exprs, envelope, outputText, customOps, customSels) {
|
|
494
|
+
for (const expr of exprs) {
|
|
495
|
+
const result = evaluateExpression(expr, envelope, outputText, {
|
|
496
|
+
customOperators: customOps,
|
|
497
|
+
customSelectors: customSels
|
|
498
|
+
});
|
|
499
|
+
if (result instanceof PolicyError) return result;
|
|
500
|
+
if (!result) return false;
|
|
501
|
+
}
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
function _evalAny(exprs, envelope, outputText, customOps, customSels) {
|
|
505
|
+
for (const expr of exprs) {
|
|
506
|
+
const result = evaluateExpression(expr, envelope, outputText, {
|
|
507
|
+
customOperators: customOps,
|
|
508
|
+
customSelectors: customSels
|
|
509
|
+
});
|
|
510
|
+
if (result instanceof PolicyError) return result;
|
|
511
|
+
if (result) return true;
|
|
512
|
+
}
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
function _evalNot(expr, envelope, outputText, customOps, customSels) {
|
|
516
|
+
const result = evaluateExpression(expr, envelope, outputText, {
|
|
517
|
+
customOperators: customOps,
|
|
518
|
+
customSelectors: customSels
|
|
519
|
+
});
|
|
520
|
+
if (result instanceof PolicyError) return result;
|
|
521
|
+
return !result;
|
|
522
|
+
}
|
|
523
|
+
function _evalLeaf(leaf, envelope, outputText, customOps, customSels) {
|
|
524
|
+
const selector = Object.keys(leaf)[0];
|
|
525
|
+
const operatorBlock = leaf[selector];
|
|
526
|
+
const value = resolveSelector(selector, envelope, outputText, customSels);
|
|
527
|
+
const opName = Object.keys(operatorBlock)[0];
|
|
528
|
+
const opValue = operatorBlock[opName];
|
|
529
|
+
return _applyOperator(opName, value, opValue, selector, customOps);
|
|
530
|
+
}
|
|
531
|
+
function _applyOperator(op, fieldValue, opValue, selector, customOperators) {
|
|
532
|
+
if (op === "exists") {
|
|
533
|
+
const isPresent = fieldValue !== _MISSING && fieldValue != null;
|
|
534
|
+
return isPresent === opValue;
|
|
535
|
+
}
|
|
536
|
+
if (fieldValue === _MISSING || fieldValue == null) return false;
|
|
537
|
+
try {
|
|
538
|
+
if (Object.hasOwn(OPERATORS, op)) return OPERATORS[op](fieldValue, opValue);
|
|
539
|
+
if (customOperators && Object.hasOwn(customOperators, op)) {
|
|
540
|
+
return Boolean(customOperators[op](fieldValue, opValue));
|
|
541
|
+
}
|
|
542
|
+
return new PolicyError(`Unknown operator: '${op}'`);
|
|
543
|
+
} catch {
|
|
544
|
+
return new PolicyError(
|
|
545
|
+
`Type mismatch: operator '${op}' cannot be applied to selector '${selector}' value ${typeof fieldValue}`
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/yaml-engine/compiler-utils.ts
|
|
551
|
+
var _PLACEHOLDER_RE = /\{([^}]+)\}/g;
|
|
552
|
+
var _PLACEHOLDER_CAP = 200;
|
|
553
|
+
function expandMessage(template, envelope, outputText, customSelectors) {
|
|
554
|
+
const redaction = new RedactionPolicy();
|
|
555
|
+
return template.replace(_PLACEHOLDER_RE, (match, selectorRaw) => {
|
|
556
|
+
const value = resolveSelector(selectorRaw, envelope, outputText, customSelectors);
|
|
557
|
+
if (value === _MISSING || value == null) return match;
|
|
558
|
+
let text = String(value);
|
|
559
|
+
if (redaction._looksLikeSecret(text)) text = "[REDACTED]";
|
|
560
|
+
if (text.length > _PLACEHOLDER_CAP) text = text.slice(0, _PLACEHOLDER_CAP - 3) + "...";
|
|
561
|
+
return text;
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
function validateOperators(bundle, customOperators) {
|
|
565
|
+
const known = /* @__PURE__ */ new Set([
|
|
566
|
+
...BUILTIN_OPERATOR_NAMES,
|
|
567
|
+
...Object.keys(customOperators ?? {})
|
|
568
|
+
]);
|
|
569
|
+
const contracts = bundle.contracts ?? [];
|
|
570
|
+
for (const contract of contracts) {
|
|
571
|
+
const when = contract.when;
|
|
572
|
+
if (when) {
|
|
573
|
+
_validateExpressionOperators(when, known, contract.id);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function _validateExpressionOperators(expr, known, contractId) {
|
|
578
|
+
if (expr == null || typeof expr !== "object") return;
|
|
579
|
+
const e = expr;
|
|
580
|
+
if ("all" in e) {
|
|
581
|
+
for (const sub of e.all) {
|
|
582
|
+
_validateExpressionOperators(sub, known, contractId);
|
|
583
|
+
}
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if ("any" in e) {
|
|
587
|
+
for (const sub of e.any) {
|
|
588
|
+
_validateExpressionOperators(sub, known, contractId);
|
|
589
|
+
}
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if ("not" in e) {
|
|
593
|
+
_validateExpressionOperators(e.not, known, contractId);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
for (const [, operator] of Object.entries(e)) {
|
|
597
|
+
if (operator != null && typeof operator === "object") {
|
|
598
|
+
for (const opName of Object.keys(operator)) {
|
|
599
|
+
if (!known.has(opName)) {
|
|
600
|
+
throw new EdictumConfigError(
|
|
601
|
+
`Contract '${contractId}': unknown operator '${opName}'`
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
function precompileRegexes(expr) {
|
|
609
|
+
if (expr == null || typeof expr !== "object") return expr;
|
|
610
|
+
const e = expr;
|
|
611
|
+
if ("all" in e) {
|
|
612
|
+
return { all: e.all.map(precompileRegexes) };
|
|
613
|
+
}
|
|
614
|
+
if ("any" in e) {
|
|
615
|
+
return { any: e.any.map(precompileRegexes) };
|
|
616
|
+
}
|
|
617
|
+
if ("not" in e) {
|
|
618
|
+
return { not: precompileRegexes(e.not) };
|
|
619
|
+
}
|
|
620
|
+
const compiled = {};
|
|
621
|
+
for (const [selector, operator] of Object.entries(e)) {
|
|
622
|
+
if (operator == null || typeof operator !== "object") {
|
|
623
|
+
compiled[selector] = operator;
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
const newOp = { ...operator };
|
|
627
|
+
if ("matches" in newOp && typeof newOp.matches === "string") {
|
|
628
|
+
newOp.matches = new RegExp(newOp.matches);
|
|
629
|
+
}
|
|
630
|
+
if ("matches_any" in newOp && Array.isArray(newOp.matches_any)) {
|
|
631
|
+
newOp.matches_any = newOp.matches_any.map(
|
|
632
|
+
(p) => typeof p === "string" ? new RegExp(p) : p
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
compiled[selector] = newOp;
|
|
636
|
+
}
|
|
637
|
+
return compiled;
|
|
638
|
+
}
|
|
639
|
+
function extractOutputPatterns(expr) {
|
|
640
|
+
if (expr == null || typeof expr !== "object") return [];
|
|
641
|
+
const e = expr;
|
|
642
|
+
if ("all" in e) {
|
|
643
|
+
const patterns = [];
|
|
644
|
+
for (const sub of e.all) {
|
|
645
|
+
patterns.push(...extractOutputPatterns(sub));
|
|
646
|
+
}
|
|
647
|
+
return patterns;
|
|
648
|
+
}
|
|
649
|
+
if ("any" in e) {
|
|
650
|
+
const patterns = [];
|
|
651
|
+
for (const sub of e.any) {
|
|
652
|
+
patterns.push(...extractOutputPatterns(sub));
|
|
653
|
+
}
|
|
654
|
+
return patterns;
|
|
655
|
+
}
|
|
656
|
+
if ("not" in e) {
|
|
657
|
+
return extractOutputPatterns(e.not);
|
|
658
|
+
}
|
|
659
|
+
const collected = [];
|
|
660
|
+
for (const [selector, operator] of Object.entries(e)) {
|
|
661
|
+
if (selector !== "output.text" || operator == null || typeof operator !== "object") continue;
|
|
662
|
+
const op = operator;
|
|
663
|
+
if ("matches" in op && op.matches instanceof RegExp) {
|
|
664
|
+
collected.push(op.matches);
|
|
665
|
+
}
|
|
666
|
+
if ("matches_any" in op && Array.isArray(op.matches_any)) {
|
|
667
|
+
for (const p of op.matches_any) {
|
|
668
|
+
if (p instanceof RegExp) collected.push(p);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return collected;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// src/yaml-engine/compile-contracts.ts
|
|
676
|
+
function _evalAndVerdict(whenExpr, envelope, outputText, messageTemplate, tags, thenMetadata, customOps, customSels) {
|
|
677
|
+
try {
|
|
678
|
+
const result = evaluateExpression(whenExpr, envelope, outputText, {
|
|
679
|
+
customOperators: customOps,
|
|
680
|
+
customSelectors: customSels
|
|
681
|
+
});
|
|
682
|
+
if (result instanceof PolicyError) {
|
|
683
|
+
const msg = expandMessage(messageTemplate, envelope, outputText, customSels);
|
|
684
|
+
return Verdict.fail(msg, { tags, policyError: true, ...thenMetadata });
|
|
685
|
+
}
|
|
686
|
+
if (result) {
|
|
687
|
+
const msg = expandMessage(messageTemplate, envelope, outputText, customSels);
|
|
688
|
+
return Verdict.fail(msg, { tags, ...thenMetadata });
|
|
689
|
+
}
|
|
690
|
+
return Verdict.pass_();
|
|
691
|
+
} catch (exc) {
|
|
692
|
+
const msg = expandMessage(messageTemplate, envelope, outputText, customSels);
|
|
693
|
+
return Verdict.fail(msg, { tags, policyError: true, errorDetail: String(exc), ...thenMetadata });
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
function _maybeObserve(result, contract) {
|
|
697
|
+
if (contract._observe === true || contract._shadow === true) result._edictum_observe = true;
|
|
698
|
+
}
|
|
699
|
+
function compilePre(contract, mode, customOps, customSels) {
|
|
700
|
+
const contractId = contract.id;
|
|
701
|
+
const tool = contract.tool;
|
|
702
|
+
const whenExpr = precompileRegexes(contract.when);
|
|
703
|
+
const then = contract.then;
|
|
704
|
+
const msgTpl = then.message;
|
|
705
|
+
const tags = then.tags ?? [];
|
|
706
|
+
const meta = then.metadata ?? {};
|
|
707
|
+
const check = (envelope) => _evalAndVerdict(whenExpr, envelope, void 0, msgTpl, tags, meta, customOps, customSels);
|
|
708
|
+
const result = {
|
|
709
|
+
check,
|
|
710
|
+
name: contractId,
|
|
711
|
+
tool,
|
|
712
|
+
type: "precondition",
|
|
713
|
+
mode,
|
|
714
|
+
_edictum_type: "precondition",
|
|
715
|
+
_edictum_tool: tool,
|
|
716
|
+
_edictum_when: null,
|
|
717
|
+
_edictum_mode: mode,
|
|
718
|
+
_edictum_id: contractId,
|
|
719
|
+
_edictum_source: "yaml_precondition",
|
|
720
|
+
_edictum_effect: then.effect ?? "deny",
|
|
721
|
+
_edictum_timeout: then.timeout ?? 300,
|
|
722
|
+
_edictum_timeout_effect: then.timeout_effect ?? "deny"
|
|
723
|
+
};
|
|
724
|
+
_maybeObserve(result, contract);
|
|
725
|
+
return result;
|
|
726
|
+
}
|
|
727
|
+
function compilePost(contract, mode, customOps, customSels) {
|
|
728
|
+
const contractId = contract.id;
|
|
729
|
+
const tool = contract.tool;
|
|
730
|
+
const whenExpr = precompileRegexes(contract.when);
|
|
731
|
+
const then = contract.then;
|
|
732
|
+
const msgTpl = then.message;
|
|
733
|
+
const tags = then.tags ?? [];
|
|
734
|
+
const meta = then.metadata ?? {};
|
|
735
|
+
const check = (envelope, response) => {
|
|
736
|
+
const outputText = response != null ? String(response) : void 0;
|
|
737
|
+
return _evalAndVerdict(whenExpr, envelope, outputText, msgTpl, tags, meta, customOps, customSels);
|
|
738
|
+
};
|
|
739
|
+
const effectValue = then.effect ?? "warn";
|
|
740
|
+
const result = {
|
|
741
|
+
check,
|
|
742
|
+
name: contractId,
|
|
743
|
+
tool,
|
|
744
|
+
type: "postcondition",
|
|
745
|
+
mode,
|
|
746
|
+
effect: effectValue,
|
|
747
|
+
_edictum_type: "postcondition",
|
|
748
|
+
_edictum_tool: tool,
|
|
749
|
+
_edictum_when: null,
|
|
750
|
+
_edictum_mode: mode,
|
|
751
|
+
_edictum_id: contractId,
|
|
752
|
+
_edictum_source: "yaml_postcondition",
|
|
753
|
+
_edictum_effect: effectValue,
|
|
754
|
+
_edictum_redact_patterns: extractOutputPatterns(whenExpr)
|
|
755
|
+
};
|
|
756
|
+
_maybeObserve(result, contract);
|
|
757
|
+
return result;
|
|
758
|
+
}
|
|
759
|
+
function compileSession(contract, mode, limits) {
|
|
760
|
+
const contractId = contract.id;
|
|
761
|
+
const then = contract.then;
|
|
762
|
+
const messageTemplate = then.message;
|
|
763
|
+
const tags = then.tags ?? [];
|
|
764
|
+
const thenMetadata = then.metadata ?? {};
|
|
765
|
+
const capturedLimits = { ...limits };
|
|
766
|
+
const check = async (session) => {
|
|
767
|
+
const execCount = await session.executionCount();
|
|
768
|
+
if (execCount >= capturedLimits.maxToolCalls) {
|
|
769
|
+
return Verdict.fail(messageTemplate, { tags, ...thenMetadata });
|
|
770
|
+
}
|
|
771
|
+
const attemptCount = await session.attemptCount();
|
|
772
|
+
if (attemptCount >= capturedLimits.maxAttempts) {
|
|
773
|
+
return Verdict.fail(messageTemplate, { tags, ...thenMetadata });
|
|
774
|
+
}
|
|
775
|
+
return Verdict.pass_();
|
|
776
|
+
};
|
|
777
|
+
const result = {
|
|
778
|
+
check,
|
|
779
|
+
name: contractId,
|
|
780
|
+
type: "session_contract",
|
|
781
|
+
_edictum_type: "session_contract",
|
|
782
|
+
_edictum_mode: mode,
|
|
783
|
+
_edictum_id: contractId,
|
|
784
|
+
_edictum_message: messageTemplate,
|
|
785
|
+
_edictum_tags: tags,
|
|
786
|
+
_edictum_then_metadata: thenMetadata,
|
|
787
|
+
_edictum_source: "yaml_session"
|
|
788
|
+
};
|
|
789
|
+
if (contract._observe === true || contract._shadow === true) {
|
|
790
|
+
result._edictum_observe = true;
|
|
791
|
+
}
|
|
792
|
+
return result;
|
|
793
|
+
}
|
|
794
|
+
function mergeSessionLimits(contract, existing) {
|
|
795
|
+
const sessionLimits = contract.limits;
|
|
796
|
+
let maxToolCalls = existing.maxToolCalls;
|
|
797
|
+
let maxAttempts = existing.maxAttempts;
|
|
798
|
+
const maxCallsPerTool = { ...existing.maxCallsPerTool };
|
|
799
|
+
if ("max_tool_calls" in sessionLimits) {
|
|
800
|
+
const raw = sessionLimits.max_tool_calls;
|
|
801
|
+
if (typeof raw !== "number" || !Number.isFinite(raw)) {
|
|
802
|
+
throw new EdictumConfigError(`Session limit max_tool_calls must be a finite number, got: ${String(raw)}`);
|
|
803
|
+
}
|
|
804
|
+
maxToolCalls = Math.min(maxToolCalls, raw);
|
|
805
|
+
}
|
|
806
|
+
if ("max_attempts" in sessionLimits) {
|
|
807
|
+
const raw = sessionLimits.max_attempts;
|
|
808
|
+
if (typeof raw !== "number" || !Number.isFinite(raw)) {
|
|
809
|
+
throw new EdictumConfigError(`Session limit max_attempts must be a finite number, got: ${String(raw)}`);
|
|
810
|
+
}
|
|
811
|
+
maxAttempts = Math.min(maxAttempts, raw);
|
|
812
|
+
}
|
|
813
|
+
if ("max_calls_per_tool" in sessionLimits) {
|
|
814
|
+
const perTool = sessionLimits.max_calls_per_tool;
|
|
815
|
+
for (const [tool, limit] of Object.entries(perTool)) {
|
|
816
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
817
|
+
throw new EdictumConfigError(
|
|
818
|
+
`Session limit max_calls_per_tool['${tool}'] must be a finite number, got: ${String(limit)}`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
if (Object.hasOwn(maxCallsPerTool, tool)) {
|
|
822
|
+
maxCallsPerTool[tool] = Math.min(maxCallsPerTool[tool], limit);
|
|
823
|
+
} else {
|
|
824
|
+
maxCallsPerTool[tool] = limit;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return { maxAttempts, maxToolCalls, maxCallsPerTool };
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/yaml-engine/sandbox-compiler.ts
|
|
832
|
+
import { realpathSync } from "fs";
|
|
833
|
+
import { resolve as pathResolve } from "path";
|
|
834
|
+
|
|
835
|
+
// src/fnmatch.ts
|
|
836
|
+
function fnmatch(name, pattern) {
|
|
837
|
+
if (pattern === "*") return true;
|
|
838
|
+
if (!pattern.includes("*") && !pattern.includes("?")) {
|
|
839
|
+
return name === pattern;
|
|
840
|
+
}
|
|
841
|
+
const safeName = name.length > 1e4 ? name.slice(0, 1e4) : name;
|
|
842
|
+
const safePattern = pattern.length > 1e4 ? pattern.slice(0, 1e4) : pattern;
|
|
843
|
+
let regex = "";
|
|
844
|
+
for (let i = 0; i < safePattern.length; i++) {
|
|
845
|
+
const ch = safePattern[i] ?? "";
|
|
846
|
+
if (ch === "*") {
|
|
847
|
+
regex += ".*";
|
|
848
|
+
} else if (ch === "?") {
|
|
849
|
+
regex += ".";
|
|
850
|
+
} else if (".+^${}()|[]\\".includes(ch)) {
|
|
851
|
+
regex += "\\" + ch;
|
|
852
|
+
} else {
|
|
853
|
+
regex += ch;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return new RegExp("^" + regex + "$").test(safeName);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// src/yaml-engine/sandbox-compiler.ts
|
|
860
|
+
var _REDIRECT_PREFIX_RE = /^(?:\d*>>|>>|\d*>|>|<<|<)/;
|
|
861
|
+
var _SHELL_SEPARATOR_RE = /[;|&\n\r`]|\$\(|\$\{|<\(/;
|
|
862
|
+
function tokenizeCommand(cmd) {
|
|
863
|
+
const rawTokens = _shlexSplit(cmd);
|
|
864
|
+
const tokens = [];
|
|
865
|
+
for (const t of rawTokens) {
|
|
866
|
+
const stripped = t.replace(_REDIRECT_PREFIX_RE, "");
|
|
867
|
+
if (stripped) tokens.push(stripped);
|
|
868
|
+
}
|
|
869
|
+
return tokens;
|
|
870
|
+
}
|
|
871
|
+
function _shlexSplit(s) {
|
|
872
|
+
const tokens = [];
|
|
873
|
+
let current = "";
|
|
874
|
+
let inSingle = false;
|
|
875
|
+
let inDouble = false;
|
|
876
|
+
let i = 0;
|
|
877
|
+
try {
|
|
878
|
+
while (i < s.length) {
|
|
879
|
+
const ch = s.charAt(i);
|
|
880
|
+
if (inSingle) {
|
|
881
|
+
if (ch === "'") {
|
|
882
|
+
inSingle = false;
|
|
883
|
+
} else {
|
|
884
|
+
current += ch;
|
|
885
|
+
}
|
|
886
|
+
} else if (inDouble) {
|
|
887
|
+
if (ch === "\\" && i + 1 < s.length) {
|
|
888
|
+
const next = s.charAt(i + 1);
|
|
889
|
+
if (next === '"' || next === "\\" || next === "$" || next === "`" || next === "\n") {
|
|
890
|
+
current += next;
|
|
891
|
+
i++;
|
|
892
|
+
} else {
|
|
893
|
+
current += ch;
|
|
894
|
+
}
|
|
895
|
+
} else if (ch === '"') {
|
|
896
|
+
inDouble = false;
|
|
897
|
+
} else {
|
|
898
|
+
current += ch;
|
|
899
|
+
}
|
|
900
|
+
} else if (ch === "'") {
|
|
901
|
+
inSingle = true;
|
|
902
|
+
} else if (ch === '"') {
|
|
903
|
+
inDouble = true;
|
|
904
|
+
} else if (ch === " " || ch === " ") {
|
|
905
|
+
if (current) {
|
|
906
|
+
tokens.push(current);
|
|
907
|
+
current = "";
|
|
908
|
+
}
|
|
909
|
+
} else {
|
|
910
|
+
current += ch;
|
|
911
|
+
}
|
|
912
|
+
i++;
|
|
913
|
+
}
|
|
914
|
+
if (inSingle || inDouble) {
|
|
915
|
+
return s.split(/\s+/).filter(Boolean).map((t) => t.replace(/^['"]|['"]$/g, ""));
|
|
916
|
+
}
|
|
917
|
+
if (current) tokens.push(current);
|
|
918
|
+
return tokens;
|
|
919
|
+
} catch {
|
|
920
|
+
return s.split(/\s+/).filter(Boolean).map((t) => t.replace(/^['"]|['"]$/g, ""));
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
var _PATH_ARG_KEYS = /* @__PURE__ */ new Set([
|
|
924
|
+
"path",
|
|
925
|
+
"file_path",
|
|
926
|
+
"filePath",
|
|
927
|
+
"directory",
|
|
928
|
+
"dir",
|
|
929
|
+
"folder",
|
|
930
|
+
"target",
|
|
931
|
+
"destination",
|
|
932
|
+
"source",
|
|
933
|
+
"src",
|
|
934
|
+
"dst"
|
|
935
|
+
]);
|
|
936
|
+
function _realpath(p) {
|
|
937
|
+
try {
|
|
938
|
+
return realpathSync(p);
|
|
939
|
+
} catch {
|
|
940
|
+
return pathResolve(p);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function extractPaths(envelope) {
|
|
944
|
+
const paths = [];
|
|
945
|
+
const seen = /* @__PURE__ */ new Set();
|
|
946
|
+
function add(p) {
|
|
947
|
+
if (!p) return;
|
|
948
|
+
const resolved = _realpath(p);
|
|
949
|
+
if (!seen.has(resolved)) {
|
|
950
|
+
seen.add(resolved);
|
|
951
|
+
paths.push(resolved);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (envelope.filePath) add(envelope.filePath);
|
|
955
|
+
const args = envelope.args;
|
|
956
|
+
for (const [key, value] of Object.entries(args)) {
|
|
957
|
+
if (typeof value === "string" && _PATH_ARG_KEYS.has(key)) add(value);
|
|
958
|
+
}
|
|
959
|
+
for (const [key, value] of Object.entries(args)) {
|
|
960
|
+
if (typeof value === "string" && value.startsWith("/") && !_PATH_ARG_KEYS.has(key)) add(value);
|
|
961
|
+
}
|
|
962
|
+
const cmd = envelope.bashCommand ?? args.command ?? "";
|
|
963
|
+
if (cmd) {
|
|
964
|
+
for (const token of tokenizeCommand(cmd)) {
|
|
965
|
+
if (token.startsWith("/")) add(token);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
return paths;
|
|
969
|
+
}
|
|
970
|
+
function extractCommand(envelope) {
|
|
971
|
+
const cmd = envelope.bashCommand ?? envelope.args.command;
|
|
972
|
+
if (!cmd || typeof cmd !== "string") return null;
|
|
973
|
+
const stripped = cmd.trim();
|
|
974
|
+
if (!stripped) return null;
|
|
975
|
+
if (_SHELL_SEPARATOR_RE.test(stripped)) return "\0";
|
|
976
|
+
const rawFirst = stripped.split(/\s/)[0] ?? "";
|
|
977
|
+
if (_REDIRECT_PREFIX_RE.test(rawFirst)) return "\0";
|
|
978
|
+
const tokens = tokenizeCommand(stripped);
|
|
979
|
+
return tokens.length > 0 ? tokens[0] ?? null : null;
|
|
980
|
+
}
|
|
981
|
+
function extractUrls(envelope) {
|
|
982
|
+
const urls = [];
|
|
983
|
+
const seen = /* @__PURE__ */ new Set();
|
|
984
|
+
function addUrl(u) {
|
|
985
|
+
if (!seen.has(u)) {
|
|
986
|
+
seen.add(u);
|
|
987
|
+
urls.push(u);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
for (const value of Object.values(envelope.args)) {
|
|
991
|
+
if (typeof value !== "string" || !value.includes("://")) continue;
|
|
992
|
+
if (extractHostname(value) !== null) {
|
|
993
|
+
addUrl(value);
|
|
994
|
+
} else {
|
|
995
|
+
for (const token of tokenizeCommand(value)) {
|
|
996
|
+
if (token.includes("://") && extractHostname(token) !== null) addUrl(token);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return urls;
|
|
1001
|
+
}
|
|
1002
|
+
function extractHostname(url) {
|
|
1003
|
+
try {
|
|
1004
|
+
return new URL(url).hostname || null;
|
|
1005
|
+
} catch {
|
|
1006
|
+
return null;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
function domainMatches(hostname, patterns) {
|
|
1010
|
+
return patterns.some((p) => fnmatch(hostname, p));
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// src/yaml-engine/sandbox-compile-fn.ts
|
|
1014
|
+
import { realpathSync as realpathSync2 } from "fs";
|
|
1015
|
+
import { resolve as pathResolve2 } from "path";
|
|
1016
|
+
function _realpath2(p) {
|
|
1017
|
+
try {
|
|
1018
|
+
return realpathSync2(p);
|
|
1019
|
+
} catch {
|
|
1020
|
+
return pathResolve2(p);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
function _pathWithin(filePath, prefix) {
|
|
1024
|
+
return filePath === prefix || filePath.startsWith(prefix.replace(/\/+$/, "") + "/");
|
|
1025
|
+
}
|
|
1026
|
+
function compileSandbox(contract, mode) {
|
|
1027
|
+
const contractId = contract.id;
|
|
1028
|
+
const toolPatterns = "tools" in contract ? contract.tools : [contract.tool];
|
|
1029
|
+
const within = (contract.within ?? []).map(_realpath2);
|
|
1030
|
+
const notWithin = (contract.not_within ?? []).map(_realpath2);
|
|
1031
|
+
const allows = contract.allows ?? {};
|
|
1032
|
+
const notAllows = contract.not_allows ?? {};
|
|
1033
|
+
const allowedCommands = allows.commands ?? [];
|
|
1034
|
+
const allowedDomains = allows.domains ?? [];
|
|
1035
|
+
const blockedDomains = notAllows.domains ?? [];
|
|
1036
|
+
const outside = contract.outside ?? "deny";
|
|
1037
|
+
const messageTemplate = contract.message ?? "Tool call outside sandbox boundary.";
|
|
1038
|
+
const timeout = contract.timeout ?? 300;
|
|
1039
|
+
const timeoutEffect = contract.timeout_effect ?? "deny";
|
|
1040
|
+
const check = (envelope) => {
|
|
1041
|
+
if (within.length > 0 || notWithin.length > 0) {
|
|
1042
|
+
const paths = extractPaths(envelope);
|
|
1043
|
+
if (paths.length > 0) {
|
|
1044
|
+
for (const p of paths) {
|
|
1045
|
+
for (const excluded of notWithin) {
|
|
1046
|
+
if (_pathWithin(p, excluded)) {
|
|
1047
|
+
return Verdict.fail(expandMessage(messageTemplate, envelope));
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
if (within.length > 0) {
|
|
1052
|
+
for (const p of paths) {
|
|
1053
|
+
if (!within.some((allowed) => _pathWithin(p, allowed))) {
|
|
1054
|
+
return Verdict.fail(expandMessage(messageTemplate, envelope));
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
if (allowedCommands.length > 0) {
|
|
1061
|
+
const firstToken = extractCommand(envelope);
|
|
1062
|
+
if (firstToken !== null && !allowedCommands.includes(firstToken)) {
|
|
1063
|
+
return Verdict.fail(expandMessage(messageTemplate, envelope));
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
const urls = extractUrls(envelope);
|
|
1067
|
+
if (urls.length > 0) {
|
|
1068
|
+
for (const url of urls) {
|
|
1069
|
+
const hostname = extractHostname(url);
|
|
1070
|
+
if (hostname) {
|
|
1071
|
+
if (blockedDomains.length > 0 && domainMatches(hostname, blockedDomains)) {
|
|
1072
|
+
return Verdict.fail(expandMessage(messageTemplate, envelope));
|
|
1073
|
+
}
|
|
1074
|
+
if (allowedDomains.length > 0 && !domainMatches(hostname, allowedDomains)) {
|
|
1075
|
+
return Verdict.fail(expandMessage(messageTemplate, envelope));
|
|
1076
|
+
}
|
|
1077
|
+
} else if (allowedDomains.length > 0) {
|
|
1078
|
+
return Verdict.fail(expandMessage(messageTemplate, envelope));
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
return Verdict.pass_();
|
|
1083
|
+
};
|
|
1084
|
+
const result = {
|
|
1085
|
+
check,
|
|
1086
|
+
name: contractId,
|
|
1087
|
+
tool: toolPatterns.length === 1 ? toolPatterns[0] : void 0,
|
|
1088
|
+
_edictum_type: "sandbox",
|
|
1089
|
+
_edictum_tools: toolPatterns,
|
|
1090
|
+
_edictum_mode: mode,
|
|
1091
|
+
_edictum_id: contractId,
|
|
1092
|
+
_edictum_source: "yaml_sandbox",
|
|
1093
|
+
_edictum_effect: outside,
|
|
1094
|
+
_edictum_timeout: timeout,
|
|
1095
|
+
_edictum_timeout_effect: timeoutEffect
|
|
1096
|
+
};
|
|
1097
|
+
if (contract._observe === true || contract._shadow === true) {
|
|
1098
|
+
result._edictum_observe = true;
|
|
1099
|
+
}
|
|
1100
|
+
return result;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// src/yaml-engine/compiler.ts
|
|
1104
|
+
function compileContracts(bundle, options = {}) {
|
|
1105
|
+
const customOps = options?.customOperators ?? null;
|
|
1106
|
+
const customSels = options?.customSelectors ?? null;
|
|
1107
|
+
validateOperators(bundle, customOps);
|
|
1108
|
+
if (bundle.defaults == null || typeof bundle.defaults !== "object") {
|
|
1109
|
+
throw new EdictumConfigError(
|
|
1110
|
+
"Bundle missing required 'defaults' section with 'mode' field"
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
const defaults = bundle.defaults;
|
|
1114
|
+
const defaultMode = defaults.mode;
|
|
1115
|
+
const preconditions = [];
|
|
1116
|
+
const postconditions = [];
|
|
1117
|
+
const sessionContracts = [];
|
|
1118
|
+
const sandboxContracts = [];
|
|
1119
|
+
let limits = { ...DEFAULT_LIMITS };
|
|
1120
|
+
const contracts = bundle.contracts ?? [];
|
|
1121
|
+
for (const contract of contracts) {
|
|
1122
|
+
if (contract.enabled === false) continue;
|
|
1123
|
+
const contractType = contract.type;
|
|
1124
|
+
const contractMode = contract.mode ?? defaultMode;
|
|
1125
|
+
if (contractType === "pre") {
|
|
1126
|
+
preconditions.push(compilePre(contract, contractMode, customOps, customSels));
|
|
1127
|
+
} else if (contractType === "post") {
|
|
1128
|
+
postconditions.push(compilePost(contract, contractMode, customOps, customSels));
|
|
1129
|
+
} else if (contractType === "session") {
|
|
1130
|
+
const isObserve = contract._observe ?? contract._shadow ?? false;
|
|
1131
|
+
if (!isObserve) {
|
|
1132
|
+
limits = mergeSessionLimits(contract, limits);
|
|
1133
|
+
}
|
|
1134
|
+
sessionContracts.push(compileSession(contract, contractMode, limits));
|
|
1135
|
+
} else if (contractType === "sandbox") {
|
|
1136
|
+
sandboxContracts.push(compileSandbox(contract, contractMode));
|
|
1137
|
+
} else {
|
|
1138
|
+
throw new EdictumConfigError(
|
|
1139
|
+
`Unknown contract type "${contractType}" in contract "${contract.id ?? "unknown"}". Expected "pre", "post", "session", or "sandbox".`
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
const tools = bundle.tools ?? {};
|
|
1144
|
+
return {
|
|
1145
|
+
preconditions,
|
|
1146
|
+
postconditions,
|
|
1147
|
+
sessionContracts,
|
|
1148
|
+
sandboxContracts,
|
|
1149
|
+
limits,
|
|
1150
|
+
defaultMode,
|
|
1151
|
+
tools
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// src/yaml-engine/loader.ts
|
|
1156
|
+
import { createHash } from "crypto";
|
|
1157
|
+
import { readFileSync, realpathSync as realpathSync3, statSync } from "fs";
|
|
1158
|
+
|
|
1159
|
+
// src/yaml-engine/loader-validators.ts
|
|
1160
|
+
function validateSchema(data) {
|
|
1161
|
+
if (data.apiVersion !== "edictum/v1") {
|
|
1162
|
+
throw new EdictumConfigError(
|
|
1163
|
+
`Schema validation failed: apiVersion must be 'edictum/v1', got '${String(data.apiVersion)}'`
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
if (data.kind !== "ContractBundle") {
|
|
1167
|
+
throw new EdictumConfigError(
|
|
1168
|
+
`Schema validation failed: kind must be 'ContractBundle', got '${String(data.kind)}'`
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
if (data.metadata != null && typeof data.metadata !== "object") {
|
|
1172
|
+
throw new EdictumConfigError("Schema validation failed: metadata must be an object");
|
|
1173
|
+
}
|
|
1174
|
+
if (!Array.isArray(data.contracts)) {
|
|
1175
|
+
throw new EdictumConfigError("Schema validation failed: contracts must be an array");
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
var CONTROL_CHAR_RE = /[\x00-\x1f\x7f-\x9f]/;
|
|
1179
|
+
function validateContractId(contractId) {
|
|
1180
|
+
if (CONTROL_CHAR_RE.test(contractId)) {
|
|
1181
|
+
throw new EdictumConfigError(
|
|
1182
|
+
`Contract id contains control characters: '${contractId.replace(CONTROL_CHAR_RE, "\\x??")}'`
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
function validateUniqueIds(data) {
|
|
1187
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1188
|
+
const contracts = data.contracts ?? [];
|
|
1189
|
+
for (const contract of contracts) {
|
|
1190
|
+
const contractId = contract.id;
|
|
1191
|
+
if (contractId != null) {
|
|
1192
|
+
validateContractId(contractId);
|
|
1193
|
+
if (ids.has(contractId)) {
|
|
1194
|
+
throw new EdictumConfigError(`Duplicate contract id: '${contractId}'`);
|
|
1195
|
+
}
|
|
1196
|
+
ids.add(contractId);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
function validateRegexes(data) {
|
|
1201
|
+
const contracts = data.contracts ?? [];
|
|
1202
|
+
for (const contract of contracts) {
|
|
1203
|
+
const when = contract.when;
|
|
1204
|
+
if (when != null) {
|
|
1205
|
+
validateExpressionRegexes(when);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
function validateExpressionRegexes(expr) {
|
|
1210
|
+
if (expr == null || typeof expr !== "object") return;
|
|
1211
|
+
const e = expr;
|
|
1212
|
+
if ("all" in e) {
|
|
1213
|
+
for (const sub of e.all) validateExpressionRegexes(sub);
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
if ("any" in e) {
|
|
1217
|
+
for (const sub of e.any) validateExpressionRegexes(sub);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if ("not" in e) {
|
|
1221
|
+
validateExpressionRegexes(e.not);
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
for (const operator of Object.values(e)) {
|
|
1225
|
+
if (operator == null || typeof operator !== "object") continue;
|
|
1226
|
+
const op = operator;
|
|
1227
|
+
if ("matches" in op) tryCompileRegex(op.matches);
|
|
1228
|
+
if ("matches_any" in op) {
|
|
1229
|
+
for (const pattern of op.matches_any) tryCompileRegex(pattern);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
function tryCompileRegex(pattern) {
|
|
1234
|
+
try {
|
|
1235
|
+
new RegExp(pattern);
|
|
1236
|
+
} catch (e) {
|
|
1237
|
+
throw new EdictumConfigError(`Invalid regex pattern '${pattern}': ${String(e)}`);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
function validatePreSelectors(data) {
|
|
1241
|
+
const contracts = data.contracts ?? [];
|
|
1242
|
+
for (const contract of contracts) {
|
|
1243
|
+
if (contract.type !== "pre") continue;
|
|
1244
|
+
const when = contract.when;
|
|
1245
|
+
if (when != null && expressionHasSelector(when, "output.text")) {
|
|
1246
|
+
throw new EdictumConfigError(
|
|
1247
|
+
`Contract '${contract.id ?? "?"}': output.text selector is not available in type: pre contracts`
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
function expressionHasSelector(expr, target) {
|
|
1253
|
+
if (expr == null || typeof expr !== "object") return false;
|
|
1254
|
+
const e = expr;
|
|
1255
|
+
if ("all" in e) return e.all.some((sub) => expressionHasSelector(sub, target));
|
|
1256
|
+
if ("any" in e) return e.any.some((sub) => expressionHasSelector(sub, target));
|
|
1257
|
+
if ("not" in e) return expressionHasSelector(e.not, target);
|
|
1258
|
+
return target in e;
|
|
1259
|
+
}
|
|
1260
|
+
function validateSandboxContracts(data) {
|
|
1261
|
+
const contracts = data.contracts ?? [];
|
|
1262
|
+
for (const contract of contracts) {
|
|
1263
|
+
if (contract.type !== "sandbox") continue;
|
|
1264
|
+
const cid = contract.id ?? "?";
|
|
1265
|
+
if ("not_within" in contract && !("within" in contract)) {
|
|
1266
|
+
throw new EdictumConfigError(`Contract '${cid}': not_within requires within to also be set`);
|
|
1267
|
+
}
|
|
1268
|
+
if ("not_allows" in contract && !("allows" in contract)) {
|
|
1269
|
+
throw new EdictumConfigError(`Contract '${cid}': not_allows requires allows to also be set`);
|
|
1270
|
+
}
|
|
1271
|
+
if ("not_allows" in contract) {
|
|
1272
|
+
const notAllows = contract.not_allows ?? {};
|
|
1273
|
+
if ("domains" in notAllows) {
|
|
1274
|
+
const allows = contract.allows ?? {};
|
|
1275
|
+
if (!("domains" in allows)) {
|
|
1276
|
+
throw new EdictumConfigError(
|
|
1277
|
+
`Contract '${cid}': not_allows.domains requires allows.domains to also be set`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/yaml-engine/loader.ts
|
|
1286
|
+
var MAX_BUNDLE_SIZE = 1048576;
|
|
1287
|
+
function computeHash(rawBytes) {
|
|
1288
|
+
return { hex: createHash("sha256").update(rawBytes).digest("hex") };
|
|
1289
|
+
}
|
|
1290
|
+
function requireYaml() {
|
|
1291
|
+
try {
|
|
1292
|
+
const yaml = __require("js-yaml");
|
|
1293
|
+
return yaml;
|
|
1294
|
+
} catch {
|
|
1295
|
+
throw new EdictumConfigError(
|
|
1296
|
+
"The YAML engine requires js-yaml. Install it with: npm install js-yaml"
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
function parseYaml(content) {
|
|
1301
|
+
const yaml = requireYaml();
|
|
1302
|
+
let data;
|
|
1303
|
+
try {
|
|
1304
|
+
data = yaml.load(content);
|
|
1305
|
+
} catch (e) {
|
|
1306
|
+
throw new EdictumConfigError(`YAML parse error: ${String(e)}`);
|
|
1307
|
+
}
|
|
1308
|
+
if (data == null || typeof data !== "object" || Array.isArray(data)) {
|
|
1309
|
+
throw new EdictumConfigError("YAML document must be a mapping");
|
|
1310
|
+
}
|
|
1311
|
+
return data;
|
|
1312
|
+
}
|
|
1313
|
+
function validateBundle(data) {
|
|
1314
|
+
validateSchema(data);
|
|
1315
|
+
validateUniqueIds(data);
|
|
1316
|
+
validateRegexes(data);
|
|
1317
|
+
validatePreSelectors(data);
|
|
1318
|
+
validateSandboxContracts(data);
|
|
1319
|
+
}
|
|
1320
|
+
function loadBundle(source) {
|
|
1321
|
+
const resolved = realpathSync3(source);
|
|
1322
|
+
const fileSize = statSync(resolved).size;
|
|
1323
|
+
if (fileSize > MAX_BUNDLE_SIZE) {
|
|
1324
|
+
throw new EdictumConfigError(
|
|
1325
|
+
`Bundle file too large (${fileSize} bytes, max ${MAX_BUNDLE_SIZE})`
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
const rawBytes = readFileSync(resolved);
|
|
1329
|
+
const bundleHash = computeHash(rawBytes);
|
|
1330
|
+
const data = parseYaml(rawBytes.toString("utf-8"));
|
|
1331
|
+
validateBundle(data);
|
|
1332
|
+
return [data, bundleHash];
|
|
1333
|
+
}
|
|
1334
|
+
function loadBundleString(content) {
|
|
1335
|
+
const rawBytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
1336
|
+
if (rawBytes.length > MAX_BUNDLE_SIZE) {
|
|
1337
|
+
throw new EdictumConfigError(
|
|
1338
|
+
`Bundle content too large (${rawBytes.length} bytes, max ${MAX_BUNDLE_SIZE})`
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
const bundleHash = computeHash(rawBytes);
|
|
1342
|
+
const text = typeof content === "string" ? content : new TextDecoder().decode(rawBytes);
|
|
1343
|
+
const data = parseYaml(text);
|
|
1344
|
+
validateBundle(data);
|
|
1345
|
+
return [data, bundleHash];
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// src/factory.ts
|
|
1349
|
+
function fromYaml(...args) {
|
|
1350
|
+
let paths;
|
|
1351
|
+
let options;
|
|
1352
|
+
const last = args[args.length - 1];
|
|
1353
|
+
if (typeof last === "object" && last !== null && !Array.isArray(last)) {
|
|
1354
|
+
paths = args.slice(0, -1);
|
|
1355
|
+
options = last;
|
|
1356
|
+
} else {
|
|
1357
|
+
paths = args;
|
|
1358
|
+
options = {};
|
|
1359
|
+
}
|
|
1360
|
+
if (paths.length === 0) {
|
|
1361
|
+
throw new EdictumConfigError("fromYaml() requires at least one path");
|
|
1362
|
+
}
|
|
1363
|
+
const loaded = [];
|
|
1364
|
+
for (const p of paths) {
|
|
1365
|
+
loaded.push(loadBundle(p));
|
|
1366
|
+
}
|
|
1367
|
+
let bundleData;
|
|
1368
|
+
let policyVersion;
|
|
1369
|
+
let report;
|
|
1370
|
+
if (loaded.length === 1) {
|
|
1371
|
+
const entry = loaded[0];
|
|
1372
|
+
bundleData = entry[0];
|
|
1373
|
+
policyVersion = entry[1].hex;
|
|
1374
|
+
report = { overriddenContracts: [], observeContracts: [] };
|
|
1375
|
+
} else {
|
|
1376
|
+
const bundleTuples = loaded.map(
|
|
1377
|
+
([data], i) => [data, paths[i]]
|
|
1378
|
+
);
|
|
1379
|
+
const composed = composeBundles(...bundleTuples);
|
|
1380
|
+
bundleData = composed.bundle;
|
|
1381
|
+
report = composed.report;
|
|
1382
|
+
policyVersion = createHash2("sha256").update(loaded.map(([, h]) => h.hex).join(":")).digest("hex");
|
|
1383
|
+
}
|
|
1384
|
+
const compiled = compileContracts(bundleData, {
|
|
1385
|
+
customOperators: options.customOperators ?? null,
|
|
1386
|
+
customSelectors: options.customSelectors ?? null
|
|
1387
|
+
});
|
|
1388
|
+
const guard = _buildGuard(compiled, policyVersion, options);
|
|
1389
|
+
if (options.returnReport) {
|
|
1390
|
+
return [guard, report];
|
|
1391
|
+
}
|
|
1392
|
+
return guard;
|
|
1393
|
+
}
|
|
1394
|
+
function fromYamlString(content, options = {}) {
|
|
1395
|
+
const [bundleData, bundleHash] = loadBundleString(content);
|
|
1396
|
+
const policyVersion = bundleHash.hex;
|
|
1397
|
+
const compiled = compileContracts(bundleData, {
|
|
1398
|
+
customOperators: options.customOperators ?? null,
|
|
1399
|
+
customSelectors: options.customSelectors ?? null
|
|
1400
|
+
});
|
|
1401
|
+
return _buildGuard(compiled, policyVersion, options);
|
|
1402
|
+
}
|
|
1403
|
+
function reload(guard, yamlContent, options = {}) {
|
|
1404
|
+
const [bundleData, bundleHash] = loadBundleString(yamlContent);
|
|
1405
|
+
const compiled = compileContracts(bundleData, {
|
|
1406
|
+
customOperators: options.customOperators ?? null,
|
|
1407
|
+
customSelectors: options.customSelectors ?? null
|
|
1408
|
+
});
|
|
1409
|
+
const allContracts = [
|
|
1410
|
+
...compiled.preconditions,
|
|
1411
|
+
...compiled.postconditions,
|
|
1412
|
+
...compiled.sessionContracts,
|
|
1413
|
+
...compiled.sandboxContracts
|
|
1414
|
+
];
|
|
1415
|
+
const temp = new Edictum({
|
|
1416
|
+
contracts: allContracts,
|
|
1417
|
+
limits: compiled.limits,
|
|
1418
|
+
policyVersion: bundleHash.hex
|
|
1419
|
+
});
|
|
1420
|
+
guard._replaceState(temp._getState());
|
|
1421
|
+
}
|
|
1422
|
+
function _buildGuard(compiled, policyVersion, options) {
|
|
1423
|
+
const effectiveMode = options.mode ?? compiled.defaultMode;
|
|
1424
|
+
const allContracts = [
|
|
1425
|
+
...compiled.preconditions,
|
|
1426
|
+
...compiled.postconditions,
|
|
1427
|
+
...compiled.sessionContracts,
|
|
1428
|
+
...compiled.sandboxContracts
|
|
1429
|
+
];
|
|
1430
|
+
const mergedTools = {};
|
|
1431
|
+
for (const [name, cfg] of Object.entries(compiled.tools)) {
|
|
1432
|
+
mergedTools[name] = cfg;
|
|
1433
|
+
}
|
|
1434
|
+
if (options.tools) {
|
|
1435
|
+
for (const [name, cfg] of Object.entries(options.tools)) {
|
|
1436
|
+
mergedTools[name] = cfg;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
return new Edictum({
|
|
1440
|
+
environment: options.environment ?? "production",
|
|
1441
|
+
mode: effectiveMode,
|
|
1442
|
+
limits: compiled.limits,
|
|
1443
|
+
tools: Object.keys(mergedTools).length > 0 ? mergedTools : void 0,
|
|
1444
|
+
contracts: allContracts,
|
|
1445
|
+
auditSink: options.auditSink,
|
|
1446
|
+
redaction: options.redaction,
|
|
1447
|
+
backend: options.backend,
|
|
1448
|
+
policyVersion,
|
|
1449
|
+
onDeny: options.onDeny,
|
|
1450
|
+
onAllow: options.onAllow,
|
|
1451
|
+
successCheck: options.successCheck,
|
|
1452
|
+
principal: options.principal,
|
|
1453
|
+
principalResolver: options.principalResolver,
|
|
1454
|
+
approvalBackend: options.approvalBackend
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/guard.ts
|
|
1459
|
+
function isSessionContract(c) {
|
|
1460
|
+
return !("tool" in c);
|
|
1461
|
+
}
|
|
1462
|
+
var Edictum = class _Edictum {
|
|
1463
|
+
environment;
|
|
1464
|
+
mode;
|
|
1465
|
+
backend;
|
|
1466
|
+
redaction;
|
|
1467
|
+
toolRegistry;
|
|
1468
|
+
auditSink;
|
|
1469
|
+
_localSink;
|
|
1470
|
+
_state;
|
|
1471
|
+
_beforeHooks;
|
|
1472
|
+
_afterHooks;
|
|
1473
|
+
_sessionId;
|
|
1474
|
+
// Callbacks and resolution — not private because _runner.ts needs access
|
|
1475
|
+
// (Python's _runner.py accesses self._on_deny etc. directly)
|
|
1476
|
+
/** @internal */
|
|
1477
|
+
_onDeny;
|
|
1478
|
+
/** @internal */
|
|
1479
|
+
_onAllow;
|
|
1480
|
+
/** @internal */
|
|
1481
|
+
_successCheck;
|
|
1482
|
+
_principal;
|
|
1483
|
+
_principalResolver;
|
|
1484
|
+
/** @internal */
|
|
1485
|
+
_approvalBackend;
|
|
1486
|
+
constructor(options = {}) {
|
|
1487
|
+
this.environment = options.environment ?? "production";
|
|
1488
|
+
this.mode = options.mode ?? "enforce";
|
|
1489
|
+
this.backend = options.backend ?? new MemoryBackend();
|
|
1490
|
+
this.redaction = options.redaction ?? new RedactionPolicy();
|
|
1491
|
+
this._onDeny = options.onDeny ?? null;
|
|
1492
|
+
this._onAllow = options.onAllow ?? null;
|
|
1493
|
+
this._successCheck = options.successCheck ?? null;
|
|
1494
|
+
this._principal = options.principal ?? null;
|
|
1495
|
+
this._principalResolver = options.principalResolver ?? null;
|
|
1496
|
+
this._approvalBackend = options.approvalBackend ?? null;
|
|
1497
|
+
this._localSink = new CollectingAuditSink();
|
|
1498
|
+
if (Array.isArray(options.auditSink)) {
|
|
1499
|
+
this.auditSink = new CompositeSink([
|
|
1500
|
+
this._localSink,
|
|
1501
|
+
...options.auditSink
|
|
1502
|
+
]);
|
|
1503
|
+
} else if (options.auditSink != null) {
|
|
1504
|
+
this.auditSink = new CompositeSink([
|
|
1505
|
+
this._localSink,
|
|
1506
|
+
options.auditSink
|
|
1507
|
+
]);
|
|
1508
|
+
} else {
|
|
1509
|
+
this.auditSink = this._localSink;
|
|
1510
|
+
}
|
|
1511
|
+
this.toolRegistry = new ToolRegistry();
|
|
1512
|
+
if (options.tools) {
|
|
1513
|
+
for (const [name, config] of Object.entries(options.tools)) {
|
|
1514
|
+
this.toolRegistry.register(
|
|
1515
|
+
name,
|
|
1516
|
+
config.side_effect ?? SideEffect.IRREVERSIBLE,
|
|
1517
|
+
config.idempotent ?? false
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
this._state = _Edictum._classifyContracts(
|
|
1522
|
+
options.contracts ?? [],
|
|
1523
|
+
options.limits ?? DEFAULT_LIMITS,
|
|
1524
|
+
options.policyVersion ?? null
|
|
1525
|
+
);
|
|
1526
|
+
this._beforeHooks = [];
|
|
1527
|
+
this._afterHooks = [];
|
|
1528
|
+
for (const item of options.hooks ?? []) {
|
|
1529
|
+
this._registerHook(item);
|
|
1530
|
+
}
|
|
1531
|
+
this._sessionId = randomUUID();
|
|
1532
|
+
}
|
|
1533
|
+
// -----------------------------------------------------------------------
|
|
1534
|
+
// Properties
|
|
1535
|
+
// -----------------------------------------------------------------------
|
|
1536
|
+
/** The local in-memory audit event collector. Always present. */
|
|
1537
|
+
get localSink() {
|
|
1538
|
+
return this._localSink;
|
|
1539
|
+
}
|
|
1540
|
+
/** Operation limits for the current contract set. */
|
|
1541
|
+
get limits() {
|
|
1542
|
+
return this._state.limits;
|
|
1543
|
+
}
|
|
1544
|
+
/** Update operation limits (replaces compiled state atomically). */
|
|
1545
|
+
set limits(value) {
|
|
1546
|
+
this._state = createCompiledState({ ...this._state, limits: value });
|
|
1547
|
+
}
|
|
1548
|
+
/** SHA256 hash identifying the active contract bundle. */
|
|
1549
|
+
get policyVersion() {
|
|
1550
|
+
return this._state.policyVersion;
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Replace the compiled state atomically.
|
|
1554
|
+
*
|
|
1555
|
+
* @internal — used by factory.ts reload(). Not part of the public API.
|
|
1556
|
+
*/
|
|
1557
|
+
_replaceState(newState) {
|
|
1558
|
+
this._state = newState;
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Read the current compiled state.
|
|
1562
|
+
*
|
|
1563
|
+
* @internal — used by factory.ts reload(). Not part of the public API.
|
|
1564
|
+
*/
|
|
1565
|
+
_getState() {
|
|
1566
|
+
return this._state;
|
|
1567
|
+
}
|
|
1568
|
+
/** Update policy version (replaces compiled state atomically). */
|
|
1569
|
+
set policyVersion(value) {
|
|
1570
|
+
this._state = createCompiledState({
|
|
1571
|
+
...this._state,
|
|
1572
|
+
policyVersion: value
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
/** The persistent session ID for this guard instance. */
|
|
1576
|
+
get sessionId() {
|
|
1577
|
+
return this._sessionId;
|
|
1578
|
+
}
|
|
1579
|
+
// -----------------------------------------------------------------------
|
|
1580
|
+
// Principal
|
|
1581
|
+
// -----------------------------------------------------------------------
|
|
1582
|
+
/** Update the principal used for subsequent tool calls. */
|
|
1583
|
+
setPrincipal(principal) {
|
|
1584
|
+
this._principal = principal;
|
|
1585
|
+
}
|
|
1586
|
+
/** Resolve the principal for a tool call. */
|
|
1587
|
+
_resolvePrincipal(toolName, toolInput) {
|
|
1588
|
+
if (this._principalResolver != null) {
|
|
1589
|
+
return this._principalResolver(toolName, toolInput);
|
|
1590
|
+
}
|
|
1591
|
+
return this._principal;
|
|
1592
|
+
}
|
|
1593
|
+
// -----------------------------------------------------------------------
|
|
1594
|
+
// Hooks
|
|
1595
|
+
// -----------------------------------------------------------------------
|
|
1596
|
+
_registerHook(item) {
|
|
1597
|
+
if (item.phase === "before") {
|
|
1598
|
+
this._beforeHooks.push(item);
|
|
1599
|
+
} else {
|
|
1600
|
+
this._afterHooks.push(item);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
getHooks(phase, envelope) {
|
|
1604
|
+
const hooks = phase === "before" ? this._beforeHooks : this._afterHooks;
|
|
1605
|
+
return hooks.filter(
|
|
1606
|
+
(h) => h.tool === "*" || fnmatch(envelope.toolName, h.tool)
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
// -----------------------------------------------------------------------
|
|
1610
|
+
// Contract accessors -- enforce mode
|
|
1611
|
+
// -----------------------------------------------------------------------
|
|
1612
|
+
getPreconditions(envelope) {
|
|
1613
|
+
return _Edictum._filterByTool(
|
|
1614
|
+
this._state.preconditions,
|
|
1615
|
+
envelope
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
getPostconditions(envelope) {
|
|
1619
|
+
return _Edictum._filterByTool(
|
|
1620
|
+
this._state.postconditions,
|
|
1621
|
+
envelope
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1624
|
+
getSessionContracts() {
|
|
1625
|
+
return [...this._state.sessionContracts];
|
|
1626
|
+
}
|
|
1627
|
+
getSandboxContracts(envelope) {
|
|
1628
|
+
return _Edictum._filterSandbox(
|
|
1629
|
+
this._state.sandboxContracts,
|
|
1630
|
+
envelope
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
// -----------------------------------------------------------------------
|
|
1634
|
+
// Contract accessors -- observe mode
|
|
1635
|
+
// -----------------------------------------------------------------------
|
|
1636
|
+
getObservePreconditions(envelope) {
|
|
1637
|
+
return _Edictum._filterByTool(
|
|
1638
|
+
this._state.observePreconditions,
|
|
1639
|
+
envelope
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
getObservePostconditions(envelope) {
|
|
1643
|
+
return _Edictum._filterByTool(
|
|
1644
|
+
this._state.observePostconditions,
|
|
1645
|
+
envelope
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
getObserveSessionContracts() {
|
|
1649
|
+
return [...this._state.observeSessionContracts];
|
|
1650
|
+
}
|
|
1651
|
+
getObserveSandboxContracts(envelope) {
|
|
1652
|
+
return _Edictum._filterSandbox(
|
|
1653
|
+
this._state.observeSandboxContracts,
|
|
1654
|
+
envelope
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
// -----------------------------------------------------------------------
|
|
1658
|
+
// Private helpers
|
|
1659
|
+
// -----------------------------------------------------------------------
|
|
1660
|
+
/**
|
|
1661
|
+
* Classify user-facing and internal contracts into enforce/observe lists.
|
|
1662
|
+
*
|
|
1663
|
+
* User-facing contracts (Precondition, Postcondition, SessionContract)
|
|
1664
|
+
* are converted to internal representations. Internal contracts (from
|
|
1665
|
+
* YAML compiler) carry _edictum_* metadata and are classified by their
|
|
1666
|
+
* _edictum_observe flag (Python uses _edictum_shadow — wire-format parity).
|
|
1667
|
+
*/
|
|
1668
|
+
static _classifyContracts(contracts, limits, policyVersion) {
|
|
1669
|
+
const pre = [];
|
|
1670
|
+
const post = [];
|
|
1671
|
+
const session = [];
|
|
1672
|
+
const sandbox = [];
|
|
1673
|
+
const oPre = [];
|
|
1674
|
+
const oPost = [];
|
|
1675
|
+
const oSession = [];
|
|
1676
|
+
const oSandbox = [];
|
|
1677
|
+
for (const item of contracts) {
|
|
1678
|
+
const raw = item;
|
|
1679
|
+
const edictumType = raw._edictum_type;
|
|
1680
|
+
const isObserve = raw._edictum_observe ?? raw._edictum_shadow ?? false;
|
|
1681
|
+
if (edictumType != null) {
|
|
1682
|
+
_Edictum._classifyInternal(
|
|
1683
|
+
raw,
|
|
1684
|
+
edictumType,
|
|
1685
|
+
isObserve,
|
|
1686
|
+
{ pre, post, session, sandbox, oPre, oPost, oSession, oSandbox }
|
|
1687
|
+
);
|
|
1688
|
+
} else if (isSessionContract(item)) {
|
|
1689
|
+
const name = raw.name ?? "anonymous";
|
|
1690
|
+
session.push({
|
|
1691
|
+
type: "session_contract",
|
|
1692
|
+
name,
|
|
1693
|
+
check: item.check
|
|
1694
|
+
});
|
|
1695
|
+
} else if ("tool" in item && item.contractType === "post") {
|
|
1696
|
+
const postItem = item;
|
|
1697
|
+
const name = raw.name ?? "anonymous";
|
|
1698
|
+
post.push({
|
|
1699
|
+
type: "postcondition",
|
|
1700
|
+
name,
|
|
1701
|
+
tool: postItem.tool,
|
|
1702
|
+
check: postItem.check,
|
|
1703
|
+
when: postItem.when
|
|
1704
|
+
});
|
|
1705
|
+
} else if ("tool" in item) {
|
|
1706
|
+
const ct = raw.contractType;
|
|
1707
|
+
if (ct != null && ct !== "pre") {
|
|
1708
|
+
throw new EdictumConfigError(
|
|
1709
|
+
`Contract with tool "${item.tool}" has unknown contractType "${String(ct)}". Expected "pre" or omitted for Precondition, "post" for Postcondition.`
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
if (ct == null && item.check.length >= 2) {
|
|
1713
|
+
throw new EdictumConfigError(
|
|
1714
|
+
`Contract with tool "${item.tool}" has a check function with ${item.check.length} parameters (looks like a Postcondition) but is missing contractType: "post". Add it to prevent misclassification.`
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
const preItem = item;
|
|
1718
|
+
const name = raw.name ?? "anonymous";
|
|
1719
|
+
pre.push({
|
|
1720
|
+
type: "precondition",
|
|
1721
|
+
name,
|
|
1722
|
+
tool: preItem.tool,
|
|
1723
|
+
check: preItem.check,
|
|
1724
|
+
when: preItem.when
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
return createCompiledState({
|
|
1729
|
+
preconditions: pre,
|
|
1730
|
+
postconditions: post,
|
|
1731
|
+
sessionContracts: session,
|
|
1732
|
+
sandboxContracts: sandbox,
|
|
1733
|
+
observePreconditions: oPre,
|
|
1734
|
+
observePostconditions: oPost,
|
|
1735
|
+
observeSessionContracts: oSession,
|
|
1736
|
+
observeSandboxContracts: oSandbox,
|
|
1737
|
+
limits,
|
|
1738
|
+
policyVersion
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
/** Route an internal contract to the appropriate enforce/observe list. */
|
|
1742
|
+
static _classifyInternal(raw, edictumType, isObserve, lists) {
|
|
1743
|
+
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 };
|
|
1744
|
+
if (edictumType === "precondition") target.pre.push(raw);
|
|
1745
|
+
else if (edictumType === "postcondition") target.post.push(raw);
|
|
1746
|
+
else if (edictumType === "session_contract") target.session.push(raw);
|
|
1747
|
+
else if (edictumType === "sandbox") target.sandbox.push(raw);
|
|
1748
|
+
else {
|
|
1749
|
+
throw new EdictumConfigError(
|
|
1750
|
+
`Unknown _edictum_type "${edictumType}". Expected "precondition", "postcondition", "session_contract", or "sandbox".`
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
/** Filter contracts by tool pattern and optional `when` guard. */
|
|
1755
|
+
static _filterByTool(contracts, envelope) {
|
|
1756
|
+
const result = [];
|
|
1757
|
+
for (const p of contracts) {
|
|
1758
|
+
const tool = p.tool ?? "*";
|
|
1759
|
+
const when = p.when ?? null;
|
|
1760
|
+
if (tool !== "*" && !fnmatch(envelope.toolName, tool)) {
|
|
1761
|
+
continue;
|
|
1762
|
+
}
|
|
1763
|
+
if (when != null) {
|
|
1764
|
+
try {
|
|
1765
|
+
if (!when(envelope)) continue;
|
|
1766
|
+
} catch {
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
result.push(p);
|
|
1770
|
+
}
|
|
1771
|
+
return result;
|
|
1772
|
+
}
|
|
1773
|
+
/** Filter sandbox contracts by tool patterns array. */
|
|
1774
|
+
static _filterSandbox(contracts, envelope) {
|
|
1775
|
+
const result = [];
|
|
1776
|
+
for (const s of contracts) {
|
|
1777
|
+
const tools = s.tools ?? ["*"];
|
|
1778
|
+
if (tools.some((p) => fnmatch(envelope.toolName, p))) {
|
|
1779
|
+
result.push(s);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
return result;
|
|
1783
|
+
}
|
|
1784
|
+
// -----------------------------------------------------------------------
|
|
1785
|
+
// Delegated methods — run, evaluate, evaluateBatch
|
|
1786
|
+
// -----------------------------------------------------------------------
|
|
1787
|
+
/** Execute a tool call with full governance pipeline. */
|
|
1788
|
+
async run(toolName, args, toolCallable, options) {
|
|
1789
|
+
const { run: run2 } = await import("./runner-ASI4JIW2.mjs");
|
|
1790
|
+
return run2(this, toolName, args, toolCallable, options);
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Dry-run evaluation of a tool call against all matching contracts.
|
|
1794
|
+
*
|
|
1795
|
+
* Never executes the tool. Evaluates exhaustively (no short-circuit).
|
|
1796
|
+
* Session contracts are skipped.
|
|
1797
|
+
*/
|
|
1798
|
+
evaluate(toolName, args, options) {
|
|
1799
|
+
return import("./dry-run-54PYIM6T.mjs").then(
|
|
1800
|
+
({ evaluate }) => evaluate(this, toolName, args, options)
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
/** Evaluate a batch of tool calls. Thin wrapper over evaluate(). */
|
|
1804
|
+
evaluateBatch(calls) {
|
|
1805
|
+
return import("./dry-run-54PYIM6T.mjs").then(
|
|
1806
|
+
({ evaluateBatch }) => evaluateBatch(this, calls)
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
static fromYaml(...args) {
|
|
1810
|
+
return fromYaml(...args);
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Create an Edictum instance from a YAML string or Uint8Array.
|
|
1814
|
+
*/
|
|
1815
|
+
static fromYamlString(content, options) {
|
|
1816
|
+
return fromYamlString(content, options);
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Atomically replace this guard's contracts from a YAML string.
|
|
1820
|
+
*
|
|
1821
|
+
* Pass customOperators/customSelectors if the new YAML uses custom
|
|
1822
|
+
* operators or selectors that were passed to fromYaml/fromYamlString.
|
|
1823
|
+
*/
|
|
1824
|
+
reload(yamlContent, options) {
|
|
1825
|
+
reload(this, yamlContent, options);
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
|
|
1829
|
+
// src/index.ts
|
|
1830
|
+
var VERSION = "0.1.0";
|
|
1831
|
+
export {
|
|
1832
|
+
ApprovalStatus,
|
|
1833
|
+
AuditAction,
|
|
1834
|
+
BUILTIN_OPERATOR_NAMES,
|
|
1835
|
+
BUILTIN_SELECTOR_PREFIXES,
|
|
1836
|
+
BashClassifier,
|
|
1837
|
+
CollectingAuditSink,
|
|
1838
|
+
CompositeSink,
|
|
1839
|
+
DEFAULT_LIMITS,
|
|
1840
|
+
Edictum,
|
|
1841
|
+
EdictumConfigError,
|
|
1842
|
+
EdictumDenied,
|
|
1843
|
+
EdictumToolError,
|
|
1844
|
+
FileAuditSink,
|
|
1845
|
+
GovernancePipeline,
|
|
1846
|
+
HookDecision,
|
|
1847
|
+
HookResult,
|
|
1848
|
+
LocalApprovalBackend,
|
|
1849
|
+
MAX_BUNDLE_SIZE,
|
|
1850
|
+
MAX_REGEX_INPUT,
|
|
1851
|
+
MarkEvictedError,
|
|
1852
|
+
MemoryBackend,
|
|
1853
|
+
PolicyError,
|
|
1854
|
+
RedactionPolicy,
|
|
1855
|
+
Session,
|
|
1856
|
+
SideEffect,
|
|
1857
|
+
StdoutAuditSink,
|
|
1858
|
+
ToolRegistry,
|
|
1859
|
+
VERSION,
|
|
1860
|
+
Verdict,
|
|
1861
|
+
_validateToolName,
|
|
1862
|
+
buildFindings,
|
|
1863
|
+
classifyFinding,
|
|
1864
|
+
compileContracts,
|
|
1865
|
+
composeBundles,
|
|
1866
|
+
computeHash,
|
|
1867
|
+
createAuditEvent,
|
|
1868
|
+
createCompiledState,
|
|
1869
|
+
createContractResult,
|
|
1870
|
+
createEnvelope,
|
|
1871
|
+
createEvaluationResult,
|
|
1872
|
+
createFinding,
|
|
1873
|
+
createPostCallResult,
|
|
1874
|
+
createPostDecision,
|
|
1875
|
+
createPreDecision,
|
|
1876
|
+
createPrincipal,
|
|
1877
|
+
deepFreeze,
|
|
1878
|
+
defaultSuccessCheck,
|
|
1879
|
+
evaluateExpression,
|
|
1880
|
+
expandMessage,
|
|
1881
|
+
fnmatch,
|
|
1882
|
+
fromYaml,
|
|
1883
|
+
fromYamlString,
|
|
1884
|
+
loadBundle,
|
|
1885
|
+
loadBundleString,
|
|
1886
|
+
reload,
|
|
1887
|
+
run,
|
|
1888
|
+
validateOperators
|
|
1889
|
+
};
|
|
1890
|
+
//# sourceMappingURL=index.mjs.map
|