Resolve native imports beneath nested roots

This commit is contained in:
2026-10-20 00:23:55 +00:00
parent 38fb315415
commit 75da54150f
3 changed files with 63 additions and 3 deletions

View File

@@ -1,6 +1,6 @@
# Import Discovery and Scheduling Review Findings
Status: in progress; issues 1–2 complete, four findings remain.
Status: in progress; issues 1–3 complete, three findings remain.
Reviewed implementation: `aa93aed..15943e2`, ending at
`15943e233ca3cd1286c03c0e7085871db344cd4b`, against the accepted
@@ -92,7 +92,7 @@ items, underlays, and selected main items, and failed before the change. The
full everyday check passed, including existing default/enabled source-version
and assigned-index ownership regressions.
## 3. [ ] P2 — Accept valid multi-component native filesystem roots
## 3. [x] P2 — Accept valid multi-component native filesystem roots
Owner: [resolveNativeImportPath](../../internal/engine/filesystem_links.go),
especially lines 170–181.
@@ -114,6 +114,12 @@ resolved targets. Test directory and glob discovery under multi-component roots,
with ordinary entries and supported symlinks. Do not require consumers to flatten
their roots or remove optional link-inspection capability.
Completed: native link resolution allows ancestor prefixes while resolving a
contained multi-component path; selected paths and resolved link targets still
undergo their existing containment checks. Public directory/glob regressions
cover ordinary and aliased roots plus escaping links. All four valid cases
failed before the fix. The full everyday check passed.
## 4. [ ] P2 — Distinguish finite symlink reuse from actual cycles
Owners: [resolveHostImportPath and resolveNativeImportPath](../../internal/engine/filesystem_links.go),

View File

@@ -0,0 +1,52 @@
package configkit_test
import (
"errors"
"os"
"path/filepath"
"testing"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
)
func TestFacadeNativeDiscoveryWithNestedRoot(t *testing.T) {
root := t.TempDir()
parts := filepath.Join(root, "nested/root/parts")
if err := os.MkdirAll(parts, 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(parts, "value.yml"), []byte("value: 1\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.Symlink("parts", filepath.Join(root, "nested/root/linked")); err != nil {
t.Skipf("symlinks unavailable: %v", err)
}
filesystem := configkit.NewFilesystem("nested", os.DirFS(root))
context := &configkit.FilesystemContext{FS: &filesystem, Root: "nested/root", BaseDir: "nested/root"}
for _, selector := range []string{
"dir: parts", "glob: 'parts/*.yml'", "dir: linked", "glob: 'lin*/*.yml'",
} {
t.Run(selector, func(t *testing.T) {
result, err := configkit.Load(configkit.Bytes("main", []byte("configkit:\n imports:\n - "+selector+"\n")), configkit.Options[map[string]any]{Imports: true, Filesystem: context})
if err != nil {
t.Fatal(err)
}
if result.Config["value"] != int64(1) {
t.Fatalf("configuration = %#v", result.Config)
}
})
}
if err := os.WriteFile(filepath.Join(root, "nested/outside.yml"), []byte("value: outside\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.Symlink("../../outside.yml", filepath.Join(parts, "escape.yml")); err != nil {
t.Fatal(err)
}
for _, selector := range []string{"dir: parts", "glob: 'parts/*.yml'"} {
_, err := configkit.Load(configkit.Bytes("main", []byte("configkit:\n imports:\n - "+selector+"\n")), configkit.Options[map[string]any]{Imports: true, Filesystem: context})
var loadError *configkit.LoadError
if !errors.As(err, &loadError) || len(loadError.Diagnostics()) != 1 || loadError.Diagnostics()[0].Code != configkit.CodeImportOutsideRoot {
t.Fatalf("escaping %s: %v", selector, err)
}
}
}

View File

@@ -177,7 +177,9 @@ func resolveNativeImportPath(access *filesystemAccess, filesystem fs.ReadLinkFS,
if candidate == "" {
candidate = "."
}
if !access.contains(candidate) {
// Ancestors of a multi-component root are resolution prefixes, not
// selected targets. Complete paths and link targets remain contained.
if !access.contains(candidate) && !strings.HasPrefix(access.rootPath, candidate+"/") {
return "", errImportPathOutsideRoot
}
info, err := filesystem.Lstat(candidate)