Make configured artifacts manifest authoritative
This commit is contained in:
134
internal/artifacts/analyze_evidence.go
Normal file
134
internal/artifacts/analyze_evidence.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/fileops"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/pathsafe"
|
||||
)
|
||||
|
||||
// AnalyzeEvidenceState distinguishes a verified current configured artifact
|
||||
// from every form of unavailable evidence.
|
||||
type AnalyzeEvidenceState string
|
||||
|
||||
const (
|
||||
AnalyzeEvidenceCurrent AnalyzeEvidenceState = "current"
|
||||
AnalyzeEvidenceNonCurrent AnalyzeEvidenceState = "non_current"
|
||||
)
|
||||
|
||||
// AnalyzeEvidence is the read-only result of inspecting one configured
|
||||
// artifact's manifest record and durable output.
|
||||
type AnalyzeEvidence struct {
|
||||
State AnalyzeEvidenceState
|
||||
Reason string
|
||||
SourceID string
|
||||
Path string
|
||||
ProducerRunID string
|
||||
}
|
||||
|
||||
// InspectAnalyzeEvidence verifies that one configured artifact has supported,
|
||||
// current manifest evidence for the exact canonical bytes on disk.
|
||||
func InspectAnalyzeEvidence(
|
||||
paths SessionPaths,
|
||||
m *manifest.Manifest,
|
||||
key string,
|
||||
configured ConfiguredArtifactDefinition,
|
||||
) AnalyzeEvidence {
|
||||
normalizedKey := strings.TrimSpace(key)
|
||||
sourceID := ConfiguredArtifactSourceID(normalizedKey)
|
||||
nonCurrent := func(reason string) AnalyzeEvidence {
|
||||
return AnalyzeEvidence{State: AnalyzeEvidenceNonCurrent, Reason: reason, SourceID: sourceID}
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
return nonCurrent("analyze manifest evidence is absent; regenerate the artifact")
|
||||
}
|
||||
stageRecord := m.Stages["analyze"]
|
||||
if stageRecord == nil || stageRecord.Name != "analyze" {
|
||||
return nonCurrent("analyze manifest evidence is absent; regenerate the artifact")
|
||||
}
|
||||
if !stageRecord.HasVersionedAnalyzeState() {
|
||||
return nonCurrent("analyze manifest evidence is legacy or unsupported; regenerate the artifact")
|
||||
}
|
||||
record, ok := stageRecord.AnalyzeArtifacts[normalizedKey]
|
||||
if !ok {
|
||||
return nonCurrent("configured artifact has no analyze manifest record; regenerate the artifact")
|
||||
}
|
||||
if record.Status != manifest.AnalyzeArtifactCurrent {
|
||||
return nonCurrent(fmt.Sprintf("configured artifact manifest status is %q; regenerate the artifact", record.Status))
|
||||
}
|
||||
if err := manifest.ValidateAnalyzeArtifactCollection(
|
||||
stageRecord.AnalyzeStateVersion,
|
||||
map[string]manifest.AnalyzeArtifactRecord{normalizedKey: record},
|
||||
); err != nil {
|
||||
return nonCurrent("configured artifact manifest evidence is malformed; regenerate the artifact")
|
||||
}
|
||||
|
||||
configuredPath, err := pathsafe.NormalizeRelativeDestination(strings.TrimSpace(configured.OutputPath))
|
||||
if err != nil {
|
||||
return nonCurrent("configured artifact output path is unsafe; correct the configuration")
|
||||
}
|
||||
if record.Output.LocalPath != configuredPath {
|
||||
return nonCurrent("configured artifact manifest path differs from current configuration; regenerate the artifact")
|
||||
}
|
||||
if record.Output.SourceID != sourceID || record.Output.Kind != "scriptorium_artifact" {
|
||||
return nonCurrent("configured artifact manifest identity is incompatible; regenerate the artifact")
|
||||
}
|
||||
|
||||
file, err := fileops.OpenConfinedRegularFile(paths.Root, configuredPath)
|
||||
if err != nil {
|
||||
return nonCurrent("configured artifact output is missing or unsafe; regenerate the artifact")
|
||||
}
|
||||
defer file.Close()
|
||||
info, err := file.Stat()
|
||||
if err != nil || !info.Mode().IsRegular() {
|
||||
return nonCurrent("configured artifact output is not a safe regular file; regenerate the artifact")
|
||||
}
|
||||
hash := sha256.New()
|
||||
size, err := io.Copy(hash, file)
|
||||
if err != nil {
|
||||
return nonCurrent("configured artifact output could not be verified; regenerate the artifact")
|
||||
}
|
||||
if size != info.Size() || size != record.OutputSize {
|
||||
return nonCurrent("configured artifact output size differs from manifest evidence; regenerate the artifact")
|
||||
}
|
||||
if hex.EncodeToString(hash.Sum(nil)) != record.Output.Checksum {
|
||||
return nonCurrent("configured artifact output checksum differs from manifest evidence; regenerate the artifact")
|
||||
}
|
||||
|
||||
return AnalyzeEvidence{
|
||||
State: AnalyzeEvidenceCurrent,
|
||||
SourceID: sourceID,
|
||||
Path: filepath.Join(paths.Root, filepath.FromSlash(configuredPath)),
|
||||
ProducerRunID: record.ProducerRunID,
|
||||
}
|
||||
}
|
||||
|
||||
// HydrateAnalyzeArtifacts makes configured sources available only from
|
||||
// validated current manifest evidence. It never mutates manifest state.
|
||||
func (c *ArtifactCatalog) HydrateAnalyzeArtifacts(
|
||||
paths SessionPaths,
|
||||
m *manifest.Manifest,
|
||||
configured map[string]ConfiguredArtifactDefinition,
|
||||
) {
|
||||
if c == nil || len(configured) == 0 {
|
||||
return
|
||||
}
|
||||
for _, entry := range c.ListConfigured() {
|
||||
definition, ok := configured[entry.ConfiguredKey]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
evidence := InspectAnalyzeEvidence(paths, m, entry.ConfiguredKey, definition)
|
||||
if evidence.State != AnalyzeEvidenceCurrent {
|
||||
continue
|
||||
}
|
||||
_ = c.markAvailableFromAnalyzeManifest(evidence.SourceID, evidence.Path, evidence.ProducerRunID)
|
||||
}
|
||||
}
|
||||
222
internal/artifacts/analyze_evidence_test.go
Normal file
222
internal/artifacts/analyze_evidence_test.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifactmodel"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
||||
)
|
||||
|
||||
func TestInspectAnalyzeEvidenceAcceptsCurrentCanonicalOutput(t *testing.T) {
|
||||
paths := buildSessionPaths(t.TempDir(), "campaign", "session")
|
||||
body := []byte("recap\n")
|
||||
m := analyzeEvidenceFixture(t, paths, "session_recap", "artifacts/session_recap.md", body)
|
||||
|
||||
got := InspectAnalyzeEvidence(paths, m, "session_recap", ConfiguredArtifactDefinition{OutputPath: "artifacts/session_recap.md"})
|
||||
if got.State != AnalyzeEvidenceCurrent {
|
||||
t.Fatalf("State = %q, reason = %q", got.State, got.Reason)
|
||||
}
|
||||
if got.SourceID != ConfiguredArtifactSourceID("session_recap") || got.Path != filepath.Join(paths.ArtifactsDir, "session_recap.md") || got.ProducerRunID != "run-1" {
|
||||
t.Fatalf("evidence = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectAnalyzeEvidenceRejectsNonCurrentAndInvalidEvidence(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mutate func(*manifest.Manifest)
|
||||
config ConfiguredArtifactDefinition
|
||||
reason string
|
||||
}{
|
||||
{name: "absent manifest", mutate: func(m *manifest.Manifest) { *m = manifest.Manifest{} }, reason: "absent"},
|
||||
{name: "legacy", mutate: func(m *manifest.Manifest) {
|
||||
m.Stages["analyze"].AnalyzeStateVersion = 0
|
||||
m.Stages["analyze"].AnalyzeArtifacts = nil
|
||||
}, reason: "legacy"},
|
||||
{name: "unsupported version", mutate: func(m *manifest.Manifest) { m.Stages["analyze"].AnalyzeStateVersion++ }, reason: "legacy or unsupported"},
|
||||
{name: "missing record", mutate: func(m *manifest.Manifest) { delete(m.Stages["analyze"].AnalyzeArtifacts, "session_recap") }, reason: "no analyze manifest record"},
|
||||
{name: "stale", mutate: analyzeEvidenceStatus(manifest.AnalyzeArtifactStale), reason: `status is "stale"`},
|
||||
{name: "missing", mutate: analyzeEvidenceStatus(manifest.AnalyzeArtifactMissing), reason: `status is "missing"`},
|
||||
{name: "failed", mutate: analyzeEvidenceStatus(manifest.AnalyzeArtifactFailed), reason: `status is "failed"`},
|
||||
{name: "unselected", mutate: analyzeEvidenceStatus(manifest.AnalyzeArtifactUnselected), reason: `status is "unselected"`},
|
||||
{name: "fingerprint version", mutate: func(m *manifest.Manifest) {
|
||||
m.Stages["analyze"].AnalyzeArtifacts["session_recap"] = mutateAnalyzeEvidenceRecord(m, func(r *manifest.AnalyzeArtifactRecord) { r.FingerprintVersion++ })
|
||||
}, reason: "malformed"},
|
||||
{name: "key mismatch", mutate: func(m *manifest.Manifest) {
|
||||
m.Stages["analyze"].AnalyzeArtifacts["session_recap"] = mutateAnalyzeEvidenceRecord(m, func(r *manifest.AnalyzeArtifactRecord) { r.Key = "other" })
|
||||
}, reason: "malformed"},
|
||||
{name: "source mismatch", mutate: func(m *manifest.Manifest) {
|
||||
m.Stages["analyze"].AnalyzeArtifacts["session_recap"] = mutateAnalyzeEvidenceRecord(m, func(r *manifest.AnalyzeArtifactRecord) { r.Output.SourceID = ConfiguredArtifactSourceID("other") })
|
||||
}, reason: "malformed"},
|
||||
{name: "missing contract", mutate: func(m *manifest.Manifest) {
|
||||
m.Stages["analyze"].AnalyzeArtifacts["session_recap"] = mutateAnalyzeEvidenceRecord(m, func(r *manifest.AnalyzeArtifactRecord) { r.Output.Contract = nil })
|
||||
}, reason: "malformed"},
|
||||
{name: "configured path mismatch", config: ConfiguredArtifactDefinition{OutputPath: "artifacts/renamed.md"}, reason: "differs from current configuration"},
|
||||
{name: "record path mismatch", mutate: func(m *manifest.Manifest) {
|
||||
m.Stages["analyze"].AnalyzeArtifacts["session_recap"] = mutateAnalyzeEvidenceRecord(m, func(r *manifest.AnalyzeArtifactRecord) { r.Output.LocalPath = "artifacts/other.md" })
|
||||
}, reason: "differs from current configuration"},
|
||||
{name: "size mismatch", mutate: func(m *manifest.Manifest) {
|
||||
m.Stages["analyze"].AnalyzeArtifacts["session_recap"] = mutateAnalyzeEvidenceRecord(m, func(r *manifest.AnalyzeArtifactRecord) { r.OutputSize++ })
|
||||
}, reason: "size differs"},
|
||||
{name: "checksum mismatch", mutate: func(m *manifest.Manifest) {
|
||||
m.Stages["analyze"].AnalyzeArtifacts["session_recap"] = mutateAnalyzeEvidenceRecord(m, func(r *manifest.AnalyzeArtifactRecord) { r.Output.Checksum = strings.Repeat("0", 64) })
|
||||
}, reason: "checksum differs"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
paths := buildSessionPaths(t.TempDir(), "campaign", "session")
|
||||
m := analyzeEvidenceFixture(t, paths, "session_recap", "artifacts/session_recap.md", []byte("recap\n"))
|
||||
if test.mutate != nil {
|
||||
test.mutate(m)
|
||||
}
|
||||
definition := test.config
|
||||
if definition.OutputPath == "" {
|
||||
definition.OutputPath = "artifacts/session_recap.md"
|
||||
}
|
||||
got := InspectAnalyzeEvidence(paths, m, "session_recap", definition)
|
||||
if got.State != AnalyzeEvidenceNonCurrent || !strings.Contains(got.Reason, test.reason) {
|
||||
t.Fatalf("evidence = %#v, want non-current reason containing %q", got, test.reason)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectAnalyzeEvidenceRejectsMissingAndUnsafeFiles(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
alter func(t *testing.T, paths SessionPaths, outputPath string)
|
||||
}{
|
||||
{name: "missing", alter: func(t *testing.T, _ SessionPaths, outputPath string) {
|
||||
if err := os.Remove(outputPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}},
|
||||
{name: "directory", alter: func(t *testing.T, _ SessionPaths, outputPath string) {
|
||||
if err := os.Remove(outputPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Mkdir(outputPath, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}},
|
||||
{name: "symlink leaf", alter: func(t *testing.T, paths SessionPaths, outputPath string) {
|
||||
if err := os.Remove(outputPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
target := filepath.Join(paths.Root, "target.md")
|
||||
if err := os.WriteFile(target, []byte("recap\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(target, outputPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}},
|
||||
{name: "symlink ancestor", alter: func(t *testing.T, paths SessionPaths, outputPath string) {
|
||||
if err := os.RemoveAll(paths.ArtifactsDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outside := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(outside, "session_recap.md"), []byte("recap\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(outside, paths.ArtifactsDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
paths := buildSessionPaths(t.TempDir(), "campaign", "session")
|
||||
m := analyzeEvidenceFixture(t, paths, "session_recap", "artifacts/session_recap.md", []byte("recap\n"))
|
||||
outputPath := filepath.Join(paths.ArtifactsDir, "session_recap.md")
|
||||
test.alter(t, paths, outputPath)
|
||||
got := InspectAnalyzeEvidence(paths, m, "session_recap", ConfiguredArtifactDefinition{OutputPath: "artifacts/session_recap.md"})
|
||||
if got.State != AnalyzeEvidenceNonCurrent || !strings.Contains(got.Reason, "missing or unsafe") {
|
||||
t.Fatalf("evidence = %#v", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHydrateAnalyzeArtifactsUsesOnlyCurrentConfiguredKeys(t *testing.T) {
|
||||
paths := buildSessionPaths(t.TempDir(), "campaign", "session")
|
||||
m := analyzeEvidenceFixture(t, paths, "session_recap", "artifacts/session_recap.md", []byte("recap\n"))
|
||||
m.Stages["analyze"].AnalyzeArtifacts["removed"] = analyzeEvidenceRecord(t, paths, "removed", "artifacts/removed.md", []byte("old\n"))
|
||||
configured := map[string]ConfiguredArtifactDefinition{"session_recap": {OutputPath: "artifacts/session_recap.md"}}
|
||||
catalog := NewArtifactCatalog()
|
||||
if err := catalog.RegisterConfiguredArtifacts(configured, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
catalog.HydrateAnalyzeArtifacts(paths, m, configured)
|
||||
entry, _ := catalog.Lookup(ConfiguredArtifactSourceID("session_recap"))
|
||||
if !entry.Available || entry.Provenance != ArtifactProvenanceCurrentAnalyzeManifest || entry.ProducerRunID != "run-1" {
|
||||
t.Fatalf("entry = %#v", entry)
|
||||
}
|
||||
if _, ok := catalog.Lookup(ConfiguredArtifactSourceID("removed")); ok {
|
||||
t.Fatal("removed manifest record was advertised in current catalog")
|
||||
}
|
||||
}
|
||||
|
||||
func analyzeEvidenceFixture(t *testing.T, paths SessionPaths, key, relativePath string, body []byte) *manifest.Manifest {
|
||||
t.Helper()
|
||||
record := analyzeEvidenceRecord(t, paths, key, relativePath, body)
|
||||
now := time.Date(2026, 8, 29, 12, 0, 0, 0, time.UTC)
|
||||
m := manifest.New(paths.SessionID, now)
|
||||
m.Stages["analyze"] = &manifest.StageRecord{
|
||||
Name: "analyze", Status: manifest.StatusSucceeded, CreatedAt: now, UpdatedAt: now,
|
||||
AnalyzeStateVersion: manifest.AnalyzeStateContractVersion,
|
||||
AnalyzeArtifacts: map[string]manifest.AnalyzeArtifactRecord{key: record},
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func analyzeEvidenceRecord(t *testing.T, paths SessionPaths, key, relativePath string, body []byte) manifest.AnalyzeArtifactRecord {
|
||||
t.Helper()
|
||||
outputPath := filepath.Join(paths.Root, filepath.FromSlash(relativePath))
|
||||
if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(outputPath, body, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
checksum, err := SHA256File(outputPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Date(2026, 8, 29, 12, 0, 0, 0, time.UTC)
|
||||
return manifest.AnalyzeArtifactRecord{
|
||||
Key: key, Status: manifest.AnalyzeArtifactCurrent,
|
||||
FingerprintVersion: manifest.AnalyzeFingerprintContractVersion,
|
||||
Fingerprint: strings.Repeat("1", 64),
|
||||
Output: &manifest.ArtifactRecord{
|
||||
Kind: "scriptorium_artifact", SourceID: ConfiguredArtifactSourceID(key), LocalPath: relativePath,
|
||||
Contract: &artifactmodel.ContractMetadata{MediaType: "text/markdown", SchemaID: "narratio." + key, SchemaVersion: "1"},
|
||||
ProducerRunID: "run-1", Checksum: checksum,
|
||||
},
|
||||
OutputSize: int64(len(body)), ProducerRunID: "run-1", UpdatedAt: now,
|
||||
}
|
||||
}
|
||||
|
||||
func analyzeEvidenceStatus(status manifest.AnalyzeArtifactStatus) func(*manifest.Manifest) {
|
||||
return func(m *manifest.Manifest) {
|
||||
record := m.Stages["analyze"].AnalyzeArtifacts["session_recap"]
|
||||
record.Status = status
|
||||
record.Output = nil
|
||||
record.OutputSize = 0
|
||||
if status == manifest.AnalyzeArtifactFailed {
|
||||
record.Error = "generation failed"
|
||||
}
|
||||
m.Stages["analyze"].AnalyzeArtifacts["session_recap"] = record
|
||||
}
|
||||
}
|
||||
|
||||
func mutateAnalyzeEvidenceRecord(m *manifest.Manifest, mutate func(*manifest.AnalyzeArtifactRecord)) manifest.AnalyzeArtifactRecord {
|
||||
record := m.Stages["analyze"].AnalyzeArtifacts["session_recap"]
|
||||
mutate(&record)
|
||||
return record
|
||||
}
|
||||
@@ -364,7 +364,7 @@ func TestResolveSessionArtifactWithCatalogConfiguredAvailableGenerated(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveSessionArtifactWithCatalogConfiguredAvailableFromDisk(t *testing.T) {
|
||||
func TestResolveSessionArtifactWithCatalogConfiguredAvailableFromManifest(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
paths := buildSessionPaths(workspace, "campaign", "session")
|
||||
outputPath := filepath.Join(paths.ArtifactsDir, "player_handout.md")
|
||||
@@ -385,16 +385,17 @@ func TestResolveSessionArtifactWithCatalogConfiguredAvailableFromDisk(t *testing
|
||||
t.Fatalf("RegisterConfiguredArtifacts() error = %v", err)
|
||||
}
|
||||
sourceID := ConfiguredArtifactSourceID("player_handout")
|
||||
if err := catalog.MarkAvailableFromDisk(sourceID, outputPath); err != nil {
|
||||
t.Fatalf("MarkAvailableFromDisk() error = %v", err)
|
||||
}
|
||||
m := analyzeEvidenceFixture(t, paths, "player_handout", "artifacts/player_handout.md", []byte("handout\n"))
|
||||
catalog.HydrateAnalyzeArtifacts(paths, m, map[string]ConfiguredArtifactDefinition{
|
||||
"player_handout": {Enabled: false, OutputPath: "artifacts/player_handout.md"},
|
||||
})
|
||||
|
||||
resolved, err := ResolveSessionArtifactWithCatalog(paths, nil, sourceID, catalog)
|
||||
resolved, err := ResolveSessionArtifactWithCatalog(paths, m, sourceID, catalog)
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveSessionArtifactWithCatalog() error = %v", err)
|
||||
}
|
||||
if resolved.Provenance != ArtifactProvenanceDisabledFromDisk {
|
||||
t.Fatalf("provenance = %q, want %q", resolved.Provenance, ArtifactProvenanceDisabledFromDisk)
|
||||
if resolved.Provenance != ArtifactProvenanceCurrentAnalyzeManifest {
|
||||
t.Fatalf("provenance = %q, want %q", resolved.Provenance, ArtifactProvenanceCurrentAnalyzeManifest)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
const (
|
||||
ArtifactProvenanceGeneratedCurrentAnalyzeRun = "generated.current_analyze_run"
|
||||
ArtifactProvenanceDisabledFromDisk = "filesystem.disabled_artifact_output"
|
||||
ArtifactProvenanceCurrentAnalyzeManifest = "manifest.current_analyze_artifact"
|
||||
ArtifactProvenanceCurrentExtractManifest = "manifest.current_extract_run"
|
||||
)
|
||||
|
||||
@@ -282,11 +282,6 @@ func (c *ArtifactCatalog) MarkAvailableGenerated(sourceID, path string) error {
|
||||
return c.markAvailable(sourceID, path, ArtifactProvenanceGeneratedCurrentAnalyzeRun)
|
||||
}
|
||||
|
||||
// MarkAvailableFromDisk marks one source as available from disabled artifact on disk.
|
||||
func (c *ArtifactCatalog) MarkAvailableFromDisk(sourceID, path string) error {
|
||||
return c.markAvailable(sourceID, path, ArtifactProvenanceDisabledFromDisk)
|
||||
}
|
||||
|
||||
func (c *ArtifactCatalog) markAvailableFromExtractManifest(sourceID, path, producerRunID string) error {
|
||||
if err := c.markAvailable(sourceID, path, ArtifactProvenanceCurrentExtractManifest); err != nil {
|
||||
return err
|
||||
@@ -297,6 +292,16 @@ func (c *ArtifactCatalog) markAvailableFromExtractManifest(sourceID, path, produ
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ArtifactCatalog) markAvailableFromAnalyzeManifest(sourceID, path, producerRunID string) error {
|
||||
if err := c.markAvailable(sourceID, path, ArtifactProvenanceCurrentAnalyzeManifest); err != nil {
|
||||
return err
|
||||
}
|
||||
entry := c.entries[strings.TrimSpace(sourceID)]
|
||||
entry.ProducerRunID = strings.TrimSpace(producerRunID)
|
||||
c.entries[entry.SourceID] = entry
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ArtifactCatalog) markAvailable(sourceID, path, provenance string) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("artifact catalog is nil")
|
||||
|
||||
@@ -179,30 +179,6 @@ func TestArtifactCatalogMarkAvailableGenerated(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactCatalogMarkAvailableFromDisk(t *testing.T) {
|
||||
catalog := NewArtifactCatalog()
|
||||
if err := catalog.RegisterConfiguredArtifacts(
|
||||
map[string]ConfiguredArtifactDefinition{
|
||||
"session_recap": {Enabled: false, OutputPath: "artifacts/session_recap.md"},
|
||||
},
|
||||
nil,
|
||||
); err != nil {
|
||||
t.Fatalf("RegisterConfiguredArtifacts() error = %v", err)
|
||||
}
|
||||
|
||||
sourceID, _ := catalog.SourceIDForConfiguredKey("session_recap")
|
||||
if err := catalog.MarkAvailableFromDisk(sourceID, "/tmp/session_recap.md"); err != nil {
|
||||
t.Fatalf("MarkAvailableFromDisk() error = %v", err)
|
||||
}
|
||||
entry, _ := catalog.Lookup(sourceID)
|
||||
if !entry.Available {
|
||||
t.Fatalf("entry.Available = false, want true")
|
||||
}
|
||||
if entry.Provenance != ArtifactProvenanceDisabledFromDisk {
|
||||
t.Fatalf("entry.Provenance = %q, want %q", entry.Provenance, ArtifactProvenanceDisabledFromDisk)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactCatalogLookupPlannedButUnavailable(t *testing.T) {
|
||||
catalog := NewArtifactCatalog()
|
||||
if err := catalog.RegisterConfiguredArtifacts(
|
||||
|
||||
Reference in New Issue
Block a user