Configuration-as-Code for AI Policies
AI governance policies are infrastructure. They should live in Git, go through code review, and promote through environments the same way application code does. This guide covers structuring YAML policy configs, validating in CI, reviewing diffs, and promoting changes from dev through staging to production.
Use this page when
- You are structuring YAML policy configs in a Git repository with per-team, per-environment layout
- You need to set up CI validation with
kt policy lintand CODEOWNERS-based review - You want to define an environment promotion workflow (dev → staging → prod) for policy configs
Primary audience
- Primary: Technical Engineers
- Secondary: AI Agents, Technical Leaders
Policy Configuration Structure
Anatomy of a Policy Config
Every Keeptrusts gateway consumes a policy-config.yaml:
pack:
name: payments-team-prod
version: 0.1.0
enabled: true
description: "Payments team production gateway"
providers:
targets:
- id: openai-primary
provider: openai
model: gpt-4o-mini
base_url: https://api.openai.com
secret_key_ref:
store: OPENAI_API_KEY
- id: anthropic-fallback
provider: anthropic
provider_type: anthropic
format: anthropic
model: claude-sonnet-4-20250514
base_url: https://api.anthropic.com
secret_key_ref:
store: ANTHROPIC_API_KEY
policies:
chain:
- prompt-injection
- pii-detector
- spend-limit
- audit-logger
policy:
pii-detector:
action: redact
spend-limit:
max_daily_usd: 250
audit-logger:
retention_days: 365
Repository Layout
Organize configs by team and environment:
ai-policies/
├── README.md
├── templates/
│ ├── standard-guardrails.yaml
│ ├── healthcare-hipaa.yaml
│ └── finance-sox.yaml
├── teams/
│ ├── payments/
│ │ ├── dev/
│ │ │ └── policy-config.yaml
│ │ ├── staging/
│ │ │ └── policy-config.yaml
│ │ └── prod/
│ │ └── policy-config.yaml
│ └── data-science/
│ ├── dev/
│ │ └── policy-config.yaml
│ └── prod/
│ └── policy-config.yaml
└── shared/
├── blocked-patterns.yaml
└── approved-models.yaml
Validation with kt policy lint
Local Validation
Validate configs before committing:
# Validate a single config
kt policy lint --file teams/payments/prod/policy-config.yaml
# Validate all configs in the repository
find teams -name 'policy-config.yaml' -exec kt policy lint --file {} \;
Exit codes: 0 = valid, 2 = lint failure. The validator checks:
- YAML syntax
- Supported top-level document shape
- Valid policy and provider field names
- Provider configuration completeness
CI Validation Pipeline
# .github/workflows/policy-validate.yml
name: Validate AI Policies
on:
pull_request:
paths:
- 'teams/**'
- 'templates/**'
- 'shared/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install kt CLI
run: |
curl -fsSL https://get.keeptrusts.dev/cli | sh
echo "$HOME/.keeptrusts/bin" >> $GITHUB_PATH
- name: Validate all configs
run: |
errors=0
for config in $(find teams -name 'policy-config.yaml'); do
echo "::group::${config}"
if ! kt policy lint --file "${config}"; then
errors=$((errors + 1))
fi
echo "::endgroup::"
done
if [[ ${errors} -gt 0 ]]; then
echo "::error::${errors} config(s) failed validation"
exit 1
fi
- name: Validate templates
run: |
for tmpl in templates/*.yaml; do
kt policy lint --file "${tmpl}"
done
Diff-Based Review
Meaningful Diffs
YAML configs produce clean diffs. A policy change looks like:
policies:
- name: spend-limit
type: spend_limit
action: block
- max_daily_usd: 250
+ max_daily_usd: 500
+ - name: model-allowlist
+ type: input_filter
+ action: block
+ allowed_models:
+ - gpt-4o
+ - gpt-4o-mini
+ - claude-sonnet-4-20250514
CODEOWNERS for Policy Review
Enforce review by the governance team for production configs:
# .github/CODEOWNERS
teams/*/prod/ @org/governance-team @org/platform-team
teams/*/staging/ @org/platform-team
templates/ @org/governance-team
PR Review Checklist
Automate a review checklist on policy PRs:
# .github/workflows/policy-review.yml
name: Policy Review Checks
on:
pull_request:
paths:
- 'teams/**/prod/**'
jobs:
review-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Diff summary
run: |
echo "## Policy Changes" >> $GITHUB_STEP_SUMMARY
echo '```diff' >> $GITHUB_STEP_SUMMARY
git diff origin/main -- teams/*/prod/ >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
- name: Check for spend limit changes
run: |
if git diff origin/main -- teams/*/prod/ | grep -q 'max_daily_usd'; then
echo "::warning::Spend limit change detected — requires finance team approval"
fi
- name: Check for provider changes
run: |
if git diff origin/main -- teams/*/prod/ | grep -q 'providers:'; then
echo "::warning::Provider config change detected — requires security review"
fi
Environment Promotion
Promotion Workflow: Dev → Staging → Prod
# 1. Develop and test in dev
vi teams/payments/dev/policy-config.yaml
kt policy lint --file teams/payments/dev/policy-config.yaml
kt gateway run --listen 0.0.0.0:41002 --policy-config teams/payments/dev/policy-config.yaml
# 2. Promote to staging
cp teams/payments/dev/policy-config.yaml teams/payments/staging/policy-config.yaml
# Update gateway name
sed -i 's/payments-team-dev/payments-team-staging/' teams/payments/staging/policy-config.yaml
git add teams/payments/staging/ && git commit -m "Promote payments policy to staging"
# 3. After staging validation, promote to prod
cp teams/payments/staging/policy-config.yaml teams/payments/prod/policy-config.yaml
sed -i 's/payments-team-staging/payments-team-prod/' teams/payments/prod/policy-config.yaml
git add teams/payments/prod/ && git commit -m "Promote payments policy to prod"
Automated Promotion with CI
# .github/workflows/promote.yml
name: Promote Policy Config
on:
workflow_dispatch:
inputs:
team:
description: "Team name"
required: true
from_env:
description: "Source environment"
required: true
type: choice
options: [dev, staging]
to_env:
description: "Target environment"
required: true
type: choice
options: [staging, prod]
jobs:
promote:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Copy config
run: |
src="teams/${{ inputs.team }}/${{ inputs.from_env }}/policy-config.yaml"
dst="teams/${{ inputs.team }}/${{ inputs.to_env }}/policy-config.yaml"
cp "${src}" "${dst}"
# Update gateway name to match target environment
sed -i "s/${{ inputs.team }}-${{ inputs.from_env }}/${{ inputs.team }}-${{ inputs.to_env }}/" "${dst}"
- name: Validate promoted config
run: kt policy lint --file "teams/${{ inputs.team }}/${{ inputs.to_env }}/policy-config.yaml"
- name: Create PR
uses: peter-evans/create-pull-request@v6
with:
title: "Promote ${{ inputs.team }} policy: ${{ inputs.from_env }} → ${{ inputs.to_env }}"
branch: "promote/${{ inputs.team }}-${{ inputs.from_env }}-to-${{ inputs.to_env }}"
body: |
Automated policy promotion for **${{ inputs.team }}**.
Source: `${{ inputs.from_env }}` → Target: `${{ inputs.to_env }}`
Environment-Specific Config Variables
Use the API's config-variable system to handle environment-specific secrets without duplicating configs:
# Set provider keys per environment
curl -X PUT "${API_URL}/v1/config-variables/OPENAI_API_KEY" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d '{"value": "sk-dev-...", "scope": "dev"}'
curl -X PUT "${API_URL}/v1/config-variables/OPENAI_API_KEY" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d '{"value": "sk-prod-...", "scope": "prod"}'
The policy config references a config-variable-backed secret, for example:
secret_key_ref:
store: OPENAI_API_KEY
The gateway resolves the correct value at runtime. Secrets stay out of Git.
For AI systems
- Canonical terms: policy-config.yaml,
kt policy lint, config variables,secret_key_ref, environment promotion, CODEOWNERS, CI validation - CLI commands:
kt policy lint --file <path>,kt gateway run --listen <host:port> --policy-config <path> - API endpoints:
PUT /v1/config-variables/{name} - Config structure:
pack,providers.targets[],policies.chain,policy - Related pages: GitOps for AI Policy, Golden Paths, Secret Management
For engineers
- Organize configs as
teams/<name>/<env>/policy-config.yamlfor clean Git diffs and scoped CODEOWNERS - Run
kt policy lint --file <path>locally before committing; exit code0means valid - Add CI workflow that lints all changed
policy-config.yamlfiles on PR usingfind+ lint loop - Use provider config with block-form
secret_key_ref.storereferences so secrets stay outside YAML - Promote configs between environments by copying the file and updating the gateway name
- Validate: after merge, confirm the gateway auto-reloads by checking
/readyzor config version in console
For leaders
- Treating policies as code enforces change management discipline — every change is reviewed, versioned, and auditable
- CODEOWNERS ensures governance and security teams approve production policy changes
- Environment promotion (dev → staging → prod) reduces the blast radius of misconfigurations
- CI validation catches broken configs before they reach any environment
- Secrets stay out of Git entirely via config-variable resolution at runtime
Next steps
- Set up GitOps for AI Policy for automated sync from Git to running gateways
- Define Golden Paths with standardized template configs for new teams
- Configure Secret Management for provider API key storage