package engine import "gitea.maximumdirect.net/eric/ConfigKit/internal/document" type historyPathOrigin struct { path Path origin Origin sensitive bool } type historySourcePayloadKind uint8 const ( historySourceApplicationPayload historySourcePayloadKind = iota historySourceOverlayPayload historySourceSummaryPayload ) // historySourcePayload keeps source values and replacement facts under the // shared model's ownership. Application documents remain shared when immutable. type historySourcePayload struct { kind historySourcePayloadKind application historyApplication overlay overlayRecord applicationRef historyPayloadRef hasApplicationRef bool path Path origin Origin contributions []historyPayloadRef } func (builder *historyBuilder) observeBaseline(root *document.Node) { builder.requireOpen() if root == nil { return } builder.model.baseline = root payload := builder.nextPayload(historyPhaseBaseline) defaultAttribution := builder.addAttribution(Origin{Layer: LayerDefault}, nil, nil) walkDocument(root, Path{}, func(path Path, node *document.Node) { builder.addObservation(historyObservation{ path: path, phase: historyPhaseBaseline, payload: payload, reportedPath: path, role: historyRoleDefinition, afterPresent: true, sensitive: node.Sensitive, attributionRef: defaultAttribution, }) }) } // observeSourceEvidence registers raw source contributions, replacement sides, // and the surviving structural definition summaries in producer order. func (builder *historyBuilder) observeSourceEvidence(root *document.Node, origins map[string]Origin, applications []historyApplication, records []overlayRecord) { builder.requireOpen() if len(applications) == 0 && len(records) == 0 { return } applicationsBySource := make(map[string]historyPayloadRef) applicationsByLayer := make(map[Layer][]historyPayloadRef) for index := range applications { application := applications[index] payload := builder.addSourcePayload(&application, nil) applicationsByLayer[application.layer] = append(applicationsByLayer[application.layer], payload) walkHistoryApplication(application, func(_, effective Path, node *document.Node) { if node.Location.SourceID != "" { applicationsBySource[node.Location.SourceID] = payload } origin := sourceApplicationOrigin(application, node) for _, location := range origin.Locations { if location.SourceID != "" { applicationsBySource[location.SourceID] = payload } } attribution := builder.addAttribution(origin, nil, nil) builder.addObservation(historyObservation{ path: effective, phase: historyPhaseSources, payload: payload, reportedPath: effective, role: historyRoleContribution, afterPresent: true, sensitive: node.Sensitive, attributionRef: attribution, }) }) } for index := range records { record := records[index] payload := builder.addSourcePayload(nil, &record) applicationRef, hasApplication := sourceApplicationForRecord(record, applicationsBySource, applicationsByLayer) if hasApplication { owned := &builder.model.sourcePayloads[payload.id] owned.applicationRef = applicationRef owned.hasApplicationRef = true } for displacedIndex, displaced := range record.displaced { if sourceOriginRepresentedByOperation(builder.model, displaced.path, displaced.origin) { continue } definitionPayload := payload definitionPayload.substep = displacedIndex + 1 definition := builder.addAttribution(displaced.origin, nil, nil) builder.addObservation(historyObservation{ path: displaced.path, phase: historyPhaseSources, payload: definitionPayload, reportedPath: displaced.path, role: historyRoleDefinition, beforePresent: true, attributionRef: definition, }) } attribution := builder.addAttribution(record.change.Origin, nil, nil) for _, displaced := range record.displaced { builder.addObservation(historyObservation{ path: displaced.path, phase: historyPhaseSources, payload: payload, reportedPath: record.change.Path, role: historyRoleOperation, beforePresent: true, attributionRef: attribution, }) } for _, introduced := range record.introduced { builder.addObservation(historyObservation{ path: introduced.path, phase: historyPhaseSources, payload: payload, reportedPath: record.change.Path, role: historyRoleOperation, afterPresent: true, sensitive: introduced.sensitive, attributionRef: attribution, }) } } if root == nil { return } walkDocument(root, Path{}, func(path Path, node *document.Node) { origin, found := origins[path.String()] if !found || origin.Layer == "" || sourceOriginRepresentedByOperation(builder.model, path, origin) { return } contributions := builder.sourceContributions(path, origin) payload := builder.addSourceSummary(path, origin, contributions) attribution := builder.addAttribution(origin, nil, nil) builder.addObservation(historyObservation{ path: path, phase: historyPhaseSources, payload: payload, reportedPath: path, role: historyRoleCompositeDefinition, afterPresent: true, sensitive: node.Sensitive, attributionRef: attribution, }) }) } func sourceApplicationForRecord(record overlayRecord, bySource map[string]historyPayloadRef, byLayer map[Layer][]historyPayloadRef) (historyPayloadRef, bool) { for _, location := range record.change.Origin.Locations { if reference, found := bySource[location.SourceID]; found && location.SourceID != "" { return reference, true } } applications := byLayer[record.change.Origin.Layer] if len(applications) == 1 { return applications[0], true } return historyPayloadRef{}, false } func (builder *historyBuilder) addSourcePayload(application *historyApplication, overlay *overlayRecord) historyPayloadRef { payload := builder.nextPayload(historyPhaseSources) if payload.id != len(builder.model.sourcePayloads) { panic("configkit: source payload identifiers are out of order") } owned := historySourcePayload{} switch { case application != nil: owned.kind = historySourceApplicationPayload owned.application = cloneHistoryApplications([]historyApplication{*application})[0] case overlay != nil: owned.kind = historySourceOverlayPayload owned.overlay = cloneOverlayRecords([]overlayRecord{*overlay})[0] default: panic("configkit: source payload requires a producer") } builder.model.sourcePayloads = append(builder.model.sourcePayloads, owned) return payload } func (builder *historyBuilder) addSourceSummary(path Path, origin Origin, contributions []historyPayloadRef) historyPayloadRef { payload := builder.nextPayload(historyPhaseSources) if payload.id != len(builder.model.sourcePayloads) { panic("configkit: source payload identifiers are out of order") } builder.model.sourcePayloads = append(builder.model.sourcePayloads, historySourcePayload{ kind: historySourceSummaryPayload, path: path.clone(), origin: cloneOrigin(origin), contributions: append([]historyPayloadRef(nil), contributions...), }) return payload } func (builder *historyBuilder) sourceContributions(path Path, summary Origin) []historyPayloadRef { var contributions []historyPayloadRef seen := make(map[historyPayloadRef]struct{}) for _, observationID := range builder.model.observationsByPointer[path.String()] { observation := builder.model.observations[observationID] if observation.phase != historyPhaseSources || observation.role != historyRoleContribution || !sameConcreteHistoryPath(path, observation.path) { continue } attribution, ok := builder.model.resolveAttribution(observation.attributionRef) if !ok || !sourceLocationsContainedBy(attribution.origin, summary) { continue } if observation.payload.id < 0 || observation.payload.id >= len(builder.model.sourcePayloads) { continue } payload := builder.model.sourcePayloads[observation.payload.id] if payload.kind != historySourceApplicationPayload { continue } local, ok := historyApplicationLocalPath(payload.application, path) if !ok { continue } if _, exists := documentAtPath(payload.application.value, local); !exists { continue } if _, exists := seen[observation.payload]; exists { continue } seen[observation.payload] = struct{}{} contributions = append(contributions, observation.payload) } return contributions } func sourceLocationsContainedBy(candidate, summary Origin) bool { if len(candidate.Locations) == 0 { return false } available := make(map[Location]struct{}, len(summary.Locations)) for _, location := range summary.Locations { available[location] = struct{}{} } for _, location := range candidate.Locations { if _, found := available[location]; !found { return false } } return true } func sourceApplicationOrigin(application historyApplication, node *document.Node) Origin { origin := cloneOrigin(application.origin) origin.Layer = application.layer if node.Location.SourceID != "" { origin.Locations = []Location{publicLocation(node.Location)} } return origin } func sourceOriginRepresentedByOperation(model *historyModel, path Path, origin Origin) bool { represented := false model.reportedPathIndex.visitAncestors(path.String(), func(observationID int) { observation := model.observations[observationID] if observation.phase != historyPhaseSources || observation.role != historyRoleOperation { return } if !observation.afterPresent || !sameConcreteHistoryPath(path, observation.path) { return } operationOrigin, ok := model.resolveAttribution(observation.attributionRef) if !ok { return } if originRepresentedByChange(path, origin, Change{ Path: observation.reportedPath, Origin: operationOrigin.origin, }) { represented = true } }) return represented } func originRepresentedByChange(path Path, origin Origin, change Change) bool { if change.Path.String() == path.String() { return originContainedBy(origin, change.Origin) } if !pathContains(change.Path, path) || origin.Layer != change.Origin.Layer || origin.Operation != change.Origin.Operation { return false } // A subtree replacement records descendant contributions only when their // source identities are present in the ancestor operation attribution. recordedSources := make(map[string]struct{}, len(change.Origin.Locations)) for _, recorded := range change.Origin.Locations { recordedSources[recorded.SourceID] = struct{}{} } for _, location := range origin.Locations { if _, found := recordedSources[location.SourceID]; !found { return false } } return true }