action

package
v0.13.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 30, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Overview

Package action provides types and execution logic for the Actions system. Actions consume resolved data from resolvers and perform side-effect operations.

Index

Constants

View Source
const (
	// ErrorTypeHTTP indicates an HTTP-related error with a status code.
	ErrorTypeHTTP = "http"

	// ErrorTypeExec indicates a process execution error with an exit code.
	ErrorTypeExec = "exec"

	// ErrorTypeTimeout indicates a timeout or deadline exceeded error.
	ErrorTypeTimeout = "timeout"

	// ErrorTypeValidation indicates a validation error.
	ErrorTypeValidation = "validation"

	// ErrorTypeUnknown indicates an unclassified error.
	ErrorTypeUnknown = "unknown"
)

Error type constants for categorization.

View Source
const (
	// APIVersion is the version of the rendered graph format.
	APIVersion = "scafctl.oakwood-commons.github.io/v1alpha1"

	// KindActionGraph is the kind for rendered action graphs.
	KindActionGraph = "ActionGraph"

	// FormatJSON indicates JSON output format.
	FormatJSON = "json"

	// FormatYAML indicates YAML output format.
	FormatYAML = "yaml"
)

Variables

This section is empty.

Functions

func BuildOutputData added in v0.6.0

func BuildOutputData(result *ExecutionResult, executionData map[string]any) map[string]any

BuildOutputData transforms an ExecutionResult and optional execution metadata into a structured map suitable for JSON/YAML output or programmatic consumption.

The returned map contains:

  • "status": the final execution status string
  • "startTime", "endTime": RFC3339 timestamps
  • "duration": human-readable duration
  • "actions": map of action name → {status, results?, error?, skipReason?}
  • "failedActions", "skippedActions": lists (if non-empty)
  • "__execution": execution metadata (if provided)

func CreateIterationName

func CreateIterationName(baseName string, index int) string

CreateIterationName creates the expanded name for a forEach iteration. Format: "baseName[index]" (e.g., "deploy[0]", "deploy[1]")

func ExpandForEachItems

func ExpandForEachItems(ctx context.Context, forEach *spec.ForEachClause, resolverData map[string]any) ([]any, error)

ExpandForEachItems evaluates the forEach.In expression and returns the items to iterate over. This is a convenience function that wraps evaluateForEachArray for external use.

func GetDeferredInputNames

func GetDeferredInputNames(values map[string]any) []string

GetDeferredInputNames returns the names of inputs that contain deferred values.

func GetFormat

func GetFormat(format string) (string, error)

GetFormat normalizes and validates the output format string.

func HasDeferredValues

func HasDeferredValues(values map[string]any) bool

HasDeferredValues checks if any values in the map are deferred.

func IsForEachIteration

func IsForEachIteration(name string) bool

IsForEachIteration checks if an action name represents a forEach iteration. Returns true if the name matches the pattern "baseName[index]".

func Materialize

func Materialize(ctx context.Context, v *spec.ValueRef, resolverData map[string]any) (any, error)

Materialize evaluates a ValueRef, returning either a concrete value or a DeferredValue if it references __actions. This is used during the render phase to prepare action inputs.

func MaterializeInputs

func MaterializeInputs(ctx context.Context, inputs map[string]*spec.ValueRef, resolverData map[string]any) (map[string]any, error)

MaterializeInputs processes all inputs for an action, materializing immediate values and preserving deferred values that reference __actions. Returns a map where concrete values are resolved and __actions references are DeferredValues.

func ParseIterationName

func ParseIterationName(name string) (baseName string, index int, ok bool)

ParseIterationName extracts the base name and index from a forEach iteration name. Returns the base name, index, and true if successful. Returns empty string, -1, false if not a valid iteration name.

func Render

func Render(graph *Graph, opts *RenderOptions) ([]byte, error)

Render produces an executor-ready action graph artifact from the built graph. The output can be serialized to JSON or YAML based on options.

func ResolveDeferredValues

func ResolveDeferredValues(ctx context.Context, values, resolverData, additionalVars map[string]any) (map[string]any, error)

ResolveDeferredValues evaluates all deferred values in the map using the provided additional variables. additionalVars should contain __actions namespace and any alias variables. Non-deferred values are passed through unchanged.

func ValidateResult

func ValidateResult(result any, schema *jsonschema.Schema) error

ValidateResult validates a provider result against the JSON Schema. Returns nil if validation passes or schema is nil.

func ValidateWorkflow

func ValidateWorkflow(w *Workflow, registry RegistryInterface) error

ValidateWorkflow validates the entire workflow definition. It checks all validation rules and returns an aggregated error if any fail. Pass nil for registry to skip provider capability checks.

Types

type Action

type Action struct {
	// Name is the action identifier, set from the map key.
	// Cannot start with "__" (reserved) or contain "[" or "]".
	Name string `` /* 250-byte string literal not displayed */

	// Alias provides a short name for use in CEL/template expressions.
	// When set, the action's result data is available as a top-level variable
	// under this alias, in addition to the standard __actions.<name> reference.
	// For example, alias: config allows using config.results.endpoint instead of
	// __actions.fetchConfiguration.results.endpoint.
	Alias string `` /* 248-byte string literal not displayed */

	// Description provides documentation for the action.
	Description string `` /* 175-byte string literal not displayed */

	// DisplayName is a human-friendly name for UI display.
	DisplayName string `` /* 132-byte string literal not displayed */

	// Sensitive indicates the action handles sensitive data (masks in logs).
	Sensitive bool `json:"sensitive,omitempty" yaml:"sensitive,omitempty" doc:"If true, inputs and outputs are masked in logs"`

	// Provider specifies which action provider to use for execution.
	// The provider must have CapabilityAction.
	Provider string `json:"provider" yaml:"provider" doc:"Action provider name" maxLength:"100" example:"shell"`

	// Inputs is a map of input values passed to the provider.
	// Values can be literals, resolver references, expressions, or templates.
	// Expressions referencing __actions are deferred until runtime.
	Inputs map[string]*spec.ValueRef `json:"inputs,omitempty" yaml:"inputs,omitempty" doc:"Input values for the provider"`

	// DependsOn lists action names that must complete before this action runs.
	// For regular actions, only other regular actions can be referenced.
	// For finally actions, only other finally actions can be referenced.
	DependsOn []string `json:"dependsOn,omitempty" yaml:"dependsOn,omitempty" doc:"Actions that must complete before this action runs" maxItems:"100"`

	// Exclusive lists action names that cannot run in parallel with this action.
	// Declaration is one-way: only this action needs to declare the exclusivity.
	// The executor serializes execution so that this action and any listed action
	// never run simultaneously, regardless of which starts first.
	// exclusive does NOT imply dependsOn — order is not guaranteed.
	// Referenced actions must exist in the same section (actions or finally).
	Exclusive []string `` /* 127-byte string literal not displayed */

	// When is a condition that must evaluate to true for the action to execute.
	// If false, the action is skipped with SkipReasonCondition.
	When *spec.Condition `json:"when,omitempty" yaml:"when,omitempty" doc:"Condition for execution (skipped if false)"`

	// OnError defines behavior when this action fails.
	// Default is "fail" which stops workflow execution.
	OnError spec.OnErrorBehavior `json:"onError,omitempty" yaml:"onError,omitempty" doc:"Error handling behavior" maxLength:"16" example:"fail" default:"fail"`

	// Timeout limits how long the action can run.
	// If exceeded, the action fails with StatusTimeout.
	Timeout *duration.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty" doc:"Maximum execution duration" example:"30s"`

	// Retry configures automatic retry on failure.
	Retry *RetryConfig `json:"retry,omitempty" yaml:"retry,omitempty" doc:"Retry configuration for transient failures"`

	// ForEach enables iteration, executing the action once per array element.
	// Only allowed in workflow.actions, not workflow.finally.
	ForEach *spec.ForEachClause `json:"forEach,omitempty" yaml:"forEach,omitempty" doc:"Iteration configuration (not allowed in finally)"`

	// ResultSchema defines the expected structure of the action's output using JSON Schema.
	// If provided, the provider's output is validated against this schema at runtime.
	// Supports full JSON Schema 2020-12 specification including $ref, allOf, anyOf, oneOf, etc.
	// This enables self-documenting actions and catches mismatches early.
	// Use ResultSchemaMode to control validation behavior (error, warn, ignore).
	ResultSchema *jsonschema.Schema `json:"resultSchema,omitempty" yaml:"resultSchema,omitempty" doc:"JSON Schema for result validation"`

	// ResultSchemaMode controls behavior when result schema validation fails.
	// Overrides the workflow-level default. Options: "error" (fail action), "warn" (log and continue), "ignore" (skip validation).
	ResultSchemaMode ResultSchemaMode `` /* 134-byte string literal not displayed */
}

