Policy as [Versioned] Code: A Mea Culpa, a Technical Argument, and a Lonely Experiment

I owe someone a credit. I owe him more than a credit, actually — I owe him a lineage. For six years I carried an idea around, refined it, engineered it into working code, and presented it at twenty-one conferences across three continents without once citing where it came from. Not out of malice. Out of something that anyone who has ever absorbed a good idea will recognise: I had been so thoroughly convinced by his argument that I forgot it was his.

The man is Michael Brunton-Spall, and in 2016, at GOTO Amsterdam, he gave a talk called "Rugged: Being Secure & Agile" that planted a seed I didn't recognise as someone else's until it had already grown into a tree with my name on the trunk. But ideas have a lineage longer than any one person. Michael would be the first to say — and has said — that he stood on shoulders of his own: James Abley and Gareth Rushgrove at GDS, conversations at ScaleCamp and ScaleSummit, the DevOpsLondon community. What Michael did — and it was substantial — was take those scattered conversations and forge them into a narrative that could travel. He spent two or three years refining that talk, making the ideas communicable, pragmatic, and sticky. That is its own form of creation.

Article content
Michael Brunton-Spall's impressing talk, as true in 2026 as it was in 2016 watch it, now!

Michael's argument was elegant and, in retrospect, obvious. It was also early. 2016 was too soon for the industry to be ready, and Michael knew it. Security principles should not be bolted onto agile delivery like armour plating on a Ford Fiesta. They should be woven into the fabric of how teams build software. He was working at the Government Digital Service at the time, helping create security design principles that would later be adopted and maintained by the National Cyber Security Centre. He went on to co-author Agile Application Security with Laura Bell, Rich Smith, and Jim Bird. He is now Deputy Director of Cyber Policy and Capabilities at the Cabinet Office . Someone who earned their credentials.

I watched that talk and something happened that I have since learnt is the highest compliment a speaker can receive and the most dangerous trap a listener can fall into: I absorbed his principles so completely that I began to believe they were mine. When I sat down in 2022 to build what became Policy as Versioned Code, I was not consciously drawing on Michael's work. I was drawing on what I thought was my own conviction that policy should be testable, versionable, and consumable as a dependency. The philosophy was shaped by Michael and the community around him. The engineering was mine. And I failed to cite my sources — not because I chose not to, but because the ideas had become so thoroughly part of how I think that I forgot where they came from.

Michael, was kind enough to give this article blessing and added the philosophical commentary that knowledge is a communal product. "We take a bit of this, a pinch of that, and if it is good, it grows and spreads". Michael took conversations from ScaleCamp and Government Digital Service and turned them into a conference talk that changed how I think. I took that talk and turned it into working code and YAML and Mend.io Renovate configs. Someone will take this article and turn it into something neither of us has imagined yet. That is how ideas are supposed to work. The best measure of a teacher's influence is when the student genuinely believes the ideas are his own — and the best response is not guilt, but gratitude and a proper citation.

What I did not understand then — what I am only beginning to understand now, twenty-one conferences and a lot of solitary airports later — is that carrying an idea forward is its own form of loneliness. It does not matter whether the idea started as yours or someone else's. You are alone with a conviction in rooms full of people who will forget your name by the next session. That loneliness is the reason this piece exists — as a test of whether the technical argument I built from this shared philosophy can stand on its own in writing the way it never quite could from a stage.

The Lift

Here is the central image. You are in a lift — an elevator, if you must — in a large organisation. Four people ride with you.

The CIO says: "I have no idea what our teams are actually doing. I set policy, but I cannot tell you whether anyone follows it, bends it, or ignores it entirely."

The Product Manager says: "Bureaucracy is killing us. Every policy change means weeks of back-and-forth before we can ship."

The Developer says: "I just want to know what the rules are. Which ones I have to follow, which ones I can bend, and which ones lose me my job."

And the Cleaner — the person everyone else in the lift has stopped noticing — says: "We get a memo. We compile it into our operational manual. Sometimes a new memo arrives and we miss it. Last week I wiped the war room whiteboard because nobody told me it mattered."

Article content
AI Generated: Four workers in a cramped office lift, hierarchies visible in their postures and uniforms

