Skip to content

operator runbooks

Intended Documentation

Author a Policy

Author an Intended authority policy as a governed draft — structure a policy set, scope its rules and effects, create the draft through the Policies API or console, and submit it for review. No fictional CLI command group.

Author a Policy#

A policy is what the authority runtime consults to reach a decision. This runbook walks you through authoring one as a governed draft: you structure a policy set, scope its rules, create the draft, and submit it for review. Promotion to a live version is covered in Deploy and Rollback.

There is no `intended policy …` command

Policy authoring is driven through the Policies API (/authority/policies and the /policy/* lifecycle) and the console policy editor. The Intended CLI is flat and hyphenated; the only policy-related CLI verbs are deploy-policy (= policy-pack-install) and policy-pack-inspect. There is no intended policy init / validate / test / review — those commands do not exist. A first-class authoring CLI is Roadmap.

Prerequisites#

  • An API key (intended_live_…; legacy mrt_… also accepted) or a portal session with the authority:policy:write permission. Authoring and every lifecycle transition require it.
  • The tenant id you are authoring for. Every policy call is tenant-scoped: the credential's tenant, the x-tenant-id header, and the body tenantId must all agree, or the request is rejected with 403 TENANT_MISMATCH.

How a policy decides#

Before you write rules, understand how they combine. The engine loads the tenant's policy sets, matches rules by their bindings and conditions, and resolves the outcome most-severe-wins (DENY > REQUIRE_APPROVAL > ESCALATE > ALLOW). When no rule matches, it defaults to DENY. Interpretation-layer (LIM) signals can only upgrade severity — never soften it. The risk score is baseRiskScore + (privileged?20) + (production?20) + (sensitive?15), capped at 100.

Warning

Always keep defaultDecision: "DENY" for production policy sets. A default-allow set creates an unaudited authorization gap — any binding you forgot to cover would silently pass.

Policy set structure#

A policy set is the unit the engine evaluates. It is JSON, not YAML, and is carried inside the draft you create.

A policy set
json
{
  "name": "prod-release-controls",
  "description": "Require approval for production release dispatches",
  "enabled": true,
  "version": 1,
  "defaultDecision": "DENY",
  "rules": [
    {
      "id": "rule_prod_release",
      "description": "Approval-gate prod release workflows",
      "intentTypes": ["workflow.dispatch"],
      "actions": ["dispatch release workflow"],
      "targetSystems": ["acme/platform-api"],
      "tools": ["github-actions"],
      "actorRoles": ["service", "deployer"],
      "environments": ["production"],
      "effect": {
        "decision": "REQUIRE_APPROVAL",
        "approvalRequirement": { "approverRoles": ["approver"], "minApprovals": 1 }
      }
    }
  ]
}

Fields#

FieldRequiredDescription
nameYesHuman-readable policy-set name.
versionYesInteger version. Increment when the rule set changes.
enabledYesWhether the engine loads this set.
defaultDecisionYesThe decision when no rule matches. Use DENY in production.
rules[]YesOne or more rules; see below.
descriptionNoWhy this set exists.

Rule bindings and effect#

A rule matches an evaluation when every binding it declares is satisfied. Bindings left unset are wildcards.

BindingMatches against
intentTypesThe compiled intent type.
actionsproposedAction on the intent.
targetSystemsThe targetSystem being acted on.
toolsThe adapter/tool in context (github-actions, jira, …).
actorRolesThe actor's role.
environmentsThe runtime environment (production, staging, …).

A rule may also carry confidenceThresholds, budgetCeilings, and conditions. Its effect is { decision, approvalRequirement?, escalation? }, where decision is ALLOW / DENY / REQUIRE_APPROVAL / ESCALATE.

Step 1 — Create the draft#

Author through the governed lifecycle: create a draft rather than upserting the live set directly. Every lifecycle step appends to the audit ledger and requires authority:policy:write.

Open a draft

POST /policy/drafts with the policy set inline.

bash
curl -X POST https://api.intended.so/policy/drafts \
  -H "Authorization: Bearer $INTENDED_API_KEY" \
  -H "x-tenant-id: tenant_acme_prod" \
  -H "Content-Type: application/json" \
  -d '{
    "tenantId": "tenant_acme_prod",
    "policyPackName": "prod-release-controls",
    "title": "Require approval for prod release dispatch",
    "createdBy": "user_admin",
    "policySet": {
      "name": "prod-release-controls",
      "version": 1,
      "defaultDecision": "DENY",
      "enabled": true,
      "rules": []
    }
  }'

The response is 201 with the new draft, including its id.

Fill in the rules

Edit the draft with PUT /policy/drafts/:id, supplying the complete policySet. Map each binding to the intents you want to govern, and choose an effect (REQUIRE_APPROVAL to gate, DENY to block, ESCALATE to route to a human, ALLOW to permit).

bash
curl -X PUT https://api.intended.so/policy/drafts/draft_123 \
  -H "Authorization: Bearer $INTENDED_API_KEY" \
  -H "x-tenant-id: tenant_acme_prod" \
  -H "Content-Type: application/json" \
  -d '{
    "tenantId": "tenant_acme_prod",
    "updatedBy": "user_admin",
    "policySet": { "name": "prod-release-controls", "version": 1, "defaultDecision": "DENY", "enabled": true, "rules": [ { "id": "rule_prod_release", "intentTypes": ["workflow.dispatch"], "environments": ["production"], "effect": { "decision": "REQUIRE_APPROVAL", "approvalRequirement": { "approverRoles": ["approver"], "minApprovals": 1 } } } ] }
  }'

Tip

Prefer the console policy editor for interactive authoring — it edits the same drafts and shows the impact summary inline. Use the API when you want authoring under version control or in CI.

Step 2 — Submit for review#

When the rules are complete, submit the draft for review. The reviewer who approves must hold authority:policy:write; separation of duties is a review-process control, not enforced by the endpoint.

Submit to review

bash
curl -X POST https://api.intended.so/policy/drafts/draft_123/review \
  -H "Authorization: Bearer $INTENDED_API_KEY" \
  -H "x-tenant-id: tenant_acme_prod" \
  -H "Content-Type: application/json" \
  -d '{
    "tenantId": "tenant_acme_prod",
    "submittedBy": "user_admin",
    "comments": "Gating prod release dispatch behind one approval.",
    "simulationRunId": "sim_abc123"
  }'

Attach a simulationRunId so the reviewer sees the projected impact alongside the change. Run that simulation first — see Simulate Impact.

Reviewer approves or rejects

A reviewer calls POST /policy/drafts/:id/approve or POST /policy/drafts/:id/reject with { tenantId, reviewerId, comments? }. Approval moves the draft to a deployable state; it does not deploy. Deploying is a separate, explicit step.

Failure modes#

StatusCodeCause
400validationDraft body or policySet failed schema.
400TENANT_MISMATCHBody tenantIdintent/header tenant.
401UNAUTHORIZEDNo valid credential.
403FORBIDDENMissing authority:policy:write.
403TENANT_MISMATCHBody tenant ≠ credential tenant.
404not foundDraft id does not exist for this tenant.

Common mistakes#

MistakeConsequenceFix
defaultDecision: "ALLOW" in prodUncovered bindings pass silentlyAlways default to DENY.
Upserting the live set directlyNo review, weak audit trailAuthor through /policy/drafts.
Over-broad bindings (all wildcards)Rule fires on unintended intentsScope targetSystems / environments / actorRoles explicitly.
Submitting without a simulationReviewer can't see impactAttach simulationRunId to the review.

Next steps#

Author a Policy | Intended