NODEDC_1C/llm_normalizer/backend/src/services/assistantTruthAnswerPolicyR...

430 lines
16 KiB
TypeScript

import {
ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
type AssistantAnswerShapeKind,
type AssistantAnswerShapeReplyType,
type AssistantCarryoverDepth,
type AssistantCoverageStatus,
type AssistantEvidenceGrade,
type AssistantGroundingStatus,
type AssistantRuntimeContractShadowDecision,
type AssistantTruthAnswerPolicyRuntimeContract,
type AssistantTruthGateContractStatus,
type AssistantTruthMode
} from "../types/assistantRuntimeContracts";
import { toAddressCoverageEvidenceContract } from "./addressCoverageEvidencePolicy";
import { toAddressTruthGateContract } from "./addressTruthGatePolicy";
import { resolveAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver";
export interface ResolveAssistantTruthAnswerPolicyRuntimeInput {
addressDebug?: Record<string, unknown> | null;
addressRuntimeMeta?: Record<string, unknown> | null;
groundingStatus?: unknown;
coverageReport?: Record<string, unknown> | null;
replyType?: unknown;
}
export interface AssistantTruthAnswerPolicyRuntimeFields {
assistant_truth_answer_policy_v1: AssistantTruthAnswerPolicyRuntimeContract;
coverage_gate_contract: AssistantTruthAnswerPolicyRuntimeContract["truth_gate"];
answer_shape_contract: AssistantTruthAnswerPolicyRuntimeContract["answer_shape"];
truth_mode: AssistantTruthMode;
carryover_eligibility: AssistantCarryoverDepth;
answer_shape: AssistantAnswerShapeKind;
}
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 asNumber(value: unknown): number | null {
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (typeof value === "string" && value.trim().length > 0) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
return 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 isGroundingStatus(value: string | null): value is AssistantGroundingStatus {
return (
value === "grounded" ||
value === "partial" ||
value === "route_mismatch_blocked" ||
value === "no_grounded_answer" ||
value === "unsupported"
);
}
function isEvidenceGrade(value: string | null): value is AssistantEvidenceGrade {
return value === "weak" || value === "medium" || value === "strong" || value === "none";
}
function normalizeReplyType(value: unknown): AssistantAnswerShapeReplyType {
if (value === "factual" || value === "partial_coverage" || value === "deep_analysis") {
return value;
}
return "unknown";
}
function groundingStatusFrom(
debug: Record<string, unknown>,
input: ResolveAssistantTruthAnswerPolicyRuntimeInput,
truthGateStatus: AssistantTruthGateContractStatus
): AssistantGroundingStatus {
const explicit =
toNonEmptyString(input.groundingStatus) ??
toNonEmptyString(toRecordObject(debug.answer_grounding_check)?.status);
if (isGroundingStatus(explicit)) {
return explicit;
}
if (truthGateStatus === "blocked_route_expectation_failure") {
return "route_mismatch_blocked";
}
if (truthGateStatus === "full_confirmed") {
return "grounded";
}
if (truthGateStatus === "partial_supported" || truthGateStatus === "limited_temporal_or_contextual") {
return "partial";
}
if (truthGateStatus === "blocked_missing_anchor" || truthGateStatus === "blocked_execution_error") {
return "no_grounded_answer";
}
const rowsMatched = asNumber(debug.rows_matched);
if (rowsMatched !== null && rowsMatched > 0) {
return "grounded";
}
return "unsupported";
}
function coverageStatusFrom(
debug: Record<string, unknown>,
input: ResolveAssistantTruthAnswerPolicyRuntimeInput,
truthGateStatus: AssistantTruthGateContractStatus,
groundingStatus: AssistantGroundingStatus
): AssistantCoverageStatus {
const explicitCoverageEvidence = toAddressCoverageEvidenceContract(debug.address_coverage_evidence_v1);
if (truthGateStatus === "full_confirmed") {
return "full";
}
if (truthGateStatus === "partial_supported" || truthGateStatus === "limited_temporal_or_contextual") {
return "partial";
}
if (truthGateStatus.startsWith("blocked")) {
return "blocked";
}
if (toStringList(debug.missing_required_filters).length > 0 || groundingStatus === "route_mismatch_blocked" || groundingStatus === "no_grounded_answer") {
return "blocked";
}
if (explicitCoverageEvidence) {
return explicitCoverageEvidence.coverage_status;
}
const coverageReport = toRecordObject(input.coverageReport) ?? toRecordObject(debug.coverage_report);
if (coverageReport) {
const total = asNumber(coverageReport.requirements_total);
const covered = asNumber(coverageReport.requirements_covered) ?? 0;
const uncovered = toStringList(coverageReport.requirements_uncovered);
const partial = toStringList(coverageReport.requirements_partially_covered);
const clarification = toStringList(coverageReport.clarification_needed_for);
const outOfScope = toStringList(coverageReport.out_of_scope_requirements);
if (total !== null && total > 0) {
if (covered >= total && uncovered.length === 0 && partial.length === 0 && clarification.length === 0 && outOfScope.length === 0) {
return "full";
}
if (covered > 0 || partial.length > 0) {
return "partial";
}
return "blocked";
}
}
const rowsMatched = asNumber(debug.rows_matched);
if (rowsMatched !== null && rowsMatched > 0) {
return "full";
}
return groundingStatus === "partial" ? "partial" : "blocked";
}
function truthModeFrom(input: {
coverageStatus: AssistantCoverageStatus;
groundingStatus: AssistantGroundingStatus;
truthGateStatus: AssistantTruthGateContractStatus;
debug: Record<string, unknown>;
}): AssistantTruthMode {
if (input.truthGateStatus === "blocked_missing_anchor" || toStringList(input.debug.missing_required_filters).length > 0) {
return "clarification_required";
}
if (input.truthGateStatus === "full_confirmed" || (input.coverageStatus === "full" && input.groundingStatus === "grounded")) {
return "confirmed";
}
if (input.truthGateStatus === "partial_supported" || input.truthGateStatus === "limited_temporal_or_contextual" || input.coverageStatus === "partial") {
return "limited";
}
return "unsupported";
}
function evidenceGradeFrom(
debug: Record<string, unknown>,
coverageStatus: AssistantCoverageStatus,
groundingStatus: AssistantGroundingStatus,
truthGateStatus: AssistantTruthGateContractStatus
): AssistantEvidenceGrade {
const explicitCoverageEvidence = toAddressCoverageEvidenceContract(debug.address_coverage_evidence_v1);
if (explicitCoverageEvidence?.evidence_strength && isEvidenceGrade(explicitCoverageEvidence.evidence_strength)) {
return explicitCoverageEvidence.evidence_strength;
}
const explicit = toNonEmptyString(debug.evidence_strength);
if (isEvidenceGrade(explicit)) {
return explicit;
}
if (coverageStatus === "blocked") {
return "none";
}
if (truthGateStatus === "full_confirmed" || groundingStatus === "grounded") {
return "strong";
}
const rowsMatched = asNumber(debug.rows_matched);
if (rowsMatched !== null && rowsMatched > 0) {
return "medium";
}
return coverageStatus === "partial" ? "weak" : "none";
}
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 collectReasonCodes(input: {
debug: Record<string, unknown>;
coverageReport: Record<string, unknown> | null;
shadow: AssistantRuntimeContractShadowDecision;
truthMode: AssistantTruthMode;
truthGateStatus: AssistantTruthGateContractStatus;
explicitGateReasonCodes: string[];
explicitCoverageReasonCodes: 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.explicitCoverageReasonCodes.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}`));
toStringList(input.debug.limitations).forEach((item) => pushReason(reasons, item));
toStringList(input.debug.reasons).forEach((item) => pushReason(reasons, item));
pushReason(reasons, input.debug.route_expectation_reason);
toStringList(toRecordObject(input.debug.answer_grounding_check)?.reasons).forEach((item) => pushReason(reasons, item));
toStringList(input.coverageReport?.requirements_uncovered).forEach((item) => pushReason(reasons, `coverage_uncovered_${item}`));
toStringList(input.coverageReport?.requirements_partially_covered).forEach((item) => pushReason(reasons, `coverage_partial_${item}`));
toStringList(input.coverageReport?.clarification_needed_for).forEach((item) => pushReason(reasons, `coverage_clarification_${item}`));
return reasons.slice(0, 32);
}
function explanationFor(
truthGateStatus: AssistantTruthGateContractStatus,
truthMode: AssistantTruthMode,
coverageStatus: AssistantCoverageStatus
): string | null {
if (truthGateStatus === "full_confirmed" && truthMode === "confirmed") {
return null;
}
if (truthGateStatus === "blocked_missing_anchor" || truthMode === "clarification_required") {
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 (truthGateStatus === "partial_supported" || truthMode === "limited" || coverageStatus === "partial") {
return "evidence_or_coverage_is_partial";
}
return "truth_gate_not_confirmed";
}
function answerShapeFrom(input: {
coverageStatus: AssistantCoverageStatus;
truthMode: AssistantTruthMode;
truthGateStatus: AssistantTruthGateContractStatus;
}): AssistantAnswerShapeKind {
if (input.truthMode === "confirmed") {
return "confirmed_factual";
}
if (input.truthMode === "limited") {
return "limited_with_reason";
}
if (input.truthMode === "clarification_required") {
return "clarification_required";
}
if (input.coverageStatus === "blocked" || input.truthGateStatus.startsWith("blocked")) {
return "blocked_no_answer";
}
return input.truthGateStatus === "unknown" ? "unknown" : "unsupported_boundary";
}
function requiredSectionsFor(shape: AssistantAnswerShapeKind): string[] {
if (shape === "confirmed_factual") {
return ["direct_answer", "evidence_basis"];
}
if (shape === "limited_with_reason") {
return ["direct_answer", "evidence_window", "limitations"];
}
if (shape === "clarification_required") {
return ["clarifying_question", "missing_anchors"];
}
if (shape === "blocked_no_answer") {
return ["blocked_reason", "safe_next_step"];
}
if (shape === "unsupported_boundary") {
return ["boundary_reason", "safe_next_step"];
}
return ["safe_next_step"];
}
export function resolveAssistantTruthAnswerPolicyRuntime(
input: ResolveAssistantTruthAnswerPolicyRuntimeInput
): AssistantTruthAnswerPolicyRuntimeContract {
const debug = toRecordObject(input.addressDebug) ?? {};
const explicitAddressTruthGate = toAddressTruthGateContract(debug.address_truth_gate_v1);
const explicitCoverageEvidence = toAddressCoverageEvidenceContract(debug.address_coverage_evidence_v1);
const shadow = resolveAssistantRuntimeContractShadow({
addressDebug: debug,
addressRuntimeMeta: input.addressRuntimeMeta,
groundingStatus: input.groundingStatus
});
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({
coverageStatus,
groundingStatus,
truthGateStatus,
debug
});
const evidenceGrade = evidenceGradeFrom(debug, coverageStatus, groundingStatus, truthGateStatus);
const coverageReport = toRecordObject(input.coverageReport) ?? toRecordObject(debug.coverage_report);
const reasonCodes = collectReasonCodes({
debug,
coverageReport,
shadow,
truthMode,
truthGateStatus,
explicitGateReasonCodes: explicitAddressTruthGate?.reason_codes ?? [],
explicitCoverageReasonCodes: explicitCoverageEvidence?.reason_codes ?? []
});
const shape = answerShapeFrom({
coverageStatus,
truthMode,
truthGateStatus
});
const carryoverEligibility =
coverageStatus === "blocked" || truthMode === "unsupported"
? "none"
: explicitAddressTruthGate?.carryover_eligibility ?? shadow.carryover_eligibility;
const truthGate = {
schema_version: ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
coverage_status: coverageStatus,
evidence_grade: evidenceGrade,
grounding_status: groundingStatus,
truth_mode: truthMode,
carryover_eligibility: carryoverEligibility,
reason_codes: reasonCodes,
source_truth_gate_status: truthGateStatus,
blocked_or_limited_explanation:
explicitAddressTruthGate?.blocked_or_limited_explanation ?? explanationFor(truthGateStatus, truthMode, coverageStatus)
} satisfies AssistantTruthAnswerPolicyRuntimeContract["truth_gate"];
const answerShape = {
schema_version: ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
answer_shape: shape,
reply_type: normalizeReplyType(input.replyType),
capability_contract_id: shadow.capability_contract_id,
transition_contract_id: shadow.transition_contract_id,
may_state_confirmed_facts: truthMode === "confirmed" || truthMode === "limited",
must_include_limitation: truthMode !== "confirmed",
may_power_followup: carryoverEligibility !== "none" && coverageStatus !== "blocked",
required_sections: requiredSectionsFor(shape),
downgrade_only: true
} satisfies AssistantTruthAnswerPolicyRuntimeContract["answer_shape"];
return {
schema_version: ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
truth_gate: truthGate,
answer_shape: answerShape
};
}
export function buildAssistantTruthAnswerPolicyRuntimeFields(
input: ResolveAssistantTruthAnswerPolicyRuntimeInput
): AssistantTruthAnswerPolicyRuntimeFields {
const policy = resolveAssistantTruthAnswerPolicyRuntime(input);
return {
assistant_truth_answer_policy_v1: policy,
coverage_gate_contract: policy.truth_gate,
answer_shape_contract: policy.answer_shape,
truth_mode: policy.truth_gate.truth_mode,
carryover_eligibility: policy.truth_gate.carryover_eligibility,
answer_shape: policy.answer_shape.answer_shape
};
}
export function attachAssistantTruthAnswerPolicy<T extends Record<string, unknown>>(
debugPayload: T,
input: Omit<ResolveAssistantTruthAnswerPolicyRuntimeInput, "addressDebug">
): T & AssistantTruthAnswerPolicyRuntimeFields {
return {
...debugPayload,
...buildAssistantTruthAnswerPolicyRuntimeFields({
...input,
addressDebug: debugPayload
})
};
}