Skip to content
v0.3.8GitHub

Release gate

Verified against cerbix 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.

Terminal window
export CERBIX_URL=https://cerbix.example.com
export 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.

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 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:

  1. A clause assigned ignore contributes nothing, even when its fact is unavailable.
  2. Any known, matching block clause → BLOCK, whatever else is unknown.
  3. Otherwise any unavailable block or warn clause → UNKNOWN.
  4. Otherwise any matching warn clause → WARN.
  5. Otherwise ALLOW.

UNKNOWN means a fact the policy depends on could not be answered. It is reported as UNKNOWN whatever the action.

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.

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.

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.

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}.

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.

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" ;;
esac

Exit 4 is your choice to make explicit: here it warns and continues. Verify the downloaded binary against SHA256SUMS from the same release.