Skip to content

Stage cargo-binstall archives and simplify the binstall metadata - #534

Merged
leynos merged 15 commits into
mainfrom
binstall-archives
Aug 7, 2026
Merged

Stage cargo-binstall archives and simplify the binstall metadata#534
leynos merged 15 commits into
mainfrom
binstall-archives

Conversation

@leynos

@leynos leynos commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

This branch makes cargo binstall netsuke-build fetch real prebuilt
archives instead of falling back to a source build. It adopts the
cargo-binstall archive support in the shared stage-release-artefacts
action: every release target now stages a
{package_name}-{version}-{target}.tar.gz archive (plus a .sha256
sidecar) containing the bare netsuke binary, the release job hoists
those archives to the release root so they upload under their plain
names, and the six hand-maintained per-target pkg-url overrides in
Cargo.toml collapse to a single template with pkg-fmt = "tgz".

The branch is stacked on fix-snapshot
(#532) so its tests run
against the repaired version contracts; it also carries the
shared-actions pin bump that landed on main (#528). GitHub will
retarget it to main once #532 merges.

Review walkthrough

  • Start with .github/release-staging.toml for the [common.binstall] enablement.
  • Then review .github/workflows/release.yml for the hoist step that moves the staged archives to the dist/ root before upload-release-assets runs (it fails the job when no archives are found, so a staging regression cannot pass silently).
  • Cargo.toml replaces the six per-target overrides with the single { name }-{ version }-{ target }.tar.gz template.
  • Finish with tests/binstall_metadata_tests.rs, rewritten to hold the metadata, the staging configuration (enabled flag and default archive name), and the workflow (target matrix plus hoist step) to the new contract.

Validation

  • make check-fmt: pass
  • make lint (Clippy + Whitaker, warnings denied): pass
  • make test (full workspace nextest + doctests): pass

Notes

  • Publishing the v0.1.0-beta1 assets remains blocked on the Windows
    MSI fix in shared-actions#406;
    once that merges, netsuke's shared-actions pins need bumping past it
    and the release workflow re-running for the tag.
  • The archive naming uses the crate name (netsuke-build), matching
    binstall's { name } interpolation, while the other release assets
    keep their netsuke-based names.

Summary by Sourcery

Adopt staged cargo-binstall archives for netsuke-build releases and simplify the corresponding binstall metadata and workflows.

New Features:

  • Serve cargo-binstall installs from per-target {package_name}-{version}-{target}.tar.gz archives containing the netsuke binary.

Enhancements:

  • Define a common [package.metadata.binstall] template that covers all targets instead of per-target overrides.
  • Configure shared release staging to enable cargo-binstall archive generation for every release target.
  • Add a release workflow step to hoist staged cargo-binstall archives and checksums to the release root so they upload under plain archive names.
  • Update contract tests to validate the new binstall metadata, staging configuration, and release workflow behaviour.

CI:

  • Bump leynos/shared-actions references across CI, release, packaging, coverage, Dependabot automerge, mutation testing, and netsukefile-test workflows to a newer pinned revision.

Tests:

  • Restructure binstall metadata tests to assert the new archive-based distribution contract rather than per-target override URLs.

@coderabbitai

coderabbitai Bot commented Aug 6, 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

  • Use prebuilt per-target .tar.gz archives for cargo binstall netsuke-build.
  • Stage, validate, and hoist archives with .sha256 sidecars before release upload.
  • Simplify Cargo binstall metadata to one tgz URL template.
  • Add archive discovery, collision checks, symlink rejection, rollback, and failure-state handling.
  • Expand contract and property-based tests for metadata, staging, workflow ordering, CLI wiring, and filesystem errors.
  • Improve test-binary lookup diagnostics and share diagnostic JSON snapshot settings.
  • Update release documentation and ADR-007 to define the archive workflow and its required relationships.
  • Correct spelling configuration and documentation wording.
  • Update shared-action pins and related tests.

Publishing v0.1.0-beta1 remains blocked by the referenced Windows MSI fix.

Walkthrough

The change packages cargo-binstall releases as staged .tar.gz archives with checksum sidecars, validates and hoists them before upload, updates related contracts and documentation, improves test support, and centralizes diagnostic snapshot redaction.

Changes

Release packaging and installation contracts

Layer / File(s) Summary
Binstall archive contract
.github/release-staging.toml, Cargo.toml, docs/adr-007-publish-as-netsuke-build.md, docs/developers-guide.md, tests/binstall_metadata_tests.rs
Define target archive staging, tgz packaging, checksum sidecars, archive naming, and one package URL template.
Archive validation and hoisting
scripts/hoist_binstall_discovery.py, scripts/hoist_binstall_archives.py
Derive expected archive names, validate archive and checksum pairs, prevent partial moves, and roll back failed transfers.
Release workflow integration
.github/workflows/release.yml, tests/workflow_contracts/hoist_binstall_archives_test.py, Makefile
Run hoisting with the resolved release version before asset upload. Test validation, rollback, CLI, workflow, and generated cases. Install hypothesis for workflow contract tests.

Test-support and snapshot testing

Layer / File(s) Summary
Binary resolution and filesystem tests
test_support/src/netsuke.rs, test_support/src/fs.rs, test_support/src/fs_tests.rs, tests/documentation_installation_tests.rs
Use reusable fixtures and scenarios to test binary candidate precedence, filesystem errors, missing-binary diagnostics, and filesystem helpers. Record documentation examples only after validation.
Diagnostic snapshot redaction
src/snapshot_test_support.rs, src/diagnostic_json_tests.rs, docs/snapshot-testing-in-netsuke-using-insta.md
Centralize diagnostic snapshot settings and redact only the Netsuke generator version.
Documentation and spelling configuration
docs/execplans/rstest-bdd-v0-5-0-behavioural-suite-migration.md, docs/netsuke-design.md, typos.local.toml
Correct spelling and configure inline-code exclusions for typo checks.

Sequence Diagram(s)

sequenceDiagram
  participant ReleaseWorkflow
  participant HoistScript
  participant Distribution
  participant AssetUpload
  ReleaseWorkflow->>HoistScript: pass resolved release version
  HoistScript->>Distribution: validate and move archive/checksum pairs
  ReleaseWorkflow->>AssetUpload: upload assets from dist/
Loading

Possibly related PRs

  • leynos/vk#189: Modifies cargo-binstall release metadata and release asset packaging.
  • leynos/netsuke#532: Overlaps with diagnostic snapshot redaction and binary-locator test support.
  • leynos/netsuke#523: Modifies the package’s binstall metadata.

Suggested reviewers: codescene-access

Poem

Stage archives in ordered rows,
Validate checksums before they go.
Hoist each pair, then upload clean,
Restore failed moves to the scene.
Snapshots mask one version field,
Locator tests keep paths revealed.


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 1 inconclusive)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error The hoist tests are substantive, but no test asserts the new enable-cache: false setting or rejection of a dangling destination symlink; both regressions would pass. Add a YAML contract assertion for enable-cache: false and a behavioural case with a dangling symlink at an archive or sidecar destination.
Performance And Resource Use ❓ Inconclusive Pending code review of archive discovery complexity and collection bounds. Inspect the new discovery and hoisting implementation against release input sizes and resource-use requirements.
✅ Passed checks (18 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the archive staging, binstall metadata, workflow, tests, and release constraints covered by the changeset.
Title check ✅ Passed The title accurately summarises the main changes: staging cargo-binstall archives and simplifying binstall metadata.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
User-Facing Documentation ✅ Passed The user's guide already documents cargo binstall netsuke-build as the preferred prebuilt release-binary install and explains that it avoids the Rust toolchain requirement (lines 21–30).
Developer Documentation ✅ Passed Accept the check: the developer guide documents the archive contract, hoist boundary, Python 3.13 tooling, and tests; ADR-007 records the design, and the snapshot guide documents the new helper.
Module-Level Documentation ✅ Passed All changed Rust modules start with explanatory //! documentation, and all changed Python modules have module docstrings covering purpose, use, and component relationships.
Testing (Unit And Behavioural) ✅ Passed Accept the check: real filesystem tests cover success, edge cases, failures, rollback, CLI behaviour, and generated invariants; contract tests verify metadata and release workflow wiring.
Testing (Property / Proof) ✅ Passed Hypothesis coverage is substantive: it generates package/version, target-state combinations, and nested layouts, then checks archive-name, sidecar, all-or-none, and collision invariants; rollback c...
Testing (Compile-Time / Ui) ✅ Passed Mark this check passed: no new Rust/TypeScript compile-time behaviour requires trybuild; diagnostic JSON uses focused Insta snapshots, explicit version redaction, and semantic shape assertions.
Unit Architecture ✅ Passed Keep the change: discovery is read-only and exposes I/O failures, while hoist owns moves and rollback; injected paths, a patchable move seam, and boundary tests make side-effects explicit.
Domain Architecture ✅ Passed Release filesystem and TOML logic stays in dedicated Python infrastructure scripts; snapshot helpers are cfg(test), and ambient filesystem/process access remains in test_support adapters.
Observability ✅ Passed Accept the change: the hoist logs expected names, staged paths, failure reasons, and pair counts; set -e blocks upload on failure, while CI status provides the release signal.
Security And Privacy ✅ Passed No secrets or sensitive data were added. The new hoist uses pinned setup-uv, stdlib-only Python, lstat checks, symlink rejection, collision checks, and rollback; workflow permissions are unchanged.
Concurrency And State ✅ Passed Pass: the hoist uses private sequential state, runs before upload in one release job, and documents/tests validation, rollback, retry, and combined failure; no async or global state exists.
Architectural Complexity And Maintainability ✅ Passed The hoist has a focused discovery/mutation seam for the all-or-none invariant; StagedArchive and test scenarios have immediate consumers, with ownership and reuse documented.
Rust Compiler Lint Integrity ✅ Passed PR Rust diff adds no broad dead-code/import suppressions, artificial anchors, or clone calls; the new fs tests remain cfg(test)-only and changed imports/helpers have real uses.
✨ 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 binstall-archives

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

@sourcery-ai

sourcery-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Switches cargo-binstall support from hand-maintained per-target binary assets to staged tar.gz archives produced by the shared stage-release-artefacts action, hoisted to the release root and described by a single pkg-url template, with tests updated to enforce the new contract and shared GitHub Actions pins bumped.

Sequence diagram for cargo-binstall using staged archives

sequenceDiagram
  participant User
  participant cargo_binstall
  participant GitHub_Releases

  User->>cargo_binstall: run netsuke-build install
  cargo_binstall->>GitHub_Releases: GET {repo}/releases/download/v{version}/{name}-{version}-{target}.tar.gz
  GitHub_Releases-->>cargo_binstall: {name}-{version}-{target}.tar.gz
  cargo_binstall->>cargo_binstall: extract tgz
  cargo_binstall->>User: install {bin}{binary-ext}
Loading

File-Level Changes

Change Details Files
Adopt staged {package_name}-{version}-{target}.tar.gz archives for cargo-binstall and enable them in release staging.
  • Add [common.binstall] section to release-staging configuration, enabling binstall archive staging and relying on the default archive name
  • Document the relationship between staging config, hoisted archives, and binstall metadata in comments
.github/release-staging.toml
Simplify cargo-binstall metadata to a single template that matches the staged archives and remove per-target overrides.
  • Update [package.metadata.binstall] to use pkg-fmt = "tgz" and a single pkg-url template { repo }/releases/download/v{ version }/{ name }-{ version }-{ target }.tar.gz
  • Set bin-dir = "{ bin }{ binary-ext }" to match the archive layout
  • Remove six hand-maintained per-target pkg-url overrides and replace surrounding comments with an explanation of the new archive-based scheme
Cargo.toml
Update tests to assert the new binstall contract across Cargo.toml, staging config, and release workflow.
  • Replace per-target override and artefact-name logic with checks for [common.binstall] enablement, default archive naming, and absence of overrides
  • Add validation that pkg-fmt, pkg-url, and bin-dir match the new archive-based scheme using the crate name netsuke-build
  • Add a test to ensure every staging target has a corresponding target_key in the release workflow and that the workflow includes the new hoist step
tests/binstall_metadata_tests.rs
Hoist staged binstall archives to the release root during releases so they upload under plain archive names, and fail if none are found.
  • Add a Bash step "Hoist cargo-binstall archives to the release root" that moves dist/*/*/*.tar.gz and .tar.gz.sha256 files to dist/ and errors out if no archives exist
  • Ensure this hoist runs before upload-release-assets so uploaded asset names match the binstall pkg-url template
.github/workflows/release.yml
Bump shared-actions pins across CI, build, coverage, packaging, and automation workflows to a newer commit that includes binstall archive support and other fixes (e.g., Windows MSI).
  • Update references to leynos/shared-actions actions (determine-release-modes, ensure-cargo-version, export-cargo-metadata, rust-build-release, stage-release-artefacts, linux-packages, windows-package, macos-package, setup-rust, generate-coverage, upload-codescene-coverage, dependabot-automerge, mutation-cargo, netsukefile-test) to a new commit hash
  • Keep workflow logic otherwise unchanged aside from the new hoist step in release.yml and the updated staging behavior
.github/workflows/release.yml
.github/workflows/build-and-package.yml
.github/workflows/ci.yml
.github/workflows/coverage-main.yml
.github/workflows/dependabot-automerge.yml
.github/workflows/mutation-testing.yml
.github/workflows/netsukefile-test.yml

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.

Base automatically changed from fix-snapshot to main August 6, 2026 00:31
@leynos
leynos marked this pull request as ready for review August 6, 2026 00:32
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

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

This comment was marked as resolved.

leynos added a commit that referenced this pull request Aug 6, 2026
Address the review round on #534:

- Replace the inline hoist step with
  `scripts/hoist_binstall_archives.py`: the expected archive set is
  derived from `.github/release-staging.toml` and `Cargo.toml`, every
  target's archive and `.sha256` sidecar is validated before anything
  moves, expected and found names are logged, and a shortfall fails
  the release listing the missing assets instead of uploading a
  partial set. Behavioural coverage lives in
  `tests/workflow_contracts/hoist_binstall_archives_test.py` (happy
  path, missing target, missing sidecar, empty dist, duplicate
  candidates, wiring), and `tests/binstall_metadata_tests.rs` now
  pins the workflow to the script rather than a step name.
- Move the diagnostic-JSON snapshot settings into
  `snapshot_test_support::diagnostic_json_snapshot_settings()`,
  extending the module's existing shared helper so the documented
  policy holds for any module; tighten the redaction anchor to the
  enclosing `"generator"` object and prove non-generator `version`
  fields survive via an inline-snapshot test.
- Cover `try_is_file`'s absent-path branch, add an exhaustive
  presence-combination test pinning the locator's first-match and
  error invariants, and surface candidate-probe filesystem errors in
  a dedicated test.
- Drop the `example.id.clone()` in the registry-install contract by
  recording matches per example.
- Refresh the developers' guide binstall bullet for the archive
  scheme and point the insta guide at the shared helper.

The suggestion to make the `documentation_examples` module
declaration private is deliberately not applied: with a private
`mod`, the shared module's helpers used only by sibling test binaries
fail the `-D warnings` gate as dead code; `pub mod` follows the
existing loader-tests precedent. The ADR-007 reference is also
unactioned: its binstall passage describes the package-name decision,
which remains accurate under the archive scheme.
codescene-access[bot]

This comment was marked as outdated.

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 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[bot]

This comment was marked as resolved.

leynos added a commit that referenced this pull request Aug 6, 2026
Address the review round on #534:

- Replace the inline hoist step with
  `scripts/hoist_binstall_archives.py`: the expected archive set is
  derived from `.github/release-staging.toml` and `Cargo.toml`, every
  target's archive and `.sha256` sidecar is validated before anything
  moves, expected and found names are logged, and a shortfall fails
  the release listing the missing assets instead of uploading a
  partial set. Behavioural coverage lives in
  `tests/workflow_contracts/hoist_binstall_archives_test.py` (happy
  path, missing target, missing sidecar, empty dist, duplicate
  candidates, wiring), and `tests/binstall_metadata_tests.rs` now
  pins the workflow to the script rather than a step name.
- Move the diagnostic-JSON snapshot settings into
  `snapshot_test_support::diagnostic_json_snapshot_settings()`,
  extending the module's existing shared helper so the documented
  policy holds for any module; tighten the redaction anchor to the
  enclosing `"generator"` object and prove non-generator `version`
  fields survive via an inline-snapshot test.
- Cover `try_is_file`'s absent-path branch, add an exhaustive
  presence-combination test pinning the locator's first-match and
  error invariants, and surface candidate-probe filesystem errors in
  a dedicated test.
- Drop the `example.id.clone()` in the registry-install contract by
  recording matches per example.
- Refresh the developers' guide binstall bullet for the archive
  scheme and point the insta guide at the shared helper.

The suggestion to make the `documentation_examples` module
declaration private is deliberately not applied: with a private
`mod`, the shared module's helpers used only by sibling test binaries
fail the `-D warnings` gate as dead code; `pub mod` follows the
existing loader-tests precedent. The ADR-007 reference is also
unactioned: its binstall passage describes the package-name decision,
which remains accurate under the archive scheme.
leynos added a commit that referenced this pull request Aug 6, 2026
Address the second review round on #534:

- Make the hoist script's filesystem probes explicitly fallible:
  discovery walks with `os.walk(onerror=...)` so traversal failures
  propagate instead of being suppressed by `Path.rglob`, and a
  `stat`-based probe distinguishes absent assets from unreadable
  ones. Validate destination collisions at the dist root before any
  move, and document why rollback is deliberately absent (the dist
  tree is rebuilt from workflow artefacts on every job run, and the
  upload step is gated on this script succeeding).
- Give every public function a NumPy-style docstring, matching the
  convention of the repository's existing Python.
- Rework the behavioural tests: diagnostic messages on every assert,
  a justified E402 suppression, source-absence and content-integrity
  assertions on the happy path, an exhaustive parametrization proving
  nothing moves for any failing state combination, varied
  manifest/config inputs, destination-collision and traversal-error
  cases, a `main()` CLI invocation test, and a YAML-parsed assertion
  that the hoist step precedes `upload_assets`.
- Cover `try_is_file`'s regular-file branch, and split the fs unit
  tests into `fs_tests.rs` via the crate's `#[path]` convention to
  keep `fs.rs` within the Whitaker module-length cap.
@leynos
leynos force-pushed the binstall-archives branch from cfb1052 to a9365bb Compare August 6, 2026 09:14
codescene-access[bot]

This comment was marked as outdated.

leynos added a commit that referenced this pull request Aug 6, 2026
Complete the second review round on #534:

- Rewrite the developers' guide release-maintenance passage: the
  per-target binstall overrides and artefact-name table it referenced
  no longer exist. New targets are added to
  `.github/release-staging.toml` and the workflow target matrices;
  the single `pkg-url` template resolves them automatically, and the
  binstall and hoist contract tests fail if that drifts. The
  registry-install pin reference now names
  `tests/documentation_installation_tests.rs`, where those tests
  moved.
- Correct ADR-007's Decision and Consequences to describe the
  single-template mechanism (repo ADRs carry no amendment
  convention, so the prose is corrected in place); the core decision
  to publish as `netsuke-build` is unchanged.
- Annotate the fs unit tests with `#[rstest]`, per the AGENTS.md
  mandate for unit tests; the module previously used plain `#[test]`
  and was the outlier.
codescene-access[bot]

This comment was marked as outdated.

Comment thread tests/workflow_contracts/hoist_binstall_archives_test.py Outdated
@leynos

leynos commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Responding to the failed pre-merge checks, row by row (fixes in
a9365bb and d53faab):

Testing (Overall) — fixed. The suite now asserts source absence and
content integrity at the release root on the happy path, proves nothing
moves for every failing state combination (exhaustive parametrization over
missing-archive/missing-sidecar states per target), varies manifest and
staging-config inputs so hard-coded name logic cannot pass, and parses the
workflow with YAML to assert the hoist step precedes upload_assets.

Unit Architecture — fixed. Discovery now walks with
os.walk(onerror=...) so traversal failures propagate, the sidecar probe is
an explicit stat-based check that folds only FileNotFoundError into
"absent", destination collisions are validated before any move, and a
permission-denied traversal test pins the error boundary.

Developer Documentation — fixed. The developers' guide
release-maintenance passage and ADR-007's Decision/Consequences now describe
the single-template mechanism, staging, hoisting, and the contract tests;
the obsolete overrides and artefact-table guidance is gone.

Testing (Unit And Behavioural) — fixed. main() is invoked with CLI
arguments, asserting exit status, output, and moved files; the parsed
workflow wiring test is separate.

Testing (Property / Proof) — partially actioned, remainder dismissed
with reasoning: the failing-state space is covered exhaustively (every
per-target state combination), which is strictly stronger than sampled
property testing for a domain this small; the locator similarly carries a
bounded exhaustive presence-combination test. Adding Hypothesis would
introduce a new dependency to the test-workflow-contracts harness for no
additional coverage of these finite domains.

Concurrency And State — dismissed with reasoning, plus a mitigation:
rollback/resumability contravenes the execution model. The dist/ tree is
recreated from workflow artefacts on every run of the release job, and the
upload step only runs after the hoist script exits successfully, so an
interrupted run leaves no state a re-run would not rebuild (now documented
in the module docstring). Destination collisions — the one concrete hazard —
are validated before any move and covered by a test that proves a
pre-existing root file is never overwritten.

codescene-access[bot]

This comment was marked as outdated.

@coderabbitai

coderabbitai Bot commented Aug 6, 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

printf '%s\n' '== commits =='
git show --no-ext-diff --format='%H %s' --no-patch a9365bb d53faab

printf '%s\n' '== changed files =='
git diff --no-ext-diff --name-status a9365bb^..d53faab

printf '%s\n' '== implementation outline =='
ast-grep outline scripts/hoist_binstall_archives.py --items all || true

printf '%s\n' '== implementation =='
cat -n scripts/hoist_binstall_archives.py

printf '%s\n' '== behavioural test outline =='
ast-grep outline tests/workflow_contracts/hoist_binstall_archives_test.py --items all || true

printf '%s\n' '== behavioural tests =='
cat -n tests/workflow_contracts/hoist_binstall_archives_test.py

printf '%s\n' '== release workflow hoist and upload context =='
rg -n -C 12 'hoist_binstall_archives|upload_assets' .github/workflows/release.yml

printf '%s\n' '== documentation references =='
rg -n -C 5 'binstall|hoist|archive|staging' docs/developers-guide.md docs/adr/ADR-007* 2>/dev/null || true

Length of output: 1505


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '== current revision =='
git rev-parse --short HEAD || true
git status --short || true

printf '%s\n' '== candidate files =='
fd -a 'hoist_binstall_archives|ADR-007|developers-guide|release\.yml' . || true

printf '%s\n' '== implementation outline =='
ast-grep outline scripts/hoist_binstall_archives.py --items all 2>&1 || true
printf '%s\n' '== implementation =='
cat -n scripts/hoist_binstall_archives.py 2>&1 || true

printf '%s\n' '== behavioural test outline =='
ast-grep outline tests/workflow_contracts/hoist_binstall_archives_test.py --items all 2>&1 || true
printf '%s\n' '== behavioural tests =='
cat -n tests/workflow_contracts/hoist_binstall_archives_test.py 2>&1 || true

printf '%s\n' '== release workflow context =='
rg -n -C 12 'hoist_binstall_archives|upload_assets' .github/workflows/release.yml 2>&1 || true

printf '%s\n' '== documentation files and references =='
fd -a -i 'ADR-007*' docs 2>&1 || true
rg -n -i -C 5 'binstall|hoist|archive|staging' docs/developers-guide.md docs 2>&1 || true

printf '%s\n' '== claimed coverage markers =='
rg -n -C 3 'permission|collision|duplicate|sidecar|missing|parametr|product|exhaust|onerror|os\.walk|main\(' \
  scripts/hoist_binstall_archives.py tests/workflow_contracts/hoist_binstall_archives_test.py 2>&1 || true

Length of output: 155472


@leynos: Keep the exhaustive finite-state tests. They provide the required coverage without Hypothesis.

Keep the no-rollback design. The release job recreates dist/, and upload_assets runs only after successful hoisting.

Mark the testing, unit architecture, documentation, behavioural testing, workflow wiring, and finite-state coverage findings as verified. The current files implement and test those changes.

Fix one remaining destination-collision case. locate_archives() calls _is_file() for dist/<archive> and dist/<archive>.sha256. This does not reject an existing directory or another non-regular destination entry. In that condition, shutil.move() can move the archive into the existing directory instead of replacing the expected release-root path.

Reject every existing destination entry before the move loop. Add tests for archive and sidecar destination directories.


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

Timestamp: 2026-08-06T09:24:29.559Z
Learning: For the Netsuke release workflow, `dist/` is recreated from downloaded workflow artefacts for each release-job run. The `upload_assets` step runs only after `scripts/hoist_binstall_archives.py` succeeds. The archive-hoisting design therefore does not require rollback or resumability after interruption.

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.

codescene-access[bot]

This comment was marked as outdated.

@leynos

leynos commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

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

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

tests/workflow_contracts/hoist_binstall_archives_test.py

Comment on lines +85 to +112

def stage_pair(
    dist: Path,
    artefact: str,
    staging_dir: str,
    name: str,
    *,
    with_sidecar: bool = True,
) -> None:
    """Create an archive (and optionally its sidecar) in the nested layout.

    Parameters
    ----------
    dist
        Dist root to stage below.
    artefact
        Workflow-artefact directory name (first nesting level).
    staging_dir
        Staging directory name (second nesting level).
    name
        Archive file name to create.
    with_sidecar
        Whether to create the matching ``.sha256`` sidecar.
    """
    nested = dist / artefact / staging_dir
    nested.mkdir(parents=True, exist_ok=True)
    (nested / name).write_bytes(f"archive:{name}".encode())
    if with_sidecar:
        (nested / f"{name}.sha256").write_text(f"checksum:{name}", encoding="utf-8")

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

@coderabbitai

coderabbitai Bot commented Aug 6, 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

printf '%s\n' '== revision =='
git rev-parse --short HEAD

printf '%s\n' '== outline =='
ast-grep outline tests/workflow_contracts/hoist_binstall_archives_test.py --items all

printf '%s\n' '== stage_pair definition and uses =='
rg -n -C 6 '\bstage_pair\s*\(' tests/workflow_contracts/hoist_binstall_archives_test.py

printf '%s\n' '== nearby test configuration =='
sed -n '1,260p' tests/workflow_contracts/hoist_binstall_archives_test.py

printf '%s\n' '== Python test runner configuration =='
fd -a -t f 'pyproject.toml|pytest.ini|tox.ini|requirements.*|Makefile' . \
  -E .git -E target | sort

Length of output: 15318


@leynos tests/workflow_contracts/hoist_binstall_archives_test.pystage_pair

Do not make a further code change. The current codegraph at revision 0f842bd already fixes this diagnostic.

stage_pair now has four parameters:

def stage_pair(
    dist: Path,
    nested_dir: str,
    name: str,
    *,
    with_sidecar: bool = True,
) -> None:

The previous artefact and staging_dir parameters are now the single nested_dir parameter. Current callers pass values such as "netsuke-linux-amd64/s1". Further refactoring would reduce test-helper clarity without reducing the parameter count.

Paste this into the CodeScene diagnostic suppression field:

The current implementation of tests/workflow_contracts/hoist_binstall_archives_test.py::stage_pair has four parameters, which meets the configured maximum. The diagnostic refers to an earlier version that had separate artefact and staging_dir parameters. The current nested_dir parameter combines these two tightly coupled path fragments and preserves the test fixture’s release-artifact directory structure.

Do not refactor this helper further. The remaining parameters represent separate test inputs: the dist root, the relative staging directory, the archive name, and the optional sidecar state. Further aggregation would obscure test intent and provide no maintainability benefit.

You are interacting with an AI system.

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@leynos

leynos commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Aug 7, 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.

@leynos

leynos commented Aug 7, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Reconciling the round-3 pre-merge rows against head f01797c, row by row:

Testing (Overall) — ❌ — valid; both gaps are now closed in f01797c. test_release_workflow_disables_the_uv_cache asserts enable-cache: false on the release job's setup-uv step, and test_hoist_rejects_a_dangling_symlink_at_the_destination (parametrised over the archive and .sha256 destinations) stages a link to nowhere at each destination and asserts the hoist fails with "destination already occupied", the symlink is untouched, and nothing moves — pinning the lstat probe's treatment of any entry as a collision where exists() would report the dangling link as absent. The suite now stands at 45 passing workflow-contract tests.

Performance And Resource Use — ❓ — validated against release input sizes; no findings. Discovery walks the dist tree once with os.walk (O(entries), with onerror making traversal failures explicit) and holds only path lists in memory. Resolution scans the staged list once per expected name, which is O(names × staged); the release stages six targets with two files each, so the working set is a dozen paths against six names. Moves are sequential same-filesystem renames within dist/ (no copies of archive payloads), and the rollback list is bounded by the number of completed moves. There is no unbounded collection and no input-size-dependent recursion.

codescene-access[bot]

This comment was marked as outdated.

@coderabbitai

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

printf '%s\n' '== revision =='
git rev-parse --short HEAD
git status --short

printf '%s\n' '== relevant test symbols =='
ast-grep outline tests/workflow_contracts/hoist_binstall_archives_test.py \
  --items all --match 'test_release_workflow_disables_the_uv_cache|test_hoist_rejects_a_dangling_symlink_at_the_destination'

printf '%s\n' '== cache and dangling-symlink test context =='
rg -n -C 12 \
  'def test_release_workflow_disables_the_uv_cache|def test_hoist_rejects_a_dangling_symlink_at_the_destination|enable-cache|dangling|destination already occupied|symlink' \
  tests/workflow_contracts/hoist_binstall_archives_test.py .github/workflows/release.yml

printf '%s\n' '== discovery structure =='
ast-grep outline scripts/hoist_binstall_discovery.py --items all \
  --match '_walk_files|_resolve_archive|locate_archives'
rg -n -C 8 \
  '^(def (_walk_files|_resolve_archive|locate_archives)|class )|os\.walk|onerror|for .* in .*files|lstat|staged' \
  scripts/hoist_binstall_discovery.py

printf '%s\n' '== test command definition =='
rg -n -C 3 '^test-workflow-contracts:|test-workflow-contracts|hypothesis' Makefile

Length of output: 29306


@leynos Mark both rows as resolved at HEAD f01797c.

  • Testing (Overall): test_release_workflow_disables_the_uv_cache loads the release workflow and asserts enable-cache is false. .github/workflows/release.yml sets enable-cache: false on the release job setup-uv step. test_hoist_rejects_a_dangling_symlink_at_the_destination covers both the archive and .sha256 destinations. It asserts failure, reports "destination already occupied", preserves the dangling symlink, and verifies that no archive pair moves.

  • Performance And Resource Use: scripts/hoist_binstall_discovery.py::_walk_files performs one os.walk() traversal and propagates traversal errors through onerror. locate_archives() retains paths only. _resolve_archive() scans the staged-path list once per expected archive name. The release target set bounds this work to a small fixed input. The move and rollback lists are also bounded by the number of archive and sidecar files.

Do not open a follow-up issue for either row.

The source inspection did not run make test-workflow-contracts. It does not independently confirm the reported runtime total of 45 passing tests.


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

Timestamp: 2026-08-07T09:29:45.524Z
Learning: For the Netsuke release workflow, the `setup-uv` step in `.github/workflows/release.yml` must set `enable-cache: false`. The hoist script is stdlib-only and runs with `uv run --no-project`, so cache restoration adds a supply-chain input to a release job with `contents: write` without providing a build benefit.

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.

leynos added 14 commits August 7, 2026 10:35
The `v0.1.0-beta1` release left `cargo binstall netsuke-build` falling
back to a source build: the crates.io metadata was published, but the
GitHub release assets it pointed at were never uploaded, and the six
per-target `pkg-url` overrides depended on a fragile
workflow-artefact/staging-dir naming convention.

Adopt the `stage-release-artefacts` action's cargo-binstall support
instead:

- Enable `[common.binstall]` in `.github/release-staging.toml`, so
  every target stages a `{package_name}-{version}-{target}.tar.gz`
  archive (with a `.sha256` sidecar) containing the bare `netsuke`
  binary.
- Hoist those archives to the `dist/` root in the release job before
  `upload-release-assets` runs, so they upload under their plain
  names rather than the namespaced `<artifact>__<staging-dir>-` form;
  the step fails when no archives are found.
- Replace the six per-target overrides in `Cargo.toml` with a single
  `pkg-url` template (`{ name }-{ version }-{ target }.tar.gz`,
  `pkg-fmt = "tgz"`), keeping `Cargo.toml` as the single source of
  truth for the asset names.
- Rewrite `tests/binstall_metadata_tests.rs` to hold the metadata,
  the staging configuration, and the release workflow (including the
  hoist step) to the new contract.
Address the review round on #534:

- Replace the inline hoist step with
  `scripts/hoist_binstall_archives.py`: the expected archive set is
  derived from `.github/release-staging.toml` and `Cargo.toml`, every
  target's archive and `.sha256` sidecar is validated before anything
  moves, expected and found names are logged, and a shortfall fails
  the release listing the missing assets instead of uploading a
  partial set. Behavioural coverage lives in
  `tests/workflow_contracts/hoist_binstall_archives_test.py` (happy
  path, missing target, missing sidecar, empty dist, duplicate
  candidates, wiring), and `tests/binstall_metadata_tests.rs` now
  pins the workflow to the script rather than a step name.
- Move the diagnostic-JSON snapshot settings into
  `snapshot_test_support::diagnostic_json_snapshot_settings()`,
  extending the module's existing shared helper so the documented
  policy holds for any module; tighten the redaction anchor to the
  enclosing `"generator"` object and prove non-generator `version`
  fields survive via an inline-snapshot test.
- Cover `try_is_file`'s absent-path branch, add an exhaustive
  presence-combination test pinning the locator's first-match and
  error invariants, and surface candidate-probe filesystem errors in
  a dedicated test.
- Drop the `example.id.clone()` in the registry-install contract by
  recording matches per example.
- Refresh the developers' guide binstall bullet for the archive
  scheme and point the insta guide at the shared helper.

The suggestion to make the `documentation_examples` module
declaration private is deliberately not applied: with a private
`mod`, the shared module's helpers used only by sibling test binaries
fail the `-D warnings` gate as dead code; `pub mod` follows the
existing loader-tests precedent. The ADR-007 reference is also
unactioned: its binstall passage describes the package-name decision,
which remains accurate under the archive scheme.
Address the second review round on #534:

- Make the hoist script's filesystem probes explicitly fallible:
  discovery walks with `os.walk(onerror=...)` so traversal failures
  propagate instead of being suppressed by `Path.rglob`, and a
  `stat`-based probe distinguishes absent assets from unreadable
  ones. Validate destination collisions at the dist root before any
  move, and document why rollback is deliberately absent (the dist
  tree is rebuilt from workflow artefacts on every job run, and the
  upload step is gated on this script succeeding).
- Give every public function a NumPy-style docstring, matching the
  convention of the repository's existing Python.
- Rework the behavioural tests: diagnostic messages on every assert,
  a justified E402 suppression, source-absence and content-integrity
  assertions on the happy path, an exhaustive parametrization proving
  nothing moves for any failing state combination, varied
  manifest/config inputs, destination-collision and traversal-error
  cases, a `main()` CLI invocation test, and a YAML-parsed assertion
  that the hoist step precedes `upload_assets`.
- Cover `try_is_file`'s regular-file branch, and split the fs unit
  tests into `fs_tests.rs` via the crate's `#[path]` convention to
  keep `fs.rs` within the Whitaker module-length cap.
Complete the second review round on #534:

- Rewrite the developers' guide release-maintenance passage: the
  per-target binstall overrides and artefact-name table it referenced
  no longer exist. New targets are added to
  `.github/release-staging.toml` and the workflow target matrices;
  the single `pkg-url` template resolves them automatically, and the
  binstall and hoist contract tests fail if that drifts. The
  registry-install pin reference now names
  `tests/documentation_installation_tests.rs`, where those tests
  moved.
- Correct ADR-007's Decision and Consequences to describe the
  single-template mechanism (repo ADRs carry no amendment
  convention, so the prose is corrected in place); the core decision
  to publish as `netsuke-build` is unchanged.
- Annotate the fs unit tests with `#[rstest]`, per the AGENTS.md
  mandate for unit tests; the module previously used plain `#[test]`
  and was the outlier.
CodeScene flagged `stage_pair` at five arguments against the
four-argument ceiling. The artefact and staging-directory names only
ever combine into one relative path, so accept that path directly as
`nested_dir` and let call sites write `"artefact/staging"`.
Address the third review round on #534:

- Destination validation now probes with `lstat` via `_exists_any`:
  any entry — regular file, directory, symlink (including dangling),
  socket, FIFO, or device node — occupying either the archive or
  sidecar destination is a collision; only `FileNotFoundError` maps
  to "absent" and every other `OSError` propagates. Directory
  collisions at both destinations are covered by tests asserting
  failure, reporting, preserved sources, and no partial movement.
- The move phase is all-or-nothing: `_move_all` records completed
  moves and rolls them back in reverse on any failure, re-raising the
  original; a rollback failure surfaces both causes as an exception
  group. An injection test fails the third move, asserts propagation,
  an empty release root, and restored sources with original content,
  then clears the fault and proves a rerun succeeds. The module
  docstring's "rollback is deliberately absent" statement is replaced
  with the actual guarantee.
- Hypothesis joins the workflow-contract harness (via the Makefile's
  uv dependency list) with a bounded, derandomized generated-layout
  test: package names, versions, unique target sets, nesting layouts,
  and per-target states (ok, missing archive, missing sidecar,
  duplicate, collision), asserting the all-or-none and
  name-derivation invariants without hard-coded fixture names.
- Convert the locator tests to `rstest`: a `temp_root` fixture, a
  `LocatorScenario`-parametrized layout test, and eight named
  presence-mask cases for the exhaustive precedence test.
- Remove the comma before "because" in ADR-007's template sentence.
CodeScene flagged the Hypothesis test for excess arguments, nested
conditionals, and cyclomatic complexity. Group the four generated
values into a frozen `GeneratedHoistCase` dataclass (with the archive
name derivation and failure expectation as members), generate one
case per example, and extract workspace construction, per-target
staging, and the valid/invalid outcome assertions into focused
helpers. The test body is now linear: build, stage, run, dispatch.
Input domains, bounds, determinism, and every assertion are
unchanged; `make test-workflow-contracts` passes 36 tests.
CodeScene flagged the module's overall complexity, centred on
`_move_all` carrying both the forward-move transaction and the
reverse-order restoration loop. Move the latter into
`_rollback_completed_moves`, which restores completed moves in
reverse order and lets its failures propagate; `_move_all` now only
orchestrates — forward moves, delegation to the rollback on failure,
and the unchanged combination semantics (re-raise the original after
a clean rollback, or surface both failures as an exception group).
Behaviour, move order, and the all-or-nothing guarantee are
unchanged; the existing rollback-injection test exercises the helper
through `_move_all`, so no duplicate filesystem scenario is added.
The estate-wide en-GB-oxendict base dictionary in agent-helper-scripts
now ignores "mis-grouping", which `docs/netsuke-design.md` uses. The
`spelling-config` gate regenerates `typos.toml` and fails on any drift
from the tracked copy, so commit the refreshed output.
The shared en-GB-oxendict base dictionary no longer exempts inline
code spans, so `typos` began flagging verbatim identifiers such as
tokio's `flavor` attribute argument. Re-add the code-span ignore in
`typos.local.toml`, where repository-specific policy belongs, and
regenerate `typos.toml`.

Also correct two prose uses of "hand-written" (backticked as if code)
to "handwritten", matching the shared dictionary's phrase correction.
Address the outstanding CodeRabbit findings on the cargo-binstall hoist.

- Reject duplicate target triples in `release-staging.toml`. A repeated
  triple derived the same archive name twice, so `locate_archives` claimed
  the same staged file twice and the second move failed on an
  already-relocated source, rolling back an otherwise valid release.
- Probe staged assets with `lstat` and validate the archive as well as the
  sidecar. `Path.stat()` follows symlinks, so a symlink named like the
  archive was previously hoisted in its place, publishing a broken link.
- Run the hoist under a Python 3.13 installed by `setup-uv` rather than the
  runner's bare `python3`; the script relies on `BaseExceptionGroup`, which
  needs Python 3.11 or later.
- Split the read-only discovery and validation half into
  `scripts/hoist_binstall_discovery.py` so both files stay within the
  400-line cap and CodeScene's complexity thresholds, with a hard seam
  between validation and mutation.
- Replace the `monkeypatch` stub in the rollback test with a scoped
  `unittest.mock.patch.object`, keeping the retry outside the mock scope.
- Extract a shared temporary-directory fixture in `fs_tests.rs`, and drop
  the snapshot collection from the presence-combination test in
  `netsuke.rs` in favour of direct traversal.
- Correct the ADR and developers' guide: release archives are named after
  the Cargo package, not the binary, and `cargo binstall`'s default
  patterns miss them because they order the target before the version.
Reconcile the two actionable CodeRabbit pre-merge check rows.

- Add `test_hoist_surfaces_both_errors_when_the_rollback_also_fails`, which
  injects a forward move failure and then a rollback failure and asserts the
  raised group carries both. Verified by mutation: replacing the
  `BaseExceptionGroup` with a bare re-raise fails the new test, so the branch
  is no longer removable without a test failing.
- Refresh the developers' guide test map, which described
  `binstall_metadata_tests.rs` as holding per-target `cargo binstall`
  overrides — the opposite of the single `pkg-url` template it now pins — and
  document that `make test-workflow-contracts` runs a Hypothesis-backed suite
  provisioned through `uv run --with`.
- Disable the `setup-uv` cache in the release job: the hoist script is
  stdlib-only under `--no-project`, so a restored cache adds a
  supply-chain input to a `contents: write` job for no benefit.
- Report an omitted `[targets]` table through the named contract
  failure instead of a bare `KeyError`, and cover it alongside the
  empty-table case.
- Decompose the `StagedArchive | str` resolution with structural
  pattern matching, per the path instructions.
- Correct "fall back" to the noun "fallback" in the developers' guide.
The third review round noted two behaviours no test asserted: the
release job's `enable-cache: false` on `setup-uv`, and rejection of a
dangling symlink occupying an archive or sidecar destination. Both
regressions would previously have passed unnoticed.

Add a workflow-contract test for the cache setting and a parametrised
behavioural case staging a link to nowhere at each destination, pinning
the `lstat`-based probe's treatment of any entry as a collision.
@leynos

leynos commented Aug 7, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Have the following now been resolved?

tests/workflow_contracts/hoist_binstall_archives_test.py (1)

819-847: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Split this test module; it exceeds the 400-line cap.
The file now runs to 847 lines. The path instructions cap Python files at 400 logical lines and require large modules to be decomposed. Three cohesive groups are already visible: the example-based hoist cases, the release-workflow contract tests, and the Hypothesis generated-layout suite with its helpers. Move the shared fixtures and staging helpers into a conftest.py beside the tests, then split the generated-layout suite from lines 627 onwards into its own module.
As per path instructions, "Files must not exceed 400 logical lines: Decompose large modules into subpackages" and "Use pytest fixtures for shared setup (conftest.py or fixtures/)".

🤖 Detailed instructions

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

In @tests/workflow_contracts/hoist_binstall_archives_test.py around lines 819 -
847, Split the oversized test module into cohesive files: keep example-based
hoist cases and release-workflow contract tests separate, move the
generated-layout suite beginning at
test_hoist_invariants_hold_for_generated_layouts into its own module, and
relocate shared fixtures and staging helpers into a neighboring conftest.py.
Preserve existing pytest discovery, fixture behavior, and test coverage while
ensuring each Python file stays within the 400-line limit.

Source: Path instructions

@coderabbitai

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

printf '%s\n' '== revision =='
git rev-parse --short HEAD
git status --short

printf '%s\n' '== workflow-contract Python files and physical line counts =='
fd -a -e py . tests/workflow_contracts -x wc -l | sort -n

printf '%s\n' '== workflow-contract file tree =='
fd -a -t f . tests/workflow_contracts | sort

printf '%s\n' '== module outlines =='
fd -a -e py . tests/workflow_contracts -x sh -c '
  printf "\n--- %s ---\n" "$1"
  ast-grep outline "$1" --items all || true
' sh {} \;

printf '%s\n' '== shared fixture and helper definitions/usages =='
rg -n -C 3 \
  '^(def (workspace|stage_pair|run_hoist|assert_nothing_moved|prepare_generated|_stage_generated|assert_(valid|invalid)|test_hoist_invariants)|`@pytest`\.fixture|class (Generated|Prepared))|\b(workspace|stage_pair|run_hoist|assert_nothing_moved|prepare_generated|test_hoist_invariants)\b' \
  tests/workflow_contracts -g '*.py'

printf '%s\n' '== imports among workflow-contract modules =='
rg -n -C 2 '^(from |import )' tests/workflow_contracts -g '*.py'

printf '%s\n' '== current diff file list =='
git diff --name-status HEAD^..HEAD || true

Length of output: 53950


@leynos tests/workflow_contracts/hoist_binstall_archives_test.pyworkspace, stage_pair, run_hoist, assert_nothing_moved, GeneratedHoistCase, test_hoist_invariants_hold_for_generated_layouts

Do not mark this finding as resolved.

At current HEAD f01797c, tests/workflow_contracts/hoist_binstall_archives_test.py has 901 lines. The directory has no conftest.py and no separate generated-layout test module. The shared fixture and helpers remain in the oversized module. The generated-layout suite remains in the same module from Line 681 through Line 901.

Apply this split:

  1. Create tests/workflow_contracts/conftest.py.

    • Move the shared workspace fixture.
    • Move stage_pair, run_hoist, and assert_nothing_moved.
    • Move shared constants only if both resulting test modules require them.
    • Preserve the scripts/ import setup required by the helpers.
  2. Keep example-based hoist tests in tests/workflow_contracts/hoist_binstall_archives_test.py.

    • Keep this file at or below 400 logical lines.
  3. Create tests/workflow_contracts/hoist_binstall_archives_generated_test.py.

    • Move GeneratedHoistCase, GENERATED_CASES, generated workspace and staging helpers, generated outcome assertions, and test_hoist_invariants_hold_for_generated_layouts.
    • Keep this file at or below 400 logical lines.
  4. Move release-workflow contract tests into a separate module if the example-based module still exceeds 400 logical lines after the generated-layout split.

  5. Preserve pytest discovery, fixtures, Hypothesis settings, test identifiers, and all current assertions.

  6. Run make test-workflow-contracts after the split. Report the command result and the line count of each Python file in tests/workflow_contracts/.

Work in repository `leynos/netsuke` at the current PR revision.

Resolve the file-size and test-structure finding for the workflow-contract hoist tests.

Affected directory:
- `tests/workflow_contracts/`

Current state:
- `tests/workflow_contracts/hoist_binstall_archives_test.py` contains 901 lines.
- The repository policy limits Python files to 400 logical lines.
- The module contains shared fixture code, example-based hoist tests, release-workflow contract tests, and the Hypothesis generated-layout suite.
- No `tests/workflow_contracts/conftest.py` exists.

Requirements:

1. Create `tests/workflow_contracts/conftest.py`.
   - Move the shared `workspace` pytest fixture from
     `hoist_binstall_archives_test.py`.
   - Move shared helpers used by multiple hoist test modules:
     `stage_pair`, `run_hoist`, and `assert_nothing_moved`.
   - Move shared constants such as `VERSION`, `EXPECTED_NAMES`,
     `STAGING_CONFIG`, and `MANIFEST` only when both test modules need them.
   - Preserve the required `sys.path` insertion and import of
     `hoist_binstall_archives` for helpers that invoke production code.
   - Keep `conftest.py` at or below 400 logical lines.

2. Keep example-based archive hoist tests in:
   - `tests/workflow_contracts/hoist_binstall_archives_test.py`
   - Keep archive naming, validation failures, CLI, filesystem safety,
     rollback, and rollback-failure tests together where this remains within
     the 400-line cap.

3. Create:
   - `tests/workflow_contracts/hoist_binstall_archives_generated_test.py`
   - Move the complete Hypothesis generated-layout suite into this module:
     `TARGET_POOL`, `STATE_POOL`, `GeneratedHoistCase`, `GENERATED_CASES`,
     `build_generated_workspace`, `stage_generated_target`,
     `assert_invalid_generated_outcome`, `assert_valid_generated_outcome`,
     and `test_hoist_invariants_hold_for_generated_layouts`.
   - Preserve all Hypothesis strategies, bounds, settings, and assertions.
   - Keep this module at or below 400 logical lines.

4. If `hoist_binstall_archives_test.py` still exceeds 400 logical lines after
   the generated-layout suite moves, create a third cohesive module for the
   release-workflow contract tests:
   - `tests/workflow_contracts/release_workflow_hoist_test.py`
   - Move only tests that inspect `.github/workflows/release.yml`.
   - Keep each resulting Python file at or below 400 logical lines.

5. Preserve existing pytest discovery and test behaviour.
   - Do not change `scripts/hoist_binstall_archives.py`.
   - Do not weaken any example-based or generated validation coverage.
   - Do not duplicate fixtures or helpers between modules.

6. Validate the result.
   - Run `make test-workflow-contracts`.
   - Report its result.
   - Report `wc -l` output for all Python files in
     `tests/workflow_contracts/`.

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

Timestamp: 2026-08-07T09:42:43.002Z
Learning: In `tests/workflow_contracts/`, Python test modules must stay at or below 400 logical lines. Put shared pytest fixtures and reusable staging helpers in `tests/workflow_contracts/conftest.py`, and split cohesive example-based, workflow-contract, and Hypothesis-generated suites into separate modules when needed.

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.

@leynos
leynos force-pushed the binstall-archives branch from f01797c to a6eba4a Compare August 7, 2026 09:42
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

The workflow-contract hoist tests had grown to 901 lines against the
repository's 400-line cap. Divide the module by concern, with no test
behaviour changed:

- `conftest.py` holds the shared `workspace` fixture, the `stage_pair`,
  `run_hoist`, and `assert_nothing_moved` helpers, the shared staging
  constants, and the `sys.path` seam to the production script.
- `hoist_binstall_archives_test.py` keeps the example-based naming,
  validation-failure, filesystem-safety, and CLI tests.
- `hoist_binstall_rollback_test.py` holds the move-rollback and
  failed-rollback transaction tests.
- `hoist_binstall_archives_generated_test.py` holds the Hypothesis
  generated-layout suite with its strategies and bounds unchanged.
- `release_workflow_hoist_test.py` holds the `release.yml` wiring
  contracts.
codescene-access[bot]

This comment was marked as outdated.

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.

@leynos
leynos merged commit ae2e146 into main Aug 7, 2026
17 checks passed
@leynos
leynos deleted the binstall-archives branch August 7, 2026 10:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants