engine

package
v0.1.0-dev.20260127185200 Latest Latest
Warning

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

Go to latest
Published: Jan 27, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package engine provides a common execution engine for graph-based operations. Both writ and lore build execution graphs and hand them to this engine for processing. The engine dispatches operations to registered handlers, threads content through transform pipelines, and produces receipts.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExpandDelegates

func ExpandDelegates(ctx context.Context, graph *Graph, builder GraphBuilder, opts BuildOptions) error

ExpandDelegates replaces delegate nodes in the graph with subgraphs produced by the given builder. Delegate nodes are identified by having "delegate" as their sole operation.

The expanded subgraph's nodes and edges are appended to the parent graph. An ordering edge is added from the delegate node's predecessor (if any) to each root node of the subgraph.

The original delegate node is removed from the graph after expansion.

Types

type BackupOp

type BackupOp struct{}

BackupOp moves the existing file at node.Target to a timestamped backup.

func (*BackupOp) Execute

func (o *BackupOp) Execute(ctx *Context, node *Node, state *PipelineState) error

func (*BackupOp) Name

func (o *BackupOp) Name() string

type BuildOptions

type BuildOptions struct {
	// DryRun prevents the builder from making any filesystem queries
	// that have side effects.
	DryRun bool

	// Features lists enabled features (e.g., "rootless", "compose").
	Features []string

	// Data holds tool context: platform info, environment, segments.
	Data map[string]any
}

BuildOptions configures graph building behavior.

type Conflict

type Conflict struct {
	Node         *Node
	Type         ConflictType
	ExistingPath string // For symlinks, where it points
	Message      string
}

Conflict represents a pre-flight detected conflict.

type ConflictResolution

type ConflictResolution int

ConflictResolution specifies how to handle conflicts.

const (
	// ResolutionStop aborts execution on first conflict.
	ResolutionStop ConflictResolution = iota
	// ResolutionBackup moves conflicting files to timestamped backups.
	ResolutionBackup
	// ResolutionOverwrite removes conflicting files without backup.
	ResolutionOverwrite
	// ResolutionSkip skips conflicting files and continues.
	ResolutionSkip
)

type ConflictType

type ConflictType int

ConflictType describes the kind of conflict at a target path.

const (
	// ConflictNone indicates no conflict exists.
	ConflictNone ConflictType = iota
	// ConflictRegularFile indicates a regular file exists at target.
	ConflictRegularFile
	// ConflictDirectory indicates a directory exists at target.
	ConflictDirectory
	// ConflictForeignSymlink indicates a symlink pointing elsewhere exists.
	ConflictForeignSymlink
	// ConflictOurSymlink indicates our symlink already exists (no action needed).
	ConflictOurSymlink
)

type Context

type Context struct {
	context.Context

	// DryRun prevents filesystem modifications when true.
	DryRun bool

	// Logger receives operation output messages.
	Logger io.Writer

	// Data holds tool-provided context: template variables, SOPS config,
	// identities, segment maps, etc. Each tool populates this before
	// calling Engine.Run().
	Data map[string]any
}

Context provides execution context to operations.

type CopyOp

type CopyOp struct{}

CopyOp writes state.Content to node.Target and sets state.TargetChecksum.

func (*CopyOp) Execute

func (o *CopyOp) Execute(ctx *Context, node *Node, state *PipelineState) error

func (*CopyOp) Name

func (o *CopyOp) Name() string

type DecryptOp

type DecryptOp struct{}

DecryptOp decrypts state.Content using the SOPS API. The decryption configuration is expected in ctx.Data. The decrypted result replaces state.Content.

func (*DecryptOp) Execute

func (o *DecryptOp) Execute(ctx *Context, node *Node, state *PipelineState) error

func (*DecryptOp) Name

func (o *DecryptOp) Name() string

type DelegateOp

type DelegateOp struct{}

DelegateOp is a no-op that marks a node for cross-tool handoff.

func (*DelegateOp) Execute

func (o *DelegateOp) Execute(_ *Context, node *Node, state *PipelineState) error

func (*DelegateOp) Name

func (o *DelegateOp) Name() string

type Edge

type Edge struct {
	From     string // Source node ID
	To       string // Target node ID
	Relation string // "depends_on", "delegates", "orders"
}

Edge defines an ordering constraint between nodes.

type Engine

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

Engine executes operation graphs.

func New

func New(registry *Registry, opts Options) *Engine

New creates an engine with the given registry and options.

func (*Engine) Preflight

func (e *Engine) Preflight(graph *Graph) *PreflightResult

Preflight performs pre-flight conflict detection without modifying anything. Only applies to nodes with file operations (link, copy).

func (*Engine) Run

func (e *Engine) Run(ctx context.Context, graph *Graph) ([]*Result, error)

Run executes all nodes in the graph, respecting ordering constraints. Nodes are processed in topological order when edges define dependencies. Returns results for each node.

TODO: Add graph optimization pass before execution. Native PM operations (e.g., multiple "install" nodes with the same package manager) should be batched into a single operation to reduce PM invocations. See docs/plans/uniform-pipeline-interface.md for design details.

type ExpandOp

type ExpandOp struct{}

ExpandOp processes state.Content as a Go text/template with ctx.Data as the template data. The expanded result replaces state.Content.

func (*ExpandOp) Execute

func (o *ExpandOp) Execute(ctx *Context, node *Node, state *PipelineState) error

func (*ExpandOp) Name

func (o *ExpandOp) Name() string

type Graph

type Graph struct {
	Nodes []*Node
	Edges []Edge
}

Graph represents an execution graph: nodes with operations and edges defining ordering constraints.

type GraphBuilder

