ARCH: довести surface-aware planner и catalog search

This commit is contained in:
2026-04-23 10:25:03 +03:00
parent 8b6cd4c329
commit 3a89cca6bb
30 changed files with 2521 additions and 157 deletions
@@ -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", () => {