124 lines
4.3 KiB
Go
124 lines
4.3 KiB
Go
package engine
|
|
|
|
import "go.maximumdirect.net/configkit/internal/document"
|
|
|
|
// resolutionReplayGroup is the explanation projection of one or more ordered
|
|
// resolution records. The covering record identifies the before/after roots;
|
|
// originalRecordIDs keep every coalesced record available to other projections.
|
|
type resolutionReplayGroup struct {
|
|
originalRecordIDs []int
|
|
coveringRecordID int
|
|
change Change
|
|
}
|
|
|
|
type historyResolutionPayload struct {
|
|
snapshots []resolutionSnapshot
|
|
replayGroups []resolutionReplayGroup
|
|
}
|
|
|
|
// observeResolutionEvidence retains audited records in producer order and
|
|
// indexes the concrete shapes in every original before/after subtree. Snapshot
|
|
// roots are already detached by runResolution and are shared with the replay
|
|
// group that refers to their covering record.
|
|
func (builder *historyBuilder) observeResolutionEvidence(snapshots []resolutionSnapshot) {
|
|
builder.requireOpen()
|
|
if len(snapshots) == 0 {
|
|
return
|
|
}
|
|
if builder.model.resolutionPayload != nil {
|
|
panic("configkit: resolution history already registered")
|
|
}
|
|
if builder.scratch == nil {
|
|
panic("configkit: resolution history requires the pre-resolution document")
|
|
}
|
|
|
|
payload := &historyResolutionPayload{
|
|
snapshots: make([]resolutionSnapshot, len(snapshots)),
|
|
replayGroups: coalescedResolutionSnapshots(snapshots),
|
|
}
|
|
attributions := make([]historyAttributionRef, len(snapshots))
|
|
lookup := buildDocumentLookup(builder.scratch)
|
|
for recordID, snapshot := range snapshots {
|
|
snapshot.change = cloneChange(snapshot.change)
|
|
payload.snapshots[recordID] = snapshot
|
|
|
|
ref := builder.nextPayload(historyPhaseResolution)
|
|
if ref.id != recordID {
|
|
panic("configkit: resolution payload identifiers are out of order")
|
|
}
|
|
attribution := builder.addAttribution(snapshot.change.Origin, snapshot.change.Reasons, snapshot.change.Dependencies)
|
|
attributions[recordID] = attribution
|
|
beforePath, beforeFound := historyConcretePathAt(builder.scratch, snapshot.change.Path, lookup)
|
|
if beforeFound {
|
|
observeResolutionSubtree(builder, snapshot.before, beforePath, snapshot.change.Path, ref, attribution, true, false)
|
|
}
|
|
}
|
|
for _, group := range payload.replayGroups {
|
|
if group.coveringRecordID < 0 || group.coveringRecordID >= len(payload.snapshots) {
|
|
continue
|
|
}
|
|
covering := payload.snapshots[group.coveringRecordID]
|
|
replaceHistoryPath(&builder.scratch, group.change.Path, covering.after)
|
|
}
|
|
lookup = buildDocumentLookup(builder.scratch)
|
|
for recordID, snapshot := range payload.snapshots {
|
|
ref := historyPayloadRef{phase: historyPhaseResolution, id: recordID}
|
|
afterPath, afterFound := historyConcretePathAt(builder.scratch, snapshot.change.Path, lookup)
|
|
if afterFound {
|
|
observeResolutionSubtree(builder, snapshot.after, afterPath, snapshot.change.Path, ref, attributions[recordID], false, true)
|
|
}
|
|
}
|
|
builder.model.resolutionPayload = payload
|
|
}
|
|
|
|
func observeResolutionSubtree(builder *historyBuilder, root *document.Node, path, reportedPath Path, ref historyPayloadRef, attribution historyAttributionRef, before, after bool) {
|
|
walkDocument(root, path, func(path Path, node *document.Node) {
|
|
builder.addObservation(historyObservation{
|
|
path: path,
|
|
phase: historyPhaseResolution,
|
|
payload: ref,
|
|
reportedPath: reportedPath,
|
|
role: historyRoleOperation,
|
|
beforePresent: before,
|
|
afterPresent: after,
|
|
sensitive: node.Sensitive,
|
|
attributionRef: attribution,
|
|
})
|
|
})
|
|
}
|
|
|
|
func historyConcretePathAt(root *document.Node, path Path, lookup *documentLookup) (Path, bool) {
|
|
if root == nil || !path.valid() {
|
|
return Path{}, false
|
|
}
|
|
current := root
|
|
var concrete Path
|
|
for _, segment := range path.segments {
|
|
switch current.Kind {
|
|
case document.Mapping:
|
|
if segment.kind == pathSegmentIndex {
|
|
return Path{}, false
|
|
}
|
|
position := lookup.find(current, segment.key)
|
|
if position < 0 {
|
|
return Path{}, false
|
|
}
|
|
current = current.Mapping[position].Value
|
|
concrete = concrete.Key(segment.key)
|
|
case document.Sequence:
|
|
index, ok := schemaIndex(segment)
|
|
if !ok || index >= len(current.Sequence) {
|
|
return Path{}, false
|
|
}
|
|
current = current.Sequence[index]
|
|
concrete = concrete.Index(index)
|
|
default:
|
|
return Path{}, false
|
|
}
|
|
if current == nil {
|
|
return Path{}, false
|
|
}
|
|
}
|
|
return concrete, true
|
|
}
|