Files
waggle-os/packages/core/src/compliance/status-checker.ts
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

176 lines
6.8 KiB
TypeScript

/**
* ComplianceStatusChecker — evaluates AI Act compliance per workspace.
*
* Checks:
* - Art. 12: Automatic event logging (ai_interactions count)
* - Art. 14: Human oversight (approval/denial actions recorded)
* - Art. 19: Log retention (oldest log >= 6 months ago)
* - Art. 26: Deployer monitoring (active monitors)
* - Art. 50: Model transparency (models disclosed)
*/
import type { InteractionStore } from './interaction-store.js';
import type { ComplianceStatus, ArticleStatus } from './types.js';
const SIX_MONTHS_MS = 180 * 24 * 60 * 60 * 1000;
export class ComplianceStatusChecker {
private store: InteractionStore;
constructor(store: InteractionStore) {
this.store = store;
}
/** Evaluate full compliance status for a workspace (or all workspaces if null). */
check(workspaceId?: string): ComplianceStatus {
const art12 = this.checkArt12(workspaceId);
const art14 = this.checkArt14(workspaceId);
const art19 = this.checkArt19();
const art26 = this.checkArt26();
const art50 = this.checkArt50(workspaceId);
const statuses = [art12.status, art14.status, art19.status, art26.status, art50.status];
const overall = statuses.includes('non-compliant') ? 'non-compliant'
: statuses.includes('warning') ? 'warning'
: 'compliant';
return {
overall,
art12Logging: art12,
art14Oversight: art14,
art19Retention: art19,
art26Monitoring: art26,
art50Transparency: art50,
};
}
/** Art. 12: Automatic recording of events. */
private checkArt12(workspaceId?: string): ArticleStatus & { totalInteractions: number } {
const total = this.store.count(workspaceId);
if (total === 0) {
// M11: if the DB has been active for >24h with zero interactions, escalate to non-compliant
const firstRun = this.store.getFirstRunAt();
const activeOver24h = firstRun
? (Date.now() - new Date(firstRun).getTime()) > 24 * 60 * 60 * 1000
: false;
return {
status: activeOver24h ? 'non-compliant' : 'warning',
detail: activeOver24h
? 'No interactions logged despite system being active for over 24 hours. Verify logging pipeline.'
: 'No interactions logged yet. Logging activates automatically on first AI interaction.',
totalInteractions: 0,
};
}
return {
status: 'compliant',
detail: `${total} interactions logged with full model, token, cost, and tool tracking.`,
totalInteractions: total,
};
}
/** Art. 14: Human oversight capability. */
private checkArt14(workspaceId?: string): ArticleStatus & { humanActions: number; approvalRate: number } {
const counts = this.store.getOversightCounts(workspaceId);
if (counts.total === 0) {
return {
status: 'compliant',
detail: 'Human oversight capabilities available (approval gates, tool deny lists). No oversight actions recorded yet.',
humanActions: 0,
approvalRate: 0,
};
}
const approvalRate = counts.total > 0
? Math.round((counts.approved / counts.total) * 100)
: 0;
return {
status: 'compliant',
detail: `${counts.total} human oversight actions: ${counts.approved} approved, ${counts.denied} denied, ${counts.modified} modified.`,
humanActions: counts.total,
approvalRate,
};
}
/** Art. 19: Log retention (minimum 6 months). */
private checkArt19(): ArticleStatus & { oldestLogDate: string | null; retentionDays: number } {
const oldest = this.store.getOldestTimestamp();
if (!oldest) {
return {
status: 'compliant',
detail: 'No logs to retain yet. Retention policy is permanent by default.',
oldestLogDate: null,
retentionDays: 0,
};
}
const oldestDate = new Date(oldest);
const now = new Date();
const retentionMs = now.getTime() - oldestDate.getTime();
const retentionDays = Math.floor(retentionMs / (24 * 60 * 60 * 1000));
// Review Critical #2: the previous expression was a tautology
// (`retentionMs >= SIX_MONTHS_MS || retentionDays < 180`) that covered every
// non-negative value of retentionDays. A deployment that pruned logs after 30
// days still reported compliant.
//
// Proper fix requires distinguishing 'system is young' from 'logs were pruned'.
// We track system age via MindDB's `meta.first_run_at` entry (set on schema init,
// backfilled for pre-existing DBs). If the system has been running for 180+ days
// but the oldest log is younger than that, something pruned the logs and we
// correctly report warning.
const firstRun = this.store.getFirstRunAt();
const systemAgeMs = firstRun ? now.getTime() - new Date(firstRun).getTime() : retentionMs;
const hasBeenRunning6Months = systemAgeMs >= SIX_MONTHS_MS;
const logsOlderThan6Months = retentionMs >= SIX_MONTHS_MS;
const meetsMinimum = !hasBeenRunning6Months || logsOlderThan6Months;
return {
status: meetsMinimum ? 'compliant' : 'warning',
detail: meetsMinimum
? hasBeenRunning6Months
? `Logs retained since ${oldest.split('T')[0]} (${retentionDays} days). Art. 19 minimum (180 days) met.`
: `Logs retained since ${oldest.split('T')[0]} (${retentionDays} days). System is still within its first 180 days — retention compliance will be enforceable after 2026-${(new Date(firstRun ?? now).getMonth() + 7).toString().padStart(2, '0')}.`
: `Oldest log: ${oldest.split('T')[0]} (${retentionDays} days) but system is ${Math.floor(systemAgeMs / (24 * 60 * 60 * 1000))} days old. Logs appear to have been pruned — EU AI Act Art. 19 requires 180-day minimum retention.`,
oldestLogDate: oldest,
retentionDays,
};
}
/** Art. 26: Deployer monitoring obligations. */
private checkArt26(): ArticleStatus & { activeMonitors: string[] } {
// Waggle always has these monitors active
const monitors = [
'cost_tracking', // CostTracker in packages/agent
'tool_logging', // Tool calls logged per interaction
'model_identification', // Model recorded per interaction
'persona_tracking', // Persona recorded per interaction
];
return {
status: 'compliant',
detail: `${monitors.length} active monitors: cost, tools, model ID, persona.`,
activeMonitors: monitors,
};
}
/** Art. 50: Transparency — models disclosed. */
private checkArt50(workspaceId?: string): ArticleStatus & { modelsDisclosed: boolean } {
const inventory = this.store.getModelInventory(undefined, undefined, workspaceId);
const modelsDisclosed = inventory.length > 0;
return {
status: 'compliant',
detail: modelsDisclosed
? `${inventory.length} model(s) in use, all identified in StatusBar and interaction logs.`
: 'Model identification active. Models will be disclosed on first interaction.',
modelsDisclosed,
};
}
}