Skip to content

LCORE-2337: Migration tool — dumb-mode lift-and-shift#2029

Open
max-svistunov wants to merge 2 commits into
lightspeed-core:mainfrom
max-svistunov:lcore-2337-migrate-legacy-config-to-unified
Open

LCORE-2337: Migration tool — dumb-mode lift-and-shift#2029
max-svistunov wants to merge 2 commits into
lightspeed-core:mainfrom
max-svistunov:lcore-2337-migrate-legacy-config-to-unified

Conversation

@max-svistunov

@max-svistunov max-svistunov commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Description

Implements LCORE-2337 — the dumb-mode migration tool, sibling of the merged
LCORE-2336 (unified lightspeed-stack.yaml schema + synthesizer).

Adds --migrate-config, a lift-and-shift that converts a legacy two-file config
(run.yaml + lightspeed-stack.yaml) into a unified single file: it drops
library_client_config_path and lifts the entire run.yaml body under
llama_stack.config.native_override with baseline: empty, preserving all
other lightspeed-stack.yaml content. Synthesizing the result reproduces the
original run.yaml exactly (lossless round-trip), so existing enrichment keeps
working unchanged. No attempt is made to factor the run.yaml into high-level
sections — that "smart" mode is deferred future work.

  • migrate_config_dumb() in src/llama_stack_configuration.py.
  • --migrate-config, --run-yaml, --migrate-output flags in
    src/lightspeed_stack.py, dispatched in main() before load_configuration
    (the -c file still uses the legacy shape and is read raw). Missing
    --run-yaml/--migrate-output exits with status 1; --help documents the
    flags and advises replacing literal secrets with ${env.VAR} references.

Type of change

  • Refactor
  • New feature
  • Bug fix
  • CVE fix
  • Optimization
  • Documentation Update
  • Configuration Update
  • Bump-up service version
  • Bump-up dependent library
  • Bump-up library or tool used for development (does not change the final image)
  • CI configuration change
  • Konflux configuration change
  • Unit tests improvement
  • Integration tests improvement
  • End to end tests improvement
  • Benchmarks improvement

Tools used to create PR

  • Assisted-by: Claude Opus 4.8
  • Generated by: Claude Opus 4.8

Related Tickets & Documents

  • Related Issue # LCORE-2337
  • Closes # LCORE-2337

Checklist before requesting a review

  • I have performed a self-review of my code.
  • PR has passed all pre-merge test jobs.
  • If it is a core feature, I have added thorough tests.

Testing

Manual verification (migrate a legacy config, then boot it)

  1. Migrate the repo's CI run.yaml + a legacy lightspeed-stack.yaml:

    uv run lightspeed-stack --migrate-config \
      --run-yaml tests/e2e/configs/run-ci.yaml \
      -c examples/lightspeed-stack-lls-library.yaml \
      --migrate-output /tmp/unified.yaml
    

    The output drops library_client_config_path and carries the whole run.yaml
    under llama_stack.config.native_override with baseline: empty.

  2. Boot library mode with the migrated file and query it:

    export OPENAI_API_KEY=<key>
    uv run lightspeed-stack -c /tmp/unified.yaml
    curl -s localhost:8080/readiness          # {"ready":true,...}
    curl -s -X POST localhost:8080/v1/query -H 'Content-Type: application/json' \
      -d '{"query":"Name three primary colors."}'   # real model response
    
  3. Lossless round-trip — synthesizing the migrated config reproduces the
    original run.yaml (dict-equal):

    synthesize_configuration(migrated) == yaml.safe_load(run-ci.yaml)   # True
    

Tests specific to this change

uv run python -m pytest tests/unit/test_llama_stack_synthesize.py \
    tests/unit/test_lightspeed_stack.py -q

Covers migrate_config_dumb structure, the migrate→synthesize round-trip, flag
parsing, the main() dispatch, and the missing-flag exit.

Full unit suite

