ARCH: довести surface-aware planner и catalog search
This commit is contained in:
@@ -4,7 +4,9 @@ import {
|
||||
buildAssistantMcpCatalogIndex,
|
||||
getAssistantMcpCatalogPrimitive,
|
||||
reviewAssistantMcpDiscoveryPlanAgainstCatalog,
|
||||
searchAssistantMcpCatalogPrimitivesByDecompositionCandidates
|
||||
searchAssistantMcpCatalogPrimitivesByDecompositionCandidates,
|
||||
searchAssistantMcpCatalogPrimitivesByFactAxis,
|
||||
searchAssistantMcpCatalogPrimitivesByMetadataSurface
|
||||
} from "../src/services/assistantMcpCatalogIndex";
|
||||
|
||||
describe("assistant MCP catalog index", () => {
|
||||
@@ -18,6 +20,9 @@ describe("assistant MCP catalog index", () => {
|
||||
expect(entry.safe_for_model_planning).toBe(true);
|
||||
expect(entry.runtime_must_execute).toBe(true);
|
||||
expect(entry.decomposition_hints.length).toBeGreaterThan(0);
|
||||
expect(Array.isArray(entry.supported_fact_families)).toBe(true);
|
||||
expect(Array.isArray(entry.supported_action_families)).toBe(true);
|
||||
expect(Array.isArray(entry.planning_tags)).toBe(true);
|
||||
expect(entry.required_axes_any_of.length).toBeGreaterThan(0);
|
||||
expect(entry.output_fact_kinds.length).toBeGreaterThan(0);
|
||||
}
|
||||
@@ -55,6 +60,41 @@ describe("assistant MCP catalog index", () => {
|
||||
expect(primitives).toEqual(["query_movements", "probe_coverage"]);
|
||||
});
|
||||
|
||||
it("can search reviewed primitives directly from fact family and required axes", () => {
|
||||
const primitives = searchAssistantMcpCatalogPrimitivesByFactAxis({
|
||||
business_fact_family: "document_evidence",
|
||||
action_family: "list_documents",
|
||||
has_subject_candidates: true,
|
||||
required_axes: ["counterparty", "period", "coverage_target"]
|
||||
});
|
||||
|
||||
expect(primitives).toEqual(["resolve_entity_reference", "query_documents", "probe_coverage"]);
|
||||
});
|
||||
|
||||
it("can search reviewed primitives directly from a confirmed document metadata surface", () => {
|
||||
const primitives = searchAssistantMcpCatalogPrimitivesByMetadataSurface({
|
||||
downstream_route_family: "document_evidence",
|
||||
selected_entity_set: "Document",
|
||||
selected_surface_objects: ["Document.InvoiceIssued"],
|
||||
recommended_next_primitive: "query_documents",
|
||||
required_axes: ["counterparty", "period", "coverage_target"]
|
||||
});
|
||||
|
||||
expect(primitives).toEqual(["query_documents", "resolve_entity_reference", "probe_coverage"]);
|
||||
});
|
||||
|
||||
it("can search reviewed primitives directly from a confirmed movement metadata surface", () => {
|
||||
const primitives = searchAssistantMcpCatalogPrimitivesByMetadataSurface({
|
||||
downstream_route_family: "movement_evidence",
|
||||
selected_entity_set: "AccumulationRegister",
|
||||
selected_surface_objects: ["Register.BankOperations"],
|
||||
recommended_next_primitive: "query_movements",
|
||||
required_axes: ["counterparty", "period", "coverage_target"]
|
||||
});
|
||||
|
||||
expect(primitives).toEqual(["query_movements", "resolve_entity_reference", "probe_coverage"]);
|
||||
});
|
||||
|
||||
it("marks a counterparty turnover discovery plan as catalog-compatible when required axes exist", () => {
|
||||
const plan = buildAssistantMcpDiscoveryPlan({
|
||||
semanticDataNeed: "counterparty turnover evidence",
|
||||
|
||||
@@ -311,6 +311,42 @@ describe("assistant MCP discovery answer adapter", () => {
|
||||
expect(draft.must_not_claim).toContain("Do not claim rows were checked when mcp_execution_performed=false.");
|
||||
});
|
||||
|
||||
it("keeps metadata lane-choice clarification human-facing when planner selects it from data-need graph", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: ["НДС"],
|
||||
business_fact_family: "metadata_surface",
|
||||
action_family: "resolve_next_lane",
|
||||
aggregation_need: null,
|
||||
time_scope_need: null,
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "supporting_evidence",
|
||||
clarification_gaps: ["lane_family_choice"],
|
||||
decomposition_candidates: [],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_requires_lane_family_choice"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "metadata",
|
||||
asked_action_family: "resolve_next_lane",
|
||||
explicit_entity_candidates: ["НДС"],
|
||||
unsupported_but_understood_family: "metadata_lane_choice_clarification"
|
||||
}
|
||||
});
|
||||
const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([]));
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
|
||||
expect(draft.answer_mode).toBe("needs_clarification");
|
||||
expect(draft.headline).toContain("data-lane");
|
||||
expect(draft.next_step_line).toContain("по документам");
|
||||
expect(draft.next_step_line).toContain("по движениям/регистрам");
|
||||
expect(draft.next_step_line).not.toContain("Уточните контрагента");
|
||||
});
|
||||
|
||||
it("keeps movement clarification anchored to the chosen lane after metadata ambiguity was resolved", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
|
||||
@@ -181,4 +181,29 @@ describe("assistant MCP discovery data need graph", () => {
|
||||
expect(result.reason_codes).toContain("data_need_graph_open_scope_total_without_subject");
|
||||
expect(result.reason_codes).toContain("data_need_graph_open_scope_total_needs_organization");
|
||||
});
|
||||
|
||||
it("treats all-time open-scope totals as an open-ended period rather than a missing period", () => {
|
||||
const result = buildAssistantMcpDiscoveryDataNeedGraph({
|
||||
semanticDataNeed: "counterparty value-flow evidence",
|
||||
rawUtterance: "сколько вообще денег мы заработали за все время?",
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.business_fact_family).toBe("value_flow");
|
||||
expect(result.subject_candidates).toEqual([]);
|
||||
expect(result.time_scope_need).toBe("all_time_scope");
|
||||
expect(result.clarification_gaps).toEqual([]);
|
||||
expect(result.proof_expectation).toBe("coverage_checked_fact");
|
||||
expect(result.decomposition_candidates).toEqual([
|
||||
"collect_scoped_movements",
|
||||
"aggregate_checked_amounts",
|
||||
"probe_coverage"
|
||||
]);
|
||||
expect(result.reason_codes).toContain("data_need_graph_open_scope_total_without_subject");
|
||||
expect(result.reason_codes).toContain("data_need_graph_all_time_scope_hint");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -378,6 +378,105 @@ describe("assistant MCP discovery pilot executor", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("selects a downstream lane from dominant metadata surface objects when one family clearly prevails", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
asked_domain_family: "metadata",
|
||||
asked_action_family: "inspect_surface",
|
||||
explicit_entity_candidates: ["НДС"]
|
||||
}
|
||||
});
|
||||
const deps = buildMetadataDeps([
|
||||
{
|
||||
FullName: "Документ.СчетФактураВыданный",
|
||||
MetaType: "Документ",
|
||||
attributes: [{ Name: "Дата" }]
|
||||
},
|
||||
{
|
||||
FullName: "Документ.СчетФактураПолученный",
|
||||
MetaType: "Документ",
|
||||
attributes: [{ Name: "Контрагент" }]
|
||||
},
|
||||
{
|
||||
FullName: "РегистрНакопления.НДСПокупок",
|
||||
MetaType: "РегистрНакопления",
|
||||
resources: [{ Name: "СуммаНДС" }]
|
||||
}
|
||||
]);
|
||||
|
||||
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
|
||||
|
||||
expect(result.pilot_status).toBe("executed");
|
||||
expect(result.derived_metadata_surface).toMatchObject({
|
||||
metadata_scope: "НДС",
|
||||
available_entity_sets: ["Документ", "РегистрНакопления"],
|
||||
selected_entity_set: null,
|
||||
selected_surface_objects: [
|
||||
"Документ.СчетФактураВыданный",
|
||||
"Документ.СчетФактураПолученный"
|
||||
],
|
||||
surface_family_scores: {
|
||||
document_evidence: 2,
|
||||
movement_evidence: 1,
|
||||
catalog_drilldown: 0
|
||||
},
|
||||
downstream_route_family: "document_evidence",
|
||||
route_family_selection_basis: "dominant_surface_objects",
|
||||
recommended_next_primitive: "query_documents",
|
||||
ambiguity_detected: false,
|
||||
ambiguity_entity_sets: []
|
||||
});
|
||||
expect(result.reason_codes).toContain("pilot_selected_metadata_route_family_from_dominant_surface_objects");
|
||||
expect(result.evidence.inferred_facts).toContain(
|
||||
"A likely next checked lane may be inferred as document_evidence from the confirmed metadata surface"
|
||||
);
|
||||
expect(result.evidence.unknown_facts).not.toContain(
|
||||
"Exact downstream metadata surface remains ambiguous across: Документ, РегистрНакопления"
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps metadata ambiguity unresolved when surface-family scores are nearly tied", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
asked_domain_family: "metadata",
|
||||
asked_action_family: "inspect_surface",
|
||||
explicit_entity_candidates: ["НДС"]
|
||||
}
|
||||
});
|
||||
const deps = buildMetadataDeps([
|
||||
{ FullName: "Документ.A", MetaType: "Документ" },
|
||||
{ FullName: "Документ.B", MetaType: "Документ" },
|
||||
{ FullName: "Документ.C", MetaType: "Документ" },
|
||||
{ FullName: "Документ.D", MetaType: "Документ" },
|
||||
{ FullName: "РегистрНакопления.A", MetaType: "РегистрНакопления" },
|
||||
{ FullName: "РегистрНакопления.B", MetaType: "РегистрНакопления" },
|
||||
{ FullName: "РегистрНакопления.C", MetaType: "РегистрНакопления" }
|
||||
]);
|
||||
|
||||
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
|
||||
|
||||
expect(result.pilot_status).toBe("executed");
|
||||
expect(result.derived_metadata_surface).toMatchObject({
|
||||
metadata_scope: "НДС",
|
||||
selected_entity_set: null,
|
||||
surface_family_scores: {
|
||||
document_evidence: 4,
|
||||
movement_evidence: 3,
|
||||
catalog_drilldown: 0
|
||||
},
|
||||
downstream_route_family: null,
|
||||
route_family_selection_basis: null,
|
||||
recommended_next_primitive: null,
|
||||
ambiguity_detected: true
|
||||
});
|
||||
expect(result.derived_metadata_surface?.ambiguity_entity_sets).toContain("Документ");
|
||||
expect(result.derived_metadata_surface?.ambiguity_entity_sets).toContain("РегистрНакопления");
|
||||
expect(result.reason_codes).not.toContain("pilot_selected_metadata_route_family_from_dominant_surface_objects");
|
||||
expect(result.evidence.unknown_facts).toContain(
|
||||
"Exact downstream metadata surface remains ambiguous across: Документ, РегистрНакопления"
|
||||
);
|
||||
});
|
||||
|
||||
it("infers metadata entity-set families from object names when meta type columns are absent", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
|
||||
@@ -113,6 +113,110 @@ describe("assistant MCP discovery planner", () => {
|
||||
expect(result.required_axes).toEqual(["counterparty", "coverage_target"]);
|
||||
});
|
||||
|
||||
it("expands a document evidence chain from catalog fact-axis search when decomposition hints are absent", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: ["SVK"],
|
||||
business_fact_family: "document_evidence",
|
||||
action_family: "list_documents",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: [],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "documents",
|
||||
asked_action_family: "list_documents",
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "document_evidence"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.selected_chain_id).toBe("document_evidence");
|
||||
expect(result.proposed_primitives).toEqual(["resolve_entity_reference", "query_documents", "probe_coverage"]);
|
||||
expect(result.reason_codes).toContain("planner_selected_catalog_primitives_from_fact_axis_search");
|
||||
expect(result.reason_codes).not.toContain("planner_fell_back_to_recipe_primitives_after_empty_catalog_search");
|
||||
});
|
||||
|
||||
it("filters conflicting document-vs-movement primitives when confirmed metadata surface recommends documents", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: ["SVK"],
|
||||
business_fact_family: "document_evidence",
|
||||
action_family: "list_documents",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["fetch_scoped_documents", "fetch_scoped_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built"]
|
||||
},
|
||||
metadataSurface: {
|
||||
selected_entity_set: "Документ",
|
||||
selected_surface_objects: ["Документ.СчетФактураВыданный"],
|
||||
downstream_route_family: "document_evidence",
|
||||
route_family_selection_basis: "selected_entity_set",
|
||||
recommended_next_primitive: "query_documents",
|
||||
ambiguity_detected: false,
|
||||
ambiguity_entity_sets: []
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "documents",
|
||||
asked_action_family: "list_documents",
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "document_evidence"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.metadata_surface_ref?.recommended_next_primitive).toBe("query_documents");
|
||||
expect(result.proposed_primitives).toEqual(["resolve_entity_reference", "query_documents", "probe_coverage"]);
|
||||
expect(result.proposed_primitives).not.toContain("query_movements");
|
||||
expect(result.reason_codes).toContain("planner_consumed_metadata_surface_ref_v1");
|
||||
expect(result.reason_codes).toContain("planner_filtered_catalog_primitives_by_confirmed_metadata_surface");
|
||||
expect(result.reason_codes).toContain("planner_surface_aware_next_lane_from_confirmed_metadata_objects");
|
||||
});
|
||||
|
||||
it("can select document evidence directly from a confirmed metadata surface when the follow-up itself is thin", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
metadataSurface: {
|
||||
selected_entity_set: "Document",
|
||||
selected_surface_objects: ["Document.InvoiceIssued"],
|
||||
downstream_route_family: "document_evidence",
|
||||
route_family_selection_basis: "selected_entity_set",
|
||||
recommended_next_primitive: "query_documents",
|
||||
ambiguity_detected: false,
|
||||
ambiguity_entity_sets: []
|
||||
},
|
||||
turnMeaning: {
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
explicit_date_scope: "2020"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.selected_chain_id).toBe("document_evidence");
|
||||
expect(result.proposed_primitives).toEqual(["resolve_entity_reference", "query_documents", "probe_coverage"]);
|
||||
expect(result.required_axes).toEqual(["counterparty", "period", "coverage_target"]);
|
||||
expect(result.reason_codes).toContain("planner_selected_document_from_confirmed_metadata_surface_ref");
|
||||
expect(result.reason_codes).toContain("planner_selected_catalog_primitives_from_metadata_surface_search");
|
||||
});
|
||||
|
||||
it("builds a movement discovery plan without aggregating value-flow totals", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
@@ -151,6 +255,141 @@ describe("assistant MCP discovery planner", () => {
|
||||
expect(result.reason_codes).toContain("planner_selected_catalog_primitives_from_decomposition_candidates");
|
||||
});
|
||||
|
||||
it("filters conflicting document-vs-movement primitives when confirmed metadata surface recommends movements", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: ["SVK"],
|
||||
business_fact_family: "movement_evidence",
|
||||
action_family: "list_movements",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["fetch_scoped_documents", "fetch_scoped_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built"]
|
||||
},
|
||||
metadataSurface: {
|
||||
selected_entity_set: "РегистрНакопления",
|
||||
selected_surface_objects: ["РегистрНакопления.ДвиженияДенежныхСредств"],
|
||||
downstream_route_family: "movement_evidence",
|
||||
route_family_selection_basis: "dominant_surface_objects",
|
||||
recommended_next_primitive: "query_movements",
|
||||
ambiguity_detected: false,
|
||||
ambiguity_entity_sets: []
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "movement_evidence"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.metadata_surface_ref?.recommended_next_primitive).toBe("query_movements");
|
||||
expect(result.proposed_primitives).toEqual(["resolve_entity_reference", "query_movements", "probe_coverage"]);
|
||||
expect(result.proposed_primitives).not.toContain("query_documents");
|
||||
expect(result.reason_codes).toContain("planner_filtered_catalog_primitives_by_confirmed_metadata_surface");
|
||||
});
|
||||
|
||||
it("can select movement evidence directly from a confirmed metadata surface when the follow-up itself is thin", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
metadataSurface: {
|
||||
selected_entity_set: "AccumulationRegister",
|
||||
selected_surface_objects: ["Register.BankOperations"],
|
||||
downstream_route_family: "movement_evidence",
|
||||
route_family_selection_basis: "dominant_surface_objects",
|
||||
recommended_next_primitive: "query_movements",
|
||||
ambiguity_detected: false,
|
||||
ambiguity_entity_sets: []
|
||||
},
|
||||
turnMeaning: {
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
explicit_date_scope: "2020"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.selected_chain_id).toBe("movement_evidence");
|
||||
expect(result.proposed_primitives).toEqual(["resolve_entity_reference", "query_movements", "probe_coverage"]);
|
||||
expect(result.required_axes).toEqual(["counterparty", "period", "coverage_target"]);
|
||||
expect(result.reason_codes).toContain("planner_selected_movement_from_confirmed_metadata_surface_ref");
|
||||
expect(result.reason_codes).toContain("planner_selected_catalog_primitives_from_metadata_surface_search");
|
||||
});
|
||||
|
||||
it("does not force a lane from ambiguous metadata surface even when decomposition hints mention both documents and movements", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: ["SVK"],
|
||||
business_fact_family: "document_evidence",
|
||||
action_family: "list_documents",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["fetch_scoped_documents", "fetch_scoped_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built"]
|
||||
},
|
||||
metadataSurface: {
|
||||
selected_entity_set: null,
|
||||
selected_surface_objects: ["Документ.СчетФактураВыданный", "РегистрНакопления.ДвиженияДенежныхСредств"],
|
||||
downstream_route_family: null,
|
||||
route_family_selection_basis: null,
|
||||
recommended_next_primitive: null,
|
||||
ambiguity_detected: true,
|
||||
ambiguity_entity_sets: ["Документ", "РегистрНакопления"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "documents",
|
||||
asked_action_family: "list_documents",
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "document_evidence"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.reason_codes).toContain("planner_consumed_metadata_surface_ref_v1");
|
||||
expect(result.reason_codes).not.toContain("planner_filtered_catalog_primitives_by_confirmed_metadata_surface");
|
||||
expect(result.proposed_primitives).toContain("query_documents");
|
||||
expect(result.proposed_primitives).toContain("query_movements");
|
||||
});
|
||||
|
||||
it("does not force a thin follow-up into a lane when the carried metadata surface is still ambiguous", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
metadataSurface: {
|
||||
selected_entity_set: null,
|
||||
selected_surface_objects: ["Document.InvoiceIssued", "Register.BankOperations"],
|
||||
downstream_route_family: null,
|
||||
route_family_selection_basis: null,
|
||||
recommended_next_primitive: null,
|
||||
ambiguity_detected: true,
|
||||
ambiguity_entity_sets: ["Document", "AccumulationRegister"]
|
||||
},
|
||||
turnMeaning: {
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
explicit_date_scope: "2020"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.selected_chain_id).toBe("entity_resolution");
|
||||
expect(result.reason_codes).toContain("planner_selected_entity_resolution_recipe");
|
||||
expect(result.reason_codes).not.toContain("planner_selected_document_from_confirmed_metadata_surface_ref");
|
||||
expect(result.reason_codes).not.toContain("planner_selected_movement_from_confirmed_metadata_surface_ref");
|
||||
});
|
||||
|
||||
it("can select value-flow chain from data need graph even when turn meaning family is still under-specified", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
|
||||
@@ -253,6 +253,41 @@ describe("assistant MCP discovery runtime bridge", () => {
|
||||
expect(result.reason_codes).toContain("runtime_bridge_status_checked_sources_only");
|
||||
});
|
||||
|
||||
it("keeps document evidence executable when the planner expands primitives from fact-axis search", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: ["SVK"],
|
||||
business_fact_family: "document_evidence",
|
||||
action_family: "list_documents",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: [],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "documents",
|
||||
asked_action_family: "list_documents",
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "document_evidence"
|
||||
},
|
||||
deps: buildDeps([{ Period: "2020-01-15T00:00:00", Registrator: "DOC-1", Counterparty: "SVK" }])
|
||||
});
|
||||
|
||||
expect(result.bridge_status).toBe("answer_draft_ready");
|
||||
expect(result.planner.selected_chain_id).toBe("document_evidence");
|
||||
expect(result.planner.proposed_primitives).toEqual(["resolve_entity_reference", "query_documents", "probe_coverage"]);
|
||||
expect(result.planner.reason_codes).toContain("planner_selected_catalog_primitives_from_fact_axis_search");
|
||||
expect(result.business_fact_answer_allowed).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves the answer adapter boundary against internal mechanics leakage", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
||||
turnMeaning: {
|
||||
|
||||
@@ -801,7 +801,10 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
followupContext: {
|
||||
previous_discovery_pilot_scope: "metadata_inspection_v1",
|
||||
previous_discovery_metadata_route_family: "document_evidence",
|
||||
previous_discovery_metadata_route_family_selection_basis: "selected_entity_set",
|
||||
previous_discovery_metadata_selected_entity_set: "Документ",
|
||||
previous_discovery_metadata_selected_surface_objects: ["Документ.СчетФактураВыданный"],
|
||||
previous_discovery_metadata_recommended_next_primitive: "query_documents",
|
||||
previous_filters: {
|
||||
counterparty: "SVK",
|
||||
period_from: "2020-01-01",
|
||||
@@ -824,7 +827,18 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
unsupported_but_understood_family: "document_evidence",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.metadata_surface_ref).toMatchObject({
|
||||
selected_entity_set: "Документ",
|
||||
selected_surface_objects: ["Документ.СчетФактураВыданный"],
|
||||
downstream_route_family: "document_evidence",
|
||||
route_family_selection_basis: "selected_entity_set",
|
||||
recommended_next_primitive: "query_documents",
|
||||
ambiguity_detected: false,
|
||||
ambiguity_entity_sets: []
|
||||
});
|
||||
expect(result.reason_codes).toContain("mcp_discovery_metadata_grounded_document_followup");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_metadata_surface_ref_from_followup_context");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_metadata_next_primitive_from_followup_context");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_counterparty_from_followup_context");
|
||||
});
|
||||
|
||||
@@ -1164,6 +1178,15 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
unsupported_but_understood_family: "metadata_lane_choice_clarification",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.metadata_surface_ref).toMatchObject({
|
||||
selected_entity_set: null,
|
||||
selected_surface_objects: [],
|
||||
downstream_route_family: null,
|
||||
route_family_selection_basis: null,
|
||||
recommended_next_primitive: null,
|
||||
ambiguity_detected: true,
|
||||
ambiguity_entity_sets: ["Документ", "РегистрНакопления"]
|
||||
});
|
||||
expect(result.reason_codes).toContain("mcp_discovery_metadata_ambiguity_requires_lane_choice");
|
||||
});
|
||||
|
||||
@@ -1477,6 +1500,37 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not keep a stale follow-up date when the user switches an open-scope total to all-time wording", () => {
|
||||
const orgName = "ООО Альтернатива Плюс";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "сколько вообще денег мы заработали за все время?",
|
||||
followupContext: {
|
||||
previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1",
|
||||
previous_filters: {
|
||||
organization: orgName,
|
||||
as_of_date: "2026-04-23"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_organization_scope: orgName,
|
||||
unsupported_but_understood_family: "counterparty_value_or_turnover",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
|
||||
expect(result.reason_codes).toContain("mcp_discovery_all_time_scope_signal_detected");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_date_scope_from_followup_context");
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||
expect(result.data_need_graph?.time_scope_need).toBe("all_time_scope");
|
||||
expect(result.data_need_graph?.reason_codes).toContain("data_need_graph_all_time_scope_hint");
|
||||
});
|
||||
|
||||
it("resumes an open-scope ranking from follow-up context when the user clarifies only the organization", () => {
|
||||
const orgName = "ООО Альтернатива Плюс";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
|
||||
@@ -1365,7 +1365,10 @@ describe("assistantTransitionPolicy", () => {
|
||||
pilot_scope: "metadata_inspection_v1",
|
||||
derived_metadata_surface: {
|
||||
selected_entity_set: "Документ",
|
||||
selected_surface_objects: ["Документ.СчетФактураВыданный"],
|
||||
downstream_route_family: "document_evidence",
|
||||
route_family_selection_basis: "selected_entity_set",
|
||||
recommended_next_primitive: "query_documents",
|
||||
ambiguity_detected: false
|
||||
}
|
||||
},
|
||||
@@ -1384,7 +1387,12 @@ describe("assistantTransitionPolicy", () => {
|
||||
|
||||
expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe("metadata_inspection_v1");
|
||||
expect(carryover?.followupContext?.previous_discovery_metadata_route_family).toBe("document_evidence");
|
||||
expect(carryover?.followupContext?.previous_discovery_metadata_route_family_selection_basis).toBe("selected_entity_set");
|
||||
expect(carryover?.followupContext?.previous_discovery_metadata_selected_entity_set).toBe("Документ");
|
||||
expect(carryover?.followupContext?.previous_discovery_metadata_selected_surface_objects).toEqual([
|
||||
"Документ.СчетФактураВыданный"
|
||||
]);
|
||||
expect(carryover?.followupContext?.previous_discovery_metadata_recommended_next_primitive).toBe("query_documents");
|
||||
expect(carryover?.followupContext?.previous_discovery_metadata_ambiguity_detected).toBeUndefined();
|
||||
});
|
||||
it("carries metadata ambiguity entity sets into follow-up context for downstream lane arbitration", () => {
|
||||
|
||||
Reference in New Issue
Block a user