// SPDX-License-Identifier: Apache-2.0 // © 2026 SZL Holdings · Doctrine v12 (additive over v11 LOCKED). Yachay. // // szlBreaker.ts — Hystrix-style breakers for TS surfaces (a11oy Node serve, etc.) // cockatiel: circuitBreaker (CLOSED/OPEN/HALF-OPEN) + retry (exp backoff+jitter) // + timeout (per-call) + fallback. Khipu receipt on every transition. // Alternate noted by brief: @nodecg/circuitbreaker (older; we use cockatiel). // // ADDITIVE only: wraps calls; never alters the 13-axis Yuyay gate or LOCKED numbers. // HONEST: receipt signature is DSSE PLACEHOLDER (Sigstore CI not wired). import { circuitBreaker, ConsecutiveBreaker, retry, timeout, fallback, wrap, handleAll, ExponentialBackoff, TimeoutStrategy, IPolicy, } from "cockatiel"; const SIGNATURE_PLACEHOLDER = "PLACEHOLDER — Sigstore CI not wired (Doctrine v12)"; // Reuse the live in-process Khipu DAG ingest (Wire F) if present on the Node side. declare function ingestReceipt(receipt: Record): Promise; async function emitDegradation(p: { breaker: string; flagship: string; failureMode: string; fallbackTier: string; state: string; traceparent?: string; }): Promise { const receipt = { schema: "szl.degradation.receipt/v1", event_id: `deg-${new Date().toISOString()}-${p.flagship}-${p.breaker}`, flagship: p.flagship, failure_mode: p.failureMode, circuit: p.breaker, breaker_state: p.state, fallback_tier_served: p.fallbackTier, detected_at: new Date().toISOString(), user_visible: true, traceparent: p.traceparent ?? null, doctrine: "v12", dsse: { sig: SIGNATURE_PLACEHOLDER, keyid: "PENDING" }, }; try { await ingestReceipt(receipt); } // szl_wire equivalent on the Node side catch { /* local-only; never throw from the audit path */ } } export interface BreakerOpts { name: string; flagship: string; failureMode: string; fallbackTier: string; timeoutMs: number; retryBudget: number; failureThreshold: number; resetTimeoutMs: number; traceparent?: string; } /** Compose timeout -> retry -> circuitBreaker -> fallback into one Policy. */ export function makePolicy(opts: BreakerOpts, fallbackFn: () => Promise): IPolicy { const cb = circuitBreaker(handleAll, { halfOpenAfter: opts.resetTimeoutMs, breaker: new ConsecutiveBreaker(opts.failureThreshold), }); cb.onStateChange((state) => emitDegradation({ breaker: opts.name, flagship: opts.flagship, failureMode: opts.failureMode, fallbackTier: opts.fallbackTier, state: String(state), traceparent: opts.traceparent })); const to = timeout(opts.timeoutMs, TimeoutStrategy.Aggressive); const rt = retry(handleAll, { maxAttempts: opts.retryBudget, backoff: new ExponentialBackoff({ initialDelay: 1000, maxDelay: 300_000 }), }); const fb = fallback(handleAll, async () => { await emitDegradation({ breaker: opts.name, flagship: opts.flagship, failureMode: opts.failureMode, fallbackTier: opts.fallbackTier, state: "FALLBACK", traceparent: opts.traceparent }); return fallbackFn(); }); // Order matters: fallback wraps breaker wraps retry wraps timeout. return wrap(fb, cb, rt, to); } // ── Example: LLM router fallback (D2) on the Node side ──────────────────────── declare function t0CacheLookup(): Promise; declare function t1SmallModelIfAvailable(): Promise; declare function callProviderRouter(prompt: string): Promise; const llmPolicy = makePolicy( { name: "llm_router", flagship: "a11oy", failureMode: "llm_all_providers_rate_limited", fallbackTier: "T0/T1/honest-error", timeoutMs: 25_000, retryBudget: 0, failureThreshold: 5, resetTimeoutMs: 15_000 }, async () => { const hit = await t0CacheLookup(); if (hit) return { completion: hit, degraded: true, tier: "T0_cache" }; const small = await t1SmallModelIfAvailable(); if (small) return { completion: small, degraded: true, tier: "T1_small" }; return { error: "all_llm_providers_rate_limited", retry_after_s: 30, degraded: true, honest: "No model could answer within budget." }; }, ); export async function routeLLM(prompt: string) { return llmPolicy.execute(() => callProviderRouter(prompt)); // real call }