uv run python -m pytest tests/unit/ -q
# 2929 passed, 1 skipped

uv run make verify passes for all files changed in this PR (black, pylint
10.00/10, ruff, pyright, pydocstyle). NOTE: make verify's check-types-tests
is currently red on two pre-existing upstream test-annotation issues
(tests/unit/conftest.py, tests/integration/container_lifecycle/test_container_lifecycle.py),
neither touched by this PR; CI's mypy scope passes.

Summary by CodeRabbit

  • New Features

    • Added a new command-line migration flow to convert older split configuration files into a single unified config and exit.
    • The migration requires input and output file paths and reports success or failure during the process.
  • Bug Fixes

    • Improved startup behavior so the migration runs before normal app initialization.
    • Unified configuration handling now preserves existing settings while removing deprecated fields during conversion.

Add migrate_config_dumb(), the lift-and-shift migration that converts a legacy
two-file config (run.yaml + lightspeed-stack.yaml) into a unified single file.

The operator's lightspeed-stack.yaml is kept verbatim except for its
llama_stack section, where library_client_config_path is dropped and replaced
by a config block that lifts the entire run.yaml body into native_override with
baseline: empty. Synthesizing the result then starts from an empty baseline and
deep-merges only the lifted run.yaml, so it reproduces the original run.yaml
exactly (Decision T7) - a lossless round-trip - without trying to factor
anything into high-level sections (that "smart" mode is deferred future work).
All other lightspeed-stack.yaml content (name, service, byok_rag, ...) is
preserved, so existing enrichment keeps working in unified mode as before.

Add unit tests for the migrated structure and the migrate -> synthesize
round-trip (synthesized dict equals the original run.yaml). The synthesizer
module crossed pylint's 1000-line module limit, so disable too-many-lines at
module scope, matching the precedent in src/models/config.py.
Expose the migration on the lightspeed-stack CLI: --migrate-config plus
--run-yaml and --migrate-output. main() dispatches to migrate_config_dumb and
exits early, before load_configuration, because the input -c file still uses
the legacy library_client_config_path shape and is read raw rather than
validated against the current schema. Missing --run-yaml/--migrate-output exits
with status 1, and the help text advises replacing literal secrets with
${env.VAR} references.

Add CLI tests: flag parsing, the main() happy path (migrates the legacy pair
and returns without starting the service), and the missing-flag exit.
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a migrate_config_dumb function to llama_stack_configuration.py that converts a legacy two-file configuration (run.yaml + lightspeed-stack.yaml) into a unified single config. Wires this into the CLI via three new flags (--migrate-config, --run-yaml, --migrate-output) with early-exit handling in main(). Includes unit tests for structure, round-trip, and error paths.

Changes

Legacy Config Migration

