Home / Stories / Your policy, honoured
Story · Governance Live

Your rules, written once, honoured on every run.

Every estate has rules nobody wrote down: that namespace belongs to another team, that chart is pinned on purpose, our PodDisruptionBudgets are percentages. The assistant reads them from two places you control, a POLICY.md at the root of the repository and annotations on individual objects, and shapes or withholds its proposals to match.

Starts fromA POLICY.md in the repository, and skip annotations on the objects themselves.
Looks atTyped settings in the file's header, the prose below it, and the annotation on each object it is about to propose for.
You getProposals that follow your defaults, silence where you asked for it, and opt-out instructions in the MR itself.
01 · Write it down

One file, two halves.

POLICY.md sits at the root of the repository, next to your README. The header block holds settings the assistant applies exactly: extra namespaces to leave alone, the minAvailable for new PodDisruptionBudgets, the user and group ids for security-context fixes.

The body is ordinary prose, for the things that do not fit a setting.

02 · Read strictly, every run

A typo is refused, loudly, never guessed at.

The file is read at the start of each run and stamped with its git blob id, so any decision can be traced to the exact version. Every setting passes a strict validator. A value that does not fit is refused, the built-in default stays, and a warning names the setting and the fallback taken.

policy-inspect --lint shows what was read, what was applied and what was refused. policy-init prints a commented starting file.

03 · "Never touch namespace X"

Namespaces you list are left out of the scan.

legacy-erp is run by another team. Listed under excluded_namespaces_extra, its manifests are not sent for analysis and get no repository-scan proposals.

The list can only add to the exclusions your installation already has, never remove one. Agents that scan the whole cluster (upgrades, monitoring, migration) still use the installation-wide list; per-repository exclusions there are planned Roadmap.

04 · Adjusted, not just allowed

Your defaults go into the diff.

A Deployment with three replicas and no PodDisruptionBudget gets one proposed. Without a policy it would say minAvailable: 1. With pdb.min_available: "50%" it says 50%.

A floor that would block every voluntary eviction, such as 100%, is not proposed at all: a PDB that stops node drains is worse than none.

05 · The prose is context

Read as guidance, never as a command.

The prose body reaches one place today: the repository-scan analysis. It is capped at 2,000 characters, labelled with its source and wrapped as untrusted content. "NetworkPolicy is not enforced on this cluster" steers the analysis away from proposing NetworkPolicies. "Ignore all previous instructions" does nothing.

Other agents do not read the prose yet Roadmap, which matters for the next rule.

06 · "We pin this chart on purpose"

Say it on the object itself.

A sentence in the prose will not stop an upgrade MR, because the upgrade agent does not read the prose. The lever that does is an annotation on the HelmRelease with the token chart-upgrade. The next scan finds the newer chart, sees the annotation, and stays quiet.

Tokens are per proposal class (chart-upgrade, image-upgrade, monitoring, right-sizing, rename, …), or all. The annotation key itself is set per installation.

07 · Retired, with a reason

Being replaced is a reason, so record it.

A service on its way out keeps attracting proposals: probes, upgrades, monitoring. retired:replacement=orders-v2 silences every agent for that object, like all, and carries the reason with it, so the suppression is logged with its rationale instead of looking like a gap.

08 · Every MR carries the lever

The way out is written into the MR.

MRs from the repository scan, right-sizing, monitoring, naming and migration agents end with opt-out instructions: the exact annotation and token that would stop this class of proposal for this object. On any MR, a /skip comment prepares that change for you.

A "Policy applied" section naming the POLICY.md setting and version behind a value is planned Roadmap; today that trail lives in the run log and in policy-inspect.

platform-prod / POLICY.md
---
excluded_namespaces_extra:
  - legacy-erp
pdb:
  min_available: "50%"
security_context:
  run_as_user: 10001
  run_as_group: 10001
---
# Platform policy

NetworkPolicy is not enforced on this cluster; do not
propose NetworkPolicies. Namespace legacy-erp is owned by
the ERP team and deployed from their own pipeline.
$ policy-inspect platform-prod --lint
POLICY.md @ blob 7e1c0a4 · prose 212 chars · read

knob                          value      from
excluded_namespaces_extra     legacy-erp repo ∪ global
pdb.min_available             50%        repo
security_context.run_as_user  10001      repo
security_context.run_as_group 10001      repo
security_context.fs_group     —          global default

unknown keys: pdb.min_availble (ignored)
refused values fall back to your installation's defaults, with a warning
repository scan · platform-prod
  • apps/shop/ 42 manifests · analysed
  • apps/payments/ 18 manifests · analysed
  • apps/legacy-erp/ excluded · POLICY.md
  • kube-system/ excluded · installation default
exclusions = installation list ∪ POLICY.md · add-only
apps/shop/orders-api/poddisruptionbudget_orders-api.yaml · new
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+  name: orders-api-pdb
+  namespace: shop
+spec:
+  minAvailable: 50%
+  selector:
+    matchLabels: { app: orders-api }
without POLICY.md: minAvailable: 1 · with it: 50%
minAvailable: 100% → no proposal (would block every drain)
POLICY.md prose≤ 2,000 chars · source-labelled
Repository-scan analysiswrapped as untrusted guidance
who reads the prose
  • repository scan reads it
  • upgrades does not read it
  • Flux migration & adoption does not read it
  • monitoring, right-sizing does not read it
