import type { AssistantCarryoverDepth, AssistantTruthGateContractStatus } from "../types/assistantRuntimeContracts"; import type { AddressFilterSet, AddressIntent, AddressLimitedReasonCategory, AddressRuntimeReadiness, AddressSemanticFrame } from "../types/addressQuery"; export const ADDRESS_TRUTH_GATE_SCHEMA_VERSION = "address_truth_gate_v1" as const; export interface AddressTruthGateContract { schema_version: typeof ADDRESS_TRUTH_GATE_SCHEMA_VERSION; policy_owner: "addressTruthGatePolicy"; truth_gate_status: AssistantTruthGateContractStatus; carryover_eligibility: AssistantCarryoverDepth; limited_reason_category: AddressLimitedReasonCategory | null; runtime_readiness: AddressRuntimeReadiness; reason_codes: string[]; blocked_or_limited_explanation: string | null; } export interface ResolveAddressTruthGateInput { intent: AddressIntent; filters?: AddressFilterSet | null; semanticFrame?: AddressSemanticFrame | null; selectedRecipe?: string | null; truthGateStatusHint?: AssistantTruthGateContractStatus | null; rowsMatched?: number; limitedReasonCategory?: AddressLimitedReasonCategory | null; runtimeReadiness?: AddressRuntimeReadiness | null; missingRequiredFilters?: string[]; limitations?: string[]; reasons?: string[]; routeExpectationStatus?: "matched" | "mismatch" | "not_found" | null; routeExpectationReason?: string | null; temporalGuardOutcome?: string | null; temporalAlignmentStatus?: string | null; replyType?: "factual" | "partial_coverage" | "deep_analysis" | "unknown" | null; } function toRecordObject(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 (value === null || value === undefined) { return null; } const text = String(value).trim(); return text.length > 0 ? text : null; } function toStringList(value: unknown): string[] { if (!Array.isArray(value)) { return []; } return value .map((item) => toNonEmptyString(item)) .filter((item): item is string => Boolean(item)); } function isTruthGateStatus(value: string | null): value is AssistantTruthGateContractStatus { return ( value === "full_confirmed" || value === "partial_supported" || value === "blocked_missing_anchor" || value === "blocked_route_expectation_failure" || value === "blocked_execution_error" || value === "limited_temporal_or_contextual" || value === "unknown" ); } function isCarryoverDepth(value: string | null): value is AssistantCarryoverDepth { return value === "full" || value === "root_only" || value === "object_only" || value === "meta_only" || value === "none"; } function isLimitedReasonCategory(value: string | null): value is AddressLimitedReasonCategory { return ( value === "empty_match" || value === "missing_anchor" || value === "recipe_visibility_gap" || value === "execution_error" || value === "unsupported" ); } function isRuntimeReadiness(value: string | null): value is AddressRuntimeReadiness { return ( value === "LIVE_QUERYABLE" || value === "LIVE_QUERYABLE_WITH_LIMITS" || value === "REQUIRES_SPECIALIZED_RECIPE" || value === "DEEP_ONLY" || value === "UNKNOWN" ); } function normalizeReasonCode(value: string): string | null { const normalized = value .trim() .replace(/[^\p{L}\p{N}_.:-]+/gu, "_") .replace(/^_+|_+$/g, "") .toLowerCase(); return normalized.length > 0 ? normalized.slice(0, 120) : null; } function pushReason(target: string[], value: unknown): void { const text = toNonEmptyString(value); if (!text) { return; } const normalized = normalizeReasonCode(text); if (normalized && !target.includes(normalized)) { target.push(normalized); } } function hasFilterScope(filters: AddressFilterSet | null | undefined): boolean { if (!filters) { return false; } const scopeKeys: Array = [ "period_from", "period_to", "as_of_date", "organization", "counterparty", "contract", "account", "item", "warehouse", "document_type", "document_ref", "status" ]; return scopeKeys.some((key) => toNonEmptyString(filters[key]) !== null); } function hasObjectFocus(input: ResolveAddressTruthGateInput): boolean { if (toNonEmptyString(input.filters?.item)) { return true; } if (input.semanticFrame?.selected_object_scope_detected) { return true; } if (input.semanticFrame?.anchor_kind === "selected_object" || input.semanticFrame?.anchor_kind === "item") { return true; } return ( input.intent === "inventory_purchase_provenance_for_item" || input.intent === "inventory_purchase_documents_for_item" || input.intent === "inventory_sale_trace_for_item" || input.intent === "inventory_profitability_for_item" || input.intent === "inventory_purchase_to_sale_chain" || input.intent === "inventory_aging_by_purchase_date" ); } function hasReusableRootScope(input: ResolveAddressTruthGateInput): boolean { if (hasFilterScope(input.filters)) { return true; } return Boolean(input.semanticFrame && input.semanticFrame.scope_kind !== "none"); } function truthGateStatusFrom(input: ResolveAddressTruthGateInput): AssistantTruthGateContractStatus { if (input.truthGateStatusHint) { return input.truthGateStatusHint; } const missingRequiredFilters = input.missingRequiredFilters ?? []; if (input.routeExpectationStatus === "mismatch") { return "blocked_route_expectation_failure"; } if (input.limitedReasonCategory === "execution_error") { return "blocked_execution_error"; } if (missingRequiredFilters.length > 0 || input.limitedReasonCategory === "missing_anchor") { return "blocked_missing_anchor"; } if (input.temporalGuardOutcome === "ambiguous_limited" || input.temporalAlignmentStatus === "conflicting") { return "limited_temporal_or_contextual"; } if (input.replyType === "factual" && input.limitedReasonCategory === "empty_match") { return "full_confirmed"; } if ( input.limitedReasonCategory === "empty_match" || input.limitedReasonCategory === "recipe_visibility_gap" || input.limitedReasonCategory === "unsupported" || input.replyType === "partial_coverage" ) { return "partial_supported"; } if ((input.rowsMatched ?? 0) > 0) { return "full_confirmed"; } return "unknown"; } function carryoverEligibilityFor( input: ResolveAddressTruthGateInput, truthGateStatus: AssistantTruthGateContractStatus ): AssistantCarryoverDepth { if (truthGateStatus.startsWith("blocked")) { return "none"; } if (input.limitedReasonCategory === "recipe_visibility_gap" || input.limitedReasonCategory === "unsupported") { return "meta_only"; } if (hasObjectFocus(input)) { return "object_only"; } if (truthGateStatus === "limited_temporal_or_contextual") { return hasReusableRootScope(input) ? "root_only" : "meta_only"; } if (truthGateStatus === "full_confirmed" || truthGateStatus === "partial_supported") { return hasReusableRootScope(input) ? "root_only" : "meta_only"; } return "none"; } function explanationFor( truthGateStatus: AssistantTruthGateContractStatus, limitedReasonCategory: AddressLimitedReasonCategory | null ): string | null { if (truthGateStatus === "full_confirmed") { return null; } if (truthGateStatus === "blocked_missing_anchor") { return "required_anchor_missing"; } if (truthGateStatus === "blocked_route_expectation_failure") { return "route_expectation_failed"; } if (truthGateStatus === "blocked_execution_error") { return "execution_failed"; } if (truthGateStatus === "limited_temporal_or_contextual") { return "temporal_or_contextual_limit"; } if (limitedReasonCategory === "recipe_visibility_gap") { return "specialized_recipe_required"; } if (limitedReasonCategory === "unsupported") { return "unsupported_in_current_contour"; } return truthGateStatus === "partial_supported" ? "evidence_or_coverage_is_partial" : "truth_gate_not_confirmed"; } function collectReasonCodes( input: ResolveAddressTruthGateInput, truthGateStatus: AssistantTruthGateContractStatus ): string[] { const reasons: string[] = []; pushReason(reasons, `address_truth_gate_${truthGateStatus}`); pushReason(reasons, input.routeExpectationReason); pushReason(reasons, input.limitedReasonCategory ? `limited_category_${input.limitedReasonCategory}` : null); (input.missingRequiredFilters ?? []).forEach((item) => pushReason(reasons, `missing_filter_${item}`)); (input.limitations ?? []).forEach((item) => pushReason(reasons, item)); (input.reasons ?? []).forEach((item) => pushReason(reasons, item)); return reasons.slice(0, 32); } export function resolveAddressTruthGate(input: ResolveAddressTruthGateInput): AddressTruthGateContract { const truthGateStatus = truthGateStatusFrom(input); return { schema_version: ADDRESS_TRUTH_GATE_SCHEMA_VERSION, policy_owner: "addressTruthGatePolicy", truth_gate_status: truthGateStatus, carryover_eligibility: carryoverEligibilityFor(input, truthGateStatus), limited_reason_category: input.limitedReasonCategory ?? null, runtime_readiness: input.runtimeReadiness ?? "UNKNOWN", reason_codes: collectReasonCodes(input, truthGateStatus), blocked_or_limited_explanation: explanationFor(truthGateStatus, input.limitedReasonCategory ?? null) }; } export function toAddressTruthGateContract(value: unknown): AddressTruthGateContract | null { const record = toRecordObject(value); if (!record) { return null; } const truthGateStatus = toNonEmptyString(record.truth_gate_status); const carryoverEligibility = toNonEmptyString(record.carryover_eligibility); const limitedReasonCategory = toNonEmptyString(record.limited_reason_category); const runtimeReadiness = toNonEmptyString(record.runtime_readiness); if (!isTruthGateStatus(truthGateStatus) || !isCarryoverDepth(carryoverEligibility) || !isRuntimeReadiness(runtimeReadiness)) { return null; } return { schema_version: ADDRESS_TRUTH_GATE_SCHEMA_VERSION, policy_owner: "addressTruthGatePolicy", truth_gate_status: truthGateStatus, carryover_eligibility: carryoverEligibility, limited_reason_category: isLimitedReasonCategory(limitedReasonCategory) ? limitedReasonCategory : null, runtime_readiness: runtimeReadiness, reason_codes: toStringList(record.reason_codes).slice(0, 32), blocked_or_limited_explanation: toNonEmptyString(record.blocked_or_limited_explanation) }; } export function buildAddressTruthGateDebugFields(input: ResolveAddressTruthGateInput): { address_truth_gate_v1: AddressTruthGateContract; } { return { address_truth_gate_v1: resolveAddressTruthGate(input) }; } export function attachAddressTruthGate>( debugPayload: T, input: ResolveAddressTruthGateInput ): T & { address_truth_gate_v1: AddressTruthGateContract } { return { ...debugPayload, ...buildAddressTruthGateDebugFields(input) }; }