Construct placement directly across the facade

This commit is contained in:
2026-10-26 00:18:12 +00:00
parent 643f43ce6a
commit d66338a1c5
26 changed files with 105 additions and 113 deletions

View File

@@ -204,7 +204,8 @@ Focused checks:
Finding: **F07**, placement handoff. Owners: engine `destination.go`, `load.go`, Finding: **F07**, placement handoff. Owners: engine `destination.go`, `load.go`,
`layered.go`, normalized input and placement consumers; root `lifecycle.go`, `layered.go`, normalized input and placement consumers; root `lifecycle.go`,
`layered.go`, and placement fixtures. Status: **not started**. `layered.go`, and placement fixtures. Status: **complete**. Validation passed:
`env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./internal/engine -run 'Destination|Placement|Item|SourceVersion'`, `env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test . -run 'Placement|Destination|SourceInfo|SourceVersion|PublicFacadeBoundary|DependencyImportBoundary'`, `env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./...`, and `git diff --check`.
1. Rename the existing engine enum and its constants to `PlacementMode`, 1. Rename the existing engine enum and its constants to `PlacementMode`,
`PlacementModeMapping`, and `PlacementModeSequenceItem`, preserving underlying `PlacementModeMapping`, and `PlacementModeSequenceItem`, preserving underlying

View File

