/** Package-owned approval audit-stream invariants. @module @deepseek-ai/dsh-user-approval/invariant */
import { APPROVAL_POLICIES } from "./index.js";
const PACKAGE_NAME = '@deepseek-ai/dsh-user-approval';
const APPROVAL_OUTCOMES = ['allowed-once', 'rejected', 'cancelled', 'unavailable'];
/** Cordis companion plugin name. */
export const name = 'user-approval-invariant';
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants'];
/** Validate one approval event against committed unmatched questions. */
function validateApprovalEvent(trace, event, fail) {
    if (event.type === 'approval/asked') {
        if (trace.openTurn === null)
            fail('approval/asked appended outside any open turn');
        if (event.data.toolName.length === 0)
            fail('approval/asked toolName must be non-empty');
        if (trace.pending.has(event.data.id))
            fail(`approval/asked repeated open id ${JSON.stringify(event.data.id)}`);
        return { kind: 'asked', id: event.data.id };
    }
    if (event.type === 'approval/decided') {
        if (trace.openTurn === null)
            fail('approval/decided appended outside any open turn');
        if (!trace.pending.has(event.data.id))
            fail(`approval/decided has no matching approval/asked for id ${JSON.stringify(event.data.id)}`);
        if (!APPROVAL_OUTCOMES.includes(event.data.outcome)) {
            fail(`approval/decided carries unknown outcome ${JSON.stringify(event.data.outcome)}`);
        }
        return { kind: 'decided', id: event.data.id };
    }
    if (event.type === 'approval/policy' && !APPROVAL_POLICIES.includes(event.data.policy)) {
        fail(`approval/policy carries unknown policy ${JSON.stringify(event.data.policy)}`);
    }
    return undefined;
}
/** Apply one accepted approval-pair transition. */
function applyApprovalTransition(pending, transition) {
    if (transition.kind === 'asked')
        pending.add(transition.id);
    else
        pending.delete(transition.id);
}
/** Install audit pairing and closed-vocabulary checks. */
// Event owners keep precommit staging local so their vocabularies never move into a central helper.
/* jscpd:ignore-start */
const install = Object.assign((ctx, fail) => {
    const traces = new WeakMap();
    const staged = new WeakMap();
    const seed = (session) => {
        const trace = { openTurn: null, pending: new Set() };
        traces.set(session, trace);
        for (const event of session.events) {
            if (event.type === 'turn/start')
                trace.openTurn = event.data.turn;
            else if (event.type === 'turn/end')
                trace.openTurn = null;
            const transition = validateApprovalEvent(trace, event, fail);
            if (transition !== undefined)
                applyApprovalTransition(trace.pending, transition);
        }
        return trace;
    };
    const traceFor = (session) => traces.get(session) ?? seed(session);
    for (const session of ctx.sessions.list())
        seed(session);
    ctx.on('session/created', (session) => { seed(session); }, { global: true });
    ctx.on('session/event', (session, event) => {
        const trace = traceFor(session);
        if (event.type === 'turn/start') {
            trace.openTurn = event.data.turn;
            return;
        }
        if (event.type === 'turn/end') {
            trace.openTurn = null;
            return;
        }
        if (event.type !== 'approval/asked' && event.type !== 'approval/decided')
            return;
        const candidate = staged.get(event);
        /* v8 ignore next -- internal/dispatch stages every package-owned pair event */
        if (candidate === undefined || candidate.session !== session)
            return fail('approval audit event published without pre-commit validation');
        staged.delete(event);
        applyApprovalTransition(trace.pending, candidate.transition);
    }, { global: true });
    ctx.on('internal/dispatch', (_mode, eventName, args) => {
        if (eventName !== 'session/event')
            return;
        const [session, event] = args;
        const transition = validateApprovalEvent(traceFor(session), event, fail);
        if (transition !== undefined)
            staged.set(event, { session, transition });
    }, { global: true });
}, { inject: ['sessions'] });
/* jscpd:ignore-end */
/**
 * Register the approval invariant companion.
 * @param ctx - Cordis context carrying the invariant service.
 * @returns the installed registration's disposer after setup succeeds.
 */
export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
//# sourceMappingURL=invariant.js.map