Files
configkit/internal/engine/history_cli.go

165 lines
5.8 KiB
Go

package engine
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
type historyCheckpointPayload struct {
root *document.Node
origins map[string]Origin
}
func (builder *historyBuilder) observePreCLICheckpoint(root *document.Node, origins map[string]Origin) {
builder.observeTypedCheckpoint(historyPhasePreCLI, root, origins, "applied retained defaults and canonical typed decoding")
}
func (builder *historyBuilder) observePostCLICheckpoint(root *document.Node, origins map[string]Origin) {
builder.observeTypedCheckpoint(historyPhasePostCLI, root, origins, "applied the typed CLI assignment")
}
func (builder *historyBuilder) observeTypedCheckpoint(phase historyPhase, root *document.Node, origins map[string]Origin, reason string) {
builder.requireOpen()
if phase != historyPhasePreCLI && phase != historyPhasePostCLI {
panic("configkit: typed checkpoint has an invalid history phase")
}
payload := builder.nextPayload(phase)
checkpoint := historyCheckpointPayload{root: root, origins: cloneOriginIndex(origins)}
switch phase {
case historyPhasePreCLI:
if builder.model.preCLICheckpoint != nil || payload.id != 0 {
panic("configkit: pre-CLI checkpoint was already registered")
}
builder.model.preCLICheckpoint = &checkpoint
case historyPhasePostCLI:
if builder.model.postCLICheckpoint != nil || payload.id != 0 {
panic("configkit: post-CLI checkpoint was already registered")
}
builder.model.postCLICheckpoint = &checkpoint
}
beforeRoot := builder.scratch
paths := unionCLIHistoryPaths(beforeRoot, root)
for _, path := range paths {
beforeNode, before := documentAtPath(beforeRoot, path)
afterNode, after := documentAtPath(root, path)
sensitive := beforeNode != nil && beforeNode.Sensitive || afterNode != nil && afterNode.Sensitive
builder.addObservation(historyObservation{
path: path, phase: phase, payload: payload,
reportedPath: path, role: historyRoleInventory,
sensitive: sensitive, attributionRef: noHistoryAttribution,
})
if configurationValuesEqual(beforeNode, afterNode) {
continue
}
attributionRef := noHistoryAttribution
if after {
origin, found := origins[path.String()]
if !found {
origin = Origin{Layer: LayerDefault}
}
origin.Operation = ChangeDecoding
attributionRef = builder.addAttribution(origin, []string{reason}, nil)
}
builder.addObservation(historyObservation{
path: path, phase: phase, payload: payload,
reportedPath: path, role: historyRoleCheckpoint,
beforePresent: before, afterPresent: after, sensitive: sensitive,
attributionRef: attributionRef,
})
}
builder.advanceScratch(document.Clone(root))
}
func (builder *historyBuilder) observeCLIEvidence(applications []historyApplication, records []overlayRecord) {
builder.requireOpen()
count := max(len(applications), len(records))
if count == 0 {
return
}
index := new(mappingIndex)
for item := 0; item < count; item++ {
var applicationRef historyPayloadRef
hasApplication := false
if item < len(applications) {
application := applications[item]
applicationRef = builder.addCLIPayload(&application, nil)
hasApplication = true
walkHistoryApplication(application, func(_, effective Path, node *document.Node) {
origin := sourceApplicationOrigin(application, node)
attribution := builder.addAttribution(origin, nil, nil)
builder.addObservation(historyObservation{
path: effective, phase: historyPhaseCLI, payload: applicationRef,
reportedPath: effective, role: historyRoleContribution,
afterPresent: true, sensitive: node.Sensitive,
attributionRef: attribution,
})
})
applyHistoryApplication(&builder.scratch, application, index)
}
if item < len(records) {
record := records[item]
payload := builder.addCLIPayload(nil, &record)
if hasApplication {
owned := &builder.model.cliPayloads[payload.id]
owned.applicationRef = applicationRef
owned.hasApplicationRef = true
}
attribution := builder.addAttribution(record.change.Origin, nil, nil)
for _, displaced := range record.displaced {
builder.addObservation(historyObservation{
path: displaced.path, phase: historyPhaseCLI, payload: payload,
reportedPath: record.change.Path, role: historyRoleOperation,
beforePresent: true, attributionRef: attribution,
})
}
for _, introduced := range record.introduced {
builder.addObservation(historyObservation{
path: introduced.path, phase: historyPhaseCLI, payload: payload,
reportedPath: record.change.Path, role: historyRoleOperation,
afterPresent: true, sensitive: introduced.sensitive,
attributionRef: attribution,
})
}
}
}
}
func (builder *historyBuilder) addCLIPayload(application *historyApplication, record *overlayRecord) historyPayloadRef {
payload := builder.nextPayload(historyPhaseCLI)
if payload.id != len(builder.model.cliPayloads) {
panic("configkit: CLI payload identifiers are out of order")
}
owned := historySourcePayload{}
switch {
case application != nil:
owned.kind = historySourceApplicationPayload
owned.application = cloneHistoryApplications([]historyApplication{*application})[0]
case record != nil:
owned.kind = historySourceOverlayPayload
owned.overlay = cloneOverlayRecords([]overlayRecord{*record})[0]
default:
panic("configkit: CLI payload requires an application or operation")
}
builder.model.cliPayloads = append(builder.model.cliPayloads, owned)
return payload
}
func unionCLIHistoryPaths(roots ...*document.Node) []Path {
var paths []Path
seen := make(map[string][]Path)
for _, root := range roots {
if root == nil {
continue
}
walkDocument(root, Path{}, func(path Path, _ *document.Node) {
for _, existing := range seen[path.String()] {
if sameConcreteHistoryPath(existing, path) {
return
}
}
owned := path.clone()
paths = append(paths, owned)
seen[path.String()] = append(seen[path.String()], owned)
})
}
return paths
}