Skip to content

Cache config file layer discovery (#319) - #548

Open
lodyai[bot] wants to merge 3 commits into
mainfrom
issue-319-cache-config-file-layer-discovery-to-avoid-double-i-o-on-startup
Open

Cache config file layer discovery (#319)#548
lodyai[bot] wants to merge 3 commits into
mainfrom
issue-319-cache-config-file-layer-discovery-to-avoid-double-i-o-on-startup

Conversation

@lodyai

@lodyai lodyai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

This branch discovers file-backed configuration layers once during diagnostic
mode resolution and passes the loaded result to the full configuration merge.
It preserves standalone merge callers, selector precedence, and verbose
selector tracing without a second environment lookup or filesystem load.

The regression test drives the diagnostic and merge phases with one
mockable::MockEnv, requiring exactly one NETSUKE_CONFIG lookup while
confirming that configuration values remain merged.

Closes #319.

Validation

  • make check-fmt
  • make test (1,915 tests passed; 1 skipped; doctests passed)
  • make lint
  • make typecheck
  • coderabbit review --agent (0 findings)

References

Summary by Sourcery

Cache configuration file layer discovery so diagnostic JSON resolution and full configuration merge share a single environment-driven discovery pass.

New Features:

  • Introduce a DiscoveredLayers struct to hold discovered config file layers, associated errors, and selector resolution for reuse across diagnostic and merge phases.
  • Add APIs to resolve diagnostic JSON mode while returning the discovered file layers for subsequent merging.

Bug Fixes:

  • Ensure NETSUKE_CONFIG is only read once by reusing the cached discovery result between diagnostic and merge phases, preventing redundant environment lookups and file loads.

Enhancements:

  • Replace custom EnvProvider with the mockable crate’s Env/DefaultEnv interfaces across CLI discovery, diagnostics, merge logic, and tests.
  • Adjust discovery tracing to reuse cached selector resolution, preserving verbose trace output without repeating discovery.
  • Refine environment handling in merges to consume explicit environment maps via Env::all rather than custom entry collection.

Build:

  • Add the mockable crate as a build dependency to support environment mocking.

Tests:

  • Extend unit and integration tests to cover cached file layer reuse between diagnostic and merge phases, explicit config success/failure, discovery without selectors, and project-scope second-pass behaviour.
  • Update existing discovery, precedence, diagnostics, and BDD tests to use MockEnv-based helpers instead of the previous TestEnv environment double.

@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

  • Cache file-backed configuration layer discovery between diagnostic resolution and full configuration merging.
  • Add DiscoveredLayers to retain loaded layers, deferred errors, and path-resolution diagnostics.
  • Reuse cached layers and replay selector tracing without repeating environment lookup or filesystem loading.
  • Replace the custom environment provider with mockable::Env, DefaultEnv, and MockEnv.
  • Preserve selector precedence, standalone merge callers, explicit configuration errors, selector-free discovery, and project-scope behaviour.
  • Add tests for cached discovery and single selector lookup, including issue #319 coverage.
  • Update public exports and merge and diagnostic APIs.
  • Add mockable as a build dependency.

Walkthrough

The CLI now uses mockable::Env, retains configuration discovery results, resolves diagnostic JSON mode from those results, and passes the same layers into merging. Tests replace custom environment doubles with MockEnv.

Changes

Configuration discovery and environment abstraction

Layer / File(s) Summary
Discover and retain configuration layers
Cargo.toml, src/cli/discovery.rs, src/cli/discovery_layers.rs, src/cli/test_support.rs, src/cli/discovery_layer_tests.rs, src/cli/discovery_tracing_tests.rs, src/cli/config_path_precedence_tests.rs
DiscoveredLayers retains layers, errors, and path-resolution diagnostics. Discovery uses mockable::Env. Tests cover explicit paths, empty discovery, project-scope discovery, and precedence.
Resolve diagnostic mode from cached layers
src/cli/diag.rs
JSON resolution returns the effective JSON setting and discovered layers. It reports discovery errors before parsing environment values.
Merge retained layers during startup
src/cli/merge.rs, src/cli/mod.rs, src/main.rs, tests/bdd/helpers/config_environment.rs, tests/cli_tests/merge_diag.rs, tests/logging_stderr/config_tracing.rs
Startup passes discovered layers into merging. The merge combines file, environment, and CLI layers. Tests verify layer reuse, non-Unicode environment handling, and diagnostic tracing.

Sequence Diagram(s)

sequenceDiagram
  participant main
  participant diag
  participant discovery
  participant merge
  participant config_files
  main->>diag: resolve JSON mode and layers
  diag->>discovery: discover configuration
  discovery->>config_files: load configuration layers
  config_files-->>discovery: return loaded layers
  discovery-->>diag: return DiscoveredLayers
  diag-->>main: return JSON mode and DiscoveredLayers
  main->>merge: merge with DiscoveredLayers
  merge-->>main: return merged CLI configuration
Loading

Possibly related PRs

Suggested labels: Issue

Suggested reviewers: leynos, codescene-access

Poem

Discover layers once.
Reuse them for JSON and merge.
Mock the environment.
Replay the selector trace.
Read each config file once.


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 4 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Unit Architecture ❌ Error The query path calls tracing and warning functions during discovery, and tests capture those events; DiscoveredLayers also combines cached data with diagnostic replay. Return discovery data and diagnostics without emitting them. Move tracing and warnings to the startup boundary, and expose discovery failures through an explicit Result or outcome type.
User-Facing Documentation ⚠️ Warning The PR removes public ConfigEnvProvider and ConfigStdEnvProvider and adds public cache APIs, but changes no user guide, changelog, or migration document. Document the public API removals and additions in docs/users-guide.md, and signpost this breaking change in the next pre-1.0 migration guide.
Developer Documentation ⚠️ Warning The PR changes CLI architecture and adds mockable, but no docs/ files changed; the guide still documents removed EnvProvider APIs and omits DiscoveredLayers reuse. Update docs/developers-guide.md and the relevant configuration design or execplan with the mockable::Env boundary, cached-layer flow, public APIs, and dependency requirement.
Testing (Compile-Time / Ui) ⚠️ Warning Public CLI signatures and exports changed, but no dedicated Rust UI/trybuild test covers them; merge_diag.rs only exercises runtime integration calls. Add a trybuild or equivalent Rust compile-pass/UI fixture for the changed CLI API, and assert any required compile-fail contract.
Observability ⚠️ Warning The PR caches configuration discovery, but src/cli and src/main.rs emit no cache reuse or discovery latency metric; the repository already instruments other caches with bounded metrics. Add low-cardinality metrics for discovery outcome, cached reuse, and discovery duration. Keep selector and failure fields bounded and free of paths, secrets, and payloads.
Rust Compiler Lint Integrity ❓ Inconclusive Investigation is still in progress; the checkout contains the PR across multiple commits, so the complete change set has not yet been reviewed. Inspect the full PR range and changed Rust code for suppressions, stale helpers, and unnecessary clones.
✅ Passed checks (14 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR caches discovered layers, reuses them during diagnostic resolution and merging, and adds regression coverage for issue #319.
Out of Scope Changes check ✅ Passed The environment abstraction changes, dependency update, tracing updates, and tests support the linked caching objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Testing (Overall) ✅ Passed Tests exercise cached discovery end-to-end: one NETSUKE_CONFIG lookup is enforced while JSON and jobs=13 are merged; discovery errors, project scope, and trace replay also have assertions.
Module-Level Documentation ✅ Passed Accept the check: all 393 scanned Rust modules have //! documentation, and every changed module states its purpose, utility, or component relationship.
Testing (Unit And Behavioural) ✅ Passed Accept the coverage: unit, property, edge-case, and error-path tests cover discovery and JSON rules; public API integration enforces one selector lookup and merged values; subprocess tests verify s...
Testing (Property / Proof) ✅ Passed Accept the coverage: resolve_config_path_obeys_precedence_invariant uses proptest over generated paths, while diag_and_merge_reuse_one_discovery_result checks exact-once lookup and layer reuse.
Domain Architecture ✅ Passed The change stays within the CLI composition boundary: config files, environment, OrthoConfig layers, and tracing remain in src/cli and main; IR, manifest, and runner domain models do not gain infra...
Security And Privacy ✅ Passed The PR adds no secrets or auth changes; config values stay in typed merge layers, raw environment entries are validated, and selector diagnostics expose only bounded path metadata.
Performance And Resource Use ✅ Passed Accept the change: startup reuses one DiscoveredLayers result, removes duplicate discovery I/O, and tests enforce one NETSUKE_CONFIG lookup while retaining bounded layer storage.
Concurrency And State ✅ Passed Keep DiscoveredLayers as an owned value: startup resolves it, then moves it into merge; no new async or shared mutable state exists, and the test enforces one selector lookup.
Architectural Complexity And Maintainability ✅ Passed DiscoveredLayers provides an immediate cache seam for diagnostic and merge phases; the custom EnvProvider is removed, composition remains explicit, and tests verify reuse.
Title check ✅ Passed The title accurately describes caching configuration file layer discovery and links issue #319.
Description check ✅ Passed The description clearly explains the cached discovery changes, related APIs, tests, validation, and issue references.
📋 Issue Planner

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

View plan used: #319

✨ 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-319-cache-config-file-layer-discovery-to-avoid-double-i-o-on-startup

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.

@sourcery-ai

sourcery-ai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactors CLI configuration discovery and merge to cache file-backed config layers discovered in a single pre-pass driven by a generic Env interface, then reuse those layers for diagnostics and full merge; replaces custom EnvProvider with mockable::Env/DefaultEnv, adjusts JSON/merge flows and tests to use the cached DiscoveredLayers and the mockable test helpers.

Sequence diagram for cached config layer discovery and merge

sequenceDiagram
    actor User
    participant Main as main_rs
    participant Diag as cli_diag
    participant Discovery as cli_discovery
    participant Merge as cli_merge
    participant Env as DefaultEnv

    User ->> Main: run_with_args
    Main ->> Diag: resolve_diag_mode_or_exit(parsed_cli, matches, fallback_mode)
    Diag ->> Diag: resolve_json_and_layers_with_env(cli, matches, Env)
    Diag ->> Discovery: collect_diag_file_layers_with_env(cli, Env)
    Discovery ->> Discovery: discover_file_layers(cli, Env)
    Discovery ->> Env: resolve_config_selector(cli.config, Env)
    Discovery -->> Diag: DiscoveredLayers
    Diag ->> Diag: json_from_layers(DiscoveredLayers.layers())
    Diag ->> Env: json_from_env(Env)
    Diag -->> Main: (DiagMode, DiscoveredLayers)

    Main ->> Discovery: DiscoveredLayers.replay_config_path_trace()
    Main ->> Merge: merge_cli_or_exit(parsed_cli, matches, DiagMode, DiscoveredLayers)
    Merge ->> Merge: merge_with_layers(cli, matches, Env, DiscoveredLayers)
    Merge ->> Discovery: push_discovered_file_layers(composer, errors, DiscoveredLayers)
    Merge ->> Env: Env.all()
    Merge ->> Merge: Figment::from(EnvironmentLayer::new(env_entries))
    Merge -->> Main: merged Cli
    Main -->> User: exit code / program outcome
Loading

File-Level Changes

Change Details Files
Introduce a cached configuration discovery result (DiscoveredLayers) and use it for both diagnostic JSON resolution and full configuration merge.
  • Add DiscoveredLayers struct to hold discovered file layers, associated errors, and the ConfigPathResolution used for tracing.
  • Implement discover_file_layers to perform one discovery pass using Env, returning DiscoveredLayers instead of raw layers or immediate errors.
  • Implement push_discovered_file_layers to add previously discovered layers and their errors into a MergeComposer, preserving existing error-accumulation semantics.
  • Change collect_file_layers_with_env to return (ConfigPathResolution, OrthoResult<Vec>) so resolution and outcome can be reused.
  • Update collect_diag_file_layers_with_env to return DiscoveredLayers instead of OrthoResult<Vec> and adjust callers/tests accordingly.
  • Expose methods on DiscoveredLayers for borrowing layers, accessing first_error, splitting into parts, and replaying config path trace without re-querying the environment.
src/cli/discovery.rs
src/cli/discovery_layer_tests.rs
src/cli/discovery_tracing_tests.rs
Replace the bespoke EnvProvider/StdEnvProvider with the mockable::Env/DefaultEnv abstraction and adapt discovery, diagnostic, merge, and test code to the new interface.
  • Remove EnvProvider trait and StdEnvProvider implementation; switch all discovery and diagnostic functions to accept &impl mockable::Env.
  • Use Env::os_string for individual lookups and Env::all for environment entries instead of EnvProvider::get/entries.
  • Update env_config_path and resolve_config_selector to operate on Env with os_string.
  • Change merge_with_config to use DefaultEnv and delegate to merge_with_layers with discovered file layers.
  • In merge_with_layers, build EnvironmentLayer from Env::all instead of env.entries().
  • Adjust CLI module exports to re-export DiscoveredLayers and the new resolve_json_and_layers_with_env and merge_with_layers functions instead of EnvProvider-related types.
src/cli/discovery.rs
src/cli/diag.rs
src/cli/merge.rs
src/cli/mod.rs
src/cli/discovery_layers.rs
src/main.rs
Cargo.toml
Update diagnostic JSON resolution to compute JSON mode and discovered layers together, then reuse those layers in the subsequent merge and tracing, eliminating duplicate discovery and environment lookups.
  • Add resolve_json_and_layers_with_env to compute JSON enabled flag and return DiscoveredLayers from the diagnostic discovery pass.
  • Refactor resolve_merged_json_with_env to call resolve_json_and_layers_with_env and discard the layers when only JSON mode is needed.
  • Change json_from_file_layers into json_from_layers, operating on a slice of MergeLayer without performing discovery itself.
  • Ensure discovery errors are surfaced immediately by resolve_json_and_layers_with_env using DiscoveredLayers::first_error.
  • Update json_from_env to work with Env::os_string instead of EnvProvider::get.
  • In main, replace resolve_json_mode_or_exit with resolve_diag_mode_or_exit that returns both DiagMode and DiscoveredLayers, replays config path trace after filters are set, and passes the cached layers into merge_cli_or_exit which now calls merge_with_layers.
src/cli/diag.rs
src/main.rs
Rework test support and tests to use mockable::MockEnv builders rather than custom TestEnv, and add coverage for the single-pass discovery and reuse of config layers between diagnostic and merge phases.
  • Replace TestEnv helper with mock_env_with and empty_mock_env that construct MockEnv instances with predefined os_string behaviour.
  • Adjust unit tests in discovery, discovery tracing, config path precedence, and diag modules to use empty_mock_env/mock_env_with and interact with Env/os_string instead of EnvProvider/get.
  • Add new tests in discovery_layer_tests.rs to validate discover_file_layers behaviour for explicit configs, missing configs, discovery without selectors, and project-scope second-pass discovery.
  • In merge_diag integration tests, replace the old TestEnv with MockEnv using expectation-based setup for os_string and add diag_and_merge_reuse_one_discovery_result test to assert that NETSUKE_CONFIG is looked up exactly once and that discovered layers are reused correctly by merge_with_layers.
  • Update BDD config_environment helper to build a MockEnv from TestWorld, wiring os_string and all to the world’s env_vars_forward, and use merge_with_config_and_env with this environment.
src/cli/test_support.rs
src/cli/discovery.rs
src/cli/discovery_layer_tests.rs
src/cli/discovery_tracing_tests.rs
src/cli/config_path_precedence_tests.rs
src/cli/diag.rs
tests/cli_tests/merge_diag.rs
tests/bdd/helpers/config_environment.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#319 Introduce a cache of discovered configuration file layers so they are computed once and reused across startup phases.
#319 Ensure both diagnostic JSON resolution and the full configuration merge consume the same cached discovery result, avoiding redundant filesystem I/O and environment lookups.
#319 Add an automated check (test or benchmark) that demonstrates the reduction in duplicate discovery, such as enforcing a single environment lookup/config load across diagnostic and merge phases.

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.

codescene-access[bot]

This comment was marked as outdated.

@leynos

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

src/cli/discovery_layer_tests.rs

Comment on lines +201 to +219

fn discover_file_layers_records_an_explicit_load_error() -> Result<()> {
    let dir = tempdir().context("create temporary config directory")?;
    let cli = Cli {
        config: Some(dir.path().join("missing.toml")),
        ..Cli::default()
    };

    let discovered = discover_file_layers(&cli, &empty_mock_env());

    ensure!(
        discovered.layers().is_empty(),
        "a missing explicit config should not produce layers"
    );
    ensure!(
        discovered.errors.len() == 1,
        "a missing explicit config should record one error"
    );
    Ok(())
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: discover_file_layers_records_an_explicit_load_error,discover_file_layers_supports_discovery_without_a_selector

@coderabbitai

This comment was marked as resolved.

@leynos
leynos marked this pull request as ready for review August 9, 2026 14:04
codescene-access[bot]

This comment was marked as outdated.

@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

@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

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

Inline comments:
In `@tests/bdd/helpers/config_environment.rs`:
- Around line 13-29: Update environment_from_world so selector_values is built
directly from env_vars_forward, preserving raw OsString values for
expect_os_string; retain the UTF-8-filtered values map exclusively for
expect_all. Add Unix-specific coverage verifying that a non-UTF-8 NETSUKE_CONFIG
path is returned by Env::os_string rather than treated as unset.
🪄 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: 296d6fcc-282c-4b8d-aee3-3dfdbcd4d84d

📥 Commits

Reviewing files that changed from the base of the PR and between 48d1e6c and c81eefb.

📒 Files selected for processing (13)
  • Cargo.toml
  • src/cli/config_path_precedence_tests.rs
  • src/cli/diag.rs
  • src/cli/discovery.rs
  • src/cli/discovery_layer_tests.rs
  • src/cli/discovery_layers.rs
  • src/cli/discovery_tracing_tests.rs
  • src/cli/merge.rs
  • src/cli/mod.rs
  • src/cli/test_support.rs
  • src/main.rs
  • tests/bdd/helpers/config_environment.rs
  • tests/cli_tests/merge_diag.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 on lines +13 to +29
fn environment_from_world(world: &TestWorld) -> MockEnv {
let values = world
.env_vars_forward
.borrow()
.iter()
.filter_map(|(key, raw_value)| {
raw_value
.to_str()
.map(|text| (key.clone(), text.to_owned()))
})
.collect::<HashMap<_, _>>();
let selector_values = values.clone();
let mut env = MockEnv::new();
env.expect_os_string()
.returning(move |key| selector_values.get(key).map(OsString::from));
env.expect_all().return_const(values);
env

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -eu

printf '%s\n' '--- target helper ---'
cat -n tests/bdd/helpers/config_environment.rs

printf '%s\n' '--- relevant symbols ---'
rg -n -C 3 'MockEnv|os_string|NETSUKE_CONFIG|config.*discover|discover.*config|selector' . \
  -g '*.rs' -g '*.toml' -g '*.md' | head -n 300

Repository: leynos/netsuke

Length of output: 22659


🏁 Script executed:

set -eu

printf '%s\n' '--- helper structure ---'
ast-grep outline tests/bdd/helpers/config_environment.rs

printf '%s\n' '--- MockEnv definitions and implementations ---'
rg -n -C 8 'struct MockEnv|impl MockEnv|trait Env|fn os_string|fn all' . \
  -g '*.rs' | head -n 400

printf '%s\n' '--- selector and environment call sites ---'
rg -n -C 6 'NETSUKE_CONFIG|os_string\(|\.all\(\)|Env::' . \
  -g '*.rs' | head -n 500

Repository: leynos/netsuke

Length of output: 48919


🏁 Script executed:

set -eu

printf '%s\n' '--- configuration selector resolver ---'
cat -n src/cli/discovery.rs | sed -n '140,245p'

printf '%s\n' '--- discovery callers and environment injection ---'
rg -n -C 8 'resolve_config_selector|discover.*config|merge_with_config_and_env|environment_from_world|env_vars_forward' \
  src tests -g '*.rs' | head -n 500

printf '%s\n' '--- TestWorld definition and environment steps ---'
rg -n -C 10 'struct TestWorld|env_vars_forward|NETSUKE_CONFIG' tests -g '*.rs' | head -n 500

printf '%s\n' '--- dependency declarations ---'
rg -n -C 3 'mockable' Cargo.toml Cargo.lock tests test_support -g '*.toml' -g '*.rs' | head -n 250

Repository: leynos/netsuke

Length of output: 50370


🏁 Script executed:

set -eu

python3 - <<'PY'
from pathlib import Path

source = Path("tests/bdd/helpers/config_environment.rs").read_text()
assert "raw_value.to_str()" in source
assert "selector_values = values.clone()" in source

# Model the exact transformation used by the helper on Unix, where arbitrary
# bytes can represent an OsString. The result must not equal the original.
raw = b"/tmp/config-\xff.toml"
decoded = raw.decode("utf-8", errors="strict") if False else None
filtered = None
try:
    filtered = raw.decode("utf-8")
except UnicodeDecodeError:
    filtered = None

reconstructed = filtered.encode() if filtered is not None else None
print({
    "raw_value": raw,
    "to_str_result": filtered,
    "os_string_value_returned_by_current_fixture": reconstructed,
    "value_lost": reconstructed != raw,
})
PY

printf '%s\n' '--- exact environment APIs in checked-in sources ---'
rg -n -C 5 'pub trait Env|trait Env|fn all|fn os_string|expect_all|expect_os_string' \
  . -g '*.rs' -g '*.toml' | head -n 300

Repository: leynos/netsuke

Length of output: 240


🏁 Script executed:

set -eu

python3 - <<'PY'
from pathlib import Path

# Reproduce the helper's transformation for a Unix OsString containing an
# invalid UTF-8 byte. The fixture filters this value before configuring
# `expect_os_string`.
raw_value = b"/tmp/config-\xff.toml"
utf8_value = None
try:
    utf8_value = raw_value.decode("utf-8")
except UnicodeDecodeError:
    pass

returned_by_current_fixture = (
    utf8_value.encode("utf-8") if utf8_value is not None else None
)

print(f"raw_value={raw_value!r}")
print(f"to_str={utf8_value!r}")
print(f"os_string_returned_by_current_fixture={returned_by_current_fixture!r}")
print(f"selector_value_preserved={returned_by_current_fixture == raw_value}")
assert utf8_value is None
assert returned_by_current_fixture is None
assert returned_by_current_fixture != raw_value
PY

printf '%s\n' '--- helper call sites ---'
rg -n -C 4 'merge_with_world_env|track_env_var' tests/bdd -g '*.rs' | head -n 300

printf '%s\n' '--- BDD feature references to NETSUKE_CONFIG ---'
rg -n -C 3 'NETSUKE_CONFIG|configuration file|config path' tests -g '*.feature' -g '*.rs' | head -n 300

Repository: leynos/netsuke

Length of output: 28433


Preserve raw OsString values for Env::os_string.

Build selector_values from env_vars_forward before the UTF-8 filter. Keep the filtered map only for Env::all. Add Unix coverage for a non-UTF-8 NETSUKE_CONFIG path, which the current filter treats as unset.

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

In `@tests/bdd/helpers/config_environment.rs` around lines 13 - 29, Update
environment_from_world so selector_values is built directly from
env_vars_forward, preserving raw OsString values for expect_os_string; retain
the UTF-8-filtered values map exclusively for expect_all. Add Unix-specific
coverage verifying that a non-UTF-8 NETSUKE_CONFIG path is returned by
Env::os_string rather than treated as unset.

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

ℹ️ 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 src/cli/merge.rs Outdated
Comment on lines +89 to +93
let environment_entries = env
.all()
.into_iter()
.map(|(key, value)| (key.into(), value.into()))
.collect();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve non-Unicode entries in the environment snapshot

On Unix, if the process inherits any non-UTF-8 environment key or value, DefaultEnv::all() enumerates into Unicode Strings and can panic before this conversion runs. Previously vars_os() preserved these entries so EnvironmentLayer::parse_entry could reject Netsuke-prefixed entries with its fixed error and skip unrelated ones; converting the already-Unicode map back into OsString makes that handling unreachable. Keep the production environment behind the sanctioned injected seam while retaining an OsString snapshot.

AGENTS.md reference: AGENTS.md:L300-L306

Useful? React with 👍 / 👎.

Comment thread src/main.rs
Comment on lines 235 to 238
Err(err) => {
let fallback_filter = startup_filter(fallback_mode, parsed_cli.verbose);
set_tracing_filter(fallback_filter);
// Resolution failed before its diagnostics could be emitted. Replay
// only for human output after enabling its filter; JSON remains OFF.
if fallback_filter != LevelFilter::OFF {
drop(cli::resolve_merged_json(parsed_cli, matches));
}
Err(config_err_to_exit(err.as_ref(), fallback_mode))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Replay cached selector traces before returning errors

When discovery or NETSUKE_JSON validation fails with --verbose, the first discovery pass emitted its debug/trace events while the subscriber was still limited to WARN, and this error arm enables TRACE but returns without replaying the cached resolution. Consequently failed runs no longer show which selector won or which environment lookup occurred, even though successful runs replay those events and the users' guide promises that diagnostic context; preserve the resolution on the error path and replay it after changing the filter.

AGENTS.md reference: AGENTS.md:L358-L362

Useful? React with 👍 / 👎.

@lodyai
lodyai Bot marked this pull request as draft August 9, 2026 15:06
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@leynos

leynos commented Aug 9, 2026

Copy link
Copy Markdown
Owner

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

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

tests/logging_stderr/config_tracing.rs

Comment on lines +136 to +139

    ensure!(
        joined.contains("resolved config path") && joined.contains("selector=\"cli_flag\""),
        "verbose stderr should replay the cached selector decision: {joined}"
    );

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: explicit_selection_traces_bounded_fields,invalid_config_traces_without_parser_text

@coderabbitai

coderabbitai Bot commented Aug 9, 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' '--- current revision ---'
git rev-parse --short HEAD

printf '%s\n' '--- module outline ---'
ast-grep outline tests/logging_stderr/config_tracing.rs --items all --view expanded

printf '%s\n' '--- target tests and local helpers ---'
rg -n -A70 -B20 \
  '^\s*(async\s+)?fn\s+(explicit_selection_traces_bounded_fields|invalid_config_traces_without_parser_text)\b|^\s*fn\s+[A-Za-z0-9_]+.*\{' \
  tests/logging_stderr/config_tracing.rs

printf '%s\n' '--- target names across relevant test sources ---'
rg -n -A8 -B8 \
  '\b(explicit_selection_traces_bounded_fields|invalid_config_traces_without_parser_text)\b' \
  tests src

Length of output: 12015


@leynostests/logging_stderr/config_tracing.rs: explicit_selection_traces_bounded_fields, invalid_config_traces_without_parser_text

Suppress this CodeScene diagnostic. I validated the finding against current revision 79f2d41.

Do not merge these tests. explicit_selection_traces_bounded_fields verifies the successful explicit-selection contract. It checks the selector, bounded path fields, visible filename, and raw-path redaction. invalid_config_traces_without_parser_text verifies the malformed-file failure contract. It checks failure_kind=LoadError, cached selector-decision replay, parser-input redaction, and raw-path redaction.

A helper would only hide the short command execution and diagnostic collection sequence. The test setup, configuration content, and assertions must remain separate. Parameterization would combine independent success and failure contracts and reduce failure localization. The shared structure is intentional end-to-end test scaffolding. It is not duplicated production logic.

CodeScene diagnostic suppression text:

These tests intentionally share a small end-to-end setup because they validate separate configuration-tracing contracts. explicit_selection_traces_bounded_fields validates successful explicit configuration selection and bounded path-field output. invalid_config_traces_without_parser_text validates malformed configuration handling, cached selector-decision replay, and redaction of parser input.

Extracting the shared setup would hide test-specific configuration data and diagnostic output while saving only a few lines. Parameterizing the tests would combine independent success and failure behaviour and reduce failure localization. The duplicated structure is limited to readable test scaffolding and does not duplicate production behaviour.

You are interacting with an AI system.

leynos and others added 3 commits August 9, 2026 19:25
Replace the bespoke config-selector environment trait with `mockable::Env`
so discovery and merging use the established injectable seam. Keep automatic
discovery from re-reading `NETSUKE_CONFIG` after that injected lookup, and
adapt deterministic unit, integration, and BDD coverage to `MockEnv`.
Discover file-backed configuration layers once during diagnostic resolution
and pass that result into the full merge. This preserves existing standalone
merge behaviour while removing repeated startup file loading.

Keep verbose selector tracing by replaying the cached decision after the
diagnostic output mode enables the tracing filter. Cover the shared flow with
a mock environment that permits one config-selector lookup only.
Keep non-Unicode configuration selectors and process environment entries in
their raw form so selection remains correct and environment-layer validation
can apply its documented policy without a startup panic.

Retain cached selector diagnostics through failed startup resolution, so
verbose users receive the same bounded context for configuration errors.
@lodyai
lodyai Bot force-pushed the issue-319-cache-config-file-layer-discovery-to-avoid-double-i-o-on-startup branch from 79f2d41 to 7847192 Compare August 9, 2026 17:35

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

Gates Failed
Enforce advisory code health rules (1 file with Code Duplication)

Our agent can fix these. Install it.

Gates Passed
5 Quality Gates Passed

Reason for failure
Enforce advisory code health rules Violations Code Health Impact
config_tracing.rs 1 advisory rule 10.00 → 9.39 Suppress

See analysis details in CodeScene

Active suppressions
1 suppression

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

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No quality gates enabled for this code.

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

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

@leynos
leynos marked this pull request as ready for review August 10, 2026 00:14

@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

@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: 78471925eb

ℹ️ 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 src/cli/mod.rs
Comment on lines +24 to +28
pub use diag::{
resolve_json_and_layers_outcome_with_env, resolve_json_and_layers_with_env,
resolve_merged_json, resolve_merged_json_with_env,
};
pub use discovery::DiscoveredLayers;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep the environment seam documentation in sync

Replacing the exported ConfigEnvProvider/ConfigStdEnvProvider seam with DiscoveredLayers and the new resolve/merge pair leaves docs/developers-guide.md lines 2257–2309 describing removed types and signatures, so its adapter examples no longer compile and the new abstraction's ownership and reuse contract is absent. Preserve compatibility or update the relevant developer/design documentation in this change.

AGENTS.md reference: AGENTS.md:L44-L56

Useful? React with 👍 / 👎.

Comment thread src/cli/discovery.rs
Comment on lines +67 to +68
pub fn replay_config_path_trace(&self) {
trace_config_path_resolution(&self.resolution);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Replay the complete cached discovery trace

When --verbose is supplied, the discovery pre-pass runs while the subscriber is still filtered at WARN, and this method later replays only the selector and environment-lookup events. The using explicit config path, using config discovery, and project-layer debug events emitted during collection therefore remain discarded, even though the users' guide promises that verbose configuration diagnostics identify whether explicit or discovered layers were used; cache and replay those bounded decisions as well.

Useful? React with 👍 / 👎.

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.

Cache config file layer discovery to avoid double I/O on startup

2 participants