166 lines
5.6 KiB
Go
166 lines
5.6 KiB
Go
package manifest
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ErrorRecord captures structured error metadata at run or stage scope.
|
|
type ErrorRecord struct {
|
|
Message string `json:"message"`
|
|
Code string `json:"code,omitempty"`
|
|
At *time.Time `json:"at,omitempty"`
|
|
}
|
|
|
|
// InputRecord captures one resolved input and optional checksum.
|
|
type InputRecord struct {
|
|
Kind string `json:"kind"`
|
|
Path string `json:"path"`
|
|
Checksum string `json:"checksum,omitempty"`
|
|
Source string `json:"source,omitempty"`
|
|
S3Bucket string `json:"s3_bucket,omitempty"`
|
|
S3Key string `json:"s3_key,omitempty"`
|
|
S3Size int64 `json:"s3_size,omitempty"`
|
|
S3ETag string `json:"s3_etag,omitempty"`
|
|
SpoolPath string `json:"spool_path,omitempty"`
|
|
}
|
|
|
|
// ArtifactRecord captures one produced artifact and optional remote metadata.
|
|
type ArtifactRecord struct {
|
|
Kind string `json:"kind"`
|
|
SourceID string `json:"source_id,omitempty"`
|
|
LocalPath string `json:"local_path"`
|
|
// ProducerRunID identifies the run that produced this durable artifact.
|
|
ProducerRunID string `json:"producer_run_id,omitempty"`
|
|
RemoteKey string `json:"remote_key,omitempty"`
|
|
Checksum string `json:"checksum,omitempty"`
|
|
}
|
|
|
|
// StageRecord tracks lifecycle and provenance for one pipeline stage.
|
|
type StageRecord struct {
|
|
Name string `json:"name"`
|
|
Status StageStatus `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
StartedAt *time.Time `json:"started_at,omitempty"`
|
|
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
|
Outputs []ArtifactRecord `json:"outputs,omitempty"`
|
|
Logs []string `json:"logs,omitempty"`
|
|
GeneratedConfigs []string `json:"generated_configs,omitempty"`
|
|
Error *ErrorRecord `json:"error,omitempty"`
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
}
|
|
|
|
// Manifest is the durable run-state record for a session execution.
|
|
type Manifest struct {
|
|
SessionID string `json:"session_id"`
|
|
Campaign string `json:"campaign,omitempty"`
|
|
RunID string `json:"run_id,omitempty"`
|
|
LocalWorkDir string `json:"local_workdir,omitempty"`
|
|
LocalSpoolDir string `json:"local_spool_dir,omitempty"`
|
|
S3Bucket string `json:"s3_bucket,omitempty"`
|
|
S3SessionPrefix string `json:"s3_session_prefix,omitempty"`
|
|
S3RunPrefix string `json:"s3_run_prefix,omitempty"`
|
|
PipelineVersion string `json:"pipeline_version,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
LastError *ErrorRecord `json:"last_error,omitempty"`
|
|
Inputs []InputRecord `json:"inputs,omitempty"`
|
|
Artifacts []ArtifactRecord `json:"artifacts,omitempty"`
|
|
Stages map[string]*StageRecord `json:"stages"`
|
|
}
|
|
|
|
// New constructs a new manifest with deterministic timestamps.
|
|
func New(sessionID string, now time.Time) *Manifest {
|
|
return &Manifest{
|
|
SessionID: strings.TrimSpace(sessionID),
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
Stages: map[string]*StageRecord{},
|
|
}
|
|
}
|
|
|
|
// MarkStageRunning marks a stage as running and updates timestamps.
|
|
func (m *Manifest) MarkStageRunning(name string, at time.Time) {
|
|
s := m.ensureStage(name, at)
|
|
s.Status = StatusRunning
|
|
s.StartedAt = timePtr(at)
|
|
s.CompletedAt = nil
|
|
s.Error = nil
|
|
s.UpdatedAt = at
|
|
m.UpdatedAt = at
|
|
}
|
|
|
|
// MarkStageSucceeded marks a stage as succeeded, stores outputs, and updates timestamps.
|
|
func (m *Manifest) MarkStageSucceeded(name string, at time.Time, outputs []ArtifactRecord) {
|
|
s := m.ensureStage(name, at)
|
|
s.Status = StatusSucceeded
|
|
s.CompletedAt = timePtr(at)
|
|
s.Error = nil
|
|
s.Outputs = append([]ArtifactRecord(nil), outputs...)
|
|
s.UpdatedAt = at
|
|
m.UpdatedAt = at
|
|
}
|
|
|
|
// MarkStageFailed marks a stage as failed and records error metadata.
|
|
func (m *Manifest) MarkStageFailed(name string, at time.Time, message string) {
|
|
s := m.ensureStage(name, at)
|
|
s.Status = StatusFailed
|
|
s.CompletedAt = timePtr(at)
|
|
s.Error = &ErrorRecord{Message: strings.TrimSpace(message), At: timePtr(at)}
|
|
s.UpdatedAt = at
|
|
m.LastError = &ErrorRecord{Message: strings.TrimSpace(message), At: timePtr(at)}
|
|
m.UpdatedAt = at
|
|
}
|
|
|
|
// MarkStageSkipped marks a stage as skipped and records the skip reason.
|
|
func (m *Manifest) MarkStageSkipped(name string, at time.Time, reason string) {
|
|
s := m.ensureStage(name, at)
|
|
s.Status = StatusSkipped
|
|
s.CompletedAt = timePtr(at)
|
|
s.Error = &ErrorRecord{Message: strings.TrimSpace(reason), Code: "skipped", At: timePtr(at)}
|
|
s.UpdatedAt = at
|
|
m.UpdatedAt = at
|
|
}
|
|
|
|
// MarkStageStale marks a stage as stale so it is not skipped as idempotently complete.
|
|
func (m *Manifest) MarkStageStale(name string, at time.Time, reason string) {
|
|
s := m.ensureStage(name, at)
|
|
s.Status = StatusStale
|
|
s.Error = &ErrorRecord{Message: strings.TrimSpace(reason), Code: "stale", At: timePtr(at)}
|
|
s.UpdatedAt = at
|
|
m.UpdatedAt = at
|
|
}
|
|
|
|
func (m *Manifest) ensureStage(name string, at time.Time) *StageRecord {
|
|
if m.Stages == nil {
|
|
m.Stages = map[string]*StageRecord{}
|
|
}
|
|
|
|
stageName := strings.TrimSpace(name)
|
|
s, ok := m.Stages[stageName]
|
|
if !ok || s == nil {
|
|
s = &StageRecord{
|
|
Name: stageName,
|
|
Status: StatusPending,
|
|
CreatedAt: at,
|
|
UpdatedAt: at,
|
|
}
|
|
m.Stages[stageName] = s
|
|
}
|
|
|
|
if s.Name == "" {
|
|
s.Name = stageName
|
|
}
|
|
if s.CreatedAt.IsZero() {
|
|
s.CreatedAt = at
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func timePtr(t time.Time) *time.Time {
|
|
v := t
|
|
return &v
|
|
}
|