Integrate policy-aware item scopes

This commit is contained in:
2026-10-23 18:55:03 +00:00
parent d03e56398b
commit dfeaf90320
7 changed files with 353 additions and 30 deletions

View File

@@ -59,6 +59,26 @@ func (policy *collectionPolicy) setRecordPolicy(record collectionRecordPolicy) {
policy.recordPolicies[record.owner] = cloneCollectionPolicyScope(record.scope)
}
func (policy *collectionPolicy) setRecordPolicyAt(index int, record collectionRecordPolicy) {
if index < 0 {
return
}
if record.owner == nil {
record.owner = newCollectionRecordPolicy().owner
}
if policy.recordPolicies == nil {
policy.recordPolicies = make(map[*collectionRecordOwner]collectionPolicyScope)
}
for len(policy.recordOrder) <= index {
owner := newCollectionRecordPolicy().owner
policy.recordOrder = append(policy.recordOrder, owner)
policy.recordPolicies[owner] = collectionPolicyScope{}
}
delete(policy.recordPolicies, policy.recordOrder[index])
policy.recordOrder[index] = record.owner
policy.recordPolicies[record.owner] = cloneCollectionPolicyScope(record.scope)
}
func (policy collectionPolicy) recordPolicyAt(index int) (collectionRecordPolicy, bool) {
if index < 0 || index >= len(policy.recordOrder) {
return collectionRecordPolicy{}, false

View File

@@ -316,13 +316,14 @@ func resolveStrictCollectionPolicies(
}
identityFieldsByPath := make(map[string][]string)
collectionPolicies := cloneCollectionPolicyScope(lower)
effectiveLower := cloneCollectionPolicyScope(lower)
collectionPolicies := cloneCollectionPolicyScope(effectiveLower)
var diagnostics []Diagnostic
for _, path := range paths {
key := path.String()
currentDeclarations := declarationsByPath[key]
lowerEntry, hasLower := lower.collections[key]
lowerEntry, hasLower := effectiveLower.collections[key]
var lowerPolicy *collectionPolicy
if hasLower {
copy := cloneCollectionPolicy(lowerEntry.policy)
@@ -346,6 +347,10 @@ func resolveStrictCollectionPolicies(
lowerPolicy.identityFields = nil
lowerPolicy.identityPresent = false
lowerPolicy.identityLocation = nil
lowerPolicy.recordOrder = nil
lowerPolicy.recordPolicies = nil
effectiveLower.retireCollectionPoliciesAtOrBelow(path)
collectionPolicies.retireCollectionPoliciesAtOrBelow(path)
}
resolution, found := resolveCollectionPolicy(path, currentDeclarations, applicationIdentity, lowerPolicy)

View File

@@ -201,10 +201,12 @@ func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystem
state.stack = append(state.stack, mainRoute)
var body *document.Node
var declarations []importDeclarationSpec
var sequenceDeclarations []sequenceDeclarationSpec
var diagnostics []Diagnostic
if processing != nil {
body = processing.payload
declarations = processing.imports
sequenceDeclarations = cloneSequenceDeclarations(processing.sequences)
} else {
body, declarations, diagnostics = splitImportDirectives(main.document)
}
@@ -213,6 +215,7 @@ func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystem
state.scope.contributions = append(state.scope.contributions, acquiredContribution{
source: cloneSourceInfo(mainInfo), identity: mainNodeKey, role: importRoleMain,
scopeOwner: mainNodeKey, payload: body, document: body, processing: processing,
sequenceDeclarations: cloneSequenceDeclarations(sequenceDeclarations),
})
diagnostics = state.walkDeclarations(basePath, declarations, 0, Path{})
} else {
@@ -255,7 +258,7 @@ func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystem
contributions: []acquiredContribution{{
source: cloneSourceInfo(mainInfo), payload: body, identity: mainNodeKey,
role: importRoleMain, scopeOwner: mainNodeKey, destination: destination.clone(),
processing: processing, pendingItem: &localScope,
processing: processing, sequenceDeclarations: cloneSequenceDeclarations(sequenceDeclarations), pendingItem: &localScope,
}},
}
}
@@ -313,14 +316,17 @@ func composeAcquiredInputWithPolicies(input assembledInput, lower collectionPoli
if len(scope.contributions) == 1 && scope.contributions[0].role == importRoleMain && scope.contributions[0].pendingItem != nil {
main := scope.contributions[0]
localScope := cloneAcquisitionScope(*main.pendingItem)
if diagnostics := traversal.composePendingItems(&localScope); len(diagnostics) != 0 {
if diagnostics := traversal.composePendingItems(&localScope, policy); len(diagnostics) != 0 {
return nil, prependCompositionDiagnosticPaths(diagnostics, main.destination.Index(0))
}
documents := make([]*document.Node, 0, len(localScope.contributions))
for _, contribution := range localScope.contributions {
documents = append(documents, contribution.document)
}
completed, diagnostics := composeDocuments(documents...)
completed, diagnostics := composeDocumentsWithCollectionPolicy(
policy, localScope.collectionPolicies,
acquiredSequenceDeclarations(localScope), documents...,
)
if len(diagnostics) != 0 {
return nil, prependCompositionDiagnosticPaths(diagnostics, main.destination.Index(0))
}
@@ -392,19 +398,20 @@ func composeAcquiredScopeWithPolicy(
versionState *sourceVersionState,
accounting *importAccounting,
) (*composedDocument, []Diagnostic) {
_ = lower
traversal := importTraversal{
accounting: accounting, limits: defaultImportLimits(), rule: rule,
versionState: versionState, importGraph: graph.clone(),
}
if diagnostics := traversal.composePendingItems(scope); len(diagnostics) != 0 {
if diagnostics := traversal.composePendingItems(scope, policy); len(diagnostics) != 0 {
return nil, diagnostics
}
documents := make([]*document.Node, 0, len(scope.contributions))
for _, contribution := range scope.contributions {
documents = append(documents, contribution.document)
}
composition, diagnostics := composeDocumentsWithPolicy(policy, documents...)
composition, diagnostics := composeDocumentsWithCollectionPolicy(
policy, lower, acquiredSequenceDeclarations(*scope), documents...,
)
if len(diagnostics) != 0 {
return nil, diagnostics
}
@@ -617,6 +624,7 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
var body *document.Node
var declarations []importDeclarationSpec
var sequenceDeclarations []sequenceDeclarationSpec
var diagnostics []Diagnostic
var processing *sourceVersionProcessingResult
if s.versionState != nil && s.versionState.mode != sourceVersionModeDefault {
@@ -652,7 +660,7 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
s.active[identity] = len(s.stack)
s.visited[identity] = route
s.stack = append(s.stack, route)
diagnostics := s.visitItemSource(canonical, nodeKey, destination, relativeDestination, depth, info, body, declarations, processing)
diagnostics := s.visitItemSource(canonical, nodeKey, destination, relativeDestination, depth, info, body, declarations, sequenceDeclarations, processing)
delete(s.active, identity)
s.stack = s.stack[:len(s.stack)-1]
return diagnostics
@@ -671,6 +679,7 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
source: cloneSourceInfo(info), payload: body, document: placed,
identity: nodeKey, role: role, scopeOwner: s.scope.owner,
destination: placementDestination.clone(), relativeDestination: relativeDestination.clone(), processing: processing,
sequenceDeclarations: cloneSequenceDeclarations(sequenceDeclarations),
})
s.active[identity] = len(s.stack)
s.visited[identity] = route
@@ -701,7 +710,7 @@ func (s *importTraversal) sourceDestination(destination Path) Path {
return s.itemFallback.clone()
}
func (s *importTraversal) visitItemSource(canonical string, identity importNodeKey, destination, relativeDestination Path, depth int, info SourceInfo, body *document.Node, declarations []importDeclarationSpec, processing *sourceVersionProcessingResult) []Diagnostic {
func (s *importTraversal) visitItemSource(canonical string, identity importNodeKey, destination, relativeDestination Path, depth int, info SourceInfo, body *document.Node, declarations []importDeclarationSpec, sequenceDeclarations []sequenceDeclarationSpec, processing *sourceVersionProcessingResult) []Diagnostic {
parentScope := s.scope
parentItemScope, parentPrefix, parentFallback := s.itemScope, s.itemPrefix, s.itemFallback
s.scope = acquisitionScope{owner: identity}
@@ -715,7 +724,7 @@ func (s *importTraversal) visitItemSource(canonical string, identity importNodeK
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
source: cloneSourceInfo(info), payload: body, document: body,
identity: identity, role: importRoleItem, scopeOwner: identity,
processing: processing,
processing: processing, sequenceDeclarations: cloneSequenceDeclarations(sequenceDeclarations),
})
diagnostics := s.walkDeclarations(s.access.dir(canonical), declarations, depth, Path{})
itemScope := s.scope
@@ -742,21 +751,24 @@ func (s *importTraversal) scheduleAcquisitionScopes(scope *acquisitionScope) {
}
}
func (s *importTraversal) composePendingItems(scope *acquisitionScope) []Diagnostic {
func (s *importTraversal) composePendingItems(scope *acquisitionScope, policy sequencePolicy) []Diagnostic {
for index := range scope.contributions {
contribution := &scope.contributions[index]
if contribution.pendingItem == nil {
continue
}
itemScope := contribution.pendingItem
if diagnostics := s.composePendingItems(itemScope); len(diagnostics) != 0 {
if diagnostics := s.composePendingItems(itemScope, policy); len(diagnostics) != 0 {
return projectItemDiagnosticPaths(diagnostics, *scope, contribution.destination, index)
}
documents := make([]*document.Node, 0, len(itemScope.contributions))
for _, nested := range itemScope.contributions {
documents = append(documents, nested.document)
}
completed, diagnostics := composeDocuments(documents...)
completed, diagnostics := composeDocumentsWithCollectionPolicy(
policy, itemScope.collectionPolicies,
acquiredSequenceDeclarations(*itemScope), documents...,
)
if len(diagnostics) != 0 {
return projectItemDiagnosticPaths(diagnostics, *scope, contribution.destination, index)
}
@@ -783,6 +795,18 @@ func (s *importTraversal) composePendingItems(scope *acquisitionScope) []Diagnos
return nil
}
func acquiredSequenceDeclarations(scope acquisitionScope) []sequenceDeclarationSpec {
var declarations []sequenceDeclarationSpec
for _, contribution := range scope.contributions {
for _, declaration := range contribution.sequenceDeclarations {
projected := cloneSequenceDeclarations([]sequenceDeclarationSpec{declaration})[0]
projected.destination = appendDestination(contribution.relativeDestination, declaration.destination)
declarations = append(declarations, projected)
}
}
return declarations
}
func importedVersionClaim(documents []*document.Node, scope acquisitionScope, composed *composedDocument, rule *VersionRule) *Diagnostic {
if composed == nil || rule == nil {
return nil
@@ -915,6 +939,37 @@ func projectNestedEvidence(composed *composedDocument, scope acquisitionScope) {
projectNestedOrigins(composed.origins, contribution.nested.root, contribution.nested.origins, prefix)
}
composed.applications = projectedApplications
projectNestedCollectionPolicies(composed, scope, ranges)
}
func projectNestedCollectionPolicies(composed *composedDocument, scope acquisitionScope, ranges map[sequenceRangeKey]sequenceRange) {
if composed == nil {
return
}
baseOffsets := make(map[string]int)
for sourceIndex, contribution := range scope.contributions {
if contribution.nested == nil || len(contribution.nested.collectionPolicies.collections) == 0 {
continue
}
itemIndex := priorSequenceLength([]acquisitionScope{{contributions: scope.contributions[:sourceIndex]}}, contribution.destination)
if assigned, found := ranges[sequenceRangeKey{sourceIndex: sourceIndex, path: contribution.destination.String()}]; found {
if projected, ok := assigned.outputForInput(0); ok {
itemIndex = projected
}
}
key := contribution.destination.String()
base, initialized := baseOffsets[key]
policy, _ := composed.collectionPolicies.at(contribution.destination)
if !initialized {
base = len(policy.recordOrder)
baseOffsets[key] = base
}
policy.setRecordPolicyAt(base+itemIndex, collectionRecordPolicy{
owner: newCollectionRecordPolicy().owner,
scope: contribution.nested.collectionPolicies,
})
composed.collectionPolicies.set(contribution.destination, policy)
}
}
func projectNestedOrigins(target map[string]Origin, root *document.Node, origins map[string]Origin, prefix Path) {

View File

@@ -0,0 +1,229 @@
package engine
import (
"fmt"
"strings"
"testing"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
yamladapter "gitea.maximumdirect.net/eric/ConfigKit/internal/yaml"
)
func parseItemPolicyDocument(t *testing.T, sourceID, input string) *document.Node {
t.Helper()
root, issues := yamladapter.Parse(strings.NewReader(input), sourceID, yamladapter.Limits{
Depth: maxDocumentDepth, Nodes: maxDocumentNodes,
})
if len(issues) != 0 {
t.Fatalf("parse document: %#v", issues)
}
return root
}
func TestPendingItemCompositionKeepsLocalDeclarationsAndHelperIdentity(t *testing.T) {
t.Parallel()
declaration := sequenceDeclarationSpec{
destination: Keys("prompts"), identityFields: []string{"id"}, identityPresent: true,
}
itemScope := acquisitionScope{contributions: []acquiredContribution{
{
document: parseItemPolicyDocument(t, "item", "name: authored\nprompts:\n - id: authored\n"),
sequenceDeclarations: []sequenceDeclarationSpec{declaration},
},
{
document: parseItemPolicyDocument(t, "helper", "id: supplied\nprompts:\n - id: helper\n"),
},
}}
outer := acquisitionScope{contributions: []acquiredContribution{{
source: SourceInfo{ID: "item"},
payload: parseItemPolicyDocument(t, "outer", "{}\n"),
destination: Keys("items"), pendingItem: &itemScope,
}}}
composed, diagnostics := composeAcquiredScopeWithPolicy(
&outer, collectionPolicyScope{}, sequencePolicy{}, newImportInclusionGraph(), nil, nil, nil, &importAccounting{},
)
if len(diagnostics) != 0 {
t.Fatalf("compose pending item scope: %#v", diagnostics)
}
item := outer.contributions[0].payload
if got, ok := documentAtPath(item, Keys("id")); !ok || got == nil || got.Text != "supplied" {
t.Fatalf("helper-supplied record identity = %#v", got)
}
if got, ok := documentAtPath(item, Keys("prompts")); !ok || got == nil || got.Kind != document.Sequence || len(got.Sequence) != 2 {
t.Fatalf("composed prompts = %#v, want two identified entries", got)
}
itemPolicy, ok := composed.collectionPolicies.at(Keys("items"))
if !ok {
t.Fatalf("projected item collection policy missing")
}
owned, ok := itemPolicy.recordPolicyAt(0)
if !ok {
t.Fatalf("projected item owner missing: %#v", itemPolicy)
}
if policy, ok := owned.scope.at(Keys("prompts")); !ok || !policy.identityPresent || len(policy.identityFields) != 1 || policy.identityFields[0] != "id" {
t.Fatalf("item-owned prompt policy = %#v, %v", policy, ok)
}
if _, ok := composed.collectionPolicies.at(Keys("prompts")); ok {
t.Fatal("item-local declaration escaped its owning record")
}
}
func TestPendingItemCompositionProjectsNestedIdentityDiagnostics(t *testing.T) {
t.Parallel()
childScope := acquisitionScope{contributions: []acquiredContribution{
{document: parseItemPolicyDocument(t, "child", "prompts:\n - id: same\n"), sequenceDeclarations: []sequenceDeclarationSpec{{
destination: Keys("prompts"), identityFields: []string{"id"}, identityPresent: true,
}}},
{document: parseItemPolicyDocument(t, "child-helper", "prompts:\n - id: same\n")},
}}
itemScope := acquisitionScope{contributions: []acquiredContribution{{
document: parseItemPolicyDocument(t, "item", "name: parent\n"),
}, {
source: SourceInfo{ID: "child"}, payload: parseItemPolicyDocument(t, "child", "{}\n"),
destination: Keys("children"), pendingItem: &childScope,
}}}
outer := acquisitionScope{contributions: []acquiredContribution{{
source: SourceInfo{ID: "item"}, payload: parseItemPolicyDocument(t, "outer", "{}\n"),
destination: Keys("items"), pendingItem: &itemScope,
}}}
traversal := importTraversal{accounting: &importAccounting{}, limits: defaultImportLimits()}
diagnostics := traversal.composePendingItems(&outer, sequencePolicy{})
if len(diagnostics) != 1 {
t.Fatalf("diagnostics = %#v, want one conflict", diagnostics)
}
if diagnostics[0].Code != CodeComposeConflict || diagnostics[0].Path.String() != "/items/0/children/0/prompts" {
t.Fatalf("diagnostic = %#v, want projected collection path", diagnostics[0])
}
if strings.Contains(fmt.Sprint(diagnostics[0]), "same") {
t.Fatalf("diagnostic disclosed identity value: %v", diagnostics[0])
}
}
func TestPendingItemCompositionAppliesApplicationIdentityConstraints(t *testing.T) {
t.Parallel()
itemScope := acquisitionScope{contributions: []acquiredContribution{{
document: parseItemPolicyDocument(t, "item", "prompts:\n - text: missing-id\n"),
}}}
outer := acquisitionScope{contributions: []acquiredContribution{{
source: SourceInfo{ID: "item"}, payload: parseItemPolicyDocument(t, "outer", "{}\n"),
destination: Keys("items"), pendingItem: &itemScope,
}}}
policy, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{{
selector: sequenceIdentitySelector{literalSequenceSelector("prompts")}, identityFields: []string{"id"},
}})
if len(diagnostics) != 0 {
t.Fatalf("compile application identity: %#v", diagnostics)
}
traversal := importTraversal{accounting: &importAccounting{}, limits: defaultImportLimits()}
diagnostics = traversal.composePendingItems(&outer, policy)
if len(diagnostics) != 1 || diagnostics[0].Code != CodeComposeInvalid || diagnostics[0].Path.String() != "/items/0/prompts/0/id" {
t.Fatalf("application identity diagnostics = %#v", diagnostics)
}
}
func TestReplacingOuterCollectionRetiresDiscardedItemIdentity(t *testing.T) {
t.Parallel()
outerPath := Keys("items")
nestedPath := Keys("items").Index(0).Key("prompts")
lower := collectionPolicyScope{}
outerPolicy := collectionPolicy{}
outerPolicy.setRecordPolicy(newCollectionRecordPolicy())
lower.set(outerPath, outerPolicy)
lower.set(nestedPath, collectionPolicy{identityFields: []string{"id"}, identityPresent: true})
declarations := []sequenceDeclarationSpec{{
destination: outerPath, mode: sequenceModeReplace, modePresent: true,
}}
input := parseItemPolicyDocument(t, "replacement", "items:\n - prompts:\n - text: new\n")
composed, diagnostics := composeDocumentsWithCollectionPolicy(sequencePolicy{}, lower, declarations, input)
if len(diagnostics) != 0 {
t.Fatalf("compose replacement: %#v", diagnostics)
}
policy, ok := composed.collectionPolicies.at(nestedPath)
if !ok || policy.identityPresent || len(policy.recordOrder) != 0 {
t.Fatalf("discarded nested owner policy = %#v, %v", policy, ok)
}
}
func TestNestedItemPolicyProjectionUsesStableOutputIndexes(t *testing.T) {
t.Parallel()
itemsPath := Keys("items")
outerPolicy := collectionPolicy{}
retainedScope := collectionPolicyScope{}
retainedScope.set(Keys("retained"), collectionPolicy{identityFields: []string{"id"}, identityPresent: true})
outerPolicy.setRecordPolicy(collectionRecordPolicy{owner: newCollectionRecordPolicy().owner, scope: retainedScope})
composed := &composedDocument{collectionPolicies: collectionPolicyScope{}}
composed.collectionPolicies.set(itemsPath, outerPolicy)
firstScope := collectionPolicyScope{}
firstScope.set(Keys("prompts"), collectionPolicy{identityFields: []string{"id"}, identityPresent: true})
secondScope := collectionPolicyScope{}
secondScope.set(Keys("prompts"), collectionPolicy{identityFields: []string{"slug"}, identityPresent: true})
scope := acquisitionScope{contributions: []acquiredContribution{
{document: parseItemPolicyDocument(t, "first", "items:\n - id: first\n"), destination: itemsPath, nested: &composedDocument{collectionPolicies: firstScope}},
{document: parseItemPolicyDocument(t, "second", "items:\n - id: second\n"), destination: itemsPath, nested: &composedDocument{collectionPolicies: secondScope}},
}}
projectNestedCollectionPolicies(composed, scope, nil)
policy, ok := composed.collectionPolicies.at(itemsPath)
if !ok || len(policy.recordOrder) != 3 {
t.Fatalf("record-owned policies = %#v, %v; want retained plus two item scopes", policy, ok)
}
for index, wantField := range []string{"id", "id", "slug"} {
record, ok := policy.recordPolicyAt(index)
if !ok {
t.Fatalf("record policy %d missing", index)
}
path := Keys("retained")
if index != 0 {
path = Keys("prompts")
}
nested, ok := record.scope.at(path)
if !ok || !nested.identityPresent || len(nested.identityFields) != 1 || nested.identityFields[0] != wantField {
t.Errorf("record policy %d = %#v, %v; want %q", index, nested, ok, wantField)
}
}
}
func TestItemSourceVersionProcessingConsumesDeclarationsBeforeComposition(t *testing.T) {
t.Parallel()
root := parseItemPolicyDocument(t, "item", `version: v1
configkit:
sequences:
- destination: [prompts]
key: id
prompts:
- id: example
`)
rule := &VersionRule{Path: Keys("version"), Check: func(version Version, _ *Validation) error {
if !version.Present || version.Text != "v1" {
t.Errorf("version = %#v, want v1", version)
}
return nil
}}
processed := processSourceVersionWithDirectiveContext(
root, rule, sourceVersionContribution, Keys("items").Index(0), sourceVersionModeRequired, rootDirectiveContextSource,
)
if len(processed.diagnostics) != 0 {
t.Fatalf("process item source version: %#v", processed.diagnostics)
}
if len(processed.sequences) != 1 || !processed.sequences[0].identityPresent {
t.Fatalf("extracted item declarations = %#v", processed.sequences)
}
if _, ok := documentAtPath(processed.payload, Keys("version")); ok {
t.Fatalf("consumed version header remains in item payload: %#v", processed.payload)
}
if _, ok := documentAtPath(processed.payload, Keys("configkit")); ok {
t.Fatalf("consumed controls remain in item payload: %#v", processed.payload)
}
}

View File

@@ -23,18 +23,19 @@ type assembledInput struct {
// The latter is used for composition; the former preserves the acquisition
// boundary for later scoped assembly and evidence.
type acquiredContribution struct {
source SourceInfo
identity importNodeKey
role importContributionRole
scopeOwner importNodeKey
payload *document.Node
document *document.Node
destination Path
relativeDestination Path
processing *sourceVersionProcessingResult
nested *composedDocument
pendingItem *acquisitionScope
placementCharged bool
source SourceInfo
identity importNodeKey
role importContributionRole
scopeOwner importNodeKey
payload *document.Node
document *document.Node
destination Path
relativeDestination Path
processing *sourceVersionProcessingResult
sequenceDeclarations []sequenceDeclarationSpec
nested *composedDocument
pendingItem *acquisitionScope
placementCharged bool
}
// acquisitionScope is the ordered strict mapping scope produced by one input
@@ -59,7 +60,8 @@ func cloneAcquisitionScope(scope acquisitionScope) acquisitionScope {
role: contribution.role, scopeOwner: contribution.scopeOwner, payload: contribution.payload,
document: contribution.document, destination: contribution.destination.clone(),
relativeDestination: contribution.relativeDestination.clone(), processing: contribution.processing,
nested: cloneComposedDocument(contribution.nested), pendingItem: cloneAcquisitionScopePointer(contribution.pendingItem),
sequenceDeclarations: cloneSequenceDeclarations(contribution.sequenceDeclarations),
nested: cloneComposedDocument(contribution.nested), pendingItem: cloneAcquisitionScopePointer(contribution.pendingItem),
placementCharged: contribution.placementCharged,
}
}

View File

@@ -141,6 +141,7 @@ func (state *sourceVersionState) process(root *document.Node, role sourceVersion
type sourceVersionProcessingResult struct {
payload *document.Node
imports []importDeclarationSpec
sequences []sequenceDeclarationSpec
evidence *sourceVersionEvidence
diagnostics []Diagnostic
}
@@ -188,6 +189,10 @@ type sourceVersionPath struct {
}
func processSourceVersion(root *document.Node, rule *VersionRule, role sourceVersionRole, destination Path, mode sourceVersionMode) sourceVersionProcessingResult {
return processSourceVersionWithDirectiveContext(root, rule, role, destination, mode, rootDirectiveContextImportsOnly)
}
func processSourceVersionWithDirectiveContext(root *document.Node, rule *VersionRule, role sourceVersionRole, destination Path, mode sourceVersionMode, directiveContext rootDirectiveContext) sourceVersionProcessingResult {
if role != sourceVersionMain && role != sourceVersionContribution {
return sourceVersionProcessingResult{diagnostics: []Diagnostic{sourceVersionOptionsDiagnostic("source role is invalid")}}
}
@@ -204,11 +209,14 @@ func processSourceVersion(root *document.Node, rule *VersionRule, role sourceVer
return sourceVersionProcessingResult{}
}
payload, imports, diagnostics := splitImportDirectives(root)
extracted, diagnostics := extractRootDirectives(root, directiveContext)
if len(diagnostics) != 0 {
return sourceVersionProcessingResult{diagnostics: diagnostics}
}
result := sourceVersionProcessingResult{payload: payload, imports: imports}
result := sourceVersionProcessingResult{
payload: extracted.body, imports: extracted.imports,
sequences: cloneSequenceDeclarations(extracted.sequences),
}
if mode == sourceVersionModeDefault {
return result
}