execution

package
v0.1.0-dev.20260217020211 Latest Latest
Warning

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

Go to latest
Published: Feb 17, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package execution provides the core execution graph primitives and executor shared by writ (configuration deployment) and lore (package management).

Core Types

  • Graph: A directed graph of nodes and edges representing work to be done
  • Node: A single unit of work with an action to execute
  • Edge: A dependency relationship between nodes

Execution Model

  • GraphBuilder: Interface for building graphs (implementations in tools)
  • GraphExecutor: Runs graphs by executing actions on nodes
  • ActionRegistry: Maps action names to implementations

Actions

Each action implements Action.Do(ctx, slots) returning (Result, UndoState, error). Content flows via Result and promise slots — upstream actions return content as Result, downstream actions receive it through a "content" promise slot.

Graph Lifecycle

The Graph represents both plans (before execution) and receipts (after execution):

  • Before Run(): State is "pending", nodes describe what will happen
  • After Run(): State is "executed", nodes describe what happened
  • Serialized before execution: "dry-run" or "purchase order"
  • Serialized after execution: "receipt"

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ChecksumBytes

func ChecksumBytes(content []byte) string

ChecksumBytes computes SHA256 of content and returns "sha256:<hex>".

func ChecksumFile

func ChecksumFile(path string) string

ChecksumFile computes SHA256 of a file and returns "sha256:<hex>". Returns empty string if the file cannot be read.

func FillSlotsFromData

func FillSlotsFromData(slots map[string]any, data map[string]any)

FillSlotsFromData fills unfilled slots from Context.Data. Slots already set by the caller or resolved from promises are not overwritten.

func GitStyleChecksum

func GitStyleChecksum(objectType, basename string, content []byte) string

GitStyleChecksum computes a git-style checksum. Format: SHA256("<type> <basename> <len>\0<content>") Returns format "sha256:<hex>".

Types

type Action

type Action interface {
	// Name returns the action identifier (e.g., "file.link", "template.render").
	Name() string

	// Do performs the forward action using resolved slot values.
	// Returns a result (flows to downstream nodes via promise slots) and undo
	// state (stored on recovery stack for rollback).
	Do(ctx *Context, slots map[string]any) (Result, UndoState, error)

	// Undo performs the compensating action using the state captured by Do.
	Undo(ctx *Context, slots map[string]any, state UndoState) error
}

Action is the interface for all executable actions. Actions receive resolved slots — they never touch *Node. The executor resolves all promise slots before calling Do.

func StubAction

func StubAction(name string) Action

StubAction creates a named Action stub for testing and receipt deserialization. Do and Undo return errors — stub actions are not executable.

type ActionRegistry

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

ActionRegistry maps action names to their implementations. Each tool registers its actions before calling GraphExecutor.Run().

func NewActionRegistry

func NewActionRegistry() *ActionRegistry

NewActionRegistry creates an empty action registry.

func (*ActionRegistry) Get

func (r *ActionRegistry) Get(name string) (Action, bool)

Get returns the action registered under the given name.

func (*ActionRegistry) MustGet

func (r *ActionRegistry) MustGet(name string) Action

MustGet returns the action registered under the given name. Panics if the action is not registered (safe: all actions are pre-registered before any builder runs).

func (*ActionRegistry) Names

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

Names returns all registered action names.

func (*ActionRegistry) Register

func (r *ActionRegistry) Register(action Action)

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

type ActivationState

type ActivationState struct {
	Status         NodeStatus
	Timestamp      string
	Error          string
	SourceChecksum string
	TargetChecksum string
}

ActivationState captures per-execution mutable state for a node.

For non-gather execution, activation state is inlined on the Node struct (Status, SourceChecksum, TargetChecksum). For gather's concurrent execution, ActivationState lives in a per-iteration map so that shared nodes are never mutated.

ActivationState is transient — it is discarded after results and undo state are captured.

type Attempt

type Attempt struct {
	// Number is the 1-based attempt number.
	Number int `json:"number" yaml:"number"`

	// Status is "completed" or "failed".
	Status string `json:"status" yaml:"status"`

	// Error is the error message if the attempt failed.
	Error string `json:"error,omitempty" yaml:"error,omitempty"`

	// Timestamp is when this attempt completed (RFC3339).
	Timestamp string `json:"timestamp" yaml:"timestamp"`
}

Attempt records one execution attempt of a phase.

type BackoffStrategy

type BackoffStrategy string

BackoffStrategy defines how delays increase between retries.

const (
	BackoffNone        BackoffStrategy = "none"
	BackoffLinear      BackoffStrategy = "linear"
	BackoffExponential BackoffStrategy = "exponential"
)

type ChooseCase

type ChooseCase struct {
	Predicate Predicate
	PhaseID   string
}

ChooseCase pairs a predicate with a phase to execute if the predicate matches.

type ChooseUndoState

type ChooseUndoState struct {
	Results map[string]any  // node results for promise re-resolution
	Entries []RecoveryEntry // branch node refs + per-node undo state
}

ChooseUndoState preserves the selected branch's recovery state. Choose's Do returns this as its UndoState.

type Collision

