plan

package
v0.1.0-dev.20260817234111 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package plan provides graph-construction actions for the plan namespace.

Its methods execute during script evaluation to create nodes in the operation graph. The plan Provider is an executing receiver — not a planning receiver — because its methods run immediately to build the graph.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Provider

type Provider struct {
	op.ProviderBase
	// contains filtered or unexported fields
}

Provider creates graph nodes for plan-time graph construction.

Provider implements a three-tier attribute resolution (phase-8 D12, plus I4):

  • Tier 1 — sub-namespace adapters (`plan.file`, `plan.shell`, ...). Lazy-minted in Provider.ResolveAttr via [newAdapter], cached in `adapters`. Each adapter is a starlark.HasAttrs that routes `.<method>(args, kwargs)` through [Provider.invocation].
  • Tier 2 — promoted methods from root-placed providers (`plan.choose`, `plan.gather`, ...). Surfaced flat under plan.* via builtins discovered from op.ReceiverRegistry.RootProviders at construction (any RoleAction+RoleRoot provider contributes its methods).
  • Tier 3 — Provider's own methods (`plan.variable`, `plan.assemble_definition`, `plan.save_definition`, ...). Surfaced by the executing receiver path that wraps plan.Provider itself as a [goReceiver].

Any collision across the three tiers fails Provider construction with a message naming both providers and the offending method. promotedBuiltins is write-once at construction; the adapters are lazily populated under `adaptersMutex`.

+devlore:access=immediate

func NewProvider

func NewProvider(runtimeEnvironment *op.RuntimeEnvironment) *Provider

NewProvider creates a plan Provider bound to the given runtime environment.

Per phase-8 D5, no op.Graph is constructed here — nodes produced during script evaluation live on detached *op.Invocation handles registered in [Provider.invocations]. The graph is materialized by Provider.AssembleDefinition from the supplied invocation set.

