Centralize artifact source and publish destination policy

This commit is contained in:
2026-05-23 13:28:25 +00:00
parent 572a112c31
commit 7d584ee6cd
9 changed files with 319 additions and 115 deletions

View File

@@ -12,6 +12,7 @@ import (
"time"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/scriptorium"
"gitea.maximumdirect.net/eric/narratio/internal/artifactpolicy"
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
"gitea.maximumdirect.net/eric/narratio/internal/config"
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
@@ -549,12 +550,8 @@ func extractPlanNames(plans []analyzeArtifactExecutionPlan) []string {
}
func configuredArtifactNameFromSourceID(sourceID string) string {
trimmed := strings.TrimSpace(sourceID)
const prefix = "narratio.artifact."
if !strings.HasPrefix(trimmed, prefix) {
return ""
}
return strings.TrimPrefix(trimmed, prefix)
name, _ := artifactpolicy.ParseConfiguredSource(sourceID)
return name
}
func discoverProcessedTranscript(m *manifest.Manifest, paths artifacts.SessionPaths) (string, string, error) {
@@ -631,7 +628,11 @@ func resolveScriptoriumInput(
runtimeCatalog *artifacts.ArtifactCatalog,
) (string, bool, *artifacts.ResolvedSessionArtifact, error) {
source := strings.TrimSpace(inputCfg.Source)
if artifacts.IsPreviousSessionArtifactSource(source) {
classified, classifyErr := artifactpolicy.ClassifySource(source)
if classifyErr != nil {
return "", false, nil, classifyErr
}
if classified.Kind == artifactpolicy.SourceKindPreviousArtifact {
resolved, err := artifacts.ResolvePreviousSessionArtifactWithCatalog(paths, m, source, runtimeCatalog)
if err == nil {
copy := resolved
@@ -656,17 +657,13 @@ func resolveScriptoriumInput(
return resolved.Path, true, &copy, nil
}
if errors.Is(err, artifacts.ErrSessionArtifactNotFound) {
if artifacts.IsConfiguredArtifactSource(source) {
if classified.Kind == artifactpolicy.SourceKindConfiguredArtifact {
if inputCfg.Required {
return "", false, nil, fmt.Errorf("configured artifact source %q is unavailable", source)
}
return "", false, nil, nil
}
normalized, normalizeErr := artifacts.NormalizeSessionArtifactSource(source)
if normalizeErr != nil {
return "", false, nil, normalizeErr
}
switch normalized {
switch classified.ID {
case artifacts.ArtifactTranscriptPolished:
return "", false, nil, nil
case artifacts.ArtifactTranscriptFinal:

View File

@@ -13,10 +13,10 @@ import (
"time"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/storage"
"gitea.maximumdirect.net/eric/narratio/internal/artifactpolicy"
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
"gitea.maximumdirect.net/eric/narratio/internal/config"
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
"gitea.maximumdirect.net/eric/narratio/internal/pathsafe"
)
type archiveStage struct{}
@@ -359,15 +359,16 @@ func resolvePublishOutputs(
lockedOutputs := make([]publishLockedOutput, 0)
lockSet := archiveLockSet(locks)
selectedSet := archiveSelectedArtifactSet(selectedArtifactKeys)
configuredOutputs := configuredOutputPathMapFromCatalog(catalog)
for _, rule := range rules {
source := strings.TrimSpace(rule.Source)
required := rule.Required == nil || *rule.Required
dest, err := resolvePublishOutputDest(rule, catalog)
dest, err := resolvePublishOutputDest(rule, configuredOutputs)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("source %q: %w", source, err)
}
if len(selectedSet) > 0 {
if key, ok := artifacts.ConfiguredArtifactName(source); ok {
if key, ok := artifactpolicy.ParseConfiguredSource(source); ok {
if _, selected := selectedSet[key]; !selected {
skippedUnselectedOutputs = append(skippedUnselectedOutputs, publishSkippedUnselectedOutput{
Source: source,
@@ -452,19 +453,22 @@ func archiveLockSet(locks []config.PublishLockRule) map[string]config.PublishLoc
return out
}
func resolvePublishOutputDest(rule config.PublishOutputRule, catalog *artifacts.ArtifactCatalog) (string, error) {
dest := strings.TrimSpace(rule.Dest)
if dest == "" {
entry, ok := catalog.Lookup(strings.TrimSpace(rule.Source))
if !ok {
return "", fmt.Errorf("destination omitted and source is unknown")
}
dest = strings.TrimSpace(entry.CanonicalRelPath)
if dest == "" {
return "", fmt.Errorf("destination omitted and no canonical destination is available")
}
func resolvePublishOutputDest(rule config.PublishOutputRule, configured map[string]string) (string, error) {
return artifactpolicy.ResolvePublishedDestination(rule.Source, rule.Dest, configured)
}
func configuredOutputPathMapFromCatalog(catalog *artifacts.ArtifactCatalog) map[string]string {
out := map[string]string{}
if catalog == nil {
return out
}
return pathsafe.NormalizeRelativeDestination(dest)
for _, entry := range catalog.ListConfigured() {
if strings.TrimSpace(entry.ConfiguredKey) == "" {
continue
}
out[entry.ConfiguredKey] = strings.TrimSpace(entry.CanonicalRelPath)
}
return out
}
func buildArchiveRuntimeArtifactCatalog(