package engine import ( "slices" "sort" "go.maximumdirect.net/configkit/internal/document" ) type historySelection struct { path Path phase historyPhase payload historyPayloadRef reportedPath Path role historyRole before bool after bool attribution historyAttribution hasAttribution bool previous []Origin } type historySensitivityCandidate struct { path Path sensitive bool classify bool } func (model *historyModel) selectHistory(path Path) []historySelection { if model == nil || !path.valid() || !model.hasObservedShape(path) { return nil } candidateSet := make(map[int]struct{}) for _, observationID := range model.observationsByPointer[path.String()] { candidateSet[observationID] = struct{}{} } // Producers register all affected before/after paths. Exact facts cover // ancestor replacements; descendants cover operations within an enclosing // query. Logical operations are still deduplicated by payload below. model.affectedPathIndex.visitDescendants(path.String(), func(observationID int) { candidateSet[observationID] = struct{}{} }) candidates := make([]int, 0, len(candidateSet)) for observationID := range candidateSet { candidates = append(candidates, observationID) } slices.Sort(candidates) selections := make([]historySelection, 0, len(candidates)) selectionByPayload := make(map[historyPayloadRef]int) for _, observationID := range candidates { observation := model.observations[observationID] if observation.role == historyRoleInventory { continue } reportedPath := observation.reportedPath attributionRef := observation.attributionRef if observation.role == historyRoleContribution { if !historyObservationMatchesQuery(path, observation.path) { continue } application, found := model.applicationPayload(observation.payload) if !found { continue } if pathContains(observation.path, path) && len(observation.path.segments) < len(path.segments) { local, ok := historyApplicationLocalPath(application, path) if !ok { continue } if _, present := documentAtPathWithLookup(application.value, local, model.documentLookup); !present { continue } } if operationPath, relevant := applicationOperationPath(application, path, model.documentLookup); relevant { reportedPath = operationPath } } else if !historyObservationMatchesQuery(path, observation.path) { continue } selectionIndex, found := selectionByPayload[observation.payload] if !found { selectionIndex = len(selections) selectionByPayload[observation.payload] = selectionIndex selection := historySelection{ path: path.clone(), phase: observation.phase, payload: observation.payload, reportedPath: reportedPath.clone(), role: observation.role, } if attribution, ok := model.resolveAttribution(attributionRef); ok { selection.attribution = attribution selection.hasAttribution = true } selections = append(selections, selection) } selection := &selections[selectionIndex] selection.before = selection.before || observation.beforePresent selection.after = selection.after || observation.afterPresent } if len(selections) == 0 { return nil } var previous *Origin for index := range selections { selection := &selections[index] if selection.hasAttribution && metadataHistoryRole(selection.role) { if previous != nil { selection.previous = []Origin{cloneOrigin(*previous)} } origin := cloneOrigin(selection.attribution.origin) previous = &origin } } return selections } func historyObservationMatchesQuery(query, observed Path) bool { if matchesPathShape(query, observed) { return true } return pathContains(query, observed) && pathShapePrefixMatches(query, observed) } func (model *historyModel) hasObservedShape(path Path) bool { if model == nil || !path.valid() { return false } for _, candidate := range model.shapesByPointer[path.String()] { if matchesPathShape(path, candidate) { return true } } return false } func (model *historyModel) concreteShapes(pointer string) []Path { if model == nil { return nil } shapes := model.shapesByPointer[pointer] cloned := make([]Path, len(shapes)) for index, path := range shapes { cloned[index] = path.clone() } return cloned } func (model *historyModel) observedPaths() []Path { if model == nil { return nil } paths := make([]Path, len(model.shapeOrder)) for index, path := range model.shapeOrder { paths[index] = path.clone() } return paths } func (model *historyModel) historicalPaths() []Path { if model == nil { return []Path{} } pointers := make([]string, 0, len(model.shapesByPointer)) for pointer := range model.shapesByPointer { pointers = append(pointers, pointer) } sort.Strings(pointers) paths := make([]Path, 0, len(pointers)) for _, pointer := range pointers { path, err := ParsePath(pointer) if err == nil { paths = append(paths, path) } } return paths } func (model *historyModel) latestHistoricalLocation(path Path) (Location, bool) { selections := model.selectHistory(path) for selectionIndex := len(selections) - 1; selectionIndex >= 0; selectionIndex-- { selection := selections[selectionIndex] if selection.hasAttribution { if location, found := latestOriginDocumentLocation(selection.attribution.origin); found { return Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true } } for previousIndex := len(selection.previous) - 1; previousIndex >= 0; previousIndex-- { if location, found := latestOriginDocumentLocation(selection.previous[previousIndex]); found { return Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true } } } return Location{}, false } func (model *historyModel) sensitivityCandidates(inherited []secretDependency) []historySensitivityCandidate { if model == nil { return nil } candidates := make([]historySensitivityCandidate, 0, len(model.observations)) indices := make(map[string][]int) seenAttributions := make(map[historyAttributionRef]struct{}) add := func(path Path, sensitive, classify bool) { if !path.valid() { return } pointer := path.String() for _, index := range indices[pointer] { if slices.Equal(candidates[index].path.segments, path.segments) { candidates[index].sensitive = candidates[index].sensitive || sensitive candidates[index].classify = candidates[index].classify || classify return } } index := len(candidates) candidates = append(candidates, historySensitivityCandidate{path: path.clone(), sensitive: sensitive, classify: classify}) indices[pointer] = append(indices[pointer], index) } for _, observation := range model.observations { concreteCandidate := observation.role == historyRoleControl if observation.role == historyRoleContribution { if application, found := model.applicationPayload(observation.payload); found && len(application.sequences) != 0 { concreteCandidate = true } } add(observation.path, observation.sensitive, concreteCandidate) if parsed, err := ParsePath(observation.path.String()); err == nil { add(parsed, observation.sensitive, true) } if _, seen := seenAttributions[observation.attributionRef]; seen { continue } seenAttributions[observation.attributionRef] = struct{}{} if attribution, found := model.resolveAttribution(observation.attributionRef); found { for _, dependency := range attribution.dependencies { if dependency.valid() { add(dependency, false, true) } } } } for _, link := range inherited { add(link.target, false, true) add(link.dependency, false, true) } return candidates } // sensitivityDependencies returns the original attribution edges and the // bidirectional links represented by inheritance payloads. It intentionally // does not derive edges from the public metadata projection, which may omit // evidence needed to keep historical values redacted. func (model *historyModel) sensitivityDependencies() (dependencies, inherited []secretDependency) { if model == nil { return nil, nil } // Every affected subtree fact borrows its operation's attribution. Expand // each logical target/dependency list once, not once for every descendant. type dependencyAttribution struct { ref historyAttributionRef target string } seen := make(map[dependencyAttribution]struct{}) for _, observation := range model.observations { if observation.phase == historyPhaseInheritance || observation.attributionRef == noHistoryAttribution { continue } key := dependencyAttribution{ref: observation.attributionRef, target: observation.reportedPath.String()} if _, found := seen[key]; found { continue } seen[key] = struct{}{} attribution, found := model.resolveAttribution(observation.attributionRef) if !found { continue } for _, dependency := range attribution.dependencies { if observation.reportedPath.valid() && dependency.valid() { dependencies = append(dependencies, secretDependency{ target: observation.reportedPath.clone(), dependency: dependency.clone(), }) } } } for _, payload := range model.inheritancePayloads { for _, operation := range payload.operations { if operation.control != nil { control := operation.control.path for _, contributor := range operation.controlOrigin.contributors { if control.valid() && contributor.source.valid() { inherited = append(inherited, secretDependency{ target: control.clone(), dependency: contributor.source.clone(), }) } } continue } if operation.transfer == nil || operation.parent == nil { continue } transfer := operation.transfer walkDocument(operation.parent, transfer.to, func(destination Path, _ *document.Node) { relative, ok := relativeHistoryPath(transfer.to, destination) if !ok { return } source := appendInheritancePath(transfer.from, relative) if _, found := operation.parentOrigins[source.String()]; !found { return } inherited = append(inherited, secretDependency{ target: destination.clone(), dependency: source, }) }) } } return dependencies, inherited } func (model *historyModel) resolveAttribution(ref historyAttributionRef) (historyAttribution, bool) { if model == nil || ref < 0 || int(ref) >= len(model.attributions) { return historyAttribution{}, false } attribution := model.attributions[ref] return historyAttribution{ origin: cloneOrigin(attribution.origin), reasons: append([]string(nil), attribution.reasons...), dependencies: clonePaths(attribution.dependencies), }, true } func (model *historyModel) applicationPayload(ref historyPayloadRef) (historyApplication, bool) { if model == nil || ref.id < 0 { return historyApplication{}, false } var payloads []historySourcePayload switch ref.phase { case historyPhaseSources: payloads = model.sourcePayloads case historyPhaseCLI: payloads = model.cliPayloads default: return historyApplication{}, false } if ref.id >= len(payloads) || payloads[ref.id].kind != historySourceApplicationPayload { return historyApplication{}, false } return payloads[ref.id].application, true } func pathShapePrefixMatches(left, right Path) bool { length := min(len(left.segments), len(right.segments)) leftPrefix := Path{segments: clonePathSegments(left.segments[:length], 0)} rightPrefix := Path{segments: clonePathSegments(right.segments[:length], 0)} return matchesPathShape(leftPrefix, rightPrefix) } func metadataHistoryRole(role historyRole) bool { switch role { case historyRoleDefinition, historyRoleCompositeDefinition, historyRoleOperation, historyRoleControl: return true default: return false } }