At construction, the Provider instantiates the invocation registry, then discovers every RoleAction+RoleRoot provider via the registry to build Tier 2 builtins for their promoted methods. Any name collision across Tier 1 (sub-namespace adapter names), Tier 2 (promoted method names), or Tier 3 (this Provider's own method names) is a program-init panic.

Parameters:

  • `ctx`: the runtime environment the Provider binds to and reaches for the receiver registry, runtime context, and downstream provider construction.

Returns:

  • *Provider: the constructed Provider with Tier 2 promoted builtins populated and Tier 1 adapter cache empty.

func (*Provider) AssembleDefinition

func (p *Provider) AssembleDefinition(
	invocations []*op.Invocation,
	slots map[string]any,
	onError []*op.Invocation,
	onRetry []*op.Invocation,
	retryPolicy *op.RetryPolicy,
	transitionPolicy *op.TransitionPolicy,
	origin op.Origin,
) (*op.Graph, error)

AssembleDefinition materializes a *op.Graph from a list of plan-time invocations.

Signature is codegen-compatible — all parameter types are reachable from starlark via the standard starlarkbridge conversion path; plan-specific projections happen inside this method.

Pipeline:

  1. Project the inputs: the invocation list becomes the graph's root children ([]op.ExecutableUnit); the error-action invocations become a `*op.Subgraph` via [subgraphFromInvocations]; and the slot map becomes [op.Binding]s via [projectToBinding].
  2. Take ownership of the catalog: capture op.RuntimeEnvironment.Catalog and clear the runtime environment's reference to it — ownership transfers to the graph being constructed.
  3. Construct the graph: stamp `origin.Tool` from the planning program name ([RuntimeEnvironment.Application].Name), then call the sealed op.NewGraph constructor with the origin, catalog, root children, retry policy, error action, and slots. op.NewGraph materializes the edges, sorts the children, computes the canonical content, and hashes it via op.GitStyleChecksum. Sub-graphs are left unsigned pending the sops rewrite — no signing client is propagated.
  4. Scan for orphans: any invocation in the registry whose Target carries an empty parentID was never rooted by this AssembleDefinition call and is not a child of any other container. Aggregate via errors.Join and return `(nil, err)` when the set is non-empty.
  5. Validate: op.ValidateGraph runs against the sealed graph and returns its joined violations as a single error.

Parameters:

  • `invocations`: the top-level invocations to root under `graph.Root`.
  • `slots`: the non-reserved kwargs to populate as slots on `graph.Root`. Values are projected to op.Binding via [projectToBinding].
  • `onError`: the list of invocations from `on_error=[...]`. Materializes internally into a Subgraph; empty / nil means no error action.
  • `retryPolicy`: the resolved retry policy from `retry_policy=`, or nil.
  • `transitionPolicy`: the resolved transition policy from `transition_policy=`, or nil.
  • `origin`: the tool-stamp op.Origin for the assembled graph; the zero value when omitted (the .star `plan.assemble_definition` surface never supplies it — Origin is a Go-side caller concern).

Returns:

  • `*op.Graph`: the assembled graph, bound to this Provider's runtime environment.
  • `error`: non-nil when the orphan scan reports any unreachable invocations; the returned error is an errors.Join of one entry per orphan.

+devlore:defaults retryPolicy=nil, onError=nil, onRetry=nil, transitionPolicy=nil, slots=nil, origin=

func (*Provider) Case

func (p *Provider) Case(when, then any) (*flow.Case, error)

Case constructs a flow.Case pairing a when-subgraph with a then-subgraph.

Exposed to starlark as `plan.case(when=..., then=...)`. Each body is a list of invocations — the same construction `plan.subgraph(body=[...])` uses — or a singleton: a bare invocation is a one-element body, and a bare starlark function or lambda is sugar for `plan.function.call(<lambda>)` (both settled 2026-07-02). Delegates to flow.NewCase; the case is plan-time data flow.ChoosePlanner assembles into the choose subgraph's decision tree (phase-8 step 10).

Parameters:

  • `when`: the when-body; its subgraph is a decision node, evaluated for truthiness at execution.
  • `then`: the then-body; its subgraph is the leaf run when the when's result is truthy.

Returns:

  • `*flow.Case`: the constructed case, ready to pass to plan.choose.
  • `error`: non-nil when either body is malformed.

func (*Provider) Clear

func (p *Provider) Clear() error

Clear resets this Provider's session ledger via op.InvocationRegistry.Reset.

Discards every registered invocation and zeroes the auto-label counters. Previously assembled Graphs (returned by Provider.AssembleDefinition or Provider.LoadDefinition) hold their own references to *Invocation values and are unaffected — Clear only drops the registry's view, so subsequent plan-mode calls start with a clean ledger for the next assembly.

Returns:

  • `error`: always nil today; the signature carries an error return so future implementations (e.g., canceling a session-scoped resource) can surface failures without breaking the bridge-side builtin signature.

func (*Provider) InvocationRegistry

func (p *Provider) InvocationRegistry() *op.InvocationRegistry

InvocationRegistry returns the session-scoped ledger of invocations constructed during plan-time evaluation.

Provided so *Provider satisfies op.PlanInvocator — planners reach the registry through this accessor for body-resolution lookups during their dispatch.

Returns:

  • *op.InvocationRegistry: the session ledger; never nil during planning.

func (*Provider) Item

func (p *Provider) Item(field string) *op.Variable

Item returns a projected reference to one field of the enclosing gather's per-iteration item.

Sugar over Provider.Variable (`plan.variable("item", field=field)`): the reference names the reserved iteration variable `item` — bound per iteration by the gather's frame — and projects `field` from the record it holds (phase-8 step 45). Outside a gather body the reference is unresolvable (op.ValidateGraph rejects it at plan time).

Parameters:

  • `field`: the record field to project from the iteration item.

Returns:

  • *op.Variable: the projected variable reference.

func (*Provider) LoadDefinition

func (p *Provider) LoadDefinition(path string) (*op.Graph, error)

LoadDefinition deserializes a *op.Graph from a file at `path`. Format is inferred from `path`'s extension.

Supported extensions: `.json` → JSON; `.yaml` / `.yml` → YAML. Any other extension is an error.

The returned graph is unbound from any runtime environment; the next session-owner (a Go-side op.GraphExecutor) binds it during execution.

Parameters:

  • `path`: the source file path. Format is inferred from the extension.

Returns:

  • *op.Graph: the deserialized graph (unbound).
  • `error`: non-nil when the file cannot be read, the format is unsupported, or decoding fails.

func (*Provider) Origin

func (p *Provider) Origin(scope string) op.Origin

Origin constructs an op.Origin carrying the planning scope for the assembled graph.

Exposed to starlark as `plan.origin(scope)`. Tool is deliberately NOT a parameter — Provider.AssembleDefinition stamps it from the program name ([RuntimeEnvironment.Application].Name); Tool is framework-owned. Graph-level annotations are not exposed through this constructor.

Parameters:

  • `scope`: the planning scope for the graph (e.g. writ "system"/"home"); drives the persisted graph filename.

Returns:

func (*Provider) Plan

func (p *Provider) Plan(name op.ActionName, args []any, kwargs map[string]any) (*op.Invocation, error)

Plan registers am invocation from Go, mirroring what the starlark bridge does from a `plan.<name>(...)` call.

The framework resolves the action from `name` (e.g. "pkg.install"). The caller never builds an op.Action.

The resolved leaf is planned through the owning method's op.ActionPlanner, wrapped in an *op.Invocation, and registered in this Provider's session ledger; a later Provider.AssembleDefinition over the ledger materializes the graph, so Go-built and `.star`-built invocations pool in the same registry.

Parameters:

  • `name`: the dotted action name "<receiver>.<method>" (e.g. "pkg.install"), as op.Method.ActionName reports it.
  • `args`: positional arguments for the method, in declared order.
  • `kwargs`: keyword arguments by parameter name.

Returns:

  • `*op.Invocation`: the registered invocation; its `Target` is the planned unit.
  • `error`: non-nil when `name` resolves to no known action, or the planner / registry rejects the call.

func (*Provider) ResolveAttr

func (p *Provider) ResolveAttr(name string) any

ResolveAttr implements op.AttributeResolver.

Walks the attribute tiers in order:

  1. Tier 2: promoted method builtins (including `plan.choose`, `plan.gather`, ...) discovered from op.ReceiverRegistry.RootProviders at construction. `promotedBuiltins` are write-once, so the read is lock-free.

  2. Tier 1: sub-namespace adapters (`plan.file`, `plan.shell`,...). Looked up via op.ReceiverRegistry.PlannerByName]. Root-placed providers are excluded, so their methods surface flat via Tier 2 instead. On hit, the adapter is minted via [Provider.adapterFor] (lazy, cached).