Action represents a single action definition. Actions perform side-effect operations using providers and can depend on other actions for sequencing and data flow.

type ActionResult

type ActionResult struct {
	// Inputs contains the resolved input values that were passed to the provider.
	Inputs map[string]any `json:"inputs" yaml:"inputs" doc:"Resolved inputs passed to provider"`

	// Results contains the output data from the provider.
	// This is accessible as __actions.<name>.results in CEL/templates.
	Results any `json:"results,omitempty" yaml:"results,omitempty" doc:"Provider output data"`

	// Status is the final execution status.
	Status ActionStatus `json:"status" yaml:"status" doc:"Execution status" maxLength:"16" example:"success"`

	// SkipReason explains why the action was skipped (if Status is StatusSkipped).
	SkipReason SkipReason `` /* 129-byte string literal not displayed */

	// StartTime is when execution began.
	StartTime *time.Time `json:"startTime,omitempty" yaml:"startTime,omitempty" doc:"Execution start time"`

	// EndTime is when execution completed.
	EndTime *time.Time `json:"endTime,omitempty" yaml:"endTime,omitempty" doc:"Execution end time"`

	// Error contains the error message if Status is StatusFailed or StatusTimeout.
	Error string `` /* 131-byte string literal not displayed */

	// Streamed indicates the provider already wrote its output to the terminal.
	// When true, the CLI output layer should not re-print the action's results.
	Streamed bool `json:"streamed,omitempty" yaml:"streamed,omitempty" doc:"Whether output was streamed to terminal"`
}

ActionResult represents the result of an action execution. It is stored in the __actions namespace and accessible to dependent actions.

func AggregateForEachResults

func AggregateForEachResults(
	_ string,
	iterations []*ForEachIterationResult,
	inputs map[string]any,
) *ActionResult

AggregateForEachResults aggregates forEach iteration results into an ActionResult. This creates the combined result that will be stored in __actions namespace.

func (*ActionResult) Duration

func (r *ActionResult) Duration() time.Duration

Duration returns the execution duration, or zero if not available.

type ActionStatus

type ActionStatus string

ActionStatus represents the execution status of an action.

const (
	// StatusPending indicates the action has not started.
	StatusPending ActionStatus = "pending"

	// StatusRunning indicates the action is currently executing.
	StatusRunning ActionStatus = "running"

	// StatusSucceeded indicates the action completed successfully.
	StatusSucceeded ActionStatus = "succeeded"

	// StatusFailed indicates the action failed.
	StatusFailed ActionStatus = "failed"

	// StatusSkipped indicates the action was not executed.
	StatusSkipped ActionStatus = "skipped"

	// StatusTimeout indicates the action exceeded its timeout.
	StatusTimeout ActionStatus = "timeout"

	// StatusCancelled indicates the action was cancelled.
	StatusCancelled ActionStatus = "cancelled"
)

func (ActionStatus) IsSuccess

func (s ActionStatus) IsSuccess() bool

IsSuccess returns true if the status indicates successful completion.

func (ActionStatus) IsTerminal

func (s ActionStatus) IsTerminal() bool

IsTerminal returns true if the status is a terminal state.

type AggregatedValidationError

type AggregatedValidationError struct {
	// Errors contains all validation errors found.
	Errors []*ValidationError `json:"errors" yaml:"errors" doc:"All validation errors" minItems:"1"`
}

AggregatedValidationError represents multiple validation errors. This is returned when ValidateWorkflow finds multiple issues.

func (*AggregatedValidationError) AddError

func (e *AggregatedValidationError) AddError(err *ValidationError)

AddError adds a validation error.

func (*AggregatedValidationError) Error

func (e *AggregatedValidationError) Error() string

Error implements the error interface.

func (*AggregatedValidationError) HasErrors

func (e *AggregatedValidationError) HasErrors() bool

HasErrors returns true if there are any validation errors.

func (*AggregatedValidationError) ToError

func (e *AggregatedValidationError) ToError() error

ToError returns nil if no errors, or the AggregatedValidationError itself.

type BackoffType

type BackoffType string

BackoffType defines the backoff strategy for retries.

const (
	// BackoffFixed uses a constant delay between retries.
	BackoffFixed BackoffType = "fixed"

	// BackoffLinear increases delay linearly (initialDelay * attempt).
	BackoffLinear BackoffType = "linear"

	// BackoffExponential doubles the delay with each retry.
	BackoffExponential BackoffType = "exponential"
)

func (BackoffType) IsValid

func (b BackoffType) IsValid() bool

IsValid returns true if the backoff type is valid.

func (BackoffType) OrDefault

func (b BackoffType) OrDefault() BackoffType

OrDefault returns the backoff type or the default (BackoffFixed) if empty.

type BuildGraphOptions

type BuildGraphOptions struct {
	// SkipInputMaterialization skips input materialization (for validation-only use cases).
	SkipInputMaterialization bool
}

BuildGraphOptions configures graph building behavior.

type ConfigInput

type ConfigInput struct {
	// DefaultTimeout is the default timeout per action execution
	DefaultTimeout time.Duration `json:"defaultTimeout" yaml:"defaultTimeout" doc:"Default timeout per action execution"`
	// GracePeriod is the cancellation grace period
	GracePeriod time.Duration `json:"gracePeriod" yaml:"gracePeriod" doc:"Cancellation grace period"`
	// MaxConcurrency is the max concurrent actions (0 = unlimited)
	MaxConcurrency int `json:"maxConcurrency" yaml:"maxConcurrency" doc:"Maximum concurrent actions (0 = unlimited)" maximum:"1000" example:"5"`
}

ConfigInput holds the configuration values for action executor initialization. This mirrors config.ActionConfig but avoids circular dependencies.

type Context

type Context struct {
	// contains filtered or unexported fields
}

Context manages the __actions namespace during workflow execution. It provides thread-safe storage for action results and supports forEach iteration result aggregation.

func NewContext

func NewContext() *Context

NewContext creates a new action context for workflow execution.

func (*Context) ActionCount

func (c *Context) ActionCount() int

ActionCount returns the number of actions with results.

func (*Context) AddIteration

func (c *Context) AddIteration(actionName string, result *ForEachIterationResult)

AddIteration records a forEach iteration result. Results are stored in order by index.

func (*Context) AllActionNames

func (c *Context) AllActionNames() []string

AllActionNames returns all action names that have results.

func (*Context) Clone

func (c *Context) Clone() *Context

Clone creates a deep copy of the action context. Useful for testing or creating snapshots.

func (*Context) FinalizeForEach

func (c *Context) FinalizeForEach(actionName string, inputs map[string]any) *ActionResult

FinalizeForEach aggregates forEach iteration results into a single ActionResult. This should be called after all iterations complete to create the combined result.

func (*Context) GetIterations

func (c *Context) GetIterations(actionName string) []*ForEachIterationResult

GetIterations retrieves all iteration results for a forEach action.

func (*Context) GetNamespace

func (c *Context) GetNamespace() map[string]any

