engine

package
v0.1.0-dev.20260129080746 Latest Latest
Warning

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

Go to latest
Published: Jan 29, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package engine provides a shared execution engine for graph-based operations.

Architecture

The engine is shared by both writ (configuration deployment) and lore (package management). Each tool builds an execution graph containing nodes that represent operations to perform. The engine processes these graphs uniformly:

┌─────────────────┐     ┌─────────────────┐
│   writ deploy   │     │   lore deploy   │
│  (config files) │     │   (packages)    │
└────────┬────────┘     └────────┬────────┘
         │                       │
         ▼                       ▼
┌─────────────────┐     ┌─────────────────┐
│  File Tree      │     │  Package Graph  │
│  Builder        │     │  Builder        │
└────────┬────────┘     └────────┬────────┘
         │                       │
         └───────────┬───────────┘
                     ▼
          ┌─────────────────────┐
          │   Execution Graph   │
          │   (unified nodes)   │
          └──────────┬──────────┘
                     ▼
          ┌─────────────────────┐
          │   Engine.Run()      │
          │   (shared runner)   │
          └──────────┬──────────┘
                     ▼
          ┌─────────────────────┐
          │      Receipt        │
          └─────────────────────┘

Graph Builders

Different graph builders produce nodes for different operation types:

  • File Tree Builder (writ): Walks environment repositories, produces nodes for link, copy, expand, decrypt operations on configuration files.

  • Package Graph Builder (lore): Resolves package specifications, produces nodes for install, configure, verify operations on software packages. NOT YET IMPLEMENTED - see internal/lore/graph package.

When writ encounters a packages-manifest.yaml file, it should use the Package Graph Builder to add package nodes to the same execution graph. There is no delegation or handoff between tools—both use the same engine.

Operation Categories

Operations are classified by their data flow behavior:

  • Transform: Read content, produce transformed content (decrypt, expand)
  • Writer: Read content, write to filesystem (copy)
  • Direct: Manage own I/O, no content flow (link, mkdir, install, verify)

The engine threads content through Transform operations, passes it to Writer operations, and executes Direct operations independently.

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. The backup path is stored in node.Metadata["backup_path"] after execution.

func (*BackupOp) Category

func (o *BackupOp) Category() OpCategory

func (*BackupOp) Execute

func (o *BackupOp) Execute(ctx *Context, node *Node) 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 content to node.Target and returns the target checksum.

func (*CopyOp) Category

func (o *CopyOp) Category() OpCategory

func (*CopyOp) Name

func (o *CopyOp) Name() string

func (*CopyOp) Write

func (o *CopyOp) Write(ctx *Context, node *Node, content []byte) (string, error)

type DecryptOp

type DecryptOp struct{}

DecryptOp decrypts content using the SOPS API. The decryption configuration is expected in ctx.Data. Returns the decrypted content.

func (*DecryptOp) Category

func (o *DecryptOp) Category() OpCategory

func (*DecryptOp) Name

func (o *DecryptOp) Name() string

func (*DecryptOp) Transform

func (o *DecryptOp) Transform(ctx *Context, node *Node, content []byte) ([]byte, error)

type Direct

type Direct interface {
	Operation
	Execute(ctx *Context, node *Node) error
}

Direct operations manage their own I/O with no content flow. Used for: link, mkdir, backup, unlink, remove, validate, rename.

type Edge

type Edge struct {
	From     string // Source node ID
	To       string // Target node ID
	Relation string // "depends_on", "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 content as a Go text/template with ctx.Data as the template data. Returns the expanded content.

func (*ExpandOp) Category

func (o *ExpandOp) Category() OpCategory

func (*ExpandOp) Name

func (o *ExpandOp) Name() string

func (*ExpandOp) Transform

func (o *ExpandOp) Transform(ctx *Context, node *Node, content []byte) ([]byte, error)

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) Category

func (o *LinkOp) Category() OpCategory

func (*LinkOp) Execute

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

func (*LinkOp) Name

func (o *LinkOp) Name() string

type MkdirOp

type MkdirOp struct{}

MkdirOp creates a directory at node.Target.

func (*MkdirOp) Category

func (o *MkdirOp) Category() OpCategory

func (*MkdirOp) Execute

func (o *MkdirOp) Execute(ctx *Context, node *Node) error

func (*MkdirOp) Name

func (o *MkdirOp) 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 is DEPRECATED - there is no delegation between tools.
	// writ and lore share the same execution engine. Retained for backwards
	// compatibility with old receipts.
	DelegateTo string

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

Node represents a unit of work in the execution graph.

type OpCategory

type OpCategory int

OpCategory classifies operations by their data flow behavior.

const (
	// OpTransform reads content, produces transformed content.
	OpTransform OpCategory = iota

	// OpWriter reads content, writes to filesystem, produces checksum.
	OpWriter

	// OpDirect manages its own I/O, no content flow.
	OpDirect
)

type Operation

type Operation interface {
	// Name returns the operation identifier (e.g., "link", "decrypt").
	Name() string

	// Category returns the operation category for pipeline validation.
	Category() OpCategory
}

Operation is the base interface for all executable actions.

