import type { FastifyBaseLogger } from "fastify"; export interface CapabilityDescriptor { id: string; status?: string; version?: string; owner?: string; surfaces?: string[]; } export type Cl0udStatus = "disabled" | "connected" | "unavailable" | "denied"; export interface Cl0udProbe { status: Cl0udStatus; capabilities: Record; checkedAt: string | null; detail?: string; } export interface Cl0udClientDeps { baseUrl?: string; apiToken?: string; capabilityIds?: string[]; logger?: FastifyBaseLogger; fetchImpl?: typeof fetch; probeIntervalMs?: number; } const EXPECTED_IDS = ["c0py.capture.run.v1", "c0py.registry.manage.v1"]; // CL0UD wiring, contract-first: probes the documented capability discovery API // (GET /capabilities/:id) and surfaces the result in /health as evidence. // Full policy-decision calls remain gated on CL0UD B-4 / ADR-0031 (estate-level) // and are deliberately NOT simulated here. With CL0UD_REQUIRED=true the API // fails closed (503) while the probe cannot confirm ACTIVE capabilities. export function createCl0udClient(deps: Cl0udClientDeps) { const fetchImpl = deps.fetchImpl ?? fetch; const ids = deps.capabilityIds ?? EXPECTED_IDS; let last: Cl0udProbe = { status: deps.baseUrl ? "unavailable" : "disabled", capabilities: {}, checkedAt: null, detail: deps.baseUrl ? undefined : "CL0UD_BASE_URL not configured", }; async function probe(): Promise { if (!deps.baseUrl) { last = { status: "disabled", capabilities: {}, checkedAt: new Date().toISOString(), detail: "CL0UD_BASE_URL not configured" }; return last; } const capabilities: Record = {}; let unreachable = 0; let notActive = 0; for (const id of ids) { try { const res = await fetchImpl(`${deps.baseUrl.replace(/\/$/, "")}/capabilities/${encodeURIComponent(id)}`, { headers: deps.apiToken ? { Authorization: `Bearer ${deps.apiToken}` } : {}, signal: AbortSignal.timeout(5000), }); if (res.ok) { const body = (await res.json()) as CapabilityDescriptor & { capability?: CapabilityDescriptor }; const cap = body.capability ?? body; capabilities[id] = cap.status ?? "unknown"; if (capabilities[id] !== "ACTIVE") notActive += 1; } else { capabilities[id] = `http_${res.status}`; unreachable += 1; } } catch { capabilities[id] = "unreachable"; unreachable += 1; } } const status: Cl0udStatus = unreachable === ids.length ? "unavailable" : unreachable === 0 && notActive === 0 ? "connected" : "denied"; last = { status, capabilities, checkedAt: new Date().toISOString() }; deps.logger?.info({ cl0ud: last.status }, "CL0UD probe done"); return last; } let timer: NodeJS.Timeout | null = null; function start() { void probe().catch(() => {}); if (deps.probeIntervalMs && deps.probeIntervalMs > 0) { timer = setInterval(() => void probe().catch(() => {}), deps.probeIntervalMs); timer.unref?.(); } } function stop() { if (timer) clearInterval(timer); } return { probe, start, stop, get status(): Cl0udProbe { return last; }, }; }