# Human-Gate Steps
{{< tier level="sovereign" note="Human gates keep final authority with the operator while the surrounding process remains automated." >}}

Use a `human_gate` when the workflow must stop for an explicit human decision. The run inspector presents the authored prompt and enables the decision only for an authorized operator.

## Configuration

```yaml
- key: approve_release
  name: Approve Release
  kind: human_gate
  needs: [prepare_release]
  humanGate:
    prompt: "Approve ${steps.prepare_release.outputs.releaseName}?"
    approvalText: Ship
    rejectionText: Hold
  waitsFor:
    kind: workflow_input
    expected: [approved, rejected]
    authorizedHandles: ["@release-operator"]
  outputs:
    outcome: "$.normalized.input.outcome"
```

| Field | Required | Purpose |
| --- | --- | --- |
| `humanGate.enabled` | No | Set `false` only when this version intentionally bypasses the gate |
| `humanGate.prompt` | No | Decision shown to the operator |
| `humanGate.approvalText` | No | Approval button label |
| `humanGate.rejectionText` | No | Rejection button label |
| `humanGate.references` | No | Up to eight labeled task, session, run, or URL links |
| `humanGate.preview` | No | One to five documents to review before deciding |
| `humanGate.feedback` | No | Writer session, task, and review that receive requested corrections; requires at least one preview document |
| `humanGate.pullRequestProposal` | No | Editable pull-request proposal backed by frozen branch evidence |
| `waitsFor.expected` | Yes | One or both of `approved`, `rejected` |
| `waitsFor.authorizedHandles` | Yes | Operators allowed to decide |
| `outputs` | No | Named decision for downstream routing |

When copy is omitted, Valdr uses the step name, **Approve**, and **Reject**.

{{< callout type="warning" >}}
`humanGate.enabled: false` intentionally bypasses the decision and records the step as approved without waiting for human input. The step must still include `approved` in `waitsFor.expected`. Use a new workflow version when changing this setting. A run that already froze an enabled gate remains waiting for its operator decision.
{{< /callout >}}

## Link supporting records

References give the operator direct context without changing the decision contract:

```yaml
humanGate:
  references:
    - kind: task
      target: "${workflow.inputs.taskKey}"
      label: Open task
    - kind: run
      target: "${runtime.runUlid}"
      label: Open run
```

Each reference needs `kind`, `target`, and `label`. Supported kinds are `task`, `session`, `run`, and `url`.

## Preview reviewed documents

A gate can present one to five documents before the operator decides:

```yaml
humanGate:
  prompt: "Approve the reviewed specification?"
  approvalText: Approve spec
  rejectionText: Reject
  preview:
    documents:
      - key: spec
        label: Specification
        path: "${steps.create_spec.outputs.path}"
        sourceSessionUlid: "${steps.create_spec.outputs.writerSessionUlid}"
```

Each document needs a unique key, a visible label, its path, and the session that created it.

## Send feedback before deciding

```yaml
humanGate:
  feedback:
    sourceSessionUlid: "${steps.create_spec.outputs.writerSessionUlid}"
    taskKey: "${workflow.inputs.taskKey}"
    reviewId: "${steps.create_spec.outputs.reviewId}"
```

Feedback returns a correction request to the configured writer session and refreshes the preview. It does **not** approve or reject the gate. The operator reviews the updated document, then makes the final decision.

The Builder exposes structured **Preview documents** and **Feedback** controls. Use its YAML dialog when you need to inspect or edit the full definition directly.

## Approve a pull-request proposal

A pull-request gate lets the operator edit the title, body, and draft state before approving publication. Bind it to frozen repository evidence:

```yaml
humanGate:
  prompt: Review and approve the pull request proposal.
  approvalText: Publish pull request
  rejectionText: Reject
  pullRequestProposal:
    repository: "${steps.observe_remote.outputs.repositoryOwner}/${steps.observe_remote.outputs.repositoryName}"
    remoteName: "${workflow.inputs.remoteName}"
    baseBranch: "${workflow.inputs.baseBranch}"
    headBranch: "${workflow.inputs.headBranch}"
    headSha: "${steps.resolve_head.outputs.resolvedSha}"
    writerSessionUlid: "${steps.draft_proposal.outputs.sessionUlid}"
    evidenceDigest: "${steps.read_diff.outputs.digest}"
    initial:
      title: "${steps.draft_proposal.outputs.title}"
      body: "${steps.draft_proposal.outputs.body}"
      draftFrom: "${workflow.inputs.initialDraft}"
```

`draftFrom` must be an exact reference; the referenced value must resolve to a Boolean at runtime. You may use a literal Boolean in `initial.draft` instead. Initial and edited titles must be one line and at most 256 characters; bodies must use LF line endings and be at most 65,536 bytes. Map `$.normalized.input.payload.title`, `.body`, and `.draft` as step outputs when the later [GitHub](../github/) step should publish the operator-approved values.

## Route the decision

```yaml
- key: release
  name: Release
  kind: tool
  needs: [approve_release]
  when:
    - kind: value_equals
      value: "${steps.approve_release.outputs.outcome}"
      equals: approved
```

A rejection is recorded as the `rejected` workflow value. Without a block or fail policy, route it to the appropriate follow-up step. Set `onFailure.action` to `block` or `fail` when rejection should stop the run at the gate.

## Next step

Continue with [Outcome route](../condition/) to route the decision explicitly.

