All lessons › Function E — The Agentic SOC › Recover and Root Cause — From Stopped to Back at Target
E5.5

Proposing the policy change — the diff, and what it does not fix

E5
▶
Lightboard recording

Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.

Video placeholderpublishes automatically from recordings/E5.5.mp4
Risk

The lesson from the incident lives in a postmortem document nobody reads, and the policy that permitted it is unchanged.

Control

A policy change proposal generated from the root cause record, as a diff with the incident as its evidence.

The lab

Generate a policy diff from a root cause record and review what it would have prevented.

◉Use case relevance

The incident produced a fix to what is deployed and no change at all to what is allowed. So the next system built under the same policy reproduces the same conditions, and the postmortem sits in a folder being correct.

At CyberTravels. propose() will not construct without does_not_fix. A proposal presented as closing the whole class is one nobody reads carefully, and this one says plainly that it leaves the direct API path D1.9 measured. The diff is the deliverable; the postmortem is the reasoning behind it and changes nothing on its own.

◴What this lesson is — Day 0, Day 1, Day 2

The policy change as a diff generated from the root cause record, carrying the incident as its evidence and stating what it does not fix — because a change presented as closing everything is one nobody scrutinises.

Day 0 — why

Most incidents change what is deployed and leave what is allowed untouched, so the next system reproduces the conditions.

Day 1 — how

State the change as a policy diff with the incident's numbers as the reason, and name what the diff does not fix.

Day 2 — measure

Clauses changed, and the indicators no policy change can fix — which are engineering items, named so they do not fall between functions.

Measure this

what to measuredenominator or target
policy clauses changedcount
indicators no policy change can fixcount, named as engineering items

▦The framework, and how it works

   root cause record
        |
        v
   +--------------------------------------------------------------+
   | clause                          from -> to            why     |
   | mcp.vendor.tool_descriptions    trusted -> pinned     the     |
   | tool.call.provenance            when present ->       altered |
   |                                 required, else refuse desc    |
   | payments.refund.approval        >500 -> every amount  KCI-03  |
   +--------------------------------------------------------------+
        |
        v
   NOT addressed: KCI-04
   the policy already required 15 minutes. the control was never built
   to meet it -> engineering item, named here so it is not lost between
   the two functions

Most incidents end with a change to what is deployed. The policy that permitted the incident is usually untouched — so the next system built under it reproduces the conditions.

Stated as a diff, with the clause as it stands, the new text, and the incident as the reason, the change can be argued with. Stated as a postmortem paragraph, it cannot.

Anchor → E1.0. Recovery that changes only what is deployed leaves the policy that permitted the incident intact, so the next system built under it reproduces the conditions. The diff is what stops the same intervals being measured again next quarter.

2 · Name what the diff does not fix

The proposal here changes three clauses and explicitly fails to address one indicator — because no policy change can. The policy already required detection within fifteen minutes; the control was simply never built to meet it.

That is an engineering item, not a policy item, and saying so in the proposal is what stops it falling between the two functions forever.

3 · A root cause record becomes a diff

Every 'why' column is a measured number from the incident. A proposal argued on opinions gets decided by whoever is loudest.

▶Real time execution as skill

The skill — skills/grc/policy-change-proposal/SKILL.md

name: policy-change-proposal
description: >-
  Turn a root cause record into a reviewable policy diff with the incident
  attached as evidence, and name what the diff does not fix. Use at the end of an
  incident, when a postmortem's lessons never reach the policy, or when a
  proposed control change needs to be argued on evidence rather than opinion.
allowed-tools: Read, Grep, Glob

The last step of an incident is a change to what is allowed

Most incidents end with a change to what is deployed. The policy that permitted the incident is usually untouched, so the next system built under it reproduces the conditions.

Stated as a diff — clause, old value, new value, and the incident as the reason — the change can be argued with. Stated as a postmortem paragraph, it cannot.

When to use this

After root-cause-record and kci-fix-validation, with both in hand. Before the incident is closed, because the appetite for a policy change decays fast.

Step-by-step

1 — Quote the clause as it stands. A proposal that paraphrases the current policy is arguing with something nobody wrote.

2 — Write the new value as text that could be adopted. Not a direction of travel — the words.

3 — Put the measured number in the "why". "41% of calls carried provenance during the incident" ends an argument that "we should tighten provenance" starts.

4 — Name what the diff does NOT address. A KCI that no policy change fixes is an engineering item, and saying so stops it falling between the two.

5 — Expect the expensive clause to be refused, and say so. A proposal whose every line is easy did not come from a real incident.

Example

Input — a root cause record and four policy clauses, in scripts/policy_change_proposal.py.

Output — one change from a real run:

  tool.call.provenance
  - recorded when present
  + required; a call without it is refused
    why: 41% of calls carried provenance during the incident, so it was optional

Output contract

{
  "incident": "str",
  "changes": [{"clause": "str", "from": "str", "to": "str", "why": "str"}],
  "unaddressed": ["str"]
}

Common edge cases

  • The policy already required it. Then the gap is engineering, not policy — and that is the finding.
  • The change breaks integrations. State it in the diff. That review is the one that should happen before it ships.
  • No clause covers the behaviour. A new clause is a bigger ask than an amendment; say which you are making.

Failure modes

  • Proposals with an opinion in the "why" column. They get argued on opinions, and the loudest person wins.
  • Silence on what is not fixed. The unaddressed KCI is the one that recurs.
  • Deferring to "the next policy review". By then the evidence is cold.

Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.

1In your AI agent — you are taught

The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.

Install it, once. From the folder you cloned. --all does every agent it finds on your machine:

python3 scripts/install_skills.py --all --lessons E5.5

Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.

Then pick e5-5-proposing-the-policy-change:

agentinstall just for this agentstart it
Claude Codepython3 scripts/install_skills.py --tool claude --lessons E5.5type /e5-5-proposing-the-policy-change
GitHub Copilot (VS Code)python3 scripts/install_skills.py --tool copilot --lessons E5.5type /e5-5-proposing-the-policy-change in Chat
Cursorpython3 scripts/install_skills.py --tool cursor --lessons E5.5type / in Agent chat and search for it
Codexpython3 scripts/install_skills.py --tool agents --lessons E5.5type $e5-5-proposing-the-policy-change, or run /skills

2As one Python command — you just run it

No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.

python3 scripts/lesson.py E5.5

3Read the code, and the difference — nothing runs

Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.

python3 scripts/checkpoint.py --at E5.5 --out work/cybertravels
python3 scripts/checkpoint.py --at E5.5 --diff

On Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.

Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.

Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.

If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.

✓What you just proved

Three clauses changed with the incident as evidence, and KCI-04 named as unaddressed because it is an engineering gap rather than a policy one.

✎Your turn

Write the fourth clause — the one your organisation would refuse. A proposal whose every line is easy did not come from a real incident.

Tools used

OPA

Skill source: skills/grc/policy-change-proposal

← PreviousE5.4 · Validating the fix — re-measuring the indicators the incident moved