/** * The per-module jail exemptions, as one query every reporting surface shares * (per-module-jail-policy task 3.1-3.3). * * An exemption is a recorded module policy weaker than the system's * `hooks.jail_policy`. The three surfaces — `module list`'s marker, * `system doctor`'s count, `system audit`'s category — MUST agree on what * that is, so the comparison lives in `isWeakerJailPolicy` and this file only * fetches rows and applies it. A surface that re-derived the definition here * would eventually report a different set from the other two, and a countable * exemption that two surfaces miss is the failure this slice exists to close. * * Read-time refusal follows the rule the resolvers already follow: a stored * value outside the accepted set THROWS (peba's ruling on ce-8832). The set * path validates, so a bad row can only arrive via a restore or a hand edit — * the exact foreign state the rule refuses to coerce into a silent default. */ import { eq } from 'drizzle-orm'; import type { getDb as getDbType } from '../db/client'; import { moduleJailPolicies, systemConfig } from '../db/schema'; import { type JailPolicy, isWeakerJailPolicy, resolveJailPolicy } from '../hooks/jail'; const JAIL_SYSTEM_KEY = 'hooks.jail_policy'; export interface JailExemption { moduleId: string; /** The module's own recorded policy — the weaker of the two. */ policy: JailPolicy; /** The system policy it is weaker than: the stored key or the default. */ systemPolicy: JailPolicy; } type Db = ReturnType; /** * The system-wide policy every module without its own row follows. * * Throws on a stored value outside the accepted set, matching the read-time * refusal rule; the error names the fix, and the same throw will be failing * every hook invocation too (the executor resolves through the same key). */ function systemJailPolicy(db: Db): JailPolicy { const stored = db .select() .from(systemConfig) .where(eq(systemConfig.key, JAIL_SYSTEM_KEY)) .get()?.value; if (stored === undefined) return 'off'; try { return resolveJailPolicy(undefined, undefined, stored).policy; } catch { throw new Error( `Stored ${JAIL_SYSTEM_KEY}='${stored}' is not a hook jail policy. Fix it with 'celilo system config set ${JAIL_SYSTEM_KEY} '.`, ); } } /** * Every module running under a recorded policy weaker than the system's. * * A module row stronger than or equal to the system's is NOT an exemption — * the operator strengthened that module's posture, and reporting it as an * exemption would be a phantom finding (per-module-jail-policy task 3.4). * A bad module row throws rather than being skipped or coerced. */ export function collectJailExemptions(db: Db): JailExemption[] { const system = systemJailPolicy(db); const rows = db.select().from(moduleJailPolicies).all(); const exemptions: JailExemption[] = []; for (const row of rows) { // Read-time validation through the same resolver the executor uses, so a // hand-edited row fails with the same message here that a hook would get. const policy = resolveJailPolicy(undefined, row.policy, undefined).policy; if (isWeakerJailPolicy(policy, system)) { exemptions.push({ moduleId: row.moduleId, policy, systemPolicy: system }); } } return exemptions; }