diff --git a/docs/ARCH/11 - architecture_turnaround/20 - planner_autonomy_consolidation_2026-05-01.md b/docs/ARCH/11 - architecture_turnaround/20 - planner_autonomy_consolidation_2026-05-01.md index 99d6faf..579a919 100644 --- a/docs/ARCH/11 - architecture_turnaround/20 - planner_autonomy_consolidation_2026-05-01.md +++ b/docs/ARCH/11 - architecture_turnaround/20 - planner_autonomy_consolidation_2026-05-01.md @@ -110,6 +110,14 @@ The next local scoring step broadened metadata-surface autonomy without adding a - inferred catalog surfaces instantiate `catalog_drilldown`; - mixed or ambiguous surfaces still do not guess and continue through clarification / explicit data-need scoring. +The following consolidation step added catalog-level chain-template scoring: + +- `assistantMcpCatalogIndex` can now score reviewed `chain_templates` directly from fact family, action family, required axes, comparison, ranking, and aggregation needs; +- comparison-shaped value-flow ranks `value_flow_comparison` above the generic value-flow template; +- ranking-shaped value-flow ranks `value_flow_ranking` above the generic value-flow template; +- document/movement/inventory/lifecycle templates can now be inspected as catalog search results, not only as local planner branch constants; +- `assistantMcpDiscoveryPlanner` now records the top catalog chain-template match in reason codes while preserving existing guarded execution behavior. + ## Why This Matters This reduces the pressure to add one hard route per user wording. @@ -183,6 +191,13 @@ Latest validation after unambiguous metadata-surface lane inference: - live inventory full-pack attempt: `inventory_stock_exact_bridge_live_20260501_after_runtime_bridge`, status `partial` - live attempt interpretation: route/intent/recipe/capability selection matched, but MCP execution failed with `MCP fetch failed: This operation was aborted`; direct proxy `get_metadata` also timed out while `/health` reported `active_sessions_count=0` and pending commands, so this is an infrastructure/polling-session blocker rather than accepted semantic evidence. +Latest validation after catalog chain-template scoring: + +- targeted catalog/planner tests: passed, `54 passed` +- full MCP-discovery suite: passed, `282 passed`, `9 skipped` +- `npm.cmd run build`: passed +- graphify rebuild: `5938 nodes`, `12903 edges`, `139 communities` + ## Next Step The next safe step is to re-run live replay once the 1C side is actively polling the proxy, then continue into broader reviewed scoring. diff --git a/docs/ARCH/11 - architecture_turnaround/README.md b/docs/ARCH/11 - architecture_turnaround/README.md index 5552952..90875e0 100644 --- a/docs/ARCH/11 - architecture_turnaround/README.md +++ b/docs/ARCH/11 - architecture_turnaround/README.md @@ -80,6 +80,7 @@ It now documents a turnaround that is already operational in code, already mater - runtime bridge and answer adapter now keep unsupported inventory route templates behind an explicit user-facing boundary instead of letting template planning look like confirmed stock/supplier/purchase/sale evidence; - inventory catalog templates now bridge through existing exact inventory recipes (`41.01` scoped stock, supplier overlap, purchase provenance, and sale trace) inside the bounded MCP discovery pilot, while missing selected-item anchors still clarify instead of guessing; - unambiguous metadata surfaces can now infer the next reviewed lane from `Document.*`, `Register.*`, or `Catalog.*` objects even before upstream labels `downstream_route_family`, while mixed surfaces still do not guess; + - catalog index now scores reviewed chain templates directly from fact/action/axis/comparison/ranking needs, and planner exposes the top catalog chain match in reason codes; - live map sync: [20 - planner_autonomy_consolidation_2026-05-01.md](./20%20-%20planner_autonomy_consolidation_2026-05-01.md) Current honest status: @@ -91,8 +92,8 @@ Current honest status: - open-world bounded-autonomy readiness: `~85%` - Post-F semantic integrity module progress: `~99%` operationally closed, with remaining risk now treated as next-slice discovery rather than an open blocker inside the closed slice - active inventory-stock breadth slice progress: `100%` for the declared scenario pack, not for arbitrary inventory questions -- Planner Autonomy Consolidation progress: `~78%` for the declared module, with catalog-fabric, value-flow arbitration, lifecycle bounded inference, broad-evaluation bridge, inventory catalog templates, inventory runtime-boundary honesty, exact inventory recipe bridging, and unambiguous metadata-surface lane inference validated locally, but live replay for the new bridge is currently blocked by missing active 1C polling and broader unfamiliar 1C asks still need replay-backed growth -- graph snapshot after latest rebuild: `5937 nodes`, `12899 edges`, `138 communities` +- Planner Autonomy Consolidation progress: `~80%` for the declared module, with catalog-fabric, value-flow arbitration, lifecycle bounded inference, broad-evaluation bridge, inventory catalog templates, inventory runtime-boundary honesty, exact inventory recipe bridging, unambiguous metadata-surface lane inference, and catalog chain-template scoring validated locally, but live replay for the new bridge is currently blocked by missing active 1C polling and broader unfamiliar 1C asks still need replay-backed growth +- graph snapshot after latest rebuild: `5938 nodes`, `12903 edges`, `139 communities` - current breakpoint: - the validated hot paths are no longer structurally broken; - flagship continuity collapse is no longer the primary risk; @@ -137,6 +138,7 @@ Latest live proof now includes: - inventory exact-runtime bridge accepted locally: runtime-bridge/answer-adapter/pilot-executor slice passed `70/70` with `1` skipped; full MCP-discovery slice passed `279/279` with `9` skipped; build passed; graphify rebuilt to `5930 nodes`, `12884 edges`, `135 communities` - unambiguous metadata-surface lane inference accepted locally: planner slice passed `36/36`; full MCP-discovery slice passed `281/281` with `9` skipped; build passed; graphify rebuilt to `5937 nodes`, `12899 edges`, `138 communities` - live inventory exact-bridge rerun `inventory_stock_exact_bridge_live_20260501_after_runtime_bridge` is recorded as infrastructure-blocked, not accepted: route/intent/recipe/capability matched, but MCP calls aborted and direct `get_metadata` timed out while proxy health showed `active_sessions_count=0` with pending commands +- catalog chain-template scoring accepted locally: catalog/planner slice passed `54/54`; full MCP-discovery slice passed `282/282` with `9` skipped; build passed; graphify rebuilt to `5938 nodes`, `12903 edges`, `139 communities` Current architectural reading: diff --git a/llm_normalizer/backend/dist/services/assistantMcpCatalogIndex.js b/llm_normalizer/backend/dist/services/assistantMcpCatalogIndex.js index 5fbf303..7725cb0 100644 --- a/llm_normalizer/backend/dist/services/assistantMcpCatalogIndex.js +++ b/llm_normalizer/backend/dist/services/assistantMcpCatalogIndex.js @@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.ASSISTANT_MCP_CATALOG_PLAN_REVIEW_SCHEMA_VERSION = exports.ASSISTANT_MCP_CATALOG_INDEX_SCHEMA_VERSION = void 0; exports.searchAssistantMcpCatalogPrimitivesByDecompositionCandidates = searchAssistantMcpCatalogPrimitivesByDecompositionCandidates; exports.searchAssistantMcpCatalogPrimitivesByFactAxis = searchAssistantMcpCatalogPrimitivesByFactAxis; +exports.searchAssistantMcpCatalogChainTemplatesByFactAxis = searchAssistantMcpCatalogChainTemplatesByFactAxis; exports.searchAssistantMcpCatalogPrimitivesByMetadataSurface = searchAssistantMcpCatalogPrimitivesByMetadataSurface; exports.buildAssistantMcpCatalogIndex = buildAssistantMcpCatalogIndex; exports.getAssistantMcpCatalogPrimitive = getAssistantMcpCatalogPrimitive; @@ -700,6 +701,53 @@ function searchAssistantMcpCatalogPrimitivesByFactAxis(input) { .sort((left, right) => right.score - left.score) .map((item) => item.primitive); } +function searchAssistantMcpCatalogChainTemplatesByFactAxis(input) { + const requiredAxisSet = toStringSet(input.required_axes ?? []); + const desiredTags = tagSetFromFactAxisInput({ + business_fact_family: input.business_fact_family, + action_family: input.action_family, + required_axes: input.required_axes, + comparison_need: input.comparison_need, + ranking_need: input.ranking_need, + aggregation_need: input.aggregation_need + }); + const scored = []; + for (const template of CHAIN_TEMPLATES) { + const factMatch = matchesPlanningToken(input.business_fact_family, template.supported_fact_families); + const actionMatch = matchesPlanningToken(input.action_family, template.supported_action_families); + const tagMatches = template.planning_tags.filter((tag) => desiredTags.has(normalizePlanningToken(tag))); + const axisOverlap = template.base_required_axes.filter((axis) => requiredAxisSet.has(axis)).length; + let score = 0; + if (factMatch) { + score += 8; + } + if (actionMatch) { + score += 5; + } + score += tagMatches.length * 2; + score += axisOverlap; + if (input.comparison_need && template.planning_tags.some((tag) => normalizePlanningToken(tag) === "comparison")) { + score += 6; + } + if (input.ranking_need && template.planning_tags.some((tag) => normalizePlanningToken(tag) === "ranking")) { + score += 6; + } + if (input.aggregation_need === "by_month" && + template.planning_tags.some((tag) => normalizePlanningToken(tag) === "monthly_aggregation")) { + score += 4; + } + if (score <= 0) { + continue; + } + scored.push({ + chainId: template.chain_id, + score + }); + } + return scored + .sort((left, right) => right.score - left.score) + .map((item) => item.chainId); +} function searchAssistantMcpCatalogPrimitivesByMetadataSurface(input) { const allowAggregateByAxis = input.allow_aggregate_by_axis !== false; const requiredAxisSet = toStringSet(input.required_axes ?? []); diff --git a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryPlanner.js b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryPlanner.js index 8c9dab5..be27a3d 100644 --- a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryPlanner.js +++ b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryPlanner.js @@ -278,6 +278,20 @@ function selectPrimitivesFromGraphAndCatalog(input) { if (factAxisPrimitives.length > 0) { reasonCodes.push("planner_selected_catalog_primitives_from_fact_axis_search"); } + const chainTemplateMatches = input.dataNeedGraph + ? (0, assistantMcpCatalogIndex_1.searchAssistantMcpCatalogChainTemplatesByFactAxis)({ + business_fact_family: input.dataNeedGraph.business_fact_family, + action_family: input.actionFamily ?? input.dataNeedGraph.action_family, + required_axes: input.requiredAxes, + comparison_need: input.dataNeedGraph.comparison_need, + ranking_need: input.dataNeedGraph.ranking_need, + aggregation_need: input.dataNeedGraph.aggregation_need + }) + : []; + if (chainTemplateMatches.length > 0) { + reasonCodes.push("planner_scored_catalog_chain_templates_from_fact_axis"); + reasonCodes.push(`planner_catalog_chain_template_search_top_${chainTemplateMatches[0]}`); + } const combinedCatalogPrimitives = []; for (const primitive of decompositionPrimitives) { if (!combinedCatalogPrimitives.includes(primitive)) { diff --git a/llm_normalizer/backend/src/services/assistantMcpCatalogIndex.ts b/llm_normalizer/backend/src/services/assistantMcpCatalogIndex.ts index dd70889..637410a 100644 --- a/llm_normalizer/backend/src/services/assistantMcpCatalogIndex.ts +++ b/llm_normalizer/backend/src/services/assistantMcpCatalogIndex.ts @@ -622,6 +622,15 @@ export interface AssistantMcpCatalogFactAxisSearchInput { allow_aggregate_by_axis?: boolean; } +export interface AssistantMcpCatalogChainTemplateSearchInput { + business_fact_family?: string | null; + action_family?: string | null; + required_axes?: string[]; + comparison_need?: string | null; + ranking_need?: string | null; + aggregation_need?: string | null; +} + export interface AssistantMcpCatalogPrimitiveSearchInput { decomposition_candidates: string[]; allow_aggregate_by_axis?: boolean; @@ -847,6 +856,62 @@ export function searchAssistantMcpCatalogPrimitivesByFactAxis( .map((item) => item.primitive); } +export function searchAssistantMcpCatalogChainTemplatesByFactAxis( + input: AssistantMcpCatalogChainTemplateSearchInput +): AssistantMcpCatalogChainTemplateId[] { + const requiredAxisSet = toStringSet(input.required_axes ?? []); + const desiredTags = tagSetFromFactAxisInput({ + business_fact_family: input.business_fact_family, + action_family: input.action_family, + required_axes: input.required_axes, + comparison_need: input.comparison_need, + ranking_need: input.ranking_need, + aggregation_need: input.aggregation_need + }); + const scored: Array<{ chainId: AssistantMcpCatalogChainTemplateId; score: number }> = []; + + for (const template of CHAIN_TEMPLATES) { + const factMatch = matchesPlanningToken(input.business_fact_family, template.supported_fact_families); + const actionMatch = matchesPlanningToken(input.action_family, template.supported_action_families); + const tagMatches = template.planning_tags.filter((tag) => desiredTags.has(normalizePlanningToken(tag))); + const axisOverlap = template.base_required_axes.filter((axis) => requiredAxisSet.has(axis)).length; + + let score = 0; + if (factMatch) { + score += 8; + } + if (actionMatch) { + score += 5; + } + score += tagMatches.length * 2; + score += axisOverlap; + if (input.comparison_need && template.planning_tags.some((tag) => normalizePlanningToken(tag) === "comparison")) { + score += 6; + } + if (input.ranking_need && template.planning_tags.some((tag) => normalizePlanningToken(tag) === "ranking")) { + score += 6; + } + if ( + input.aggregation_need === "by_month" && + template.planning_tags.some((tag) => normalizePlanningToken(tag) === "monthly_aggregation") + ) { + score += 4; + } + + if (score <= 0) { + continue; + } + scored.push({ + chainId: template.chain_id, + score + }); + } + + return scored + .sort((left, right) => right.score - left.score) + .map((item) => item.chainId); +} + export function searchAssistantMcpCatalogPrimitivesByMetadataSurface( input: AssistantMcpCatalogMetadataSurfaceSearchInput ): AssistantMcpDiscoveryPrimitive[] { diff --git a/llm_normalizer/backend/src/services/assistantMcpDiscoveryPlanner.ts b/llm_normalizer/backend/src/services/assistantMcpDiscoveryPlanner.ts index 779c7b9..4168459 100644 --- a/llm_normalizer/backend/src/services/assistantMcpDiscoveryPlanner.ts +++ b/llm_normalizer/backend/src/services/assistantMcpDiscoveryPlanner.ts @@ -6,6 +6,7 @@ import { } from "./assistantMcpDiscoveryPolicy"; import { getAssistantMcpCatalogChainTemplate, + searchAssistantMcpCatalogChainTemplatesByFactAxis, searchAssistantMcpCatalogPrimitivesByDecompositionCandidates, searchAssistantMcpCatalogPrimitivesByFactAxis, searchAssistantMcpCatalogPrimitivesByMetadataSurface, @@ -457,6 +458,21 @@ function selectPrimitivesFromGraphAndCatalog(input: { reasonCodes.push("planner_selected_catalog_primitives_from_fact_axis_search"); } + const chainTemplateMatches = input.dataNeedGraph + ? searchAssistantMcpCatalogChainTemplatesByFactAxis({ + business_fact_family: input.dataNeedGraph.business_fact_family, + action_family: input.actionFamily ?? input.dataNeedGraph.action_family, + required_axes: input.requiredAxes, + comparison_need: input.dataNeedGraph.comparison_need, + ranking_need: input.dataNeedGraph.ranking_need, + aggregation_need: input.dataNeedGraph.aggregation_need + }) + : []; + if (chainTemplateMatches.length > 0) { + reasonCodes.push("planner_scored_catalog_chain_templates_from_fact_axis"); + reasonCodes.push(`planner_catalog_chain_template_search_top_${chainTemplateMatches[0]}`); + } + const combinedCatalogPrimitives: AssistantMcpDiscoveryPrimitive[] = []; for (const primitive of decompositionPrimitives) { if (!combinedCatalogPrimitives.includes(primitive)) { diff --git a/llm_normalizer/backend/tests/assistantMcpCatalogIndex.test.ts b/llm_normalizer/backend/tests/assistantMcpCatalogIndex.test.ts index dcf63d4..678ae8a 100644 --- a/llm_normalizer/backend/tests/assistantMcpCatalogIndex.test.ts +++ b/llm_normalizer/backend/tests/assistantMcpCatalogIndex.test.ts @@ -5,6 +5,7 @@ import { getAssistantMcpCatalogChainTemplate, getAssistantMcpCatalogPrimitive, reviewAssistantMcpDiscoveryPlanAgainstCatalog, + searchAssistantMcpCatalogChainTemplatesByFactAxis, searchAssistantMcpCatalogPrimitivesByDecompositionCandidates, searchAssistantMcpCatalogPrimitivesByFactAxis, searchAssistantMcpCatalogPrimitivesByMetadataSurface @@ -151,6 +152,30 @@ describe("assistant MCP catalog index", () => { expect(primitives).toEqual(["resolve_entity_reference", "query_documents", "probe_coverage"]); }); + it("can score reviewed chain templates directly from fact family and required axes", () => { + const documentTemplates = searchAssistantMcpCatalogChainTemplatesByFactAxis({ + business_fact_family: "document_evidence", + action_family: "list_documents", + required_axes: ["counterparty", "period", "coverage_target"] + }); + const comparisonTemplates = searchAssistantMcpCatalogChainTemplatesByFactAxis({ + business_fact_family: "value_flow", + action_family: "net_value_flow", + comparison_need: "incoming_vs_outgoing", + required_axes: ["organization", "period", "amount", "coverage_target"] + }); + const rankingTemplates = searchAssistantMcpCatalogChainTemplatesByFactAxis({ + business_fact_family: "value_flow", + action_family: "turnover", + ranking_need: "top_desc", + required_axes: ["organization", "period", "aggregate_axis", "amount", "coverage_target"] + }); + + expect(documentTemplates[0]).toBe("document_evidence"); + expect(comparisonTemplates[0]).toBe("value_flow_comparison"); + expect(rankingTemplates[0]).toBe("value_flow_ranking"); + }); + it("can search reviewed primitives for inventory stock snapshot chains", () => { const primitives = searchAssistantMcpCatalogPrimitivesByFactAxis({ business_fact_family: "inventory_stock_snapshot", diff --git a/llm_normalizer/backend/tests/assistantMcpDiscoveryPlanner.test.ts b/llm_normalizer/backend/tests/assistantMcpDiscoveryPlanner.test.ts index fb36c79..917e6eb 100644 --- a/llm_normalizer/backend/tests/assistantMcpDiscoveryPlanner.test.ts +++ b/llm_normalizer/backend/tests/assistantMcpDiscoveryPlanner.test.ts @@ -51,6 +51,8 @@ describe("assistant MCP discovery planner", () => { expect(result.reason_codes).toContain("planner_enabled_chunked_coverage_probe_budget"); expect(result.reason_codes).toContain("planner_consumed_data_need_graph_v1"); expect(result.reason_codes).toContain("planner_selected_catalog_primitives_from_decomposition_candidates"); + expect(result.reason_codes).toContain("planner_scored_catalog_chain_templates_from_fact_axis"); + expect(result.reason_codes).toContain("planner_catalog_chain_template_search_top_value_flow"); }); it("keeps a value-flow plan in clarification state when period axis is missing", () => { @@ -145,6 +147,7 @@ describe("assistant MCP discovery planner", () => { 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).toContain("planner_catalog_chain_template_search_top_document_evidence"); expect(result.reason_codes).not.toContain("planner_fell_back_to_recipe_primitives_after_empty_catalog_search"); });