package artifacts import ( "crypto/sha256" "encoding/hex" "fmt" "path/filepath" "strings" "gitea.maximumdirect.net/eric/narratio/internal/fileops" "gitea.maximumdirect.net/eric/narratio/internal/manifest" ) const extractionMetadataInput = "direct_input" // ExtractionInputIdentity identifies the exact trimmed transcript used for extraction. type ExtractionInputIdentity struct { Path string `json:"-"` Checksum string `json:"checksum"` SourceID string `json:"source_id"` ProducerStage string `json:"producer_stage"` OutputKind string `json:"output_kind"` ProducerRunID string `json:"producer_run_id"` Provenance string `json:"provenance"` } // ResolveExtractionInputIdentity resolves and hashes the direct transcript input. func ResolveExtractionInputIdentity(paths SessionPaths, m *manifest.Manifest) (ExtractionInputIdentity, error) { resolved, err := ResolveSessionArtifact(paths, m, ArtifactTranscriptFinalTrimmed) if err != nil { return ExtractionInputIdentity{}, err } path, err := filepath.Abs(resolved.Path) if err != nil { return ExtractionInputIdentity{}, fmt.Errorf("resolve trimmed transcript path: %w", err) } bytes, err := fileops.ReadRegularFile(path, MaxResolvedArtifactBytes) if err != nil { return ExtractionInputIdentity{}, fmt.Errorf("read trimmed transcript: %w", err) } digest := sha256.Sum256(bytes) return ExtractionInputIdentity{ Path: filepath.Clean(path), Checksum: hex.EncodeToString(digest[:]), SourceID: resolved.ID, ProducerStage: resolved.ProducerStage, OutputKind: resolved.OutputKind, ProducerRunID: resolved.ProducerRunID, Provenance: resolved.Provenance, }, nil } // Metadata returns the durable fields needed to compare this identity on reuse. func (identity ExtractionInputIdentity) Metadata() map[string]any { return map[string]any{ "checksum": identity.Checksum, "source_id": identity.SourceID, "producer_stage": identity.ProducerStage, "output_kind": identity.OutputKind, "producer_run_id": identity.ProducerRunID, "provenance": identity.Provenance, } } func extractionInputMatchesMetadata(metadata map[string]any, current ExtractionInputIdentity) bool { stored, ok := metadata[extractionMetadataInput].(map[string]any) if !ok || strings.TrimSpace(current.Checksum) == "" || strings.TrimSpace(current.SourceID) == "" { return false } for key, want := range current.Metadata() { if extractionMetadataString(stored, key) != want { return false } } return true }