Harden pipeline state and plan release upgrades

This commit is contained in:
2026-08-30 18:51:20 +00:00
parent 3da97ca50c
commit c812fe3655
18 changed files with 1381 additions and 1237 deletions

View File

@@ -4,6 +4,8 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
@@ -77,7 +79,14 @@ func recomputePipelineEffectiveDigest(cfg *PipelineConfig) error {
if cfg == nil || cfg.resolution == nil {
return fmt.Errorf("pipeline resolution metadata is required")
}
data, err := yaml.Marshal(cfg)
digestConfig := *cfg
if cfg.Notarius != nil && cfg.resolution.logicalNotariusCaptured {
notarius := *cfg.Notarius
notarius.ConfigPath = cfg.resolution.logicalNotariusConfig
notarius.WorkingDirectory = cfg.resolution.logicalNotariusWorking
digestConfig.Notarius = &notarius
}
data, err := yaml.Marshal(&digestConfig)
if err != nil {
return fmt.Errorf("serialize normalized effective pipeline: %w", err)
}
@@ -93,3 +102,28 @@ func recomputePipelineEffectiveDigest(cfg *PipelineConfig) error {
cfg.resolution.effectiveDigest = hex.EncodeToString(digest[:])
return nil
}
// captureLogicalNotariusPaths retains normalized user-facing path semantics
// before runtime resolution makes relative paths depend on the checkout or
// installation directory. Runtime paths remain absolute; provenance does not.
func captureLogicalNotariusPaths(cfg *PipelineConfig) {
if cfg == nil || cfg.resolution == nil || cfg.Notarius == nil {
return
}
configPath := normalizeLogicalFilesystemPath(cfg.Notarius.ConfigPath)
workingDirectory := normalizeLogicalFilesystemPath(cfg.Notarius.WorkingDirectory)
if cfg.Notarius.Enabled && workingDirectory == "" && configPath != "" {
workingDirectory = normalizeLogicalFilesystemPath(filepath.Dir(configPath))
}
cfg.resolution.logicalNotariusConfig = configPath
cfg.resolution.logicalNotariusWorking = workingDirectory
cfg.resolution.logicalNotariusCaptured = true
}
func normalizeLogicalFilesystemPath(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return ""
}
return filepath.ToSlash(filepath.Clean(value))
}

View File

@@ -95,6 +95,7 @@ func LoadPipelineProfilePair(path, leftProfile, rightProfile string) (*PipelineC
func finalizeLoadedPipeline(path string, cfg *PipelineConfig) (*PipelineConfig, error) {
cfg.resolution.publishDeclared = cfg.Publish != nil
applyPipelineDefaults(cfg)
captureLogicalNotariusPaths(cfg)
if err := resolveNotariusPaths(cfg, path); err != nil {
return nil, fmt.Errorf("load pipeline config: %w", err)
}

View File

@@ -100,6 +100,36 @@ notarius:
}
}
func TestNotariusRelativePathsDoNotMakeEffectiveDigestLocationDependent(t *testing.T) {
pipelineYAML := testPipelineBaseYAML + `
notarius:
enabled: true
config_path: notarius/config.yml
pipeline_id: dnd-session
`
paths := make([]string, 2)
configs := make([]*PipelineConfig, 2)
for index := range paths {
dir := t.TempDir()
paths[index] = filepath.Join(dir, "pipeline.yml")
if err := os.WriteFile(paths[index], []byte(pipelineYAML), 0o644); err != nil {
t.Fatalf("write pipeline %d: %v", index, err)
}
loaded, err := LoadPipeline(paths[index])
if err != nil {
t.Fatalf("LoadPipeline(%d) error = %v", index, err)
}
configs[index] = loaded
}
if configs[0].Notarius.ConfigPath == configs[1].Notarius.ConfigPath ||
configs[0].Notarius.WorkingDirectory == configs[1].Notarius.WorkingDirectory {
t.Fatalf("runtime Notarius paths should remain location-specific: %#v / %#v", configs[0].Notarius, configs[1].Notarius)
}
if first, second := EffectivePipelineDigest(configs[0]), EffectivePipelineDigest(configs[1]); first == "" || first != second {
t.Fatalf("relocated logical configuration digests = %q / %q, want equal non-empty values", first, second)
}
}
func TestNotariusStrictYAML(t *testing.T) {
tests := []struct {
name string

View File

@@ -18,6 +18,9 @@ type pipelineResolutionMetadata struct {
sources []string
selectedProfile *pipelineProfileSelection
effectiveDigest string
logicalNotariusConfig string
logicalNotariusWorking string
logicalNotariusCaptured bool
ownership []pipelineFieldOwnership
artifactFamilies map[string]ScriptoriumArtifactFamilyConfig
artifactFamiliesExpanded bool