Implement manifest model and local store
This commit is contained in:
@@ -1,18 +1,140 @@
|
||||
package manifest
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StageState tracks status and metadata for one stage execution.
|
||||
type StageState struct {
|
||||
Status StageStatus
|
||||
UpdatedAt time.Time
|
||||
Error string
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// Manifest is the durable state record for a session run.
|
||||
// InputRecord captures one resolved input and optional checksum.
|
||||
type InputRecord struct {
|
||||
Kind string `json:"kind"`
|
||||
Path string `json:"path"`
|
||||
Checksum string `json:"checksum,omitempty"`
|
||||
}
|
||||
|
||||
// ArtifactRecord captures one produced artifact and optional remote metadata.
|
||||
type ArtifactRecord struct {
|
||||
Kind string `json:"kind"`
|
||||
LocalPath string `json:"local_path"`
|
||||
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
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Stages map[string]StageState
|
||||
SessionID string `json:"session_id"`
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user