package configkit import "gitea.maximumdirect.net/eric/ConfigKit/internal/document" // 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 } // 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 || !r.metadata.hasHistoricalPath(path) { 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 } after := valueSnapshot(working, path, r.secrets) if !before.Present && !after.Present { continue } explanation.History = append(explanation.History, ValueChange{ Before: before, After: after, Origin: historyApplicationOrigin(application, operationPath), }) } // Intermediate input replacements do not permanently suppress defaults: // decoding applies them to the final input tree. Show that reconciliation // explicitly, including canonical scalar conversions, before domain changes. replayed, _ := documentAtPath(working, path) decoded, _ := documentAtPath(r.input, path) if !configurationValuesEqual(replayed, decoded) { origin := cloneOrigin(r.inputOrigins[path.String()]) origin.Operation = ChangeDecoding explanation.History = append(explanation.History, ValueChange{ Before: valueSnapshot(working, path, r.secrets), After: valueSnapshot(r.input, path, r.secrets), Origin: origin, Reasons: []string{"applied retained defaults and canonical typed decoding"}, }) } working = document.Clone(r.input) for _, snapshot := range coalescedResolutionSnapshots(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 } after := valueSnapshot(working, path, r.secrets) if !before.Present && !after.Present { continue } explanation.History = append(explanation.History, ValueChange{ Before: before, After: after, 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 }