Replay explanations through shared history selections

This commit is contained in:
2026-10-26 17:01:32 +00:00
parent 4b4e15fc58
commit 738c8a9cc7
12 changed files with 386 additions and 135 deletions

View File

@@ -36,6 +36,9 @@ func (model *historyModel) selectHistory(path Path) []historySelection {
model.reportedPathIndex.visitAncestors(path.String(), func(observationID int) {
candidateSet[observationID] = struct{}{}
})
model.reportedPathIndex.visitDescendants(path.String(), func(observationID int) {
candidateSet[observationID] = struct{}{}
})
candidates := make([]int, 0, len(candidateSet))
for observationID := range candidateSet {
candidates = append(candidates, observationID)
@@ -46,7 +49,32 @@ func (model *historyModel) selectHistory(path Path) []historySelection {
selectionByPayload := make(map[historyPayloadRef]int)
for _, observationID := range candidates {
observation := model.observations[observationID]
if observation.role == historyRoleInventory || !matchesPathShape(path, observation.path) {
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]
@@ -57,10 +85,10 @@ func (model *historyModel) selectHistory(path Path) []historySelection {
path: path.clone(),
phase: observation.phase,
payload: observation.payload,
reportedPath: observation.reportedPath.clone(),
reportedPath: reportedPath.clone(),
role: observation.role,
}
if attribution, ok := model.resolveAttribution(observation.attributionRef); ok {
if attribution, ok := model.resolveAttribution(attributionRef); ok {
selection.attribution = attribution
selection.hasAttribution = true
}
@@ -88,6 +116,13 @@ func (model *historyModel) selectHistory(path Path) []historySelection {
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
@@ -187,6 +222,32 @@ func (model *historyModel) resolveAttribution(ref historyAttributionRef) (histor
}, 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: