@zanii/blackbox 0.20.1 → 0.21.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.
@@ -103,4 +103,4 @@ export declare function verifyRegression(events: Ev[]): Finding[];
103
103
  /** Stage 2 R4, judged at close: a run labelled (or claimed) a success that its own record contradicts:
104
104
  * a check's last result failed, or the last tool call failed with nothing succeeding after it. */
105
105
  export declare function falseSuccess(events: Ev[], now: number): Finding[];
106
- export { type Lessons, lessons, POWER_KINDS, promptChanged, SELF_CHANGE_KINDS, selfChanges, systemPromptHash, } from "./learning.ts";
106
+ export { type Growth, growth, isoWeek, type Lessons, lessons, POWER_KINDS, promptChanged, SELF_CHANGE_KINDS, selfChanges, systemPromptHash, toolsHash, } from "./learning.ts";
@@ -586,4 +586,4 @@ export function falseSuccess(events, now) {
586
586
  : "The agent claimed it was done, but its last tool call failed and nothing succeeded after it.");
587
587
  return [];
588
588
  }
589
- export { lessons, POWER_KINDS, promptChanged, SELF_CHANGE_KINDS, selfChanges, systemPromptHash, } from "./learning.js";
589
+ export { growth, isoWeek, lessons, POWER_KINDS, promptChanged, SELF_CHANGE_KINDS, selfChanges, systemPromptHash, toolsHash, } from "./learning.js";
@@ -52,3 +52,62 @@ export interface Lessons {
52
52
  * own prompt. Codes and counts only, never content.
53
53
  */
54
54
  export declare function lessons(summaries: readonly Summary[], label: string | null): Lessons;
55
+ /**
56
+ * spec/fleet.md §9: the fingerprint of a model request's tool definitions: `sha256:` + hex over the
57
+ * canonical JSON of the sorted list of each tool's canonical JSON (so their order doesn't matter;
58
+ * Anthropic's `cache_control` on a tool is left out, it moves without changing the tool), from
59
+ * `tools` (Anthropic, OpenAI, Gemini) or Bedrock's `toolConfig.tools`. Null when there are none.
60
+ */
61
+ export declare function toolsHash(body: unknown): string | null;
62
+ /** The ISO week of a UTC timestamp: `2026-W41`. */
63
+ export declare function isoWeek(ts: string): string;
64
+ export interface Growth {
65
+ label: string | null;
66
+ sessions: number;
67
+ autonomy: Array<{
68
+ week: string;
69
+ sessions: number;
70
+ success: number;
71
+ failed: number;
72
+ autonomous: number;
73
+ autonomy_ppm: number;
74
+ failure_ppm: number;
75
+ }>;
76
+ versions: Array<{
77
+ id: string;
78
+ prompt: string | null;
79
+ tools: string | null;
80
+ model: string | null;
81
+ changed: string[];
82
+ sessions: number;
83
+ first_seen: string;
84
+ last_seen: string;
85
+ success_ppm: number;
86
+ spend_per_run_micro_usd: number;
87
+ human_steps: number;
88
+ declared: boolean;
89
+ }>;
90
+ versions_total: number;
91
+ learned: Array<{
92
+ session_id: string;
93
+ seq: number;
94
+ at: string;
95
+ kind: string;
96
+ approval_id: string | null;
97
+ approved: number;
98
+ corrected: number;
99
+ unknown: number;
100
+ checked: boolean;
101
+ }>;
102
+ hints: string[];
103
+ }
104
+ /**
105
+ * spec/fleet.md §9: how one agent (its label) grows, from its closed sessions' summaries, newest
106
+ * first. Autonomy per ISO week (a run done alone is a success with no human step, shown next to the
107
+ * failed runs, so an agent that stops asking and fails doesn't look better); its versions (the
108
+ * gateway's prompt and tools fingerprints and the model) with what changed and how each did; and
109
+ * the learned log, each self-change with the approvals it names checked against the window's.
110
+ */
111
+ export declare function growth(summaries: readonly Summary[], label: string | null, options?: {
112
+ weeks?: number;
113
+ }): Growth;
@@ -189,3 +189,142 @@ export function lessons(summaries, label) {
189
189
  brief,
190
190
  };
191
191
  }
