2026-10-15 15:15:05 +00:00
package engine
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
type sourceVersionMode uint8
const (
sourceVersionModeDefault sourceVersionMode = iota
sourceVersionModeIfPresent
sourceVersionModeRequired
)
2026-10-15 15:48:59 +00:00
// SourceVersionModeFromFacade converts the root-owned public mode while
// keeping the engine's policy type private. Unknown values remain intact for
// pre-acquisition validation.
func SourceVersionModeFromFacade ( value uint8 ) sourceVersionMode {
return sourceVersionMode ( value )
}
2026-10-15 15:15:05 +00:00
type sourceVersionRole uint8
const (
sourceVersionMain sourceVersionRole = iota
sourceVersionContribution
)
type sourceVersionAncestor struct {
path Path
kind document . Kind
}
type sourceVersionEvidence struct {
2026-10-17 04:42:23 +00:00
sourceID string
path Path
resolvedPath Path
destination Path
projectedPaths [ ] Path
version Version
location * Location
ancestors [ ] sourceVersionAncestor
absence sourceVersionPathAbsence
checked bool
redacted bool
2026-10-15 15:15:05 +00:00
}
2026-10-15 15:33:40 +00:00
type sourceVersionState struct {
localRule * VersionRule
effectiveRule * VersionRule
mode sourceVersionMode
mainProcessing * sourceVersionProcessingResult
evidence [ ] * sourceVersionEvidence
diagnostics [ ] Diagnostic
}
2026-10-15 16:12:21 +00:00
func sourceInfosWithVersionEvidence ( sources [ ] SourceInfo , evidence [ ] * sourceVersionEvidence , secrets map [ string ] bool , conservative bool ) [ ] SourceInfo {
result := cloneSourceInfos ( sources )
if len ( evidence ) == 0 {
return result
}
sensitivity := newDiffSensitivity ( secrets , nil )
for _ , item := range evidence {
if item == nil || item . sourceID == "" {
continue
}
for index := range result {
if result [ index ] . ID == item . sourceID {
result [ index ] . VersionCheck = sourceVersionCheckForEvidence ( item , sensitivity , conservative )
break
}
}
}
return result
}
func sourceVersionCheckForEvidence ( evidence * sourceVersionEvidence , sensitivity * diffSensitivity , conservative bool ) * SourceVersionCheck {
path := evidence . resolvedPath
if ! path . valid ( ) {
path = evidence . path
}
version := evidence . version
redacted := evidence . redacted
if conservative {
redacted = redacted || version . Text != ""
2026-10-17 04:42:23 +00:00
} else if sensitivity . isSensitiveAtOrAbove ( evidence . sensitivityPaths ( ) [ 0 ] . String ( ) ) {
2026-10-15 16:12:21 +00:00
redacted = true
}
if redacted {
version . Text = ""
}
return & SourceVersionCheck {
Path : path . clone ( ) , Version : version , Location : cloneLocation ( evidence . location ) , Redacted : redacted ,
}
}
2026-10-17 04:42:23 +00:00
// sensitivityPaths preserves source-local evidence while describing its assigned
// application coordinates. Before assembly, the original mount is the fallback.
func ( evidence * sourceVersionEvidence ) sensitivityPaths ( ) [ ] Path {
if evidence . projectedPaths != nil {
return evidence . projectedPaths
}
local := evidence . resolvedPath
if ! local . valid ( ) {
local = evidence . path
}
paths := make ( [ ] Path , 0 , len ( evidence . ancestors ) + 1 )
paths = append ( paths , appendDestination ( evidence . destination , local ) )
for _ , ancestor := range evidence . ancestors {
paths = append ( paths , appendDestination ( evidence . destination , ancestor . path ) )
}
return paths
}
2026-10-16 19:11:08 +00:00
func ( state * sourceVersionState ) process ( root * document . Node , role sourceVersionRole , destination Path , sourceID string , placement placementMode ) ( sourceVersionProcessingResult , [ ] Diagnostic ) {
2026-10-15 15:33:40 +00:00
if state == nil || state . mode == sourceVersionModeDefault {
return sourceVersionProcessingResult { } , nil
}
2026-10-16 19:11:08 +00:00
projectedDestination := destination . clone ( )
if placement == placementModeSequenceItem {
projectedDestination = projectedDestination . Index ( 0 )
}
result := processSourceVersion ( root , state . localRule , role , projectedDestination , state . mode )
2026-10-15 15:33:40 +00:00
if result . evidence != nil {
result . evidence . sourceID = sourceID
state . evidence = append ( state . evidence , result . evidence )
}
var failures [ ] Diagnostic
for _ , diagnostic := range result . diagnostics {
if diagnostic . Severity == SeverityWarning {
state . diagnostics = append ( state . diagnostics , diagnostic )
} else if diagnostic . Severity == SeverityError {
failures = append ( failures , diagnostic )
}
}
return result , failures
}
2026-10-15 15:15:05 +00:00
type sourceVersionProcessingResult struct {
payload * document . Node
imports [ ] importDeclaration
evidence * sourceVersionEvidence
diagnostics [ ] Diagnostic
}
2026-10-15 15:24:53 +00:00
func effectiveSourceVersionRule ( local * VersionRule , destination Path , mode sourceVersionMode ) * VersionRule {
2026-10-16 19:11:08 +00:00
return effectiveSourceVersionRuleForPlacement ( local , destination , mode , placementModeMapping )
}
func effectiveSourceVersionRuleForPlacement ( local * VersionRule , destination Path , mode sourceVersionMode , placement placementMode ) * VersionRule {
2026-10-15 15:24:53 +00:00
if local == nil {
return nil
}
if mode == sourceVersionModeDefault {
return local
}
effective := * local
2026-10-16 19:11:08 +00:00
prefix := destination . clone ( )
if placement == placementModeSequenceItem {
prefix = prefix . Index ( 0 )
}
2026-10-15 15:24:53 +00:00
effective . Path = Path {
2026-10-16 19:11:08 +00:00
segments : append ( clonePathSegments ( prefix . segments , len ( local . Path . segments ) ) , local . Path . segments ... ) ,
invalid : prefix . invalid || local . Path . invalid ,
2026-10-15 15:24:53 +00:00
}
return & effective
}
2026-10-15 15:15:05 +00:00
type sourceVersionPathAbsence uint8
const (
sourceVersionPathPresent sourceVersionPathAbsence = iota
sourceVersionPathInvalid
sourceVersionMemberMissing
sourceVersionIndexUnavailable
sourceVersionPathObstructed
)
type sourceVersionPath struct {
node * document . Node
present bool
absence sourceVersionPathAbsence
nearest document . Location
resolvedPath Path
ancestors [ ] sourceVersionAncestor
}
func processSourceVersion ( root * document . Node , rule * VersionRule , role sourceVersionRole , destination Path , mode sourceVersionMode ) sourceVersionProcessingResult {
if role != sourceVersionMain && role != sourceVersionContribution {
return sourceVersionProcessingResult { diagnostics : [ ] Diagnostic { sourceVersionOptionsDiagnostic ( "source role is invalid" ) } }
}
if mode != sourceVersionModeDefault && mode != sourceVersionModeIfPresent && mode != sourceVersionModeRequired {
return sourceVersionProcessingResult { diagnostics : [ ] Diagnostic { sourceVersionOptionsDiagnostic ( "source-version mode is invalid" ) } }
}
if mode != sourceVersionModeDefault && ( rule == nil || rule . Check == nil || ! rule . Path . valid ( ) ) {
return sourceVersionProcessingResult { diagnostics : [ ] Diagnostic { sourceVersionOptionsDiagnostic ( "source-version checking requires a valid version rule and checker" ) } }
}
if root == nil {
if role == sourceVersionMain && mode != sourceVersionModeDefault {
return sourceVersionProcessingResult { diagnostics : canonicalDiagnostics ( reportVersion ( rule , Version { } , nil ) ) }
}
return sourceVersionProcessingResult { }
}
payload , imports , diagnostics := splitImportDirectives ( root )
if len ( diagnostics ) != 0 {
return sourceVersionProcessingResult { diagnostics : diagnostics }
}
result := sourceVersionProcessingResult { payload : payload , imports : imports }
if mode == sourceVersionModeDefault {
return result
}
resolved := resolveSourceVersionPath ( root , rule . Path )
evidence := & sourceVersionEvidence {
path : rule . Path . clone ( ) , resolvedPath : resolved . resolvedPath ,
destination : destination . clone ( ) , ancestors : resolved . ancestors ,
absence : resolved . absence ,
}
location := resolved . nearest
if resolved . present {
location = resolved . node . Location
}
if locationKnown ( location ) {
public := publicLocation ( location )
evidence . location = & public
}
result . evidence = evidence
interpreted := interpretVersion ( rule , root )
evidence . version = interpreted . version
2026-10-15 22:22:56 +00:00
if resolved . present && sourceVersionControlOverlap ( root , evidence . resolvedPath ) {
2026-10-15 15:15:05 +00:00
evidence . redactText ( )
result . payload = nil
result . diagnostics = [ ] Diagnostic { sourceVersionOverlapDiagnostic ( rule , evidence ) }
return result
}
if hasErrors ( interpreted . diagnostics ) {
evidence . redactText ( )
result . payload = nil
result . diagnostics = canonicalDiagnostics ( interpreted . diagnostics )
return result
}
if ! interpreted . version . Present {
if mode == sourceVersionModeRequired {
evidence . redactText ( )
result . payload = nil
result . diagnostics = [ ] Diagnostic { sourceVersionMissingDiagnostic ( rule , evidence ) }
return result
}
if role == sourceVersionContribution {
return result
}
}
evidence . checked = true
result . diagnostics = canonicalDiagnostics ( reportVersion ( rule , interpreted . version , root ) )
if hasErrors ( result . diagnostics ) {
evidence . redactText ( )
result . payload = nil
return result
}
if role == sourceVersionContribution && interpreted . version . Present {
if ! removeSourceVersionNode ( result . payload , evidence . resolvedPath ) {
evidence . redactText ( )
result . payload = nil
result . diagnostics = [ ] Diagnostic { sourceVersionOptionsDiagnostic ( "selected source version could not be removed from its owned payload" ) }
return result
}
}
return result
}
func sourceVersionOptionsDiagnostic ( message string ) Diagnostic {
return Diagnostic { Code : CodeOptionsInvalid , Severity : SeverityError , Message : message }
}
func sourceVersionMissingDiagnostic ( rule * VersionRule , evidence * sourceVersionEvidence ) Diagnostic {
return Diagnostic {
Code : CodeVersionError , Severity : SeverityError , Path : rule . Path . clone ( ) ,
Message : "required schema version is missing" , Primary : cloneLocation ( evidence . location ) ,
}
}
func sourceVersionOverlapDiagnostic ( rule * VersionRule , evidence * sourceVersionEvidence ) Diagnostic {
return Diagnostic {
Code : CodeVersionError , Severity : SeverityError , Path : rule . Path . clone ( ) ,
Message : "schema version path overlaps a structural directive" , Primary : cloneLocation ( evidence . location ) ,
}
}
func ( evidence * sourceVersionEvidence ) redactText ( ) {
if evidence == nil {
return
}
evidence . version . Text = ""
evidence . redacted = true
}
func resolveSourceVersionPath ( root * document . Node , path Path ) sourceVersionPath {
2026-10-15 22:20:20 +00:00
// Refine only segments whose container shape is known, keeping the full
// requested suffix when lookup stops before the declaration.
resolved := sourceVersionPath { absence : sourceVersionPathInvalid , resolvedPath : path . clone ( ) }
2026-10-15 15:15:05 +00:00
if root == nil || ! path . valid ( ) {
return resolved
}
current := root
resolved . nearest = current . Location
resolved . ancestors = append ( resolved . ancestors , sourceVersionAncestor { kind : current . Kind } )
2026-10-15 22:20:20 +00:00
for position , segment := range path . segments {
2026-10-15 15:15:05 +00:00
switch current . Kind {
case document . Mapping :
if segment . kind == pathSegmentIndex {
resolved . absence = sourceVersionPathObstructed
return resolved
}
2026-10-15 22:20:20 +00:00
resolved . resolvedPath . segments [ position ] = pathSegment { kind : pathSegmentKey , key : segment . key }
2026-10-15 15:15:05 +00:00
found := false
for _ , entry := range current . Mapping {
if entry . Key == segment . key {
current = entry . Value
found = true
break
}
}
if ! found {
resolved . absence = sourceVersionMemberMissing
return resolved
}
case document . Sequence :
index , ok := schemaIndex ( segment )
if ! ok {
resolved . absence = sourceVersionIndexUnavailable
return resolved
}
2026-10-15 22:20:20 +00:00
resolved . resolvedPath . segments [ position ] = pathSegment { kind : pathSegmentIndex , index : index }
2026-10-15 15:15:05 +00:00
if index >= len ( current . Sequence ) {
resolved . absence = sourceVersionIndexUnavailable
return resolved
}
current = current . Sequence [ index ]
default :
resolved . absence = sourceVersionPathObstructed
return resolved
}
resolved . nearest = current . Location
resolved . ancestors = append ( resolved . ancestors , sourceVersionAncestor {
2026-10-15 22:20:20 +00:00
path : Path { segments : clonePathSegments ( resolved . resolvedPath . segments [ : position + 1 ] , 0 ) } , kind : current . Kind ,
2026-10-15 15:15:05 +00:00
} )
}
resolved . node = current
resolved . present = true
resolved . absence = sourceVersionPathPresent
return resolved
}
func sourceVersionControlOverlap ( root * document . Node , path Path ) bool {
if root == nil || ! path . valid ( ) {
return false
}
if root . Kind == document . Mapping {
for _ , entry := range root . Mapping {
if entry . Key == directiveKey && pathHasPrefix ( path , Keys ( directiveKey ) ) {
return true
}
}
}
return sourceVersionInheritanceOverlap ( root , Path { } , path )
}
func sourceVersionInheritanceOverlap ( node * document . Node , current , path Path ) bool {
if node == nil {
return false
}
if node . Kind == document . Mapping {
if _ , found := inheritanceDirective ( node ) ; found {
controlPath := current . Key ( inheritanceKey )
if pathHasPrefix ( path , controlPath ) {
return true
}
}
for _ , entry := range node . Mapping {
if entry . Key == inheritanceKey {
continue
}
if sourceVersionInheritanceOverlap ( entry . Value , current . Key ( entry . Key ) , path ) {
return true
}
}
} else if node . Kind == document . Sequence {
for index , child := range node . Sequence {
if sourceVersionInheritanceOverlap ( child , current . Index ( index ) , path ) {
return true
}
}
}
return false
}
func pathHasPrefix ( path , prefix Path ) bool {
if ! path . valid ( ) || ! prefix . valid ( ) || len ( prefix . segments ) > len ( path . segments ) {
return false
}
for index , segment := range prefix . segments {
candidate := path . segments [ index ]
if candidate . kind != segment . kind || candidate . key != segment . key || candidate . index != segment . index {
return false
}
}
return true
}
func removeSourceVersionNode ( root * document . Node , path Path ) bool {
if root == nil || ! path . valid ( ) || len ( path . segments ) == 0 {
return false
}
current := root
for _ , segment := range path . segments [ : len ( path . segments ) - 1 ] {
switch current . Kind {
case document . Mapping :
if segment . kind == pathSegmentIndex {
return false
}
found := false
for _ , entry := range current . Mapping {
if entry . Key == segment . key {
current = entry . Value
found = true
break
}
}
if ! found {
return false
}
case document . Sequence :
index , ok := schemaIndex ( segment )
if ! ok || index >= len ( current . Sequence ) {
return false
}
current = current . Sequence [ index ]
default :
return false
}
}
last := path . segments [ len ( path . segments ) - 1 ]
switch current . Kind {
case document . Mapping :
if last . kind == pathSegmentIndex {
return false
}
for index , entry := range current . Mapping {
if entry . Key == last . key {
current . Mapping = append ( current . Mapping [ : index ] , current . Mapping [ index + 1 : ] ... )
return true
}
}
case document . Sequence :
index , ok := schemaIndex ( last )
if ! ok || index >= len ( current . Sequence ) {
return false
}
current . Sequence = append ( current . Sequence [ : index ] , current . Sequence [ index + 1 : ] ... )
return true
}
return false
}