GetNamespace returns the __actions map for CEL/template evaluation. The returned map contains action results in a format suitable for expression evaluation (e.g., __actions.build.results.exitCode).

func (*Context) GetResult

func (c *Context) GetResult(name string) (*ActionResult, bool)

GetResult retrieves an action's result by name. Returns the result and true if found, nil and false otherwise.

func (*Context) HasResult

func (c *Context) HasResult(name string) bool

HasResult checks if a result exists for the given action name.

func (*Context) MarkCancelled

func (c *Context) MarkCancelled(name string)

MarkCancelled marks an action as cancelled.

func (*Context) MarkFailed

func (c *Context) MarkFailed(name, errMsg string)

MarkFailed marks an action as failed with an error message.

func (*Context) MarkRunning

func (c *Context) MarkRunning(name string, inputs map[string]any)

MarkRunning marks an action as running with the current time.

func (*Context) MarkSkipped

func (c *Context) MarkSkipped(name string, reason SkipReason)

MarkSkipped marks an action as skipped with a reason.

func (*Context) MarkStreamed added in v0.4.0

func (c *Context) MarkStreamed(name string)

MarkStreamed marks an action as having streamed its output to the terminal.

func (*Context) MarkSucceeded

func (c *Context) MarkSucceeded(name string, results any)

MarkSucceeded marks an action as successfully completed.

func (*Context) MarkTimeout

func (c *Context) MarkTimeout(name string)

MarkTimeout marks an action as timed out.

func (*Context) Reset

func (c *Context) Reset()

Reset clears all stored results and iterations.

func (*Context) SetResult

func (c *Context) SetResult(name string, result *ActionResult)

SetResult stores an action's result in the context. This is called after an action completes (success, failure, or skip).

type DeferredValue

type DeferredValue struct {
	// OriginalExpr is the original CEL expression string (if expr-based).
	OriginalExpr string `json:"originalExpr,omitempty" yaml:"originalExpr,omitempty" doc:"Original CEL expression"`

	// OriginalTmpl is the original Go template string (if tmpl-based).
	OriginalTmpl string `json:"originalTmpl,omitempty" yaml:"originalTmpl,omitempty" doc:"Original Go template"`

	// Deferred indicates this value requires runtime evaluation.
	Deferred bool `json:"deferred" yaml:"deferred" doc:"If true, value requires runtime evaluation"`
}

DeferredValue represents an expression that is preserved for runtime evaluation. This is used when a ValueRef references __actions, which cannot be resolved until the referenced action has completed during workflow execution.

func (*DeferredValue) Evaluate

func (d *DeferredValue) Evaluate(ctx context.Context, resolverData, additionalVars map[string]any) (any, error)

Evaluate resolves the deferred value using the provided resolver data and additional variables. The additionalVars should contain both the __actions namespace (keyed by celexp.VarActions) and any alias top-level variables pointing to individual action result data.

func (*DeferredValue) IsDeferred

func (d *DeferredValue) IsDeferred() bool

IsDeferred returns true if this value requires runtime evaluation.

type ErrorContext

type ErrorContext struct {
	// Message is the error message string.
	Message string `json:"message" yaml:"message" doc:"Error message string" maxLength:"4096" example:"connection refused"`

	// Type categorizes the error source.
	// Values: "http", "exec", "timeout", "validation", "unknown"
	Type string `json:"type" yaml:"type" doc:"Error type classification" maxLength:"32" example:"http"`

	// StatusCode is the HTTP status code (0 if not an HTTP error).
	StatusCode int `json:"statusCode" yaml:"statusCode" doc:"HTTP status code (0 if not HTTP)" maximum:"599" example:"503"`

	// ExitCode is the process exit code (0 if not an exec error).
	ExitCode int `json:"exitCode" yaml:"exitCode" doc:"Process exit code (0 if not exec)" maximum:"255" example:"1"`

	// Attempt is the current attempt number (1-based).
	// First attempt is 1, first retry is 2, etc.
	Attempt int `json:"attempt" yaml:"attempt" doc:"Current attempt number (1-based)" maximum:"100" example:"2"`

	// MaxAttempts is the maximum attempts configured.
	MaxAttempts int `json:"maxAttempts" yaml:"maxAttempts" doc:"Maximum configured attempts" maximum:"100" example:"5"`
}

ErrorContext provides error information for retryIf CEL expressions. It is exposed as __error in the CEL evaluation context.

func NewErrorContext

func NewErrorContext(err error, attempt, maxAttempts int) *ErrorContext

NewErrorContext creates an ErrorContext from an error and attempt info. It inspects the error to determine the type, status code, and exit code.

func (*ErrorContext) ToMap

func (e *ErrorContext) ToMap() map[string]any

ToMap converts ErrorContext to a map for CEL evaluation.

type ExecuteFunc

type ExecuteFunc func(ctx context.Context) (*provider.Output, error)

ExecuteFunc is the function signature for action execution. It takes a context and returns the provider output and any error.

type ExecuteResult

type ExecuteResult struct {
	// Iterations contains results for each iteration
	Iterations []*ForEachIterationResult `json:"iterations" yaml:"iterations" doc:"Results for each iteration"`

	// TotalCount is the total number of iterations
	TotalCount int `json:"totalCount" yaml:"totalCount" doc:"Total number of iterations"`

	// SuccessCount is the number of successful iterations
	SuccessCount int `json:"successCount" yaml:"successCount" doc:"Number of successful iterations"`

	// FailureCount is the number of failed iterations
	FailureCount int `json:"failureCount" yaml:"failureCount" doc:"Number of failed iterations"`

	// AllSucceeded indicates if all iterations succeeded
	AllSucceeded bool `json:"allSucceeded" yaml:"allSucceeded" doc:"Whether all iterations succeeded"`

	// FirstError is the first error encountered (if any)
	FirstError error `json:"-" yaml:"-"`
}

ExecuteResult contains the results of forEach execution.

func (*ExecuteResult) AggregatedResults

func (r *ExecuteResult) AggregatedResults() []any

AggregatedResults returns all iteration results as a slice for __actions.name.results access.

type ExecutionResult

type ExecutionResult struct {
	// Actions contains results for all executed actions
	Actions map[string]*ActionResult `json:"actions" yaml:"actions" doc:"Results for all actions"`

	// FinalStatus is the overall execution status
	FinalStatus ExecutionStatus `json:"finalStatus" yaml:"finalStatus" doc:"Overall execution status"`

	// StartTime is when execution began
	StartTime time.Time `json:"startTime" yaml:"startTime" doc:"Execution start time"`

	// EndTime is when execution completed
	EndTime time.Time `json:"endTime" yaml:"endTime" doc:"Execution end time"`

	// FailedActions contains names of actions that failed
	FailedActions []string `json:"failedActions,omitempty" yaml:"failedActions,omitempty" doc:"Names of failed actions"`

	// SkippedActions contains names of actions that were skipped
	SkippedActions []string `json:"skippedActions,omitempty" yaml:"skippedActions,omitempty" doc:"Names of skipped actions"`
}

ExecutionResult contains the final execution state.

func (*ExecutionResult) Duration

func (r *ExecutionResult) Duration() time.Duration

Duration returns the total execution duration.

type ExecutionStatus

type ExecutionStatus string

ExecutionStatus represents the overall execution status.

const (
	// ExecutionSucceeded means all actions completed successfully.
	ExecutionSucceeded ExecutionStatus = "succeeded"

	// ExecutionFailed means one or more actions failed.
	ExecutionFailed ExecutionStatus = "failed"

	// ExecutionCancelled means execution was cancelled.
	ExecutionCancelled ExecutionStatus = "cancelled"

	// ExecutionPartialSuccess means some actions succeeded with onError:continue.
	ExecutionPartialSuccess ExecutionStatus = "partial-success"
)

type Executor

type Executor struct {
	// contains filtered or unexported fields
}

