Govern

One verdict before a workflow step runs.

Govern turns a deploy, a migration, an infrastructure change or an agent's action into an execution intent and asks Decionis about exactly that intent. The command runs only on an ALLOW whose single-use grant the step claimed. An ESCALATE can hold it for a person, and a BLOCK never starts it. Every run leaves a signed Decision Dossier, and nothing is decided locally.

Action
decionis/govern@v2
Binary
2.1.0
License
Apache-2.0
Checked
2026-09-26

Start in a repository

From the repository, one command installs the binary, checked against the release's SHA256SUMS, and runs govern init. That writes a shadow-mode workflow for the runner the repository uses and a DECIONIS_POLICY.md at its root. It keeps any file that exists, never touches git, and prints what is left: the key, the tenant and the commit.

curl -fsSL https://decionis.com/govern/install.sh | sh

Installer

The binary alone, for macOS and Linux.

curl -fsSL https://raw.githubusercontent.com/decionis/agent-safe-pipeline/master/govern/install.sh | sh

Homebrew

This repository is the tap. Homebrew 5 and later ask you to trust it once.

brew tap decionis/agent-safe https://github.com/decionis/agent-safe-pipeline && brew trust decionis/agent-safe && brew install govern

Go

The module tag is signed, and go.mod pins the toolchain.

go install github.com/decionis/agent-safe-pipeline/govern/v2/cmd/govern@v2.1.0

Each archive is built twice on its own platform and shipped only when both builds are the same bytes. Windows is a zip: check it against SHA256SUMS and put govern.exe on the path. To check a release's provenance:

gh attestation verify govern-<version>-<os>-<arch>.tar.gz --repo decionis/agent-safe-pipeline

Releases

GitHub Actions

Add the step to a workflow. It needs the workspace's API key as the secret DECIONIS_API_KEY and its tenant id as the variable DECIONIS_TENANT_ID. Until both exist, a shadow step is inert.

Shadow first

The command starts at once and the verdict is recorded beside it. Shadow never fails a build for the gate's sake. The comment needs pull-requests: write.

- uses: decionis/govern@v2
  with:
    api-key: ${{ secrets.DECIONIS_API_KEY }}
    tenant-id: ${{ vars.DECIONIS_TENANT_ID }}
    mode: shadow # the command starts at once; the verdict is recorded beside it
    action: production-deploy
    run: ./scripts/deploy.sh
    comment: "true" # the verdict on the pull request, updated in place

Then enforce

The command runs through the gate or not at all, so there is no if: to delete. It runs only on an ALLOW whose grant this step claimed.

- uses: decionis/govern@v2
  with:
    api-key: ${{ secrets.DECIONIS_API_KEY }}
    tenant-id: ${{ vars.DECIONIS_TENANT_ID }}
    action: production-deploy
    environment: production
    payload: '{ "service": "api" }'
    run: ./scripts/deploy.sh # runs only on an ALLOW whose grant this step claimed

Without run, the step is advisory: its decision output is ALLOW, ESCALATE or BLOCK, and fail-on says which of them fails it. With escalation: managed, or an approver or approver-role, an ESCALATE holds the step while Decionis orchestrates the approval, for at most the intent's five minutes.

decionis/govern@v2 runs Govern 2.0.0: the action pins the gate at one commit and moves only when that pin does. The 2.1.0 binary adds the Windows archive and the PowerShell and cmd shells.

decionis/govern@v1 is the earlier action, which spoke the evaluate-decision API with org-id and workflow-key; it stays on its own tags. v2 decides nothing locally and takes the organization as tenant-id, with the same API key.

Outputs

Any later step can read them. The same facts go to the run summary.

