/**
 * HITLPort — Human-In-The-Loop state machine port.
 *
 * Plan v6 §1.7 + §3.4: State lives in Postgres (CC), NOT in MessagingChannel.
 * Channels = transports; state = HITLPort + Postgres.
 *
 * State machine (legal transitions):
 *   pending → approved | rejected | expired   ✅
 *   approved | rejected → executed             ✅
 *   expired → *                                ❌ terminal
 *   approved → rejected (or inverse)           ❌ forbidden
 *
 * @public
 */
// Legal transition table: from → set of valid to states
const LEGAL_TRANSITIONS = {
    pending: new Set(["approved", "rejected", "expired"]),
    approved: new Set(["executed"]),
    rejected: new Set(["executed"]),
    expired: new Set(),
    executed: new Set(),
};
/**
 * Thrown when a state-machine transition is attempted that violates the HITL
 * state diagram (e.g. expired → approved, approved → rejected).
 */
export class InvalidStateTransitionError extends Error {
    from;
    to;
    constructor(from, to) {
        super(`Invalid HITL state transition: ${from} → ${to}`);
        this.name = "InvalidStateTransitionError";
        this.from = from;
        this.to = to;
        Object.setPrototypeOf(this, new.target.prototype);
    }
}
/**
 * Thrown when a HITL request id is not found in the state store.
 */
export class HITLNotFoundError extends Error {
    id;
    constructor(id) {
        super(`HITL request not found: ${id}`);
        this.name = "HITLNotFoundError";
        this.id = id;
        Object.setPrototypeOf(this, new.target.prototype);
    }
}
/**
 * Validate that a transition from → to is legal per the HITL state machine.
 * Throws `InvalidStateTransitionError` if illegal.
 */
export function validateTransition(from, to) {
    if (!LEGAL_TRANSITIONS[from].has(to)) {
        throw new InvalidStateTransitionError(from, to);
    }
}
// ─── Background expiry helper ────────────────────────────────────────────────
/**
 * Start a background interval that expires overdue pending requests.
 *
 * @param store    HITLPort implementation (must support internal expiry logic)
 * @param intervalMs  Polling interval (default 60 000 ms)
 * @returns NodeJS.Timeout — call clearInterval() to stop the job
 */
export function runExpireJob(store, intervalMs = 60_000) {
    return setInterval(async () => {
        if (typeof store._expireOverdue === "function") {
            await store._expireOverdue().catch(() => {
                // Background job: swallow errors to prevent process crash
            });
        }
    }, intervalMs);
}
//# sourceMappingURL=hitl.js.map