/**
 * redis-circuit-breaker — Redis-backed OODA RiskGuard.
 *
 * V5 anti-pattern #4 fix: resetVia is EXPLICIT ('cron-rth' | 'admin-endpoint' |
 * 'never') — NOT TTL-based blind expiry. A tripped circuit stays tripped until
 * an explicit reset action fires (cron job at RTH open, admin endpoint call, or
 * never — for manual-only recovery).
 *
 * Redis key: `cb:<name>:tripped` = "1" when tripped, absent when clear.
 *
 * Decoupled from src/router/circuit-breaker.ts — the agent SDK must not import
 * from the CC server layer. This is a standalone, minimal implementation.
 *
 * @public
 */
// ─── Guard factory ────────────────────────────────────────────────────────
/**
 * Returns a RiskGuard that blocks the OODA cycle when the named circuit
 * breaker is tripped in Redis.
 *
 * The guard itself is read-only — it never trips or resets the breaker.
 * Use `tripCircuitBreaker` / `resetCircuitBreaker` from external code
 * (failure handlers, admin endpoints, cron jobs).
 */
export function redisCircuitBreaker(opts) {
    const redisKey = `cb:${opts.name}:tripped`;
    return {
        name: `redis-cb:${opts.name}`,
        async check(_ctx) {
            const url = opts.redisUrl ?? process.env["REDIS_URL"] ?? "redis://localhost:6379";
            const client = opts._redisClientFactory
                ? opts._redisClientFactory(url)
                : await createIoRedisClient(url);
            try {
                const val = await client.get(redisKey);
                if (val === "1") {
                    return {
                        proceed: false,
                        reason: `Circuit breaker '${opts.name}' tripped (resetVia=${opts.resetVia})`,
                    };
                }
                return { proceed: true };
            }
            finally {
                await client.quit().catch(() => undefined);
            }
        },
    };
}
// ─── Trip / reset helpers ─────────────────────────────────────────────────
/**
 * Trip the named circuit breaker in Redis.
 * The key has NO expiry — reset is always explicit (anti-pattern #4).
 */
export async function tripCircuitBreaker(redisUrl, name, redisClientFactory) {
    const client = redisClientFactory
        ? redisClientFactory(redisUrl)
        : await createIoRedisClient(redisUrl);
    try {
        await client.set(`cb:${name}:tripped`, "1");
    }
    finally {
        await client.quit().catch(() => undefined);
    }
}
/**
 * Reset the named circuit breaker in Redis (remove the trip key).
 * Called by cron-rth jobs, admin endpoints, or manual ops.
 */
export async function resetCircuitBreaker(redisUrl, name, redisClientFactory) {
    const client = redisClientFactory
        ? redisClientFactory(redisUrl)
        : await createIoRedisClient(redisUrl);
    try {
        await client.del(`cb:${name}:tripped`);
    }
    finally {
        await client.quit().catch(() => undefined);
    }
}
// ─── ioredis client factory ───────────────────────────────────────────────
async function createIoRedisClient(url) {
    // Dynamic import keeps ioredis as a peer dep — tests can inject a mock factory
    // without pulling the full ioredis module.
    const { default: IORedis } = await import("ioredis");
    const client = new IORedis(url, {
        // Fail-fast: if Redis is unreachable, throw immediately rather than queuing.
        enableOfflineQueue: false,
        connectTimeout: 3_000,
        maxRetriesPerRequest: 1,
        lazyConnect: true,
    });
    await client.connect();
    return client;
}
//# sourceMappingURL=redis-circuit-breaker.js.map