a11oy / resilience /szlBreaker.ts
betterwithage's picture
sync(space): complete build context — fix BUILD_ERROR (CTO)
518343a verified
Raw
History Blame
4.32 kB
// 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<string, unknown>): Promise<unknown>;
async function emitDegradation(p: {
breaker: string; flagship: string; failureMode: string;
fallbackTier: string; state: string; traceparent?: string;
}): Promise<void> {
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<T>(opts: BreakerOpts, fallbackFn: () => Promise<T>): 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<string | null>;
declare function t1SmallModelIfAvailable(): Promise<string | null>;
declare function callProviderRouter(prompt: string): Promise<unknown>;
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
}