Skip to content

fix(deploy): complete Quadlet isolation review fixes#351

Closed
sheepdestroyer wants to merge 9 commits into
masterfrom
fix/quadlet-environment-isolation
Closed

fix(deploy): complete Quadlet isolation review fixes#351
sheepdestroyer wants to merge 9 commits into
masterfrom
fix/quadlet-environment-isolation

Conversation

@sheepdestroyer

@sheepdestroyer sheepdestroyer commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Supersedes #350.

Addresses all valid review findings:

  • Restart failures report the exact namespaced or legacy owner unit.
  • Quadlet identifier suffixes are centralized in the renderer.
  • Removed unused lifecycle helpers.
  • Added behavioral renderer coverage for dev/prod unit names and preservation of image names and URLs containing llm-routing.
  • Strengthened namespace validation assertions.
  • Documented that default DATA_ROOT=${WORKDIR}/data is physically isolated by the separate ~/dev/ and ~/prod/ worktrees.
  • Corrected systemctl documentation and stale Quadlet path comments.
  • Confirmed the local safety net remains reachable at http://127.0.0.1:8083 and is not dependent on TLS-terminated host routes.

Validation:

  • Full suite: 209 passed, one existing Starlette/httpx deprecation warning.
  • bash -n start-stack.sh passed.
  • git diff --check passed.
  • Dev full rebuild and canonical verification passed 29/29.
  • Dev and production remain under separate llm-routing-dev-* and llm-routing-prod-* Quadlet namespaces.
  • llama-router safety-net endpoint is reachable with the approved local models.

Summary by Sourcery

Isolate Quadlet-based deployment between dev and prod environments and improve namespace handling, ownership detection, and documentation.

New Features:

  • Introduce environment-specific Quadlet namespace configuration with validation for dev and prod deployments.
  • Namespace rendered Quadlet unit filenames and internal dependencies while preserving image names and URLs.

Bug Fixes:

  • Ensure Quadlet stack ownership and restart diagnostics report the exact pod unit name, including legacy units.
  • Prevent dev and prod deployments from sharing rendered Quadlet files, systemd unit names, or data roots by default.

Enhancements:

  • Centralize Quadlet namespace and pod unit naming in the renderer and startup script.
  • Strengthen validation of Quadlet namespace values to avoid invalid systemd unit names.
  • Refine restart and teardown logic to operate on the detected owner unit rather than a hard-coded name.
  • Update tests to cover environment-specific Quadlet namespaces, namespace rendering behavior, and data root isolation.

Documentation:

  • Clarify documentation for environment-specific Quadlet unit names and namespaces in README files.
  • Document the default worktree-scoped DATA_ROOT behavior and its role in isolating dev and prod data.
  • Adjust usage docs to reference environment-specific systemd unit names and lifecycle commands.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@sheepdestroyer, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b475139e-98b6-443f-874b-445e86fb65f9

📥 Commits

Reviewing files that changed from the base of the PR and between 8e7eb90 and 2529318.

📒 Files selected for processing (5)
  • .env.dev
  • README.md
  • scripts/README.md
  • start-stack.sh
  • tests/test_quadlet_templates.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/quadlet-environment-isolation

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.

@github-actions github-actions Bot added documentation Improvements or additions to documentation scripts tests labels Jul 23, 2026
@sourcery-ai

sourcery-ai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR finishes the Quadlet isolation review by making the Quadlet namespace environment-specific (dev vs prod), centralizing and tightening the renderer’s namespace handling, improving restart/ownership behavior, and updating tests and documentation to reflect the new deployment model and safety-net behavior.

Sequence diagram for Quadlet-owned stack restart with namespaced units

sequenceDiagram
    actor User
    participant StartStack as start-stack.sh
    participant Podman
    participant Systemd as systemd_user

    User->>StartStack: run_start_stack
    StartStack->>Podman: stack_ownership
    Podman-->>StartStack: quadlet:llm-routing-prod-pod.service

    StartStack->>StartStack: parse_owner_unit
    StartStack->>Systemd: systemctl_user_restart(owner_unit)
    alt restart_failed
        Systemd-->>StartStack: error
        StartStack-->>User: print_restart_error_with_owner_unit
    else restart_succeeded
        Systemd-->>StartStack: ok
        StartStack-->>User: confirm_restart
    end
Loading

File-Level Changes

Change Details Files
Introduce environment-specific Quadlet namespace and directory, and thread it through ownership and lifecycle logic.
  • Default QUADLET_NAMESPACE to llm-routing-prod and validate it against a strict lowercase/digit/hyphen regex
  • Export QUADLET_NAMESPACE and derive QUADLET_DIR from it instead of a fixed llm-routing path
  • Change LLM_ROUTING_POD_UNIT to be derived from QUADLET_NAMESPACE and add a legacy unit name for backward compatibility
  • Adjust stack_ownership to return quadlet: for both current and legacy units, including systemctl-based detection
  • Update safe_pod_teardown and restart paths to operate on the exact owner unit instead of a hard-coded name
start-stack.sh
Centralize namespace-aware Quadlet rendering so dev/prod units and dependencies are namespaced while non-identifiers (images, URLs, credentials) are preserved.
  • Read QUADLET_NAMESPACE into the embedded Python renderer and define a set of identifier suffixes and a prefix regex for llm-routing-* identifiers
  • Add namespace_identifier function that rewrites Pod/After/Wants/BindsTo/Requires/PartOf lines to use the namespaced identifiers and pod service names
  • Rename rendered filenames by replacing llm-routing with the namespace, and similarly adjust the rendered_names set used for stale cleanup
  • Ensure environment lines remain quoted correctly while keeping image names and PUBLIC_BASE_URL values containing llm-routing unchanged
start-stack.sh
Strengthen tests around Quadlet namespace behavior, renderer isolation, restart ownership, and data-root/worktree separation.
  • Add tests that assert dev .env and start-stack.sh use environment-specific QUADLET_NAMESPACE and namespaced renderer helpers
  • Add behavioral test that executes the embedded Python renderer with a synthetic env to verify identifier namespacing and non-identifier preservation (images/URLs)
  • Add tests that validate QUADLET_NAMESPACE regex and that ownership/restart logging uses the exact unit name
  • Add tests that documentation uses dev/prod-specific unit names and that DATA_ROOT defaults are worktree-scoped
tests/test_quadlet_templates.py
Update documentation to describe environment-isolated Quadlet deployment, dev/prod unit names, and safety-net endpoint behavior.
  • Update README examples to use llm-routing-prod-/llm-routing-dev- unit names and clarify list-units/journalctl usage for both namespaces
  • Add a dedicated section explaining environment-isolated Quadlet deployment, including namespaces, directories, pod names, ports, and DATA_ROOT behavior across dev/prod worktrees
  • Update scripts/README to describe environment-specific pod services and paths for diagnostics using the namespace
  • Clarify that the dev safety-net LLAMA_* URLs use the local 127.0.0.1:8083 listener and do not depend on TLS-terminated hostnames
README.md
scripts/README.md

Possibly linked issues

  • #fix(dev): restore fallback capacity for canonical E2E chat verification: PR makes the dev safety-net reachable at 127.0.0.1:8083 and reruns canonical verification to a clean 29/29 pass.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue, and left some high level feedback:

  • In the embedded Quadlet renderer, namespace_identifier has an elif field == "Pod" branch that is unreachable because Pod is already handled in the preceding if field in {...} block; consider simplifying this logic so each field is handled in exactly one place.
  • The tests that extract the embedded Python renderer by splitting the start-stack.sh here-doc content are quite brittle with respect to formatting or delimiter changes; consider moving the renderer into a separate Python module that both the script and tests can import directly.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the embedded Quadlet renderer, `namespace_identifier` has an `elif field == "Pod"` branch that is unreachable because `Pod` is already handled in the preceding `if field in {...}` block; consider simplifying this logic so each field is handled in exactly one place.
- The tests that extract the embedded Python renderer by splitting the `start-stack.sh` here-doc content are quite brittle with respect to formatting or delimiter changes; consider moving the renderer into a separate Python module that both the script and tests can import directly.

## Individual Comments

### Comment 1
<location path="start-stack.sh" line_range="793-802" />
<code_context>
+        def namespace_identifier(match):
</code_context>
<issue_to_address>
**issue (bug_risk):** The `elif field == "Pod"` branch is currently unreachable and can be simplified

In `namespace_identifier`, the first condition `if field in {"Pod", "After", "Wants", "BindsTo", "Requires", "PartOf"}:` already covers `"Pod"`, so the subsequent `elif field == "Pod":` is never reached. If `Pod` needs special handling, remove it from the set and rely on the dedicated branch; otherwise, remove the `elif` to avoid dead code.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread start-stack.sh
Comment on lines +793 to +802
def namespace_identifier(match):
field, value = match.group(1), match.group(2)
if field in {"Pod", "After", "Wants", "BindsTo", "Requires", "PartOf"}:
value = identifier_prefix.sub(namespace + "-", value)
value = value.replace("llm-routing.pod", namespace + ".pod")
value = value.replace("llm-routing-pod.service", namespace + "-pod.service")
elif field == "Pod":
value = value.replace("llm-routing.pod", namespace + ".pod")
return f"{field}={value}"
text = re.sub(r"(?m)^(Pod|After|Wants|BindsTo|Requires|PartOf)=(.*)$", namespace_identifier, text)

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.

issue (bug_risk): The elif field == "Pod" branch is currently unreachable and can be simplified

In namespace_identifier, the first condition if field in {"Pod", "After", "Wants", "BindsTo", "Requires", "PartOf"}: already covers "Pod", so the subsequent elif field == "Pod": is never reached. If Pod needs special handling, remove it from the set and rely on the dedicated branch; otherwise, remove the elif to avoid dead code.

@sheepdestroyer

Copy link
Copy Markdown
Owner Author

Superseded by #352 with the final legacy ownership guard and documentation consistency fixes.

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

Labels

documentation Improvements or additions to documentation scripts tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant