Protect the main version inside item scopes
This commit is contained in:
@@ -158,7 +158,12 @@ is an unintended restriction beyond the requested feature.
|
||||
|
||||
## 5. Keep default-mode main version ownership inside item scopes — P2
|
||||
|
||||
- [ ] Addressed and regression-tested.
|
||||
- [x] Addressed and regression-tested.
|
||||
|
||||
Completion: default-mode effective protection is translated into the main item's
|
||||
local scope, preserving segment intent and effective diagnostic paths. Both
|
||||
public entry points reject helper-supplied main declarations and permit unrelated
|
||||
version fields. Focused source-version tests and the everyday check passed.
|
||||
|
||||
**Owner:** the `mainIsItem` branch of
|
||||
[imports.go](../../internal/engine/imports.go), lines 228–235.
|
||||
|
||||
@@ -101,6 +101,47 @@ func TestFacadeSourceVersionOwnershipAcceptsContextualIndexes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFacadeItemMainKeepsDefaultVersionOwnership(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, layered := range []bool{false, true} {
|
||||
for _, protected := range []bool{true, false} {
|
||||
body, helper := "id: main\n", "version: imported-version\n"
|
||||
if !protected {
|
||||
body += "version: main-version\n"
|
||||
helper = "other: {version: permitted}\n"
|
||||
}
|
||||
filesystem := configkit.NewFilesystem("item-main", fstest.MapFS{
|
||||
"main.yml": {Data: []byte(body + "configkit: {imports: [{path: helper.yml}]}\n")}, "helper.yml": {Data: []byte(helper)},
|
||||
})
|
||||
checks := 0
|
||||
definition := configkit.Definition[map[string]any]{Version: &configkit.VersionRule{
|
||||
Path: configkit.Keys("records").Index(0).Key("version"),
|
||||
Check: func(version configkit.Version, _ *configkit.Validation) error {
|
||||
checks++
|
||||
if version.Text != "main-version" {
|
||||
t.Error("helper supplied main version")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}}
|
||||
var err error
|
||||
if layered {
|
||||
_, err = configkit.LoadLayered[map[string]any](configkit.Input{Source: filesystem.File("main.yml"), Imports: true, Destination: configkit.Keys("records"), PlacementMode: configkit.PlacementModeSequenceItem}, configkit.LayeredOptions[map[string]any]{Definition: definition})
|
||||
} else {
|
||||
_, err = configkit.Load(filesystem.File("main.yml"), configkit.Options[map[string]any]{Definition: definition, Imports: true, Destination: configkit.Keys("records"), PlacementMode: configkit.PlacementModeSequenceItem})
|
||||
}
|
||||
if protected {
|
||||
loadError, ok := err.(*configkit.LoadError)
|
||||
if !ok || len(loadError.Diagnostics()) != 1 || loadError.Diagnostics()[0].Code != configkit.CodeImportVersion || loadError.Diagnostics()[0].Path.String() != "/records/0/version" || checks != 0 {
|
||||
t.Fatalf("layered=%t ownership error = %v, checks=%d", layered, err, checks)
|
||||
}
|
||||
} else if err != nil || checks != 1 {
|
||||
t.Fatalf("unrelated helper version rejected: %v, checks=%d", err, checks)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type sourceVersionModeReader struct {
|
||||
*strings.Reader
|
||||
reads int
|
||||
|
||||
@@ -230,7 +230,22 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
return nil, cloneSourceInfos(state.sources), prependDiagnosticPaths(failures, destination.Index(0))
|
||||
}
|
||||
if versionState != nil {
|
||||
if diagnostic := importedVersionClaim(documents, completed, versionState.localRule, 0); diagnostic != nil {
|
||||
protectionRule := versionState.localRule
|
||||
if versionState.mode == sourceVersionModeDefault && protectionRule != nil {
|
||||
prefix := destination.Index(0)
|
||||
relative, inside := relativeHistoryPath(prefix, protectionRule.Path)
|
||||
if !inside || !matchesPathShape(Path{segments: protectionRule.Path.segments[:len(prefix.segments)]}, prefix) {
|
||||
protectionRule = nil
|
||||
} else {
|
||||
scoped := *protectionRule
|
||||
scoped.Path = relative
|
||||
protectionRule = &scoped
|
||||
}
|
||||
}
|
||||
if diagnostic := importedVersionClaim(documents, completed, protectionRule, 0); diagnostic != nil {
|
||||
if rule != nil {
|
||||
diagnostic.Path = rule.Path.clone()
|
||||
}
|
||||
return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user