Add stage planning and placeholder runner

This commit is contained in:
2026-05-02 11:05:28 -05:00
parent fb659e0d3d
commit 78e7e4f41b
14 changed files with 598 additions and 68 deletions

View File

@@ -4,60 +4,55 @@ import (
"context"
"fmt"
"gitea.maximumdirect.net/eric/narratio/internal/app"
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
)
func notImplemented(name string) error {
return fmt.Errorf("stage %q: not yet implemented", name)
const placeholderMessage = "placeholder stage: no real work executed"
type placeholderStage struct {
name string
}
type Prepare struct{}
func (s placeholderStage) Name() string { return s.name }
type Transcribe struct{}
type Normalize struct{}
type Merge struct{}
type Polish struct{}
type Analyze struct{}
type Archive struct{}
type Notify struct{}
func (Prepare) Name() string { return "prepare" }
func (Transcribe) Name() string { return "transcribe" }
func (Normalize) Name() string { return "normalize" }
func (Merge) Name() string { return "merge" }
func (Polish) Name() string { return "polish" }
func (Analyze) Name() string { return "analyze" }
func (Archive) Name() string { return "archive" }
func (Notify) Name() string { return "notify" }
func (s Prepare) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
func (s placeholderStage) Declares() IODecl {
return IODecl{
Inputs: []artifacts.Ref{{Kind: "artifact", Category: "input", RelativePath: s.name + ".input.placeholder"}},
Outputs: []artifacts.Ref{{Kind: "artifact", Category: "output", RelativePath: s.name + ".output.placeholder"}},
}
}
func (s Transcribe) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
func (s placeholderStage) Run(_ context.Context, _ *Env, _ *manifest.Manifest) (*StageResult, error) {
return &StageResult{
Metadata: map[string]any{
"placeholder": true,
"stage": s.name,
"message": placeholderMessage,
},
}, nil
}
func (s Normalize) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
// All returns the canonical ordered stage list for full pipeline execution.
func All() []Stage {
return []Stage{
placeholderStage{name: "prepare"},
placeholderStage{name: "transcribe"},
placeholderStage{name: "normalize"},
placeholderStage{name: "merge"},
placeholderStage{name: "polish"},
placeholderStage{name: "analyze"},
placeholderStage{name: "archive"},
placeholderStage{name: "notify"},
}
}
func (s Merge) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
}
func (s Polish) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
}
func (s Analyze) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
}
func (s Archive) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
}
func (s Notify) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
// Select returns one stage by exact name from the canonical stage set.
func Select(name string) (Stage, error) {
for _, s := range All() {
if s.Name() == name {
return s, nil
}
}
return nil, fmt.Errorf("unknown stage %q", name)
}

View File

@@ -0,0 +1,30 @@
package stage
import (
"context"
"testing"
"time"
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
)
func TestPlaceholderStagesReturnSuccessMetadata(t *testing.T) {
stages := All()
if len(stages) == 0 {
t.Fatal("expected non-empty stage list")
}
m := manifest.New("2026-05-03", time.Now().UTC())
for _, s := range stages {
result, err := s.Run(context.Background(), nil, m)
if err != nil {
t.Fatalf("stage %q returned unexpected error: %v", s.Name(), err)
}
if result == nil {
t.Fatalf("stage %q returned nil result", s.Name())
}
if result.Metadata["placeholder"] != true {
t.Fatalf("stage %q missing placeholder metadata", s.Name())
}
}
}

View File

@@ -2,20 +2,48 @@ package stage
import (
"context"
"log/slog"
"gitea.maximumdirect.net/eric/narratio/internal/app"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/analyzer"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/audita"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/notify"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/seriatim"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/whisperx"
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
"gitea.maximumdirect.net/eric/narratio/internal/config"
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
)
// Env is the shared dependency container visible to stages.
type Env struct {
Config *config.Config
ArtifactStore artifacts.Store
Logger *slog.Logger
WhisperX whisperx.Client
Seriatim seriatim.Runner
Audita audita.Runner
Analyzer analyzer.Runner
Notifier notify.Sender
}
// IODecl declares the intended input/output artifact kinds for a stage.
type IODecl struct {
Inputs []artifacts.Ref
Outputs []artifacts.Ref
}
// Stage is the pipeline unit contract.
type Stage interface {
Name() string
Run(ctx context.Context, env *app.Env, m *manifest.Manifest) (*Result, error)
Declares() IODecl
Run(ctx context.Context, env *Env, m *manifest.Manifest) (*StageResult, error)
}
// Result is the declared output of a stage execution.
type Result struct {
Outputs []artifacts.Ref
Metadata map[string]any
// StageResult is the declared output of a stage execution.
type StageResult struct {
Outputs []artifacts.Ref
Logs []string
GeneratedConfigs []string
Metadata map[string]any
}