diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index 5d80e47..842adc1 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -1,6 +1,6 @@ # Configuration Discovery Review Findings -Status: findings 1–4 complete; finding 5 remains open. +Status: complete. All five findings have been addressed in separate commits. Reviewed commit: `e50fee2`, including the discovery implementation commits after `424c897`. The review compared the implementation with the @@ -185,7 +185,15 @@ code, not a new Windows support promise or native-platform release gate. ## 5. Preserve Drive-Root-Relative Windows Path Meaning -Priority: P3. Status: open. Algorithmic audit: correctness finding. +Priority: P3. Status: complete. Algorithmic audit: correctness finding. + +**Completed:** drive-root-relative Windows paths now use `filepath.Abs` alongside +drive-relative paths, preserving native host interpretation. Windows regression +tests compare candidates and loaded files with explicit `File` for relative, +drive-root-relative (both separators), drive-relative, and absolute paths, through +both `Paths` and `SearchDirectory`. Existing Unix path/short-circuit tests and +`./scripts/check.sh` passed. The Windows engine test binary cross-compiled; no +native Windows execution or new release gate was introduced. **Location:** [discovery.go](../../internal/engine/discovery.go), `absoluteCandidate`, lines 380–391; also used for literal directory searches. @@ -257,8 +265,8 @@ ordinary file acquisition. It did not benchmark or reopen those choices. No unplanned quadratic algorithm or other actionable asymptotic regression was identified. Findings 4 and 5 record the additional ordinary correctness issues found while examining those algorithms; findings 1–3 were carried forward from -the earlier review. Existing configuration-tree algorithms outside the discovery changes -were not re-audited as new feature work. +the earlier review. Existing configuration-tree algorithms outside the discovery +changes were not re-audited as new feature work. ## Review Validation @@ -278,3 +286,13 @@ were not re-audited as new feature work. helper regression overlay described in finding 4. It collected no timing or allocation measurements. Findings 4 and 5 are platform-specific correctness findings, not claims of a native Windows validation run. + +## Completed Remediation Validation + +All five fixes include focused regression coverage and per-issue completion +markers above. The final `./scripts/check.sh` and `go test -race ./...` passed +with Go 1.26.10 on Linux. The Windows engine tests also cross-compiled with +`GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go test -c`, with the binary kept outside +the repository. No native Windows or macOS run was performed or made a gate. +The implementation preserves the public API, candidate ordering, optional +absence, and existing composition/version/ownership contracts. diff --git a/internal/engine/discovery.go b/internal/engine/discovery.go index 80800e4..c6f02bb 100644 --- a/internal/engine/discovery.go +++ b/internal/engine/discovery.go @@ -370,7 +370,9 @@ func (iterator *searchCandidateIterator) absoluteCandidate(path string) (string, if filepath.IsAbs(path) { return filepath.Clean(path), nil } - if filepath.VolumeName(path) != "" { + // Drive-relative and drive-root-relative Windows paths need native + // resolution; joining a rooted path to the whole working directory changes it. + if filepath.VolumeName(path) != "" || runtime.GOOS == "windows" && len(path) > 0 && os.IsPathSeparator(path[0]) { return filepath.Abs(path) } working, err := iterator.helpers.workingDir() diff --git a/internal/engine/discovery_windows_test.go b/internal/engine/discovery_windows_test.go index c1addaf..00d06f4 100644 --- a/internal/engine/discovery_windows_test.go +++ b/internal/engine/discovery_windows_test.go @@ -4,6 +4,8 @@ package engine import ( "errors" + "path/filepath" + "strings" "testing" ) @@ -30,3 +32,60 @@ func TestWindowsSearchRejectsReservedComponentsBeforeAcquisition(t *testing.T) { } } } + +func TestWindowsSearchPathsMatchHostAbsoluteResolution(t *testing.T) { + root := t.TempDir() + file := writeImportFile(t, root, "settings/config.yml", "value: selected\n") + working := t.TempDir() + t.Chdir(working) + relative, err := filepath.Rel(working, file) + if err != nil { + t.Fatal(err) + } + volume := filepath.VolumeName(file) + rooted := strings.TrimPrefix(file, volume) + for _, entry := range []struct { + name string + path string + }{ + {"relative", relative}, + {"drive root relative", rooted}, + {"slash root relative", filepath.ToSlash(rooted)}, + {"drive relative", volume + relative}, + {"absolute", file}, + } { + t.Run(entry.name, func(t *testing.T) { + want, err := filepath.Abs(entry.path) + if err != nil { + t.Fatal(err) + } + explicit, err := testLoad[discoveryLoadConfig](File(entry.path)) + if err != nil { + t.Fatal(err) + } + for _, search := range []Search{ + Paths(entry.path), + SearchDirectory(filepath.Dir(entry.path), DirectorySearchOptions{ + Filenames: []FilenameGroup{CustomFilenames(filepath.Base(entry.path))}, + }), + } { + iterator, diagnostics := newSearchCandidateIterator(search, "", discoveryDirectoryHelpers{}) + if len(diagnostics) != 0 { + t.Fatal(diagnostics) + } + candidate, ok, err := iterator.next() + if err != nil || !ok || candidate != want { + t.Fatalf("candidate = %q, %v, %v; want %q", candidate, ok, err, want) + } + result, err := testLoad[discoveryLoadConfig](SearchConfig("", search)) + if err != nil || result.Config.Value != explicit.Config.Value { + t.Fatalf("searched path = %#v, %v; explicit value %q", result, err, explicit.Config.Value) + } + sources := result.Sources() + if len(sources) != 1 || sources[0].Path != explicit.Sources()[0].Path { + t.Fatalf("searched source = %#v; explicit source %#v", sources, explicit.Sources()) + } + } + }) + } +}