Layer / File(s) Summary
migrate_config_dumb implementation
src/llama_stack_configuration.py
New function reads run.yaml and lightspeed-stack.yaml, rewrites llama_stack.config with baseline: empty and native_override set to the legacy run.yaml contents, drops library_client_config_path, and writes output via YamlDumper. Adds a pylint too-many-lines suppression.
CLI flags and main() migration flow
src/lightspeed_stack.py
Imports migrate_config_dumb; extends parser with --migrate-config, --run-yaml, --migrate-output; adds early main() block that validates required flags, calls the migration helper, and exits (with SystemExit(1) on error) before normal startup.
Unit tests
tests/unit/test_lightspeed_stack.py, tests/unit/test_llama_stack_synthesize.py
Tests cover CLI flag parsing, main() output structure, missing-flag SystemExit, migrate_config_dumb field preservation/removal, and round-trip equivalence with synthesize_configuration.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: a legacy config migration tool in dumb-mode.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/llama_stack_configuration.py`:
- Around line 965-966: The migrated output written in the configuration export
path currently uses the default file mode, which can leave `run.yaml` contents
overly permissive; update the write logic around `output_path`/`yaml.dump` to
explicitly set restrictive permissions consistent with the synthesized-config
path (mode 0600) so secrets in the generated file are protected. Use the
existing output-writing flow in `llama_stack_configuration.py` as the place to
apply the permission change, keeping the behavior aligned with the other config
export path.
- Around line 946-957: Handle the empty/comment-only lightspeed-stack.yaml case
in the configuration load path by checking the result of yaml.safe_load before
using lcs_config; in the llama_stack configuration block, treat None as an
invalid/empty config and fail with a clear, documented error instead of letting
dict access or assignment on lcs_config raise AttributeError/TypeError. Keep the
fix localized around the existing lcs_config, llama_stack, and
lightspeed_yaml_path handling so the behavior stays consistent for valid YAML
inputs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: da1a667a-3420-4de6-ac1f-28d442ae6c55

📥 Commits

Reviewing files that changed from the base of the PR and between 7ad310a and fa53efe.

📒 Files selected for processing (4)
  • src/lightspeed_stack.py
  • src/llama_stack_configuration.py
  • tests/unit/test_lightspeed_stack.py
  • tests/unit/test_llama_stack_synthesize.py
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: spectral
  • GitHub Check: mypy
  • GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-0-6-on-pull-request
  • GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Use absolute imports for internal modules: from authentication import get_auth_dependency
Llama Stack imports: Use from llama_stack_client import AsyncLlamaStackClient
Check constants.py for shared constants before defining new ones
All modules must start with descriptive docstrings explaining purpose
Use logger = get_logger(__name__) from log.py for module logging
All functions must have complete type annotations for parameters and return types, use modern syntax (str | int), and include descriptive docstrings
Use snake_case with descriptive, action-oriented names for functions (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying function parameters
Use async def for I/O operations and external API calls
Use standard log levels with clear purposes: debug() for diagnostic info, info() for program execution, warning() for unexpected events, error() for serious problems
All classes must have descriptive docstrings explaining purpose and use PascalCase with standard suffixes: Configuration, Error/Exception, Resolver, Interface
Abstract classes must use ABC with @abstractmethod decorators
Follow Google Python docstring conventions with required sections: Parameters, Returns, Raises, and Attributes for classes

Files:

  • src/lightspeed_stack.py
  • src/llama_stack_configuration.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.py: Use pytest for all unit and integration tests; do not use unittest
Use pytest.mark.asyncio marker for async tests

Files:

  • tests/unit/test_llama_stack_synthesize.py
  • tests/unit/test_lightspeed_stack.py
🧠 Learnings (1)
📚 Learning: 2026-06-24T13:45:37.249Z
Learnt from: Jdubrick
Repo: lightspeed-core/lightspeed-stack PR: 1971
File: src/utils/markdown_repair.py:31-36
Timestamp: 2026-06-24T13:45:37.249Z
Learning: In the lightspeed-stack repository, docstrings must use the section header name "Parameters:" (not "Args:") for function arguments, even if the project references Google Python docstring conventions. Ensure docstrings follow the project’s established "Parameters:" header format for any documented function parameters.

Applied to files:

  • src/lightspeed_stack.py
  • src/llama_stack_configuration.py
  • tests/unit/test_llama_stack_synthesize.py
  • tests/unit/test_lightspeed_stack.py
🪛 ast-grep (0.44.0)
src/llama_stack_configuration.py

[warning] 943-943: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(run_yaml_path, "r", encoding="utf-8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)


[warning] 945-945: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(lightspeed_yaml_path, "r", encoding="utf-8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)


[warning] 964-964: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(output_path, "w", encoding="utf-8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🔇 Additional comments (3)
src/lightspeed_stack.py (1)

79-103: LGTM!

Also applies to: 138-154

tests/unit/test_lightspeed_stack.py (1)

24-99: LGTM!

tests/unit/test_llama_stack_synthesize.py (1)

381-447: LGTM!

Comment on lines +946 to +957
with open(lightspeed_yaml_path, "r", encoding="utf-8") as file:
lcs_config = yaml.safe_load(file)

# Preserve the whole lightspeed-stack.yaml; only rewrite the llama_stack
# section: drop the legacy path, add the unified config block.
llama_stack = dict(lcs_config.get("llama_stack") or {})
llama_stack.pop("library_client_config_path", None)
llama_stack["config"] = {
"baseline": "empty",
"native_override": run_yaml,
}
lcs_config["llama_stack"] = llama_stack

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Empty lightspeed-stack.yaml will raise an opaque AttributeError/TypeError.

yaml.safe_load returns None for an empty or comment-only file. Line 951 (lcs_config.get(...)) then raises AttributeError, and line 957 (lcs_config["llama_stack"] = ...) would raise TypeError — neither is among the documented OSError/YAMLError. Guard for the None case to fail clearly.

🛡️ Proposed guard
     with open(lightspeed_yaml_path, "r", encoding="utf-8") as file:
         lcs_config = yaml.safe_load(file)
+    if not isinstance(lcs_config, dict):
+        raise ValueError(
+            f"{lightspeed_yaml_path} did not parse to a mapping; cannot migrate."
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
with open(lightspeed_yaml_path, "r", encoding="utf-8") as file:
lcs_config = yaml.safe_load(file)
# Preserve the whole lightspeed-stack.yaml; only rewrite the llama_stack
# section: drop the legacy path, add the unified config block.
llama_stack = dict(lcs_config.get("llama_stack") or {})
llama_stack.pop("library_client_config_path", None)
llama_stack["config"] = {
"baseline": "empty",
"native_override": run_yaml,
}
lcs_config["llama_stack"] = llama_stack
with open(lightspeed_yaml_path, "r", encoding="utf-8") as file:
lcs_config = yaml.safe_load(file)
if not isinstance(lcs_config, dict):
raise ValueError(
f"{lightspeed_yaml_path} did not parse to a mapping; cannot migrate."
)
# Preserve the whole lightspeed-stack.yaml; only rewrite the llama_stack
# section: drop the legacy path, add the unified config block.
llama_stack = dict(lcs_config.get("llama_stack") or {})
llama_stack.pop("library_client_config_path", None)
llama_stack["config"] = {
"baseline": "empty",
"native_override": run_yaml,
}
lcs_config["llama_stack"] = llama_stack
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/llama_stack_configuration.py` around lines 946 - 957, Handle the
empty/comment-only lightspeed-stack.yaml case in the configuration load path by
checking the result of yaml.safe_load before using lcs_config; in the
llama_stack configuration block, treat None as an invalid/empty config and fail
with a clear, documented error instead of letting dict access or assignment on
lcs_config raise AttributeError/TypeError. Keep the fix localized around the
existing lcs_config, llama_stack, and lightspeed_yaml_path handling so the
behavior stays consistent for valid YAML inputs.

Comment on lines +965 to +966
with open(output_path, "w", encoding="utf-8") as file:
yaml.dump(lcs_config, file, Dumper=YamlDumper, default_flow_style=False)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Consider restricting permissions on the migrated output file.

The lifted run.yaml may contain literal secrets, and the unified output is written with the default umask. The synthesized-config path in this same codebase is written with mode 0600; applying the same here would keep parity. (The --help guidance to use ${env.VAR} mitigates but doesn't enforce this.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/llama_stack_configuration.py` around lines 965 - 966, The migrated output
written in the configuration export path currently uses the default file mode,
which can leave `run.yaml` contents overly permissive; update the write logic
around `output_path`/`yaml.dump` to explicitly set restrictive permissions
consistent with the synthesized-config path (mode 0600) so secrets in the
generated file are protected. Use the existing output-writing flow in
`llama_stack_configuration.py` as the place to apply the permission change,
keeping the behavior aligned with the other config export path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant