import { readFileSync, writeFileSync, existsSync } from 'node:fs'; // Manages runtime policy overrides for groups and individual actions. // Groups and action overrides are independent: disabling a group does not // touch action-level overrides. Re-enabling a group restores them intact. export function load_policy_overrides(file_path) { let groups = {}; // { group_name: 'disabled' } let overrides = {}; // { action_name: policy } if (file_path && existsSync(file_path)) { try { const parsed = JSON.parse(readFileSync(file_path, 'utf8')); groups = parsed.groups ?? {}; overrides = parsed.overrides ?? {}; } catch (err) { throw new Error(`Cannot load policy overrides from ${file_path}: ${err.message}`); } } function write() { if (!file_path) { return; } writeFileSync(file_path, JSON.stringify({ groups, overrides }, null, '\t') + '\n', 'utf8'); } // Resolve effective policy for an action at request time. // Precedence: group disabled > action override > hardcoded default function effective(action_name, default_policy, group_name) { if (group_name && groups[group_name] === 'disabled') { return 'auto-deny'; } return overrides[action_name] ?? default_policy; } function set_group(group_name, disabled) { if (disabled) { groups[group_name] = 'disabled'; } else { delete groups[group_name]; } write(); } // policy = null clears the override (falls back to hardcoded default) function set_action(action_name, policy) { if (policy === null) { delete overrides[action_name]; } else { overrides[action_name] = policy; } write(); } // Returns full policy state suitable for the TUI and /policies endpoint. function get_all(action_defs) { const known_groups = [...new Set( Object.values(action_defs).map(d => d.group).filter(Boolean) )].sort(); return { groups: Object.fromEntries(known_groups.map(g => [g, groups[g] === 'disabled'])), action_overrides: { ...overrides }, effective: Object.fromEntries( Object.entries(action_defs).map(([name, def]) => [ name, effective(name, def.policy, def.group) ]) ), }; } return { effective, set_group, set_action, get_all }; }