Build evidence context for output encoders
This commit is contained in:
103
internal/framework/pipeline/evidence_output.go
Normal file
103
internal/framework/pipeline/evidence_output.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/evidencecontext"
|
||||
)
|
||||
|
||||
// debugEvidenceContextSummary intentionally contains only publication-safe
|
||||
// identifiers and aggregate counts. The evidence document itself can include
|
||||
// source text and must never be written to this debug envelope.
|
||||
type debugEvidenceContextSummary struct {
|
||||
SourceID string `json:"source_id"`
|
||||
SelectedLanes []string `json:"selected_lanes"`
|
||||
WindowUnits int `json:"window_units"`
|
||||
ContextCount int `json:"context_count"`
|
||||
UnitCount int `json:"unit_count"`
|
||||
SourceDigest string `json:"source_digest"`
|
||||
}
|
||||
|
||||
// buildOutputEvidenceContext projects the prepared output policy from accepted
|
||||
// normalized artifacts. It is intentionally separate from lane execution so
|
||||
// checkpointed normalized outputs use the same reconstruction path.
|
||||
func buildOutputEvidenceContext(prepared *PreparedPipeline, doc *source.SourceDocument, outputs []contracts.SerializedOutput) (*contracts.SerializedArtifact, *debugEvidenceContextSummary, error) {
|
||||
if prepared == nil || prepared.evidencePlan == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
if doc == nil {
|
||||
return nil, nil, fmt.Errorf("evidence context output: source document is unavailable")
|
||||
}
|
||||
if prepared.artifactCodecs == nil {
|
||||
return nil, nil, fmt.Errorf("evidence context output: artifact codecs are unavailable")
|
||||
}
|
||||
|
||||
byLane := make(map[string]contracts.SerializedOutput, len(outputs))
|
||||
for _, output := range outputs {
|
||||
laneID := strings.TrimSpace(output.LaneID)
|
||||
if _, exists := byLane[laneID]; exists {
|
||||
return nil, nil, fmt.Errorf("evidence context output: accepted normalized outputs contain duplicate lane %q", laneID)
|
||||
}
|
||||
byLane[laneID] = contracts.CloneSerializedOutput(output)
|
||||
}
|
||||
|
||||
request := evidencecontext.BuildRequest{
|
||||
Source: doc,
|
||||
WindowUnits: prepared.evidencePlan.policy.WindowUnits,
|
||||
SelectedLanes: append([]string(nil), prepared.evidencePlan.policy.LaneIDs...),
|
||||
LaneEvidence: make([]evidencecontext.LaneEvidence, 0, len(prepared.evidencePlan.lanes)),
|
||||
}
|
||||
for _, lane := range prepared.evidencePlan.lanes {
|
||||
output, ok := byLane[lane.laneID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if output.SourceID != doc.ID {
|
||||
return nil, nil, fmt.Errorf("evidence context output lane %q: accepted normalized output source is incompatible", lane.laneID)
|
||||
}
|
||||
if output.Artifact.Kind != lane.kind {
|
||||
return nil, nil, fmt.Errorf("evidence context output lane %q: accepted normalized output artifact kind is incompatible", lane.laneID)
|
||||
}
|
||||
value, err := prepared.artifactCodecs.Decode(contracts.CloneSerializedArtifact(output.Artifact))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("evidence context output lane %q: accepted normalized artifact cannot be decoded", lane.laneID)
|
||||
}
|
||||
references, err := lane.project(value)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("evidence context output lane %q: accepted normalized artifact cannot be projected", lane.laneID)
|
||||
}
|
||||
request.LaneEvidence = append(request.LaneEvidence, evidencecontext.LaneEvidence{
|
||||
LaneID: lane.laneID,
|
||||
SourceRefs: append([]source.SourceRef(nil), references...),
|
||||
})
|
||||
}
|
||||
|
||||
document, err := evidencecontext.Build(request)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("evidence context output: accepted evidence references are invalid")
|
||||
}
|
||||
content, err := evidencecontext.New().Encode(document)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("evidence context output: evidence context serialization failed")
|
||||
}
|
||||
artifact := &contracts.SerializedArtifact{
|
||||
Kind: evidencecontext.ArtifactKind,
|
||||
Schema: evidencecontext.New().Schema(),
|
||||
MediaType: evidencecontext.MediaType,
|
||||
Content: content,
|
||||
}
|
||||
summary := debugEvidenceContextSummary{
|
||||
SourceID: document.SourceID,
|
||||
SelectedLanes: append([]string(nil), document.SelectedLanes...),
|
||||
WindowUnits: document.WindowUnits,
|
||||
ContextCount: len(document.Contexts),
|
||||
SourceDigest: document.SourceDigest,
|
||||
}
|
||||
for _, context := range document.Contexts {
|
||||
summary.UnitCount += len(context.Units)
|
||||
}
|
||||
return contracts.CloneSerializedArtifactPointer(artifact), &summary, nil
|
||||
}
|
||||
Reference in New Issue
Block a user