@zanii/blackbox 0.2.0 → 0.4.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.
Files changed (94) hide show
  1. package/README.md +26 -1
  2. package/dist/a2a/index.d.ts +77 -0
  3. package/dist/a2a/index.js +305 -0
  4. package/dist/agents/index.d.ts +8 -0
  5. package/dist/analysis/accuracy.d.ts +24 -0
  6. package/dist/analysis/accuracy.js +45 -0
  7. package/dist/analysis/credential.d.ts +101 -0
  8. package/dist/analysis/credential.js +142 -0
  9. package/dist/analysis/faults.js +115 -0
  10. package/dist/analysis/grounding.d.ts +122 -0
  11. package/dist/analysis/grounding.js +445 -0
  12. package/dist/analysis/hallucination.d.ts +32 -0
  13. package/dist/analysis/hallucination.js +357 -0
  14. package/dist/analysis/index.d.ts +23 -0
  15. package/dist/analysis/index.js +93 -0
  16. package/dist/analysis/memory.d.ts +8 -0
  17. package/dist/analysis/memory.js +35 -8
  18. package/dist/analysis/reference.d.ts +49 -0
  19. package/dist/analysis/reference.js +164 -0
  20. package/dist/analysis/taxonomy.d.ts +18 -0
  21. package/dist/analysis/taxonomy.js +66 -0
  22. package/dist/approvals/index.d.ts +23 -0
  23. package/dist/approvals/index.js +48 -0
  24. package/dist/archive/index.d.ts +39 -0
  25. package/dist/archive/index.js +96 -0
  26. package/dist/archive/parquet.d.ts +2 -0
  27. package/dist/archive/parquet.js +185 -0
  28. package/dist/badge/index.d.ts +16 -0
  29. package/dist/badge/index.js +48 -0
  30. package/dist/bom/index.d.ts +14 -0
  31. package/dist/bom/index.js +152 -0
  32. package/dist/cli.js +114 -10
  33. package/dist/compliance/art12.d.ts +35 -0
  34. package/dist/compliance/art12.js +190 -0
  35. package/dist/compliance/index.d.ts +36 -2
  36. package/dist/compliance/index.js +78 -11
  37. package/dist/compliance/zanii.d.ts +29 -0
  38. package/dist/compliance/zanii.js +84 -0
  39. package/dist/constitution/index.d.ts +57 -0
  40. package/dist/constitution/index.js +131 -0
  41. package/dist/cv/index.d.ts +39 -0
  42. package/dist/cv/index.js +108 -0
  43. package/dist/disclosure/index.d.ts +31 -0
  44. package/dist/disclosure/index.js +113 -0
  45. package/dist/encryption/index.d.ts +9 -0
  46. package/dist/encryption/index.js +31 -0
  47. package/dist/evidence/index.d.ts +60 -0
  48. package/dist/evidence/index.js +151 -0
  49. package/dist/federation/index.d.ts +35 -0
  50. package/dist/federation/index.js +102 -0
  51. package/dist/finance/index.d.ts +126 -0
  52. package/dist/finance/index.js +320 -0
  53. package/dist/fleet/index.js +9 -0
  54. package/dist/gov/index.d.ts +108 -0
  55. package/dist/gov/index.js +225 -0
  56. package/dist/health/index.d.ts +120 -0
  57. package/dist/health/index.js +233 -0
  58. package/dist/index.d.ts +34 -5
  59. package/dist/index.js +34 -5
  60. package/dist/memory/index.d.ts +36 -0
  61. package/dist/memory/index.js +85 -0
  62. package/dist/occurrence/index.d.ts +11 -0
  63. package/dist/occurrence/index.js +18 -0
  64. package/dist/ocsf/index.d.ts +1 -1
  65. package/dist/ocsf/index.js +36 -3
  66. package/dist/otlp/index.d.ts +8 -1
  67. package/dist/otlp/index.js +258 -1
  68. package/dist/packs/index.js +44 -4
  69. package/dist/policy/delta.js +7 -1
  70. package/dist/policy/index.d.ts +40 -6
  71. package/dist/policy/index.js +186 -8
  72. package/dist/policy/zanii.d.ts +31 -0
  73. package/dist/policy/zanii.js +87 -0
  74. package/dist/pq/index.d.ts +23 -0
  75. package/dist/pq/index.js +104 -0
  76. package/dist/search/index.d.ts +23 -0
  77. package/dist/search/index.js +69 -0
  78. package/dist/session/index.d.ts +107 -1
  79. package/dist/session/index.js +189 -11
  80. package/dist/sla/index.d.ts +61 -0
  81. package/dist/sla/index.js +197 -0
  82. package/dist/succession/index.d.ts +50 -0
  83. package/dist/succession/index.js +123 -0
  84. package/dist/timestamp/index.d.ts +24 -0
  85. package/dist/timestamp/index.js +274 -0
  86. package/dist/tokens/index.d.ts +6 -0
  87. package/dist/tokens/index.js +46 -0
  88. package/dist/transparency/index.d.ts +188 -0
  89. package/dist/transparency/index.js +712 -0
  90. package/dist/version.d.ts +1 -1
  91. package/dist/version.js +1 -1
  92. package/dist/walls/index.d.ts +31 -0
  93. package/dist/walls/index.js +119 -0
  94. package/package.json +1 -1