192
+ /**
193
+ * spec/fleet.md §9: the fingerprint of a model request's tool definitions: `sha256:` + hex over the
194
+ * canonical JSON of the sorted list of each tool's canonical JSON (so their order doesn't matter;
195
+ * Anthropic's `cache_control` on a tool is left out, it moves without changing the tool), from
196
+ * `tools` (Anthropic, OpenAI, Gemini) or Bedrock's `toolConfig.tools`. Null when there are none.
197
+ */
198
+ export function toolsHash(body) {
199
+ if (!isObj(body))
200
+ return null;
201
+ const list = Array.isArray(body.tools)
202
+ ? body.tools
203
+ : isObj(body.toolConfig) && Array.isArray(body.toolConfig.tools)
204
+ ? body.toolConfig.tools
205
+ : [];
206
+ if (!list.length)
207
+ return null;
208
+ const parts = list
209
+ .map((t) => canonical(isObj(t) ? Object.fromEntries(Object.entries(t).filter(([k]) => k !== "cache_control")) : t))
210
+ .sort();
211
+ return `sha256:${createHash("sha256").update(canonical(parts)).digest("hex")}`;
212
+ }
213
+ /** The ISO week of a UTC timestamp: `2026-W41`. */
214
+ export function isoWeek(ts) {
215
+ const d = new Date(Date.UTC(Number(ts.slice(0, 4)), Number(ts.slice(5, 7)) - 1, Number(ts.slice(8, 10))));
216
+ d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7)); // the week's Thursday names its year
217
+ const year = d.getUTCFullYear();
218
+ const week = Math.floor((d.getTime() - Date.UTC(year, 0, 1)) / 86_400_000 / 7) + 1;
219
+ return `${year}-W${String(week).padStart(2, "0")}`;
220
+ }
221
+ const ppm = (n, d) => (d ? Math.floor((n * 1_000_000) / d) : 0);
222
+ const VERSIONS = 20;
223
+ const LEARNED = 50;
224
+ /**
225
+ * spec/fleet.md §9: how one agent (its label) grows, from its closed sessions' summaries, newest
226
+ * first. Autonomy per ISO week (a run done alone is a success with no human step, shown next to the
227
+ * failed runs, so an agent that stops asking and fails doesn't look better); its versions (the
228
+ * gateway's prompt and tools fingerprints and the model) with what changed and how each did; and
229
+ * the learned log, each self-change with the approvals it names checked against the window's.
230
+ */
231
+ export function growth(summaries, label, options = {}) {
232
+ const weeks = options.weeks ?? 12;
233
+ const runs = summaries.filter((s) => s.closed && s.label === label);
234
+ const byWeek = new Map();
235
+ for (const s of runs) {
236
+ const week = isoWeek(s.started_at);
237
+ const w = byWeek.get(week) ?? {
238
+ week,
239
+ sessions: 0,
240
+ success: 0,
241
+ failed: 0,
242
+ autonomous: 0,
243
+ autonomy_ppm: 0,
244
+ failure_ppm: 0,
245
+ };
246
+ w.sessions++;
247
+ if (s.outcome === "success") {
248
+ w.success++;
249
+ if (!s.human_steps)
250
+ w.autonomous++;
251
+ }
252
+ else if (s.outcome === "failure" || s.outcome === "abandoned")
253
+ w.failed++;
254
+ byWeek.set(week, w);
255
+ }
256
+ const autonomy = [...byWeek.values()]
257
+ .sort((a, b) => (a.week < b.week ? -1 : 1))
258
+ .slice(-weeks)
259
+ .map((w) => ({
260
+ ...w,
261
+ autonomy_ppm: ppm(w.autonomous, w.sessions),
262
+ failure_ppm: ppm(w.failed, w.sessions),
263
+ }));
264
+ const groups = new Map();
265
+ for (const s of runs)
266
+ if (s.version)
267
+ groups.set(s.version.id, [...(groups.get(s.version.id) ?? []), s]);
268
+ const all = [...groups.values()]
269
+ .map((ss) => {
270
+ const v = ss[0]?.version;
271
+ const starts = ss.map((s) => s.started_at).sort();
272
+ return {
273
+ id: v.id,
274
+ prompt: v.prompt,
275
+ tools: v.tools,
276
+ model: v.model,
277
+ changed: [],
278
+ sessions: ss.length,
279
+ first_seen: starts[0],
280
+ last_seen: starts[starts.length - 1],
281
+ success_ppm: ppm(ss.filter((s) => s.outcome === "success").length, ss.length),
282
+ spend_per_run_micro_usd: Math.floor(ss.reduce((n, s) => n + s.spend_micro_usd, 0) / ss.length),
283
+ human_steps: ss.reduce((n, s) => n + (s.human_steps ?? 0), 0),
284
+ declared: ss.some((s) => (s.self_changes?.length ?? 0) > 0),
285
+ };
286
+ })
287
+ .sort((a, b) => a.first_seen < b.first_seen ? -1 : a.first_seen > b.first_seen ? 1 : a.id < b.id ? -1 : 1);
288
+ for (let i = 1; i < all.length; i++) {
289
+ const [p, v] = [all[i - 1], all[i]];
290
+ v.changed = ["prompt", "tools", "model"].filter((k) => p[k] !== v[k]);
291
+ }
292
+ const decided = new Map();
293
+ for (const s of runs)
294
+ for (const [id, d] of Object.entries(s.approvals ?? {}))
295
+ decided.set(id, d);
296
+ const learned = [];
297
+ for (const s of runs)
298
+ for (const c of [...(s.self_changes ?? [])].reverse()) {
299
+ if (learned.length === LEARNED)
300
+ break;
301
+ const found = c.taught_by.map((id) => decided.get(id));
302
+ const approved = found.filter((d) => d === "approve").length;
303
+ const corrected = found.filter((d) => d === "reject").length;
304
+ const unknown = found.length - approved - corrected;
305
+ learned.push({
306
+ session_id: s.session_id,
307
+ seq: c.seq,
308
+ at: c.at,
309
+ kind: c.kind,
310
+ approval_id: c.approval_id,
311
+ approved,
312
+ corrected,
313
+ unknown,
314
+ checked: found.length > 0 && unknown === 0,
315
+ });
316
+ }
317
+ const hints = [];
318
+ // a prompt that embeds the date or a request id is a new version every run
319
+ if (runs.length >= 10 && all.length * 2 > runs.length)
320
+ hints.push("many_versions");
321
+ return {
322
+ label,
323
+ sessions: runs.length,
324
+ autonomy,
325
+ versions: all.slice(-VERSIONS),
326
+ versions_total: all.length,
327
+ learned,
328
+ hints,
329
+ };
330
+ }
@@ -33,7 +33,31 @@ export interface Summary {
33
33
  };