type Collision struct {
	Target            string `json:"target" yaml:"target"`
	Winner            string `json:"winner" yaml:"winner"`
	WinnerLayer       string `json:"winner_layer,omitempty" yaml:"winner_layer,omitempty"`
	WinnerSpecificity int    `json:"winner_specificity,omitempty" yaml:"winner_specificity,omitempty"`
	Loser             string `json:"loser" yaml:"loser"`
	LoserLayer        string `json:"loser_layer,omitempty" yaml:"loser_layer,omitempty"`
	LoserSpecificity  int    `json:"loser_specificity,omitempty" yaml:"loser_specificity,omitempty"`
}

Collision records a source conflict resolved during tree building (writ-specific).

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 during execution.

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 action output messages.
	Logger io.Writer

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

	// Graph is the graph being executed. Flow actions use this to look up
	// phases referenced by their slots (e.g., gather body, choose branch).
	Graph *Graph

	// NodeID is the ID of the currently executing node. Flow actions use
	// this to identify themselves (e.g., gather uses it for proxy context).
	NodeID string

	// Per-node checksums (written by actions, read by executor).
	SourceChecksum string
	TargetChecksum string
}

Context provides execution context to actions.

type DependencyView

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

DependencyView provides dependency analysis for a single execution graph. It indexes the graph's edges to enable efficient dependency queries.

func NewDependencyView

func NewDependencyView(g *Graph) *DependencyView

NewDependencyView creates a DependencyView for the given graph.

func (*DependencyView) AllDependencies

func (v *DependencyView) AllDependencies(nodeID string) []string

AllDependencies returns the transitive closure of dependencies for a node. This includes all nodes that must complete before this node can start.

func (*DependencyView) AllDependents

func (v *DependencyView) AllDependents(nodeID string) []string

AllDependents returns the transitive closure of dependents for a node. This includes all nodes that directly or indirectly depend on this node.

func (*DependencyView) CriticalPath

func (v *DependencyView) CriticalPath() []string

CriticalPath returns the longest dependency chain in the graph. This represents the minimum sequential execution path.

func (*DependencyView) Dependents

func (v *DependencyView) Dependents(nodeID string) []string

Dependents returns the direct dependents of a node (nodes that wait for this one).

func (*DependencyView) DependsOn

func (v *DependencyView) DependsOn(nodeID string) []string

DependsOn returns the direct dependencies of a node (nodes that must complete first).

func (*DependencyView) EdgeCount

func (v *DependencyView) EdgeCount() int

EdgeCount returns the number of edges in the graph.

func (*DependencyView) Graph

func (v *DependencyView) Graph() *Graph

Graph returns the underlying graph.

func (*DependencyView) HasCycle

func (v *DependencyView) HasCycle() bool

HasCycle returns true if the graph contains a cycle.

func (*DependencyView) IndependentSets

func (v *DependencyView) IndependentSets() [][]string

IndependentSets returns groups of nodes that have no dependencies between them. Each set can be executed fully in parallel with other sets.

func (*DependencyView) Leaves

func (v *DependencyView) Leaves() []string

Leaves returns nodes with no dependents (final nodes in the graph).

func (*DependencyView) Node

func (v *DependencyView) Node(id string) *Node

Node returns the node with the given ID, or nil if not found.

func (*DependencyView) NodeCount

func (v *DependencyView) NodeCount() int

NodeCount returns the number of nodes in the graph.

func (*DependencyView) ParallelLevels

func (v *DependencyView) ParallelLevels() [][]string

ParallelLevels returns nodes grouped by execution level. Level 0 contains roots (can execute immediately). Level N contains nodes whose dependencies are all in levels < N. Within each level, nodes can execute in parallel.

func (*DependencyView) PathBetween

func (v *DependencyView) PathBetween(source, target string) []string

PathBetween returns the shortest path from source to target, or nil if none exists.

func (*DependencyView) Roots

func (v *DependencyView) Roots() []string

Roots returns nodes with no dependencies (can execute immediately).

func (*DependencyView) Subgraph

func (v *DependencyView) Subgraph(nodeIDs []string) *DependencyView

Subgraph returns a new DependencyView containing only the specified nodes and the edges between them.

func (*DependencyView) TopologicalOrder

func (v *DependencyView) TopologicalOrder() []string

TopologicalOrder returns nodes in a valid execution order. Nodes appear after all their dependencies. Returns nil if the graph has a cycle.

type Edge

type Edge struct {
	From string `json:"from" yaml:"from"`
	To   string `json:"to" yaml:"to"`
}

Edge represents a dependency relationship between two nodes. From must complete before To can begin execution.

type Encoder

type Encoder interface {
	Encode(v any) error
}

Encoder is the interface for graph serialization. Both *json.Encoder and *yaml.Encoder satisfy this interface.

type EntryType

type EntryType string

EntryType distinguishes between package and file entries.

const (
	// EntryPackage represents a lore package lifecycle entry.
	EntryPackage EntryType = "package"
	// EntryFile represents a project file entry (link/copy/expand/decrypt).
	EntryFile EntryType = "file"
)

type ExecutorOptions

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

	// Logger receives action 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
}

ExecutorOptions configures GraphExecutor behavior.

type FileEntry

