Add L0-L4 security clearance for fleet commands and AI elevation.

Gate manual and AI commands by per-agent clearance, auto-elevate stuck hosts to L4 when AI mode allows, and surface clearance in Access Depth and LOTL timeline.
This commit is contained in:
AetherForge
2026-06-07 02:30:00 -07:00
parent dd612251d1
commit f89ba94cb7
21 changed files with 952 additions and 46 deletions

View File

@@ -0,0 +1,28 @@
/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest';
import {
clearanceLabel,
clearancePermissions,
formatClearanceElevation,
} from './clearance';
describe('clearance helpers', () => {
it('labels L0L4', () => {
expect(clearanceLabel(0)).toBe('L0');
expect(clearanceLabel(4)).toBe('L4');
expect(clearanceLabel(99)).toBe('L4');
});
it('describes permissions per level', () => {
expect(clearancePermissions(2)).toMatch(/spread/i);
expect(clearancePermissions(3)).toMatch(/shell/i);
});
it('formats AI elevation flash copy', () => {
expect(
formatClearanceElevation({ to_level: 4, source: 'ai_scheduler', reason: 'stuck host recovery' }),
).toBe('AI requested L4 — approved');
});
});

View File

@@ -0,0 +1,58 @@
/** Security clearance L0L4 (mirrors server/internal/clearance). */
export const CLEARANCE_MIN = 0;
export const CLEARANCE_MAX = 4;
export interface ClearanceEventRecord {
id: number;
agent_id: string;
from_level: number;
to_level: number;
reason: string;
source: string;
ts: string;
}
export function clearanceLabel(level: number): string {
const n = Math.max(CLEARANCE_MIN, Math.min(CLEARANCE_MAX, level));
return `L${n}`;
}
export function clearancePermissions(level: number): string {
switch (Math.max(CLEARANCE_MIN, Math.min(CLEARANCE_MAX, level))) {
case 0:
return 'Stats and read-only probes';
case 1:
return 'Mining: pause, resume, restart';
case 2:
return 'Spread: discover_and_join, spread_now, stage_fetch';
case 3:
return 'Shell: exec_shell, agent_command';
case 4:
return 'Forge: set_agent_version, reorder_tiers fleet-wide';
default:
return 'Unknown clearance';
}
}
export function formatClearanceElevation(event: {
to_level: number;
source?: string;
reason?: string;
}): string {
const level = clearanceLabel(event.to_level);
if (event.source === 'ai_scheduler') {
return `AI requested ${level} — approved`;
}
if (event.reason?.trim()) {
return `${level}${event.reason.trim()}`;
}
return `Elevated to ${level}`;
}
export function clearanceTimelineSummary(event: ClearanceEventRecord): string {
const from = clearanceLabel(event.from_level);
const to = clearanceLabel(event.to_level);
const who = event.source === 'ai_scheduler' ? 'AI' : event.source || 'system';
return `${who}: ${from}${to}${event.reason ? ` (${event.reason})` : ''}`;
}

View File

@@ -92,4 +92,5 @@ export const WS_LATEST_MESSAGE_TYPES = new Set([
'new_share',
'fleet_alert',
'command_result',
'clearance_elevated',
]);