Executor runs actions in dependency order with support for parallel execution, retry, timeout, and error handling.

func NewExecutor

func NewExecutor(opts ...ExecutorOption) *Executor

NewExecutor creates a new action executor with the given options. It captures the current working directory at creation time for use as __cwd in action expressions.

func (*Executor) Execute

func (e *Executor) Execute(ctx context.Context, w *Workflow) (*ExecutionResult, error)

Execute runs the workflow actions in dependency order. It executes main actions first, then always runs finally actions regardless of failures.

func (*Executor) GetContext

func (e *Executor) GetContext() *Context

GetContext returns the action context for inspection.

func (*Executor) Reset

func (e *Executor) Reset()

Reset clears the executor state for reuse.

type ExecutorOption

type ExecutorOption func(*Executor)

ExecutorOption configures the executor.

func OptionsFromAppConfig

func OptionsFromAppConfig(cfg ConfigInput) []ExecutorOption

OptionsFromAppConfig creates executor options from app configuration. CLI flags can override these defaults using the returned executor options.

Example:

cfg := action.ActionConfigInput{
    DefaultTimeout: 5 * time.Minute,
    GracePeriod:    30 * time.Second,
    MaxConcurrency: 0,
}
opts := action.OptionsFromAppConfig(cfg)
executor := action.NewExecutor(opts...)

func WithCwd added in v0.6.0

func WithCwd(cwd string) ExecutorOption

WithCwd sets the original working directory for the executor. This is exposed as __cwd in action expressions.

func WithDefaultTimeout

func WithDefaultTimeout(d time.Duration) ExecutorOption

WithDefaultTimeout sets the default timeout for actions.

func WithExecutionData added in v0.7.0

func WithExecutionData(data map[string]any) ExecutorOption

WithExecutionData sets the resolver execution metadata for action contexts. When set, the data is injected as __execution into action when conditions and provider inputs, allowing actions to read resolver status, duration, and phase.

func WithGracePeriod

func WithGracePeriod(d time.Duration) ExecutorOption

WithGracePeriod sets how long to wait for running actions during cancellation.

func WithIOStreams added in v0.4.0

func WithIOStreams(streams *provider.IOStreams) ExecutorOption

WithIOStreams sets the terminal IO streams for provider output streaming. When set, providers that support streaming (e.g., exec, message) can write output directly to the terminal in real-time. All actions share the same raw streams; use the message provider for structured terminal output.

func WithMaxConcurrency

func WithMaxConcurrency(n int) ExecutorOption

WithMaxConcurrency limits the number of parallel actions. Set to 0 for unlimited concurrency.

func WithProgressCallback

func WithProgressCallback(callback ProgressCallback) ExecutorOption

WithProgressCallback sets the progress callback for execution events.

func WithRegistry

func WithRegistry(registry RegistryInterface) ExecutorOption

WithRegistry sets the provider registry for the executor.

func WithResolverData

func WithResolverData(data map[string]any) ExecutorOption

WithResolverData sets the resolver data for input resolution.

type ExpandedAction

type ExpandedAction struct {
	// Action is the original action definition.
	*Action `json:",inline" yaml:",inline"`

	// ExpandedName is the name for this expanded action.
	// For forEach actions, this is "baseName[index]" (e.g., "deploy[0]").
	// For regular actions, this matches the action's name.
	ExpandedName string `json:"expandedName" yaml:"expandedName" doc:"Name for this expanded action" example:"deploy[0]"`

	// MaterializedInputs contains inputs that were fully resolved during graph building.
	// These do not reference __actions and can be used directly.
	MaterializedInputs map[string]any `json:"materializedInputs,omitempty" yaml:"materializedInputs,omitempty" doc:"Resolved input values"`

	// DeferredInputs contains inputs that reference __actions and must be resolved at runtime.
	DeferredInputs map[string]*DeferredValue `json:"deferredInputs,omitempty" yaml:"deferredInputs,omitempty" doc:"Inputs requiring runtime resolution"`

	// Section indicates which workflow section this action belongs to.
	Section string `json:"section" yaml:"section" doc:"Workflow section (actions or finally)" example:"actions"`

	// ForEachMetadata contains expansion information if this action was expanded from a forEach.
	ForEachMetadata *ForEachExpansionMetadata `json:"forEachMetadata,omitempty" yaml:"forEachMetadata,omitempty" doc:"ForEach expansion info"`

	// Dependencies contains the same-section scheduling dependencies for this expanded action.
	// This includes explicit dependsOn entries and any __actions references that resolve to actions
	// within the same section. Only same-section deps affect ExecutionOrder / FinallyOrder phase
	// computation; use CrossSectionRefs for informational traceability of cross-section reads.
	// For expanded forEach actions, this includes dependencies on all iterations of referenced forEach actions.
	Dependencies []string `json:"dependencies" yaml:"dependencies" doc:"Same-section scheduling dependencies"`

	// CrossSectionRefs lists action names from a different section that this action reads via
	// __actions references (e.g., a finally action reading results from a main actions action).
	// These are informational only—they do not affect FinallyOrder or ExecutionOrder phase
	// computation because cross-section ordering is guaranteed structurally (all main actions
	// complete before any finally action runs).
	CrossSectionRefs []string `` /* 145-byte string literal not displayed */

	// WhenSkipped is true when the action's when condition was evaluated at graph-build
	// time (because it only references resolver data, not __actions/aliases) and returned
	// false. The executor skips this action without re-evaluating the condition.
	WhenSkipped bool `json:"whenSkipped,omitempty" yaml:"whenSkipped,omitempty" doc:"Action pre-skipped by when condition during graph building"`

	// ExpandedExclusive contains the effective exclusive action names for this expanded action.
	// For forEach base action references, this expands to all iterations (e.g., "deploy" → "deploy[0]", "deploy[1]").
	ExpandedExclusive []string `` /* 133-byte string literal not displayed */
}

ExpandedAction is an action with materialized inputs ready for execution. For forEach actions, each iteration becomes a separate ExpandedAction.

func (*ExpandedAction) GetExpandedName

func (e *ExpandedAction) GetExpandedName() string

GetExpandedName returns the expanded action name. For forEach actions this is "baseName[index]", for regular actions it matches the original name.

func (*ExpandedAction) GetOriginalName

func (e *ExpandedAction) GetOriginalName() string

GetOriginalName returns the original action name (before forEach expansion).

func (*ExpandedAction) HasDeferredInputs

func (e *ExpandedAction) HasDeferredInputs() bool

HasDeferredInputs returns true if the action has any deferred inputs.

func (*ExpandedAction) IsForEachIteration

func (e *ExpandedAction) IsForEachIteration() bool

IsForEachIteration returns true if this action is an expanded forEach iteration.

type ForEachExecuteFunc

type ForEachExecuteFunc func(ctx context.Context, item any, index int) (*provider.Output, error)

ForEachExecuteFunc is the function signature for executing a single forEach iteration. It receives the execution context, the current item, and the iteration index. Returns the provider output and any error.

type ForEachExecutor

type ForEachExecutor struct {
	// contains filtered or unexported fields
}

ForEachExecutor handles forEach iteration execution with concurrency control. It manages parallel execution of iterations while respecting concurrency limits and error handling policies.

func FromForEachClause

func FromForEachClause(clause *spec.ForEachClause, progressCallback RetryObserver) *ForEachExecutor

FromForEachClause creates a ForEachExecutor configured from a ForEachClause.

func NewForEachExecutor

func NewForEachExecutor(opts ...ForEachExecutorOption) *ForEachExecutor

NewForEachExecutor creates a new ForEachExecutor with the given options.

func (*ForEachExecutor) Execute

func (e *ForEachExecutor) Execute(
	ctx context.Context,
	actionName string,
	items []any,
	execute ForEachExecuteFunc,
) (*ExecuteResult, error)

Execute runs forEach iterations with concurrency control and error handling. Items is the array to iterate over, actionName is used for naming iterations.

