Integrate artifact locks into publish

This commit is contained in:
2026-09-05 13:04:08 +00:00
parent b74e31e1cc
commit 4bc33773fa
13 changed files with 503 additions and 376 deletions

View File

@@ -529,32 +529,6 @@ func needsNotariusForRun(cfg *config.Config, stages []stage.Stage) bool {
return false
}
func needsRemoteLocksForRun(cfg *config.Config, stages []stage.Stage) bool {
if cfg == nil || cfg.Pipeline == nil || cfg.Session == nil {
return false
}
publishRequested := false
for _, s := range stages {
if s != nil && s.Name() == "publish" {
publishRequested = true
break
}
}
if !publishRequested {
return false
}
if cfg.Pipeline.Publish == nil {
return false
}
if cfg.Pipeline.Publish.Enabled != nil && !*cfg.Pipeline.Publish.Enabled {
return false
}
if cfg.Pipeline.Publish.UploadRun != nil && !*cfg.Pipeline.Publish.UploadRun {
return false
}
return cfg.Pipeline.Storage.S3 != nil
}
func configuredScriptoriumArtifacts(cfg *config.Config) map[string]config.ScriptoriumArtifactConfig {
if cfg == nil || cfg.Pipeline == nil || cfg.Pipeline.Scriptorium == nil {
return nil

View File

@@ -1,11 +1,9 @@
package app
import (
"context"
"fmt"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/notify"
"gitea.maximumdirect.net/eric/narratio/internal/config"
)
func composeInvocation(runtime *invocationRuntime) error {
@@ -47,21 +45,6 @@ func composeInvocation(runtime *invocationRuntime) error {
}
env.ObjectStore = objectStore
}
if needsRemoteLocksForRun(env.Config, stages) {
locks, err := dependencies.loadEffectiveLocks(runtime.ctx, env.Config, env.ObjectStore)
if err != nil {
return runtime.persistTerminalFailure(fmt.Errorf("load remote publish locks: %w", err))
}
applyEffectiveLocks(env.Config, locks.All)
staticLocks := append([]config.PublishLockRule(nil), locks.Static...)
env.RevalidatePublishLocks = func(recheckCtx context.Context) ([]config.PublishLockRule, error) {
remote, _, _, err := dependencies.loadRemoteLockStore(recheckCtx, env.Config, env.ObjectStore)
if err != nil {
return nil, err
}
return config.MergePublishLockRules(staticLocks, remote.Locks), nil
}
}
if env.Notifier == nil && stagesContainAny(stages, "notify") {
env.Notifier = &notify.NoopSender{}
}

View File

@@ -15,17 +15,15 @@ import (
)
type invocationDependencies struct {
now func() time.Time
newRunID func() (string, error)
newWhisperX func(*config.Config) (whisperx.Client, error)
newSeriatim func(*config.Config) (seriatim.Runner, error)
newAudita func(*config.Config) (audita.Runner, error)
newNotarius func() notarius.Runner
newScriptorium func() scriptorium.Runner
newObjectStore func(context.Context, *config.Config) (storage.ObjectStore, error)
loadEffectiveLocks func(context.Context, *config.Config, storage.ObjectStore) (*effectiveLocks, error)
loadRemoteLockStore func(context.Context, *config.Config, storage.ObjectStore) (*config.PublishLockStore, string, string, error)
removeScopedDir func(root, target, policy string) error
now func() time.Time
newRunID func() (string, error)
newWhisperX func(*config.Config) (whisperx.Client, error)
newSeriatim func(*config.Config) (seriatim.Runner, error)
newAudita func(*config.Config) (audita.Runner, error)
newNotarius func() notarius.Runner
newScriptorium func() scriptorium.Runner
newObjectStore func(context.Context, *config.Config) (storage.ObjectStore, error)
removeScopedDir func(root, target, policy string) error
}
func (dependencies invocationDependencies) normalized() invocationDependencies {
@@ -55,12 +53,6 @@ func (dependencies invocationDependencies) normalized() invocationDependencies {
return initializeObjectStoreBackend(ctx, cfg, storage.NewObjectStoreFromConfig)
}
}
if dependencies.loadEffectiveLocks == nil {
dependencies.loadEffectiveLocks = loadEffectiveLocks
}
if dependencies.loadRemoteLockStore == nil {
dependencies.loadRemoteLockStore = loadRemoteLockStore
}
if dependencies.removeScopedDir == nil {
dependencies.removeScopedDir = removeRunScopedDir
}

View File

@@ -537,16 +537,20 @@ func TestExecuteStagesPublishSkipsRequiredUnselectedConfiguredOutput(t *testing.
t.Fatalf("Load manifest error = %v", err)
}
meta := loadedManifest.Stages["publish"].Metadata
skipped, ok := meta["skipped_unselected_outputs"].([]any)
if !ok || len(skipped) != 1 {
t.Fatalf("skipped_unselected_outputs = %#v, want one item", meta["skipped_unselected_outputs"])
}
item, ok := skipped[0].(map[string]any)
outcomes, ok := meta["publish_output_outcomes"].([]any)
if !ok {
t.Fatalf("skipped item = %#v, want object", skipped[0])
t.Fatalf("publish_output_outcomes = %#v, want array", meta["publish_output_outcomes"])
}
if item["source"] != "narratio.artifact.session_recap" || item["dest"] != "artifacts/session_recap.md" || item["required"] != true {
t.Fatalf("skipped item = %#v, want required session_recap published output", item)
var item map[string]any
for _, raw := range outcomes {
candidate, candidateOK := raw.(map[string]any)
if candidateOK && candidate["source"] == "narratio.artifact.session_recap" {
item = candidate
break
}
}
if item == nil || item["status"] != "unselected" || item["destination"] != "artifacts/session_recap.md" {
t.Fatalf("publish output outcome = %#v, want unselected session_recap", item)
}
}