> ## Documentation Index
> Fetch the complete documentation index at: https://sdlc-rstack.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Approvals & Policy

> Human-in-the-loop gates, manager allow-lists, and the approval token.

No change ships without sign-off. RStack gates planning, requirements, and
architecture by default, and lets you enforce stricter, role-based policy.

## How approvals work

When a task reaches a gate, the run blocks and records an `approval_gate_blocked`
event. A human resolves it:

```text theme={null}
sdlc_approve(artifact="architecture.md", status="APPROVED")
```

The approver is recorded as the resolved identity (git config or `RSTACK_USER`)
— not a generic placeholder. Resolving from the Business Hub is also possible
(see [the token](#dashboard-approval-token) below).

## Enforce policy — `.rstack/policy.json`

Make selected stages require approval **in every mode**, including express runs:

```json theme={null}
{
  "required_approvals": {
    "008-release-readiness": ["release-readiness.json"]
  },
  "required_stage_approvals": {
    "07-code": ["architecture.md"]
  },
  "approvals": { "every_stage": true },
  "enforce_in_express": true,
  "managers": ["maya@acme.com", "lena@acme.com"]
}
```

* **`required_approvals`** — task id → artifacts that must be approved before
  that task can run. Enforced in interactive **and** express mode.
* **`required_stage_approvals`** — canonical stage id (`00-environment` …
  `14-cost-estimation`) → artifacts that must be approved before **any task
  entering that stage** can run. No task ids needed. Enforced in every mode.
* **`approvals.every_stage`** — blanket human gate: every task must have a
  `stage-approval:<stage-id>` artifact approved for each canonical stage it
  enters (e.g. `sdlc_approve(artifact="stage-approval:07-code", ...)`).
  Approving a stage once unblocks all tasks entering that stage for the rest
  of the run. Enforced in every mode.
* **`enforce_in_express`** — also apply the default interactive gates to
  express runs.
* **`managers`** — only these people may resolve approvals (see below).

## Manager allow-list

When a manager list is configured (via `policy.json` `managers[]` or the
`RSTACK_MANAGER_USERS` env var, comma-separated), only those identities can
resolve a gate. Anyone else is rejected. With no list configured, any
identified user may approve.

```bash theme={null}
export RSTACK_MANAGER_USERS="maya@acme.com,lena@acme.com"
```

## Dashboard approval token

Approving from the browser requires a signed token so a manager's identity
can't be spoofed from an unauthenticated request:

```bash theme={null}
export RSTACK_APPROVAL_TOKEN="a-long-random-secret"
```

* With the token set, the dashboard sends it as a header; approvals also
  require a same-origin request and `Content-Type: application/json`.
* **Without the token set, browser approvals are disabled** (the secure
  default) — approve via `sdlc_approve` instead.
* Every dashboard approval records audit-proof actor evidence, not just a name.

<Note>
  The CLI path (`sdlc_approve`) always enforces the manager allow-list. The
  token specifically protects the **dashboard** endpoint from spoofed requests.
</Note>

## Everyone gets paged when a gate blocks

A blocked gate fires a notification to every configured channel
([webhooks](/reference/webhooks)) and pops a browser notification in the hub —
so the manager doesn't have to be watching the dashboard to know work is
waiting.

## Email approval notifications

Register your team once and the **right person** gets an email the moment a
gate blocks — the manager for guardrail overrides, the team lead for code-stage
sign-offs, the release pair for `release-readiness.json`. Email is a
**notification layer only**: the claim gate stays the enforcement, and the
approval itself is always recorded through RStack's audited, token-verified
path — never from the email.

Sending uses **Azure Communication Services Email** (REST + HMAC access-key
signing, implemented with Node built-ins — no new dependencies).

**1. The secret stays in the environment** — never in any `.rstack/*.json`
file (credential-shaped keys there are a hard validation error):

```bash theme={null}
export RSTACK_ACS_CONNECTION_STRING="endpoint=https://<resource>.communication.azure.com/;accesskey=<key>"
```

**2. The committable half lives in `.rstack/notifications.json`** — sender
address, people, and routing:

```json theme={null}
{
  "channels": {
    "email": { "sender": "DoNotReply@<your-domain>.azurecomm.net" }
  },
  "recipients": {
    "manager":   { "name": "Priya", "email": "priya@acme.com" },
    "team_lead": { "name": "Sam",   "email": "sam@acme.com" },
    "developer": { "name": "Dee",   "email": "dee@acme.com" },
    "tester":    { "name": "Quinn", "email": "quinn@acme.com" },
    "cicd":      { "name": "Ops",   "email": "ops@acme.com" }
  },
  "routing": {
    "guardrail-override:*":     ["manager"],
    "destructive-action:*":     ["manager"],
    "stage-approval:07-code":   ["team_lead"],
    "08-testing":               ["tester"],
    "release-readiness.json":   ["manager", "cicd"]
  }
}
```

Role names are free-form. Routing patterns resolve in precedence order:
**exact artifact name** → **kind wildcard** (`guardrail-override:*`,
`stage-approval:*`, `destructive-action:*`) → **canonical stage id** of the
blocked task. An artifact with no route falls back to the people named in
`policy.json` `managers[]` (matched by role, name, or email); if nobody
matches, the block is logged as "no email recipients resolved". A route whose
roles don't exist in `recipients` is flagged at config-validation time —
*"this route resolves to nobody"* — so a typo can't silently mute approvals.

The channel activates only when **both** the environment connection string and
`channels.email.sender` are present (`rstack-agents doctor` reports exactly
which half is missing). Each resolved person gets their **own** email (To
only — recipient lists never leak) naming the run, task, blocked artifact, and
why, plus a deep link to the Business Hub approvals page
(`http://localhost:3008/?page=approvals`; port follows `RSTACK_BUSINESS_PORT`).

<Note>
  **Why a deep link instead of Approve/Reject buttons in the email:** Outlook
  HTML approval buttons do not reliably return the *approver's identity* —
  only the selected option — so an email-button approval could not carry the
  actor evidence RStack's audit trail requires. Microsoft's supported
  identity-capturing path (Logic Apps / Power Automate Approvals) lives inside
  M365, outside RStack's audited approval records. v1 therefore links you to
  the Business Hub approval card, where the existing token-verified path
  records who approved. Signed one-time approval links are a v2 candidate.
</Note>
