Regenerate discovered nested lockfiles during lading bump (regenerate-lockfiles) - #160
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
🔗 Linked repositories identifiedCodeRabbit considers these linked repositories for cross-repo context during reviews:
Summary
Related references
Walkthrough
ChangesNested lockfile regeneration
Pinned typechecking toolchain
Publish validation control flow
Documentation wording cleanup
Sequence Diagram(s)sequenceDiagram
participant User
participant LadingBump
participant Git
participant Cargo
User->>LadingBump: run lading bump
LadingBump->>Git: discover tracked Cargo.lock files
Git-->>LadingBump: return lockfile paths
LadingBump->>Cargo: update workspace per merged manifest
Cargo-->>LadingBump: return success or failure
LadingBump-->>User: list refreshed lockfiles or repair commands
Possibly related PRs
Suggested reviewers: Poem
Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (1 error, 1 warning, 3 inconclusive)
✅ Passed checks (15 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
fbc0a6d to
2509734
Compare
2509734 to
1198e46
Compare
84f36e7 to
27ca243
Compare
|
@coderabbitai resume |
✅ Action performedReviews resumed. |
|
@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 (4 warnings)
|
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
58bb2a7 to
37be73c
Compare
There was a problem hiding this comment.
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 `@lading/commands/bump_lockfile_regeneration.py`:
- Around line 183-189: Update _regeneration_failure_cause to use structural
pattern matching on error.__cause__ instead of the current isinstance chain,
preserving the existing CommandSpawnError precedence, ValueError mapping, and
cargo_exit fallback.
In `@tests/unit/test_bump_lockfile_regeneration_metrics.py`:
- Around line 107-125: The parametrized test
test_regenerate_lockfiles_records_failure_cause currently omits the runner_value
failure branch. Add a runner helper that raises ValueError and include it with
expected_cause set to runner_value, ensuring _regeneration_failure_cause() is
verified for this operational failure.
🪄 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: 31d3af26-7bb7-425d-a779-539c97e818d0
📒 Files selected for processing (9)
docs/developers-guide.mdlading/commands/bump_lockfile_manifests.pylading/commands/bump_lockfile_regeneration.pylading/commands/bump_lockfiles.pylading/commands/lockfile.pytests/unit/test_bump_lockfile_manifest_merge.pytests/unit/test_bump_lockfile_rebuild.pytests/unit/test_bump_lockfile_regeneration_metrics.pytests/unit/test_lockfile.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)
Use structural matching for bounded regeneration failure causes and cover runner value failures. Synchronize the completed ExecPlan with the final module sizes, public interfaces, observability contracts, and portable repository path.
|
@coderabbitai resume |
✅ Action performedReviews resumed. |
There was a problem hiding this comment.
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 `@docs/execplans/regenerate-lockfiles.md`:
- Around line 346-351: Update the documentation sentence describing
discover_tracked_lockfiles to say “In a non-Git directory, it logs”, preserving
the existing behavior description and applying the required en-GB-oxendict
grammar.
In `@tests/unit/test_bump_lockfile_regeneration_metrics.py`:
- Around line 78-91: Update every bare assert at
tests/unit/test_bump_lockfile_regeneration_metrics.py:78-91, 108-115, and
179-194, plus tests/unit/test_lockfile.py:663-665, to use assert …, "focused
diagnostic message". Add messages for the returned lockfiles, success/duration
checks, expected failure-cause counter, partial-success and Cargo-exit counters,
discovered paths, suppressed counter, and suppressed informational log; preserve
all existing semantic checks.
🪄 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: 9474ed13-45aa-442c-92d4-eebb6371b3d0
📒 Files selected for processing (10)
docs/developers-guide.mddocs/execplans/regenerate-lockfiles.mdlading/commands/bump_lockfile_manifests.pylading/commands/bump_lockfile_regeneration.pylading/commands/bump_lockfiles.pylading/commands/lockfile.pytests/unit/test_bump_lockfile_manifest_merge.pytests/unit/test_bump_lockfile_rebuild.pytests/unit/test_bump_lockfile_regeneration_metrics.pytests/unit/test_lockfile.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)
Add focused assertion messages for lockfile observability contracts and correct the completed ExecPlan grammar. Synchronize Ruff 0.15.21 across the project gates and adjust the property summaries required by that version.
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai please create a new GitHub issue for the following: User-Facing Documentation |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '=== Matching open issues ==='
gh issue list \
--repo leynos/lading \
--state open \
--limit 100 \
--search 'migration 0.3.0 lockfile discovery dry-run' \
--json number,title,url,body \
--jq '.[] | {number,title,url,body}' || true
echo '=== PR metadata ==='
gh pr view 160 \
--repo leynos/lading \
--json url,title \
--jq '{url,title}'
echo '=== Requesting comment permalink ==='
gh api 'repos/leynos/lading/issues/160/comments?per_page=100' \
--jq '.[] | select(.user.login == "leynos") | select(.body | contains("please create a new GitHub issue for the following")) | {url: .html_url, body: .body}' || trueLength of output: 1626 🏁 Script executed: #!/bin/bash
set -euo pipefail
body=$(cat <<'EOF'
## Summary
Add a 0.3.0 migration document or a 0.3.0 migration section for the lockfile regeneration changes delivered by PR `#160`.
## Rationale
The user guide documents the behaviour in detail. Users upgrading to the next release need a migration entry that signposts the changed lockfile behaviour and the available recovery path.
## Affected areas
- Add the migration document or section in the repository's release or migration documentation.
- Link to the existing user-guide material when that avoids duplicated operational detail.
## Required content
Document all of the following:
1. `lading bump` discovers Git-tracked `Cargo.lock` files and includes their adjacent `Cargo.toml` manifests.
2. Dry-run discovers and reports the same lockfile set without modifying files.
3. In a non-Git workspace, discovery falls back to `bump.lockfile_manifests` and emits a warning.
4. `bump.lockfile_manifests` extends coverage for untracked lockfiles and nested lockfiles in non-Git workspaces.
5. `lading bump --no-rebuild-lockfiles` is the regeneration escape hatch. Explain that users must regenerate stale lockfiles before publishing when they use it or modify manifests directly.
## Acceptance criteria
- Add a 0.3.0 migration document or migration section.
- Explain tracked-lockfile discovery, dry-run output, configuration fallback, and the regeneration escape hatch.
- Keep the content consistent with `docs/users-guide.md`.
- Add navigation or an index link when the documentation structure requires one.
- Validate documentation formatting and links with the repository's documented validation targets.
## Backlinks
- Source PR: https://github.com/leynos/lading/pull/160
- Source comment: https://github.com/leynos/lading/pull/160#issuecomment-5150893389
- Requested by: `@leynos`
EOF
)
gh issue create \
--repo leynos/lading \
--title 'Document 0.3.0 migration for lockfile discovery and regeneration' \
--assignee leynos \
--body "$body"Length of output: 2223
The issue includes the required migration content, acceptance criteria, and backlinks to PR You are interacting with an AI system. |
Repair artefacts from rebasing the DOC docstring work onto main's lockfile refactor (#160): - restore the WorkspaceGraph and CallTrackingRunner class docstrings that the entity merge dropped; - drop the "Returns: None" blocks that DOC202 rejects on None-returning steps and fixtures, keeping the informative prose as Notes; - suppress DOC502 at the single propagated-raise site in the new bump_lockfile_paths.resolve_lockfile_paths, using a function-level noqa rather than a file-wide exemption; - re-apply the partial-update wording fix in its new home: the single-versus-aggregated decision keys on how many manifests were attempted, matching _raise_aggregated_failure's len(manifests) branch, not on how many lockfiles were regenerated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
discover_tracked_lockfiles hid a non-git workspace behind a warning and a silent empty tuple, so callers could not distinguish "no tracked lockfiles" from "discovery never ran". Filesystem access was also mixed into the function rather than confined to a port. Raise a typed NotAGitRepositoryError (subclass of LockfileDiscoveryError) for non-git workspaces and keep LockfileDiscoveryError for other git failures. The skip policy moves to the caller: publish pre-flight catches NotAGitRepositoryError, warns, and continues, preserving existing operator behaviour while making the condition explicit at the API. Filesystem access is now documented as confined to the injected manifest_exists port and git access to the injected runner; the function performs no direct I/O of its own. Replace the silent-skip unit test with a typed-exception assertion, add a caller-policy test for the pre-flight skip, and add integration tests that exercise discovery against real git repositories in temporary directories through the real subprocess runner (tracked versus untracked lockfiles, target/ exclusion, manifest adjacency, and the non-git error). Extract the lockfile freshness policy (_validate_lockfile_freshness, _collect_stale_lockfiles, _build_stale_lockfile_message) into the colocated module publish_lockfile_preflight. Adding the skip branch took publish_preflight past the repository's 400-line file limit, and the freshness policy is a coherent unit: it depends only on the LockfileInspectionRepository port, while publish_preflight retains the cargo and git command orchestration and stays the composition root that binds the adapter. Apply the same caller-owned skip policy on the bump side. Since #160, bump discovers tracked lockfiles through bump_lockfile_manifests.merge_discovered_manifests, whose documented contract is that a non-git workspace returns the configured manifests unchanged. That relied on discovery's silent empty tuple, so the typed error would otherwise abort `lading bump` outside git control; the merge helper now catches NotAGitRepositoryError, warns, and returns the configured tuple. Closes #79
* Truncate private helper docstrings (#162) Keep private helper documentation to the required single-line summaries and remove structured sections intended only for public interfaces. * Normalize public bump docstrings (#162) Document the public TOML update helpers with complete NumPy-style parameter and return sections. * Normalize preflight fixture docstring (#162) Convert the remaining mixed Google and NumPy-style fixture docstring to the repository's structured public-interface format. * Add runnable docstring examples (#162) Show representative update results for the public TOML helpers and the preflight fixture's compiletest dependency override. * Enforce DOC docstring rules across the codebase Add the pydoclint `DOC` rule group to the Ruff lint selection so that docstrings must document returns, yields, and directly-raised exceptions, complementing the existing NumPy `D` convention and preview mode already configured. Enabling the rule surfaced missing sections throughout `lading/` and the test suite. Add NumPy-style `Returns`, `Raises`, and `Yields` sections to the affected functions, fixtures, and helpers, and remove the handful of extraneous `Returns`/`Raises` entries that documented exceptions raised only by delegated calls. Docstrings and the single config line are the only changes; no runtime logic was altered. Relates to #162. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Scope DOC completeness rules to public interfaces Enable ``ignore-one-line-docstrings`` for pydoclint so the DOC rule group enforces Returns/Raises/Yields completeness on multi-line public docstrings while exempting the concise single-line summaries the project convention uses for private helpers. This reconciles DOC enforcement with the established private-helper docstring style. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Apply docstring review feedback across the tree Action CodeRabbit review findings for the DOC docstring work: collapse private-helper docstrings to concise single-line summaries (now DOC-exempt via ignore-one-line-docstrings), complete public interface contracts with typed Parameters/Returns/Examples, and correct Raises/Returns wording. Findings that would violate the enforced DOC rules (renaming factory raise-callables to concrete types, documenting delegated/propagated raises) are intentionally not applied. Also fix counter_value to default missing keys to zero, and refactor duplicated E2E/BDD step setup and invocation-recording helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Apply second round of docstring review feedback Action the follow-up CodeRabbit findings: clarify lockfile empty-tuple docs, add runnable public Examples (cli, config, cmd_mox_runner), complete/collapse docstrings per the public/private convention, remove the create_nontrivial_workspace Examples block (CodeScene Large Method), and consolidate redundant unit-test fixture aliases. Findings that would violate the enforced DOC rules (propagated-exception Raises on transpose_readme_to_crate/optional_mapping/subprocess helpers, factory raise-callable renames) are intentionally skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Trim publish.run docstring Examples for CodeScene Remove only the Examples section from the public ``run`` docstring to resolve CodeScene's "Large Method" diagnostic. The summary, Parameters, and Returns sections are unchanged, and no production logic, signature, delegation boundary (``_dispatch_publication``), or test is touched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Extract fixture-crate creation from create_nontrivial_workspace Move the core/utils/app crate-creation block into a new private helper `_create_fixture_crates`, returning the manifest-path mapping, to resolve CodeScene's Large Method diagnostic through a meaningful extraction. Behaviour, dependency fixture text, crate names, and the returned NonTrivialWorkspace are unchanged; the public docstring is untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document the LadingError contract in TOML-coercion Raises Replace the misleading `_reject` entry in the NumPy-style Raises headings of expect_mapping, string_mapping, expect_string, boolean, and non_negative_int with `LadingError` — the base of the caller-injected `error` subclass that `_reject` constructs for callers to raise. Each validation-specific failure description is preserved, and the private `_validate_string_pair` helper keeps its one-line docstring. pydoclint (DOC501/DOC502) keys the Raises type token on the syntactic raise-callable (`_reject`) and cannot resolve it to the real exception, so a scoped per-file-ignore for those two rules on the two coercion modules lets the docstrings state the accurate contract, mirroring the existing `ignore-one-line-docstrings` DOC reconciliation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document _prepare_cmd_mox_context Returns and Raises Expand the private helper's docstring with NumPy-style Returns and Raises sections: it returns the validated IPC timeout in seconds and raises CmdMoxError when CMOX_IPC_SOCKET is unset (raised directly) or when CMOX_IPC_TIMEOUT is unparseable, non-finite, or non-positive (propagated from the reachable _resolve_cmd_mox_timeout call). Documentation-only; CmdMoxError is raised directly in the body, so the DOC rules pass without suppression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Complete make_plan_factory fixture docstring Add a NumPy-style Parameters section documenting the make_crate fixture/factory and its role, clarify the Returns section describing the returned plan-building callable, and add a runnable Examples section that invokes the returned factory (via __wrapped__ with a lightweight stand-in) and shows the resulting plan's publishable crate names. Signature and behaviour are unchanged; documentation-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document _process_readme_transposition Returns Add a NumPy-style Returns section for the returned set[Path]: the crate README paths that were written, or reported as would be written during a dry run. Expanding the docstring surfaces the direct re-raise of ReadmeTranspositionError (bare `raise` in the except block), so a matching Raises section is added to satisfy DOC501. Documentation-only; signature and runtime behaviour unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document E2E helper contracts and true exception type Add NumPy-style Parameters sections to the E2EExpectationError factory methods (unsupported_fixture_version, dependency_entry_not_string, args_prefix_mismatch) and to run_cli, extract_dependency_requirement, find_staging_root, and filter_records; add Parameters to given_cargo_commands_stubbed. In extract_dependency_requirement, find_staging_root, and given_nontrivial_workspace_in_git_repo, name the raised type as E2EExpectationError (constructed by the relevant factory classmethod), keeping the factory methods documented as factories. Those callers raise via factory classmethods, so pydoclint keys DOC501/DOC502 on the method name; a scoped per-file-ignore for the two E2E files lets the docstrings state the true E2EExpectationError contract, mirroring the toml_coerce reconciliation. Documentation-only; signatures and runtime behaviour unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document propagated exception contracts Add NumPy-style Raises sections documenting the exceptions callers must handle: subprocess_runner (ValueError, CommandSpawnError), invoke_via_subprocess (CommandSpawnError), write_to_sink (OSError, ValueError), transpose_readme_to_crate (ReadmeTranspositionError), and optional_mapping (_reject factory). These functions propagate their exceptions from delegated helpers rather than raising directly, so pydoclint (DOC501/DOC502) — which only tracks direct raises — cannot express the contract. Scope those two rules off for subprocess_runner.py and bump_readme.py (optional_mapping's module already carries the ignore). Documentation-only; behaviour unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document the true exception types in sequence/metadata Raises Replace the factory raise-callable names in NumPy-style Raises headings with the real exception classes: expect_sequence, validate_string_sequence, string_tuple, _validate_matrix_entry, and string_matrix now document LadingError (the base of the injected `error` subclass that `_reject` constructs and the function raises), and _parse_cargo_metadata documents CargoMetadataParseError while preserving the invalid-JSON and non-object-payload conditions. pydoclint keys DOC501/DOC502 on the syntactic raise-callable (`_reject`, the CargoMetadataParseError factory classmethods) and cannot resolve them to the real exception, so a scoped per-file-ignore on the two modules lets the docstrings state the true contract, mirroring the earlier toml_coerce reconciliation. Documentation-only; runtime behaviour unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Align docstrings with the updated AGENTS.md guidance Action the third round of review findings under the new docstring convention (AGENTS.md, commit e731d1f): public APIs get comprehensive NumPy-style documentation with runnable examples where appropriate, and private helpers collapse to concise single-line summaries, keeping structured sections only where they document non-obvious behaviour. Public surfaces: typed Parameters and runnable Examples across the toml_coerce coercion helpers, runner.coerce_text, config.use_configuration, the E2E helper factories, and the workspace-metadata steps; corrected the cli bump/publish examples to assert stable substrings of the delegated string contracts rather than outdated literals; documented the publish workflow's propagated exception contract; and fixed bump.run's Returns to cover dry-run previews. Private helpers: collapsed graph_build, toml_coerce, bump, cmd_mox, and test-helper docstrings to single lines, retaining the CrossHair contracts in bump_output and the typed-wrapper rationale in _coercion where the prose documents a genuine local constraint. Removed test Examples that only restated test logic, per the updated test-documentation rule. Scoped DOC per-file-ignores cover the modules whose findings pydoclint cannot express: propagated exceptions (it tracks only direct raises) and bump_output's CrossHair contracts, which keep private docstrings multi-line where the convention omits Returns. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Trim plan_publication docstring Examples for CodeScene Remove only the Examples section from plan_publication's docstring to resolve CodeScene's "Large Method" diagnostic. The summary and the Parameters, Returns, and Raises sections are unchanged, and no executable logic is touched: the coordinator flow still delegates to _categorize_crates, _resolve_configured_order, and _resolve_topological_order, with the same signature, PublishPlan construction, and exception behaviour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Correct and complete docstring contracts Fix inaccurate documented contracts and fill gaps found in review: - bump.run: the dry-run example showed a fabricated summary; use the real "Dry run; would update version to ..." prefix that _format_header emits. - publish.run: packaging failures raise PublishPreflightError, not PublishError, and PublishPreparationError also covers staged-manifest patch preparation. - cmd_mox_runner: add a structured Raises section (CmdMoxError, ValueError) and trim the now-redundant Notes prose; collapse the private _prepare_cmd_mox_context to a single-line summary. - config: document use_configuration's parameter and add a runnable current_configuration example. - metadata: collapse three private helpers, add runnable examples to both CargoMetadataParseError factories, and complete load_cargo_metadata with typed parameters, an example, and its full propagated Raises set. - subprocess_runner: type every public Parameters entry and add examples; relay_stream now states in prose that it returns None and appends to buffer (DOC202 forbids a Returns section there). - toml_coerce: note that _validate_string_pair rejects non-string keys and values, document is_non_empty_sequence, collapse _validate_matrix_entry. - bdd steps: examples now exercise the documented step functions and define the names they reference. Documentation-only: the AST of every changed file is unchanged once docstrings are stripped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Document step failure modes and tidy fixture examples Add Raises sections to the publish package-ordering, dry-run, and live-publish steps covering both the missing-invocation failure from _get_required_invocations and the downstream ordering and flag assertions, suppressing DOC502 per function since those raises are propagated. Extend the five cargo::test pre-flight steps' existing Raises wording to name the missing-invocation cause too. Rewrite the six publish conftest doctests to use TemporaryDirectory context managers instead of tempfile.mkdtemp(), keeping each example's operations inside the context so the directory is removed automatically. Documentation-only; every changed file's AST is unchanged once docstrings are stripped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Reconcile docstring work with the lockfile module split Repair artefacts from rebasing the DOC docstring work onto main's lockfile refactor (#160): - restore the WorkspaceGraph and CallTrackingRunner class docstrings that the entity merge dropped; - drop the "Returns: None" blocks that DOC202 rejects on None-returning steps and fixtures, keeping the informative prose as Notes; - suppress DOC502 at the single propagated-raise site in the new bump_lockfile_paths.resolve_lockfile_paths, using a function-level noqa rather than a file-wide exemption; - re-apply the partial-update wording fix in its new home: the single-versus-aggregated decision keys on how many manifests were attempted, matching _raise_aggregated_failure's len(manifests) branch, not on how many lockfiles were regenerated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Restore WorkspaceGraph fields dropped by the entity merge The rebase onto main's lockfile refactor silently dropped the workspace_root and crates field declarations from WorkspaceGraph along with its class docstring, so every attribute access failed typecheck with "Object of type WorkspaceGraph has no attribute crates". Restore both fields. An AST audit across all 65 changed files confirms no other class lost fields and no functions or classes were lost, beyond this branch's two intentional removals (the four specialised invocation getters replaced by _get_required_invocations, and the redundant fixture aliases). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Complete exception contracts and correct docstring inaccuracies Document the exceptions callers must handle, and fix documentation that disagreed with the code: - config: add ConfigurationError Raises to the five from_mapping classmethods and load_configuration, extend load_from_loader's entry to cover validation failures, and inline the single-use loader local per .rules/python-return.md R504. - cmd_mox_runner: document CommandSpawnError propagated from passthrough spawning. - load_workspace: document the four cargo-metadata errors and WorkspaceModelError it propagates. - metrics.duration_stats: the empty-stats fallback applies when the (name, labels) series is unrecorded, not when the name is absent. - bump_lockfile_regeneration: the Raises text now keys on how many manifests were attempted, matching len(manifests) == 1. - _update_manifest: the summary now covers dependency sections too. - lockfile, publish_plan.render_section, subprocess_runner: type the Parameters entries; widen normalise_environment to Mapping[str, object] since it stringifies every value (its own doctest already passed an int). - toml_coerce doctests now use LadingError rather than a built-in ValueError, matching _ErrorType and the Raises documentation. - bdd steps: drop the unrunnable then_dependency_requirement example, document shlex.split's ValueError, record the first-override-wins and --allow-dirty normalisation rules, demonstrate tmp_path by equality rather than a fabricated PosixPath, and type cli_run as _BumpCliRun. Every DOC502 suppression now carries an inline justification naming the propagation path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Align the subprocess env contract and apply the one-line helper convention - subprocess_runner: widen SubprocessContext.env to Mapping[str, object], matching normalise_environment, and widen the two consumers that read it (_log_subprocess_environment, _redact_environment) so the boundary stays type-consistent. _redact_environment already stringified every value, so only the annotations were stale. Refresh the normalise_environment comment that described non-string values as arriving "despite the annotated signature", which the widened signature no longer contradicts. - config: every DOC502 suppression now carries an inline justification naming its propagation path -- the five from_mapping classmethods raise through the shared mapping validators, and load_configuration delegates to load_from_loader. - bdd steps: collapse _normalise_preflight_responses and when_invoke_lading_publish to single-line summaries, per the ignore-one-line-docstrings convention for private helpers. The two non-obvious rules the former documented (first publish override wins, --allow-dirty normalised to config.allow_dirty) move to inline comments at the branches that implement them, so no behaviour detail is lost. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Key the lockfile failure-mode docs on manifests attempted _raise_aggregated_failure branches on len(manifests) == 1, so describing the re-raise path in terms of how many lockfiles were regenerated is too narrow -- and doubly wrong, since a failed manifest produces no regenerated lockfile at all. Every manifest is attempted, so the distinction is the attempt count. The regenerate_lockfiles docstring was corrected in 5af1852; this applies the same correction to the three surviving prose copies in the user and developer guides. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Widen the subprocess_runner env parameter to match the boundary 9fe366b widened SubprocessContext.env and its consumers to Mapping[str, object], but left subprocess_runner's own env parameter as Mapping[str, str]. That made the public entry point the narrowest link in the chain: a caller could not pass a Path or int through subprocess_runner even though the context field, normalise_environment and the redacting logger all accept one. Widening a parameter is contravariance-safe, so subprocess_runner still satisfies the CommandRunner protocol; ty confirms this. The protocol itself is left alone -- narrowing that port would cascade to every implementation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Apply the one-line convention to two workspace coercion helpers _expect_sequence: the discarded paragraph explained why the helper is a typed wrapper rather than functools.partial. That is design rationale rather than caller-facing behaviour, and the return-narrowing it describes is already documented on the two overload stubs, which are the caller-visible contract. The overloads and their docstrings are untouched. _coerce_publish_setting: its prose documented a coercion rule that is not recoverable from the (value: object, package_id: str) -> bool signature -- None and non-empty registry lists mean publishable, false and an empty list do not. AGENTS.md requires non-obvious behaviour to survive, so the replacement one-liner carries the rule itself rather than restating the function name. Both are now exactly one physical line, so ignore-one-line-docstrings exempts them from DOC201/DOC501 despite dropping Returns and Raises. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Share one typed contract for BDD CLI run results Introduce CliRunResult, a TypedDict with the four keys _run_cli actually produces, and use it wherever a publish step passes that dict around. The contract lives in its own module rather than in test_common_steps, which owns _run_cli. test_common_steps imports the bump and publish step modules at the bottom of the file so their steps register with pytest-bdd, which makes it a hub: test_common_steps -> test_publish_when_steps -> test_publish_infrastructure -> test_common_steps would close a real cycle. That is why _run_cli is reached through TYPE_CHECKING guards and deferred function-body imports today. cli_run_types imports no sibling step module, so it cannot join that cycle. pytest collects only test_*.py, so the module is not mistaken for a test, and the tests/bdd/steps/*.py per-file ignores still cover it. test_bump_steps already declared _BumpCliRun with these exact four fields, so the shared contract replaces it rather than sitting beside it. Typing _run_cli also makes the typ.cast in _invoke_lading_bump redundant, so it becomes a direct return. Every import of the contract sits in a TYPE_CHECKING block: the annotations are deferred by __future__.annotations and the type has no runtime use, so ruff's TC001 requires it there. The e2e run_cli helper is left alone -- it returns workspace_root plus an extra command key, so it does not fit this contract. The shared then_* steps in test_common_steps and test_bump_steps keep dict[str, typ.Any]; they are not publish-specific, and pytest-bdd injects them as fixtures, so no call edge forces the change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Type the end-to-end CLI result contract run_cli builds one dict literal with five unconditional keys, so declare _CliRunResult alongside it and return that instead of dict[str, typ.Any]. The shape stays exactly as it was: command is a list[str] and workspace_root is the Path passed through unconverted. This is a separate contract from the BDD tree's CliRunResult rather than a shared one: the e2e result carries an extra command key and names the path workspace_root, so the two cannot be unified without changing one of the dictionaries. Retype the consumers that actually hold a CLI result -- the private _run_lading_in_e2e_workspace helper, the three when_run_lading_* steps that target the cli_run fixture, and the two then steps that read it -- plus the Returns sections that named the old type. The e2e_state and publish_spies mappings keep dict[str, typ.Any]; they are unrelated bags, and e2e_state in particular gains a regenerated_marker key at runtime that a closed TypedDict would reject. _CliRunResult is imported under TYPE_CHECKING because test_e2e_steps only names it in annotations; ruff's TC001 requires that placement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Inline the two single-use CargoMetadataParseError factories CodeScene flagged invalid_json and non_object_payload as duplicated structure. The duplication was real but the shared part was scaffolding, not behaviour: each body was one return cls("<literal>"), and each was called exactly once, on adjacent lines of _parse_cargo_metadata. Extracting a shared abstraction would only have produced cls(message) -- the constructor that already exists. Inlining removes 30 lines and makes the module consistent with itself: every other raise here already constructs the error directly, so the factories were the outlier rather than the pattern. The class is not re-exported and nothing outside the two call sites referenced either factory. This also retires a file-wide lint suppression. The DOC501/DOC502 ignore for metadata.py existed because pydoclint keys those rules on the syntactic raise-callable and could not resolve a factory classmethod to its exception type. With the raises direct, only load_cargo_metadata still needs DOC502 -- for exceptions it genuinely propagates -- so a justified per-function noqa replaces the file-wide entry, and the pyproject comment no longer describes factories that have been deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: leynos <leynos@rohga> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: leynos <leynos@troubledskies.net>
discover_tracked_lockfiles hid a non-git workspace behind a warning and a silent empty tuple, so callers could not distinguish "no tracked lockfiles" from "discovery never ran". Filesystem access was also mixed into the function rather than confined to a port. Raise a typed NotAGitRepositoryError (subclass of LockfileDiscoveryError) for non-git workspaces and keep LockfileDiscoveryError for other git failures. The skip policy moves to the caller: publish pre-flight catches NotAGitRepositoryError, warns, and continues, preserving existing operator behaviour while making the condition explicit at the API. Filesystem access is now documented as confined to the injected manifest_exists port and git access to the injected runner; the function performs no direct I/O of its own. Replace the silent-skip unit test with a typed-exception assertion, add a caller-policy test for the pre-flight skip, and add integration tests that exercise discovery against real git repositories in temporary directories through the real subprocess runner (tracked versus untracked lockfiles, target/ exclusion, manifest adjacency, and the non-git error). Extract the lockfile freshness policy (_validate_lockfile_freshness, _collect_stale_lockfiles, _build_stale_lockfile_message) into the colocated module publish_lockfile_preflight. Adding the skip branch took publish_preflight past the repository's 400-line file limit, and the freshness policy is a coherent unit: it depends only on the LockfileInspectionRepository port, while publish_preflight retains the cargo and git command orchestration and stays the composition root that binds the adapter. Apply the same caller-owned skip policy on the bump side. Since #160, bump discovers tracked lockfiles through bump_lockfile_manifests.merge_discovered_manifests, whose documented contract is that a non-git workspace returns the configured manifests unchanged. That relied on discovery's silent empty tuple, so the typed error would otherwise abort `lading bump` outside git control; the merge helper now catches NotAGitRepositoryError, warns, and returns the configured tuple. Address review feedback. NotAGitRepositoryError now carries the failing workspace_root as a structured attribute, matching CommandSpawnError and WorkspaceDependencyCycleError, so callers need not parse the message. Add a bounded lockfile.discovery.failed counter (reason=not_git|git_error) and log the unexpected-failure branch at the failure boundary; success volume stays on lockfile.discovered so quiet runs stay quiet. Discovery classifies a non-git workspace by matching git's English text, which a localized machine would translate and silently misclassify. Pin the C locale in subprocess_runner, the single adapter that spawns processes, via the new lading.utils.process.c_locale_env helper. Test doubles bypass it, so no double needs widening, and cargo's output is covered too. Classifying on the exit status instead is not possible: git ls-files exits 128 for every fatal condition. Move the LockfileInspectionRepository port and its adapter into lockfile_repository, with their tests, keeping lockfile.py inside the 400-line limit and separating the hexagonal boundary from the domain operations. Restore the inline-code exclusion in typos.local.toml. Regenerating typos.toml from the shared authority had dropped it, so the spelling gate flagged identifiers such as `normalise_workspace_root` that must keep their source spelling. Closes #79
Summary
This branch makes
lading bumpregenerate every git-trackedCargo.lockin the repository, not just the workspace root plus manifests listed inbump.lockfile_manifests. Previously,lading publishvalidated all tracked lockfiles under--locked, so any nested fixture lockfile that bump had not been configured to refresh went stale and publish aborted with a manual repair message — leaving the user wondering why bump had not done the repair itself. The users' guide already promised discovery-based refresh; this branch makes the code keep that promise.Execplan: docs/execplans/regenerate-lockfiles.md (implemented in full; status COMPLETE, including the Stage A prototype evidence, red/green test transcripts, an end-to-end acceptance run, and the retrospective).
The branch also carries three supporting changes:
lading bump; it now names the remaining causes (manifest edits made without bump, or bump runs with--no-rebuild-lockfiles).make typecheckgate is restored and future-proofed: six pre-existing diagnostics under the drifting unpinned ty toolchain are fixed, and ty is now pinned (TY_VERSION ?= 0.0.56) in the Makefile with CI running the pinned version viamake typecheckrather than installing ty separately.make fmtruns clean.Review walkthrough
merge_discovered_manifests, which unions configuredbump.lockfile_manifestsentries (order preserved) with manifests implied by git-tracked lockfiles (sorted, de-duplicated by resolved path). Discovery reusesdiscover_tracked_lockfiles, the same helper the publish pre-flight uses; a non-git workspace degrades to the configured list with a warning._process_lockfilesnow feeds the merged tuple to both the dry-run and live regeneration paths, so both report the same set.NoReturncalls) and lading/commands/bump_toml.py; the pinning is in the Makefile and .github/workflows/ci.yml.lockfile_manifestskey) and docs/developers-guide.md (module descriptions,TY_VERSIONvariable).Validation
make test: 683 passed, 62 snapshots passed.make lint: Ruff clean; interrogate 100%; Pylint 10.00/10.make check-fmt: 132 files already formatted.make typecheck(ty 0.0.56, pinned): all checks passed.make markdownlintandmake nixie: clean.uv tool run mbake validate Makefile: valid syntax.coderabbit review --agent: run after each milestone (feature, docs, ty pinning); zero findings each time.- fixtures/minimal/Cargo.lock (lockfile)in its output, refreshed the nested lockfile to the new version, andcargo metadata --lockedsubsequently exited 0.Notes
alpha = { path = ..., version = "0.1.0" }) now fails at bump time with cargo's version-selection error, because bump does not rewrite non-member manifests. Such a repository already failed at publish time, so the error merely surfaces earlier and more actionably; rewriting those requirements is recorded in the execplan as an open follow-up.bump.lockfile_manifestsremains supported for lockfiles that git does not track;--no-rebuild-lockfilesremains the global escape hatch.References