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.
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 | shThe binary alone, for macOS and Linux.
curl -fsSL https://raw.githubusercontent.com/decionis/agent-safe-pipeline/master/govern/install.sh | shThis 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 governThe module tag is signed, and go.mod pins the toolchain.
go install github.com/decionis/agent-safe-pipeline/govern/v2/cmd/govern@v2.1.0Each 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-pipelineAdd 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.
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 placeThe 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 claimedWithout 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.
Any later step can read them. The same facts go to the run summary.
| Output | Meaning |
|---|---|
decision | ALLOW, ESCALATE or BLOCK; empty when the authority was not asked |
decision-id, dossier-id, dossier-url | The decision, its Decision Dossier, and the dossier's authenticated path |
verify-url | A page anyone can open to verify the decision, when the authority attached one |
intent-id, intent-hash | The intent and its canonical SHA-256, what every record names |
reason-codes | The authority's reason codes, comma separated |
policy-version | The policy version the decision was made under |
policy-sha256, policy-path | The repository policy file the intent carried |
mode | SHADOW or ENFORCEMENT |
fail-closed | true when no authoritative decision was reached and the gate refused |
executed, claimed, exit-code | Whether the command ran, whether a grant was claimed for it, and how it exited |
outcome, finalization | COMMITTED, FAILED or INDETERMINATE, and whether Decionis RECORDED it or left it PENDING |
badge-markdown | A “Governed by Decionis” badge linking to the proof |
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.
Govern · production-deploy — Blocked
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
The badge-markdown output is this badge linking to the run's proof. For a README, the same badge links to Govern:
[](https://github.com/decionis/agent-safe-pipeline/tree/master/govern)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| Runner | Detected by | Facts | Outputs | Comment |
|---|---|---|---|---|
| GitHub Actions | Detected by:GITHUB_ACTIONS | Facts:GITHUB_* | Outputs:GITHUB_OUTPUT, the step summary | Comment:the pull request, with GITHUB_TOKEN (pull-requests: write) |
| GitLab CI | Detected by:GITLAB_CI | Facts:CI_* | Outputs:a dotenv report (govern.env, or GOVERN_OUTPUT_FILE) | Comment:the merge request, with GOVERN_GITLAB_TOKEN (api scope) |
| Jenkins | Detected by:JENKINS_URL and BUILD_NUMBER | Facts:JOB_NAME, BUILD_URL, GIT_* | Outputs:a properties file (govern.env, or GOVERN_OUTPUT_FILE) | Comment:— |
| anything else | Detected by:— | Facts:GOVERN_REPOSITORY, GOVERN_SHA… | Outputs:GOVERN_OUTPUT_FILE, when set | Comment:— |
Complete workflows in the repository, each ready to copy.
How it works
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.
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.
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.
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.
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.
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.