type ForEachExecutorOption

type ForEachExecutorOption func(*ForEachExecutor)

ForEachExecutorOption configures the ForEachExecutor.

func WithForEachConcurrency

func WithForEachConcurrency(n int) ForEachExecutorOption

WithForEachConcurrency sets the concurrency limit for forEach execution. Set to 0 for unlimited concurrency.

func WithForEachOnError

func WithForEachOnError(behavior spec.OnErrorBehavior) ForEachExecutorOption

WithForEachOnError sets the error handling behavior.

func WithForEachProgressCallback

func WithForEachProgressCallback(callback RetryObserver) ForEachExecutorOption

WithForEachProgressCallback sets the progress callback for forEach execution. It accepts a RetryObserver since forEach only reports retry and iteration progress.

type ForEachExpansionMetadata

type ForEachExpansionMetadata struct {
	// ExpandedFrom is the original action name before forEach expansion.
	ExpandedFrom string `json:"expandedFrom" yaml:"expandedFrom" doc:"Original action name" example:"deploy"`

	// Index is the iteration index (0-based) within the forEach expansion.
	Index int `json:"index" yaml:"index" doc:"Iteration index" example:"0"`

	// Item is the current iteration item value.
	Item any `json:"item,omitempty" yaml:"item,omitempty" doc:"Current iteration item"`
}

ForEachExpansionMetadata tracks forEach expansion information.

type ForEachIterationResult

type ForEachIterationResult struct {
	// Index is the 0-based iteration index.
	Index int `json:"index" yaml:"index" doc:"Iteration index (0-based)" maximum:"10000" example:"0"`

	// Name is the expanded action name (e.g., "deploy[0]", "deploy[1]").
	Name string `json:"name" yaml:"name" doc:"Expanded action name" maxLength:"150" example:"deploy[0]"`

	// Results contains the output data from this iteration.
	Results any `json:"results,omitempty" yaml:"results,omitempty" doc:"Iteration output data"`

	// Status is the execution status of this iteration.
	Status ActionStatus `json:"status" yaml:"status" doc:"Iteration execution status" maxLength:"16" example:"success"`

	// StartTime is when this iteration began.
	StartTime *time.Time `json:"startTime,omitempty" yaml:"startTime,omitempty" doc:"Iteration start time"`

	// EndTime is when this iteration completed.
	EndTime *time.Time `json:"endTime,omitempty" yaml:"endTime,omitempty" doc:"Iteration end time"`

	// Error contains the error message if this iteration failed.
	Error string `json:"error,omitempty" yaml:"error,omitempty" doc:"Error message (if failed)" maxLength:"4096" example:"timeout exceeded"`
}

ForEachIterationResult represents the result of a single forEach iteration. When an action uses forEach, results are collected into an array of these.

func (*ForEachIterationResult) Duration

func (r *ForEachIterationResult) Duration() time.Duration

Duration returns the iteration execution duration, or zero if not available.

type Graph

type Graph struct {
	// Actions is a map of all expanded actions keyed by their name.
	// ForEach actions are expanded with indexed names like "deploy[0]", "deploy[1]".
	Actions map[string]*ExpandedAction `json:"actions" yaml:"actions" doc:"All expanded actions"`

	// ExecutionOrder contains phases of action names that can run in parallel.
	// Phase 0 contains actions with no dependencies, phase 1 contains actions
	// that depend only on phase 0 actions, and so on.
	ExecutionOrder [][]string `json:"executionOrder" yaml:"executionOrder" doc:"Parallel execution phases for main actions"`

	// FinallyOrder contains phases for the finally section.
	// Finally actions have an implicit dependency on all main actions completing.
	FinallyOrder [][]string `json:"finallyOrder,omitempty" yaml:"finallyOrder,omitempty" doc:"Parallel execution phases for finally actions"`

	// AliasMap maps action aliases to their original action names.
	// This enables shorter, more readable references in CEL expressions.
	// For example, alias "config" → action name "fetchConfiguration".
	AliasMap map[string]string `json:"aliasMap,omitempty" yaml:"aliasMap,omitempty" doc:"Alias-to-action-name mapping"`
}

Graph represents the executable action dependency graph. It contains expanded actions with materialized inputs and execution order.

func BuildGraph

func BuildGraph(ctx context.Context, w *Workflow, resolverData map[string]any, opts *BuildGraphOptions) (*Graph, error)

BuildGraph constructs the action dependency graph from a workflow. It expands forEach actions, materializes inputs (where possible), extracts dependencies, and computes execution phases.

func (*Graph) GetActionsByPhase

func (g *Graph) GetActionsByPhase(phase int) []*ExpandedAction

GetActionsByPhase returns all actions in a specific execution phase.

func (*Graph) GetAllActionNames

func (g *Graph) GetAllActionNames() []string

GetAllActionNames returns all action names in the graph including expanded forEach names.

func (*Graph) GetFinallyActionNames

func (g *Graph) GetFinallyActionNames() []string

GetFinallyActionNames returns only the finally section action names.

func (*Graph) GetFinallyActionsByPhase

func (g *Graph) GetFinallyActionsByPhase(phase int) []*ExpandedAction

GetFinallyActionsByPhase returns all finally actions in a specific execution phase.

func (*Graph) GetForEachIterations

func (g *Graph) GetForEachIterations(baseName string) []string

GetForEachIterations returns all expanded iteration names for a base action name. Returns nil if the action is not a forEach action or doesn't exist.

func (*Graph) GetMainActionNames

func (g *Graph) GetMainActionNames() []string

GetMainActionNames returns only the main section action names.

func (*Graph) TotalFinallyPhases

func (g *Graph) TotalFinallyPhases() int

TotalFinallyPhases returns the total number of execution phases for finally actions.

func (*Graph) TotalPhases

func (g *Graph) TotalPhases() int

TotalPhases returns the total number of execution phases for main actions.

type GraphVisualization

type GraphVisualization struct {
	// Phases contains main action phases for visualization.
	Phases []*VisualizationPhase `json:"phases" yaml:"phases" doc:"Main action phases for visualization" maxItems:"100"`

	// FinallyPhases contains finally action phases.
	FinallyPhases []*VisualizationPhase `json:"finallyPhases,omitempty" yaml:"finallyPhases,omitempty" doc:"Finally/cleanup action phases" maxItems:"100"`

	// Edges represents same-section scheduling dependencies between actions.
	Edges []*VisualizationEdge `json:"edges" yaml:"edges" doc:"Same-section scheduling dependencies" maxItems:"10000"`

	// CrossSectionEdges represents informational cross-section references
	// (e.g. finally actions reading main-section results via __actions).
	CrossSectionEdges []*VisualizationEdge `` /* 131-byte string literal not displayed */

	// Stats contains graph statistics.
	Stats *VisualizationStats `json:"stats" yaml:"stats" doc:"Graph statistics"`
}

GraphVisualization holds visualization data for rendering.

func BuildVisualization

func BuildVisualization(graph *Graph) *GraphVisualization

BuildVisualization creates visualization data from a Graph.

func (*GraphVisualization) RenderASCII

func (v *GraphVisualization) RenderASCII(w io.Writer) error

RenderASCII generates ASCII art representation of the action graph.

func (*GraphVisualization) RenderDOT

func (v *GraphVisualization) RenderDOT(w io.Writer) error

RenderDOT generates GraphViz DOT format.

func (*GraphVisualization) RenderMermaid

func (v *GraphVisualization) RenderMermaid(w io.Writer) error

RenderMermaid generates Mermaid diagram format.

type HTTPError

type HTTPError struct {
	StatusCode int    `json:"statusCode" yaml:"statusCode" doc:"HTTP status code" maximum:"599" example:"503"`
	Message    string `json:"message" yaml:"message" doc:"Error message" maxLength:"4096" example:"Service Unavailable"`
}

HTTPError represents an HTTP error with a status code. Providers can return this error type to enable status code-based retry logic.

