@systima/aiact-audit-log 0.1.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/COMPLIANCE.md +180 -0
- package/LICENSE +21 -0
- package/README.md +406 -0
- package/dist/ai-sdk/index.cjs +102 -0
- package/dist/ai-sdk/index.cjs.map +1 -0
- package/dist/ai-sdk/index.d.cts +341 -0
- package/dist/ai-sdk/index.d.ts +341 -0
- package/dist/ai-sdk/index.js +77 -0
- package/dist/ai-sdk/index.js.map +1 -0
- package/dist/ai-sdk/middleware/index.cjs +259 -0
- package/dist/ai-sdk/middleware/index.cjs.map +1 -0
- package/dist/ai-sdk/middleware/index.d.cts +323 -0
- package/dist/ai-sdk/middleware/index.d.ts +323 -0
- package/dist/ai-sdk/middleware/index.js +236 -0
- package/dist/ai-sdk/middleware/index.js.map +1 -0
- package/dist/cli/index.js +3332 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +1385 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +501 -0
- package/dist/index.d.ts +501 -0
- package/dist/index.js +1342 -0
- package/dist/index.js.map +1 -0
- package/dist/prompt-F4GUFYMH.js +755 -0
- package/dist/prompt-F4GUFYMH.js.map +1 -0
- package/package.json +91 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/ai-sdk/middleware/index.ts","../../../src/context.ts","../../../src/utils/uuid.ts"],"sourcesContent":["/**\n * AI SDK middleware — automatic capture for Vercel AI SDK models.\n *\n * Wraps a LanguageModelV1 and automatically logs every inference call.\n * This is the primary mechanism for satisfying Article 12(1)'s\n * \"automatic recording\" requirement for inference events.\n *\n * Usage:\n * const model = auditMiddleware(anthropic('claude-sonnet-4-5-20250929'), { logger })\n * const result = await generateText({ model, prompt })\n * // ^ Inference is automatically logged\n */\n\nimport {\n wrapLanguageModel,\n type LanguageModelV1,\n type LanguageModelV1Middleware,\n} from 'ai'\nimport type { AuditLogger } from '../../logger.js'\nimport { getAuditContext } from '../../context.js'\nimport { generateUUIDv7 } from '../../utils/uuid.js'\n\nexport interface AuditMiddlewareOptions {\n logger: AuditLogger\n captureInputs?: boolean\n captureOutputs?: boolean\n captureToolCalls?: boolean\n captureParameters?: boolean\n}\n\nexport function auditMiddleware(\n model: LanguageModelV1,\n options: AuditMiddlewareOptions,\n): LanguageModelV1 {\n const {\n logger,\n captureInputs = true,\n captureOutputs = true,\n } = options\n\n const middleware: LanguageModelV1Middleware = {\n middlewareVersion: 'v1',\n\n wrapGenerate: async ({ doGenerate, params }) => {\n const startTime = Date.now()\n const context = getAuditContext()\n const decisionId = context?.decisionId ?? generateUUIDv7()\n\n try {\n const result = await doGenerate()\n const latencyMs = Date.now() - startTime\n\n const responseModelId = extractModelId(result)\n const text = extractText(result)\n const usage = extractUsage(result)\n const finishReason = extractFinishReason(result)\n\n await logger.log({\n decisionId,\n eventType: 'inference',\n modelId: responseModelId ?? model.modelId ?? null,\n providerId: responseModelId?.split('/')[0] ?? model.provider ?? null,\n input: captureInputs\n ? { value: serialisePrompt(params.prompt) }\n : { value: '', type: 'hash' },\n output: captureOutputs\n ? { value: text, finishReason }\n : text\n ? { value: '', type: 'hash', finishReason }\n : null,\n latencyMs,\n usage,\n parameters: options.captureParameters !== false\n ? extractParams(params)\n : null,\n error: null,\n captureMethod: 'middleware',\n metadata: context?.metadata,\n })\n\n if (options.captureToolCalls !== false) {\n const toolCalls = extractToolCalls(result)\n for (const tc of toolCalls) {\n await logger.log({\n decisionId,\n eventType: 'tool_call',\n modelId: responseModelId ?? model.modelId ?? null,\n providerId: null,\n input: { value: JSON.stringify(tc.args) },\n output: null,\n latencyMs: null,\n usage: null,\n parameters: null,\n error: null,\n toolCall: {\n toolName: tc.toolName,\n toolArgs: tc.args,\n },\n captureMethod: 'middleware',\n metadata: context?.metadata,\n })\n }\n }\n\n return result\n } catch (error) {\n const latencyMs = Date.now() - startTime\n\n await logger.log({\n decisionId,\n eventType: 'inference',\n modelId: model.modelId ?? null,\n providerId: model.provider ?? null,\n input: captureInputs\n ? { value: serialisePrompt(params.prompt) }\n : { value: '', type: 'hash' },\n output: null,\n latencyMs,\n usage: null,\n parameters: options.captureParameters !== false\n ? extractParams(params)\n : null,\n error: {\n code: (error as Error).name ?? 'UNKNOWN',\n message: (error as Error).message ?? String(error),\n },\n captureMethod: 'middleware',\n metadata: context?.metadata,\n })\n\n throw error\n }\n },\n\n wrapStream: async ({ doStream, params }) => {\n const startTime = Date.now()\n const context = getAuditContext()\n const decisionId = context?.decisionId ?? generateUUIDv7()\n\n try {\n const result = await doStream()\n const { stream, ...rest } = result\n\n const chunks: string[] = []\n let streamUsage: { promptTokens: number; completionTokens: number; totalTokens: number } | null = null\n let streamFinishReason: string | undefined\n let streamModelId: string | null = null\n\n const loggingStream = stream.pipeThrough(\n new TransformStream({\n transform(chunk, controller) {\n if (chunk.type === 'text-delta') {\n chunks.push(chunk.textDelta)\n }\n if (chunk.type === 'finish') {\n const finishChunk = chunk as Record<string, unknown>\n streamUsage = finishChunk.usage as typeof streamUsage ?? null\n streamFinishReason = finishChunk.finishReason as string | undefined\n const response = finishChunk.response as Record<string, unknown> | undefined\n streamModelId = response?.modelId as string ?? null\n }\n controller.enqueue(chunk)\n },\n async flush() {\n const latencyMs = Date.now() - startTime\n const fullText = chunks.join('')\n\n await logger.log({\n decisionId,\n eventType: 'inference',\n modelId: streamModelId ?? model.modelId ?? null,\n providerId: streamModelId?.split('/')[0] ?? model.provider ?? null,\n input: captureInputs\n ? { value: serialisePrompt(params.prompt) }\n : { value: '', type: 'hash' },\n output: captureOutputs\n ? { value: fullText, finishReason: streamFinishReason }\n : fullText\n ? { value: '', type: 'hash', finishReason: streamFinishReason }\n : null,\n latencyMs,\n usage: streamUsage,\n parameters: options.captureParameters !== false\n ? extractParams(params)\n : null,\n error: null,\n captureMethod: 'middleware',\n metadata: context?.metadata,\n })\n },\n }),\n )\n\n return { stream: loggingStream, ...rest }\n } catch (error) {\n const latencyMs = Date.now() - startTime\n\n await logger.log({\n decisionId,\n eventType: 'inference',\n modelId: model.modelId ?? null,\n providerId: model.provider ?? null,\n input: captureInputs\n ? { value: serialisePrompt(params.prompt) }\n : { value: '', type: 'hash' },\n output: null,\n latencyMs,\n usage: null,\n parameters: options.captureParameters !== false\n ? extractParams(params)\n : null,\n error: {\n code: (error as Error).name ?? 'UNKNOWN',\n message: (error as Error).message ?? String(error),\n },\n captureMethod: 'middleware',\n metadata: context?.metadata,\n })\n\n throw error\n }\n },\n }\n\n return wrapLanguageModel({ model, middleware })\n}\n\n// ── Extraction helpers ──────────────────────────────────────\n\nfunction serialisePrompt(prompt: unknown): string {\n if (typeof prompt === 'string') return prompt\n try {\n return JSON.stringify(prompt)\n } catch {\n return String(prompt)\n }\n}\n\nfunction extractModelId(result: Record<string, unknown>): string | null {\n const response = result.response as Record<string, unknown> | undefined\n return (response?.modelId as string) ?? null\n}\n\nfunction extractText(result: Record<string, unknown>): string {\n return (result.text as string) ?? ''\n}\n\nfunction extractFinishReason(result: Record<string, unknown>): string | undefined {\n return result.finishReason as string | undefined\n}\n\nfunction extractUsage(result: Record<string, unknown>): {\n promptTokens: number\n completionTokens: number\n totalTokens: number\n} | null {\n const usage = result.usage as Record<string, unknown> | undefined\n if (!usage) return null\n return {\n promptTokens: (usage.promptTokens as number) ?? 0,\n completionTokens: (usage.completionTokens as number) ?? 0,\n totalTokens: (usage.totalTokens as number) ?? 0,\n }\n}\n\nfunction extractToolCalls(result: Record<string, unknown>): Array<{\n toolName: string\n args: Record<string, unknown>\n}> {\n const toolCalls = result.toolCalls as Array<Record<string, unknown>> | undefined\n if (!toolCalls || !Array.isArray(toolCalls)) return []\n\n return toolCalls.map((tc) => ({\n toolName: (tc.toolName as string) ?? 'unknown',\n args: (tc.args as Record<string, unknown>) ?? {},\n }))\n}\n\nfunction extractParams(params: Record<string, unknown>): Record<string, unknown> {\n const extracted: Record<string, unknown> = {}\n if (params.temperature !== undefined) extracted['temperature'] = params.temperature\n if (params.maxTokens !== undefined) extracted['maxTokens'] = params.maxTokens\n if (params.topP !== undefined) extracted['topP'] = params.topP\n if (params.topK !== undefined) extracted['topK'] = params.topK\n if (params.frequencyPenalty !== undefined) extracted['frequencyPenalty'] = params.frequencyPenalty\n if (params.presencePenalty !== undefined) extracted['presencePenalty'] = params.presencePenalty\n if (params.seed !== undefined) extracted['seed'] = params.seed\n return Object.keys(extracted).length > 0 ? extracted : {}\n}\n","/**\n * AsyncLocalStorage-based context propagation for audit logging.\n *\n * Allows decision IDs and metadata to flow through async call chains\n * without manual threading. This reduces the integration burden that\n * leads to coverage gaps.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks'\n\nexport interface AuditContext {\n decisionId: string\n parentDecisionId?: string\n metadata?: Record<string, string | number | boolean>\n}\n\nconst auditStorage = new AsyncLocalStorage<AuditContext>()\n\nexport function withAuditContext<T>(\n context: AuditContext,\n callback: () => T | Promise<T>,\n): T | Promise<T> {\n return auditStorage.run(context, callback)\n}\n\nexport function getAuditContext(): AuditContext | undefined {\n return auditStorage.getStore()\n}\n\nexport class MissingDecisionIdError extends Error {\n constructor() {\n super(\n 'decisionId is required. Either provide it explicitly in the log entry, ' +\n 'or wrap the call in withAuditContext({ decisionId: \"...\" }, callback).',\n )\n this.name = 'MissingDecisionIdError'\n }\n}\n","import { randomBytes } from 'node:crypto'\n\n/**\n * Generate a UUIDv7 (time-ordered, RFC 9562).\n *\n * UUIDv7 embeds a Unix timestamp in the most significant 48 bits,\n * making IDs naturally time-ordered and sortable without parsing\n * timestamps. The remaining bits are random for uniqueness.\n *\n * Format: tttttttt-tttt-7rrr-Rrrr-rrrrrrrrrrrr\n * t = timestamp bits (48-bit ms since epoch)\n * 7 = version nibble\n * R = variant bits (10xx)\n * r = random bits\n */\nexport function generateUUIDv7(): string {\n const now = Date.now()\n const bytes = new Uint8Array(16)\n\n const randomPart = randomBytes(10)\n bytes.set(randomPart, 6)\n\n bytes[0] = (now / 2 ** 40) & 0xff\n bytes[1] = (now / 2 ** 32) & 0xff\n bytes[2] = (now / 2 ** 24) & 0xff\n bytes[3] = (now / 2 ** 16) & 0xff\n bytes[4] = (now / 2 ** 8) & 0xff\n bytes[5] = now & 0xff\n\n bytes[6] = (bytes[6] & 0x0f) | 0x70\n bytes[8] = (bytes[8] & 0x3f) | 0x80\n\n return formatUUID(bytes)\n}\n\nfunction formatUUID(bytes: Uint8Array): string {\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`\n}\n\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/\n\nexport function isValidUUIDv7(id: string): boolean {\n return UUID_PATTERN.test(id)\n}\n\nexport function extractTimestampFromUUIDv7(id: string): number {\n const hex = id.replace(/-/g, '').slice(0, 12)\n return parseInt(hex, 16)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { LanguageModelV1 } from 'ai';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Storage backend interface.
|
|
5
|
+
*
|
|
6
|
+
* Abstraction layer for log persistence. v0.1 ships with S3-compatible
|
|
7
|
+
* storage only; future versions will add Azure Blob, GCS, and local
|
|
8
|
+
* filesystem backends.
|
|
9
|
+
*/
|
|
10
|
+
interface StorageBackend {
|
|
11
|
+
write(key: string, data: Buffer): Promise<void>;
|
|
12
|
+
read(key: string): Promise<Buffer>;
|
|
13
|
+
list(prefix: string): Promise<string[]>;
|
|
14
|
+
exists(key: string): Promise<boolean>;
|
|
15
|
+
getObjectMetadata(key: string): Promise<ObjectMetadata>;
|
|
16
|
+
}
|
|
17
|
+
interface ObjectMetadata {
|
|
18
|
+
lastModified: Date;
|
|
19
|
+
size: number;
|
|
20
|
+
}
|
|
21
|
+
interface S3StorageConfig {
|
|
22
|
+
type: 's3';
|
|
23
|
+
bucket: string;
|
|
24
|
+
region: string;
|
|
25
|
+
prefix?: string;
|
|
26
|
+
endpoint?: string;
|
|
27
|
+
forcePathStyle?: boolean;
|
|
28
|
+
credentials?: {
|
|
29
|
+
accessKeyId: string;
|
|
30
|
+
secretAccessKey: string;
|
|
31
|
+
sessionToken?: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
type StorageConfig = S3StorageConfig;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Schema definitions and runtime validation for audit log entries.
|
|
38
|
+
*
|
|
39
|
+
* Every field is annotated with the Article paragraph it satisfies.
|
|
40
|
+
* The schema IS the Article 12 mapping.
|
|
41
|
+
*/
|
|
42
|
+
declare const EVENT_TYPES: readonly ["inference", "tool_call", "tool_result", "human_intervention", "system_event", "session_start", "session_end"];
|
|
43
|
+
type EventType = (typeof EVENT_TYPES)[number];
|
|
44
|
+
declare const CAPTURE_METHODS: readonly ["middleware", "manual", "context"];
|
|
45
|
+
type CaptureMethod = (typeof CAPTURE_METHODS)[number];
|
|
46
|
+
interface InputData {
|
|
47
|
+
type: 'raw' | 'hash';
|
|
48
|
+
value: string;
|
|
49
|
+
tokenCount?: number;
|
|
50
|
+
}
|
|
51
|
+
interface OutputData {
|
|
52
|
+
type: 'raw' | 'hash';
|
|
53
|
+
value: string;
|
|
54
|
+
tokenCount?: number;
|
|
55
|
+
finishReason?: string;
|
|
56
|
+
}
|
|
57
|
+
interface TokenUsage {
|
|
58
|
+
promptTokens: number;
|
|
59
|
+
completionTokens: number;
|
|
60
|
+
totalTokens: number;
|
|
61
|
+
}
|
|
62
|
+
interface ErrorData {
|
|
63
|
+
code: string;
|
|
64
|
+
message: string;
|
|
65
|
+
stack?: string;
|
|
66
|
+
}
|
|
67
|
+
declare const HUMAN_INTERVENTION_TYPES: readonly ["approval", "rejection", "modification", "override", "escalation"];
|
|
68
|
+
type HumanInterventionType = (typeof HUMAN_INTERVENTION_TYPES)[number];
|
|
69
|
+
interface HumanIntervention {
|
|
70
|
+
/** @article 12(2)(c), 14(5), 12(3)(d) */
|
|
71
|
+
type: HumanInterventionType;
|
|
72
|
+
userId: string;
|
|
73
|
+
reason?: string;
|
|
74
|
+
originalOutput?: {
|
|
75
|
+
type: 'raw' | 'hash';
|
|
76
|
+
value: string;
|
|
77
|
+
};
|
|
78
|
+
timestamp: string;
|
|
79
|
+
}
|
|
80
|
+
interface ToolCallData {
|
|
81
|
+
toolName: string;
|
|
82
|
+
toolArgs: Record<string, unknown> | string;
|
|
83
|
+
toolResult?: string;
|
|
84
|
+
}
|
|
85
|
+
interface MatchResult {
|
|
86
|
+
matched: boolean;
|
|
87
|
+
matchConfidence?: number;
|
|
88
|
+
matchedRecordId?: string;
|
|
89
|
+
}
|
|
90
|
+
interface AuditLogEntry {
|
|
91
|
+
schemaVersion: 'v1';
|
|
92
|
+
/** @article 12(1) — unique event identification */
|
|
93
|
+
entryId: string;
|
|
94
|
+
/** @article 12(2)(a) — risk identification requires correlating related events */
|
|
95
|
+
decisionId: string;
|
|
96
|
+
/** @article 12(1) — system identification */
|
|
97
|
+
systemId: string;
|
|
98
|
+
/** @article 12(1), 12(3)(a) — automatic recording, period of each use */
|
|
99
|
+
timestamp: string;
|
|
100
|
+
/** @article 12(2)(a) — identifying situations that may present a risk */
|
|
101
|
+
eventType: EventType;
|
|
102
|
+
/** @article 12(2)(a), 72 — model version tracking */
|
|
103
|
+
modelId: string | null;
|
|
104
|
+
providerId: string | null;
|
|
105
|
+
/** @article 12(2)(a), 12(2)(c), 12(3)(c) — input context and deployer monitoring */
|
|
106
|
+
input: InputData;
|
|
107
|
+
/** @article 12(2)(a), 12(2)(b) — risk identification and post-market monitoring */
|
|
108
|
+
output: OutputData | null;
|
|
109
|
+
/** @article 12(2)(b), 72 — performance degradation detection */
|
|
110
|
+
latencyMs: number | null;
|
|
111
|
+
/** @article 72 — post-market monitoring (usage tracking) */
|
|
112
|
+
usage: TokenUsage | null;
|
|
113
|
+
/** @article 12(2)(a) — identifying situations that may present a risk */
|
|
114
|
+
error: ErrorData | null;
|
|
115
|
+
/** @article 12(2)(a) — risk identification requires knowing configuration */
|
|
116
|
+
parameters: Record<string, unknown> | null;
|
|
117
|
+
captureMethod: CaptureMethod;
|
|
118
|
+
/** @article 12(1) — event ordering */
|
|
119
|
+
seq: number;
|
|
120
|
+
prevHash: string;
|
|
121
|
+
hash: string;
|
|
122
|
+
}
|
|
123
|
+
interface AuditLogEntryExtended extends AuditLogEntry {
|
|
124
|
+
/** @article 12(2)(c), 14(5), 12(3)(d) */
|
|
125
|
+
humanIntervention?: HumanIntervention;
|
|
126
|
+
/** @article 12(2)(a) — tracing complex decision chains */
|
|
127
|
+
stepIndex?: number;
|
|
128
|
+
parentEntryId?: string;
|
|
129
|
+
/** @article 12(2)(a) — tool calls as risk vectors */
|
|
130
|
+
toolCall?: ToolCallData;
|
|
131
|
+
/** @article 12(3)(b) — reference database for biometric systems */
|
|
132
|
+
referenceDatabase?: string;
|
|
133
|
+
/** @article 12(3)(c) — match results for biometric systems */
|
|
134
|
+
matchResult?: MatchResult;
|
|
135
|
+
metadata?: Record<string, string | number | boolean>;
|
|
136
|
+
}
|
|
137
|
+
interface LogEntryInput {
|
|
138
|
+
decisionId?: string;
|
|
139
|
+
eventType: EventType;
|
|
140
|
+
modelId: string | null;
|
|
141
|
+
providerId: string | null;
|
|
142
|
+
input: {
|
|
143
|
+
value: string;
|
|
144
|
+
type?: 'raw' | 'hash';
|
|
145
|
+
tokenCount?: number;
|
|
146
|
+
};
|
|
147
|
+
output: {
|
|
148
|
+
value: string;
|
|
149
|
+
type?: 'raw' | 'hash';
|
|
150
|
+
tokenCount?: number;
|
|
151
|
+
finishReason?: string;
|
|
152
|
+
} | null;
|
|
153
|
+
latencyMs: number | null;
|
|
154
|
+
usage: TokenUsage | null;
|
|
155
|
+
parameters: Record<string, unknown> | null;
|
|
156
|
+
error: ErrorData | null;
|
|
157
|
+
captureMethod?: CaptureMethod;
|
|
158
|
+
humanIntervention?: HumanIntervention;
|
|
159
|
+
stepIndex?: number;
|
|
160
|
+
parentEntryId?: string;
|
|
161
|
+
toolCall?: ToolCallData;
|
|
162
|
+
referenceDatabase?: string;
|
|
163
|
+
matchResult?: MatchResult;
|
|
164
|
+
metadata?: Record<string, string | number | boolean>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* AuditLogger — core class for structured, tamper-evident audit logging.
|
|
169
|
+
*
|
|
170
|
+
* Satisfies Article 12(1): automatic recording of events over the
|
|
171
|
+
* lifetime of the system. Entries are batched in memory and flushed
|
|
172
|
+
* to S3-compatible storage with SHA-256 hash chains.
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
interface RetentionOptions {
|
|
176
|
+
minimumDays?: number;
|
|
177
|
+
acknowledgeSubMinimum?: boolean;
|
|
178
|
+
autoConfigureLifecycle?: boolean;
|
|
179
|
+
}
|
|
180
|
+
interface PIIOptions {
|
|
181
|
+
hashInputs?: boolean;
|
|
182
|
+
hashOutputs?: boolean;
|
|
183
|
+
redactPatterns?: RegExp[];
|
|
184
|
+
}
|
|
185
|
+
interface BatchingOptions {
|
|
186
|
+
maxSize?: number;
|
|
187
|
+
maxDelayMs?: number;
|
|
188
|
+
}
|
|
189
|
+
interface ObjectLockOptions {
|
|
190
|
+
enabled?: boolean;
|
|
191
|
+
mode?: 'GOVERNANCE' | 'COMPLIANCE';
|
|
192
|
+
}
|
|
193
|
+
interface HealthCheckOptions {
|
|
194
|
+
enabled?: boolean;
|
|
195
|
+
intervalMs?: number;
|
|
196
|
+
onDrift?: 'warn' | 'throw' | ((drift: ComplianceDrift) => void);
|
|
197
|
+
}
|
|
198
|
+
interface ComplianceDrift {
|
|
199
|
+
check: string;
|
|
200
|
+
status: 'fail' | 'warn';
|
|
201
|
+
message: string;
|
|
202
|
+
}
|
|
203
|
+
type ErrorHandler = 'log-and-continue' | 'throw' | ((error: Error) => void);
|
|
204
|
+
interface AuditLoggerConfig {
|
|
205
|
+
systemId: string;
|
|
206
|
+
storage: StorageConfig;
|
|
207
|
+
retention?: RetentionOptions;
|
|
208
|
+
pii?: PIIOptions;
|
|
209
|
+
batching?: BatchingOptions;
|
|
210
|
+
onError?: ErrorHandler;
|
|
211
|
+
objectLock?: ObjectLockOptions;
|
|
212
|
+
healthCheck?: HealthCheckOptions;
|
|
213
|
+
}
|
|
214
|
+
declare class AuditLogger {
|
|
215
|
+
private readonly config;
|
|
216
|
+
private readonly systemId;
|
|
217
|
+
private readonly storage;
|
|
218
|
+
private readonly storageConfig;
|
|
219
|
+
private readonly retention;
|
|
220
|
+
private readonly pii;
|
|
221
|
+
private readonly batching;
|
|
222
|
+
private readonly onError;
|
|
223
|
+
private readonly objectLock;
|
|
224
|
+
private buffer;
|
|
225
|
+
private seq;
|
|
226
|
+
private prevHash;
|
|
227
|
+
private currentFileIndex;
|
|
228
|
+
private currentFileSize;
|
|
229
|
+
private flushTimer;
|
|
230
|
+
private closed;
|
|
231
|
+
private initialised;
|
|
232
|
+
private healthCheckTimer;
|
|
233
|
+
private shutdownHandler;
|
|
234
|
+
constructor(config: AuditLoggerConfig);
|
|
235
|
+
/**
|
|
236
|
+
* Initialise the logger. Loads chain head from storage,
|
|
237
|
+
* recovers chain state, and writes initial metadata.
|
|
238
|
+
*
|
|
239
|
+
* Must be called before the first log() call. If not called
|
|
240
|
+
* explicitly, log() will call it lazily.
|
|
241
|
+
*/
|
|
242
|
+
init(): Promise<void>;
|
|
243
|
+
/**
|
|
244
|
+
* Log a single event.
|
|
245
|
+
*
|
|
246
|
+
* If no decisionId is provided and no AsyncLocalStorage context is active,
|
|
247
|
+
* throws MissingDecisionIdError.
|
|
248
|
+
*/
|
|
249
|
+
log(input: LogEntryInput): Promise<AuditLogEntryExtended>;
|
|
250
|
+
/**
|
|
251
|
+
* Force flush all buffered entries to storage.
|
|
252
|
+
*/
|
|
253
|
+
flush(): Promise<void>;
|
|
254
|
+
/**
|
|
255
|
+
* Flush remaining entries and release all resources.
|
|
256
|
+
*/
|
|
257
|
+
close(): Promise<void>;
|
|
258
|
+
/**
|
|
259
|
+
* Run a health check against the storage backend.
|
|
260
|
+
*/
|
|
261
|
+
healthCheck(): Promise<HealthCheckResult>;
|
|
262
|
+
getSystemId(): string;
|
|
263
|
+
getStorageBackend(): StorageBackend;
|
|
264
|
+
getStorageConfig(): StorageConfig;
|
|
265
|
+
getCurrentSeq(): number;
|
|
266
|
+
getPrevHash(): string;
|
|
267
|
+
/** Exposed for testing with custom storage backends */
|
|
268
|
+
static createWithStorage(config: Omit<AuditLoggerConfig, 'storage'> & {
|
|
269
|
+
storage: StorageConfig;
|
|
270
|
+
}, storage: StorageBackend): AuditLogger;
|
|
271
|
+
private processInputData;
|
|
272
|
+
private processOutputData;
|
|
273
|
+
private redact;
|
|
274
|
+
private currentDatePath;
|
|
275
|
+
private currentFilePath;
|
|
276
|
+
private writeEntries;
|
|
277
|
+
private persistChainHead;
|
|
278
|
+
private loadChainHead;
|
|
279
|
+
private writeMetadata;
|
|
280
|
+
private scheduleFlush;
|
|
281
|
+
private clearFlushTimer;
|
|
282
|
+
private handleError;
|
|
283
|
+
private setupShutdownHooks;
|
|
284
|
+
private removeShutdownHooks;
|
|
285
|
+
private checkWriteAccess;
|
|
286
|
+
private checkReadAccess;
|
|
287
|
+
private checkChainHeadConsistency;
|
|
288
|
+
private checkSchemaVersion;
|
|
289
|
+
}
|
|
290
|
+
interface HealthCheckResult {
|
|
291
|
+
timestamp: string;
|
|
292
|
+
checks: HealthCheck[];
|
|
293
|
+
healthy: boolean;
|
|
294
|
+
}
|
|
295
|
+
interface HealthCheck {
|
|
296
|
+
name: string;
|
|
297
|
+
status: 'pass' | 'fail' | 'warn';
|
|
298
|
+
message: string;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* AI SDK middleware — automatic capture for Vercel AI SDK models.
|
|
303
|
+
*
|
|
304
|
+
* Wraps a LanguageModelV1 and automatically logs every inference call.
|
|
305
|
+
* This is the primary mechanism for satisfying Article 12(1)'s
|
|
306
|
+
* "automatic recording" requirement for inference events.
|
|
307
|
+
*
|
|
308
|
+
* Usage:
|
|
309
|
+
* const model = auditMiddleware(anthropic('claude-sonnet-4-5-20250929'), { logger })
|
|
310
|
+
* const result = await generateText({ model, prompt })
|
|
311
|
+
* // ^ Inference is automatically logged
|
|
312
|
+
*/
|
|
313
|
+
|
|
314
|
+
interface AuditMiddlewareOptions {
|
|
315
|
+
logger: AuditLogger;
|
|
316
|
+
captureInputs?: boolean;
|
|
317
|
+
captureOutputs?: boolean;
|
|
318
|
+
captureToolCalls?: boolean;
|
|
319
|
+
captureParameters?: boolean;
|
|
320
|
+
}
|
|
321
|
+
declare function auditMiddleware(model: LanguageModelV1, options: AuditMiddlewareOptions): LanguageModelV1;
|
|
322
|
+
|
|
323
|
+
export { type AuditMiddlewareOptions, auditMiddleware };
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { LanguageModelV1 } from 'ai';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Storage backend interface.
|
|
5
|
+
*
|
|
6
|
+
* Abstraction layer for log persistence. v0.1 ships with S3-compatible
|
|
7
|
+
* storage only; future versions will add Azure Blob, GCS, and local
|
|
8
|
+
* filesystem backends.
|
|
9
|
+
*/
|
|
10
|
+
interface StorageBackend {
|
|
11
|
+
write(key: string, data: Buffer): Promise<void>;
|
|
12
|
+
read(key: string): Promise<Buffer>;
|
|
13
|
+
list(prefix: string): Promise<string[]>;
|
|
14
|
+
exists(key: string): Promise<boolean>;
|
|
15
|
+
getObjectMetadata(key: string): Promise<ObjectMetadata>;
|
|
16
|
+
}
|
|
17
|
+
interface ObjectMetadata {
|
|
18
|
+
lastModified: Date;
|
|
19
|
+
size: number;
|
|
20
|
+
}
|
|
21
|
+
interface S3StorageConfig {
|
|
22
|
+
type: 's3';
|
|
23
|
+
bucket: string;
|
|
24
|
+
region: string;
|
|
25
|
+
prefix?: string;
|
|
26
|
+
endpoint?: string;
|
|
27
|
+
forcePathStyle?: boolean;
|
|
28
|
+
credentials?: {
|
|
29
|
+
accessKeyId: string;
|
|
30
|
+
secretAccessKey: string;
|
|
31
|
+
sessionToken?: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
type StorageConfig = S3StorageConfig;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Schema definitions and runtime validation for audit log entries.
|
|
38
|
+
*
|
|
39
|
+
* Every field is annotated with the Article paragraph it satisfies.
|
|
40
|
+
* The schema IS the Article 12 mapping.
|
|
41
|
+
*/
|
|
42
|
+
declare const EVENT_TYPES: readonly ["inference", "tool_call", "tool_result", "human_intervention", "system_event", "session_start", "session_end"];
|
|
43
|
+
type EventType = (typeof EVENT_TYPES)[number];
|
|
44
|
+
declare const CAPTURE_METHODS: readonly ["middleware", "manual", "context"];
|
|
45
|
+
type CaptureMethod = (typeof CAPTURE_METHODS)[number];
|
|
46
|
+
interface InputData {
|
|
47
|
+
type: 'raw' | 'hash';
|
|
48
|
+
value: string;
|
|
49
|
+
tokenCount?: number;
|
|
50
|
+
}
|
|
51
|
+
interface OutputData {
|
|
52
|
+
type: 'raw' | 'hash';
|
|
53
|
+
value: string;
|
|
54
|
+
tokenCount?: number;
|
|
55
|
+
finishReason?: string;
|
|
56
|
+
}
|
|
57
|
+
interface TokenUsage {
|
|
58
|
+
promptTokens: number;
|
|
59
|
+
completionTokens: number;
|
|
60
|
+
totalTokens: number;
|
|
61
|
+
}
|
|
62
|
+
interface ErrorData {
|
|
63
|
+
code: string;
|
|
64
|
+
message: string;
|
|
65
|
+
stack?: string;
|
|
66
|
+
}
|
|
67
|
+
declare const HUMAN_INTERVENTION_TYPES: readonly ["approval", "rejection", "modification", "override", "escalation"];
|
|
68
|
+
type HumanInterventionType = (typeof HUMAN_INTERVENTION_TYPES)[number];
|
|
69
|
+
interface HumanIntervention {
|
|
70
|
+
/** @article 12(2)(c), 14(5), 12(3)(d) */
|
|
71
|
+
type: HumanInterventionType;
|
|
72
|
+
userId: string;
|
|
73
|
+
reason?: string;
|
|
74
|
+
originalOutput?: {
|
|
75
|
+
type: 'raw' | 'hash';
|
|
76
|
+
value: string;
|
|
77
|
+
};
|
|
78
|
+
timestamp: string;
|
|
79
|
+
}
|
|
80
|
+
interface ToolCallData {
|
|
81
|
+
toolName: string;
|
|
82
|
+
toolArgs: Record<string, unknown> | string;
|
|
83
|
+
toolResult?: string;
|
|
84
|
+
}
|
|
85
|
+
interface MatchResult {
|
|
86
|
+
matched: boolean;
|
|
87
|
+
matchConfidence?: number;
|
|
88
|
+
matchedRecordId?: string;
|
|
89
|
+
}
|
|
90
|
+
interface AuditLogEntry {
|
|
91
|
+
schemaVersion: 'v1';
|
|
92
|
+
/** @article 12(1) — unique event identification */
|
|
93
|
+
entryId: string;
|
|
94
|
+
/** @article 12(2)(a) — risk identification requires correlating related events */
|
|
95
|
+
decisionId: string;
|
|
96
|
+
/** @article 12(1) — system identification */
|
|
97
|
+
systemId: string;
|
|
98
|
+
/** @article 12(1), 12(3)(a) — automatic recording, period of each use */
|
|
99
|
+
timestamp: string;
|
|
100
|
+
/** @article 12(2)(a) — identifying situations that may present a risk */
|
|
101
|
+
eventType: EventType;
|
|
102
|
+
/** @article 12(2)(a), 72 — model version tracking */
|
|
103
|
+
modelId: string | null;
|
|
104
|
+
providerId: string | null;
|
|
105
|
+
/** @article 12(2)(a), 12(2)(c), 12(3)(c) — input context and deployer monitoring */
|
|
106
|
+
input: InputData;
|
|
107
|
+
/** @article 12(2)(a), 12(2)(b) — risk identification and post-market monitoring */
|
|
108
|
+
output: OutputData | null;
|
|
109
|
+
/** @article 12(2)(b), 72 — performance degradation detection */
|
|
110
|
+
latencyMs: number | null;
|
|
111
|
+
/** @article 72 — post-market monitoring (usage tracking) */
|
|
112
|
+
usage: TokenUsage | null;
|
|
113
|
+
/** @article 12(2)(a) — identifying situations that may present a risk */
|
|
114
|
+
error: ErrorData | null;
|
|
115
|
+
/** @article 12(2)(a) — risk identification requires knowing configuration */
|
|
116
|
+
parameters: Record<string, unknown> | null;
|
|
117
|
+
captureMethod: CaptureMethod;
|
|
118
|
+
/** @article 12(1) — event ordering */
|
|
119
|
+
seq: number;
|
|
120
|
+
prevHash: string;
|
|
121
|
+
hash: string;
|
|
122
|
+
}
|
|
123
|
+
interface AuditLogEntryExtended extends AuditLogEntry {
|
|
124
|
+
/** @article 12(2)(c), 14(5), 12(3)(d) */
|
|
125
|
+
humanIntervention?: HumanIntervention;
|
|
126
|
+
/** @article 12(2)(a) — tracing complex decision chains */
|
|
127
|
+
stepIndex?: number;
|
|
128
|
+
parentEntryId?: string;
|
|
129
|
+
/** @article 12(2)(a) — tool calls as risk vectors */
|
|
130
|
+
toolCall?: ToolCallData;
|
|
131
|
+
/** @article 12(3)(b) — reference database for biometric systems */
|
|
132
|
+
referenceDatabase?: string;
|
|
133
|
+
/** @article 12(3)(c) — match results for biometric systems */
|
|
134
|
+
matchResult?: MatchResult;
|
|
135
|
+
metadata?: Record<string, string | number | boolean>;
|
|
136
|
+
}
|
|
137
|
+
interface LogEntryInput {
|
|
138
|
+
decisionId?: string;
|
|
139
|
+
eventType: EventType;
|
|
140
|
+
modelId: string | null;
|
|
141
|
+
providerId: string | null;
|
|
142
|
+
input: {
|
|
143
|
+
value: string;
|
|
144
|
+
type?: 'raw' | 'hash';
|
|
145
|
+
tokenCount?: number;
|
|
146
|
+
};
|
|
147
|
+
output: {
|
|
148
|
+
value: string;
|
|
149
|
+
type?: 'raw' | 'hash';
|
|
150
|
+
tokenCount?: number;
|
|
151
|
+
finishReason?: string;
|
|
152
|
+
} | null;
|
|
153
|
+
latencyMs: number | null;
|
|
154
|
+
usage: TokenUsage | null;
|
|
155
|
+
parameters: Record<string, unknown> | null;
|
|
156
|
+
error: ErrorData | null;
|
|
157
|
+
captureMethod?: CaptureMethod;
|
|
158
|
+
humanIntervention?: HumanIntervention;
|
|
159
|
+
stepIndex?: number;
|
|
160
|
+
parentEntryId?: string;
|
|
161
|
+
toolCall?: ToolCallData;
|
|
162
|
+
referenceDatabase?: string;
|
|
163
|
+
matchResult?: MatchResult;
|
|
164
|
+
metadata?: Record<string, string | number | boolean>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* AuditLogger — core class for structured, tamper-evident audit logging.
|
|
169
|
+
*
|
|
170
|
+
* Satisfies Article 12(1): automatic recording of events over the
|
|
171
|
+
* lifetime of the system. Entries are batched in memory and flushed
|
|
172
|
+
* to S3-compatible storage with SHA-256 hash chains.
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
interface RetentionOptions {
|
|
176
|
+
minimumDays?: number;
|
|
177
|
+
acknowledgeSubMinimum?: boolean;
|
|
178
|
+
autoConfigureLifecycle?: boolean;
|
|
179
|
+
}
|
|
180
|
+
interface PIIOptions {
|
|
181
|
+
hashInputs?: boolean;
|
|
182
|
+
hashOutputs?: boolean;
|
|
183
|
+
redactPatterns?: RegExp[];
|
|
184
|
+
}
|
|
185
|
+
interface BatchingOptions {
|
|
186
|
+
maxSize?: number;
|
|
187
|
+
maxDelayMs?: number;
|
|
188
|
+
}
|
|
189
|
+
interface ObjectLockOptions {
|
|
190
|
+
enabled?: boolean;
|
|
191
|
+
mode?: 'GOVERNANCE' | 'COMPLIANCE';
|
|
192
|
+
}
|
|
193
|
+
interface HealthCheckOptions {
|
|
194
|
+
enabled?: boolean;
|
|
195
|
+
intervalMs?: number;
|
|
196
|
+
onDrift?: 'warn' | 'throw' | ((drift: ComplianceDrift) => void);
|
|
197
|
+
}
|
|
198
|
+
interface ComplianceDrift {
|
|
199
|
+
check: string;
|
|
200
|
+
status: 'fail' | 'warn';
|
|
201
|
+
message: string;
|
|
202
|
+
}
|
|
203
|
+
type ErrorHandler = 'log-and-continue' | 'throw' | ((error: Error) => void);
|
|
204
|
+
interface AuditLoggerConfig {
|
|
205
|
+
systemId: string;
|
|
206
|
+
storage: StorageConfig;
|
|
207
|
+
retention?: RetentionOptions;
|
|
208
|
+
pii?: PIIOptions;
|
|
209
|
+
batching?: BatchingOptions;
|
|
210
|
+
onError?: ErrorHandler;
|
|
211
|
+
objectLock?: ObjectLockOptions;
|
|
212
|
+
healthCheck?: HealthCheckOptions;
|
|
213
|
+
}
|
|
214
|
+
declare class AuditLogger {
|
|
215
|
+
private readonly config;
|
|
216
|
+
private readonly systemId;
|
|
217
|
+
private readonly storage;
|
|
218
|
+
private readonly storageConfig;
|
|
219
|
+
private readonly retention;
|
|
220
|
+
private readonly pii;
|
|
221
|
+
private readonly batching;
|
|
222
|
+
private readonly onError;
|
|
223
|
+
private readonly objectLock;
|
|
224
|
+
private buffer;
|
|
225
|
+
private seq;
|
|
226
|
+
private prevHash;
|
|
227
|
+
private currentFileIndex;
|
|
228
|
+
private currentFileSize;
|
|
229
|
+
private flushTimer;
|
|
230
|
+
private closed;
|
|
231
|
+
private initialised;
|
|
232
|
+
private healthCheckTimer;
|
|
233
|
+
private shutdownHandler;
|
|
234
|
+
constructor(config: AuditLoggerConfig);
|
|
235
|
+
/**
|
|
236
|
+
* Initialise the logger. Loads chain head from storage,
|
|
237
|
+
* recovers chain state, and writes initial metadata.
|
|
238
|
+
*
|
|
239
|
+
* Must be called before the first log() call. If not called
|
|
240
|
+
* explicitly, log() will call it lazily.
|
|
241
|
+
*/
|
|
242
|
+
init(): Promise<void>;
|
|
243
|
+
/**
|
|
244
|
+
* Log a single event.
|
|
245
|
+
*
|
|
246
|
+
* If no decisionId is provided and no AsyncLocalStorage context is active,
|
|
247
|
+
* throws MissingDecisionIdError.
|
|
248
|
+
*/
|
|
249
|
+
log(input: LogEntryInput): Promise<AuditLogEntryExtended>;
|
|
250
|
+
/**
|
|
251
|
+
* Force flush all buffered entries to storage.
|
|
252
|
+
*/
|
|
253
|
+
flush(): Promise<void>;
|
|
254
|
+
/**
|
|
255
|
+
* Flush remaining entries and release all resources.
|
|
256
|
+
*/
|
|
257
|
+
close(): Promise<void>;
|
|
258
|
+
/**
|
|
259
|
+
* Run a health check against the storage backend.
|
|
260
|
+
*/
|
|
261
|
+
healthCheck(): Promise<HealthCheckResult>;
|
|
262
|
+
getSystemId(): string;
|
|
263
|
+
getStorageBackend(): StorageBackend;
|
|
264
|
+
getStorageConfig(): StorageConfig;
|
|
265
|
+
getCurrentSeq(): number;
|
|
266
|
+
getPrevHash(): string;
|
|
267
|
+
/** Exposed for testing with custom storage backends */
|
|
268
|
+
static createWithStorage(config: Omit<AuditLoggerConfig, 'storage'> & {
|
|
269
|
+
storage: StorageConfig;
|
|
270
|
+
}, storage: StorageBackend): AuditLogger;
|
|
271
|
+
private processInputData;
|
|
272
|
+
private processOutputData;
|
|
273
|
+
private redact;
|
|
274
|
+
private currentDatePath;
|
|
275
|
+
private currentFilePath;
|
|
276
|
+
private writeEntries;
|
|
277
|
+
private persistChainHead;
|
|
278
|
+
private loadChainHead;
|
|
279
|
+
private writeMetadata;
|
|
280
|
+
private scheduleFlush;
|
|
281
|
+
private clearFlushTimer;
|
|
282
|
+
private handleError;
|
|
283
|
+
private setupShutdownHooks;
|
|
284
|
+
private removeShutdownHooks;
|
|
285
|
+
private checkWriteAccess;
|
|
286
|
+
private checkReadAccess;
|
|
287
|
+
private checkChainHeadConsistency;
|
|
288
|
+
private checkSchemaVersion;
|
|
289
|
+
}
|
|
290
|
+
interface HealthCheckResult {
|
|
291
|
+
timestamp: string;
|
|
292
|
+
checks: HealthCheck[];
|
|
293
|
+
healthy: boolean;
|
|
294
|
+
}
|
|
295
|
+
interface HealthCheck {
|
|
296
|
+
name: string;
|
|
297
|
+
status: 'pass' | 'fail' | 'warn';
|
|
298
|
+
message: string;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* AI SDK middleware — automatic capture for Vercel AI SDK models.
|
|
303
|
+
*
|
|
304
|
+
* Wraps a LanguageModelV1 and automatically logs every inference call.
|
|
305
|
+
* This is the primary mechanism for satisfying Article 12(1)'s
|
|
306
|
+
* "automatic recording" requirement for inference events.
|
|
307
|
+
*
|
|
308
|
+
* Usage:
|
|
309
|
+
* const model = auditMiddleware(anthropic('claude-sonnet-4-5-20250929'), { logger })
|
|
310
|
+
* const result = await generateText({ model, prompt })
|
|
311
|
+
* // ^ Inference is automatically logged
|
|
312
|
+
*/
|
|
313
|
+
|
|
314
|
+
interface AuditMiddlewareOptions {
|
|
315
|
+
logger: AuditLogger;
|
|
316
|
+
captureInputs?: boolean;
|
|
317
|
+
captureOutputs?: boolean;
|
|
318
|
+
captureToolCalls?: boolean;
|
|
319
|
+
captureParameters?: boolean;
|
|
320
|
+
}
|
|
321
|
+
declare function auditMiddleware(model: LanguageModelV1, options: AuditMiddlewareOptions): LanguageModelV1;
|
|
322
|
+
|
|
323
|
+
export { type AuditMiddlewareOptions, auditMiddleware };
|