runtime

package
v0.0.0-...-419c6d3 Latest Latest
Warning

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

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

Documentation

Overview

Package runtime is the deterministic Launch executor for a frozen Aileron Flight Plan (ADR-0027, issue #1511). It loads a sealed, signed plan from the store, verifies it, resolves declared inputs once at the launch boundary (#1523), and walks the step graph in topological order through the sealed action boundary (#1507), materializing declared file artifacts (#1519) and emitting a customer-owned audit record.

The no-LLM guarantee is structural, not a runtime check. The executor has exactly four step-kind branches (action-call, transform, tool, llm-seam) and only the explicitly marked llm-seam branch can reach a language model. The action-call, transform, and tool branches hold no reference to any seam type, so no deterministic step can reach an LLM by construction. In v1 the seam is unwired by default: an llm-seam step with no configured provider is a hard error, so a default launch reaches no LLM at all.

The runtime depends on the manifest, freeze, and store packages but takes the action boundary, the approval channel, and the audit sink as thin SPI interfaces (ActionDispatcher, Approver, AuditSink, Clock, LLMSeam). The CLI wires those seams to the real daemon-backed implementations; the runtime core stays unit-testable with fakes, mirroring the freeze DigestResolver/FeatureComposer seam discipline.

This package never reads or carries a credential value. Step args are binding references only (the closed binding grammar enforces this at decode), and credentials are injected host-side at the action boundary (ADR-0005, ADR-0019). No secret ever reaches plan code through this runtime.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EnforceConstraint

func EnforceConstraint(name string, v any, c *Constraint) error

EnforceConstraint checks a resolved value against its declared constraint. The comparison is over the value's string form (fmt.Sprintf("%v", v)), so a number or timestamp input stays checkable: enum requires equality with one allowed string, pattern requires the compiled regexp to match. A violation names the input, the (capped) value, and the constraint so the failure is actionable without echoing an unbounded resolved value back to the operator.

It is exported so the CLI's interactive input walk validates a typed entry against the SAME authoritative constraint pass the final resolveInputs check runs (#2063), rather than mirroring the logic and risking drift.

func NonSourceSeamBindings

func NonSourceSeamBindings(plan *Plan, stepID string, bindings map[string]any) map[string]any

NonSourceSeamBindings returns the subset of a seam step's resolved bindings that is safe to persist in the flightplan.launch.seam audit record (#2119), honoring the ADR-0027 audit boundary. The full bindings map is deliberately sent whole to the agent on the seam_pending envelope (the agent needs it), but a binding of BindInput kind that references a `source`-rule input carries the source dataset inline in that map. Such an inline dataset must never land in the persisted audit trail, so this helper drops any binding whose BindInput name resolves to a source input; every BindStep binding and every non-source BindInput binding passes through verbatim.

It single-sources the ADR-0027 boundary rule in the runtime (mirroring launchConfigInputs) so the daemon-side emission stays free of binding/input knowledge. Deterministic and nil-safe: a nil plan, an unknown step id, or nil bindings all yield an empty (non-nil) map.

Types

type Action

type Action struct {
	Ref           string
	TrustContract TrustContract
}

Action is one declared action requirement plus its decoded trust contract.

type ActionDispatcher

type ActionDispatcher interface {
	Dispatch(ctx context.Context, ref string, args map[string]any) (DispatchResult, error)
}

ActionDispatcher dispatches a declared action through the sealed action boundary (ADR-0003/0005/0008). ref is the manifest action ref (aileron:<connector>.<action>); args are resolved binding values, never secrets. The host injects credentials at the boundary; dispatcher code never sees them.

type ApprovalRequest

type ApprovalRequest struct {
	// ActionRef is the action awaiting a decision.
	ActionRef string
	// Effect is the operation effect that routed this action to approval.
	Effect Effect
	// Args is a redacted summary of the resolved args presented to the
	// approver. Never secrets (bindings are references).
	Args map[string]any
}

ApprovalRequest is the effect-driven gate the runtime raises before a non-read action-call. It mirrors the ActionApproval shape the daemon approval lifecycle consumes (ADR-0009) without taking the dependency.

type Approver

type Approver interface {
	Approve(ctx context.Context, req ApprovalRequest) (Decision, error)
}

Approver routes an effect-gated action through the out-of-band approval channel (ADR-0009). A read action never reaches the Approver; the runtime calls it only for write/delete/spend/external-send. It has three outcomes: approve (Decision.Approved), deny (a zero Decision or a Reason'd deny), and pending (Decision.Pending) — the third suspends the run at this step rather than blocking, so a decision that is not yet available parks the run instead of holding the goroutine (#2100).

type Artifact

type Artifact struct {
	Name     string
	Path     string
	MimeType string
	// Content is the materialized utf-8 bytes. Empty for a target:none
	// artifact (recorded but not written).
	Content []byte
	// Digest is the sha256 of Content, formatted `sha256:<hex>`. It is computed
	// at construction over the exact bytes writeArtifacts lands on disk, so an
	// operator can independently verify a loose output file against the digest
	// recorded in the per-launch audit (ADR-0027 snapshot identifier). Empty
	// content hashes deterministically to the sha256 of zero bytes, so the
	// digest is always recordable — including for a target:none artifact that
	// is retained but never written.
	Digest string
	// Written reports whether the artifact should be written to disk
	// (publish target `file`) vs retained in the run record only (`none`).
	Written bool
}

Artifact is one materialized output: the declared output's name, the path it was written to, its mime type, and the bytes. The runtime records it in the audit by name and content digest; the bytes are written to disk by the run orchestration when the publish target is `file`.

type AuditRecord

type AuditRecord struct {
	// Kind is the record's kind, the explicit discriminator the CLI sink maps
	// to a model.EventType (RecordKindAction → flightplan.launch.action,
	// RecordKindLaunch → flightplan.launch, RecordKindOutput →
	// output.materialized, RecordKindReach → flightplan.launch.reach,
	// RecordKindSeam → flightplan.launch.seam).
	Kind AuditRecordKind
	// ActionRef is the action the record describes, or "" for a per-launch
	// summary or per-output record.
	ActionRef string
	// Fields holds the declared audit field values. For a RecordKindOutput
	// record it holds the flat `aileron.*` attribute map the sink surfaces as
	// the top-level event payload.
	Fields map[string]any
	// Sink is the customer-owned sink reference from the trust contract.
	Sink string
}

AuditRecord is one customer-owned audit entry. Fields holds exactly the declared audit.fields for the action (data reads referenced by resolved binding, never the dataset inline; ADR-0027 audit boundary).

type AuditRecordKind

type AuditRecordKind int

AuditRecordKind is the closed set of runtime audit record kinds. It is the explicit discriminator the CLI sink switches on to map each record to an internal/model.EventType, so the runtime stays free of an internal/model import while the third (output) kind is explicit rather than overloaded onto ActionRef.

const (
	// RecordKindAction is one per-action-call dispatch record.
	RecordKindAction AuditRecordKind = iota
	// RecordKindLaunch is the per-launch summary record.
	RecordKindLaunch
	// RecordKindOutput is one per-materialized-output provenance record (#1752),
	// emitted for both action-call and transform materializing steps.
	RecordKindOutput
	// RecordKindReach is one per tool-step record declaring the step's
	// per-step network reach (#1784, enforced by #1829): the declared Effect
	// plus the hosts the step ran under. `enforced:true` means the hosts are
	// the verified lock's sealed reach and the step ran under a step-scoped
	// proxy credential restricted to exactly them; `enforced:false` means the
	// step declared a contract but no sealed reach existed (a
	// directly-constructed plan outside the verified load path).
	RecordKindReach
	// RecordKindSeam is one per-distinct-seam-step record (#2119). It is
	// constructed daemon-side (not emitted by the runtime's emitStepAudit) when a
	// launch/resume suspends at a marked llm-seam step: the runtime is stateless
	// across suspend/resume calls, so only the daemon (which owns the run record)
	// can dedupe a re-suspend. This kind exists so both audit sinks translate the
	// daemon-constructed record onto model.EventTypeFlightPlanLaunchSeam
	// uniformly. Its flat `aileron.*` payload carries the seam step id, the
	// recorded model hint, and the seam's non-source bindings (the source-input
	// inline dataset is excluded per the ADR-0027 audit boundary).
	RecordKindSeam
)

type AuditSink

type AuditSink interface {
	Record(ctx context.Context, rec AuditRecord) string
}

AuditSink receives the per-action and per-launch audit records. The CLI wires it to internal/audit.Recorder over the configured store; tests use a recording fake. Record returns a record id the RunResult surfaces.

type AuditStructure

type AuditStructure struct {
	Fields []string
	Sink   string
}

AuditStructure declares the closed set of audit fields an action emits and the customer-owned sink reference.

type Binding

type Binding struct {
	Kind BindingKind
	// Raw is the original binding string, retained for error messages and
	// audit summaries.
	Raw string
	// Name is the input name for BindInput.
	Name string
	// StepID and Output identify a prior step output for BindStep.
	StepID string
	Output string
}

Binding is a parsed data-flow binding. A binding is a REFERENCE, never a value (schema $defs.binding). The closed grammar is the structural guarantee that step args are wiring, not embedded secrets.

func ParseBinding

func ParseBinding(raw string) (Binding, error)

ParseBinding parses a binding string against the closed grammar. A string outside the grammar is a hard decode error: it could smuggle a literal value where only a reference is permitted.

type BindingKind

type BindingKind int

BindingKind discriminates the two reference forms in the closed binding grammar: a resolved input or a prior step output.

const (
	// BindInput references a declared input resolved at the launch boundary:
	// inputs.<name>.
	BindInput BindingKind = iota
	// BindStep references a named output of a prior step: steps.<id>.<out>.
	BindStep
)

type Clock

type Clock interface {
	Now() time.Time
}

Clock supplies the single launch-time read for dynamic inputs (now/today). It is injected so the resolution phase is deterministic in tests: the runtime reads the clock ONCE at the launch boundary and reuses that value for every dynamic input, so two steps reading the same dynamic input see one value (#1523 boundary-straddle guarantee).

type Constraint

type Constraint struct {
	Enum    []string
	Pattern *regexp.Regexp
}

Constraint bounds an input's resolved value. Exactly one field is set: Enum is the closed set of allowed string forms, or Pattern is the compiled author-anchored RE2 regexp the resolved value's string form must match. The pattern is compiled once at decode and stored here so enforcement never recompiles.

type Decision

type Decision struct {
	Approved bool
	// Pending reports that no decision has landed yet, so the run suspends at
	// this action-call instead of blocking. Mutually exclusive with Approved.
	Pending bool
	// Reason carries an optional human reason recorded in the audit on deny.
	Reason string
}

Decision is an approval outcome. It is a closed three-way: approved, denied, or pending. A zero Decision ({Approved:false, Pending:false}) is an explicit deny. Pending:true means no decision has landed yet and the run must SUSPEND at this step rather than block (#2100); the caller (the daemon, #2101) later resumes with the memoized outputs once a decision is made. Approved and Pending are mutually exclusive: a pending decision is neither approved nor denied, so the enforcer short-circuits before dispatch and the effect never fires.

type DecodeError

type DecodeError struct {
	Reason string
}

DecodeError reports a strict-decode refusal. A decode error means the plan is structurally invalid and the runtime refuses to run any step. The message names the offending element so the author can fix it.

func (*DecodeError) Error

func (e *DecodeError) Error() string

type DenyError

type DenyError struct {
	ActionRef string
	Reason    string
}

DenyError reports that an effect-gated action was denied at the approval channel. The step aborts and the run fails; the denial is audited.

func (*DenyError) Error

func (e *DenyError) Error() string

type DispatchResult

type DispatchResult struct {
	// Output is the action's result payload, a JSON-shaped map the runtime
	// binds downstream steps against and redacts before surfacing.
	Output map[string]any

	// ConnectorVersion is the pinned semantic version of the connector that
	// produced the result.
	ConnectorVersion string
	// ConnectorHash is the connector's content hash (`sha256:<hex>`).
	ConnectorHash string
	// IdentityLabel is the non-secret identity label of the credential
	// binding the action used, or "" when none resolved.
	IdentityLabel string
	// CredentialBinding is the name of the credential binding the action
	// used (a reference, never the secret), or "" when none resolved.
	CredentialBinding string
	// ConsentDecision is the consent posture the action ran under
	// (`unattended` on the synchronous read path), or "" when unset.
	ConsentDecision string
}

DispatchResult is the outcome of one action dispatch through the sealed boundary. Output is the parsed action result the runtime reads downstream; it carries no credential (credentials are injected host-side).

type Effect

type Effect string

Effect is the operation effect that drives approval routing (ADR-0009).

const (
	EffectRead         Effect = "read"
	EffectWrite        Effect = "write"
	EffectDelete       Effect = "delete"
	EffectSpend        Effect = "spend"
	EffectExternalSend Effect = "external-send"
)

type Encoding

type Encoding string

Encoding is the artifact content encoding. v1 implements utf-8 only; base64 is reserved and refused at materialization.

const (
	EncodingUTF8   Encoding = "utf-8"
	EncodingBase64 Encoding = "base64"
)

type FixedClock

type FixedClock struct{ T time.Time }

FixedClock returns a fixed time. It is the deterministic test clock and the pin point for the determinism property test.

func (FixedClock) Now

func (c FixedClock) Now() time.Time

Now returns the fixed time.

type Idempotency

type Idempotency struct {
	SafeToRetry    bool
	IdempotencyKey bool
}

Idempotency records whether an action is safe to retry and whether it accepts a client-supplied idempotency key.

type ImageRunResult

type ImageRunResult struct {
	// ContentHash is the verified content hash of the frozen unit that ran.
	ContentHash string
	// ResolvedInputs is the frozen resolved-input set.
	ResolvedInputs map[string]any
	// StepOutputs maps steps.<id> → its named outputs.
	StepOutputs map[string]map[string]any
	// Artifacts are the materialized output artifacts.
	Artifacts []Artifact
	// AuditIDs are the audit record ids emitted to the sink.
	AuditIDs []string
}

ImageRunResult maps onto RunResult so the image-boot path returns the same public shape as the in-process path. A caller cannot tell from the result which path produced it, which keeps launch output identical across the boot-vs-in-process branch.

type ImageRunSpec

type ImageRunSpec struct {
	// Image is the exact `ref@sha256:<hex>` the verified lock pinned. It is the
	// load-bearing security value: the runner MUST boot this image verbatim so
	// the environment entered corresponds to the lock's signed assertion.
	Image string
	// Name is the frozen skill name (the store selector).
	Name string
	// Version is the frozen version id (the store directory id).
	Version string
	// Inputs are the literal input overrides supplied at launch.
	Inputs LaunchArgs
	// OutDir is the directory file-target artifacts are written to. Empty skips
	// writing (artifacts are still recorded in the result).
	OutDir string
}

ImageRunSpec is the input to the ImageRunner seam. It carries the verified pinned image (the `ref@digest` string the runtime booted from the signed lock) plus everything the in-container launch needs to run the plan to completion: the frozen-unit selector (Name/Version), the launch input overrides, and the out-dir artifacts are written to.

type ImageRunner

type ImageRunner interface {
	Run(ctx context.Context, spec ImageRunSpec) (ImageRunResult, error)
}

ImageRunner boots the verified pinned environment image and runs the frozen plan to completion inside it (issue #1731). The runtime core depends only on this seam; the CLI (cmd/aileron) wires the production implementation over internal/sandbox/container, so the runtime never imports the container package. Its contract: boot the exact image named in ImageRunSpec.Image, run the selected frozen unit against the given inputs/out-dir, and return the RunResult-shaped outcome. The runtime supplies ImageRunSpec.Image straight from the verified lock, so the runner is handed a pin it must not re-resolve.

type Input

type Input struct {
	Name        string
	Type        InputType
	Description string
	Resolution  Resolution
	// Constraint bounds the resolved value. Nil means unconstrained (today's
	// behavior). When non-nil it holds exactly one of an Enum allow-set or a
	// compiled Pattern; the launch/resolution boundary rejects a resolved
	// value that falls outside it.
	Constraint *Constraint
	// HasExample records whether the manifest declared an example value. It
	// mirrors the Resolution.HasDefault/Default pair so a declared example of a
	// zero-ish value (empty string, false, 0) is still distinguishable from an
	// absent one.
	HasExample bool
	// Example is the declared first-class example value the guided launch walk
	// displays on the prompt line. It is deliberately untyped so an example may
	// be a string, number, object, or array. Meaningful only when HasExample is
	// true; it never affects resolution or required-ness.
	Example any
	// NoPrompt marks an advanced or non-interactive input the guided launch walk
	// skips (the manifest's `prompt: false`). The zero value is promptable, so an
	// input with no `prompt` key walks as usual. A skipped input's declared
	// default applies silently and it stays overridable via --input.
	NoPrompt bool
}

Input is one declared input with its resolution rule. Inputs resolve once, at the launch boundary, into a concrete resolved-input set (#1523).

type InputPrompter

type InputPrompter interface {
	PromptInput(in Input) (string, error)
}

InputPrompter resolves a missing required literal input interactively at the launch boundary. The runtime consults it ONLY for a literal input that has no `--input` launch override AND declares no default; every other input (dynamic, source, or a literal with an override or a default) never reaches it, so a resolvable input is never turned into a prompt. The returned string is treated identically to a `--input name=value` override: it is deep-copied into the frozen resolved set and validated by the same final constraint pass, so a prompted value outside an enum/pattern constraint fails the launch closed. Nil (the default) preserves fail-fast: a missing required literal errors without prompting, so a piped or CI launch never blocks waiting on input.

type InputType

type InputType string

InputType is the declared value type of an input.

type InputWalker

type InputWalker interface {
	Walk(inputs []Input, args LaunchArgs) (LaunchArgs, error)
}

InputWalker resolves EVERY declared literal input into a launch-args set by walking the declared inputs in declaration order, one interactive prompt each (#2063). Unlike InputPrompter (which the runtime consults only for a missing required literal), the walker is a host-side pre-pass Run runs BEFORE the image-boot / in-process branch, so it reaches the sealed-image (frozen-plan) mainline where the in-container prompter is never consulted. The runtime hands it the plan's declared inputs and the launch args parsed from `--input`, and the walker returns a merged LaunchArgs carrying a value for every literal: an operator-typed entry as a string, an Enter-accepted default as its native typed value. Both downstream paths then consume the merged args identically (the image path serializes them onto the `--input` container re-entry, the in-process path resolves them as overrides in resolveInputs). Dynamic and source inputs are never editable; the walker renders a read-only line and resolves them normally at the boundary. Nil (the default) skips the walk, so a non-interactive launch keeps today's silent-default one-shot behavior. The CLI wires it only on an interactive TTY that did not pass `--accept-defaults`.

type LLMSeam

type LLMSeam interface {
	Run(ctx context.Context, req SeamRequest) (map[string]any, error)
}

LLMSeam is a marked non-deterministic seam (ADR-0027, multi-seam per #2100). It is the ONLY interface in the runtime that may reach a language model. A plan may declare one or more llm-seam steps; every seam is structurally marked and served through this one interface. In v1 the seam is unset by default, so an llm-seam step errors with "no seam provider configured" and a default launch reaches no LLM at all (the suspend/resume path, #2100, is the opt-in alternative to a synchronously wired seam). The action-call and transform branches hold no reference to this type, so no deterministic step can reach an LLM by construction.

type LaunchArgs

type LaunchArgs map[string]any

LaunchArgs are the literal input overrides supplied at launch (the `--input name=value` CLI flags). A literal input takes its launch override, then its declared default; a missing required literal (no override, no default) is an error.

type LoadError

type LoadError struct {
	Reason string
}

LoadError reports a load/verify refusal. A frozen unit that fails verification returns a *LoadError and runs zero steps.

func (*LoadError) Error

func (e *LoadError) Error() string

type LoadedPlan

type LoadedPlan struct {
	Plan        *Plan
	ContentHash string
	// ResolvedImages carries the verified image digest pins from the frozen
	// lock. When non-empty, Run boots the pinned image and
	// runs the plan inside it; when empty, Run stays on the in-process path.
	// The pins come from the verified manifest lock block, so the digest booted
	// is exactly the one the author signature attested.
	ResolvedImages []freeze.ImagePin
	// StepTrust is the verified lock's sealed per-step reach
	// (lock.stepTrust), keyed by tool step id (#1829). It is the ONLY source
	// of the network reach the runtime enforces for a tool step: the
	// frontmatter trust-contract copy is audit context, never the enforcement
	// input, so a reach can never be re-supplied at launch. Populated only on
	// the verified path. Nil when the frozen unit seals no tool-step reach.
	StepTrust map[string]freeze.StepReach
	// SignerFingerprint is the `sha256:<hex>` fingerprint of the verified
	// author public key (from freeze.VerifyFrozen). It is threaded into the
	// launch audit as the plan's signer identity (#1752). Populated only on the
	// verified path, so it names the key that actually attested this unit.
	SignerFingerprint string
	// Publisher is the connector-style publisher authority the frozen plan
	// declares in its verified lock (`github://owner/repo` or bare
	// `github://owner`), or "" when the plan declares no publisher. The
	// host-side publisher-trust gate (#1900) enforces trust only when this is
	// non-empty. Populated only on the verified path from freeze.VerifyFrozen,
	// so a tampered publisher refuses at verification before it is read here.
	Publisher string
	// SignerKey is the raw ed25519 public key the plan's signature verified
	// against (from freeze.VerifyFrozen). The publisher-trust gate checks its
	// membership in the keyring for the declared Publisher. Populated only on
	// the verified path.
	SignerKey ed25519.PublicKey
	// ImageOrigin is the recorded install source for a plan installed by OCI
	// reference (#1902/#1903), read from the store's non-signed origin sidecar.
	// Present is true only when the version directory carries the sidecar (an
	// OCI install); a locally-frozen plan leaves it zero, which is exactly the
	// signal runInImage uses to stay on the local-tag boot path. The origin is
	// a fetch coordinate, NOT a verification trust anchor: the pulled image is
	// still verified against the signature-covered ResolvedImages pin.
	ImageOrigin RegistryImageOrigin
}

LoadedPlan is a verified, decoded plan ready to run, plus the verified content hash for the audit trail.

func LoadVerified

func LoadVerified(s *store.Store, name, id string) (LoadedPlan, error)

LoadVerified loads a frozen skill version from the store, verifies it (signature + content hash), parses the verified manifest, and decodes it into a typed Plan. Any verification failure or decode refusal returns an error and the runtime runs no step.

The verification gate (#1509/#1511) is the security boundary: a tampered manifest, a flipped signature, or a content-hash mismatch all refuse before execution. Verification reuses freeze.VerifyFrozen so the canonical-bytes reconstruction lives in exactly one place.

type LocalImageDigestResolver

type LocalImageDigestResolver interface {
	Resolve(ctx context.Context, image string) (string, error)
}

LocalImageDigestResolver resolves a locally-resolvable image reference (a tag the local daemon carries) to its content digest, so the runtime can re-check that the composed local tag it is about to boot still resolves to the digest the signed lock attested (#1863). The runtime core depends only on this seam; the CLI (cmd/aileron) wires the production implementation over the same `image inspect` (RepoDigests-then-.Id) logic that PRODUCED the pin's Digest at freeze time, so the runtime never imports the container package (mirroring the ImageRunner discipline).

Its contract, consumed ONLY on the composed-tools boot path (pin.LocalTag != ""): return the local `sha256:` digest the daemon resolves image to. The boot compares that digest against the pin's attested Digest and fails closed on any mismatch. A resolve ERROR (the image is gone from the daemon, the inspect fails) is likewise fail-closed at the call site: the attested image is not present, so the boot must refuse rather than boot an unverified tag.

type Options

type Options struct {
	// Store is the canonical skill store the frozen unit loads from.
	Store *store.Store
	// Name is the frozen skill name.
	Name string
	// Version is the frozen version id (the store directory id).
	Version string

	// Inputs are the literal input overrides supplied at launch.
	Inputs LaunchArgs

	// Dispatcher is the action boundary seam (required to run any action-call
	// or source input).
	Dispatcher ActionDispatcher
	// Approver routes effect-gated actions (required when any non-read action
	// runs).
	Approver Approver
	// Audit receives the customer-owned audit records. Nil emits no audit.
	Audit AuditSink
	// Seam is a marked LLM seam. Nil (the v1 default) makes any llm-seam step
	// error on the non-suspendable path, so a default launch reaches no LLM. A
	// plan may declare more than one seam (#2100); each is served by this one
	// provider.
	Seam LLMSeam

	// Suspendable opts this run into the generic suspend/resume path (#2100).
	// When true, the runtime SUSPENDS (returns a nil error with
	// RunResult.Pending set) at the first step it cannot complete in-band: an
	// unfulfilled llm-seam (no memo entry, no wired Seam value) or a gated
	// action-call whose Approver returned Decision.Pending. When false (the
	// default), behavior is exactly as before: a nil Seam is a hard error and a
	// pending Approver decision is impossible (a synchronous approver never
	// returns pending). A non-nil ResumeOutputs also implies a suspendable run,
	// so a resume never needs both flags set.
	Suspendable bool
	// ResumeOutputs is the caller-supplied memo (stepId → its named outputs) from
	// a prior suspend, replayed on resume. Every step whose id is present here is
	// injected WITHOUT re-execution (exactly-once for effects) and re-audits
	// nothing. Nil on a fresh launch. A non-nil value implies Suspendable.
	ResumeOutputs map[string]map[string]any
	// RunID is stable across a suspend/resume sequence. The runtime mints one
	// when empty and echoes it on the suspend result, so every call in a sequence
	// shares the same id.
	RunID string
	// ImageRunner boots the verified pinned environment image and runs the
	// plan inside it. When the loaded plan carries a resolved image pin and this
	// seam is wired, Run delegates to it; when the plan pins no image, Run stays
	// on the in-process path and never touches this seam. A plan that pins an
	// image with no ImageRunner configured is an explicit error, never a silent
	// in-process fallback (a declared environment must be entered to honor the
	// attestation).
	ImageRunner ImageRunner
	// ImageDigestResolver re-checks, at boot time, that a composed-tools pin's
	// LocalTag still resolves in the local daemon to the host platform's attested
	// config content digest (#1863). It is consulted ONLY on the composed boot
	// path (pin.LocalTag != ""): a composed image is booted by its mutable local
	// tag (its recorded content digest is a locally-built image Id, not a registry
	// digest, so `ref@digest` would not resolve), and nothing else re-checks that
	// the daemon image behind that tag is still the attested one. When this seam is
	// wired and the pin is composed, the resolved digest MUST equal the host
	// platform's entry from the pin's per-arch configDigests set or the boot fails
	// closed (no ImageRunner.Run call); a plan not built for this host's platform is
	// likewise fail-closed, as is a resolve error (the
	// attested image is absent). Nil (the zero value) skips the guard and boots as
	// before (backward-compatible), mirroring the ImageRunner nil-guard discipline.
	ImageDigestResolver LocalImageDigestResolver
	// RegistryImageResolver pulls and verifies the published image for a plan
	// installed by OCI reference (#1903). It is consulted ONLY when the loaded
	// plan carries a registry origin (LoadedPlan.ImageOrigin.Present): such a
	// plan has no local build, so runInImage pulls the published image from the
	// recorded registry, verifies it against the signed lock pin per the pin's
	// binding kind, and boots the returned reference. When the plan needs this
	// seam (a registry origin) but it is nil, the boot is a fail-closed error,
	// never a silent fall-through to the local-tag path (a plan installed from a
	// registry has no local tag to boot). A locally-frozen plan (no origin)
	// never touches this seam and boots by its local tag as before. The CLI
	// wires the production impl over pull.PullImage and nils it on the image-boot
	// re-entry (the sentinel routes in-process before any boot).
	RegistryImageResolver RegistryImageResolver
	// ToolRunner executes a `kind: tool` step as a deterministic subprocess in
	// the current pinned environment (#1829). Unlike ImageRunner, the plan
	// orchestration stays in-process (runPlan); the tool step never dispatches
	// a sibling container. When a loaded plan carries a tool step and this
	// seam is unset, that step is an explicit error, never a silent skip
	// (mirrors the ImageRunner nil-guard discipline).
	ToolRunner ToolStepRunner
	// PublisherVerifier is the host-side publisher-trust gate (#1900). When it
	// is wired and the loaded plan declares a publisher in its verified lock,
	// Run resolves the plan's verified signing key against the operator's
	// keyring for that publisher and refuses to run when the publisher is not
	// trusted (fail-closed), before any boot or step. Nil skips the gate; a
	// plan that declares no publisher also skips it. The CLI wires the
	// keyring-backed impl for a host launch and wires nil on the image-boot
	// re-entry (the host already gated before boot, and no keyring is mounted
	// into the sealed container).
	PublisherVerifier PublisherVerifier
	// InPinnedImage marks this run as already executing INSIDE the verified
	// pinned environment image, the image-boot re-entry (#1731). It routes
	// a whole-plan-pinned unit onto the in-process path instead of booting the
	// pin again: inside the container, in-process IS the certified
	// environment, and re-booting would recurse (the image carries no nested
	// container runtime, and with one it would never terminate). The CLI sets
	// this from the AILERON_SKILL_IMAGE_BOOTED sentinel the image runner
	// injects into the boot env.
	InPinnedImage bool

	// Clock supplies the single launch-time read for dynamic inputs. Nil uses
	// SystemClock.
	Clock Clock
	// Transforms is the deterministic transform registry. Nil uses the
	// default registry.
	Transforms *TransformRegistry

	// InputPrompter resolves a missing required literal input interactively at
	// the launch boundary (a literal input with no `--input` override and no
	// declared default). When it is wired, resolveInputs asks it for the value
	// instead of failing; when it is nil (the default), the runtime keeps
	// today's fail-fast error, so a piped or CI launch never blocks on input.
	// The CLI wires it only when stdin is a TTY. A prompted value is a string
	// treated identically to a `--input name=value` override: it is deep-copied
	// into the frozen resolved set and validated by the same final constraint
	// pass.
	//
	// On the interactive path the InputWalker (below) resolves every literal into
	// Inputs before either branch runs, so a still-wired InputPrompter is never
	// consulted there; the seam is retained for the non-walk path and its own
	// tests.
	InputPrompter InputPrompter

	// InputWalker runs a host-side interactive pass over every declared literal
	// input BEFORE the image-boot / in-process branch (#2063), collecting a value
	// for each into Inputs. It is the only interactive seam that reaches the
	// sealed-image mainline: the in-container prompter is never consulted for a
	// whole-plan-pinned unit (Run short-circuits to the boot before resolveInputs
	// runs), so wiring only InputPrompter would ship the guided walk inert on the
	// primary path. Run consults it only when it is non-nil AND this run is NOT
	// the in-container image-boot re-entry (InPinnedImage): the re-entry runs
	// non-TTY and must never re-walk, so gating on InPinnedImage makes
	// no-recursion structural rather than dependent on the container's TTY state.
	// Nil (the default) skips the walk and keeps today's silent-default one-shot
	// launch. The CLI wires it only on an interactive TTY without
	// `--accept-defaults`.
	InputWalker InputWalker

	// OutDir is the directory file-target artifacts are written to. Empty
	// skips writing (artifacts are still recorded in the result).
	OutDir string
}

Options configures a launch. The Store + Name + Version select the frozen unit; the SPI seams wire the runtime to the daemon-backed boundary; the Clock and TransformRegistry default to deterministic, LLM-free implementations.

type Output

type Output struct {
	Name     string
	MimeType string
	Encoding Encoding
	Target   PublishTarget
	Path     string
}

Output is one declared output artifact: the declared interface the runtime materializes through the file-map transport (#1519).

type PendingApprovalError

type PendingApprovalError struct {
	// ActionRef is the action awaiting a decision.
	ActionRef string
	// Effect is the operation effect that routed this action to approval.
	Effect Effect
	// Args is the redacted args summary the approver was shown, carried through
	// so the suspend result presents the same request without re-deriving it.
	Args map[string]any
}

PendingApprovalError reports that an effect-gated action's Approver returned Decision.Pending: no decision has landed yet, so the run SUSPENDS at this step rather than blocking (#2100). It is a sentinel the executor recognizes and converts into a SuspendResult without re-deriving the approval request. Unlike DenyError, it is NOT a run failure: the executor unwinds to a suspend, and the effect never fires (dispatch short-circuits before Dispatch).

func (*PendingApprovalError) Error

func (e *PendingApprovalError) Error() string

type Plan

type Plan struct {
	// Name is the skill name from the manifest frontmatter.
	Name string
	// Actions are the declared action requirements indexed by ref. The
	// trust contract drives approval routing, idempotency, redaction, and
	// audit for each action-call.
	Actions map[string]Action
	// Inputs are the declared inputs in declaration order. Each resolves
	// once at the launch boundary.
	Inputs []Input
	// Outputs are the declared output artifacts indexed by name.
	Outputs map[string]Output
	// Steps are the step-graph steps in declaration order. The executor
	// walks them in a topologically-sorted order (see Order).
	Steps []Step
	// Order is the deterministic topological order of step indices the
	// executor walks, computed from `steps.*` edges only.
	Order []int
}

Plan is the typed, validated model of a frozen plan's `aileron` block that the executor walks. It is produced by Decode from a manifest.Manifest and is the single in-memory shape the runtime reads; the loosely-typed `[]any` fields on the manifest never escape the decode boundary.

func Decode

func Decode(m *manifest.Manifest) (*Plan, error)

Decode builds a typed, validated Plan from a parsed manifest. It is strict: an unknown step kind, a malformed binding, a duplicate id, a binding that references an undeclared input or absent step, a materializesOutput naming an undeclared output, or a cycle in the steps.* dependency graph is a hard refusal returned as a *DecodeError. No step runs unless Decode succeeds.

An instruction-only manifest (no aileron block) has no composition to run and is refused: Launch executes a step graph, and there is none.

Decode takes no image pins: a `kind: tool` step (#1829) runs inside the plan's single pinned environment, so the pins are a boot concern (the load path threads them to LoadedPlan.ResolvedImages), never a decode input.

type PublishTarget

type PublishTarget string

PublishTarget is where the runtime materializes an output artifact.

const (
	PublishFile PublishTarget = "file"
	PublishNone PublishTarget = "none"
)

type PublisherVerifier

type PublisherVerifier interface {
	VerifyPublisher(publisher string, signingKey ed25519.PublicKey) error
}

PublisherVerifier is the host-side publisher-trust seam (ADR-0013, #1900). When a frozen plan declares a publisher in its signed lock, Run resolves the plan's verified signing key against the operator's keyring for that publisher and refuses to run when the publisher is not trusted.

The interface takes only stdlib ed25519 so the runtime stays free of any internal/cstore dependency: the CLI wires the concrete keyring-backed implementation (cmd/aileron/skill_launch_publisher.go). VerifyPublisher returns a non-nil error when the declared publisher does not trust the signing key (fail-closed); it returns nil when the publisher trusts the key.

The gate is host-side only. On the image-boot re-entry (#1731) the CLI wires a nil verifier: the host already ran the gate before booting the pin, the keyring is not mounted into the sealed container, and re-checking inside the container would resolve an empty keyring and fail closed for every image-pinned plan. A nil verifier (or a plan that declares no publisher) skips the gate.

type RedactionKind

type RedactionKind string

RedactionKind is the closed set of redaction operations.

const (
	RedactDrop RedactionKind = "drop"
	RedactMask RedactionKind = "mask"
	RedactHash RedactionKind = "hash"
)

type RedactionRule

type RedactionRule struct {
	Field string
	Rule  RedactionKind
}

RedactionRule names a result field path and how it is redacted before the result surfaces into the graph or any audit summary.

type RegistryImageOrigin

type RegistryImageOrigin struct {
	// Registry is the registry+repository the published image lives in (e.g.
	// "ghcr.io/acme/plan"), without tag or digest.
	Registry string
	// VersionTag is the store version id (freeze slug) the artifact was published
	// under; the composed image was published under a tag derived from it.
	VersionTag string
	// Present reports whether the loaded plan carries a registry origin. When
	// false, the runtime stays on the local-tag boot path; when true, the runtime
	// pulls and verifies the published image via RegistryImageResolver.
	Present bool
}

RegistryImageOrigin is the recorded install source of a plan installed by OCI reference (#1902/#1903): the registry+repository the signed artifact was pulled from and the version tag it was published under. Present is false for a locally-frozen plan, which has no origin sidecar and boots by its local tag. It answers only WHERE to pull the published image; the signed lock pin answers WHAT to verify, so this carries no trust anchor.

type RegistryImageResolver

type RegistryImageResolver interface {
	Resolve(ctx context.Context, origin RegistryImageOrigin, pin freeze.ImagePin) (bootRef string, err error)
}

RegistryImageResolver pulls a published composed (or foreign-base) image from the recorded registry origin and verifies it against the signed lock pin under the pin's binding kind (freeze.BindingKind), returning a bootable reference (#1903). The runtime core depends only on this seam; the CLI (cmd/aileron) wires the production implementation over pull.PullImage, so the runtime never imports oras/registry code (mirroring the ImageRunner discipline).

Its contract, consumed ONLY when a loaded plan carries a registry origin (RegistryImageOrigin.Present): pull the published image the origin points at, verify it against the signature-covered pin per the pin's binding (the host platform's entry from the configDigests set for a composed pin, the manifest digest for a foreign-base pin), and return a content-addressed bootable "ref@manifest-digest" (both binding kinds anchor the boot to a manifest digest so a mutable tag is never booted after verification). Any mismatch, missing image, or pull failure is a fail-closed error and no reference: a registry-origin plan must never boot an unverified image.

type Resolution

type Resolution struct {
	Rule ResolutionRule
	// Literal fields.
	HasDefault bool
	Default    any
	// Dynamic field: "now" or "today".
	DynamicValue string
	// Source fields.
	SourceActionRef string
	SourceSelect    string
}

Resolution is a decoded input resolution rule. Exactly one of the three rule shapes is populated, discriminated by Rule.

type ResolutionRule

type ResolutionRule string

ResolutionRule discriminates how an input resolves at the launch boundary.

const (
	ResolutionLiteral ResolutionRule = "literal"
	ResolutionDynamic ResolutionRule = "dynamic"
	ResolutionSource  ResolutionRule = "source"
)

type ResolvedInputs

type ResolvedInputs struct {
	// Values maps input name → resolved value.
	Values map[string]any
	// SourceBindings records, per source input, the resolved binding the read
	// was recorded by (action ref + select), never the dataset inline. This
	// is what the audit references (ADR-0027 audit boundary).
	SourceBindings map[string]SourceBinding
}

ResolvedInputs is the frozen, read-only set of input values produced once at the launch boundary (#1523). Phase B (the DAG walk) consumes it without re-resolving: a dynamic input is read from the clock exactly once, so two steps reading the same input see one value.

type RunResult

type RunResult struct {
	// ContentHash is the verified content hash of the frozen unit that ran.
	ContentHash string
	// ResolvedInputs is the frozen resolved-input set (Phase A output).
	ResolvedInputs map[string]any
	// StepOutputs maps steps.<id> → its named outputs.
	StepOutputs map[string]map[string]any
	// Artifacts are the materialized output artifacts.
	Artifacts []Artifact
	// AuditIDs are the audit record ids emitted to the sink.
	AuditIDs []string
	// Pending is set (non-nil) when the run SUSPENDED instead of completing
	// (#2100): a suspendable run hit an unfulfilled seam or a pending approval.
	// A completed run has Pending == nil. When Pending is set, StepOutputs /
	// Artifacts / AuditIDs reflect only what completed THIS call, and the caller
	// fulfills the pending step (SuspendResult) and resumes with the carried
	// memo. Run returns a nil error alongside a non-nil Pending: a suspend is not
	// a failure.
	Pending *SuspendResult
}

RunResult is the outcome of a launch: the resolved inputs, the step outputs, the materialized artifacts, and the emitted audit record ids.

func Run

func Run(ctx context.Context, opts Options) (RunResult, error)

Run is the deterministic Launch entry point (#1511). It loads and verifies the frozen unit, resolves declared inputs once (#1523), walks the step graph in topological order through the sealed action boundary with trust-contract enforcement (#1507), materializes declared file artifacts (#1519), writes them to OutDir, and emits the customer-owned audit. Any verification failure or step error aborts with zero side effects beyond the audit trail.

func (RunResult) IsSuspended

func (r RunResult) IsSuspended() bool

IsSuspended reports whether the run suspended rather than completing (#2100). A caller that only cares about the completed-vs-suspended distinction reads this instead of nil-checking Pending directly.

type SeamRequest

type SeamRequest struct {
	StepID   string
	Bindings map[string]any
	// Outputs are the named results the seam must produce.
	Outputs []string
	// Prompt is the seam's sealed instruction template (#2105), carried so a
	// suspend/resume caller (#2101) can present it to the agent that fulfills the
	// seam. Empty when the seam declares no prompt. It is the frozen template
	// verbatim; a caller that wants the bindings rendered in resolves them from
	// Bindings.
	Prompt string
	// Model is the seam's recorded model target hint (#2105), for example
	// anthropic:claude-haiku-4-5. A request/hint, never a pin. Empty when the
	// seam declares no model.
	Model string
}

SeamRequest is the input to a marked LLM seam (an llm-seam step). A plan may declare more than one seam (#2100); each is served by this same request shape.

type SourceBinding

type SourceBinding struct {
	ActionRef string
	Select    string
}

SourceBinding is the audit-safe record of a source input resolution: the action ref and selector that produced it. The resolved dataset is never stored inline here.

type Step

type Step struct {
	ID        string
	Kind      StepKind
	ActionRef string
	// Transform names the deterministic transform to apply, selected from the
	// runtime's closed TransformRegistry. Meaningful only for KindTransform;
	// empty means the identity passthrough (the v1 default). Never carries an
	// LLM-backed transform: that is what KindLLMSeam is for.
	Transform string
	// Args binds action-call argument names to binding references.
	Args map[string]Binding
	// Bindings binds transform / tool / llm-seam input names to binding
	// references.
	Bindings map[string]Binding
	// Outputs are the named results this step produces, referenced by a
	// later step as steps.<id>.<output>.
	Outputs []string
	// MaterializesOutput names a declared output this step's result
	// materializes into. Empty when the step materializes nothing.
	MaterializesOutput string

	// Command is the argv the tool step executes: the first element is the
	// program, the rest its arguments. Always exec'd directly (no shell), so
	// the signed invocation carries no injection surface.
	Command []string
	// MountPath is the in-environment path the resolved step input is
	// written to (as input.json), or empty when the step declares no mount.
	MountPath string
	// CollectPath is the in-environment path whose contents are collected
	// as the step's single declared output, or empty when the step declares
	// no collect.
	CollectPath string
	// TrustContract, when non-nil, is the tool step's declared per-step
	// trust contract: its Hosts declare the step's network reach and Effect
	// the operation effect, validated at decode exactly like an action's
	// contract. The reach the runtime ENFORCES comes from the verified
	// lock's sealed stepTrust section (LoadedPlan.StepTrust), never from
	// this frontmatter copy; a contracted tool step with no sealed entry is
	// a load refusal. Nil when the step declares no reach.
	TrustContract *TrustContract

	// Prompt is the seam's sealed instruction template, using the plan's
	// `{{ inputs.<name> }}` / `{{ steps.<id>.<output> }}` binding grammar. Empty
	// when the seam declares no prompt. Audit-only; unconsumed in v1.
	Prompt string
	// Model is the seam's recorded model target (for example
	// anthropic:claude-haiku-4-5). It is a request/hint recorded not enforced,
	// never a pin. Empty when the seam declares no model. Audit-only;
	// unconsumed in v1.
	Model string
}

Step is one step in the deterministic step graph. A single struct carries every kind's fields; Kind selects which are meaningful. ActionRef is set only for action-call; Args is the action-call binding map; Bindings is the transform/tool/llm-seam binding map. Keeping one struct keeps the executor's kind switch the single dispatch point.

type StepKind

type StepKind string

StepKind is the closed step-kind enum. The no-LLM guarantee is structural: only KindLLMSeam can reach a language model. KindTool runs a declared environment tool as a deterministic subprocess inside the plan's single composed container (#1829); it never reaches an LLM.

const (
	KindActionCall StepKind = "action-call"
	KindTransform  StepKind = "transform"
	KindTool       StepKind = "tool"
	KindLLMSeam    StepKind = "llm-seam"
)

type SuspendKind

type SuspendKind int

SuspendKind discriminates why a run suspended: an unfulfilled seam or a pending approval. It is a small closed enum mirroring StepKind / AuditRecordKind style.

const (
	// SuspendKindSeam means the run suspended at an unfulfilled llm-seam step:
	// no memo entry carries its outputs and no seam value is wired to produce
	// them. The caller fulfills it by producing the seam's declared outputs (via
	// #2101's provider) and resuming with them added to the memo.
	SuspendKindSeam SuspendKind = iota
	// SuspendKindApproval means the run suspended at a gated action-call whose
	// Approver returned Decision.Pending. The effect did NOT fire. The caller
	// presents the approval, and on approve resumes with the same memo and an
	// Approver that now returns Decision.Approved for the step.
	SuspendKindApproval
)

type SuspendResult

type SuspendResult struct {
	// RunID is stable across the whole suspend/resume sequence. The runtime mints
	// one when Options.RunID is empty and echoes the provided one on resume, so
	// every call in a sequence shares the same id.
	RunID string
	// Kind discriminates the suspend reason (seam vs approval).
	Kind SuspendKind
	// StepID is the id of the step the run suspended at.
	StepID string

	// Seam is populated for a SuspendKindSeam suspend: the SeamRequest-shaped
	// payload the caller's provider (#2101) needs to produce the seam's outputs.
	// Nil for a SuspendKindApproval suspend.
	Seam *SeamRequest
	// Approval is populated for a SuspendKindApproval suspend: the
	// ApprovalRequest-shaped payload the caller's approval surface (#2101)
	// presents. Nil for a SuspendKindSeam suspend.
	Approval *ApprovalRequest

	// StepOutputs is the accumulated memo AT THE POINT OF SUSPENSION: every step
	// whose output was already memoized (injected this call) plus every step that
	// actually executed this call, keyed by step id. On resume the caller passes
	// this back as Options.ResumeOutputs (with the newly-fulfilled step's output
	// added, for a seam) so the runtime replays the completed prefix without
	// re-executing it.
	StepOutputs map[string]map[string]any
	// AuditIDs are the audit record ids emitted DURING THIS CALL only. Steps
	// injected from the memo re-audit nothing, so a multi-resume sequence records
	// each real execution exactly once. A suspend emits no per-launch summary
	// record; the terminal (completing) resume emits it.
	AuditIDs []string
}

SuspendResult carries everything a caller needs to fulfill a suspended step and resume the run. It is surfaced on RunResult.Pending; a completed run has RunResult.Pending == nil. A suspend is NOT a failure: Run returns a nil error alongside a non-nil Pending.

type SystemClock

type SystemClock struct{}

SystemClock reads the wall clock in UTC. Production launches use it.

func (SystemClock) Now

func (SystemClock) Now() time.Time

Now returns the current UTC time.

type ToolStepResult

type ToolStepResult struct {
	// Output is the value read back from CollectPath. It becomes the step's
	// named output; a step that declared no collect returns a nil Output.
	//
	// The production runner returns the collect file's RAW BYTES as a
	// string, never a decoded structure. That is the same contract the
	// materialize path already speaks: a string carrier is parsed as the
	// JSON it emitted (decodeCarrier), so a tool that wants its collected
	// output to materialize as a file artifact writes a file-map JSON
	// document ({path, mimeType, encoding, content}) or a JSON data
	// object/array to its collect path. A non-JSON collect file still flows
	// to downstream bindings as a plain string; it only refuses at the
	// materialize boundary if a step tries to materialize it directly.
	Output any
}

ToolStepResult is the outcome of one tool-step execution: the value collected from CollectPath, which becomes the step's declared output and flows into downstream steps' dataflow unchanged.

type ToolStepRunner

type ToolStepRunner interface {
	Run(ctx context.Context, spec ToolStepSpec) (ToolStepResult, error)
}

ToolStepRunner executes a single `kind: tool` step as a subprocess in the current (pinned) environment (#1829). Unlike ImageRunner (which boots one image and runs the WHOLE plan inside it), ToolStepRunner is a per-step seam consumed by the executor already running inside that boot: no sibling container is ever dispatched. The contract: write the resolved input at MountPath, exec the argv with the step-scoped proxy env when Hosts is non-empty (failing closed when the scope cannot be obtained), and read back CollectPath as the step's output. The runtime core depends only on this seam; the CLI wires the production subprocess implementation.

type ToolStepSpec

type ToolStepSpec struct {
	// StepID is the executing step's id, for scope addressing and error
	// context.
	StepID string
	// Command is the argv to exec: Command[0] is the program, the rest its
	// arguments. It comes from the verified manifest, is exec'd with no
	// shell interpretation, and is the executed-command identity the audit
	// records.
	Command []string
	// MountPath is the in-environment directory the resolved Input is
	// written to before the exec (the read side of the mount boundary).
	// Empty means the step declared no mount and no input file is written.
	MountPath string
	// Input is the step's resolved binding input, written under MountPath.
	// It is a binding-resolved value only, never a credential (credentials
	// are injected host-side at the network boundary, not here).
	Input any
	// CollectPath is the in-environment path whose contents are read back as
	// the step's output (the run-and-collect boundary). Empty means the step
	// declared no collect and the step produces no collected output.
	CollectPath string
	// Hosts is the step's SEALED network reach from the verified lock's
	// stepTrust section, never the re-read frontmatter. Non-empty means the
	// runner MUST run the subprocess under a step-scoped proxy credential
	// registered for exactly these hosts, and MUST fail closed (never run
	// unscoped) when it cannot obtain one. Empty means the step declared no
	// reach and runs under the plan-boot proxy environment unchanged.
	Hosts []string
	// CredentialKind and IdentityLabel carry the step's declared credential
	// identity (its kind and non-secret identity label from the trust
	// contract), threaded to the runner so the step-scope mint tells the
	// daemon which credential identity the step's outbound requests belong
	// to (#1980). They are the declared identity, never credential material
	// (credentials are injected host-side at the network boundary). Both are
	// "" when the step declares no trust contract or no credential identity;
	// the mint then sends no credential block and the scope stays
	// unconstrained, exactly as before. Carried, not yet consumed for egress
	// selection (the umbrella's next sub-issue).
	CredentialKind string
	IdentityLabel  string
}

ToolStepSpec is the input to the ToolStepRunner seam (#1829): one `kind: tool` step executed as a deterministic subprocess INSIDE the plan's single pinned environment (the container the whole-plan boot entered). There is no per-step image: the environment identity is the plan's one composed pin, already asserted by the signed lock at boot. The runner writes the resolved Input to MountPath (as input.json), execs the argv directly (no shell), and reads CollectPath back as the step's output.

type Transform

type Transform func(bindings map[string]any, outputs []string) (map[string]any, error)

Transform is one deterministic, no-LLM transform. It reshapes data already in the graph and has NO host, network, credential, or LLM surface. A Transform receives the step's resolved bindings and the names it must produce, and returns one value per declared output.

The signature deliberately holds no reference to any seam or dispatcher type: a transform cannot reach an LLM or the action boundary by construction. This is half of the structural no-LLM guarantee (the other half is that the executor's transform branch only calls into here).

type TransformRegistry

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

TransformRegistry is the closed set of named deterministic transforms a plan may use. v1 ships a default registry; the seam discipline keeps it a pure, LLM-free unit. A transform step names its transform; an unnamed transform step uses the identity passthrough so the worked example (whose transform reshapes a series into a CSV) runs without a bespoke registry entry in v1.

func NewTransformRegistry

func NewTransformRegistry() *TransformRegistry

NewTransformRegistry returns the default registry. v1 registers the deterministic transforms the runtime needs; callers may extend it before a run. The registry never contains an LLM-backed transform: that is what the llm-seam kind is for.

func (*TransformRegistry) Register

func (r *TransformRegistry) Register(name string, t Transform)

Register adds a named transform. It overwrites an existing name so a host can supply its own deterministic transform for a plan.

type TrustContract

type TrustContract struct {
	CredentialKind string
	Hosts          []string
	Paths          []string
	Effect         Effect
	Idempotency    Idempotency
	Redaction      []RedactionRule
	IdentityLabel  string
	Audit          AuditStructure
}

TrustContract mirrors the manifest trust-contract fields the runtime enforces per action-call: effect routing, idempotency, redaction, audit. Credential/host/path are the declared access scope; the runtime grants nothing undeclared.

Jump to

Keyboard shortcuts

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