type GraphBuilder interface {
	BuildGraph(ctx context.Context, manifestPath string, opts BuildOptions) (*Graph, error)
}

GraphBuilder builds an execution graph from a manifest file. Tools implement this interface to translate their manifest format into an execution graph that the engine can process.

When writ encounters a delegate node, it calls BuildGraph with the manifest path to get a subgraph. That subgraph is then executed by the same engine — no separate tool invocation needed.

type LinkOp

type LinkOp struct{}

LinkOp creates a symlink from node.Target pointing to node.Source.

func (*LinkOp) Execute

func (o *LinkOp) Execute(ctx *Context, node *Node, state *PipelineState) error

func (*LinkOp) Name

func (o *LinkOp) Name() string

type Node

type Node struct {
	// ID uniquely identifies this node (e.g., relative target path for writ,
	// package name for lore).
	ID string

	// Operations is the pipeline of operations to execute on this node.
	// Examples: ["link"], ["decrypt", "expand", "copy"], ["install"].
	Operations []string

	// Source is the source path (for file operations).
	Source string

	// Target is the target path (for file operations).
	Target string

	// Project is the grouping key (writ: project name, lore: package name).
	Project string

	// Mode is the target file permissions (0 means use default 0644).
	Mode os.FileMode

	// DelegateTo names the tool to delegate to (for delegate operations).
	DelegateTo string

	// Metadata holds tool-specific extensions.
	Metadata map[string]string
}

Node represents a unit of work in the execution graph.

type Operation

type Operation interface {
	// Execute performs the operation on the given node with pipeline state.
	// Transforms modify state.Content in place. Writers consume state.Content
	// and produce filesystem output. Direct operations ignore state.Content.
	Execute(ctx *Context, node *Node, state *PipelineState) error

	// Name returns the operation identifier (e.g., "link", "decrypt").
	Name() string
}

Operation defines an executable action. Implementations fall into three categories: transforms (modify state.Content), writers (produce filesystem side effects from state.Content), and direct operations (manage their own I/O).

func FileOps

func FileOps() []Operation

FileOps returns all built-in file operations for registration.

type Options

type Options struct {
	// DryRun prevents filesystem modifications.
	DryRun bool

	// Logger receives operation output.
	Logger io.Writer

	// Data holds tool-provided context (template vars, SOPS config, etc.).
	Data map[string]any

	// ConflictResolution specifies how to handle conflicts detected during preflight.
	ConflictResolution ConflictResolution

	// BackupSuffix is appended to backup filenames (default: ".writ-backup").
	BackupSuffix string
}

Options configures engine behavior.

type PipelineState

type PipelineState struct {
	// Content is the current file content. Transforms modify this in place;
	// writers consume it to produce output files.
	Content []byte

	// SourceChecksum is computed from the original source file content
	// before any transforms are applied. Format: "sha256:<hex>".
	SourceChecksum string

	// TargetChecksum is set by writer operations after writing content
	// to the target path. Format: "sha256:<hex>".
	TargetChecksum string

	// Metadata holds per-node extensible state that operations can read/write.
	Metadata map[string]string
}

PipelineState holds mutable state threaded through a node's operation pipeline. The engine pre-reads source content into Content when the pipeline begins with a transform or writer operation.

type PreflightResult

type PreflightResult struct {
	Conflicts   []Conflict
	AlreadyDone []Conflict // Symlinks that already point correctly
	Ready       []*Node    // Nodes ready to deploy (no conflict)
}

PreflightResult contains the results of pre-flight conflict detection.

func (*PreflightResult) HasConflicts

func (p *PreflightResult) HasConflicts() bool

HasConflicts returns true if any conflicts were detected.

type Registry

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

Registry maps operation names to implementations. Each tool registers its operations before calling Engine.Run().

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates an empty operation registry.

func (*Registry) Get

func (r *Registry) Get(name string) (Operation, bool)

Get returns the operation registered under the given name.

func (*Registry) Names

func (r *Registry) Names() []string

Names returns all registered operation names.

func (*Registry) Register

func (r *Registry) Register(op Operation)

Register adds an operation to the registry. If an operation with the same name already exists, it is replaced.

type RemoveOp

type RemoveOp struct{}

RemoveOp deletes the file at node.Target. If ctx.Data["prune_empty_dirs"] is true and ctx.Data["prune_boundary"] is set, empty parent directories are removed up to the boundary.

func (*RemoveOp) Execute

func (o *RemoveOp) Execute(ctx *Context, node *Node, _ *PipelineState) error

func (*RemoveOp) Name

func (o *RemoveOp) Name() string

type Result

type Result struct {
	NodeID         string
	Status         Status
	Error          error
	Message        string
	SourceChecksum string
	TargetChecksum string
}

Result represents the outcome of executing a single node.

type Status

type Status int

Status represents the execution status of a node.

const (
	// StatusPending means the node has not been processed yet.
	StatusPending Status = iota
	// StatusRunning means the node is currently executing.
	StatusRunning
	// StatusCompleted means the node executed successfully.
	StatusCompleted
	// StatusFailed means the node encountered an error.
	StatusFailed
	// StatusSkipped means the node was skipped (conflict, already deployed, etc.).
	StatusSkipped
)

func (Status) String

func (s Status) String() string

String returns a human-readable status label.

type UnlinkOp

type UnlinkOp struct{}

UnlinkOp removes a symlink at node.Target. If ctx.Data["prune_empty_dirs"] is true and ctx.Data["prune_boundary"] is set, empty parent directories are removed up to the boundary.

func (*UnlinkOp) Execute

func (o *UnlinkOp) Execute(ctx *Context, node *Node, _ *PipelineState) error

func (*UnlinkOp) Name

func (o *UnlinkOp) Name() string

Jump to

Keyboard shortcuts

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