# bumpkit.yml Reference

Release rules that live in people's heads aren't applied the same way twice; bumpkit.yml writes them down as policy, for whoever owns how a repository ships.

`bumpkit.yml` is the policy file for your repository. The [Release Gate](/docs/release-gate) reads its `gate:` section to decide whether a release passes, needs approval or fails. Other top-level sections configure [Agent Controls](/docs/agent-controls), [Code Quality & Architecture](/docs/quality) and behavioral verification.

This page lists every key. Feature guides show the snippets you need and link back here.

## Example

The **Run a check** tab on the dashboard's **Checks** page has a YAML editor and an expandable guide to the main policy fields. This is an editor for the actual policy, not a second rule format: indentation, YAML structure and rule names are validated when you run the check. Editing the trial policy does not save a repository policy or change the base branch.

This file uses every key. You won't need most of them: start from the [default policy](#default-policy) and add what your team asks for. Keys marked `# Beta` are covered by their own Beta sections below.

```yaml
gate:
  block_on: [secret, data_loss]
  on_error: fail

  require_approval:
    - breaking
    - area: [billing, authentication]
      when: agent
      approvers: ["@acme/payments", alice]
      count: 2
    - untested: true
      when: agent_or_unknown
      codeowners: true
    - contract_breaking            # Beta
    - intent                       # Beta
    - agent_track_record_below: 0.8  # Beta
      min_releases: 3
      work_types: [migrations]

  require_checks: [test, lint]
  security_alerts: high
  vulnerabilities: high
  break_glass: true

  hotfix:
    block_on: [breaking, data_loss, migration]
    max_files: 20
    review_within: 72h
    backports: approve

  blast_radius:                    # Beta
    approve_areas: [billing, "@acme/payments"]
    max_depth: 4

  deploy:                          # Beta
    environments: [production]
    require: approved

  timezone: America/New_York          # Beta
  windows:                         # Beta
    - days: [mon-thu]
      from: "09:00"
      to: "16:00"
  outside_windows: approve         # Beta
  freezes:                         # Beta
    - name: Year-end freeze
      start: 2026-12-20
      end: 2027-01-02
      mode: block
      hotfix: approve

  exports:                         # Beta
    severity: high
    ignore_paths: [generated/**]
    max_findings: 100
  behavior:                        # Beta
    changes: approve
    hotspots: approve
    severity: medium
    hotspots_min: medium

  quality:                         # Beta
    architecture_drift: block
    code_quality:
      action: approve
      severity: medium
      when: agent
    performance: warn
    max_function_lines: 80
    max_complexity: 15
    clone_tokens: 40
    hot_path_depth: 4
    bench_threshold: 5

  cross_repo:                      # Beta
    block_on_consumer_break: false
    require_approval: true

  fix_tasks: block                 # Beta

  require_regression_evidence: true  # Beta
  regression_evidence_action: block  # Beta

  stacks:                          # Beta
    require_parent_merged: false

architecture:                      # Beta
  layers:
    - name: api
      paths: [services/api]
    - name: store
      paths: [services/db]
  forbidden:
    - from: store
      to: api

agents:                            # Beta
  claude-*:
    max_open_prs: 2
    max_changed_lines: 400
    paths_allow: [services/notify/**]
    paths_deny: [services/billing/**]
    areas_deny: [billing]
    spend_limit: 10
    on_violation: block

agent:                             # Beta
  ask: [rm_rf, "npm publish *"]
  deny: [force_push]
  allow: ["rm -rf ./dist*"]
  protected_branches: [main, "release/*"]
  remote: true
  timeout: 30s

memory:                            # Beta
  require_approval: true

verify:                            # Beta
  test: go test ./...
  timeout: 10m
  network: localhost
  http:
    start: ./bin/server
    ready: /healthz
  require_ci_attestation: true

train:                             # Beta
  label: "train:{slug}"
  require_approval:
    teams: ["@acme/release-managers"]
    count: 1
```

## File name and location

Put the file at the root of your repository. Bumpkit looks for `bumpkit.yml` first and then `.bumpkit.yml`, and uses the first one it finds. If both exist, `.bumpkit.yml` is ignored.

All keys are lowercase with underscores. The gate rejects keys it doesn't know at the top level and under `gate:`, `agent:`, `agents:`, `memory:` and `architecture:`, so a typo such as `requre_approval` is an error, not a silently ignored line. Keys under `train:` and `verify:` are checked by their own features when they read the file, not by the gate.

## Policy from the base commit

The gate reads `bumpkit.yml` as it is at the **base** of the change, never from the pull request itself. A pull request that edits `bumpkit.yml` can't loosen the rules it's judged by; its new policy applies to the pull requests after it merges. With the GitHub App, a pull request into any branch other than the default branch uses the default branch's copy, so a branch can't loosen the rules either.