34
34
  blocked_by: string | null;
35
35
  near_misses: number;
36
+ /** spec/fleet.md §9: which version of the agent ran (only when it called a model) */
37
+ version?: {
38
+ id: string;
39
+ prompt: string | null;
40
+ tools: string | null;
41
+ model: string | null;
42
+ };
43
+ /** spec/fleet.md §9: the times a person stepped in (only when one did) */
44
+ human_steps?: number;
45
+ /** spec/fleet.md §9: the changes the agent reported to itself, the first 10 */
46
+ self_changes?: SelfChangeEntry[];
47
+ /** spec/fleet.md §9: approvals a person decided here, the first 50: id → approve | reject */
48
+ approvals?: Record<string, "approve" | "reject">;
49
+ }
50
+ export interface SelfChangeEntry {
51
+ seq: number;
52
+ at: string;
53
+ kind: string;
54
+ approval_id: string | null;
55
+ taught_by: string[];
36
56
  }
57
+ /** An approval id: what a lesson may point at, and nothing else (spec/fleet.md §9). */
58
+ export declare const APPROVAL_ID: RegExp;
59
+ /** spec/fleet.md §9: the approval ids a self-change names as what taught it, at most 20. */
60
+ export declare function taughtBy(v: unknown): string[];
37
61
  export declare function sessionSummary(lines: readonly string[], bodies: Bodies, prices: LoadedPrices): Summary;
38
62
  /** The incident report (spec/fleet.md §2) in Markdown, in English or Arabic. */
39
63
  export declare function incidentReport(lines: readonly string[], bodies: Bodies, prices: LoadedPrices, lang: "en" | "ar"): string;
@@ -10,6 +10,45 @@ import { authorityTimeline } from "../authority/index.js";
10
10
  import { formatAed, sessionCost, toAedFils } from "../cost/index.js";
11
11
  import { callsOf } from "../reconcile/record.js";
12
12
  import { canonical } from "../reconcile/shared.js";
13
+ /** An approval id: what a lesson may point at, and nothing else (spec/fleet.md §9). */
14
+ export const APPROVAL_ID = /^apr_[0-9a-f]{18}$/;
15
+ /** spec/fleet.md §9: the approval ids a self-change names as what taught it, at most 20. */
16
+ export function taughtBy(v) {
17
+ if (!Array.isArray(v))
18
+ return [];
19
+ const out = [];
20
+ for (const x of v)
21
+ if (typeof x === "string" && APPROVAL_ID.test(x) && !out.includes(x) && out.length < 20)
22
+ out.push(x);
23
+ return out;
24
+ }
25
+ /** The agent's version: the first request's prompt and tools fingerprints, the first model. Only
26
+ * when the gateway fingerprinted that request: a version is evidence, never a guess. */
27
+ function versionOf(events) {
28
+ const req = events.find((e) => e.kind === "llm.request");
29
+ const str = (v) => (typeof v === "string" ? v : null);
30
+ const prompt = str(req?.meta.system_sha256);
31
+ const tools = str(req?.meta.tools_sha256);
32
+ if (prompt === null && tools === null)
33
+ return undefined;
34
+ const model = str(events.find((e) => e.kind === "llm.response")?.meta.model);
35
+ const id = `ver_${createHash("sha256")
36
+ .update(canonical([prompt, tools, model]))
37
+ .digest("hex")
38
+ .slice(0, 12)}`;
39
+ return { id, prompt, tools, model };
40
+ }
41
+ /** A person stepping in: an approval they decided, a resume, an authority change, an `ask`. */
42
+ function isHumanStep(e) {
43
+ if (e.kind !== "control")
44
+ return false;
45
+ const m = e.meta;
46
+ if (m.action === "approval")
47
+ return (m.decision === "approve" || m.decision === "reject") && m.by !== "classifier";
48
+ if (m.action === "policy_check")
49
+ return m.decision === "ask";
50
+ return m.action === "resume" || m.action === "authority";
51
+ }
13
52
  const sorted = (m) => Object.fromEntries(Object.keys(m)
14
53
  .sort()
15
54
  .map((k) => [k, m[k]]));
