Skip to content

Prevent manifest fixture overwrites (#61) - #545

Open
lodyai[bot] wants to merge 7 commits into
mainfrom
issue-61-replace-namedtempfile-persist-with-more-explicit-file-creation-method
Open

Prevent manifest fixture overwrites (#61)#545
lodyai[bot] wants to merge 7 commits into
mainfrom
issue-61-replace-namedtempfile-persist-with-more-explicit-file-creation-method

Conversation

@lodyai

@lodyai lodyai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

This branch replaces overwrite-capable manifest persistence with an
explicit no-clobber operation. It retains atomic same-directory staging and
treats a target created concurrently as satisfying the manifest contract.

Closes #61.

Review walkthrough

Validation

  • cargo nextest run -p test_support --all-features: 74 passed.
  • cargo nextest run --test bdd_tests --all-features: 245 passed.
  • make check-fmt, make test, make typecheck, and make lint: passed.
  • coderabbit review --agent: 0 findings.

References

Summary by Sourcery

Prevent manifest fixture persistence from overwriting existing manifest files while maintaining race-tolerant behavior.

Bug Fixes:

  • Ensure manifest persistence uses a no-clobber operation so pre-existing manifest files are not overwritten.

Tests:

  • Add a regression test verifying that persisting a staged manifest leaves an existing manifest file and its contents unchanged.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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

  • Prevent overwriting existing manifest files during persistence.
  • Preserve atomic same-directory staging.
  • Treat concurrent non-directory creation as successful.
  • Reject directory targets with IsADirectory.
  • Propagate filesystem inspection and persistence errors other than NotFound.
  • Add public PathState and inspect_path helpers.
  • Add Rustdoc, developer documentation, deterministic race tests, and bounded property-based coverage.
  • Configure DYLINT_TOML for the lint-whitaker target.
  • Validate the changes with formatting, type-checking, tests, and linting.

Walkthrough

Manifest creation now classifies filesystem paths, preserves existing files, rejects directories, and propagates other errors. Tests cover missing targets, parent validation, persistence races, and non-overwriting behaviour. Documentation and Whitaker lint configuration were updated.

Changes

Manifest persistence

Layer / File(s) Summary
Path-state inspection
test_support/src/fs.rs, test_support/src/fs_tests.rs
Add PathState and inspect_path. Classify absent, directory, and non-directory paths. Propagate non-NotFound errors.
Manifest target handling
test_support/src/manifest.rs
Inspect targets and ancestors explicitly. Reject directories. Preserve existing files. Use non-clobbering persistence. Support controlled race tests.
Persistence validation and documentation
test_support/src/manifest/tests.rs, docs/developers-guide.md
Test missing, existing, raced-file, and raced-directory states. Document persistence and inspection behaviour.
Whitaker lint configuration
Makefile
Set DYLINT_TOML from dylint.toml for the Whitaker command.

Possibly related PRs

Suggested labels: Issue

Suggested reviewers: leynos

Poem

Inspect each path before you write.
Keep existing manifests in sight.
Reject directories at the door.
Preserve errors and contents more.
Let tests guard the filesystem night.


Important

Pre-merge checks failed

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

❌ Failed checks (6 inconclusive)

Check name Status Explanation Resolution
Testing (Property / Proof) ❓ Inconclusive Investigation is still in progress; no verdict submitted yet. Inspect the actual diff and property-test implementation before deciding.
Observability ❓ Inconclusive Investigation started; no verdict is available yet. Inspect the pull-request diff and repository observability patterns before deciding.
Security And Privacy ❓ Inconclusive Investigation started; no verdict yet. Inspect the pull request diff and relevant filesystem and manifest code before deciding.
Performance And Resource Use ❓ Inconclusive Diff review is incomplete because the feature snapshot also changes unrelated files not yet inspected for resource-use effects. Inspect all remaining changed files, especially Cargo.toml, test migrations, and deleted test data, before deciding.
Concurrency And State ❓ Inconclusive Investigation is still in progress; no final assessment has been made. Inspect the changed manifest and filesystem state paths, then verify their race handling and test coverage.
Rust Compiler Lint Integrity ❓ Inconclusive Investigation in progress; no verdict yet. Await code inspection.
✅ Passed checks (14 passed)
Check name Status Explanation
Title check ✅ Passed The title describes the manifest overwrite prevention change and includes the linked issue reference (#61).
Description check ✅ Passed The description explains the no-clobber persistence change, related tests, validation, and linked issue #61.
Linked Issues check ✅ Passed The changes implement issue #61 by preventing manifest overwrites and adding coverage for race, directory, and filesystem error cases.
Out of Scope Changes check ✅ Passed The documentation, filesystem helper, tests, and lint configuration support the manifest persistence change and its validation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Testing (Overall) ✅ Passed Accept the testing coverage: dedicated tests and bounded property cases verify no-clobber persistence, file and directory races, creation, path-state errors, cleanup, and meaningful content or erro...
User-Facing Documentation ✅ Passed Treat this check as inapplicable: the diff changes only unpublished test_support fixtures and developer tooling; no end-user behaviour changes, and docs/users-guide.md is untouched.
Developer Documentation ✅ Passed The developer guide documents the new PathState, inspect_path, and ensure_manifest_exists APIs and their race/error guarantees; no changed design document or roadmap item is identified.
Module-Level Documentation ✅ Passed Accept the module documentation: every changed Rust module begins with //!; the headers explain fixture purpose, test function, and parent-module relationships where needed.
Testing (Unit And Behavioural) ✅ Passed The implementation adds public-boundary manifest tests for creation, preservation, races, directories, and parent errors, with real temporary filesystem effects.
Testing (Compile-Time / Ui) ✅ Passed Assessment pending repository evidence.
Unit Architecture ✅ Passed PASS: Keep the separation. inspect_path returns explicit I/O errors, while named ensure, create, and persist functions own writes; the race hook is cfg(test) and scoped.
Domain Architecture ✅ Passed Keep the change: the diff is confined to test_support, documentation, and Makefile; test_support is publish-disabled test infrastructure, and no core domain module changed.
Architectural Complexity And Maintainability ✅ Passed Keep the change: PathState/inspect_path serve the manifest and filesystem boundary, while the documented hook is test-only, scoped, and race-focused; no new dependency or cycle was introduced.
📋 Issue Planner

Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).

View plan for ticket: #61

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-61-replace-namedtempfile-persist-with-more-explicit-file-creation-method

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

❤️ Share

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

codescene-access[bot]

This comment was marked as outdated.

@sourcery-ai

sourcery-ai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR changes manifest fixture persistence to use a no-clobber atomic persist operation and adds a regression test to ensure existing manifest files are never overwritten while preserving race-tolerant behavior.

File-Level Changes

Change Details Files
Switch manifest persistence to a no-clobber operation while preserving race-tolerant semantics.
  • Replace use of overwrite-capable NamedTempFile::persist with NamedTempFile::persist_noclobber for manifest files.
  • Maintain handling of AlreadyExists errors as a successful outcome, treating concurrently created targets as satisfying the manifest contract.
  • Update manifest persistence helper documentation comment to describe the new no-overwrite behavior and concurrency tolerance.
test_support/src/manifest.rs
Add a regression test verifying existing manifests are not overwritten by persistence.
  • Import std::io::Write to support writing staged manifest contents in tests.
  • Create a pre-existing manifest file in a temporary directory, then stage a replacement manifest and call persist_manifest_file.
  • Assert that the manifest contents remain unchanged after persistence, ensuring no overwrite occurs.
test_support/src/manifest.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#61 Replace use of NamedTempFile::persist for manifest files with a non-overwriting, explicit persistence method to prevent unintended file overwrites.

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-access[bot]

This comment was marked as outdated.

Persist staged manifest fixtures without replacing an existing target.
Treat a concurrent creation as success and cover that no-clobber path.
@lodyai
lodyai Bot force-pushed the issue-61-replace-namedtempfile-persist-with-more-explicit-file-creation-method branch from adfd1f1 to d9f5dce Compare August 9, 2026 02:43
codescene-access[bot]

This comment was marked as outdated.

@leynos
leynos marked this pull request as ready for review August 9, 2026 02:53

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry @leynos, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai coderabbitai Bot added the Issue label Aug 9, 2026

@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: d9f5dce7bc

ℹ️ 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 test_support/src/manifest.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@test_support/src/manifest.rs`:
- Around line 269-274: Extract the repeated TempDir creation and UTF-8 path
conversion from the manifest tests into a shared rstest fixture returning the
temporary directory and manifest path. Apply the fixture to all four manifest
tests, including
persisting_manifest_tolerates_existing_file_without_overwriting, and replace
each duplicated setup with the fixture values.
- Around line 107-109: Update the AlreadyExists branch in persist_manifest_file
to inspect manifest_path: return an IsADirectory error when the raced target is
a directory, and preserve success only for an existing non-directory target. Add
a regression test covering persist_manifest_file with a directory destination.
🪄 Autofix

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: bda5f391-8665-4f48-b782-531f57942afa

📥 Commits

Reviewing files that changed from the base of the PR and between 76456df and d9f5dce.

📒 Files selected for processing (1)
  • test_support/src/manifest.rs
🔗 Linked repositories identified

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

  • leynos/rstest-bdd (auto-detected)
  • leynos/ortho-config (auto-detected)
  • leynos/whitaker (auto-detected)
  • leynos/shared-actions (auto-detected)

Comment thread test_support/src/manifest.rs Outdated
Comment thread test_support/src/manifest.rs Outdated
codescene-access[bot]

This comment was marked as outdated.

Verify targets after no-clobber persistence reports `AlreadyExists` so
existing files remain acceptable while directory targets are rejected.

Share manifest test workspace setup and cover the raced-directory path.
codescene-access[bot]

This comment was marked as outdated.

@leynos

leynos commented Aug 9, 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 o11y, code safety, documentation and validation coverage as in scope).

❌ Failed checks (3 warnings, 4 inconclusive)

Check name Status Explanation Resolution
User-Facing Documentation ⚠️ Warning The public test_support::ensure_manifest_exists behaviour changed, but docs/users-guide.md does not document its no-clobber and concurrent-creation behaviour. Document the changed manifest persistence behaviour in the applicable users' guide, or show that this unpublished test-support API is out of scope.
Testing (Unit And Behavioural) ⚠️ Warning The regression test calls private persist_manifest_file directly; no public-boundary or BDD/E2E test exercises the changed no-clobber persistence behaviour. Add an integration or behavioural test through ensure_manifest_exists that exercises the race/creation contract and verifies the target remains unchanged.
Testing (Property / Proof) ⚠️ Warning The patch introduces no-clobber invariants across filesystem states and creation orderings, but manifest.rs has only example-based tests and no property test or bounded model. Add a substantive proptest or bounded model for existing and missing targets, varied contents, and both creation orderings; document the limits of race coverage.

@coderabbitai

This comment was marked as resolved.

codescene-access[bot]

This comment was marked as outdated.

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor
✅ 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
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@test_support/src/manifest.rs`:
- Around line 102-105: Update the public `ensure_manifest_exists` Rustdoc to
explicitly document that it does not overwrite an existing target and tolerates
a concurrently created non-directory target as satisfying the manifest-exists
contract. Retain the private helper’s documentation for implementation details.
- Around line 289-334: Extend the public-boundary tests for
ensure_manifest_exists rather than testing persist_manifest_file directly. Add a
test-only seam immediately before persist_manifest_file that deterministically
creates the target file or directory, then assert existing file contents remain
unchanged and directory targets return IsADirectory with the target path in the
error. Cover all five bounded race states as explicit deterministic cases,
avoiding scheduling-dependent tests.
🪄 Autofix

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: 99b8e563-24d3-4ed4-b84c-857f156fb547

📥 Commits

Reviewing files that changed from the base of the PR and between 76456df and 5cfed52.

📒 Files selected for processing (1)
  • test_support/src/manifest.rs
🔗 Linked repositories identified

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

  • leynos/rstest-bdd (auto-detected)
  • leynos/ortho-config (auto-detected)
  • leynos/whitaker (auto-detected)
  • leynos/shared-actions (auto-detected)

Comment thread test_support/src/manifest.rs
Comment thread test_support/src/manifest.rs Outdated
Repository owner deleted a comment from coderabbitai Bot Aug 9, 2026
Describe preservation of existing and concurrently created file targets,
directory rejection, atomic staging, and the controlled proof boundary.
@wafflecat-df12

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Already reviewed.

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.

Document the no-clobber contract and exercise all controlled target
creation orderings through a test-only persistence seam.

Keep generated staged content, existing files, and directory errors under
bounded property coverage without asserting arbitrary scheduler behaviour.
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@leynos

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

Inspect manifest paths through a fallible test-support filesystem wrapper
so inaccessible metadata is never mistaken for an absent target.

Exercise the exported manifest helper with a scoped pre-persist hook and
document the controlled no-clobber contract for fixture authors.
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@leynos

leynos commented Aug 12, 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.

test_support/src/fs_tests.rs

Comment on lines +90 to +103

fn inspect_path_propagates_errors_other_than_not_found(temp_dir: TempDir) -> anyhow::Result<()> {
    let temp = temp_dir?;
    let file = temp.path().join("regular-file");
    write(&file, b"fixture")?;

    let Err(error) = inspect_path(file.join("child")) else {
        anyhow::bail!("traversing through a regular file should fail");
    };
    anyhow::ensure!(
        error.kind() != io::ErrorKind::NotFound,
        "traversal through a file should not be reported as absence, got {error:?}"
    );
    Ok(())
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: inspect_path_propagates_errors_other_than_not_found,try_is_file_propagates_errors_other_than_not_found

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== PR metadata and changed files =='
gh pr view 545 --repo leynos/netsuke \
  --json number,url,headRefName,baseRefName,updatedAt,files,statusCheckRollup \
  --jq '{
    number, url, headRefName, baseRefName, updatedAt,
    files: [.files[] | {path, additions, deletions}],
    checks: [.statusCheckRollup[]? | {name, status, conclusion, detailsUrl}]
  }'

echo '== Relevant files =='
fd -t f -a '^(manifest\.rs|tests\.rs|fs\.rs|lib\.rs|developers-guide\.md|Cargo\.toml|AGENTS\.md)$' \
  test_support docs . 2>/dev/null | sort

echo '== Manifest module outline =='
ast-grep outline test_support/src/manifest.rs --items all --view expanded 2>/dev/null || true

echo '== Manifest implementation and tests =='
cat -n test_support/src/manifest.rs 2>/dev/null || true
printf '\n== Separate manifest tests, if present ==\n'
cat -n test_support/src/manifest/tests.rs 2>/dev/null || true

echo '== Filesystem wrapper API =='
ast-grep outline test_support/src/fs.rs --items all --view expanded 2>/dev/null || true
rg -n -C 5 \
  'pub\s+(?:enum|fn)\s+(?:Target|.*metadata|.*exist|.*dir)|\b(?:metadata|symlink_metadata|exists|is_dir)\s*\(' \
  test_support/src/fs.rs test_support/src/manifest.rs 2>/dev/null || true

echo '== Public API exports and documentation =='
rg -n -C 5 \
  '\bensure_manifest_exists\b|no-clobber|noclobber|concurrent|raced|IsADirectory|manifest fixture' \
  test_support/src/lib.rs test_support/src/manifest.rs docs/developers-guide.md 2>/dev/null || true

echo '== Property-test dependency and manifest test usage =='
rg -n -C 4 \
  '^(proptest|rstest)\s*=|\bproptest!\s*\{|TestCaseError|ensure_manifest_exists(?:\s*\()?' \
  Cargo.toml test_support/Cargo.toml test_support/src/manifest.rs test_support/src/manifest/tests.rs 2>/dev/null || true

echo '== Linked issue =='
gh issue view 61 --repo leynos/netsuke --json number,state,title,url \
  --jq '{number,state,title,url}'

Length of output: 70971


@leynos, treat all six reported checks as resolved on the current PR branch.

Check Status Evidence
Testing (Overall) Resolved test_support/src/manifest/tests.rs uses the exported ensure_manifest_exists with a deterministic scoped pre-persist hook. It tests raced file and raced directory targets.
Unit Architecture Resolved test_support/src/fs.rs adds fallible PathState inspection. test_support/src/manifest.rs uses fs::inspect_path at target, persistence-race, parent, and ancestor decision points. It propagates errors other than NotFound.
Developer Documentation Resolved docs/developers-guide.md now has a test_support::ensure_manifest_exists section. It documents no-clobber persistence, raced files, raced directories, fallible inspection, staging, and the controlled-ordering limit.
Testing (Unit And Behavioural) Resolved The raced-file test asserts Ok(expected_path) and byte-identical competing contents. The raced-directory test asserts io::ErrorKind::IsADirectory. Both call ensure_manifest_exists.
Testing (Property / Proof) Resolved The bounded 16-case proptest! covers missing, existing file, raced file, existing directory, and raced directory states. It generates bounded staged and competing content. It states that it tests controlled creation orderings, not arbitrary scheduler or filesystem behaviour.
Concurrency And State Resolved The test-only hook runs after initial target inspection and before persist_noclobber. Its guard restores prior state on drop. Production builds do not contain the hook.

Do not create a new GitHub issue. Open issue #61 covers the work.

GitHub CI shows a successful build-test check and successful required build checks. The supplied evidence does not show direct exit statuses for make check-fmt, make lint, and make test. Report those command results in the PR description or a follow-up comment before merge if the repository requires explicit validation evidence.


✏️ Learnings added
Learnt from: leynos
URL: https://github.com/leynos/netsuke/pull/545

Timestamp: 2026-08-12T00:33:17.011Z
Learning: For PR reviews in `leynos/netsuke`, treat warnings as required work. Treat observability, code safety, documentation, and validation coverage as in scope. If required work is out of scope and no existing issue covers it, propose a GitHub follow-up issue.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@coderabbitai

This comment was marked as resolved.

Share the file-traversal assertion while keeping the named tests for the
`inspect_path` and `try_is_file` public contracts. Forward Whitaker's root
configuration to its isolated driver so its documented scoped exclusions
remain effective.
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 12, 2026

Copy link
Copy Markdown
Contributor
✅ 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
Contributor

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 `@test_support/src/fs.rs`:
- Around line 131-164: Update the Rustdoc for inspect_path to explicitly state
that fs::metadata follows symlinks, so dangling symlinks are reported as
PathState::Absent even when the directory entry exists. Keep the implementation
unchanged and document this contract near the behavior description or examples.

In `@test_support/src/manifest.rs`:
- Around line 80-91: Update the Rustdoc for install_before_persist_hook to
explicitly state that the installed action is one-shot and is removed after
run_before_persist_hook executes, so it runs at most once per guard even across
multiple ensure_manifest_exists calls.
- Around line 183-190: Update both PathState::NonDirectory arms in
ensure_parent_directory (test_support/src/manifest.rs:183-190) and
find_existing_ancestor (test_support/src/manifest.rs:234-241) to return
io::ErrorKind::NotADirectory instead of AlreadyExists, preserving their existing
error messages and control flow.

In `@test_support/src/manifest/tests.rs`:
- Around line 120-170: Extend both raced persistence tests,
raced_file_manifest_path_is_returned_unchanged and
raced_directory_manifest_path_is_rejected, to assert that the temporary
workspace directory contains no leftover staged temporary file after
ensure_manifest_exists returns or errors. Use the existing temp_path workspace
root and directory-inspection utilities, while preserving the current content,
error-kind, and returned-path assertions.
- Around line 343-347: Update the TargetState::ExistingDirectory |
TargetState::RacedDirectory branch to validate result is an error with
prop_assert! before extracting it, replacing result.expect_err. Preserve the
existing IsADirectory and expected_path assertions after safely obtaining the
error.
🪄 Autofix

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: dfc60452-dd35-4e7b-a09a-79a63e96e3a2

📥 Commits

Reviewing files that changed from the base of the PR and between 5cfed52 and 814c485.

📒 Files selected for processing (6)
  • Makefile
  • docs/developers-guide.md
  • test_support/src/fs.rs
  • test_support/src/fs_tests.rs
  • test_support/src/manifest.rs
  • test_support/src/manifest/tests.rs

Comment thread test_support/src/fs.rs
Comment thread test_support/src/manifest.rs
Comment thread test_support/src/manifest.rs
Comment thread test_support/src/manifest/tests.rs
Comment thread test_support/src/manifest/tests.rs
Report non-directory parent components as `NotADirectory` and cover both
internal branches. Confirm raced persistence removes staged files, improve
the property assertion, and document the filesystem and hook contracts.
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.

@wafflecat-df12

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor
✅ 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
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
test_support/src/manifest.rs (1)

151-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the raced-target classification into a named helper.

persist_manifest_file now nests a three-arm PathState match inside a three-arm persistence match. The function mixes two responsibilities: persistence and post-race target classification. Extract the AlreadyExists handling into a helper so each function keeps one responsibility and the nesting stays shallow.

♻️ Proposed extraction
 fn persist_manifest_file(file: NamedTempFile, manifest_path: &Utf8Path) -> io::Result<()> {
     match file.persist_noclobber(manifest_path.as_std_path()) {
         Ok(_) => Ok(()),
-        Err(e) if e.error.kind() == io::ErrorKind::AlreadyExists => {
-            match inspect_manifest_target(manifest_path)? {
-                fs::PathState::Directory => Err(manifest_path_is_directory_error(manifest_path)),
-                fs::PathState::NonDirectory => Ok(()),
-                fs::PathState::Absent => Err(io::Error::new(
-                    io::ErrorKind::AlreadyExists,
-                    format!(
-                        "Manifest target disappeared after no-clobber persistence reported it existed: {manifest_path}"
-                    ),
-                )),
-            }
-        }
+        Err(e) if e.error.kind() == io::ErrorKind::AlreadyExists => {
+            accept_raced_manifest_target(manifest_path)
+        }
         Err(e) => Err(io::Error::new(
             e.error.kind(),
             format!(
                 "Failed to persist manifest file to {} from {}: {}",
                 manifest_path,
                 e.file.path().display(),
                 e.error
             ),
         )),
     }
 }
+
+/// Classify a target that no-clobber persistence reported as already present.
+fn accept_raced_manifest_target(manifest_path: &Utf8Path) -> io::Result<()> {
+    match inspect_manifest_target(manifest_path)? {
+        fs::PathState::Directory => Err(manifest_path_is_directory_error(manifest_path)),
+        fs::PathState::NonDirectory => Ok(()),
+        fs::PathState::Absent => Err(io::Error::new(
+            io::ErrorKind::AlreadyExists,
+            format!(
+                "Manifest target disappeared after no-clobber persistence reported it existed: {manifest_path}"
+            ),
+        )),
+    }
+}

As per path instructions, "Seek to keep the cognitive complexity of functions no more than 9", "Adhere to single responsibility and CQRS", and "Move conditionals with >2 branches into a predicate function".

🤖 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 `@test_support/src/manifest.rs` around lines 151 - 176, Extract the
AlreadyExists branch from persist_manifest_file into a named helper that accepts
manifest_path, performs the inspect_manifest_target classification, and returns
the corresponding io::Result. Replace the nested match arm with a call to this
helper, preserving the Directory, NonDirectory, and Absent behaviors and leaving
other persistence errors unchanged.

Source: Path instructions

🤖 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 `@test_support/src/manifest/tests.rs`:
- Around line 342-396: Extract a shared conversion helper near the property test
that turns an error into TestCaseError::fail(error.to_string()), then replace
the repeated inline map_err closures throughout property_workspace and each
result branch with that helper. Preserve the existing error propagation and
assertion behavior while making each conversion a single helper call.

---

Outside diff comments:
In `@test_support/src/manifest.rs`:
- Around line 151-176: Extract the AlreadyExists branch from
persist_manifest_file into a named helper that accepts manifest_path, performs
the inspect_manifest_target classification, and returns the corresponding
io::Result. Replace the nested match arm with a call to this helper, preserving
the Directory, NonDirectory, and Absent behaviors and leaving other persistence
errors unchanged.
🪄 Autofix

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: 1f732dd7-edf4-49b4-a75c-5aad3a037484

📥 Commits

Reviewing files that changed from the base of the PR and between 5cfed52 and b11808b.

📒 Files selected for processing (6)
  • Makefile
  • docs/developers-guide.md
  • test_support/src/fs.rs
  • test_support/src/fs_tests.rs
  • test_support/src/manifest.rs
  • test_support/src/manifest/tests.rs

Comment on lines +342 to +396
let (_temp, temp_path) = property_workspace()?;
let cli_file = Utf8Path::new("manifest.yml");
let expected_path = temp_path.join(cli_file);
let staged_contents = manifest_yaml(&format!(
"targets:\n - name: {staged_name}\n command: \"echo hi\"\n"
))
.into_bytes();

match state {
TargetState::ExistingFile => fs::write(expected_path.as_std_path(), &competing_contents)
.map_err(|error| TestCaseError::fail(error.to_string()))?,
TargetState::ExistingDirectory => fs::create_dir(expected_path.as_std_path())
.map_err(|error| TestCaseError::fail(error.to_string()))?,
TargetState::Missing | TargetState::RacedFile | TargetState::RacedDirectory => {}
}

let expected_staged_contents = staged_contents.clone();
let hook_contents = competing_contents.clone();
let _hook = install_before_persist_hook(move |file, manifest_path| {
let replacement = replace_staged_manifest(file, manifest_path, &staged_contents)?;
match state {
TargetState::RacedFile => {
fs::write(manifest_path.as_std_path(), hook_contents)?;
}
TargetState::RacedDirectory => fs::create_dir(manifest_path.as_std_path())?,
TargetState::Missing
| TargetState::ExistingFile
| TargetState::ExistingDirectory => {}
}
Ok(replacement)
});
let result = ensure_manifest_exists(&temp_path, cli_file);

match state {
TargetState::Missing => {
let returned_path = result.map_err(|error| TestCaseError::fail(error.to_string()))?;
prop_assert_eq!(&returned_path, &expected_path);
let contents = fs::read(expected_path.as_std_path())
.map_err(|error| TestCaseError::fail(error.to_string()))?;
prop_assert_eq!(contents, expected_staged_contents);
}
TargetState::ExistingFile | TargetState::RacedFile => {
let returned_path = result.map_err(|error| TestCaseError::fail(error.to_string()))?;
prop_assert_eq!(&returned_path, &expected_path);
let contents = fs::read(expected_path.as_std_path())
.map_err(|error| TestCaseError::fail(error.to_string()))?;
prop_assert_eq!(contents, competing_contents);
}
TargetState::ExistingDirectory | TargetState::RacedDirectory => {
prop_assert!(result.is_err(), "directory target should be rejected");
let error = result
.err()
.ok_or_else(|| TestCaseError::fail("directory target should be rejected"))?;
prop_assert_eq!(error.kind(), io::ErrorKind::IsADirectory);
prop_assert!(error.to_string().contains(expected_path.as_str()));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Replace the repeated TestCaseError::fail(error.to_string()) mapping with one helper.

The property body repeats .map_err(|error| TestCaseError::fail(error.to_string())) eight times. Extract a single conversion helper and reuse it. The property assertions then read as one statement each.

♻️ Proposed helper
+fn fail(error: impl std::fmt::Display) -> TestCaseError {
+    TestCaseError::fail(error.to_string())
+}
-        let (_temp, temp_path) = property_workspace()?;
+        let (_temp, temp_path) = property_workspace()?;
@@
-            TargetState::ExistingFile => fs::write(expected_path.as_std_path(), &competing_contents)
-                .map_err(|error| TestCaseError::fail(error.to_string()))?,
-            TargetState::ExistingDirectory => fs::create_dir(expected_path.as_std_path())
-                .map_err(|error| TestCaseError::fail(error.to_string()))?,
+            TargetState::ExistingFile =>
+                fs::write(expected_path.as_std_path(), &competing_contents).map_err(fail)?,
+            TargetState::ExistingDirectory =>
+                fs::create_dir(expected_path.as_std_path()).map_err(fail)?,

Apply the same substitution in property_workspace and in each result branch.

As per path instructions, tests must avoid repetition; the guideline requires rstest fixtures and shared helpers "to avoid repetition between tests".

📝 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
let (_temp, temp_path) = property_workspace()?;
let cli_file = Utf8Path::new("manifest.yml");
let expected_path = temp_path.join(cli_file);
let staged_contents = manifest_yaml(&format!(
"targets:\n - name: {staged_name}\n command: \"echo hi\"\n"
))
.into_bytes();
match state {
TargetState::ExistingFile => fs::write(expected_path.as_std_path(), &competing_contents)
.map_err(|error| TestCaseError::fail(error.to_string()))?,
TargetState::ExistingDirectory => fs::create_dir(expected_path.as_std_path())
.map_err(|error| TestCaseError::fail(error.to_string()))?,
TargetState::Missing | TargetState::RacedFile | TargetState::RacedDirectory => {}
}
let expected_staged_contents = staged_contents.clone();
let hook_contents = competing_contents.clone();
let _hook = install_before_persist_hook(move |file, manifest_path| {
let replacement = replace_staged_manifest(file, manifest_path, &staged_contents)?;
match state {
TargetState::RacedFile => {
fs::write(manifest_path.as_std_path(), hook_contents)?;
}
TargetState::RacedDirectory => fs::create_dir(manifest_path.as_std_path())?,
TargetState::Missing
| TargetState::ExistingFile
| TargetState::ExistingDirectory => {}
}
Ok(replacement)
});
let result = ensure_manifest_exists(&temp_path, cli_file);
match state {
TargetState::Missing => {
let returned_path = result.map_err(|error| TestCaseError::fail(error.to_string()))?;
prop_assert_eq!(&returned_path, &expected_path);
let contents = fs::read(expected_path.as_std_path())
.map_err(|error| TestCaseError::fail(error.to_string()))?;
prop_assert_eq!(contents, expected_staged_contents);
}
TargetState::ExistingFile | TargetState::RacedFile => {
let returned_path = result.map_err(|error| TestCaseError::fail(error.to_string()))?;
prop_assert_eq!(&returned_path, &expected_path);
let contents = fs::read(expected_path.as_std_path())
.map_err(|error| TestCaseError::fail(error.to_string()))?;
prop_assert_eq!(contents, competing_contents);
}
TargetState::ExistingDirectory | TargetState::RacedDirectory => {
prop_assert!(result.is_err(), "directory target should be rejected");
let error = result
.err()
.ok_or_else(|| TestCaseError::fail("directory target should be rejected"))?;
prop_assert_eq!(error.kind(), io::ErrorKind::IsADirectory);
prop_assert!(error.to_string().contains(expected_path.as_str()));
let (_temp, temp_path) = property_workspace()?;
let cli_file = Utf8Path::new("manifest.yml");
let expected_path = temp_path.join(cli_file);
let staged_contents = manifest_yaml(&format!(
"targets:\n - name: {staged_name}\n command: \"echo hi\"\n"
))
.into_bytes();
match state {
TargetState::ExistingFile =>
fs::write(expected_path.as_std_path(), &competing_contents).map_err(fail)?,
TargetState::ExistingDirectory =>
fs::create_dir(expected_path.as_std_path()).map_err(fail)?,
TargetState::Missing | TargetState::RacedFile | TargetState::RacedDirectory => {}
}
let expected_staged_contents = staged_contents.clone();
let hook_contents = competing_contents.clone();
let _hook = install_before_persist_hook(move |file, manifest_path| {
let replacement = replace_staged_manifest(file, manifest_path, &staged_contents)?;
match state {
TargetState::RacedFile => {
fs::write(manifest_path.as_std_path(), hook_contents)?;
}
TargetState::RacedDirectory => fs::create_dir(manifest_path.as_std_path())?,
TargetState::Missing
| TargetState::ExistingFile
| TargetState::ExistingDirectory => {}
}
Ok(replacement)
});
let result = ensure_manifest_exists(&temp_path, cli_file);
match state {
TargetState::Missing => {
let returned_path = result.map_err(fail)?;
prop_assert_eq!(&returned_path, &expected_path);
let contents = fs::read(expected_path.as_std_path()).map_err(fail)?;
prop_assert_eq!(contents, expected_staged_contents);
}
TargetState::ExistingFile | TargetState::RacedFile => {
let returned_path = result.map_err(fail)?;
prop_assert_eq!(&returned_path, &expected_path);
let contents = fs::read(expected_path.as_std_path()).map_err(fail)?;
prop_assert_eq!(contents, competing_contents);
}
TargetState::ExistingDirectory | TargetState::RacedDirectory => {
prop_assert!(result.is_err(), "directory target should be rejected");
let error = result
.err()
.ok_or_else(|| TestCaseError::fail("directory target should be rejected"))?;
prop_assert_eq!(error.kind(), io::ErrorKind::IsADirectory);
prop_assert!(error.to_string().contains(expected_path.as_str()));
🤖 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 `@test_support/src/manifest/tests.rs` around lines 342 - 396, Extract a shared
conversion helper near the property test that turns an error into
TestCaseError::fail(error.to_string()), then replace the repeated inline map_err
closures throughout property_workspace and each result branch with that helper.
Preserve the existing error propagation and assertion behavior while making each
conversion a single helper call.

Source: Path instructions

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.

Replace NamedTempFile::persist with more explicit file creation method

3 participants