Part IV Govern It Safe Evolution
13 min read

Chapter 10 – Immutable Infrastructure (The Guardrails)

Start with the boundary, not the philosophy.

Once the loop can move faster and touch more surfaces, the grader boundary becomes part of the threat model.

Minimal posture:

# governance/immutable_paths.txt
.github/
policies/
validators/
CODEOWNERS
# CODEOWNERS (illustrative)
/policies/           @platform-engineering @security
/validators/         @platform-engineering
/.github/workflows/  @platform-engineering
/CODEOWNERS          @platform-engineering
# runner posture (illustrative)
user=sdac-bot
write_allowlist=src/ docs/
read_only=governance/ .github/ policies/ validators/

If the loop can propose app changes but cannot write its own guardrails, it stays an Effector. If it can rewrite the graders that decide what ships, it becomes a governance risk.

This is where immutable infrastructure becomes non-negotiable. In Software Development as Code (SDaC), immutable infrastructure means the protected paths and pipelines that grade the system: CODEOWNERS, branch protection, CI workflows, policy validators, and the manifests that define the protected boundary. The agent may propose changes to them, but it cannot merge or weaken them on its own.

The goal is not to slow the agent down. The goal is to ensure every proposal is checked against human-defined policy that the agent itself cannot rewrite.

The Canon of Truth (what can override what)

The Hierarchy of Stewardship

Lower layers provide capability. Upper layers decide what that capability is allowed to do.

Intent
Why
Physics + Guardrails
What is allowed
Loop Architecture
How work is structured (and where missing guardrails are discovered)
Execution Loops
Write → Judge → Refine
Compute + Models
Runtime base

In any automated system that modifies itself, disagreements between sources aren’t a documentation problem. They’re a governance bypass.

So we define a canon: a practical hierarchy of truth. Higher tiers constrain lower tiers, and lower tiers are never allowed to “outvote” deterministic reality.

Tier Source examples What it means
Tier 1 Code, type definitions, schemas, Immune System cases, CI gates Deterministic truth. Machine-enforced. If Tier 1 says “no,” the change does not exist.
Tier 2 Structured configuration, domain models, design docs, runbooks Authoritative intent. Human-readable and often machine-checked against Tier 1. This is where you declare what you mean.
Tier 3 General documentation, UI copy, release notes Explanations. Useful, but non-authoritative. Must remain consistent with higher tiers.

Immutable Infrastructure is how you enforce this hierarchy in practice: you protect Tier 1 (and the gates that interpret it) so the system cannot weaken the very constraints that keep it safe.

The Meta-Patterns Applied to Governance

Immutable Infrastructure is where the Meta-Patterns become enforcement.

1) Don’t Chat, Compile

Governance cannot live as advice in a wiki. It must live as versioned artifacts: CODEOWNERS, branch protection rules, CI workflows, policy schemas, and the scripts that enforce them.

A rule is enforceable only when it lives in a file and is wired into a gate. Otherwise it’s a vibe.

2) Physics is Law

If a change fails required checks, it does not exist. Governance is the work of closing bypass paths:

3) Recursion

The same gates apply to everyone: humans, agents, and maintenance loops.

There is no privileged mode for the Dream Daemon. It emits Missions and runs the same Validators, under the same Immutable Infrastructure boundaries.

The Core Problem: Guardrails that Can Grade Themselves

Imagine an autonomous agent proposing a CI change. Perhaps it adds a useful linter or speeds up the Immune System suite. In the same diff, it could also disable a security scan or relax CODEOWNERS so its own changes no longer need human review. If that change can approve itself, you no longer have guardrails. You have self-authorization.

Our SDaC agents operate on the principle of proposing changes, not merging them directly into protected branches. The mechanism that reviews and admits changes to governance surfaces must sit explicitly outside the agent’s control. The guardrails themselves must be immutable to the autonomous system; only explicit human action can change them.

Protected Paths: Your Engineering Constitution

To establish immutable infrastructure, define the repository paths that act as the “constitution” of your engineering process. Changes to those paths get the highest scrutiny.

If the Map is your intent and the Terrain is your code, immutable infrastructure is the bedrock: you build on it, but you don’t let the loop rewrite it.

Directory topology as governance (a self-referential example)

In this repository, we make the governance boundary legible in the filesystem. The “engine” that runs loops is separated from the “products” it produces.

core/   # workflow runtime, step implementations (validators, judges)
tools/  # controllers and entrypoints (dream, writers, map-updaters)
mk/     # make orchestration
dist/   # products (each project has its own book/ and meta/)

That separation is not cosmetic. It’s a practical definition of immutable infrastructure: changes to core/ and tools/ change the machine that grades work, so they deserve stricter review than changes to a manuscript file under dist/.

What should be immutable? (a decision framework)

There’s no canonical list, but teams tend to draw the boundary along one principle:

If a surface can change the loop’s privileges, widen its blast radius, or weaken its graders, it belongs behind the immutable boundary.

Use a simple rubric:

Surface Mutable by the Effector? Rationale
Application code Yes This is the work. It’s what you’re automating.
Unit tests Yes Tests are part of the artifact; they travel with code changes.
Integration tests Limited Higher cost and higher flake risk; often need human review when touching real systems.
CI/CD workflows No They control deployment safety and can silently weaken gates.
Production configs No Direct production impact; small mistakes can create large outages.
Security policies / secrets surfaces No Trust boundary; protect from both drift and exfiltration.
Database migrations Human review Data integrity risk; allow assistance, require approval and audits.
The immutable manifest itself No If the Effector can edit the boundary, it can remove the boundary.

Immutability enforcement (defense in depth)

Immutability is not a single mechanism. It’s a stack of independent controls that all say “no” in different ways.

At minimum, you want three independent controls:

  1. Userspace boundary (OS permissions): the loop runs as a dedicated user that simply cannot write protected paths.
  2. Runtime scope guard: the runner refuses to write outside an allowlist, even in its working copy.
  3. CI diff gate: CI rejects any change that touches immutable paths, even if the runner was misconfigured.

Then you layer in VCS governance (CODEOWNERS + branch protection) so changes to the boundary itself require humans.

Immutability here means “not modifiable by the Effector.” Humans can still change these paths through normal review processes. The point is that autonomy cannot silently weaken its own graders.

Layer 0: userspace boundary (make it physically unwritable)

This is the missing piece in many “protected paths” stories: if the loop runs with the same OS privileges as a human developer, then “immutable” is a convention, not a property.

On Unix-like systems, the simplest invariant is: the process cannot open the file for write. Run your loop as a dedicated user (for example sdac-bot) and use file access bits / ACLs so protected directories are writable only by a human-owned group.

This matters because CI runners configured as restricted Unix users physically neuter self-rewrite: even a very capable large language model (LLM) cannot edit .github/workflows/*.yml or other immutable paths from which it could weaken its own controls.

Example (illustrative):

# Protected surfaces owned by platform/security; agent user has read-only access.
chgrp -R platform .github policies validators .sdac
chmod -R g-w,o-w .github policies validators .sdac

# Run the loop as a dedicated user with no sudo.
# (Containerize if you want a cleaner boundary.)

In containers, you can mount protected paths read-only and only mount a writable workspace for allowlisted surfaces. The principle is the same: the Effector can propose changes, but it cannot mutate the bedrock.

Layer 1: immutable manifest + deterministic gate

Keep the boundary in a manifest so the policy is versioned and inspectable. Then enforce it with a deterministic validator that fails on any touched immutable path.

Example manifest:

# .sdac/immutable.yaml
version: 1
immutable:
  paths:
    - ".github/workflows/**"
    - "policies/**"
    - "validators/**"
    - "config/production.yaml"
    - ".sdac/immutable.yaml"
  patterns:
    - "*.pem"
    - "*.key"
reasons:
  ".sdac/immutable.yaml": "Defines the immutable boundary"
  "config/production.yaml": "Production configuration"

Example gate (illustrative):

import fnmatch

IMMUTABLE = [
    ".github/workflows/**",
    "policies/**",
    "validators/**",
    "config/production.yaml",
    ".sdac/immutable.yaml",
]

def is_immutable(path: str) -> bool:
    return any(fnmatch.fnmatch(path, pat) for pat in IMMUTABLE)

def validate_touched_paths(touched_paths: list[str]) -> None:
    violations = [p for p in touched_paths if is_immutable(p)]
    if violations:
        raise RuntimeError(f"Immutable path(s) modified: {violations}")

This gate belongs in CI, not in the agent. A .gitignore rule does not count as enforcement; it hides files, but it doesn’t prevent changes.

Layer 2: immutable doesn’t mean “trusted input”

Even when a file is immutable, it can still contain instruction-shaped text. If you include immutable configs, runbooks, or tickets in context, treat them as evidence with provenance (Chapter 2), not as authority.

CODEOWNERS: Defining Human Responsibility

CODEOWNERS is the first human accountability layer. It assigns review responsibility to specific paths. For SDaC, that means the agent definition, its policies, and the validation pipelines already have named human owners before any PR is opened.

Example (trimmed):

# CODEOWNERS (illustrative)
/core/              @platform-engineering
/tools/             @platform-engineering
/validators/        @platform-engineering @security-team
/policies/          @platform-engineering @security-team
/.sdac/             @platform-engineering @security-team
/.github/workflows/ @platform-engineering
/CODEOWNERS         @platform-engineering

With this configuration, most modern Git platforms can automatically require approval from the specified teams when a pull request (PR) touches your engine, Validators, policies, or CI workflow definitions. Just as important, the CODEOWNERS file itself is protected. Otherwise the approval model can be edited out of existence.

Branch Protection Rules: Enforcing the Rules of Engagement

CODEOWNERS declares who must review. Branch protection makes that requirement enforceable. At the repository level, it prevents direct pushes to critical branches (like main or production), requires passing status checks, and mandates approving reviews, often integrating directly with CODEOWNERS.

Key Branch Protection Rules for SDaC:

These rules create a hard barrier. An SDaC agent can open a PR with its proposed changes, and it can even run tests and linting. But the merge button remains inaccessible until all human approvals and automated checks are satisfied.

Scope Guard: Blast Radius as a Permission System

Immutable Infrastructure protects the graders. A Scope Guard protects the rest of the repository from scope leak by enforcing a write allowlist.

Enforce it twice:

Example (Mission scope):

scope:
  write_allowlist:
    - "src/**"
    - "tests/**"
  denylist:
    - ".github/**"
    - "policies/**"
    - "validators/**"

This is how you keep autonomy focused: the Mission defines the blast radius, and the system enforces it mechanically.

Mission Gate: Validation Without the Agent

To keep Physics impartial, decouple validation from generation. A Mission Gate runs a Mission’s acceptance criteria without running the model.

In practice:

This turns “good” into executable law.

CI Path Filters: Targeted Vetting

Branch protection ensures every change passes some CI. Path filters decide which extra checks run for critical paths.

Mechanically, this is just: “when policies/** changes, run the policy suite,” and “when .github/workflows/** changes, run the CI self-check suite.” Everyday app PRs stay fast; governance surfaces get deeper scrutiny.

Human as Supreme Court; Break-Glass; Kill Switches

Some decisions stay explicitly human. These are the “Supreme Court” moments, the break-glass procedures, and the kill switches.

Human as Supreme Court

The human role in SDaC is to serve as the ultimate arbiter of intent and safety. When an autonomous system proposes a change and the automated checks are green, a human still provides the final approval. This is especially true for changes to the SDaC system itself. CODEOWNERS and branch protection funnel these high-impact changes to the engineers with the most context and authority.

Break-Glass Procedures

Despite all planning, emergencies happen. A critical bug in a policy, an agent generating bad code, or a security vulnerability might require an immediate, unreviewed fix that bypasses standard procedures. A break-glass procedure is a documented, auditable process for bypassing your standard controls in an emergency.

A good break-glass procedure is:

  1. Rare: It should be a last resort, not a shortcut.

  2. Documented: Steps are clear, including who can invoke it and under what circumstances.

  3. Auditable: Every invocation leaves an undeniable trace (logs, incident tickets, alerts).

  4. Post-mortem enforced: Every use requires a post-mortem to understand why it was needed and how to prevent future occurrences.

Example:

Keep it compact and enforceable:

  1. Trigger: only Sev-1 / Sev-2 incidents.
  2. Approvals: at least two designated approvers.
  3. Action: designated responder performs the emergency merge.
  4. Audit: incident ticket id required; alerts and logs required.
  5. Follow-up: post-mortem required within 24 hours.

The companion repo (github.com/kjwise/aoi_code) includes a concrete governance/BREAK_GLASS.md template and a workflow template (governance/ci/break_glass_checker.yml) you can adapt. The checklist above is illustrative policy prose; those repo files are the copy-and-specialize starting points.

This procedure makes it possible to act fast in a crisis, but ensures it’s not done lightly and always leaves an audit trail for accountability and learning.

Kill Switches That Work

Autonomous systems only stay safe if you can stop them quickly. A kill switch is the mechanism that immediately pauses or disables the agent’s ability to propose or merge changes.

Characteristics of an effective kill switch:

Example:

  1. Environment Variable: The simplest kill switch might be an environment variable (SDAC_AGENT_ENABLED=false) read by the agent’s runtime. If false, the agent merely logs its intent but takes no action.

  2. Feature Flag Service: Integrate with a feature flag service (e.g., LaunchDarkly, Optimizely, or an internal equivalent). Toggling a flag like auto_merge_enabled can instantly control the agent’s ability to create or merge pull requests.

  3. Git Branch State: A dedicated disable-auto-merge branch. A CI check on every agent-generated PR could check for the existence of this branch. If it exists, the CI job automatically fails, preventing the agent from merging.

You do not need a complex workflow to get the safety property. You need: “if kill switch is active, agent PRs cannot merge.” Implement it however your platform supports that invariant (feature flags, CI checks, required status checks).

The companion repo includes a governance/ci/agent_merge_blocker.yml workflow that implements a branch-based kill switch.

Immutable infrastructure, protected paths, and emergency controls are what keep autonomy governable. They define what the loop may change, who must approve changes to the guardrails, and how to stop the system when needed. With that boundary in place, deeper autonomy becomes a governance choice, not a leap of faith.

Actionable: What you can do this week

  1. Define CODEOWNERS for SDaC components: Identify the critical directories (e.g., agents/, policies/, validators/, CI/CD config files like .github/workflows/) in your repository. Create or update your CODEOWNERS file to mandate specific team reviews for changes to these paths, including the CODEOWNERS file itself.

  2. Configure Branch Protection for main: Set up branch protection rules for your main branch to require:

    • At least one approving review (or more, if your CODEOWNERS specify).

    • CODEOWNERS approval (if your platform supports it).

    • All required status checks (CI/CD) to pass.

    • Restrict direct pushes to main.

  3. Implement a basic Kill Switch: Choose one of the kill switch mechanisms (environment variable, feature flag, or disable-auto-merge Git branch). Implement it in your agent’s logic or a CI gate. Test that flipping the switch successfully prevents your agent from merging PRs.

  4. Draft a Break-Glass Procedure (initial version): Work with your team leads to draft a preliminary BREAK_GLASS.md document outlining who, why, and how to bypass standard controls in a critical emergency, emphasizing auditability and post-mortem requirements.

Share