@@ -1,12 +1,48 @@
1
1
  // A session as OpenTelemetry traces (spec/otlp.md): OTLP/JSON with the GenAI semantic conventions.
2
2
  // Deterministic (ids derived from the session), so both SDKs produce the same bytes. Mirrors otlp.py.
3
3
  import { createHash } from "node:crypto";
4
+ import { taxonomyOf } from "../analysis/taxonomy.js";
4
5
  import { tokensOf } from "../cost/index.js";
5
6
  const hex = (s, n) => createHash("sha256").update(s).digest("hex").slice(0, n);
6
7
  const nanos = (ts) => `${Date.parse(ts)}000000`;
7
8
  const str = (key, v) => typeof v === "string" ? [{ key, value: { stringValue: v } }] : [];
8
9
  const int = (key, v) => typeof v === "number" && Number.isSafeInteger(v) ? [{ key, value: { intValue: String(v) } }] : [];
9
- export function toOtlp(lines) {
10
+ /**
11
+ * spec/otlp.md: a session as OTLP/JSON. `semconv: 2` (the default) follows the GenAI semantic
12
+ * conventions as of v1.42 (`gen_ai.provider.name`, `invoke_agent`, MCP `tools/call`); `semconv: 1`
13
+ * is the first version's output, pinned by spec/vectors/otlp.json.
14
+ */
15
+ export function toOtlp(lines, options = {}) {
16
+ return linkCallers(options.semconv === 1 ? toOtlpV1(lines) : toOtlpV2(lines), lines);
17
+ }
18
+ const TRACEPARENT = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
19
+ /** spec/otlp.md §3: a call that carried the caller's W3C `traceparent` links to the caller's span. */
20
+ function linkCallers(out, lines) {
21
+ const links = new Map();
22
+ let session = "";
23
+ for (const l of lines) {
24
+ const e = JSON.parse(l);
25
+ session ||= e.session_id;
26
+ const tp = e.meta.headers?.traceparent;
27
+ const m = typeof tp === "string" ? TRACEPARENT.exec(tp) : null;
28
+ if (m && !/^0+$/.test(m[1]) && !/^0+$/.test(m[2]))
29
+ links.set(hex(`${session}:${e.seq}`, 16), {
30
+ traceId: m[1],
31
+ spanId: m[2],
32
+ });
33
+ }
34
+ if (links.size === 0)
35
+ return out;
36
+ for (const rs of out.resourceSpans)
37
+ for (const ss of rs.scopeSpans)
38
+ for (const sp of ss.spans) {
39
+ const link = links.get(sp.spanId);
40
+ if (link)
41
+ sp.links = [link];
42
+ }
43
+ return out;
44
+ }
45
+ function toOtlpV1(lines) {
10
46
  const events = lines.map((l) => JSON.parse(l));
11
47
  const first = events[0];
12
48
  if (!first)
@@ -141,3 +177,224 @@ export function toOtlp(lines) {
141
177
  ],
142
178
  };
143
179
  }