func (*HTTPError) Error

func (e *HTTPError) Error() string

Error implements the error interface.

type NoOpProgressCallback

type NoOpProgressCallback struct{}

NoOpProgressCallback is a progress callback that does nothing. Useful for testing or when progress tracking is not needed.

func (NoOpProgressCallback) OnActionCancelled

func (NoOpProgressCallback) OnActionCancelled(_ string)

func (NoOpProgressCallback) OnActionComplete

func (NoOpProgressCallback) OnActionComplete(_ string, _ any)

func (NoOpProgressCallback) OnActionFailed

func (NoOpProgressCallback) OnActionFailed(_ string, _ error)

func (NoOpProgressCallback) OnActionSkipped

func (NoOpProgressCallback) OnActionSkipped(_, _ string)

func (NoOpProgressCallback) OnActionStart

func (NoOpProgressCallback) OnActionStart(_ string)

func (NoOpProgressCallback) OnActionTimeout

func (NoOpProgressCallback) OnActionTimeout(_ string, _ time.Duration)

func (NoOpProgressCallback) OnFinallyComplete

func (NoOpProgressCallback) OnFinallyComplete()

func (NoOpProgressCallback) OnFinallyStart

func (NoOpProgressCallback) OnFinallyStart()

func (NoOpProgressCallback) OnForEachProgress

func (NoOpProgressCallback) OnForEachProgress(_ string, _, _ int)

func (NoOpProgressCallback) OnPhaseComplete

func (NoOpProgressCallback) OnPhaseComplete(_ int)

func (NoOpProgressCallback) OnPhaseStart

func (NoOpProgressCallback) OnPhaseStart(_ int, _ []string)

func (NoOpProgressCallback) OnRetryAttempt

func (NoOpProgressCallback) OnRetryAttempt(_ string, _, _ int, _ error)

type Observer added in v0.6.0

type Observer interface {
	// OnActionStart is called when an action begins execution.
	OnActionStart(actionName string)

	// OnActionComplete is called when an action completes successfully.
	OnActionComplete(actionName string, results any)

	// OnActionFailed is called when an action fails.
	OnActionFailed(actionName string, err error)

	// OnActionSkipped is called when an action is skipped.
	OnActionSkipped(actionName, reason string)

	// OnActionTimeout is called when an action times out.
	OnActionTimeout(actionName string, timeout time.Duration)

	// OnActionCancelled is called when an action is cancelled.
	OnActionCancelled(actionName string)
}

Observer receives lifecycle events for individual actions. Implement this interface when you only need to track action-level events (start, complete, fail, skip, timeout, cancel).

type PhaseObserver added in v0.6.0

type PhaseObserver interface {
	// OnPhaseStart is called when a new execution phase begins.
	OnPhaseStart(phase int, actionNames []string)

	// OnPhaseComplete is called when an execution phase completes.
	OnPhaseComplete(phase int)

	// OnFinallyStart is called when the finally section begins.
	OnFinallyStart()

	// OnFinallyComplete is called when the finally section completes.
	OnFinallyComplete()
}

PhaseObserver receives lifecycle events for execution phases. Implement this interface when you only need to track phase boundaries and the finally section.

type ProgressCallback

type ProgressCallback interface {
	Observer
	PhaseObserver
	RetryObserver
}

ProgressCallback receives execution events for progress reporting. It composes ActionObserver, PhaseObserver, and RetryObserver for full lifecycle coverage. New code should prefer accepting the narrower observer interface(s) it actually needs.

type RegistryInterface

type RegistryInterface interface {
	// Get retrieves a provider by name.
	Get(name string) (provider.Provider, bool)

	// Has checks if a provider exists.
	Has(name string) bool
}

RegistryInterface defines the provider registry operations needed for validation. This interface allows for mocking in tests.

type RenderOptions

type RenderOptions struct {
	// Format specifies the output format: "json" (default) or "yaml".
	Format string `json:"format" yaml:"format" doc:"Output format" maxLength:"16" example:"json"`

	// IncludeTimestamp adds a generation timestamp to metadata.
	IncludeTimestamp bool `json:"includeTimestamp" yaml:"includeTimestamp" doc:"Add generation timestamp to metadata"`

	// PrettyPrint enables indented output for readability.
	PrettyPrint bool `json:"prettyPrint" yaml:"prettyPrint" doc:"Enable indented output"`
}

RenderOptions configures rendering behavior.

func DefaultRenderOptions

func DefaultRenderOptions() *RenderOptions

DefaultRenderOptions returns the default rendering options.

type RenderedAction

type RenderedAction struct {
	// Name is the action name (may be expanded with index for forEach).
	Name string `json:"name" yaml:"name" doc:"Action name" maxLength:"150" example:"deploy[0]"`

	// OriginalName is the action name before forEach expansion (if applicable).
	OriginalName string `` /* 127-byte string literal not displayed */

	// Description provides documentation for the action.
	Description string `json:"description,omitempty" yaml:"description,omitempty" doc:"Human-readable description" maxLength:"500"`

	// DisplayName is a human-friendly name for UI display.
	DisplayName string `json:"displayName,omitempty" yaml:"displayName,omitempty" doc:"Display name for UI" maxLength:"100"`

	// Sensitive indicates the action handles sensitive data.
	Sensitive bool `json:"sensitive,omitempty" yaml:"sensitive,omitempty" doc:"If true, mask in logs"`

	// Provider specifies which action provider to use.
	Provider string `json:"provider" yaml:"provider" doc:"Provider name" maxLength:"100" example:"shell"`

	// DependsOn lists action names that must complete first.
	DependsOn []string `json:"dependsOn,omitempty" yaml:"dependsOn,omitempty" doc:"Dependency list" maxItems:"100"`

	// CrossSectionRefs lists action names from the other workflow section that this action
	// reads via __actions references. Informational only — does not affect phase scheduling.
	// Populated for finally actions that read from workflow.actions via __actions.<name>.
	CrossSectionRefs []string `` /* 139-byte string literal not displayed */

	// When contains the condition for execution.
	// Can be a boolean (already evaluated) or a DeferredValue (requires runtime evaluation).
	When any `json:"when,omitempty" yaml:"when,omitempty" doc:"Execution condition (bool or deferred)"`

	// OnError defines behavior when this action fails.
	OnError string `json:"onError,omitempty" yaml:"onError,omitempty" doc:"Error handling behavior" example:"fail"`

	// Timeout is the maximum execution duration as a string.
	Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty" doc:"Max duration" example:"30s"`

	// Retry contains retry configuration.
	Retry *RenderedRetryConfig `json:"retry,omitempty" yaml:"retry,omitempty" doc:"Retry settings"`

	// Inputs contains the action inputs.
	// Values are either concrete (materialized) or DeferredValue structs.
	Inputs map[string]any `json:"inputs" yaml:"inputs" doc:"Action inputs (may contain deferred values)"`

	// Section indicates which workflow section this action belongs to.
	Section string `json:"section" yaml:"section" doc:"Workflow section" example:"actions"`

	// ForEach contains forEach metadata if this is an expanded iteration.
	ForEach *RenderedForEachMetadata `json:"forEach,omitempty" yaml:"forEach,omitempty" doc:"ForEach expansion info"`
}

RenderedAction is a fully rendered action ready for execution.

type RenderedForEachMetadata

type RenderedForEachMetadata struct {
	// ExpandedFrom is the original action name before expansion.
	ExpandedFrom string `json:"expandedFrom" yaml:"expandedFrom" doc:"Original action name" example:"deploy"`

	// Index is the iteration index (0-based).
	Index int `json:"index" yaml:"index" doc:"Iteration index" example:"0"`

	// Item is the current iteration item value.
	Item any `json:"item,omitempty" yaml:"item,omitempty" doc:"Iteration item value"`

	// Concurrency is the concurrency limit from the original forEach clause.
	Concurrency int `json:"concurrency,omitempty" yaml:"concurrency,omitempty" doc:"Concurrency limit (0=unlimited)"`

	// OnError is the error handling behavior for forEach iterations.
	OnError string `json:"onError,omitempty" yaml:"onError,omitempty" doc:"ForEach error handling" example:"fail"`
}

