← The Build Loop The Rails · CI/CD Next: Phase 7 →

Home › The Rails · CI/CD

The Rails · CI/CD

The rails, explained The pipeline every agent-built change rides — the idea beside the real example (expand any section for the full method), the complete worked example, and a precise reference.

How it works the idea beside the real example — expand any section for the full method · Example the complete Harbor episodes · Steps the same procedure, no company, just the plugin · Reference the precise mechanics

An AI agent will tell you the code is done, the tests are green, the change is safe. Sometimes it's right. Sometimes it's confidently wrong — and that's the whole problem.

The rails are how you let an agent move fast anyway: it can propose anything, but a gate — not the agent — decides what actually ships.

Every idea below is paired with the real thing — examples come from a fictional but fully worked engagement, Harbor Mutual, a regional insurer.

01

Why the rails exist at all

Speed is only safe if something catches the mistakes

An agent can produce a working change in minutes. That's a gift right up until the moment it's wrong — and a probabilistic actor is sometimes wrong. Without something standing between "the agent says done" and "it's live," speed just means breaking things faster.

The idea

The rails are a set of automatic checks every change must pass before it can take effect. They're called rails because nobody has to remember them — every change runs on them, every time, whether a human or an agent wrote it.

Build them once, early, and from then on the agent can move as fast as it likes. The rails are what make that speed trustworthy instead of terrifying.

At Harbor Mutual

Harbor's pod built the rails before a single feature existed — five automatic checks, a locked main branch, and a dev environment defined in code. Then everything they built afterward rode through them.

The rails, built once
"Five workflows, branch protection on main, a blocking Stop hook, a Bicep dev environment. Proven on the walking skeleton. Everything since has ridden them."

The rails were built once and never stopped running — under every change, from the first one to go-live.

Skip the rails and…

You're trusting the agent's word that each change is safe. The first time it's confidently wrong about something that touches real customer data, you find out in production.

Go deeper — the full method

The rails are not a phase. They get built in Phase 3 (the factory), they run under every change all through Build, the same artifact gets promoted to production in Phase 8, and they get watched in Phase 9. The pipeline is a standing standard: the workflows, the merge bar, the deploy-and-promote path, the infrastructure pipeline, the agents that work inside the pipeline — and the one principle that governs all of it.

That principle is the whole reason agent-built software can be trusted to ship at all: the agent proposes, a gate disposes. An agent investigates, plans, and produces a change as a reviewable artifact — a branch and a pull request, an infrastructure plan, a fix suggestion — but a deterministic policy layer plus a named human decides whether that change ever takes effect. This is not our invention. It is what every mature agentic-DevOps system in the industry converged on, independently, by 2026: none of them lets an agent merge to a protected branch or apply destructive infrastructure unsupervised. The rails are where that rule stops being a good intention and becomes a fact about the world.

The gates at a glance

Five workflows, plus the branch protection that makes them mandatory. ci — build/test/lint/coverage — hard block. grader — a fresh agent's check-by-check verdict against the spec, pinned to the exact changed lines — required to run; advises, never blocks. correctness — a fresh agent hunts the changed lines for plain logic defects — blocks on a high-confidence defect. security — the security-reviewer agent, on a risk:high label or any guarded path — blocks on HIGH. deploy-dev — ships the merged artifact to dev and rolls back on a failed deploy. All five ride one rule: the agent proposes, a gate disposes.

02

The rule that governs everything

The agent proposes; a gate disposes

This single sentence is the spine of the whole pipeline. The agent does the investigating, the planning, the producing — but it stops at a reviewable thing: a proposed change someone (or something) else gets to judge. The agent never has the last word on anything that's already protected.

The idea

This isn't a house rule. Every serious agent-and-DevOps system in the industry landed on the exact same shape, independently — because it's the only rule that holds when the agent is wrong.

  • The agent may produce any change — a code branch, an infrastructure plan, a fix for a broken pipeline.
  • A gate — an automatic check plus, where it matters, a named human — decides whether that change ever takes effect.

Machines decide the mechanical questions — does it build, do the tests pass. Humans own the judgment — is this the right change, is the risk acceptable.

Across the industry

Three different companies solving three different problems, one rule underneath all of them:

A leading coding agent
"Can only push to branches it creates, cannot approve or merge its own work, and its checks won't even run without explicit human approval."
A self-healing CI agent
"Diagnoses a broken pipeline and stops at a merge request with the fix. It does not push to the protected branch; it proposes."
An infrastructure drift system
"Detects configuration drift and proposes a remediation plan. Applying it is a human choice, never a silent auto-apply."
Go deeper — the full method

Strip away the vendor names and every working agentic-DevOps system in 2025–2026 is the same shape. The agent does the investigating, planning, and producing. It stops at a reviewable artifact. A deterministic policy layer plus a human decides whether the artifact takes effect. The agent never has the last word on a protected resource. We did not adopt it because the industry did; we arrived at it for the same reason they did — it is the only rule that holds when the agent is wrong, and a probabilistic actor is sometimes wrong. This is the same sentence that runs through the whole standard — gates report, humans decide — mechanically enforced rather than merely intended.

Three corollaries fall out of the principle. Each one is a design rule for the rails, not a nicety:

  • Mechanical self-validation is mandatory. Before an agent surfaces a change as done, it must re-run the build, the tests, the linters from the environment itself and prove them green — not assert success, prove it. This is exactly what the Stop hook is: the agent cannot end its turn on a red test or a broken build. An agent's claim that something works is an opinion; the environment's green is ground truth.
  • A bounded tool surface beats raw access. An agent given named, constrained tools (read a file, write a file, run the tests, run the linter) outperforms — and is far safer than — an agent handed the whole filesystem and a shell with "fix it." The constraint is what makes the agent's behavior legible and its blast radius small.
  • Autonomy is graded, and it widens slowly. A new piece of agent automation starts in a propose-and-approve posture and only graduates to a tighter loop after its behavior has been watched and trusted on real work. The risk of agentic systems is almost never the model being dumb; it is granting too much autonomy too quickly.

Underneath all three: identity is the real guardrail. What an agent can do is bounded by the permissions of the credential it runs under, not by the words in its prompt. When a prompt fails — and prompts fail — the identity is what is still holding.

03

Why "done" can't be the agent's opinion

The agent has to prove it works — not just say so

"It works on my machine" is bad enough from a person. From an agent that will cheerfully report success it didn't earn, it's a trap. So the rails refuse to take the agent's word for anything they can check themselves.

The idea

Before an agent is allowed to call a change done, it has to re-run the build, the tests, and the checks from the environment itself and show them green. Not assert success — prove it.

There's a small script that enforces this: when the agent tries to finish its turn, the script fires and looks at the actual state. Broken build or a red test? It refuses to let the agent stop. "Done" stops being the agent's opinion and becomes a fact the environment confirmed.

At Harbor Mutual

Every change Harbor's agents built had to clear this before a human ever saw it. On spec 0016 — the change that merges duplicate claims — the agent built it, the script held it to a green suite, and:

11 tests passed before a human ever opened the change

Eleven green tests — and, as the next module shows, that still wasn't enough on its own. Green is necessary; it's just not sufficient.

Why this comes first

Every other gate assumes the basics already pass. The self-check is what makes "the tests are green" mean something — the agent couldn't have stopped otherwise.

Go deeper — the full method

Mechanical self-validation is the first corollary of agent proposes, gate disposes. Before an agent surfaces a change as done, it re-runs the build, the tests, and the linters from the environment itself and proves them green. This is the Stop hook: a script that fires when an agent tries to finish its turn. If tests fail or the build is broken, it refuses to let the agent stop. "Done" stops being the agent's opinion.

The same discipline applies to agents working inside the pipeline. A self-healing CI agent does not get to claim the pipeline is fixed — it re-runs the tests and the lint from the environment and proves green before it surfaces a fix. A proposed fix the agent has not validated against the environment is not a fix; it is a guess wearing a green checkmark.

The Stop hook is also the first rail proven by deliberate failure: before Foundation closes, a failing test is planted to confirm the hook actually blocks an agent from finishing. A rail that has only ever seen green has not been tested; it has been assumed.

04

What actually stands at the gate

Five checks — and knowing which ones can say "no"

The rails are five automatic checks that run on every proposed change. The thing that matters most about them is which ones block (a hard stop, the machine's call) and which only advise (an input to a human's call). Confusing the two is how teams either ship unreviewed agent code or drown every typo in ceremony.

The idea — the five checks
  1. The build-and-test check — does it compile, do the tests pass, is enough of the new code covered? Blocks. A red one is a closed door.
  2. The grader — a fresh agent reads the spec and the change and posts a verdict. Required to run, but only advises.
  3. The correctness check — a fresh agent, separate from the grader, hunts the changed lines for plain logic bugs: the off-by-one a green test suite never exercises. Blocks on a high-confidence defect; a named human can override on the record.
  4. The security check — a reviewer agent runs on high-risk changes and anything touching a dangerous file. Blocks on high risk.
  5. The deploy — once merged, ships the change to dev and rolls back if the deploy fails.
At Harbor Mutual — the grader earns its place

On spec 0016, the build-and-test check went green — all eleven tests. Then the grader, a fresh agent that did not write the code, walked the spec's checks one by one and caught the hole the eleven tests never thought to test:

What the grader found that green tests missed
"Two claims with an empty policy number were landing in the same match bucket and merging into one. Real bug, real data, invisible to a green suite."

The grader advised — its verdict was a comment. Sara, who didn't write the change, read it, agreed, and bounced it back to be fixed.

Why the grader never blocks

A confident, polished AI verdict is exactly how an agent talks a human into approving harm. So the grader surfaces the truth and hands it to a person who owns the call. The machine informs; it never decides.

Go deeper — the full method

The rails are five workflows in the repo, plus the branch protection that makes them mandatory. Each one is clear about whether it blocks (a hard gate, a machine's call) or advises (an input to a human's call).

WorkflowFires onBlocks or advisesWhat it does
cievery PRBlocks (hard gate)Build, tests, lint, 80% coverage on new code. The mechanical floor — a red CI is a closed door.
graderevery PRAdvises (required to run, can't block)A fresh AI agent reads the spec in the diff and posts a check-by-check verdict as a PR comment, each check pinned to an exact changed line. "The grader ran" is required; what it said is the human Checker's input.
correctnessevery PR that changes sourceBlocks on a high-confidence defectA fresh AI agent — separate from the grader — hunts the changed lines for plain logic defects: the bug class ci can't see (it compiles, the tests pass) and security doesn't look for (it's not exploitable, just wrong). Passes trivially when no source changed; a named human can override on the record.
securitythe risk:high label or any PR touching a registered gated pathBlocks on HIGH; advises otherwiseRuns the security-reviewer agent. Path-triggered: fires on any PR touching a guarded path (auth, migrations, the pipeline, infra), independent of the spec's tier.
deploy-devmerge to mainn/a (it ships)Deploys the merged artifact to the client's dev environment, and restores the last good version when a deploy fails.
deploy-promotemanual only — never a triggern/a (it ships)Promotes that same artifact onward to test and production. Holds until a named approver signs, and refuses a target environment that has no approver configured. Restores the last good version when a promotion fails.

The grader advises; it never blocks. It is tempting to let a confident AI verdict gate the merge. We do not, and the reason is in the threat model: a polished, plausible explanation is exactly how an agent talks a human into approving harm. The grader's job is to surface the check-by-check truth — including the hole the author was blind to — and hand it to a person who owns the decision.

An agent gate may block on a defect, never on a judgment. Security blocks on a HIGH vulnerability and correctness blocks on a high-confidence logic defect; the grader does not. The line is not how much we trust the agent — it is what is being decided. "There is an off-by-one on this line" is a concrete, checkable defect an agent can hold the door on; "is this the right change for the spec" is a judgment a polished verdict should never settle alone. Both blocking agent gates carry a named-human override on the record, so a false positive informs without trapping a good change behind a machine's mistake.

The reviewers are scaffolded, so their verdicts are reproducible. An AI reviewer told to "inspect the diff" invents line numbers, wanders outside the change, and skims a large one. The grader and the correctness gate are instead handed a deterministic, machine-generated list of exactly which lines changed as their scope — so every finding pins to a real changed line and none is skipped. Same diff in, same anchors out.

The correctness gate runs on the server because the local check can't bind a human. A pre-push check on the agent's own machine refuses to let it push until its self-review is recorded — but that binds only the agent; a human pushing from their terminal sails past it. So the review runs again on every PR: the local check is fast-feedback courtesy, the server gate is the universal backstop that does not care who pushed or how.

The security gate is path-triggered, not just tier-triggered. A change can be tiered MEDIUM and still touch the auth code, the migration folder, or the pipeline YAML. Registering a guarded path with the security workflow makes it run on any PR touching that path, in addition to firing on the risk:high label. The gate is the workflow plus the path registration.

05

The one moment that matters

The merge bar — what every change must clear to land

Five checks are just suggestions until something makes them mandatory at the moment a change merges into the main branch. That's the merge bar: settings on the repository that turn the rails from a good habit into a law.

The idea — the bar every change clears
  • The checks are green — build, tests, coverage all passing.
  • The grader has run — it can say anything, but it must have run and posted.
  • The correctness check passed — no high-confidence defect, or a named human recorded the override.
  • Someone who didn't write it approved it — the author is never their own only approver. This rule survives even a two-person pod.

A high-risk change adds two more: the security check passed, and a named human recorded a sentence accepting the risk — not a thumbs-up, a name attached to a decision.

At Harbor Mutual

Spec 0016 was tiered HIGH — merging claim records is hard to undo, and a wrong merge mangles two policyholders' data. So it had to clear the full bar:

Wes Carter — lead engineer
recorded a named sign-off in the change, accepting the HIGH risk, before a non-author merged it

The agent could push only to its own branch, could not merge its own work, and every commit it made was co-authored — so the history always shows who, or what, wrote each line.

The one escape hatch

A true-emergency merge past a gate needs the Pod Lead plus one other human, an exception label, and a retro item. Two of those in a month isn't bad luck — it means the gate or the specs are wrong. Fix that; don't normalize the bypass.

Go deeper — the full method

Branch protection is what turns five workflows from suggestions into rails. It is repository configuration — set by the Setup Owner or a named client admin — and it makes the gates mandatory at the one moment that matters: the merge.

Every PR, to merge, must clear:

  • CI green — build, tests, lint, coverage all passing. Hard block.
  • The grader has run — the workflow completed and posted. The verdict can say anything; the running is required.
  • Correctness review passed — no high-confidence defect on the changed lines, or a named human recorded the override. The override is a label, audited in the PR timeline; the guarantee that the person clearing it is not the author comes from the non-author approval below, not the label — a gate should never claim an enforcement it cannot check.
  • A non-author approval — someone who did not write the change approved it. The author is never their own only approver; this survives even a two-person pod.

A risk:high change adds two more:

  • The security workflow passed — the security-reviewer agent's pass, blocking on HIGH.
  • A named human sign-off recorded in the PR — a person, by name, accepting the risk. A recorded sentence, not a thumbs-up.

Constraints on the agent, enforced by the platform, not by trust: it pushes only to branches it creates (spec/NNNN-*), never to main; it cannot approve or merge its own work; every commit it makes is co-authored, so provenance is in the history.

The one escape hatch is deliberately expensive: a true-emergency merge past a gate requires the Pod Lead plus one other human, an exception label, and a Retro+ agenda item. Two exceptions in a month means the gate or the specs are wrong — fix that, don't normalize the bypass.

06

From merged to in production

A merge isn't a deploy — promotion is

Landing on the main branch doesn't put a change in front of customers. The rails move it up through environments — dev, then test, then production — and the gap between "merged" and "in production" is where the most protected stop in the whole standard lives.

The idea
  • Merge → dev, automatically. The merge already cleared the bar, and dev is the safe place to exercise the rails constantly.
  • Dev → test, on demand. Promoted deliberately, smoke-tested on arrival.
  • Test → production, by ceremony. Evidence on the table, every named role asked, a human saying go out loud, on the record. No amount of agent confidence ever removes this stop.

Two rules govern every promotion: promote the proven artifact, never rebuild it (a rebuild is something no environment ever verified), and a rollback that's never run is a wish — rehearse it before you need it.

At Harbor Mutual

At go-live, the pod rehearsed the rollback in test first — deploy, roll back, redeploy — and it failed:

The rollback that failed in rehearsal
"The previous artifact came back, but a config key had moved ahead of it, so the rolled-back app booted against config it didn't understand."
Found on a Tuesday, not in an incident
"The fix rode the loop as spec 0046 — config versioned with the artifact. Re-rehearsed Wednesday: clean. The same artifact that passed test, rc-1.0.1, was promoted to production, not rebuilt."
11.4 days3h 6m old baseline → the first real claim's coverage recommendation after go-live
Go deeper — the full method

A merge is not a deploy to production. The rails move a change up through environments, and the gap between "merged" and "in production" is where the most important word lives: promotion.

  • Merge → dev, automatically. The deploy-dev workflow ships every merged change to dev with no human in the loop — the merge already cleared the bar, and dev is the safe place for the rails to be exercised constantly.
  • Dev → test, on demand. Promoted deliberately, smoke-tested on arrival. The test environment is added at the first hardening pass, not in Phase 3.
  • Test → prod, by ceremony. The first promotion is the Phase 8 go/no-go: evidence on the table, every named role asked, a human saying go out loud, on the record. Every promotion after rides the client's release cadence — and a human go/no-go every time. The single most protected stop in the standard.

Promote the artifact; never rebuild it. The same build that passed test goes to production — promoted, not freshly compiled "real quick." A rebuilt artifact is something no environment ever verified, running in the one environment where that matters.

A rollback that has never run is a wish. The deploy pipeline restores the last good version on a failed deploy — and that path is rehearsed in test (deploy → roll back → redeploy) with the trigger condition written down in advance, not invented mid-incident. A rail that has never failed safely has not been proven.

07

The change with no clean undo

Infrastructure is HIGH risk every time — so it's read-only until the last step

The cloud environment itself is defined in version-controlled files instead of clicked together by hand. An agent can draft a change to it in seconds — but a wrong one is hard to undo and runs in production later. So the whole pipeline between draft and changed cloud is deliberately read-only.

The idea — a read-only funnel

The agent's draft goes through a funnel where every step before the last one just looks, changing nothing:

  • Does it even compile? A malformed change is caught by a machine, not by a half-finished apply.
  • Does it break the rules? An automatic policy check blocks public storage, unencrypted disks, untagged resources — on every change, not on a reviewer remembering.
  • What exactly would it do? A dry-run previews every create, change, and delete — and a human reads it before approving.

Only then does a scoped apply run. A change approved without reading its preview is a change approved blind.

At Harbor Mutual

Adding the test environment was an infrastructure change — so it went down the funnel. The compile check passed. The policy check did not:

Caught mechanically, on the change, before anything was applied
"PSRule flagged two things — a storage account that would have defaulted to public network access, and a resource group missing the required cost-center tag."

The agent fixed both, the gate went green, Tom read the dry-run to confirm nothing unexpected, and only then did the apply run — under an identity scoped to that one resource group and nothing else.

The one mistake with no clean undo

An agent running a destructive infrastructure apply unattended — deleting or replacing, with no human reading the dry-run first. Every read-only step exists for exactly this. Never auto-apply a destructive change.

Go deeper — the full method

Infrastructure is code, it lives in the repo, and it is HIGH risk every time — because it is hard to undo and it runs in production later. The agent-safe IaC pipeline is a read-only-until-the-last-step funnel:

StepWhat runsBlocks?
Schema validatebicep build against the resource schemaHard block on a malformed template
Policy-as-codePSRule for Azure (public access, encryption, tags, region)Hard block on a policy violation
Dry-runbicep what-if — the exact change previewPosted to the PR; a human reads it
Cost / budget reviewThe price of the diff on the PRCan block on a budget breach
ApprovalA named human approves the what-if and the policy resultRequired to proceed
ApplyScoped, least-privilege deploy identity— (the only step that changes cloud)
Drift assessmentScheduled real-infra-vs-code compareOpens a remediation PR; never auto-applies

Every step before the apply is read-only — the agent and the pipeline can do all the thinking, validating, and previewing they want, and nothing changes until a human has read the dry-run and a least-privilege identity executes a scoped apply. The default profile is .NET/Azure (Bicep); the pattern is stack-independent. A destructive apply an agent runs unattended is the one mistake on this page with no clean undo.

08

What holds when the prompt fails

Identity is the real guardrail — not the instructions

An agent's instructions can be talked into something they shouldn't do — prompts fail. What an agent can actually do isn't bounded by the words in its prompt; it's bounded by the permissions of the credential it runs under. That's the wall still standing when everything else gives way.

The idea
  • Every actor gets its own narrow credential — scoped to exactly what its job needs and nothing more. An over-scoped identity means a subverted prompt can reach everything that identity could.
  • Secrets live in the client's vault, never in the repo — not in code, not in a config file, not in a spec. A key in a commit is the one unrecoverable mistake.
  • There's a kill switch — a whole class of agent identity can be disabled in one move if something goes wrong.
At Harbor Mutual

When a red pipeline got a self-healing agent pointed at it, the agent ran under its own scoped credential — able to read the repo and open a proposed change, nothing more:

The fix agent's identity could open a change but never merge one.
If its prompt had been steered somewhere bad, the identity was the wall still standing.

And before go-live, every secret the pod had ever touched was rotated into production-only values the pod couldn't read. The engagement reached production with the pod having never known a production secret.

Go deeper — the full method

Every actor that touches the rails — human or agent — runs under its own credential, scoped to exactly what its job needs. This is the guardrail still standing when a prompt has been talked into something it shouldn't do. No role grant, no resource access — and an agent's instructions cannot grant it a role.

  • Each agent that acts on infra or the pipeline gets its own least-privilege identity — a managed or federated credential (no stored secret, rotated automatically) scoped to the resource or resource group, never the whole subscription. Every action is then attributable to it in the audit log.
  • Secrets live in the client's vault, never in the repo — not in code, not in CLAUDE.md, not in a spec. A key in a commit is the one unrecoverable foundation mistake: a rotation event and an audit-log review, not an edit.
  • Secrets rotate before production — into production-only values the pod cannot read, signed by the client's security. The engagement should end with the pod having never known a production secret.
  • There is a kill switch — conditional-access policy at the class level can disable a whole category of agent identity in one operation.

Treat an agent as a first-class non-human identity with its own lifecycle, owner, and time-bounded access — not a script running under a human's credentials. Every agent identity has a named owner or it does not exist.

09

Now watch one change ride the rails

A single change, from proposed to deployed

You've got the ideas; here's the actual journey one change takes — spec 0016 at Harbor, the duplicate-claim merge. Notice that the agent's work stops early, at a proposed change, and a gate (and a human) carry it the rest of the way. Step through it.

Open PR · the agent proposes

The agent stops at a proposed change

The agent built the change on its own branch — it can't touch main — and the Stop hook held it to a green suite first: eleven tests passing before it was allowed to finish. Then it opens the change for review and stops. From here, gates and humans take over.

CI · the mechanical floor

Does it build, test, and cover?

The build-and-test check runs automatically: compile, the full test suite, the linter, and coverage on new code. It went green — all eleven tests. This one blocks: a red result is a closed door no human can wave through without an expensive exception.

Grader · a fresh set of eyes

A second agent checks against the spec

A fresh agent that did not write the code reads the spec and the change, walking the acceptance checks one by one. It caught the hole eleven green tests missed — two claims with an empty policy number merging into one. It posts a verdict and advises; it never blocks.

Security · because it's HIGH

The security reviewer runs

Spec 0016 is tiered HIGH — merging claim records is hard to undo. That fires the security check, the reviewer agent. On a HIGH change this one blocks, and the merge bar will also demand a named human's recorded sign-off accepting the risk.

Review · a human disposes

Someone who didn't write it decides

Sara — not the author — read the grader's catch, agreed, and bounced the change back to be fixed on the same branch. Every gate, including a fresh grader run, ran again on the corrected change. Then Wes recorded his HIGH sign-off.

Merge · the bar clears

A non-author merges

With CI green, the grader run, a non-author approval, the security pass, and Wes's named sign-off all true, the merge bar clears. A non-author merges — the agent never could have merged its own work even if it tried.

Deploy · to dev, automatically

The deploy ships it to dev

The merge already cleared the bar, so the deploy workflow ships the change to dev with no human in the loop — and would roll back automatically if the deploy failed. Production is later, and by ceremony; dev is where the rails get exercised constantly.

1 / 7
Go deeper — the full method

One spec = one branch = one PR. The journey of spec 0016 is the principle in motion: the agent investigates, plans, and produces the change as a reviewable artifact, then stops. The Stop hook proved the suite green before the agent could finish. On the PR, ci blocks and went green; grader ran and advised, catching the empty-policy-number bug the eleven tests never thought to cover; security fired on the risk:high label and blocked until it passed.

A non-author — Sara — read the grader's verdict and bounced the change back; the fix landed on the same branch and every gate, including a fresh grader run, re-ran. Wes recorded the HIGH named sign-off. Only with CI green, the grader run, a non-author approval, the security pass, and the recorded sign-off all true did the merge bar clear — and a non-author merged, because the agent cannot approve or merge its own work. The deploy-dev workflow then shipped the merged artifact to dev automatically, and would have rolled back on a failed deploy. Production is later, by ceremony; dev is where the rails get exercised constantly.

10

How it goes wrong

The failure modes, and the defense against each

Every one of these has happened to someone. Knowing them by name is half the defense — and Harbor's rails caught several of them in the act.

The trapWhat it looks likeThe defenseAt Harbor
The unexercised pipelineCI green because nothing real ever ran through itForce each rail to fail on purpose and prove it catches.Every rail shaken down before Foundation closed
Auto-merging the agent's fixA self-healing pipeline pushes its own fix to mainThe agent proposes; the merge bar disposes. Stop at the PR.Held — the self-heal agent stopped at a PR
The raw-shell agent"Here's the whole repo and a shell, fix the build"A small, named tool surface — read, write, run tests, run lint.Fix agent had four tools, nothing more
The rebuilt artifactProduction gets a fresh build, not the one that passed testPromote the proven artifact; never rebuild.rc-1.0.1 promoted, not recompiled
The rollback only ever writtenDocumented, reviewed, never run — until an incidentRehearse it in test, before it's needed.Caught — failed in Tuesday's rehearsal
A secret in the repoA key committed to code, a config, or a specThe client's vault from day one.Pod never knew a production secret
The unattended destructive applyAn agent applies infra that deletes or replaces, unreadRead-only until the last step; a human reads the dry-run.Tom read every what-if before any apply
Ignoring driftReal infra diverges from code and nobody actsDrift is a proposed fix waiting for a human, not a notice to dismiss.Drift opened a PR; Tom absorbed it into the code
Go deeper — the full method
  • The pipeline that exists but was never exercised. CI green because nothing real ever ran through it. The rails are proven by a change breaking and being caught, not by the YAML being present. Force each failure.
  • Auto-merging the agent's fix. A self-healing pipeline that pushes its own fix to the protected branch has thrown away the only rule that makes it safe. Stopping at the PR is the whole design.
  • The raw-shell agent. Handing an agent the whole repo and a shell with "fix the build" instead of a bounded, named tool surface. It will touch three things nobody wanted touched, and the log won't tell you why.
  • The rebuilt artifact. Production gets a fresh build instead of the promoted one that passed test — now production runs something no environment verified. Promote, never rebuild.
  • The rollback that was only ever written. Documented, reviewed, never run — then executed for the first time during an incident. Rehearse it in test.
  • A secret in the repo. The one unrecoverable foundation mistake. The client's vault from day one.
  • The unattended destructive apply. An agent runs an infrastructure apply that deletes or replaces, with no human reading the dry-run first. Every step before the apply is read-only for exactly this reason.
  • Autonomy granted too fast. Wiring a brand-new agent automation straight to closed-loop because it worked twice. Autonomy widens slowly, after behavior is watched.
  • The over-scoped agent identity. An agent running with subscription-wide rights "to keep it simple." When its prompt is subverted, the blast radius is everything it could reach. Scope to the resource, give it its own identity, keep the kill switch.
  • Approval fatigue. Gating every trivial step behind a human until they rubber-stamp without reading. Shift oversight from per-step to per-strategy — approve the plan up front, retain the ability to intervene.
  • Ignoring drift. The schedule flags that real infrastructure diverged from the code, and nobody acts, so the repo stops being the truth. Drift is a proposed remediation waiting for an operator, not a notification to dismiss.

When the rails are doing their job

The agent moves fast — and you never have to trust its word

The rails aren't a brake on the agent. They're what lets you take it off the leash: it can propose anything, as fast as it likes, because nothing it proposes takes effect until a gate — and where it matters, a named human — says so. One principle, wired into every place a change could go wrong. Where to go next:

01

The rails are not a phase — they are the standing pipeline every change rides

What rides the rails

Harbor Mutual — a fictional regional insurer — hired a five-person pod to rebuild how property-insurance claims get reported and decided. A claim takes a median of 11.4 days from FNOL (first notice of loss) to a coverage decision; the target is 5 days or less. The rails have no calendar and no exit gate. They have a merge bar, and one thing arrives at it: a pull request from one spec. One spec = one branch = one PR. This page walks what that PR carries in, the five rails it rides, and what it leaves behind when it clears.

What arrives at the bar — the reviewable artifact and its context specs/NNNN-name.md the diff (base … HEAD) risk tier — from risk-tier-map.md the co-authored commits (who, or what, wrote each line)
The spec, the diff, the tier

Three things define a change before a single rail fires.

  • The spec. A committed file, specs/NNNN-name.md, that rides in the PR's own diff — so intent and implementation sit in one view. The grader reads this file, not the PR description, as the authority on what the change was for.
  • The diff. Everything the branch changed against main. A deterministic helper (diff-anchors.sh) turns it into a changed-line set the AI reviewers must anchor every finding to — no drifting, no skimming.
  • The risk tier. HIGH / MEDIUM / LOW, assigned from the Phase-3 risk-tier-map.md. A HIGH change (or one touching a gated path) pulls in the security rail and a named sign-off; the tier is what decides how hard the change is gated.
Before the rails: the local hooks

The rails are server-side, on the PR. But two gates fire earlier, on the agent's own machine, and they are part of the story: done means the hook lets you stop.

  • The Stop hook (stop-gate.ps1) refuses to let the agent end its turn on a red build — the environment's green, not the agent's opinion.
  • The review gate (review-gate.ps1) refuses a push until /code-review and /simplify receipts exist for the exact commit.
The coverage boundary the hooks admit
A hook only fires for actions taken through Claude Code. A human pushing from their own terminal sails past it — which is exactly why the same checks run again, server-side, as required rails. A rail that only holds when the well-behaved actor cooperates is not a rail.

The rails (automated actors)

ci.ymlbuild-and-test — the mechanical floor; hard block
grader.ymla fresh agent's verdict against the spec; advises
correctness.ymla fresh agent hunts logic defects; blocks on one
security.ymlthe security-reviewer on gated paths / risk:high; blocks on HIGH
deploy-dev.ymlships the merged artifact to dev; rolls back on failure

The humans at the bar

Rob FeldSetup Owner — owns the rails as a product; merges none of it alone
Jonah KimRob's named deputy — reads every rails change
Sara WhitfieldOrchestrator / Checker — the non-author at the merge bar
Nadia BrooksQuality Engineer — proves each rail fails safely
Tom ReillyHarbor platform — owns branch protection, runners, secrets
Wes CarterHarbor lead engineer — signs HIGH changes
The codes and labels, decoded

The rails run on a small vocabulary of stable IDs and GitHub labels. A change's identity, its tier, and every override it uses are all one of these:

Code / labelMeansWhere it acts
NNNN-name.mdA spec file — one feature, one branch, one PRThe grader reads it as the authority on intent
risk:highA PR label declaring the change HIGH riskFires security.yml; adds a named sign-off to the bar
accepted-risk:correctnessA PR label recording an audited correctness overrideLets correctness.yml pass a BLOCK verdict
gate-exceptionThe label on a true emergency merge past a gateNeeds the Pod Lead + one human + a Retro+ item
ADR-NNAn architecture decision record — a signed choiceADR-001 the replica staleness rule (02:00–04:30)
REQ-NN · D-NN · Q-NNRequirement · product decision · open questionD-07 duplicate-merge; REQ-019 fast path; Q-15 replica access
rc-X.Y.ZA release-candidate artifact — the proven bytesrc-1.0.1 promoted to prod, never rebuilt (Phase 8)

The required-check names are the job names, not the file names: build-and-test, grader, correctness-review, security-review. Rename a job and you must rename its required-status-check context in branch-protection.json or the bar stops enforcing it.

02

Five workflows — and the single most important fact about each is whether it can say "no"

The five rails, in order

A PR fires all five rails. Two block mechanically (a machine's call), two advise or block on a defect (an AI agent's call, scaffolded so it is checkable), and one ships. Confusing block with advise is how a team either ships unreviewed agent code or drowns every typo in ceremony. Below, each rail is read from the actual YAML in kit/workflows/: what triggers it, what it does, whether it blocks or advises, and what it leaves behind. Step through it.

Legend a command does it — and writes the file a person does it — and it is recorded a person does it — and nothing records it
Rail 1 — ci.yml · fires on every PR + push to main · job build-and-test

Does it compile, do the tests pass, is it covered — the mechanical floor

The only rail with no AI in it. It restores, builds, runs the test suite with coverage, and uploads the built artifact. It answers only what a machine can answer for certain, and any red here is a hard block — a closed door no human waves through without a recorded exception. The standard's 80% coverage floor is enforced in the test runner so a coverage miss is a non-zero exit, not a low number in a passing report.

Tooling ci.yml build-and-test ci.yml eval-gate (optional) gh pr checks
Leaves behind the CI run (Actions log) coverage/ — the uploaded artifact deploy-dev promotes
At Harbor

On the first walking-skeleton slice (spec 0001), the coverage gate miscounted generated files and read low. Caught on the PR, fixed cheap, before any feature depended on it. The optional eval-gate is a second hard block for agentic behavior that must not regress; a time-boxed bypass is recorded in profile/eval-bypasses.md — the gate still runs, the ledger only says why a known failure was accepted.

Rail 2 — grader.yml · fires on every non-draft PR · job grader

A fresh agent grades the change against its spec — and only advises

An agent that did not write the code reads the committed spec file in the diff and walks its acceptance checks one by one, pinning each verdict to a changed line, and posts one PR comment (updated on re-runs). This rail is required to RUNgrader is a required check, so a PR can't merge until it has run — but its verdict never blocks. "The grader ran" is what the method requires; what it said is the human Checker's input, not a gate. It is the one rail that fails soft: the Claude step is continue-on-error, so a missing key or an API hiccup never adds false red.

Tooling grader.yml claude-code-action (--model sonnet) diff-anchors.sh changed-line set rubrics/grader.md
Leaves behind the grader's PR comment (verdict, per-check, line-anchored) the Checker's decision — a human reads it and acts
At Harbor

On spec 0016 (duplicate-claim merge, HIGH), eleven tests passed and CI went green. The grader flagged the hole the eleven never covered: two claims with an empty policy number merging into one. It advised; it did not block. Sara (not the author) read it, agreed, and bounced it back — the principle in its purest form. The full episode is section 04.

The gap you should know about

The grader's value depends on a human reading the advisory verdict — and nothing records that they did. The non-author approval at the bar implies it, but there is no receipt that the grader's comment was read and weighed. It is also lenient by design: if the PR carries no specs/NNNN-*.md, the grader falls back to the PR description and emits a warning, not a block.

Rail 3 — correctness.yml · fires on every PR; reviews when source changed · job correctness-review

A different fresh agent hunts logic defects — and blocks on one

The bug class the others miss: CI proves it compiles and tests pass, security covers exploitability, the grader checks intent — none asks "is this code correct?" A fresh agent (neither the author nor the grader) reviews the changed lines for off-by-ones, null paths, inverted conditions, and writes a machine verdict. It blocks on a high-confidence defect (CORRECTNESS_VERDICT: BLOCK) unless a named human records the accepted-risk:correctness label — an audited override on the PR timeline. It fails closed: a source change whose review can't complete blocks rather than passes. When no source changed, it short-circuits to a trivial pass, so it can be a required check without leaving low-risk PRs stuck pending.

Tooling correctness.yml claude-code-action (--model opus) verdict runner temp (anti-tamper) gh pr edit --add-label accepted-risk:correctness
Leaves behind the correctness PR comment + the red/green check the audited override label, when a human accepts the risk
At Harbor

At Phase C close, spec 0051 extracted the replica-staleness guard into one shared check — a refactor the tests couldn't vouch for and the grader was silent on (the spec promised no new behavior). The correctness rail read the extraction against both originals, confirmed the 02:00–04:30 boundary and the degraded path were byte-for-byte the prior behavior, and cleared it. The refactor merged on proof, not crossed fingers (section 04).

Where the local check can't bind

A pre-push hook does correctness self-review on the agent's machine — but it binds only the agent; a human pushing from their terminal sails past. So this rail runs again server-side on every PR. The label is an audited override, not an authorization check: the workflow cannot reliably tell the label's applier from the author, so the "a non-author signs off" guarantee comes from the bar's non-author approval, not the label. The YAML says so in its own comments.

Rail 4 — security.yml · fires on every PR; reviews on gated paths / risk:high · job security-review

The security-reviewer, path-triggered — and blocks on HIGH

Path-triggered, not just tier-triggered: the workflow runs the security-reviewer agent on any PR touching a guarded directory — the regex is (^\.github/|/Auth/|/Identity/|/Security/|/Migrations/|^infra/) — or carrying the risk:high label. A change tiered MEDIUM that touches the auth folder is caught regardless. It blocks on a HIGH finding (SECURITY_VERDICT: BLOCK) and fails closed if the review can't complete. When no gated path changed and no risk:high label is present, it passes trivially.

Tooling security.yml security-reviewer (--model opus) gh pr edit --add-label risk:high rubrics/security.md · CODEOWNERS
Leaves behind the security PR comment + the red/green check the named human sign-off — a PR convention, not a tool receipt
At Harbor

On the walking skeleton's HIGH slice (spec 0002, the replica verification read), risk:high fired this rail; Dan recorded a named sign-off under the Q-15 controls before merge. The first PR to touch acknowledgment templates (spec 0003) tripped the PII template-review gate the same way — the path caught it, independent of its tier.

The override the YAML doesn't implement

The merge bar promises a risk:high change can proceed on "a named human sign-off recorded in the PR," and the workflow's own error text says "Resolve them or record a named accepted-risk sign-off." But the enforce step has no label and no condition that lets a recorded sign-off pass — on a HIGH verdict it always exit 1. Unlike the correctness rail, there is no coded override; the sign-off is human convention the YAML never checks. (Drift, filed.)

Rail 5 — deploy-dev.yml · fires on a successful CI run on main (the merge) · job deploy-dev

Ships the merged artifact to dev — and rolls back on failure

Not a gate; the merge already cleared the bar. Triggered by a successful CI run on main, it promotes the exact artifact CI built (downloaded from the triggering run — never a rebuild), captures the last known-good version, deploys, health- checks, and restores the last good version on any failure. Merge→dev is automatic and human-free; promotion beyond dev (dev→test→prod) is deliberate and human-gated, and is not this workflow.

Tooling deploy-dev.yml workflow_run(CI, success, main) download-artifact promote, never rebuild restore last-good on failure()
Leaves behind the deployed dev environment (the GitHub dev Environment) a rollback to the last good version, on a failed deploy
At Harbor

The rollback path is proven by failing safely first. In the Phase 8 rehearsal it failed in test on a Tuesday — a config key had moved ahead of the artifact — which spec 0046 fixed by versioning config with the release. Thursday, rc-1.0.1 was promoted to production, not rebuilt; the first real FNOL (a burst pipe, 08:14) got a coverage recommendation in 3 hours 6 minutes against the 11.4-day baseline (section 04).

Ships as a starter that fails until wired

This is the one rail built fresh to the standard, not generalized from the source harness — the source deferred deploy entirely. As shipped it intentionally fails: the deploy step is echo "::error::<<DEPLOY_STEP>> not yet wired — this starter intentionally fails until adapted."; exit 1. It encodes the §5 rules (promote never rebuild; a rollback that has never run is a wish) correctly, but the deploy and rollback steps are placeholders a client must wire and rehearse before trusting.

1 / 5
03

Every receipt a cleared PR leaves — and the ones the standard wants but no tool writes

What a merge leaves behind

A phase leaves a folder of artifacts; a merge leaves a ledger of receipts. Blue rows are written by a rail and have a real place to live. Amber rows are what the standard demands and no tool emits — the receipts that exist only as memory, convention, or a metric nobody records. Those are the rows to argue about.

ArtifactWhat it actually isWritten bySigned byLives atFeeds
grader PR commentA fresh agent's per-check verdict against the committed spec, each check pinned to a changed line; one comment, updated on re-runsgrader.yml (claude-code-action, sonnet)— (advisory)the PR conversationThe human Checker's decision
correctness verdict + commentLogic-defect findings on the changed lines, plus a machine PASS/BLOCK token on the first linecorrectness.yml (opus)PR comment + the correctness-review check; verdict in runner temp (ephemeral, anti-tamper)The merge bar — blocks on BLOCK
security verdict + commentSecurity findings on the gated files, plus a PASS/BLOCK token; fires on gated paths or risk:highsecurity.yml (opus)PR comment + the security-review checkThe merge bar — blocks on HIGH
CI run + coverage artifactBuild, tests, lint, 80% coverage — and the deployable bytes deploy-dev promotesci.yml (build-and-test)the Actions run + the uploaded coverage/ artifactdeploy-dev (promotes the CI artifact)
review receiptProof /code-review and /simplify ran against this exact commit — the reviewer's own summary, commit-boundsave-review-receipt.ps1 (local, pre-push)the reviewer (the content is theirs).claude/.review-receipts/<sha>.<kind> (gitignored, per-clone)The review gate (blocks the push until present)
co-authored merge commitProvenance in the history: who, or what, wrote each line — agent commits carry a co-author trailerthe committer / agentgit history on mainAudit; Phase C transfer
deployed dev environmentThe merged artifact, promoted (never rebuilt) and running in dev; a rollback record on a failed deploydeploy-dev.yml (once wired)the GitHub dev EnvironmentThe Build loop's constant exercise of the rails
the metrics linePer-merge dashboard data — accepted-as-is rate, review wait, the DORA four, escaped bugs (GOLD §9)nothing — no rail appends itSetup Owner (would)no path — nothing writes itThe internal dashboard; Retro+
the central provenance logEvery agent recommendation, applied artifact, and gate outcome, logged centrally (the-rails.md §9)nothing emits itSetup Ownerno path — nothing writes itAudit after a poisoned tool-return
the "grader was read" receiptEvidence the human Checker read the advisory verdict and acted on it — the whole point of an advising raila human, un-recordedthe Checkerno path — the approval implies it, nothing records itThe merge decision
the risk:high sign-offA named human's recorded sentence accepting a HIGH risk — a name, not a thumbs-upa human, by convention in a PR commentthe named signerno path — nothing templates or checks itThe merge bar (HIGH path)
Read the amber rows again

Four of the receipts the standard leans on have nowhere to live. The DORA four and the accepted-as-is rate are the numbers the rails are supposed to be watched by — yet no rail appends a metrics line, so the dashboard is hand-kept or absent. The central provenance log the-rails.md calls "what makes the rails auditable rather than merely automated" is not written by anything in the kit. And the two human receipts — that the grader was read, that a HIGH sign-off happened — survive only as a PR approval and a comment. Human work is not the problem. Human work without a receipt is.

Deliberately not produced by the rails: feature code (the specs and the loop own that), the test and production environments (the first hardening pass and Phase 8 add those), and the metrics dashboard itself. The rails emit receipts, not products — the enforcement, not the thing enforced.

04

Seven real changes, each meeting a real gate — the rails not as spec but as story

The rails doing their job

These episodes span Build (2026-04-13 to 07-10), the Phase 8 go-live (week of 07-20), Phase 9 hypercare (from 07-27), and into Phase C close (08-17). The rails were built once and never stopped running. Three ran on rails the plugin ships today (episodes 1, 5, 7); four use machinery net-new to the kit, flagged inline and tallied in section 05.

Episode 1 — the grader catches what eleven green tests missed

Build week four · Thursday 2026-05-07 · spec 0016 (duplicate-claim merge), HIGH risk.

Tooling — No plugin command. Spec 0016 rides the loop; the PR fires ci.yml + grader.yml; risk:high fires security.yml → the security-reviewer agent; Wes's named sign-off in the PR.

  • Spec 0016 implements D-07 — the same loss reported twice becomes one claim, never a rejection. Maya tiered it HIGH: merging claim records is hard to undo, and a wrong merge mangles two policyholders' data. The agent built it under tight permissions; the Stop hook held it to a green suite; eleven tests passed.
  • On the PR, ci.yml went green. Then the grader — a fresh agent that did not write the code — walked the spec's acceptance checks one by one and flagged the hole the eleven tests never covered: two claims with an empty policy number were landing in the same match bucket and merging into one. Real bug, real data, invisible to a green suite that never thought to test the empty-key case.
  • The grader advised; it did not block. Its verdict was a PR comment. Sara (not the author) read it, agreed, and bounced the change back to the same Orchestrator, who fixed the keying on the same branch. Every gate — including a fresh grader run — ran again on the updated PR.
  • Because HIGH, risk:high fired security.yml and Wes recorded a named sign-off in the PR before merge. Then a non-author merged, and deploy-dev shipped it to dev.

The rail that mattered: the grader. An agent produced a confident, green-looking change; a separate gate caught what the author was blind to; a human owned the call. Agent proposes, gate disposes.

Episode 2 — self-healing CI: a proposed fix that stops at a PR

Build week six · Tuesday 2026-05-19 · (net-new to the kit).

Tooling — No plugin command (net-new to the kit) — self-heal.yml → a bounded fix agent (read/write/run-tests/run-lint only); emits a fix/ PR, never a merge.

  • A scheduled dependency bump landed a minor version of a JSON serializer that changed a default, and the nightly build on main went red — three integration tests failing on a serialization edge. Nothing a human had touched.
  • A new kit workflow, self-heal.yml, pointed an agent at the failure. It did not get the repo and a shell. It got a bounded tool surface — read a file, write a file, run the tests, run the linter — and the failing job's log. It formed a hypothesis (the changed default), made the two-line fix, and re-ran the tests from the environment until they went green. It proved the fix; it did not assert it.
  • Then it stopped. It opened a fix/serializer-default pull request and went no further. It cannot push to main; it cannot approve its own work. Sara reviewed the proposed fix, agreed, and merged it through the same merge bar as everything else.
  • The agent ran under its own least-privilege identity — a workload credential scoped to read the repo and open a PR, nothing more. If its prompt had been steered somewhere bad, the identity is the wall that was still standing.

The rail that mattered: the merge bar, applied to the pipeline's own repairs. A self-healing pipeline that pushed its own fix would have thrown away the only rule that makes it safe. Stopping at the PR is the whole design.

Episode 3 — the flaky test that got quarantined, not "fixed"

Hardening pass one · Wednesday 2026-05-27 · (net-new to the kit).

Tooling — No plugin command (net-new to the kit) — the flaky-test state machine (Active → Quarantined → Disabled) moves the test out of the blocking set; the real fix then rides the loop.

  • The replica refresh-window test — the one guarding the 02:00–04:30 staleness degradation from ADR-001 — started failing intermittently. Same code, different result run to run: roughly one PR in twelve went red on it, on changes that had nothing to do with the replica.
  • A flaky test is not always a bug to fix; sometimes it is a test to quarantine while a human looks. The flaky-test state machine moved it from Active to Quarantined only after it cleared eligibility thresholds — failing above a set rate, failing on the default branch, across more than one pipeline, not on a single unlucky run — so it stopped gating PRs while staying visible and tracked.
  • Nadia investigated and found the real cause: the test built its boundary timestamps in local time while the service worked in UTC, so runs near midnight Chicago time straddled the window. A genuine clock-boundary race, not noise. The fix rode the loop like any change — a spec, a plan, a non-author Checker — and the test sat under a grace period before rejoining the blocking suite.
  • The pattern — build time-boundary tests in the service's own clock — went on the harvest list as the timezone test pattern.

The rail that mattered: triage over reflex. Active → Quarantined → Disabled with eligibility gating and a grace period beats "an agent fixes every red test" — which would have papered over a real race with a flakier assertion.

Episode 4 — a HIGH-risk infrastructure change through the funnel

Hardening pass one · Thursday 2026-05-28 · the test environment · (net-new gates flagged).

Tooling — No plugin command (net-new to the kit) — iac.yml runs the funnel: schema-validate → PSRule policy gate → bicep what-if (Tom reads it) → human approval → scoped least-privilege apply.

  • Hardening pass one is where the test environment gets added alongside dev. That is a Bicep change, and Bicep is HIGH risk every time. Claude drafted the test-environment Bicep; then it went down the funnel, every step read-only until the last.
  • Schema validation passed. The policy-as-code gate did not: PSRule flagged two things — a storage account that would have defaulted to public network access, and a resource group missing the required cost-center tag. Caught mechanically, on the PR, not by a reviewer remembering to look. The agent fixed both; the gate went green.
  • bicep what-if produced the dry-run — the exact list of what would be created in the test subscription. Tom read it before approving: the app hosting, the buffered queue, the private endpoint to the replica, the Key Vault — and nothing he didn't expect. An infrastructure change approved without reading its what-if is a change approved blind; this one wasn't.
  • Only then did the apply run, under a deploy identity scoped to the test resource group and nothing else. The test environment came up from code, the same way dev had — and the same way prod would in eight weeks.

The rail that mattered: the infrastructure funnel. Everything before the apply is read-only — the policy gate and the what-if did their work while nothing in the cloud had changed yet.

Episode 5 — promotion, and the rollback that failed in rehearsal first

Phase 8 go-live week · 2026-07-20 to 07-23 · rc-1.0.1.

Tooling — No plugin command — the rehearsal and the go/no-go are human-run on the rails; spec 0046 rides the loop; /sdlc-gate at the Phase 8 boundary.

  • Tuesday, the rehearsal in test: deploy → roll back → redeploy, run by the hands that would run it at 2 a.m. The deploy went fine. The rollback failed. The previous artifact came back, but a configuration key had moved ahead of it, so the rolled-back app booted against config it didn't understand. A rollback that had only ever been written would have failed exactly here — in production, during an incident, instead of in test on a Tuesday.
  • The fix rode the loop as spec 0046 — configuration versioned with the release artifact, so a promoted build always carries the config it was tested with and a rollback restores both together. Re-rehearsed Wednesday: clean. The failed first rehearsal went in the go/no-go packet too: found-and-fixed is stronger evidence than never-stressed.
  • Thursday, go-live. The same artifact that passed test — rc-1.0.1 — was promoted to production, not rebuilt. Tom executed; the release manager called each step. The first real FNOL — a burst pipe, reported on the portal at 08:14 — produced a coverage recommendation in 3 hours 6 minutes, against the 11.4-day baseline.
  • Secrets had rotated before go-live to production-only values the pod could not read; Dan held his go until the rotation record was attached, then gave it. The engagement reached production with the pod having never known a production secret.
  • /sdlc-gate passed the Phase 8 gate; billing milestone 6.

The rails that mattered: promotion (the proven artifact moved up, never rebuilt) and the rehearsed rollback (proven by failing safely in test, not assumed). Plus identity: secrets the pod never held.

Episode 6 — drift: proposed, never auto-applied

Phase 9 hypercare · Monday 2026-07-28 · (net-new to the kit).

Tooling — No plugin command (net-new to the kit) — drift-check.yml runs on a schedule, compares real infrastructure to the code, and opens a remediation PR; a human decides.

  • During a Friday-night hypercare hiccup — a postal-vendor blip causing an acknowledgment-dispatch retry burst — an on-call engineer widened a firewall rule on the test environment by hand to unblock a verification run. It worked, the night ended, and the change lived only in the cloud, not in the code.
  • Monday morning, the scheduled drift assessment caught it: the real test environment no longer matched its Bicep. It did not silently re-apply the code over the hand change — that would have quietly undone something a human did for a reason. It flagged the drift and opened a remediation PR describing the difference, leaving the decision to a person.
  • Tom made the call. The widened rule was legitimate and worth keeping, so he absorbed it into the Bicep rather than reverting — and now the code is the truth again, the proper way. Had the change been a mistake, the same PR would have reverted it. Either way: a human chose; the schedule only proposed.

The rail that mattered: drift assessment that proposes, never auto-applies — the agent-proposes/gate-disposes rule, one more time, on the live environment itself. And underneath it all: identity — every agent ran under its own scoped credential.

Episode 7 — the correctness rail clears a refactor that changed no behavior

Phase C close & transfer · Monday 2026-08-17 · spec 0051 (extract the replica-staleness guard), MEDIUM risk.

Tooling — No plugin command — spec 0051 rides the loop; the PR fires ci.yml + grader.yml + correctness.yml; Wes's non-author check at the merge bar.

  • Close-and-transfer is when the codebase gets tidied for the people inheriting it. The replica-staleness guard — the 02:00–04:30 degradation rule from ADR-001 — lived in two copies: one on the intake path, one on the verification path. Ines, now driving on Harbor's side, had an agent extract it into one shared check so the client team inherits one hardened copy, not two that can drift apart. Same behavior, less surface.
  • That is exactly the change a green suite can't vouch for. The tests passed — they always had — and the grader was nearly silent: there was no new behavior to check against the spec, because the spec promised none. A refactor is invisible to "does it meet the spec," and that is precisely where a silent regression hides.
  • The correctness rail did the work. A fresh agent whose one job is did this diff change behavior it shouldn't read the extraction against both originals and confirmed the invariant survived: the boundary still fired at 02:00 and cleared at 04:30, the degraded-response path was byte-for-byte the prior behavior, the check still ran before the call and not after. No drift — and it said so on the PR.
  • Because the diff was proven equivalent, not assumed so, Wes (not the author) signed at the merge bar on the strength of the correctness verdict, and every gate re-ran on the final commit before merge. The client team inherited one guard instead of two — and the proof that the one still behaves like the two it replaced.

The rail that mattered: the correctness review — the no-regression twin of the grader. The grader asks does this meet the spec; correctness asks did this break something it shouldn't. On a refactor the spec hasn't moved, so the grader goes quiet and the correctness rail carries the load. The agent proposes a "safe" cleanup; the gate proves it is one.

05

Four episodes lean on machinery the plugin does not ship today

What was net-new to the kit

The plugin's rails are the five workflows (ci, grader, correctness, security, deploy-dev), the grader and security-reviewer agents, the blocking Stop hook, and branch protection — everything Phase 3 installs and proves. The agentic-pipeline pieces below were invented for this example to show the rails deep-dive in action. None is a command a pod can type now; each is the kind of thing a first agentic-ops engagement would build once and harvest back into the kit.

PieceWhat it isIn the plugin today?
self-heal.yml + bounded fix agentOn a red pipeline, an agent with a four-tool surface diagnoses, fixes, self-validates, and opens a fix/ PR — never merges.No — net-new; harvest candidate
iac.yml funnel gatesschema-validate → policy-as-code (PSRule) → bicep what-if dry-run → scoped least-privilege apply.Partial — Phase 3 ships ci/deploy; the policy + what-if gates are net-new
drift-check.ymlScheduled real-infra-vs-code comparison that proposes a remediation PR; never auto-applies.No — net-new
The flaky-test state machineActive → Quarantined → Disabled, with eligibility thresholds and a post-fix grace period.No — net-new
Per-agent least-privilege identityEach pipeline agent (the fix agent, the deploy identity) runs under its own scoped workload credential.No — net-new; an Entra Agent ID / federated-credential pattern

This is the develop-with-agents track reaching toward the AI-as-product track. The net-new pieces are where the two tracks first touch — exactly what a first agentic-ops-heavy engagement would build once and harvest back into the kit.

06

The two artifacts these episodes turn on, reproduced whole

Two artifacts, whole

The rollback fix (spec 0046) and the infrastructure funnel (iac.yml), reproduced in full — then the per-episode tooling tally.

Artifact: spec 0046 in full (the rollback fix)

Spec 0046 — Configuration versioned with the release artifact
Risk tier: HIGH · Authored: Jonah Kim · Checked: Sara Whitfield · Security sign-off: Dan Kowalski · Merged 2026-07-22

Goal. A promoted build carries the exact configuration it was tested with, so that a rollback restores the application and its config together, never one without the other.

Why. The Tuesday rehearsal rolled back the artifact but not a config key that had moved ahead of it; the rolled-back app booted against config it didn't understand. Promotion must move one versioned thing, not an artifact plus a separately-drifting config.

Scope in. The release-packaging step, the config bundling, the rollback restore path. Scope out. Application config values (those are environment settings, untouched); any change to what the app reads at runtime.

Acceptance checks. A promoted artifact includes its config manifest, content-addressed. Rollback restores artifact + config as one unit. A redeploy after rollback is byte-identical to the prior good state. The Tuesday failure mode (config ahead of artifact) cannot recur — proven by re-running the exact rehearsal.

How it rode the loop. Plan approved by Jonah → agent implemented under tight permissions → Stop hook green → PR fired ci.yml, grader.yml, and (on risk:high) security.yml → Dan signed off → Sara merged → the Wednesday rehearsal proved it clean. It went into the kit harvest as config-with-artifact.

Artifact: the infrastructure funnel (iac.yml)

Net-new to the kit. Drafted by Claude, reviewed by Tom; every step before the apply is read-only.

StepWhat runsBlocks?
Schema validatebicep build against the resource schemaHard block on a malformed template
Policy-as-codePSRule for Azure (public access, encryption, tags, region)Hard block on a policy violation
Dry-runbicep what-if — the exact change previewPosted to the PR; a human reads it
ApprovalA named human approves the what-ifRequired to proceed
ApplyScoped, least-privilege deploy identity— (the only step that changes cloud)
Drift assessment (drift-check.yml)Scheduled real-infra-vs-code compareOpens a remediation PR; never auto-applies

The tooling behind these episodes

EpisodeHow it ran
1 · Grader catch (0016)Spec rides the loop; ci.yml + grader.yml on the PR; security.yml on risk:high → security-reviewer agent; Wes's sign-off
2 · Self-healing CINet-new: self-heal.yml → a bounded fix agent; opens a fix/ PR; a non-author merges
3 · Flaky-test quarantineNet-new: the Active/Quarantined/Disabled state machine; the real fix then rides the loop
4 · Infra funnel (test env)Net-new gates: iac.yml schema → PSRule policy → bicep what-if (Tom reads) → scoped apply
5 · Promotion + rollbackHuman-run rehearsal and go/no-go on the rails; spec 0046 rides the loop; /sdlc-gate at the Phase 8 boundary
6 · Drift proposedNet-new: drift-check.yml on a schedule → a remediation PR; Tom decides
7 · Correctness-rail refactor (0051)Spec rides the loop; ci.yml + grader.yml + correctness.yml on the PR; Wes's non-author sign-off at the merge bar
07

Not an exit gate — a bar every change clears, one principle behind all of it

The merge bar

Branch protection is what turns five workflows from suggestions into rails. It is repository configuration — set by the Setup Owner or a named client admin — and it makes the gates mandatory at the one moment that matters: the merge. Behind it stands the whole reason agent-built software can be trusted to ship: the agent proposes, a gate disposes. An agent may produce any change, but only as a reviewable artifact; a deterministic policy layer plus a named human decides whether it takes effect.

Every PR, to merge, must clear
  • CI green — build, tests, lint, coverage, all passing. Hard block.
  • The grader has run — the workflow completed and posted its verdict. The verdict can say anything; the running is required.
  • Correctness review passed — no high-confidence defect, or a named human recorded the accepted-risk:correctness override.
  • A non-author approval — someone who did not write the change approved it.

A risk:high change adds two more: the security workflow passed (blocking on HIGH) and a named human sign-off recorded in the PR — a person, by name, accepting the risk. Not a thumbs-up; a recorded sentence with a name attached.

The non-author rule

The author of a change is never its checker. The platform forbids an agent from approving or merging its own work exactly as it forbids a human author from being their own approver — and the correctness override is honest about it: a label records who accepted a defect, but the guarantee that the person clearing it is not the author comes from this rule, not the label.

The rule that survives every collapse of pod size
Even a two-person pod holds it. An agent pushes only to branches it creates (spec/NNNN-*), never to main; every commit it makes is co-authored, so who — or what — wrote a line is never a guess.
The one escape hatch — deliberately expensive

A true emergency merge past a gate requires the Pod Lead plus one other human, a gate-exception label, and a Retro+ agenda item. Two exceptions in a month is not bad luck — it means the gate or the specs are wrong. Fix that; do not normalize the bypass. On a single-maintainer repo, GitHub forbids self-approval, so the ruleset ships armed with an owner bypass (bypass_mode: pull_request): the owner can self-merge a PR, but even they cannot push directly to main skipping CI — every change still rides a PR and its checks. Remove the bypass the moment a second reviewer joins.

That is the merge bar: not a phase boundary the engagement crosses once, but a standing line every change clears, forever. The rails do not report to a calendar. They report to the bar — and the bar reports to a human. Gates report; humans decide.

Your senior said “set up the rails on this repo and make sure they actually work.” This page is what you actually type, in order, and what you do between the typing.

The rails are not a phase. They are a standing standard — the same pipeline every change rides, for the whole engagement. So this runbook is shaped differently from a phase runbook: set it up, then prove it catches something. The second half is the half people skip, and it is the half that matters.

Before you type anything

What you need first

Four things. Two are permissions you may not have yet — ask for them on day one, because everything after step 3 is blocked without them.

  • The plugin, installed. If /sdlc-harness isn't in your slash command list, run /plugin marketplace add MCKRUZ/claude-code-sdlc then /plugin install claude-code-sdlc@mckruz.
  • Admin rights on the repo. You cannot install a GitHub App, add a repository secret, or apply a branch ruleset without them. Not “write access” — admin.
  • GitHub Actions enabled on the repo, and the gh CLI installed and logged in. The branch-protection script drives the GitHub API through gh.
  • An Anthropic API key you're allowed to put in this repo's secrets. Three of the gates call the Claude API on every PR they review, and there is a real per-PR token cost. Somebody owns that bill — find out who before you wire it.
The mistake new people make

Installing the workflow files, seeing green ticks, and calling it done. A workflow file that is present is not a rail. A rail is a check that runs, plus a rule that requires it to pass. Both halves, or you have a decoration.

01

Type this — once, in the repo you're arming

Install the rails

You type /sdlc-harness

What happens: it copies the whole delivery harness out of the plugin and into the repo. For the rails specifically that means the workflow files land in .github/workflows/, the reviewer rubrics in .github/profile/rubrics/, the branch-protection ruleset in .github/rulesets/, CODEOWNERS in .github/, and the rails scripts in scripts/rails/. It also installs the local .claude/hooks/ — the Stop hook lives there. It is idempotent: files that already exist are left alone and reported as SKIPPED.

What you do: read the list it printed, then read .github/RAILS.md end to end. That file is the operator's guide for these exact gates — it is the source this page is written from, and it ships with your repo so it can't drift out of reach. Commit everything.

You now have .github/workflows/ — the workflow YAML .github/RAILS.md — the operator's guide .github/rulesets/branch-protection.json scripts/rails/ — apply-branch-protection.sh, diff-anchors.sh

Don't move on until: the workflow files are committed and pushed. A workflow on a branch that was never pushed cannot run on anything. Later, when the plugin ships a new gate, /sdlc-upgrade is how you bring the repo forward — it updates factory-original files, leaves your adaptations alone, and drops both-sides conflicts beside the original as .harness-new for you to merge by hand.

02

Nothing to type — this is editing, by hand, in your editor

Fill in the placeholders

The workflows ship generic. Every value that depends on your repo is marked and waiting for you. Nothing automates this step.

What you do: search the installed files for << markers and for the owner handle @your-org/your-team, and replace every one. The ones that will bite you first:

WhereWhat you're setting
ci.ymlYour toolchain, and the restore / build / test commands. Also the coverage floor and the regex for what counts as source.
security.ymlThe gated-path regex — the directories where any change gets a security review regardless of risk tier. Keep it in sync with CODEOWNERS and the security rubric; there are three copies of that path list and they must agree.
correctness.ymlThe source pathspec — which directories the logic-defect reviewer looks at.
grader.ymlThe committed-spec directory the grader reads.
deploy-dev.ymlThe real deploy step, the health check, and how to capture and restore the last known-good version. Ships as a starter that does nothing until you wire it.
CODEOWNERSThe owning user or team handle.
Two marker styles, and searching for one finds only half

Most placeholders are double-angle — <<LIKE_THIS>>. But the eval-runner wiring and the infrastructure files use single-angle <PLACEHOLDER> markers instead. Search for both styles, or you will ship a half-adapted pipeline that looks finished.

Don't move on until: a search for << across .github/ comes back empty, and a search for your-org comes back empty too.

03

Type one command, then do the rest in the GitHub web UI

Give the agent gates their credentials

You type /install-github-app

What happens: it walks you through installing the Claude GitHub App on this repo. Three of the gates — the grader, the security review, and the correctness review — are AI agents that need it. You can do the same thing by hand at github.com/apps/claude. Repo admin required.

What you do next, in the browser: Settings → Secrets and variables → Actions, and add a repository secret named ANTHROPIC_API_KEY. Never put the key in a file. Not in the workflow, not in CLAUDE.md, not in a spec — a key in a commit is a rotation event and an audit review, not an edit.

What happens if you skip the key

The two blocking agent gates fail closed — on purpose. Security review and correctness review will go red on the PRs they were meant to review, because a review that could not run is not a review that passed. The grader is the one exception: it only advises, so it stays green and does nothing. That is by design, and it means a missing key silently costs you the grader without costing you a red tick.

04

Type this — dry run first, always

Arm the merge bar

You type bash scripts/rails/apply-branch-protection.sh --dry-run
Then bash scripts/rails/apply-branch-protection.sh

What happens: the script reads .github/rulesets/branch-protection.json and applies it to the repo through the GitHub API. The dry run prints the plan without changing anything; the real run prompts you to confirm. The ruleset is what turns the workflows from suggestions into rails: it names the status checks that must be green, requires a review from someone who did not write the change, requires the code owner, and blocks force-pushes and deletion of the default branch.

What you do: read the dry-run output before you confirm. Then check one thing in the GitHub UI — Settings → Rules → the ruleset should say Active, not Evaluate. An Evaluate ruleset reports and merges anyway.

The rule about this file

This script is the only sanctioned way to change branch protection. Edit the JSON, re-run the script. Do not hand-edit rules in the GitHub UI — a hand-edit is invisible to the repo, and the next person to run the script silently reverts it.

A trap worth knowing: the required-check names in the JSON are the workflow job names, not the file names. If you ever rename a job, rename its required check in the same commit — otherwise GitHub waits forever for a check that will never report, and every PR sits pending with no error to read.

05

Read this once — it's what you just armed

The rails that block, one at a time

Blocking means a red check stops the merge. Each one has a defined escape, and the escape is always a label plus a written reason — recorded on the PR, never a quiet override.

RailWhat it blocksThe recorded escape
build-and-testA red build, a red test, a secret found by the scan, or coverage under the floor. The mechanical floor.None. Fix it.
spec-gateA PR that touches source but carries no committed spec in the same diff. “No spec, no build.”no-spec:chore label + the reason in the PR description
spike-guardA merge from a spike/ branch. A spike's deliverable is the written finding; the code is disposable.None, deliberately. Rebuild it under a spec.
risk-signoffA PR labelled risk:high with no named human accepting the risk in a written sentence. A thumbs-up does not satisfy it.Write the sign-off line. That is the gate.
repro-gateA PR labelled type:bugfix whose new test also passes without the fix — a test that would never have caught the bug.None. Write a test that fails first.
dependency-gateA change that introduces a package with a known High or Critical advisory. It compares this branch against the base branch, so pre-existing findings are not your problem.accepted-risk:dependency label + reason and expiry in .github/dependency-exceptions.md
correctness-reviewA high-confidence logic defect on the changed lines — an off-by-one, an inverted null check — found by a fresh agent that did not write the code.accepted-risk:correctness label, applied by a named human
security-reviewA HIGH security finding. Fires on the risk:high label or on any PR touching a gated path, whatever its risk tier.Fix it, or the risk sign-off recorded on the PR

And one that never blocks: the grader. A fresh agent reads the spec against the diff and posts a check-by-check verdict as a PR comment. The merge bar requires that it ran. It does not care what it said. That is deliberate — a polished, confident AI verdict is exactly how an agent talks a human into approving harm, so the grader informs the human reviewer and never replaces them.

Two documents, two different lists

The standard's own rails page describes five workflows. What you just installed carries more gates than that — the extra ones were added to the plugin later and the standard's summary table has not caught up. When the two disagree, .github/RAILS.md in your repo and the job names in ci.yml are the truth, because those are the files GitHub actually executes.

06

Nothing to type — browser work, then wiring

The deploy and promotion path

Merge ships to dev by itself. Everything above dev is deliberate, and a human says go every single time.

What happens on merge: deploy-dev.yml fires after CI succeeds on the default branch. It downloads the exact artifact CI built for that commit — it does not rebuild — ships it to dev, runs a health check, and restores the last known-good version if the deploy or the health check fails.

What you do: wire its placeholder deploy, health-check, and rollback steps to your platform, point it at a real dev environment, then set the repository variable DEPLOY_WIRED to true. Until you do, the job runs, warns that deploy is not wired, and stops — deliberately warning rather than failing, because a job that is red on every single merge teaches the team that red is normal.

For test and prod: deploy-promote.yml is manual only. It never fires on a trigger. Before you use it, go to Settings → Environments → test, then prod, and add Required reviewers to each. That approval is the go/no-go — the run pauses until a named person approves, and the approval lands in the deployment's own audit trail.

An environment with no required reviewers is an automatic promotion

That is the standard's single most protected stop, silently absent, and nothing on the screen looks wrong. The workflow's preflight refuses to run against a target with no reviewers configured — and unlike deploy-dev it fails rather than warns when unwired, because a human asked for this promotion and is standing there waiting for an answer.

The preflight also refuses to promote a build straight to prod that never reached test. That is “promote the artifact, never rebuild it” enforced mechanically rather than remembered.

07

The important part — block out a day for it

Prove each rail by making it fail on purpose

A pipeline that has never caught anything is not proven. It is merely present. A rail that has only ever seen green has not been tested — it has been assumed. So you break things deliberately, one at a time, and watch each gate catch you. This is called the shakedown, and it is the difference between a rail and a decoration.

How each drill works: open a throwaway PR that plants the exact fault the gate exists to catch, confirm the check goes red and says something useful, then — for the gates that have an escape — apply the escape label and confirm it goes green. Close every drill PR unmerged. A blocking gate is only proven when both its block and its escape have been seen to work.

RailPlant thisYou must see
Stop hookA compile error or a failing test, then try to end a Claude Code turnThe hook refuses to let the agent finish, and hands back the build error
Secret scanA fake but realistic credential in a config file — an invented AKIA…-style key. Never a real one.build-and-test red at its first step, Secret scan (gitleaks), with the planted string redacted in the log
Coverage floorA sizeable new class under source with no testsbuild-and-test red at Enforce coverage floor, naming the measured percentage and the floor it missed
spec-gateA source file touched, no spec in the diffRed, listing the touched files. Then apply no-spec:chore, re-run, watch it go green
correctness-reviewAn inverted null check, or an off-by-one that drops a rowRed with the verdict anchored to the exact changed line. Then apply accepted-risk:correctness and watch it clear
security-reviewA probe PR touching a gated path with a planted HIGH issueRed. This proves your gated-path regex is actually matching — the part most likely to be wrong
graderA spec that claims something the diff does not doA PR comment calling out the mismatch. Nothing blocks — you are confirming it posts the miss
dependency-gateA package with a published High or Critical advisoryRed, naming the package and the advisory. Then apply accepted-risk:dependency and watch it clear
deploy-devA known-bad deploy — a deliberately broken artifact, or a health check pointed at a failing buildThe deploy fails and the last known-good version is restored
deploy-promoteA promotion request against testIt pauses for a named reviewer. Separately: try to promote a build to prod that only ever reached dev — the preflight must refuse
Run the dependency drill even if you skip every other one

Every other rail fails loudly when it's misconfigured — you get a red tick and an error to read. The dependency gate fails silent and green. A scanner that cannot parse its tool's output reports no findings, and no findings looks exactly, character for character, like a clean repo.

The reason is an exit code. The .NET scan command the gate ships with (dotnet list package --vulnerable) exits 0 even when it finds a High advisory — the findings live in its output, never in its status. So the gate is written to treat a non-zero exit as “the tooling broke,” not “the code is clean,” and to fail closed on it. But if the scan runs and produces nothing usable for any other reason, you get a green tick and no signal at all.

Planting a known-vulnerable package is the only way to know this gate is wired. There is no other test. Do it.

Be honest about what has actually been drilled

The drills are written down; nothing records whether they were run on your repo. The newest three rails — the dependency gate, promotion, and the telemetry file — ship with their drills listed as a migration step, which is a polite way of saying somebody still has to do them. Assume nothing here has been proven on this repo until you watched it go red yourself.

08

Nothing to type — write the evidence down

Record the proof

Write one entry per rail into pipeline-proof.md: which rail, what you planted, what you saw, the link to the drill PR, the date, and your name.

Why this step exists: six months from now somebody will ask whether the rollback has ever actually run. “I think so” is not an answer. A dated line with a PR link is.

Nothing verifies this but you

The automated phase gate checks that pipeline-proof.md exists and has no leftover placeholder text. It cannot tell whether you ran a single drill. Writing “proven” next to a rail you never forced to fail is the one lie on this page that nobody downstream can detect.

There is also a weekly telemetry workflow that writes .github/rails-telemetry.json — a record of this repo's own gate outcomes, computed from the repo's own history with no external calls. The one thing it catches that counting cannot: a gate whose check still runs but whose requirement was removed. A disarmed gate looks completely normal on the PR page — the red run just merges anyway. Only comparing what is required against what exists finds it.

09

When a rail goes red on real work

What to do when a gate stops you

The instinct is to find the way around it. Resist that — the way around is what the rails exist to prevent. There are exactly three legitimate responses, in this order.

  • Fix the change. Almost always the right answer. The gate found something real: a broken test, a missed spec, an off-by-one, a vulnerable package.
  • Record the escape. If the finding is real but consciously accepted, apply that gate's escape label and write the reason where the label points — the PR description, .github/dependency-exceptions.md, .github/eval-bypasses.md. A label with no sentence behind it is a shrug in a costume. And the escape never removes the non-author approval: someone who did not write the change still has to approve it.
  • Fix the gate. If it's a false positive, that is a defect in the rails and the rails ride the same loop as everything else — a spec, a branch, a PR, a reviewer who is not you. Never a quiet edit to the YAML on the branch that the gate is currently blocking.
The one thing you must not do

Do not weaken the gate on the branch the gate is blocking. These workflows run the PR's own copy of themselves, so a PR can in principle loosen the regex that would have caught it. That is a known residual risk, and the thing that actually closes it is a review by someone who did not write the change. If you find yourself editing .github/ to get your own PR through, stop and go get a second person.

The emergency bypass is deliberately expensive: two humans, an exception label, and a retro item. Two bypasses in a month is not bad luck — it means the gate or the specs are wrong. Fix that; do not normalise the bypass.

Keep these handy

Commands you'll use constantly

Type thisWhen
/sdlc-harnessFirst install, and any time you want to re-check what the plugin would put in this repo.
/sdlc-upgradeAfter a plugin update. Brings the harness forward without stomping your adaptations.
bash scripts/rails/apply-branch-protection.sh --dry-runAny time you touch the ruleset JSON, or you want to see what is currently required. Read it before you apply it.
gh pr checksWhich gates ran on this PR and which are still pending. Faster than the browser, and it shows you a check that is waiting on a job that will never report.
gh run view --log-failedA gate went red and you want the failing step's output without clicking through Actions.

Rule of thumb for the whole standard: the agent proposes, the gate disposes. An agent may produce any change, but only as something a person can review, and a deterministic check plus a named human decides whether it takes effect. Every rail on this page is one more place that stops being a good intention and becomes a fact about the world.

Reference · The Rails · CI/CD & DevOps

The precise mechanics — the five workflows, the merge bar, deploy and promotion, the infrastructure funnel, identity, and the governing principle, including the tooling specifics the How-it-works view leaves out. The full prose method sits under each section's “Go deeper” on the How it works tab; for the complete Harbor artifacts (every ID and quote), see Example.

The governing principle

Agent proposes, gate disposes. An agent may produce any change, but only as a reviewable artifact — a branch and a PR, an infrastructure plan, a fix suggestion. A deterministic policy layer plus a named human decides whether that change ever takes effect. This is the same sentence that runs through the whole standard — gates report, humans decide — mechanically enforced rather than merely intended.

Three corollaries fall out of it, each a design rule:

  1. Mechanical self-validation is mandatory. Before surfacing a change as done, the agent re-runs build, tests, and linters from the environment and proves them green. This is the Stop hook: the agent cannot end its turn on a red test or broken build.
  2. A bounded tool surface beats raw access. Named, constrained tools (read a file, write a file, run tests, run lint) outperform and are far safer than the whole filesystem and a shell.
  3. Autonomy is graded and widens slowly. New agent automation starts in a propose-and-approve posture and only tightens after its behavior is watched on real work.

Underneath all three: identity is the real guardrail. What an agent can do is bounded by the permissions of the credential it runs under, not by the words in its prompt.

The five workflows

WorkflowFires onBlocks or advisesWhat it does
cievery PRBlocks (hard gate)Build, tests, lint, 80% coverage on new code. The mechanical floor — a red CI is a closed door.
graderevery PRAdvises (required to run, can't block)A fresh AI agent reads the spec in the diff and posts a check-by-check verdict as a PR comment, each check pinned to an exact changed line. "The grader ran" is a required check; what it said is the human Checker's input.
correctnessevery PR that changes sourceBlocks on a high-confidence defectA fresh AI agent, separate from the grader, hunts the changed lines for plain logic defects — the bug class ci can't see (it compiles, the tests pass) and security doesn't look for (just wrong, not exploitable). Trivial pass when no source changed; named-human override on the record.
securitythe risk:high label or any PR touching a registered gated pathBlocks on HIGH; advises otherwiseRuns the security-reviewer agent. Path-triggered: fires on any PR touching a guarded path (auth, migrations, the pipeline, infra), independent of the spec's tier.
deploy-devmerge to mainn/a (it ships)Deploys the merged artifact to the client's dev environment, and restores the last good version when a deploy fails.
deploy-promotemanual only — never a triggern/a (it ships)Promotes that same artifact onward to test and production. Holds until a named approver signs, and refuses a target environment that has no approver configured. Restores the last good version when a promotion fails.

The grader advises; it never blocks — a polished, plausible AI verdict is exactly how an agent talks a human into approving harm. An agent gate may block on a defect (security on a HIGH vulnerability, correctness on a high-confidence logic bug) but never on a judgment — both blocking agent gates carry a named-human override. Both AI reviewers are fed a deterministic changed-line set so every finding pins to a real line. The security gate is path-triggered, not just tier-triggered: a MEDIUM change touching the auth code or the migration folder still fires it.

The merge bar

Branch protection turns five workflows into rails. It is repository configuration — set by the Setup Owner or a named client admin — making the gates mandatory at the merge.

Every PR, to merge, must clear:

  • CI green — build, tests, lint, coverage all passing. Hard block.
  • The grader has run — the workflow completed and posted. The verdict can say anything; the running is required.
  • Correctness review passed — no high-confidence defect, or a named human recorded the override (audited in the PR timeline; the non-author guarantee comes from the approval below, not the label).
  • A non-author approval — someone who did not write the change approved it. The author is never their own only approver; this survives even a two-person pod.

A risk:high change adds two more:

  • The security workflow passed — the security-reviewer agent's pass, blocking on HIGH.
  • A named human sign-off recorded in the PR — a person, by name, accepting the risk. A recorded sentence, not a thumbs-up.

Constraints on the agent, enforced by the platform, not by trust:

  • The agent pushes only to branches it creates (spec/NNNN-*), never to main.
  • The agent cannot approve or merge its own work — the platform forbids it, the same way it forbids a human author from being their own approver.
  • Every commit an agent makes is co-authored, so provenance is in the history.

The one escape hatch is deliberately expensive: a true-emergency merge past a gate requires the Pod Lead plus one other human, an exception label, and a Retro+ agenda item. Two exceptions in a month means the gate or the specs are wrong — fix that, don't normalize the bypass.

Deploy and promotion

StepHowGate
Merge → devThe deploy-dev workflow ships every merged change to devAutomatic — the merge already cleared the bar
Dev → testThe deploy-promote workflow, run by hand, smoke-tested on arrivalOn demand — and it will not start until a named approver signs
Test → prodThe same deploy-promote workflow. The first promotion is the Phase 8 go/no-go; every one after rides the client's release cadenceA human go/no-go every time — the single most protected stop in the standard, and enforced by the environment's own approval rule rather than by convention

Two rules govern every promotion:

  • Promote the artifact; never rebuild it. The same build that passed test goes to production. A rebuilt artifact is something no environment ever verified.
  • A rollback that has never run is a wish. The deploy pipeline restores the last good version on a failed deploy — and that path is rehearsed in test (deploy → roll back → redeploy) with the trigger condition written down in advance. A rail that has never failed safely has not been proven.

The infrastructure funnel

Infrastructure is code, lives in the repo, and is HIGH risk every time. The agent-safe IaC pipeline is a read-only-until-the-last-step funnel:

StepWhat runsBlocks?
Schema validatebicep build against the resource schemaHard block on a malformed template
Policy-as-codePSRule for Azure (public access, encryption, tags, region)Hard block on a policy violation
Dry-runbicep what-if — the exact change previewPosted to the PR; a human reads it
Cost / budget reviewThe price of the diff on the PRCan block on a budget breach
ApprovalA named human approves the what-if and the policy resultRequired to proceed
ApplyScoped, least-privilege deploy identity— (the only step that changes cloud)
Drift assessmentScheduled real-infra-vs-code compareOpens a remediation PR; never auto-applies

Every step before the apply is read-only. The agent and the pipeline can do all the thinking, validating, and previewing they want, and nothing changes until a human has read the dry-run and a least-privilege identity executes a scoped apply. The default profile is .NET/Azure (Bicep); the pattern is stack-independent. A destructive apply an agent runs unattended is the one mistake on this page with no clean undo.

Agents inside the pipeline

PatternHow it stays safe
Self-healing CIOn a red build, an agent works the problem through a bounded set of named tools (read a file, write a file, run tests, run lint), self-validates until green, then emits the fix as a PR suggestion — it stops at a merge request, never pushes to the protected branch.
Flaky-test handlingA state machine — Active → Quarantined → Disabled — with eligibility thresholds before quarantine (a real failure rate, failures on the default branch, across more than one pipeline) and a grace period after a confirmed fix before the test rejoins the suite. Triage over reflex.
ContainmentAn agent touching the pipeline runs in a sandbox with default-deny network egress and writes confined to its workspace. The environment layer catches what the model's judgment misses.

Identity, secrets, and blast radius

  • Each agent that acts on infra or the pipeline gets its own least-privilege identity — a managed or federated credential (no stored secret, rotated automatically) scoped to the resource or resource group, never the whole subscription. Every action is then attributable to it in the audit log.
  • Secrets live in the client's vault, never in the repo — not in code, not in CLAUDE.md, not in a spec. A key in a commit is the one unrecoverable foundation mistake.
  • Secrets rotate before production — into production-only values the pod cannot read, signed by the client's security. The engagement should end with the pod having never known a production secret.
  • There is a kill switch — conditional-access policy at the class level can disable a whole category of agent identity in one operation.

Treat an agent as a first-class non-human identity with its own lifecycle, owner, and time-bounded access — not a script running under a human's credentials. Every agent identity has a named owner or it does not exist.

Proving and watching the rails

A pipeline that has never caught anything is not proven — it is merely present. Before Foundation closes, each rail is made to fail on purpose and caught:

  • A failing test proves the Stop hook blocks an agent from finishing.
  • A PR with a planted spec mismatch proves the grader posts the miss.
  • A known-bad deploy proves the pipeline restores the last good version.
  • A probe PR touching a guarded path proves the security gate fires.

Watch the rails with the DORA four — deploy frequency, lead time, change-fail rate, time-to-recover — read as trends. Never velocity, story points, PR count, or lines of code: agents inflate every one of those. Log everything with provenance so any change is traceable to the identity that produced it.

Who owns the rails

PersonIn the rails
Setup OwnerOwns the rails as a product: the five workflows, branch protection, the IaC pipeline, secrets, agent identities. Drafts with Claude; reviews everything; merges none of it alone.
Setup Owner's deputyNamed on day one. Reviews every rails change — workflow YAML, IaC, identity scope. The both-eyes rule applied to the foundation.
Quality EngineerWires the mechanical gates and proves the rails fail safely — the Stop hook blocks, the grader posts, the deploy rolls back.
OrchestratorsRun changes through the rails all day. When the pipeline needs a fix, it rides the loop like any change.
Pod LeadOwns the risk tiers that decide how hard each path is gated, and the gate-exception rule.
Client DevOps / platformReviews every pipeline and IaC change — they operate the rails after we leave. Owns branch-protection admin, runner policy, the secrets vault.
Client SecuritySigns off on the pipeline, secrets handling, agent identities and their RBAC scope.

Where the rails live across phases

  • Built in Phase 3 (Foundation), before any feature exists, and proven on the walking skeleton.
  • Run under every change all through Build.
  • Promote the proven artifact to production in Phase 8.
  • Watched in Phase 9 (hypercare). The rails outlast every phase boundary.