import fs from "fs"; import path from "path"; import type { AddressIntent, AddressResultMode } from "../types/addressQuery"; export type AddressRouteExpectationStatus = "matched" | "mismatch" | "not_found"; export interface AddressRouteExpectationEntry { intent: AddressIntent; expected_selected_recipes: string[]; expected_requested_result_modes?: AddressResultMode[]; expected_result_modes?: AddressResultMode[]; } export interface AddressRouteExpectationsContract { schema_version: "address_route_expectations_v1"; updated_at: string; entries: AddressRouteExpectationEntry[]; } export interface AddressRouteExpectationAudit { status: AddressRouteExpectationStatus; reason: string; expected_selected_recipes: string[]; expected_requested_result_modes: AddressResultMode[]; expected_result_modes: AddressResultMode[]; } const EXPECTATIONS_FILE = path.resolve(__dirname, "..", "..", "..", "..", "docs", "TECH", "address_route_expectations_v1.json"); function toObject(value: unknown): Record | null { if (!value || typeof value !== "object" || Array.isArray(value)) { return null; } return value as Record; } function toNonEmptyString(value: unknown): string | null { if (typeof value !== "string") { return null; } const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; } function toStringArray(value: unknown): string[] { if (!Array.isArray(value)) { return []; } return value.map((item) => toNonEmptyString(item)).filter((item): item is string => Boolean(item)); } function parseResultModes(value: unknown): AddressResultMode[] { const raw = toStringArray(value); return raw.filter((mode): mode is AddressResultMode => mode === "heuristic_candidates" || mode === "confirmed_balance"); } function parseEntry(value: unknown): AddressRouteExpectationEntry | null { const object = toObject(value); if (!object) { return null; } const intent = toNonEmptyString(object.intent) as AddressIntent | null; const expectedSelectedRecipes = toStringArray(object.expected_selected_recipes); if (!intent || expectedSelectedRecipes.length === 0) { return null; } const expectedRequestedResultModes = parseResultModes(object.expected_requested_result_modes); const expectedResultModes = parseResultModes(object.expected_result_modes); return { intent, expected_selected_recipes: expectedSelectedRecipes, ...(expectedRequestedResultModes.length > 0 ? { expected_requested_result_modes: expectedRequestedResultModes } : {}), ...(expectedResultModes.length > 0 ? { expected_result_modes: expectedResultModes } : {}) }; } export function loadAddressRouteExpectationsContract(): AddressRouteExpectationsContract { const raw = fs.readFileSync(EXPECTATIONS_FILE, "utf-8"); const parsed = JSON.parse(raw) as unknown; const root = toObject(parsed); if (!root) { throw new Error("address_route_expectations_v1: invalid root payload"); } const schemaVersion = toNonEmptyString(root.schema_version); if (schemaVersion !== "address_route_expectations_v1") { throw new Error(`address_route_expectations_v1: unexpected schema version '${schemaVersion ?? "null"}'`); } const updatedAt = toNonEmptyString(root.updated_at) ?? new Date().toISOString(); const entriesRaw = Array.isArray(root.entries) ? root.entries : []; const entries = entriesRaw.map(parseEntry).filter((entry): entry is AddressRouteExpectationEntry => entry !== null); if (entries.length === 0) { throw new Error("address_route_expectations_v1: no valid entries"); } return { schema_version: "address_route_expectations_v1", updated_at: updatedAt, entries }; } export function evaluateAddressRouteExpectation(input: { intent: AddressIntent; selectedRecipe: string | null; requestedResultMode?: AddressResultMode; resultMode?: AddressResultMode; }): AddressRouteExpectationAudit { const contract = loadAddressRouteExpectationsContract(); const entry = contract.entries.find((item) => item.intent === input.intent); if (!entry) { return { status: "not_found", reason: "route_expectation_not_defined_for_intent", expected_selected_recipes: [], expected_requested_result_modes: [], expected_result_modes: [] }; } if (input.selectedRecipe && !entry.expected_selected_recipes.includes(input.selectedRecipe)) { return { status: "mismatch", reason: "selected_recipe_mismatch", expected_selected_recipes: entry.expected_selected_recipes, expected_requested_result_modes: entry.expected_requested_result_modes ?? [], expected_result_modes: entry.expected_result_modes ?? [] }; } if ( input.requestedResultMode && Array.isArray(entry.expected_requested_result_modes) && entry.expected_requested_result_modes.length > 0 && !entry.expected_requested_result_modes.includes(input.requestedResultMode) ) { return { status: "mismatch", reason: "requested_result_mode_mismatch", expected_selected_recipes: entry.expected_selected_recipes, expected_requested_result_modes: entry.expected_requested_result_modes, expected_result_modes: entry.expected_result_modes ?? [] }; } if ( input.resultMode && Array.isArray(entry.expected_result_modes) && entry.expected_result_modes.length > 0 && !entry.expected_result_modes.includes(input.resultMode) ) { return { status: "mismatch", reason: "result_mode_mismatch", expected_selected_recipes: entry.expected_selected_recipes, expected_requested_result_modes: entry.expected_requested_result_modes ?? [], expected_result_modes: entry.expected_result_modes }; } return { status: "matched", reason: "route_expectation_matched", expected_selected_recipes: entry.expected_selected_recipes, expected_requested_result_modes: entry.expected_requested_result_modes ?? [], expected_result_modes: entry.expected_result_modes ?? [] }; }