RenderedForEachMetadata tracks forEach expansion information in rendered output.

type RenderedGraph

type RenderedGraph struct {
	// APIVersion identifies the schema version.
	APIVersion string `json:"apiVersion" yaml:"apiVersion" doc:"Schema version" example:"scafctl.oakwood-commons.github.io/v1alpha1"`

	// Kind identifies the resource type.
	Kind string `json:"kind" yaml:"kind" doc:"Resource kind" example:"ActionGraph"`

	// Metadata contains graph-level metadata.
	Metadata *RenderedMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty" doc:"Graph metadata"`

	// ExecutionOrder contains phases of action names that can run in parallel.
	ExecutionOrder [][]string `json:"executionOrder" yaml:"executionOrder" doc:"Parallel execution phases for main actions"`

	// FinallyOrder contains phases for the finally section.
	FinallyOrder [][]string `json:"finallyOrder,omitempty" yaml:"finallyOrder,omitempty" doc:"Parallel execution phases for finally actions"`

	// Actions is a map of all rendered actions keyed by their name.
	Actions map[string]*RenderedAction `json:"actions" yaml:"actions" doc:"All actions in the graph"`
}

RenderedGraph is the executor-ready action graph output structure. This is the serializable form that can be consumed by external executors.

func RenderToStruct

func RenderToStruct(graph *Graph, opts *RenderOptions) (*RenderedGraph, error)

RenderToStruct produces a RenderedGraph struct without serializing to bytes. This is useful for programmatic access to the rendered graph.

type RenderedMetadata

type RenderedMetadata struct {
	// GeneratedAt is the timestamp when the graph was rendered.
	GeneratedAt string `json:"generatedAt,omitempty" yaml:"generatedAt,omitempty" doc:"Render timestamp" example:"2026-01-29T12:00:00Z"`

	// TotalActions is the total number of actions in the graph.
	TotalActions int `json:"totalActions" yaml:"totalActions" doc:"Total action count" example:"5"`

	// TotalPhases is the total number of execution phases (main + finally).
	TotalPhases int `json:"totalPhases" yaml:"totalPhases" doc:"Total phase count" example:"3"`

	// HasFinally indicates if the graph has finally actions.
	HasFinally bool `json:"hasFinally" yaml:"hasFinally" doc:"Whether finally section exists"`

	// ForEachExpansions maps original action names to their expanded names.
	ForEachExpansions map[string][]string `json:"forEachExpansions,omitempty" yaml:"forEachExpansions,omitempty" doc:"ForEach expansion mapping"`
}

RenderedMetadata contains graph-level metadata.

type RenderedRetryConfig

type RenderedRetryConfig struct {
	// MaxAttempts is the total number of execution attempts.
	MaxAttempts int `json:"maxAttempts" yaml:"maxAttempts" doc:"Total attempts" minimum:"1" example:"3"`

	// Backoff defines the delay strategy between retries.
	Backoff string `json:"backoff,omitempty" yaml:"backoff,omitempty" doc:"Backoff strategy" example:"exponential"`

	// InitialDelay is the delay before the first retry.
	InitialDelay string `json:"initialDelay,omitempty" yaml:"initialDelay,omitempty" doc:"Initial delay" example:"1s"`

	// MaxDelay caps the maximum delay between retries.
	MaxDelay string `json:"maxDelay,omitempty" yaml:"maxDelay,omitempty" doc:"Max delay cap" example:"30s"`
}

RenderedRetryConfig is the serializable retry configuration.

type ResultSchemaMode

type ResultSchemaMode string

ResultSchemaMode defines the validation behavior when result schema validation fails.

const (
	// ResultSchemaModeError fails the action when schema validation fails (default).
	ResultSchemaModeError ResultSchemaMode = "error"

	// ResultSchemaModeWarn logs a warning and continues execution.
	ResultSchemaModeWarn ResultSchemaMode = "warn"

	// ResultSchemaModeIgnore skips schema validation entirely.
	ResultSchemaModeIgnore ResultSchemaMode = "ignore"
)

func (ResultSchemaMode) IsValid

func (m ResultSchemaMode) IsValid() bool

IsValid returns true if the result schema mode is valid.

func (ResultSchemaMode) OrDefault

func (m ResultSchemaMode) OrDefault() ResultSchemaMode

OrDefault returns the mode or the default (ResultSchemaModeError) if empty.

type RetryCallback

type RetryCallback interface {
	// OnRetryAttempt is called before each retry attempt (not for the initial attempt).
	// attempt is 1-indexed (first retry is attempt 2).
	// err is the error from the previous attempt.
	OnRetryAttempt(actionName string, attempt, maxAttempts int, err error)
}

RetryCallback receives retry events for progress reporting.

type RetryConfig

type RetryConfig struct {
	// MaxAttempts is the total number of execution attempts (including initial).
	// Must be >= 1.
	MaxAttempts int `json:"maxAttempts" yaml:"maxAttempts" doc:"Total execution attempts (min: 1)" minimum:"1" maximum:"100" example:"3"`

	// Backoff defines the delay strategy between retries.
	// Default is "fixed".
	Backoff BackoffType `json:"backoff,omitempty" yaml:"backoff,omitempty" doc:"Backoff strategy" maxLength:"16" example:"exponential" default:"fixed"`

	// InitialDelay is the delay before the first retry.
	// For exponential backoff, subsequent delays are multiplied by 2.
	InitialDelay *duration.Duration `json:"initialDelay,omitempty" yaml:"initialDelay,omitempty" doc:"Delay before first retry" example:"1s"`

	// MaxDelay caps the maximum delay between retries.
	// Only meaningful for linear and exponential backoff.
	MaxDelay *duration.Duration `json:"maxDelay,omitempty" yaml:"maxDelay,omitempty" doc:"Maximum delay between retries" example:"30s"`

	// RetryIf is a CEL expression that determines whether a retry should occur.
	// The expression has access to __error context with error details:
	//   - __error.message: Error message string
	//   - __error.type: Error type ("http", "exec", "timeout", "validation", "unknown")
	//   - __error.statusCode: HTTP status code (0 if not HTTP)
	//   - __error.exitCode: Process exit code (0 if not exec)
	//   - __error.attempt: Current attempt number (1-based)
	//   - __error.maxAttempts: Maximum configured attempts
	// If not specified, all errors are retried (default behavior).
	// If specified and evaluates to false, no retry occurs.
	// Example: "${ __error.statusCode == 429 || __error.statusCode >= 500 }"
	RetryIf *celexp.Expression `json:"retryIf,omitempty" yaml:"retryIf,omitempty" doc:"CEL expression to determine if retry should occur"`
}

RetryConfig defines automatic retry behavior for failed actions.

type RetryExecutor

type RetryExecutor struct {
	// contains filtered or unexported fields
}

RetryExecutor wraps action execution with retry logic and backoff strategies. It handles transient failures by retrying the action up to maxAttempts times with configurable delays between attempts.

func NewRetryExecutor

func NewRetryExecutor(config *RetryConfig) *RetryExecutor

NewRetryExecutor creates a new retry executor with the given configuration. If config is nil, returns an executor that performs no retries (single attempt).

func (*RetryExecutor) CalculateDelay

func (r *RetryExecutor) CalculateDelay(attempt int) time.Duration

CalculateDelay computes the delay before a retry attempt based on the backoff strategy. attempt is 1-indexed (first retry is attempt 2, since attempt 1 is the initial execution). Returns 0 for the first attempt or if no retry config is set.

func (*RetryExecutor) ExecuteWithRetry

