On this page
Architectural property
COADF P-6: a guardrail that lives in a document is advice; a guardrail that runs in the pipeline and blocks a release is a fence. Fences fall into four categories (data, architecture, copy and process), each with an identifier, a rule and an enforcement method, and a fence earns its place only by a proof of teeth. COADF does not publish the vocabulary its own publication fence matches on, because a list of what is watched is a map of what is protected.
The property is not "there is a check". It is that the forbidden state cannot travel past a given point, and that somebody has watched it being stopped there.
Why it matters
Every other property in this Companion can be implemented correctly once and lost in the next change. A fence is what keeps a property true across changes nobody reviewed with that property in mind. The hard part is rarely writing the check. It is placing the check on the path the forbidden state must travel, and proving that it fires there, on the real path, rather than in a unit test of the checking function.
Valid implementation strategies
Enforcement surfaces
Each surface sees something different, and each has a characteristic bypass. A fence belongs on the surface that sees the forbidden state, and is only as strong as that surface's bypass.
| Surface | Sees | Typical bypass |
|---|---|---|
| Compiler and type checker | Code structure and declared types | Casts, dynamic typing, reflection, generated code |
| Application validation | Values at a boundary, at run time | A second entry point: a batch job, a script, a migration |
| Architecture test | The dependency graph | Loading by name at run time; a new package the rule does not name |
| Contract and schema test | Interface compatibility | A consumer nobody registered |
| Continuous integration | The repository at one commit | A required check that is skipped and reported as success |
| Merge gate | Which checks must pass before a merge | Administrators, direct pushes, protection the hosting plan does not enforce |
| Release pipeline | The artifact being promoted | A manual upload |
| Deployment admission | API objects submitted to the cluster | A failure policy that ignores errors; objects that existed before the policy |
| Publication build | The rendered public output | Content served from outside the build |
Three chains, each ending in a stopped transition, are implemented in the profiles:
- An architecture violation fails an architecture test, and the merge does not happen (Python and FastAPI).
- A contract incompatibility fails a contract check, and the release does not happen.
- A synthetic forbidden sentinel in the built site fails the publication fence, and the publication does not happen (Python and FastAPI, Cloud-Native).
Continuous integration is one surface among nine, not the definition of a fence. GitHub Actions, GitLab CI and Jenkins can all host one; so can a type checker, an admission policy or the build that renders a site.
Proof of teeth
- Introduce a controlled, synthetic defect.
- Exercise the real enforcement path, not a unit test of the checking function.
- Verify that it fails, and fails for the planted reason.
- Restore the exact fixture, and prove it by comparing a digest before and after.
- Verify that it passes again.
Commit before planting. Restoring by discarding changes restores the last commit, which on a working branch may not be where you started, and the proof then destroys the work it was meant to protect.
Failure modes
The fence is on the wrong path
It checks the repository, while the forbidden content is injected at build time, at deploy time or at run time.
A skipped required check reads as success
GitHub reports a skipped job as successful, and a skipped job does not prevent a merge even when it is a required check. A required job that depends on a failed fence is skipped, and the merge goes through. Require the fence itself, or a summary job that always runs and fails unless every fence passed (GitHub's own advice for checks that depend on other jobs).
Advisory by configuration
continue-on-error,allow_failure: truein GitLab CI, an admission webhook whose failure policy ignores errors, a Gatekeeper constraint indryrunorwarn: each turns a fence into advice without anybody writing the word.Nothing checked, reported as clean
An empty pattern list, a test selection that matches nothing, a scan of an empty directory. The number of checks actually executed belongs in the result, and zero is a failure.
The fence fires on legitimate work and is switched off
Precision keeps a fence alive. A rule that matches ordinary copy will be disabled rather than obeyed, and a disabled fence is worse than none, because everybody still believes it is there.
The fence's own list is published
A public list of watched terms tells a reader exactly what is being protected.
Existing state is never checked
Admission and pre-merge checks act on requests as they are made, not on what is already there. Kubernetes documents it for one admission plugin in so many words: when a LimitRange is added, the pods that already exist continue unchanged. Gatekeeper has an audit of existing resources for exactly this gap.
Green is read as sufficient
A passing check proves that the check passed. Whether it is the right check is an architectural question no pipeline can answer; see the technology note.
Verification
Architecture test
Passes when: The dependency rule passes on the current tree.
Proof of teeth: Plant the forbidden import: it fails, and the merge gate refuses.
Contract test
Passes when: Producer and consumer schemas are compatible.
Proof of teeth: Remove a required field from the producer: the check fails and the release job does not run.
End-to-end test
Passes when: The publication fence scans the built output and passes.
Proof of teeth: Plant a synthetic sentinel in the output: the build fails. Remove it, compare the digest, and the build passes.
Deployment or admission test
Passes when: An object that follows the rule is admitted.
Proof of teeth: Submit one that breaks it: refused, with the rule's message.
Manual evidence
Passes when: At merge, a named person confirms that each fence ran in this run: the run identifier and the number of executed checks are recorded, not remembered.
Alternative realizations
- Pre-commit hooks. Fast feedback, and bypassable on the developer's own machine: advice, not a fence.
- Runtime guards. Stop a forbidden state in production rather than before it. Necessary where the state only exists at run time.
- Manual gates. A named person performs a written procedure. COADF reports such a control honestly as manual, never as enforced.
- Policy engines for infrastructure rules. Kubernetes ValidatingAdmissionPolicy in-process, or Open Policy Agent with Gatekeeper, where the forbidden state is an API object.
Limitations
- A text fence cannot see a mechanism expressed through control flow, renamed identifiers, data flow or a diagram's geometry. Automated guards are necessary and not sufficient; human review remains.
- A fence detects what it was written to detect, and nothing else.
- Passing fences establish that the fences passed. They establish no regulatory status.
Sources
- GitHub: Control jobs with conditions · official documentation · Checked on 2026-09-11
- GitHub: Troubleshooting required status checks · official documentation · Checked on 2026-09-11
- GitLab: CI/CD YAML: allow_failure · official documentation · Checked on 2026-09-11
- Kubernetes: Dynamic admission control: failure policy · official documentation · Kubernetes 1.37 · Checked on 2026-09-11
- Open Policy Agent Gatekeeper: Gatekeeper: enforcement actions · official documentation · Gatekeeper 3.23 · Checked on 2026-09-11
- Kubernetes: Limit Ranges: admission-time validation · official documentation · Kubernetes 1.37 · Checked on 2026-09-11
- Open Policy Agent Gatekeeper: Gatekeeper: audit · official documentation · Gatekeeper 3.23 · Checked on 2026-09-11
- Kubernetes: Validating Admission Policy · official documentation · Kubernetes 1.37 · Checked on 2026-09-11
