АРЧ АП11 - Вынести exact-lane truth gate для factual-negative и limited ответов в отдельный policy-owner
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveAddressTruthGate } from "../src/services/addressTruthGatePolicy";
|
||||
|
||||
describe("address truth gate policy", () => {
|
||||
it("treats factual exact no-match as confirmed negative with reusable root scope", () => {
|
||||
const gate = resolveAddressTruthGate({
|
||||
intent: "open_items_by_counterparty_or_contract",
|
||||
filters: {
|
||||
account: "60",
|
||||
period_from: "2022-08-01",
|
||||
period_to: "2022-08-31"
|
||||
},
|
||||
selectedRecipe: "open_items_by_counterparty_or_contract",
|
||||
rowsMatched: 0,
|
||||
limitedReasonCategory: "empty_match",
|
||||
runtimeReadiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
reasons: ["open_items_account_exact_negative_response"],
|
||||
routeExpectationStatus: "matched",
|
||||
replyType: "factual"
|
||||
});
|
||||
|
||||
expect(gate.truth_gate_status).toBe("full_confirmed");
|
||||
expect(gate.carryover_eligibility).toBe("root_only");
|
||||
expect(gate.blocked_or_limited_explanation).toBeNull();
|
||||
expect(gate.reason_codes).toContain("limited_category_empty_match");
|
||||
});
|
||||
|
||||
it("keeps selected-item limited answers object-scoped", () => {
|
||||
const gate = resolveAddressTruthGate({
|
||||
intent: "inventory_sale_trace_for_item",
|
||||
filters: {
|
||||
item: "Рабочая станция"
|
||||
},
|
||||
semanticFrame: {
|
||||
scope_kind: "selected_object_scope",
|
||||
anchor_kind: "selected_object",
|
||||
anchor_value: "Рабочая станция",
|
||||
date_scope_kind: "explicit",
|
||||
date_basis_hint: "explicit_as_of_date",
|
||||
self_scope_detected: false,
|
||||
selected_object_scope_detected: true
|
||||
},
|
||||
selectedRecipe: "inventory_sale_trace_for_item",
|
||||
rowsMatched: 0,
|
||||
limitedReasonCategory: "empty_match",
|
||||
runtimeReadiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limitations: ["no_rows_after_recipe_and_scope_filter"],
|
||||
routeExpectationStatus: "matched",
|
||||
replyType: "partial_coverage"
|
||||
});
|
||||
|
||||
expect(gate.truth_gate_status).toBe("partial_supported");
|
||||
expect(gate.carryover_eligibility).toBe("object_only");
|
||||
expect(gate.blocked_or_limited_explanation).toBe("evidence_or_coverage_is_partial");
|
||||
});
|
||||
|
||||
it("blocks missing-anchor clarifications instead of pretending they are reusable evidence", () => {
|
||||
const gate = resolveAddressTruthGate({
|
||||
intent: "inventory_purchase_provenance_for_item",
|
||||
selectedRecipe: "inventory_purchase_provenance_for_item",
|
||||
rowsMatched: 0,
|
||||
limitedReasonCategory: "missing_anchor",
|
||||
runtimeReadiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
missingRequiredFilters: ["item"],
|
||||
limitations: ["missing_required_filters"],
|
||||
routeExpectationStatus: "matched",
|
||||
replyType: "partial_coverage"
|
||||
});
|
||||
|
||||
expect(gate.truth_gate_status).toBe("blocked_missing_anchor");
|
||||
expect(gate.carryover_eligibility).toBe("none");
|
||||
expect(gate.blocked_or_limited_explanation).toBe("required_anchor_missing");
|
||||
expect(gate.reason_codes).toContain("missing_filter_item");
|
||||
});
|
||||
});
|
||||
@@ -143,4 +143,27 @@ describe("assistant runtime contract registry", () => {
|
||||
|
||||
expect(decision.truth_gate_contract_status).toBe("limited_temporal_or_contextual");
|
||||
});
|
||||
|
||||
it("reuses explicit exact-lane truth gate contracts for factual no-match carryover", () => {
|
||||
const decision = resolveAssistantRuntimeContractShadow({
|
||||
addressDebug: {
|
||||
detected_intent: "open_items_by_counterparty_or_contract",
|
||||
selected_recipe: "open_items_by_counterparty_or_contract",
|
||||
rows_matched: 0,
|
||||
address_truth_gate_v1: {
|
||||
schema_version: "address_truth_gate_v1",
|
||||
policy_owner: "addressTruthGatePolicy",
|
||||
truth_gate_status: "full_confirmed",
|
||||
carryover_eligibility: "root_only",
|
||||
limited_reason_category: "empty_match",
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
reason_codes: ["open_items_account_exact_negative_response"],
|
||||
blocked_or_limited_explanation: null
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(decision.truth_gate_contract_status).toBe("full_confirmed");
|
||||
expect(decision.carryover_eligibility).toBe("root_only");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -89,6 +89,34 @@ describe("assistant truth answer policy runtime adapter", () => {
|
||||
expect(policy.answer_shape.may_power_followup).toBe(false);
|
||||
});
|
||||
|
||||
it("honors explicit exact-lane truth gate contracts for factual negative answers", () => {
|
||||
const policy = resolveAssistantTruthAnswerPolicyRuntime({
|
||||
addressDebug: {
|
||||
capability_id: "inventory_counterparty_item_flow",
|
||||
rows_matched: 0,
|
||||
address_truth_gate_v1: {
|
||||
schema_version: "address_truth_gate_v1",
|
||||
policy_owner: "addressTruthGatePolicy",
|
||||
truth_gate_status: "full_confirmed",
|
||||
carryover_eligibility: "root_only",
|
||||
limited_reason_category: "empty_match",
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
reason_codes: ["counterparty_item_flow_exact_negative_response"],
|
||||
blocked_or_limited_explanation: null
|
||||
}
|
||||
},
|
||||
replyType: "factual"
|
||||
});
|
||||
|
||||
expect(policy.truth_gate.coverage_status).toBe("full");
|
||||
expect(policy.truth_gate.grounding_status).toBe("grounded");
|
||||
expect(policy.truth_gate.truth_mode).toBe("confirmed");
|
||||
expect(policy.truth_gate.carryover_eligibility).toBe("root_only");
|
||||
expect(policy.truth_gate.reason_codes).toContain("counterparty_item_flow_exact_negative_response");
|
||||
expect(policy.answer_shape.answer_shape).toBe("confirmed_factual");
|
||||
expect(policy.answer_shape.may_power_followup).toBe(true);
|
||||
});
|
||||
|
||||
it("attaches top-level debug fields without hiding the nested contract", () => {
|
||||
const debug = attachAssistantTruthAnswerPolicy(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user