The Cleaner is the emotional heart of this story. Not because cleaning is trivial — it is not — but because the Cleaner is doing exactly the same job as everyone else in that lift. Receiving policy. Compiling it into a working manual. Trying to stay current. Dealing with version conflicts. Missing updates. And nobody recognises it, because nobody has ever framed policy management as a universal problem rather than a domain-specific one.

I think about the Cleaner more than I should. I think about the Cleaner at 2am when Renovate opens a pull request in a repository nobody has looked at in four months. I think about the Cleaner when an enterprise CIO shows me a compliance dashboard that tracks everything except whether anyone actually changed their behaviour. The Cleaner is working in good faith with a broken system, and nobody — not the vendor, not the consultancy, not the framework — is solving the Cleaner's problem.

Every person in that lift is a consumer of policy. The CIO produces it. The Product Manager resents it. The Developer needs it. The Cleaner follows it — or tries to. They are all doing the same thing. They are all failing at it for the same reason.

Policy, in most organisations, is a Word document in a SharePoint folder that nobody can find, nobody has versioned, and nobody can prove they have read.

The Industrialisation Nobody Noticed

The industrialisation of policy is happening whether we notice or not. Kyverno achieved CNCF Graduated status this year — the same level of maturity as Kubernetes itself. The CNCF published an introduction to Policy as Code that reads like mainstream recognition of something a small number of people have been banging on about for years. KubeCon London 2025 put policy at the centre of platform engineering. Policy is evolving from custom-built, hand-cranked enforcement into something that looks increasingly like commodity infrastructure. Most people have not noticed.

But here is what frustrates me. The consensus position — "policy-as-code is good; use OPA, use Kyverno, use Checkov; enforce at the gate" — is wrong. Not wrong in principle. Wrong in architecture.

Article content
AI Generated: Airport security checkpoint reimagined as a software admission control system

Most policy-as-code implementations treat policy as a gatekeeper. An admission controller that blocks your deployment. A pre-commit hook that rejects your code. A compliance scanner that tells you what you got wrong after you have already built it. This is guardrails thinking. You hit the guardrail, your car is totalled, and technically the guardrail did its job because you did not go off the cliff. Congratulations. Your car is still wrecked.

Gregor Hohpe makes a useful distinction in his work on strategy mechanisms: the difference between a guardrail and lane keeping assist. A guardrail stops you at the point of failure. Lane keeping assist nudges you continuously, correcting in real time before you ever reach the edge. I should be honest that the dependency model I am proposing is not quite lane keeping assist — a versioned policy arriving as a pull request is a discrete event, not a continuous correction. But it is far closer to lane assist than to a guardrail. It nudges teams toward compliance before deployment rather than blocking them at the point of it.

Now — I need to be honest about the limits of this argument. Some policies belong at the gate. Access control. Data protection. Cryptographic key management. There are policies where the cost of a violation is so catastrophic and so irreversible that admission control is not just appropriate — it is the only responsible architecture. If a workload is about to deploy with an unencrypted database connection to a production environment containing personal data, I do not want lane keeping assist. I want a locked door.

The distinction is this: policies that govern how teams label, tag, configure, and structure their work are candidates for the dependency model. Policies that govern whether a workload is permitted to exist at all — security boundaries, data classification, access control — those belong at the gate. Confusing the two categories is how organisations end up blocking deployments over missing metadata while waving through genuine security risks, because the admission controller treats a missing department label and an exposed secret with equal severity.

Policy should be lane keeping assist where lane keeping assist is appropriate. And that turns out to be most of the policy surface area that enterprises actually struggle with. The labelling. The tagging. The configuration standards. The operational metadata. The stuff that makes the Cleaner's manual either current or dangerously stale.

Policy — for that surface area — should be something you pull towards you, not something that blocks you. Policy should be a dependency — versioned, tested, consumed, and updated automatically — not a gate.

The Dependency Model

Here is the technical argument, and I am going to show my working.

If policy is a dependency, then it should behave like one. It should have a version number. It should follow semantic versioning. It should live in a Git repository. It should have unit tests. And it should be consumed by the teams that need it the way they consume any other dependency — pulled in, pinned to a version, and updated via automated pull requests.

I built this. The policy-as-versioned-code GitHub organisation has eleven repositories and is still active. Renovate — the dependency update tool — has generated over 1,222 automated pull requests across those repositories. Each PR is a measurable signal: did the team accept the policy update? Did it break their build? Did they pin to the old version and ignore it?

Let me show you what v1.0.0 looks like. A simple policy: every Kubernetes resource must have a department label.

In Kyverno:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-department-label
  annotations:
    policies.kyverno.io/title: Require Department Label
    policies.kyverno.io/category: Example Org Policy
    policies.kyverno.io/description: >-
      It is important we know the department that resources
      belong to, so you need to define a 'mycompany.com/department'
      label on all your resources.
    pod-policies.kyverno.io/autogen-controllers: none
spec:
  validationFailureAction: enforce
  background: false
  rules:
    - name: require-department-label
      validate:
        message: >-
          The label `mycompany.com/department` is required.
        pattern:
          metadata:
            labels:
              "mycompany.com/department": "?*"
        

In Checkov, for Terraform:

metadata:
  name: >-
    Check that all resources are tagged with the key - department"
  id: "CUSTOM_AWS_1"
  category: "CONVENTION"
scope:
  provider: aws
definition:
  and:
    - cond_type: "attribute"
      resource_types: "all"
      attribute: "tags.mycompany.com.department"
      operator: "exists"
        

And it has tests — because policy without tests is just an opinion:

# fail0.yaml — should be rejected
apiVersion: v1
kind: Pod
metadata:
  name: require-department-label-fail0
spec: ...
---
# pass0.yaml — should be accepted
apiVersion: v1
kind: Pod
metadata:
  name: require-department-label-pass0
  labels:
    mycompany.com/department: finance
spec: ...
        

That v1.0.0 becomes v2.0.0 when the organisation decides the department label must come from a constrained list rather than freetext — a breaking change, so a major version bump. Then v2.1.0 when someone spots a spelling mistake in the validation message. Then v2.1.1 when a new department is added to the allowed list. The version number communicates the nature of the change. Major means you must act. Minor means you should look. Patch means the system handles it.

This is where it matters for the Cleaner. The Cleaner's operational manual is a dependency too. When the policy bumps from v1.0.0 to v2.0.0, the Cleaner does not need to wait for a memo. The Cleaner does not need to check the SharePoint folder. The update arrives as a pull request. It either passes or fails. It is testable. It is visible. And the version number tells the Cleaner exactly how much attention to pay: a major bump means the rules have changed and the manual needs rewriting; a minor bump means a correction has been made; a patch means carry on. That is more information than any memo has ever carried, and it arrives automatically rather than three weeks late.

Article content
AI Generated: Victorian telegraph office reimagined as a modern policy operations centre

The Renovate configuration that makes this work:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "labels": ["policy"],
  "regexManagers": [{
    "fileMatch": ["kustomization.yaml"],
    "matchStrings": ["mycompany.com/policy-version: \"(?<currentValue>.*)\"\\s+"],
    "datasourceTemplate": "github-tags",
    "depNameTemplate": "policy",
    "packageNameTemplate": "policy-as-versioned-code/policy",
    "versioningTemplate": "semver"
  },{
    "fileMatch": [".*tf$"],
    "matchStrings": ["#\\s*renovate:\\s*policy?\\s*default = \"(?<currentValue>.*)\"\\s"],
    "datasourceTemplate": "github-tags",
    "depNameTemplate": "policy",
    "lookupNameTemplate": "policy-as-versioned-code/policy",
    "versioningTemplate": "semver"
  }]
}
        

When a team's build fails because they have not adopted the new policy version, the feedback is immediate and clear. When the CIO wants to know how many teams are compliant, the answer is a GitHub PR search away.

Go back to the lift. The CIO now has visibility — not a dashboard that measures deployment counts, but a PR acceptance rate that measures actual adoption. The Product Manager has automation instead of bureaucracy — policy updates arrive as pull requests, not as emails requiring three meetings. The Developer has explicit, testable rules with version numbers that distinguish "you must act" from "carry on." And the Cleaner — the Cleaner gets a notification that the policy version has bumped from v1.0.0 to v2.0.0, opens the pull request, sees exactly what changed (freetext labels are now a constrained list), and updates the operational manual in the same morning. Not three weeks later. Not after chasing someone in procurement for the latest memo. The same morning, using the same mechanism as the Developer three floors up. Policy-as-a-dependency does not care about your job title. It cares about whether you are consuming the current version.

The Code for Humans

There is a missing layer in this argument that I did not see until Michael pointed it out, and it is characteristic of him that the thing I missed was the human part.

The dependency model solves the distribution problem. Policies get versioned, tested, consumed, updated automatically. But who writes the policy? Who decides when it is stale? Who clears out the dead wood?

Michael created something at GDS called The GDS Way that answers these questions, and the answers are deceptively simple. There is no policy writing committee. A team notices a practice that works — how they run incident reviews, say, or how they structure service healthchecks — and they submit it as a proposal. Other teams review it, challenge it, adopt it or push back. Every accepted practice carries a date. Every practice must be regularly reviewed. And here is the part that matters most: if nobody can argue that a practice is still good — if nobody will stand up in a review and say "yes, this is still right, and here is why" — it gets removed. Not archived. Not deprecated. Removed. The dead wood gets cleared because the system demands that someone actively defend every piece of guidance that remains.

The Kyverno YAML, the Renovate PRs, the semver tags — that machinery serves teams who live in Git, who understand pull request workflows, who can read a version number and know what it means. The actual Cleaner — the person with the mop and the operational manual — does not have a GitHub account. The dependency model provides the single source of truth: the policy is versioned, tested, and current. But the last mile to non-technical consumers — how the Cleaner's manual stays in sync — is a different problem that the versioning alone does not solve. The GDS Way model starts to bridge that gap, because its governance is human-readable. A dated practice with a mandatory review cycle is something any consumer can understand, whether they read YAML or not.

This is the complement to the versioning model. Semantic versioning tells the Developer what changed and how much attention to pay. The review cycle tells everyone — including the Cleaner — something equally important: this policy is still alive. Someone still believes in it. It has not been left to rot in a SharePoint folder where nobody remembers who wrote it or why.

The combination is powerful. Version the policy so it can be distributed as a dependency. But also date the policy, review the policy, and — crucially — be willing to delete the policy when it no longer serves. Make explicit what is otherwise implicit. That phrase is Michael's, and it is the human half of the technical argument I have been making for six years without realising I was only telling half the story.

The Enterprise Temptation

I need to name something that the industry does not want named.

Enterprises default to buying policy solutions rather than changing culture. This is not stupidity. It is structural. Procurement is measurable — a purchase order has a date, a cost, a vendor, a contract. Culture change has none of those properties. You cannot put "transformed how 3,000 engineers think about compliance" on a quarterly report. You can put "deployed Kyverno across 47 clusters" on one.

In the 1830s and 1840s, Victorian factory owners faced the same structural incentive. Parliament passed the Factories Act of 1833 — the first law to require factory inspectors. The owners responded rationally: they bought safety equipment, posted notices, filed the paperwork. What they did not do was redesign the work itself. A posted notice about machinery guards satisfies an inspector. Redesigning a production line to eliminate the hazard satisfies nobody's quarterly report but saves the worker's hand.

Article content
AI Generated: Victorian factory inspector ticking clipboard while worker operates dangerous machinery

This is precisely what happens when an enterprise buys a policy engine and bolts it onto the CI pipeline. The admission controller satisfies the auditor. The dashboard satisfies the board. The YAML file in the repository satisfies the compliance team. But the Cleaner — the person who actually needs to know what changed and why — is still working from a memo that arrived three weeks late. The purchase order closed. The problem did not.

The EU Cyber Resilience Act begins enforcement in September 2026 for vulnerability reporting and December 2027 for full SBOM requirements. When Log4Shell hit in December 2021, most organisations could not even answer the question "are we running Log4j, and if so, which version?" The xz Utils backdoor in March 2024 demonstrated that the supply chain threat had not gone away. Policy that cannot move as fast as the risk landscape is already obsolete.

Purposeless policy is potentially practically pointless policy.

The Lonely Experiment

From June 2022 to December 2023, I took this talk to twenty-one conferences across three continents. Cloud Native London and Wales. Open Source Summit in Brazil and Dublin. DevSecCon in Germany and the Netherlands. The UK Government Cyber Security Conference. Detroit. Virtual stages I cannot remember the names of any more.

I did not sell a product. I shared principles. There was no sales pipeline, no lead generation, no ROI spreadsheet. I gave conference organisers a rare thing — an independent voice not hawking wares — and in return I got audiences who told me I was "very clever" without ever engaging with the substance.

