Skip to content

Complete command pipeline extraction (#170) - #177

Open
lodyai[bot] wants to merge 8 commits into
mainfrom
issue-170-further-extraction-needed
Open

Complete command pipeline extraction (#170)#177
lodyai[bot] wants to merge 8 commits into
mainfrom
issue-170-further-extraction-needed

Conversation

@lodyai

@lodyai lodyai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

This branch completes the command-module extraction deferred from PR #136. It moves bump run-sequence orchestration, per-crate publication execution, and workspace staging into canonical delegate modules so every affected source file remains below the 400-line guideline without compatibility shims.

All tests now patch and call the modules that own the moved symbols. The developer extraction map records the resulting boundaries, and staging cleanup no longer removes caller-owned build-directory contents.

Closes #170.

Review walkthrough

Validation

  • make check-fmt: passed
  • make lint: passed (Ruff, interrogate 100%, Pylint 10/10)
  • make typecheck: passed
  • make test: passed (741 tests; 70 snapshots)
  • make markdownlint: passed
  • make nixie: passed
  • coderabbit review --agent: completed after the required rate-limit back-off; actionable extraction concerns resolved

Notes

The five affected source modules are 208, 256, 280, 388, and 177 lines respectively, all below the 400-line guideline.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @LodyAI[bot], you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary

  • Extracted bump orchestration into bump_pipeline and publication staging/execution into publish_pipeline and publish_staging, reducing the top-level command modules to coordinators.
  • Updated tests and monkeypatch targets to use canonical extracted modules without compatibility aliases.
  • Added safer workspace staging, cleanup preservation, README-copying coverage, dry-run error handling, and publication failure-detail tests.
  • Documented the new module boundaries and updated the publish data-flow design in docs/developers-guide.md and docs/lading-design.md.
  • Validation passed: formatting, linting, type checking, 741 tests, 70 snapshots, Markdown linting, and Nixie.

Walkthrough

Changes

Command pipeline extraction

Layer / File(s) Summary
Bump pipeline stages
lading/commands/bump.py, lading/commands/bump_pipeline.py, lading/commands/bump_manifests.py, tests/unit/test_bump_*, tests/unit/test_dependency_sections.py
Moves bump manifest, documentation, README, lockfile, and result-ordering stages into bump_pipeline, leaving bump.py as a coordinator and retargeting unit tests.
Publish staging
lading/commands/publish_staging.py, lading/commands/publish_manifest.py, tests/unit/test_publish_staging.py, tests/unit/test_publish_staging_properties.py
Adds workspace copying, path validation, staged crate resolution, cleanup registration, preparation summaries, and property-based staging coverage.
Publish execution
lading/commands/publish.py, lading/commands/publish_pipeline.py, lading/commands/publish_index_check.py, tests/unit/publish/*
Moves live and dry-run package/publish sequencing, error classification, already-published handling, and dispatch into publish_pipeline, then updates execution seams and tests.
Architecture documentation
docs/developers-guide.md, docs/lading-design.md
Documents canonical module ownership, extracted pipeline responsibilities, staging boundaries, and updated publish control flow.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant publish
  participant publish_staging
  participant publish_pipeline
  participant Cargo
  CLI->>publish: run(options)
  publish->>publish_staging: prepare_workspace(plan)
  publish->>publish_pipeline: _dispatch_publication(state)
  publish_pipeline->>Cargo: cargo package
  publish_pipeline->>Cargo: cargo publish
Loading

Possibly related PRs

  • leynos/crate-tools#20: Updates the publish staging flow for copying the workspace README.
  • leynos/lading#120: Changes live publication pipeline exception handling in the same execution area.

Suggested labels: Issue

Suggested reviewers: leynos

Poem

Pipelines split, and staging grows,
Cargo follows where the workflow flows.
Bump finds its steps in a clearer line,
Publish ships in ordered time.
README rests where docs decree—
Modules march in harmony.

🚥 Pre-merge checks | ✅ 18 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Performance And Resource Use ⚠️ Warning FAIL: prepare_workspace cleans only staging_root; when it auto-creates the temp build dir, the empty lading-publish-* parent is leaked on every cleanup run. Track whether the build dir was auto-created and shutil.rmtree the build dir itself in that case; keep staging_root cleanup only for caller-supplied directories.
Concurrency And State ⚠️ Warning prepare_workspace() always cleans only staging_root, so auto-created mkdtemp() build dirs leak; tests cover only caller-supplied build dirs. Track ownership of the build directory and remove the whole temp dir when build_directory is omitted; add a test for the auto-created cleanup path.
✅ Passed checks (18 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the extraction work and includes the linked issue reference.
Description check ✅ Passed The description accurately summarises the command extraction and validation work.
Linked Issues check ✅ Passed The PR appears to satisfy #170: bump.py and publish.py were reduced, tests were retargeted, docs updated, and validation passed.
Out of Scope Changes check ✅ Passed No clear out-of-scope changes appear beyond the extraction, docs, and test retargeting needed for #170.
Docstring Coverage ✅ Passed Docstring coverage is 90.55% which is sufficient. The required threshold is 80.00%.
Testing (Overall) ✅ Passed PASS: New tests cover real behaviour with direct assertions on staging safety, cleanup, publish ordering, error propagation, and preflight gating; they fail on plausible regressions.
User-Facing Documentation ✅ Passed The branch only extracts internal modules; docs/users-guide.md already covers the public publish behaviour, including live interleaving and README adoption.
Developer Documentation ✅ Passed Document the new bump/publish boundaries in docs/developers-guide.md and refresh the publish flow diagrams in docs/lading-design.md; no execplan or roadmap item appears to need checking off.
Module-Level Documentation ✅ Passed All touched Python modules carry top-level docstrings; the new pipeline/staging modules clearly describe their purpose and coordinator/delegate boundaries.
Testing (Unit And Behavioural) ✅ Passed PASS: CLI BDD scenarios exercise the real lading subprocess boundary, while the new unit/property tests cover edge cases, invariants, and error paths in staging and publish orchestration.
Testing (Property / Proof) ✅ Passed Hypothesis covers the new path-safety, short-circuit, and publish-order invariants; no new proof obligations were introduced.
Testing (Compile-Time / Ui) ✅ Passed PASS: No Rust/TypeScript compile-time code exists here, and the PR adds focused snapshot coverage with redaction and semantic assertions for human-readable publish output.
Unit Architecture ✅ Passed PASS: command orchestration is split into small edge modules, with explicit runner/options seams and tests patched to the owning modules; query helpers stay pure.
Domain Architecture ✅ Passed PASS: the diff stays in command/docs/test layers, and the new modules keep filesystem and subprocess work behind staging/pipeline adapters; domain types remain untouched.
Observability ✅ Passed PASS: publish and staging now log mode, per-crate actions, staging summary, and abort/failure boundaries; no new metrics, tracing, or alerts are needed.
Security And Privacy ✅ Passed PASS: The extraction keeps parameterised subprocess calls and path validation, and adds no secrets, auth gaps, unsafe deserialisation, or wider permissions.
Architectural Complexity And Maintainability ✅ Passed PASS: bump.py and publish.py are now thin coordinators, extracted modules own the new seams, and the docs record canonical boundaries without shim layers.
Rust Compiler Lint Integrity ✅ Passed The PR only changes a Python test file; no Rust sources, lint suppressions, or suspicious clone-heavy ownership changes were introduced.
📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #170

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-170-further-extraction-needed

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

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@leynos

leynos commented Jul 14, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

lading/commands/publish_pipeline.py

Comment on lines +336 to +388

def _dispatch_publication(
    plan: PublishPlan,
    preparation: PublishPreparation,
    *,
    options: _PublishExecutionOptions,
    runner: CommandRunner,
) -> None:
    """Route to the live or dry-run publication pipeline.

    Design note (issue #72): this helper is more than a relocated branch.
    It owns the operator-facing pipeline-mode log line, sequences the
    dry-run two-phase pipeline (package everything, then publish
    everything), and gives tests a single seam to exercise mode dispatch
    without driving ``run()`` end to end. Inlining it would push ``run()``
    back toward the complexity ceiling that prompted the extraction.
    """
    if options.live:
        LOGGER.info("Publication mode: live (interleaved per-crate pipeline)")
        _execute_live_publication_pipeline(
            plan,
            preparation,
            options=options,
            runner=runner,
        )
    else:
        LOGGER.info("Publication mode: dry-run (batched two-phase pipeline)")
        try:
            _package_publishable_crates(
                plan,
                preparation,
                options=options,
                runner=runner,
            )
        except PublishPreparationError as exc:
            LOGGER.exception("Dry-run pipeline: packaging phase failed")
            raise PublishPreflightError(str(exc)) from exc
        except PublishPreflightError:
            LOGGER.exception("Dry-run pipeline: packaging phase failed")
            raise
        LOGGER.info("Dry-run pipeline: packaging complete; starting publish phase")
        try:
            _publish_crates(
                plan,
                preparation,
                runner=runner,
                options=options,
            )
        except PublishPreparationError as exc:
            LOGGER.exception("Dry-run pipeline: publish phase failed")
            raise PublishPreflightError(str(exc)) from exc
        except PublishPreflightError:
            LOGGER.exception("Dry-run pipeline: publish phase failed")
            raise

❌ New issue: Complex Method
_dispatch_publication has a cyclomatic complexity of 11, threshold = 9

@leynos

leynos commented Jul 14, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

tests/unit/publish/test_packaging.py

Comment on lines +277 to +279

    publish_plan_and_prep: tuple[
        publish_plan.PublishPlan, publish_staging.PublishPreparation, Path
    ],

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: test_package_publishable_crates_prefers_stderr_over_stdout,test_package_publishable_crates_reports_stdout_on_failure

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

codescene-access[bot]

This comment was marked as outdated.

@pandalump
pandalump marked this pull request as ready for review July 14, 2026 18:24
codescene-access[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot added the Issue label Jul 14, 2026
coderabbitai[bot]

This comment was marked as resolved.

@leynos

leynos commented Jul 21, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

tests/unit/publish/test_packaging.py

Comment on lines +295 to +302

def test_package_publishable_crates_reports_failure_detail(
    publish_plan_and_prep: tuple[
        publish_plan.PublishPlan, publish_staging.PublishPreparation, Path
    ],
    stdout: str,
    stderr: str,
    expected_in_message: str,
    not_expected_in_message: str | None,

❌ New issue: Excess Number of Function Arguments
test_package_publishable_crates_reports_failure_detail has 5 arguments, max arguments = 4

@coderabbitai

This comment was marked as resolved.

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@leynos

leynos commented Jul 22, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following failed checks now been resolved?

If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures.

Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already. (Treat documentation and validation coverage as in scope).

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error test_prepare_workspace_skips_readme_staging_for_opted_in_crates has no assertions, so it would pass even if prepare_workspace were a no-op. Add assertions that opted-in crates still omit staged README paths and that the staged workspace contents are correct; remove the call-only test.
Module-Level Documentation ❌ Error FAIL: lading/commands/publish_pipeline.py has only a one-line docstring and does not explain its role or relationship to publish.py/publish_execution.py. Expand publish_pipeline.py's module docstring to state the pipeline's purpose, utility, and how the top-level publish command delegates to it.
Developer Documentation ⚠️ Warning Developer guide is updated, but the design doc still shows the old publish_execution/re-exported helper flow and omits the new pipeline/staging boundaries. Update docs/lading-design.md or add an ADR to record the new publish_pipeline/publish_staging architecture and refresh the stale diagram.

@leynos

leynos commented Jul 22, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

Code Duplication

tests/unit/test_publish_staging.py:

What lead to degradation?

The module contains 2 functions with similar structure: test_prepare_workspace_copies_workspace_readme_verbatim,test_prepare_workspace_does_not_adopt_workspace_readme_for_crates

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@lodyai
lodyai Bot force-pushed the issue-170-further-extraction-needed branch from bba7bfa to 1ea8053 Compare July 22, 2026 13:27
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@coderabbitai

This comment was marked as resolved.

@leynos

leynos commented Jul 22, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following now been resolved?

tests/unit/test_publish_staging.py (1)

286-306: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
This test no longer verifies what its docstring claims.
The docstring reads "Staging reports no copied READMEs regardless of readme opt-in status," but PublishPreparation dropped its copied_readmes field, and the body only asserts staging_root.exists(). That assertion holds for every crate_spec variant regardless of readme behaviour, so the parametrisation no longer distinguishes anything meaningful. Either assert on the actual staged file contents (e.g. that a workspace README is copied verbatim by the plain tree copy) or drop the stale docstring/parametrisation and rename the test to reflect that staging is now README-agnostic.

🤖 Detailed instructions

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

In @tests/unit/test_publish_staging.py around lines 286 - 306, The test
test_prepare_workspace_skips_readme_staging no longer validates its
README-related claim. Update it to assert the actual staged README file
contents, confirming the workspace README is copied verbatim across crate_spec
variants; otherwise remove the stale docstring and parametrization and rename
the test to describe only the staging-root existence behavior.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- test module outline ---'
ast-grep outline tests/unit/test_publish_staging.py --items all

printf '%s\n' '--- reported test and README-related tests ---'
rg -n -A70 -B15 \
  'def test_prepare_workspace_skips_readme_staging\b|def test_prepare_workspace_copies_workspace_readme\b|def test_prepare_workspace_.*readme\b|README\.md|copied_readmes' \
  tests/unit/test_publish_staging.py

printf '%s\n' '--- current staging entrypoint and copy helper ---'
ast-grep outline lading/commands/publish_staging.py --items all
rg -n -A115 -B20 \
  'def prepare_workspace\b|def _copy_workspace_tree\b|README\.md|copied_readmes' \
  lading/commands/publish_staging.py

Length of output: 17536


@leynosFile: tests/unit/test_publish_staging.pySymbol: test_prepare_workspace_skips_readme_staging (removed; replaced by test_prepare_workspace_copies_workspace_readme_verbatim)

Treat this finding as resolved. The stale test no longer exists in the current codegraph. Its replacement at Lines 284–312 now performs meaningful assertions for both crate-spec variants:

  • Creates a workspace-level README.md with known contents.
  • Calls publish_staging.prepare_workspace(...).
  • Asserts the staging root exists.
  • Asserts the staged root README.md contains exactly "Workspace README".
  • Asserts no crate-local README.md was created.

This matches the current publish_staging.prepare_workspace() behaviour: it copies the workspace tree verbatim and no longer tracks copied_readmes. The test would fail if staging became a no-op or stopped copying the workspace README, so no further change is required for this specific finding.

Keep the separate duplication follow-up in scope: test_prepare_workspace_does_not_adopt_workspace_readme_for_crates at Lines 209–231 overlaps with the parametrized replacement test’s readme_workspace=True case. Remove that redundant test and rename the retained test as described in the earlier duplication fix to address CodeScene cleanly. Runtime validation could not be run in this inspection environment; validate with the focused staging tests and the repository’s required test/lint commands.

@leynos

leynos commented Jul 22, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following failed checks now been resolved?

If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures.

Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already. (Treat documentation and validation coverage as in scope).

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error test_prepare_workspace_skips_readme_staging_for_opted_in_crates has no assertions, so it would pass even if prepare_workspace were a no-op. Add assertions that opted-in crates still omit staged README paths and that the staged workspace contents are correct; remove the call-only test.
Module-Level Documentation ❌ Error FAIL: lading/commands/publish_pipeline.py has only a one-line docstring and does not explain its role or relationship to publish.py/publish_execution.py. Expand publish_pipeline.py's module docstring to state the pipeline's purpose, utility, and how the top-level publish command delegates to it.
Developer Documentation ⚠️ Warning Developer guide is updated, but the design doc still shows the old publish_execution/re-exported helper flow and omits the new pipeline/staging boundaries. Update docs/lading-design.md or add an ADR to record the new publish_pipeline/publish_staging architecture and refresh the stale diagram.

@coderabbitai

This comment was marked as resolved.

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lading/commands/publish.py (1)

254-272: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Pass effective_options to prepare_workspace for a single source of truth.

prepare_workspace(plan, options=options) feeds the raw parameter, so when options is None staging rebuilds its own default PublishOptions independently of the effective_options already computed on Line 237. The results coincide today, but wiring effective_options keeps one authoritative options object flowing through the run.

♻️ Proposed tweak
-    preparation = publish_staging.prepare_workspace(plan, options=options)
+    preparation = publish_staging.prepare_workspace(plan, options=effective_options)
🤖 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 `@lading/commands/publish.py` around lines 254 - 272, Update the publish flow
to pass effective_options, rather than the raw options parameter, to
publish_staging.prepare_workspace. Keep the existing _PublishExecutionOptions
construction and _dispatch_publication call unchanged so effective_options
remains the single authoritative configuration throughout the run.
🤖 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 `@docs/lading-design.md`:
- Around line 520-541: Align the changed sequence diagram with the call order in
publish.py: show plan_publication and prepare_workspace before
_run_preflight_checks, then dispatch publication only after pre-flight succeeds.
Update the relevant participant messages and success/failure branches while
preserving the documented _invoke behavior and PublishPreflightError handling.

In `@lading/commands/bump_pipeline.py`:
- Around line 138-147: Remove the try/except and _log.exception call around
bump_readme.transpose_readme_to_crate in the pipeline stage, allowing
ReadmeTranspositionError to propagate unchanged to the CLI or bump.run boundary
for single-point logging.

In `@lading/commands/publish_pipeline.py`:
- Line 41: Update the module-level LOGGER declaration to obtain the logger via
logging.getLogger(__name__) instead of the hard-coded "lading.commands.publish"
name, preserving the existing LOGGER symbol for callers.

---

Outside diff comments:
In `@lading/commands/publish.py`:
- Around line 254-272: Update the publish flow to pass effective_options, rather
than the raw options parameter, to publish_staging.prepare_workspace. Keep the
existing _PublishExecutionOptions construction and _dispatch_publication call
unchanged so effective_options remains the single authoritative configuration
throughout the run.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 85279781-11df-432c-a833-a6512aeaa9c2

📥 Commits

Reviewing files that changed from the base of the PR and between 5e5be63 and 28746b8.

📒 Files selected for processing (33)
  • docs/developers-guide.md
  • docs/lading-design.md
  • lading/commands/bump.py
  • lading/commands/bump_manifests.py
  • lading/commands/bump_pipeline.py
  • lading/commands/publish.py
  • lading/commands/publish_index_check.py
  • lading/commands/publish_manifest.py
  • lading/commands/publish_pipeline.py
  • lading/commands/publish_staging.py
  • scripts/typos_rollout.py
  • tests/unit/publish/conftest.py
  • tests/unit/publish/preflight_test_utils.py
  • tests/unit/publish/test_command_logging.py
  • tests/unit/publish/test_downgrade_metrics.py
  • tests/unit/publish/test_index_order_properties.py
  • tests/unit/publish/test_packaging.py
  • tests/unit/publish/test_phase_dispatch.py
  • tests/unit/publish/test_plan_validation.py
  • tests/unit/publish/test_run_preflight.py
  • tests/unit/publish/test_run_publish_ordering.py
  • tests/unit/publish/test_run_workspace_config.py
  • tests/unit/publish/test_snapshot_messages.py
  • tests/unit/test_bump_context_properties.py
  • tests/unit/test_bump_manifest_writing.py
  • tests/unit/test_bump_result_formatting.py
  • tests/unit/test_bump_selectors.py
  • tests/unit/test_dependency_sections.py
  • tests/unit/test_publish_formatting.py
  • tests/unit/test_publish_patch_strategy.py
  • tests/unit/test_publish_planning.py
  • tests/unit/test_publish_staging.py
  • typos.toml
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • leynos/cmd-mox (auto-detected)
  • leynos/cuprum (auto-detected)
  • leynos/shared-actions (auto-detected)

Comment thread docs/lading-design.md
Comment thread lading/commands/bump_pipeline.py Outdated
Comment thread lading/commands/publish_pipeline.py Outdated
@lodyai
lodyai Bot force-pushed the issue-170-further-extraction-needed branch from 28746b8 to e643151 Compare July 24, 2026 02:24
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@tests/unit/test_publish_staging_properties.py`:
- Around line 51-52: Add diagnostic messages to both assertions in the
path-safety test, including the relevant resolved paths and relationship being
validated so Hypothesis counterexamples identify the failure immediately.
Preserve the existing assertion conditions and behavior.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 93076247-a339-4113-a9da-2ce7c5b4e446

📥 Commits

Reviewing files that changed from the base of the PR and between 28746b8 and e643151.

📒 Files selected for processing (33)
  • docs/developers-guide.md
  • docs/lading-design.md
  • lading/commands/bump.py
  • lading/commands/bump_manifests.py
  • lading/commands/bump_pipeline.py
  • lading/commands/publish.py
  • lading/commands/publish_index_check.py
  • lading/commands/publish_manifest.py
  • lading/commands/publish_pipeline.py
  • lading/commands/publish_staging.py
  • scripts/typos_rollout.py
  • tests/unit/publish/conftest.py
  • tests/unit/publish/preflight_test_utils.py
  • tests/unit/publish/test_command_logging.py
  • tests/unit/publish/test_downgrade_metrics.py
  • tests/unit/publish/test_index_order_properties.py
  • tests/unit/publish/test_packaging.py
  • tests/unit/publish/test_phase_dispatch.py
  • tests/unit/publish/test_run_preflight.py
  • tests/unit/publish/test_run_preflight_properties.py
  • tests/unit/publish/test_run_publish_ordering.py
  • tests/unit/publish/test_run_workspace_config.py
  • tests/unit/publish/test_snapshot_messages.py
  • tests/unit/test_bump_context_properties.py
  • tests/unit/test_bump_manifest_writing.py
  • tests/unit/test_bump_result_formatting.py
  • tests/unit/test_bump_selectors.py
  • tests/unit/test_dependency_sections.py
  • tests/unit/test_publish_formatting.py
  • tests/unit/test_publish_patch_strategy.py
  • tests/unit/test_publish_planning.py
  • tests/unit/test_publish_staging.py
  • tests/unit/test_publish_staging_properties.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • leynos/cmd-mox (auto-detected)
  • leynos/cuprum (auto-detected)
  • leynos/shared-actions (auto-detected)

Comment thread tests/unit/test_publish_staging_properties.py Outdated
leynos added 7 commits July 27, 2026 14:58
Move bump orchestration, publication execution, and workspace staging into canonical delegate modules. Repoint tests to those module boundaries and document the extraction map.
Extract the dry-run phase error boundary so publication dispatch retains the
existing log and exception contract without duplicating its control flow.
Add direct coverage for packaging, publish, and successful phases.
Cover stdout fallback and stderr precedence through one shared test body,
removing duplicate setup while retaining both behavioural cases.
Remove unused staging input, test invocation helpers at their canonical
owner, and replace the no-op README staging test with behavioural coverage.
Document the coordinator, staging, pipeline, and execution boundaries.
Keep the test below the parameter-count threshold while preserving both
failure-detail scenarios and their existing assertions.
Retain the parametrized behavioural coverage for README copying and crate
non-adoption while removing its redundant one-case counterpart.
Preserve the extracted pipeline boundaries while correcting logger ownership,
centralising README error propagation, and retaining the public publish-plan
exception contract. Add generated coverage for staging-path safety and
preflight failure cut-off behaviour.
@lodyai
lodyai Bot force-pushed the issue-170-further-extraction-needed branch from e643151 to 6242ccc Compare July 27, 2026 13:00
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

Report resolved paths and the expected containment relationship when a
Hypothesis staging-path counterexample fails.

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Our agent can fix these. Install it.

Gates Passed
6 Quality Gates Passed

Absence of Expected Change Pattern

  • lading/lading/commands/bump.py is usually changed with: lading/tests/unit/test_cli.py
  • lading/lading/commands/publish.py is usually changed with: lading/tests/bdd/steps/test_publish_steps.py

Quality Gate Profile: Pay Down Tech Debt
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No quality gates enabled for this code.

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@lading/commands/publish_staging.py`:
- Around line 147-151: Update _format_preparation_summary to return a tuple
literal containing the two existing summary lines directly, removing the
intermediate lines list and tuple conversion while preserving the current text
and order.
- Around line 36-45: Update the PublishPreparation docstring’s Attributes entry
so staging_root includes its type using numpydoc syntax, changing the field
header to use “staging_root : Path” while preserving the existing description.
- Around line 169-172: Remove the stale “no cover” pragma from the
staged_root.exists() guard in the staged crate publication preparation logic,
leaving the defensive check and PublishPreparationError behavior unchanged.
- Around line 115-144: Track whether build_directory was auto-created before
calling _normalise_build_directory. In the _cleanup closure within the
preparation flow, remove the entire auto-created directory when no explicit
build_directory was supplied; otherwise retain the current staging_root-only
cleanup for caller-supplied directories.

In `@tests/unit/publish/test_phase_dispatch.py`:
- Around line 55-101: Update all three _run_dry_run_phase tests to pass
publish_pipeline.LOGGER.name to caplog.set_level instead of the hard-coded
lading.commands.publish logger name, so the tests configure the logger used by
publish_pipeline.LOGGER.exception.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b99ad2b2-561b-44ca-a120-dc0929cce2e3

📥 Commits

Reviewing files that changed from the base of the PR and between 28746b8 and a7d240c.

📒 Files selected for processing (32)
  • docs/developers-guide.md
  • docs/lading-design.md
  • lading/commands/bump.py
  • lading/commands/bump_manifests.py
  • lading/commands/bump_pipeline.py
  • lading/commands/publish.py
  • lading/commands/publish_index_check.py
  • lading/commands/publish_manifest.py
  • lading/commands/publish_pipeline.py
  • lading/commands/publish_staging.py
  • tests/unit/publish/conftest.py
  • tests/unit/publish/preflight_test_utils.py
  • tests/unit/publish/test_command_logging.py
  • tests/unit/publish/test_downgrade_metrics.py
  • tests/unit/publish/test_index_order_properties.py
  • tests/unit/publish/test_packaging.py
  • tests/unit/publish/test_phase_dispatch.py
  • tests/unit/publish/test_run_preflight.py
  • tests/unit/publish/test_run_preflight_properties.py
  • tests/unit/publish/test_run_publish_ordering.py
  • tests/unit/publish/test_run_workspace_config.py
  • tests/unit/publish/test_snapshot_messages.py
  • tests/unit/test_bump_context_properties.py
  • tests/unit/test_bump_manifest_writing.py
  • tests/unit/test_bump_result_formatting.py
  • tests/unit/test_bump_selectors.py
  • tests/unit/test_dependency_sections.py
  • tests/unit/test_publish_formatting.py
  • tests/unit/test_publish_patch_strategy.py
  • tests/unit/test_publish_planning.py
  • tests/unit/test_publish_staging.py
  • tests/unit/test_publish_staging_properties.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • leynos/cmd-mox (auto-detected)
  • leynos/cuprum (auto-detected)
  • leynos/shared-actions (auto-detected)

Comment on lines +36 to +45
class PublishPreparation:
"""Details about the staged workspace copy.

Attributes
----------
staging_root:
Root of the copied workspace used by publication commands.
"""

staging_root: Path

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix the numpydoc Attributes entry to include the type.

staging_root: should read staging_root : Path per the project's numpydoc convention.

✏️ Proposed fix
     Attributes
     ----------
-    staging_root:
+    staging_root : Path
         Root of the copied workspace used by publication commands.

As per path instructions, "Docstrings must follow the numpy style guide."

📝 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
class PublishPreparation:
"""Details about the staged workspace copy.
Attributes
----------
staging_root:
Root of the copied workspace used by publication commands.
"""
staging_root: Path
class PublishPreparation:
"""Details about the staged workspace copy.
Attributes
----------
staging_root : Path
Root of the copied workspace used by publication commands.
"""
staging_root: Path
🤖 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 `@lading/commands/publish_staging.py` around lines 36 - 45, Update the
PublishPreparation docstring’s Attributes entry so staging_root includes its
type using numpydoc syntax, changing the field header to use “staging_root :
Path” while preserving the existing description.

Source: Path instructions

Comment on lines +115 to +144
if options is None:
from lading.commands.publish import PublishOptions

active_options = PublishOptions()
else:
active_options = options
build_directory = _normalise_build_directory(
plan.workspace_root, active_options.build_directory
)
LOGGER.info(
"Preparing staged workspace for publication under %s",
build_directory,
)
staging_root = _copy_workspace_tree(
plan.workspace_root,
build_directory,
preserve_symlinks=active_options.preserve_symlinks,
)
LOGGER.info("Staged workspace created at %s", staging_root)
LOGGER.info("Workspace README staging skipped; handled by lading bump")
preparation = PublishPreparation(staging_root=staging_root)
if active_options.cleanup:
cleanup_target = staging_root

def _cleanup() -> None:
"""Remove the staged workspace tree on process exit."""
shutil.rmtree(cleanup_target, ignore_errors=True)

atexit.register(_cleanup)
return preparation

Copy link
Copy Markdown

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

Clean up the whole auto-created temp directory, not just the staged copy.

When build_directory is None, _normalise_build_directory mints a fresh tempfile.mkdtemp directory that lading owns exclusively — nothing else can be in it. The _cleanup closure only removes staging_root (the <build_directory>/<workspace_name> subtree), so the empty lading-publish-* parent directory is left behind on every dry-run invocation with cleanup=True and no explicit build_directory. Track whether the directory was auto-created and sweep the whole thing in that case, while still preserving caller-supplied directories as intended.

🧹 Proposed fix
-    if options is None:
-        from lading.commands.publish import PublishOptions
-
-        active_options = PublishOptions()
-    else:
-        active_options = options
+    if options is None:
+        from lading.commands.publish import PublishOptions
+
+        active_options = PublishOptions()
+    else:
+        active_options = options
+    owns_build_directory = active_options.build_directory is None
     build_directory = _normalise_build_directory(
         plan.workspace_root, active_options.build_directory
     )
     LOGGER.info(
         "Preparing staged workspace for publication under %s",
         build_directory,
     )
     staging_root = _copy_workspace_tree(
         plan.workspace_root,
         build_directory,
         preserve_symlinks=active_options.preserve_symlinks,
     )
     LOGGER.info("Staged workspace created at %s", staging_root)
     LOGGER.info("Workspace README staging skipped; handled by lading bump")
     preparation = PublishPreparation(staging_root=staging_root)
     if active_options.cleanup:
-        cleanup_target = staging_root
+        cleanup_target = build_directory if owns_build_directory else staging_root
 
         def _cleanup() -> None:
             """Remove the staged workspace tree on process exit."""
             shutil.rmtree(cleanup_target, ignore_errors=True)
 
         atexit.register(_cleanup)
     return preparation
📝 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
if options is None:
from lading.commands.publish import PublishOptions
active_options = PublishOptions()
else:
active_options = options
build_directory = _normalise_build_directory(
plan.workspace_root, active_options.build_directory
)
LOGGER.info(
"Preparing staged workspace for publication under %s",
build_directory,
)
staging_root = _copy_workspace_tree(
plan.workspace_root,
build_directory,
preserve_symlinks=active_options.preserve_symlinks,
)
LOGGER.info("Staged workspace created at %s", staging_root)
LOGGER.info("Workspace README staging skipped; handled by lading bump")
preparation = PublishPreparation(staging_root=staging_root)
if active_options.cleanup:
cleanup_target = staging_root
def _cleanup() -> None:
"""Remove the staged workspace tree on process exit."""
shutil.rmtree(cleanup_target, ignore_errors=True)
atexit.register(_cleanup)
return preparation
if options is None:
from lading.commands.publish import PublishOptions
active_options = PublishOptions()
else:
active_options = options
owns_build_directory = active_options.build_directory is None
build_directory = _normalise_build_directory(
plan.workspace_root, active_options.build_directory
)
LOGGER.info(
"Preparing staged workspace for publication under %s",
build_directory,
)
staging_root = _copy_workspace_tree(
plan.workspace_root,
build_directory,
preserve_symlinks=active_options.preserve_symlinks,
)
LOGGER.info("Staged workspace created at %s", staging_root)
LOGGER.info("Workspace README staging skipped; handled by lading bump")
preparation = PublishPreparation(staging_root=staging_root)
if active_options.cleanup:
cleanup_target = build_directory if owns_build_directory else staging_root
def _cleanup() -> None:
"""Remove the staged workspace tree on process exit."""
shutil.rmtree(cleanup_target, ignore_errors=True)
atexit.register(_cleanup)
return preparation
🤖 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 `@lading/commands/publish_staging.py` around lines 115 - 144, Track whether
build_directory was auto-created before calling _normalise_build_directory. In
the _cleanup closure within the preparation flow, remove the entire auto-created
directory when no explicit build_directory was supplied; otherwise retain the
current staging_root-only cleanup for caller-supplied directories.

Comment on lines +147 to +151
def _format_preparation_summary(preparation: PublishPreparation) -> tuple[str, ...]:
"""Return formatted summary lines for staging results."""
lines = [f"Staged workspace at: {preparation.staging_root}"]
lines.append("Workspace READMEs are handled by lading bump.")
return tuple(lines)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify to a tuple literal.

Building a list then wrapping it in tuple() is unnecessary for two static lines.

✏️ Proposed fix
 def _format_preparation_summary(preparation: PublishPreparation) -> tuple[str, ...]:
     """Return formatted summary lines for staging results."""
-    lines = [f"Staged workspace at: {preparation.staging_root}"]
-    lines.append("Workspace READMEs are handled by lading bump.")
-    return tuple(lines)
+    return (
+        f"Staged workspace at: {preparation.staging_root}",
+        "Workspace READMEs are handled by lading bump.",
+    )
📝 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
def _format_preparation_summary(preparation: PublishPreparation) -> tuple[str, ...]:
"""Return formatted summary lines for staging results."""
lines = [f"Staged workspace at: {preparation.staging_root}"]
lines.append("Workspace READMEs are handled by lading bump.")
return tuple(lines)
def _format_preparation_summary(preparation: PublishPreparation) -> tuple[str, ...]:
"""Return formatted summary lines for staging results."""
return (
f"Staged workspace at: {preparation.staging_root}",
"Workspace READMEs are handled by lading bump.",
)
🤖 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 `@lading/commands/publish_staging.py` around lines 147 - 151, Update
_format_preparation_summary to return a tuple literal containing the two
existing summary lines directly, removing the intermediate lines list and tuple
conversion while preserving the current text and order.

Comment on lines +169 to +172
staged_root = staging_root / relative_root
if not staged_root.exists(): # pragma: no cover - defensive guard
message = f"Staged crate root not found for {crate.name!r}: {staged_root}"
raise PublishPreparationError(message)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the stale no cover pragma — this branch is now exercised.

test_execute_live_publication_pipeline_wraps_preparation_errors removes a staged crate directory and drives this exact "staged crate root not found" branch, so the exclusion comment no longer reflects reality and would silently hide a coverage regression if that test is ever removed.

🧹 Proposed fix
     staged_root = staging_root / relative_root
-    if not staged_root.exists():  # pragma: no cover - defensive guard
+    if not staged_root.exists():
         message = f"Staged crate root not found for {crate.name!r}: {staged_root}"
         raise PublishPreparationError(message)
📝 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
staged_root = staging_root / relative_root
if not staged_root.exists(): # pragma: no cover - defensive guard
message = f"Staged crate root not found for {crate.name!r}: {staged_root}"
raise PublishPreparationError(message)
staged_root = staging_root / relative_root
if not staged_root.exists():
message = f"Staged crate root not found for {crate.name!r}: {staged_root}"
raise PublishPreparationError(message)
🤖 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 `@lading/commands/publish_staging.py` around lines 169 - 172, Remove the stale
“no cover” pragma from the staged_root.exists() guard in the staged crate
publication preparation logic, leaving the defensive check and
PublishPreparationError behavior unchanged.

Comment on lines +55 to +101
def test_run_dry_run_phase_normalises_packaging_preparation_failure(
caplog: pytest.LogCaptureFixture,
) -> None:
"""A packaging preparation error becomes a preflight error."""
caplog.set_level(logging.ERROR, logger="lading.commands.publish")
failure = publish_pipeline.PublishPreparationError("staging failed")

with pytest.raises(
publish.PublishPreflightError, match="staging failed"
) as excinfo:
publish_pipeline._run_dry_run_phase(
"packaging",
lambda: (_ for _ in ()).throw(failure),
)

assert excinfo.value.__cause__ is failure
assert caplog.messages == ["Dry-run pipeline: packaging phase failed"]


def test_run_dry_run_phase_reraises_publish_preflight_failure(
caplog: pytest.LogCaptureFixture,
) -> None:
"""A publish preflight error retains its type and traceback."""
caplog.set_level(logging.ERROR, logger="lading.commands.publish")
failure = publish.PublishPreflightError("publish failed")

with pytest.raises(publish.PublishPreflightError) as excinfo:
publish_pipeline._run_dry_run_phase(
"publish",
lambda: (_ for _ in ()).throw(failure),
)

assert excinfo.value is failure
assert caplog.messages == ["Dry-run pipeline: publish phase failed"]


def test_run_dry_run_phase_succeeds_without_logging(
caplog: pytest.LogCaptureFixture,
) -> None:
"""A successful phase invokes its action without emitting an error."""
caplog.set_level(logging.ERROR, logger="lading.commands.publish")
calls: list[str] = []

publish_pipeline._run_dry_run_phase("packaging", lambda: calls.append("ran"))

assert calls == ["ran"]
assert caplog.messages == []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Point caplog.set_level at the actual pipeline logger.

All three new _run_dry_run_phase tests call caplog.set_level(logging.ERROR, logger="lading.commands.publish"), but publish_pipeline.LOGGER is logging.getLogger(__name__), i.e. "lading.commands.publish_pipeline" — a sibling logger, not the one _run_dry_run_phase actually writes to. The tests pass today only because Python's root logger defaults to WARNING and LOGGER.exception fires at ERROR, so the level check is a no-op. Point the calls at publish_pipeline.LOGGER.name, matching the pattern already used correctly elsewhere in this cohort (e.g. test_snapshot_messages.py's test_pipeline_info_log_snapshot).

🛠️ Proposed fix
 def test_run_dry_run_phase_normalises_packaging_preparation_failure(
     caplog: pytest.LogCaptureFixture,
 ) -> None:
     """A packaging preparation error becomes a preflight error."""
-    caplog.set_level(logging.ERROR, logger="lading.commands.publish")
+    caplog.set_level(logging.ERROR, logger=publish_pipeline.LOGGER.name)
     failure = publish_pipeline.PublishPreparationError("staging failed")
@@
 def test_run_dry_run_phase_reraises_publish_preflight_failure(
     caplog: pytest.LogCaptureFixture,
 ) -> None:
     """A publish preflight error retains its type and traceback."""
-    caplog.set_level(logging.ERROR, logger="lading.commands.publish")
+    caplog.set_level(logging.ERROR, logger=publish_pipeline.LOGGER.name)
     failure = publish.PublishPreflightError("publish failed")
@@
 def test_run_dry_run_phase_succeeds_without_logging(
     caplog: pytest.LogCaptureFixture,
 ) -> None:
     """A successful phase invokes its action without emitting an error."""
-    caplog.set_level(logging.ERROR, logger="lading.commands.publish")
+    caplog.set_level(logging.ERROR, logger=publish_pipeline.LOGGER.name)
     calls: list[str] = []
📝 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
def test_run_dry_run_phase_normalises_packaging_preparation_failure(
caplog: pytest.LogCaptureFixture,
) -> None:
"""A packaging preparation error becomes a preflight error."""
caplog.set_level(logging.ERROR, logger="lading.commands.publish")
failure = publish_pipeline.PublishPreparationError("staging failed")
with pytest.raises(
publish.PublishPreflightError, match="staging failed"
) as excinfo:
publish_pipeline._run_dry_run_phase(
"packaging",
lambda: (_ for _ in ()).throw(failure),
)
assert excinfo.value.__cause__ is failure
assert caplog.messages == ["Dry-run pipeline: packaging phase failed"]
def test_run_dry_run_phase_reraises_publish_preflight_failure(
caplog: pytest.LogCaptureFixture,
) -> None:
"""A publish preflight error retains its type and traceback."""
caplog.set_level(logging.ERROR, logger="lading.commands.publish")
failure = publish.PublishPreflightError("publish failed")
with pytest.raises(publish.PublishPreflightError) as excinfo:
publish_pipeline._run_dry_run_phase(
"publish",
lambda: (_ for _ in ()).throw(failure),
)
assert excinfo.value is failure
assert caplog.messages == ["Dry-run pipeline: publish phase failed"]
def test_run_dry_run_phase_succeeds_without_logging(
caplog: pytest.LogCaptureFixture,
) -> None:
"""A successful phase invokes its action without emitting an error."""
caplog.set_level(logging.ERROR, logger="lading.commands.publish")
calls: list[str] = []
publish_pipeline._run_dry_run_phase("packaging", lambda: calls.append("ran"))
assert calls == ["ran"]
assert caplog.messages == []
def test_run_dry_run_phase_normalises_packaging_preparation_failure(
caplog: pytest.LogCaptureFixture,
) -> None:
"""A packaging preparation error becomes a preflight error."""
caplog.set_level(logging.ERROR, logger=publish_pipeline.LOGGER.name)
failure = publish_pipeline.PublishPreparationError("staging failed")
with pytest.raises(
publish.PublishPreflightError, match="staging failed"
) as excinfo:
publish_pipeline._run_dry_run_phase(
"packaging",
lambda: (_ for _ in ()).throw(failure),
)
assert excinfo.value.__cause__ is failure
assert caplog.messages == ["Dry-run pipeline: packaging phase failed"]
def test_run_dry_run_phase_reraises_publish_preflight_failure(
caplog: pytest.LogCaptureFixture,
) -> None:
"""A publish preflight error retains its type and traceback."""
caplog.set_level(logging.ERROR, logger=publish_pipeline.LOGGER.name)
failure = publish.PublishPreflightError("publish failed")
with pytest.raises(publish.PublishPreflightError) as excinfo:
publish_pipeline._run_dry_run_phase(
"publish",
lambda: (_ for _ in ()).throw(failure),
)
assert excinfo.value is failure
assert caplog.messages == ["Dry-run pipeline: publish phase failed"]
def test_run_dry_run_phase_succeeds_without_logging(
caplog: pytest.LogCaptureFixture,
) -> None:
"""A successful phase invokes its action without emitting an error."""
caplog.set_level(logging.ERROR, logger=publish_pipeline.LOGGER.name)
calls: list[str] = []
publish_pipeline._run_dry_run_phase("packaging", lambda: calls.append("ran"))
assert calls == ["ran"]
assert caplog.messages == []
🤖 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 `@tests/unit/publish/test_phase_dispatch.py` around lines 55 - 101, Update all
three _run_dry_run_phase tests to pass publish_pipeline.LOGGER.name to
caplog.set_level instead of the hard-coded lading.commands.publish logger name,
so the tests configure the logger used by publish_pipeline.LOGGER.exception.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Further extraction needed: bump.py and publish.py still exceed 400-line guideline

2 participants