@zanii/blackbox 0.3.0 → 0.5.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 (82) hide show
  1. package/README.md +31 -1
  2. package/dist/a2a/index.d.ts +27 -0
  3. package/dist/a2a/index.js +104 -1
  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.js +1 -0
  21. package/dist/approvals/index.d.ts +23 -0
  22. package/dist/approvals/index.js +48 -0
  23. package/dist/archive/parquet.d.ts +2 -0
  24. package/dist/archive/parquet.js +185 -0
  25. package/dist/badge/index.d.ts +16 -0
  26. package/dist/badge/index.js +48 -0
  27. package/dist/bom/index.js +20 -0
  28. package/dist/cli.js +114 -10
  29. package/dist/compliance/art12.js +36 -9
  30. package/dist/compliance/index.d.ts +36 -2
  31. package/dist/compliance/index.js +78 -11
  32. package/dist/compliance/zanii.d.ts +29 -0
  33. package/dist/compliance/zanii.js +84 -0
  34. package/dist/constitution/index.d.ts +57 -0
  35. package/dist/constitution/index.js +131 -0
  36. package/dist/cv/index.d.ts +39 -0
  37. package/dist/cv/index.js +108 -0
  38. package/dist/disclosure/index.d.ts +31 -0
  39. package/dist/disclosure/index.js +113 -0
  40. package/dist/encryption/index.d.ts +9 -0
  41. package/dist/encryption/index.js +31 -0
  42. package/dist/evidence/index.d.ts +60 -0
  43. package/dist/evidence/index.js +151 -0
  44. package/dist/federation/index.d.ts +35 -0
  45. package/dist/federation/index.js +102 -0
  46. package/dist/finance/index.d.ts +126 -0
  47. package/dist/finance/index.js +320 -0
  48. package/dist/fleet/index.js +9 -0
  49. package/dist/gov/index.d.ts +108 -0
  50. package/dist/gov/index.js +225 -0
  51. package/dist/health/index.d.ts +120 -0
  52. package/dist/health/index.js +233 -0
  53. package/dist/index.d.ts +29 -7
  54. package/dist/index.js +28 -6
  55. package/dist/memory/index.d.ts +36 -0
  56. package/dist/memory/index.js +85 -0
  57. package/dist/occurrence/index.d.ts +11 -0
  58. package/dist/occurrence/index.js +18 -0
  59. package/dist/otlp/index.js +28 -1
  60. package/dist/packs/index.js +44 -4
  61. package/dist/policy/delta.js +7 -1
  62. package/dist/policy/index.d.ts +23 -6
  63. package/dist/policy/index.js +151 -8
  64. package/dist/policy/zanii.d.ts +31 -0
  65. package/dist/policy/zanii.js +87 -0
  66. package/dist/pq/index.d.ts +23 -0
  67. package/dist/pq/index.js +104 -0
  68. package/dist/search/index.d.ts +23 -0
  69. package/dist/search/index.js +69 -0
  70. package/dist/session/index.d.ts +106 -1
  71. package/dist/session/index.js +163 -11
  72. package/dist/sla/index.d.ts +61 -0
  73. package/dist/sla/index.js +197 -0
  74. package/dist/succession/index.d.ts +50 -0
  75. package/dist/succession/index.js +123 -0
  76. package/dist/tokens/index.d.ts +6 -0
  77. package/dist/tokens/index.js +46 -0
  78. package/dist/version.d.ts +1 -1
  79. package/dist/version.js +1 -1
  80. package/dist/walls/index.d.ts +31 -0
  81. package/dist/walls/index.js +119 -0
  82. package/package.json +1 -1
@@ -0,0 +1,87 @@
1
+ // Zanii's policy format, in and out (spec/policy.md §8). A rule that can't be said the same way on
2
+ // the other side is skipped and named, never loosened: dropping a condition would widen what a rule
3
+ // denies or allows. Pure; mirrors sdks/python/src/zanii_blackbox/policy_zanii.py; pinned by
4
+ // spec/vectors/policy-zanii.json.
5
+ const EFFECTS = new Set(["deny", "allow", "require_approval"]);
6
+ const idOf = (raw, n) => {
7
+ const s = String(raw ?? "")
8
+ .toLowerCase()
9
+ .replace(/[^a-z0-9-]+/g, "-")
10
+ .replace(/^-+|-+$/g, "")
11
+ .slice(0, 60);
12
+ return s || `rule-${n + 1}`;
13
+ };
14
+ /** A Zanii target as a Blackbox tool pattern, or null (`mcp.<server>.<tool>`, `mcp.<server>.*`, `*`). */
15
+ function toolOf(target) {
16
+ if (target === "*" || target === "mcp.*")
17
+ return target === "*" ? "*" : "mcp__*";
18
+ const m = /^mcp\.([A-Za-z0-9_-]+)\.([A-Za-z0-9_.*-]+)$/.exec(target);
19
+ return m ? `mcp__${m[1]}__${m[2]}` : null;
20
+ }
21
+ /** A Blackbox tool pattern as a Zanii target, or null (a bare tool name matches any server). */
22
+ function targetOf(tool) {
23
+ if (tool === "*")
24
+ return "*";
25
+ if (tool === "mcp__*")
26
+ return "mcp.*";
27
+ const m = /^mcp__([A-Za-z0-9_-]+?)__(.+)$/.exec(tool);
28
+ return m ? `mcp.${m[1]}.${m[2]}` : null;
29
+ }
30
+ /** Zanii → Blackbox: `{policy: {version: 1, rules}, skipped}`. */
31
+ export function fromZaniiPolicy(z) {
32
+ const p = (z ?? {});
33
+ const rules = [];
34
+ const skipped = [];
35
+ const list = Array.isArray(p.rules) ? p.rules : [];
36
+ for (const [n, r] of list.entries()) {
37
+ const id = idOf(r.id, n);
38
+ const skip = (reason) => skipped.push({ id, reason });
39
+ if (!EFFECTS.has(String(r.effect)))
40
+ skip(`effect ${String(r.effect)}`);
41
+ else if (r.where !== undefined)
42
+ skip("conditions on the payload (where) have no exact Blackbox form");
43
+ else if (r.rateLimit !== undefined)
44
+ skip("rate limits are not tool policy (use the session limits)");
45
+ else {
46
+ const targets = Array.isArray(r.targets) ? r.targets.map(String) : ["*"];
47
+ const tools = targets.map(toolOf);
48
+ const bad = targets.filter((_, i) => tools[i] === null);
49
+ if (bad.length)
50
+ skip(`not an MCP tool target: ${bad.join(", ")}`);
51
+ else
52
+ for (const [i, tool] of tools.entries())
53
+ rules.push({
54
+ id: tools.length > 1 ? `${id}-${i + 1}`.slice(0, 64) : id,
55
+ tool: tool,
56
+ action: String(r.effect),
57
+ });
58
+ }
59
+ }
60
+ if (p.default === "deny")
61
+ rules.push({ id: "zanii-default", tool: "*", action: "deny" });
62
+ return { policy: { version: 1, rules }, skipped };
63
+ }
64
+ /** Blackbox → Zanii: `{policy: {default: "allow", rules}, skipped}`. */
65
+ export function toZaniiPolicy(ours) {
66
+ const p = (ours ?? {});
67
+ const rules = [];
68
+ const skipped = [];
69
+ for (const r of (Array.isArray(p.rules) ? p.rules : [])) {
70
+ const id = String(r.id);
71
+ const extra = ["args_match", "hints", "environment"].filter((k) => r[k] !== undefined);
72
+ if (r.audit === true)
73
+ skipped.push({ id, reason: "an audit-only rule decides nothing" });
74
+ else if (r.action === "require_proof")
75
+ skipped.push({ id, reason: "Zanii has no require_proof effect" });
76
+ else if (extra.length)
77
+ skipped.push({ id, reason: `${extra.join(", ")} have no Zanii form` });
78
+ else {
79
+ const target = targetOf(String(r.tool));
80
+ if (target === null)
81
+ skipped.push({ id, reason: `a bare tool name matches any server: ${String(r.tool)}` });
82
+ else
83
+ rules.push({ id, effect: String(r.action), targets: [target] });
84
+ }
85
+ }
86
+ return { policy: { default: "allow", rules }, skipped };
87
+ }
@@ -0,0 +1,23 @@
1
+ type Obj = Record<string, unknown>;
2
+ export declare const PQ_ALG = "ML-DSA-65";
3
+ /** The ML-DSA-65 seed for a gateway identity: HMAC-SHA256(its Ed25519 key, a fixed label). */
4
+ export declare const pqSeedOf: (identityPrivateKey: Uint8Array) => Uint8Array;
5
+ /** The raw ML-DSA-65 public key (1952 bytes) for a 32-byte seed. */
6
+ export declare const pqPublicKey: (seed: Uint8Array) => Uint8Array;
7
+ /** Whether this runtime has ML-DSA-65 (Node ≥ 24.6 with OpenSSL ≥ 3.5). */
8
+ export declare function pqAvailable(): boolean;
9
+ /** Zanii's pq.binding: both keys sign the same body, so holding one key can't forge it. */
10
+ export declare function bindPqKey(o: {
11
+ did: string;
12
+ ts: string;
13
+ edPrivateKey: Uint8Array;
14
+ pqSeed: Uint8Array;
15
+ }): Obj;
16
+ /** Zanii's check: the structure, and BOTH signatures over the same body. */
17
+ export declare function verifyPqBinding(binding: unknown): {
18
+ ok: boolean;
19
+ reasons: string[];
20
+ };
21
+ /** Zanii's transition payload, recorded UNSALTED: the binding as compact JSON. */
22
+ export declare function pqTransitionPayload(binding: Obj): string;
23
+ export {};
@@ -0,0 +1,104 @@
1
+ // Post-quantum binding of the gateway identity (spec/anchoring.md §13): Zanii's pq.binding, an
2
+ // ML-DSA-65 key (FIPS 204) bound to a did:key and signed by both keys, and the unsalted transition
3
+ // payload anchored to prove the binding existed before any break of Ed25519 (@zanii/pq). Needs
4
+ // Node ≥ 24.6 for ML-DSA. Pure; mirrors sdks/python/src/zanii_blackbox/pq.py; pinned by
5
+ // spec/vectors/pq-binding.json.
6
+ import { createHmac, createPrivateKey, createPublicKey, sign, verify } from "node:crypto";
7
+ import { canonicalBytes, publicKeyFromDid } from "@zanii/core";
8
+ import { ed25519Sign, ed25519Verify } from "../transparency/index.js";
9
+ export const PQ_ALG = "ML-DSA-65";
10
+ const SEED_INFO = "zanii-blackbox identity ml-dsa-65 v1";
11
+ const PKCS8 = Buffer.from("3034020100300b060960864801650304031204228020", "hex");
12
+ const SPKI = Buffer.from("308207b2300b0609608648016503040312038207a100", "hex");
13
+ /** The ML-DSA-65 seed for a gateway identity: HMAC-SHA256(its Ed25519 key, a fixed label). */
14
+ export const pqSeedOf = (identityPrivateKey) => new Uint8Array(createHmac("sha256", identityPrivateKey).update(SEED_INFO).digest());
15
+ const privateKey = (seed) => {
16
+ if (seed.length !== 32)
17
+ throw new Error("an ML-DSA-65 seed is 32 bytes");
18
+ return createPrivateKey({ key: Buffer.concat([PKCS8, seed]), format: "der", type: "pkcs8" });
19
+ };
20
+ /** The raw ML-DSA-65 public key (1952 bytes) for a 32-byte seed. */
21
+ export const pqPublicKey = (seed) => new Uint8Array(createPublicKey(privateKey(seed)).export({ format: "der", type: "spki" }).subarray(SPKI.length));
22
+ /** Whether this runtime has ML-DSA-65 (Node ≥ 24.6 with OpenSSL ≥ 3.5). */
23
+ export function pqAvailable() {
24
+ try {
25
+ pqPublicKey(new Uint8Array(32));
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ const validTs = (ts) => typeof ts === "string" && !Number.isNaN(Date.parse(ts));
33
+ const hex = (b) => Buffer.from(b).toString("hex");
34
+ /** Zanii's pq.binding: both keys sign the same body, so holding one key can't forge it. */
35
+ export function bindPqKey(o) {
36
+ if (!o.did)
37
+ throw new Error("did is required");
38
+ if (!validTs(o.ts))
39
+ throw new Error("ts must be an ISO timestamp");
40
+ const unsigned = {
41
+ v: 1,
42
+ type: "pq.binding",
43
+ did: o.did,
44
+ alg: PQ_ALG,
45
+ pq_pub: hex(pqPublicKey(o.pqSeed)),
46
+ ts: o.ts,
47
+ };
48
+ const raw = canonicalBytes(unsigned);
49
+ return {
50
+ ...unsigned,
51
+ sig_ed: `ed25519:${hex(ed25519Sign(o.edPrivateKey, raw))}`,
52
+ sig_pq: `mldsa65:${hex(sign(null, raw, privateKey(o.pqSeed)))}`,
53
+ };
54
+ }
55
+ const pqVerify = (publicKey, msg, sig) => {
56
+ try {
57
+ const key = createPublicKey({
58
+ key: Buffer.concat([SPKI, publicKey]),
59
+ format: "der",
60
+ type: "spki",
61
+ });
62
+ return verify(null, msg, key, sig);
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ };
68
+ /** Zanii's check: the structure, and BOTH signatures over the same body. */
69
+ export function verifyPqBinding(binding) {
70
+ const b = (binding ?? {});
71
+ const reasons = [];
72
+ if (b.v !== 1 || b.type !== "pq.binding")
73
+ reasons.push("not a pq.binding (v1)");
74
+ if (b.alg !== PQ_ALG)
75
+ reasons.push("unsupported alg (want ML-DSA-65)");
76
+ if (!b.did)
77
+ reasons.push("missing did");
78
+ const pqPub = typeof b.pq_pub === "string" && /^([0-9a-f]{2})+$/.test(b.pq_pub)
79
+ ? new Uint8Array(Buffer.from(b.pq_pub, "hex"))
80
+ : null;
81
+ if (!pqPub)
82
+ reasons.push("missing/invalid pq_pub");
83
+ if (!validTs(b.ts))
84
+ reasons.push("missing/invalid ts");
85
+ if (reasons.length > 0 || !pqPub)
86
+ return { ok: false, reasons };
87
+ const { sig_ed: sigEd, sig_pq: sigPq, ...unsigned } = b;
88
+ const raw = canonicalBytes(unsigned);
89
+ const pub = publicKeyFromDid(String(b.did));
90
+ const ed = typeof sigEd === "string" ? /^ed25519:([0-9a-f]{128})$/.exec(sigEd) : null;
91
+ if (!pub || !ed || !ed25519Verify(pub, raw, Buffer.from(ed[1], "hex")))
92
+ reasons.push("Ed25519 signature does not verify against the did");
93
+ const pq = typeof sigPq === "string" ? /^mldsa65:((?:[0-9a-f]{2})+)$/.exec(sigPq) : null;
94
+ if (!pq || !pqVerify(pqPub, raw, Buffer.from(pq[1], "hex")))
95
+ reasons.push("ML-DSA signature does not verify against pq_pub (possession not proven)");
96
+ return { ok: reasons.length === 0, reasons };
97
+ }
98
+ /** Zanii's transition payload, recorded UNSALTED: the binding as compact JSON. */
99
+ export function pqTransitionPayload(binding) {
100
+ const check = verifyPqBinding(binding);
101
+ if (!check.ok)
102
+ throw new Error(`refusing to anchor an invalid binding: ${check.reasons.join("; ")}`);
103
+ return JSON.stringify(binding);
104
+ }
@@ -0,0 +1,23 @@
1
+ export interface SearchQuery {
2
+ /** An event kind, e.g. `tool.call`, `finding`, `control`. */
3
+ kind?: string;
4
+ /** A tool's name, as called: `delete_record`, `mcp__db__delete_record`, an SDK tool. */
5
+ tool?: string;
6
+ /** A finding code, e.g. `TOOL_CHANGED`. */
7
+ code?: string;
8
+ /** A person who decided an approval or a grant: their `sub`, `email` (any case) or `name`. */
9
+ person?: string;
10
+ /** The session's environment (`session.open` meta.environment). */
11
+ environment?: string;
12
+ }
13
+ export interface SearchMatch {
14
+ seq: number;
15
+ kind: string;
16
+ ts: string;
17
+ /** What matched: the tool, the code, the person, or the kind. */
18
+ match: string;
19
+ }
20
+ /** The query's fields, checked: each a 1-256 character string; `environment` alone is allowed. */
21
+ export declare function checkSearch(q: Record<string, unknown>): string | undefined;
22
+ /** Every event of the record that matches all of the query's fields, in seq order. */
23
+ export declare function searchEvents(lines: readonly string[], q: SearchQuery): SearchMatch[];
@@ -0,0 +1,69 @@
1
+ // Structured search over a session's record (spec/search.md): exact matches on what the events say
2
+ // (a tool, a fault code, a person, an event kind, the session's environment), never text inside
3
+ // bodies. Precise by construction, and it reads nothing that was redacted. Pure; mirrors
4
+ // sdks/python/src/zanii_blackbox/search.py; pinned by spec/vectors/search.json.
5
+ const DECISIONS = new Set(["approval", "approval_vote", "permission"]);
6
+ /** The query's fields, checked: each a 1-256 character string; `environment` alone is allowed. */
7
+ export function checkSearch(q) {
8
+ const fields = ["kind", "tool", "code", "person", "environment"];
9
+ for (const k of Object.keys(q))
10
+ if (!fields.includes(k))
11
+ return `unknown search field ${JSON.stringify(k)}`;
12
+ for (const k of fields) {
13
+ const v = q[k];
14
+ if (v !== undefined && (typeof v !== "string" || v.length < 1 || v.length > 256))
15
+ return `${k} must be 1-256 characters`;
16
+ }
17
+ return fields.some((k) => q[k] !== undefined)
18
+ ? undefined
19
+ : "give kind, tool, code, person or environment";
20
+ }
21
+ function toolOf(e) {
22
+ if (e.kind === "tool.call" && typeof e.meta.tool === "string")
23
+ return typeof e.meta.server === "string"
24
+ ? [e.meta.tool, `mcp__${e.meta.server}__${e.meta.tool}`]
25
+ : [e.meta.tool];
26
+ if (e.kind === "sdk.event" && e.meta.type === "tool.call" && typeof e.meta.name === "string")
27
+ return [e.meta.name];
28
+ return [];
29
+ }
30
+ function personMatch(e, who) {
31
+ if (e.kind !== "control" || !DECISIONS.has(String(e.meta.action)))
32
+ return false;
33
+ const p = e.meta.person;
34
+ if (!p)
35
+ return false;
36
+ return (p.sub === who ||
37
+ p.name === who ||
38
+ (typeof p.email === "string" && p.email.toLowerCase() === who.toLowerCase()));
39
+ }
40
+ /** Every event of the record that matches all of the query's fields, in seq order. */
41
+ export function searchEvents(lines, q) {
42
+ const events = lines.map((l) => JSON.parse(l));
43
+ const open = events.find((e) => e.kind === "session.open");
44
+ if (q.environment !== undefined && open?.meta.environment !== q.environment)
45
+ return [];
46
+ const eventWise = q.kind !== undefined || q.tool !== undefined || q.code !== undefined || q.person !== undefined;
47
+ if (!eventWise)
48
+ return open
49
+ ? [{ seq: open.seq, kind: open.kind, ts: open.ts, match: q.environment }]
50
+ : [];
51
+ const out = [];
52
+ for (const e of events) {
53
+ if (q.kind !== undefined && e.kind !== q.kind)
54
+ continue;
55
+ if (q.tool !== undefined && !toolOf(e).includes(q.tool))
56
+ continue;
57
+ if (q.code !== undefined && !(e.kind === "finding" && e.meta.code === q.code))
58
+ continue;
59
+ if (q.person !== undefined && !personMatch(e, q.person))
60
+ continue;
61
+ out.push({
62
+ seq: e.seq,
63
+ kind: e.kind,
64
+ ts: e.ts,
65
+ match: q.tool ?? q.code ?? q.person ?? q.kind,
66
+ });
67
+ }
68
+ return out;
69
+ }
@@ -1,4 +1,5 @@
1
1
  import { type Attestation } from "../attest/index.ts";
2
+ import { buildPayment, type screenCounterparty } from "../finance/index.ts";
2
3
  export interface SessionOptions {
3
4
  /** The gateway, e.g. http://127.0.0.1:8787 */
4
5
  url: string;
@@ -33,6 +34,11 @@ export interface SessionOptions {
33
34
  tenant?: string;
34
35
  /** spec/authority.md: `supervised` from the start (risky actions wait for a second person). */
35
36
  authority?: "agent" | "supervised";
37
+ /** spec/api.md: where it runs (`production`, `staging`, …); policy rules can match it. */
38
+ environment?: string;
39
+ /** spec/findings.md §13.1: hold this session's streamed answers until they're checked (`true`),
40
+ * or never (`false`); unset follows the server's BLACKBOX_HOLD_STREAMS. */
41
+ holdStreams?: boolean;
36
42
  /** spec/preflight.md: the equipment the run needs. A no-go returns a disabled session (and
37
43
  * `stats.lastError` says why); optional items that are down show in `state().degraded`. */
38
44
  preflight?: {
@@ -95,6 +101,16 @@ export interface LandingResult {
95
101
  /** How many more tries `maxAttempts` allows; at 0, stop and report. */
96
102
  attemptsLeft: number;
97
103
  }
104
+ /** spec/findings.md §13.1: what would hold an answer, from `checkAnswer()`. */
105
+ export interface AnswerCheck {
106
+ seq: number;
107
+ risks: Array<{
108
+ index: number;
109
+ kind: string;
110
+ status: "contradicted" | "ungrounded";
111
+ }>;
112
+ hold: boolean;
113
+ }
98
114
  export interface SessionState {
99
115
  session_id: string;
100
116
  closed: boolean;
@@ -144,6 +160,10 @@ export declare class BlackboxSession {
144
160
  private timers;
145
161
  private closed;
146
162
  private landings;
163
+ /** spec/approvals.md §2: approved tools with arguments, until their call is reported. */
164
+ private approved;
165
+ /** spec/agents.md §4: the memory chain's tip. */
166
+ private memoryLast;
147
167
  /** A session that records nothing. `reason`, when opening failed, goes to `stats.lastError` (audit K1). */
148
168
  static disabled(options: SessionOptions, reason?: string): BlackboxSession;
149
169
  private readonly options;
@@ -161,6 +181,67 @@ export declare class BlackboxSession {
161
181
  };
162
182
  }): void;
163
183
  step(name: string, data?: Record<string, unknown>): void;
184
+ /**
185
+ * spec/gov.md §2: a decision about a person, under the rulebook `manifestHash`, the person named
186
+ * only by their subject tag. Returns the factors' nonce (keep it to disclose them in a dispute).
187
+ * Throws on a decision that can't be lawful evidence: no rulebook, a bad kind, no tag.
188
+ */
189
+ decision(kind: string, o: {
190
+ manifestHash: string;
191
+ outcome: Record<string, unknown>;
192
+ subjectTag: string;
193
+ factors?: Record<string, unknown>;
194
+ appealBy?: string;
195
+ }): {
196
+ nonce: string;
197
+ };
198
+ /** spec/health.md §2: who accessed a patient's record (an episode tag, never the seed), and why. */
199
+ healthAccess(o: {
200
+ episodeTag: string;
201
+ actor: string;
202
+ purpose: string;
203
+ consentRef?: string;
204
+ details?: Record<string, unknown>;
205
+ }): {
206
+ nonce: string;
207
+ };
208
+ /** spec/health.md §2: a model's recommendation under a protocol. `hash` is what the clinician signs. */
209
+ recommendation(o: {
210
+ episodeTag: string;
211
+ modelId: string;
212
+ manifestHash: string;
213
+ outcome: Record<string, unknown>;
214
+ runtimeHash?: string;
215
+ deviation?: Record<string, unknown>;
216
+ }): {
217
+ nonce: string;
218
+ hash: string;
219
+ };
220
+ /** spec/health.md §2: a clinician's signed confirmation (clinicianConfirmation). Throws on a bad signature. */
221
+ clinicianConfirmation(episodeTag: string, payload: Record<string, unknown>): void;
222
+ /** spec/health.md §2: an emergency access, signed by the clinician (breakGlass). Loud on purpose. */
223
+ breakGlass(episodeTag: string, payload: Record<string, unknown>): void;
224
+ private signedHealth;
225
+ /** spec/finance.md §2: a payment, exact (minor units), with its settlement reference. Throws on a bad amount. */
226
+ payment(input: Parameters<typeof buildPayment>[0]): void;
227
+ /** spec/finance.md §3: a counterparty screening (screenCounterparty), before paying them. */
228
+ screening(result: ReturnType<typeof screenCounterparty>): void;
229
+ /** spec/finance.md §4: a tax filing the agent prepared. It never files; a Tax Agent does. */
230
+ ftaPrepared(o: {
231
+ filingType: string;
232
+ period: string;
233
+ filingRef?: string;
234
+ manifestHash?: string;
235
+ by?: string;
236
+ }): void;
237
+ /** spec/finance.md §4: the prepared filing handed to a licensed Tax Agent. */
238
+ ftaHandoff(o: {
239
+ filingType: string;
240
+ period: string;
241
+ toTaxAgent: string;
242
+ filingRef?: string;
243
+ by?: string;
244
+ }): void;
164
245
  toolCall(name: string, args?: unknown): void;
165
246
  toolResult(name: string, result?: {
166
247
  ok?: boolean;
@@ -200,6 +281,15 @@ export declare class BlackboxSession {
200
281
  summary?: string;
201
282
  file_path?: string;
202
283
  }): void;
284
+ /** spec/findings.md §11 (H3): pin the documents the agent read, by hash; the content never leaves.
285
+ * Each doc is `{id, uri?, version?}` with its `content` (hashed here) or its `sha256`. */
286
+ retrieved(docs: ReadonlyArray<{
287
+ id: string;
288
+ uri?: string;
289
+ version?: string;
290
+ content?: string | Uint8Array;
291
+ sha256?: string;
292
+ }>): void;
203
293
  /** Stage 2 R3: the result of one of the customer's own checks (tests, a schema, a policy, a
204
294
  * partial goal), mid-run or at the end. A pass then a fail is VERIFY_REGRESSION; a success whose last
205
295
  * check failed is FALSE_SUCCESS (spec/findings.md §6). */
@@ -234,7 +324,18 @@ export declare class BlackboxSession {
234
324
  height?: number;
235
325
  }): void;
236
326
  /** spec/agents.md §1: the agent's memory store wrote, revoked or returned memories. */
237
- memoryWrite(memoryId: string, summary?: string): void;
327
+ /** spec/agents.md §4: with `content`, the write also carries Zanii's memory entry (a salted
328
+ * commitment, chained after `prev`, else this session's last entry). Keep the salt it returns. */
329
+ memoryWrite(memoryId: string, summary?: string, opts?: {
330
+ content: string;
331
+ kind?: string;
332
+ prev?: Record<string, unknown>;
333
+ agent?: string;
334
+ tags?: string[];
335
+ }): {
336
+ entry: Record<string, unknown>;
337
+ salt: string;
338
+ } | undefined;
238
339
  memoryRevoke(memoryId: string, reason?: string): void;
239
340
  memoryRead(memoryIds: string[], query?: string): void;
240
341
  /** Ships what's pending now. Resolves true when everything recorded is acknowledged. With
@@ -245,6 +346,10 @@ export declare class BlackboxSession {
245
346
  /** Audit S17: the gateway's view of this session (blocked? findings?), or null if it can't be
246
347
  * read. Lets an agent react to its own block before its next model call. Never throws. */
247
348
  state(): Promise<SessionState | null>;
349
+ /** spec/findings.md §13.1: what in an answer (the latest, or the one at `seq`) would hold it,
350
+ * for an app that streams the answer by itself: `{seq, risks, hold}`. `hold` says whether the
351
+ * server would hold it. `null` when there's no answer yet or the gateway can't be asked. */
352
+ checkAnswer(seq?: number): Promise<AnswerCheck | null>;
248
353
  /** spec/approvals.md §2: asks a second person before running one of the agent's own risky tools,
249
354
  * and waits (polling) for the answer. Never throws. Only `"approved"` means go ahead; anything
250
355
  * else (`rejected`, `timeout`, or `error` when the gateway can't be asked) means don't. */