type FileEntry struct {
	// Target is the relative target path (e.g., ".bashrc").
	Target string `json:"target" yaml:"target"`

	// Source is the absolute source path.
	Source string `json:"source" yaml:"source"`

	// Project this file belongs to.
	Project string `json:"project" yaml:"project"`

	// Layer is the repository layer (base, team, personal).
	Layer string `json:"layer,omitempty" yaml:"layer,omitempty"`

	// History of deployment operations, ordered by time.
	History []HistoryRecord `json:"history" yaml:"history"`
}

FileEntry represents a project file's deployment history.

func (*FileEntry) IsCopied

func (e *FileEntry) IsCopied() bool

IsCopied returns true if the latest operation was a copy (not symlink).

func (*FileEntry) IsLinked

func (e *FileEntry) IsLinked() bool

IsLinked returns true if the latest operation was a symlink.

func (*FileEntry) LastOp

func (e *FileEntry) LastOp() string

LastOp returns the operation from the latest deployment.

func (*FileEntry) LastOperation

func (e *FileEntry) LastOperation() *HistoryRecord

LastOperation returns the most recent operation, or nil if no history.

func (*FileEntry) SourceChecksum

func (e *FileEntry) SourceChecksum() string

SourceChecksum returns the source checksum from the latest operation.

func (*FileEntry) TargetChecksum

func (e *FileEntry) TargetChecksum() string

TargetChecksum returns the target checksum from the latest operation.

type FileTree

type FileTree struct {
	// Root is the target root path (e.g., $HOME).
	Root string `json:"root" yaml:"root"`

	// Entries provides flat lookup by relative target path.
	Entries map[string]*FileEntry `json:"entries" yaml:"entries"`

	// Tree is the hierarchical view.
	Tree *FileTreeNode `json:"tree" yaml:"tree"`
}

FileTree provides both flat and hierarchical access to files.

func (*FileTree) CopiedFiles

func (t *FileTree) CopiedFiles() map[string]*FileEntry

CopiedFiles returns all entries that were copied (not symlinked).

func (*FileTree) ForProject

func (t *FileTree) ForProject(project string) map[string]*FileEntry

ForProject returns all file entries for a specific project.

func (*FileTree) LinkedFiles

func (t *FileTree) LinkedFiles() map[string]*FileEntry

LinkedFiles returns all entries that are symlinks.

func (*FileTree) Projects

func (t *FileTree) Projects() []string

Projects returns a list of all projects with files in the tree.

type FileTreeNode

type FileTreeNode struct {
	// Name is the filename or directory name.
	Name string `json:"name" yaml:"name"`

	// IsDir is true if this is a directory.
	IsDir bool `json:"is_dir" yaml:"is_dir"`

	// Entry is the file entry (nil for directories).
	Entry *FileEntry `json:"entry,omitempty" yaml:"entry,omitempty"`

	// Children are the child nodes (nil for files).
	Children map[string]*FileTreeNode `json:"children,omitempty" yaml:"children,omitempty"`
}

FileTreeNode represents a node in the target filesystem tree.

type GatherUndoState

type GatherUndoState struct {
	Iterations []IterationUndo
}

GatherUndoState preserves per-iteration state needed for rollback. Gather's Do returns this as its UndoState. Recovery entries reference shared nodes — no lifecycle issue.

type Graph

type Graph struct {
	// Version is the graph format version.
	Version string `json:"version" yaml:"version"`

	// Tool identifies which tool created this graph ("writ" or "lore").
	Tool string `json:"tool" yaml:"tool"`

	// Timestamp is when the graph was created/executed.
	Timestamp time.Time `json:"timestamp" yaml:"timestamp"`

	// State is the execution state (pending, executed, failed).
	State GraphState `json:"state" yaml:"state"`

	// Platform records the OS and architecture.
	Platform Platform `json:"platform" yaml:"platform"`

	// Context contains tool-specific metadata.
	Context GraphContext `json:"context" yaml:"context"`

	// Nodes are the actions to perform/performed.
	Nodes []*Node `json:"nodes" yaml:"nodes"`

	// Edges are the dependencies between nodes.
	Edges []Edge `json:"edges,omitempty" yaml:"edges,omitempty"`

	// Phases defines the ordered lifecycle phases (nil for non-phased graphs).
	// When present, the executor uses phase-aware execution with retry and rollback.
	// When nil, the executor falls back to flat node execution.
	Phases []*Phase `json:"phases,omitempty" yaml:"phases,omitempty"`

	// Collisions records source conflicts resolved during tree building (writ-specific).
	Collisions []Collision `json:"collisions,omitempty" yaml:"collisions,omitempty"`

	// Summary contains execution statistics (populated after Run).
	Summary Summary `json:"summary,omitempty" yaml:"summary,omitempty"`

	// Rollback records compensating actions executed during rollback (populated on failure).
	Rollback []RollbackEntry `json:"rollback,omitempty" yaml:"rollback,omitempty"`

	// Checksum is the git-style integrity hash.
	Checksum string `json:"checksum,omitempty" yaml:"checksum,omitempty"`

	// Signature contains the cryptographic signature (optional).
	Signature *Signature `json:"signature,omitempty" yaml:"signature,omitempty"`
}

Graph represents an execution graph containing nodes and edges. This is THE graph used by both writ and lore - they differ only in content.

Before Run(): State is "pending", represents the plan After Run(): State is "executed", represents the receipt

func (*Graph) ApplyResults

func (g *Graph) ApplyResults(results []*NodeResult)