@@ -11,26 +11,13 @@ var (
errDestinationLimit = errors.New("configkit: destination exceeds document limits") errDestinationLimit = errors.New("configkit: destination exceeds document limits")
) )
type placementMode uint8 type PlacementMode uint8
const ( const (
placementModeMapping placementMode = iota PlacementModeMapping PlacementMode = iota
placementModeSequenceItem PlacementModeSequenceItem
) )
// InputWithPlacement copies the root-owned mode into a private engine input.
func InputWithPlacement(input Input, value uint8) Input {
input.placementMode = placementMode(value)
return input
}
// OptionsWithPlacement copies root-owned controls into private engine options.
func OptionsWithPlacement[T any](options Options[T], destination Path, value uint8) Options[T] {
options.destination = destination.clone()
options.placementMode = placementMode(value)
return options
}
func normalizeDestination(path Path) (Path, error) { func normalizeDestination(path Path) (Path, error) {
if !path.valid() { if !path.valid() {
return Path{}, errInvalidDestination return Path{}, errInvalidDestination
@@ -67,26 +54,26 @@ func invalidDestinationDiagnostic(destination Path) Diagnostic {
// placeDocument checks all per-document bounds before allocating destination // placeDocument checks all per-document bounds before allocating destination
// wrappers. Aggregate accounting remains the caller's responsibility. // wrappers. Aggregate accounting remains the caller's responsibility.
func placeDocument(body *document.Node, destination Path, sourceRoot document.Location, maxDepth, maxNodes int) (Path, *document.Node, error) { func placeDocument(body *document.Node, destination Path, sourceRoot document.Location, maxDepth, maxNodes int) (Path, *document.Node, error) {
return placeDocumentWithMode(body, destination, placementModeMapping, sourceRoot, maxDepth, maxNodes) return placeDocumentWithMode(body, destination, PlacementModeMapping, sourceRoot, maxDepth, maxNodes)
} }
// placeDocumentWithMode places a source mapping beneath its literal destination. // placeDocumentWithMode places a source mapping beneath its literal destination.
// Sequence-item placement adds one sequence containing the complete mapping. // Sequence-item placement adds one sequence containing the complete mapping.
func placeDocumentWithMode(body *document.Node, destination Path, mode placementMode, sourceRoot document.Location, maxDepth, maxNodes int) (Path, *document.Node, error) { func placeDocumentWithMode(body *document.Node, destination Path, mode PlacementMode, sourceRoot document.Location, maxDepth, maxNodes int) (Path, *document.Node, error) {
normalized, err := normalizeDestination(destination) normalized, err := normalizeDestination(destination)
if err != nil { if err != nil {
return Path{}, nil, err return Path{}, nil, err
} }
if mode != placementModeMapping && mode != placementModeSequenceItem { if mode != PlacementModeMapping && mode != PlacementModeSequenceItem {
return Path{}, nil, errInvalidDestination return Path{}, nil, errInvalidDestination
} }
if mode == placementModeSequenceItem && len(normalized.segments) == 0 { if mode == PlacementModeSequenceItem && len(normalized.segments) == 0 {
return Path{}, nil, errInvalidDestination return Path{}, nil, errInvalidDestination
} }
if body == nil { if body == nil {
return normalized, nil, nil return normalized, nil, nil
} }
if mode == placementModeSequenceItem && body.Kind != document.Mapping { if mode == PlacementModeSequenceItem && body.Kind != document.Mapping {
return normalized, nil, errInvalidDestination return normalized, nil, errInvalidDestination
} }
if !destinationWithinLimitsWithMode(body, normalized, mode, maxDepth, maxNodes) { if !destinationWithinLimitsWithMode(body, normalized, mode, maxDepth, maxNodes) {
@@ -94,7 +81,7 @@ func placeDocumentWithMode(body *document.Node, destination Path, mode placement
} }
placed := body placed := body
if mode == placementModeSequenceItem { if mode == PlacementModeSequenceItem {
placed = &document.Node{ placed = &document.Node{
Kind: document.Sequence, Location: sourceRoot, Kind: document.Sequence, Location: sourceRoot,
Sequence: []*document.Node{placed}, Sequence: []*document.Node{placed},
@@ -118,20 +105,20 @@ func placeDocumentWithMode(body *document.Node, destination Path, mode placement
// destinationWithinLimits expects a normalized mapping-key destination. // destinationWithinLimits expects a normalized mapping-key destination.
func destinationWithinLimits(body *document.Node, destination Path, maxDepth, maxNodes int) bool { func destinationWithinLimits(body *document.Node, destination Path, maxDepth, maxNodes int) bool {
return destinationWithinLimitsWithMode(body, destination, placementModeMapping, maxDepth, maxNodes) return destinationWithinLimitsWithMode(body, destination, PlacementModeMapping, maxDepth, maxNodes)
} }
func destinationWithinLimitsWithMode(body *document.Node, destination Path, mode placementMode, maxDepth, maxNodes int) bool { func destinationWithinLimitsWithMode(body *document.Node, destination Path, mode PlacementMode, maxDepth, maxNodes int) bool {
if body == nil { if body == nil {
return true return true
} }
if maxDepth < 0 || maxNodes < 0 || !destination.valid() || (mode != placementModeMapping && mode != placementModeSequenceItem) { if maxDepth < 0 || maxNodes < 0 || !destination.valid() || (mode != PlacementModeMapping && mode != PlacementModeSequenceItem) {
return false return false
} }
segments := len(destination.segments) segments := len(destination.segments)
sequenceNodes := 0 sequenceNodes := 0
sequenceDepth := 0 sequenceDepth := 0
if mode == placementModeSequenceItem { if mode == PlacementModeSequenceItem {
if segments == 0 { if segments == 0 {
return false return false
} }
@@ -153,9 +140,9 @@ func destinationWithinLimitsWithMode(body *document.Node, destination Path, mode
return countInputNodes(body) <= maxNodes-wrapperNodes return countInputNodes(body) <= maxNodes-wrapperNodes
} }
func destinationWrapperNodeCountForMode(destination Path, mode placementMode) int { func destinationWrapperNodeCountForMode(destination Path, mode PlacementMode) int {
count := destinationWrapperNodeCount(destination) count := destinationWrapperNodeCount(destination)
if mode == placementModeSequenceItem { if mode == PlacementModeSequenceItem {
count++ count++
} }
return count return count

View File

@@ -92,9 +92,9 @@ id: parent
childDestination := appendDestination(destination, Keys("children")) childDestination := appendDestination(destination, Keys("children"))
otherDestination := Keys("other") otherDestination := Keys("other")
wantNodes := parseNodes(mainPath) + parseNodes(fragmentPath) + countInputNodes(&document.Node{Kind: document.Mapping}) + wantNodes := parseNodes(mainPath) + parseNodes(fragmentPath) + countInputNodes(&document.Node{Kind: document.Mapping}) +
destinationWrapperNodeCountForMode(destination, placementModeSequenceItem) + destinationWrapperNodeCountForMode(destination, PlacementModeSequenceItem) +
destinationWrapperNodeCountForMode(childDestination, placementModeSequenceItem) + destinationWrapperNodeCountForMode(childDestination, PlacementModeSequenceItem) +
destinationWrapperNodeCountForMode(otherDestination, placementModeSequenceItem) destinationWrapperNodeCountForMode(otherDestination, PlacementModeSequenceItem)
limits := defaultImportLimits() limits := defaultImportLimits()
for _, test := range []struct { for _, test := range []struct {
@@ -108,9 +108,9 @@ id: parent
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
accounting := &importAccounting{nodes: limits.nodes - wantNodes + test.delta} accounting := &importAccounting{nodes: limits.nodes - wantNodes + test.delta}
acquired, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{ acquired, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{
source: File(mainPath), imports: true, destination: destination, placementMode: placementModeSequenceItem, source: File(mainPath), imports: true, destination: destination, placementMode: PlacementModeSequenceItem,
}, { }, {
source: Mapping("other", map[string]any{"id": "other"}), destination: otherDestination, placementMode: placementModeSequenceItem, source: Mapping("other", map[string]any{"id": "other"}), destination: otherDestination, placementMode: PlacementModeSequenceItem,
}}}, 1, nil, accounting) }}}, 1, nil, accounting)
if test.wantError { if test.wantError {
if len(diagnostics) == 0 || diagnostics[0].Code != CodeSourceLimitExceeded { if len(diagnostics) == 0 || diagnostics[0].Code != CodeSourceLimitExceeded {

View File

@@ -201,7 +201,7 @@ func TestLayeredItemVersionProtectionTracksAssignedPeerIndexes(t *testing.T) {
sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("catalog"), literalSequenceSelector("entries")}, identityFields: []string{"id"}}}, sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("catalog"), literalSequenceSelector("entries")}, identityFields: []string{"id"}}},
} }
item := func(label string, value map[string]any) Input { item := func(label string, value map[string]any) Input {
return Input{Source: Mapping(label, value), Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem} return Input{Source: Mapping(label, value), Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem}
} }
options := func(inputs ...Input) LayeredOptions[map[string]any] { options := func(inputs ...Input) LayeredOptions[map[string]any] {
return LayeredOptions[map[string]any]{Definition: definition, SourceVersionMode: sourceVersionModeIfPresent, Underlays: []Underlay{{Inputs: inputs}}} return LayeredOptions[map[string]any]{Definition: definition, SourceVersionMode: sourceVersionModeIfPresent, Underlays: []Underlay{{Inputs: inputs}}}

View File

@@ -22,11 +22,11 @@ func TestLayeredLoadPlacesCompletedMappingAsOneItem(t *testing.T) {
t.Parallel() t.Parallel()
loaded, err := LoadLayered[destinationItemConfig]( loaded, err := LoadLayered[destinationItemConfig](
Input{Source: NoSource(), Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem}, Input{Source: NoSource(), Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem},
testSecretBinding(), testSecretBinding(),
LayeredOptions[destinationItemConfig]{Underlays: []Underlay{{Inputs: []Input{{ LayeredOptions[destinationItemConfig]{Underlays: []Underlay{{Inputs: []Input{{
Source: Mapping("empty-item", map[string]any{}), Destination: Keys("catalog", "entries"), Source: Mapping("empty-item", map[string]any{}), Destination: Keys("catalog", "entries"),
placementMode: placementModeSequenceItem, PlacementMode: PlacementModeSequenceItem,
}}}}}, }}}}},
) )
if err != nil { if err != nil {
@@ -40,7 +40,7 @@ func TestLayeredLoadPlacesCompletedMappingAsOneItem(t *testing.T) {
t.Fatal("sequence index was treated as a literal mapping key during metadata lookup") t.Fatal("sequence index was treated as a literal mapping key during metadata lookup")
} }
sources := result.Sources() sources := result.Sources()
if len(sources) != 1 || sources[0].placementMode != placementModeSequenceItem || sources[0].destinationUnresolved { if len(sources) != 1 || sources[0].placementMode != PlacementModeSequenceItem || sources[0].destinationUnresolved {
t.Fatalf("source placement evidence = %#v", sources) t.Fatalf("source placement evidence = %#v", sources)
} }
if got := sources[0].Destination.String(); got != "/catalog/entries" { if got := sources[0].Destination.String(); got != "/catalog/entries" {
@@ -48,7 +48,7 @@ func TestLayeredLoadPlacesCompletedMappingAsOneItem(t *testing.T) {
} }
absent, err := LoadLayered[destinationItemConfig]( absent, err := LoadLayered[destinationItemConfig](
Input{Source: NoSource(), Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem}, Input{Source: NoSource(), Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem},
testSecretBinding(), testSecretBinding(),
) )
if err != nil { if err != nil {
@@ -64,17 +64,17 @@ func TestLayeredItemPlacementPreflightsModeAndDestination(t *testing.T) {
for _, test := range []struct { for _, test := range []struct {
name string name string
mode placementMode mode PlacementMode
path Path path Path
}{ }{
{name: "root item", mode: placementModeSequenceItem}, {name: "root item", mode: PlacementModeSequenceItem},
{name: "unknown mode", mode: placementMode(255), path: Keys("catalog")}, {name: "unknown mode", mode: PlacementMode(255), path: Keys("catalog")},
} { } {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
t.Parallel() t.Parallel()
reader := bytes.NewBufferString("value: unused\n") reader := bytes.NewBufferString("value: unused\n")
_, err := LoadLayered[destinationItemConfig]( _, err := LoadLayered[destinationItemConfig](
Input{Source: Reader("reader", reader), Destination: test.path, placementMode: test.mode}, Input{Source: Reader("reader", reader), Destination: test.path, PlacementMode: test.mode},
testSecretBinding(), testSecretBinding(),
) )
if err == nil || reader.Len() == 0 { if err == nil || reader.Len() == 0 {
@@ -93,7 +93,7 @@ func TestLoadOptionsPlaceOneMappingItem(t *testing.T) {
loaded, err := Load[destinationItemConfig]( loaded, err := Load[destinationItemConfig](
Mapping("ordinary", map[string]any{}), testSecretBinding(), Mapping("ordinary", map[string]any{}), testSecretBinding(),
Options[destinationItemConfig]{destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem}, Options[destinationItemConfig]{Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem},
) )
if err != nil { if err != nil {
t.Fatalf("Load: %v", err) t.Fatalf("Load: %v", err)

View File

@@ -54,7 +54,7 @@ func TestMappingPlacementModeMatchesZeroValue(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
explicitPath, explicit, err := placeDocumentWithMode(body, destination, placementModeMapping, body.Location, 5, 32) explicitPath, explicit, err := placeDocumentWithMode(body, destination, PlacementModeMapping, body.Location, 5, 32)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -235,7 +235,7 @@ func TestPlaceDocumentSequenceItemAndMountedBounds(t *testing.T) {
root := document.Location{SourceID: "item", Line: 1, Column: 1} root := document.Location{SourceID: "item", Line: 1, Column: 1}
body := &document.Node{Kind: document.Mapping, Location: root} body := &document.Node{Kind: document.Mapping, Location: root}
destination := Keys("catalog", "entries") destination := Keys("catalog", "entries")
placedPath, placed, err := placeDocumentWithMode(body, destination, placementModeSequenceItem, root, 3, 6) placedPath, placed, err := placeDocumentWithMode(body, destination, PlacementModeSequenceItem, root, 3, 6)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -253,13 +253,13 @@ func TestPlaceDocumentSequenceItemAndMountedBounds(t *testing.T) {
t.Fatal("synthetic sequence or authored mapping lost source coordinates") t.Fatal("synthetic sequence or authored mapping lost source coordinates")
} }
if _, _, err := placeDocumentWithMode(body, Path{}, placementModeSequenceItem, root, 3, 6); !errors.Is(err, errInvalidDestination) { if _, _, err := placeDocumentWithMode(body, Path{}, PlacementModeSequenceItem, root, 3, 6); !errors.Is(err, errInvalidDestination) {
t.Fatalf("root item placement error = %v, want invalid destination", err) t.Fatalf("root item placement error = %v, want invalid destination", err)
} }
if _, _, err := placeDocumentWithMode(&document.Node{Kind: document.String}, destination, placementModeSequenceItem, root, 3, 6); !errors.Is(err, errInvalidDestination) { if _, _, err := placeDocumentWithMode(&document.Node{Kind: document.String}, destination, PlacementModeSequenceItem, root, 3, 6); !errors.Is(err, errInvalidDestination) {
t.Fatalf("non-mapping item placement error = %v, want invalid destination", err) t.Fatalf("non-mapping item placement error = %v, want invalid destination", err)
} }
if _, _, err := placeDocumentWithMode(body, destination, placementMode(255), root, 3, 6); !errors.Is(err, errInvalidDestination) { if _, _, err := placeDocumentWithMode(body, destination, PlacementMode(255), root, 3, 6); !errors.Is(err, errInvalidDestination) {
t.Fatalf("unknown placement mode error = %v, want invalid destination", err) t.Fatalf("unknown placement mode error = %v, want invalid destination", err)
} }
@@ -275,7 +275,7 @@ func TestPlaceDocumentSequenceItemAndMountedBounds(t *testing.T) {
} { } {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
t.Parallel() t.Parallel()
_, _, err := placeDocumentWithMode(body, destination, placementModeSequenceItem, root, test.depth, test.nodes) _, _, err := placeDocumentWithMode(body, destination, PlacementModeSequenceItem, root, test.depth, test.nodes)
if test.wantAccept && err != nil { if test.wantAccept && err != nil {
t.Fatalf("exact bounds rejected: %v", err) t.Fatalf("exact bounds rejected: %v", err)
} }

View File

@@ -28,7 +28,7 @@ settings: {main: selected}
importPath := writeImportFile(t, root, "fragments/extra.yml", "settings: {imported: value}\n") importPath := writeImportFile(t, root, "fragments/extra.yml", "settings: {imported: value}\n")
search := SearchConfig("app", SearchDirectory(root)) search := SearchConfig("app", SearchDirectory(root))
options := Options[discoveryLoadConfig]{ options := Options[discoveryLoadConfig]{
Imports: true, Filesystem: &FilesystemContext{Root: root}, destination: Keys("service"), Imports: true, Filesystem: &FilesystemContext{Root: root}, Destination: Keys("service"),
} }
searched, err := testLoad(search, options) searched, err := testLoad(search, options)
if err != nil { if err != nil {
@@ -172,7 +172,7 @@ func TestSearchProbeFailureRetainsFileEvidence(t *testing.T) {
load func(Source) (*Loaded[discoveryLoadConfig], error) load func(Source) (*Loaded[discoveryLoadConfig], error)
}{ }{
{"ordinary", "source-1", func(source Source) (*Loaded[discoveryLoadConfig], error) { {"ordinary", "source-1", func(source Source) (*Loaded[discoveryLoadConfig], error) {
return Load(source, testSecretBinding(), Options[discoveryLoadConfig]{destination: Keys("settings")}) return Load(source, testSecretBinding(), Options[discoveryLoadConfig]{Destination: Keys("settings")})
}}, }},
{"underlay after acquired sources", "source-3", func(source Source) (*Loaded[discoveryLoadConfig], error) { {"underlay after acquired sources", "source-3", func(source Source) (*Loaded[discoveryLoadConfig], error) {
return LoadLayered(Input{Source: File(main)}, testSecretBinding(), LayeredOptions[discoveryLoadConfig]{ return LoadLayered(Input{Source: File(main)}, testSecretBinding(), LayeredOptions[discoveryLoadConfig]{

View File

@@ -94,7 +94,7 @@ func acquireImportedWithAccess(request importAcquisitionRequest, policy *acquisi
} }
mainInfo := cloneSourceInfo(*main.source) mainInfo := cloneSourceInfo(*main.source)
mainInfo.Destination = destination.clone() mainInfo.Destination = destination.clone()
mainIsItem := main.source.placementMode == placementModeSequenceItem mainIsItem := main.source.placementMode == PlacementModeSequenceItem
if mainPath != "" { if mainPath != "" {
mainInfo.Path = mainPath mainInfo.Path = mainPath
} }
@@ -244,7 +244,7 @@ func (s *importTraversal) walkDeclarations(basePath string, declarations []impor
if len(diagnostics) != 0 { if len(diagnostics) != 0 {
return diagnostics return diagnostics
} }
if policy.placement == placementModeSequenceItem && len(appendDestination(parentDestination, policy.destination).segments) == 0 { if policy.placement == PlacementModeSequenceItem && len(appendDestination(parentDestination, policy.destination).segments) == 0 {
site := spec.fieldSites["placement"] site := spec.fieldSites["placement"]
return []Diagnostic{{Code: CodeImportInvalid, Severity: SeverityError, Path: site.path.clone(), Message: "sequence-item placement requires a nonempty destination", Primary: cloneLocation(&site.location)}} return []Diagnostic{{Code: CodeImportInvalid, Severity: SeverityError, Path: site.path.clone(), Message: "sequence-item placement requires a nonempty destination", Primary: cloneLocation(&site.location)}}
} }
@@ -305,8 +305,8 @@ func (s *importTraversal) admitImportSource(canonical string, identity resourceI
label = s.access.label + "/" + canonical label = s.access.label + "/" + canonical
} }
info := SourceInfo{ID: sourceID, Label: label, Path: canonical, Destination: s.sourceDestination(destination), Layer: LayerBase, Kind: kind, ImportedFrom: &location} info := SourceInfo{ID: sourceID, Label: label, Path: canonical, Destination: s.sourceDestination(destination), Layer: LayerBase, Kind: kind, ImportedFrom: &location}
if policy.placement == placementModeSequenceItem { if policy.placement == PlacementModeSequenceItem {
info.placementMode = placementModeSequenceItem info.placementMode = PlacementModeSequenceItem
} }
// A source's own item placement still has an exact sequence-container // A source's own item placement still has an exact sequence-container
// destination. Only an unresolved enclosing item index makes the path a // destination. Only an unresolved enclosing item index makes the path a
@@ -433,17 +433,17 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
admitted := admission.source admitted := admission.source
placementDestination := destination placementDestination := destination
mode := placementModeMapping mode := PlacementModeMapping
if policy.placement == placementModeSequenceItem { if policy.placement == PlacementModeSequenceItem {
mode = placementModeSequenceItem mode = PlacementModeSequenceItem
} }
role := importRoleMapping role := importRoleMapping
if mode == placementModeSequenceItem { if mode == PlacementModeSequenceItem {
role = importRoleItem role = importRoleItem
} }
s.importGraph.addNode(importGraphNode{key: admitted.nodeKey, canonical: admitted.canonical, sourceID: admitted.source.ID, role: role}) s.importGraph.addNode(importGraphNode{key: admitted.nodeKey, canonical: admitted.canonical, sourceID: admitted.source.ID, role: role})
s.importGraph.addEdge(importerKey, admitted.nodeKey, location) s.importGraph.addEdge(importerKey, admitted.nodeKey, location)
if mode == placementModeSequenceItem { if mode == PlacementModeSequenceItem {
route := importRoute{canonical: admitted.canonical, locations: currentRoute.locations, key: admitted.nodeKey} route := importRoute{canonical: admitted.canonical, locations: currentRoute.locations, key: admitted.nodeKey}
s.active[admitted.identity] = len(s.stack) s.active[admitted.identity] = len(s.stack)
s.tier.visited[admitted.identity] = route s.tier.visited[admitted.identity] = route

View File

@@ -213,7 +213,7 @@ func projectAcquiredDestinations(composed *composedDocument, scope acquisitionSc
itemIndex = projected itemIndex = projected
} }
} }
if contribution.source.placementMode == placementModeSequenceItem { if contribution.source.placementMode == PlacementModeSequenceItem {
container := appendDestination(contributionPrefix, contribution.destination) container := appendDestination(contributionPrefix, contribution.destination)
contribution.source.Destination = container.clone() contribution.source.Destination = container.clone()
contribution.source.destinationUnresolved = false contribution.source.destinationUnresolved = false

View File

@@ -73,12 +73,12 @@ func composeAcquiredInputWithPolicies(input assembledInput, lower collectionPoli
} }
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(localScope)} completed.acquisition = []acquisitionScope{cloneAcquisitionScope(localScope)}
projectNestedEvidence(completed, localScope) projectNestedEvidence(completed, localScope)
_, placed, err := placeDocumentWithMode(completed.root, main.destination, placementModeSequenceItem, main.payload.Location, maxDocumentDepth, maxDocumentNodes) _, placed, err := placeDocumentWithMode(completed.root, main.destination, PlacementModeSequenceItem, main.payload.Location, maxDocumentDepth, maxDocumentNodes)
if err != nil { if err != nil {
return nil, []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, main.source.ID, "configuration exceeds the source depth or node limit after destination placement")} return nil, []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, main.source.ID, "configuration exceeds the source depth or node limit after destination placement")}
} }
if !main.placementCharged { if !main.placementCharged {
placementNodes := destinationWrapperNodeCountForMode(main.destination, placementModeSequenceItem) placementNodes := destinationWrapperNodeCountForMode(main.destination, PlacementModeSequenceItem)
if accounting.nodes+placementNodes > traversal.policy.importLimits.nodes { if accounting.nodes+placementNodes > traversal.policy.importLimits.nodes {
return nil, []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", main.source.ImportedFrom)} return nil, []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", main.source.ImportedFrom)}
} }
@@ -154,7 +154,7 @@ func reservePendingItemPlacements(scope *acquisitionScope, limits importLimits,
if contribution.pendingItem == nil { if contribution.pendingItem == nil {
continue continue
} }
placementNodes := destinationWrapperNodeCountForMode(contribution.destination, placementModeSequenceItem) placementNodes := destinationWrapperNodeCountForMode(contribution.destination, PlacementModeSequenceItem)
if accounting.nodes+placementNodes > limits.nodes { if accounting.nodes+placementNodes > limits.nodes {
diagnostic := importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom) diagnostic := importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom)
return &diagnostic return &diagnostic
@@ -228,11 +228,11 @@ func (s *importTraversal) composePendingItems(scope *acquisitionScope) []Diagnos
// item has its effective coordinates, not against its local field names. // item has its effective coordinates, not against its local field names.
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(*itemScope)} completed.acquisition = []acquisitionScope{cloneAcquisitionScope(*itemScope)}
projectNestedEvidence(completed, *itemScope) projectNestedEvidence(completed, *itemScope)
_, placed, err := placeDocumentWithMode(completed.root, contribution.destination, placementModeSequenceItem, contribution.payload.Location, maxDocumentDepth, maxDocumentNodes) _, placed, err := placeDocumentWithMode(completed.root, contribution.destination, PlacementModeSequenceItem, contribution.payload.Location, maxDocumentDepth, maxDocumentNodes)
if err != nil { if err != nil {
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source depth or node limit after destination placement", contribution.source.ImportedFrom)} return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source depth or node limit after destination placement", contribution.source.ImportedFrom)}
} }
placementNodes := destinationWrapperNodeCountForMode(contribution.destination, placementModeSequenceItem) placementNodes := destinationWrapperNodeCountForMode(contribution.destination, PlacementModeSequenceItem)
if !contribution.placementCharged { if !contribution.placementCharged {
if s.state.accounting.nodes+placementNodes > s.policy.importLimits.nodes { if s.state.accounting.nodes+placementNodes > s.policy.importLimits.nodes {
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom)} return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom)}

View File

@@ -38,7 +38,7 @@ type importDeclarationSpec struct {
depth int depth int
depthSet bool depthSet bool
destination Path destination Path
placement placementMode placement PlacementMode
fieldSites map[string]importFieldSite fieldSites map[string]importFieldSite
} }
@@ -66,7 +66,7 @@ type resolvedImportPolicy struct {
depthSet bool depthSet bool
exclude []string exclude []string
destination Path destination Path
placement placementMode placement PlacementMode
} }
func snapshotImportDefaults(value ImportDefaults) ImportDefaults { func snapshotImportDefaults(value ImportDefaults) ImportDefaults {
@@ -173,7 +173,7 @@ func parseImportDeclarationSpec(node *document.Node, declarationPath Path) (impo
spec := importDeclarationSpec{ spec := importDeclarationSpec{
path: declarationPath.clone(), path: declarationPath.clone(),
location: publicLocation(node.Location), location: publicLocation(node.Location),
placement: placementModeMapping, placement: PlacementModeMapping,
fieldSites: make(map[string]importFieldSite, len(node.Mapping)), fieldSites: make(map[string]importFieldSite, len(node.Mapping)),
} }
selectors := 0 selectors := 0
@@ -281,19 +281,19 @@ func parseImportWildcardAssignment(spec *importDeclarationSpec, field string, no
return nil return nil
} }
func parseImportPlacement(node *document.Node, fieldPath Path) (placementMode, *Diagnostic) { func parseImportPlacement(node *document.Node, fieldPath Path) (PlacementMode, *Diagnostic) {
if node.Kind != document.String { if node.Kind != document.String {
diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", node) diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", node)
return placementModeMapping, &diagnostic return PlacementModeMapping, &diagnostic
} }
switch node.Text { switch node.Text {
case "mapping": case "mapping":
return placementModeMapping, nil return PlacementModeMapping, nil
case "sequence-item": case "sequence-item":
return placementModeSequenceItem, nil return PlacementModeSequenceItem, nil
default: default:
diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", node) diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", node)
return placementModeMapping, &diagnostic return PlacementModeMapping, &diagnostic
} }
} }

View File

@@ -31,7 +31,7 @@ func TestParseImportDeclarationSpecNormalizesPresenceAndPlacement(t *testing.T)
if first.selector != importSelectorPath || len(first.paths) != 2 || first.paths[0] != "one.yaml" || first.paths[1] != "two.yaml" { if first.selector != importSelectorPath || len(first.paths) != 2 || first.paths[0] != "one.yaml" || first.paths[1] != "two.yaml" {
t.Fatalf("path selector = %#v", first) t.Fatalf("path selector = %#v", first)
} }
if first.required == nil || *first.required || first.destination.String() != "/services" || first.placement != placementModeSequenceItem { if first.required == nil || *first.required || first.destination.String() != "/services" || first.placement != PlacementModeSequenceItem {
t.Fatalf("explicit declaration values = %#v", first) t.Fatalf("explicit declaration values = %#v", first)
} }
if first.path.String() != "/configkit/imports/0" || first.fieldSites["required"].path.String() != "/configkit/imports/0/required" { if first.path.String() != "/configkit/imports/0" || first.fieldSites["required"].path.String() != "/configkit/imports/0/required" {

View File

@@ -413,7 +413,7 @@ label: helper
loaded, err := LoadLayered( loaded, err := LoadLayered(
Input{Source: NoSource(), Imports: true}, testSecretBinding(), Input{Source: NoSource(), Imports: true}, testSecretBinding(),
LayeredOptions[config]{Underlays: []Underlay{{Inputs: []Input{{ LayeredOptions[config]{Underlays: []Underlay{{Inputs: []Input{{
Source: File(itemPath), Imports: true, Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem, Source: File(itemPath), Imports: true, Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem,
}}}}}, }}}}},
) )
if err != nil { if err != nil {
@@ -503,7 +503,7 @@ func TestLoadItemGlobKeepsDefaultModeIndependentAndOptionalMissesAbsent(t *testi
t.Fatalf("config = %#v", result.Config) t.Fatalf("config = %#v", result.Config)
} }
sources := result.Sources() sources := result.Sources()
if len(sources) != 3 || sources[1].Path != canonicalTestPath(t, emptyPath) || sources[1].Destination.String() != "/items" || sources[1].placementMode != placementModeSequenceItem || sources[2].placementMode != placementModeMapping || sources[2].Destination.String() != "/other" { if len(sources) != 3 || sources[1].Path != canonicalTestPath(t, emptyPath) || sources[1].Destination.String() != "/items" || sources[1].placementMode != PlacementModeSequenceItem || sources[2].placementMode != PlacementModeMapping || sources[2].Destination.String() != "/other" {
t.Fatalf("source placement evidence = %#v", sources) t.Fatalf("source placement evidence = %#v", sources)
} }
} }
@@ -521,7 +521,7 @@ func TestLoadMainItemPlacementKeepsItsHelpersInTheItem(t *testing.T) {
mainPath := writeImportFile(t, directory, "main.yml", "configkit:\n imports:\n - path: helper.yml\nid: main\n") mainPath := writeImportFile(t, directory, "main.yml", "configkit:\n imports:\n - path: helper.yml\nid: main\n")
helperPath := writeImportFile(t, directory, "helper.yml", "value: helper\n") helperPath := writeImportFile(t, directory, "helper.yml", "value: helper\n")
result, err := testLoad[config](File(mainPath), Options[config]{ result, err := testLoad[config](File(mainPath), Options[config]{
Imports: true, destination: Keys("items"), placementMode: placementModeSequenceItem, Imports: true, Destination: Keys("items"), PlacementMode: PlacementModeSequenceItem,
}) })
if err != nil { if err != nil {
t.Fatalf("load main item: %v", err) t.Fatalf("load main item: %v", err)
@@ -530,7 +530,7 @@ func TestLoadMainItemPlacementKeepsItsHelpersInTheItem(t *testing.T) {
t.Fatalf("main item = %#v", result.Config.Items) t.Fatalf("main item = %#v", result.Config.Items)
} }
sources := result.Sources() sources := result.Sources()
if len(sources) != 2 || sources[0].Destination.String() != "/items" || sources[0].placementMode != placementModeSequenceItem || sources[1].Destination.String() != "/items/0" || sources[1].placementMode != placementModeMapping { if len(sources) != 2 || sources[0].Destination.String() != "/items" || sources[0].placementMode != PlacementModeSequenceItem || sources[1].Destination.String() != "/items/0" || sources[1].placementMode != PlacementModeMapping {
t.Fatalf("main item sources = %#v", sources) t.Fatalf("main item sources = %#v", sources)
} }
if sources[1].Path != canonicalTestPath(t, helperPath) { if sources[1].Path != canonicalTestPath(t, helperPath) {
@@ -549,7 +549,7 @@ func TestProjectAcquiredDestinationsUsesMappedSequenceItemIndex(t *testing.T) {
items: []sequenceItemProjection{{inputIndex: 0, outputIndex: 2}}, items: []sequenceItemProjection{{inputIndex: 0, outputIndex: 2}},
}}} }}}
scope := acquisitionScope{contributions: []acquiredContribution{{ scope := acquisitionScope{contributions: []acquiredContribution{{
source: SourceInfo{ID: "outer-source", placementMode: placementModeSequenceItem}, source: SourceInfo{ID: "outer-source", placementMode: PlacementModeSequenceItem},
destination: Keys("records"), nested: child, destination: Keys("records"), nested: child,
}}} }}}
sources := []SourceInfo{{ID: "outer-source"}, {ID: "nested-source"}} sources := []SourceInfo{{ID: "outer-source"}, {ID: "nested-source"}}
@@ -760,7 +760,7 @@ func TestParseImportPlacementDefaultsPerDeclaration(t *testing.T) {
- glob: records/*.yml - glob: records/*.yml
placement: sequence-item placement: sequence-item
`) `)
if len(declarations) != 2 || declarations[0].placement != placementModeMapping || declarations[1].placement != placementModeSequenceItem { if len(declarations) != 2 || declarations[0].placement != PlacementModeMapping || declarations[1].placement != PlacementModeSequenceItem {
t.Fatalf("declaration placements = %#v", declarations) t.Fatalf("declaration placements = %#v", declarations)
} }
} }
@@ -1107,7 +1107,7 @@ id: item
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: helper.yml}]}\nid: main\n") mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: helper.yml}]}\nid: main\n")
writeImportFile(t, directory, "helper.yml", "id: helper\n") writeImportFile(t, directory, "helper.yml", "id: helper\n")
options := Options[importTestConfig]{ options := Options[importTestConfig]{
Imports: true, destination: Keys("items"), placementMode: placementModeSequenceItem, Imports: true, Destination: Keys("items"), PlacementMode: PlacementModeSequenceItem,
} }
loadError := requireLoadCode(t, File(mainPath), options, CodeComposeConflict) loadError := requireLoadCode(t, File(mainPath), options, CodeComposeConflict)
diagnostic := loadError.Diagnostics()[0] diagnostic := loadError.Diagnostics()[0]

View File

@@ -8,7 +8,7 @@ type Input struct {
Imports bool Imports bool
Filesystem *FilesystemContext Filesystem *FilesystemContext
Destination Path Destination Path
placementMode placementMode PlacementMode PlacementMode
} }
// Underlay contains strictly composed contributions at one precedence tier. // Underlay contains strictly composed contributions at one precedence tier.
@@ -38,7 +38,7 @@ func LoadLayered[T any](main Input, secrets SecretBinding, supplied ...LayeredOp
request := loadRequest[T]{ request := loadRequest[T]{
main: loadInput{ main: loadInput{
source: main.Source, imports: main.Imports, filesystem: main.Filesystem, source: main.Source, imports: main.Imports, filesystem: main.Filesystem,
destination: main.Destination.clone(), placementMode: main.placementMode, destination: main.Destination.clone(), placementMode: main.PlacementMode,
}, },
definition: options.Definition, sourceVersionMode: options.SourceVersionMode, definition: options.Definition, sourceVersionMode: options.SourceVersionMode,
importDefaults: options.ImportDefaults, importDefaults: options.ImportDefaults,
@@ -52,7 +52,7 @@ func LoadLayered[T any](main Input, secrets SecretBinding, supplied ...LayeredOp
for inputIndex, input := range underlay.Inputs { for inputIndex, input := range underlay.Inputs {
request.underlays[index].inputs[inputIndex] = loadInput{ request.underlays[index].inputs[inputIndex] = loadInput{
source: input.Source, imports: input.Imports, filesystem: input.Filesystem, source: input.Source, imports: input.Imports, filesystem: input.Filesystem,
destination: input.Destination.clone(), placementMode: input.placementMode, destination: input.Destination.clone(), placementMode: input.PlacementMode,
} }
} }
} }

View File

@@ -33,8 +33,8 @@ type Options[T any] struct {
Overrides []Override Overrides []Override
SourceVersionMode sourceVersionMode SourceVersionMode sourceVersionMode
ImportDefaults ImportDefaults ImportDefaults ImportDefaults
destination Path Destination Path
placementMode placementMode PlacementMode PlacementMode
} }
// FilesystemContext supplies filesystem roots for imports and overlays. For an // FilesystemContext supplies filesystem roots for imports and overlays. For an
@@ -161,7 +161,7 @@ func loadWithSecretLookupLimit[T any](source Source, secrets SecretBinding, look
} }
input, diagnostics := normalizeInput(loadInput{ input, diagnostics := normalizeInput(loadInput{
source: source, imports: options.Imports, filesystem: options.Filesystem, source: source, imports: options.Imports, filesystem: options.Filesystem,
destination: options.destination.clone(), placementMode: options.placementMode, destination: options.Destination.clone(), placementMode: options.PlacementMode,
}, options.Overlay) }, options.Overlay)
defaults, policy, settingsDiagnostics := loadSettings(options.ImportDefaults, options.Definition, options.SourceVersionMode) defaults, policy, settingsDiagnostics := loadSettings(options.ImportDefaults, options.Definition, options.SourceVersionMode)
diagnostics = append(diagnostics, settingsDiagnostics...) diagnostics = append(diagnostics, settingsDiagnostics...)

View File

@@ -32,7 +32,7 @@ type loadInput struct {
imports bool imports bool
filesystem *FilesystemContext filesystem *FilesystemContext
destination Path destination Path
placementMode placementMode placementMode PlacementMode
} }
type loadGroup struct{ inputs []loadInput } type loadGroup struct{ inputs []loadInput }

View File

@@ -161,8 +161,8 @@ func TestUnderlayItemConflictUsesScheduledGroupIndex(t *testing.T) {
request := loadRequest[map[string]any]{ request := loadRequest[map[string]any]{
main: loadInput{source: NoSource()}, main: loadInput{source: NoSource()},
underlays: []loadGroup{{inputs: []loadInput{ underlays: []loadGroup{{inputs: []loadInput{
{source: File(zItem), destination: Keys("items"), placementMode: placementModeSequenceItem}, {source: File(zItem), destination: Keys("items"), placementMode: PlacementModeSequenceItem},
{source: File(aItem), destination: Keys("items"), placementMode: placementModeSequenceItem}, {source: File(aItem), destination: Keys("items"), placementMode: PlacementModeSequenceItem},
}}}, }}},
definition: Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}}, definition: Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}},
} }
@@ -205,8 +205,8 @@ func TestUnderlayItemHelperConflictUsesScheduledGroupIndex(t *testing.T) {
request := loadRequest[map[string]any]{ request := loadRequest[map[string]any]{
main: loadInput{source: NoSource()}, main: loadInput{source: NoSource()},
underlays: []loadGroup{{inputs: []loadInput{ underlays: []loadGroup{{inputs: []loadInput{
{source: File(zBad), imports: true, destination: Keys("items"), placementMode: placementModeSequenceItem}, {source: File(zBad), imports: true, destination: Keys("items"), placementMode: PlacementModeSequenceItem},
{source: File(aGood), destination: Keys("items"), placementMode: placementModeSequenceItem}, {source: File(aGood), destination: Keys("items"), placementMode: PlacementModeSequenceItem},
}}}, }}},
definition: Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}}, definition: Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}},
} }

View File

@@ -2,7 +2,7 @@ package engine
func normalizeInput(input loadInput, ordinaryOverlay string) (loadInput, []Diagnostic) { func normalizeInput(input loadInput, ordinaryOverlay string) (loadInput, []Diagnostic) {
var diagnostics []Diagnostic var diagnostics []Diagnostic
if input.placementMode != placementModeMapping && input.placementMode != placementModeSequenceItem { if input.placementMode != PlacementModeMapping && input.placementMode != PlacementModeSequenceItem {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "input placement mode is invalid")) diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "input placement mode is invalid"))
} }
if normalized, err := normalizeDestination(input.destination); err != nil { if normalized, err := normalizeDestination(input.destination); err != nil {
@@ -10,7 +10,7 @@ func normalizeInput(input loadInput, ordinaryOverlay string) (loadInput, []Diagn
} else { } else {
input.destination = normalized input.destination = normalized
} }
if input.placementMode == placementModeSequenceItem && len(input.destination.segments) == 0 { if input.placementMode == PlacementModeSequenceItem && len(input.destination.segments) == 0 {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "sequence-item placement requires a nonempty destination")) diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "sequence-item placement requires a nonempty destination"))
} }
if input.filesystem != nil { if input.filesystem != nil {

View File

@@ -196,7 +196,7 @@ type SourceInfo struct {
VersionCheck *SourceVersionCheck VersionCheck *SourceVersionCheck
importSelections []importSelectionEvidence importSelections []importSelectionEvidence
namespace *namespaceToken namespace *namespaceToken
placementMode placementMode placementMode PlacementMode
destinationUnresolved bool destinationUnresolved bool
} }

View File

@@ -158,7 +158,7 @@ func TestLoadingClosesNativeFilesAcrossPipelineOutcomes(t *testing.T) {
child: "configkit: {imports: [{path: helper.yml}]}\nid: child\nvalue: child\n", child: "configkit: {imports: [{path: helper.yml}]}\nid: child\nvalue: child\n",
helper: "value: helper\n", helper: "value: helper\n",
options: Options[config]{ options: Options[config]{
Imports: true, destination: Keys("groups"), placementMode: placementModeSequenceItem, Imports: true, Destination: Keys("groups"), PlacementMode: PlacementModeSequenceItem,
}, },
wantErr: true, wantErr: true,
}, },

View File

@@ -132,7 +132,7 @@ func loadSelectedOverlay(request selectedOverlayRequest, access *filesystemAcces
var failures []Diagnostic var failures []Diagnostic
if versionState != nil { if versionState != nil {
processed, failures = versionState.processWithDirectiveContext( processed, failures = versionState.processWithDirectiveContext(
overlayRoot, sourceVersionContribution, Path{}, sourceID, placementModeMapping, rootDirectiveContextOverlay, overlayRoot, sourceVersionContribution, Path{}, sourceID, PlacementModeMapping, rootDirectiveContextOverlay,
) )
} else { } else {
processed = processSourceVersionWithDirectiveContext( processed = processSourceVersionWithDirectiveContext(

View File

@@ -117,19 +117,19 @@ func (evidence *sourceVersionEvidence) sensitivityPaths() []Path {
return paths return paths
} }
func (state *sourceVersionState) process(root *document.Node, role sourceVersionRole, destination Path, sourceID string, placement placementMode) (sourceVersionProcessingResult, []Diagnostic) { func (state *sourceVersionState) process(root *document.Node, role sourceVersionRole, destination Path, sourceID string, placement PlacementMode) (sourceVersionProcessingResult, []Diagnostic) {
if state == nil || state.mode == sourceVersionModeDefault { if state == nil || state.mode == sourceVersionModeDefault {
return sourceVersionProcessingResult{}, nil return sourceVersionProcessingResult{}, nil
} }
return state.processWithDirectiveContext(root, role, destination, sourceID, placement, rootDirectiveContextImportsOnly) return state.processWithDirectiveContext(root, role, destination, sourceID, placement, rootDirectiveContextImportsOnly)
} }
func (state *sourceVersionState) processWithDirectiveContext(root *document.Node, role sourceVersionRole, destination Path, sourceID string, placement placementMode, directiveContext rootDirectiveContext) (sourceVersionProcessingResult, []Diagnostic) { func (state *sourceVersionState) processWithDirectiveContext(root *document.Node, role sourceVersionRole, destination Path, sourceID string, placement PlacementMode, directiveContext rootDirectiveContext) (sourceVersionProcessingResult, []Diagnostic) {
if state == nil { if state == nil {
return sourceVersionProcessingResult{}, nil return sourceVersionProcessingResult{}, nil
} }
projectedDestination := destination.clone() projectedDestination := destination.clone()
if placement == placementModeSequenceItem { if placement == PlacementModeSequenceItem {
projectedDestination = projectedDestination.Index(0) projectedDestination = projectedDestination.Index(0)
} }
result := processSourceVersionWithDirectiveContext(root, state.localRule, role, projectedDestination, state.mode, directiveContext) result := processSourceVersionWithDirectiveContext(root, state.localRule, role, projectedDestination, state.mode, directiveContext)
@@ -158,10 +158,10 @@ type sourceVersionProcessingResult struct {
} }
func effectiveSourceVersionRule(local *VersionRule, destination Path, mode sourceVersionMode) *VersionRule { func effectiveSourceVersionRule(local *VersionRule, destination Path, mode sourceVersionMode) *VersionRule {
return effectiveSourceVersionRuleForPlacement(local, destination, mode, placementModeMapping) return effectiveSourceVersionRuleForPlacement(local, destination, mode, PlacementModeMapping)
} }
func effectiveSourceVersionRuleForPlacement(local *VersionRule, destination Path, mode sourceVersionMode, placement placementMode) *VersionRule { func effectiveSourceVersionRuleForPlacement(local *VersionRule, destination Path, mode sourceVersionMode, placement PlacementMode) *VersionRule {
if local == nil { if local == nil {
return nil return nil
} }
@@ -170,7 +170,7 @@ func effectiveSourceVersionRuleForPlacement(local *VersionRule, destination Path
} }
effective := *local effective := *local
prefix := destination.clone() prefix := destination.clone()
if placement == placementModeSequenceItem { if placement == PlacementModeSequenceItem {
prefix = prefix.Index(0) prefix = prefix.Index(0)
} }
effective.Path = Path{ effective.Path = Path{

View File

@@ -142,11 +142,11 @@ func TestFragmentVersionClaimUsesAssignedSequenceProjection(t *testing.T) {
t.Fatalf("destructive sequence ancestor claim = %#v, want mapping node", node) t.Fatalf("destructive sequence ancestor claim = %#v, want mapping node", node)
} }
effective := effectiveSourceVersionRuleForPlacement(&VersionRule{Path: Keys("version")}, Keys("catalog", "entries"), sourceVersionModeRequired, placementModeSequenceItem) effective := effectiveSourceVersionRuleForPlacement(&VersionRule{Path: Keys("version")}, Keys("catalog", "entries"), sourceVersionModeRequired, PlacementModeSequenceItem)
if got, want := effective.Path.String(), "/catalog/entries/0/version"; got != want { if got, want := effective.Path.String(), "/catalog/entries/0/version"; got != want {
t.Fatalf("item effective version path = %q, want %q", got, want) t.Fatalf("item effective version path = %q, want %q", got, want)
} }
defaultRule := effectiveSourceVersionRuleForPlacement(&VersionRule{Path: Keys("version")}, Keys("catalog", "entries"), sourceVersionModeDefault, placementModeSequenceItem) defaultRule := effectiveSourceVersionRuleForPlacement(&VersionRule{Path: Keys("version")}, Keys("catalog", "entries"), sourceVersionModeDefault, PlacementModeSequenceItem)
if got, want := defaultRule.Path.String(), "/version"; got != want { if got, want := defaultRule.Path.String(), "/version"; got != want {
t.Fatalf("default effective version path = %q, want %q", got, want) t.Fatalf("default effective version path = %q, want %q", got, want)
} }
@@ -628,7 +628,7 @@ func TestSourceVersionItemPlacementKeepsLocalChecksAndProtectsProjectedPath(t *t
load: func(definition Definition[map[string]any]) error { load: func(definition Definition[map[string]any]) error {
_, err := Load(Mapping("main", map[string]any{"version": "v1", "name": "ordinary"}), testSecretBinding(), Options[map[string]any]{ _, err := Load(Mapping("main", map[string]any{"version": "v1", "name": "ordinary"}), testSecretBinding(), Options[map[string]any]{
Definition: definition, SourceVersionMode: sourceVersionModeRequired, Definition: definition, SourceVersionMode: sourceVersionModeRequired,
destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem, Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem,
Overrides: []Override{{Path: Keys("catalog", "entries"), Value: []any{}, Flag: "--entries"}}, Overrides: []Override{{Path: Keys("catalog", "entries"), Value: []any{}, Flag: "--entries"}},
}) })
return err return err
@@ -637,7 +637,7 @@ func TestSourceVersionItemPlacementKeepsLocalChecksAndProtectsProjectedPath(t *t
{ {
name: "layered", name: "layered",
load: func(definition Definition[map[string]any]) error { load: func(definition Definition[map[string]any]) error {
_, err := LoadLayered(Input{Source: Mapping("main", map[string]any{"version": "v1", "name": "layered"}), Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem}, testSecretBinding(), LayeredOptions[map[string]any]{ _, err := LoadLayered(Input{Source: Mapping("main", map[string]any{"version": "v1", "name": "layered"}), Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem}, testSecretBinding(), LayeredOptions[map[string]any]{
Definition: definition, SourceVersionMode: sourceVersionModeRequired, Definition: definition, SourceVersionMode: sourceVersionModeRequired,
Overrides: []Override{{Path: Keys("catalog", "entries"), Value: []any{}, Flag: "--entries"}}, Overrides: []Override{{Path: Keys("catalog", "entries"), Value: []any{}, Flag: "--entries"}},
}) })
@@ -682,7 +682,7 @@ func TestSourceVersionItemEvidenceUsesProjectedSensitivityPath(t *testing.T) {
Secret: func(path Path) bool { return path.String() == secretPath.String() }, Secret: func(path Path) bool { return path.String() == secretPath.String() },
}, },
SourceVersionMode: sourceVersionModeRequired, SourceVersionMode: sourceVersionModeRequired,
destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem, Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem,
}) })
if err != nil { if err != nil {
t.Fatalf("item source-version load: %v", err) t.Fatalf("item source-version load: %v", err)

View File

@@ -65,8 +65,9 @@ func toEngineInput(input Input) engine.Input {
result := engine.Input{ result := engine.Input{
Source: input.Source.toEngine(), Imports: input.Imports, Source: input.Source.toEngine(), Imports: input.Imports,
Filesystem: toEngineFilesystemContext(input.Filesystem), Destination: toEnginePath(input.Destination), Filesystem: toEngineFilesystemContext(input.Filesystem), Destination: toEnginePath(input.Destination),
PlacementMode: engine.PlacementMode(input.PlacementMode),
} }
return engine.InputWithPlacement(result, uint8(input.PlacementMode)) return result
} }
func toEngineLayeredOptions[T any](options LayeredOptions[T]) engine.LayeredOptions[T] { func toEngineLayeredOptions[T any](options LayeredOptions[T]) engine.LayeredOptions[T] {

View File

@@ -318,8 +318,9 @@ func toEngineOptions[T any](options Options[T]) engine.Options[T] {
Overlay: options.Overlay, Overlay: options.Overlay,
SourceVersionMode: engine.SourceVersionModeFromFacade(uint8(options.SourceVersionMode)), SourceVersionMode: engine.SourceVersionModeFromFacade(uint8(options.SourceVersionMode)),
ImportDefaults: toEngineImportDefaults(options.ImportDefaults), ImportDefaults: toEngineImportDefaults(options.ImportDefaults),
Destination: toEnginePath(options.Destination.clone()),
PlacementMode: engine.PlacementMode(options.PlacementMode),
} }
result = engine.OptionsWithPlacement(result, toEnginePath(options.Destination.clone()), uint8(options.PlacementMode))
if options.Filesystem != nil { if options.Filesystem != nil {
result.Filesystem = toEngineFilesystemContext(options.Filesystem) result.Filesystem = toEngineFilesystemContext(options.Filesystem)
} }

View File

@@ -30,7 +30,9 @@ func TestOverlayPublicValues(t *testing.T) {
func TestSourceInfoDestinationConversion(t *testing.T) { func TestSourceInfoDestinationConversion(t *testing.T) {
destination := Keys("collections", "a/b", "0") destination := Keys("collections", "a/b", "0")
path := writeRecordConfig(t, "value: main\n") path := writeRecordConfig(t, "value: main\n")
options := engine.OptionsWithPlacement(engine.Options[map[string]any]{}, engine.Keys("collections", "a/b", "0"), uint8(PlacementModeSequenceItem)) options := engine.Options[map[string]any]{
Destination: engine.Keys("collections", "a/b", "0"), PlacementMode: engine.PlacementMode(PlacementModeSequenceItem),
}
internal := engineSourceInfoFromLoad(t, path, "source-1", options) internal := engineSourceInfoFromLoad(t, path, "source-1", options)
converted := fromEngineSourceInfo(internal) converted := fromEngineSourceInfo(internal)
if converted.Destination.String() != destination.String() || converted.PlacementMode != PlacementModeSequenceItem || converted.DestinationUnresolved { if converted.Destination.String() != destination.String() || converted.PlacementMode != PlacementModeSequenceItem || converted.DestinationUnresolved {