@zanii/blackbox 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export { type Invoice, type InvoiceLine, invoice, loadPlans, type Plan, type Pla
10
10
  export { BlackboxApiError, type Client, type ClientOptions, client, type SessionQuery, } from "./client/index.ts";
11
11
  export { complianceFacts, complianceReport, type Facts, type Framework, type Report as ComplianceReport, renderReport, } from "./compliance/index.ts";
12
12
  export { type CallCost, type CostReport, callMicroUsd, effectivePrice, formatAed, type LoadedPrices, loadPrices, type ModelPrice, type PriceTable, priceCall, priceFor, promptTokens, requestsOf, sessionCost, type Tokens, toAedFils, tokensOf, } from "./cost/index.ts";
13
+ export { type AuditRow, CORE_EVIDENCE, CORE_IDENTIFIERS, type Connector as AuditConnector, canaryIdentifier, checkAuditConnector, type DataFinding, type DataRules, dataClasses, dataFindings, dataFingerprint, dataMeta, dataScan, dataTopology, hasEvidence, type Identifier, type Lineage, lineage, luhn, normalizeValue, parseAuditLog, parseCsv, reconcileSystem, type SystemReport, type Topology, toolWitness, unevidenced, verifyToolWitness, witnessOf, } from "./data/index.ts";
13
14
  export { type Directive, type DirectiveItem, type DirectiveOptions, directives, fleetDirectives, } from "./directives/index.ts";
14
15
  export { checkDrill, drillReport, type Fault as DrillFault } from "./drills/index.ts";
15
16
  export { checkDuty, type DutyFinding, type DutyLimits, dutyFindings } from "./duty/index.ts";
@@ -24,7 +25,7 @@ export { type AmountUnit, anthropicStatement, type Baseline, baselines, type Con
24
25
  export { filingPack, type OccurrenceFacts, type OccurrenceFramework, type OccurrenceReport, occurrenceFacts, occurrenceReport, renderOccurrence, } from "./occurrence/index.ts";
25
26
  export { ingestOcsf, type OcsfEvent, type OcsfVersion, ocsfLine, toOcsf } from "./ocsf/index.ts";
26
27
  export { toOtlp } from "./otlp/index.ts";
27
- export { checkPack, corePack, type PackEntry, packFrameworks } from "./packs/index.ts";
28
+ export { checkIdentifier, checkPack, corePack, type PackEntry, packData, packFrameworks, } from "./packs/index.ts";
28
29
  export { type PolicyChange, type PolicyDelta, policyDelta } from "./policy/delta.ts";
29
30
  export { type Draft, type DraftGroup, policyDrafts } from "./policy/drafts.ts";
30
31
  export { audited, type CompiledPolicy, compilePolicy, type Decision, decide, loadPolicy, type Policy, policyFindings, type Rule, } from "./policy/index.ts";
@@ -37,7 +38,7 @@ export { type Finding, type FindingCode, type Harness, loadLocal, type Reconcile
37
38
  export { cassette, requestKey, type Take } from "./replay/index.ts";
38
39
  export { ORPHAN_TEXT, repairToolCalls } from "./replay/repair.ts";
39
40
  export { type DrainResult, drainSpools } from "./session/drain.ts";
40
- export { BlackboxSession, type FlightPlan, type LlmCallIds, type SessionError, type SessionOptions, type SessionState, type SessionStats, session, stableEventId, } from "./session/index.ts";
41
+ export { BlackboxSession, checkEgress, type EgressCheck, type FlightPlan, type LlmCallIds, type SessionError, type SessionOptions, type SessionState, type SessionStats, session, stableEventId, } from "./session/index.ts";
41
42
  export { type Compensation, checkCompensations, runUndo, type UndoFinding, type UndoStatus, type UndoStep, undoFindings, undoPlan, } from "./undo/index.ts";
42
43
  export * from "./verify/index.ts";
43
44
  export { VERSION } from "./version.ts";
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export { invoice, loadPlans, renderTaxInvoice, stripeSignatureOk, taxInvoice, }
12
12
  export { BlackboxApiError, client, } from "./client/index.js";
13
13
  export { complianceFacts, complianceReport, renderReport, } from "./compliance/index.js";
14
14
  export { callMicroUsd, effectivePrice, formatAed, loadPrices, priceCall, priceFor, promptTokens, requestsOf, sessionCost, toAedFils, tokensOf, } from "./cost/index.js";
15
+ export { CORE_EVIDENCE, CORE_IDENTIFIERS, canaryIdentifier, checkAuditConnector, dataClasses, dataFindings, dataFingerprint, dataMeta, dataScan, dataTopology, hasEvidence, lineage, luhn, normalizeValue, parseAuditLog, parseCsv, reconcileSystem, toolWitness, unevidenced, verifyToolWitness, witnessOf, } from "./data/index.js";
15
16
  export { directives, fleetDirectives, } from "./directives/index.js";
16
17
  export { checkDrill, drillReport } from "./drills/index.js";
17
18
  export { checkDuty, dutyFindings } from "./duty/index.js";
@@ -26,7 +27,7 @@ export { anthropicStatement, baselines, checkConnector, checkStatement, csvState
26
27
  export { filingPack, occurrenceFacts, occurrenceReport, renderOccurrence, } from "./occurrence/index.js";
27
28
  export { ingestOcsf, ocsfLine, toOcsf } from "./ocsf/index.js";
28
29
  export { toOtlp } from "./otlp/index.js";
29
- export { checkPack, corePack, packFrameworks } from "./packs/index.js";
30
+ export { checkIdentifier, checkPack, corePack, packData, packFrameworks, } from "./packs/index.js";
30
31
  export { policyDelta } from "./policy/delta.js";
31
32
  export { policyDrafts } from "./policy/drafts.js";
32
33
  export { audited, compilePolicy, decide, loadPolicy, policyFindings, } from "./policy/index.js";
@@ -39,7 +40,7 @@ export { loadLocal, RecordNotVerified, reconcile, } from "./reconcile/index.js";
39
40
  export { cassette, requestKey } from "./replay/index.js";
40
41
  export { ORPHAN_TEXT, repairToolCalls } from "./replay/repair.js";
41
42
  export { drainSpools } from "./session/drain.js";
42
- export { BlackboxSession, session, stableEventId, } from "./session/index.js";
43
+ export { BlackboxSession, checkEgress, session, stableEventId, } from "./session/index.js";
43
44
  export { checkCompensations, runUndo, undoFindings, undoPlan, } from "./undo/index.js";
44
45
  export * from "./verify/index.js";
45
46
  export { VERSION } from "./version.js";
@@ -1,3 +1,4 @@
1
+ import { type DataRules } from "../data/index.ts";
1
2
  type Obj = Record<string, unknown>;
2
3
  export interface PackEntry {
3
4
  pack: string;
@@ -9,6 +10,13 @@ export interface PackEntry {
9
10
  }
10
11
  /** spec/packs.md §1: why a pack is invalid, or undefined. */
11
12
  export declare function checkPack(value: unknown): string | undefined;
13
+ /** spec/data.md §1: why an identifier is invalid, or undefined. */
14
+ export declare function checkIdentifier(v: unknown, at: string): string | undefined;
15
+ /**
16
+ * spec/data.md §4: the data rules of packs merged in order, then the core. Pack identifiers come
17
+ * before the core ones; throws on an invalid pack or an identifier id in two packs.
18
+ */
19
+ export declare function packData(packs: readonly unknown[]): DataRules;
12
20
  /** spec/packs.md §2: the core pack, from the built-in frameworks files. */
13
21
  export declare function corePack(compliance: {
14
22
  notes: string;
@@ -1,6 +1,7 @@
1
1
  // Framework packs (spec/packs.md): compliance and occurrence frameworks as data a deployment
2
2
  // switches on. Pure; mirrors sdks/python/src/zanii_blackbox/packs.py; pinned by
3
- // spec/vectors/packs.json.
3
+ // spec/vectors/packs.json. The `data` part is spec/data.md §4.
4
+ import { CORE_EVIDENCE, CORE_IDENTIFIERS } from "../data/index.js";
4
5
  const FACTS = new Set([
5
6
  "sessions",
6
7
  "events",
@@ -130,7 +131,9 @@ function checkArabic(ar, fw, at) {
130
131
  export function checkPack(value) {
131
132
  if (!isObj(value))
132
133
  return "a pack must be an object";
134
+ // `data` came later (spec/data.md §4); the pinned messages below name the v1 keys only.
133
135
  if (!only(value, [
136
+ "data",
134
137
  "v",
135
138
  "id",
136
139
  "title",
@@ -154,7 +157,7 @@ export function checkPack(value) {
154
157
  typeof value.reviewed_at !== "string" ||
155
158
  !DATE.test(value.reviewed_at)))
156
159
  return "a reviewed pack needs reviewed_by and reviewed_at (YYYY-MM-DD)";
157
- if (value.compliance === undefined && value.occurrence === undefined)
160
+ if (value.compliance === undefined && value.occurrence === undefined && value.data === undefined)
158
161
  return "a pack needs compliance, occurrence or both";
159
162
  for (const kind of ["compliance", "occurrence"])
160
163
  if (value[kind] !== undefined) {
@@ -162,8 +165,131 @@ export function checkPack(value) {
162
165
  if (bad)
163
166
  return bad;
164
167
  }
168
+ return value.data === undefined ? undefined : checkData(value.data);
169
+ }
170
+ const IDENTIFIER_ID = /^[a-z0-9_]{1,64}$/;
171
+ const NORMALIZE = new Set(["digits", "lower", "upper", "exact"]);
172
+ // Escapes Python reads over Unicode and JavaScript over ASCII (spec/data.md §1).
173
+ const UNPORTABLE = /\\[dDwWbBsS]/;
174
+ const ALLOW = /^(?:region|model|tool|api):(?:[A-Za-z0-9._-]{1,128}\*?|\*)$/;
175
+ const TOOL = /^[A-Za-z0-9._-]{1,128}$/;
176
+ const FIELD = /^[A-Za-z0-9_.-]{1,64}$/;
177
+ const compiles = (pattern) => {
178
+ try {
179
+ new RegExp(pattern);
180
+ return true;
181
+ }
182
+ catch {
183
+ return false;
184
+ }
185
+ };
186
+ /** spec/data.md §1: why an identifier is invalid, or undefined. */
187
+ export function checkIdentifier(v, at) {
188
+ if (!isObj(v) ||
189
+ !only(v, ["id", "title", "pattern", "normalize", "keep_last", "check", "personal"]))
190
+ return `${at} may only have id, title, pattern, normalize, keep_last, check, personal`;
191
+ if (typeof v.id !== "string" || !IDENTIFIER_ID.test(v.id))
192
+ return `${at}.id must be 1-64 of a-z 0-9 _`;
193
+ if (!text(v.title, 200))
194
+ return `${at}.title must be 1-200 characters`;
195
+ if (typeof v.pattern !== "string" || !text(v.pattern, 500) || !compiles(v.pattern))
196
+ return `${at}.pattern must be a regular expression of 1-500 characters`;
197
+ if (UNPORTABLE.test(v.pattern))
198
+ return `${at}.pattern must not use \\d \\w \\b or \\s`;
199
+ if (!NORMALIZE.has(v.normalize))
200
+ return `${at}.normalize must be digits, lower, upper or exact`;
201
+ if (v.keep_last !== undefined &&
202
+ (!Number.isInteger(v.keep_last) || v.keep_last < 1 || v.keep_last > 30))
203
+ return `${at}.keep_last must be 1-30`;
204
+ if (v.check !== undefined && v.check !== "luhn")
205
+ return `${at}.check must be luhn`;
206
+ if (typeof v.personal !== "boolean")
207
+ return `${at}.personal must be true or false`;
208
+ return undefined;
209
+ }
210
+ function checkData(part) {
211
+ if (!isObj(part) || !only(part, ["notes", "identifiers", "residency", "restricted", "evidence"]))
212
+ return "data may only have notes, identifiers, residency, restricted, evidence";
213
+ if (!text(part.notes, 2000))
214
+ return "data.notes must be 1-2000 characters";
215
+ const ids = part.identifiers ?? [];
216
+ if (!Array.isArray(ids) || ids.length > 50)
217
+ return "data.identifiers must hold 0-50 identifiers";
218
+ const seen = new Set(CORE_IDENTIFIERS.map((i) => i.id));
219
+ for (const [i, v] of ids.entries()) {
220
+ const bad = checkIdentifier(v, `data.identifiers[${i}]`);
221
+ if (bad)
222
+ return bad;
223
+ const id = v.id;
224
+ if (seen.has(id))
225
+ return `data.identifiers[${i}].id ${id} is already defined`;
226
+ seen.add(id);
227
+ }
228
+ if (part.residency !== undefined && typeof part.residency !== "boolean")
229
+ return "data.residency must be true or false";
230
+ const rules = part.restricted ?? [];
231
+ if (!Array.isArray(rules) || rules.length > 50)
232
+ return "data.restricted must hold 0-50 rules";
233
+ for (const [i, r] of rules.entries()) {
234
+ const at = `data.restricted[${i}]`;
235
+ if (!isObj(r) || !only(r, ["identifiers", "allow"]))
236
+ return `${at} must be {identifiers, allow}`;
237
+ if (!Array.isArray(r.identifiers) ||
238
+ r.identifiers.length < 1 ||
239
+ r.identifiers.length > 50 ||
240
+ !r.identifiers.every((x) => typeof x === "string" && seen.has(x)))
241
+ return `${at}.identifiers must hold 1-50 known identifier ids`;
242
+ if (!Array.isArray(r.allow) ||
243
+ r.allow.length < 1 ||
244
+ r.allow.length > 20 ||
245
+ !r.allow.every((x) => typeof x === "string" && ALLOW.test(x)))
246
+ return `${at}.allow must hold 1-20 of region:, model:, tool:, api:`;
247
+ }
248
+ const evidence = part.evidence ?? [];
249
+ if (!Array.isArray(evidence) || evidence.length > 200)
250
+ return "data.evidence must hold 0-200 rules";
251
+ for (const [i, e] of evidence.entries())
252
+ if (!isObj(e) ||
253
+ !only(e, ["tool", "field"]) ||
254
+ typeof e.tool !== "string" ||
255
+ !TOOL.test(e.tool) ||
256
+ typeof e.field !== "string" ||
257
+ !FIELD.test(e.field))
258
+ return `data.evidence[${i}] must be {tool, field}`;
165
259
  return undefined;
166
260
  }
261
+ /**
262
+ * spec/data.md §4: the data rules of packs merged in order, then the core. Pack identifiers come
263
+ * before the core ones; throws on an invalid pack or an identifier id in two packs.
264
+ */
265
+ export function packData(packs) {
266
+ const identifiers = [];
267
+ const restricted = [];
268
+ const evidence = [];
269
+ const owner = new Map();
270
+ let residency = false;
271
+ for (const p of packs) {
272
+ const bad = checkPack(p);
273
+ const id = isObj(p) && typeof p.id === "string" ? p.id : "?";
274
+ if (bad)
275
+ throw new Error(`pack ${id}: ${bad}`);
276
+ const data = p.data;
277
+ if (!data)
278
+ continue;
279
+ for (const ident of data.identifiers ?? []) {
280
+ const had = owner.get(ident.id);
281
+ if (had)
282
+ throw new Error(`identifier ${ident.id} is in packs ${had} and ${id}`);
283
+ owner.set(ident.id, id);
284
+ identifiers.push(ident);
285
+ }
286
+ residency ||= data.residency === true;
287
+ restricted.push(...(data.restricted ?? []));
288
+ evidence.push(...(data.evidence ?? []));
289
+ }
290
+ identifiers.push(...CORE_IDENTIFIERS);
291
+ return { identifiers, residency, restricted, evidence: [...evidence, ...CORE_EVIDENCE] };
292
+ }
167
293
  /** spec/packs.md §2: the core pack, from the built-in frameworks files. */
168
294
  export function corePack(compliance, occurrence) {
169
295
  return {
@@ -76,6 +76,16 @@ export interface FlightPlan {
76
76
  }
77
77
  /** Audit S17: what the gateway says about this session right now (GET /v1/sessions/:id/state). */
78
78
  /** What `land()` found (spec/findings.md §5). */
79
+ export interface EgressCheck {
80
+ url: string;
81
+ /** The request got any answer: the agent can reach the internet around the gateway. */
82
+ open: boolean;
83
+ }
84
+ /** spec/data.md §6: a direct HTTPS request to `url` (default https://example.com). Never throws. */
85
+ export declare function checkEgress(options?: {
86
+ url?: string;
87
+ timeoutMs?: number;
88
+ }): Promise<EgressCheck>;
79
89
  export interface LandingResult {
80
90
  /** Only a `satisfied` verdict lands: `unverified` never counts as a pass. */
81
91
  landed: boolean;
@@ -149,6 +159,15 @@ export declare class BlackboxSession {
149
159
  ok?: boolean;
150
160
  [key: string]: unknown;
151
161
  }): void;
162
+ /** spec/data.md §7: a subject's consent for a purpose. The subject is fingerprinted at capture,
163
+ * so it must be an identifier the deployment knows (an e-mail, an Emirates ID, a pack's own). */
164
+ consent(subject: string, purpose: string, granted: boolean): void;
165
+ /** spec/data.md §6: tries the internet directly, outside the gateway, and records the answer.
166
+ * `open: true` means the gateway isn't the only way out (EGRESS_OPEN). */
167
+ egressCheck(options?: {
168
+ url?: string;
169
+ timeoutMs?: number;
170
+ }): Promise<EgressCheck>;
152
171
  llmCall(ids: LlmCallIds): void;
153
172
  note(text: string): void;
154
173
  /** Files the flight plan (spec/findings.md §5), unless one was filed when the session opened. */
@@ -7,6 +7,17 @@ import { request as httpsRequest } from "node:https";
7
7
  import { tmpdir } from "node:os";
8
8
  import { join } from "node:path";
9
9
  import { attest, isReadOnly } from "../attest/index.js";
10
+ /** spec/data.md §6: a direct HTTPS request to `url` (default https://example.com). Never throws. */
11
+ export async function checkEgress(options = {}) {
12
+ const url = options.url ?? "https://example.com";
13
+ try {
14
+ await fetch(url, { method: "HEAD", signal: AbortSignal.timeout(options.timeoutMs ?? 5000) });
15
+ return { url, open: true };
16
+ }
17
+ catch {
18
+ return { url, open: false };
19
+ }
20
+ }
10
21
  const TYPE = /^[a-z][a-z0-9_.]{0,63}$/;
11
22
  /** N4 (idea R6): a caller's logical id for an event, so a re-sent one is recorded as a duplicate. */
12
23
  const EVENT_ID = /^[A-Za-z0-9._:-]{1,128}$/;
@@ -181,6 +192,18 @@ export class BlackboxSession {
181
192
  toolResult(name, result) {
182
193
  this.event("tool.result", name, result);
183
194
  }
195
+ /** spec/data.md §7: a subject's consent for a purpose. The subject is fingerprinted at capture,
196
+ * so it must be an identifier the deployment knows (an e-mail, an Emirates ID, a pack's own). */
197
+ consent(subject, purpose, granted) {
198
+ this.event("consent", granted ? "granted" : "withdrawn", { subject, purpose });
199
+ }
200
+ /** spec/data.md §6: tries the internet directly, outside the gateway, and records the answer.
201
+ * `open: true` means the gateway isn't the only way out (EGRESS_OPEN). */
202
+ async egressCheck(options = {}) {
203
+ const result = await checkEgress(options);
204
+ this.event("egress_check", result.open ? "open" : "closed", { url: result.url });
205
+ return result;
206
+ }
184
207
  llmCall(ids) {
185
208
  this.event("llm.call", ids.provider, { ...ids });
186
209
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.1.0";
1
+ export declare const VERSION = "0.2.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // The SDK version, on its own so the CLI can print it without loading the whole SDK.
2
- export const VERSION = "0.1.0";
2
+ export const VERSION = "0.2.0";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zanii/blackbox",
3
3
  "license": "Apache-2.0",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "description": "The flight recorder for AI agents: sessions, a zero-loss spool, framework hooks, approvals, and offline verification of the gateway's hash-chained record.",
6
6
  "keywords": [
7
7
  "ai-agents",