decisionALLOW, ESCALATE or BLOCK; empty when the authority was not asked
decision-id, dossier-id, dossier-urlThe decision, its Decision Dossier, and the dossier's authenticated path
verify-urlA page anyone can open to verify the decision, when the authority attached one
intent-id, intent-hashThe intent and its canonical SHA-256, what every record names
reason-codesThe authority's reason codes, comma separated
policy-versionThe policy version the decision was made under
policy-sha256, policy-pathThe repository policy file the intent carried
modeSHADOW or ENFORCEMENT
fail-closedtrue when no authoritative decision was reached and the gate refused
executed, claimed, exit-codeWhether the command ran, whether a grant was claimed for it, and how it exited
outcome, finalizationCOMMITTED, FAILED or INDETERMINATE, and whether Decionis RECORDED it or left it PENDING
badge-markdownA “Governed by Decionis” badge linking to the proof

Every input, flag and exit code

What a reviewer sees

With comment: "true", the gate posts one comment on the pull request and updates it in place on every run, so the thread holds one comment, not one per push. On GitLab the merge request gets it.

github-actions bot commented
DecionisBlocked

Govern · production-deploy — Blocked

Verdict
BLOCK
Mode
enforcement
Reasons
POLICY_ACTION_AUTO_REJECT
Command
not run

Verify this decision → — the signed Decision Dossier.

Enforcing — the command runs only on an ALLOW with a claimed grant.

Governed by Decionis — one verdict before a deploy, a migration or an infrastructure change runs, with a signed record of it. govern

An example: a BLOCK in enforcement. The live comment also names the policy version, the policy file's revision, the intent hash, the decision and the dossier.

The README badge

The badge-markdown output is this badge linking to the run's proof. For a README, the same badge links to Govern:

