АРЧ АП11 - Выделить truth и answer-shape policy в assistant runtime
This commit is contained in:
parent
9d5928125d
commit
89bcaccda8
|
|
@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.runAssistantAddressLaneResponseRuntime = runAssistantAddressLaneResponseRuntime;
|
exports.runAssistantAddressLaneResponseRuntime = runAssistantAddressLaneResponseRuntime;
|
||||||
const assistantAddressTurnFinalizeRuntimeAdapter_1 = require("./assistantAddressTurnFinalizeRuntimeAdapter");
|
const assistantAddressTurnFinalizeRuntimeAdapter_1 = require("./assistantAddressTurnFinalizeRuntimeAdapter");
|
||||||
const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver");
|
const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver");
|
||||||
|
const assistantTruthAnswerPolicyRuntimeAdapter_1 = require("./assistantTruthAnswerPolicyRuntimeAdapter");
|
||||||
function toRecordObject(value) {
|
function toRecordObject(value) {
|
||||||
if (!value || typeof value !== "object") {
|
if (!value || typeof value !== "object") {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -192,6 +193,10 @@ function runAssistantAddressLaneResponseRuntime(input) {
|
||||||
userMessage: input.userMessage,
|
userMessage: input.userMessage,
|
||||||
addressRuntimeMeta: input.llmPreDecomposeMeta
|
addressRuntimeMeta: input.llmPreDecomposeMeta
|
||||||
});
|
});
|
||||||
|
const debugWithTruthAnswerPolicy = (0, assistantTruthAnswerPolicyRuntimeAdapter_1.attachAssistantTruthAnswerPolicy)(debugWithRuntimeContracts, {
|
||||||
|
addressRuntimeMeta: input.llmPreDecomposeMeta,
|
||||||
|
replyType: normalizeAddressReplyType(input.addressLane.reply_type)
|
||||||
|
});
|
||||||
const finalization = finalizeAddressTurnSafe({
|
const finalization = finalizeAddressTurnSafe({
|
||||||
sessionId: input.sessionId,
|
sessionId: input.sessionId,
|
||||||
userMessage: input.userMessage,
|
userMessage: input.userMessage,
|
||||||
|
|
@ -199,7 +204,7 @@ function runAssistantAddressLaneResponseRuntime(input) {
|
||||||
assistantReply: safeAddressReply,
|
assistantReply: safeAddressReply,
|
||||||
replyType: normalizeAddressReplyType(input.addressLane.reply_type),
|
replyType: normalizeAddressReplyType(input.addressLane.reply_type),
|
||||||
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
|
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
|
||||||
debug: debugWithRuntimeContracts,
|
debug: debugWithTruthAnswerPolicy,
|
||||||
carryoverMeta: normalizeCarryoverMeta(input.carryoverMeta),
|
carryoverMeta: normalizeCarryoverMeta(input.carryoverMeta),
|
||||||
llmPreDecomposeMeta: normalizeLlmPreDecomposeMeta(input.llmPreDecomposeMeta),
|
llmPreDecomposeMeta: normalizeLlmPreDecomposeMeta(input.llmPreDecomposeMeta),
|
||||||
appendItem: input.appendItem,
|
appendItem: input.appendItem,
|
||||||
|
|
@ -211,6 +216,6 @@ function runAssistantAddressLaneResponseRuntime(input) {
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
response: finalization.response,
|
response: finalization.response,
|
||||||
debug: debugWithRuntimeContracts
|
debug: debugWithTruthAnswerPolicy
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.buildDeepAnalysisDebugPayload = buildDeepAnalysisDebugPayload;
|
exports.buildDeepAnalysisDebugPayload = buildDeepAnalysisDebugPayload;
|
||||||
const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver");
|
const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver");
|
||||||
|
const assistantTruthAnswerPolicyRuntimeAdapter_1 = require("./assistantTruthAnswerPolicyRuntimeAdapter");
|
||||||
const assistantStage4AnswerContractAudit_1 = require("./assistantStage4AnswerContractAudit");
|
const assistantStage4AnswerContractAudit_1 = require("./assistantStage4AnswerContractAudit");
|
||||||
function toAnalysisContext(input) {
|
function toAnalysisContext(input) {
|
||||||
if (!input.active) {
|
if (!input.active) {
|
||||||
|
|
@ -94,8 +95,14 @@ function buildDeepAnalysisDebugPayload(input) {
|
||||||
investigation_state_snapshot: input.investigationStateSnapshot,
|
investigation_state_snapshot: input.investigationStateSnapshot,
|
||||||
normalized: input.normalizedPayload
|
normalized: input.normalizedPayload
|
||||||
};
|
};
|
||||||
return (0, assistantRuntimeContractResolver_1.attachAssistantRuntimeContractShadow)(debugPayload, {
|
const debugWithRuntimeContracts = (0, assistantRuntimeContractResolver_1.attachAssistantRuntimeContractShadow)(debugPayload, {
|
||||||
addressRuntimeMeta: input.addressRuntimeMetaForDeep,
|
addressRuntimeMeta: input.addressRuntimeMetaForDeep,
|
||||||
groundingStatus: input.groundingCheck.status
|
groundingStatus: input.groundingCheck.status
|
||||||
});
|
});
|
||||||
|
return (0, assistantTruthAnswerPolicyRuntimeAdapter_1.attachAssistantTruthAnswerPolicy)(debugWithRuntimeContracts, {
|
||||||
|
addressRuntimeMeta: input.addressRuntimeMetaForDeep,
|
||||||
|
groundingStatus: input.groundingCheck.status,
|
||||||
|
coverageReport: input.coverageReport,
|
||||||
|
replyType: "deep_analysis"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
315
llm_normalizer/backend/dist/services/assistantTruthAnswerPolicyRuntimeAdapter.js
vendored
Normal file
315
llm_normalizer/backend/dist/services/assistantTruthAnswerPolicyRuntimeAdapter.js
vendored
Normal file
|
|
@ -0,0 +1,315 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.resolveAssistantTruthAnswerPolicyRuntime = resolveAssistantTruthAnswerPolicyRuntime;
|
||||||
|
exports.buildAssistantTruthAnswerPolicyRuntimeFields = buildAssistantTruthAnswerPolicyRuntimeFields;
|
||||||
|
exports.attachAssistantTruthAnswerPolicy = attachAssistantTruthAnswerPolicy;
|
||||||
|
const assistantRuntimeContracts_1 = require("../types/assistantRuntimeContracts");
|
||||||
|
const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver");
|
||||||
|
function toRecordObject(value) {
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
function toNonEmptyString(value) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const text = String(value).trim();
|
||||||
|
return text.length > 0 ? text : null;
|
||||||
|
}
|
||||||
|
function asNumber(value) {
|
||||||
|
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) {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
.map((item) => toNonEmptyString(item))
|
||||||
|
.filter((item) => Boolean(item));
|
||||||
|
}
|
||||||
|
function isGroundingStatus(value) {
|
||||||
|
return (value === "grounded" ||
|
||||||
|
value === "partial" ||
|
||||||
|
value === "route_mismatch_blocked" ||
|
||||||
|
value === "no_grounded_answer" ||
|
||||||
|
value === "unsupported");
|
||||||
|
}
|
||||||
|
function isEvidenceGrade(value) {
|
||||||
|
return value === "weak" || value === "medium" || value === "strong" || value === "none";
|
||||||
|
}
|
||||||
|
function normalizeReplyType(value) {
|
||||||
|
if (value === "factual" || value === "partial_coverage" || value === "deep_analysis") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
function groundingStatusFrom(debug, input, truthGateStatus) {
|
||||||
|
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, input, truthGateStatus, groundingStatus) {
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
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, coverageStatus, groundingStatus, truthGateStatus) {
|
||||||
|
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) {
|
||||||
|
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, value) {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (!text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const normalized = normalizeReasonCode(text);
|
||||||
|
if (normalized && !target.includes(normalized)) {
|
||||||
|
target.push(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function collectReasonCodes(input) {
|
||||||
|
const reasons = [];
|
||||||
|
pushReason(reasons, `truth_gate_${input.truthGateStatus}`);
|
||||||
|
pushReason(reasons, `truth_mode_${input.truthMode}`);
|
||||||
|
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, truthMode, coverageStatus) {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
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"];
|
||||||
|
}
|
||||||
|
function resolveAssistantTruthAnswerPolicyRuntime(input) {
|
||||||
|
const debug = toRecordObject(input.addressDebug) ?? {};
|
||||||
|
const shadow = (0, assistantRuntimeContractResolver_1.resolveAssistantRuntimeContractShadow)({
|
||||||
|
addressDebug: debug,
|
||||||
|
addressRuntimeMeta: input.addressRuntimeMeta,
|
||||||
|
groundingStatus: input.groundingStatus
|
||||||
|
});
|
||||||
|
const truthGateStatus = 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
|
||||||
|
});
|
||||||
|
const shape = answerShapeFrom({
|
||||||
|
coverageStatus,
|
||||||
|
truthMode,
|
||||||
|
truthGateStatus
|
||||||
|
});
|
||||||
|
const carryoverEligibility = coverageStatus === "blocked" || truthMode === "unsupported" ? "none" : shadow.carryover_eligibility;
|
||||||
|
const truthGate = {
|
||||||
|
schema_version: assistantRuntimeContracts_1.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: explanationFor(truthGateStatus, truthMode, coverageStatus)
|
||||||
|
};
|
||||||
|
const answerShape = {
|
||||||
|
schema_version: assistantRuntimeContracts_1.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
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
schema_version: assistantRuntimeContracts_1.ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
|
||||||
|
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
|
||||||
|
truth_gate: truthGate,
|
||||||
|
answer_shape: answerShape
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function buildAssistantTruthAnswerPolicyRuntimeFields(input) {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function attachAssistantTruthAnswerPolicy(debugPayload, input) {
|
||||||
|
return {
|
||||||
|
...debugPayload,
|
||||||
|
...buildAssistantTruthAnswerPolicyRuntimeFields({
|
||||||
|
...input,
|
||||||
|
addressDebug: debugPayload
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = void 0;
|
exports.ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION = exports.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = void 0;
|
||||||
exports.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = "assistant_runtime_contracts_v1";
|
exports.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = "assistant_runtime_contracts_v1";
|
||||||
|
exports.ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION = "assistant_truth_answer_policy_runtime_v1";
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
type FinalizeAssistantAddressTurnInput
|
type FinalizeAssistantAddressTurnInput
|
||||||
} from "./assistantAddressTurnFinalizeRuntimeAdapter";
|
} from "./assistantAddressTurnFinalizeRuntimeAdapter";
|
||||||
import { attachAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver";
|
import { attachAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver";
|
||||||
|
import { attachAssistantTruthAnswerPolicy } from "./assistantTruthAnswerPolicyRuntimeAdapter";
|
||||||
|
|
||||||
export interface RunAssistantAddressLaneResponseRuntimeInput<ResponseType = AssistantMessageResponsePayload> {
|
export interface RunAssistantAddressLaneResponseRuntimeInput<ResponseType = AssistantMessageResponsePayload> {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
|
|
@ -251,6 +252,10 @@ export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantM
|
||||||
userMessage: input.userMessage,
|
userMessage: input.userMessage,
|
||||||
addressRuntimeMeta: input.llmPreDecomposeMeta
|
addressRuntimeMeta: input.llmPreDecomposeMeta
|
||||||
});
|
});
|
||||||
|
const debugWithTruthAnswerPolicy = attachAssistantTruthAnswerPolicy(debugWithRuntimeContracts, {
|
||||||
|
addressRuntimeMeta: input.llmPreDecomposeMeta,
|
||||||
|
replyType: normalizeAddressReplyType(input.addressLane.reply_type)
|
||||||
|
});
|
||||||
const finalization = finalizeAddressTurnSafe({
|
const finalization = finalizeAddressTurnSafe({
|
||||||
sessionId: input.sessionId,
|
sessionId: input.sessionId,
|
||||||
userMessage: input.userMessage,
|
userMessage: input.userMessage,
|
||||||
|
|
@ -258,7 +263,7 @@ export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantM
|
||||||
assistantReply: safeAddressReply,
|
assistantReply: safeAddressReply,
|
||||||
replyType: normalizeAddressReplyType(input.addressLane.reply_type),
|
replyType: normalizeAddressReplyType(input.addressLane.reply_type),
|
||||||
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
|
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
|
||||||
debug: debugWithRuntimeContracts,
|
debug: debugWithTruthAnswerPolicy,
|
||||||
carryoverMeta: normalizeCarryoverMeta(input.carryoverMeta),
|
carryoverMeta: normalizeCarryoverMeta(input.carryoverMeta),
|
||||||
llmPreDecomposeMeta: normalizeLlmPreDecomposeMeta(input.llmPreDecomposeMeta),
|
llmPreDecomposeMeta: normalizeLlmPreDecomposeMeta(input.llmPreDecomposeMeta),
|
||||||
appendItem: input.appendItem,
|
appendItem: input.appendItem,
|
||||||
|
|
@ -271,6 +276,6 @@ export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantM
|
||||||
|
|
||||||
return {
|
return {
|
||||||
response: finalization.response as ResponseType,
|
response: finalization.response as ResponseType,
|
||||||
debug: debugWithRuntimeContracts
|
debug: debugWithTruthAnswerPolicy
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import type {
|
||||||
TemporalGuardAudit
|
TemporalGuardAudit
|
||||||
} from "./assistantRuntimeGuards";
|
} from "./assistantRuntimeGuards";
|
||||||
import { attachAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver";
|
import { attachAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver";
|
||||||
|
import { attachAssistantTruthAnswerPolicy } from "./assistantTruthAnswerPolicyRuntimeAdapter";
|
||||||
import { buildStage4AnswerContractAuditV1 } from "./assistantStage4AnswerContractAudit";
|
import { buildStage4AnswerContractAuditV1 } from "./assistantStage4AnswerContractAudit";
|
||||||
|
|
||||||
type RetrievalStatusItem = AssistantDebugPayload["retrieval_status"][number];
|
type RetrievalStatusItem = AssistantDebugPayload["retrieval_status"][number];
|
||||||
|
|
@ -174,8 +175,14 @@ export function buildDeepAnalysisDebugPayload(input: DeepAnalysisDebugPayloadInp
|
||||||
investigation_state_snapshot: input.investigationStateSnapshot,
|
investigation_state_snapshot: input.investigationStateSnapshot,
|
||||||
normalized: input.normalizedPayload
|
normalized: input.normalizedPayload
|
||||||
} as unknown as AssistantDebugPayload;
|
} as unknown as AssistantDebugPayload;
|
||||||
return attachAssistantRuntimeContractShadow(debugPayload as unknown as Record<string, unknown>, {
|
const debugWithRuntimeContracts = attachAssistantRuntimeContractShadow(debugPayload as unknown as Record<string, unknown>, {
|
||||||
addressRuntimeMeta: input.addressRuntimeMetaForDeep as unknown as Record<string, unknown> | null | undefined,
|
addressRuntimeMeta: input.addressRuntimeMetaForDeep as unknown as Record<string, unknown> | null | undefined,
|
||||||
groundingStatus: input.groundingCheck.status
|
groundingStatus: input.groundingCheck.status
|
||||||
|
});
|
||||||
|
return attachAssistantTruthAnswerPolicy(debugWithRuntimeContracts, {
|
||||||
|
addressRuntimeMeta: input.addressRuntimeMetaForDeep as unknown as Record<string, unknown> | null | undefined,
|
||||||
|
groundingStatus: input.groundingCheck.status,
|
||||||
|
coverageReport: input.coverageReport as unknown as Record<string, unknown>,
|
||||||
|
replyType: "deep_analysis"
|
||||||
}) as unknown as AssistantDebugPayload;
|
}) as unknown as AssistantDebugPayload;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,408 @@
|
||||||
|
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 { 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 {
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}): string[] {
|
||||||
|
const reasons: string[] = [];
|
||||||
|
pushReason(reasons, `truth_gate_${input.truthGateStatus}`);
|
||||||
|
pushReason(reasons, `truth_mode_${input.truthMode}`);
|
||||||
|
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 shadow = resolveAssistantRuntimeContractShadow({
|
||||||
|
addressDebug: debug,
|
||||||
|
addressRuntimeMeta: input.addressRuntimeMeta,
|
||||||
|
groundingStatus: input.groundingStatus
|
||||||
|
});
|
||||||
|
const truthGateStatus = 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
|
||||||
|
});
|
||||||
|
const shape = answerShapeFrom({
|
||||||
|
coverageStatus,
|
||||||
|
truthMode,
|
||||||
|
truthGateStatus
|
||||||
|
});
|
||||||
|
const carryoverEligibility =
|
||||||
|
coverageStatus === "blocked" || truthMode === "unsupported" ? "none" : 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: 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
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,10 @@ import type {
|
||||||
import type { AccountingGraphBuildResult } from "./stage4Graph";
|
import type { AccountingGraphBuildResult } from "./stage4Graph";
|
||||||
import type { AddressNavigationState } from "./addressNavigation";
|
import type { AddressNavigationState } from "./addressNavigation";
|
||||||
import type {
|
import type {
|
||||||
|
AssistantAnswerShapeKind,
|
||||||
AssistantRuntimeContractShadowDecision,
|
AssistantRuntimeContractShadowDecision,
|
||||||
|
AssistantTruthAnswerPolicyRuntimeContract,
|
||||||
|
AssistantTruthMode,
|
||||||
AssistantTransitionClassId
|
AssistantTransitionClassId
|
||||||
} from "./assistantRuntimeContracts";
|
} from "./assistantRuntimeContracts";
|
||||||
|
|
||||||
|
|
@ -454,6 +457,12 @@ export interface AssistantDebugPayload {
|
||||||
transition_contract_id?: AssistantTransitionClassId | null;
|
transition_contract_id?: AssistantTransitionClassId | null;
|
||||||
capability_contract_id?: string | null;
|
capability_contract_id?: string | null;
|
||||||
truth_gate_contract_status?: AssistantRuntimeContractShadowDecision["truth_gate_contract_status"];
|
truth_gate_contract_status?: AssistantRuntimeContractShadowDecision["truth_gate_contract_status"];
|
||||||
|
assistant_truth_answer_policy_v1?: AssistantTruthAnswerPolicyRuntimeContract;
|
||||||
|
coverage_gate_contract?: AssistantTruthAnswerPolicyRuntimeContract["truth_gate"];
|
||||||
|
answer_shape_contract?: AssistantTruthAnswerPolicyRuntimeContract["answer_shape"];
|
||||||
|
truth_mode?: AssistantTruthMode;
|
||||||
|
carryover_eligibility?: AssistantTruthAnswerPolicyRuntimeContract["truth_gate"]["carryover_eligibility"];
|
||||||
|
answer_shape?: AssistantAnswerShapeKind;
|
||||||
execution_lane?: "address_query" | "deep_analysis";
|
execution_lane?: "address_query" | "deep_analysis";
|
||||||
llm_decomposition_applied?: boolean;
|
llm_decomposition_applied?: boolean;
|
||||||
llm_decomposition_attempted?: boolean;
|
llm_decomposition_attempted?: boolean;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { AddressIntent } from "./addressQuery";
|
import type { AddressIntent } from "./addressQuery";
|
||||||
|
|
||||||
export const ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = "assistant_runtime_contracts_v1" as const;
|
export const ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = "assistant_runtime_contracts_v1" as const;
|
||||||
|
export const ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION = "assistant_truth_answer_policy_runtime_v1" as const;
|
||||||
|
|
||||||
export type AssistantLivingMode = "address_data" | "assistant_data_scope" | "chat" | "meta_followup" | "clarification";
|
export type AssistantLivingMode = "address_data" | "assistant_data_scope" | "chat" | "meta_followup" | "clarification";
|
||||||
export type AssistantFrameStatus = "active" | "suspended" | "closed" | "blocked";
|
export type AssistantFrameStatus = "active" | "suspended" | "closed" | "blocked";
|
||||||
|
|
@ -15,6 +16,26 @@ export type AssistantStateSlice =
|
||||||
| "answer_context_state";
|
| "answer_context_state";
|
||||||
export type AssistantCarryoverDepth = "full" | "root_only" | "object_only" | "meta_only" | "none";
|
export type AssistantCarryoverDepth = "full" | "root_only" | "object_only" | "meta_only" | "none";
|
||||||
export type AssistantAnswerMode = "confirmed" | "limited" | "clarification" | "boundary" | "meta" | "recap";
|
export type AssistantAnswerMode = "confirmed" | "limited" | "clarification" | "boundary" | "meta" | "recap";
|
||||||
|
export type AssistantCoverageStatus = "full" | "partial" | "blocked";
|
||||||
|
export type AssistantEvidenceGrade = "none" | "weak" | "medium" | "strong";
|
||||||
|
export type AssistantGroundingStatus = "grounded" | "partial" | "route_mismatch_blocked" | "no_grounded_answer" | "unsupported";
|
||||||
|
export type AssistantTruthMode = "confirmed" | "limited" | "clarification_required" | "unsupported";
|
||||||
|
export type AssistantTruthGateContractStatus =
|
||||||
|
| "full_confirmed"
|
||||||
|
| "partial_supported"
|
||||||
|
| "blocked_missing_anchor"
|
||||||
|
| "blocked_route_expectation_failure"
|
||||||
|
| "blocked_execution_error"
|
||||||
|
| "limited_temporal_or_contextual"
|
||||||
|
| "unknown";
|
||||||
|
export type AssistantAnswerShapeKind =
|
||||||
|
| "confirmed_factual"
|
||||||
|
| "limited_with_reason"
|
||||||
|
| "clarification_required"
|
||||||
|
| "unsupported_boundary"
|
||||||
|
| "blocked_no_answer"
|
||||||
|
| "unknown";
|
||||||
|
export type AssistantAnswerShapeReplyType = "factual" | "partial_coverage" | "deep_analysis" | "unknown";
|
||||||
|
|
||||||
export interface AssistantDateScopeState {
|
export interface AssistantDateScopeState {
|
||||||
as_of_date: string | null;
|
as_of_date: string | null;
|
||||||
|
|
@ -58,14 +79,42 @@ export interface AssistantClarificationState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AssistantCoverageGateState {
|
export interface AssistantCoverageGateState {
|
||||||
coverage_status: "full" | "partial" | "blocked";
|
coverage_status: AssistantCoverageStatus;
|
||||||
evidence_grade: "none" | "weak" | "medium" | "strong";
|
evidence_grade: AssistantEvidenceGrade;
|
||||||
grounding_status: "grounded" | "partial" | "route_mismatch_blocked" | "no_grounded_answer" | "unsupported";
|
grounding_status: AssistantGroundingStatus;
|
||||||
truth_mode: "confirmed" | "limited" | "clarification_required" | "unsupported";
|
truth_mode: AssistantTruthMode;
|
||||||
carryover_eligibility: AssistantCarryoverDepth;
|
carryover_eligibility: AssistantCarryoverDepth;
|
||||||
reason_codes: string[];
|
reason_codes: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AssistantTruthGateRuntimeContract extends AssistantCoverageGateState {
|
||||||
|
schema_version: typeof ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION;
|
||||||
|
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter";
|
||||||
|
source_truth_gate_status: AssistantTruthGateContractStatus;
|
||||||
|
blocked_or_limited_explanation: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssistantAnswerShapeRuntimeContract {
|
||||||
|
schema_version: typeof ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION;
|
||||||
|
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter";
|
||||||
|
answer_shape: AssistantAnswerShapeKind;
|
||||||
|
reply_type: AssistantAnswerShapeReplyType;
|
||||||
|
capability_contract_id: string | null;
|
||||||
|
transition_contract_id: AssistantTransitionClassId | null;
|
||||||
|
may_state_confirmed_facts: boolean;
|
||||||
|
must_include_limitation: boolean;
|
||||||
|
may_power_followup: boolean;
|
||||||
|
required_sections: string[];
|
||||||
|
downgrade_only: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssistantTruthAnswerPolicyRuntimeContract {
|
||||||
|
schema_version: typeof ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION;
|
||||||
|
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter";
|
||||||
|
truth_gate: AssistantTruthGateRuntimeContract;
|
||||||
|
answer_shape: AssistantAnswerShapeRuntimeContract;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AssistantSessionAggregateState {
|
export interface AssistantSessionAggregateState {
|
||||||
schema_version: typeof ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION;
|
schema_version: typeof ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION;
|
||||||
living_mode_state: {
|
living_mode_state: {
|
||||||
|
|
@ -160,13 +209,6 @@ export interface AssistantRuntimeContractShadowDecision {
|
||||||
transition_contract_reason: string[];
|
transition_contract_reason: string[];
|
||||||
capability_contract_id: string | null;
|
capability_contract_id: string | null;
|
||||||
capability_contract_reason: string[];
|
capability_contract_reason: string[];
|
||||||
truth_gate_contract_status:
|
truth_gate_contract_status: AssistantTruthGateContractStatus;
|
||||||
| "full_confirmed"
|
|
||||||
| "partial_supported"
|
|
||||||
| "blocked_missing_anchor"
|
|
||||||
| "blocked_route_expectation_failure"
|
|
||||||
| "blocked_execution_error"
|
|
||||||
| "limited_temporal_or_contextual"
|
|
||||||
| "unknown";
|
|
||||||
carryover_eligibility: AssistantCarryoverDepth;
|
carryover_eligibility: AssistantCarryoverDepth;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,16 @@ describe("assistant address lane response runtime adapter", () => {
|
||||||
address_followup_offer: { suggestion: "continue_previous" },
|
address_followup_offer: { suggestion: "continue_previous" },
|
||||||
assistant_runtime_contract_v1: expect.objectContaining({
|
assistant_runtime_contract_v1: expect.objectContaining({
|
||||||
schema_version: "assistant_runtime_contracts_v1"
|
schema_version: "assistant_runtime_contracts_v1"
|
||||||
|
}),
|
||||||
|
assistant_truth_answer_policy_v1: expect.objectContaining({
|
||||||
|
schema_version: "assistant_truth_answer_policy_runtime_v1",
|
||||||
|
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter"
|
||||||
|
}),
|
||||||
|
coverage_gate_contract: expect.objectContaining({
|
||||||
|
schema_version: "assistant_truth_answer_policy_runtime_v1"
|
||||||
|
}),
|
||||||
|
answer_shape_contract: expect.objectContaining({
|
||||||
|
reply_type: "factual"
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -107,9 +117,21 @@ describe("assistant address lane response runtime adapter", () => {
|
||||||
capability_contract_id: null,
|
capability_contract_id: null,
|
||||||
truth_gate_contract_status: "unknown"
|
truth_gate_contract_status: "unknown"
|
||||||
}),
|
}),
|
||||||
|
assistant_truth_answer_policy_v1: expect.objectContaining({
|
||||||
|
schema_version: "assistant_truth_answer_policy_runtime_v1"
|
||||||
|
}),
|
||||||
|
coverage_gate_contract: expect.objectContaining({
|
||||||
|
coverage_status: "blocked",
|
||||||
|
truth_mode: "unsupported"
|
||||||
|
}),
|
||||||
|
answer_shape_contract: expect.objectContaining({
|
||||||
|
answer_shape: "blocked_no_answer",
|
||||||
|
reply_type: "partial_coverage"
|
||||||
|
}),
|
||||||
transition_contract_id: null,
|
transition_contract_id: null,
|
||||||
capability_contract_id: null,
|
capability_contract_id: null,
|
||||||
truth_gate_contract_status: "unknown"
|
truth_gate_contract_status: "unknown",
|
||||||
|
carryover_eligibility: "none"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(runtime.response).toEqual({ ok: true });
|
expect(runtime.response).toEqual({ ok: true });
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,24 @@ describe("assistant debug payload assembler", () => {
|
||||||
expect(payload.address_llm_predecompose_applied).toBe(true);
|
expect(payload.address_llm_predecompose_applied).toBe(true);
|
||||||
expect(payload.assistant_outcome_class_v1).toBe("FULLY_ANSWERED");
|
expect(payload.assistant_outcome_class_v1).toBe("FULLY_ANSWERED");
|
||||||
expect(payload.answer_contract_stage4_v1?.is_stage4_shape).toBe(true);
|
expect(payload.answer_contract_stage4_v1?.is_stage4_shape).toBe(true);
|
||||||
|
expect(payload.assistant_truth_answer_policy_v1).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
schema_version: "assistant_truth_answer_policy_runtime_v1",
|
||||||
|
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(payload.coverage_gate_contract).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
coverage_status: "full",
|
||||||
|
truth_mode: "confirmed"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(payload.answer_shape_contract).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
answer_shape: "confirmed_factual",
|
||||||
|
reply_type: "deep_analysis"
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("omits optional fields when they are not provided", () => {
|
it("omits optional fields when they are not provided", () => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
attachAssistantTruthAnswerPolicy,
|
||||||
|
resolveAssistantTruthAnswerPolicyRuntime
|
||||||
|
} from "../src/services/assistantTruthAnswerPolicyRuntimeAdapter";
|
||||||
|
|
||||||
|
describe("assistant truth answer policy runtime adapter", () => {
|
||||||
|
it("emits confirmed truth gate and factual answer shape for grounded exact results", () => {
|
||||||
|
const policy = resolveAssistantTruthAnswerPolicyRuntime({
|
||||||
|
addressDebug: {
|
||||||
|
capability_id: "confirmed_inventory_on_hand_as_of_date",
|
||||||
|
detected_intent: "inventory_on_hand_as_of_date",
|
||||||
|
rows_matched: 3,
|
||||||
|
route_expectation_status: "matched",
|
||||||
|
evidence_strength: "strong"
|
||||||
|
},
|
||||||
|
groundingStatus: "grounded",
|
||||||
|
replyType: "factual"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(policy.truth_gate).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
schema_version: "assistant_truth_answer_policy_runtime_v1",
|
||||||
|
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
|
||||||
|
coverage_status: "full",
|
||||||
|
grounding_status: "grounded",
|
||||||
|
truth_mode: "confirmed",
|
||||||
|
evidence_grade: "strong",
|
||||||
|
source_truth_gate_status: "full_confirmed",
|
||||||
|
carryover_eligibility: "root_only",
|
||||||
|
blocked_or_limited_explanation: null
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(policy.answer_shape).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
answer_shape: "confirmed_factual",
|
||||||
|
reply_type: "factual",
|
||||||
|
capability_contract_id: "confirmed_inventory_on_hand_as_of_date",
|
||||||
|
transition_contract_id: "T1",
|
||||||
|
may_state_confirmed_facts: true,
|
||||||
|
must_include_limitation: false,
|
||||||
|
may_power_followup: true,
|
||||||
|
required_sections: ["direct_answer", "evidence_basis"],
|
||||||
|
downgrade_only: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps temporal/contextual limits as limited answer shape with explicit reason codes", () => {
|
||||||
|
const policy = resolveAssistantTruthAnswerPolicyRuntime({
|
||||||
|
addressDebug: {
|
||||||
|
capability_id: "confirmed_inventory_on_hand_as_of_date",
|
||||||
|
temporal_guard_outcome: "ambiguous_limited",
|
||||||
|
rows_matched: 2,
|
||||||
|
limitations: ["period_window_auto_broadened_to_available_data"]
|
||||||
|
},
|
||||||
|
groundingStatus: "partial",
|
||||||
|
replyType: "partial_coverage"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(policy.truth_gate.coverage_status).toBe("partial");
|
||||||
|
expect(policy.truth_gate.truth_mode).toBe("limited");
|
||||||
|
expect(policy.truth_gate.source_truth_gate_status).toBe("limited_temporal_or_contextual");
|
||||||
|
expect(policy.truth_gate.blocked_or_limited_explanation).toBe("temporal_or_contextual_limit");
|
||||||
|
expect(policy.truth_gate.reason_codes).toContain("period_window_auto_broadened_to_available_data");
|
||||||
|
expect(policy.answer_shape.answer_shape).toBe("limited_with_reason");
|
||||||
|
expect(policy.answer_shape.must_include_limitation).toBe(true);
|
||||||
|
expect(policy.answer_shape.required_sections).toEqual(["direct_answer", "evidence_window", "limitations"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("blocks route expectation failures and prevents follow-up carryover", () => {
|
||||||
|
const policy = resolveAssistantTruthAnswerPolicyRuntime({
|
||||||
|
addressDebug: {
|
||||||
|
capability_id: "confirmed_inventory_on_hand_as_of_date",
|
||||||
|
route_expectation_status: "mismatch",
|
||||||
|
route_expectation_reason: "expected_confirmed_balance_route"
|
||||||
|
},
|
||||||
|
groundingStatus: "route_mismatch_blocked",
|
||||||
|
replyType: "partial_coverage"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(policy.truth_gate.coverage_status).toBe("blocked");
|
||||||
|
expect(policy.truth_gate.grounding_status).toBe("route_mismatch_blocked");
|
||||||
|
expect(policy.truth_gate.truth_mode).toBe("unsupported");
|
||||||
|
expect(policy.truth_gate.carryover_eligibility).toBe("none");
|
||||||
|
expect(policy.truth_gate.reason_codes).toContain("expected_confirmed_balance_route");
|
||||||
|
expect(policy.answer_shape.answer_shape).toBe("blocked_no_answer");
|
||||||
|
expect(policy.answer_shape.may_state_confirmed_facts).toBe(false);
|
||||||
|
expect(policy.answer_shape.may_power_followup).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("attaches top-level debug fields without hiding the nested contract", () => {
|
||||||
|
const debug = attachAssistantTruthAnswerPolicy(
|
||||||
|
{
|
||||||
|
capability_id: "inventory_inventory_purchase_provenance_for_item",
|
||||||
|
missing_required_filters: ["item"],
|
||||||
|
limited_reason_category: "missing_anchor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replyType: "partial_coverage"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(debug.assistant_truth_answer_policy_v1.schema_version).toBe("assistant_truth_answer_policy_runtime_v1");
|
||||||
|
expect(debug.coverage_gate_contract.truth_mode).toBe("clarification_required");
|
||||||
|
expect(debug.answer_shape_contract.answer_shape).toBe("clarification_required");
|
||||||
|
expect(debug.truth_mode).toBe("clarification_required");
|
||||||
|
expect(debug.carryover_eligibility).toBe("none");
|
||||||
|
expect(debug.answer_shape).toBe("clarification_required");
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue