aira-sdk 1.0.0 → 2.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.
- package/README.md +300 -104
- package/dist/client.d.ts +195 -4
- package/dist/client.js +347 -6
- package/dist/extras/index.d.ts +49 -2
- package/dist/extras/index.js +90 -3
- package/dist/extras/langchain.d.ts +69 -18
- package/dist/extras/langchain.js +118 -35
- package/dist/extras/mcp.d.ts +24 -3
- package/dist/extras/mcp.js +70 -9
- package/dist/extras/openai-agents.d.ts +48 -16
- package/dist/extras/openai-agents.js +85 -29
- package/dist/extras/vercel-ai.d.ts +54 -17
- package/dist/extras/vercel-ai.js +104 -30
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -1
- package/dist/session.d.ts +15 -4
- package/dist/session.js +11 -3
- package/dist/types.d.ts +197 -20
- package/dist/types.js +26 -5
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActionReceipt, ActionDetail, AgentDetail, AgentVersion, EvidencePackage, ComplianceSnapshot, EscrowAccount, EscrowTransaction, VerifyResult, PaginatedList } from "./types";
|
|
1
|
+
import { Authorization, ActionReceipt, ActionDetail, AgentDetail, AgentVersion, CosignResult, EvidencePackage, ComplianceSnapshot, EscrowAccount, EscrowTransaction, VerifyResult, PaginatedList, ComplianceReport, ComplianceReportListResponse, ComplianceReportVerification, ActionExplanation, ExplanationVerification, OutputPolicy, OutputPolicyUpdate } from "./types";
|
|
2
2
|
import { AiraSession } from "./session";
|
|
3
3
|
export interface AiraOptions {
|
|
4
4
|
apiKey: string;
|
|
@@ -16,21 +16,51 @@ export declare class Aira {
|
|
|
16
16
|
private get;
|
|
17
17
|
private post;
|
|
18
18
|
private put;
|
|
19
|
+
private patch;
|
|
19
20
|
private del;
|
|
20
21
|
private paginated;
|
|
21
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Step 1 — Authorize an action BEFORE it executes.
|
|
24
|
+
*
|
|
25
|
+
* Returns an `Authorization` with a status:
|
|
26
|
+
* - "authorized" → safe to execute the action, then call `notarize()`
|
|
27
|
+
* - "pending_approval" → enqueue `action_id` and wait for human approval
|
|
28
|
+
*
|
|
29
|
+
* If a policy denies the action, this throws `AiraError` with code
|
|
30
|
+
* `POLICY_DENIED` (HTTP 403). Duplicate idempotent requests throw
|
|
31
|
+
* `DUPLICATE_REQUEST` (HTTP 409).
|
|
32
|
+
*/
|
|
33
|
+
authorize(params: {
|
|
22
34
|
actionType: string;
|
|
23
35
|
details: string;
|
|
24
36
|
agentId?: string;
|
|
25
37
|
agentVersion?: string;
|
|
38
|
+
instructionHash?: string;
|
|
26
39
|
modelId?: string;
|
|
27
40
|
modelVersion?: string;
|
|
28
|
-
instructionHash?: string;
|
|
29
41
|
parentActionId?: string;
|
|
42
|
+
endpointUrl?: string;
|
|
30
43
|
storeDetails?: boolean;
|
|
31
44
|
idempotencyKey?: string;
|
|
32
45
|
requireApproval?: boolean;
|
|
33
46
|
approvers?: string[];
|
|
47
|
+
systemPromptHash?: string;
|
|
48
|
+
toolInputsHash?: string;
|
|
49
|
+
modelParams?: Record<string, unknown>;
|
|
50
|
+
executionEnv?: Record<string, unknown>;
|
|
51
|
+
}): Promise<Authorization>;
|
|
52
|
+
/**
|
|
53
|
+
* Step 2 — Notarize the outcome of an already-authorized action.
|
|
54
|
+
*
|
|
55
|
+
* Call this AFTER executing the action. Outcome is "completed" by default;
|
|
56
|
+
* pass "failed" if the action ran but failed so the audit trail captures
|
|
57
|
+
* the failure. The returned `ActionReceipt` carries the Ed25519 signature
|
|
58
|
+
* and RFC 3161 timestamp token when the status is "notarized".
|
|
59
|
+
*/
|
|
60
|
+
notarize(params: {
|
|
61
|
+
actionId: string;
|
|
62
|
+
outcome?: "completed" | "failed";
|
|
63
|
+
outcomeDetails?: string;
|
|
34
64
|
}): Promise<ActionReceipt>;
|
|
35
65
|
getAction(actionId: string): Promise<ActionDetail>;
|
|
36
66
|
listActions(params?: {
|
|
@@ -40,7 +70,17 @@ export declare class Aira {
|
|
|
40
70
|
agentId?: string;
|
|
41
71
|
status?: string;
|
|
42
72
|
}): Promise<PaginatedList<ActionDetail>>;
|
|
43
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Add a human co-signature to an action that already exists.
|
|
75
|
+
*
|
|
76
|
+
* This is distinct from the authorization gate (it runs against `/cosign`,
|
|
77
|
+
* not `/authorize`). It records that a specific human has acknowledged or
|
|
78
|
+
* signed off on an action that was already authorized and notarized.
|
|
79
|
+
* Requires JWT auth (dashboard user, not an API key).
|
|
80
|
+
*/
|
|
81
|
+
cosign(params: {
|
|
82
|
+
actionId: string;
|
|
83
|
+
}): Promise<CosignResult>;
|
|
44
84
|
setLegalHold(actionId: string): Promise<Record<string, unknown>>;
|
|
45
85
|
releaseLegalHold(actionId: string): Promise<Record<string, unknown>>;
|
|
46
86
|
getActionChain(actionId: string): Promise<Record<string, unknown>[]>;
|
|
@@ -163,6 +203,157 @@ export declare class Aira {
|
|
|
163
203
|
attestReputation(slug: string, counterpartyDid: string, actionId: string, attestation: string, signature: string): Promise<Record<string, unknown>>;
|
|
164
204
|
/** Verify a reputation score by returning inputs and score_hash. */
|
|
165
205
|
verifyReputation(slug: string): Promise<Record<string, unknown>>;
|
|
206
|
+
/**
|
|
207
|
+
* Get all reproducibility metadata stored for an action.
|
|
208
|
+
*
|
|
209
|
+
* Returns the system_prompt_hash, tool_inputs_hash, model_params,
|
|
210
|
+
* execution_env, and other knobs that an external replay tool
|
|
211
|
+
* needs to confirm it has the same inputs as the original run.
|
|
212
|
+
*/
|
|
213
|
+
getReplayContext(actionId: string): Promise<Record<string, unknown>>;
|
|
214
|
+
/**
|
|
215
|
+
* Seal a regulator-ready evidence bundle for a date range.
|
|
216
|
+
*
|
|
217
|
+
* `framework` must be one of: `eu_ai_act_art12`, `iso_42001`,
|
|
218
|
+
* `aiuc_1`, `soc_2_cc7`, `raw`.
|
|
219
|
+
*/
|
|
220
|
+
createComplianceBundle(params: {
|
|
221
|
+
framework: "eu_ai_act_art12" | "iso_42001" | "aiuc_1" | "soc_2_cc7" | "raw";
|
|
222
|
+
periodStart: string;
|
|
223
|
+
periodEnd: string;
|
|
224
|
+
title?: string;
|
|
225
|
+
agentFilter?: string[];
|
|
226
|
+
/**
|
|
227
|
+
* Client-supplied key (unique per org) — retrying with the same key
|
|
228
|
+
* returns the original bundle and does NOT charge a second operation.
|
|
229
|
+
* Use this if your job runner may replay the call on network flakes.
|
|
230
|
+
*/
|
|
231
|
+
idempotencyKey?: string;
|
|
232
|
+
}): Promise<Record<string, unknown>>;
|
|
233
|
+
listComplianceBundles(page?: number, perPage?: number): Promise<PaginatedList<Record<string, unknown>>>;
|
|
234
|
+
getComplianceBundle(bundleId: string): Promise<Record<string, unknown>>;
|
|
235
|
+
/**
|
|
236
|
+
* Download the self-contained JSON document for the bundle. The
|
|
237
|
+
* exported document inlines every receipt's signed payload + signature
|
|
238
|
+
* and the JWKS URL so an auditor can re-verify offline.
|
|
239
|
+
*/
|
|
240
|
+
exportComplianceBundle(bundleId: string): Promise<Record<string, unknown>>;
|
|
241
|
+
getBundleInclusionProof(bundleId: string, receiptId: string): Promise<Record<string, unknown>>;
|
|
242
|
+
/**
|
|
243
|
+
* Score the agent's recent behavior against its active baseline.
|
|
244
|
+
* Read-only — does NOT persist an alert. Use this for dashboards.
|
|
245
|
+
*/
|
|
246
|
+
getDriftStatus(agentId: string, lookbackHours?: number): Promise<Record<string, unknown>>;
|
|
247
|
+
/** Compute a behavioral baseline from production action history. */
|
|
248
|
+
computeDriftBaseline(params: {
|
|
249
|
+
agentId: string;
|
|
250
|
+
windowStart: string;
|
|
251
|
+
windowEnd: string;
|
|
252
|
+
activate?: boolean;
|
|
253
|
+
}): Promise<Record<string, unknown>>;
|
|
254
|
+
/** Seed a baseline from a config dict (for cold-start agents). */
|
|
255
|
+
seedSyntheticBaseline(params: {
|
|
256
|
+
agentId: string;
|
|
257
|
+
expectedDistribution: Record<string, number>;
|
|
258
|
+
expectedActionsPerDay: number;
|
|
259
|
+
activate?: boolean;
|
|
260
|
+
}): Promise<Record<string, unknown>>;
|
|
261
|
+
/** Score the current window and persist an alert if it exceeds the threshold. */
|
|
262
|
+
runDriftCheck(agentId: string, lookbackHours?: number): Promise<Record<string, unknown> | null>;
|
|
263
|
+
listDriftAlerts(agentId: string, page?: number, acknowledged?: boolean): Promise<PaginatedList<Record<string, unknown>>>;
|
|
264
|
+
acknowledgeDriftAlert(agentId: string, alertId: string): Promise<Record<string, unknown>>;
|
|
265
|
+
/**
|
|
266
|
+
* Seal every unsettled receipt for the org into a new settlement.
|
|
267
|
+
* Admin-only. Returns the new settlement, or null if there were no
|
|
268
|
+
* unsettled receipts (no-op).
|
|
269
|
+
*/
|
|
270
|
+
createSettlement(): Promise<Record<string, unknown> | null>;
|
|
271
|
+
listSettlements(page?: number, perPage?: number): Promise<PaginatedList<Record<string, unknown>>>;
|
|
272
|
+
getSettlement(settlementId: string): Promise<Record<string, unknown>>;
|
|
273
|
+
/** Get the Merkle inclusion proof for one receipt in its settlement. */
|
|
274
|
+
getSettlementInclusionProof(receiptId: string): Promise<Record<string, unknown>>;
|
|
275
|
+
/**
|
|
276
|
+
* Generate a regulatory PDF report.
|
|
277
|
+
*
|
|
278
|
+
* Frameworks:
|
|
279
|
+
* - `eu_ai_act_art12` — Annex VII technical file. Requires period.
|
|
280
|
+
* - `eu_ai_act_art9` — risk management register. Requires period.
|
|
281
|
+
* - `eu_ai_act_art6` — single-action explanation. Requires actionId.
|
|
282
|
+
* - `eu_ai_act_annex_iv` — full Annex IV technical documentation
|
|
283
|
+
* (§§1..9). Requires period. Typical use: annual file for the
|
|
284
|
+
* high-risk AI system provider obligations in Article 11.
|
|
285
|
+
*/
|
|
286
|
+
createComplianceReport(params: {
|
|
287
|
+
framework: string;
|
|
288
|
+
periodStart?: string;
|
|
289
|
+
periodEnd?: string;
|
|
290
|
+
actionId?: string;
|
|
291
|
+
agentFilter?: string[];
|
|
292
|
+
}): Promise<ComplianceReport>;
|
|
293
|
+
/** Get the metadata for a compliance report (no PDF bytes). */
|
|
294
|
+
getComplianceReport(reportId: string): Promise<ComplianceReport>;
|
|
295
|
+
/** List compliance reports with optional filters. */
|
|
296
|
+
listComplianceReports(params?: {
|
|
297
|
+
framework?: string;
|
|
298
|
+
status?: string;
|
|
299
|
+
limit?: number;
|
|
300
|
+
offset?: number;
|
|
301
|
+
}): Promise<ComplianceReportListResponse>;
|
|
302
|
+
/**
|
|
303
|
+
* Download the generated PDF as raw bytes (Uint8Array).
|
|
304
|
+
*
|
|
305
|
+
* Retries on transient 5xx and network errors (3 attempts,
|
|
306
|
+
* exponential backoff). 4xx responses surface immediately.
|
|
307
|
+
*/
|
|
308
|
+
downloadComplianceReport(reportId: string): Promise<Uint8Array>;
|
|
309
|
+
/** Verify a compliance report's signature and content hash. */
|
|
310
|
+
verifyComplianceReport(reportId: string): Promise<ComplianceReportVerification>;
|
|
311
|
+
/**
|
|
312
|
+
* Return the org's output content-scan policy.
|
|
313
|
+
*
|
|
314
|
+
* Scans apply to the `outcomeDetails` passed to `notarize()`. Mode
|
|
315
|
+
* controls behaviour:
|
|
316
|
+
* - `flag` — hits are recorded on the receipt, nothing blocked
|
|
317
|
+
* - `deny` — a hit at or above `deny_severity_threshold` makes
|
|
318
|
+
* notarize return 422 with code `OUTPUT_SCAN_VIOLATION`
|
|
319
|
+
* - `redact` — matched spans are replaced with `[REDACTED]` and
|
|
320
|
+
* the receipt signs over the cleaned bytes
|
|
321
|
+
*/
|
|
322
|
+
getOutputPolicy(): Promise<OutputPolicy>;
|
|
323
|
+
/**
|
|
324
|
+
* Merge the supplied fields into the org's output content-scan
|
|
325
|
+
* policy. Omitted fields stay at their current values. Admin role
|
|
326
|
+
* required server-side.
|
|
327
|
+
*/
|
|
328
|
+
updateOutputPolicy(updates: OutputPolicyUpdate): Promise<OutputPolicy>;
|
|
329
|
+
/**
|
|
330
|
+
* Article 6 right-to-explanation for a single action.
|
|
331
|
+
*
|
|
332
|
+
* The response includes a cryptographic ``_envelope`` — verify it
|
|
333
|
+
* later with {@link verifyActionExplanation} (the verify endpoint
|
|
334
|
+
* is public, so anyone holding the JSON can re-check it).
|
|
335
|
+
*/
|
|
336
|
+
getActionExplanation(actionId: string): Promise<ActionExplanation>;
|
|
337
|
+
/**
|
|
338
|
+
* Public verify — recompute an explanation envelope's signature.
|
|
339
|
+
*
|
|
340
|
+
* POSTs the full explanation JSON to the unauthenticated
|
|
341
|
+
* ``/verify/explanation`` endpoint. The server looks up the public
|
|
342
|
+
* key by ``_envelope.signing_key_id`` and re-derives the canonical
|
|
343
|
+
* content hash + Ed25519 signature.
|
|
344
|
+
*
|
|
345
|
+
* ``request_id`` is stripped before sending, so a saved JSON
|
|
346
|
+
* explanation verifies the same way regardless of whether the
|
|
347
|
+
* caller round-tripped it through their own logs.
|
|
348
|
+
*/
|
|
349
|
+
verifyActionExplanation(explanation: ActionExplanation | Record<string, unknown>): Promise<ExplanationVerification>;
|
|
350
|
+
/**
|
|
351
|
+
* Download the Article 6 explanation as a PDF.
|
|
352
|
+
*
|
|
353
|
+
* Retries on transient 5xx and network errors (3 attempts,
|
|
354
|
+
* exponential backoff). 4xx responses surface immediately.
|
|
355
|
+
*/
|
|
356
|
+
downloadActionExplanationPdf(actionId: string): Promise<Uint8Array>;
|
|
166
357
|
/** Create a scoped session with pre-filled defaults. */
|
|
167
358
|
session(agentId: string, defaults?: Record<string, unknown>): AiraSession;
|
|
168
359
|
/** Number of queued offline requests. */
|
package/dist/client.js
CHANGED
|
@@ -7,6 +7,44 @@ const session_1 = require("./session");
|
|
|
7
7
|
const DEFAULT_BASE_URL = "https://api.airaproof.com";
|
|
8
8
|
const DEFAULT_TIMEOUT = 30_000;
|
|
9
9
|
const MAX_DETAILS_LENGTH = 50_000;
|
|
10
|
+
// Binary download endpoints retry on transient 5xx (server hiccups,
|
|
11
|
+
// brief gateway issues). 3 attempts with exponential backoff
|
|
12
|
+
// (250ms -> 500ms -> 1000ms) keeps the worst case under 2s while
|
|
13
|
+
// absorbing the most common flakes. 4xx errors are NOT retried —
|
|
14
|
+
// those indicate a real problem the caller needs to see.
|
|
15
|
+
const DOWNLOAD_MAX_ATTEMPTS = 3;
|
|
16
|
+
const DOWNLOAD_BACKOFF_BASE_MS = 250;
|
|
17
|
+
function sleep(ms) {
|
|
18
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Run a fetch with retries on transient 5xx and network errors.
|
|
22
|
+
* Returns the final Response (which may itself be a 5xx after all
|
|
23
|
+
* attempts are exhausted — caller decides whether to throw).
|
|
24
|
+
*/
|
|
25
|
+
async function fetchWithRetry(doFetch) {
|
|
26
|
+
let lastErr;
|
|
27
|
+
for (let attempt = 0; attempt < DOWNLOAD_MAX_ATTEMPTS; attempt++) {
|
|
28
|
+
try {
|
|
29
|
+
const res = await doFetch();
|
|
30
|
+
if (res.status >= 500 && attempt < DOWNLOAD_MAX_ATTEMPTS - 1) {
|
|
31
|
+
await sleep(DOWNLOAD_BACKOFF_BASE_MS * 2 ** attempt);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
return res;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
lastErr = err;
|
|
38
|
+
if (attempt < DOWNLOAD_MAX_ATTEMPTS - 1) {
|
|
39
|
+
await sleep(DOWNLOAD_BACKOFF_BASE_MS * 2 ** attempt);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Unreachable in practice — the loop either returns or throws.
|
|
46
|
+
throw lastErr ?? new Error("download retry loop exited without a response");
|
|
47
|
+
}
|
|
10
48
|
function buildBody(obj) {
|
|
11
49
|
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null));
|
|
12
50
|
}
|
|
@@ -46,7 +84,8 @@ class Aira {
|
|
|
46
84
|
return {};
|
|
47
85
|
const data = (await res.json().catch(() => ({ error: res.statusText, code: "UNKNOWN" })));
|
|
48
86
|
if (!res.ok) {
|
|
49
|
-
|
|
87
|
+
const message = data.message ?? res.statusText;
|
|
88
|
+
throw new types_1.AiraError(res.status, data.code ?? "UNKNOWN", message, data.details ?? {});
|
|
50
89
|
}
|
|
51
90
|
return data;
|
|
52
91
|
}
|
|
@@ -75,6 +114,13 @@ class Aira {
|
|
|
75
114
|
}
|
|
76
115
|
return this.request("PUT", path, body);
|
|
77
116
|
}
|
|
117
|
+
patch(path, body) {
|
|
118
|
+
if (this.queue) {
|
|
119
|
+
const qid = this.queue.enqueue("PATCH", path, body);
|
|
120
|
+
return Promise.resolve({ _offline: true, _queue_id: qid });
|
|
121
|
+
}
|
|
122
|
+
return this.request("PATCH", path, body);
|
|
123
|
+
}
|
|
78
124
|
del(path) {
|
|
79
125
|
if (this.queue) {
|
|
80
126
|
const qid = this.queue.enqueue("DELETE", path, {});
|
|
@@ -86,24 +132,55 @@ class Aira {
|
|
|
86
132
|
const p = data.pagination;
|
|
87
133
|
return { data: data.data, total: p.total, page: p.page, per_page: p.per_page, has_more: p.has_more };
|
|
88
134
|
}
|
|
89
|
-
// ==================== Actions ====================
|
|
90
|
-
|
|
135
|
+
// ==================== Actions (two-step: authorize → notarize) ====================
|
|
136
|
+
/**
|
|
137
|
+
* Step 1 — Authorize an action BEFORE it executes.
|
|
138
|
+
*
|
|
139
|
+
* Returns an `Authorization` with a status:
|
|
140
|
+
* - "authorized" → safe to execute the action, then call `notarize()`
|
|
141
|
+
* - "pending_approval" → enqueue `action_id` and wait for human approval
|
|
142
|
+
*
|
|
143
|
+
* If a policy denies the action, this throws `AiraError` with code
|
|
144
|
+
* `POLICY_DENIED` (HTTP 403). Duplicate idempotent requests throw
|
|
145
|
+
* `DUPLICATE_REQUEST` (HTTP 409).
|
|
146
|
+
*/
|
|
147
|
+
async authorize(params) {
|
|
91
148
|
const body = buildBody({
|
|
92
149
|
action_type: params.actionType,
|
|
93
150
|
details: truncateDetails(params.details),
|
|
94
151
|
agent_id: params.agentId,
|
|
95
152
|
agent_version: params.agentVersion,
|
|
153
|
+
instruction_hash: params.instructionHash,
|
|
96
154
|
model_id: params.modelId,
|
|
97
155
|
model_version: params.modelVersion,
|
|
98
|
-
instruction_hash: params.instructionHash,
|
|
99
156
|
parent_action_id: params.parentActionId,
|
|
157
|
+
endpoint_url: params.endpointUrl,
|
|
100
158
|
store_details: params.storeDetails || undefined,
|
|
101
159
|
idempotency_key: params.idempotencyKey,
|
|
102
160
|
require_approval: params.requireApproval || undefined,
|
|
103
161
|
approvers: params.approvers,
|
|
162
|
+
system_prompt_hash: params.systemPromptHash,
|
|
163
|
+
tool_inputs_hash: params.toolInputsHash,
|
|
164
|
+
model_params: params.modelParams,
|
|
165
|
+
execution_env: params.executionEnv,
|
|
104
166
|
});
|
|
105
167
|
return this.post("/actions", body);
|
|
106
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Step 2 — Notarize the outcome of an already-authorized action.
|
|
171
|
+
*
|
|
172
|
+
* Call this AFTER executing the action. Outcome is "completed" by default;
|
|
173
|
+
* pass "failed" if the action ran but failed so the audit trail captures
|
|
174
|
+
* the failure. The returned `ActionReceipt` carries the Ed25519 signature
|
|
175
|
+
* and RFC 3161 timestamp token when the status is "notarized".
|
|
176
|
+
*/
|
|
177
|
+
async notarize(params) {
|
|
178
|
+
const body = buildBody({
|
|
179
|
+
outcome: params.outcome ?? "completed",
|
|
180
|
+
outcome_details: params.outcomeDetails,
|
|
181
|
+
});
|
|
182
|
+
return this.post(`/actions/${params.actionId}/notarize`, body);
|
|
183
|
+
}
|
|
107
184
|
async getAction(actionId) {
|
|
108
185
|
return this.get(`/actions/${actionId}`);
|
|
109
186
|
}
|
|
@@ -113,8 +190,16 @@ class Aira {
|
|
|
113
190
|
}));
|
|
114
191
|
return this.paginated(data);
|
|
115
192
|
}
|
|
116
|
-
|
|
117
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Add a human co-signature to an action that already exists.
|
|
195
|
+
*
|
|
196
|
+
* This is distinct from the authorization gate (it runs against `/cosign`,
|
|
197
|
+
* not `/authorize`). It records that a specific human has acknowledged or
|
|
198
|
+
* signed off on an action that was already authorized and notarized.
|
|
199
|
+
* Requires JWT auth (dashboard user, not an API key).
|
|
200
|
+
*/
|
|
201
|
+
async cosign(params) {
|
|
202
|
+
return this.post(`/actions/${params.actionId}/cosign`, {});
|
|
118
203
|
}
|
|
119
204
|
async setLegalHold(actionId) {
|
|
120
205
|
return this.post(`/actions/${actionId}/hold`, {});
|
|
@@ -341,6 +426,262 @@ class Aira {
|
|
|
341
426
|
async verifyReputation(slug) {
|
|
342
427
|
return this.get(`/agents/${slug}/reputation/verify`);
|
|
343
428
|
}
|
|
429
|
+
// ==================== Replay context (F10) ====================
|
|
430
|
+
/**
|
|
431
|
+
* Get all reproducibility metadata stored for an action.
|
|
432
|
+
*
|
|
433
|
+
* Returns the system_prompt_hash, tool_inputs_hash, model_params,
|
|
434
|
+
* execution_env, and other knobs that an external replay tool
|
|
435
|
+
* needs to confirm it has the same inputs as the original run.
|
|
436
|
+
*/
|
|
437
|
+
async getReplayContext(actionId) {
|
|
438
|
+
return this.get(`/actions/${actionId}/replay-context`);
|
|
439
|
+
}
|
|
440
|
+
// ==================== Compliance bundles ====================
|
|
441
|
+
/**
|
|
442
|
+
* Seal a regulator-ready evidence bundle for a date range.
|
|
443
|
+
*
|
|
444
|
+
* `framework` must be one of: `eu_ai_act_art12`, `iso_42001`,
|
|
445
|
+
* `aiuc_1`, `soc_2_cc7`, `raw`.
|
|
446
|
+
*/
|
|
447
|
+
async createComplianceBundle(params) {
|
|
448
|
+
const body = buildBody({
|
|
449
|
+
framework: params.framework,
|
|
450
|
+
period_start: params.periodStart,
|
|
451
|
+
period_end: params.periodEnd,
|
|
452
|
+
title: params.title,
|
|
453
|
+
agent_filter: params.agentFilter,
|
|
454
|
+
idempotency_key: params.idempotencyKey,
|
|
455
|
+
});
|
|
456
|
+
return this.post("/compliance/bundles", body);
|
|
457
|
+
}
|
|
458
|
+
async listComplianceBundles(page = 1, perPage = 20) {
|
|
459
|
+
const data = await this.get(`/compliance/bundles?page=${page}&per_page=${perPage}`);
|
|
460
|
+
return this.paginated(data);
|
|
461
|
+
}
|
|
462
|
+
async getComplianceBundle(bundleId) {
|
|
463
|
+
return this.get(`/compliance/bundles/${bundleId}`);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Download the self-contained JSON document for the bundle. The
|
|
467
|
+
* exported document inlines every receipt's signed payload + signature
|
|
468
|
+
* and the JWKS URL so an auditor can re-verify offline.
|
|
469
|
+
*/
|
|
470
|
+
async exportComplianceBundle(bundleId) {
|
|
471
|
+
return this.get(`/compliance/bundles/${bundleId}/export`);
|
|
472
|
+
}
|
|
473
|
+
async getBundleInclusionProof(bundleId, receiptId) {
|
|
474
|
+
return this.get(`/compliance/bundles/${bundleId}/inclusion-proof/${receiptId}`);
|
|
475
|
+
}
|
|
476
|
+
// ==================== Drift detection ====================
|
|
477
|
+
/**
|
|
478
|
+
* Score the agent's recent behavior against its active baseline.
|
|
479
|
+
* Read-only — does NOT persist an alert. Use this for dashboards.
|
|
480
|
+
*/
|
|
481
|
+
async getDriftStatus(agentId, lookbackHours = 24) {
|
|
482
|
+
return this.get(`/agents/${agentId}/drift?lookback_hours=${lookbackHours}`);
|
|
483
|
+
}
|
|
484
|
+
/** Compute a behavioral baseline from production action history. */
|
|
485
|
+
async computeDriftBaseline(params) {
|
|
486
|
+
return this.post(`/agents/${params.agentId}/drift/baseline`, buildBody({
|
|
487
|
+
window_start: params.windowStart,
|
|
488
|
+
window_end: params.windowEnd,
|
|
489
|
+
activate: params.activate ?? true,
|
|
490
|
+
}));
|
|
491
|
+
}
|
|
492
|
+
/** Seed a baseline from a config dict (for cold-start agents). */
|
|
493
|
+
async seedSyntheticBaseline(params) {
|
|
494
|
+
return this.post(`/agents/${params.agentId}/drift/baseline/synthetic`, buildBody({
|
|
495
|
+
expected_distribution: params.expectedDistribution,
|
|
496
|
+
expected_actions_per_day: params.expectedActionsPerDay,
|
|
497
|
+
activate: params.activate ?? true,
|
|
498
|
+
}));
|
|
499
|
+
}
|
|
500
|
+
/** Score the current window and persist an alert if it exceeds the threshold. */
|
|
501
|
+
async runDriftCheck(agentId, lookbackHours = 24) {
|
|
502
|
+
return this.post(`/agents/${agentId}/drift/check?lookback_hours=${lookbackHours}`, {});
|
|
503
|
+
}
|
|
504
|
+
async listDriftAlerts(agentId, page = 1, acknowledged) {
|
|
505
|
+
let params = `page=${page}&per_page=50`;
|
|
506
|
+
if (acknowledged !== undefined) {
|
|
507
|
+
params += `&acknowledged=${acknowledged}`;
|
|
508
|
+
}
|
|
509
|
+
const data = await this.get(`/agents/${agentId}/drift/alerts?${params}`);
|
|
510
|
+
return this.paginated(data);
|
|
511
|
+
}
|
|
512
|
+
async acknowledgeDriftAlert(agentId, alertId) {
|
|
513
|
+
return this.post(`/agents/${agentId}/drift/alerts/${alertId}/acknowledge`, {});
|
|
514
|
+
}
|
|
515
|
+
// ==================== Merkle settlement (F8) ====================
|
|
516
|
+
/**
|
|
517
|
+
* Seal every unsettled receipt for the org into a new settlement.
|
|
518
|
+
* Admin-only. Returns the new settlement, or null if there were no
|
|
519
|
+
* unsettled receipts (no-op).
|
|
520
|
+
*/
|
|
521
|
+
async createSettlement() {
|
|
522
|
+
return this.post("/settlements", {});
|
|
523
|
+
}
|
|
524
|
+
async listSettlements(page = 1, perPage = 20) {
|
|
525
|
+
const data = await this.get(`/settlements?page=${page}&per_page=${perPage}`);
|
|
526
|
+
return this.paginated(data);
|
|
527
|
+
}
|
|
528
|
+
async getSettlement(settlementId) {
|
|
529
|
+
return this.get(`/settlements/${settlementId}`);
|
|
530
|
+
}
|
|
531
|
+
/** Get the Merkle inclusion proof for one receipt in its settlement. */
|
|
532
|
+
async getSettlementInclusionProof(receiptId) {
|
|
533
|
+
return this.get(`/settlements/inclusion-proof/${receiptId}`);
|
|
534
|
+
}
|
|
535
|
+
// ==================== Compliance reports (Phase 1) ====================
|
|
536
|
+
/**
|
|
537
|
+
* Generate a regulatory PDF report.
|
|
538
|
+
*
|
|
539
|
+
* Frameworks:
|
|
540
|
+
* - `eu_ai_act_art12` — Annex VII technical file. Requires period.
|
|
541
|
+
* - `eu_ai_act_art9` — risk management register. Requires period.
|
|
542
|
+
* - `eu_ai_act_art6` — single-action explanation. Requires actionId.
|
|
543
|
+
* - `eu_ai_act_annex_iv` — full Annex IV technical documentation
|
|
544
|
+
* (§§1..9). Requires period. Typical use: annual file for the
|
|
545
|
+
* high-risk AI system provider obligations in Article 11.
|
|
546
|
+
*/
|
|
547
|
+
async createComplianceReport(params) {
|
|
548
|
+
const body = buildBody({
|
|
549
|
+
framework: params.framework,
|
|
550
|
+
period_start: params.periodStart,
|
|
551
|
+
period_end: params.periodEnd,
|
|
552
|
+
action_id: params.actionId,
|
|
553
|
+
agent_filter: params.agentFilter,
|
|
554
|
+
});
|
|
555
|
+
return this.post("/compliance/reports", body);
|
|
556
|
+
}
|
|
557
|
+
/** Get the metadata for a compliance report (no PDF bytes). */
|
|
558
|
+
async getComplianceReport(reportId) {
|
|
559
|
+
return this.get(`/compliance/reports/${reportId}`);
|
|
560
|
+
}
|
|
561
|
+
/** List compliance reports with optional filters. */
|
|
562
|
+
async listComplianceReports(params) {
|
|
563
|
+
return this.get("/compliance/reports", buildBody({ ...params }));
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Download the generated PDF as raw bytes (Uint8Array).
|
|
567
|
+
*
|
|
568
|
+
* Retries on transient 5xx and network errors (3 attempts,
|
|
569
|
+
* exponential backoff). 4xx responses surface immediately.
|
|
570
|
+
*/
|
|
571
|
+
async downloadComplianceReport(reportId) {
|
|
572
|
+
if (this.queue) {
|
|
573
|
+
throw new types_1.AiraError(0, "OFFLINE", "Downloads are not available in offline mode");
|
|
574
|
+
}
|
|
575
|
+
const controller = new AbortController();
|
|
576
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
577
|
+
try {
|
|
578
|
+
const res = await fetchWithRetry(() => fetch(`${this.baseUrl}/compliance/reports/${reportId}/download`, {
|
|
579
|
+
method: "GET",
|
|
580
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
581
|
+
signal: controller.signal,
|
|
582
|
+
}));
|
|
583
|
+
if (!res.ok) {
|
|
584
|
+
throw new types_1.AiraError(res.status, "DOWNLOAD_FAILED", res.statusText);
|
|
585
|
+
}
|
|
586
|
+
const buf = await res.arrayBuffer();
|
|
587
|
+
return new Uint8Array(buf);
|
|
588
|
+
}
|
|
589
|
+
finally {
|
|
590
|
+
clearTimeout(timer);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/** Verify a compliance report's signature and content hash. */
|
|
594
|
+
async verifyComplianceReport(reportId) {
|
|
595
|
+
return this.get(`/compliance/reports/${reportId}/verify`);
|
|
596
|
+
}
|
|
597
|
+
// ==================== Output content-scan policy ====================
|
|
598
|
+
/**
|
|
599
|
+
* Return the org's output content-scan policy.
|
|
600
|
+
*
|
|
601
|
+
* Scans apply to the `outcomeDetails` passed to `notarize()`. Mode
|
|
602
|
+
* controls behaviour:
|
|
603
|
+
* - `flag` — hits are recorded on the receipt, nothing blocked
|
|
604
|
+
* - `deny` — a hit at or above `deny_severity_threshold` makes
|
|
605
|
+
* notarize return 422 with code `OUTPUT_SCAN_VIOLATION`
|
|
606
|
+
* - `redact` — matched spans are replaced with `[REDACTED]` and
|
|
607
|
+
* the receipt signs over the cleaned bytes
|
|
608
|
+
*/
|
|
609
|
+
async getOutputPolicy() {
|
|
610
|
+
return this.get("/output-policies");
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Merge the supplied fields into the org's output content-scan
|
|
614
|
+
* policy. Omitted fields stay at their current values. Admin role
|
|
615
|
+
* required server-side.
|
|
616
|
+
*/
|
|
617
|
+
async updateOutputPolicy(updates) {
|
|
618
|
+
// Strip undefined so they don't travel as `null` and accidentally
|
|
619
|
+
// reset server-side values.
|
|
620
|
+
const body = {};
|
|
621
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
622
|
+
if (v !== undefined)
|
|
623
|
+
body[k] = v;
|
|
624
|
+
}
|
|
625
|
+
return this.patch("/output-policies", body);
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Article 6 right-to-explanation for a single action.
|
|
629
|
+
*
|
|
630
|
+
* The response includes a cryptographic ``_envelope`` — verify it
|
|
631
|
+
* later with {@link verifyActionExplanation} (the verify endpoint
|
|
632
|
+
* is public, so anyone holding the JSON can re-check it).
|
|
633
|
+
*/
|
|
634
|
+
async getActionExplanation(actionId) {
|
|
635
|
+
return this.get(`/actions/${actionId}/explanation`);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Public verify — recompute an explanation envelope's signature.
|
|
639
|
+
*
|
|
640
|
+
* POSTs the full explanation JSON to the unauthenticated
|
|
641
|
+
* ``/verify/explanation`` endpoint. The server looks up the public
|
|
642
|
+
* key by ``_envelope.signing_key_id`` and re-derives the canonical
|
|
643
|
+
* content hash + Ed25519 signature.
|
|
644
|
+
*
|
|
645
|
+
* ``request_id`` is stripped before sending, so a saved JSON
|
|
646
|
+
* explanation verifies the same way regardless of whether the
|
|
647
|
+
* caller round-tripped it through their own logs.
|
|
648
|
+
*/
|
|
649
|
+
async verifyActionExplanation(explanation) {
|
|
650
|
+
const payload = {};
|
|
651
|
+
for (const [k, v] of Object.entries(explanation)) {
|
|
652
|
+
if (k === "request_id")
|
|
653
|
+
continue;
|
|
654
|
+
payload[k] = v;
|
|
655
|
+
}
|
|
656
|
+
return this.request("POST", "/verify/explanation", { explanation: payload }, false);
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Download the Article 6 explanation as a PDF.
|
|
660
|
+
*
|
|
661
|
+
* Retries on transient 5xx and network errors (3 attempts,
|
|
662
|
+
* exponential backoff). 4xx responses surface immediately.
|
|
663
|
+
*/
|
|
664
|
+
async downloadActionExplanationPdf(actionId) {
|
|
665
|
+
if (this.queue) {
|
|
666
|
+
throw new types_1.AiraError(0, "OFFLINE", "Downloads are not available in offline mode");
|
|
667
|
+
}
|
|
668
|
+
const controller = new AbortController();
|
|
669
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
670
|
+
try {
|
|
671
|
+
const res = await fetchWithRetry(() => fetch(`${this.baseUrl}/actions/${actionId}/explanation/pdf`, {
|
|
672
|
+
method: "GET",
|
|
673
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
674
|
+
signal: controller.signal,
|
|
675
|
+
}));
|
|
676
|
+
if (!res.ok) {
|
|
677
|
+
throw new types_1.AiraError(res.status, "DOWNLOAD_FAILED", res.statusText);
|
|
678
|
+
}
|
|
679
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
680
|
+
}
|
|
681
|
+
finally {
|
|
682
|
+
clearTimeout(timer);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
344
685
|
// ==================== Session ====================
|
|
345
686
|
/** Create a scoped session with pre-filled defaults. */
|
|
346
687
|
session(agentId, defaults) {
|