import * as fs from "node:fs"; import * as path from "node:path"; export type ModelAccessSurface = "pi" | "openrouter" | "openai" | "chatgpt-cyber" | "anthropic-fable" | "anthropic-mythos" | "sakana-fugu" | "hermes-moa" | "other"; export type ModelAccessStatus = "available" | "blocked" | "missing" | "unknown"; export interface ModelAccessProbe { surface: ModelAccessSurface; command: string; status: ModelAccessStatus; evidence: string; model?: string; } export interface ModelAccessReceiptOptions { probes: ModelAccessProbe[]; output?: string; freshnessHours?: number; now?: Date; } export interface ModelAccessReceipt { schema: "fable.model_access.receipt.v1"; createdAt: string; probes: ModelAccessProbe[]; summary: Record; freshness: { hours: number; expiresAt: string }; decision: "verified" | "partial" | "blocked"; claims: string[]; reasons: string[]; } export function createModelAccessReceipt(opts: ModelAccessReceiptOptions): ModelAccessReceipt { const now = opts.now ?? new Date(); const freshnessHours = opts.freshnessHours ?? 24; const summary = opts.probes.reduce( (acc, probe) => ({ ...acc, [probe.status]: acc[probe.status] + 1 }), { available: 0, blocked: 0, missing: 0, unknown: 0 }, ); const reasons: string[] = []; if (opts.probes.length === 0) reasons.push("no model access probes supplied"); if (opts.probes.some((probe) => probe.surface === "chatgpt-cyber" && probe.status !== "available")) reasons.push("ChatGPT Cyber access not verified"); if (summary.unknown > 0) reasons.push("one or more model surfaces are unknown"); return { schema: "fable.model_access.receipt.v1", createdAt: now.toISOString(), probes: opts.probes, summary, freshness: { hours: freshnessHours, expiresAt: new Date(now.getTime() + freshnessHours * 60 * 60 * 1000).toISOString() }, decision: opts.probes.length === 0 || summary.available === 0 ? "blocked" : reasons.length ? "partial" : "verified", claims: opts.probes.filter((probe) => probe.status === "available").map((probe) => `${probe.surface}${probe.model ? `:${probe.model}` : ""} available`), reasons, }; } export function writeModelAccessReceipt(file: string, receipt: ModelAccessReceipt): string { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, `${JSON.stringify(receipt, null, 2)}\n`); return file; }