ApplyResults updates node states from execution results.

func (*Graph) CanonicalContent

func (g *Graph) CanonicalContent() ([]byte, error)

CanonicalContent returns the graph serialized as YAML without checksum and signature. This is used for computing checksums and verifying signatures.

func (*Graph) CollectPhaseNodes

func (g *Graph) CollectPhaseNodes(phase *Phase) ([]*Node, []Edge)

CollectPhaseNodes returns the nodes and intra-phase edges for the given phase. Nodes are returned in graph order; edges are filtered to only those between phase-internal nodes.

func (*Graph) ComputeSummary

func (g *Graph) ComputeSummary()

ComputeSummary calculates summary statistics from nodes. For phased graphs, node statuses reflect the phase execution outcome (nodes in rolled-back phases may show as completed from before rollback).

func (*Graph) Filename

func (g *Graph) Filename() string

Filename returns the standard filename for this graph. Format: "<tool>-<timestamp>.yaml"

func (*Graph) Hydrate

func (g *Graph) Hydrate(reg *ActionRegistry) error

Hydrate replaces stubActions on graph nodes with real actions from the registry. This enables loaded/deserialized graphs to be executed. Nodes with no action name (e.g., nodes that were never serialized with an action) are skipped.

func (*Graph) PhaseByID

func (g *Graph) PhaseByID(id string) *Phase

PhaseByID returns the phase with the given ID, or nil if not found.

func (*Graph) Serialize

func (g *Graph) Serialize(enc Encoder) error

Serialize writes the graph to the given encoder. The checksum is computed before encoding.

Usage:

enc := yaml.NewEncoder(file)
enc.SetIndent(2)
defer enc.Close()
g.Serialize(enc)

type GraphBuilder

type GraphBuilder interface {
	// Build creates an execution graph.
	// Implementations hold their configuration internally (set at construction).
	Build(ctx context.Context) (*Graph, error)
}

GraphBuilder is the interface for building execution graphs. Implementations are provided by tools (writ, lore) and create graphs from their respective inputs (file trees, package manifests, etc.).

type GraphContext

type GraphContext struct {
	// SourceRoot is the source directory (writ: repo path, lore: registry cache).
	SourceRoot string `json:"source_root,omitempty" yaml:"source_root,omitempty"`

	// TargetRoot is the target directory (typically $HOME).
	TargetRoot string `json:"target_root,omitempty" yaml:"target_root,omitempty"`

	// Projects lists the projects included (writ-specific).
	Projects []string `json:"projects,omitempty" yaml:"projects,omitempty"`

	// Packages lists the packages included (lore-specific).
	Packages []string `json:"packages,omitempty" yaml:"packages,omitempty"`

	// Segments contains platform segment values (writ-specific).
	Segments map[string]string `json:"segments,omitempty" yaml:"segments,omitempty"`

	// Layers lists repository layers used (writ-specific).
	Layers []string `json:"layers,omitempty" yaml:"layers,omitempty"`

	// Platform is the target platform string (lore-specific, e.g., "Darwin", "Linux.Debian").
	TargetPlatform string `json:"target_platform,omitempty" yaml:"target_platform,omitempty"`

	// Features enabled for package installation (lore-specific).
	Features []string `json:"features,omitempty" yaml:"features,omitempty"`

	// Settings for package installation (lore-specific).
	Settings map[string]string `json:"settings,omitempty" yaml:"settings,omitempty"`
}

GraphContext contains tool-specific metadata stored in the graph. Both writ and lore populate this with their relevant context.

type GraphExecutor

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

GraphExecutor executes action graphs.

func NewGraphExecutor

func NewGraphExecutor(opts ExecutorOptions) *GraphExecutor

NewGraphExecutor creates an executor with the given options.

func (*GraphExecutor) ExecutePhaseInner

func (e *GraphExecutor) ExecutePhaseInner(ctx *Context, g *Graph, phase *Phase, results map[string]any, stack *RecoveryStack) error

ExecutePhaseInner runs the inner nodes of a phase.

func (*GraphExecutor) Run

func (e *GraphExecutor) Run(ctx context.Context, g *Graph) error

Run executes all nodes in the graph, respecting ordering constraints. When the graph has phases, execution is delegated to RunPhased which implements the saga pattern with retry and rollback. Otherwise, nodes are processed in topological order (flat execution).

func (*GraphExecutor) RunNodes

func (e *GraphExecutor) RunNodes(ctx context.Context, nodes []*Node, edges []Edge) ([]*NodeResult, error)

RunNodes executes a slice of nodes with the given edges. This is a lower-level API for callers that don't have a full Graph.

func (*GraphExecutor) RunPhased

func (e *GraphExecutor) RunPhased(ctx context.Context, g *Graph) error

RunPhased executes a phased graph using the saga pattern. Phases are executed in order. Each phase runs its inner nodes via topological sort. On failure, completed phases are compensated in LIFO order (rollback).

Phases referenced as compensating actions (via Compensate fields) are skipped during the forward pass — they execute only during rollback.

func (*GraphExecutor) SetHooks

func (e *GraphExecutor) SetHooks(hooks *HookRegistry)

SetHooks sets the lifecycle hook registry for this executor.

type GraphState

type GraphState string

GraphState represents the execution state of the graph.

