package source import ( "crypto/sha256" "encoding/hex" "encoding/json" "fmt" ) // DigestDocument returns a deterministic digest of the canonical source // document content. The existing Digest field is excluded from its own digest. func DigestDocument(doc *SourceDocument) (string, error) { if doc == nil { return "", fmt.Errorf("source document must not be nil") } payload := struct { ID string `json:"id"` Kind string `json:"kind"` Format string `json:"format"` Units []SourceUnit `json:"units"` Metadata map[string]any `json:"metadata,omitempty"` }{ ID: doc.ID, Kind: doc.Kind, Format: doc.Format, Units: doc.Units, Metadata: doc.Metadata, } encoded, err := json.Marshal(payload) if err != nil { return "", fmt.Errorf("encode source document for digest: %w", err) } sum := sha256.Sum256(encoded) return "sha256:" + hex.EncodeToString(sum[:]), nil } // DigestChunk returns a deterministic digest of a chunk, including its source // provenance, content, units, and metadata. func DigestChunk(chunk Chunk) (string, error) { payload := struct { ID string `json:"id"` SourceID string `json:"source_id"` Index int `json:"index"` Ref SourceRef `json:"ref"` Content []byte `json:"content"` MediaType string `json:"media_type"` Units []SourceUnit `json:"units"` Metadata map[string]any `json:"metadata,omitempty"` }{ ID: chunk.ID, SourceID: chunk.SourceID, Index: chunk.Index, Ref: chunk.Ref, Content: chunk.Content, MediaType: chunk.MediaType, Units: chunk.Units, Metadata: chunk.Metadata, } encoded, err := json.Marshal(payload) if err != nil { return "", fmt.Errorf("encode source chunk for digest: %w", err) } sum := sha256.Sum256(encoded) return "sha256:" + hex.EncodeToString(sum[:]), nil }