ai-audit-logging
SKILL.md
AI Audit Logging
Implement compliance-ready audit trails for AI system decisions and outputs.
When to Use
- Meeting regulatory requirements (EU AI Act, SOC2, HIPAA)
- Tracking AI decisions for accountability
- Debugging AI system behavior
- Investigating incidents involving AI
- Demonstrating AI governance
Regulatory Context (2026)
EU AI Act Requirements
-
High-risk AI systems must maintain logs that enable:
- Traceability of AI decisions
- Recording of reference data
- Logging of events throughout lifecycle
- Logs must be kept for appropriate period
-
Effective August 2026 with fines up to 7% of global revenue
SOC2 AI Considerations
- Processing Integrity: AI outputs must be accurate and complete
- Confidentiality: AI must not expose confidential data
- Availability: AI systems must maintain audit logs
Audit Log Schema
interface AIAuditLog {
// Identification
id: string;
timestamp: Date;
correlationId: string;
sessionId: string;
// Actor
actor: {
type: 'user' | 'system' | 'automated';
id: string;
name?: string;
ip?: string;
userAgent?: string;
};
// AI Operation
operation: {
type: 'inference' | 'generation' | 'classification' | 'analysis';
model: string;
modelVersion: string;
provider: string;
};
// Input/Output
io: {
inputHash: string; // Hash of input for privacy
inputTokens: number;
outputHash: string; // Hash of output
outputTokens: number;
inputSummary?: string; // Optional human-readable summary
outputSummary?: string;
};
// Decisions
decision?: {
action: string;
confidence: number;
alternatives?: { action: string; confidence: number }[];
reasoning?: string;
};
// Safety
safety: {
contentFiltered: boolean;
filterReasons?: string[];
humanReviewRequired: boolean;
humanReviewCompleted?: boolean;
reviewerId?: string;
};
// Performance
performance: {
latencyMs: number;
queueTimeMs?: number;
tokensPerSecond?: number;
};
// Cost
cost: {
inputCost: number;
outputCost: number;
totalCost: number;
currency: string;
};
// Context
context: {
application: string;
environment: 'production' | 'staging' | 'development';
feature?: string;
tags: string[];
};
// Metadata
metadata: Record<string, unknown>;
}
Implementation
Logger Class
import { createHash } from 'crypto';
class AIAuditLogger {
constructor(
private storage: AuditStorage,
private config: AuditConfig
) {}
async log(event: Partial<AIAuditLog>): Promise<string> {
const log: AIAuditLog = {
id: crypto.randomUUID(),
timestamp: new Date(),
correlationId: event.correlationId || crypto.randomUUID(),
sessionId: event.sessionId || 'unknown',
actor: event.actor || { type: 'system', id: 'unknown' },
operation: event.operation!,
io: event.io!,
safety: event.safety || { contentFiltered: false, humanReviewRequired: false },
performance: event.performance || { latencyMs: 0 },
cost: event.cost || { inputCost: 0, outputCost: 0, totalCost: 0, currency: 'USD' },
context: event.context || { application: 'unknown', environment: 'production', tags: [] },
metadata: event.metadata || {}
};
// Validate required fields
this.validate(log);
// Hash sensitive content
log.io.inputHash = this.hashContent(log.io.inputHash);
log.io.outputHash = this.hashContent(log.io.outputHash);
// Store
await this.storage.write(log);
// Alert if needed
if (log.safety.humanReviewRequired) {
await this.alertForReview(log);
}
return log.id;
}
private hashContent(content: string): string {
return createHash('sha256').update(content).digest('hex');
}
private validate(log: AIAuditLog): void {
if (!log.operation?.model) {
throw new Error('Audit log must include model information');
}
if (log.io.inputTokens === undefined || log.io.outputTokens === undefined) {
throw new Error('Audit log must include token counts');
}
}
private async alertForReview(log: AIAuditLog): Promise<void> {
// Send to review queue
await this.config.reviewQueue?.push({
logId: log.id,
reason: log.safety.filterReasons?.join(', '),
priority: 'high'
});
}
}
Middleware for API Calls
function withAuditLogging(
client: LLMClient,
logger: AIAuditLogger,
context: Partial<AIAuditLog['context']>
): LLMClient {
return {
async complete(params: CompletionParams): Promise<CompletionResponse> {
const startTime = Date.now();
try {
const response = await client.complete(params);
await logger.log({
operation: {
type: 'generation',
model: params.model,
modelVersion: response.model,
provider: client.provider
},
io: {
inputHash: params.messages.map(m => m.content).join(''),
inputTokens: response.usage.input_tokens,
outputHash: response.content[0].text,
outputTokens: response.usage.output_tokens
},
performance: {
latencyMs: Date.now() - startTime
},
cost: calculateCost(params.model, response.usage),
context: {
...context,
application: context.application || 'default',
environment: context.environment || 'production',
tags: context.tags || []
}
});
return response;
} catch (error) {
await logger.log({
operation: {
type: 'generation',
model: params.model,
modelVersion: 'unknown',
provider: client.provider
},
io: {
inputHash: params.messages.map(m => m.content).join(''),
inputTokens: 0,
outputHash: '',
outputTokens: 0
},
performance: {
latencyMs: Date.now() - startTime
},
metadata: {
error: (error as Error).message,
errorType: (error as Error).name
},
context: context as any
});
throw error;
}
}
};
}
Storage Backends
Database Storage
import { PrismaClient } from '@prisma/client';
class DatabaseAuditStorage implements AuditStorage {
constructor(private prisma: PrismaClient) {}
async write(log: AIAuditLog): Promise<void> {
await this.prisma.aiAuditLog.create({
data: {
id: log.id,
timestamp: log.timestamp,
correlationId: log.correlationId,
sessionId: log.sessionId,
actorType: log.actor.type,
actorId: log.actor.id,
model: log.operation.model,
operationType: log.operation.type,
inputTokens: log.io.inputTokens,
outputTokens: log.io.outputTokens,
inputHash: log.io.inputHash,
outputHash: log.io.outputHash,
latencyMs: log.performance.latencyMs,
totalCost: log.cost.totalCost,
contentFiltered: log.safety.contentFiltered,
humanReviewRequired: log.safety.humanReviewRequired,
application: log.context.application,
environment: log.context.environment,
metadata: log.metadata as any
}
});
}
async query(filters: AuditQueryFilters): Promise<AIAuditLog[]> {
return this.prisma.aiAuditLog.findMany({
where: {
timestamp: {
gte: filters.startDate,
lte: filters.endDate
},
actorId: filters.actorId,
model: filters.model,
application: filters.application
},
orderBy: { timestamp: 'desc' },
take: filters.limit || 100
});
}
}
Immutable Log Storage
// For compliance, logs should be immutable and tamper-evident
class ImmutableAuditStorage implements AuditStorage {
private previousHash = '0';
async write(log: AIAuditLog): Promise<void> {
// Chain logs with hashes for tamper evidence
const logWithChain = {
...log,
previousLogHash: this.previousHash,
logHash: this.hashLog(log, this.previousHash)
};
await this.storage.append(logWithChain);
this.previousHash = logWithChain.logHash;
}
private hashLog(log: AIAuditLog, previousHash: string): string {
const content = JSON.stringify({ ...log, previousHash });
return createHash('sha256').update(content).digest('hex');
}
async verifyIntegrity(logs: AIAuditLog[]): Promise<boolean> {
let expectedHash = '0';
for (const log of logs) {
const computedHash = this.hashLog(log, expectedHash);
if (computedHash !== (log as any).logHash) {
return false; // Tampering detected
}
expectedHash = computedHash;
}
return true;
}
}
Compliance Reports
interface ComplianceReport {
period: { start: Date; end: Date };
summary: {
totalAIOperations: number;
uniqueUsers: number;
totalCost: number;
contentFilteredCount: number;
humanReviewCount: number;
};
modelUsage: { model: string; count: number; cost: number }[];
riskEvents: {
id: string;
timestamp: Date;
type: string;
severity: 'low' | 'medium' | 'high';
resolution?: string;
}[];
dataRetention: {
logsRetained: number;
oldestLog: Date;
retentionPolicy: string;
};
}
async function generateComplianceReport(
storage: AuditStorage,
period: { start: Date; end: Date }
): Promise<ComplianceReport> {
const logs = await storage.query({ startDate: period.start, endDate: period.end });
// Aggregate metrics
const uniqueUsers = new Set(logs.map(l => l.actor.id)).size;
const totalCost = logs.reduce((sum, l) => sum + l.cost.totalCost, 0);
const contentFiltered = logs.filter(l => l.safety.contentFiltered).length;
const humanReview = logs.filter(l => l.safety.humanReviewRequired).length;
// Model usage breakdown
const modelUsage = new Map<string, { count: number; cost: number }>();
for (const log of logs) {
const existing = modelUsage.get(log.operation.model) || { count: 0, cost: 0 };
modelUsage.set(log.operation.model, {
count: existing.count + 1,
cost: existing.cost + log.cost.totalCost
});
}
return {
period,
summary: {
totalAIOperations: logs.length,
uniqueUsers,
totalCost,
contentFilteredCount: contentFiltered,
humanReviewCount: humanReview
},
modelUsage: Array.from(modelUsage.entries()).map(([model, data]) => ({
model,
...data
})),
riskEvents: logs
.filter(l => l.safety.contentFiltered || l.safety.humanReviewRequired)
.map(l => ({
id: l.id,
timestamp: l.timestamp,
type: l.safety.filterReasons?.[0] || 'unknown',
severity: l.safety.humanReviewRequired ? 'high' : 'medium' as const
})),
dataRetention: {
logsRetained: logs.length,
oldestLog: logs[logs.length - 1]?.timestamp || new Date(),
retentionPolicy: '90 days'
}
};
}
Best Practices
- Log everything - Better to have too much than too little
- Hash sensitive data - Don't store PII in plain text
- Use immutable storage - Prevent tampering
- Set retention policies - Balance compliance and cost
- Enable search - Logs are useless if unsearchable
- Automate reporting - Compliance shouldn't be manual
- Test your logging - Ensure it works before you need it
Weekly Installs
3
Repository
latestaiagents/…t-skillsGitHub Stars
2
First Seen
Feb 4, 2026
Installed on
mcpjam3
claude-code3
replit3
junie3
windsurf3
zencoder3