package artifacts import ( "encoding/json" "os" "path/filepath" "strings" "gitea.maximumdirect.net/eric/narratio/internal/fileops" "gitea.maximumdirect.net/eric/narratio/internal/manifest" ) type ExtractionEvidenceState string const ( ExtractionEvidenceValid ExtractionEvidenceState = "valid" ExtractionEvidenceAbsent ExtractionEvidenceState = "absent" ExtractionEvidenceObsolete ExtractionEvidenceState = "obsolete" ExtractionEvidenceUnsafe ExtractionEvidenceState = "unsafe" ) // ExtractionEvidence is a policy-neutral proof of configured extraction output. type ExtractionEvidence struct { State ExtractionEvidenceState Reason, ProducerRunID string Outputs map[string]string } // InspectExtractionEvidence verifies structure, confinement, identities, contracts, and payload bytes. func InspectExtractionEvidence( paths SessionPaths, m *manifest.Manifest, configured map[string]ExtractionArtifactDefinition, input ExtractionInputIdentity, ) ExtractionEvidence { if m == nil || len(configured) == 0 { return ExtractionEvidence{State: ExtractionEvidenceAbsent, Reason: "extraction evidence is absent"} } r := m.Stages[extractStageName] if r == nil || r.Name != extractStageName || r.Status != manifest.StatusSucceeded { return ExtractionEvidence{State: ExtractionEvidenceAbsent, Reason: "extract stage has no succeeded result"} } if !extractionInputMatchesMetadata(r.Metadata, input) { return ExtractionEvidence{State: ExtractionEvidenceObsolete, Reason: "extract result does not match the current trimmed transcript"} } runID := extractionMetadataString(r.Metadata, extractionMetadataRun) if !safeExtractionPathSegment(runID) { return ExtractionEvidence{State: ExtractionEvidenceObsolete, Reason: "extract result has an invalid producing run ID"} } root := filepath.Clean(filepath.Join(paths.ArtifactsDir, "notarius", runID)) if !filepath.IsAbs(root) || extractionMetadataString(r.Metadata, extractionMetadataRoot) != root { return ExtractionEvidence{State: ExtractionEvidenceObsolete, Reason: "extract result does not identify its canonical immutable bundle"} } info, err := os.Lstat(root) if os.IsNotExist(err) { return ExtractionEvidence{State: ExtractionEvidenceObsolete, Reason: "immutable Notarius bundle is missing"} } if err != nil || info.Mode()&os.ModeSymlink != 0 { return ExtractionEvidence{State: ExtractionEvidenceUnsafe, Reason: "immutable Notarius bundle is unsafe"} } if !info.IsDir() { return ExtractionEvidence{State: ExtractionEvidenceObsolete, Reason: "immutable Notarius bundle is not a directory"} } if !safeExistingExtractionDirectory(paths.Root, root) { return ExtractionEvidence{State: ExtractionEvidenceUnsafe, Reason: "immutable Notarius bundle is unsafe"} } receiptRunID, receiptPipelineID := extractionReceiptIdentity(r.Metadata) if receiptRunID == "" || receiptPipelineID == "" { return ExtractionEvidence{State: ExtractionEvidenceObsolete, Reason: "extract result has incompatible Notarius receipt identity"} } expected := make(map[string]ExtractionArtifactDefinition, len(configured)) for key, d := range configured { expected[ExtractionArtifactSourceID(key)] = d } seen, outputs := map[string]struct{}{}, map[string]string{} indexSeen := false for _, out := range r.Outputs { if strings.TrimSpace(out.ProducerRunID) != runID { return ExtractionEvidence{State: ExtractionEvidenceObsolete, Reason: "extract output producer identity is inconsistent"} } if out.SourceID == "" { if indexSeen || out.Kind != extractionIndexKind || filepath.Clean(out.LocalPath) != filepath.Join(root, "index.json") { return ExtractionEvidence{State: ExtractionEvidenceObsolete, Reason: "extract index path is not canonical"} } if state, reason := inspectExtractionPayload(root, out.LocalPath, out.Checksum); state != ExtractionEvidenceValid { return ExtractionEvidence{State: state, Reason: reason} } indexSeen = true continue } d, ok := expected[out.SourceID] if !ok || out.Kind != extractionLaneKind { return ExtractionEvidence{State: ExtractionEvidenceObsolete, Reason: "extract result source set differs from current configuration"} } if _, duplicate := seen[out.SourceID]; duplicate { return ExtractionEvidence{State: ExtractionEvidenceObsolete, Reason: "extract result contains a duplicate configured source"} } if !compatibleCatalogExtractionContract(out.Contract, d) || !compatibleCatalogExtractionProvenance(out.ExternalProvenance, receiptRunID, receiptPipelineID, d) { return ExtractionEvidence{State: ExtractionEvidenceObsolete, Reason: "extract output contract or provenance is incompatible"} } if state, reason := inspectExtractionPayload(root, out.LocalPath, out.Checksum); state != ExtractionEvidenceValid { return ExtractionEvidence{State: state, Reason: reason} } seen[out.SourceID] = struct{}{} outputs[out.SourceID] = out.LocalPath } if !indexSeen || len(seen) != len(expected) || len(r.Outputs) != len(expected)+1 { return ExtractionEvidence{State: ExtractionEvidenceObsolete, Reason: "extract result is incomplete"} } return ExtractionEvidence{State: ExtractionEvidenceValid, ProducerRunID: runID, Outputs: outputs} } func inspectExtractionPayload(root, path, checksum string) (ExtractionEvidenceState, string) { if !filepath.IsAbs(path) || !pathWithinExtractionRoot(root, path) || strings.TrimSpace(checksum) == "" { return ExtractionEvidenceUnsafe, "extract output path or checksum is unsafe" } info, err := os.Lstat(path) if os.IsNotExist(err) { return ExtractionEvidenceObsolete, "extract output is missing" } if !safeExtractionComponents(root, path) { return ExtractionEvidenceUnsafe, "extract output path contains unsafe components" } if err != nil || !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 { return ExtractionEvidenceUnsafe, "extract output is not a regular file" } actual, err := SHA256File(path) if err != nil { return ExtractionEvidenceUnsafe, "extract output checksum cannot be read" } if actual != checksum { return ExtractionEvidenceObsolete, "extract output checksum does not match durable bytes" } body, err := fileops.ReadRegularFile(path, MaxExtractionPayloadBytes) if err != nil || !json.Valid(body) { return ExtractionEvidenceObsolete, "extract output is not valid JSON" } return ExtractionEvidenceValid, "" }