180
+ // ---------------------------------------------------------------- semconv 2 (GenAI v1.42)
181
+ /** Provider kinds as `gen_ai.provider.name` well-known values; any other provider id as it is. */
182
+ const PROVIDER_NAMES = {
183
+ anthropic: "anthropic",
184
+ openai: "openai",
185
+ "azure-openai": "azure.ai.openai",
186
+ gemini: "gcp.gemini",
187
+ vertex: "gcp.vertex_ai",
188
+ bedrock: "aws.bedrock",
189
+ };
190
+ const list = (key, vs) => vs.length
191
+ ? [{ key, value: { arrayValue: { values: vs.map((v) => ({ stringValue: v })) } } }]
192
+ : [];
193
+ const strs = (key, v) => typeof v === "string" ? [{ key, value: { arrayValue: { values: [{ stringValue: v }] } } }] : [];
194
+ const failure = (type, message) => ({
195
+ attrs: str("error.type", type),
196
+ status: { status: { code: 2, message } },
197
+ });
198
+ function toOtlpV2(lines) {
199
+ const events = lines.map((l) => JSON.parse(l));
200
+ const first = events[0];
201
+ if (!first)
202
+ return { resourceSpans: [] };
203
+ const session = first.session_id;
204
+ const traceId = hex(session, 32);
205
+ const spanId = (seq) => hex(`${session}:${seq}`, 16);
206
+ const root = spanId(0);
207
+ const last = events[events.length - 1];
208
+ const label = typeof first.meta.label === "string" ? first.meta.label : undefined;
209
+ const conversation = str("gen_ai.conversation.id", session);
210
+ const spans = [];
211
+ // The session is the agent's invocation, with the findings as its events.
212
+ spans.push({
213
+ traceId,
214
+ spanId: root,
215
+ name: label ? `invoke_agent ${label}` : "invoke_agent",
216
+ kind: 1,
217
+ startTimeUnixNano: nanos(first.ts),
218
+ endTimeUnixNano: nanos(last.ts),
219
+ attributes: [
220
+ ...str("gen_ai.operation.name", "invoke_agent"),
221
+ ...str("gen_ai.agent.name", label),
222
+ ...conversation,
223
+ ...str("blackbox.session_id", session),
224
+ ...str("blackbox.mode", first.meta.mode),
225
+ ...str("blackbox.tenant", first.meta.tenant),
226
+ ...int("blackbox.events", events.length),
227
+ ],
228
+ events: events
229
+ .filter((e) => e.kind === "finding")
230
+ .map((e) => ({
231
+ timeUnixNano: nanos(e.ts),
232
+ name: String(e.meta.code),
233
+ attributes: [
234
+ ...str("blackbox.severity", e.meta.severity),
235
+ ...str("blackbox.fault", e.meta.fault),
236
+ ...list("blackbox.owasp_asi", taxonomyOf(String(e.meta.code)).owasp.map((o) => o.id)),
237
+ ...list("blackbox.mitre_atlas", taxonomyOf(String(e.meta.code)).atlas.map((a) => a.id)),
238
+ ...int("blackbox.seq", e.seq),
239
+ ],
240
+ })),
241
+ });
242
+ // Model calls: CLIENT spans named `chat {model}`.
243
+ const ends = new Map();
244
+ for (const e of events)
245
+ if ((e.kind === "llm.response" || e.kind === "llm.incomplete") &&
246
+ typeof e.meta.request_seq === "number")
247
+ ends.set(e.meta.request_seq, e);
248
+ for (const e of events.filter((x) => x.kind === "llm.request")) {
249
+ const end = ends.get(e.seq);
250
+ const usage = end?.meta.usage && typeof end.meta.usage === "object"
251
+ ? tokensOf(end.meta.usage)
252
+ : null;
253
+ const status = end?.meta.status;
254
+ const model = typeof end?.meta.model === "string" ? end.meta.model : undefined;
255
+ const provider = typeof e.meta.provider === "string" ? e.meta.provider : undefined;
256
+ const reason = String(end?.meta.reason ?? "incomplete");
257
+ const fail = !end
258
+ ? failure("incomplete", "no response recorded")
259
+ : end.kind === "llm.incomplete"
260
+ ? failure(reason, reason)
261
+ : typeof status === "number" && status >= 400
262
+ ? failure(String(status), `status ${status}`)
263
+ : null;
264
+ spans.push({
265
+ traceId,
266
+ spanId: spanId(e.seq),
267
+ parentSpanId: root,
268
+ name: model ? `chat ${model}` : "chat",
269
+ kind: 3,
270
+ startTimeUnixNano: nanos(e.ts),
271
+ endTimeUnixNano: nanos((end ?? e).ts),
272
+ attributes: [
273
+ ...str("gen_ai.operation.name", "chat"),
274
+ ...str("gen_ai.provider.name", provider ? (PROVIDER_NAMES[provider] ?? provider) : undefined),
275
+ ...str("gen_ai.response.model", model),
276
+ ...str("gen_ai.response.id", end?.meta.message_id ?? end?.meta.response_id),
277
+ ...strs("gen_ai.response.finish_reasons", end?.meta.stop_reason ?? end?.meta.finish_reason),
278
+ ...(usage
279
+ ? [
280
+ ...int("gen_ai.usage.input_tokens", usage.input + usage.cache_read + usage.cache_write),
281
+ ...int("gen_ai.usage.output_tokens", usage.output),
282
+ ...(usage.cache_read
283
+ ? int("gen_ai.usage.cache_read.input_tokens", usage.cache_read)
284
+ : []),
285
+ ...(usage.cache_write
286
+ ? int("gen_ai.usage.cache_write.input_tokens", usage.cache_write)
287
+ : []),
288
+ ]
289
+ : []),
290
+ ...conversation,
291
+ ...str("blackbox.provider", provider),
292
+ ...str("blackbox.region", e.meta.region),
293
+ ...int("http.response.status_code", status),
294
+ ...(fail ? fail.attrs : []),
295
+ ...int("blackbox.seq", e.seq),
296
+ ],
297
+ ...(fail ? fail.status : {}),
298
+ });
299
+ }
300
+ // MCP and broker calls, closed by their result (call_seq). MCP spans follow the MCP conventions.
301
+ const results = new Map();
302
+ for (const e of events)
303
+ if (e.kind === "tool.result" && typeof e.meta.call_seq === "number")
304
+ results.set(e.meta.call_seq, e);
305
+ for (const e of events.filter((x) => x.kind === "tool.call")) {
306
+ const end = results.get(e.seq);
307
+ const broker = e.meta.via === "broker";
308
+ const method = typeof e.meta.method === "string" ? e.meta.method : undefined;
309
+ const tool = typeof e.meta.tool === "string" ? e.meta.tool : undefined;
310
+ const isCall = broker || method === "tools/call";
311
+ const fail = e.meta.policy !== undefined
312
+ ? failure("policy_denied", "denied by policy")
313
+ : end?.meta.rpc_error !== undefined
314
+ ? failure(String(end.meta.rpc_error), "rpc error")
315
+ : end?.meta.is_error === true
316
+ ? failure("tool_error", "tool error")
317
+ : broker && typeof end?.meta.status === "number" && end.meta.status >= 400
318
+ ? failure(String(end.meta.status), `status ${end.meta.status}`)
319
+ : null;
320
+ const rpcId = e.meta.rpc_id;
321
+ spans.push({
322
+ traceId,
323
+ spanId: spanId(e.seq),
324
+ parentSpanId: root,
325
+ name: broker
326
+ ? `execute_tool ${tool ?? "api"}`
327
+ : `${method ?? "mcp"}${tool && isCall ? ` ${tool}` : ""}`,
328
+ kind: 3,
329
+ startTimeUnixNano: nanos(e.ts),
330
+ endTimeUnixNano: nanos((end ?? e).ts),
331
+ attributes: [
332
+ ...(isCall ? str("gen_ai.operation.name", "execute_tool") : []),
333
+ ...str("gen_ai.tool.name", tool),
334
+ ...(broker ? str("gen_ai.tool.type", "extension") : []),
335
+ ...(broker ? [] : str("mcp.method.name", method)),
336
+ ...(broker ? [] : str("mcp.protocol.version", e.meta.protocol_version)),
337
+ ...(typeof rpcId === "string" || typeof rpcId === "number"
338
+ ? str("jsonrpc.request.id", String(rpcId))
339
+ : []),
340
+ ...(broker ? str("http.request.method", e.meta.method) : []),
341
+ ...(broker ? str("url.path", e.meta.path) : []),
342
+ ...str("blackbox.mcp_server", e.meta.server),
343
+ ...int("rpc.response.status_code", end?.meta.rpc_error),
344
+ ...conversation,
345
+ ...(fail ? fail.attrs : []),
346
+ ...int("blackbox.seq", e.seq),
347
+ ],
348
+ ...(fail ? fail.status : {}),
349
+ });
350
+ }
351
+ // The agent's own tools (SDK events): INTERNAL spans, from a call to the next result of its name.
352
+ const open = new Map();
353
+ const sdk = (e, type) => e.kind === "sdk.event" && e.meta.type === type;
354
+ for (const e of events) {
355
+ const name = typeof e.meta.name === "string" ? e.meta.name : undefined;
356
+ if (!name)
357
+ continue;
358
+ if (sdk(e, "tool.call"))
359
+ open.set(name, [...(open.get(name) ?? []), e]);
360
+ if (!sdk(e, "tool.result"))
361
+ continue;
362
+ const call = open.get(name)?.shift();
363
+ spans.push({
364
+ traceId,
365
+ spanId: spanId(e.seq),
366
+ parentSpanId: root,
367
+ name: `execute_tool ${name}`,
368
+ kind: 1,
369
+ startTimeUnixNano: nanos((call ?? e).ts),
370
+ endTimeUnixNano: nanos(e.ts),
371
+ attributes: [
372
+ ...str("gen_ai.operation.name", "execute_tool"),
373
+ ...str("gen_ai.tool.name", name),
374
+ ...str("gen_ai.tool.type", "function"),
375
+ ...conversation,
376
+ ...str("blackbox.reported_by", "agent"),
377
+ ...int("blackbox.seq", e.seq),
378
+ ],
379
+ });
380
+ }
381
+ return {
382
+ resourceSpans: [
383
+ {
384
+ resource: {
385
+ attributes: [
386
+ ...str("service.name", "zanii-blackbox"),
387
+ ...str("blackbox.session_id", session),
388
+ ],
389
+ },
390
+ scopeSpans: [
391
+ {
392
+ scope: { name: "zanii-blackbox", version: "2" },
393
+ schemaUrl: "https://opentelemetry.io/schemas/1.44.0",
394
+ spans,
395
+ },
396
+ ],
397
+ },
398
+ ],
399
+ };
400
+ }
@@ -16,6 +16,13 @@ const FACTS = new Set([
16
16
  "resumes_with_note",
17
17
  "outcomes",
18
18
  "data_region",
19
+ // spec/compliance.md §1a (H6)
20
+ "model_responses",
21
+ "hallucination_findings",
22
+ "grounding_checks",
23
+ "judge_checks",
24
+ "hallucination_controls",
25
+ "detector_accuracy",
19
26
  ]);
20
27
  const FILLS = new Set(["record", "findings", "redactions", "operator"]);
21
28
  const PACK_ID = /^[a-z0-9-]{1,64}$/;
@@ -63,8 +70,8 @@ function checkPart(part, kind) {
63
70
  if (!isObj(fw) || !text(fw.title, 500))
64
71
  return `${at}.title must be 1-500 characters`;
65
72
  if (kind === "compliance") {
66
- if (!only(fw, ["title", "sections"]))
67
- return `${at} may only have title and sections`;
73
+ if (!only(fw, ["title", "sections", "ar"]))
74
+ return `${at} may only have title, sections and ar`;
68
75
  const bad = checkSections(fw.sections, 50, at, (s, sat) => {
69
76
  if (!only(s, ["id", "title", "requirement", "facts"]))
70
77
  return `${sat} may only have id, title, requirement, facts`;
@@ -79,10 +86,15 @@ function checkPart(part, kind) {
79
86
  });
80
87
  if (bad)
81
88
  return bad;
89
+ if (fw.ar !== undefined) {
90
+ const badAr = checkComplianceArabic(fw.ar, fw, `${at}.ar`);
91
+ if (badAr)
92
+ return badAr;
93
+ }
82
94
  }
83
95
  else {
84
- if (!only(fw, ["title", "authority", "deadlines", "sections", "ar"]))
85
- return `${at} may only have title, authority, deadlines, sections`;
96
+ if (!only(fw, ["title", "authority", "deadlines", "clock_hours", "sections", "ar"]))
97
+ return `${at} may only have title, authority, deadlines, clock_hours, sections`;
86
98
  if (!text(fw.authority, 500))
87
99
  return `${at}.authority must be 1-500 characters`;
88
100
  if (!Array.isArray(fw.deadlines) ||
@@ -90,6 +102,13 @@ function checkPart(part, kind) {
90
102
  fw.deadlines.length > 10 ||
91
103
  !fw.deadlines.every((d) => text(d, 500)))
92
104
  return `${at}.deadlines must hold 1-10 texts of 1-500 characters`;
105
+ // spec/packs.md §1: each deadline's hours after awareness, or null when it runs from another event
106
+ if (fw.clock_hours !== undefined &&
107
+ !(Array.isArray(fw.clock_hours) &&
108
+ fw.clock_hours.length === fw.deadlines.length &&
109
+ fw.clock_hours.every((h) => h === null ||
110
+ (Number.isSafeInteger(h) && h >= 1 && h <= 8760))))
111
+ return `${at}.clock_hours must give each deadline 1-8760 hours, or null`;
93
112
  const bad = checkSections(fw.sections, 30, at, (s, sat) => {
94
113
  if (!only(s, ["id", "title", "fill"]))
95
114
  return `${sat} may only have id, title, fill`;
@@ -108,6 +127,27 @@ function checkPart(part, kind) {
108
127
  }
109
128
  return undefined;
110
129
  }
130
+ /** spec/packs.md §1: a compliance framework's Arabic: its title, and each section's title and
131
+ * requirement, by id. */
132
+ function checkComplianceArabic(ar, fw, at) {
133
+ if (!isObj(ar) || !only(ar, ["title", "sections"]))
134
+ return `${at} must be {title, sections}`;
135
+ if (!text(ar.title, 500))
136
+ return `${at}.title must be 1-500 characters`;
137
+ const ids = fw.sections.map((s) => s.id);
138
+ const secs = ar.sections;
139
+ if (!isObj(secs) ||
140
+ Object.keys(secs).length !== ids.length ||
141
+ !ids.every((id) => {
142
+ const s = secs[id];
143
+ return (isObj(s) &&
144
+ only(s, ["title", "requirement"]) &&
145
+ text(s.title, 500) &&
146
+ text(s.requirement, 2000));
147
+ }))
148
+ return `${at}.sections must translate each section's title and requirement, by id`;
149
+ return undefined;
150
+ }
111
151
  /** spec/packs.md §1: an occurrence framework's Arabic: the same deadlines and sections, translated. */
112
152
  function checkArabic(ar, fw, at) {
113
153
  if (!isObj(ar) || !only(ar, ["title", "authority", "deadlines", "sections"]))
@@ -3,7 +3,13 @@
3
3
  // in doubt, it adds power. Mirrors policy/delta.py.
4
4
  /** How much a rule holds back: an enforced allow opens (0), an audit rule does nothing (1), a hold
5
5
  * (2) and a deny (3) close. */
6
- const rank = (r) => r.audit === true ? 1 : r.action === "allow" ? 0 : r.action === "require_approval" ? 2 : 3;
6
+ const rank = (r) => r.audit === true || r.action === "require_proof"
7
+ ? 1
8
+ : r.action === "allow"
9
+ ? 0
10
+ : r.action === "require_approval"
11
+ ? 2
12
+ : 3;
7
13
  const matcher = (r) => JSON.stringify([r.tool, r.args_match ?? null, r.ignore_case === true]);
8
14
  export function policyDelta(current, proposed) {
9
15
  const before = new Map(current.rules.map((r) => [r.id, r]));
@@ -6,12 +6,42 @@ export interface Rule {
6
6
  tool: string;
7
7
  args_match?: string;
8
8
  ignore_case?: boolean;
9
- /** spec/approvals.md: `require_approval` holds the call for a second person. */
10
- action: "deny" | "allow" | "require_approval";
9
+ /** spec/approvals.md: `require_approval` holds the call for a second person; spec/policy.md
10
+ * §9: `require_proof` lets it run, and its result must prove itself. */
11
+ action: "deny" | "allow" | "require_approval" | "require_proof";
12
+ /** spec/policy.md §9: with `require_proof`, the system's own id in the result (`refund_id`). */
13
+ field?: string;
11
14
  reason?: string;
12
15
  /** N3 (idea C5): record-only. Never decides a call; what it would do is recorded (POLICY_AUDIT). */
13
16
  audit?: boolean;
17
+ /** spec/policy.md §1: MCP tool annotations the call must have (from the server's `tools/list`). */
18
+ hints?: Partial<ToolHints>;
19
+ /** spec/policy.md §1: the session environments the rule holds in (a session's `environment`). */
20
+ environment?: string[];
14
21
  }
22
+ /** spec/api.md: a session's environment, e.g. `production`, `staging`, `dev`. */
23
+ export declare const ENVIRONMENT: RegExp;
24
+ /** MCP tool annotations as booleans, with the spec's defaults for what a server leaves out. */
25
+ export interface ToolHints {
26
+ read_only: boolean;
27
+ destructive: boolean;
28
+ idempotent: boolean;
29
+ open_world: boolean;
30
+ }
31
+ /**
32
+ * spec/policy.md §1: a tool's annotations (MCP `readOnlyHint`, `destructiveHint`, `idempotentHint`,
33
+ * `openWorldHint`) as hints. Missing ones take MCP's defaults: not read-only, destructive, not
34
+ * idempotent, open-world; destructive and idempotent only mean something for a tool that writes.
35
+ * An unknown tool gets the defaults too. Hints are the server's word, not proof.
36
+ */
37
+ export declare function mcpToolHints(annotations?: unknown): ToolHints;
38
+ /**
39
+ * spec/policy.md §1a: a tool definition's identity, `sha256:<hex>` of the canonical JSON of its
40
+ * name, title, description, schemas and annotations (the fields present). A changed description
41
+ * or schema changes it: what the agent was shown is no longer what was approved. `null` for
42
+ * something that isn't a named tool.
43
+ */
44
+ export declare function toolDefinitionHash(tool: unknown): string | null;
15
45
  export interface Policy {
16
46
  version: 1;
17
47
  rules: Rule[];
@@ -20,7 +50,7 @@ export interface Policy {
20
50
  }
21
51
  export interface Decision {
22
52
  rule: string;
23
- action: "deny" | "allow" | "require_approval";
53
+ action: "deny" | "allow" | "require_approval" | "require_proof";
24
54
  reason?: string;
25
55
  }
26
56
  interface Compiled {
@@ -31,11 +61,15 @@ interface Compiled {
31
61
  /** Parses a policy file; throws on anything invalid (the server refuses to start). */
32
62
  export declare function loadPolicy(bytes: Uint8Array): Policy;
33
63
  export declare function compilePolicy(policy: Policy): Compiled[];
34
- /** The first rule that matches this call, or null (allowed). `names`: the tool's names (an MCP tool has two). */
35
- export declare function decide(compiled: readonly Compiled[], names: readonly string[], args: unknown): Decision | null;
64
+ /** spec/api.md: the session's environment, from its `session.open` (the first line). */
65
+ export declare function environmentOf(lines: readonly string[]): string | undefined;
66
+ /** The first rule that matches this call, or null (allowed). `names`: the tool's names (an MCP tool
67
+ * has two). `hints`: an MCP tool's annotations; a rule with `hints` never matches a call without.
68
+ * `environment`: the session's; a rule with `environment` never matches a session without one. */
69
+ export declare function decide(compiled: readonly Compiled[], names: readonly string[], args: unknown, hints?: ToolHints, environment?: string): Decision | null;
36
70
  export type CompiledPolicy = ReturnType<typeof compilePolicy>;
37
71
  /** N3 (idea C5): every audit rule this call matches, and what it would do if it were enforced. */
38
- export declare function audited(compiled: readonly Compiled[], names: readonly string[], args: unknown): Array<{
72
+ export declare function audited(compiled: readonly Compiled[], names: readonly string[], args: unknown, hints?: ToolHints, environment?: string): Array<{
39
73
  rule: string;
40
74
  would: Rule["action"];
41
75
  }>;