"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_PROMPT_POLICY_RULE_IDS = exports.CANONICAL_POLICY_RULES = void 0;
exports.defineCanonicalPolicyRegistry = defineCanonicalPolicyRegistry;
exports.composeCanonicalPolicy = composeCanonicalPolicy;
const POLICY_RULE_ID = /^policy\.[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
const TASK_DELTA_ID = /^task\.[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
const MAX_TASK_DELTA_INSTRUCTIONS = 16;
const MAX_TASK_DELTA_INSTRUCTION_BYTES = 512;
const MAX_TASK_DELTA_BYTES = 4096;
exports.CANONICAL_POLICY_RULES = defineCanonicalPolicyRegistry([
    {
        id: "policy.action.authoritative-descriptor",
        text: "Resolve callable commands and their posture from the authoritative command descriptor; unresolved guidance is non-callable.",
        appliesTo: ["callable-actions", "all-prompts"],
        contractReferences: ["src/lib/output/command-descriptors.ts"]
    },
    {
        id: "policy.action.explicit-authorization",
        text: "A recommendation is evidence only; perform a write only through an explicitly selected write-capable command with its declared confirmation.",
        appliesTo: ["write-actions", "all-prompts"],
        requires: ["policy.action.authoritative-descriptor"],
        contractReferences: ["src/lib/agent-context/workflow-actions.ts"]
    },
    {
        id: "policy.context.selected-evidence",
        text: "Keep selected-artifact context read-only and bounded; context evidence does not authorize a transition.",
        appliesTo: ["artifact-context", "all-prompts"],
        requires: ["policy.action.explicit-authorization"],
        contractReferences: ["src/lib/agent-context/index.ts"]
    },
    {
        id: "policy.mutation.atomic-boundary",
        text: "Preserve the command's declared atomic and precondition boundary; never infer successful mutation from prompt composition.",
        appliesTo: ["write-actions", "all-prompts"],
        requires: ["policy.action.explicit-authorization"],
        contractReferences: ["src/lib/filesystem/atomic-write.ts"]
    },
    {
        id: "policy.prompt.task-delta",
        text: "Compose task-specific instructions as a bounded delta that references canonical rules without redefining them.",
        appliesTo: ["all-prompts"],
        requires: ["policy.context.selected-evidence", "policy.mutation.atomic-boundary"]
    }
]);
exports.DEFAULT_PROMPT_POLICY_RULE_IDS = Object.freeze([
    "policy.prompt.task-delta"
]);
function defineCanonicalPolicyRegistry(rules) {
    const normalized = [];
    const seen = new Map();
    for (const rule of rules) {
        const current = normalizeRule(rule);
        const prior = seen.get(current.id);
        if (prior !== undefined) {
            const kind = sameRuleDefinition(prior, current) ? "Duplicate" : "Conflicting";
            throw new Error(`${kind} canonical policy rule definition for '${current.id}'.`);
        }
        seen.set(current.id, current);
        normalized.push(current);
    }
    validateDependencies(normalized, seen);
    return Object.freeze(normalized);
}
function composeCanonicalPolicy(options) {
    const registry = options.registry === undefined
        ? exports.CANONICAL_POLICY_RULES
        : defineCanonicalPolicyRegistry(options.registry);
    const byId = new Map(registry.map((rule) => [rule.id, rule]));
    const delta = normalizeTaskDelta(options.taskDelta ?? null, byId);
    const requestedIds = uniqueSorted([
        ...options.ruleIds.map((id) => normalizeRuleId(id, "policy rule reference")),
        ...(delta?.ruleReferences ?? [])
    ]);
    const resolved = [];
    const emitted = new Set();
    const visit = (id) => {
        if (emitted.has(id))
            return;
        const rule = byId.get(id);
        if (rule === undefined) {
            throw new Error(`Unknown canonical policy rule reference '${id}'.`);
        }
        for (const dependencyId of rule.requires)
            visit(dependencyId);
        emitted.add(id);
        resolved.push(rule);
    };
    for (const id of requestedIds)
        visit(id);
    const taskDelta = delta === null ? null : Object.freeze({
        id: delta.id,
        instructions: delta.instructions
    });
    const compactText = [
        ...resolved.map((rule) => `[${rule.id}] ${rule.text}`),
        ...(taskDelta === null
            ? []
            : taskDelta.instructions.map((instruction) => `[${taskDelta.id}] ${instruction}`))
    ].join("\n");
    return Object.freeze({
        ruleIds: Object.freeze(resolved.map((rule) => rule.id)),
        rules: Object.freeze(resolved),
        taskDelta,
        compactText
    });
}
function normalizeRule(rule) {
    const id = normalizeRuleId(rule.id, "canonical policy rule id");
    const text = requireBoundedText(rule.text, `${id} text`, 1024);
    const appliesTo = uniqueSorted(rule.appliesTo.map((value) => {
        if (!["all-prompts", "artifact-context", "callable-actions", "write-actions"].includes(value)) {
            throw new Error(`Canonical policy rule '${id}' has invalid applicability '${value}'.`);
        }
        return value;
    }));
    if (appliesTo.length === 0) {
        throw new Error(`Canonical policy rule '${id}' must declare applicability.`);
    }
    return Object.freeze({
        id,
        text,
        appliesTo: Object.freeze(appliesTo),
        requires: Object.freeze(uniqueSorted((rule.requires ?? []).map((value) => normalizeRuleId(value, `${id} dependency`)))),
        contractReferences: Object.freeze(uniqueSorted((rule.contractReferences ?? []).map((value) => requireBoundedText(value, `${id} contract reference`, 512))))
    });
}
function validateDependencies(rules, byId) {
    const completed = new Set();
    const active = new Set();
    const visit = (id) => {
        if (completed.has(id))
            return;
        if (active.has(id))
            throw new Error(`Canonical policy dependency cycle includes '${id}'.`);
        const rule = byId.get(id);
        if (rule === undefined)
            throw new Error(`Unknown canonical policy rule dependency '${id}'.`);
        active.add(id);
        for (const dependencyId of rule.requires)
            visit(dependencyId);
        active.delete(id);
        completed.add(id);
    };
    for (const rule of rules)
        visit(rule.id);
}
function normalizeTaskDelta(delta, byId) {
    if (delta === null)
        return null;
    const id = requireBoundedText(delta.id, "task policy delta id", 128);
    if (!TASK_DELTA_ID.test(id)) {
        if (byId.has(id) || POLICY_RULE_ID.test(id)) {
            throw new Error(`Task policy delta '${id}' shadows a canonical policy rule id.`);
        }
        throw new Error(`Invalid task policy delta id '${id}'.`);
    }
    const ruleReferences = uniqueSorted((delta.ruleReferences ?? []).map((value) => normalizeRuleId(value, `${id} rule reference`)));
    const instructions = delta.instructions.map((value, index) => requireBoundedText(value, `${id} instruction ${index + 1}`, MAX_TASK_DELTA_INSTRUCTION_BYTES));
    if (instructions.length === 0 || instructions.length > MAX_TASK_DELTA_INSTRUCTIONS) {
        throw new Error(`Task policy delta '${id}' must contain 1-${MAX_TASK_DELTA_INSTRUCTIONS} instructions.`);
    }
    if (Buffer.byteLength(instructions.join("\n"), "utf8") > MAX_TASK_DELTA_BYTES) {
        throw new Error(`Task policy delta '${id}' exceeds ${MAX_TASK_DELTA_BYTES} bytes.`);
    }
    return Object.freeze({
        id,
        ruleReferences: Object.freeze(ruleReferences),
        instructions: Object.freeze(instructions)
    });
}
function normalizeRuleId(value, label) {
    const id = requireBoundedText(value, label, 128);
    if (!POLICY_RULE_ID.test(id))
        throw new Error(`Invalid ${label} '${id}'.`);
    return id;
}
function requireBoundedText(value, label, maxBytes) {
    const normalized = value.trim();
    if (normalized.length === 0)
        throw new Error(`${label} must not be empty.`);
    if (/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(normalized)) {
        throw new Error(`${label} must not contain terminal control characters.`);
    }
    if (Buffer.byteLength(normalized, "utf8") > maxBytes) {
        throw new Error(`${label} exceeds ${maxBytes} bytes.`);
    }
    return normalized;
}
function uniqueSorted(values) {
    return [...new Set(values)].sort((left, right) => left.localeCompare(right));
}
function sameRuleDefinition(left, right) {
    return left.text === right.text
        && JSON.stringify(left.appliesTo) === JSON.stringify(right.appliesTo)
        && JSON.stringify(left.requires) === JSON.stringify(right.requires)
        && JSON.stringify(left.contractReferences) === JSON.stringify(right.contractReferences);
}
