The policy vocabulary

Everything a policy can say.

A policy is a small decision table. You declare the actors and the resources, then write allow and deny rules over a fixed set of effects. The typed library only authors; the compiled IR decides. This page is the whole vocabulary — every word you can use, and what each one means.

Author in

Read one policy, line by line.

This policy says: the coding agent may never force-push main, and may push main only with a human approval.

policy.go
// fluent builder → canonical IR
il.Policy("repository-policy.v1").
  Actor("agent").
  Branch("main", "repo://branch/main").
  Deny("deny-force-push-main").By("agent").
    To(il.ForcePush).On("main").
    Because("force-pushing main is not allowed").Add().
  Allow("push-main").By("agent").
    To(il.Push).On("main").
    Requiring(il.HumanApproval("release-main")).
    Because("pushing main needs human approval").Add().
  Emit()

What each line means

policy(id) — names the policy with a stable id.
actor("agent") — declares who a rule is about. Actor ids are free-form.
branch(id, "repo://…") — declares a resource. A branch matches exactly.
deny(id) — a named rule that refuses an effect.
allow(id) — a named rule that permits an effect.
by(actor).to(effect).on(resource) — the rule's subject, verb, and object.
because("…") — the reason, returned with every decision.
requiring(humanApproval(…)) — an allow that needs evidence. Without it the decision is require, and the effect fails closed.
emit() — compiles to the canonical IR: the decision table the engine runs.
The verbs

Effects — the actions a rule controls.

Every rule points at exactly one effect. The set is small and named; a request for anything outside it is a fault, not a silent allow.

filesystem
Read
filesystem.read
Read a file or tree.
Write
filesystem.write
Create or modify a path.
Delete
filesystem.delete
Remove a path.
RenameFrom
filesystem.rename_from
Move a path away from a source.
RenameTo
filesystem.rename_to
Move a path onto a target.
process
Execute
process.execute
Run an executable class.
artifact
Publish
artifact.publish
Release a built artifact.
version control
Push
vcs.push
Push commits to a branch.
ForcePush
vcs.force_push
Overwrite branch history.
Merge
vcs.merge
Merge a branch into another.
Tag
vcs.tag
Create or move a tag.
network · secret · deploy
NetworkEgress
network.egress
Reach an outside endpoint.
SecretRead
secret.read
Read a named secret.
Deploy
deploy.release
Ship a release to an environment.
The objects

Resources — what a rule points at.

You declare each resource once with an id and a URI, then reference it by id with .on(id). The kind sets how the URI matches.

File
repo://out/result.json
A single path. Matches exactly.
Tree
repo://generated/**
A subtree. Trailing ** matches by prefix.
Process
proc://node
An executable class. Matches exactly.
Branch
repo://branch/main
A single branch. Matches exactly.
Tag
repo://tag/v*
A tag name or pattern.
Release
repo://release/1.4.0
A named release channel.
Endpoint
net://api.github.com
A network host.
Secret
secret://OPENAI_API_KEY
A named secret.

URIs are a naming convention. repo://, net://, and secret:// just give each resource a stable name; the kind decides how it matches.

The grammar

Rules and outcomes.

You author two kinds of rule. The engine returns one of four outcomes. Rules are tried in order, first match wins, and anything unmatched is denied.

rules you write
Allow Deny Audit — record, never block
outcomes the engine returns
allow — a matching allow fired, evidence satisfied deny — a deny matched, or nothing matched require — allow matched, evidence missing fault — request outside the vocabulary

Fail closed. Requirements attach only to allow rules. If an allow matches but its evidence is absent, the outcome is require and the effect does not happen.

The conditions

Requirements — evidence an allow can demand.

Attach one or more to an allow with .requiring(…). Every requirement must be satisfied by the request's claimed evidence, or the effect fails closed.

HumanApproval(id)
A person granted the named approval for this request.
VerifiedPublisher(actor)
The effect comes from a verified publisher identity, not the agent.
ReceiptStatus(schema, status)
A receipt of that schema reports that status (for example, tests green).
StagedHashMatch()
The staged bytes match the hash the request claims.
PolicyHashMatch()
The claimed policy hash equals the live policy hash.
TargetHashMatch()
The prior target matches the hash the request claims.
TwoPersonApproval(id, n)
At least n distinct people granted the approval.
TimeWindow(start, end)
The request happens inside an allowed time window.
SignedBy(key)
The request carries a signature from a trusted key.
The subjects

Actors — who the rules are about.

Actors are free-form ids you declare with .actor(id) and bind to rules with .by(id). Give the coding agent one name and every trusted automation its own — then allow each only what it needs.

agent publisher sdk-generator build-runner release-bot

A "verified publisher" is just a separate actor whose allow rule carries strong requirements — a pattern, expressed in the same words.

More of each

The same words, bigger policies.

Every policy below uses only the vocabulary above — more actors, more effects, more requirements.

Protect generated files

The coding agent cannot change generated code. Only a verified generator may refresh it, and only with evidence.

generated.go
il.Policy("generated.v1").
  Actor("agent").Actor("sdk-generator").
  Tree("generated", "repo://generated/**").
  Deny("agent-no-write").By("agent").
    To(il.Write).On("generated").
    Because("the build owns generated files").Add().
  Allow("generator-refresh").By("sdk-generator").
    To(il.Publish).On("generated").
    Requiring(il.VerifiedPublisher("sdk-generator"),
              il.ReceiptStatus("codegen.v1", "green")).
    Because("only a verified generator may refresh them").Add().
  Emit()

Guard a release

A release ships only through the release bot, with two approvals, inside work hours.

release.go
il.Policy("release.v1").
  Actor("release-bot").
  Release("ver", "repo://release/*").
  Allow("guarded-release").By("release-bot").
    To(il.Deploy).On("ver").
    Requiring(il.TwoPersonApproval("ship", 2),
              il.TimeWindow("09:00", "17:00")).
    Because("releases need two approvals in work hours").Add().
  Emit()

Lock down secrets and the network

The coding agent may not read secrets or reach the network. Both fail closed.

egress.go
il.Policy("egress.v1").
  Actor("agent").
  Endpoint("gh", "net://api.github.com").
  Secret("key", "secret://OPENAI_API_KEY").
  Deny("no-egress").By("agent").
    To(il.NetworkEgress).On("gh").
    Because("the agent may not call the network").Add().
  Deny("no-secret-read").By("agent").
    To(il.SecretRead).On("key").
    Because("the agent may not read secrets").Add().
  Emit()
Author your first policy

Scaffold, compile, decide.

init writes a runnable policy module. compile builds the canonical IR. decide runs any request through it.

scaffold a policy
$ interlock init --template main-branch
build the IR
$ interlock compile -o policy.json
run a request
$ interlock decide --request request.json
check your setup
$ interlock doctor
Apache-2.0 licensed github.com/operatorstack/interlock Author in Go, TypeScript, Python, or Rust