package configkit import ( "bytes" "errors" "io" "slices" "strconv" "strings" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" yamladapter "gitea.maximumdirect.net/eric/ConfigKit/internal/yaml" ) const historyNodeLimit = 4_000_000 var ( errExplanationUnavailable = errors.New("configkit: configuration explanation is unavailable") errExplanationUnknownPath = errors.New("configkit: configuration explanation path is unknown") ) // ValueSnapshot is a detached, deterministic YAML representation of one value. // Present distinguishes absence or deletion from an explicit null value. type ValueSnapshot struct { Present bool Redacted bool YAML string } // ValueChange describes one explicit definition or audited domain operation. type ValueChange struct { Before ValueSnapshot After ValueSnapshot Origin Origin Reasons []string Dependencies []Path } // SecretReferenceInfo describes a successfully resolved effective reference. // Name is the environment-variable name, never its value. type SecretReferenceInfo struct { Name string } // Explanation is a detached safe account of one effective or historical path. type Explanation struct { Path Path Effective ValueSnapshot Origin Origin History []ValueChange SecretReference *SecretReferenceInfo } type historyApplication struct { layer Layer path Path value *document.Node origin Origin } func cloneHistoryApplications(values []historyApplication) []historyApplication { if values == nil { return nil } cloned := make([]historyApplication, len(values)) for index, value := range values { cloned[index] = historyApplication{ layer: value.layer, path: value.path.clone(), value: value.value, origin: cloneOrigin(value.origin), } } return cloned } func cloneSecretReferences(values map[string]string) map[string]string { if values == nil { return nil } cloned := make(map[string]string, len(values)) for path, name := range values { cloned[path] = name } return cloned } // Explain returns safe effective and chronological values for a known final or // historical path. Unknown and malformed paths return false. func (r *Result[T]) Explain(path Path) (Explanation, bool) { if r == nil || r.final == nil || r.baseline == nil || !path.valid() { return Explanation{}, false } explanation := Explanation{Path: path.clone(), Effective: valueSnapshot(r.final, path, r.secrets)} if info, ok := r.metadata.Lookup(path); ok { explanation.Origin = cloneOrigin(info.Origin) } working := document.Clone(r.baseline) if _, present := documentAtPath(working, path); present { explanation.History = append(explanation.History, ValueChange{ After: valueSnapshot(working, path, r.secrets), Origin: Origin{Layer: LayerDefault}, }) } for _, application := range r.applications { operationPath, relevant := applicationOperationPath(application, path) var before ValueSnapshot if relevant { before = valueSnapshot(working, path, r.secrets) } applyHistoryApplication(&working, application) if !relevant { continue } explanation.History = append(explanation.History, ValueChange{ Before: before, After: valueSnapshot(working, path, r.secrets), Origin: historyApplicationOrigin(application, operationPath), }) } working = document.Clone(r.input) for _, snapshot := range r.resolution { relevant := pathsIntersect(snapshot.change.Path, path) var before ValueSnapshot if relevant { before = valueSnapshot(working, path, r.secrets) } replaceHistoryPath(&working, snapshot.change.Path, snapshot.after) if !relevant { continue } explanation.History = append(explanation.History, ValueChange{ Before: before, After: valueSnapshot(working, path, r.secrets), Origin: cloneOrigin(snapshot.change.Origin), Reasons: append([]string(nil), snapshot.change.Reasons...), Dependencies: clonePaths(snapshot.change.Dependencies), }) } if !explanation.Effective.Present && len(explanation.History) == 0 { return Explanation{}, false } if name, ok := r.secretReferences[path.String()]; ok && !pathIsSecret(r.secrets, path) { explanation.SecretReference = &SecretReferenceInfo{Name: name} } return explanation, true } func applicationOperationPath(application historyApplication, query Path) (Path, bool) { if !application.path.valid() || application.value == nil { return Path{}, false } if application.path.String() != "" { if pathsIntersect(application.path, query) { return application.path.clone(), true } return Path{}, false } if len(query.segments) == 0 { return Path{}, true } for length := 1; length <= len(query.segments); length++ { prefix := Path{segments: clonePathSegments(query.segments[:length], 0)} node, present := documentAtPath(application.value, prefix) if !present { return Path{}, false } if length == len(query.segments) || node.Kind != document.Mapping { return prefix, true } } return Path{}, false } func historyApplicationOrigin(application historyApplication, operationPath Path) Origin { if application.origin.Layer != "" { return cloneOrigin(application.origin) } origin := Origin{Layer: application.layer} relative, ok := relativeHistoryPath(application.path, operationPath) if !ok { return origin } if node, present := documentAtPath(application.value, relative); present { origin.Locations = []Location{publicLocation(node.Location)} } return origin } func relativeHistoryPath(root, path Path) (Path, bool) { if !pathContains(root, path) { return Path{}, false } return Path{segments: clonePathSegments(path.segments[len(root.segments):], 0)}, true } func pathsIntersect(left, right Path) bool { return pathContains(left, right) || pathContains(right, left) } func applyHistoryApplication(root **document.Node, application historyApplication) { if application.path.String() == "" { mergeHistoryDocument(root, application.value) return } mergeHistoryAtPath(root, application.path, application.value) } func mergeHistoryDocument(target **document.Node, incoming *document.Node) { if incoming == nil { return } if *target == nil || (*target).Kind != document.Mapping || incoming.Kind != document.Mapping { *target = document.Clone(incoming) return } for _, entry := range incoming.Mapping { position := documentEntryIndex((*target).Mapping, entry.Key) if position < 0 { cloned := entry cloned.Value = document.Clone(entry.Value) (*target).Mapping = append((*target).Mapping, cloned) continue } mergeHistoryDocument(&(*target).Mapping[position].Value, entry.Value) } } func mergeHistoryAtPath(root **document.Node, path Path, incoming *document.Node) { if root == nil || *root == nil || len(path.segments) == 0 { mergeHistoryDocument(root, incoming) return } current := *root for index, segment := range path.segments { if current.Kind != document.Mapping || segment.kind == pathSegmentIndex { return } position := documentEntryIndex(current.Mapping, segment.key) if index == len(path.segments)-1 { if position < 0 { current.Mapping = append(current.Mapping, document.Entry{Key: segment.key, Value: document.Clone(incoming)}) } else { mergeHistoryDocument(¤t.Mapping[position].Value, incoming) } return } if position < 0 { child := &document.Node{Kind: document.Mapping} current.Mapping = append(current.Mapping, document.Entry{Key: segment.key, Value: child}) current = child } else { current = current.Mapping[position].Value } } } func replaceHistoryPath(root **document.Node, path Path, replacement *document.Node) { if root == nil || *root == nil { return } if len(path.segments) == 0 { *root = document.Clone(replacement) return } current := *root for index, segment := range path.segments { last := index == len(path.segments)-1 switch current.Kind { case document.Mapping: if segment.kind == pathSegmentIndex { return } position := documentEntryIndex(current.Mapping, segment.key) if position < 0 { return } if last { if replacement == nil { current.Mapping = append(current.Mapping[:position], current.Mapping[position+1:]...) } else { current.Mapping[position].Value = document.Clone(replacement) } return } current = current.Mapping[position].Value case document.Sequence: position, ok := schemaIndex(segment) if !ok || position >= len(current.Sequence) { return } if last { if replacement != nil { current.Sequence[position] = document.Clone(replacement) } return } current = current.Sequence[position] default: return } } } func valueSnapshot(root *document.Node, path Path, secrets map[string]bool) ValueSnapshot { node, present := documentAtPath(root, path) if !present { return ValueSnapshot{} } safe, redacted := safeSnapshotDocument(node, secrets, path) var output bytes.Buffer if err := yamladapter.Write(&output, safe); err != nil { return ValueSnapshot{Present: true, Redacted: true, YAML: "'[REDACTED]'\n"} } return ValueSnapshot{Present: true, Redacted: redacted, YAML: output.String()} } func safeSnapshotDocument(node *document.Node, secrets map[string]bool, path Path) (*document.Node, bool) { if node == nil { return nil, false } if pathIsSecret(secrets, path) { return &document.Node{Kind: document.String, Text: "[REDACTED]"}, true } result := *node redacted := false switch node.Kind { case document.Mapping: result.Mapping = make([]document.Entry, len(node.Mapping)) copy(result.Mapping, node.Mapping) sortDocumentMapping(result.Mapping) for index := range result.Mapping { entry := &result.Mapping[index] entry.Value, redacted = safeSnapshotChild(entry.Value, secrets, path.Key(entry.Key), redacted) } case document.Sequence: result.Sequence = make([]*document.Node, len(node.Sequence)) for index, child := range node.Sequence { result.Sequence[index], redacted = safeSnapshotChild(child, secrets, path.Index(index), redacted) } } return &result, redacted } func safeSnapshotChild(node *document.Node, secrets map[string]bool, path Path, prior bool) (*document.Node, bool) { safe, redacted := safeSnapshotDocument(node, secrets, path) return safe, prior || redacted } func sortDocumentMapping(entries []document.Entry) { slices.SortFunc(entries, func(left, right document.Entry) int { return strings.Compare(left.Key, right.Key) }) } func historyNodesWithinLimit(baseline, input, final *document.Node, applications []historyApplication, resolutions []resolutionSnapshot) bool { return historyNodesWithin(historyNodeLimit, baseline, input, final, applications, resolutions) } func historyNodesWithin(limit int, baseline, input, final *document.Node, applications []historyApplication, resolutions []resolutionSnapshot) bool { seen := make(map[*document.Node]bool) count := 0 var visit func(*document.Node) bool visit = func(node *document.Node) bool { if node == nil || seen[node] { return true } seen[node] = true count++ if count > limit { return false } for _, entry := range node.Mapping { if !visit(entry.Value) { return false } } for _, child := range node.Sequence { if !visit(child) { return false } } return true } if !visit(baseline) || !visit(input) || !visit(final) { return false } for _, application := range applications { if !visit(application.value) { return false } } for _, resolution := range resolutions { if !visit(resolution.before) || !visit(resolution.after) { return false } } return true } // WriteExplanation writes a deterministic human-readable rendering of Explain. // Unknown paths produce no output. Writer failures may leave partial output. func (r *Result[T]) WriteExplanation(output io.Writer, path Path) error { if r == nil || output == nil { return errExplanationUnavailable } explanation, ok := r.Explain(path) if !ok { return errExplanationUnknownPath } var rendered strings.Builder rendered.WriteString("path: ") rendered.WriteString(strconv.Quote(explanation.Path.String())) rendered.WriteByte('\n') writeExplanationOrigin(&rendered, "effective source", explanation.Origin, r.sources) writeExplanationSnapshot(&rendered, "effective (canonical typed)", explanation.Effective, 0) if explanation.SecretReference != nil { rendered.WriteString("secret reference: ") rendered.WriteString(strconv.Quote(explanation.SecretReference.Name)) rendered.WriteString(" (resolved from process environment)\n") } rendered.WriteString("history:\n") for _, change := range explanation.History { rendered.WriteString(" - representation: ") if change.Origin.Layer == LayerBase || change.Origin.Layer == LayerProfile || change.Origin.Layer == LayerCLI { rendered.WriteString("policy-validated input\n") } else { rendered.WriteString("canonical typed\n") } writeExplanationOrigin(&rendered, " source", change.Origin, r.sources) writeExplanationSnapshot(&rendered, " before", change.Before, 4) writeExplanationSnapshot(&rendered, " after", change.After, 4) for _, reason := range change.Reasons { rendered.WriteString(" reason: ") rendered.WriteString(strconv.Quote(reason)) rendered.WriteByte('\n') } for _, dependency := range change.Dependencies { rendered.WriteString(" dependency: ") rendered.WriteString(strconv.Quote(dependency.String())) rendered.WriteByte('\n') } } data := []byte(rendered.String()) written, err := output.Write(data) if err == nil && written != len(data) { return io.ErrShortWrite } return err } func writeExplanationOrigin(output *strings.Builder, label string, origin Origin, sources []SourceInfo) { output.WriteString(label) output.WriteString(": layer=") output.WriteString(strconv.Quote(string(origin.Layer))) if origin.Operation != "" { output.WriteString(" operation=") output.WriteString(strconv.Quote(string(origin.Operation))) } output.WriteByte('\n') for _, location := range origin.Locations { output.WriteString(" location: source=") output.WriteString(strconv.Quote(location.SourceID)) if label := sourceLabel(sources, location.SourceID); label != "" { output.WriteString(" label=") output.WriteString(strconv.Quote(label)) } output.WriteString(" line=") output.WriteString(strconv.Itoa(location.Line)) output.WriteString(" column=") output.WriteString(strconv.Itoa(location.Column)) output.WriteByte('\n') } } func sourceLabel(sources []SourceInfo, sourceID string) string { for _, source := range sources { if source.ID == sourceID { return source.Label } } return "" } func writeExplanationSnapshot(output *strings.Builder, label string, snapshot ValueSnapshot, indent int) { if !snapshot.Present { output.WriteString(label) output.WriteString(": absent\n") return } output.WriteString(label) if snapshot.Redacted { output.WriteString(" (redacted)") } output.WriteString(":\n") prefix := strings.Repeat(" ", indent+2) for _, line := range strings.SplitAfter(snapshot.YAML, "\n") { if line == "" { continue } output.WriteString(prefix) output.WriteString(line) if !strings.HasSuffix(line, "\n") { output.WriteByte('\n') } } }