АРЧ АП11 - Вынести exact-lane truth gate для factual-negative и limited ответов в отдельный policy-owner
This commit is contained in:
@@ -63,6 +63,7 @@ import {
|
||||
normalizeOrganizationScopeValue,
|
||||
resolveOrganizationSelectionFromMessage
|
||||
} from "./assistantOrganizationMatcher";
|
||||
import { attachAddressTruthGate } from "./addressTruthGatePolicy";
|
||||
import { OpenAIResponsesClient, type OpenAIRequestConfig } from "./openaiResponsesClient";
|
||||
import { readJsonFile } from "../utils/files";
|
||||
|
||||
@@ -3157,20 +3158,9 @@ function buildLimitedExecutionResult(input: {
|
||||
requestedResultMode: requestedResultMode,
|
||||
resultMode: resultSemantics.result_mode
|
||||
});
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: composeLimitedReply({
|
||||
category: input.category,
|
||||
reason: input.reasonText,
|
||||
nextStep: input.nextStep,
|
||||
shape: input.shape,
|
||||
intent: input.intent.intent,
|
||||
filters: input.filters,
|
||||
missingRequiredFilters: input.missingRequiredFilters
|
||||
}),
|
||||
reply_type: "partial_coverage",
|
||||
response_type: "LIMITED_WITH_REASON",
|
||||
debug: {
|
||||
const runtimeReadiness = runtimeReadinessForLimitedCategory(input.category);
|
||||
const debugPayload = attachAddressTruthGate(
|
||||
{
|
||||
detected_mode: input.mode.mode,
|
||||
detected_mode_confidence: input.mode.confidence,
|
||||
query_shape: input.shape.shape,
|
||||
@@ -3204,10 +3194,10 @@ function buildLimitedExecutionResult(input: {
|
||||
account_scope_fields_checked: accountScopeAudit.accountScopeFieldsChecked,
|
||||
account_scope_match_strategy: accountScopeAudit.accountScopeMatchStrategy,
|
||||
account_scope_drop_reason: accountScopeAudit.accountScopeDropReason,
|
||||
runtime_readiness: runtimeReadinessForLimitedCategory(input.category),
|
||||
runtime_readiness: runtimeReadiness,
|
||||
limited_reason_category: input.category,
|
||||
semantic_frame: input.semanticFrame ?? null,
|
||||
response_type: "LIMITED_WITH_REASON",
|
||||
response_type: "LIMITED_WITH_REASON" as const,
|
||||
capability_id: input.capabilityAudit?.capabilityId ?? null,
|
||||
capability_layer: input.capabilityAudit?.layer ?? null,
|
||||
capability_route_mode: input.capabilityAudit?.routeMode ?? null,
|
||||
@@ -3224,7 +3214,37 @@ function buildLimitedExecutionResult(input: {
|
||||
...resultSemantics,
|
||||
limitations: input.limitations,
|
||||
reasons
|
||||
},
|
||||
{
|
||||
intent: input.intent.intent,
|
||||
filters: input.filters,
|
||||
semanticFrame: input.semanticFrame ?? null,
|
||||
selectedRecipe: input.selectedRecipe,
|
||||
rowsMatched: input.rowsMatched,
|
||||
limitedReasonCategory: input.category,
|
||||
runtimeReadiness,
|
||||
missingRequiredFilters: input.missingRequiredFilters,
|
||||
limitations: input.limitations,
|
||||
reasons,
|
||||
routeExpectationStatus: routeExpectationAudit.status,
|
||||
routeExpectationReason: routeExpectationAudit.reason,
|
||||
replyType: "partial_coverage"
|
||||
}
|
||||
);
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: composeLimitedReply({
|
||||
category: input.category,
|
||||
reason: input.reasonText,
|
||||
nextStep: input.nextStep,
|
||||
shape: input.shape,
|
||||
intent: input.intent.intent,
|
||||
filters: input.filters,
|
||||
missingRequiredFilters: input.missingRequiredFilters
|
||||
}),
|
||||
reply_type: "partial_coverage",
|
||||
response_type: "LIMITED_WITH_REASON",
|
||||
debug: debugPayload
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4088,12 +4108,21 @@ export class AddressQueryService {
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: replyText,
|
||||
reply_type: inferReplyType(responseType),
|
||||
response_type: responseType,
|
||||
debug: {
|
||||
const factualNoRowsLimitations = [...filters.warnings, ...extraLimitations];
|
||||
const factualNoRowsReasons = withConfirmedBalanceFallbackReason(
|
||||
[...baseReasons, noRowsReason],
|
||||
requestedResultMode,
|
||||
undefined,
|
||||
semantics.result_mode
|
||||
);
|
||||
const routeExpectationAudit = buildRouteExpectationAudit({
|
||||
intent: routeExpectationIntent,
|
||||
selectedRecipe: effectiveRecipeId,
|
||||
requestedResultMode,
|
||||
resultMode: semantics.result_mode
|
||||
});
|
||||
const debugPayload = attachAddressTruthGate(
|
||||
{
|
||||
detected_mode: mode.mode,
|
||||
detected_mode_confidence: mode.confidence,
|
||||
query_shape: shape.shape,
|
||||
@@ -4127,17 +4156,17 @@ export class AddressQueryService {
|
||||
account_scope_fields_checked: accountScopeAudit.accountScopeFieldsChecked,
|
||||
account_scope_match_strategy: accountScopeAudit.accountScopeMatchStrategy,
|
||||
account_scope_drop_reason: accountScopeAudit.accountScopeDropReason,
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS" as const,
|
||||
limited_reason_category: null,
|
||||
response_type: responseType,
|
||||
route_expectation_status: routeExpectationAudit.status,
|
||||
route_expectation_reason: routeExpectationAudit.reason,
|
||||
route_expectation_expected_selected_recipes: routeExpectationAudit.expectedSelectedRecipes,
|
||||
route_expectation_expected_requested_result_modes: routeExpectationAudit.expectedRequestedResultModes,
|
||||
route_expectation_expected_result_modes: routeExpectationAudit.expectedResultModes,
|
||||
...semantics,
|
||||
limitations: [...filters.warnings, ...extraLimitations],
|
||||
reasons: withConfirmedBalanceFallbackReason(
|
||||
[...baseReasons, noRowsReason],
|
||||
requestedResultMode,
|
||||
undefined,
|
||||
semantics.result_mode
|
||||
),
|
||||
limitations: factualNoRowsLimitations,
|
||||
reasons: factualNoRowsReasons,
|
||||
...(capabilityAudit
|
||||
? {
|
||||
capability_id: capabilityAudit.capabilityId,
|
||||
@@ -4149,10 +4178,33 @@ export class AddressQueryService {
|
||||
: {}),
|
||||
...(shadowRouteAudit
|
||||
? {
|
||||
shadow_route_intent: shadowRouteAudit.intent,
|
||||
shadow_route_selected_recipe: shadowRouteAudit.selectedRecipe,
|
||||
shadow_route_status: shadowRouteAudit.status
|
||||
}
|
||||
: {})
|
||||
},
|
||||
{
|
||||
intent: intent.intent,
|
||||
filters: filters.extracted_filters,
|
||||
semanticFrame,
|
||||
selectedRecipe: effectiveRecipeId,
|
||||
rowsMatched: 0,
|
||||
limitedReasonCategory: "empty_match",
|
||||
runtimeReadiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limitations: factualNoRowsLimitations,
|
||||
reasons: factualNoRowsReasons,
|
||||
routeExpectationStatus: routeExpectationAudit.status,
|
||||
routeExpectationReason: routeExpectationAudit.reason,
|
||||
replyType: inferReplyType(responseType)
|
||||
}
|
||||
);
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: replyText,
|
||||
reply_type: inferReplyType(responseType),
|
||||
response_type: responseType,
|
||||
debug: debugPayload
|
||||
};
|
||||
};
|
||||
if (organizationWarehouseRecoveryApplied) {
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
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;
|
||||
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<string, unknown> | null {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return null;
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
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<keyof AddressFilterSet> = [
|
||||
"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 {
|
||||
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<T extends Record<string, unknown>>(
|
||||
debugPayload: T,
|
||||
input: ResolveAddressTruthGateInput
|
||||
): T & { address_truth_gate_v1: AddressTruthGateContract } {
|
||||
return {
|
||||
...debugPayload,
|
||||
...buildAddressTruthGateDebugFields(input)
|
||||
};
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
type AssistantTransitionClassId
|
||||
} from "../types/assistantRuntimeContracts";
|
||||
import type { AddressIntent } from "../types/addressQuery";
|
||||
import { toAddressTruthGateContract } from "./addressTruthGatePolicy";
|
||||
import {
|
||||
getAssistantCapabilityContract,
|
||||
getAssistantCapabilityContractByIntent,
|
||||
@@ -190,7 +191,11 @@ function resolveTransitionId(input: {
|
||||
function resolveTruthGateStatus(input: {
|
||||
debug: Record<string, unknown>;
|
||||
groundingStatus: string | null;
|
||||
explicitTruthGateStatus?: AssistantRuntimeContractShadowDecision["truth_gate_contract_status"] | null;
|
||||
}): AssistantRuntimeContractShadowDecision["truth_gate_contract_status"] {
|
||||
if (input.explicitTruthGateStatus) {
|
||||
return input.explicitTruthGateStatus;
|
||||
}
|
||||
if (input.groundingStatus === "route_mismatch_blocked" || input.debug.route_expectation_status === "mismatch") {
|
||||
return "blocked_route_expectation_failure";
|
||||
}
|
||||
@@ -219,11 +224,15 @@ function resolveTruthGateStatus(input: {
|
||||
|
||||
function carryoverEligibilityFor(
|
||||
transitionId: AssistantTransitionClassId | null,
|
||||
truthGateStatus: AssistantRuntimeContractShadowDecision["truth_gate_contract_status"]
|
||||
truthGateStatus: AssistantRuntimeContractShadowDecision["truth_gate_contract_status"],
|
||||
explicitCarryoverEligibility?: AssistantCarryoverDepth | null
|
||||
): AssistantCarryoverDepth {
|
||||
if (truthGateStatus.startsWith("blocked")) {
|
||||
return "none";
|
||||
}
|
||||
if (explicitCarryoverEligibility) {
|
||||
return explicitCarryoverEligibility;
|
||||
}
|
||||
if (transitionId === "T3" || transitionId === "T4" || transitionId === "T5") {
|
||||
return "object_only";
|
||||
}
|
||||
@@ -244,6 +253,7 @@ export function resolveAssistantRuntimeContractShadow(
|
||||
): AssistantRuntimeContractShadowDecision {
|
||||
const debug = toRecordObject(input.addressDebug) ?? {};
|
||||
const meta = runtimeMetaFrom(input);
|
||||
const addressTruthGate = toAddressTruthGateContract(debug.address_truth_gate_v1);
|
||||
const groundingStatus =
|
||||
toNonEmptyString(input.groundingStatus) ??
|
||||
toNonEmptyString(toRecordObject(debug.answer_grounding_check)?.status);
|
||||
@@ -255,7 +265,11 @@ export function resolveAssistantRuntimeContractShadow(
|
||||
groundingStatus
|
||||
});
|
||||
const transitionContract = transition.transitionId ? getAssistantTransitionContract(transition.transitionId) : null;
|
||||
const truthGateStatus = resolveTruthGateStatus({ debug, groundingStatus });
|
||||
const truthGateStatus = resolveTruthGateStatus({
|
||||
debug,
|
||||
groundingStatus,
|
||||
explicitTruthGateStatus: addressTruthGate?.truth_gate_status ?? null
|
||||
});
|
||||
return {
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_contract_id: transition.transitionId,
|
||||
@@ -264,7 +278,11 @@ export function resolveAssistantRuntimeContractShadow(
|
||||
capability_contract_id: capability.capabilityId,
|
||||
capability_contract_reason: capability.reasons,
|
||||
truth_gate_contract_status: truthGateStatus,
|
||||
carryover_eligibility: carryoverEligibilityFor(transition.transitionId, truthGateStatus)
|
||||
carryover_eligibility: carryoverEligibilityFor(
|
||||
transition.transitionId,
|
||||
truthGateStatus,
|
||||
addressTruthGate?.carryover_eligibility ?? null
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type AssistantTruthGateContractStatus,
|
||||
type AssistantTruthMode
|
||||
} from "../types/assistantRuntimeContracts";
|
||||
import { toAddressTruthGateContract } from "./addressTruthGatePolicy";
|
||||
import { resolveAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver";
|
||||
|
||||
export interface ResolveAssistantTruthAnswerPolicyRuntimeInput {
|
||||
@@ -228,10 +229,12 @@ function collectReasonCodes(input: {
|
||||
shadow: AssistantRuntimeContractShadowDecision;
|
||||
truthMode: AssistantTruthMode;
|
||||
truthGateStatus: AssistantTruthGateContractStatus;
|
||||
explicitGateReasonCodes: string[];
|
||||
}): string[] {
|
||||
const reasons: string[] = [];
|
||||
pushReason(reasons, `truth_gate_${input.truthGateStatus}`);
|
||||
pushReason(reasons, `truth_mode_${input.truthMode}`);
|
||||
input.explicitGateReasonCodes.forEach((item) => pushReason(reasons, item));
|
||||
input.shadow.transition_contract_reason.forEach((item) => pushReason(reasons, item));
|
||||
input.shadow.capability_contract_reason.forEach((item) => pushReason(reasons, item));
|
||||
toStringList(input.debug.missing_required_filters).forEach((item) => pushReason(reasons, `missing_filter_${item}`));
|
||||
@@ -314,12 +317,13 @@ export function resolveAssistantTruthAnswerPolicyRuntime(
|
||||
input: ResolveAssistantTruthAnswerPolicyRuntimeInput
|
||||
): AssistantTruthAnswerPolicyRuntimeContract {
|
||||
const debug = toRecordObject(input.addressDebug) ?? {};
|
||||
const explicitAddressTruthGate = toAddressTruthGateContract(debug.address_truth_gate_v1);
|
||||
const shadow = resolveAssistantRuntimeContractShadow({
|
||||
addressDebug: debug,
|
||||
addressRuntimeMeta: input.addressRuntimeMeta,
|
||||
groundingStatus: input.groundingStatus
|
||||
});
|
||||
const truthGateStatus = shadow.truth_gate_contract_status;
|
||||
const truthGateStatus = explicitAddressTruthGate?.truth_gate_status ?? shadow.truth_gate_contract_status;
|
||||
const groundingStatus = groundingStatusFrom(debug, input, truthGateStatus);
|
||||
const coverageStatus = coverageStatusFrom(debug, input, truthGateStatus, groundingStatus);
|
||||
const truthMode = truthModeFrom({
|
||||
@@ -335,7 +339,8 @@ export function resolveAssistantTruthAnswerPolicyRuntime(
|
||||
coverageReport,
|
||||
shadow,
|
||||
truthMode,
|
||||
truthGateStatus
|
||||
truthGateStatus,
|
||||
explicitGateReasonCodes: explicitAddressTruthGate?.reason_codes ?? []
|
||||
});
|
||||
const shape = answerShapeFrom({
|
||||
coverageStatus,
|
||||
@@ -343,7 +348,9 @@ export function resolveAssistantTruthAnswerPolicyRuntime(
|
||||
truthGateStatus
|
||||
});
|
||||
const carryoverEligibility =
|
||||
coverageStatus === "blocked" || truthMode === "unsupported" ? "none" : shadow.carryover_eligibility;
|
||||
coverageStatus === "blocked" || truthMode === "unsupported"
|
||||
? "none"
|
||||
: explicitAddressTruthGate?.carryover_eligibility ?? shadow.carryover_eligibility;
|
||||
|
||||
const truthGate = {
|
||||
schema_version: ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
|
||||
@@ -355,7 +362,8 @@ export function resolveAssistantTruthAnswerPolicyRuntime(
|
||||
carryover_eligibility: carryoverEligibility,
|
||||
reason_codes: reasonCodes,
|
||||
source_truth_gate_status: truthGateStatus,
|
||||
blocked_or_limited_explanation: explanationFor(truthGateStatus, truthMode, coverageStatus)
|
||||
blocked_or_limited_explanation:
|
||||
explicitAddressTruthGate?.blocked_or_limited_explanation ?? explanationFor(truthGateStatus, truthMode, coverageStatus)
|
||||
} satisfies AssistantTruthAnswerPolicyRuntimeContract["truth_gate"];
|
||||
|
||||
const answerShape = {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
export type AddressQuestionMode = "address_query" | "deep_analysis" | "unsupported";
|
||||
|
||||
import type {
|
||||
AssistantCarryoverDepth,
|
||||
AssistantTruthGateContractStatus
|
||||
} from "./assistantRuntimeContracts";
|
||||
|
||||
export type AddressIntent =
|
||||
| "period_coverage_profile"
|
||||
| "document_type_and_account_section_profile"
|
||||
@@ -261,6 +266,16 @@ export interface AddressExecutionDebug {
|
||||
| "rows_remaining_after_scope_filter";
|
||||
runtime_readiness: AddressRuntimeReadiness;
|
||||
limited_reason_category: AddressLimitedReasonCategory | null;
|
||||
address_truth_gate_v1?: {
|
||||
schema_version: "address_truth_gate_v1";
|
||||
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;
|
||||
} | null;
|
||||
organization_candidates?: string[];
|
||||
semantic_frame?: AddressSemanticFrame | null;
|
||||
response_type: AddressResponseType;
|
||||
|
||||
Reference in New Issue
Block a user