# Steps

## Usage

```ops
@deploy {
  bash.shellcheck() needs [lint test]
  git.bump() export VERSION
  git.config() as configure
  git.bump() needs [sync] export VERSION
  bash.coverage(
    threshold: 80
  )
}

@install-hooks needs [install.go-install.lefthook] {
  HELLO = bash {
    echo "Hello World!"
  }
  log.info($HELLO)
}
```

## Grammar

```
Step            = OpPath Invocation StepMods?
OpPath          = IDENT ('.' IDENT)*
StepMods        = (NeedsClause | ExportClause | AsClause)*
ExportClause    = 'export' ENV_KEY
AsClause        = 'as' IDENT
TypedAssign     = '@' DslType ENV_KEY '=' Step
ExportAssign    = ENV_KEY '=' Step
```

(`NeedsClause` / `NeedRef` are shared with imperative blocks; see [imperative-blocks.md](./imperative-blocks.md).)

## IR Representation

Canonical step shape lives in [pipelines-and-steps](../ir/pipelines-and-steps.md).

Bare step (string):

```json
"qualified.op.name"
```

Step with modifiers:

```json
{
  "role": "call",
  "payload": [
    { "role": "target", "payload": "qualified.op.name" },
    { "role": "args", "payload": [
      { "role": "positional", "payload": "arg1" },
      { "role": "positional", "payload": "arg2" },
      { "role": "named", "name": "threshold", "payload": 80 }
    ]},
    { "role": "need", "payload": [
      { "role": "pipeline", "payload": "dep1" },
      { "role": "var", "payload": "VAR_NAME" }
    ]},
    { "role": "export", "payload": "VERSION" },
    { "role": "capture", "payload": "my_step" }
  ]
}
```

## Types

Optional DSL type annotation on assignment: `TypedAssign` allows `('@' DslType)? ENV_KEY '=' Step`. `ExportAssign` is untyped `ENV_KEY '=' Step`. Exact type grammar and checking live with the broader type system docs.

## Runtime

- `@out.type field value` — validates and accumulates a named field into the output buffer. Does not return.
- `@result.get [path]` — reads a field from the result buffer. `@result` (no `.get`) returns the full buffer.
- `@return` — returns void (no output).
- `@return.out` — returns the full output buffer (no subshell fork).
- `@throw message` — clears result buffer, sets error context, exits non-zero. Caught by `try`/`catch`.
- `@export` — produces a cross-group value. Writes to `__OPS_EXPORTS[VAR]`, or to a FIFO in parallel branches.

The runtime captures the return value (delivered via `@return.out` or an explicit `@return "$(@result.get path)"`) into `__OPS_OUTPUTS[step]` on success. `as` captures read from there. On failure (`@throw`), the error context routes to catch handlers.

## Implementation

### Build tier (Tier 3 — step modifiers)

- `token.rs`: keyword recognition for `needs`, `export`, `as`.
- `ast.rs`: `Step` gains `needs`, `export`, `name`.
- `parser.rs`: after `)`, parse modifier chain. Multi-line: keep reading while parens are unbalanced.
- `ir.rs`: `StepIr` — `Bare(String)` | `Full { call, decorators, bindings, ... }`.
- `lower.rs`: modifiers → object form (bare string when unmodified). `$VAR` in `needs` → `{"var": "VAR"}` inside a dependency decorator.

### Behavioral reference

- Step tokenizer (`__compile__tokenize_step`): strips `"` from value; `[](){}` → separate tokens; space/tab splits.
- Reserved keys skipped in arg formatting: `needs`, `postinstall`, `export`, `if`, `then`, `elif`, `else`, `var`, `equals`, `name`, `try`, `catch`, `finally`, `with`, `do`, `parallel_all`, `parallel_any`, `each`, `as`, `in`, `op`, and any `_`-prefixed key.

### Validation

Bats tests for each modifier, combined modifiers, and multiline invocations.

## Design Decisions

- **`as` captures** are immutable — duplicate `as name` in the same scope is a compile error. Block-scoped: captures inside `if` / `try` / `match` / `with` do not survive their enclosing block.
- **`export`** is cross-group value sharing via `needs [$VAR]`.
- **Inline Bash:** `bash { ... }` is an imperative-block-style op for inline shell. Stdout is the return value when assigned: `OK = bash { command }`. Non-zero exit is a failure. Unlike `.op.sh` files, `echo` and `return` are allowed in inline bash — it's the escape hatch where normal bash semantics apply. `@out.type`, `@return.out`, `@throw`, and type constructors are not available in inline bash blocks.

### Data flow layers

- **env** — file surface, `$UPPER_CASE`
- **export** — cross-group, `$UPPER_CASE` via `needs`
- **as** — within group, `$lower.key.path`

## Caveats

- Modifier order and interaction with control-flow blocks must stay consistent with the scoping rules above.

## Open questions

- **Parallel `as` captures** (settled): disallowed outside the `parallel` call. Captures are only valid on individual steps, not on the parallel group itself.