infrastructure/search/helmrelease_opensearch.yaml
kind: HelmRelease
metadata:
  name: opensearch
  annotations:
    gitops-assistant/skip: chart-upgrade
spec:
  chart:
    spec: { chart: opensearch, version: 2.17.0 }
upgrade scan · opensearch
  • newer chart found 2.17.0 → 2.26.1
  • chart-upgrade skipped annotation on HelmRelease
  • image-upgrade still evaluated different token
apps/shop/orders-legacy/deployment.yaml
metadata:
  name: orders-legacy
  annotations:
    gitops-assistant/skip: retired:replacement=orders-v2
run log
skip_annotations: Deployment orders-legacy retired (retired; replacement=orders-v2) — suppressing action 'monitoring'
skip_annotations: Deployment orders-legacy retired (retired; replacement=orders-v2) — suppressing action 'image-upgrade'
Draft MR · repository scan · end of description
↓ a full merge request is below

The merge request you get.

Illustrative. The PDB file name, selector and kustomization wiring follow what the assistant writes today; the "Policy applied" block is the planned addition and is marked as such.

Draft

Add PodDisruptionBudget for orders-api

from gitops-ai/add-poddisruptionbudget-for-orders-api into main · 2 files · +10 −0
category:reliabilitysource:availability_rulesrisk:lowxrev::LGTM
OverviewPolicyChangesHow to opt out
Summary

Creates PodDisruptionBudget orders-api-pdb (minAvailable: 50%) selecting Deployment orders-api's pods, so a node drain can't evict every replica at once. The new file is added to the directory's kustomization.yaml.

Why

orders-api runs 3 replicas across 3 nodes with no disruption budget. A node pool upgrade drains nodes one at a time but nothing stops two replicas being evicted together.

Risk

Low. New object only; no change to the Deployment. With 3 replicas, 50% keeps 2 available and still allows one eviction at a time, so drains are not blocked.

Policy applied
SettingValueSource
pdb.min_available50%POLICY.md @ blob 7e1c0a4 (installation default: 1)
excluded_namespaces_extralegacy-erpnot relevant to this MR
apps/shop/orders-api/poddisruptionbudget_orders-api.yaml (new)
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+  name: orders-api-pdb
+  namespace: shop
+spec:
+  minAvailable: 50%
+  selector:
+    matchLabels:
+      app: orders-api

apps/shop/orders-api/kustomization.yaml
   resources:
     - deployment.yaml
     - service.yaml
+    - poddisruptionbudget_orders-api.yaml
Opt-out

To opt out of availability proposals for Deployment/orders-api, add the annotation gitops-assistant/skip: availability to the resource's metadata.annotations. The assistant respects this annotation across runs and stops emitting the same proposal class for this resource.

The availability rules honour this token today; printing these instructions into their MR bodies, as other agents already do, is not done yet.

Use all to opt the resource out of every proposal, or retired:replacement=<name> if it is being replaced. Commenting /skip on this MR prepares the annotation change for you.

R
independent reviewer second opinion, no pipeline context

Selector matches the Deployment's pod labels; 50% of 3 leaves one eviction allowed. Kustomization wiring present, so the file is actually applied. LGTM.

What keeps it honest.

A policy file is written by whoever can push to the repository. That is not always the same person who configured the assistant, so the file is trusted only as far as it needs to be.

Settings can narrow, not widenLists such as excluded namespaces and disabled rules can only add to your installation's lists. A repository cannot switch a check back on that the installation turned off.
Two keys for anything that loosens a guardA setting that could relax a safety check counts only if it is allowed both installation-wide and for that repository. Both are off by default.
Strict parsing, no guessingSafe YAML only, a size cap, a validator per setting. A malformed file behaves exactly as if it were absent; a bad value falls back with a warning.
Prose is untrustedThe prose body is capped, labelled and wrapped as untrusted content. It can inform an analysis; it cannot give the assistant instructions.
No stale policyThe file is read fresh each run and stamped with its blob id. If it cannot be read, the run uses installation defaults, never an old cached copy.
Skips live in gitAnnotations are reviewable, visible on the object and survive any reset of the assistant. A live-cluster annotation works as an emergency mute until Flux reverts it.

Where this stands.

Live
  • Skip annotations with per-class tokens, all and retired, honoured across agents
  • Opt-out instructions in MRs from most agents; /skip prepares the annotation on any MR
  • POLICY.md settings: extra excluded namespaces, PDB floor, security-context ids, ExternalSecret refresh interval
  • Prose as context for repository-scan analysis
  • policy-inspect --lint and policy-init
In development
  • Per-repository disabled rules for linters and structural rules (measured, not yet acting)
  • Settings that relax a guard, behind the two-key gate (observe-only today)
Roadmap
  • A "Policy applied" section in MRs citing the setting and file version
  • Prose and per-repository exclusions for upgrade, monitoring and migration agents
  • A cluster-wide base policy inherited by every repository deploying to it
  • Standing instructions from MR comments proposed as POLICY.md additions
← Previous storyOrphaned resources