package contracts import ( "crypto/sha256" "encoding/hex" ) // ArtifactKind is the stable logical identity of a domain artifact. type ArtifactKind string // ArtifactSchema describes the durable representation owned by an artifact // codec. JSONSchema is cloned whenever framework ownership changes. type ArtifactSchema struct { ID string `json:"id"` Name string `json:"name"` Version string `json:"version"` JSONSchema []byte `json:"-"` } // SerializedArtifact is the domain-neutral representation of a typed // artifact at an explicit serialization boundary. type SerializedArtifact struct { Kind ArtifactKind `json:"kind"` Schema ArtifactSchema `json:"schema"` MediaType string `json:"media_type"` Content []byte `json:"-"` Metadata map[string]any `json:"metadata,omitempty"` } // SerializedOutput associates a domain-neutral artifact with the pipeline // operation that produced it. Provenance remains outside codec-owned bytes. type SerializedOutput struct { StepID string `json:"step_id,omitempty"` LaneID string `json:"lane_id"` NormalizerKey string `json:"normalizer_key"` SourceID string `json:"source_id,omitempty"` Artifact SerializedArtifact `json:"artifact"` } // ArtifactCodec owns the stable encoding for one concrete artifact type. type ArtifactCodec[T any] interface { Kind() ArtifactKind Schema() ArtifactSchema MediaType() string // EncodeCandidate serializes a stage result before semantic validation. It // must not apply validity checks owned by typed validators; Encode remains // the strict final-artifact boundary used after validation succeeds. EncodeCandidate(T) ([]byte, error) Encode(T) ([]byte, error) Decode([]byte) (T, error) } // DigestArtifactSchema returns the SHA-256 digest of the exact JSON Schema // bytes. Schema formatting is therefore part of the registered identity. func DigestArtifactSchema(schema ArtifactSchema) string { sum := sha256.Sum256(schema.JSONSchema) return "sha256:" + hex.EncodeToString(sum[:]) } func CloneArtifactSchema(schema ArtifactSchema) ArtifactSchema { schema.JSONSchema = append([]byte(nil), schema.JSONSchema...) return schema } func CloneSerializedArtifact(artifact SerializedArtifact) SerializedArtifact { artifact.Schema = CloneArtifactSchema(artifact.Schema) artifact.Content = append([]byte(nil), artifact.Content...) artifact.Metadata = cloneArtifactMetadata(artifact.Metadata) return artifact } // CloneSerializedArtifactPointer returns an independently owned artifact when // one is present. func CloneSerializedArtifactPointer(artifact *SerializedArtifact) *SerializedArtifact { if artifact == nil { return nil } cloned := CloneSerializedArtifact(*artifact) return &cloned } func CloneSerializedOutput(output SerializedOutput) SerializedOutput { output.Artifact = CloneSerializedArtifact(output.Artifact) return output } func cloneArtifactMetadata(metadata map[string]any) map[string]any { if len(metadata) == 0 { return nil } out := make(map[string]any, len(metadata)) for key, value := range metadata { out[key] = value } return out }