package engine import "gitea.maximumdirect.net/eric/ConfigKit/internal/document" type sourceVersionMode uint8 const ( sourceVersionModeDefault sourceVersionMode = iota sourceVersionModeIfPresent sourceVersionModeRequired ) // 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) } type sourceVersionRole uint8 const ( sourceVersionMain sourceVersionRole = iota sourceVersionContribution ) type sourceVersionAncestor struct { path Path kind document.Kind } type sourceVersionEvidence struct { sourceID string path Path resolvedPath Path destination Path projectedPaths []Path version Version location *Location ancestors []sourceVersionAncestor absence sourceVersionPathAbsence checked bool redacted bool } type sourceVersionState struct { localRule *VersionRule effectiveRule *VersionRule mode sourceVersionMode mainProcessing *sourceVersionProcessingResult evidence []*sourceVersionEvidence diagnostics []Diagnostic } 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 != "" } else if sensitivity.isSensitiveAtOrAbove(evidence.sensitivityPaths()[0].String()) { redacted = true } if redacted { version.Text = "" } return &SourceVersionCheck{ Path: path.clone(), Version: version, Location: cloneLocation(evidence.location), Redacted: redacted, } } // 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 } 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 } 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) } 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 } type sourceVersionProcessingResult struct { payload *document.Node imports []importDeclaration evidence *sourceVersionEvidence diagnostics []Diagnostic } 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 } if mode == sourceVersionModeDefault { return local } effective := *local prefix := destination.clone() if placement == placementModeSequenceItem { prefix = prefix.Index(0) } effective.Path = Path{ segments: append(clonePathSegments(prefix.segments, len(local.Path.segments)), local.Path.segments...), invalid: prefix.invalid || local.Path.invalid, } return &effective } 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 if resolved.present && sourceVersionControlOverlap(root, evidence.resolvedPath) { 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 { // 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()} if root == nil || !path.valid() { return resolved } current := root resolved.nearest = current.Location resolved.ancestors = append(resolved.ancestors, sourceVersionAncestor{kind: current.Kind}) for position, segment := range path.segments { switch current.Kind { case document.Mapping: if segment.kind == pathSegmentIndex { resolved.absence = sourceVersionPathObstructed return resolved } resolved.resolvedPath.segments[position] = pathSegment{kind: pathSegmentKey, key: segment.key} 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 } resolved.resolvedPath.segments[position] = pathSegment{kind: pathSegmentIndex, index: index} 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{ path: Path{segments: clonePathSegments(resolved.resolvedPath.segments[:position+1], 0)}, kind: current.Kind, }) } 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 }