Skip to main content
RelixQ
Menu

Governance, policies and exceptions

The policy engine that renders release-gate decisions, the TTL-bounded exception workflow, and the tamper-evident audit chain that makes both defensible to an auditor.

Gate verdicts
4
Exception model
TTL-bounded
Audit chain
Verifiable
Browse the Developer Guide

Why governance is a first-class surface

A migration program that cannot answer "who accepted this risk, on what basis, and until when?" is not a program — it is a backlog. Enterprise separates three concerns that are usually tangled together: policies (the rules), exceptions (documented deviations with an expiry), and the audit chain (the tamper-evident record of both).

The policy engine

Policies are stored per project, compiled, and evaluated against a submitted summary of findings and exposures. The evaluation is synchronous and stateless — the CI endpoint persists nothing — which is what makes it safe to call on every pull request.

EndpointPurpose
GET/POST /api/v1/projects/{projectId}/policiesList and create project policies
GET/PUT/DELETE /api/v1/policies/{policyId}Read, replace, and delete a policy
GET /api/v1/templatesList built-in policy templates
POST /api/v1/projects/{projectId}/policies/from-templateInstantiate a policy from a template
POST /api/v1/projects/{projectId}/policies/simulateDry-run policies against current findings before enabling them
POST /api/v1/projects/{projectId}/policy/gate-decisionThe synchronous CI gate decision
GET /api/v1/projects/{projectId}/policy-decisions/aggregateAggregate gate outcomes — honest-empty until decisions exist

The gate decision folds every matching policy into one verdict: allow maps to pass, warn and require-review map to warn, and block maps to fail. A governance-side score-regression block forces a fail regardless of the folded verdict.

Each submitted finding and exposure carries an isNew flag computed by the same base-versus-head fingerprint diff the CLI uses, so on_new: block / on_existing: allow policies do not fail your pull request on pre-existing debt.

The exception workflow

An exception is a documented, time-bounded acceptance of a real finding. It is not a way to hide one. Exceptions are requested against a finding, reviewed by a human, and expire on a date.

EndpointPurpose
POST /api/v1/findings/{findingId}/exceptionsRequest an exception for a finding
GET /api/v1/exceptions/{exceptionId}Exception detail
GET /api/v1/projects/{projectId}/exceptionsList a project exceptions
GET /api/v1/projects/{projectId}/exceptions/activeList currently active exceptions — what the release gate reads
POST /api/v1/exceptions/{exceptionId}/approveApprove
POST /api/v1/exceptions/{exceptionId}/rejectReject
POST /api/v1/exceptions/{exceptionId}/extendExtend the expiry
POST /api/v1/exceptions/{exceptionId}/revokeRevoke an active exception

In the release gate, an approved and non-expired exception matching every gating exposure downgrades that block to a warn, with the exception id cited in the decision reason. Expired exceptions are themselves a warn-level gate condition, so lapsed acceptances surface rather than silently reverting to a block.

The tamper-evident audit chain

Every triage decision, exception action, and policy change is written to a hash-chained audit trail, verifiable independently of the application.

EndpointPurpose
GET /api/v1/audit/verify-chainVerify the integrity of the audit hash chain
GET /api/v1/projects/{projectId}/auditGovernance audit trail for a project

This composes with the immutability rule from the finding contract: findings are immutable facts, and triage state lives on separate rows. You never edit a finding, you supersede it — so the trail reconstructs what was known and decided at each point in time, not just the current state.

A workable operating model

  1. 01

    Start in simulate mode

    Dry-run a candidate policy against your current findings. You will see exactly what it would have blocked before it blocks anything.

  2. 02

    Roll out in warn mode

    Run the release gate with --mode warn. The check run concludes neutral and can never block a merge, but the verdict and its reasons appear on every pull request.

  3. 03

    Adopt a baseline

    Commit .relixq-baseline.json so the existing backlog is accepted and only new debt gates.

  4. 04

    Flip to block, and add the check to branch protection

    Only --mode block can fail a merge, and only when the check is in your required status checks.

  5. 05

    Review exceptions on a cadence

    Track the "expiring within 14 days" count on the Security dashboard. An exception that keeps getting extended is a migration decision nobody has made yet.

Related pages