diff --git a/docs/TECH/ARCH_LAYER_FOUNDATION.md b/docs/TECH/ARCH_LAYER_FOUNDATION.md index ba701ae..86b1012 100644 --- a/docs/TECH/ARCH_LAYER_FOUNDATION.md +++ b/docs/TECH/ARCH_LAYER_FOUNDATION.md @@ -91,6 +91,14 @@ Tests: - `llm_normalizer/backend/tests/addressCapabilityPolicy.test.ts` - `llm_normalizer/backend/tests/addressNavigationState.test.ts` - `llm_normalizer/backend/tests/sessionBackwardCompat.test.ts` (extended) +- `llm_normalizer/backend/tests/addressRouteBaseline.test.ts` + +Route baseline contract: + +- `docs/TECH/address_route_baseline_v1.json` +- loader: `llm_normalizer/backend/src/services/addressRouteBaseline.ts` + +This baseline freezes capability mapping for key intents and acts as anti-regression control when routing evolves. ## Why This Is a Foundation, Not a Patch diff --git a/docs/TECH/address_route_baseline_v1.json b/docs/TECH/address_route_baseline_v1.json new file mode 100644 index 0000000..4974f98 --- /dev/null +++ b/docs/TECH/address_route_baseline_v1.json @@ -0,0 +1,66 @@ +{ + "schema_version": "address_route_baseline_v1", + "updated_at": "2026-04-12T12:00:00.000Z", + "entries": [ + { + "intent": "payables_confirmed_as_of_date", + "capability_id": "confirmed_payables_as_of_date", + "capability_layer": "compute", + "capability_route_mode": "exact" + }, + { + "intent": "list_payables_counterparties", + "capability_id": "payables_candidates_list", + "capability_layer": "compute", + "capability_route_mode": "heuristic" + }, + { + "intent": "list_receivables_counterparties", + "capability_id": "receivables_candidates_list", + "capability_layer": "compute", + "capability_route_mode": "heuristic" + }, + { + "intent": "account_balance_snapshot", + "capability_id": "account_balance_exact", + "capability_layer": "compute", + "capability_route_mode": "exact" + }, + { + "intent": "documents_forming_balance", + "capability_id": "account_balance_exact", + "capability_layer": "compute", + "capability_route_mode": "exact" + }, + { + "intent": "list_documents_by_counterparty", + "capability_id": "documents_drilldown", + "capability_layer": "navigation", + "capability_route_mode": "exact" + }, + { + "intent": "list_documents_by_contract", + "capability_id": "documents_drilldown", + "capability_layer": "navigation", + "capability_route_mode": "exact" + }, + { + "intent": "list_contracts_by_counterparty", + "capability_id": "contracts_drilldown", + "capability_layer": "navigation", + "capability_route_mode": "exact" + }, + { + "intent": "bank_operations_by_counterparty", + "capability_id": "bank_operations_drilldown", + "capability_layer": "navigation", + "capability_route_mode": "exact" + }, + { + "intent": "bank_operations_by_contract", + "capability_id": "bank_operations_drilldown", + "capability_layer": "navigation", + "capability_route_mode": "exact" + } + ] +} diff --git a/llm_normalizer/backend/dist/services/addressRouteBaseline.js b/llm_normalizer/backend/dist/services/addressRouteBaseline.js new file mode 100644 index 0000000..e633493 --- /dev/null +++ b/llm_normalizer/backend/dist/services/addressRouteBaseline.js @@ -0,0 +1,64 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.loadAddressRouteBaselineContract = loadAddressRouteBaselineContract; +const fs_1 = __importDefault(require("fs")); +const path_1 = __importDefault(require("path")); +const BASELINE_FILE = path_1.default.resolve(__dirname, "..", "..", "..", "..", "docs", "TECH", "address_route_baseline_v1.json"); +function toObject(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return null; + } + return value; +} +function toNonEmptyString(value) { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} +function parseEntry(value) { + const object = toObject(value); + if (!object) { + return null; + } + const intent = toNonEmptyString(object.intent); + const capabilityId = toNonEmptyString(object.capability_id); + const layer = toNonEmptyString(object.capability_layer); + const routeMode = toNonEmptyString(object.capability_route_mode); + if (!intent || !capabilityId || !layer || !routeMode) { + return null; + } + return { + intent, + capability_id: capabilityId, + capability_layer: layer, + capability_route_mode: routeMode + }; +} +function loadAddressRouteBaselineContract() { + const raw = fs_1.default.readFileSync(BASELINE_FILE, "utf-8"); + const parsed = JSON.parse(raw); + const root = toObject(parsed); + if (!root) { + throw new Error("address_route_baseline_v1: invalid root payload"); + } + const schemaVersion = toNonEmptyString(root.schema_version); + if (schemaVersion !== "address_route_baseline_v1") { + throw new Error(`address_route_baseline_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 !== null); + if (entries.length === 0) { + throw new Error("address_route_baseline_v1: no valid entries"); + } + return { + schema_version: "address_route_baseline_v1", + updated_at: updatedAt, + entries + }; +} diff --git a/llm_normalizer/backend/src/services/addressRouteBaseline.ts b/llm_normalizer/backend/src/services/addressRouteBaseline.ts new file mode 100644 index 0000000..f2da83b --- /dev/null +++ b/llm_normalizer/backend/src/services/addressRouteBaseline.ts @@ -0,0 +1,77 @@ +import fs from "fs"; +import path from "path"; +import type { AddressCapabilityLayer, AddressCapabilityRouteMode, AddressIntent } from "../types/addressQuery"; + +export interface AddressRouteBaselineEntry { + intent: AddressIntent; + capability_id: string; + capability_layer: AddressCapabilityLayer; + capability_route_mode: AddressCapabilityRouteMode; +} + +export interface AddressRouteBaselineContract { + schema_version: "address_route_baseline_v1"; + updated_at: string; + entries: AddressRouteBaselineEntry[]; +} + +const BASELINE_FILE = path.resolve(__dirname, "..", "..", "..", "..", "docs", "TECH", "address_route_baseline_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 parseEntry(value: unknown): AddressRouteBaselineEntry | null { + const object = toObject(value); + if (!object) { + return null; + } + const intent = toNonEmptyString(object.intent) as AddressIntent | null; + const capabilityId = toNonEmptyString(object.capability_id); + const layer = toNonEmptyString(object.capability_layer) as AddressCapabilityLayer | null; + const routeMode = toNonEmptyString(object.capability_route_mode) as AddressCapabilityRouteMode | null; + if (!intent || !capabilityId || !layer || !routeMode) { + return null; + } + return { + intent, + capability_id: capabilityId, + capability_layer: layer, + capability_route_mode: routeMode + }; +} + +export function loadAddressRouteBaselineContract(): AddressRouteBaselineContract { + const raw = fs.readFileSync(BASELINE_FILE, "utf-8"); + const parsed = JSON.parse(raw) as unknown; + const root = toObject(parsed); + if (!root) { + throw new Error("address_route_baseline_v1: invalid root payload"); + } + const schemaVersion = toNonEmptyString(root.schema_version); + if (schemaVersion !== "address_route_baseline_v1") { + throw new Error(`address_route_baseline_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 AddressRouteBaselineEntry => entry !== null); + if (entries.length === 0) { + throw new Error("address_route_baseline_v1: no valid entries"); + } + return { + schema_version: "address_route_baseline_v1", + updated_at: updatedAt, + entries + }; +} diff --git a/llm_normalizer/backend/tests/addressRouteBaseline.test.ts b/llm_normalizer/backend/tests/addressRouteBaseline.test.ts new file mode 100644 index 0000000..aaabacc --- /dev/null +++ b/llm_normalizer/backend/tests/addressRouteBaseline.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from "vitest"; +import { loadAddressRouteBaselineContract } from "../src/services/addressRouteBaseline"; +import { resolveAddressCapabilityRouteDecision } from "../src/services/addressCapabilityPolicy"; + +describe("address route baseline contract", () => { + it("keeps capability route mapping aligned with frozen baseline", () => { + const baseline = loadAddressRouteBaselineContract(); + expect(baseline.schema_version).toBe("address_route_baseline_v1"); + expect(Array.isArray(baseline.entries)).toBe(true); + expect(baseline.entries.length).toBeGreaterThan(0); + + for (const entry of baseline.entries) { + const decision = resolveAddressCapabilityRouteDecision(entry.intent); + expect(decision.capability_id).toBe(entry.capability_id); + expect(decision.capability_layer).toBe(entry.capability_layer); + expect(decision.capability_route_mode).toBe(entry.capability_route_mode); + } + }); +});