func (r *RetryExecutor) ExecuteWithRetry(
	ctx context.Context,
	actionName string,
	execute ExecuteFunc,
	callback RetryCallback,
) (*provider.Output, error)

ExecuteWithRetry runs an action with retry support. It executes the action up to maxAttempts times, with delays between retries based on the configured backoff strategy.

Parameters: - ctx: Context for cancellation and timeout - actionName: Name of the action (for callbacks) - execute: Function that performs the actual action execution - callback: Optional callback for retry events (can be nil)

Returns the output from a successful execution or the last error if all attempts fail.

func (*RetryExecutor) ExecuteWithRetryDetailed

func (r *RetryExecutor) ExecuteWithRetryDetailed(
	ctx context.Context,
	actionName string,
	execute ExecuteFunc,
	callback RetryCallback,
) *RetryResult

ExecuteWithRetryDetailed runs an action with retry support and returns detailed results. This is useful for debugging and detailed progress reporting.

func (*RetryExecutor) MaxAttempts

func (r *RetryExecutor) MaxAttempts() int

MaxAttempts returns the maximum number of execution attempts. Returns 1 if no config is set (no retries).

func (*RetryExecutor) ShouldRetry

func (r *RetryExecutor) ShouldRetry(ctx context.Context, err error, attempt int) (bool, error)

ShouldRetry determines if an execution should be retried based on the error and attempt number. Returns (shouldRetry, error). The error is non-nil if retryIf expression evaluation fails. Returns false if: - No retry config is set - Max attempts reached - Context was cancelled - The error is nil (success) - retryIf expression evaluates to false

func (*RetryExecutor) WithJitter

func (r *RetryExecutor) WithJitter(fn func(time.Duration) time.Duration) *RetryExecutor

WithJitter sets a custom jitter function for testing. The jitter function receives the base delay and returns a modified delay.

type RetryObserver added in v0.6.0

type RetryObserver interface {
	// OnRetryAttempt is called before each retry attempt.
	OnRetryAttempt(actionName string, attempt, maxAttempts int, err error)

	// OnForEachProgress is called during forEach execution.
	OnForEachProgress(actionName string, completed, total int)
}

RetryObserver receives retry and iteration progress events. Implement this interface when you only need to track retries and forEach progress.

type RetryResult

type RetryResult struct {
	// Output is the successful output (nil if all attempts failed)
	Output *provider.Output `json:"output,omitempty" yaml:"output,omitempty" doc:"Successful output (nil if all attempts failed)"`

	// Attempts is the total number of attempts made
	Attempts int `json:"attempts" yaml:"attempts" doc:"Total number of attempts made" maximum:"100" example:"3"`

	// TotalDuration is the total time spent including delays
	TotalDuration time.Duration `json:"totalDuration" yaml:"totalDuration" doc:"Total time spent including delays"`

	// FinalError is the error from the last attempt (nil if succeeded)
	FinalError error `json:"finalError,omitempty" yaml:"finalError,omitempty" doc:"Error from the last attempt (nil if succeeded)"`

	// AttemptErrors contains errors from each attempt
	AttemptErrors []error `json:"attemptErrors,omitempty" yaml:"attemptErrors,omitempty" doc:"Errors from each attempt" maxItems:"100"`
}

RetryResult contains information about a retry execution.

type SkipReason

type SkipReason string

SkipReason indicates why an action was skipped.

const (
	// SkipReasonCondition indicates the when condition evaluated to false.
	SkipReasonCondition SkipReason = "condition"

	// SkipReasonDependencyFailed indicates a required dependency failed.
	SkipReasonDependencyFailed SkipReason = "dependency-failed"
)

type ValidationError

type ValidationError struct {
	// Section is the workflow section where the error occurred ("actions" or "finally").
	Section string `json:"section,omitempty" yaml:"section,omitempty" doc:"Workflow section (actions or finally)" example:"actions"`

	// ActionName is the name of the action with the validation error.
	ActionName string `json:"actionName,omitempty" yaml:"actionName,omitempty" doc:"Action that failed validation" example:"deploy"`

	// Field is the specific field that failed validation.
	Field string `json:"field,omitempty" yaml:"field,omitempty" doc:"Field that failed validation" example:"dependsOn"`

	// Message is the human-readable validation error message.
	Message string `json:"message" yaml:"message" doc:"Validation failure message" maxLength:"500"`
}

ValidationError provides detailed validation failure information. It contains context about where the validation error occurred.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error implements the error interface.

type VisualizationEdge

type VisualizationEdge struct {
	From         string `json:"from" yaml:"from" doc:"Source action name" maxLength:"256" example:"deploy"`
	To           string `json:"to" yaml:"to" doc:"Target action name" maxLength:"256" example:"build"`
	Label        string `json:"label,omitempty" yaml:"label,omitempty" doc:"Edge label" maxLength:"128" example:"depends_on"`
	CrossSection bool   `` /* 141-byte string literal not displayed */
}

VisualizationEdge represents a dependency edge.

type VisualizationPhase

type VisualizationPhase struct {
	Phase   int      `json:"phase" yaml:"phase" doc:"Phase number" maximum:"100" example:"1"`
	Actions []string `json:"actions" yaml:"actions" doc:"Action names in this phase" maxItems:"1000"`
	Section string   `json:"section" yaml:"section" doc:"Workflow section (actions or finally)" maxLength:"16" example:"actions"`
}

VisualizationPhase represents a phase in the execution order.

type VisualizationStats

type VisualizationStats struct {
	TotalActions        int     `json:"totalActions" yaml:"totalActions" doc:"Total number of actions" maximum:"1000" example:"10"`
	TotalPhases         int     `json:"totalPhases" yaml:"totalPhases" doc:"Total number of phases" maximum:"100" example:"3"`
	MaxParallelism      int     `json:"maxParallelism" yaml:"maxParallelism" doc:"Maximum parallelism across all phases" maximum:"1000" example:"4"`
	AvgDependencies     float64 `json:"avgDependencies" yaml:"avgDependencies" doc:"Average same-section scheduling dependencies per action"`
	AvgCrossSectionRefs float64 `json:"avgCrossSectionRefs,omitempty" yaml:"avgCrossSectionRefs,omitempty" doc:"Average cross-section references per action"`
	HasFinally          bool    `json:"hasFinally" yaml:"hasFinally" doc:"Whether the graph has finally actions"`
	ForEachCount        int     `json:"forEachCount" yaml:"forEachCount" doc:"Number of forEach actions" maximum:"1000" example:"2"`
}

VisualizationStats contains graph statistics.

type Workflow

type Workflow struct {
	// Actions is a map of action definitions that execute based on their dependencies.
	// Actions can depend on other actions and access their results via __actions.<name>.results.
	Actions map[string]*Action `json:"actions,omitempty" yaml:"actions,omitempty" doc:"Action definitions keyed by name"`

	// Finally is a map of actions that execute after all regular actions complete.
	// Finally actions cannot use dependsOn to reference regular actions, but can
	// access all regular action results. They have an implicit dependency on all regular actions.
	Finally map[string]*Action `json:"finally,omitempty" yaml:"finally,omitempty" doc:"Cleanup/finalization actions that run after all regular actions"`

	// ResultSchemaMode sets the default validation behavior for resultSchema across all actions.
	// Individual actions can override this setting. Default is "error".
	ResultSchemaMode ResultSchemaMode `` /* 158-byte string literal not displayed */
}

Workflow contains the action execution specification. It defines two sections: regular actions that execute based on dependencies, and finally actions that execute after all regular actions complete.

func FilterWorkflowActions added in v0.8.0

func FilterWorkflowActions(w *Workflow, targetNames []string) (*Workflow, error)

FilterWorkflowActions returns a new Workflow containing only the specified actions and their transitive dependsOn dependencies. Finally actions are always included regardless of the filter. When targetNames is empty the original workflow is returned unchanged.

An error is returned if any target name does not match an action in the workflow's actions section.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL