Expose sequence placement controls in the public API

This commit is contained in:
2026-10-16 19:23:43 +00:00
parent c19825620c
commit adef16ccdd
13 changed files with 573 additions and 55 deletions

View File

@@ -18,6 +18,26 @@ const (
placementModeSequenceItem
)
// PlacementModeFromFacade preserves root-owned numeric values until preflight
// can reject unsupported modes.
func PlacementModeFromFacade(value uint8) placementMode { return placementMode(value) }
// PlacementModeForFacade converts private source evidence to the root-owned mode.
func PlacementModeForFacade(value placementMode) uint8 { return uint8(value) }
// 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) {
if !path.valid() {
return Path{}, errInvalidDestination

View File

@@ -51,13 +51,14 @@ type importTraversal struct {
rule *VersionRule
versionState *sourceVersionState
scope acquisitionScope
itemScope bool
itemPrefix Path
sources []SourceInfo
active map[resourceIdentity]int
visited map[resourceIdentity]importRoute
stack []importRoute
scope acquisitionScope
itemScope bool
itemPrefix Path
itemFallback Path
sources []SourceInfo
active map[resourceIdentity]int
visited map[resourceIdentity]importRoute
stack []importRoute
}
func loadImportedDocuments(source Source, context *FilesystemContext, main parsedSource, rule *VersionRule, limits importLimits) (*composedDocument, []SourceInfo, []Diagnostic) {
@@ -135,9 +136,6 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
mainInfo := cloneSourceInfo(*main.source)
mainInfo.Destination = destination.clone()
mainIsItem := main.source.placementMode == placementModeSequenceItem
if mainIsItem {
mainInfo.destinationUnresolved = true
}
if mainPath != "" {
mainInfo.Path = mainPath
}
@@ -161,6 +159,7 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
if mainIsItem {
state.itemScope = true
state.itemPrefix = destination.clone()
state.itemFallback = destination.clone()
state.scope.destination = Path{}
state.sources[0] = cloneSourceInfo(mainInfo)
}
@@ -394,7 +393,10 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
if declaration.placement == placementModeSequenceItem {
info.placementMode = placementModeSequenceItem
}
info.destinationUnresolved = s.itemScope || declaration.placement == placementModeSequenceItem
// A source's own item placement still has an exact sequence-container
// destination. Only an unresolved enclosing item index makes the path a
// fallback container for this snapshot.
info.destinationUnresolved = s.itemScope
if kind == SourceKindFilesystem {
info.namespace = s.access.token
}
@@ -484,18 +486,21 @@ func (s *importTraversal) sourceDestination(destination Path) Path {
if !s.itemScope {
return destination.clone()
}
return appendDestination(s.itemPrefix, destination)
// Until the enclosing item receives its assigned index, only the collection
// container is exact. Keep the local suffix in the acquisition projection.
return s.itemFallback.clone()
}
func (s *importTraversal) visitItemSource(canonical string, declaration importDeclaration, destination, relativeDestination Path, depth int, info SourceInfo, body *document.Node, declarations []importDeclaration, processing *sourceVersionProcessingResult) []Diagnostic {
parentScope := s.scope
parentItemScope, parentPrefix := s.itemScope, s.itemPrefix
parentItemScope, parentPrefix, parentFallback := s.itemScope, s.itemPrefix, s.itemFallback
s.scope = acquisitionScope{}
s.itemScope = true
if parentItemScope {
s.itemPrefix = appendDestination(parentPrefix, destination)
} else {
s.itemPrefix = destination.clone()
s.itemFallback = destination.clone()
}
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
source: cloneSourceInfo(info), payload: body, document: body,
@@ -503,7 +508,7 @@ func (s *importTraversal) visitItemSource(canonical string, declaration importDe
})
diagnostics := s.walkDeclarations(s.access.dir(canonical), declarations, depth, Path{})
itemScope := s.scope
s.scope, s.itemScope, s.itemPrefix = parentScope, parentItemScope, parentPrefix
s.scope, s.itemScope, s.itemPrefix, s.itemFallback = parentScope, parentItemScope, parentPrefix, parentFallback
if len(diagnostics) != 0 {
return diagnostics
}

View File

@@ -197,6 +197,24 @@ type SourceInfo struct {
destinationUnresolved bool
}
// SourceInfoPlacementModeForFacade converts private placement evidence to its
// root-owned numeric representation.
func SourceInfoPlacementModeForFacade(source SourceInfo) uint8 {
return uint8(source.placementMode)
}
// SourceInfoDestinationUnresolvedForFacade returns the private fallback marker.
func SourceInfoDestinationUnresolvedForFacade(source SourceInfo) bool {
return source.destinationUnresolved
}
// SourceInfoWithPlacement copies facade-owned source evidence into private fields.
func SourceInfoWithPlacement(source SourceInfo, mode uint8, unresolved bool) SourceInfo {
source.placementMode = placementMode(mode)
source.destinationUnresolved = unresolved
return source
}
// SourceVersionCheck is a detached, public-safe record of a source-local
// version declaration observed during an enabled load.
type SourceVersionCheck struct {

View File

@@ -9,6 +9,22 @@ type sequencePolicy struct {
identityFields map[string]string
}
// DefinitionWithSequenceRegistrations attaches facade-owned registrations to a
// detached engine definition without exposing the registration representation.
func DefinitionWithSequenceRegistrations[T any](definition Definition[T], paths []Path, identityFields []string) Definition[T] {
if paths == nil {
definition.sequenceRegistrations = nil
return definition
}
definition.sequenceRegistrations = make([]sequenceRegistration, len(paths))
for index := range paths {
definition.sequenceRegistrations[index] = sequenceRegistration{
path: paths[index].clone(), identityField: identityFields[index],
}
}
return definition
}
func compileSequencePolicy(registrations []sequenceRegistration) (sequencePolicy, []Diagnostic) {
policy := sequencePolicy{identityFields: make(map[string]string, len(registrations))}
var diagnostics []Diagnostic