const (
	StatePending  GraphState = "pending"
	StateExecuted GraphState = "executed"
	StateFailed   GraphState = "failed"
)

type HistoryRecord

type HistoryRecord struct {
	// Timestamp is when this operation occurred.
	Timestamp time.Time `json:"timestamp" yaml:"timestamp"`

	// Receipt is the filename of the receipt that recorded this.
	Receipt string `json:"receipt" yaml:"receipt"`

	// Tool is which tool created this record ("lore" or "writ").
	Tool string `json:"tool" yaml:"tool"`

	// Action performed: link, copy, render, decrypt, install, etc.
	Action string `json:"action" yaml:"action"`

	// Status of this operation: completed, skipped, failed.
	Status NodeStatus `json:"status" yaml:"status"`

	// SourceChecksum is the SHA256 of the source at operation time.
	SourceChecksum string `json:"source_checksum,omitempty" yaml:"source_checksum,omitempty"`

	// TargetChecksum is the SHA256 of the target after operation.
	TargetChecksum string `json:"target_checksum,omitempty" yaml:"target_checksum,omitempty"`
}

HistoryRecord represents a single operation on an entry from a receipt.

type HookRegistry

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

HookRegistry holds registered lifecycle hooks and provides fire methods. A nil *HookRegistry is safe to use — all fire methods are no-ops.

func NewHookRegistry

func NewHookRegistry() *HookRegistry

NewHookRegistry creates an empty hook registry.

func (*HookRegistry) FireNodeComplete

func (r *HookRegistry) FireNodeComplete(ctx *Context, nodeID string, result Result, err error)

FireNodeComplete notifies all hooks that a node has finished.

func (*HookRegistry) FireNodeStart

func (r *HookRegistry) FireNodeStart(ctx *Context, nodeID string, slots map[string]any)

FireNodeStart notifies all hooks that a node is about to execute.

func (*HookRegistry) FirePhaseComplete

func (r *HookRegistry) FirePhaseComplete(ctx *Context, phaseID string, err error)

FirePhaseComplete notifies all hooks that a phase has finished.

func (*HookRegistry) FirePhaseStart

func (r *HookRegistry) FirePhaseStart(ctx *Context, phaseID string)

FirePhaseStart notifies all hooks that a phase is about to execute.

func (*HookRegistry) Register

func (r *HookRegistry) Register(hook LifecycleHook)

Register adds a lifecycle hook to the registry.

type IterationUndo

type IterationUndo struct {
	ProxyCtx map[string]any  // {gatherID: item} for slot re-resolution
	Results  map[string]any  // node results for promise re-resolution
	Entries  []RecoveryEntry // shared node refs + per-node undo state
}

IterationUndo captures one gather iteration's undo state.

type LifecycleHook

type LifecycleHook interface {
	OnNodeStart(ctx *Context, nodeID string, slots map[string]any)
	OnNodeComplete(ctx *Context, nodeID string, result Result, err error)
	OnPhaseStart(ctx *Context, phaseID string)
	OnPhaseComplete(ctx *Context, phaseID string, err error)
}

LifecycleHook receives events at phase and node boundaries during execution. Hooks are fire-and-forget — a hook panic is recovered and logged but does not fail the node or phase. Hooks run synchronously and must not block.

type Node

type Node struct {
	// ID is the unique identifier (typically relative target path or package name).
	ID string `json:"id" yaml:"id"`

	// Action to perform. Set at construction via SetAction or node.Action = reg.MustGet(...).
	// Serialized as the action name string; deserialized as a stubAction.
	Action Action `json:"-" yaml:"-"`

	// Status of this node: pending, completed, skipped, failed.
	Status NodeStatus `json:"status" yaml:"status"`

	// Timestamp is when this operation completed.
	Timestamp string `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`

	// Slots holds input values for this node. Each slot can be:
	// - Immediate: value known at analysis time
	// - Promise: reference to another node's output (creates edge)
	Slots map[string]SlotValue `json:"slots,omitempty" yaml:"slots,omitempty"`

	// Project this node belongs to.
	Project string `json:"project,omitempty" yaml:"project,omitempty"`

	// Layer is the repository layer (base, team, personal).
	Layer string `json:"layer,omitempty" yaml:"layer,omitempty"`

	// SourceChecksum is the SHA256 of the source file at deploy time.
	SourceChecksum string `json:"source_checksum,omitempty" yaml:"source_checksum,omitempty"`

	// TargetChecksum is the SHA256 of the target file after deployment.
	TargetChecksum string `json:"target_checksum,omitempty" yaml:"target_checksum,omitempty"`

	// Error message if status is failed.
	Error string `json:"error,omitempty" yaml:"error,omitempty"`

	// Retry is the retry policy for this node (nil = no retry).
	Retry *RetryPolicy `json:"retry,omitempty" yaml:"retry,omitempty"`

	// Annotations holds extensible metadata (serialized to receipts).
	Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
}

Node represents a single unit of work in an execution graph.

func OrderNodes

func OrderNodes(nodes []*Node, edges []Edge) []*Node

OrderNodes returns nodes in execution order. Nodes with edges are topologically sorted; nodes without edges are sorted by path depth.

func (*Node) ActionName

func (n *Node) ActionName() string

ActionName returns the action name. Works for both live nodes (Action interface set) and deserialized receipt nodes (stubAction).

