Planner Autonomy: вынести MCP chain templates в route fabric
This commit is contained in:
parent
b12f370784
commit
5cd4d459fe
|
|
@ -6,6 +6,7 @@ exports.searchAssistantMcpCatalogPrimitivesByFactAxis = searchAssistantMcpCatalo
|
|||
exports.searchAssistantMcpCatalogPrimitivesByMetadataSurface = searchAssistantMcpCatalogPrimitivesByMetadataSurface;
|
||||
exports.buildAssistantMcpCatalogIndex = buildAssistantMcpCatalogIndex;
|
||||
exports.getAssistantMcpCatalogPrimitive = getAssistantMcpCatalogPrimitive;
|
||||
exports.getAssistantMcpCatalogChainTemplate = getAssistantMcpCatalogChainTemplate;
|
||||
exports.reviewAssistantMcpDiscoveryPlanAgainstCatalog = reviewAssistantMcpDiscoveryPlanAgainstCatalog;
|
||||
const assistantMcpDiscoveryPolicy_1 = require("./assistantMcpDiscoveryPolicy");
|
||||
exports.ASSISTANT_MCP_CATALOG_INDEX_SCHEMA_VERSION = "assistant_mcp_catalog_index_v1";
|
||||
|
|
@ -183,6 +184,117 @@ const PRIMITIVE_CONTRACTS = [
|
|||
}
|
||||
];
|
||||
const PRIMITIVE_CONTRACT_MAP = new Map(PRIMITIVE_CONTRACTS.map((contract) => [contract.primitive_id, contract]));
|
||||
const CHAIN_TEMPLATES = [
|
||||
{
|
||||
chain_id: "metadata_inspection",
|
||||
semantic_data_need: "1C metadata evidence",
|
||||
chain_summary: "Inspect the 1C metadata surface first, then ground the next safe lane from confirmed schema evidence.",
|
||||
fallback_primitives: ["inspect_1c_metadata"],
|
||||
base_required_axes: ["metadata_scope"],
|
||||
supported_fact_families: ["schema_surface"],
|
||||
supported_action_families: ["inspect_catalog", "inspect_documents", "inspect_registers", "inspect_fields", "inspect_surface"],
|
||||
planning_tags: ["metadata", "surface_inspection"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "catalog_drilldown",
|
||||
semantic_data_need: "catalog drilldown metadata evidence",
|
||||
chain_summary: "Drill deeper into the confirmed catalog-oriented metadata surface, inspect related metadata objects, and keep the next safe lane grounded in checked schema evidence.",
|
||||
fallback_primitives: ["inspect_1c_metadata"],
|
||||
base_required_axes: ["metadata_scope"],
|
||||
supported_fact_families: ["schema_surface"],
|
||||
supported_action_families: ["inspect_catalog", "inspect_surface"],
|
||||
planning_tags: ["metadata", "surface_inspection", "drilldown"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "entity_resolution",
|
||||
semantic_data_need: "entity discovery evidence",
|
||||
chain_summary: "Search candidate business entities, resolve the most relevant 1C reference, and prove whether the entity grounding is stable enough for the next probe.",
|
||||
fallback_primitives: ["search_business_entity", "resolve_entity_reference", "probe_coverage"],
|
||||
base_required_axes: ["business_entity", "coverage_target"],
|
||||
supported_fact_families: ["entity_grounding"],
|
||||
supported_action_families: ["search_business_entity"],
|
||||
planning_tags: ["subject_resolution", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "document_evidence",
|
||||
semantic_data_need: "document evidence",
|
||||
chain_summary: "Resolve the business entity, fetch scoped document rows, and probe coverage before stating the checked document evidence.",
|
||||
fallback_primitives: ["resolve_entity_reference", "query_documents", "probe_coverage"],
|
||||
base_required_axes: ["coverage_target"],
|
||||
supported_fact_families: ["document_evidence", "activity_lifecycle"],
|
||||
supported_action_families: ["list_documents", "activity_duration"],
|
||||
planning_tags: ["document", "subject_resolution", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "movement_evidence",
|
||||
semantic_data_need: "movement evidence",
|
||||
chain_summary: "Resolve the business entity, fetch scoped movement rows, and probe coverage without pretending to have a full movement universe.",
|
||||
fallback_primitives: ["resolve_entity_reference", "query_movements", "probe_coverage"],
|
||||
base_required_axes: ["coverage_target"],
|
||||
supported_fact_families: ["movement_evidence", "value_flow"],
|
||||
supported_action_families: ["list_movements", "turnover", "payout", "net_value_flow"],
|
||||
planning_tags: ["movement", "subject_resolution", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "value_flow",
|
||||
semantic_data_need: "counterparty value-flow evidence",
|
||||
chain_summary: "Resolve the business entity, query scoped movements, aggregate checked amounts, then probe coverage before answering.",
|
||||
fallback_primitives: ["resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage"],
|
||||
base_required_axes: ["aggregate_axis", "amount", "coverage_target"],
|
||||
supported_fact_families: ["value_flow"],
|
||||
supported_action_families: ["turnover", "payout", "net_value_flow"],
|
||||
planning_tags: ["movement", "aggregation", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "value_flow_comparison",
|
||||
semantic_data_need: "bidirectional value-flow comparison evidence",
|
||||
chain_summary: "Query incoming and outgoing movements for the checked period and organization, compare the checked sides, and probe coverage before answering a bounded comparison.",
|
||||
fallback_primitives: ["query_movements", "probe_coverage"],
|
||||
base_required_axes: ["amount", "coverage_target"],
|
||||
supported_fact_families: ["value_flow"],
|
||||
supported_action_families: ["net_value_flow"],
|
||||
planning_tags: ["movement", "comparison", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "value_flow_ranking",
|
||||
semantic_data_need: "ranked value-flow evidence",
|
||||
chain_summary: "Query scoped movements for the checked period and organization, aggregate checked amounts by counterparty, then probe coverage before answering a bounded ranking.",
|
||||
fallback_primitives: ["query_movements", "aggregate_by_axis", "probe_coverage"],
|
||||
base_required_axes: ["aggregate_axis", "amount", "coverage_target"],
|
||||
supported_fact_families: ["value_flow"],
|
||||
supported_action_families: ["turnover", "payout"],
|
||||
planning_tags: ["movement", "ranking", "aggregation", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "lifecycle",
|
||||
semantic_data_need: "counterparty lifecycle evidence",
|
||||
chain_summary: "Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window.",
|
||||
fallback_primitives: ["resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis"],
|
||||
base_required_axes: ["document_date", "coverage_target", "evidence_basis"],
|
||||
supported_fact_families: ["activity_lifecycle"],
|
||||
supported_action_families: ["activity_duration"],
|
||||
planning_tags: ["document", "explanation", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
}
|
||||
];
|
||||
const CHAIN_TEMPLATE_MAP = new Map(CHAIN_TEMPLATES.map((template) => [template.chain_id, template]));
|
||||
function toStringSet(values) {
|
||||
return new Set(values.map((item) => item.trim()).filter((item) => item.length > 0));
|
||||
}
|
||||
|
|
@ -516,10 +628,18 @@ function buildAssistantMcpCatalogIndex() {
|
|||
else {
|
||||
pushReason(reasonCodes, "catalog_covers_all_discovery_primitives");
|
||||
}
|
||||
const unknownChainPrimitives = CHAIN_TEMPLATES.flatMap((template) => template.fallback_primitives.filter((primitive) => !PRIMITIVE_CONTRACT_MAP.has(primitive)));
|
||||
if (unknownChainPrimitives.length > 0) {
|
||||
pushReason(reasonCodes, "catalog_chain_template_references_unknown_primitive");
|
||||
}
|
||||
else {
|
||||
pushReason(reasonCodes, "catalog_chain_templates_reference_reviewed_primitives");
|
||||
}
|
||||
return {
|
||||
schema_version: exports.ASSISTANT_MCP_CATALOG_INDEX_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpCatalogIndex",
|
||||
primitives: PRIMITIVE_CONTRACTS,
|
||||
chain_templates: CHAIN_TEMPLATES,
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
}
|
||||
|
|
@ -530,6 +650,13 @@ function getAssistantMcpCatalogPrimitive(primitive) {
|
|||
}
|
||||
return contract;
|
||||
}
|
||||
function getAssistantMcpCatalogChainTemplate(chainId) {
|
||||
const template = CHAIN_TEMPLATE_MAP.get(chainId);
|
||||
if (!template) {
|
||||
throw new Error(`Missing MCP catalog chain template: ${chainId}`);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
function reviewAssistantMcpDiscoveryPlanAgainstCatalog(plan) {
|
||||
const reasonCodes = [];
|
||||
const axisSet = toStringSet(plan.required_axes);
|
||||
|
|
|
|||
|
|
@ -35,6 +35,28 @@ function pushUnique(target, value) {
|
|||
target.push(text);
|
||||
}
|
||||
}
|
||||
function pushAllUnique(target, values) {
|
||||
for (const value of values) {
|
||||
pushUnique(target, value);
|
||||
}
|
||||
}
|
||||
function recipeFromCatalogChainTemplate(input) {
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)(input.chainId);
|
||||
const axes = [...input.axes];
|
||||
pushAllUnique(axes, template.base_required_axes);
|
||||
return {
|
||||
semanticDataNeed: input.semanticDataNeed ?? template.semantic_data_need,
|
||||
chainId: template.chain_id,
|
||||
chainSummary: input.chainSummary ?? template.chain_summary,
|
||||
primitives: input.primitives ?? template.fallback_primitives,
|
||||
axes,
|
||||
reason: input.reason,
|
||||
extraReasons: [
|
||||
`planner_instantiated_catalog_chain_template_${template.chain_id}`,
|
||||
...(input.extraReasons ?? [])
|
||||
]
|
||||
};
|
||||
}
|
||||
function hasEntity(meaning) {
|
||||
return (meaning?.explicit_entity_candidates?.length ?? 0) > 0;
|
||||
}
|
||||
|
|
@ -306,60 +328,60 @@ function recipeFor(input) {
|
|||
const thinSurfaceRouteFamily = routeFamilyFromThinMetadataSurfaceInput(input);
|
||||
if (thinSurfaceRouteFamily === "document_evidence") {
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("document_evidence");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["resolve_entity_reference", "query_documents", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "document evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "document_evidence",
|
||||
chainSummary: "Ground the next checked document lane from the confirmed metadata surface, then fetch scoped document rows and probe coverage before answering.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_document_from_confirmed_metadata_surface_ref",
|
||||
chainSummary: "Ground the next checked document lane from the confirmed metadata surface, then fetch scoped document rows and probe coverage before answering.",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (thinSurfaceRouteFamily === "movement_evidence") {
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("movement_evidence");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["resolve_entity_reference", "query_movements", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "movement evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "movement_evidence",
|
||||
chainSummary: "Ground the next checked movement lane from the confirmed metadata surface, then fetch scoped movement rows and probe coverage before answering.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_movement_from_confirmed_metadata_surface_ref",
|
||||
chainSummary: "Ground the next checked movement lane from the confirmed metadata surface, then fetch scoped movement rows and probe coverage before answering.",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (thinSurfaceRouteFamily === "catalog_drilldown") {
|
||||
pushUnique(axes, "metadata_scope");
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("catalog_drilldown");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["inspect_1c_metadata"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "catalog drilldown metadata evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "catalog_drilldown",
|
||||
chainSummary: "Drill deeper into the confirmed catalog-oriented metadata surface, inspect related metadata objects, and keep the next safe lane grounded in checked schema evidence.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_catalog_drilldown_from_confirmed_metadata_surface_ref",
|
||||
chainSummary: "Drill deeper into the confirmed catalog-oriented metadata surface, inspect related metadata objects, and keep the next safe lane grounded in checked schema evidence.",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (graphFactFamily === "value_flow") {
|
||||
if (dataNeedGraph?.comparison_need === "incoming_vs_outgoing" && !hasSubjectCandidates(dataNeedGraph)) {
|
||||
|
|
@ -368,47 +390,45 @@ function recipeFor(input) {
|
|||
if (requestedAggregationAxis === "month" || graphAggregation === "by_month") {
|
||||
pushUnique(axes, "calendar_month");
|
||||
}
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("value_flow_comparison");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["query_movements", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action,
|
||||
allowAggregateByAxis: false
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "bidirectional value-flow comparison evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "value_flow_comparison",
|
||||
chainSummary: "Query incoming and outgoing movements for the checked period and organization, compare the checked sides, and probe coverage before answering a bounded comparison.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_bidirectional_value_flow_comparison_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (dataNeedGraph?.ranking_need && !hasSubjectCandidates(dataNeedGraph)) {
|
||||
pushUnique(axes, "aggregate_axis");
|
||||
pushUnique(axes, "amount");
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("value_flow_ranking");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["query_movements", "aggregate_by_axis", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action,
|
||||
allowAggregateByAxis: true
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "ranked value-flow evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "value_flow_ranking",
|
||||
chainSummary: "Query scoped movements for the checked period and organization, aggregate checked amounts by counterparty, then probe coverage before answering a bounded ranking.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: dataNeedGraph.ranking_need === "bottom_asc"
|
||||
? "planner_selected_bottom_ranked_value_flow_from_data_need_graph"
|
||||
: "planner_selected_top_ranked_value_flow_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (openScopeTotalWithoutSubject) {
|
||||
pushUnique(axes, "organization");
|
||||
|
|
@ -425,17 +445,17 @@ function recipeFor(input) {
|
|||
actionFamily: action,
|
||||
allowAggregateByAxis: true
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "organization-scoped value-flow evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "value_flow",
|
||||
chainSummary: "Query scoped movements for the checked period and organization without a preselected counterparty, aggregate checked amounts, then probe coverage before answering a bounded total.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
semanticDataNeed: "organization-scoped value-flow evidence",
|
||||
chainSummary: "Query scoped movements for the checked period and organization without a preselected counterparty, aggregate checked amounts, then probe coverage before answering a bounded total.",
|
||||
reason: requestedAggregationAxis === "month" || graphAggregation === "by_month"
|
||||
? "planner_selected_monthly_open_scope_value_flow_total_from_data_need_graph"
|
||||
: "planner_selected_open_scope_value_flow_total_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
pushUnique(axes, "aggregate_axis");
|
||||
pushUnique(axes, "amount");
|
||||
|
|
@ -443,70 +463,68 @@ function recipeFor(input) {
|
|||
if (requestedAggregationAxis === "month" || graphAggregation === "by_month") {
|
||||
pushUnique(axes, "calendar_month");
|
||||
}
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("value_flow");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action,
|
||||
allowAggregateByAxis: true
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "counterparty value-flow evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "value_flow",
|
||||
chainSummary: "Resolve the business entity, query scoped movements, aggregate checked amounts, then probe coverage before answering.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: requestedAggregationAxis === "month" || graphAggregation === "by_month"
|
||||
? "planner_selected_monthly_value_flow_from_data_need_graph"
|
||||
: "planner_selected_value_flow_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (graphFactFamily === "activity_lifecycle") {
|
||||
pushUnique(axes, "document_date");
|
||||
pushUnique(axes, "coverage_target");
|
||||
pushUnique(axes, "evidence_basis");
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("lifecycle");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "counterparty lifecycle evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "lifecycle",
|
||||
chainSummary: "Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_lifecycle_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (graphFactFamily === "schema_surface") {
|
||||
pushUnique(axes, "metadata_scope");
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("metadata_inspection");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["inspect_1c_metadata"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "1C metadata evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "metadata_inspection",
|
||||
chainSummary: "Inspect the 1C metadata surface first, then ground the next safe lane from confirmed schema evidence.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_metadata_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (graphFactFamily === "movement_evidence") {
|
||||
if (metadataScopedOpenLane) {
|
||||
pushUnique(axes, "organization");
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("movement_evidence");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["query_movements", "probe_coverage"],
|
||||
|
|
@ -514,38 +532,38 @@ function recipeFor(input) {
|
|||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "movement evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "movement_evidence",
|
||||
chainSummary: "Keep the metadata-scoped movement lane, ask only for the remaining business scope, then fetch scoped movement rows and probe coverage without pretending there is a grounded counterparty.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_metadata_scoped_movement_from_data_need_graph",
|
||||
chainSummary: "Keep the metadata-scoped movement lane, ask only for the remaining business scope, then fetch scoped movement rows and probe coverage without pretending there is a grounded counterparty.",
|
||||
semanticDataNeed: template.semantic_data_need,
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("movement_evidence");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["resolve_entity_reference", "query_movements", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "movement evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "movement_evidence",
|
||||
chainSummary: "Resolve the business entity, fetch scoped movement rows, and probe coverage without pretending to have a full movement universe.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_movement_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (graphFactFamily === "document_evidence") {
|
||||
if (metadataScopedOpenLane) {
|
||||
pushUnique(axes, "organization");
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("document_evidence");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["query_documents", "probe_coverage"],
|
||||
|
|
@ -553,55 +571,53 @@ function recipeFor(input) {
|
|||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "document evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "document_evidence",
|
||||
chainSummary: "Keep the metadata-scoped document lane, ask only for the remaining business scope, then fetch scoped document rows and probe coverage without pretending there is a grounded counterparty.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_metadata_scoped_document_from_data_need_graph",
|
||||
chainSummary: "Keep the metadata-scoped document lane, ask only for the remaining business scope, then fetch scoped document rows and probe coverage without pretending there is a grounded counterparty.",
|
||||
semanticDataNeed: template.semantic_data_need,
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("document_evidence");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["resolve_entity_reference", "query_documents", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "document evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "document_evidence",
|
||||
chainSummary: "Resolve the business entity, fetch scoped document rows, and probe coverage before stating the checked document evidence.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_document_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (graphFactFamily === "entity_grounding" || (!graphFactFamily && (dataNeedGraph?.subject_candidates.length ?? 0) > 0)) {
|
||||
pushUnique(axes, "business_entity");
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)("entity_resolution");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["search_business_entity", "resolve_entity_reference", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "entity discovery evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "entity_resolution",
|
||||
chainSummary: "Search candidate business entities, resolve the most relevant 1C reference, and prove whether the entity grounding is stable enough for the next probe.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: graphAction === "search_business_entity"
|
||||
? "planner_selected_entity_resolution_from_data_need_graph"
|
||||
: "planner_selected_entity_resolution_recipe",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (includesAny(combined, ["metadata_lane_choice_clarification", "resolve_next_lane"])) {
|
||||
pushUnique(axes, "lane_family_choice");
|
||||
|
|
@ -621,83 +637,64 @@ function recipeFor(input) {
|
|||
if (requestedAggregationAxis === "month") {
|
||||
pushUnique(axes, "calendar_month");
|
||||
}
|
||||
return {
|
||||
semanticDataNeed: "counterparty value-flow evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "value_flow",
|
||||
chainSummary: "Resolve the business entity, query scoped movements, aggregate checked amounts, then probe coverage before answering.",
|
||||
primitives: ["resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage"],
|
||||
axes,
|
||||
reason: requestedAggregationAxis === "month"
|
||||
? "planner_selected_monthly_value_flow_recipe"
|
||||
: "planner_selected_value_flow_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
if (includesAny(combined, ["lifecycle", "activity", "duration", "age"])) {
|
||||
pushUnique(axes, "document_date");
|
||||
pushUnique(axes, "coverage_target");
|
||||
pushUnique(axes, "evidence_basis");
|
||||
return {
|
||||
semanticDataNeed: "counterparty lifecycle evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "lifecycle",
|
||||
chainSummary: "Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window.",
|
||||
primitives: ["resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis"],
|
||||
axes,
|
||||
reason: "planner_selected_lifecycle_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
if (includesAny(combined, ["metadata", "schema", "catalog"])) {
|
||||
pushUnique(axes, "metadata_scope");
|
||||
return {
|
||||
semanticDataNeed: "1C metadata evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "metadata_inspection",
|
||||
chainSummary: "Inspect the 1C metadata surface first, then ground the next safe lane from confirmed schema evidence.",
|
||||
primitives: ["inspect_1c_metadata"],
|
||||
axes,
|
||||
reason: "planner_selected_metadata_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
if (includesAny(combined, ["movement", "movements", "bank_operations", "movement_evidence", "list_movements"])) {
|
||||
pushUnique(axes, "coverage_target");
|
||||
return {
|
||||
semanticDataNeed: "movement evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "movement_evidence",
|
||||
chainSummary: "Resolve the business entity, fetch scoped movement rows, and probe coverage without pretending to have a full movement universe.",
|
||||
primitives: ["resolve_entity_reference", "query_movements", "probe_coverage"],
|
||||
axes,
|
||||
reason: "planner_selected_movement_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
if (includesAny(combined, ["document", "documents"])) {
|
||||
pushUnique(axes, "coverage_target");
|
||||
return {
|
||||
semanticDataNeed: "document evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "document_evidence",
|
||||
chainSummary: "Resolve the business entity, fetch scoped document rows, and probe coverage before stating the checked document evidence.",
|
||||
primitives: ["resolve_entity_reference", "query_documents", "probe_coverage"],
|
||||
axes,
|
||||
reason: "planner_selected_document_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
if (hasEntity(meaning)) {
|
||||
pushUnique(axes, "business_entity");
|
||||
pushUnique(axes, "coverage_target");
|
||||
return {
|
||||
semanticDataNeed: "entity discovery evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "entity_resolution",
|
||||
chainSummary: "Search candidate business entities, resolve the most relevant 1C reference, and prove whether the entity grounding is stable enough for the next probe.",
|
||||
primitives: ["search_business_entity", "resolve_entity_reference", "probe_coverage"],
|
||||
axes,
|
||||
reason: "planner_selected_entity_resolution_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
return {
|
||||
semanticDataNeed: "unclassified 1C discovery need",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "metadata_inspection",
|
||||
semanticDataNeed: "unclassified 1C discovery need",
|
||||
chainSummary: "Start with metadata inspection instead of guessing a deeper fact route when the business need is still under-specified.",
|
||||
primitives: ["inspect_1c_metadata"],
|
||||
axes,
|
||||
reason: "planner_selected_clarification_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
function statusFrom(plan, review) {
|
||||
if (plan.plan_status === "blocked" || review.review_status === "catalog_blocked") {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,16 @@ export const ASSISTANT_MCP_CATALOG_PLAN_REVIEW_SCHEMA_VERSION = "assistant_mcp_c
|
|||
|
||||
export type AssistantMcpCatalogEvidenceFloor = "none" | "rows_received" | "rows_matched" | "source_summary";
|
||||
export type AssistantMcpCatalogPlanReviewStatus = "catalog_compatible" | "needs_more_axes" | "catalog_blocked";
|
||||
export type AssistantMcpCatalogChainTemplateId =
|
||||
| "metadata_inspection"
|
||||
| "catalog_drilldown"
|
||||
| "value_flow"
|
||||
| "value_flow_comparison"
|
||||
| "value_flow_ranking"
|
||||
| "lifecycle"
|
||||
| "movement_evidence"
|
||||
| "document_evidence"
|
||||
| "entity_resolution";
|
||||
|
||||
export interface AssistantMcpCatalogPrimitiveContract {
|
||||
primitive_id: AssistantMcpDiscoveryPrimitive;
|
||||
|
|
@ -29,9 +39,23 @@ export interface AssistantMcpCatalogIndexContract {
|
|||
schema_version: typeof ASSISTANT_MCP_CATALOG_INDEX_SCHEMA_VERSION;
|
||||
policy_owner: "assistantMcpCatalogIndex";
|
||||
primitives: AssistantMcpCatalogPrimitiveContract[];
|
||||
chain_templates: AssistantMcpCatalogChainTemplateContract[];
|
||||
reason_codes: string[];
|
||||
}
|
||||
|
||||
export interface AssistantMcpCatalogChainTemplateContract {
|
||||
chain_id: AssistantMcpCatalogChainTemplateId;
|
||||
semantic_data_need: string;
|
||||
chain_summary: string;
|
||||
fallback_primitives: AssistantMcpDiscoveryPrimitive[];
|
||||
base_required_axes: string[];
|
||||
supported_fact_families: string[];
|
||||
supported_action_families: string[];
|
||||
planning_tags: string[];
|
||||
safe_for_model_planning: true;
|
||||
requires_evidence_gate: true;
|
||||
}
|
||||
|
||||
export interface AssistantMcpCatalogPlanReview {
|
||||
schema_version: typeof ASSISTANT_MCP_CATALOG_PLAN_REVIEW_SCHEMA_VERSION;
|
||||
policy_owner: "assistantMcpCatalogIndex";
|
||||
|
|
@ -220,6 +244,127 @@ const PRIMITIVE_CONTRACT_MAP = new Map<AssistantMcpDiscoveryPrimitive, Assistant
|
|||
PRIMITIVE_CONTRACTS.map((contract) => [contract.primitive_id, contract])
|
||||
);
|
||||
|
||||
const CHAIN_TEMPLATES: AssistantMcpCatalogChainTemplateContract[] = [
|
||||
{
|
||||
chain_id: "metadata_inspection",
|
||||
semantic_data_need: "1C metadata evidence",
|
||||
chain_summary: "Inspect the 1C metadata surface first, then ground the next safe lane from confirmed schema evidence.",
|
||||
fallback_primitives: ["inspect_1c_metadata"],
|
||||
base_required_axes: ["metadata_scope"],
|
||||
supported_fact_families: ["schema_surface"],
|
||||
supported_action_families: ["inspect_catalog", "inspect_documents", "inspect_registers", "inspect_fields", "inspect_surface"],
|
||||
planning_tags: ["metadata", "surface_inspection"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "catalog_drilldown",
|
||||
semantic_data_need: "catalog drilldown metadata evidence",
|
||||
chain_summary:
|
||||
"Drill deeper into the confirmed catalog-oriented metadata surface, inspect related metadata objects, and keep the next safe lane grounded in checked schema evidence.",
|
||||
fallback_primitives: ["inspect_1c_metadata"],
|
||||
base_required_axes: ["metadata_scope"],
|
||||
supported_fact_families: ["schema_surface"],
|
||||
supported_action_families: ["inspect_catalog", "inspect_surface"],
|
||||
planning_tags: ["metadata", "surface_inspection", "drilldown"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "entity_resolution",
|
||||
semantic_data_need: "entity discovery evidence",
|
||||
chain_summary:
|
||||
"Search candidate business entities, resolve the most relevant 1C reference, and prove whether the entity grounding is stable enough for the next probe.",
|
||||
fallback_primitives: ["search_business_entity", "resolve_entity_reference", "probe_coverage"],
|
||||
base_required_axes: ["business_entity", "coverage_target"],
|
||||
supported_fact_families: ["entity_grounding"],
|
||||
supported_action_families: ["search_business_entity"],
|
||||
planning_tags: ["subject_resolution", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "document_evidence",
|
||||
semantic_data_need: "document evidence",
|
||||
chain_summary: "Resolve the business entity, fetch scoped document rows, and probe coverage before stating the checked document evidence.",
|
||||
fallback_primitives: ["resolve_entity_reference", "query_documents", "probe_coverage"],
|
||||
base_required_axes: ["coverage_target"],
|
||||
supported_fact_families: ["document_evidence", "activity_lifecycle"],
|
||||
supported_action_families: ["list_documents", "activity_duration"],
|
||||
planning_tags: ["document", "subject_resolution", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "movement_evidence",
|
||||
semantic_data_need: "movement evidence",
|
||||
chain_summary:
|
||||
"Resolve the business entity, fetch scoped movement rows, and probe coverage without pretending to have a full movement universe.",
|
||||
fallback_primitives: ["resolve_entity_reference", "query_movements", "probe_coverage"],
|
||||
base_required_axes: ["coverage_target"],
|
||||
supported_fact_families: ["movement_evidence", "value_flow"],
|
||||
supported_action_families: ["list_movements", "turnover", "payout", "net_value_flow"],
|
||||
planning_tags: ["movement", "subject_resolution", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "value_flow",
|
||||
semantic_data_need: "counterparty value-flow evidence",
|
||||
chain_summary: "Resolve the business entity, query scoped movements, aggregate checked amounts, then probe coverage before answering.",
|
||||
fallback_primitives: ["resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage"],
|
||||
base_required_axes: ["aggregate_axis", "amount", "coverage_target"],
|
||||
supported_fact_families: ["value_flow"],
|
||||
supported_action_families: ["turnover", "payout", "net_value_flow"],
|
||||
planning_tags: ["movement", "aggregation", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "value_flow_comparison",
|
||||
semantic_data_need: "bidirectional value-flow comparison evidence",
|
||||
chain_summary:
|
||||
"Query incoming and outgoing movements for the checked period and organization, compare the checked sides, and probe coverage before answering a bounded comparison.",
|
||||
fallback_primitives: ["query_movements", "probe_coverage"],
|
||||
base_required_axes: ["amount", "coverage_target"],
|
||||
supported_fact_families: ["value_flow"],
|
||||
supported_action_families: ["net_value_flow"],
|
||||
planning_tags: ["movement", "comparison", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "value_flow_ranking",
|
||||
semantic_data_need: "ranked value-flow evidence",
|
||||
chain_summary:
|
||||
"Query scoped movements for the checked period and organization, aggregate checked amounts by counterparty, then probe coverage before answering a bounded ranking.",
|
||||
fallback_primitives: ["query_movements", "aggregate_by_axis", "probe_coverage"],
|
||||
base_required_axes: ["aggregate_axis", "amount", "coverage_target"],
|
||||
supported_fact_families: ["value_flow"],
|
||||
supported_action_families: ["turnover", "payout"],
|
||||
planning_tags: ["movement", "ranking", "aggregation", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
},
|
||||
{
|
||||
chain_id: "lifecycle",
|
||||
semantic_data_need: "counterparty lifecycle evidence",
|
||||
chain_summary:
|
||||
"Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window.",
|
||||
fallback_primitives: ["resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis"],
|
||||
base_required_axes: ["document_date", "coverage_target", "evidence_basis"],
|
||||
supported_fact_families: ["activity_lifecycle"],
|
||||
supported_action_families: ["activity_duration"],
|
||||
planning_tags: ["document", "explanation", "coverage"],
|
||||
safe_for_model_planning: true,
|
||||
requires_evidence_gate: true
|
||||
}
|
||||
];
|
||||
|
||||
const CHAIN_TEMPLATE_MAP = new Map<AssistantMcpCatalogChainTemplateId, AssistantMcpCatalogChainTemplateContract>(
|
||||
CHAIN_TEMPLATES.map((template) => [template.chain_id, template])
|
||||
);
|
||||
|
||||
function toStringSet(values: string[]): Set<string> {
|
||||
return new Set(values.map((item) => item.trim()).filter((item) => item.length > 0));
|
||||
}
|
||||
|
|
@ -626,10 +771,19 @@ export function buildAssistantMcpCatalogIndex(): AssistantMcpCatalogIndexContrac
|
|||
} else {
|
||||
pushReason(reasonCodes, "catalog_covers_all_discovery_primitives");
|
||||
}
|
||||
const unknownChainPrimitives = CHAIN_TEMPLATES.flatMap((template) =>
|
||||
template.fallback_primitives.filter((primitive) => !PRIMITIVE_CONTRACT_MAP.has(primitive))
|
||||
);
|
||||
if (unknownChainPrimitives.length > 0) {
|
||||
pushReason(reasonCodes, "catalog_chain_template_references_unknown_primitive");
|
||||
} else {
|
||||
pushReason(reasonCodes, "catalog_chain_templates_reference_reviewed_primitives");
|
||||
}
|
||||
return {
|
||||
schema_version: ASSISTANT_MCP_CATALOG_INDEX_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpCatalogIndex",
|
||||
primitives: PRIMITIVE_CONTRACTS,
|
||||
chain_templates: CHAIN_TEMPLATES,
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
}
|
||||
|
|
@ -644,6 +798,16 @@ export function getAssistantMcpCatalogPrimitive(
|
|||
return contract;
|
||||
}
|
||||
|
||||
export function getAssistantMcpCatalogChainTemplate(
|
||||
chainId: AssistantMcpCatalogChainTemplateId
|
||||
): AssistantMcpCatalogChainTemplateContract {
|
||||
const template = CHAIN_TEMPLATE_MAP.get(chainId);
|
||||
if (!template) {
|
||||
throw new Error(`Missing MCP catalog chain template: ${chainId}`);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
export function reviewAssistantMcpDiscoveryPlanAgainstCatalog(
|
||||
plan: AssistantMcpDiscoveryPlanContract
|
||||
): AssistantMcpCatalogPlanReview {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ import {
|
|||
type AssistantMcpDiscoveryTurnMeaningRef
|
||||
} from "./assistantMcpDiscoveryPolicy";
|
||||
import {
|
||||
getAssistantMcpCatalogChainTemplate,
|
||||
searchAssistantMcpCatalogPrimitivesByDecompositionCandidates,
|
||||
searchAssistantMcpCatalogPrimitivesByFactAxis,
|
||||
searchAssistantMcpCatalogPrimitivesByMetadataSurface,
|
||||
reviewAssistantMcpDiscoveryPlanAgainstCatalog,
|
||||
type AssistantMcpCatalogChainTemplateId,
|
||||
type AssistantMcpCatalogPlanReview
|
||||
} from "./assistantMcpCatalogIndex";
|
||||
import type { AssistantMcpDiscoveryDataNeedGraphContract } from "./assistantMcpDiscoveryDataNeedGraph";
|
||||
|
|
@ -119,6 +121,38 @@ function pushUnique(target: string[], value: string): void {
|
|||
}
|
||||
}
|
||||
|
||||
function pushAllUnique(target: string[], values: string[]): void {
|
||||
for (const value of values) {
|
||||
pushUnique(target, value);
|
||||
}
|
||||
}
|
||||
|
||||
function recipeFromCatalogChainTemplate(input: {
|
||||
chainId: AssistantMcpCatalogChainTemplateId;
|
||||
axes: string[];
|
||||
primitives?: AssistantMcpDiscoveryPrimitive[];
|
||||
reason: string;
|
||||
extraReasons?: string[];
|
||||
chainSummary?: string;
|
||||
semanticDataNeed?: string;
|
||||
}): PlannerRecipe {
|
||||
const template = getAssistantMcpCatalogChainTemplate(input.chainId);
|
||||
const axes = [...input.axes];
|
||||
pushAllUnique(axes, template.base_required_axes);
|
||||
return {
|
||||
semanticDataNeed: input.semanticDataNeed ?? template.semantic_data_need,
|
||||
chainId: template.chain_id,
|
||||
chainSummary: input.chainSummary ?? template.chain_summary,
|
||||
primitives: input.primitives ?? template.fallback_primitives,
|
||||
axes,
|
||||
reason: input.reason,
|
||||
extraReasons: [
|
||||
`planner_instantiated_catalog_chain_template_${template.chain_id}`,
|
||||
...(input.extraReasons ?? [])
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function hasEntity(meaning: AssistantMcpDiscoveryTurnMeaningRef | null | undefined): boolean {
|
||||
return (meaning?.explicit_entity_candidates?.length ?? 0) > 0;
|
||||
}
|
||||
|
|
@ -460,63 +494,63 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
const thinSurfaceRouteFamily = routeFamilyFromThinMetadataSurfaceInput(input);
|
||||
if (thinSurfaceRouteFamily === "document_evidence") {
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = getAssistantMcpCatalogChainTemplate("document_evidence");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["resolve_entity_reference", "query_documents", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "document evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "document_evidence",
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_document_from_confirmed_metadata_surface_ref",
|
||||
chainSummary:
|
||||
"Ground the next checked document lane from the confirmed metadata surface, then fetch scoped document rows and probe coverage before answering.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
reason: "planner_selected_document_from_confirmed_metadata_surface_ref",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (thinSurfaceRouteFamily === "movement_evidence") {
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = getAssistantMcpCatalogChainTemplate("movement_evidence");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["resolve_entity_reference", "query_movements", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "movement evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "movement_evidence",
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_movement_from_confirmed_metadata_surface_ref",
|
||||
chainSummary:
|
||||
"Ground the next checked movement lane from the confirmed metadata surface, then fetch scoped movement rows and probe coverage before answering.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
reason: "planner_selected_movement_from_confirmed_metadata_surface_ref",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (thinSurfaceRouteFamily === "catalog_drilldown") {
|
||||
pushUnique(axes, "metadata_scope");
|
||||
const template = getAssistantMcpCatalogChainTemplate("catalog_drilldown");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["inspect_1c_metadata"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "catalog drilldown metadata evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "catalog_drilldown",
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_catalog_drilldown_from_confirmed_metadata_surface_ref",
|
||||
chainSummary:
|
||||
"Drill deeper into the confirmed catalog-oriented metadata surface, inspect related metadata objects, and keep the next safe lane grounded in checked schema evidence.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
reason: "planner_selected_catalog_drilldown_from_confirmed_metadata_surface_ref",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (graphFactFamily === "value_flow") {
|
||||
|
|
@ -526,50 +560,46 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
if (requestedAggregationAxis === "month" || graphAggregation === "by_month") {
|
||||
pushUnique(axes, "calendar_month");
|
||||
}
|
||||
const template = getAssistantMcpCatalogChainTemplate("value_flow_comparison");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["query_movements", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action,
|
||||
allowAggregateByAxis: false
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "bidirectional value-flow comparison evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "value_flow_comparison",
|
||||
chainSummary:
|
||||
"Query incoming and outgoing movements for the checked period and organization, compare the checked sides, and probe coverage before answering a bounded comparison.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_bidirectional_value_flow_comparison_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (dataNeedGraph?.ranking_need && !hasSubjectCandidates(dataNeedGraph)) {
|
||||
pushUnique(axes, "aggregate_axis");
|
||||
pushUnique(axes, "amount");
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = getAssistantMcpCatalogChainTemplate("value_flow_ranking");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["query_movements", "aggregate_by_axis", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action,
|
||||
allowAggregateByAxis: true
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "ranked value-flow evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "value_flow_ranking",
|
||||
chainSummary:
|
||||
"Query scoped movements for the checked period and organization, aggregate checked amounts by counterparty, then probe coverage before answering a bounded ranking.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason:
|
||||
dataNeedGraph.ranking_need === "bottom_asc"
|
||||
? "planner_selected_bottom_ranked_value_flow_from_data_need_graph"
|
||||
: "planner_selected_top_ranked_value_flow_from_data_need_graph",
|
||||
: "planner_selected_top_ranked_value_flow_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
if (openScopeTotalWithoutSubject) {
|
||||
pushUnique(axes, "organization");
|
||||
|
|
@ -586,19 +616,19 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
actionFamily: action,
|
||||
allowAggregateByAxis: true
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "organization-scoped value-flow evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "value_flow",
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
semanticDataNeed: "organization-scoped value-flow evidence",
|
||||
chainSummary:
|
||||
"Query scoped movements for the checked period and organization without a preselected counterparty, aggregate checked amounts, then probe coverage before answering a bounded total.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
reason:
|
||||
requestedAggregationAxis === "month" || graphAggregation === "by_month"
|
||||
? "planner_selected_monthly_open_scope_value_flow_total_from_data_need_graph"
|
||||
: "planner_selected_open_scope_value_flow_total_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
pushUnique(axes, "aggregate_axis");
|
||||
pushUnique(axes, "amount");
|
||||
|
|
@ -606,74 +636,72 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
if (requestedAggregationAxis === "month" || graphAggregation === "by_month") {
|
||||
pushUnique(axes, "calendar_month");
|
||||
}
|
||||
const template = getAssistantMcpCatalogChainTemplate("value_flow");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action,
|
||||
allowAggregateByAxis: true
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "counterparty value-flow evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "value_flow",
|
||||
chainSummary: "Resolve the business entity, query scoped movements, aggregate checked amounts, then probe coverage before answering.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason:
|
||||
requestedAggregationAxis === "month" || graphAggregation === "by_month"
|
||||
? "planner_selected_monthly_value_flow_from_data_need_graph"
|
||||
: "planner_selected_value_flow_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (graphFactFamily === "activity_lifecycle") {
|
||||
pushUnique(axes, "document_date");
|
||||
pushUnique(axes, "coverage_target");
|
||||
pushUnique(axes, "evidence_basis");
|
||||
const template = getAssistantMcpCatalogChainTemplate("lifecycle");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "counterparty lifecycle evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "lifecycle",
|
||||
chainSummary: "Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_lifecycle_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (graphFactFamily === "schema_surface") {
|
||||
pushUnique(axes, "metadata_scope");
|
||||
const template = getAssistantMcpCatalogChainTemplate("metadata_inspection");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["inspect_1c_metadata"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "1C metadata evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "metadata_inspection",
|
||||
chainSummary: "Inspect the 1C metadata surface first, then ground the next safe lane from confirmed schema evidence.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_metadata_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (graphFactFamily === "movement_evidence") {
|
||||
if (metadataScopedOpenLane) {
|
||||
pushUnique(axes, "organization");
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = getAssistantMcpCatalogChainTemplate("movement_evidence");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["query_movements", "probe_coverage"],
|
||||
|
|
@ -681,40 +709,40 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "movement evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "movement_evidence",
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_metadata_scoped_movement_from_data_need_graph",
|
||||
chainSummary:
|
||||
"Keep the metadata-scoped movement lane, ask only for the remaining business scope, then fetch scoped movement rows and probe coverage without pretending there is a grounded counterparty.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
reason: "planner_selected_metadata_scoped_movement_from_data_need_graph",
|
||||
semanticDataNeed: template.semantic_data_need,
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = getAssistantMcpCatalogChainTemplate("movement_evidence");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["resolve_entity_reference", "query_movements", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "movement evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "movement_evidence",
|
||||
chainSummary: "Resolve the business entity, fetch scoped movement rows, and probe coverage without pretending to have a full movement universe.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_movement_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (graphFactFamily === "document_evidence") {
|
||||
if (metadataScopedOpenLane) {
|
||||
pushUnique(axes, "organization");
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = getAssistantMcpCatalogChainTemplate("document_evidence");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["query_documents", "probe_coverage"],
|
||||
|
|
@ -722,58 +750,56 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "document evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "document_evidence",
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_metadata_scoped_document_from_data_need_graph",
|
||||
chainSummary:
|
||||
"Keep the metadata-scoped document lane, ask only for the remaining business scope, then fetch scoped document rows and probe coverage without pretending there is a grounded counterparty.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
reason: "planner_selected_metadata_scoped_document_from_data_need_graph",
|
||||
semanticDataNeed: template.semantic_data_need,
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = getAssistantMcpCatalogChainTemplate("document_evidence");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["resolve_entity_reference", "query_documents", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "document evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "document_evidence",
|
||||
chainSummary: "Resolve the business entity, fetch scoped document rows, and probe coverage before stating the checked document evidence.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason: "planner_selected_document_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (graphFactFamily === "entity_grounding" || (!graphFactFamily && (dataNeedGraph?.subject_candidates.length ?? 0) > 0)) {
|
||||
pushUnique(axes, "business_entity");
|
||||
pushUnique(axes, "coverage_target");
|
||||
const template = getAssistantMcpCatalogChainTemplate("entity_resolution");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["search_business_entity", "resolve_entity_reference", "probe_coverage"],
|
||||
fallbackPrimitives: template.fallback_primitives,
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "entity discovery evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "entity_resolution",
|
||||
chainSummary: "Search candidate business entities, resolve the most relevant 1C reference, and prove whether the entity grounding is stable enough for the next probe.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
primitives: primitiveSelection.primitives,
|
||||
reason:
|
||||
graphAction === "search_business_entity"
|
||||
? "planner_selected_entity_resolution_from_data_need_graph"
|
||||
: "planner_selected_entity_resolution_recipe",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (includesAny(combined, ["metadata_lane_choice_clarification", "resolve_next_lane"])) {
|
||||
|
|
@ -795,89 +821,70 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
if (requestedAggregationAxis === "month") {
|
||||
pushUnique(axes, "calendar_month");
|
||||
}
|
||||
return {
|
||||
semanticDataNeed: "counterparty value-flow evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "value_flow",
|
||||
chainSummary: "Resolve the business entity, query scoped movements, aggregate checked amounts, then probe coverage before answering.",
|
||||
primitives: ["resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage"],
|
||||
axes,
|
||||
reason: requestedAggregationAxis === "month"
|
||||
? "planner_selected_monthly_value_flow_recipe"
|
||||
: "planner_selected_value_flow_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (includesAny(combined, ["lifecycle", "activity", "duration", "age"])) {
|
||||
pushUnique(axes, "document_date");
|
||||
pushUnique(axes, "coverage_target");
|
||||
pushUnique(axes, "evidence_basis");
|
||||
return {
|
||||
semanticDataNeed: "counterparty lifecycle evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "lifecycle",
|
||||
chainSummary: "Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window.",
|
||||
primitives: ["resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis"],
|
||||
axes,
|
||||
reason: "planner_selected_lifecycle_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (includesAny(combined, ["metadata", "schema", "catalog"])) {
|
||||
pushUnique(axes, "metadata_scope");
|
||||
return {
|
||||
semanticDataNeed: "1C metadata evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "metadata_inspection",
|
||||
chainSummary: "Inspect the 1C metadata surface first, then ground the next safe lane from confirmed schema evidence.",
|
||||
primitives: ["inspect_1c_metadata"],
|
||||
axes,
|
||||
reason: "planner_selected_metadata_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (includesAny(combined, ["movement", "movements", "bank_operations", "movement_evidence", "list_movements"])) {
|
||||
pushUnique(axes, "coverage_target");
|
||||
return {
|
||||
semanticDataNeed: "movement evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "movement_evidence",
|
||||
chainSummary: "Resolve the business entity, fetch scoped movement rows, and probe coverage without pretending to have a full movement universe.",
|
||||
primitives: ["resolve_entity_reference", "query_movements", "probe_coverage"],
|
||||
axes,
|
||||
reason: "planner_selected_movement_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (includesAny(combined, ["document", "documents"])) {
|
||||
pushUnique(axes, "coverage_target");
|
||||
return {
|
||||
semanticDataNeed: "document evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "document_evidence",
|
||||
chainSummary: "Resolve the business entity, fetch scoped document rows, and probe coverage before stating the checked document evidence.",
|
||||
primitives: ["resolve_entity_reference", "query_documents", "probe_coverage"],
|
||||
axes,
|
||||
reason: "planner_selected_document_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (hasEntity(meaning)) {
|
||||
pushUnique(axes, "business_entity");
|
||||
pushUnique(axes, "coverage_target");
|
||||
return {
|
||||
semanticDataNeed: "entity discovery evidence",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "entity_resolution",
|
||||
chainSummary: "Search candidate business entities, resolve the most relevant 1C reference, and prove whether the entity grounding is stable enough for the next probe.",
|
||||
primitives: ["search_business_entity", "resolve_entity_reference", "probe_coverage"],
|
||||
axes,
|
||||
reason: "planner_selected_entity_resolution_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
semanticDataNeed: "unclassified 1C discovery need",
|
||||
return recipeFromCatalogChainTemplate({
|
||||
chainId: "metadata_inspection",
|
||||
semanticDataNeed: "unclassified 1C discovery need",
|
||||
chainSummary: "Start with metadata inspection instead of guessing a deeper fact route when the business need is still under-specified.",
|
||||
primitives: ["inspect_1c_metadata"],
|
||||
axes,
|
||||
reason: "planner_selected_clarification_recipe"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function statusFrom(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
|||
import { ASSISTANT_MCP_DISCOVERY_PRIMITIVES, buildAssistantMcpDiscoveryPlan } from "../src/services/assistantMcpDiscoveryPolicy";
|
||||
import {
|
||||
buildAssistantMcpCatalogIndex,
|
||||
getAssistantMcpCatalogChainTemplate,
|
||||
getAssistantMcpCatalogPrimitive,
|
||||
reviewAssistantMcpDiscoveryPlanAgainstCatalog,
|
||||
searchAssistantMcpCatalogPrimitivesByDecompositionCandidates,
|
||||
|
|
@ -15,6 +16,7 @@ describe("assistant MCP catalog index", () => {
|
|||
const primitiveIds = index.primitives.map((entry) => entry.primitive_id);
|
||||
|
||||
expect(index.reason_codes).toContain("catalog_covers_all_discovery_primitives");
|
||||
expect(index.reason_codes).toContain("catalog_chain_templates_reference_reviewed_primitives");
|
||||
expect(primitiveIds).toEqual([...ASSISTANT_MCP_DISCOVERY_PRIMITIVES]);
|
||||
for (const entry of index.primitives) {
|
||||
expect(entry.safe_for_model_planning).toBe(true);
|
||||
|
|
@ -26,6 +28,53 @@ describe("assistant MCP catalog index", () => {
|
|||
expect(entry.required_axes_any_of.length).toBeGreaterThan(0);
|
||||
expect(entry.output_fact_kinds.length).toBeGreaterThan(0);
|
||||
}
|
||||
expect(index.chain_templates.map((entry) => entry.chain_id)).toEqual([
|
||||
"metadata_inspection",
|
||||
"catalog_drilldown",
|
||||
"entity_resolution",
|
||||
"document_evidence",
|
||||
"movement_evidence",
|
||||
"value_flow",
|
||||
"value_flow_comparison",
|
||||
"value_flow_ranking",
|
||||
"lifecycle"
|
||||
]);
|
||||
for (const template of index.chain_templates) {
|
||||
expect(template.safe_for_model_planning).toBe(true);
|
||||
expect(template.requires_evidence_gate).toBe(true);
|
||||
expect(template.semantic_data_need.length).toBeGreaterThan(0);
|
||||
expect(template.chain_summary.length).toBeGreaterThan(0);
|
||||
expect(template.fallback_primitives.length).toBeGreaterThan(0);
|
||||
for (const primitive of template.fallback_primitives) {
|
||||
expect(primitiveIds).toContain(primitive);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("exposes reusable chain templates for planner route-fabric selection", () => {
|
||||
const documentTemplate = getAssistantMcpCatalogChainTemplate("document_evidence");
|
||||
const movementTemplate = getAssistantMcpCatalogChainTemplate("movement_evidence");
|
||||
const valueFlowTemplate = getAssistantMcpCatalogChainTemplate("value_flow");
|
||||
const valueFlowComparisonTemplate = getAssistantMcpCatalogChainTemplate("value_flow_comparison");
|
||||
const valueFlowRankingTemplate = getAssistantMcpCatalogChainTemplate("value_flow_ranking");
|
||||
|
||||
expect(documentTemplate.fallback_primitives).toEqual([
|
||||
"resolve_entity_reference",
|
||||
"query_documents",
|
||||
"probe_coverage"
|
||||
]);
|
||||
expect(movementTemplate.fallback_primitives).toEqual([
|
||||
"resolve_entity_reference",
|
||||
"query_movements",
|
||||
"probe_coverage"
|
||||
]);
|
||||
expect(valueFlowTemplate.base_required_axes).toEqual(["aggregate_axis", "amount", "coverage_target"]);
|
||||
expect(valueFlowComparisonTemplate.fallback_primitives).toEqual(["query_movements", "probe_coverage"]);
|
||||
expect(valueFlowRankingTemplate.fallback_primitives).toEqual([
|
||||
"query_movements",
|
||||
"aggregate_by_axis",
|
||||
"probe_coverage"
|
||||
]);
|
||||
});
|
||||
|
||||
it("can search reviewed primitives from data-need decomposition candidates", () => {
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ describe("assistant MCP discovery planner", () => {
|
|||
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).toContain("planner_instantiated_catalog_chain_template_document_evidence");
|
||||
expect(result.reason_codes).not.toContain("planner_fell_back_to_recipe_primitives_after_empty_catalog_search");
|
||||
});
|
||||
|
||||
|
|
@ -253,6 +254,7 @@ describe("assistant MCP discovery planner", () => {
|
|||
expect(result.required_axes).toEqual(["counterparty", "period", "coverage_target"]);
|
||||
expect(result.reason_codes).toContain("planner_selected_movement_from_data_need_graph");
|
||||
expect(result.reason_codes).toContain("planner_selected_catalog_primitives_from_decomposition_candidates");
|
||||
expect(result.reason_codes).toContain("planner_instantiated_catalog_chain_template_movement_evidence");
|
||||
});
|
||||
|
||||
it("filters conflicting document-vs-movement primitives when confirmed metadata surface recommends movements", () => {
|
||||
|
|
@ -412,6 +414,7 @@ describe("assistant MCP discovery planner", () => {
|
|||
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).toContain("planner_instantiated_catalog_chain_template_entity_resolution");
|
||||
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");
|
||||
});
|
||||
|
|
@ -464,6 +467,7 @@ describe("assistant MCP discovery planner", () => {
|
|||
"calendar_month"
|
||||
]);
|
||||
expect(result.reason_codes).toContain("planner_selected_monthly_value_flow_from_data_need_graph");
|
||||
expect(result.reason_codes).toContain("planner_instantiated_catalog_chain_template_value_flow");
|
||||
});
|
||||
|
||||
it("does not collapse a ranking-shaped value graph into entity-resolution just because no subject is preselected", () => {
|
||||
|
|
@ -497,6 +501,7 @@ describe("assistant MCP discovery planner", () => {
|
|||
expect(result.required_axes).toEqual(["period", "aggregate_axis", "amount", "coverage_target"]);
|
||||
expect(result.catalog_review.review_status).toBe("needs_more_axes");
|
||||
expect(result.reason_codes).toContain("planner_selected_top_ranked_value_flow_from_data_need_graph");
|
||||
expect(result.reason_codes).toContain("planner_instantiated_catalog_chain_template_value_flow_ranking");
|
||||
expect(result.selected_chain_id).not.toBe("entity_resolution");
|
||||
});
|
||||
|
||||
|
|
@ -532,6 +537,7 @@ describe("assistant MCP discovery planner", () => {
|
|||
expect(result.required_axes).toEqual(["organization", "period", "aggregate_axis", "amount", "coverage_target"]);
|
||||
expect(result.catalog_review.review_status).toBe("catalog_compatible");
|
||||
expect(result.reason_codes).toContain("planner_selected_top_ranked_value_flow_from_data_need_graph");
|
||||
expect(result.reason_codes).toContain("planner_instantiated_catalog_chain_template_value_flow_ranking");
|
||||
});
|
||||
|
||||
it("does not collapse incoming-vs-outgoing comparison into entity-resolution when no counterparty is preselected", () => {
|
||||
|
|
@ -564,6 +570,7 @@ describe("assistant MCP discovery planner", () => {
|
|||
expect(result.proposed_primitives).toEqual(["query_movements", "probe_coverage"]);
|
||||
expect(result.required_axes).toEqual(["period", "amount", "coverage_target"]);
|
||||
expect(result.reason_codes).toContain("planner_selected_bidirectional_value_flow_comparison_from_data_need_graph");
|
||||
expect(result.reason_codes).toContain("planner_instantiated_catalog_chain_template_value_flow_comparison");
|
||||
expect(result.selected_chain_id).not.toBe("entity_resolution");
|
||||
});
|
||||
|
||||
|
|
@ -599,6 +606,7 @@ describe("assistant MCP discovery planner", () => {
|
|||
expect(result.required_axes).toEqual(["organization", "period", "amount", "coverage_target"]);
|
||||
expect(result.catalog_review.review_status).toBe("catalog_compatible");
|
||||
expect(result.reason_codes).toContain("planner_selected_bidirectional_value_flow_comparison_from_data_need_graph");
|
||||
expect(result.reason_codes).toContain("planner_instantiated_catalog_chain_template_value_flow_comparison");
|
||||
});
|
||||
|
||||
it("builds an inference-safe lifecycle plan with evidence explanation", () => {
|
||||
|
|
@ -740,6 +748,7 @@ describe("assistant MCP discovery planner", () => {
|
|||
expect(result.required_axes).toEqual(["organization", "period", "aggregate_axis", "amount", "coverage_target"]);
|
||||
expect(result.catalog_review.review_status).toBe("catalog_compatible");
|
||||
expect(result.reason_codes).toContain("planner_selected_open_scope_value_flow_total_from_data_need_graph");
|
||||
expect(result.reason_codes).toContain("planner_instantiated_catalog_chain_template_value_flow");
|
||||
});
|
||||
it("keeps generic one-sided open totals in organization clarification instead of forcing entity resolution", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
|
|
@ -777,6 +786,7 @@ describe("assistant MCP discovery planner", () => {
|
|||
expect(result.catalog_review.review_status).toBe("needs_more_axes");
|
||||
expect(result.catalog_review.missing_axes_by_primitive.query_movements).toContainEqual(["organization"]);
|
||||
expect(result.reason_codes).toContain("planner_selected_open_scope_value_flow_total_from_data_need_graph");
|
||||
expect(result.reason_codes).toContain("planner_instantiated_catalog_chain_template_value_flow");
|
||||
expect(result.selected_chain_id).not.toBe("entity_resolution");
|
||||
});
|
||||
|
||||
|
|
@ -822,6 +832,7 @@ describe("assistant MCP discovery planner", () => {
|
|||
]);
|
||||
expect(result.catalog_review.review_status).toBe("catalog_compatible");
|
||||
expect(result.reason_codes).toContain("planner_selected_open_scope_value_flow_total_from_data_need_graph");
|
||||
expect(result.reason_codes).toContain("planner_instantiated_catalog_chain_template_value_flow");
|
||||
});
|
||||
|
||||
it("keeps metadata-scoped movement evidence in clarification instead of forcing entity resolution", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue