2026-09-12 15:58:06 +00:00
|
|
|
package engine
|
|
|
|
|
|
2026-11-01 14:19:37 +00:00
|
|
|
import "go.maximumdirect.net/configkit/internal/document"
|
2026-09-12 15:58:06 +00:00
|
|
|
|
|
|
|
|
// 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.
|
2026-09-12 16:12:01 +00:00
|
|
|
func (r *Inspection) Explain(path Path) (Explanation, bool) {
|
2026-10-26 17:01:32 +00:00
|
|
|
if r == nil || r.final == nil || !path.valid() {
|
|
|
|
|
return Explanation{}, false
|
|
|
|
|
}
|
2026-10-26 17:36:36 +00:00
|
|
|
model := r.history
|
2026-10-26 17:01:32 +00:00
|
|
|
if model == nil {
|
|
|
|
|
return Explanation{}, false
|
|
|
|
|
}
|
2026-10-27 03:15:01 +00:00
|
|
|
selected := model.selectHistory(path)
|
|
|
|
|
if len(selected) == 0 && !model.hasObservedShape(path) {
|
2026-09-12 15:58:06 +00:00
|
|
|
return Explanation{}, false
|
|
|
|
|
}
|
2026-10-27 03:15:01 +00:00
|
|
|
selections := model.indexReplaySelections(selected)
|
2026-10-26 17:36:36 +00:00
|
|
|
explanation := Explanation{Path: path.clone(), Effective: valueSnapshotWithLookup(r.final, path, r.secrets, model.documentLookup)}
|
2026-09-12 15:58:06 +00:00
|
|
|
if info, ok := r.metadata.Lookup(path); ok {
|
|
|
|
|
explanation.Origin = cloneOrigin(info.Origin)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-26 17:01:32 +00:00
|
|
|
working := document.Clone(model.baseline)
|
2026-10-27 03:15:01 +00:00
|
|
|
if baseline, found := selections.byPayload[historyPayloadRef{phase: historyPhaseBaseline, id: 0}]; found && baseline.after {
|
2026-09-12 15:58:06 +00:00
|
|
|
explanation.History = append(explanation.History, ValueChange{
|
2026-10-26 17:01:32 +00:00
|
|
|
After: valueSnapshot(working, path, r.secrets),
|
|
|
|
|
Origin: cloneOrigin(baseline.attribution.origin),
|
2026-09-12 15:58:06 +00:00
|
|
|
})
|
|
|
|
|
}
|
2026-09-29 17:13:14 +00:00
|
|
|
replayIndex := new(mappingIndex)
|
2026-10-26 17:01:32 +00:00
|
|
|
for payloadID, payload := range model.sourcePayloads {
|
|
|
|
|
if payload.kind != historySourceApplicationPayload {
|
|
|
|
|
continue
|
2026-09-12 15:58:06 +00:00
|
|
|
}
|
2026-10-26 17:01:32 +00:00
|
|
|
application := payload.application
|
|
|
|
|
before := valueSnapshot(working, path, r.secrets)
|
2026-09-29 17:13:14 +00:00
|
|
|
applyHistoryApplication(&working, application, replayIndex)
|
2026-10-27 03:15:01 +00:00
|
|
|
selected, relevant := selections.application(historyPayloadRef{phase: historyPhaseSources, id: payloadID})
|
2026-09-12 15:58:06 +00:00
|
|
|
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,
|
2026-10-26 17:01:32 +00:00
|
|
|
Origin: cloneOrigin(selected.attribution.origin),
|
2026-09-12 15:58:06 +00:00
|
|
|
})
|
|
|
|
|
}
|
2026-10-26 17:01:32 +00:00
|
|
|
appendInheritanceExplanation(&explanation, &working, path, model, selections, r.secrets)
|
2026-09-12 15:58:06 +00:00
|
|
|
|
|
|
|
|
// Intermediate input replacements do not permanently suppress defaults:
|
|
|
|
|
// decoding applies them to the final input tree. Show that reconciliation
|
2026-10-23 21:37:13 +00:00
|
|
|
// explicitly before typed CLI contributions and domain resolution.
|
2026-10-26 17:01:32 +00:00
|
|
|
if checkpoint := model.preCLICheckpoint; checkpoint != nil {
|
2026-10-27 03:15:01 +00:00
|
|
|
selection, selected := selections.byPayload[historyPayloadRef{phase: historyPhasePreCLI, id: 0}]
|
2026-10-26 17:01:32 +00:00
|
|
|
replayed, _ := documentAtPath(working, path)
|
|
|
|
|
decoded, _ := documentAtPathWithLookup(checkpoint.root, path, model.documentLookup)
|
|
|
|
|
if selected && !configurationValuesEqual(replayed, decoded) {
|
|
|
|
|
origin, reasons := checkpointExplanationAttribution(selection)
|
|
|
|
|
explanation.History = append(explanation.History, ValueChange{
|
|
|
|
|
Before: valueSnapshot(working, path, r.secrets),
|
|
|
|
|
After: valueSnapshotWithLookup(checkpoint.root, path, r.secrets, model.documentLookup),
|
|
|
|
|
Origin: origin,
|
|
|
|
|
Reasons: reasons,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
working = document.Clone(checkpoint.root)
|
2026-09-12 15:58:06 +00:00
|
|
|
}
|
|
|
|
|
|
2026-10-26 17:01:32 +00:00
|
|
|
for payloadID, payload := range model.cliPayloads {
|
|
|
|
|
if payload.kind != historySourceApplicationPayload {
|
|
|
|
|
continue
|
2026-10-23 21:37:13 +00:00
|
|
|
}
|
2026-10-26 17:01:32 +00:00
|
|
|
applicationRef := historyPayloadRef{phase: historyPhaseCLI, id: payloadID}
|
|
|
|
|
before := valueSnapshot(working, path, r.secrets)
|
|
|
|
|
applyHistoryApplication(&working, payload.application, replayIndex)
|
2026-10-27 03:15:01 +00:00
|
|
|
selected, relevant := selections.application(applicationRef)
|
2026-10-23 21:37:13 +00:00
|
|
|
if !relevant {
|
|
|
|
|
continue
|
2026-10-23 21:20:19 +00:00
|
|
|
}
|
2026-10-23 21:37:13 +00:00
|
|
|
after := valueSnapshot(working, path, r.secrets)
|
|
|
|
|
if !before.Present && !after.Present {
|
|
|
|
|
continue
|
2026-10-23 21:20:19 +00:00
|
|
|
}
|
2026-10-23 21:37:13 +00:00
|
|
|
explanation.History = append(explanation.History, ValueChange{
|
|
|
|
|
Before: before,
|
|
|
|
|
After: after,
|
2026-10-26 17:01:32 +00:00
|
|
|
Origin: cloneOrigin(selected.attribution.origin),
|
2026-10-23 21:37:13 +00:00
|
|
|
})
|
|
|
|
|
}
|
2026-10-26 17:01:32 +00:00
|
|
|
if checkpoint := model.postCLICheckpoint; checkpoint != nil {
|
2026-10-27 03:15:01 +00:00
|
|
|
selection, selected := selections.byPayload[historyPayloadRef{phase: historyPhasePostCLI, id: 0}]
|
2026-10-26 17:01:32 +00:00
|
|
|
replayed, _ := documentAtPath(working, path)
|
|
|
|
|
effectiveInput, _ := documentAtPathWithLookup(checkpoint.root, path, model.documentLookup)
|
|
|
|
|
if selected && !configurationValuesEqual(replayed, effectiveInput) {
|
|
|
|
|
origin, reasons := checkpointExplanationAttribution(selection)
|
|
|
|
|
explanation.History = append(explanation.History, ValueChange{
|
|
|
|
|
Before: valueSnapshot(working, path, r.secrets),
|
|
|
|
|
After: valueSnapshotWithLookup(checkpoint.root, path, r.secrets, model.documentLookup),
|
|
|
|
|
Origin: origin,
|
|
|
|
|
Reasons: reasons,
|
|
|
|
|
})
|
2026-09-12 15:58:06 +00:00
|
|
|
}
|
2026-10-26 17:01:32 +00:00
|
|
|
working = document.Clone(checkpoint.root)
|
|
|
|
|
}
|
|
|
|
|
if model.resolutionPayload != nil {
|
|
|
|
|
for _, group := range model.resolutionPayload.replayGroups {
|
2026-10-27 03:15:01 +00:00
|
|
|
selection, selected := selections.resolutionGroup(group)
|
2026-10-26 17:01:32 +00:00
|
|
|
if !selected || group.coveringRecordID < 0 || group.coveringRecordID >= len(model.resolutionPayload.snapshots) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
covering := model.resolutionPayload.snapshots[group.coveringRecordID]
|
|
|
|
|
before := valueSnapshot(working, path, r.secrets)
|
|
|
|
|
replaceHistoryPath(&working, group.change.Path, covering.after)
|
|
|
|
|
after := valueSnapshot(working, path, r.secrets)
|
|
|
|
|
if !before.Present && !after.Present {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
explanation.History = append(explanation.History, ValueChange{
|
|
|
|
|
Before: before,
|
|
|
|
|
After: after,
|
|
|
|
|
Origin: cloneOrigin(selection.attribution.origin),
|
|
|
|
|
Reasons: append([]string(nil), group.change.Reasons...),
|
|
|
|
|
Dependencies: clonePaths(group.change.Dependencies),
|
|
|
|
|
})
|
2026-09-12 15:58:06 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
2026-10-26 17:01:32 +00:00
|
|
|
|
2026-10-27 03:15:01 +00:00
|
|
|
// historyReplaySelections is query-local dispatch state, not another evidence
|
|
|
|
|
// or attribution owner. Replay still follows the model's ordered payloads.
|
|
|
|
|
type historyReplaySelections struct {
|
|
|
|
|
byPayload map[historyPayloadRef]historySelection
|
|
|
|
|
applicationFallbacks map[historyPayloadRef]historySelection
|
2026-10-26 17:01:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-10-27 03:15:01 +00:00
|
|
|
func (model *historyModel) indexReplaySelections(ordered []historySelection) historyReplaySelections {
|
|
|
|
|
indexed := historyReplaySelections{
|
|
|
|
|
byPayload: make(map[historyPayloadRef]historySelection, len(ordered)),
|
|
|
|
|
applicationFallbacks: make(map[historyPayloadRef]historySelection),
|
|
|
|
|
}
|
|
|
|
|
for _, selection := range ordered {
|
|
|
|
|
ref := selection.payload
|
|
|
|
|
indexed.byPayload[ref] = selection
|
|
|
|
|
if selection.role != historyRoleOperation || ref.substep != 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
var payloads []historySourcePayload
|
|
|
|
|
switch ref.phase {
|
|
|
|
|
case historyPhaseSources:
|
|
|
|
|
payloads = model.sourcePayloads
|
|
|
|
|
case historyPhaseCLI:
|
|
|
|
|
payloads = model.cliPayloads
|
|
|
|
|
default:
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if ref.id < 0 || ref.id >= len(payloads) {
|
2026-10-26 17:01:32 +00:00
|
|
|
continue
|
|
|
|
|
}
|
2026-10-27 03:15:01 +00:00
|
|
|
payload := payloads[ref.id]
|
|
|
|
|
if payload.kind == historySourceOverlayPayload && payload.hasApplicationRef {
|
|
|
|
|
indexed.applicationFallbacks[payload.applicationRef] = selection
|
2026-10-26 17:01:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-10-27 03:15:01 +00:00
|
|
|
return indexed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (selections historyReplaySelections) application(ref historyPayloadRef) (historySelection, bool) {
|
|
|
|
|
if direct, found := selections.byPayload[ref]; found {
|
|
|
|
|
return direct, true
|
|
|
|
|
}
|
|
|
|
|
// Only removed/replaced paths lacking a direct contribution need fallback.
|
|
|
|
|
selection, found := selections.applicationFallbacks[ref]
|
|
|
|
|
return selection, found
|
2026-10-26 17:01:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func checkpointExplanationAttribution(selection historySelection) (Origin, []string) {
|
|
|
|
|
if selection.hasAttribution {
|
|
|
|
|
return cloneOrigin(selection.attribution.origin), append([]string(nil), selection.attribution.reasons...)
|
|
|
|
|
}
|
|
|
|
|
return Origin{Operation: ChangeDecoding}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-27 03:15:01 +00:00
|
|
|
func (selections historyReplaySelections) resolutionGroup(group resolutionReplayGroup) (historySelection, bool) {
|
2026-10-26 17:01:32 +00:00
|
|
|
var fallback historySelection
|
|
|
|
|
found := false
|
|
|
|
|
for _, recordID := range group.originalRecordIDs {
|
2026-10-27 03:15:01 +00:00
|
|
|
selection, ok := selections.byPayload[historyPayloadRef{phase: historyPhaseResolution, id: recordID}]
|
2026-10-26 17:01:32 +00:00
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if recordID == group.coveringRecordID {
|
|
|
|
|
return selection, true
|
|
|
|
|
}
|
|
|
|
|
if !found {
|
|
|
|
|
fallback, found = selection, true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return fallback, found
|
|
|
|
|
}
|