func FileOps

func FileOps() []Operation

FileOps returns all built-in file operations for registration.

Transform operations (content in → content out):

  • decrypt: decrypts encrypted content via ctx.Data["decryptor"]
  • expand: expands Go text/template content with ctx.Data

Writer operations (content in → checksum out):

  • copy: writes content to node.Target

Direct operations (no content flow):

  • link: creates symlink from node.Target → node.Source
  • mkdir: creates directory at node.Target
  • backup: moves node.Target to timestamped backup
  • unlink: removes symlink at node.Target
  • remove: deletes file at node.Target
  • validate: checks precondition from ctx.Data["validators"]
  • rename: moves node.Source → node.Target (git mv when possible)

NOTE: Package operations (install, configure, verify) are provided by the Package Graph Builder (internal/lore/graph) - NOT YET IMPLEMENTED.

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 PipelineInput

type PipelineInput struct {
	// Content is the source file content (read by engine before pipeline starts).
	Content []byte

	// SourceChecksum is the SHA256 of the original content.
	SourceChecksum string
}

PipelineInput holds the input data for a pipeline.

type PipelineOutput

type PipelineOutput struct {
	// Content is the final transformed content (after all transforms).
	Content []byte

	// TargetChecksum is the SHA256 of the written target file.
	TargetChecksum string
}

PipelineOutput holds the output data from a pipeline.

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 Plan

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

Plan provides binding functions for building an execution graph. Graph producers (writ tree builder, lore pipeline executor, LLM graph builder) use Plan to add operations to the graph. Each method returns the created node for edge construction.

In Starlark scripts, the plan object is passed to each phase function:

def install(system, package, plan):
    plan.mkdir("/usr/local/bin")
    plan.link("/usr/local/bin/foo", source="/path/to/foo")

func NewPlan

func NewPlan(project string) *Plan

NewPlan creates a new plan for building an execution graph.

func (*Plan) Backup

func (p *Plan) Backup(target string) *Node

Backup adds a backup operation for an existing file.

func (*Plan) Copy

func (p *Plan) Copy(source, target string, transforms ...string) *Node

Copy adds a file copy operation. Transforms (decrypt, expand) can be prepended to the pipeline.

func (*Plan) CopyWithMode

func (p *Plan) CopyWithMode(source, target string, mode os.FileMode, transforms ...string) *Node

CopyWithMode adds a file copy operation with explicit permissions.

func (*Plan) DependsOn

func (p *Plan) DependsOn(from, to *Node)

DependsOn adds an ordering edge: from must complete before to begins.

func (*Plan) Graph

func (p *Plan) Graph() *Graph

Graph returns the built execution graph.

func (p *Plan) Link(source, target string) *Node

Link adds a symlink creation operation.

func (*Plan) Mkdir

func (p *Plan) Mkdir(target string) *Node

Mkdir adds a directory creation operation.

func (*Plan) Orders

func (p *Plan) Orders(from, to *Node)

Orders adds an ordering constraint without implying dependency.

func (*Plan) Remove

func (p *Plan) Remove(target string) *Node

Remove adds a file/directory removal operation.

func (*Plan) Rename

func (p *Plan) Rename(source, target string) *Node

Rename adds a file/directory rename operation (git mv when possible).

func (p *Plan) Unlink(target string) *Node

Unlink adds a symlink removal operation.

func (*Plan) Validate

func (p *Plan) Validate(check, message string) *Node

Validate adds a precondition check 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) Category

func (o *RemoveOp) Category() OpCategory

func (*RemoveOp) Execute

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

func (*RemoveOp) Name

func (o *RemoveOp) Name() string

type RenameOp

type RenameOp struct{}

RenameOp moves a file or directory from node.Source to node.Target using git mv when inside a git repository, falling back to os.Rename otherwise.

func (*RenameOp) Category

func (o *RenameOp) Category() OpCategory

func (*RenameOp) Execute

func (o *RenameOp) Execute(ctx *Context, node *Node) error

func (*RenameOp) Name

func (o *RenameOp) 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 Transform

type Transform interface {
	Operation
	Transform(ctx *Context, node *Node, content []byte) ([]byte, error)
}

Transform operations read content and produce transformed content. Used for: decrypt, expand.

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) Category

func (o *UnlinkOp) Category() OpCategory

func (*UnlinkOp) Execute

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

func (*UnlinkOp) Name

func (o *UnlinkOp) Name() string

type ValidateOp

type ValidateOp struct{}

ValidateOp checks a precondition and fails with a message if unmet. The check function is retrieved from ctx.Data["validators"][node.Metadata["check"]].

func (*ValidateOp) Category

func (o *ValidateOp) Category() OpCategory

func (*ValidateOp) Execute

func (o *ValidateOp) Execute(ctx *Context, node *Node) error

func (*ValidateOp) Name

func (o *ValidateOp) Name() string

type Writer

type Writer interface {
	Operation
	Write(ctx *Context, node *Node, content []byte) (targetChecksum string, err error)
}

Writer operations read content and write to the filesystem. Used for: copy.

Jump to

Keyboard shortcuts

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