| Where the gate runs | Policy it reads |
|---|---|
| Bumpkit GitHub App | `bumpkit.yml` at the base commit of a pull request into the default branch, and on the default branch for every other pull request |
| `bumpkit gate` (CLI, locally or in CI) | `bumpkit.yml` at `--base`, which defaults to the pull request's base commit, else the latest tag before `HEAD^`. `--policy FILE` reads a file instead |
| `POST /v1/gate` | The `policy` text you send in the request body |
| Deployment protection ([deploy](#deploy)) | `bumpkit.yml` on the default branch, with your org policy applied |
| A [GitHub Enterprise Server connection](/docs/release-gate#github-enterprise-server), through its webhook, `bumpkit gate` or `POST /v1/gate` | `bumpkit.yml` from your server: on the default branch, or at the base commit of a pull request into the default branch |
| `bumpkit gate` or `POST /v1/gate` with the GitHub App installed on the repository for your workspace | Bumpkit applies the `bumpkit.yml` it reads from GitHub itself, not the policy that was sent: on the default branch, or at the base commit of a pull request into the default branch. `--base` never picks the policy. If GitHub can't return it, `503 POLICY_UNAVAILABLE` |

The decision's `policy_source` says which applied: `base_branch:<ref>` (like `base_branch:main`) or `client`. A `client` policy carries a `policy_source: client` reason, because it wasn't verified from the base branch. See [The policy comes from the base](/docs/release-gate#the-policy-comes-from-the-base).

See the [CLI Reference](/docs/cli) and [API Reference](/docs/api-reference) for the flags and request fields.

## Default policy

If the repository has no `bumpkit.yml`, the file is empty, or the file has no `gate:` section, the gate applies this policy:

```yaml
gate:
  block_on: [secret, data_loss]
  require_approval:
    - breaking
    - area: [authentication, authorization, billing, cryptography]
      when: agent
    - untested: true
      when: agent
  vulnerabilities: high
  on_error: fail
```

Once you write a `gate:` section, it replaces the default as a whole: `block_on` and `require_approval` you leave out are empty, and `vulnerabilities` you leave out is off, not inherited from the default. Copy the default rules you want to keep. Keys that have their own default keep it when you leave them out: `on_error` is `fail`, `break_glass` is `true`, `hotfix.block_on` is `[breaking, contract_breaking, data_loss, migration]`, `hotfix.backports` is `approve`, `hotfix.review_within` is `72h` and, with Close the loop, `fix_tasks` is `block`.

## Invalid policy

An invalid `bumpkit.yml` never falls back to the default. The gate fails closed and shows the parse error with its line number:

- The GitHub App completes the check as a failure titled "Invalid bumpkit.yml". If it can't read the file at all, the check fails as well.
- `bumpkit gate` prints the error and exits with code `2`, even when `on_error: pass`.
- `POST /v1/gate` returns `400` with the code `INVALID_POLICY`.
- Deployment protection rejects the deployment.

Errors name the key, for example `bumpkit.yml: gate.hotfix.backports must be block, approve or off`. To try an edited file before you merge it, run `bumpkit gate --policy bumpkit.yml`, which uses that file instead of the base copy. See the [CLI Reference](/docs/cli).

## gate

The `gate:` mapping holds the release gate policy. Every key in the sections below, up to [architecture](#architecture), lives under `gate:`.

A gate decision is `pass`, `needs_approval` or `fail`. Only proven findings count: `block_on`, `require_approval` and `hotfix.block_on` apply to findings that are deterministic and not suppressed. Blast radius and quality findings are built by Bumpkit's own checkers, so they always count as deterministic. See [Release Checks & Blast Radius](/docs/release-checks) for the finding kinds.

## block_on

Finding kinds that fail the release outright. A finding fails the gate when it is high severity, deterministic and not suppressed.

| | |
|---|---|
| Type | list of strings |
| Default | `[secret, data_loss, security_regression]` with no `gate:` section; empty otherwise |
| Allowed values | `secret`, `data_loss`, `security_regression`, `breaking`, `contract_breaking` (Beta), `version_mismatch` (Beta), `compatibility_break` (Beta) |

```yaml
gate:
  block_on: [secret, data_loss, breaking]
```

`security_regression` comes from the [security regression check](/docs/release-checks#security-regressions). `contract_breaking` comes from [Contract Checks](/docs/contract-checks). `version_mismatch` comes from the version consistency check, which compares the decided version with your tags, package manifests, `VERSION` file and `CHANGELOG` headings. `compatibility_break` comes from the [compatibility matrix](/docs/contract-checks#compatibility-matrix): a proven break of a pair you declared as supported.

## require_approval

Rules that ask a person to approve a release. Each rule that applies and is triggered needs approval; until it has enough, the decision is `needs_approval`.

| | |
|---|---|
| Type | list of rules; each rule is a string or a mapping |
| Default | The three rules in the [default policy](#default-policy) with no `gate:` section; empty otherwise |

A rule written as a string uses `when: always`:

| String | Triggered by |
|---|---|
| `breaking` | A high-severity breaking change (a removed or changed public API or route), or a major version bump |
| `untested` | An untested-change finding |
| `contract_breaking` | A high-severity breaking API contract change (Beta) |
| `intent` | An acceptance criterion with no evidence (Beta) |
| `scanner_finding` | A CodeQL, Semgrep, Snyk or Socket finding on a line the change touches or in code it reaches |
| `ai_review_incomplete` | An AI review that was partial or didn't run |

The mapping form takes these keys. A rule needs at least one of `breaking`, `untested`, `area`, `contract_breaking`, `intent`, `scanner_finding`, `ai_review_incomplete` or `agent_track_record_below`.

| Key | Type | Default | Meaning |
|---|---|---|---|
| `breaking` | bool | `false` | Trigger on breaking changes and major bumps. Also triggers on `contract_breaking` findings |
| `untested` | bool | `false` | Trigger on untested changes |
| `area` | list of strings | none | Trigger on sensitive-area findings for these areas |
| `when` | string | `always` | Who wrote the change: `always`, `agent` or `agent_or_unknown` (both cover any change not confirmed as written by a person) |
| `approvers` | list of strings | any reviewer | Who may approve: GitHub logins and teams |
| `count` | int | `1` | Approvals needed, `1` to `10` |
| `codeowners` | bool | `false` | Code owners of the triggering files may approve |
| `contract_breaking` | bool | `false` | Trigger on breaking contract changes (Beta) |
| `intent` | bool | `false` | Trigger on unevidenced acceptance criteria (Beta) |
| `scanner_finding` | bool | `false` | Trigger on scanner findings the change touches or reaches |
| `ai_review_incomplete` | bool | `false` | Trigger when the AI review was partial or didn't run |
| `agent_track_record_below` | number | none | Trigger when an agent on the change has a clean share below this, `0` to `1` (Beta) |
| `min_releases` | int | `3` | With `agent_track_record_below`: ignore agents with fewer releases than this |
| `work_types` | list of strings | all work | With `agent_track_record_below`: only these work types, from `tests`, `migrations`, `auth`, `api`, `ui` |

```yaml
gate:
  require_approval:
    - breaking
    - area: [billing]
      when: agent
      approvers: ["@acme/payments"]
      count: 2
    - untested: true
      codeowners: true
```

### require_approval.area

The sensitive areas the release check reports: `authentication`, `authorization`, `billing` and `cryptography`. A rule triggers when the check finds a change in one of the listed areas. Names you list here also become named areas for [blast_radius](#blast-radius).

A change to `bumpkit.yml`, `.bumpkit.yml` or a CODEOWNERS file that targets the default branch needs one approval, with the built-in rule `policy_change`. No setting turns it off, and because the gate reads the policy from the default branch, a pull request can't remove it for itself.

### require_approval.when

| Value | The rule applies when |
|---|---|
| `always` | Always |
| `agent` | The change isn't confirmed as written by a person: an agent signal, inferred included, or no signal at all. A blocking quality rule fails only on a declared or verified agent, and asks for an approval otherwise |
| `agent_or_unknown` | The same as `agent` for approval rules |

A change counts as written by a person only with no agent signal, hooks or telemetry on for the repository, and at least 90% of such changes measured as human across at least 20 verified releases. Without hooks or telemetry, every change that triggers one of these rules needs an approval.

Bumpkit detects coding agents with no setup. It recognizes `claude` (also `anthropic`), `codex` (also `openai`), `cursor`, `copilot`, `devin`, `jules`, `amp` (also `ampcode`), `gemini`, `windsurf` (also `codeium`), `cline` and `aider`. See [Who wrote the change](/docs/release-gate#who-wrote-the-change).

### require_approval.approvers

GitHub logins (`alice` or `@alice`) and teams (`@org/team`). Only approvals from people on the list count toward `count`. Team membership can only be checked through the Bumpkit GitHub App or a repository host connection; elsewhere the decision says so and the team's approvals don't count.

Approvals count only when they are on the head commit. The author's own approval, bot approvals and approvals from agent accounts never count. See [Approvals](/docs/approvals) for Slack and dashboard approvals.

### require_approval.count

How many qualifying approvals the rule needs, from `1` to `10`. Leave it out for one approval.

### require_approval.codeowners

With `codeowners: true`, the code owners of the files that triggered the rule may approve, in addition to anyone in `approvers`. Bumpkit reads `.github/CODEOWNERS`, `CODEOWNERS` or `docs/CODEOWNERS` from the same place as the policy, and the last matching pattern wins. If no entry covers the files and `approvers` is empty, any reviewer counts. CODEOWNERS can only be checked through the Bumpkit GitHub App or a repository host connection.

### require_approval.contract_breaking

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `contract_checks`.

Triggers on high-severity breaking changes to OpenAPI and Swagger specs, GraphQL schemas, protobuf files and JSON Schema payloads. Write it as the string `contract_breaking` or as `contract_breaking: true` in a mapping. See [Contract Checks](/docs/contract-checks).

### require_approval.agent_track_record_below

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `agent_track_records`.

Asks a person to approve when an agent on the change has a poor record. Bumpkit counts each agent's releases over the last 90 days, overall and by work type, and the share that stayed clean (not reverted, not fixed up within 7 days, not blocked by the gate, no failed required checks). Put it in a rule of its own:

```yaml
gate:
  require_approval:
    - agent_track_record_below: 0.8
      min_releases: 1
      work_types: [migrations]
```

With `work_types`, the rule applies only when the change touches those work types; without it, the agent's overall record counts. Agents with fewer than `min_releases` releases (default `3`) are skipped. The reason states the numbers, for example "claude's overall record is 0% clean over 1 release in 90 days (1 reverted, 0 fixed up, 0 blocked by the gate, 0 failed required checks); the policy asks a person to approve below 80%." `approvers` and `count` work as in other rules. See `bumpkit agents records`.

### require_approval.intent

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `intent_check`.

Triggers when an acceptance criterion from the pull request, its linked issues or specs has no evidence in the changed files, tests or behavioral results. The intent rule only asks for approval; it never passes or fails a release on its own.

### require_approval.scanner_finding

Triggers when a scanner result you send with `--sarif` or `--socket` lands on a line the change touches or in a file it reaches. Scanner findings never block on their own; this rule only asks for approval. See [Scanner results](/docs/release-gate#scanner-results).

### require_approval.ai_review_incomplete

Off by default. Triggers when a check's AI review is `partly_reviewed` or `could_not_review`, including when no AI provider is set up, the check ran in fast mode, or the model refused. A generated or binary file in the change counts as not reviewed, so this rule triggers on those too. It only asks for approval; an AI review never blocks a release. With `codeowners: true`, the owners of the files not reviewed may approve. See [AI review coverage](/docs/release-checks#ai-review-coverage).

## on_error

What the gate does when Bumpkit can't decide, for example when the API is unreachable or a required provider is unavailable.

| | |
|---|---|
| Type | string |
| Default | `fail` |
| Allowed values | `fail`, `pass` |

`pass` makes the gate advisory: an error lets the release through. An invalid `bumpkit.yml` fails even with `on_error: pass`, and so does any error on a pull request whose policy sets [`require_check_apps`](#require-check-apps).

## require_checks

CI checks that must pass on the head commit before the gate decides.

| | |
|---|---|
| Type | list of strings |
| Default | none |
| Allowed values | check run or commit status names, matched case-insensitively |

```yaml
gate:
  require_checks: [test, lint]
```

While a required check is still running, the GitHub App waits and decides when it finishes. A check that didn't run on the commit fails the gate. A check passes with the conclusion `success`, `neutral` or `skipped`; anything else fails.

CI results are only visible to the Bumpkit GitHub App and repository host connections. When the gate runs from the CLI or API, a person must approve to vouch for them.

## require_check_apps

Required checks that only count when a specific GitHub App reported them on the exact head commit.

| | |
|---|---|
| Type | map of check name to GitHub App ID |
| Default | none |
| Allowed values | unique check names, matched case-insensitively, and positive App IDs; not `behavior` |

```yaml
gate:
  require_check_apps:
    test: 123456
```

A name here is required even if `require_checks` doesn't list it. The gate only counts a check run whose App ID matches and whose head SHA is the commit being gated. A check with the same name from another App, a commit status, or a run for another commit doesn't count, so the check fails with "test from GitHub App 123456 didn't run on this commit." When the same check reports more than once, the worst result wins: a failure or a run still in progress can't be hidden by a passing duplicate.

The GitHub App reads every check run on the commit, up to 1,000. More than that fails closed, and so does a check run GitHub returns for a different commit.

Only the Bumpkit GitHub App and a [GitHub Enterprise Server connection](/docs/release-gate#github-enterprise-server) can verify the issuer, using the App IDs on that server. When the gate runs from the CLI, the API or another code host, `require_check_apps` fails, and a person's approval can't replace it.

If Bumpkit can't load every check run on the commit, for example when there are more than 1,000 or GitHub returns an error, the gate fails with "Bumpkit couldn't verify the required check apps". With `require_check_apps` set, that happens whatever `on_error` says.

### require_checks: behavior

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `behavioral_verification`.

The name `behavior` (matched case-insensitively) requires a signed `bumpkit verify` report for the commit instead of a CI check. Regressions fail the gate and changed behavior needs approval. See [verify](#verify).

```yaml
gate:
  require_checks: [test, behavior]
```

## security_alerts

Fails the gate when the pull request has open code scanning alerts at or above this severity.

| | |
|---|---|
| Type | string |
| Default | off |
| Allowed values | `critical`, `high`, `medium`, `low` |

If code scanning results aren't available for the pull request, the gate fails. Like `require_checks`, this needs the GitHub App or a GitHub Enterprise Server connection; elsewhere a person must vouch.

## vulnerabilities

Fails the gate when the release adds a dependency version with a known vulnerability at or above this severity.

| | |
|---|---|
| Type | string |
| Default | `high` with no `gate:` section; off otherwise |
| Allowed values | `high`, `medium`, `low` |

If Bumpkit can't check for vulnerabilities, the release needs approval instead.

## break_glass

Whether an owner, admin or policy admin may override a failing or waiting gate, signed in to the dashboard on the commit's check page. An override applies to one commit and records the reason.

| | |
|---|---|
| Type | bool |
| Default | `true` |

With `break_glass: false`, an override is still recorded but doesn't change the decision. See [Hotfixes & Break Glass](/docs/hotfixes).

## hotfix

Rules for hotfix releases. A release is a hotfix when `bumpkit gate --hotfix` is passed, or when the branch name starts with `hotfix/`, `hotfixes/`, `release/` or `releases/` (or the same with `-`). A hotfix must be a patch release with no feature commits; otherwise it needs approval.

```yaml
gate:
  hotfix:
    block_on: [breaking, data_loss, migration]
    max_files: 20
    review_within: 72h
    backports: approve
```

### hotfix.block_on

Finding kinds a hotfix may not carry. Any deterministic, unsuppressed finding of these kinds fails a hotfix, whatever its severity.

| | |
|---|---|
| Type | list of strings |
| Default | `[breaking, contract_breaking, data_loss, migration]` (`contract_breaking` findings exist only with contract checks, Beta) |
| Allowed values | `breaking`, `contract_breaking` (Beta), `data_loss`, `migration`, `dependency`, `vulnerable_dependency`, `config`, `secret`, `risk_area`, `untested` |

### hotfix.max_files

A hotfix that changes more files than this needs approval.

| | |
|---|---|
| Type | int |
| Default | `0` (no limit) |

### hotfix.review_within

When someone overrides the gate with break glass, the override needs a post-hoc review within this time. Record it with `bumpkit override review`.

| | |
|---|---|
| Type | duration string, such as `24h` or `72h` |
| Default | `72h` |
| Allowed values | more than `0` and at most 90 days (`2160h`) |

### hotfix.backports

What happens when a regular release would ship without fixes that went out in an earlier hotfix tag (from the last year) but never reached this branch.

| | |
|---|---|
| Type | string |
| Default | `approve` |
| Allowed values | `block` (fail), `approve` (needs approval), `off` |

## blast_radius

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `blast_radius`.

The `bumpkit` CLI, locally or in CI, follows every caller of the changed functions through the code graph and report which product areas the change reaches. These keys decide which areas need approval and how far to follow calls.

```yaml
gate:
  blast_radius:
    approve_areas: [billing, "@acme/payments"]
    max_depth: 4
```

### blast_radius.approve_areas

Areas that need an approval when a change reaches them. A reached area in this list also raises the finding from `low` to `medium` severity.

| | |
|---|---|
| Type | list of strings |
| Default | none |
| Allowed values | plain area names, matched case-insensitively; no `:`, `<`, `>`, `` ` `` or `|` |

Bumpkit names the area of a file in this order:

1. A named area: a name from `approve_areas` or [require_approval.area](#require-approval-area), or a built-in sensitive area, that appears as a word in the file's path.
2. The file's CODEOWNERS owners, such as `@acme/payments`.
3. The file's top directory, or the first two segments under `internal`, `pkg`, `src`, `lib`, `libs`, `app`, `apps`, `packages`, `services`, `cmd` or `modules`.

### blast_radius.max_depth

How many calls deep to follow callers.

| | |
|---|---|
| Type | int |
| Default | `4` |
| Allowed values | `1` to `10` |

## deploy

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `deploy_protection`.

With the Bumpkit GitHub App as a deployment protection rule, Bumpkit approves or rejects each deployment from the gate decision for the deployed commit, or the pull request it came from.

```yaml
gate:
  deploy:
    environments: [production]
    require: approved
```

### deploy.environments

| | |
|---|---|
| Type | list of strings |
| Default | every environment |
| Allowed values | GitHub environment names, matched case-insensitively |

Deployments to environments not on the list are approved.

### deploy.require

| | |
|---|---|
| Type | string |
| Default | `pass` |
| Allowed values | `pass`, `approved` |

With `pass`, a passing gate approves the deployment. With `approved`, the gate decision must also have at least one approval. A failed gate always rejects the deployment, and a decision that needs approval leaves it pending.

## timezone

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `change_freezes`.

The default time zone for [windows](#windows) and [freezes](#freezes).

| | |
|---|---|
| Type | string |
| Default | `UTC` |
| Allowed values | an IANA name, such as `America/New_York` or `America/Los_Angeles` |

## windows

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `change_freezes`.

Weekly release windows. Outside every window, a release needs approval or fails, depending on [outside_windows](#outside-windows). Hotfixes may ship outside the windows.

```yaml
gate:
  timezone: America/New_York
  windows:
    - days: [mon-thu]
      from: "09:00"
      to: "16:00"
    - days: [fri]
      from: "09:00"
      to: "12:00"
```

| Key | Type | Default | Allowed values |
|---|---|---|---|
| `days` | list of strings | every day | `mon` to `sun`, full names such as `monday`, or ranges like `mon-fri` |
| `from` | string | `00:00` | a time like `09:00` |
| `to` | string | `24:00` | a time like `17:00`, after `from` |
| `timezone` | string | [timezone](#timezone) | an IANA name |

Quote times in YAML (`"09:00"`).

## outside_windows

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `change_freezes`.

| | |
|---|---|
| Type | string |
| Default | `approve` |
| Allowed values | `approve`, `block` |

## freezes

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `change_freezes`.

Scheduled change freezes. Each freeze is a date range, a list of dates, or a weekly time span. You can also declare a freeze on the spot with `bumpkit freeze start` or the Policy page. See [Org Policies & Freezes](/docs/org-policies).

```yaml
gate:
  freezes:
    - name: Year-end freeze
      start: 2026-12-20
      end: 2027-01-02
      mode: block
      hotfix: approve
    - name: Public holidays
      dates: [2026-12-25, 2027-01-01]
    - name: Friday afternoons
      days: [fri]
      from: "15:00"
      mode: approve
```

| Key | Type | Default | Allowed values |
|---|---|---|---|
| `name` | string | required | any text |
| `start`, `end` | string | none | dates like `2026-12-24` or times like `2026-12-24T18:00`; set both. A date-only `end` includes that whole day |
| `dates` | list of strings | none | whole days, like `2026-12-25` |
| `days`, `from`, `to` | | | the same as in [windows](#windows) |
| `timezone` | string | [timezone](#timezone) | an IANA name |
| `mode` | string | `block` | `block` (fail), `approve` (needs approval) |
| `hotfix` | string | `approve` | `allow`, `approve`, `block` |

During a `block` freeze, only a break-glass override lets a release through. `hotfix` sets what happens to hotfixes during the freeze.

## areas

> **Beta, early access.** Flag: `proven_changes`.

Names parts of the repository for [proven changes](/docs/confidence-score#proven-changes). Each name maps to path patterns. A file can be in more than one area. Files in no named area use their directory. The gate doesn't read this.

```yaml
areas:
  checkout: ["apps/web/checkout/**", "services/payments"]
```

## proven

> **Beta, early access.** Flag: `proven_changes`.

`proven.never` lists the kinds of change that are never proven in this repository: `tests`, `migrations`, `auth`, `api`, `ui`, `deps`, `docs` or `code`. Without it, `deps`, `migrations` and `auth` are excluded. An empty list measures every kind. The gate doesn't read this.

```yaml
proven:
  never: [deps, migrations, auth, ui]
```

## compatibility

> **Beta, early access.** Flag: `compatibility_matrix`.

A top-level section, next to `gate`, that declares your components and the version pairs you support. It feeds the [compatibility matrix](/docs/contract-checks#compatibility-matrix). Add `compatibility_break` to [block_on](#block-on) to block a release that breaks a pair.

```yaml
compatibility:
  components:
    - name: web
      role: client
      repository: acme/web
    - name: api
      role: server
      paths: ["openapi/**"]
  supported:
    - consumer: web
      consumer_versions: ">=1.4.0 <2.0.0"
      provider: api
      provider_versions: ">=2.0.0"
```

`components`, 1 to 50:

| Key | Type | Required | Allowed values |
|---|---|---|---|
| `name` | string | Yes | Letters, digits, dots, dashes or underscores, up to 64 characters; unique |
| `role` | string | Yes | `client`, `server`, `sdk`, `api`, `schema`, `app` |
| `repository` | string | No | `owner/name`; defaults to the repository this file is in |
| `paths` | list of strings | No | Up to 50 path patterns such as `openapi/**`; only breaks in these paths count. Defaults to the whole repository |

`supported`, 1 to 200:

| Key | Type | Required | Allowed values |
|---|---|---|---|
| `consumer` | string | Yes | A component's `name` |
| `consumer_versions` | string | Yes | A version range |
| `provider` | string | Yes | A different component's `name` |
| `provider_versions` | string | Yes | A version range |

A version range looks like `>=1.4.0 <2.0.0`, `>=2.0.0`, `^1.4`, `~1.4`, `1.x` or `2.3.1`. Pre-release versions aren't in a range unless the range names one.

Removing the section removes the repository's pairs the next time Bumpkit reads the file from the base branch, or when you run `bumpkit compatibility sync`.

## exports

Tunes the public API change findings, the removed or changed exports that count as breaking changes.

| Key | Type | Default | Allowed values |
|---|---|---|---|
| `severity` | string | `high` | `high`, `medium`, `low`, `info` |
| `ignore_paths` | list of strings | none | up to 100 path patterns, CODEOWNERS style, such as `generated/**` |
| `max_findings` | int | `0` (no cap) | `0` to `1000`; the rest are summarized in one finding |

Only high-severity breaking findings trigger [block_on](#block-on) and the `breaking` approval rule, so a lower `severity` makes export changes informational. These keys apply to every release check and gate; they don't need the `behavior_checks` flag.

## behavior

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `behavior_checks`.

Release checks catch small behavior changes in every [supported language](/docs/supported-languages), such as removed guard clauses or changed defaults, and the CLI flags changed files with a history of reverts. These findings are informational until you turn them on here.

```yaml
gate:
  behavior:
    changes: approve
    hotspots: block
    severity: medium
    hotspots_min: high
```

| Key | Type | Default | Allowed values |
|---|---|---|---|
| `changes` | string | `off` | `off`, `approve`, `block`: what behavior-change findings do |
| `hotspots` | string | `off` | `off`, `approve`, `block`: what hotspot findings do |
| `severity` | string | none | `high`, `medium`, `low`: raises these findings to at least this severity |
| `hotspots_min` | string | `medium` | `high`, `medium`, `low`: hotspots below this severity don't count |

## quality

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flags: `architecture_drift`, `slop_tracker`, `performance_diff`.

Decides what code quality findings do, and sets the thresholds the checks use. See [Code Quality & Architecture](/docs/quality).

```yaml
gate:
  quality:
    architecture_drift: block
    code_quality:
      action: approve
      severity: medium
      when: agent
    performance: warn
```

### quality.architecture_drift

New import cycles, breaks of the [architecture](#architecture) layers, and coupling and complexity growth.

### quality.code_quality

Copied code, interfaces with one implementation, functions that only forward, unreferenced new symbols, oversized functions and comment noise.

### quality.performance

Queries inside loops, blocking IO and complexity growth on hot paths, unbounded allocations, and benchmark regressions.

### Quality rules

Each of the three keys takes a string (the action) or a mapping:

| Key | Type | Default | Allowed values |
|---|---|---|---|
| `action` | string | `warn` | `warn` (report only), `approve` (needs approval), `block` (fail) |
| `severity` | string | `medium` | `high`, `medium`, `low`: findings at or above this count |
| `when` | string | `always` | `always`, `agent`, `agent_or_unknown` |

### Quality thresholds

| Key | Type | Default | Allowed values |
|---|---|---|---|
| `max_function_lines` | int | `80` | `1` to `10000` |
| `max_complexity` | int | `15` | `1` to `1000` |
| `clone_tokens` | int | `40` | `10` to `1000`: the smallest copied block reported |
| `hot_path_depth` | int | `4` | `1` to `10` |
| `bench_threshold` | number | `5` | `0` to `1000`: the benchmark slowdown in percent that counts |

## cross_repo

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `cross_repo_impact`.

When a release breaks a contract or public API that other repositories in your workspace consume, the gate lists each consuming repository with its call sites. See [Cross-Repo Impact](/docs/cross-repo).

| Key | Type | Default | Meaning |
|---|---|---|---|
| `block_on_consumer_break` | bool | `false` | Fail the gate when a consumer breaks |
| `require_approval` | bool | `true` | Ask for approval when a consumer breaks |

If Bumpkit can't check the consumers, the release needs approval regardless of these settings.

## fix_tasks

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `close_the_loop`.

What open fix tasks do to the gate. A fix task lists every `file:line` caller still on the old version of a Go function or method this branch renamed, removed or changed, with the expected edit. Bumpkit opens mechanical edits (a rename, or one removed parameter) as a pull request into the branch and routes the rest to the authoring agent and the pull request. A task is done only when a later gate run proves every listed place clean, never because an agent says so.

| | |
|---|---|
| Type | string |
| Default | `block` |
| Allowed values | `block` (fail), `approve` (needs approval), `off` |

```yaml
gate:
  fix_tasks: approve
```

A task holds the gate once Bumpkit has confirmed it by reading the code at the commit through the GitHub App. Until then it shows as a `fix_tasks: unconfirmed` flag and doesn't hold the gate, so on GitLab, Bitbucket and Azure DevOps a task is a flag. Creating a task from a report takes a key with write access. Each confirmed task still open after the gate run adds one reason, `fix_tasks: block` or `fix_tasks: approve`, that names the places, the symbol, its old signature, what to change, who it was routed to and the fix pull request. With `block`, approval can't override it. With `approve`, any reviewer's approval counts. With `off`, tasks are still recorded and routed but don't change the outcome. An org policy can lock `fix_tasks`; a repository can then only tighten it. `bumpkit loop --mine` lists the tasks on your branch.

## require_regression_evidence

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `regression_evidence`.

Require test results on the exact commit before a release ships, with the tests picked by the change's blast radius. It's off until you turn it on.

| | |
|---|---|
| Type | bool |
| Default | `false` |

## regression_evidence_action

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `regression_evidence`.

What happens when the evidence isn't a clean pass. It needs `require_regression_evidence: true`.

| | |
|---|---|
| Type | string |
| Default | `block` |
| Allowed values | `block` (fail), `approve` (needs approval) |

```yaml
gate:
  require_regression_evidence: true
  regression_evidence_action: approve
```

The gate adds one reason, `require_regression_evidence: block` or `require_regression_evidence: approve`. It passes only when every expected test passed in results a CI run attested for this exact commit. Results from a plain API key or another commit don't count. No results, partial results, a failed test, an unreadable upload, or a change Bumpkit can't map to tests all fail under `block`, where approval can't override it, and need approval under `approve`, where any reviewer's approval counts. See [Regression evidence](/docs/release-checks#regression-evidence) for the setup. An org policy can lock both keys; a repository can then only tighten them.

## stacks

> **Beta, early access.** Flag: `stacked_prs`. Needs the Bumpkit GitHub App.

How the gate treats stacked pull requests. A pull request whose base branch is another open pull request's head branch in the same repository is stacked on it, so `#128 → #130 → #131` is a stack of three. Bumpkit works the stack out from GitHub App events, never from what a client sends, and checks each pull request on its own changes: `#131` is compared against `#130`'s head, not against `main`. The policy and CODEOWNERS for a stacked pull request come from the default branch.

Every stacked pull request's decision carries a `stacks` reason that names its parent and what has to merge first. The result comment shows the whole stack, the pull request's position in it and the merge order.

Bumpkit keeps each decision tied to the exact head and base it checked:

- When a parent gets new commits, its direct children are checked again against the new parent head.
- When GitHub retargets a child after its parent merges, the child is checked again against its new base, and the decision against the old base no longer counts as current.
- Approvals count only for the head commit they approved.

| | |
|---|---|
| Type | mapping |
| Default | `{require_parent_merged: false}` |

### stacks.require_parent_merged

Holds a stacked pull request until the pull request it's stacked on has merged. While the parent is open, the child's decision fails with a `stacks.require_parent_merged` reason that names the pull requests to merge first. Approval can't override it. Once the parent merges, Bumpkit checks the child again and the hold clears.

| | |
|---|---|
| Type | boolean |
| Default | `false` |

```yaml
gate:
  stacks:
    require_parent_merged: true
```

An org policy can lock `stacks`; a repository can then only turn `require_parent_merged` on. Run `bumpkit stack list` to see the repository's stacks, or `bumpkit stack show NUMBER` for one pull request's position and merge order.

## architecture

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `architecture_drift`.

A top-level key, next to `gate:`. It declares your layers and the imports between them that aren't allowed. Without it, Bumpkit infers layers from the base. `gate.quality.architecture_drift` decides what a break does.

```yaml
architecture:
  layers:
    - name: api
      paths: [services/api]
    - name: store
      paths: [services/db, services/store]
    - web
  forbidden:
    - from: store
      to: api
```

| Key | Type | Meaning |
|---|---|---|
| `layers` | list | Each layer is a name and its `paths`. A plain string is a layer whose name is also its path |
| `layers[].name` | string | A unique name |
| `layers[].paths` | list of strings | Relative path prefixes; defaults to the name |
| `forbidden` | list | Imports that aren't allowed |
| `forbidden[].from`, `forbidden[].to` | string | A layer name or path |

## agent

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `agent_pause`.

A top-level key. It configures `bumpkit pause`, the hook that checks each shell command or file access a coding agent makes before it runs, and allows it, asks a human, or denies it. See [Agent Controls](/docs/agent-controls).

```yaml
agent:
  ask: [rm_rf, "npm publish *"]
  deny: [force_push, "terraform destroy*"]
  allow: ["rm -rf ./dist*"]
  protected_branches: [main, "release/*"]
  remote: true
  timeout: 45s
```

| Key | Type | Default | Meaning |
|---|---|---|---|
| `ask` | list of strings | none | Rules or patterns that pause for a human |
| `deny` | list of strings | none | Rules or patterns that are blocked |
| `allow` | list of strings | none | Rules or patterns that are let through |
| `protected_branches` | list of strings | `main`, `master`, `trunk`, `production`, `prod`, `release/*`, `releases/*` | Branch names or globs that `force_push` protects |
| `remote` | bool | `false` | When a command pauses, a teammate can approve it from the Approvals page |
| `timeout` | duration string | `30s` | How long a remote approval waits, `5s` to `120s` |

Each entry in `ask`, `deny` and `allow` is a built-in rule ID or a pattern. A pattern matches a command line or file path, with `*` and `?` as wildcards. A single lowercase word that isn't a rule ID is an error; add a space or `*` to make it a pattern.

A `deny` pattern always wins. An `allow` pattern lets a command through unless one of its rules is in `deny`. Listing a rule ID moves that rule to the level of the list.

| Rule ID | Default | Catches |
|---|---|---|
| `rm_rf` | ask | `rm -r` outside a temporary or build directory |
| `rm_rf_root` | deny | `rm -r` on `/`, the home directory, the repository root or a system directory |
| `force_push` | deny | `git push --force` or a branch delete on a protected branch |
| `git_reset_hard` | ask | `git reset --hard` |
| `git_clean` | ask | `git clean -f` |
| `sql_drop` | ask | `DROP`, `TRUNCATE` or an unfiltered `DELETE` sent to a database client |
| `infra_apply` | ask | `terraform`, `tofu` or `pulumi` apply and destroy |
| `kubectl_delete` | ask | `kubectl delete` or `drain`, `helm uninstall` |
| `pipe_to_shell` | ask | A downloaded or decoded script piped into a shell or interpreter |
| `chmod_777` | ask | `chmod -R` that makes files world-writable |
| `secret_write` | ask | A write to `.env`, a key or a secrets file |
| `credential_file` | ask | Reading or copying a credential file such as `.env`, `~/.ssh` or `~/.aws` |
| `package_publish` | ask | Publishing a package or pushing an image |
| `prod_database` | ask | A database URL whose host isn't local |
| `self_approval` | deny | An agent approving or rejecting Bumpkit memories, lessons or Approvals items |
| `agent_scope` | deny | An agent writing outside the paths and areas `agents:` gives it |
| `too_complex` | ask | A command nested too deeply to check |

`bumpkit pause` reads `bumpkit.yml` from the default branch (`origin/HEAD`, `origin/main` or `origin/master`, else `HEAD`), not from your uncommitted changes, so an agent can't edit its own rules. If the file doesn't parse, it uses the default rules.

## agents

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `agent_budgets`.

A top-level key with budgets and scopes for coding agents. Map an agent id, or a glob such as `claude-*`, to its limits:

```yaml
agents:
  claude-*:
    max_open_prs: 2
    max_changed_lines: 400
    paths_allow: [services/notify/**]
    paths_deny: [services/billing/**]
    areas_deny: [billing]
    spend_limit: 10
    on_violation: block
```

| Key | Type | Default | Meaning |
|---|---|---|---|
| `max_open_prs` | int | no limit | The most open pull requests the agent may have; this change fails the limit when the agent already has that many others open |
| `max_changed_lines` | int | no limit | The most lines one change may touch |
| `paths_allow` | list of strings | every path | Path patterns the agent may change, such as `services/notify/**` |
| `paths_deny` | list of strings | none | Path patterns the agent may not change |
| `areas_deny` | list of strings | none | Areas the agent stays out of: `tests`, `migrations`, `auth`, `api`, `ui`, `authentication`, `authorization`, `billing`, `cryptography` |
| `spend_limit` | number | no limit | US dollars per calendar month (UTC), recorded with `bumpkit agents spend` |
| `on_violation` | string | `block` | `block` fails the gate; `approve` asks for a person's approval |

The gate checks every agent on the change, including the `BUMPKIT_AGENT_ID` or `BUMPKIT_AGENT` that `bumpkit gate` sends. Each broken limit becomes its own reason, named `agents.<pattern>.paths`, `.areas_deny`, `.max_changed_lines`, `.max_open_prs` or `.spend_limit`. `bumpkit pause --hook claude` also denies file writes outside an agent's paths before they happen. Every violation is recorded and audited; see `bumpkit agents violations`. Limits can't be negative, and an unknown key or area is an error. With the GitHub App installed, the gate uses the `agents:` section from the base branch.

## memory

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `memory_approval`.

A top-level key for [codebase memory](/docs/agent-controls).

| Key | Type | Default | Meaning |
|---|---|---|---|
| `require_approval` | bool | `false` | Memories agents save wait as pending until a person approves them on the Approvals page or with `bumpkit memory approve`. Only approved memories are recalled |

```yaml
memory:
  require_approval: true
```

If `bumpkit.yml` doesn't parse, new agent memories wait for approval.

## verify

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `behavioral_verification`.

A top-level key for `bumpkit verify`, which runs the base and the head side by side in your CI and uploads a signed report. Add `behavior` to [require_checks](#require-checks-behavior) to make the gate require it. `bumpkit verify` reads this section from the base commit, like the gate.

```yaml
verify:
  test: go test ./...
  timeout: 15m
  network: localhost
  env: [DATABASE_URL]
  http:
    start: ./bin/server
    ready: /healthz
    port_env: PORT
    openapi: api/openapi.yaml
    ignore: [id, created_at]
  library:
    runs: 128
  browser:
    pages: [/, /pricing]
    threshold: 0.02
  require_ci_attestation: true
```

| Key | Type | Default | Meaning |
|---|---|---|---|
| `test` | string | detected: `go test ./...`, `npm test` or `python -m pytest` | The test command |
| `timeout` | duration string | `10m` | Up to `2h` |
| `memory_mb` | int | `16384` | Memory limit for the sandbox |
| `cpu_seconds` | int | `1800` | CPU limit for the sandbox |
| `network` | string | `localhost` | `localhost` or `allow` |
| `env` | list of strings | none | Names of environment variables to pass into the sandbox (names only, no `=`) |
| `require_ci_attestation` | bool | `true` | `gate.require_checks: [behavior]` uses the latest report attested by GitHub Actions OIDC, so a later report signed only with an API key neither replaces nor blocks it. `false` opts out and trusts anyone with a key. Once the GitHub App is installed or a GitLab, Bitbucket or Azure DevOps connection is added, Bumpkit reads this setting from your repository's policy; with neither, it uses the policy your CI sends and the decision says `policy_source: client`. With the App installed, a report counts only if the pull request leaves `.github/` and the local actions and scripts named in the default branch's workflow unchanged, and a report from a merge commit counts only while it is the pull request's current merge commit into your default branch. The workflow still runs the pull request's own code and tests. GitLab, Bitbucket and Azure DevOps have no GitHub Actions token, so set `false` there to use `require_checks: [behavior]` |

### verify.http

Replays HTTP requests against the base and head versions of your service.

| Key | Type | Default | Meaning |
|---|---|---|---|
| `start` | string | required | The command that starts the service |
| `ready` | string | `/` | The path that answers when the service is up |
| `port_env` | string | `PORT` | The environment variable the service reads its port from |
| `openapi` | string | detected | An OpenAPI spec to derive requests from |
| `requests` | string | none | A file of recorded requests |
| `ignore` | list of strings | none | JSON body fields to ignore when comparing, such as `id` or `$.created_at` |
| `startup_timeout` | duration string | `60s` | How long to wait for `ready` |

### verify.library

Generated calls to changed Go functions, compared between base and head.

| Key | Type | Default | Meaning |
|---|---|---|---|
| `disabled` | bool | `false` | Turn it off |
| `runs` | int | `64` | Calls per function, up to `1000` |
| `seed` | int | `1` | Random seed |

### verify.browser

Playwright screenshots of pages, compared pixel by pixel.

| Key | Type | Default | Meaning |
|---|---|---|---|
| `disabled` | bool | `false` | Turn it off |
| `pages` | list of strings | `[/]` | Pages to compare, up to 50 |
| `threshold` | number | `0.01` | The share of pixels, `0` to `1`, that may differ |

## train

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `release_trains`.

Top-level `train:` sets the rules for [release trains](/docs/release-trains) cut from this branch. Bumpkit reads it from `bumpkit.yml` on the branch the train ships.

```yaml
train:
  label: "train:{slug}"
  require_all_pass: true
  max_breaking: 0
  require_approval:
    teams: ["@acme/release-managers"]
    count: 1
  block_on_unhealthy_deploys: true
  release_workflow: release.yml
```

| Key | Type | Default | Meaning |
|---|---|---|---|
| `label` | string | `train:{slug}` | The pull request label that joins a train. At most 50 characters, no surrounding spaces. |
| `require_all_pass` | bool | `true` | Hold the train until every member passes the gate. A failing member always blocks it. |
| `max_breaking` | int | no limit | The most breaking changes the train may carry. Must not be negative. |
| `require_approval` | map | none | Release manager approvals: `approvers` (list), `teams` (list of `@org/team`) and `count` (up to 10; leave it out for one approval). |
| `block_on_unhealthy_deploys` | bool | `false` | Block when a member is `regressed` or `degraded` in production. |
| `release_workflow` | string | none | A workflow file name such as `release.yml`, dispatched with the train's name when the train is cut, instead of Bumpkit creating the GitHub Release. |

## locked

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `org_policies`.

An org policy is one gate policy for the whole workspace. Workspace owners and admins save it on the Policy page, signed in to the dashboard; `bumpkit policy set` and `PUT /v1/policy/org` return `403 HUMAN_APPROVAL_REQUIRED` with the link, since a key can't change org locks, or point it at a config repository whose `bumpkit.yml` on the default branch is read at gate time. Every version is kept and audited. See [Org Policies & Freezes](/docs/org-policies).

An org policy is a `bumpkit.yml` with one extra top-level key, `locked:`, the gate keys that repositories may only tighten:

```yaml
locked: [block_on, require_approval, break_glass, freezes]
gate:
  block_on: [secret, data_loss]
  require_approval:
    - breaking
  break_glass: false
  freezes:
    - name: Year-end freeze
      start: 2026-12-20
      end: 2027-01-02
```

`locked` accepts `block_on`, `require_approval`, `on_error`, `require_checks`, `security_alerts`, `vulnerabilities`, `break_glass`, `hotfix`, `blast_radius`, `deploy`, `cross_repo`, `timezone`, `windows`, `outside_windows`, `freezes`, `quality`, `exports`, `behavior`, `fix_tasks`, `require_regression_evidence`, `regression_evidence_action`, `require_check_apps` and `stacks`.

### How the org policy merges

The org policy is the starting point. For each key a repository's `bumpkit.yml` sets under `gate:`:

- An **unlocked** key replaces the org value.
- A **locked** key can only be tightened. When the repository tries to loosen it, the org value stays and the gate decision notes it.

| Locked key | Effective value |
|---|---|
| `block_on`, `require_checks` | Both lists combined |
| `require_approval`, `freezes` | The org rules plus any new repository rules |
| `on_error` | The org's `fail` stays `fail` |
| `security_alerts`, `vulnerabilities` | The repository may only lower the threshold |
| `break_glass` | The org's `false` stays `false` |
| `hotfix` | The stricter of each setting: both `block_on` lists, the smaller `max_files`, the shorter `review_within`, the stricter `backports` |
| `blast_radius` | Both `approve_areas` lists; the org's `max_depth` always applies |
| `timezone` | The org's value |
| `windows` | The org's windows, if it sets any; add freezes to narrow them |
| `outside_windows` | The org's `block` stays `block` |
| `fix_tasks` | The stricter of the two: `off` < `approve` < `block` |
| `require_regression_evidence` | The org's `true` stays `true` |
| `regression_evidence_action` | The stricter of the two: `approve` < `block` |
| `deploy`, `cross_repo`, `quality`, `exports`, `behavior` | The org's value; a different repository value is ignored |

Run `bumpkit policy show --effective` in a repository, or call `POST /v1/policy/effective`, to see the policy the gate applies and where each key came from. If the org policy doesn't parse or its config repository can't be read, the gate fails closed.
