Project version protections through item placement

This commit is contained in:
2026-10-16 19:11:08 +00:00
parent 190431740a
commit c19825620c
10 changed files with 276 additions and 50 deletions

View File

@@ -1,6 +1,6 @@
# Implementation Plan: Sequence Placement and Composition
Status: implementation in progress; stages 1–9 completed.
Status: implementation in progress; stages 1–10 completed.
## Purpose and authority
@@ -595,6 +595,22 @@ Validation: source-version/version/inheritance/override/resolution tests,
`go test -race ./internal/engine`, and `./scripts/check.sh`.
Completion: item placement changes coordinates, not version or structural policy.
Completion evidence: enabled source-version rules now derive their effective
paths through the main item's assigned index, while default-mode coordinates
remain unchanged. Structural import and underlay checks use assigned sequence
ranges so they protect the actual main item without rejecting unrelated peers;
source-local checking still runs once per original source, and projected header
evidence is used for sensitivity. Tests cover ordinary and layered placement,
protected versus peer indexes, nested projections, and redacted version
evidence. These checks passed with Go `go1.26.9 linux/amd64`:
- `GOTOOLCHAIN=local GOPROXY=off go test ./internal/engine -run 'SourceVersion|Version|Inheritance|Override|Resolution' -count=1`
- `GOTOOLCHAIN=local GOPROXY=off go test -race ./internal/engine`
- `GOTOOLCHAIN=local ./scripts/check.sh`
- `git diff --check`
No environmental limitation affected validation; checks ran on Linux/amd64.
## Stage 11 — Expose root-owned controls and detached placement evidence
Scope: root facade declarations, conversions, and public integration tests.

View File

@@ -193,6 +193,43 @@ func TestLayeredMountedVersionProtectionCoversInheritanceAndUpperTiers(t *testin
})
}
func TestLayeredItemVersionProtectionTracksAssignedPeerIndexes(t *testing.T) {
t.Parallel()
definition := Definition[map[string]any]{
Version: &VersionRule{Path: Keys("schema", "version"), Check: func(Version, *Validation) error { return nil }},
sequenceRegistrations: []sequenceRegistration{{path: Keys("catalog", "entries"), identityField: "id"}},
}
item := func(label string, value map[string]any) Input {
return Input{Source: Mapping(label, value), Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem}
}
options := func(inputs ...Input) LayeredOptions[map[string]any] {
return LayeredOptions[map[string]any]{Definition: definition, SourceVersionMode: sourceVersionModeIfPresent, Underlays: []Underlay{{Inputs: inputs}}}
}
main := item("main", map[string]any{"id": "main", "schema": map[string]any{"version": "v1"}})
t.Run("same assigned main path is protected", func(t *testing.T) {
_, err := LoadLayered(main, testSecretBinding(), options(item("underlay", map[string]any{"id": "underlay", "schema": nil})))
if got := groupedErrorCode(t, err); got != CodeUnderlayVersion {
t.Fatalf("protected item claim code = %s, want %s", got, CodeUnderlayVersion)
}
})
t.Run("appended peer does not claim main path", func(t *testing.T) {
loaded, err := LoadLayered(main, testSecretBinding(), options(
item("first", map[string]any{"id": "first", "schema": map[string]any{"other": "value"}}),
item("peer", map[string]any{"id": "peer", "schema": nil}),
))
if err != nil {
t.Fatalf("unrelated appended peer rejected: %v", err)
}
entries := layeredMapValue(t, testLoaded(loaded).Config, "catalog", "entries").([]any)
if len(entries) != 1 || entries[0].(map[string]any)["id"] != "main" {
t.Fatalf("main item projection = %#v", entries)
}
})
}
type destinationDecodeProbe string
var destinationDecodeProbeValues []string

View File

@@ -163,26 +163,54 @@ func nestedDirectiveDiagnostics(node *document.Node, current Path) []Diagnostic
}
func fragmentVersionClaim(root *document.Node, rule *VersionRule) *document.Node {
return fragmentVersionClaimProjected(root, rule, -1, nil)
}
func fragmentVersionClaimProjected(root *document.Node, rule *VersionRule, sourceIndex int, ranges []sequenceRange) *document.Node {
if rule == nil {
return nil
}
current := root
currentPath := Path{}
if len(rule.Path.segments) == 0 {
return current
}
for _, segment := range rule.Path.segments {
if current.Kind != document.Mapping {
switch current.Kind {
case document.Mapping:
if segment.kind == pathSegmentIndex {
return current
}
index := documentEntryIndex(current.Mapping, segment.key)
if index < 0 {
return nil
}
current = current.Mapping[index].Value
currentPath = currentPath.Key(segment.key)
case document.Sequence:
if segment.kind != pathSegmentIndex {
return current
}
index := segment.index
for _, assigned := range ranges {
if assigned.sourceIndex != sourceIndex || assigned.path.String() != currentPath.String() {
continue
}
if index < assigned.start || index >= assigned.end {
return nil
}
index -= assigned.start
break
}
if index < 0 || index >= len(current.Sequence) {
return nil
}
current = current.Sequence[index]
currentPath = currentPath.Index(index)
default:
// A scalar or null at an ancestor replaces the protected subtree.
return current
}
if segment.kind == pathSegmentIndex {
return nil
}
key := segment.key
index := documentEntryIndex(current.Mapping, key)
if index < 0 {
return nil
}
current = current.Mapping[index].Value
}
return current
}

View File

@@ -230,6 +230,11 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
if len(failures) != 0 {
return nil, cloneSourceInfos(state.sources), prependDiagnosticPaths(failures, destination.Index(0))
}
if versionState != nil {
if diagnostic := importedVersionClaim(documents, completed, versionState.localRule, 0); diagnostic != nil {
return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic}
}
}
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(localScope)}
projectNestedEvidence(completed, localScope)
_, placed, err := placeDocumentWithMode(completed.root, destination, placementModeSequenceItem, body.Location, maxDocumentDepth, maxDocumentNodes)
@@ -260,6 +265,11 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
documents = append(documents, contribution.document)
}
composed, diagnostics := composeDocumentsWithPolicy(policy, documents...)
if composed != nil {
if diagnostic := importedVersionClaim(documents, composed, rule, 0); diagnostic != nil {
return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic}
}
}
if composed != nil {
projectNestedEvidence(composed, state.scope)
projectAcquiredDestinations(composed, state.scope, state.sources)
@@ -416,7 +426,11 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
var diagnostics []Diagnostic
var processing *sourceVersionProcessingResult
if s.versionState != nil && s.versionState.mode != sourceVersionModeDefault {
processed, failures := s.versionState.process(root, sourceVersionContribution, destination, sourceID)
versionDestination := destination
if s.itemScope {
versionDestination = appendDestination(s.itemPrefix.Index(0), destination)
}
processed, failures := s.versionState.process(root, sourceVersionContribution, versionDestination, sourceID, declaration.placement)
if len(failures) != 0 {
return failures
}
@@ -452,18 +466,6 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &location)}
}
s.accounting.nodes += placementNodes
protectionRule := s.rule
if s.versionState != nil && s.versionState.effectiveRule != nil {
protectionRule = s.versionState.effectiveRule
}
if versionNode := fragmentVersionClaim(placed, protectionRule); versionNode != nil {
versionLocation := publicLocation(versionNode.Location)
return []Diagnostic{{
Code: CodeImportVersion, Severity: SeverityError, Path: protectionRule.Path.clone(),
Message: "imported fragments cannot contribute to the schema-version path", Primary: &versionLocation,
}}
}
route := importRoute{canonical: canonical, locations: currentRoute.locations}
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
source: cloneSourceInfo(info), payload: body, document: placed,
@@ -530,17 +532,6 @@ func (s *importTraversal) visitItemSource(canonical string, declaration importDe
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &declaration.location)}
}
s.accounting.nodes += placementNodes
protectionRule := s.rule
if s.versionState != nil && s.versionState.effectiveRule != nil {
protectionRule = s.versionState.effectiveRule
}
if versionNode := fragmentVersionClaim(placed, protectionRule); versionNode != nil {
versionLocation := publicLocation(versionNode.Location)
return []Diagnostic{{
Code: CodeImportVersion, Severity: SeverityError, Path: protectionRule.Path.clone(),
Message: "imported fragments cannot contribute to the schema-version path", Primary: &versionLocation,
}}
}
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
source: cloneSourceInfo(info), payload: completed.root, document: placed,
destination: pl.clone(), relativeDestination: relativeDestination.clone(), processing: processing,
@@ -549,6 +540,25 @@ func (s *importTraversal) visitItemSource(canonical string, declaration importDe
return nil
}
func importedVersionClaim(documents []*document.Node, composed *composedDocument, rule *VersionRule, mainIndex int) *Diagnostic {
if composed == nil || rule == nil {
return nil
}
for index, contribution := range documents {
if index == mainIndex {
continue
}
if node := fragmentVersionClaimProjected(contribution, rule, index, composed.sequenceRanges); node != nil {
location := publicLocation(node.Location)
return &Diagnostic{
Code: CodeImportVersion, Severity: SeverityError, Path: rule.Path.clone(),
Message: "imported fragments cannot contribute to the schema-version path", Primary: &location,
}
}
}
return nil
}
func prependDiagnosticPaths(diagnostics []Diagnostic, prefix Path) []Diagnostic {
cloned := append([]Diagnostic(nil), diagnostics...)
for index := range cloned {
@@ -647,6 +657,7 @@ func projectAcquiredDestinations(composed *composedDocument, scope acquisitionSc
}
contributionPrefix = appendDestination(container, Path{}.Index(start))
contributionInsideItem = true
projectSourceVersionEvidence(contribution.processing, contributionPrefix)
} else if contributionInsideItem {
exactDestination := appendDestination(contributionPrefix, contribution.destination)
contribution.source.Destination = exactDestination.clone()
@@ -656,6 +667,7 @@ func projectAcquiredDestinations(composed *composedDocument, scope acquisitionSc
sources[position].Destination = exactDestination
sources[position].destinationUnresolved = false
}
projectSourceVersionEvidence(contribution.processing, exactDestination)
}
if contribution.nested != nil && contribution.nested.acquisition != nil {
for _, nestedScope := range contribution.nested.acquisition {
@@ -667,6 +679,12 @@ func projectAcquiredDestinations(composed *composedDocument, scope acquisitionSc
projectScope(composed, scope, Path{}, false, "")
}
func projectSourceVersionEvidence(processing *sourceVersionProcessingResult, destination Path) {
if processing != nil && processing.evidence != nil {
processing.evidence.destination = destination.clone()
}
}
func (s *importTraversal) readImport(name, sourceID string, optional bool) ([]byte, bool, *Diagnostic) {
remaining := s.limits.bytes - s.accounting.bytes
limit := min(int64(maxSourceBytes), remaining)

View File

@@ -154,7 +154,7 @@ func loadWithSecretLookup[T any](source Source, secrets SecretBinding, lookup se
definition: definition, sequencePolicy: policy, overlay: options.Overlay,
overrides: options.Overrides, ordinary: true,
sourceVersionMode: options.SourceVersionMode,
effectiveVersion: effectiveSourceVersionRule(options.Definition.Version, Path{}, options.SourceVersionMode),
effectiveVersion: effectiveSourceVersionRuleForPlacement(options.Definition.Version, options.destination, options.SourceVersionMode, options.placementMode),
}
return executeLoadRequest(request, secrets, lookup)
}

View File

@@ -111,7 +111,7 @@ func assembleBaseInputTrackedForRole[T any](source Source, options Options[T], d
if parsed.source != nil {
sourceID = parsed.source.ID
}
processed, failures := versionState.process(parsed.document, role, destination, sourceID)
processed, failures := versionState.process(parsed.document, role, destination, sourceID, options.placementMode)
if len(failures) != 0 {
return input, failures
}

View File

@@ -49,7 +49,7 @@ func loadGroupedWithSecretLookup[T any](request loadRequest[T], secrets SecretBi
}})
request.main.destination = mainOptions.destination
diagnostics = append(diagnostics, mainDiagnostics...)
request.effectiveVersion = effectiveSourceVersionRule(request.definition.Version, request.main.destination, request.sourceVersionMode)
request.effectiveVersion = effectiveSourceVersionRuleForPlacement(request.definition.Version, request.main.destination, request.sourceVersionMode, request.main.placementMode)
for groupIndex := range request.underlays {
for inputIndex := range request.underlays[groupIndex].inputs {
input := &request.underlays[groupIndex].inputs[inputIndex]
@@ -180,13 +180,6 @@ func acquireUnderlayGroupWithPolicyVersion(group loadGroup, index int, rule *Ver
if acquired.document == nil {
continue
}
if node := fragmentVersionClaim(acquired.document, rule); node != nil {
location := publicLocation(node.Location)
return nil, sources, contexts, []Diagnostic{{
Code: CodeUnderlayVersion, Severity: SeverityError, Path: rule.Path.clone(),
Message: "underlay inputs cannot contribute to the schema-version path", Primary: &location,
}}
}
scopes = append(scopes, cloneAcquisitionScopes(acquired.acquisition)...)
}
var documents []*document.Node
@@ -202,6 +195,17 @@ func acquireUnderlayGroupWithPolicyVersion(group loadGroup, index int, rule *Ver
if len(diagnostics) != 0 {
return nil, sources, contexts, diagnostics
}
if composition != nil && rule != nil {
for sourceIndex, document := range documents {
if node := fragmentVersionClaimProjected(document, rule, sourceIndex, composition.sequenceRanges); node != nil {
location := publicLocation(node.Location)
return nil, sources, contexts, []Diagnostic{{
Code: CodeUnderlayVersion, Severity: SeverityError, Path: rule.Path.clone(),
Message: "underlay inputs cannot contribute to the schema-version path", Primary: &location,
}}
}
}
}
composition.inputBytes = accounting.bytes - initialBytes
composition.inputNodes = accounting.nodes - initialNodes
composition.acquisition = cloneAcquisitionScopes(scopes)

View File

@@ -136,7 +136,7 @@ func loadSelectedOverlayWithAccessAndVersion(access *filesystemAccess, base *ass
}
overlayPayload := overlayRoot
if versionState != nil && versionState.mode != sourceVersionModeDefault {
processed, failures := versionState.process(overlayRoot, sourceVersionContribution, Path{}, sourceID)
processed, failures := versionState.process(overlayRoot, sourceVersionContribution, Path{}, sourceID, placementModeMapping)
if len(failures) != 0 {
return sources, failures
}

View File

@@ -91,11 +91,15 @@ func sourceVersionCheckForEvidence(evidence *sourceVersionEvidence, sensitivity
}
}
func (state *sourceVersionState) process(root *document.Node, role sourceVersionRole, destination Path, sourceID string) (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 {
return sourceVersionProcessingResult{}, nil
}
result := processSourceVersion(root, state.localRule, role, destination, state.mode)
projectedDestination := destination.clone()
if placement == placementModeSequenceItem {
projectedDestination = projectedDestination.Index(0)
}
result := processSourceVersion(root, state.localRule, role, projectedDestination, state.mode)
if result.evidence != nil {
result.evidence.sourceID = sourceID
state.evidence = append(state.evidence, result.evidence)
@@ -119,6 +123,10 @@ type sourceVersionProcessingResult struct {
}
func effectiveSourceVersionRule(local *VersionRule, destination Path, mode sourceVersionMode) *VersionRule {
return effectiveSourceVersionRuleForPlacement(local, destination, mode, placementModeMapping)
}
func effectiveSourceVersionRuleForPlacement(local *VersionRule, destination Path, mode sourceVersionMode, placement placementMode) *VersionRule {
if local == nil {
return nil
}
@@ -126,9 +134,13 @@ func effectiveSourceVersionRule(local *VersionRule, destination Path, mode sourc
return local
}
effective := *local
prefix := destination.clone()
if placement == placementModeSequenceItem {
prefix = prefix.Index(0)
}
effective.Path = Path{
segments: append(clonePathSegments(destination.segments, len(local.Path.segments)), local.Path.segments...),
invalid: destination.invalid || local.Path.invalid,
segments: append(clonePathSegments(prefix.segments, len(local.Path.segments)), local.Path.segments...),
invalid: prefix.invalid || local.Path.invalid,
}
return &effective
}

View File

@@ -37,6 +37,40 @@ func TestSourceVersionAbsentPathKeepsUnresolvedSegmentIntent(t *testing.T) {
}
}
func TestFragmentVersionClaimUsesAssignedSequenceProjection(t *testing.T) {
t.Parallel()
fragment := sourceVersionTestDocument(t, "items: [{version: v1}]\n")
rangeForPeer := []sequenceRange{{sourceIndex: 1, path: Keys("items"), start: 1, end: 2}}
if node := fragmentVersionClaimProjected(fragment, &VersionRule{Path: Keys("items").Index(0).Key("version")}, 1, rangeForPeer); node != nil {
t.Fatalf("peer append claimed main index 0: %#v", node)
}
if node := fragmentVersionClaimProjected(fragment, &VersionRule{Path: Keys("items").Index(1).Key("version")}, 1, rangeForPeer); node == nil || node.Text != "v1" {
t.Fatalf("assigned peer index 1 claim = %#v, want its version node", node)
}
nested := sourceVersionTestDocument(t, "items: [{nested: [{version: nested-v}]}]\n")
nestedRanges := []sequenceRange{
{sourceIndex: 1, path: Keys("items"), start: 1, end: 2},
{sourceIndex: 1, path: Keys("items").Index(0).Key("nested"), start: 2, end: 3},
}
if node := fragmentVersionClaimProjected(nested, &VersionRule{Path: Keys("items").Index(1).Key("nested").Index(2).Key("version")}, 1, nestedRanges); node == nil || node.Text != "nested-v" {
t.Fatalf("nested assigned claim = %#v, want nested version", node)
}
ancestor := sourceVersionTestDocument(t, "items: {version: replacement}\n")
if node := fragmentVersionClaim(ancestor, &VersionRule{Path: Keys("items").Index(0).Key("version")}); node == nil || node.Kind != document.Mapping {
t.Fatalf("destructive sequence ancestor claim = %#v, want mapping node", node)
}
effective := effectiveSourceVersionRuleForPlacement(&VersionRule{Path: Keys("version")}, Keys("catalog", "entries"), sourceVersionModeRequired, placementModeSequenceItem)
if got, want := effective.Path.String(), "/catalog/entries/0/version"; got != want {
t.Fatalf("item effective version path = %q, want %q", got, want)
}
defaultRule := effectiveSourceVersionRuleForPlacement(&VersionRule{Path: Keys("version")}, Keys("catalog", "entries"), sourceVersionModeDefault, placementModeSequenceItem)
if got, want := defaultRule.Path.String(), "/version"; got != want {
t.Fatalf("default effective version path = %q, want %q", got, want)
}
}
type sourceVersionMountedService struct {
Version string `yaml:"version"`
}
@@ -473,6 +507,83 @@ func TestSourceVersionLoadChecksMountedMainLocallyAndProtectsEffectivePath(t *te
}
}
func TestSourceVersionItemPlacementKeepsLocalChecksAndProtectsProjectedPath(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
load func(Definition[map[string]any]) error
}{
{
name: "ordinary",
load: func(definition Definition[map[string]any]) error {
_, err := Load(Mapping("main", map[string]any{"version": "v1", "name": "ordinary"}), testSecretBinding(), Options[map[string]any]{
Definition: definition, SourceVersionMode: sourceVersionModeRequired,
destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem,
Overrides: []Override{{Path: Keys("catalog", "entries"), Value: []any{}, Flag: "--entries"}},
})
return err
},
},
{
name: "layered",
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]{
Definition: definition, SourceVersionMode: sourceVersionModeRequired,
Overrides: []Override{{Path: Keys("catalog", "entries"), Value: []any{}, Flag: "--entries"}},
})
return err
},
},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
checks := 0
definition := Definition[map[string]any]{Version: &VersionRule{
Path: Keys("version"),
Check: func(version Version, _ *Validation) error {
checks++
if !version.Present || version.Text != "v1" {
t.Errorf("source-local version = %#v, want v1", version)
}
return nil
},
}}
err := test.load(definition)
var loadErr *LoadError
if !errors.As(err, &loadErr) {
t.Fatalf("item placement override error = %T %v, want *LoadError", err, err)
}
requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeOverrideVersion, "/catalog/entries")
if checks != 1 {
t.Fatalf("source-version checker called %d times, want once", checks)
}
})
}
}
func TestSourceVersionItemEvidenceUsesProjectedSensitivityPath(t *testing.T) {
t.Parallel()
const canary = "PROJECTED-VERSION-EVIDENCE-CANARY"
secretPath := Keys("catalog", "entries").Index(0).Key("version")
loaded, err := Load(Mapping("main", map[string]any{"version": canary}), testSecretBinding(), Options[map[string]any]{
Definition: Definition[map[string]any]{
Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }},
Secret: func(path Path) bool { return path.String() == secretPath.String() },
},
SourceVersionMode: sourceVersionModeRequired,
destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem,
})
if err != nil {
t.Fatalf("item source-version load: %v", err)
}
sources := loaded.Inspection.Sources()
if len(sources) != 1 || sources[0].VersionCheck == nil || !sources[0].VersionCheck.Redacted || sources[0].VersionCheck.Version.Text != "" {
t.Fatalf("projected source-version evidence was not redacted: %#v", sources)
}
}
func TestSourceVersionLoadChecksNullAndNoSourceMain(t *testing.T) {
t.Parallel()