func (*Node) GetID

func (n *Node) GetID() string

GetID returns the node's unique identifier.

func (*Node) GetProject

func (n *Node) GetProject() string

GetProject returns the project name.

func (*Node) GetSlot

func (n *Node) GetSlot(name string) any

GetSlot returns the resolved value of a slot. If the slot is a promise, returns nil (must be resolved by executor).

func (Node) MarshalJSON

func (n Node) MarshalJSON() ([]byte, error)

MarshalJSON serializes the node with Action as its name string.

func (Node) MarshalYAML

func (n Node) MarshalYAML() (any, error)

MarshalYAML serializes the node with Action as its name string. Note: we cannot use the nodeJSONWire embedding pattern here because yaml.v3 panics on unexported concrete types behind interfaces in shadowed embedded fields, and fails to decode embedded fields when names collide (unlike encoding/json which handles both correctly). We round-trip through JSON instead.

func (*Node) RequireStringSlot

func (n *Node) RequireStringSlot(name string) (string, error)

RequireStringSlot returns the string value of a required slot. Returns an error if the slot is not set, or holds a non-string value. An empty string is valid — use GetSlot for optional slots where zero value is acceptable.

func (*Node) ResolvedSlots

func (n *Node) ResolvedSlots(results map[string]any, proxyCtx ...map[string]any) map[string]any

ResolvedSlots returns all slot values as a flat map. Promise slots are resolved from the results map; immediate slots are returned directly. Proxy slots are resolved from the optional proxyCtx map (used by gather for per-iteration item binding). Pass nil for results when all slots are immediate (e.g., in tests).

func (*Node) SetSlotImmediate

func (n *Node) SetSlotImmediate(name string, value any)

SetSlotImmediate sets a slot to an immediate value.

func (*Node) SetSlotPromise

func (n *Node) SetSlotPromise(name, nodeRef, slot string)

SetSlotPromise sets a slot to a promise (reference to another node).

func (*Node) SetSlotProxy

func (n *Node) SetSlotProxy(name, gatherRef, field string)

SetSlotProxy sets a slot to a gather proxy reference.

func (*Node) UnmarshalJSON

func (n *Node) UnmarshalJSON(data []byte) error

UnmarshalJSON deserializes a node, creating a stubAction from the action name.

func (*Node) UnmarshalYAML

func (n *Node) UnmarshalYAML(value *yaml.Node) error

UnmarshalYAML deserializes a node, creating a stubAction from the action name. Like MarshalYAML, we avoid the embedded struct pattern and decode manually.

type NodeResult

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

NodeResult represents the outcome of executing a single node.

type NodeStatus

type NodeStatus string

NodeStatus represents the execution status of a node.

const (
	StatusPending   NodeStatus = "pending"
	StatusCompleted NodeStatus = "completed"
	StatusSkipped   NodeStatus = "skipped"
	StatusFailed    NodeStatus = "failed"
)

type PackageEntry

type PackageEntry struct {
	// Name is the package name (e.g., "docker").
	Name string `json:"name" yaml:"name"`

	// History of lifecycle operations, ordered by time.
	History []HistoryRecord `json:"history" yaml:"history"`
}

PackageEntry represents a lore package's lifecycle history.

func (*PackageEntry) LastOperation

func (e *PackageEntry) LastOperation() *HistoryRecord

LastOperation returns the most recent operation, or nil if no history.

type Phase

type Phase struct {
	// ID is the unique identifier (e.g., "phase.install").
	ID string `json:"id" yaml:"id"`

	// Name is the phase name (e.g., "install").
	Name string `json:"name" yaml:"name"`

	// Status of this phase: pending, completed, failed, rolled_back, skipped.
	Status PhaseStatus `json:"status" yaml:"status"`

	// Retry governs retry behavior when inner nodes fail.
	Retry *RetryPolicy `json:"retry,omitempty" yaml:"retry,omitempty"`

	// NodeIDs lists the IDs of inner nodes belonging to this phase.
	NodeIDs []string `json:"nodes,omitempty" yaml:"nodes,omitempty"`

	// Compensate is the ID of the compensating action for rollback.
	Compensate string `json:"compensate,omitempty" yaml:"compensate,omitempty"`

	// Attempts records retry history (populated during execution).
	Attempts []Attempt `json:"attempts,omitempty" yaml:"attempts,omitempty"`

	// State holds execution metadata captured during the forward action.
	// The compensating action reads this to know what to undo.
	State map[string]any `json:"state,omitempty" yaml:"state,omitempty"`
}

Phase represents a lifecycle phase in the execution graph. Each phase owns a set of inner nodes and acts as an error boundary with retry and compensating action support (the Saga Pattern).

type PhaseStatus

type PhaseStatus string

PhaseStatus represents the execution state of a phase.

const (
	PhasePending    PhaseStatus = "pending"
	PhaseCompleted  PhaseStatus = "completed"
	PhaseFailed     PhaseStatus = "failed"
	PhaseRolledBack PhaseStatus = "rolled_back"
	PhaseSkipped    PhaseStatus = "skipped"
)

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(reg *ActionRegistry, project string) *Plan

NewPlan creates a new plan for building an execution graph.

func (*Plan) Backup

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

Backup adds a backup operation for an existing file.

