From 47544b3d38c1aeb505280ba93ed307535295644a Mon Sep 17 00:00:00 2001 From: Maxim Svistunov Date: Tue, 4 Aug 2026 12:37:26 +0200 Subject: [PATCH] LCORE-3370: apply enrichment after the native_override merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dumb migration lifts the entire legacy run.yaml into llama_stack.config.native_override, and synthesis applied enrichment to the baseline BEFORE deep-merging the override with list-replacement semantics — so for any migrated config whose original run.yaml carried the usual list-shaped sections, the merge replaced the enriched lists wholesale: BYOK and Solr/OKP vector_io providers, registered embedding models, and the Azure model_validation tweak were silently dropped, contradicting migrate_config_dumb's enrichment-keeps-working promise and R7-after-migration. Reorder the synthesis pipeline so enrichment is the last content step: baseline -> high-level inference expansion -> ensure MCP (non-empty baselines) -> native_override merge -> enrichment -> dedupe. This is exactly legacy mode's semantics — enrichment always post-processes the operator's final run.yaml and the run.yaml never beats it — so R5 keeps meaning "the override wins over the baseline and the high-level expansion" while R7 parity now also holds for migrated configs. The reorder incidentally fixes a latent same-family bug: azure enrichment used to run before apply_high_level_inference, so a high-level 'azure' provider entry replaced the enriched provider and dropped model_validation=false even without migration involved. The strict xfail documenting the defect flips to a plain assertion, and the test's azure_entra_id fixture moves to the current model schema (client_secret; client_secret_path no longer exists post-rename — the xfail had been failing partly for that stale reason). Spec R5 wording and the changelog record the ordering change. The migrate round-trip (T7) is unaffected: with no enrichment inputs the pipeline is order-insensitive, and test_migrate_then_synthesize_reproduces_run_yaml still passes along with the full unit suite (3138) and the synthesis integration module (63). --- .../llama-stack-config-merge.md | 6 ++- src/llama_stack_configuration.py | 40 ++++++++++++------- tests/integration/test_unified_synthesis.py | 11 +---- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/docs/design/llama-stack-config-merge/llama-stack-config-merge.md b/docs/design/llama-stack-config-merge/llama-stack-config-merge.md index cf43a6107..320a7d776 100644 --- a/docs/design/llama-stack-config-merge/llama-stack-config-merge.md +++ b/docs/design/llama-stack-config-merge/llama-stack-config-merge.md @@ -82,7 +82,10 @@ detail that LCORE owns, not an operator-facing artifact. - **R5:** When `llama_stack.config.native_override` overlaps a key set by the high-level section or by the baseline, deep-merge semantics apply with list replacement (maps merge recursively; lists are - replaced wholesale; scalars are replaced). + replaced wholesale; scalars are replaced). The override wins over the + baseline and the high-level expansion; enrichment (R7) applies after + the merge, exactly as in legacy mode, where enrichment always + post-processes the operator's final run.yaml (LCORE-3370). - **R6:** Secrets that LCORE itself emits are never resolved on disk: `apply_high_level_inference` writes `${env.}` references verbatim, and LCORE does not eagerly resolve env refs in the @@ -545,6 +548,7 @@ reference. | Date | Change | Reason | |---|---|---| | 2026-04-23 | Initial version | Spike completion | +| 2026-08-04 | R5: enrichment applies after the `native_override` merge | LCORE-3370 — migrated configs (run.yaml lifted into the override) replaced list-shaped enrichment artifacts wholesale, silently dropping BYOK/Solr providers, registered embedding models, and Azure `model_validation`; ordering now matches legacy, where the operator's run.yaml never beats enrichment | ## Appendix A — Worked example: legacy → unified migration diff --git a/src/llama_stack_configuration.py b/src/llama_stack_configuration.py index 1a563bc33..13c6bc03a 100644 --- a/src/llama_stack_configuration.py +++ b/src/llama_stack_configuration.py @@ -1138,11 +1138,13 @@ def synthesize_configuration( """Synthesize a full Llama Stack ``run.yaml`` dict from a unified config. Implements the unified-mode synthesis pipeline: select a baseline (profile - file, empty, or the built-in default), apply the existing enrichment - (Azure Entra ID, BYOK RAG, Solr/OKP) for parity with legacy mode (R7), - expand the high-level ``inference.providers`` section, ensure the default - MCP tool_runtime provider when the baseline was not empty, and deep-merge - the raw ``native_override`` last (R5). + file, empty, or the built-in default), expand the high-level + ``inference.providers`` section, ensure the default MCP tool_runtime + provider when the baseline was not empty, deep-merge the raw + ``native_override`` (R5: it wins over the baseline and the high-level + expansion), and apply the existing enrichment (Azure Entra ID, BYOK RAG, + vector_store, Solr/OKP) last — matching legacy mode, where enrichment + always post-processes the operator's final run.yaml (R7, LCORE-3370). Parameters: lcs_config: The full ``lightspeed-stack.yaml`` parsed into a dict. @@ -1180,27 +1182,35 @@ def synthesize_configuration( # 3. Normalize duplicated vector_io providers in the baseline. dedupe_providers_vector_io(ls_config) - # 4. Existing enrichment — same calls as legacy generate_configuration so - # unified output matches legacy output for equivalent inputs (R7). - enrich_azure_entra_id_inference(ls_config, lcs_config.get("azure_entra_id")) - enrich_byok_rag(ls_config, lcs_config.get("byok_rag", [])) - enrich_vector_store(ls_config, lcs_config.get("vector_store")) - enrich_solr(ls_config, lcs_config.get("rag", {}), lcs_config.get("okp", {})) - - # 5. High-level inference providers (Decision S5 — a root-level section). + # 4. High-level inference providers (Decision S5 — a root-level section). inference = lcs_config.get("inference") or {} if inference.get("providers"): apply_high_level_inference(ls_config, inference) - # 6. Ensure MCP tool_runtime for default/profile baselines (skipped for + # 5. Ensure MCP tool_runtime for default/profile baselines (skipped for # baseline: empty so migrate round-trips stay lossless). if not baseline_was_empty: ensure_mcp_tool_runtime(ls_config) - # 7. Raw escape hatch, deep-merged last with list replacement (R5). + # 6. Raw escape hatch, deep-merged with list replacement. It wins over the + # baseline and the high-level expansion (R5) but deliberately NOT over + # enrichment (step 7). if unified and unified.get("native_override"): ls_config = deep_merge_list_replace(ls_config, unified["native_override"]) + # 7. Existing enrichment — same calls as legacy generate_configuration so + # unified output matches legacy output for equivalent inputs (R7). + # Applied AFTER the native_override merge (LCORE-3370): in legacy mode + # enrichment always post-processes the operator's final run.yaml, so a + # migrated config (whose native_override IS the lifted run.yaml) must + # get the same treatment or list-shaped enrichment artifacts + # (vector_io providers, registered models, azure model_validation) are + # replaced wholesale by the lifted lists and silently lost. + enrich_azure_entra_id_inference(ls_config, lcs_config.get("azure_entra_id")) + enrich_byok_rag(ls_config, lcs_config.get("byok_rag", [])) + enrich_vector_store(ls_config, lcs_config.get("vector_store")) + enrich_solr(ls_config, lcs_config.get("rag", {}), lcs_config.get("okp", {})) + # 8. Dedupe again in case native_override or enrichment reintroduced dupes. dedupe_providers_vector_io(ls_config) diff --git a/tests/integration/test_unified_synthesis.py b/tests/integration/test_unified_synthesis.py index b0b9a7db0..4833c276d 100644 --- a/tests/integration/test_unified_synthesis.py +++ b/tests/integration/test_unified_synthesis.py @@ -130,7 +130,7 @@ "azure_entra_id": { "tenant_id": "test-tenant", "client_id": "test-client", - "client_secret_path": "/run/secrets/azure", + "client_secret": "test-secret", } } @@ -403,15 +403,6 @@ def test_migrate_then_synthesize_round_trip_without_enrichment( assert legacy == synthesized -@pytest.mark.xfail( - strict=True, - reason="Known defect (LCORE-3370): dumb migration lifts run.yaml " - "into native_override, which deep-merges after enrichment and replaces " - "lists wholesale (R5) — so BYOK/Solr vector_io providers, registered " - "embedding models, and the Azure model_validation enrichment are lost " - "whenever the original run.yaml already carried those list sections. " - "Contradicts migrate_config_dumb's enrichment-keeps-working promise.", -) def test_migrate_then_synthesize_preserves_enrichment_parity( tmp_path: Path, ) -> None: