/** * Two-tier rule store: persistent (operator/admin rules loaded from * rules.json, mutated by the prompt-rules tool) and external (in-memory * rules registered by other plugins via the __prismFacet cross-plugin * API). External rules are NOT saved to disk — they belong to the * registering plugin and are re-registered on every gateway start. * * Lookup precedence: persistent > external (operator wins over plugin * defaults so an explicit rules.json override is honored). */ import { readFileSync, writeFileSync } from "node:fs"; export type RuleStore = Record; // "router:key" → prompt file path const _G = globalThis as Record; const PERSISTENT_KEY = "_prismFacetRules"; const EXTERNAL_KEY = "_prismFacetExternalRules"; const RULES_PATH_KEY = "_prismFacetRulesPath"; function getPersistent(): RuleStore { if (!_G[PERSISTENT_KEY] || typeof _G[PERSISTENT_KEY] !== "object") { _G[PERSISTENT_KEY] = {}; } return _G[PERSISTENT_KEY] as RuleStore; } function getExternal(): RuleStore { if (!_G[EXTERNAL_KEY] || typeof _G[EXTERNAL_KEY] !== "object") { _G[EXTERNAL_KEY] = {}; } return _G[EXTERNAL_KEY] as RuleStore; } function getRulesPath(): string { return (_G[RULES_PATH_KEY] as string) || ""; } function save(): void { const p = getRulesPath(); if (!p) return; writeFileSync(p, JSON.stringify(getPersistent(), null, 2) + "\n", "utf8"); } export function initRuleStore(filePath: string): void { _G[RULES_PATH_KEY] = filePath; try { const raw = readFileSync(filePath, "utf8"); _G[PERSISTENT_KEY] = JSON.parse(raw) as RuleStore; } catch { _G[PERSISTENT_KEY] = {}; } } /** Operator-facing addRule (writes through to rules.json on disk). */ export function addRule(router: string, key: string, promptFile: string): void { getPersistent()[`${router}:${key}`] = promptFile; save(); } /** Cross-plugin API: register a rule that lives in memory only. */ export function addExternalRule(router: string, key: string, promptFile: string): void { getExternal()[`${router}:${key}`] = promptFile; } export function removeRule(router: string, key: string): boolean { const rules = getPersistent(); const ruleKey = `${router}:${key}`; if (!(ruleKey in rules)) return false; delete rules[ruleKey]; save(); return true; } /** Lookup. Persistent (rules.json) overrides external (plugin defaults). */ export function getRule(router: string, key: string): string | undefined { const persistentValue = getPersistent()[`${router}:${key}`]; if (persistentValue) return persistentValue; return getExternal()[`${router}:${key}`]; } /** Merged view for the admin tool. Persistent overrides external. */ export function listRules(): Record { return { ...getExternal(), ...getPersistent() }; } /** Admin tool needs to know which tier a rule came from. */ export function listRulesTiered(): { persistent: RuleStore; external: RuleStore; } { return { persistent: { ...getPersistent() }, external: { ...getExternal() } }; }