Skip to content
HCL schema

HCL schema reference

Every block and attribute Orchard’s parser accepts. The two authoring contexts are scenario files and component files.

Scenario files

A scenario file contains exactly one scenario block.

scenario

scenario "<name>" {
  description = "..."           # optional
  tags        = ["a", "b"]      # optional
  seed        = 42              # optional

  required_providers { ... }
  variable "<name>"  { ... }
  provider "<name>"  { ... }
  component "<type>" "<name>" { ... }
  action "<type>" "<name>"    { ... }
  output "<name>"    { ... }
}

A single HCL file may contain multiple scenario blocks. When it does, orchard run and orchard plan require --scenario <name> to pick one. Single-scenario files work without the flag.

AttributeTypeRequiredDescription
descriptionstringnoFree-text summary. Shown by orchard list.
tagslist(string)noCategorisation labels. Shown by orchard list in violet brackets.
seednumbernoSeed for random functions, a literal non-negative integer. --seed overrides it. Without either, each run picks a random seed. See random values and seeds.
BlockCardinalityPurpose
required_providers0..1Declare which providers the scenario uses.
variable0..nScenario inputs.
provider0..nProvider configurations.
component0..nInstances of reusable components.
action0..nExecutable units.
output0..nValues exposed to the caller.

required_providers

required_providers {
  postgres = { source = "builtin/postgres" }
  stripe   = { source = "exec:./bin/orchard-stripe" }
}

A flat attribute set, one entry per provider. Each entry is an object with:

AttributeTypeRequiredDescription
sourcestringyesProvider source: builtin/<name> or exec:<path>.

variable

variable "dsn" {
  type        = string
  default     = "postgres://localhost/dev"
  description = "Database connection string"
}
AttributeTypeRequiredDescription
typetype expressionnoDeclared type. Defaults to dynamic (any) if omitted.
defaultanynoDefault value. Used when no CLI override is given.
descriptionstringnoFree-text documentation.
sensitiveboolnoKeep the value out of the run record. Defaults to false.

Variables without a default must be supplied via --var at runtime.

A sensitive variable is stored in the run record as a hash of its value rather than the value, so a DSN or API key doesn’t end up in a file next to the scenario. orchard inspect shows it as (sensitive). To tear the run down, pass the value again with --var; teardown checks it against the hash. A sensitive variable with a default (default = env("DB_PASSWORD")) needs no --var if the default still gives the run’s value.

A component variable counts as sensitive, and is left out of the record too, when it is declared sensitive = true or its input is computed from a sensitive scenario variable (inputs = { dsn = var.dsn }, or "${var.dsn}&x=1", or upper(var.dsn)). orchard plan --show-values shows any attribute computed from a sensitive variable as (sensitive).

Wherever else the value turns up (an action’s outputs, such as a command echoing it; a typed insert’s undo step; a scenario output; an error message), orchard run and orchard teardown replace it with «sensitive:<name>» in what they print and record. Teardown puts the value back before using recorded outputs and undo steps, so a row inserted with the secret is still found and deleted. Matching is by value, so run refuses a sensitive string shorter than 4 characters: it would match unrelated text. Only strings are matched; a sensitive number or bool is kept out of the variables in the record but not out of outputs.

provider

provider "postgres" {
  dsn = var.dsn
}

The label selects which provider in required_providers is being configured. The body is provider-specific — see the providers reference.

Provider configuration is evaluated with variables in scope; it cannot reference action or component outputs.

component

component "merchant" "acme" {
  source = "../components/merchant.hcl"
  inputs = {
    name = "Acme Corp"
  }
}
LabelPurpose
FirstComponent type (must match component "<type>" { } in the source file).
SecondInstance name, unique within the scenario.
AttributeTypeRequiredDescription
sourcestringyesPath to the component file. Resolved relative to this scenario file.
inputsobject—Values for the component’s declared variables. Required if the component has any required variables.
count / for_eachnumber / collection—Create several instances. See Repetition.

action

action "postgres_query" "create_merchant" {
  query = "INSERT INTO ..."
}
LabelPurpose
FirstAction type (must match an action declared by some provider).
SecondAction name, unique within the scenario or component.

Body attributes are defined by the provider’s schema. In addition, Orchard reserves a few attributes for engine-level behavior:

AttributeTypeDescription
providerstringExplicit provider alias. Required only when two providers offer the same action type.
depends_onlist(reference)Explicit dependency edges. Each entry is a reference to another action or component node.
timeoutstringGo-style duration (e.g. "30s", "5m"). Wraps the provider’s Execute call.
countnumberCreate this many instances. See Repetition. Scenario-level actions only.
for_eachmap, object, or list/set of stringsOne instance per element. Scenario-level actions only.
retryblockRetry policy. See below.
lifecycleblockTeardown configuration. See below.

retry block

action "http_request" "flaky" {
  url = "https://example.com"
  retry {
    max_attempts = 5
    delay        = "1s"
    backoff      = "exponential"
    max_delay    = "30s"
  }
}
AttributeTypeDescription
max_attemptsnumberMaximum number of attempts including the first.
delaystringInitial delay between attempts, as a Go duration.
backoffstring"constant" or "exponential".
max_delaystringCap for exponential backoff.

lifecycle block

Declares a teardown operation for the action. Read by orchard teardown <record> when walking the graph in reverse.

action "postgres_query" "create_merchant" {
  query = "INSERT INTO merchants (name) VALUES ('${var.name}') RETURNING id, name"

  lifecycle {
    teardown {
      query = "DELETE FROM merchants WHERE id = ${self.id}"
    }
  }
}

The teardown block’s body uses the same schema as the outer action’s provider. A sql action’s teardown declares query; an http action’s declares method + url; an exec action’s declares command + args.

Inside the teardown body, three reference namespaces are in scope:

  • var.<name> — scenario variables (from the record). Inside a component, the component’s variables, as the run resolved them (also from the record).
  • self.<field> — this action’s own recorded outputs. Use it to target the resources this action created, without restating the node name.
  • action.<type>.<name>.<field> — any other action’s recorded outputs, for teardowns that reference upstream state. Inside a component, only the component’s own actions.

See lifecycle for authoring guidance and the self namespace under expressions.

output

output "merchant_id" {
  value = action.postgres_query.create_merchant.id
}
AttributeTypeRequiredDescription
valueany expressionyesExpression to evaluate after the scenario completes.

Component files

A component file contains exactly one component block.

component

component "merchant" {
  description = "..."           # optional
  tags        = ["seed"]        # optional

  variable "<name>" { ... }
  action "<type>" "<name>" { ... }
  output "<name>"   { ... }
}

The label is the component’s type. Component files may not declare required_providers or provider blocks — components inherit providers from the enclosing scenario.

AttributeTypeRequiredDescription
descriptionstringnoFree-text summary.
tagslist(string)noCategorisation labels.

Inside a component, variable, action, and output have the same schema as in a scenario.

Components reference their own variables as var.<name>, their own actions as action.<type>.<name>.<output>, and cannot reach scenario-level actions or components.

Evaluation order

  1. Parse — decode scenario and component files structurally. Action bodies and output expressions are captured but not evaluated.
  2. Resolve variables — apply defaults and CLI overrides.
  3. Evaluate provider configs — with variables in scope.
  4. Build graph — derive edges from expr.Variables() traversals plus depends_on lists.
  5. Topological sort, then execute each node once its dependencies have completed (up to orchard run --parallelism at a time):
    • For an action: evaluate its body, call provider Execute, record outputs.
    • For a component: resolve inputs, execute internal actions, resolve outputs.
  6. Evaluate scenario outputs with all node results in scope.