package config import ( "crypto/sha256" "encoding/hex" "fmt" "gopkg.in/yaml.v3" ) const pipelineDefaultOwnershipSource = "default" // PipelineProfileProvenance identifies the profile selected while resolving a // pipeline. It contains only non-secret selection metadata. type PipelineProfileProvenance struct { Name string Source string OverlayPath string } // SelectedPipelineProfile reports the profile used to resolve cfg, if any. func SelectedPipelineProfile(cfg *PipelineConfig) (*PipelineProfileProvenance, bool) { if cfg == nil || cfg.resolution == nil || cfg.resolution.selectedProfile == nil { return nil, false } selection := cfg.resolution.selectedProfile return &PipelineProfileProvenance{Name: selection.name, Source: selection.source, OverlayPath: selection.overlayPath}, true } // EffectivePipelineDigest reports the deterministic secret-free digest for a // resolved pipeline. func EffectivePipelineDigest(cfg *PipelineConfig) string { if cfg == nil || cfg.resolution == nil { return "" } return cfg.resolution.effectiveDigest } func finalizePipelineResolution(cfg *PipelineConfig) error { if cfg == nil || cfg.resolution == nil { return fmt.Errorf("pipeline resolution metadata is required") } declared := make(map[string][]string, len(cfg.resolution.ownership)) for _, ownership := range cfg.resolution.ownership { declared[ownership.path] = append([]string(nil), ownership.sources...) } data, err := yaml.Marshal(cfg) if err != nil { return fmt.Errorf("serialize normalized effective pipeline: %w", err) } document, err := parseCompositionBytes("normalized effective pipeline", data) if err != nil { return err } records, err := document.semanticRecords() if err != nil { return err } ownership := make([]pipelineFieldOwnership, 0, len(records)) for _, record := range records { sources := declared[record.Path] if len(sources) == 0 { sources = []string{pipelineDefaultOwnershipSource} } ownership = append(ownership, pipelineFieldOwnership{ path: record.Path, sources: append([]string(nil), sources...), }) } cfg.resolution.ownership = ownership return recomputePipelineEffectiveDigest(cfg) } // recomputePipelineEffectiveDigest is the single package-owned hook for // refreshing provenance after later resolution expands concrete pipeline // values. Composition declarations and runtime provenance are not serialized. func recomputePipelineEffectiveDigest(cfg *PipelineConfig) error { if cfg == nil || cfg.resolution == nil { return fmt.Errorf("pipeline resolution metadata is required") } data, err := yaml.Marshal(cfg) if err != nil { return fmt.Errorf("serialize normalized effective pipeline: %w", err) } document, err := parseCompositionBytes("normalized effective pipeline", data) if err != nil { return err } canonical, err := document.canonicalDigestInput() if err != nil { return err } digest := sha256.Sum256(canonical) cfg.resolution.effectiveDigest = hex.EncodeToString(digest[:]) return nil }