The Detroit trip crystallised something. I flew from Heathrow, landed, slept one night, gave the talk, flew back to Heathrow, went straight to the Crown Prosecution Service office in Westminster for a senior stakeholder workshop, had drinks after, and went home. Still immune to jet lag, touch wood. But the physical endurance was not the point. The point was this: I had carried Michael's philosophy six thousand miles, transmuted it into YAML and Renovate configs and semver tags, presented it to a room of strangers, and flown home to present it to a room of civil servants — and in neither room could I tell whether a single person would do anything differently on Monday morning.

I did it anyway. I would do it again. That is the defiant part, and I want to be clear about it: the loneliness was real, but so was the conviction. I believed — I still believe — that policy-as-a-dependency is architecturally right. Not knowing whether anyone acted on it does not make it wrong. It makes it unproven. There is a difference, and I have spent eighteen months learning to sit with that difference.

I have to believe there was value, because the alternative is that I spent eighteen months talking to myself. And maybe that is all writing is — talking to yourself in public and hoping someone overhears. But the policy-as-versioned-code GitHub organisation sits there with its eleven repositories and its 1,222 automated pull requests, and the engineering works whether or not anyone is watching.

I practised that alliteration line — "purposeless policy is potentially practically pointless policy" — because when you give the same talk twenty-one times, you need to find ways to keep it alive for yourself. To introduce jeopardy, to risk tripping over your own tongue, to create a moment of human engagement in a room full of people staring at their phones. The speaking circuit is made of small things like that — small moments of connection in large rooms full of polite indifference.

Article content
AI Generated: Solitary figure walking docks at twilight, containers awaiting unknown destinations

The Irony and the Aspiration

There is something I have been turning over. I took a philosophy shaped by Michael and others and turned it into engineering. That is what engineers do — and it is also what Michael did when he took scattered community conversations and turned them into a communicable narrative. Ideas move through people. The question is not who owns them but whether they are growing. I have a working demo, a recorded talk, and — finally — a proper citation.

The aspiration is simpler and, I suspect, more lasting. Even if someone else commercialises this idea — and someone will, because the land grab has already begun — the world is marginally better for the idea having been shared openly. Appvia wrote about it. The CNCF is moving in this direction. The principles are out there. Influencing the influencers is its own reward, even when you cannot measure the influence.

If you want to evaluate whether this model fits your organisation, here is a practical exercise you can do on Monday morning in fifteen minutes. Take a blank page. Draw two columns. Label the left column "Gate" and the right column "Dependency." Now list every policy your organisation enforces on engineering teams. Policies where a violation is catastrophic and irreversible — access control, data protection, cryptographic boundaries — go in the left column. Policies where a violation is correctable and the real cost is inconsistency — labelling, tagging, configuration standards, operational metadata — go in the right column. The left column belongs at the gate. The right column — and it will be the longer column — is where the dependency model applies.

Then go to github.com/policy-as-versioned-code/policy. Open the require-department-label directory. Read the Kyverno YAML. Read the tests — pass and fail. Then look at the Renovate config and imagine that automation running across every repository in your estate, opening pull requests whenever the policy version bumps. If that pattern fits the right column of your two-column map — the correctable, consistency-focused policies — you have a candidate for the dependency model. If it does not fit, you have learnt something about your organisation's policy surface that most CIOs never discover.

The Cleaner is all of us. We are all compiling memos into manuals, trying to stay current, dealing with version conflicts that nobody designed a system to manage. The CIO wants visibility but cannot get it. The Product Manager wants speed but cannot have it. The Developer wants clarity but cannot find it. And the Cleaner — the Cleaner has been solving this problem with a highlighter and a ring binder for longer than any of us have been writing YAML.

The question is whether we are brave enough to version the manual. And whether, when the automated pull request arrives, we will merge it or let it rot.

I wrote this article partly because I believe the technical argument is right, partly because ideas deserve their lineage traced, and partly because — after twenty-one conferences and eighteen months of speaking to rooms that politely applauded and moved on — I wanted to know whether anyone out there is listening.

If you have tried this approach, or argued against it, or built something better — drop a comment below and help me feel less lonely.

Article content
My talk on youtube of Policy as [Versioned] Code, elevator pitch included

(Views in this article are my own.)