2026-05-02 11:05:28 -05:00
package app
import (
"fmt"
2026-08-29 18:02:37 +00:00
"strings"
2026-05-02 11:05:28 -05:00
"gitea.maximumdirect.net/eric/narratio/internal/stage"
)
2026-08-29 18:02:37 +00:00
// BoundedPlan is one validated inclusive range of the canonical pipeline.
// It owns effective endpoints and membership so command, runner, and
// composition callers do not independently interpret range bounds.
type BoundedPlan struct {
stages [ ] stage . Stage
canonicalNames [ ] string
startIndex int
endIndex int
explicitFrom bool
explicitThrough bool
}
// BuildBoundedPlan selects an inclusive contiguous range of the canonical
// pipeline. Empty endpoints default to the beginning or end respectively.
func BuildBoundedPlan ( from , through string ) ( BoundedPlan , error ) {
registry := stage . All ( )
names := make ( [ ] string , len ( registry ) )
indices := make ( map [ string ] int , len ( registry ) )
for index , candidate := range registry {
if candidate == nil {
return BoundedPlan { } , fmt . Errorf ( "build bounded plan: canonical stage %d is nil" , index )
}
name := candidate . Name ( )
if _ , duplicate := indices [ name ] ; duplicate {
return BoundedPlan { } , fmt . Errorf ( "build bounded plan: duplicate canonical stage %q" , name )
}
names [ index ] = name
indices [ name ] = index
}
if len ( registry ) == 0 {
return BoundedPlan { } , fmt . Errorf ( "build bounded plan: canonical stage registry is empty" )
}
start := 0
if from != "" {
var ok bool
start , ok = indices [ from ]
if ! ok {
return BoundedPlan { } , fmt . Errorf ( "build bounded plan: unknown from stage %q; valid stages: %s" , from , strings . Join ( names , ", " ) )
}
}
end := len ( registry ) - 1
if through != "" {
var ok bool
end , ok = indices [ through ]
if ! ok {
return BoundedPlan { } , fmt . Errorf ( "build bounded plan: unknown through stage %q; valid stages: %s" , through , strings . Join ( names , ", " ) )
}
}
if start > end {
return BoundedPlan { } , fmt . Errorf ( "build bounded plan: from stage %q occurs after through stage %q; valid stages: %s" , from , through , strings . Join ( names , ", " ) )
}
return BoundedPlan {
stages : append ( [ ] stage . Stage ( nil ) , registry [ start : end + 1 ] ... ) ,
canonicalNames : append ( [ ] string ( nil ) , names ... ) ,
startIndex : start ,
endIndex : end ,
explicitFrom : from != "" ,
explicitThrough : through != "" ,
} , nil
}
// Stages returns a copy of the selected canonical stages.
func ( p BoundedPlan ) Stages ( ) [ ] stage . Stage {
return append ( [ ] stage . Stage ( nil ) , p . stages ... )
}
// Names returns selected stage names in canonical order.
func ( p BoundedPlan ) Names ( ) [ ] string {
out := make ( [ ] string , 0 , len ( p . stages ) )
for _ , candidate := range p . stages {
out = append ( out , candidate . Name ( ) )
}
return out
}
// From returns the effective inclusive start stage.
func ( p BoundedPlan ) From ( ) string {
if len ( p . canonicalNames ) == 0 || p . startIndex < 0 || p . startIndex >= len ( p . canonicalNames ) {
return ""
}
return p . canonicalNames [ p . startIndex ]
}
// Through returns the effective inclusive end stage.
func ( p BoundedPlan ) Through ( ) string {
if len ( p . canonicalNames ) == 0 || p . endIndex < 0 || p . endIndex >= len ( p . canonicalNames ) {
return ""
}
return p . canonicalNames [ p . endIndex ]
}
// Contains reports whether a canonical stage is selected by the range.
func ( p BoundedPlan ) Contains ( name string ) bool {
for _ , candidate := range p . stages {
if candidate . Name ( ) == name {
return true
}
}
return false
}
// PrefixNames returns canonical stages excluded before the selected start.
func ( p BoundedPlan ) PrefixNames ( ) [ ] string {
if p . startIndex <= 0 || p . startIndex > len ( p . canonicalNames ) {
return nil
}
return append ( [ ] string ( nil ) , p . canonicalNames [ : p . startIndex ] ... )
}
// HasExplicitBounds reports whether either endpoint was supplied by the caller.
func ( p BoundedPlan ) HasExplicitBounds ( ) bool {
return p . explicitFrom || p . explicitThrough
}
2026-05-02 11:05:28 -05:00
// BuildFullPlan returns the canonical full stage list in deterministic order.
func BuildFullPlan ( ) [ ] stage . Stage {
2026-08-29 18:02:37 +00:00
plan , err := BuildBoundedPlan ( "" , "" )
if err != nil {
panic ( err )
}
return plan . Stages ( )
2026-05-02 11:05:28 -05:00
}
// BuildSingleStagePlan returns a one-stage plan for an exact stage name.
func BuildSingleStagePlan ( name string ) ( [ ] stage . Stage , error ) {
s , err := stage . Select ( name )
if err != nil {
return nil , fmt . Errorf ( "build stage plan: %w" , err )
}
return [ ] stage . Stage { s } , nil
}
2026-08-29 20:49:43 +00:00
// buildSingleStageExecutionPlan selects one canonical stage without applying
// bounded-run prefix prerequisites. The run-stage family validates the stage's
// concrete inputs and intentionally retains its established direct-execution
// semantics.
func buildSingleStageExecutionPlan ( name string ) ( BoundedPlan , error ) {
stages , err := BuildSingleStagePlan ( name )
if err != nil {
return BoundedPlan { } , err
}
plan , err := BuildBoundedPlan ( name , name )
if err != nil {
return BoundedPlan { } , fmt . Errorf ( "build stage plan: %w" , err )
}
plan . stages = stages
plan . explicitFrom = false
plan . explicitThrough = false
return plan , nil
}