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
|
## 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
|
**Owner:** the `mainIsItem` branch of
|
||||||
[imports.go](../../internal/engine/imports.go), lines 228–235.
|
[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 {
|
type sourceVersionModeReader struct {
|
||||||
*strings.Reader
|
*strings.Reader
|
||||||
reads int
|
reads int
|
||||||
|
|||||||
@@ -230,7 +230,22 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
|||||||
return nil, cloneSourceInfos(state.sources), prependDiagnosticPaths(failures, destination.Index(0))
|
return nil, cloneSourceInfos(state.sources), prependDiagnosticPaths(failures, destination.Index(0))
|
||||||
}
|
}
|
||||||
if versionState != nil {
|
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}
|
return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user