func (*Plan) Copy

func (p *Plan) Copy(source, path string) *Node

Copy adds a file copy operation.

func (*Plan) CopyWithMode

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

CopyWithMode adds a file copy operation with explicit permissions.

func (*Plan) Decrypt

func (p *Plan) Decrypt(source string) *Node

Decrypt adds a decryption operation.

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, path string) *Node

Link adds a symlink creation operation.

func (*Plan) Mkdir

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

Mkdir adds a directory creation operation.

func (*Plan) Orders

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

Orders adds an ordering constraint between nodes.

func (*Plan) Remove

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

Remove adds a file/directory removal operation.

func (*Plan) Rename

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

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

func (*Plan) Render

func (p *Plan) Render(source string) *Node

Render adds a template rendering operation.

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

Unlink adds a symlink removal operation.

type Platform

type Platform struct {
	OS   string `json:"os" yaml:"os"`
	Arch string `json:"arch" yaml:"arch"`
}

Platform records the OS and architecture.

type Predicate

type Predicate interface {
	// Eval evaluates the predicate against the given value.
	// Returns true if the condition is satisfied.
	Eval(value any) (bool, error)

	// String returns a human-readable representation for display and debugging.
	String() string
}

Predicate evaluates a condition against a value at execution time.

The execution package defines this interface; the starlark package provides the RuntimePredicate implementation that wraps a starlark.Callable. This keeps the execution package free of starlark imports.

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 Preflight

func Preflight(graph *Graph) *PreflightResult

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

func (*PreflightResult) HasConflicts

func (p *PreflightResult) HasConflicts() bool

HasConflicts returns true if any conflicts were detected.

type RecoveryEntry

type RecoveryEntry struct {
	// Node is the executed node (carries the Action for Undo).
	Node *Node

	// UndoState is the state captured by Do, passed to Undo during rollback.
	UndoState UndoState
}

RecoveryEntry represents a successfully executed node and the state needed to undo it. The executor pushes one entry per completed node.

type RecoveryStack

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

RecoveryStack is a LIFO stack of recovery entries. The executor pushes entries as nodes complete and unwinds (pops + executes Undo) on failure.

func (*RecoveryStack) Entries

func (s *RecoveryStack) Entries() []RecoveryEntry

Entries returns a copy of the stack entries (bottom to top). Used for inspection and serialization.

func (*RecoveryStack) Len

func (s *RecoveryStack) Len() int

Len returns the number of entries on the stack.

func (*RecoveryStack) Push

func (s *RecoveryStack) Push(entry RecoveryEntry)

Push adds a recovery entry to the top of the stack.

func (*RecoveryStack) Unwind

func (s *RecoveryStack) Unwind(ctx *Context, results map[string]any, proxyCtx ...map[string]any) []error

Unwind executes Undo on all entries in LIFO order. Each entry's node slots are resolved from the results map before calling Undo. Undo failures do not stop the unwind — all entries are processed.

type Result

type Result = any

Result is data that flows to downstream nodes via edges (e.g., file content, a rendered template, a query result). The executor stores this keyed by node ID and resolves promise slots from stored Results before calling downstream Do.

type ResultStatus

type ResultStatus int

ResultStatus represents the execution status of a node.

const (
	// ResultPending means the node has not been processed yet.
	ResultPending ResultStatus = iota
	// ResultRunning means the node is currently executing.
	ResultRunning
	// ResultCompleted means the node executed successfully.
	ResultCompleted
	// ResultFailed means the node encountered an error.
	ResultFailed
	// ResultSkipped means the node was skipped (conflict, already deployed, etc.).
	ResultSkipped
)

func (ResultStatus) String

func (s ResultStatus) String() string

String returns a human-readable status label.

type RetryPolicy

type RetryPolicy struct {
	// MaxAttempts is the maximum number of retries (0 = no retry, fail immediately).
	MaxAttempts int `json:"max_attempts" yaml:"max_attempts"`

	// Backoff is the delay strategy: none, linear, exponential.
	Backoff BackoffStrategy `json:"backoff" yaml:"backoff"`

	// InitialDelay is the delay before the first retry (Go duration string, e.g. "1s").
	InitialDelay string `json:"initial_delay,omitempty" yaml:"initial_delay,omitempty"`

	// MaxDelay caps the delay between retries (Go duration string, e.g. "30s").
	MaxDelay string `json:"max_delay,omitempty" yaml:"max_delay,omitempty"`
}

RetryPolicy configures retry behavior for a phase.

func (*RetryPolicy) ComputeDelay

func (r *RetryPolicy) ComputeDelay(attempt int) time.Duration

ComputeDelay returns the delay for a given attempt number (0-based).

func (*RetryPolicy) ParseInitialDelay

func (r *RetryPolicy) ParseInitialDelay() time.Duration

ParseInitialDelay parses the InitialDelay string into a time.Duration. Returns 0 if the string is empty or unparseable.

func (*RetryPolicy) ParseMaxDelay

func (r *RetryPolicy) ParseMaxDelay() time.Duration

ParseMaxDelay parses the MaxDelay string into a time.Duration. Returns 0 if the string is empty or unparseable.

type RollbackEntry

