Skip to content

Surface lockfile discovery failures via typed exceptions (#79) - #131

Open
leynos wants to merge 9 commits into
mainfrom
issue-79-inject-lockfile-fs-io
Open

Surface lockfile discovery failures via typed exceptions (#79)#131
leynos wants to merge 9 commits into
mainfrom
issue-79-inject-lockfile-fs-io

Conversation

@leynos

@leynos leynos commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #79

  • discover_tracked_lockfiles previously hid a non-git workspace behind a warning and a silent empty tuple. It now raises a typed NotAGitRepositoryError (subclass of LockfileDiscoveryError) for non-git workspaces; other git failures keep raising LockfileDiscoveryError.
  • The skip policy moves to the caller: publish pre-flight catches NotAGitRepositoryError, warns, and continues — preserving operator behaviour while making the condition explicit at the API.
  • I/O is confined to ports: git access through the injected runner, filesystem access through the injected manifest_exists adapter; the function performs no direct I/O of its own (documented in the docstring).

Testing

  • New tests/integration/test_lockfile_discovery.py exercises discovery against real git repositories in temporary directories through the real subprocess runner, without stubbing internals: tracked vs untracked lockfiles, target/ exclusion, manifest adjacency, and the non-git typed error.
  • The silent-skip unit test is replaced with a typed-exception assertion; a caller-policy test pins the pre-flight skip behaviour.
  • make check-fmt, make lint, make typecheck, and make test (565 passed) all green after rebasing onto current main.
  • coderabbit review --agent: 0 findings.

🤖 Generated with Claude Code

Summary by Sourcery

Make lockfile discovery failures explicit while preserving non-Git workspace handling at publish and bump boundaries.

New Features:

  • Surface non-Git lockfile discovery failures through the typed NotAGitRepositoryError exception.
  • Record lockfile discovery failures with bounded telemetry reasons.

Bug Fixes:

  • Preserve publish and bump behavior by handling non-Git workspaces explicitly at their caller boundaries instead of silently returning no lockfiles.

Enhancements:

  • Separate publish lockfile inspection ports and freshness policy from lockfile discovery and command orchestration.
  • Confine discovery’s Git and filesystem access to injected adapters and stabilize Git diagnostics with a C locale.
  • Add integration coverage using real temporary Git repositories for tracked, untracked, filtered, and non-Git discovery scenarios.

Build:

  • Restrict Markdown linting to Git-tracked Markdown files.

Documentation:

  • Document typed discovery failures and non-Git publish and bump behavior.

Tests:

  • Add coverage for typed discovery errors, caller-level skip behavior, failure metrics, adapter boundaries, locale handling, and real Git repository discovery.

Chores:

  • Move the lockfile inspection adapter and publish freshness policy into dedicated modules and update references.

References

@coderabbitai

coderabbitai Bot commented Jun 10, 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

  • Raise typed exceptions during lockfile discovery:
    • Use NotAGitRepositoryError for non-Git workspaces.
    • Use LockfileDiscoveryError for other Git failures.
  • Route Git and filesystem access through injected adapters.
  • Move Cargo lockfile freshness checks to publish_lockfile_preflight.py.
  • Skip publish validation with a warning outside Git repositories.
  • Preserve configured manifests during bump operations outside Git repositories.
  • Report stale lockfiles with repair commands.
  • Record lockfile discovery failures with reason-specific metrics.
  • Enforce a C locale for subprocess environments.
  • Add integration tests with real temporary Git repositories.
  • Update unit tests and developer and user documentation.
  • Align the implementation with docs/lading-design.md.

Walkthrough

Lockfile discovery now raises typed exceptions for non-Git workspaces and Git command failures. Publish freshness validation uses a dedicated repository port and policy module. Subprocesses now enforce the C locale.

Changes

Lockfile discovery and publish preflight

Layer / File(s) Summary
Typed lockfile discovery failures
lading/commands/lockfile.py, tests/integration/test_lockfile_discovery.py, tests/unit/test_lockfile.py
Raise typed exceptions for non-Git workspaces and other Git failures. Record bounded discovery-failure metrics.
Caller-owned non-Git handling
lading/commands/bump_lockfile_manifests.py, lading/commands/publish_lockfile_preflight.py, tests/unit/publish/test_preflight_lockfile_validation.py
Skip publish validation with a warning and preserve configured bump manifests outside Git workspaces. Aggregate stale lockfile failures and abort on unexpected validation errors.
Publish lockfile inspection port
lading/commands/lockfile_repository.py, tests/unit/test_lockfile_repository.py
Add the repository protocol and Cargo adapter. Bind runners, optional environments, manifest checks, discovery, and freshness validation.
Publish preflight delegation
lading/commands/publish_preflight.py
Delegate lockfile freshness policy to publish_lockfile_preflight while retaining command orchestration and adapter binding.
Deterministic command environment and documentation
lading/utils/process.py, lading/runtime/subprocess_runner.py, tests/unit/utils/test_process.py, docs/developers-guide.md, docs/lading-design.md, docs/users-guide.md, typos.local.toml
Enforce the C locale for subprocesses. Update architecture, metrics, user behaviour, command catalogue, and typo-check configuration documentation.

Sequence Diagram(s)

sequenceDiagram
  participant PublishPreflight
  participant LockfilePreflight
  participant CargoLockfileInspectionRepository
  participant Git
  PublishPreflight->>LockfilePreflight: validate lockfile freshness
  LockfilePreflight->>CargoLockfileInspectionRepository: discover tracked lockfiles
  CargoLockfileInspectionRepository->>Git: run git ls-files
  Git-->>CargoLockfileInspectionRepository: return paths or failure
  CargoLockfileInspectionRepository-->>LockfilePreflight: return paths or typed exception
  LockfilePreflight-->>PublishPreflight: return or raise preflight result
Loading

Possibly related PRs

  • leynos/lading#125: Contains the lockfile validation logic and tests relocated by this change.
  • leynos/lading#135: Introduces the lockfile inspection port and adapter that this change relocates and refactors.
  • leynos/lading#160: Uses tracked Cargo lockfile discovery in bump regeneration.

Suggested labels: Issue

Poem

Typed errors mark the Git trail,
Fresh locks pass the gate.
Stale locks print repair commands,
Ports keep the layers straight.
C-locale commands speak clearly.

Merge Risk: 🟡 Moderate · up to 38a03

The change exposes non-Git workspaces through a typed error while preserving publish behavior, but a test helper passes the wrong argument type to that error and can fail static type checking; merge should wait for this correction.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning, 8 inconclusive)

Check name Status Explanation Resolution
Module-Level Documentation ❌ Error The PR adds c_locale_env to lading.utils.process, but its module docstring still states that it covers only two concerns and omits locale-environment handling. Update the module docstring to describe c_locale_env, its C-locale purpose, and its use by lading.runtime.subprocess_runner.
Out of Scope Changes check ⚠️ Warning Reject the scope as-is: typos.local.toml and locale-environment changes are unrelated to the lockfile discovery requirements in [#79]. Remove typos.local.toml and c_locale_env/subprocess locale changes, or link them to explicit requirements before merging.
Testing (Overall) ❓ Inconclusive Investigation not complete; tests and changed behaviour still require verification. Inspect the pull-request diff and run targeted static test analysis before deciding.
User-Facing Documentation ❓ Inconclusive Investigation started; no final assessment yet. Inspect the pull-request diff and user guide coverage before deciding.
Developer Documentation ❓ Inconclusive Initial repository state shows no working-tree diff; inspect the PR commit and documentation before deciding. Provide a usable PR-to-base diff if the commit comparison is unavailable.
Testing (Property / Proof) ❓ Inconclusive Investigation is still in progress; no verdict yet. Inspect the changed discovery, adapter, and locale invariants and the added test strategy before deciding.
Testing (Compile-Time / Ui) ❓ Inconclusive The change is Python-only; inspect changed CLI output and tests to determine whether focused snapshots are appropriate under this check. Inspect the changed diagnostics and test coverage before deciding.
Unit Architecture ❓ Inconclusive Investigation is still in progress; the available commit diff shows the new ports and policy split, but caller composition and all changed dependency paths need verification. Inspect composition-root wiring, bump callers, subprocess environment handling, and boundary tests before deciding.
Domain Architecture ❓ Inconclusive Investigation is still in progress; no verdict submitted yet. Inspect the changed module boundaries and verify whether filesystem concerns remain in domain code.
Concurrency And State ❓ Inconclusive Initial repository evidence confirms a focused lockfile refactor, but concurrency and shared-state behaviour still requires source and test inspection. Inspect changed stateful paths, process execution, metrics, and tests for introduced interleaving or lifecycle risks.
✅ Passed checks (10 passed)
Check name Status Explanation
Title check ✅ Passed Accept the title: it names typed lockfile discovery failures and includes the directly linked issue number (#79).
Description check ✅ Passed Accept the description: it accurately explains the typed errors, injected I/O, caller policy, and integration tests.
Linked Issues check ✅ Passed Accept the implementation: it routes discovery I/O through injected ports, raises typed failures, and adds integration tests as required by [#79].
Docstring Coverage ✅ Passed Docstring coverage is 91.11% which is sufficient. The required threshold is 80.00%.
Testing (Unit And Behavioural) ✅ Passed Real-Git integration tests cover tracked/untracked, target and manifest filtering, and non-Git errors; unit/property tests cover failures, metrics, caller skip, aggregation, adapter wiring and loca...
Observability ✅ Passed Mark Observability PASS: bounded lockfile.discovery.failed reasons, success/failure metrics, and caller warnings/errors expose non-Git, Git, stale, and validation outcomes; docs define the signals.
Security And Privacy ✅ Passed Mark PASS: the diff adds no secrets or auth changes; subprocesses use argument vectors with shell=False, repair paths use shlex.quote, metrics use bounded labels, and environment logs remain redacted.
Performance And Resource Use ✅ Passed The diff keeps lockfile discovery linear and preserves one freshness probe per tracked manifest; new locale copying and adapter closure allocations are bounded per command and no avoidable quadrati...
Architectural Complexity And Maintainability ✅ Passed Accept the change: the existing adapter moved to a clear port module, policy extraction reduced orchestration size, and tests use the injected seam without import cycles.
Rust Compiler Lint Integrity ✅ Passed The pull-request diff contains 12 Python, 3 Markdown, and 1 TOML file, with no Rust paths, Rust lint suppressions, or Rust clone changes.
✨ 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-79-inject-lockfile-fs-io

Warning

Your free Security trial is over. An organization admin can upgrade to Advanced for continuous pull request security review or dismiss this notice.


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

@sourcery-ai

sourcery-ai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors lockfile discovery to surface non-git workspaces via a typed exception instead of a silent skip, moves the skip policy to the publish preflight caller, and adds integration and unit tests that exercise discovery against real git repositories and assert the new error-handling behavior.

Sequence diagram for lockfile discovery error propagation and skip policy

sequenceDiagram
    participant PublishPreflight
    participant discover_tracked_lockfiles
    participant runner
    participant _raise_git_ls_files_failure

    PublishPreflight->>discover_tracked_lockfiles: discover_tracked_lockfiles(workspace_root, runner_with_env)
    discover_tracked_lockfiles->>runner: runner(("git", "ls-files", "**/Cargo.lock", "Cargo.lock"), cwd=workspace_root)
    runner-->>discover_tracked_lockfiles: exit_code, stdout, stderr

    alt exit_code != 0
        discover_tracked_lockfiles->>_raise_git_ls_files_failure: _raise_git_ls_files_failure(exit_code, stdout, stderr, workspace_root)
        alt ["not a git repository" in detail]
            _raise_git_ls_files_failure-->>PublishPreflight: raise NotAGitRepositoryError
            PublishPreflight->>PublishPreflight: LOGGER.warning("Skipping lockfile freshness validation...")
            PublishPreflight-->>PublishPreflight: return
        else other git failure
            _raise_git_ls_files_failure-->>PublishPreflight: raise LockfileDiscoveryError
        end
    else exit_code == 0
        discover_tracked_lockfiles-->>PublishPreflight: tracked_lockfiles
    end
Loading

File-Level Changes

Change Details Files
Surface non-git workspaces as a typed discovery error instead of silently returning an empty result.
  • Introduce NotAGitRepositoryError as a LockfileDiscoveryError subclass with documentation on caller-owned skip policy.
  • Replace _handle_git_ls_files_failure with _raise_git_ls_files_failure that always raises typed errors for non-zero git ls-files exit codes.
  • Update discover_tracked_lockfiles to call the new helper on git failures, document raised exceptions in the docstring, and clarify that all I/O goes through injected runner and manifest_exists ports.
lading/commands/lockfile.py
Move the non-git workspace skip policy into publish preflight validation logic.
  • Wrap discover_tracked_lockfiles in _validate_lockfile_freshness with a try/except for NotAGitRepositoryError.
  • On NotAGitRepositoryError, log a warning that freshness validation is being skipped for a non-git workspace and return early without running cargo commands.
lading/commands/publish_preflight.py
Align unit tests with the new typed error behavior and caller-owned skip policy.
  • Change the discover_tracked_lockfiles non-git directory unit test to expect NotAGitRepositoryError instead of an empty tuple.
  • Add a preflight unit test that injects a discovery function raising NotAGitRepositoryError and asserts that validation logs a warning and performs no cargo runner calls.
tests/unit/test_lockfile.py
tests/unit/publish/test_preflight_lockfile_validation.py
Add integration tests that exercise lockfile discovery against real git repositories via the subprocess runner.
  • Create helper functions to initialize temporary git repos, add crates with/without manifests, and run git commands with deterministic identity.
  • Add tests that verify discovery returns only tracked lockfiles with adjacent manifests, ignores untracked and target/ lockfiles, and raises NotAGitRepositoryError for non-git directories when invoked with subprocess_runner.
tests/integration/test_lockfile_discovery.py

Assessment against linked issues

Issue Objective Addressed Explanation
#79 Refactor discover_tracked_lockfiles so all filesystem operations (lockfile enumeration, manifest existence checks) are routed through injected ports (runner/manifest_exists) and the function performs no direct Path I/O.
#79 Make lockfile discovery failures explicit in the public API by surfacing errors (e.g., via typed exceptions) instead of logging warnings and returning empty tuples silently.
#79 Replace the previous mock-based tests for lockfile discovery with real integration tests that exercise discovery (and related behaviour) against temporary directories and real git/subprocess interactions.

Possibly linked issues


Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

codescene-delta-analysis[bot]

This comment was marked as outdated.

@lodyai
lodyai Bot force-pushed the issue-79-inject-lockfile-fs-io branch from 3739c1e to c6ad97d Compare June 16, 2026 19:04
@lodyai
lodyai Bot force-pushed the issue-79-inject-lockfile-fs-io branch from c6ad97d to f3ce412 Compare July 27, 2026 22:04
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@leynos
leynos marked this pull request as ready for review July 30, 2026 11:22

@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 @leynos, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f3ce4128fe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lading/commands/publish_preflight.py Outdated
@lodyai
lodyai Bot force-pushed the issue-79-inject-lockfile-fs-io branch from f3ce412 to 65ba834 Compare August 1, 2026 10:03
codescene-access[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot added the Issue label Aug 1, 2026
@lodyai
lodyai Bot force-pushed the issue-79-inject-lockfile-fs-io branch from 65ba834 to df0684d Compare August 1, 2026 10:11
codescene-access[bot]

This comment was marked as outdated.

@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

🤖 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/developers-guide.md`:
- Around line 586-597: Add a brief descriptive caption immediately before the
shim inventory table in the documentation, ensuring it clearly identifies the
table’s contents and satisfies the requirement to caption every table.

In `@lading/commands/lockfile.py`:
- Around line 67-75: Update NotAGitRepositoryError to accept and store the
failed workspace_root as a structured exception attribute, preserving it for
callers to access directly without parsing the exception message. Ensure every
construction site in the lockfile discovery flow, including the additional
occurrence, passes the workspace path when raising this error.
- Around line 92-95: Update the command execution in the lockfile discovery flow
around command_detail so the injected command environment forces a deterministic
locale while preserving the existing pre-flight environment values. Classify
non-Git workspaces using the locale-stable Git result rather than matching the
English diagnostic text, and retain the NotAGitRepositoryError path. Add a unit
test covering non-English stderr that still raises NotAGitRepositoryError.
🪄 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: ac5ed76a-32d5-4709-b328-0ce0fd3cd5e2

📥 Commits

Reviewing files that changed from the base of the PR and between 8f046ae and 65ba834.

📒 Files selected for processing (8)
  • docs/developers-guide.md
  • docs/lading-design.md
  • lading/commands/lockfile.py
  • lading/commands/publish_lockfile_preflight.py
  • lading/commands/publish_preflight.py
  • tests/integration/test_lockfile_discovery.py
  • tests/unit/publish/test_preflight_lockfile_validation.py
  • tests/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)

Comment thread docs/developers-guide.md Outdated
Comment thread lading/commands/lockfile.py
Comment thread lading/commands/lockfile.py Outdated
codescene-access[bot]

This comment was marked as outdated.

@buzzybee-df12

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 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/integration/test_lockfile_discovery.py`:
- Around line 78-79: Update the assertions in the lockfile discovery test,
including the corresponding assertions around lines 92–93, to include
descriptive failure messages. Clearly state the expected tracked and untracked
lockfiles and the required exclusion of target from the results, while
preserving the existing assertion conditions.
🪄 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: 13ce0c25-3990-4193-92b3-f494ce94a218

📥 Commits

Reviewing files that changed from the base of the PR and between 8f046ae and df0684d.

📒 Files selected for processing (9)
  • docs/developers-guide.md
  • docs/lading-design.md
  • lading/commands/bump_lockfile_manifests.py
  • lading/commands/lockfile.py
  • lading/commands/publish_lockfile_preflight.py
  • lading/commands/publish_preflight.py
  • tests/integration/test_lockfile_discovery.py
  • tests/unit/publish/test_preflight_lockfile_validation.py
  • tests/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)

Comment thread tests/integration/test_lockfile_discovery.py 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 Aug 15, 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[bot]

This comment was marked as resolved.

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
The df12 lint gate merged in #220 flags absolute paths in syrupy
snapshots. The stale-lockfile snapshots embedded the synthetic /ws
workspace root, tripping the snapshot-posix-path rule on main.

Render the fixture paths through syrupy's documented <tmp-file-path>
placeholder and keep the nested case at a single directory segment, so
the snapshots stay machine-independent and the scanner's allowlist
covers them. The added quoting around the manifest path is emitted by
the production shlex.quote path and is a faithful record of user-facing
output.

Validated with make check-fmt, make test, make typecheck, and make lint.
@leynos
leynos force-pushed the issue-79-inject-lockfile-fs-io branch from 38a03d0 to 7789463 Compare September 5, 2026 01:37
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

`make spelling` regenerates typos.toml from the shared en-GB-oxendict
authority merged with typos.local.toml. The branch pins the inline-code
exclusion in typos.local.toml so regeneration cannot drop it; refresh
the tracked generated file to match that output, keeping the working
tree clean after the spelling gate runs.
codescene-access[bot]

This comment was marked as outdated.

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

Correct the user guide: bump regenerates the lockfiles adjacent to the
configured manifests, not the manifests themselves.

Build the NotAGitRepositoryError message in a local variable before
calling super(), matching the sibling CommandSpawnError pattern.

Render c_locale_env's Examples section as doctest prompts so it follows
the doctest convention used by every other Examples block in the module
and its expected output stays machine-checkable.

Pass workspace_root directly when the pre-flight test double raises
NotAGitRepositoryError, so the exception's structured attribute holds
the recorded Path instead of a formatted string.
codescene-access[bot]

This comment was marked as outdated.

The markdownlint target swept every *.md under the working tree with
find, so gitignored agent-infrastructure files (.vtcode/, .pytest_cache/)
and any untracked scratch Markdown broke the gate with violations that
never exist in a fresh checkout. List the files with git ls-files
instead, matching the spelling target directly below, which already
restricts itself to tracked files.

Also ignore memories/, the scratch directory sub-agents write session
progress into, extending the agent-artefact ignore list from b8291b9.
codescene-access[bot]

This comment was marked as outdated.

The contract pinned the literal find exclusions (-not -path) that the
markdownlint target no longer uses. Keep guarding the property the
contract exists for — generated uv cache and tool documentation must
stay out of lint scope — by asserting the target lists files through
git ls-files and that both directories remain gitignored, which
excludes them from lint scope by construction.
codescene-access[bot]

This comment was marked as outdated.

Placing the table caption before the table it names matches the
convention used by the regenerate_lockfiles figure caption and reads as
a title rather than a footnote.
mdformat's reflow split the [`[bump]`](#bump) inline-code link across
lines, which markdownlint then rejected as MD039 (spaces inside link
text), so every `make fmt` run ended in a lint error. Refer to the
section in prose so the paragraph survives rewrapping and the gate
stays idempotent.
`make fmt` rewraps these paragraphs on every run because earlier edits
left them outside mdformat's current wrap width; commit the output so
the formatting gate leaves a clean tree, matching the precedent set by
the generated-spelling commit 7ad8f6a.
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.

Inject filesystem I/O into discover_tracked_lockfiles and surface failures via exceptions

3 participants