Tier 3: This Provider's own methods: `plan.case`, `plan.variable`, `plan.assemble_definition`, `plan.save_definition`, `plan.load_definition`, `plan.clear`) are resolved upstream by the [starlarkBridge.goReceiver] path's method lookup via the codegen-emitted op.MethodMetadata; those names never reach ResolveAttr.

A final miss returns `nil` so the upstream `starlarkbridge.goReceiver` reports a clean NoSuchAttr instead of panicking.

Parameters:

  • `name`: the snake-cased attribute name from starlark.

Returns:

  • `any`: the resolved attribute (a starlark.Value from promotedBuiltins, or an [*adapter]), or nil when no tier matches.

func (*Provider) Run

func (p *Provider) Run(graph *op.Graph, spec *op.RuntimeEnvironmentSpec) (any, error)

Run executes `graph` against the supplied *op.RuntimeEnvironmentSpec.

Exposed to starlark as `plan.run(graph, spec)`.

Builds a fresh *op.GraphExecutor from `(graph, spec)` and dispatches via op.GraphExecutor.Run. The executor owns the per-Run env's lifecycle — env construction, Catalog clone, Root close, variable resolution preflight, graph dispatch, compensation unwind on failure — all the runner-side responsibilities the .star script used to rely on the host to handle. With `plan.run` exposed, the script drives execute itself; hosts (devlore-test, writ, lore, …) reduce to evaluating the script and surfacing its errors.