type RollbackEntry struct {
	// Phase is the phase name that was rolled back.
	Phase string `json:"phase" yaml:"phase"`

	// Compensate is the ID of the compensating action.
	Compensate string `json:"compensate" yaml:"compensate"`

	// Status is "completed" or "failed".
	Status string `json:"status" yaml:"status"`

	// Error is the error message if the compensating action failed.
	Error string `json:"error,omitempty" yaml:"error,omitempty"`
}

RollbackEntry records a compensating action executed during rollback.

type Signature

type Signature struct {
	// Method is the signing method used (gpg, aws_kms, gcp_kms, azure_kv).
	Method string `json:"method" yaml:"method"`

	// Value is the signature data (base64-encoded).
	Value string `json:"value" yaml:"value"`

	// KeyID identifies the key used for signing.
	// For GPG: fingerprint, for KMS: key ARN/ID/URL.
	KeyID string `json:"key_id" yaml:"key_id"`
}

Signature contains the cryptographic signature of a graph.

type SlotValue

type SlotValue struct {
	// Immediate is the direct value (any type, known at analysis time).
	Immediate any `json:"immediate,omitempty" yaml:"immediate,omitempty"`

	// NodeRef is the ID of the node that produces this value (promise).
	NodeRef string `json:"node_ref,omitempty" yaml:"node_ref,omitempty"`

	// Slot is which output slot of the referenced node (empty = default output).
	Slot string `json:"slot,omitempty" yaml:"slot,omitempty"`

	// GatherRef is the gather node ID for proxy resolution.
	GatherRef string `json:"gather_ref,omitempty" yaml:"gather_ref,omitempty"`

	// Field is the field name to access on the proxy item.
	Field string `json:"field,omitempty" yaml:"field,omitempty"`
}

SlotValue represents a value that fills a slot in a node. Three variants, mutually exclusive:

  • Immediate: value known at analysis time
  • Promise: reference to another node's output (NodeRef)
  • Proxy: reference to a gather iteration item (GatherRef + Field)

func (SlotValue) IsImmediate

func (s SlotValue) IsImmediate() bool

IsImmediate returns true if this slot value is an immediate value.

func (SlotValue) IsPromise

func (s SlotValue) IsPromise() bool

IsPromise returns true if this slot value is a promise (reference to another node).

func (SlotValue) IsProxy

func (s SlotValue) IsProxy() bool

IsProxy returns true if this slot value is a gather proxy reference.

type StateView

type StateView struct {
	// Since is the start of the time window (inclusive).
	Since time.Time `json:"since" yaml:"since"`

	// Until is the end of the time window (inclusive).
	Until time.Time `json:"until" yaml:"until"`

	// ReceiptCount is the number of receipts included in this view.
	ReceiptCount int `json:"receipt_count" yaml:"receipt_count"`

	// Packages maps package names to their lifecycle history.
	Packages map[string]*PackageEntry `json:"packages" yaml:"packages"`

	// Files provides file entry access (flat and tree).
	Files *FileTree `json:"files" yaml:"files"`
}

StateView is a read-only view over multiple execution graphs. It represents "what we believe happened" over a time interval.

func (*StateView) Summary

func (v *StateView) Summary() (packages, links, copied int)

Summary returns counts of packages, linked files, and copied files.

type StateViewBuilder

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

StateViewBuilder creates StateViews from receipts.

func NewStateViewBuilder

func NewStateViewBuilder(opts ViewOptions) *StateViewBuilder

NewStateViewBuilder creates a new builder with the given options.

func (*StateViewBuilder) Build

func (b *StateViewBuilder) Build(receiptsDir string) (*StateView, error)

Build loads all receipts from the directory and builds a StateView.

func (*StateViewBuilder) BuildFrom

func (b *StateViewBuilder) BuildFrom(graphs []*Graph) *StateView

BuildFrom creates a StateView from the given graphs.

type Summary

type Summary struct {
	TotalFiles int `json:"total_files,omitempty" yaml:"total_files,omitempty"`
	Links      int `json:"links,omitempty" yaml:"links,omitempty"`
	Copies     int `json:"copies,omitempty" yaml:"copies,omitempty"`
	Templates  int `json:"templates,omitempty" yaml:"templates,omitempty"`
	Secrets    int `json:"secrets,omitempty" yaml:"secrets,omitempty"`
	Packages   int `json:"packages,omitempty" yaml:"packages,omitempty"`
	Skipped    int `json:"skipped,omitempty" yaml:"skipped,omitempty"`
	Failed     int `json:"failed,omitempty" yaml:"failed,omitempty"`
	BackedUp   int `json:"backed_up,omitempty" yaml:"backed_up,omitempty"`
}

Summary contains execution statistics.

func (Summary) String

func (s Summary) String() string

String returns a human-readable summary.

type UndoState

type UndoState = any

UndoState is the state captured by Do and passed to Undo during saga rollback. Each action defines its own state shape. Actions with no rollback return nil from Do; their Undo ignores the state parameter.

type ViewOptions

type ViewOptions struct {
	// Since filters to receipts after this time (zero = no lower bound).
	Since time.Time

	// Until filters to receipts before this time (zero = no upper bound).
	Until time.Time

	// Tools filters to specific tools (empty = all tools).
	Tools []string
}

ViewOptions configures how the view is built.

Directories

Path Synopsis
git
net
pkg

Jump to

Keyboard shortcuts

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