Skip to content
Blog Governance

Policy as Code vs. Policy as Documentation: Why the Format Determines Adoption

A Confluence page describing your dependency rules is documentation. A .repohelm.yaml file checked into git is policy. The difference in adoption is not subtle.

Abstract contrast between structured code form and unstructured document form

Every engineering organization has dependency policies. Some of them exist only in someone's head. Some are written in a Confluence wiki that was last updated eighteen months ago. Some are in a security runbook that gets referenced during incident reviews but rarely consulted during development. A small fraction are in code files checked into git and evaluated automatically on every push.

These are not equivalent. The format of a policy determines its enforcement characteristics, its drift rate, and whether developers actually internalize or simply ignore it. Policy-as-documentation is a record of intent. Policy-as-code is a mechanism.

Why Documentation Policies Decay

Documentation policies have a fundamental structural problem: they require a human to read them, remember them, and apply them at the right moment. None of those steps are automatic. When a developer adds a new npm dependency at 11pm to finish a feature branch, they are not opening the engineering wiki. When a new team member joins and installs packages they're familiar with from a previous job, they're not cross-referencing the approved package list.

There's also the maintenance problem. A policy document that was accurate at the time it was written starts to drift the moment the engineering environment changes. New ecosystems get added. License requirements evolve. A CVE comes out that causes the security team to ban a specific major version of a package. Each of these changes needs to be reflected in the document, communicated to the team, and remembered by every developer going forward. In practice, they usually aren't.

The result is a compliance theater situation: a document exists that could theoretically guide behavior, but the actual behavior on the ground is determined by developer habits, not by anything the document says.

What Policy-as-Code Actually Means

The policy-as-code concept, popularized in infrastructure contexts through tools like OPA (Open Policy Agent) with its Rego language and later through Kubernetes admission controllers, applies the same principle to software governance: instead of writing rules in prose, write them in a format that a machine can evaluate against real-world state.

For dependency governance specifically, a policy file might declare that any package with a CVSS score above 7.0 requires a fix within 14 days of publication, that AGPL-3.0 licensed packages are not permitted in production services, or that any package in the @legacy-internal namespace is banned from new services. These rules are unambiguous because they have to be: a machine evaluates them, and machines can't interpret vague intent.

In Repohelm's model, policy files sit in the repository (or in a central policy repository that applies to multiple repos, depending on how a team structures their governance). The policy engine reads those files, resolves the full dependency graph, and evaluates each package against each rule. When a violation is found, a PR opens. The policy file and its evaluation are both in version control, which means changes to policy are reviewable, diffable, and attributable just like code changes.

The Enforcement Difference

The most concrete difference between documented and code-form policy is what happens when a developer violates the rule without knowing it. With documentation-only policy, nothing happens automatically. The violation persists until it's caught in a code review, a security audit, or an incident. With policy-as-code, the violation triggers an automated response within the next evaluation cycle.

We're not saying documentation has no place. A good policy file should link to prose documentation that explains the rationale behind each rule. Developers who understand why a rule exists follow it more reliably than developers who just see a pass/fail signal with no context. The combination is what we aim for: machine-enforceable rules with human-readable rationale attached.

The enforcement path also matters for audit evidence. When a security audit asks "how do you ensure your teams aren't using packages with known critical CVEs?", the answer "we have a Confluence page" is meaningfully weaker than "we have a policy file evaluated on every push, and here's the git history of every violation and remediation over the past 90 days." The latter is auditable in a way the former isn't.

Practical Format Choices

There are different ways to implement policy-as-code for dependency governance, and they have different trade-offs between expressiveness and accessibility.

OPA/Rego is the most expressive: you can write arbitrary logic about package metadata, CVE scores, license types, and dependency chains. The trade-off is that Rego has a learning curve, and maintaining Rego policies requires developers who can read and write it. For teams with a dedicated platform engineering function, this is tractable. For smaller engineering teams, it can become a bottleneck where only one or two people can maintain the policies.

Declarative YAML formats (like Repohelm's own policy file structure) trade some expressiveness for accessibility. Rules that cover the vast majority of dependency governance needs can be expressed in straightforward YAML that any engineer can read and modify without learning a specialized DSL. Complex cases can often be addressed with combinations of simpler rules.

The right choice depends on your team's operational model. Repohelm supports both: a YAML format for teams that want maintainable, accessible policies, and an OPA/Rego evaluation layer for teams that need finer-grained logic. Most of the teams we've worked with start with YAML and only reach for Rego when they have genuinely complex cross-repository or conditional rules.

The Drift Argument

One argument sometimes made for documentation-based policy is that it's more flexible: the policy team can update the Confluence page without triggering a code change or a review process. This is true, and for some kinds of governance (contextual guidance, best practice suggestions, informational notes), that flexibility is appropriate.

For security-critical rules, flexibility-without-review is a liability. If your "no CVSS >7.0 dependencies without remediation plan" rule can be quietly changed to "no CVSS >9.0" by editing a wiki page without a PR, a code review, or a changelog entry, you've created an invisible escape hatch that is hard to audit. Policy-as-code makes every change to enforcement logic visible by default.

The version control audit trail is particularly valuable when policy changes coincide with incidents. Being able to see that a rule was relaxed three weeks before a vulnerability was exploited is the kind of information that drives meaningful post-mortems rather than guesswork.

Ready to get your dependency risk under control?

Connect GitHub, get a full audit, and start remediating in minutes.