Release gate
f5240f5Report a problem ↗The release gate answers one question for a pipeline, immediately before a protected step: does this Service’s reliability allow a release right now? The answer comes from facts cerbix has already sealed — error budget, burn alerts, an open Service incident — read in one database snapshot and recorded in a ledger. The gate reserves nothing and does not know whether the release happened.
Ask the gate
Section titled “Ask the gate”export CERBIX_URL=https://cerbix.example.comexport CERBIX_TOKEN="<api-token>"cerbix gate check --project <project-id> --service <service-id>| Flag | Meaning |
|---|---|
--project, --service |
Required IDs. |
--json |
Print the API response verbatim instead of the summary line. |
--timeout |
Overall request deadline, default 10s. |
| Variable | Meaning |
|---|---|
CERBIX_URL |
Plain http:// or https:// base URL. A path prefix is allowed; credentials, query and fragment are refused. |
CERBIX_TOKEN |
API bearer token. There is no --token flag. |
CERBIX_CA_FILE |
Optional PEM file added to the system roots. There is no option to skip TLS verification. |
The CLI sends exactly one POST /api/v1/projects/{projectID}/services/{serviceID}/gate with body {}. It never retries and never follows redirects.
Output and exit codes
Section titled “Output and exit codes”stdout is one line:
state=<STATE> [action=<ACTION>] policy_source=<SOURCE> policy_owner_id=<ID> policy_revision=<REVISION> window_mode=<MODE> evaluated_windows=<WINDOWS> [override=<actor_label>] decision=<decision_id>action= is omitted for NOT_CONFIGURED; override= appears only when an override was applied. Missing values print none, and evaluated_windows is a comma-separated list. Example data:
state=BLOCK action=ALLOW policy_source=project policy_owner_id=<project-id> policy_revision=3 window_mode=all evaluated_windows=24h,30d override=admin@example.com decision=<decision-id>state=NOT_CONFIGURED policy_source=none policy_owner_id=none policy_revision=none window_mode=none evaluated_windows=none decision=<decision-id>stderr carries one line per reason: <code>[ (<assignment>)][: <value>], for example budget_consumed (warn): 93. With --json, stdout holds the response bytes and nothing else, not even a trailing newline; reasons still go to stderr.
| Exit | When |
|---|---|
0 |
action is ALLOW or WARN |
2 |
action is BLOCK, or a usage error (unknown flag, missing --project/--service, invalid --timeout) |
4 |
state is NOT_CONFIGURED |
1 |
No decision: missing variable, transport, TLS or timeout failure, any 4xx or 5xx (including 429 with Retry-After printed, and 503 snapshot_conflict, ledger_unwritable or timeout), a redirect, or a malformed response |
The CLI rejects positional arguments and undocumented shorthand flags with exit 2 before any request. URL errors never echo the rejected CERBIX_URL, so credentials embedded in it stay out of CI logs.
State and action
Section titled “State and action”state is what was observed. action is what the pipeline should do.
state |
action |
|---|---|
ALLOW |
ALLOW |
WARN |
WARN |
BLOCK |
BLOCK |
UNKNOWN |
The policy’s unknown_behavior: WARN or BLOCK |
NOT_CONFIGURED |
None. The Service has no policy. |
The algebra runs over every clause:
- A clause assigned
ignorecontributes nothing, even when its fact is unavailable. - Any known, matching
blockclause →BLOCK, whatever else is unknown. - Otherwise any unavailable
blockorwarnclause →UNKNOWN. - Otherwise any matching
warnclause →WARN. - Otherwise
ALLOW.
UNKNOWN means a fact the policy depends on could not be answered. It is reported as UNKNOWN whatever the action.
Clauses
Section titled “Clauses”| Clause | Matches when | UI template |
|---|---|---|
budget_exhausted |
Budget consumed ≥ 100 % | block |
budget_consumed |
Budget consumed ≥ budget_consumed_percent |
warn |
page_burn_firing |
A page-severity burn rule of the window’s target is firing |
block |
ticket_burn_firing |
A ticket-severity burn rule is firing |
warn |
service_incident_open |
An unresolved auto-incident is anchored to the Service | warn |
A clause is unavailable — not false — under these reason codes: budget_withheld, seal_stale, facts_stale, no_objective, window_target_missing, never_sealed, never_evaluated, no_governing_revision. Budgets end at the seal watermark, never at now; seal_stale fires when that watermark is older than max_seal_lag_seconds. See SLO, budget and burn.
Policy
Section titled “Policy”PUT /api/v1/projects/{projectID}/services/{serviceID}/gate/policy (editor or above):
{ "expected_revision": null, "schema_version": 2, "window_mode": "one", "window": "30d", "clauses": { "budget_exhausted": "block", "budget_consumed": "warn", "page_burn_firing": "block", "ticket_burn_firing": "warn", "service_incident_open": "warn" }, "budget_consumed_percent": 90, "max_seal_lag_seconds": 900, "unknown_behavior": "warn"}| Field | Rule |
|---|---|
expected_revision |
Required. null when the Service has no policy of its own, else its current revision; a mismatch is 409 revision_conflict. |
schema_version |
1 or 2. Version 1 is always window_mode: one. |
window_mode |
one or all; required for version 2. |
window |
Required for one and must have an SLO target on the Service; absent for all. |
clauses |
Every clause exactly once, each block, warn or ignore. |
budget_consumed_percent |
Integer 1–100. |
max_seal_lag_seconds |
300–86,400, a whole number of minutes. |
unknown_behavior |
warn or block. No default. |
The server fills nothing in: a missing field is a 400 naming it. The values in the UI’s create template are suggestions only. An identical write changes nothing. Delete with DELETE …/gate/policy?expected_revision=<n>.
With window_mode: all, the gate evaluates every SLO target the Service has among 24h, 7d, 30d and 90d. Window clauses are checked per target and service_incident_open once. A healthy window never cancels an unhealthy one. A Service with no target makes the window clauses unavailable (no_objective). The response lists every window in evaluated_windows, healthy ones included.
Project policy and inheritance
Section titled “Project policy and inheritance”A project admin sets one project policy at /api/v1/projects/{projectID}/gate/policy (GET, PUT, DELETE) with the same document. Resolution order: a live Service policy, then the project policy, then NOT_CONFIGURED. GET …/services/{serviceID}/gate/policy returns the effective document with policy_source (service or project), policy_owner_id and policy_revision. Deleting a Service policy returns that Service to inheritance; with no Service policy to delete, the answer is 404 not_configured.
Overrides
Section titled “Overrides”A project admin can let a blocked release through for a bounded time with POST /api/v1/projects/{projectID}/services/{serviceID}/gate/override:
{ "policy_revision": 3, "reason": "Hotfix for the open incident", "expires_at": "2026-10-06T12:00:00Z" }reason is 1–500 characters; expires_at is at most 7 days ahead. Only one override is active per Service (409 override_active). An override changes action to ALLOW and nothing else: state and reasons stay, and unoverridden_action records what the action would have been. WARN, ALLOW and NOT_CONFIGURED are never overridden. Editing or deleting the policy the override was bound to revokes it. Revoke by hand with DELETE …/gate/overrides/{overrideID}.
Decision ledger
Section titled “Decision ledger”Every decision, including NOT_CONFIGURED, is stored with its evidence and policy snapshot. List decisions with GET /api/v1/projects/{projectID}/gate/decisions?from=<RFC3339>&to=<RFC3339>: the range is at most 31 days, with optional service_id, repeatable state, limit (1–200, default 50) and cursor. Read one with …/gate/decisions/{decisionID}. Decisions outlive their Service and are kept for gate.decision_retention_days (default 90). Policy and override changes go to the audit log; decisions do not.
CI token and GitHub Actions
Section titled “CI token and GitHub Actions”| Action | Granted to |
|---|---|
gate:evaluate |
viewer and above |
gate:policy:write |
editor and above |
gate:override |
project_admin and above |
Create the pipeline token with POST /api/v1/organizations/{orgID}/tokens, "role": "editor", the project_id and "actions": ["gate:evaluate", "change:record"]. That token can ask the gate and record changes and nothing else.
- name: Install cerbix CLI run: | curl -fL -o cerbix "https://github.com/teamlead-com/cerbix/releases/download/v0.3.8/cerbix_v0.3.8_linux_amd64" sudo install -m 0755 cerbix /usr/local/bin/cerbix
- name: Reliability gate env: CERBIX_URL: https://cerbix.example.com CERBIX_TOKEN: ${{ secrets.CERBIX_TOKEN }} run: | rc=0 cerbix gate check --project "${{ vars.CERBIX_PROJECT }}" --service "${{ vars.CERBIX_SERVICE }}" || rc=$? case "$rc" in 0) ;; 4) echo "::warning::No gate policy for this service" ;; *) exit "$rc" ;; esacExit 4 is your choice to make explicit: here it warns and continues. Verify the downloaded binary against SHA256SUMS from the same release.