Governed by Decionis
[![Governed by Decionis](https://img.shields.io/badge/Governed%20by-Decionis-6D28D9?logo=shield&logoColor=white)](https://github.com/decionis/agent-safe-pipeline/tree/master/govern)

GitLab CI, Jenkins and any runner

The action is one wrapper around one binary. Elsewhere, run it yourself: the same flags, with the runner detected from its own variables. The arguments after -- are quoted for the shell in use, so the command runs as typed.

govern run --action production-deploy --environment production \
  --payload '{"service":"api"}' -- ./scripts/deploy.sh
GitHub ActionsDetected by:GITHUB_ACTIONSFacts:GITHUB_*Outputs:GITHUB_OUTPUT, the step summaryComment:the pull request, with GITHUB_TOKEN (pull-requests: write)
GitLab CIDetected by:GITLAB_CIFacts:CI_*Outputs:a dotenv report (govern.env, or GOVERN_OUTPUT_FILE)Comment:the merge request, with GOVERN_GITLAB_TOKEN (api scope)
JenkinsDetected by:JENKINS_URL and BUILD_NUMBERFacts:JOB_NAME, BUILD_URL, GIT_*Outputs:a properties file (govern.env, or GOVERN_OUTPUT_FILE)Comment:—
anything elseDetected by:—Facts:GOVERN_REPOSITORY, GOVERN_SHA…Outputs:GOVERN_OUTPUT_FILE, when setComment:—

How it works

Govern: the workflow gate

Rendered from docs/govern.md in decionis/agent-safe-pipeline at v0.3.5 (b4d5c9e), synced 2026-09-26. The repository is the source of truth. View this file on GitHub

A deploy, a migration, an infrastructure change, a release, a merge an agent asked for: the step that does it runs in a workflow runner, with credentials the runner holds, on a trigger that a commit, a comment or an agent produced. Govern puts one Decionis verdict in front of that step. The step becomes an execution intent, Decionis decides on exactly that intent, the command runs only on an ALLOW whose single-use grant the gate claimed first, and what happened is finalized into a signed Decision Dossier. It is the same execution contract the runtime speaks in front of an HTTP service, at a different place in the path: before a command in a pipeline, on GitHub Actions, GitLab CI, Jenkins or any runner that can run a binary.

Nothing is decided locally. There is no policy engine in the binary, no cached verdict and no "allow when unsure": a Decionis that cannot be reached, or an answer outside the contract, is a refusal, and the command does not run. Shadow is the exception by design, and it decides nothing either: it starts the command at once and records what Decionis would have said beside it.

What one governed step is

capture   the step as agent-safe.intent/1: who runs it, what it does, on what, where, until when
decide    POST /v1/authority/enforce-and-bind → ALLOW | ESCALATE | BLOCK, a Decision Dossier, on ALLOW a grant
claim     POST /v1/execution/claim-token, once, immediately before the command
run       the command, with the decision's identifiers and the claim attestation in its environment
finalize  POST /v1/execution/finalize-token: COMMITTED, FAILED or INDETERMINATE, from the exit code

The intent is the Agent-Safe Intent v1 binding: the action's type and resource, its parameters (the step's payload), the actor (the workflow's identity, type WORKFLOW), the downstream target (the runner as the system, the action as the operation, the deployment environment, the run's URL as the endpoint), and a context that carries what the runner knows about the run (repository, ref, commit, actor, run id and URL, workflow, job) and the repository's policy file by path and SHA-256. The hash of its canonical form is what every later record names; the conformance vectors hold the Go binary to the reference implementation byte for byte.

Verdict Enforcement Shadow
ALLOW the grant is claimed, the command runs, the outcome is finalized recorded; the command already ran
ESCALATE the step fails with the command never started, or holds while Decionis orchestrates the approval (below) recorded
BLOCK the step fails with the command never started recorded
refusal AUTHORITY_UNAVAILABLE, AUTHORITY_RESPONSE_INVALID, … — the step fails with the command never started a notice; the exit code is the command's

A step that wraps no command is advisory: its decision output feeds a later step's if:, and fail-on says which verdicts fail it. Wrapping the command is the enforcing shape: there is no if: to delete.

Holding a step for a person

With escalation: managed (or an approver or approver-role), an ESCALATE does not end the step. Decionis opens a managed escalation, Presence verifies the approver, and the step polls /v1/authority/escalations/{id} until the escalation ends: GRANT_READY carries the grant and the command runs; a rejection, a cancellation or an expiry is a BLOCK. The wait is bounded by the intent's lifetime, five minutes at most, and a step that runs out of time runs nothing. Human approval and Presence evidence say what the approver's evidence is and why the gate never sees it.

What the record shows

Every run writes the same facts to every surface the runner has: outputs a later step reads (decision, decision-id, dossier-id, intent-hash, outcome, finalization, …), a run summary, a pull-request or merge-request comment updated in place, and, when asked, a JSON record (agent-safe.govern-report/1). The Decision Dossier is read back with the run's own key after the decision, and its signature material is reported; verify-url carries the public page when Decionis attaches one. The command itself receives DECIONIS_DECISION_ID, DECIONIS_DOSSIER_ID, DECIONIS_INTENT_HASH and DECIONIS_CLAIM_ATTESTATION, the authority's own signed statement that this claim was made, which a system of record can verify before it acts (Verifying Provider Profile). The grant never leaves the gate.

Where to start

govern init writes a repository's starter files and nothing else: a shadow-mode workflow for the runner the tree is set up for (.github/workflows/decionis-govern.yml, .gitlab/ci/decionis-govern.yml to include, or jenkins/decionis-govern.groovy to paste) and a DECIONIS_POLICY.md at the root; it keeps any file that exists and never touches git. Shadow first: until the key and tenant exist the step is inert, then it records; the verdicts show on pull requests; then mode: enforce and the command to gate. Shadow mode is the same idea for the runtime.

The binary ships with the repository's releases for macOS, Linux and Windows, built twice per platform and shipped only when identical, listed in SHA256SUMS, attested, described by a CycloneDX SBOM read from the shipped executables themselves, and pinned by a signed govern/v<version> module tag; the GitHub action downloads the archive its commit names and verifies it before running, or builds the same bytes from that commit when no archive exists yet. Govern's README has the install paths, every setting and output, the exit codes, and the runners' surfaces.

What it does not do

Govern does not read the command's output as decision input, does not send a credential or a person's text to Decionis, does not decide when Decionis cannot, and does not stop a step that runs outside it: a workflow with the gate on one step and the deploy on another has governed the first. The trust boundary is the same as the runtime's; the runner is where the gate runs, not what it trusts.