Check item version ownership at effective paths

This commit is contained in:
2026-10-20 00:22:43 +00:00
parent 1c79ec3aa1
commit 38fb315415
3 changed files with 75 additions and 5 deletions

View File

@@ -1,6 +1,6 @@
# Import Discovery and Scheduling Review Findings
Status: in progress; issue 1 complete, five findings remain.
Status: in progress; issues 1–2 complete, four findings remain.
Reviewed implementation: `aa93aed..15943e2`, ending at
`15943e233ca3cd1286c03c0e7085871db344cd4b`, against the accepted
@@ -57,7 +57,7 @@ source contexts, repeated paths, and distinct alias admission. The regression
failed before the fix. Focused discovery/resource tests and `./scripts/check.sh`
passed with Go 1.26.9 and offline dependency selection.
## 2. [ ] P1 — Scope main-version protection to the actual effective path
## 2. [x] P1 — Scope main-version protection to the actual effective path
Owner: [composePendingItems](../../internal/engine/imports.go), around lines
694–695, and its call to `importedVersionClaim`.
@@ -85,6 +85,13 @@ the effective paths really overlap. Cover imported/nested items, item helpers,
main-item loading, underlays, and enabled source-version modes without adding
blanket exemptions or banning ordinary domain version fields.
Completed: removed the global-rule check against item-local field names; enclosing
composition checks retain effective-path protection, including the existing
main-item scoped check. New public regressions cover item bodies, helpers, nested
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
Owner: [resolveNativeImportPath](../../internal/engine/filesystem_links.go),

View File

@@ -0,0 +1,64 @@
package configkit_test
import (
"testing"
"testing/fstest"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
)
func TestFacadeItemDomainVersionsKeepEffectiveScope(t *testing.T) {
for _, test := range []struct {
name, item, child, helper string
underlay, mainItem bool
}{
{name: "item", item: "id: item\nversion: domain\n"},
{name: "helper", item: "id: item\nconfigkit: {imports: [{path: helper.yml}]}\n", helper: "version: domain\n"},
{name: "nested", item: "id: parent\nconfigkit: {imports: [{path: child.yml, destination: [children], placement: sequence-item}]}\n", child: "id: child\nversion: domain\n"},
{name: "underlay", item: "id: item\nversion: domain\n", underlay: true},
{name: "main-item", item: "id: parent\nconfigkit: {imports: [{path: child.yml, destination: [children], placement: sequence-item}]}\n", child: "id: child\nversion: domain\n", mainItem: true},
} {
t.Run(test.name, func(t *testing.T) {
files := fstest.MapFS{
"main.yml": {Data: []byte("version: 1\nconfigkit: {imports: [{path: item.yml, destination: [items], placement: sequence-item}]}\n")},
"item.yml": {Data: []byte(test.item)},
"child.yml": {Data: []byte(test.child)},
"helper.yml": {Data: []byte(test.helper)},
"underlay.yml": {Data: []byte("configkit: {imports: [{path: item.yml, destination: [items], placement: sequence-item}]}\n")},
}
filesystem := configkit.NewFilesystem("items", files)
definition := configkit.Definition[map[string]any]{Version: &configkit.VersionRule{
Path: configkit.Keys("version"), Check: func(configkit.Version, *configkit.Validation) error { return nil },
}}
var result *configkit.Result[map[string]any]
var err error
switch {
case test.underlay:
result, err = configkit.LoadLayered(configkit.Input{Source: configkit.Mapping("main", map[string]any{"version": "1"})}, configkit.LayeredOptions[map[string]any]{
Definition: definition, Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: filesystem.File("underlay.yml"), Imports: true}}}},
})
case test.mainItem:
result, err = configkit.Load(filesystem.File("item.yml"), configkit.Options[map[string]any]{
Imports: true, Destination: configkit.Keys("items"), PlacementMode: configkit.PlacementModeSequenceItem, Definition: definition,
})
default:
result, err = configkit.Load(filesystem.File("main.yml"), configkit.Options[map[string]any]{Imports: true, Definition: definition})
}
if err != nil {
t.Fatal(err)
}
item := result.Config["items"].([]any)[0].(map[string]any)
fieldPath := configkit.Keys("items").Index(0).Key("version")
if test.child != "" {
item = item["children"].([]any)[0].(map[string]any)
fieldPath = configkit.Keys("items").Index(0).Key("children").Index(0).Key("version")
}
if item["version"] != "domain" {
t.Fatalf("domain version = %#v", item)
}
if info, ok := result.Metadata().Lookup(fieldPath); !ok || !info.Explicit {
t.Fatalf("version metadata = %#v, %v", info, ok)
}
})
}
}

View File

@@ -691,9 +691,8 @@ func (s *importTraversal) composePendingItems(scope *acquisitionScope) []Diagnos
if len(diagnostics) != 0 {
return projectItemDiagnosticPaths(diagnostics, *scope, contribution.destination, index)
}
if diagnostic := importedVersionClaim(documents, *itemScope, completed, s.rule); diagnostic != nil {
return []Diagnostic{*diagnostic}
}
// Version ownership is checked by the enclosing composition once this
// item has its effective coordinates, not against its local field names.
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(*itemScope)}
projectNestedEvidence(completed, *itemScope)
_, placed, err := placeDocumentWithMode(completed.root, contribution.destination, placementModeSequenceItem, contribution.payload.Location, maxDocumentDepth, maxDocumentNodes)