Normalize typed CLI history evidence
This commit is contained in:
156
internal/engine/history_cli.go
Normal file
156
internal/engine/history_cli.go
Normal file
@@ -0,0 +1,156 @@
|
||||
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: document.Clone(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++ {
|
||||
if item < len(applications) {
|
||||
application := applications[item]
|
||||
payload := builder.addCLIPayload(&application, nil)
|
||||
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: payload,
|
||||
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)
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user