# Pipeline markers

Block-level annotations (`remote`, `dangerous`, `deprecated`) pair with op-level safety flags. They drive skip semantics, transitive propagation, and CLI drilling.

## Usage

```ops
remote @deploy needs [$VERSION $DATACENTER] {
  infra.deploy(datacenter: $DATACENTER, version: $VERSION)
}

dangerous @wipe-db {
  db.drop-all()
  db.seed()
}

remote dangerous @nuke-and-redeploy needs [$VERSION] {
  wipe-db()
  deploy(version: $VERSION)
}

deprecated @notify needs [$VERSION] {
  slack.post(channel: "#releases", message: `{$VERSION} deployed`)
}
```

**Skip propagation**

```ops
@pipeline needs [@remote-pipeline @dangerous-pipeline $VERSION] {
  log.info("I get run regardless of flags")

  if $VERSION {
    log.warning("Proceeding with the dangerous stuff!")
    if remote-pipeline() as remote-results {
      log.info(`I get skipped because I rely on {$remote-results.value}`)
    }

    if ! dangerous-pipeline() {
      log.info("I get skipped because I depend on dangerous-pipeline")
    }
  }

  log.info("Running this pipeline will always make it here regardless")
}
```

## Grammar

```
PipelineMarkers = ('remote' | 'dangerous' | 'deprecated')+
PipelineDecl    = PipelineMarkers? 'main'? '@' BlockName BlockMods? ImperBlock
ImperBlock      = '{' Statement* '}'
```

When markers appear, one or more keywords may be listed; order is not semantically significant beyond readability.

## Future IR representation

Pipeline declarations lower markers and dependency clauses into normalized decorators:

Canonical safety and graph-consumer details live in [dependency-and-safety](../ir/dependency-and-safety.md).

```json
{
  "role": "pipeline",
  "name": "nuke-and-redeploy",
  "payload": [
    { "role": "safety", "payload": "remote" },
    { "role": "safety", "payload": "dangerous" },
    { "role": "need", "payload": [{ "role": "var", "payload": "VERSION" }] },
    ...
  ]
}
```

The resolver merges this with op-level `--remote` / `--dangerous` / `--deprecated` flags from the dependency graph to compute effective safety and skip targets.

## Runtime

**Op modifier flags (intrinsic)**

| Flag | Meaning |
|------|---------|
| `--remote` | Op talks off-site (e.g. `infra.deploy`, `api.call`, `webhook.trigger`). |
| `--dangerous` | Op can cause irreversible damage (e.g. `db.drop-all`, `rm.recursive`). |
| `--deprecated` | Op slated for removal; phased-out legacy surface. |

**Propagation**

- Marker flags on ops propagate transitively: any DSL block that calls a `--remote` op is transitively remote; same for `--dangerous`.
- DSL pipeline markers are explicit annotations on blocks; op-level flags are intrinsic and propagate through the graph automatically.

**Skip vs block**

- `--remote` and `--dangerous` use the skip mechanism: without `--allow-remote` or `--allow-dangerous`, the affected op (or block satisfying the gate) resolves as `__SKIPPED__` — same idea as `--skip-env`.
- `--deprecated` does not skip by default; it emits a **compile warning at every call site**.

**Interaction with `wants` (settled)**

- `--skip-wants` and `--allow-remote` / `--allow-dangerous` are independent: each can produce `__SKIPPED__` on its own (**OR** logic), not a single combined gate.

## Implementation

| Tier | Role |
|------|------|
| **2** | Parse `PipelineMarkers`, `PipelineDecl`, and `ImperBlock` in the grammar. |
| **15** | Resolver validation: transitive remote/dangerous from ops + block markers; consistency with `needs` / drilling. |
| **19** | Skip emission: `__SKIPPED__` for gated remote/dangerous when safety flags are absent; wiring for dependent steps. |

## Design decisions

- **Composition:** A block may carry multiple markers (e.g. `remote dangerous @nuke-and-redeploy`).
- **`deprecated` vs `std.deprecated`:** `std.deprecated` annotates a single call site; `deprecated` on the block marks the pipeline as legacy end-to-end.
- **Skip propagation, not pipeline blocking:** A gated remote/dangerous callee is skipped; the parent pipeline continues. Steps that consume skipped results are skipped; steps that do not depend on those values run normally.
- **Transitive safety:** If `@ship` needs `@deploy` and `@deploy` is remote, the resolver treats `@deploy` as transitively remote for gating and help text.
- **Drilling:** `./ops pipeline` runs with holes where remote/dangerous deps would execute. `./ops pipeline --allow-remote` fills remote blocks. `./ops pipeline --allow-remote --allow-dangerous` runs everything that is not skipped for other reasons.

**`--help` output**

Flags and safety sections reflect markers and transitivity, e.g.:

```
Flags:
  --version <semver>      required by: deploy
  --datacenter <string>   required by: deploy (remote)

Safety:
  --allow-remote          enable remote operations (off by default)
  --allow-dangerous       enable dangerous operations (off by default)
  --skip-wants            skip soft dependencies
```

## Caveats

- Skipped blocks still affect control flow for anything that reads their outputs or treats them as conditions; authors must structure `if` / `as` bindings so optional remote/dangerous work is not assumed to have run.
- Transitive marking can surface remote/dangerous on a parent pipeline even when the author only tagged leaf ops — help and resolver output should make the chain obvious.
- `deprecated` warnings at compile time can be noisy for hot paths; migration should move call sites off deprecated blocks rather than relying on suppression.