@@ -30,7 +69,22 @@ export function sessionSummary(lines, bodies, prices) {
30
69
  let outcome = null;
31
70
  let blockedBy = null;
32
71
  let nearMisses = 0;
72
+ let humanSteps = 0;
73
+ const approvals = {};
74
+ let decided = 0;
33
75
  for (const e of events) {
76
+ if (isHumanStep(e))
77
+ humanSteps++;
78
+ if (e.kind === "control" &&
79
+ e.meta.action === "approval" &&
80
+ (e.meta.decision === "approve" || e.meta.decision === "reject") &&
81
+ e.meta.by !== "classifier" &&
82
+ typeof e.meta.approval_id === "string" &&
83
+ !(e.meta.approval_id in approvals) &&
84
+ decided < 50) {
85
+ approvals[e.meta.approval_id] = e.meta.decision;
86
+ decided++;
87
+ }
34
88
  if (e.kind === "tool.call" &&
35
89
  e.meta.method === "tools/call" &&
36
90
  typeof e.meta.tool === "string") {
@@ -59,8 +113,23 @@ export function sessionSummary(lines, bodies, prices) {
59
113
  nearMisses++;
60
114
  }
61
115
  const claims = { satisfied: 0, failed: 0, unverified: 0 };
62
- for (const c of landing(eventsOf(lines, bodies), lines, bodies).claims)
116
+ const evs = eventsOf(lines, bodies);
117
+ for (const c of landing(evs, lines, bodies).claims)
63
118
  claims[c.verdict]++;
119
+ const selfChanges = evs
120
+ .filter((e) => e.kind === "sdk.event" && e.meta.type === "self_change")
121
+ .slice(0, 10)
122
+ .map((e) => {
123
+ const d = e.data ?? {};
124
+ return {
125
+ seq: e.seq,
126
+ at: e.ts,
127
+ kind: typeof d.kind === "string" && d.kind ? d.kind : "other",
128
+ approval_id: typeof d.approval_id === "string" ? d.approval_id : null,
129
+ taught_by: taughtBy(d.taught_by),
130
+ };
131
+ });
132
+ const version = versionOf(events);
64
133
  const inControl = authorityTimeline(lines).at(-1)?.mode ?? "agent";
65
134
  return {
66
135
  session_id: first.session_id,
@@ -89,6 +158,10 @@ export function sessionSummary(lines, bodies, prices) {
89
158
  claims,
90
159
  blocked_by: blockedBy,
91
160
  near_misses: nearMisses,
161
+ ...(version ? { version } : {}),
162
+ ...(humanSteps ? { human_steps: humanSteps } : {}),
163
+ ...(selfChanges.length ? { self_changes: selfChanges } : {}),
164
+ ...(decided ? { approvals } : {}),
92
165
  };
93
166
  }
94
167
  const T = {
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { A2A_STREAMING, type AgentCardReport, a2aMethod, a2aRequestMeta, a2aResp
2
2
  export { causality, memoryXray } from "./agents/index.ts";
3
3
  export { type DetectorOptions, detectors, fingerprint } from "./analysis/detectors.ts";
4
4
  export { FAULTS, type Fault, faultOf } from "./analysis/faults.ts";
5
- export { type Accuracy, type AnalyzeOptions, type AnswerClaims, analyze, answerClaims, answerCredential, answerRisk, answersOf, answerText, type ClaimEntry, type ClaimVerdict, type CriterionResult, canonicalNumber, checkAgainstPacks, checkFlightPlan, checkpointReplay, detectorAccuracy, dualWitness, Evidence, eventsOf, type Fact, type Finding as AnalysisFinding, factsIn, findingKey, type GroundedClaim, groundingFindings, groundingRequest, HOLD_TRIGGERS, identifiersIn, judgeFindings, judgeRequest, type Label as AccuracyLabel, type Landing, landing, lessons, loadReferencePacks, modelGroundingFindings, normaliseValue, OUTCOMES, type Outcome, type OutcomeRollup, packsFor, parseGroundingAnswer, parseJudgeAnswer, promptChanged, type ReferenceFact, type ReferencePack, type ReferenceVerdict, referenceContext, rollup, type Severity, schemaProblem, secretSent, selfChanges, systemPromptHash, toolDrifted, toolHallucinations, userDownvotes, verifyAnswerCredential, type WasteReason, type WasteReport, waste, wilsonLow, } from "./analysis/index.ts";
5
+ export { type Accuracy, type AnalyzeOptions, type AnswerClaims, analyze, answerClaims, answerCredential, answerRisk, answersOf, answerText, type ClaimEntry, type ClaimVerdict, type CriterionResult, canonicalNumber, checkAgainstPacks, checkFlightPlan, checkpointReplay, detectorAccuracy, dualWitness, Evidence, eventsOf, type Fact, type Finding as AnalysisFinding, factsIn, findingKey, type GroundedClaim, groundingFindings, groundingRequest, growth, HOLD_TRIGGERS, identifiersIn, isoWeek, judgeFindings, judgeRequest, type Label as AccuracyLabel, type Landing, landing, lessons, loadReferencePacks, modelGroundingFindings, normaliseValue, OUTCOMES, type Outcome, type OutcomeRollup, packsFor, parseGroundingAnswer, parseJudgeAnswer, promptChanged, type ReferenceFact, type ReferencePack, type ReferenceVerdict, referenceContext, rollup, type Severity, schemaProblem, secretSent, selfChanges, systemPromptHash, toolDrifted, toolHallucinations, toolsHash, userDownvotes, verifyAnswerCredential, type WasteReason, type WasteReport, waste, wilsonLow, } from "./analysis/index.ts";
6
6
  export { injectionFindings, injectionRule } from "./analysis/injection.ts";
7
7
  export { MITRE_ATLAS, OWASP_ASI, TAXONOMY, type Taxonomy, taxonomyOf, } from "./analysis/taxonomy.ts";
8
8
  export { type ApprovalFinding, approvalConfirmation, approvalFindings, argsHash, verifyConfirmation, } from "./approvals/index.ts";
@@ -30,7 +30,7 @@ export { openSegments, type SealedSegment } from "./encryption/index.ts";
30
30
  export { type EvidencePack, type EvidencePackOptions, evidencePack, renderEvidencePack, } from "./evidence/index.ts";
31
31
  export { buildEquivocationProof, compareViews, cosignLedgerSth, verifyEquivocationProof, verifyLedgerCosignature, } from "./federation/index.ts";
32
32
  export { buildOwnerAttestation, buildPayment, CURRENCIES, FTA_WALL_ID, financeFindings, formatMoney, ftaPayload, parseMoney, paymentsOf, screenCounterparty, screeningPayload, verifyOwnerAttestation, verifyOwnerBinding, } from "./finance/index.ts";
33
- export { aggregateFindings, airworthiness, type Collision, collisions, didFromSeed, type FindingGroup, incidentReport, logbook, type Summary, sessionSummary, signatureOf, signatures, signCertificate, verifyCertificate, } from "./fleet/index.ts";
33
+ export { APPROVAL_ID, aggregateFindings, airworthiness, type Collision, collisions, didFromSeed, type FindingGroup, incidentReport, logbook, type Summary, sessionSummary, signatureOf, signatures, signCertificate, taughtBy, verifyCertificate, } from "./fleet/index.ts";
34
34
  export { copilotContext, copilotPrompt, type Insights, insights, parseCopilotAnswer, } from "./fleet/insights.ts";
35
35
  export { type AppealItem, type AppealPack, appealPack, decisionPayload, GOV_DECISION_KINDS, renderAppealPack, ruleConsistency, signSubjectClaim, subjectTag, verifyDecision, verifyFactors, verifySubjectClaim, } from "./gov/index.ts";
36
36
  export { accessPayload, breakGlass, checkEpisodeTag, clinicianConfirmation, type EpisodeItem, episodeSeed, episodesPack, episodeTag, healthFindings, protocolTrail, recommendationHash, recommendationPayload, verifyCommitment, verifyHealthSignature, } from "./health/index.ts";
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ export { A2A_STREAMING, a2aMethod, a2aRequestMeta, a2aResponseMeta, a2aRestMetho
4
4
  export { causality, memoryXray } from "./agents/index.js";
5
5
  export { detectors, fingerprint } from "./analysis/detectors.js";
6
6
  export { FAULTS, faultOf } from "./analysis/faults.js";
7
- export { analyze, answerClaims, answerCredential, answerRisk, answersOf, answerText, canonicalNumber, checkAgainstPacks, checkFlightPlan, checkpointReplay, detectorAccuracy, dualWitness, Evidence, eventsOf, factsIn, findingKey, groundingFindings, groundingRequest, HOLD_TRIGGERS, identifiersIn, judgeFindings, judgeRequest, landing, lessons, loadReferencePacks, modelGroundingFindings, normaliseValue, OUTCOMES, packsFor, parseGroundingAnswer, parseJudgeAnswer, promptChanged, referenceContext, rollup, schemaProblem, secretSent, selfChanges, systemPromptHash, toolDrifted, toolHallucinations, userDownvotes, verifyAnswerCredential, waste, wilsonLow, } from "./analysis/index.js";
7
+ export { analyze, answerClaims, answerCredential, answerRisk, answersOf, answerText, canonicalNumber, checkAgainstPacks, checkFlightPlan, checkpointReplay, detectorAccuracy, dualWitness, Evidence, eventsOf, factsIn, findingKey, groundingFindings, groundingRequest, growth, HOLD_TRIGGERS, identifiersIn, isoWeek, judgeFindings, judgeRequest, landing, lessons, loadReferencePacks, modelGroundingFindings, normaliseValue, OUTCOMES, packsFor, parseGroundingAnswer, parseJudgeAnswer, promptChanged, referenceContext, rollup, schemaProblem, secretSent, selfChanges, systemPromptHash, toolDrifted, toolHallucinations, toolsHash, userDownvotes, verifyAnswerCredential, waste, wilsonLow, } from "./analysis/index.js";
8
8
  export { injectionFindings, injectionRule } from "./analysis/injection.js";
9
9
  export { MITRE_ATLAS, OWASP_ASI, TAXONOMY, taxonomyOf, } from "./analysis/taxonomy.js";
10
10
  export { approvalConfirmation, approvalFindings, argsHash, verifyConfirmation, } from "./approvals/index.js";
@@ -32,7 +32,7 @@ export { openSegments } from "./encryption/index.js";
32
32
  export { evidencePack, renderEvidencePack, } from "./evidence/index.js";
33
33
  export { buildEquivocationProof, compareViews, cosignLedgerSth, verifyEquivocationProof, verifyLedgerCosignature, } from "./federation/index.js";
34
34
  export { buildOwnerAttestation, buildPayment, CURRENCIES, FTA_WALL_ID, financeFindings, formatMoney, ftaPayload, parseMoney, paymentsOf, screenCounterparty, screeningPayload, verifyOwnerAttestation, verifyOwnerBinding, } from "./finance/index.js";
35
- export { aggregateFindings, airworthiness, collisions, didFromSeed, incidentReport, logbook, sessionSummary, signatureOf, signatures, signCertificate, verifyCertificate, } from "./fleet/index.js";
35
+ export { APPROVAL_ID, aggregateFindings, airworthiness, collisions, didFromSeed, incidentReport, logbook, sessionSummary, signatureOf, signatures, signCertificate, taughtBy, verifyCertificate, } from "./fleet/index.js";
36
36
  export { copilotContext, copilotPrompt, insights, parseCopilotAnswer, } from "./fleet/insights.js";
37
37
  export { appealPack, decisionPayload, GOV_DECISION_KINDS, renderAppealPack, ruleConsistency, signSubjectClaim, subjectTag, verifyDecision, verifyFactors, verifySubjectClaim, } from "./gov/index.js";
38
38
  export { accessPayload, breakGlass, checkEpisodeTag, clinicianConfirmation, episodeSeed, episodesPack, episodeTag, healthFindings, protocolTrail, recommendationHash, recommendationPayload, verifyCommitment, verifyHealthSignature, } from "./health/index.js";
@@ -388,6 +388,8 @@ export declare class BlackboxSession {
388
388
  after?: string;
389
389
  reason?: string;
390
390
  approvalId?: string;
391
+ /** The approvals (ids) that taught it this, and nothing else: never content (fleet.md §9). */
392
+ taughtBy?: readonly string[];
391
393
  }): void;
392
394
  /** spec/findings.md §13.1: what in an answer (the latest, or the one at `seq`) would hold it,
393
395
  * for an app that streams the answer by itself: `{seq, risks, hold}`. `hold` says whether the
@@ -10,6 +10,7 @@ import { SELF_CHANGE_KINDS } from "../analysis/learning.js";
10
10
  import { argsHash } from "../approvals/index.js";
11
11
  import { attest, isReadOnly, parseCommand } from "../attest/index.js";
12
12
  import { buildPayment, ftaPayload, screeningPayload, } from "../finance/index.js";
13
+ import { taughtBy } from "../fleet/index.js";
13
14
  import { decisionPayload } from "../gov/index.js";
14
15
  import { accessPayload, checkEpisodeTag, recommendationPayload, verifyHealthSignature, } from "../health/index.js";
15
16
  import { memoryEntry } from "../memory/index.js";
@@ -624,6 +625,7 @@ export class BlackboxSession {
624
625
  * pass its id), or it is SELF_CHANGE_UNAPPROVED. Never throws.
625
626
  */
626
627
  selfChange(kind, options = {}) {
628
+ const taught = taughtBy(options.taughtBy);
627
629
  const k = SELF_CHANGE_KINDS.includes(kind) ? kind : "other";
628
630
  const sha = (text) => `sha256:${createHash("sha256").update(text).digest("hex")}`;
629
631
  this.event("self_change", k, {
@@ -632,6 +634,7 @@ export class BlackboxSession {
632
634
  ...(options.after === undefined ? {} : { after_sha256: sha(options.after) }),
633
635
  ...(options.reason ? { reason: options.reason.slice(0, 500) } : {}),
634
636
  ...(options.approvalId ? { approval_id: options.approvalId.slice(0, 128) } : {}),
637
+ ...(taught.length ? { taught_by: taught } : {}),
635
638
  });
636
639
  }
637
640
  /** spec/findings.md §13.1: what in an answer (the latest, or the one at `seq`) would hold it,
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.20.1";
1
+ export declare const VERSION = "0.21.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.20.1";
2
+ export const VERSION = "0.21.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.20.1",
4
+ "version": "0.21.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",