Generating Your First Rego Policy with IssueOps
This post walks through the end-to-end IssueOps workflow for authoring and activating an Octopus Deploy Platform Hub policy using the GitHub Copilot Coding Agent. You’ll open a policy request as a GitHub issue, watch the agent author the OCL file and what-if report, review and merge the PR, then activate the policy with the Publisher agent.
By the end, you’ll have a live policy in Platform Hub and a clear picture of what the agent gets right, where you need to review carefully, and what the workflow looks like at each handoff.
This post assumes the platform-hub repo is set up as described in Setting Up Your Platform Hub Repo. If you haven’t done that yet, start there.
Prerequisites
- Platform-hub repo configured with the Policy Author and Publisher agent definitions
- GitHub Copilot with agent mode enabled and access to the platform-hub repo
- Octopus Deploy Platform Hub with the repo connected as the version control source
- At least one project slug handy — you’ll need the exact slug, not the display name
Phase 1: Open a policy issue
Navigate to your platform-hub repo on GitHub and open a new issue. Select the 🐙 Add Octopus Deploy Policy template.
TODO: Screenshot — GitHub “New Issue” page showing the issue template selection screen with the 🐙 Add Octopus Deploy Policy template visible
Fill in the two required fields:
Describe the policy — be specific. The agent maps your description directly to input.* fields in the Platform Hub schema. The more concrete you are, the more accurately the Rego reflects your intent. Avoid describing two independent rules in one issue — the agent will flag this and ask you to split them, which adds round-trips.
Good: Require at least one enabled manual intervention step for deployments to the production environment. The step must not be in the skipped steps list.
Too vague: Improve production deployment safety.
Two rules in one: Require manual intervention AND block deployments from non-main branches.
Project(s) in scope — enter the exact Octopus project slug, not the display name. Slugs are lowercase with hyphens. If you’re unsure, check the project URL in Octopus — it’s in the path.
For a single project: payments-service
For multiple: payments-service, payments-legacy
TODO: Screenshot — Issue template filled in with a concrete policy description and project slug, before submission
Submit the issue. GitHub automatically applies the 🐙 policy and 🤖 copilot labels, which trigger the Copilot Coding Agent.
Phase 2: Copilot authors the policy
The Copilot Coding Agent picks up the issue and runs the Policy Author agent. This takes a few minutes — the agent is doing real work: parsing intent, checking existing policies for overlap, fetching live project data from Octopus via MCP, writing the OCL file, and running static evaluation against each in-scope project’s deployment process.
TODO: Screenshot — GitHub issue showing the Copilot agent status indicator (“Copilot is working on this”) with a link to the agent session log
When it’s done, the agent opens a pull request containing:
- The new OCL file at
.octopus/policies/<package_name>.ocl - The what-if report at
octopus-agent/output-<timestamp>/reports/<package_name>.md - A PR comment with the full what-if report inline
TODO: Screenshot — GitHub PR opened by the Copilot agent, showing the two files changed (OCL and report) and the PR description summarising what was authored
Phase 3: Review the PR
This is the most important step in the workflow. The PR is where human judgment replaces agent speed.
Read the what-if report first
The what-if report is posted as a PR comment. Don’t read the OCL file first — read the report. It tells you what the policy will actually do to real projects before you look at the Rego that implements it.
The report has three sections:
Pass — projects whose current deployment processes already satisfy the policy. These projects will not be affected when the policy activates.
Will Be Affected — projects that will receive a warning or block. The report includes the specific reason and what the team needs to fix. For a warn policy, deployments proceed but the violation is logged. For a block policy, deployments are stopped until the process is corrected.
Unable to Evaluate Statically — conditions that depend on runtime state. The most common case is input.SkippedSteps: the agent can verify that a required step exists in the deployment process, but can’t know whether a user will choose to skip it at deployment time. The report notes what it checked and why it can’t give a definitive answer.
Here’s an example of a real what-if report output for a manual intervention policy scoped to payments-service and payments-legacy:
1
2
3
4
5
6
7
8
9
10
11
Verdict: 1 project will be affected ⚠️ | 1 project will pass ✅ | 0 projects unable to evaluate ❓
Projects: Pass (1)
| Project | Reason |
|------------------|-------------------------------------------------------------------------------------------------|
| payments-legacy | Has an enabled Octopus.Manual step ("Prod Manual intervention") scoped to Production. |
Projects: Will Be Affected (1)
| Project | Violation | What to fix |
|------------------|--------------------------------------------------------|----------------------------------------------------------|
| payments-service | No Octopus.Manual step present in deployment process. | Add an enabled manual intervention step to the process. |
If the “Will Be Affected” list includes projects you didn’t intend to touch, or misses projects you expected to see, that’s a scope issue in the Rego — leave a review comment and the agent will iterate.
TODO: Screenshot — PR comment showing the full what-if report with the three-section table format, verdict line at the top
Then read the OCL file
Once you’re satisfied the impact is correct, read the generated OCL file. Check:
- Package name matches the filename (no dashes, underscores only)
- Scope block correctly targets the right projects and execution type (
not input.Runbookfor deployment-only policies) - Conditions block has a
default result := {"allowed": false}with no inlineaction— theviolation_actionheader governs the default behaviour violation_reasonis human-readable and actionable, not generic
TODO: Screenshot — GitHub PR file diff showing the generated OCL file with the scope and conditions Rego blocks visible
Common things to push back on in review:
- Slugs that look invented rather than real — the agent should have fetched these from Octopus via MCP, but verify them against your actual environment
- A
default evaluate := truein scope when you only want a subset of projects — this catches everything and can’t be easily narrowed after the fact - Missing
not input.Runbookguard on a policy that’s meant for deployments only — without it, the policy also evaluates runbook runs and may cause evaluation errors if it referencesinput.Release
Approve and merge
When the report and OCL look correct, approve and merge the PR to main. The Policy Publisher agent won’t run until the commit is on main and pushed — that’s the gate.
TODO: Screenshot — GitHub PR approval UI with the merge button, showing the branch protection check passing
Phase 4: Activate with the Policy Publisher
After the merge, trigger the Policy Publisher agent. In a Copilot Chat session on the repo, send:
1
Publish policy: require_manual_intervention_production
Replace require_manual_intervention_production with the package name of the policy you just merged (it’s the OCL filename without .ocl).
The agent runs its pre-flight checks in sequence:
- Confirms the OCL file exists at
.octopus/policies/require_manual_intervention_production.ocl - Confirms the scope block has an active
evaluate ifclause — not fully disabled - Confirms the file is committed, not staged
- Queries the Octopus Platform Hub API to discover the configured default branch, then checks that your commit has been pushed there — hard stops if not
If all checks pass, it resolves the current policy version (or starts at 1.0.0 if this is the first publish), calls the publish endpoint, then calls the activate endpoint.
TODO: Screenshot — Copilot Chat session showing the publisher agent output with the pre-flight check results and the final publish summary table
The output looks like this:
1
2
3
| Policy | Previous Version | Published Version | Status |
|-------------------------------------------|-----------------|-------------------|--------------------------|
| require_manual_intervention_production | none | 1.0.0 | ✅ Published & Activated |
The policy is now live. Any deployment that falls within its scope will be evaluated against the conditions Rego.
TODO: Screenshot — Octopus Platform Hub policies list showing the newly activated policy with its version number and active status
Phase 5: Verify in Octopus
Trigger a deployment on an in-scope project to confirm the policy evaluates as expected.
For a project in the “Will Be Affected” category from the what-if report, the task log should show a policy violation in warn mode — the deployment proceeds but the violation is recorded.
TODO: Screenshot — Octopus deployment task log showing the policy evaluation result, with the violation reason text visible
For a project in the “Pass” category, no violation appears. Check the audit log under Configuration → Audit, filtered by Compliance Policy Evaluated, to confirm the policy ran and passed — an absence of a violation in the task log doesn’t mean the policy didn’t run.
TODO: Screenshot — Octopus audit log filtered to “Compliance Policy Evaluated” events, showing entries for the deployment you just triggered
Known limitations / what I’d do differently
The agent can’t verify slugs it can’t look up. The Policy Author agent fetches project slugs from Octopus via MCP, but it can’t always resolve environment slugs reliably if environments have non-obvious slug values. The safest practice is to check the verbose task log on a recent deployment for the exact input.Environment.Slug value, and verify it against what the agent put in the scope block before merging.
Multi-project policies are always warn. The agent enforces this automatically — if you put two or more project slugs in the issue, it downgrades violation_action to warn regardless of what you requested. This is by design to limit blast radius. To use block on multiple projects, author separate single-project policies and promote them to block individually after validating each one in warn mode.
The PR iterate loop can take a few minutes per round-trip. If the reviewer pushes back on the scope or conditions, the agent needs to re-run the full four-phase flow to produce an updated OCL and a new what-if report. For complex policies, budget for one or two review cycles before merge.
What-if analysis is static. The report tells you what will happen based on current deployment process definitions. If a team changes their process after the policy is active — removing a required step, for example — the next deployment will trigger a violation that the what-if report didn’t predict. The report is accurate at the moment it’s generated, not a permanent guarantee.
The publisher’s version increment is patch-only. Every publish bumps the patch version (1.0.0 → 1.0.1). There’s no built-in concept of a breaking change or a major version bump for policies. If you make a significant scope change — for example, extending a block policy from one project to five — the version history won’t distinguish that from a minor conditions tweak. A naming convention or PR label for significant changes helps maintain clarity in the audit trail.