The returned `any` is the terminal node's output — the same shape op.GraphExecutor.Run produces today. For the common case (graph with no value-producing terminal node) this is nil. Scripts that want richer post-execute introspection consult the env-side collectors (status narrator, result pipeline, audit receipts) rather than the return value.

Parameters:

Returns:

  • `any`: the terminal node's result, or nil when the graph has no value-producing terminal node.
  • `error`: non-nil when preflight or dispatch fails; the unwind error joins on if compensation also fails.

func (*Provider) SaveDefinition

func (p *Provider) SaveDefinition(graph *op.Graph, path string) (err error)

SaveDefinition serializes `graph` to a file at `path` in JSON or YAML format selected by `path`'s extension.

Supported extensions: `.json` → JSON (two-space indent); `.yaml` / `.yml` → YAML (two-space indent). Any other extension is an error.

Parameters:

  • `graph`: the graph to serialize.
  • `path`: the destination file path. Format is inferred from the extension.

Returns:

  • `error`: non-nil when the file cannot be created, the format is unsupported, or encoding fails.

func (*Provider) Spec

func (p *Provider) Spec(programName, rootPath string, flags map[string]any) (*op.RuntimeEnvironmentSpec, error)

Spec constructs a fresh *op.RuntimeEnvironmentSpec for use with Provider.Run.

Exposed to starlark as `plan.spec(program_name=..., root_path=..., flags=...)` — all three arguments optional.

When an argument is the zero value (empty `programName`, empty `rootPath`, or nil `flags`), the planning runtime environment's corresponding field supplies the default. The planning runtime environment always carries these — the host that invoked op.Plan passed its own application.Application and root anchor. Net effect: `plan.spec()` with no arguments produces a spec equivalent to the planning runtime environment's.

The spec carries no live fsroot.Dir — only the resolved `rootPath` anchor and fsroot.ModeConfined; each Provider.Run's executor mints (and closes) its own Root from them (issue #393). The resolved anchor is probed here via fsroot.OpenConfined and released immediately, so a bad root path still fails at the `plan.spec` call site rather than at run time. The returned spec's op.ReceiverRegistry is a freshly-built one from the announced providers — independent of the planning runtime environment's registry.

Use from a `.star` script:

graph = plan.assemble_definition([...])
plan.run(graph, plan.spec())                                  # all defaults — common case
plan.run(graph, plan.spec(root_path="/tmp/staging"))           # override one
plan.run(graph, plan.spec(flags={"dry-run": True}))            # override another

+devlore:defaults programName="", rootPath="", flags=nil

Parameters:

  • `programName`: the tool name; flows into application.Application.Name and drives the variable resolver's env-prefix derivation. Empty string → defaults to the planning env's `Application.Name`.
  • `rootPath`: the absolute path the confined fsroot.Dir is anchored at. Empty string → defaults to the planning env's `Root.Name()`.
  • `flags`: the application.Application.Flags map. Nil → defaults to the planning env's `Application.Flags`.

Returns:

  • `*op.RuntimeEnvironmentSpec`: the constructed spec.
  • `error`: non-nil when the fsroot.OpenConfined probe fails (the target root does not exist or is not accessible).

func (*Provider) Variable

func (p *Provider) Variable(name string, defaultValue any, field string) *op.Variable

Variable constructs an op.Variable reference that resolves to its slot-fill value at execution time.

Authored as `plan.variable(name)` (required), `plan.variable(name, default_value=value)` (optional with a fallback), or `plan.variable(name, field="key")` (projected: the variable holds a record and the reference resolves one field of it — phase-8 step 45; Provider.Item is the gather-body sugar). The reference becomes an op.VariableBinding at slot-stamp time. The default arg is accepted by Phase 1 but not yet propagated into the parameter surface — that wiring lands in Phase 3.

+devlore:defaults defaultValue=nil, field=""

Parameters:

  • `name`: the variable name to look up in the resolved variable map at execution time.
  • `defaultValue`: the optional fallback value when no source supplies the variable. A nil value means "no default declared", meaning that the variable is required.
  • `field`: the optional record field to project at resolve time; "" resolves the whole value.

Returns:

  • *op.Variable: the variable reference value (Value and Source are zero until the resolver fills them).

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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