Deployment Approval Gates
Deployment gates are decision points in the pipeline that pause the deployment until a condition is met or manually resolved. Use gates to enforce approvals, cost reviews, security checks, scheduled maintenance windows, and post-deployment verification.
Overview
Gates are declared in the environment configuration (spec.gates) and are evaluated at specific phases during strata deploy run:
Pre-plan gates (
approval,scheduled) — block before Terraform planPost-plan gates (
cost_review,security_review) — block after plan, with cost and CVE data availablePost-apply gates (
verify,cab,incident) — block after infrastructure is deployed
When a gate blocks, strata deploy run:
Creates a work item (stored locally or in a backend: S3, Azure Blob, GCS, etc.)
Pauses the deployment
Prints the work item ID and resume command
Exits with code 5 (
hand-off required)
To resume, the operator approves/rejects the work item, then runs strata deploy run --resume <work-item-id>.
Declaring Gates
Gates are defined in the environment YAML under spec.gates:
apiVersion: strata.huybrechts.xyz/v1
kind: environment
meta:
name: production
spec:
gates:
# Human approval for all production deploys (pre-plan)
- type: approval
when: always
approvers: [ops-team]
timeout_minutes: 60
# Cost review when monthly delta exceeds $1,000 (post-plan)
- type: cost_review
when:
cost_delta_monthly: ">= 1000"
approvers: [finance]
timeout_minutes: 240
# Security team review when critical CVEs found (post-plan)
- type: security_review
when:
cve_critical: ">= 1"
approvers: [security-team]
timeout_minutes: 480
# Only allow deploys during maintenance window (pre-plan, auto-resolves)
- type: scheduled
when:
time_utc: "02:00-04:00"
auto_resolve: true
# Manual verification after deploy applied (post-apply)
- type: verify
when: always
timeout_minutes: 30
description: "Verify application health and smoke tests pass in prod"
Gate Types
Type |
Phase |
Trigger |
Resolved by |
Use case |
|---|---|---|---|---|
|
pre-plan |
always or condition |
Approver group |
Require human sign-off before plan |
|
post-plan |
cost delta exceeds threshold |
Finance / lead |
Cost control and governance |
|
post-plan |
CVE count exceeds threshold |
Security team |
Vulnerability gate |
|
pre-plan |
outside time window |
Clock / auto or manual |
Maintenance windows, trading halts |
|
post-apply |
always or condition |
Operator |
Smoke tests, health checks |
|
pre-plan |
conditional |
CAB meeting |
Change Advisory Board approval |
|
any |
conditional |
On-call engineer |
Incident investigation pause |
Conditions
Gates evaluate conditions at evaluation time. Available conditions:
Condition |
Phase |
Data source |
Example |
|---|---|---|---|
|
post-plan |
|
|
|
post-plan |
|
|
|
post-plan |
|
|
|
post-plan |
|
|
|
pre-plan |
system clock |
|
Operators: >, >=, <, <=, ==, !=
Workflow: Deploy → Pause → Approve → Resume
Step 1: Initial Deploy (may pause)
$ strata deploy run -f deploy/production.yaml
[*] Pre-plan gates: evaluating...
[*] approval gate: blocked (requires ops-team approval)
⏸️ Deployment paused — work item created:
ID: approval/deploy-prd-abc1234d-20260727T1430
Expires: 2026-07-27 15:30:00 UTC
Resolve: strata workitem approve 'approval/deploy-prd-abc1234d-20260727T1430'
Resume: strata deploy run -f deploy/production.yaml --resume 'approval/deploy-prd-abc1234d-20260727T1430'
$ echo $?
5
Exit code 5 tells CI/CD: “hand-off required — waiting for external decision.”
Step 2: Approve (or Reject)
# Approve the gate
$ strata workitem approve 'approval/deploy-prd-abc1234d-20260727T1430' \
--note "Approved by on-call engineer"
✅ Approved: approval/deploy-prd-abc1234d-20260727T1430
Resolved by: github.com/alice
Note: Approved by on-call engineer
Resume deploy with: strata deploy run -f deploy/production.yaml --resume 'approval/deploy-prd-abc1234d-20260727T1430'
# Or reject it
$ strata workitem reject 'approval/deploy-prd-abc1234d-20260727T1430' \
--reason "Insufficient testing coverage — blocked by 3 failing tests"
❌ Rejected: approval/deploy-prd-abc1234d-20260727T1430
Resolved by: github.com/bob
Reason: Insufficient testing coverage — blocked by 3 failing tests
Step 3: Resume (if approved)
$ strata deploy run -f deploy/production.yaml \
--resume 'approval/deploy-prd-abc1234d-20260727T1430'
[*] Verifying work item: approval/deploy-prd-abc1234d-20260727T1430
[+] Gate cleared (approved by github.com/alice at 2026-07-27 14:32:00 UTC)
[*] Pre-plan gates: resumed (approval already resolved)
[*] Running Terraform plan...
...
The deployment continues from where it paused.
CI/CD Integration
The standard pattern for unattended CI pipelines:
#!/bin/bash
set -e
# Step 1: Deploy (may pause at gate)
strata deploy run -f deploy/production.yaml
EXIT_CODE=$?
if [ $EXIT_CODE -eq 5 ]; then
echo "Gate pending — deployment paused (hand-off required)"
# Trigger an external approval workflow (Slack, PagerDuty, email, etc.)
# Approver uses strata workitem approve/reject
# Then CI re-runs this script with --resume flag
exit 0 # Don't fail the pipeline; wait for human decision
fi
if [ $EXIT_CODE -eq 0 ]; then
echo "Deployment succeeded"
exit 0
else
echo "Deployment failed"
exit 1
fi
When the human approves (via strata workitem approve), a separate CI job resumes:
#!/bin/bash
# Called after approval is received
WORK_ITEM_ID="approval/deploy-prd-abc1234d-20260727T1430"
strata deploy run -f deploy/production.yaml --resume "$WORK_ITEM_ID"
Backend Storage
Work items are stored in a pluggable backend. Select with STRATA_WORKITEM_BACKEND:
# Local file system (default — good for testing)
strata deploy run -f deploy/production.yaml
# AWS S3 (shared across all CI agents)
STRATA_WORKITEM_BACKEND=s3 strata deploy run -f deploy/production.yaml
# Azure Blob Storage
STRATA_WORKITEM_BACKEND=azblob strata deploy run -f deploy/production.yaml
# GCP Cloud Storage
STRATA_WORKITEM_BACKEND=gcs strata deploy run -f deploy/production.yaml
For CI environments, use a shared backend (S3, Blob, GCS) so all CI agents can read/write the same work items.
Security Considerations
Commit Mismatch Protection
When resuming with --resume <work-item-id>, strata verifies that the current commit matches the commit that created the work item. This prevents replay attacks where an old approval is used to deploy a different code version.
# Original deploy
$ git log --oneline -1
abc1234d Add feature X
$ strata deploy run -f deploy/production.yaml
# → creates work item with commit abc1234d
# Two hours later, another commit is pushed
$ git log --oneline -1
def5678e Add feature Y
$ strata deploy run -f deploy/production.yaml --resume approval/...
# ❌ REJECTED: Work item was created for commit abc1234d, but current commit is def5678e
Asserted Identity (--as)
Approve or reject as a specific identity:
strata workitem approve <id> --as "automation-service"
# Tagged in audit log as "automation-service [asserted]"
Use for automated approvals (e.g., “auto-approve if tests pass”). The [asserted] tag marks that the approver was asserted by code, not a direct human action.
Audit Trail
All work-item events are logged:
strata audit list --filter "workitem"
See docs/guides/siem-audit-forwarding.md for SIEM integration (Sentinel, Splunk, ELK).
Examples
Production Deployment with Triple Gate
spec:
gates:
# 1. Pre-plan: Human approval
- type: approval
when: always
approvers: [ops-lead]
timeout_minutes: 30
# 2. Post-plan: Cost review (if significant delta)
- type: cost_review
when:
cost_delta_monthly: ">= 5000"
approvers: [cfo, vp-engineering]
timeout_minutes: 240
# 3. Post-apply: Smoke test verification
- type: verify
when: always
timeout_minutes: 15
description: "Run prod smoke tests; confirm no alerts spike"
Deployment pauses at each gate:
Ops lead approves the change
(If cost > $5k) Finance reviews
(After apply) Ops runs smoke tests and confirms
Maintenance Window Only
spec:
gates:
- type: scheduled
when:
time_utc: "22:00-06:00" # Only between 10 PM and 6 AM UTC
auto_resolve: true # Auto-resume outside window? Or block?
If auto_resolve: true, deployment auto-starts within the window. If false, operator must manually approve even during the window.
Security-Gated Deploys
spec:
gates:
# Block if any critical CVEs
- type: security_review
when:
cve_critical: ">= 1"
approvers: [security-team]
timeout_minutes: 480
description: "Critical CVE(s) detected — security team review required"
# Block if AI risk is moderate or higher
- type: security_review
when:
ai_risk: ">= medium"
approvers: [security-team]
timeout_minutes: 240
description: "Elevated AI risk score — security review required"
Troubleshooting
Q: The gate is blocking but I think it shouldn’t. Why?
Check the gate condition:
strata workitem show <work-item-id>
# See: context → reason, cost_delta_monthly, cve_critical, ai_risk, etc.
If the condition is wrong (e.g., evaluates cost_delta_monthly: ">= hgh" due to a typo), check the environment YAML.
Q: I lost the work item ID. How do I find it?
strata workitem list --status pending --deployment production
Lists all pending items for that deployment.
Q: Can I approve/reject from the VS Code extension?
Yes. Open the Pending Work panel (strata.workItems view); inline ✅ / ❌ buttons appear on each item.
Q: What if the work item expires?
After timeout_minutes, the work item auto-expires:
strata workitem expire
Expired items can still be approved/rejected, but the deployment won’t resume automatically — you must manually run strata deploy run --resume <id>.
See Also
docs/help/workitem— CLI command referencedocs/guides/siem-audit-forwarding.md— audit log and SIEM integrationdocs/platform/exit-codes.md— exit code 5 referenceADR-0057 — deployment workflow orchestration design