Documentation
¶
Overview ¶
Package spi is the L0 contract — the "service provider interface" — of echopoint-runner. It owns the wire-facing and execution-result types that cross process and repo boundaries: echopoint's openapi.yaml binds its enums here via x-go-type, and the control plane decodes flow results shaped by these types over SSE and the database.
spi depends on nothing inside echopoint-runner (only the standard library), so it sits at the bottom of the import graph: pkg/node, pkg/extractors, pkg/executionevents and pkg/engine all depend on spi, never the reverse. These contract types are referenced directly as spi.* throughout the runner; spi is the single source of truth, with no re-export or alias layer in between.
The JSON struct tags and enum string values in this package are a cross-repo contract; changing them is a breaking wire change.
Index ¶
- func As[T AnyResult](result AnyResult) (T, bool)
- func ErrorCode(err error) string
- func MustAs[T AnyResult](result AnyResult) T
- func SafeErrorMessage(err error) string
- type AnyResult
- type AssertionResult
- type BaseExecutionResult
- func (b *BaseExecutionResult) Fail(err error, code string)
- func (b *BaseExecutionResult) GetDisplayName() string
- func (b *BaseExecutionResult) GetError() error
- func (b *BaseExecutionResult) GetExecutedAt() time.Time
- func (b *BaseExecutionResult) GetInputs() map[string]any
- func (b *BaseExecutionResult) GetNodeID() string
- func (b *BaseExecutionResult) GetNodeType() Kind
- func (b *BaseExecutionResult) GetOutputs() map[string]any
- func (b *BaseExecutionResult) MergeOutputs(outputs map[string]any)
- func (b *BaseExecutionResult) SetAssertionResults(results []AssertionResult)
- type DynamicResolver
- type EventType
- type ExecutionContext
- type ExtractorType
- type FlowExecutionResult
- type Kind
- type ModuleExecutionRequest
- type ModuleExecutor
- type ModuleResolver
- type Node
- type OutputView
- type ResolvedModuleFlow
- type RoutingResult
- type RunWhen
- type UserError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func As ¶
As safely casts an AnyResult to a concrete result type T (e.g. As[*request.RequestExecutionResult](result)). It reports false instead of panicking when the dynamic type does not match.
func ErrorCode ¶ added in v0.50.0
ErrorCode returns a UserError's stable code, or "" for a runner fault.
func MustAs ¶
MustAs casts an AnyResult to a concrete result type T, panicking when the dynamic type does not match. Use only where the type is an invariant.
func SafeErrorMessage ¶ added in v0.50.0
SafeErrorMessage returns the part of err that may be written to a log line.
For a UserError that is its authored Message alone. The Cause is dropped: for a request or SSE failure it is the raw Go transport error, which embeds the full target URL — query string, and therefore any secret carried in it, included. Logs are not redacted, so the cause never reaches them; it stays on the error for errors.Is/errors.As, and the failure is reported to the user through the (redacted) node result instead.
Types ¶
type AnyResult ¶
type AnyResult interface {
GetNodeID() string
GetDisplayName() string
GetNodeType() Kind
GetInputs() map[string]any
GetOutputs() map[string]any
GetError() error
GetExecutedAt() time.Time
// contains filtered or unexported methods
}
AnyResult is the interface for all node execution results (polymorphic).
type AssertionResult ¶
type AssertionResult struct {
Index int `json:"index"`
Extractor string `json:"extractor"`
Operator string `json:"operator"`
Expected any `json:"expected"`
Actual any `json:"actual"`
Passed bool `json:"passed"`
Error string `json:"error,omitempty"`
}
AssertionResult records the outcome of evaluating a single node assertion, captured whether it passed or failed so the full result can be reported.
type BaseExecutionResult ¶
type BaseExecutionResult struct {
NodeID string `json:"node_id"`
DisplayName string `json:"display_name"`
NodeType Kind `json:"node_type"`
RunWhen RunWhen `json:"run_when,omitempty"`
Inputs map[string]any `json:"inputs"`
Outputs map[string]any `json:"outputs"`
Error error `json:"-"` // Don't serialize Go error
ErrorCode *string `json:"error_code,omitempty"`
ErrorMsg *string `json:"error_message,omitempty"`
SkipReason *string `json:"skip_reason,omitempty"`
MissingInputs []string `json:"missing_inputs,omitempty"`
ExecutedAt time.Time `json:"executed_at"`
// AssertionResults records every assertion evaluated on this node (pass or
// fail). It lives on the shared base so the engine-level assertion pass fills
// it uniformly for every node kind. The JSON tag is a wire contract consumed
// by echopoint.
AssertionResults []AssertionResult `json:"assertion_results,omitempty"`
}
BaseExecutionResult provides common fields for all execution results. Its JSON tags are a wire contract consumed by echopoint.
func (*BaseExecutionResult) Fail ¶
func (b *BaseExecutionResult) Fail(err error, code string)
Fail marks the result as failed: it stores the Go error and surfaces a user-facing message and stable code on the wire. A nil err is ignored.
func (*BaseExecutionResult) GetDisplayName ¶
func (b *BaseExecutionResult) GetDisplayName() string
GetDisplayName returns the node display name.
func (*BaseExecutionResult) GetError ¶
func (b *BaseExecutionResult) GetError() error
GetError returns the error if any.
func (*BaseExecutionResult) GetExecutedAt ¶
func (b *BaseExecutionResult) GetExecutedAt() time.Time
GetExecutedAt returns the execution timestamp.
func (*BaseExecutionResult) GetInputs ¶
func (b *BaseExecutionResult) GetInputs() map[string]any
GetInputs returns the inputs map.
func (*BaseExecutionResult) GetNodeID ¶
func (b *BaseExecutionResult) GetNodeID() string
GetNodeID returns the node ID.
func (*BaseExecutionResult) GetNodeType ¶
func (b *BaseExecutionResult) GetNodeType() Kind
GetNodeType returns the node type.
func (*BaseExecutionResult) GetOutputs ¶
func (b *BaseExecutionResult) GetOutputs() map[string]any
GetOutputs returns the outputs map.
func (*BaseExecutionResult) MergeOutputs ¶
func (b *BaseExecutionResult) MergeOutputs(outputs map[string]any)
MergeOutputs copies the given outputs into the result's Outputs map, lazily initializing it. Existing keys are overwritten.
func (*BaseExecutionResult) SetAssertionResults ¶
func (b *BaseExecutionResult) SetAssertionResults(results []AssertionResult)
SetAssertionResults records the evaluated assertions on the result. Used by the engine-level assertion pass so every node kind reports them uniformly.
type DynamicResolver ¶
DynamicResolver resolves a {{$name:args}} dynamic template variable to a generated value. Implemented by pkg/dynamicvars.
type EventType ¶
type EventType string
EventType identifies a runner execution/progress event on the wire. The helper sets (progress vs terminal) live with the consumers in pkg/executionevents; this package owns only the wire identifiers.
const ( EventFlowStarted EventType = "flow.started" EventNodeStarted EventType = "node.started" EventNodeCompleted EventType = "node.completed" EventNodeFailed EventType = "node.failed" EventFlowCompleted EventType = "flow.completed" EventFlowFailed EventType = "flow.failed" )
Execution/progress event types.
type ExecutionContext ¶
type ExecutionContext struct {
// Ctx is the request-scoped context for the execution. Nodes use it for
// cancellation and deadlines (e.g. the HTTP request honors it). May be nil,
// in which case callers should treat it as context.Background().
Ctx context.Context
// Inputs contains all the data this node declared it needs in InputSchema().
// Keys are in format "nodeId.outputKey" (e.g., "create-user.userId").
Inputs map[string]any
// FlowInputs contains the full effective inputs for the current flow execution,
// including inherited inputs, static overrides, and any initial input values.
FlowInputs map[string]any
// AllOutputs exposes a read-only snapshot of outputs from nodes that completed
// before the current scheduling batch started.
AllOutputs OutputView
// ModuleResolver exposes the additional flow definitions available to module
// nodes during nested execution.
ModuleResolver ModuleResolver
// ModuleExecutor runs nested flows for module nodes.
ModuleExecutor ModuleExecutor
// DynamicVars resolves {{$name}} template variables (fake-data generators).
// May be nil, in which case {{$...}} references are left untouched.
DynamicVars DynamicResolver
}
ExecutionContext provides inputs and context for a node's execution.
func (ExecutionContext) Context ¶
func (c ExecutionContext) Context() context.Context
Context returns the execution context, defaulting to context.Background() when none was provided so callers never need a nil check.
type ExtractorType ¶
type ExtractorType string
ExtractorType identifies an output/assertion extractor on the wire. The extractor implementations live in pkg/extractors and self-register; this is just the wire identifier the contract binds to.
const ( ExtractorTypeJSONPath ExtractorType = "jsonPath" ExtractorTypeXMLPath ExtractorType = "xmlPath" ExtractorTypeStatusCode ExtractorType = "statusCode" ExtractorTypeHeader ExtractorType = "header" ExtractorTypeBody ExtractorType = "body" )
Built-in extractor types.
type FlowExecutionResult ¶
type FlowExecutionResult struct {
ExecutionResults map[string]AnyResult `json:"execution_results"` // Polymorphic results!
FinalOutputs map[string]any `json:"final_outputs"` // All outputs flattened ("nodeId.outputKey": value)
Success bool `json:"success"`
Error error `json:"-"`
ErrorCode *string `json:"error_code,omitempty"`
ErrorMsg *string `json:"error_message,omitempty"`
DurationMS int64 `json:"duration_ms"`
}
FlowExecutionResult contains the complete trace of a flow execution.
type Kind ¶
type Kind string
Kind identifies a node kind on the wire. The node-kind registry, not a fixed set, decides which kinds are valid; the constants below are the built-ins.
type ModuleExecutionRequest ¶
ModuleExecutionRequest is a request to run a nested flow for a module node.
type ModuleExecutor ¶
type ModuleExecutor interface {
ExecuteModule(request ModuleExecutionRequest) (*FlowExecutionResult, error)
}
ModuleExecutor runs nested flows for module nodes.
type ModuleResolver ¶
type ModuleResolver interface {
ResolveFlow(flowID string) (ResolvedModuleFlow, bool)
}
ModuleResolver exposes the additional flow definitions available to module nodes during nested execution.
type Node ¶
type Node interface {
GetID() string
GetDisplayName() string
GetType() Kind
GetRunWhen() RunWhen
InputSchema() []string
// OutputSchema defines what this node produces
// Examples: []string{"statusCode", "userId", "responseBody"}
OutputSchema() []string
// Execute performs the node's action with provided inputs and returns the
// polymorphic result. Error indicates execution failure.
Execute(ctx ExecutionContext) (AnyResult, error)
}
Node is the engine's core view of any flow node — the capability-agnostic surface the scheduler drives. The full authoring interface (node.AnyNode) embeds this and adds the assertion/output accessors, which carry concrete extractor decode/eval behavior and so live in pkg/node.
type OutputView ¶
type OutputView interface {
HasNode(nodeID string) bool
Get(nodeID, outputKey string) (any, bool)
// Node returns a defensive copy of the requested node outputs.
Node(nodeID string) map[string]any
}
OutputView is a read-only snapshot of outputs from already-completed nodes.
type ResolvedModuleFlow ¶
ResolvedModuleFlow is a flow definition (plus input overrides) made available to a module node for nested execution.
type RoutingResult ¶
type RoutingResult interface {
AnyResult
// RoutedTargets returns the successor node IDs this result routes execution
// to. An empty slice means no successor was chosen (every successor edge is
// dead).
RoutedTargets() []string
}
RoutingResult is implemented by node results that select which successor edges are taken; the engine skips the untaken successors' subtrees. A routing node (e.g. the branch node) returns the IDs of the successor nodes it routed TO; the engine treats every other successor edge as dead and cascades the skip through that subtree.
type RunWhen ¶
type RunWhen string
RunWhen controls whether a node runs only on the success path or also after the main phase has already failed.
type UserError ¶
UserError marks a node failure that is caused by the flow author or the system they are calling — an unreachable or misconfigured target endpoint, invalid node input, a failed assertion — rather than by a fault in the runner itself.
It carries a stable Code and a user-facing Message that node results surface to the caller (error_code / error_message), and it preserves the underlying Cause for errors.Is/errors.As. The engine logs UserErrors at debug, reserving error level for genuine runner faults, because a UserError is an expected outcome already reported back to the user in the execution result.
func AsUserError ¶
AsUserError reports whether err is (or wraps) a UserError, returning it when so.
func NewUserError ¶
NewUserError builds a UserError with a stable code, a user-facing message, and the underlying cause (which may be nil).