<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Emery Huang</title>
    <description>The latest articles on DEV Community by Emery Huang (@appcpp_9071).</description>
    <link>https://dev.to/appcpp_9071</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4062905%2Ffa7a697d-092e-45db-96eb-35fc4cba65b0.png</url>
      <title>DEV Community: Emery Huang</title>
      <link>https://dev.to/appcpp_9071</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/appcpp_9071"/>
    <language>en</language>
    <item>
      <title>Hold the Write Until Five Read Receipts Match the Escalation Ack</title>
      <dc:creator>Emery Huang</dc:creator>
      <pubDate>Sun, 11 Oct 2026 21:24:48 +0000</pubDate>
      <link>https://dev.to/appcpp_9071/hold-the-write-until-five-read-receipts-match-the-escalation-ack-5a5c</link>
      <guid>https://dev.to/appcpp_9071/hold-the-write-until-five-read-receipts-match-the-escalation-ack-5a5c</guid>
      <description>&lt;p&gt;I will not let a paging model choose the first command, because the alert class should already have made that choice. The only question that matters in minute one is whether five read-only receipts exist before anyone requests a write. If those receipts are missing, I keep the shell frozen and I escalate on a clock rather than on a feeling. Have you watched a confident paragraph turn a noisy page into a restart that nobody had actually approved?&lt;/p&gt;

&lt;h2&gt;
  
  
  The page may change attention, not authority
&lt;/h2&gt;

&lt;p&gt;A page is a request for attention, and I refuse to treat it as a grant of write access on the paged host. I want that sentence in the incident channel before the bridge call collects opinions, because opinions arrive faster than evidence. The card in this article is a proposal you can adapt, not a diary of an outage I am pretending to have run last night. If your team already runs a stricter change gate, keep that gate, and steal only the receipt idea from this card.&lt;/p&gt;

&lt;p&gt;Three constraints sit above the commands, and I repeat them whenever someone offers a shortcut around the card.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intake may name an alert class, but intake may not mutate hosts, flags, or queues.&lt;/li&gt;
&lt;li&gt;The first five commands are read-only, and the class picks them before a person or a model starts improvising.&lt;/li&gt;
&lt;li&gt;Unfreeze covers one named command, and only after receipts plus an escalation ack exist in the channel.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Faster drafts did not retire the freeze
&lt;/h2&gt;

&lt;p&gt;I keep seeing discussion, including this week, about models sounding smoother while the software around them stayed stubborn. I will not quote a leaderboard I have not opened, and I will not treat a headline as a primary source for your pager. The practical lesson I am willing to keep is narrower than the headline, and it is about authority rather than fluency. A cleaner paragraph does not earn a write on a sick host, even when the alert text was copied perfectly into the prompt.&lt;/p&gt;

&lt;p&gt;If the draft is fluent and the receipts are still empty, the freeze still wins, and the escalation clock still runs. Why would smoother wording change the owner of a production shell? I want the answer written down before the bridge call turns confidence into a typed mutation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick a class before you open a shell
&lt;/h2&gt;

&lt;p&gt;I use three classes, because a single generic checklist hides the check that would actually explain this page. Class A means a customer-visible failure, or any credible risk that stored data was lost or corrupted. Class B means the error budget is burning, while nobody has confirmed an actual customer-facing break yet. Class C means a detector symptom that might belong to the detector itself, rather than to the service under the page.&lt;/p&gt;

&lt;p&gt;Why would I start in a softer class when a wrong downgrade can hide a real customer break? Downgrading the page is itself a decision, and I would rather run one extra read than miss the checkout path. If I cannot defend the class in a single line, I stay on Class A until a human explicitly moves it. That bias is intentional, which is why the table below never shows an open write posture for any class.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;What I need to see&lt;/th&gt;
&lt;th&gt;First goal of the reads&lt;/th&gt;
&lt;th&gt;Write posture&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Errors, timeouts, or data doubt&lt;/td&gt;
&lt;td&gt;Bound impact and name a secondary&lt;/td&gt;
&lt;td&gt;Frozen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;Budget burn, no confirmed break&lt;/td&gt;
&lt;td&gt;Confirm scope and the recent trend&lt;/td&gt;
&lt;td&gt;Frozen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;Detector-only, no user report&lt;/td&gt;
&lt;td&gt;Separate detector fault from host fault&lt;/td&gt;
&lt;td&gt;Frozen&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The class changes the questions I ask, and it deliberately does not change the ban on mutation during intake. Would you unlock a restart for Class C merely because a draft called the alert simple? I would not, so the linter below treats every class as frozen until the receipts actually land.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lint the first five commands on a laptop
&lt;/h2&gt;

&lt;p&gt;I keep the allowlist in a script, so a tired paste cannot smuggle a write verb into the first minute of the page. The listing is unexecuted proposal code, and I would run it against a fixture on a laptop rather than on a production shell. It classifies one alert line, prints the matching card, and returns non-zero when a suggestion looks like a mutation. You should replace the paths before you trust it, because I have not executed this file on your hosts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Proposal: require read receipts before any unfreeze. Not a prod agent.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;WRITE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(kill|delete|drop|apply|restart|scale|patch|exec|truncate|systemctl)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;CARDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date -u&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hostname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uptime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl -fsS --max-time 3 http://127.0.0.1/health || true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tail -n 40 /var/log/app/app.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date -u&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uptime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;df -h&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ps -eo pid,pcpu,pmem,comm --sort=-pcpu | head&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tail -n 30 /var/log/app/app.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date -u&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hostname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;journalctl -u app -n 40 --no-pager || true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tail -n 20 /var/log/app/app.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data loss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checkout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5xx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;burn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;alert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;class=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; posture=frozen receipts=0/5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CARDS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;WRITE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reject card[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;suggested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suggested&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;WRITE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suggested&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reject suggestion: write seen before receipts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hold unfreeze until five receipts and an escalation ack&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A fixture committed beside the script should stay short enough to reread on a phone during the page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alert: checkout 5xx ratio above page threshold for 3 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local check is supposed to feel boring, which is what I want when the bridge call is already loud.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 receipt_card.py systemctl restart app &amp;lt; fixture.txt
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An exit status of 3 means the suggestion tried to skip the receipt gate, so I discard that suggestion immediately. An exit status of 0 means I earned a reading list, and I still have not earned permission to write. After each read, I append a receipt with a tiny helper instead of trusting memory in the middle of the night.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c"&gt;# Proposal: record one read receipt. Usage: ./note_receipt.sh A "date -u" 0&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'RECEIPT class=%s cmd="%s" at=%s exit=%s\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RECEIPT class=A cmd="date -u" at=2026-10-12T03:12:01Z exit=0
RECEIPT class=A cmd="hostname" at=2026-10-12T03:12:04Z exit=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Escalation runs on a clock, not a mood
&lt;/h2&gt;

&lt;p&gt;I start the escalation clock when the class line is posted, because a vague status is not a policy anyone can audit the next morning. In this proposal, Class A pages the secondary at ten minutes, Class B at twenty, and Class C at thirty unless customer chatter appears. Those durations are planning defaults for a small rotation, not measurements taken from your pager data. Edit the numbers on the card before you adopt them, and do not cite this article as if it were a study.&lt;/p&gt;

&lt;p&gt;The order of the names matters more than the speed of any generated summary you might paste.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Primary posts the class, the freeze, and the five-command card in the incident channel.&lt;/li&gt;
&lt;li&gt;Primary names the secondary in that same message, even when the escalation timer has not fired yet.&lt;/li&gt;
&lt;li&gt;If the timer fires without a secondary ack, primary pages them and still refuses writes.&lt;/li&gt;
&lt;li&gt;If impact stays unclear at the next interval, primary pulls the service owner into the channel.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Could a model draft that acknowledgement faster than I can type the same four fields myself? Yes, and the speed helps only when class, freeze, secondary, and deadline all remain intact. If the draft omits the secondary, I do not send it, even when the surrounding prose sounds calm and complete. A missing name is an escalation failure, not a style problem I should polish after the page is already late.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unfreeze is one human-typed command
&lt;/h2&gt;

&lt;p&gt;Freeze means no write-class command and no temporary scale, including a one-liner that a chatbot offers as the obvious fix. Unfreeze is not a change of mood, and a red graph does not imply permission the channel never granted. I allow one command, typed by the human who holds the page, only after every gate below is already true.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Five read receipts are in the channel, and each receipt names the same class that intake recorded.&lt;/li&gt;
&lt;li&gt;The secondary, or the service owner, has acknowledged that same class in a written channel message.&lt;/li&gt;
&lt;li&gt;The command is absent from the read-only card, and a human typed it rather than pasting a model line untouched.&lt;/li&gt;
&lt;li&gt;The release names an end time, and the posture returns to frozen when that time passes without a fresh line.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I keep the release on one grep-friendly line, so a later review can see exactly what the page allowed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UNFREEZE class=A receipts=5/5 ack=lee cmd="service app reload" until=2026-10-12T03:40:00Z by=sam
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any field is missing, I stay frozen, even when the chart looks worse than it did during intake. A worse chart is a reason to escalate the page, not a reason to skip the receipts you already required. Would you sign a blank unfreeze because a draft said the reload looked safe from the alert text alone? I want that answer recorded as no, in the same channel that received the original page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Draft the card away from production credentials
&lt;/h2&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. I would draft and lint this card on a separate machine, and the operator says MonkeyCode offers free model access plus a free server option for that drafting work. I am not stating a token quota, a hardware shape, or an end date, because no primary source for those figures was attached here. If that option is closed when you read this, run the same linter on any laptop that cannot see production credentials.&lt;/p&gt;

&lt;p&gt;What I would send is the alert sentence and the allowlist, with secrets left out of the prompt on purpose. What I would accept back is a class label or a rejection, never a live session on the host that was paged. If the draft inserts a write verb, the script's non-zero exit is the decision, and the drafting host does not get another vote. That split is the point of keeping generation away from the shell this runbook is trying to protect.&lt;/p&gt;

&lt;p&gt;I am not using this section to rank tools, and I am not claiming the free option will still exist next quarter. Availability language here is operator-supplied, not a measurement I reran against a dated product page on the day of this draft.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should skip this card
&lt;/h2&gt;

&lt;p&gt;This approach fits poorly when you lack an incident channel, a secondary, or a read-only check that stays safe on a sick host. It also fits poorly on safety-critical control gear, where my sample commands could be the wrong shape entirely. I am not claiming a shorter recovery, a better model score, or a replacement for the vendor runbook you already trust.&lt;/p&gt;

&lt;p&gt;I keep the limits beside the suggestion, so a tired reader cannot skip them on the way to the code block.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The classifier is only a keyword stub, and novel alert names will miss until you extend those lists yourself.&lt;/li&gt;
&lt;li&gt;Read-only is not automatically harmless, so the tail and curl examples stay capped, local, and easy to delete.&lt;/li&gt;
&lt;li&gt;A free drafting host may be down, rate-limited, or unfit for secrets, so never paste credentials into the prompt.&lt;/li&gt;
&lt;li&gt;The clocks above are proposals, not results from a published on-call study that you should quote as evidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a command in the card does not exist on your hosts, delete it before the next page instead of discovering the gap live. I would rather you keep a shorter card than run an example that only matched an imaginary layout. The receipt rule still works after you swap in commands your platform team already trusts for diagnosis.&lt;/p&gt;

&lt;p&gt;If the paper card has never been linted, run it once against a fixture before the next rotation, on a separate machine you control. A free drafting server is optional for that pass, and the freeze rule does not depend on which editor helped you type the card.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>python</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Block Write-Class Commands Until the Page Clock Names Owner and Expiry</title>
      <dc:creator>Emery Huang</dc:creator>
      <pubDate>Sat, 10 Oct 2026 17:50:19 +0000</pubDate>
      <link>https://dev.to/appcpp_9071/block-write-class-commands-until-the-page-clock-names-owner-and-expiry-oa6</link>
      <guid>https://dev.to/appcpp_9071/block-write-class-commands-until-the-page-clock-names-owner-and-expiry-oa6</guid>
      <description>&lt;p&gt;I will not let an assistant draft become a shell command until a page clock names the owner, the severity, and a hard expiry. The opening stretch of a page should stay in a read class, because an early write is only a guess with privilege. Have you ever changed a unit file while the alert title was still a blur on the second monitor? I keep that impulse behind a command class gate, and I open the gate only when a human can point at the clock.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the page clock is allowed to decide
&lt;/h2&gt;

&lt;p&gt;A page clock is a small file that records who is on the hook, how severe the page looks, and when the read window ends. It does not diagnose the outage, and it does not bless a clever patch that a model just printed into chat. I use it as a gate in front of write-class commands, so a draft can sit on scratch storage away from the paging host. Would you trust a suggestion that arrived before anyone was willing to name the severity out loud?&lt;/p&gt;

&lt;p&gt;I would not, which is why the clock has to exist before that suggestion is eligible for a human review. The file lives on a scratch path, not beside the unit that woke you up in the first place. I treat the production shell as frozen until a human replaces a closed write lock with an explicit unfreeze. If the clock file is missing, I keep reading and I escalate, rather than improvising a fix from a chat pane.&lt;/p&gt;

&lt;h2&gt;
  
  
  The clock file I want beside the runbook
&lt;/h2&gt;

&lt;p&gt;The sample below is a proposal for the runbook repository, and I have not executed it against a live fleet. Copy it only into a scratch directory, then replace every placeholder with values taken from the page you received. Leave production hostnames out of this file on purpose, because a clock should never become a quiet map of targets. I would rather see an empty field than a hostname copied from a suggestion that nobody reviewed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# page-clock.yaml: proposed artifact, not a live incident record&lt;/span&gt;
&lt;span class="na"&gt;page_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace-with-alert-id"&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace-with-human-name"&lt;/span&gt;
&lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown"&lt;/span&gt;
&lt;span class="na"&gt;opened_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace-with-utc-timestamp"&lt;/span&gt;
&lt;span class="na"&gt;read_window_minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
&lt;span class="na"&gt;write_lock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;closed"&lt;/span&gt;
&lt;span class="na"&gt;escalation_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace-with-utc-timestamp"&lt;/span&gt;
&lt;span class="na"&gt;draft_state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;held"&lt;/span&gt;
&lt;span class="na"&gt;rollback_intent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unset"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  First commands that stay in the read class
&lt;/h2&gt;

&lt;p&gt;I start with commands that only observe, and I refuse anything that mutates packages, units, routes, or stored data. These examples assume a Linux host you already administer, and they stay proposals until your access policy allows the reads. I also keep a tiny wrapper that classifies a command string before I consider running that string for real. Have you noticed how a draft mixes a harmless status check with a restart on the next line?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm the page identity in the clock file before you open a process list, because a mismatched alert wastes the window.&lt;/li&gt;
&lt;li&gt;Reject the snapshot when owner or severity is still a placeholder, and spend that minute naming the fields instead.&lt;/li&gt;
&lt;li&gt;Collect service state into the scratch directory, and do not pipe that output into a restart or a package tool.&lt;/li&gt;
&lt;li&gt;Hold any command the classifier marks unknown, because an unknown verb is closer to a write than to a safe read.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# classify.sh: proposed read/write split for a scratch shell you control.&lt;/span&gt;
&lt;span class="c"&gt;# Unknown commands fail closed. This example has not been run on a fleet.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eu&lt;/span&gt;
classify&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="s2"&gt;"systemctl status "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"systemctl is-active "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"journalctl "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"ss "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"ps "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"cat "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"ls "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'READ %s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="s2"&gt;"systemctl restart "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"systemctl stop "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"apt "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"dnf "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"rm "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"kubectl apply "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"kubectl delete "&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'WRITE blocked until the page clock opens: %s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
      &lt;span class="k"&gt;return &lt;/span&gt;2
      &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'UNKNOWN hold for a human: %s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
      &lt;span class="k"&gt;return &lt;/span&gt;3
      &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;esac&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
classify &lt;span class="s2"&gt;"journalctl -u demo.service --since 20 min ago"&lt;/span&gt;
classify &lt;span class="s2"&gt;"systemctl restart demo.service"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I do not smuggle a restart into this window, even when a draft sounds sure about a cause it invented. A restart can hide the evidence the next human needed, and a package change can widen the failure without rollback intent. If the service is already down, the snapshot still comes first, and the write class stays blocked. Have you restarted first and then lost the only log line that explained why the page fired?&lt;/p&gt;

&lt;h2&gt;
  
  
  How the gate decides an unfreeze
&lt;/h2&gt;

&lt;p&gt;When the escalation time passes and the owner has not updated the clock, I stop polishing drafts and page the secondary. The secondary should inherit a closed lock and a held draft, not a half-applied change that nobody can describe. I would rather spend the read window twice than ship an unnamed edit into the same hour as the alert. If severity is still unknown at that moment, I widen the bridge instead of letting a model guess a rank.&lt;/p&gt;

&lt;h3&gt;
  
  
  The unfreeze sentence
&lt;/h3&gt;

&lt;p&gt;The unfreeze rule is short enough to read aloud without scrolling through a long bridge novel. A human may open the write lock only after owner, severity, and rollback intent are real values you trust. An assistant may suggest wording for the rollback line, but it may not flip the lock field by itself. If your severe-page policy demands a second person, the gate stays shut until that second name is written down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# gate.py: proposed local check, not a production agent.
# Exit 2 while the lock is closed or required fields are placeholders.
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;PLACEHOLDERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;replace-with-alert-id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;replace-with-human-name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page-clock.yaml&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;problems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rollback_intent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PLACEHOLDERS&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;replace-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sev1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sev2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sev3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;write_lock&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;write_lock&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;write lock stays closed:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;draft_state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reviewed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;draft still held; human review required&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;human gate passed; use your normal change process&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where a free draft bench fits
&lt;/h2&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. I bring it up here because free model access and a free server can hold this scratch clock away from the production shell. I have no measured quota or hardware sheet, so I will not invent either figure or a permanence claim. The part I actually want is a clean split between a draft bench and the host that is paging.&lt;/p&gt;

&lt;p&gt;Would I paste production credentials onto that free server just to save a copy step during the page? I would not, and the runbook should forbid that shortcut in the same paragraph as the product note. I would use the free model only after the read commands have already produced a snapshot I can stand behind on the bridge. It may tidy four short lines, and it may not invent the owner, the severity, or the rollback intent.&lt;/p&gt;

&lt;p&gt;If you already have a MonkeyCode login, render this clock on the free server and carry the reviewed file back by hand. That is the only invitation I will make, because a runbook should stay useful when the product name is deleted. I still want the classifier and the gate even if you never open that draft bench at all. Does your bridge note still make sense after you remove every product word from the page?&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision table for the first twenty minutes
&lt;/h2&gt;

&lt;p&gt;I read this table before I accept help from anyone, including myself at minute ten when the alert still feels urgent. The rows are states of the clock, not moods, and the last column is the only next action I allow. If a row says the lock stays closed, I do not negotiate with a confident paragraph from a model. Have you ever talked yourself out of a table you wrote while you were still calm?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Clock state&lt;/th&gt;
&lt;th&gt;Draft state&lt;/th&gt;
&lt;th&gt;Next action I allow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File missing&lt;/td&gt;
&lt;td&gt;Any text&lt;/td&gt;
&lt;td&gt;Create the clock on scratch, run read-class commands only, and escalate if no owner can be named&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock closed and severity unknown&lt;/td&gt;
&lt;td&gt;Held&lt;/td&gt;
&lt;td&gt;Keep observing, widen the bridge at escalation time, and do not apply a patch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock closed and severity named&lt;/td&gt;
&lt;td&gt;Held&lt;/td&gt;
&lt;td&gt;Let a human review the note, and keep production writes blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock open and draft reviewed&lt;/td&gt;
&lt;td&gt;Reviewed&lt;/td&gt;
&lt;td&gt;Follow the existing change process, with rollback intent still visible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock open and draft still held&lt;/td&gt;
&lt;td&gt;Any text&lt;/td&gt;
&lt;td&gt;Fail closed, because model text is not approval to write&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  A local test I can run without a fleet
&lt;/h2&gt;

&lt;p&gt;I would test the gate on a laptop before anyone pastes it into a shared runbook, because an untested lock is only a comment. Create a temporary directory, copy the sample clock and gate script into it, and confirm a closed lock exits with status 2. Then write real-looking values, open the lock, mark the draft reviewed, and confirm the pass line prints on your machine. Set severity back to unknown afterward, and confirm the script refuses again, since that state is not a real unfreeze.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exit codes worth trusting
&lt;/h3&gt;

&lt;p&gt;I am not claiming this rehearsal caught a production incident, and I am not publishing a timing number from a borrowed machine. The only result I want is a predictable exit code in a directory you can delete when the check is done. If the closed lock exits zero, the gate is wrong, and it does not belong in the on-call document yet. Would you hand a pager a script that passes while the lock field still says closed?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Proposed local test. Temporary directory only.&lt;/span&gt;
&lt;span class="c"&gt;# Expect status 2, then status 0, then status 2 again.&lt;/span&gt;
&lt;span class="nv"&gt;tmpdir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;page-clock.yaml gate.py &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmpdir&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmpdir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
python3 gate.py &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"closed lock failed closed, exit=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
from pathlib import Path
path = Path("page-clock.yaml")
text = path.read_text(encoding="utf-8")
text = text.replace("replace-with-alert-id", "alert-1001")
text = text.replace("replace-with-human-name", "bridge-owner")
text = text.replace('severity: "unknown"', 'severity: "sev2"')
text = text.replace('write_lock: "closed"', 'write_lock: "open"')
text = text.replace('draft_state: "held"', 'draft_state: "reviewed"')
text = text.replace('rollback_intent: "unset"', 'rollback_intent: "revert the last config commit"')
path.write_text(text, encoding="utf-8")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;python3 gate.py
python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
from pathlib import Path
path = Path("page-clock.yaml")
text = path.read_text(encoding="utf-8")
text = text.replace('severity: "sev2"', 'severity: "unknown"')
path.write_text(text, encoding="utf-8")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;python3 gate.py &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"unknown severity failed closed, exit=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Who should skip this pattern
&lt;/h2&gt;

&lt;p&gt;This pattern is a poor fit when your charter already authorizes a break-glass write, such as a documented failover that does not need a fresh clock. It is also a poor fit when you are alone on a severe page and the existing runbook already names the immediate action to take. Do not place secrets, customer payloads, or production kubeconfigs on a free server because the draft step feels convenient today. Do not use the gate to delay a human page, and do not treat model confidence as a fill-in for the owner.&lt;/p&gt;

&lt;p&gt;Free model access can change, rate-limit, or vanish, so the runbook still has to make sense with the assistant powered off completely. If your organization forbids third-party tools on an incident bridge, skip the product path and keep the clock file plus the classifier. I would rather keep a boring read window than defend a fast draft that nobody on the bridge can explain. Are you adopting a gate you cannot test on a laptop this week, or are you only collecting another note?&lt;/p&gt;

&lt;h2&gt;
  
  
  What I leave in the bridge note
&lt;/h2&gt;

&lt;p&gt;I end the read window with four lines, and I do not let the note grow into a novel while the lock is still closed. Those lines are the page id, the owner, the severity, and the next human action after the escalation time you already named. An assistant may tidy the grammar once those lines exist, but it may not invent them from a vague alert title alone. If you cannot fill those four lines, are you really ready to run a write-class command on the host?&lt;/p&gt;

</description>
      <category>devops</category>
      <category>tutorial</category>
      <category>productivity</category>
      <category>ai</category>
    </item>
    <item>
      <title>A Three-Line Scratch Journal Is the Only Ticket Past the Suggestion Fence</title>
      <dc:creator>Emery Huang</dc:creator>
      <pubDate>Fri, 09 Oct 2026 14:20:32 +0000</pubDate>
      <link>https://dev.to/appcpp_9071/a-three-line-scratch-journal-is-the-only-ticket-past-the-suggestion-fence-9l1</link>
      <guid>https://dev.to/appcpp_9071/a-three-line-scratch-journal-is-the-only-ticket-past-the-suggestion-fence-9l1</guid>
      <description>&lt;p&gt;I keep every mitigation draft behind a fence until a three-line scratch journal exists on disk. That rule is the conclusion, and the rest of this note is only the machinery that makes the rule hard to skip. Would you rather read a smooth fix than the three boring lines that prove you touched the right host? I would not, because a smooth paragraph is cheap and a wrong restart on the wrong host is expensive.&lt;/p&gt;

&lt;p&gt;I am labeling this whole workflow as an unexecuted proposal, not as a pager story from a shift I actually worked. Please copy the checker if it helps, and please do not treat my wording as evidence that a drill already passed. If your team already freezes production with a stronger control, keep that control and borrow only the journal habit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I refuse the draft first
&lt;/h2&gt;

&lt;p&gt;A draft that arrives before the journal is not a head start, because it is a contaminated input I did not ask for yet. I delete that file and I start the journal again, even when the sentences look calm and specific. Would you keep an early guess just because it happened to use the right service name in the first line? I would not, since the service name is often the only true fragment hiding inside an early draft.&lt;/p&gt;

&lt;p&gt;I want the assistant fenced into a suggestion block that a human can throw away without touching a live host. Clever wording is not the goal, because clever wording is how a quiet page becomes a loud one. The fence is local, boring, and intentionally easy to audit with the two shell scripts below. If the scripts feel too small for your estate, that smallness is a feature I am willing to defend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three lanes before any shell
&lt;/h2&gt;

&lt;p&gt;I choose a lane before I open a shell, and I write the lane name at the top of the journal. Observe means I collect read-only lines and I do not rehearse a change on any host. Rehearse means I may use a scratch host that is not production, and I still may not unfreeze anything. Escalate means I stop drafting immediately and I call the person already named in the runbook.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lane names I refuse to blur
&lt;/h3&gt;

&lt;p&gt;The checker cannot promote a lane, because promotion is a human decision I do not want hidden inside a script. If the lane is missing, I treat the page as escalate rather than inventing a policy while I am tired. How often do you discover the real severity only after you have already typed a fix into the shell? I still want the first choice written down, so the later correction has something honest to replace.&lt;/p&gt;

&lt;h2&gt;
  
  
  A table I can read without scrolling
&lt;/h2&gt;

&lt;p&gt;I keep five rows in the table, because a table I cannot memorize will not survive a bad night. If a signal is not in the table, the lane is escalate and the freeze stays on. Would you rather debate a sixth row while the mitigation draft is already half written on disk? I would rather lose a minute to a short table than lose an hour to a guessed command.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal I can verify&lt;/th&gt;
&lt;th&gt;Lane&lt;/th&gt;
&lt;th&gt;First action&lt;/th&gt;
&lt;th&gt;Freeze rule&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alert text only, no host I own&lt;/td&gt;
&lt;td&gt;Escalate&lt;/td&gt;
&lt;td&gt;Call the named secondary&lt;/td&gt;
&lt;td&gt;Stay frozen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Synthetic fixture on a scratch host&lt;/td&gt;
&lt;td&gt;Rehearse&lt;/td&gt;
&lt;td&gt;Write the three-line journal&lt;/td&gt;
&lt;td&gt;Stay frozen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Known gap, no customer impact I can show&lt;/td&gt;
&lt;td&gt;Observe&lt;/td&gt;
&lt;td&gt;Record the journal, do not edit&lt;/td&gt;
&lt;td&gt;Stay frozen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confirmed impact on a service I own&lt;/td&gt;
&lt;td&gt;Escalate&lt;/td&gt;
&lt;td&gt;Call, then journal if asked&lt;/td&gt;
&lt;td&gt;Frozen until a human writes unfreeze&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Draft file exists before any journal&lt;/td&gt;
&lt;td&gt;Refuse&lt;/td&gt;
&lt;td&gt;Delete the draft and start over&lt;/td&gt;
&lt;td&gt;Stay frozen&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is the only row I actually enforce with a script rather than with memory. The other rows remain human judgment, and that judgment stays with the person who accepted the page. I do not want a model to pick the row, even when the alert text sounds confident and complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  The only first commands I allow in the drill
&lt;/h2&gt;

&lt;p&gt;I allow only three read-only commands, and I append them to &lt;code&gt;journal.txt&lt;/code&gt; before I create &lt;code&gt;suggestion.md&lt;/code&gt;. I do not point this drill at production, because production is not a classroom and a classroom is not a change window. The fixture can live on a scratch host, or on my laptop if I do not have a scratch host yet. Can you recite those three lines from memory without opening a wiki or a chat transcript?&lt;/p&gt;

&lt;p&gt;If you cannot recite them, you are not ready to accept a mitigation draft from anyone else. Swap the commands if your estate uses different read-only checks, but do not raise the count and do not add a write. A fourth line is how a drill quietly becomes a change, and I do not want that slide. Keep the journal boring enough that a secondary can read it without asking what you meant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# Proposal only. Not executed against production in this article.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nb"&gt;umask &lt;/span&gt;077
&lt;span class="nv"&gt;journal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.txt&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
: &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$journal&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'utc=%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'host=%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'who=%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-un&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$journal&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"journal_lines=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$journal&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those three lines prove time, place, and identity, and they prove nothing about the cause of the page. That emptiness is deliberate, because I want a witness that I was on the scratch host at all. I do not want a pretend root-cause essay generated before I have looked at anything myself. If your real first commands differ, swap them, but keep them read-only and keep the count at three.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checker that holds the freeze
&lt;/h2&gt;

&lt;p&gt;I run a second script against the journal file and against the suggestion file in the same directory. It fails when the journal has fewer than three non-empty lines, and it fails when the suggestion contains a mutating verb from a short deny-list. It fails closed, so a missing file is a refusal rather than a quiet pass you might miss. Would a short deny-list stop a determined person from pasting a dangerous command somewhere else entirely?&lt;/p&gt;

&lt;p&gt;No, and I am not selling this little script as a security boundary against hostile users on your team. It is a tripwire for a tired first draft, not a sandbox and not a privilege boundary. Run it on files you already trust enough to open in an editor. If you need isolation, use the isolation your security team already approved.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# Proposal only. Unexecuted example for a local drill.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;journal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.txt&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;suggestion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;.md&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$journal&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"refuse: missing journal"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$suggestion&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"refuse: missing suggestion"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;lines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$journal&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$lines&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-lt&lt;/span&gt; 3 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"refuse: journal has &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;lines&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; lines"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi
if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Eiq&lt;/span&gt; &lt;span class="s1"&gt;'(^|[^[:alpha:]])(rm|reboot|shutdown|systemctl|kubectl|terraform|git push)([^[:alpha:]]|$)'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$suggestion&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"refuse: mutating verb in suggestion"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;3
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"hold: journal cleared, freeze still on"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A deny-list is not a boundary
&lt;/h3&gt;

&lt;p&gt;The sample deny-list also blocks read-only kubectl text, so treat it as a starting point rather than a law. Replace those words with the mutating verbs your own estate actually fears in a first draft. I would rather over-block a suggestion file than under-block a restart hidden in polite prose.&lt;/p&gt;

&lt;h3&gt;
  
  
  What hold is allowed to mean
&lt;/h3&gt;

&lt;p&gt;Please read the success string out loud before you trust the script during a tired night shift. It says hold, and it does not say unfrozen, because the checker must not sound like permission. A human unfreeze is a separate line in the handoff, written by a named person after they have read the journal. The assistant may comment on that unfreeze line, and the assistant may not author it alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Escalation stays a phone call, not a paragraph
&lt;/h2&gt;

&lt;p&gt;I name one secondary and one time box in the runbook, and I do not let the assistant choose either of them. If the lane is escalate, I call the secondary before I spend time polishing a suggestion block. If the lane is rehearse, I still call when the scratch journal disagrees with the alert text in a way I cannot explain. How long should you stare at a polished draft before you admit that you are actually stuck?&lt;/p&gt;

&lt;p&gt;I use the time box already written down, and a blank time box means I escalate now. Freeze means no production change, no config push, and no restart that someone might later call temporary. Unfreeze means a named human writes the unfreeze line after reading the journal, not after reading a confident suggestion. I keep those verbs apart so a helpful rewrite cannot smuggle a release into a quiet edit.&lt;/p&gt;

&lt;p&gt;If your handoff packet has no place for that line, add the place before you add another assistant prompt. I want the unfreeze line to name a person, a time, and the journal file they read. A missing name means the freeze continues, even if the suggestion file looks finished and calm. Would you unfreeze a service because a paragraph sounded sure of itself? I would not, not on a night when I am already behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a free scratch server is allowed to help
&lt;/h2&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;I would use free model access and a free server option only as a rehearsal bench for this fence. The operator supplying this draft says both options are available, and I am treating that claim as supplied rather than tested. I am not stating a token quota, a model list, a hardware size, or any duration. This draft has no primary source for those figures, so inventing them would make the runbook dishonest.&lt;/p&gt;

&lt;p&gt;If a current pricing or quota page is your source of truth, read that page before you plan a rotation around it. Would I send production traffic, secrets, or real alert payloads to a free rehearsal server at all? No, and you should not do that either, even when the signup flow makes the server feel disposable.&lt;/p&gt;

&lt;p&gt;MonkeyCode shows up here only as a place where I can open a scratch host and ask for wording inside the suggestion file. It does not choose the lane, it does not clear the checker, and it does not write the unfreeze line. If the free option is gone tomorrow, the runbook still works on a laptop with the two scripts above. That fallback matters more to me than any invitation to try a product on a quiet afternoon.&lt;/p&gt;

&lt;p&gt;If you want the same fence on a scratch host, start from the operator's current access note and stop where that note stops. I am leaving the product mention there, because a second pitch would not make the checker any stricter. Would another slogan make the hold string any safer when you are tired and the page is still frozen? I do not think so, and I would rather you spend that minute rereading the journal than rereading an ad.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should skip this fence
&lt;/h2&gt;

&lt;p&gt;Do not use this fence when the page requires immediate physical safety action, such as cutting power or isolating a live hazard. A journal gate is the wrong first move when people can get hurt while you create a file. Do not use a local bash script as an audit artifact for a control framework you have not scoped with your security team. Do not use an external model when your incident policy forbids third-party tools, even on fixtures that might echo real names.&lt;/p&gt;

&lt;p&gt;I also skip the model when the alert already contains secrets, customer payloads, or hostnames I am not allowed to paste into a draft box. I strip the fixture until it is boring, or I skip the model and keep the checker alone. The checker is the artifact I am willing to defend, and the model is optional help for wording. If you need a vendor to be on the hook for uptime, a free rehearsal bench is the wrong dependency for your real rotation.&lt;/p&gt;

&lt;h2&gt;
  
  
  A six-step drill before the next handoff
&lt;/h2&gt;

&lt;p&gt;I would run this on a laptop, with fake alert text, before I ever mention it in a real channel. Nothing below has been executed for this article, so treat every exit code as something you still need to see yourself. If a step surprises you, stop and fix the script before you talk about it in a handoff. A drill you have not watched fail on purpose is not a drill yet.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write a one-line fake alert into &lt;code&gt;alert.txt&lt;/code&gt;, and keep real hostnames and customer data out of that file.&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;journal.txt&lt;/code&gt; with the three-line script, and confirm the printed line count is exactly three.&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;suggestion.md&lt;/code&gt; only after the journal exists, and keep every mutating verb out of that file.&lt;/li&gt;
&lt;li&gt;Run the checker on both files, and confirm a clean suggestion prints hold rather than a release word.&lt;/li&gt;
&lt;li&gt;Paste one denied verb on purpose, rerun the checker, and confirm the process exits with code 3.&lt;/li&gt;
&lt;li&gt;Delete the drill files when you finish, so the next page cannot reuse a stale journal by accident.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If step 5 does not fail, the fence is theater and I would not carry it into a rotation. If step 4 prints anything that sounds like permission to change production, rewrite the success string before you trust the script. I am leaving both scripts unexecuted here so you can read them as proposals before you run them anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  The conclusion I am willing to keep
&lt;/h2&gt;

&lt;p&gt;A page gets safer when the first artifact is a journal, not a fix written in a hurry. The assistant can help me phrase a suggestion after that journal exists, and it cannot grant an unfreeze by itself. Free model access and a free scratch server can host the rehearsal inside the limits I already stated. If your runbook already names a human unfreeze and a read-only first move, you can ignore this note, and that is a fine outcome.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>tutorial</category>
      <category>linux</category>
      <category>ai</category>
    </item>
    <item>
      <title>Echo the Alert Fingerprint Before an Assistant Draft Leaves Quarantine</title>
      <dc:creator>Emery Huang</dc:creator>
      <pubDate>Thu, 08 Oct 2026 07:40:05 +0000</pubDate>
      <link>https://dev.to/appcpp_9071/echo-the-alert-fingerprint-before-an-assistant-draft-leaves-quarantine-5c6p</link>
      <guid>https://dev.to/appcpp_9071/echo-the-alert-fingerprint-before-an-assistant-draft-leaves-quarantine-5c6p</guid>
      <description>&lt;p&gt;I keep every assistant draft in quarantine until a rehearsal host echoes the same alert fingerprint I copied from the page. A fluent explanation can still describe the wrong incident, the wrong cluster, or a page that already closed an hour ago. Would you really execute a mutating command that cannot name the exact page it claims to repair? I would not take that shortcut, and this runbook is the gate I use before any production change.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I treat the page when it first lands
&lt;/h2&gt;

&lt;p&gt;I read the alert as a payload first and as a story second, even when the paging app shows a friendly title. The friendly title helps a human get oriented, but it is not the identity of this page. The fingerprint is the identity I will later demand from the rehearsal host, character for character. If the paging app hides that field behind a collapsed panel, I expand it before I copy anything else.&lt;/p&gt;

&lt;p&gt;I write this gate because a tidy summary can belong to a different page than the one sitting in my hand. The service name matches, the verbs sound familiar, and the timestamp quietly belongs to a page that already ended. Have you ever accepted a paragraph because the cluster label looked right and the rest felt too boring to check? I want the echo to come from a probe I can hash, not from another sentence that merely sounds careful.&lt;/p&gt;

&lt;p&gt;A model can rearrange an old runbook into confident steps without ever seeing the payload that opened this page. It can also blend two alerts that share a service name and differ only in a label a tired reader skips. Would a second generated paragraph catch that mistake if it only saw the same pasted note? I do not treat a second paragraph as evidence, so the rehearsal host must repeat the fields before I trust the note.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the draft and the echo are allowed to live
&lt;/h2&gt;

&lt;p&gt;I let the explanation live in a scratch note, and I let the echo live on a host that holds no production credentials. I use MonkeyCode's free model access for the scratch note and its free server option for the echo, and neither one is an approver. The model may rewrite that note, but it may not open a shell on the rehearsal host at all. The rehearsal host may print the fingerprint, but it may not hold a write token for the real cluster.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;What I need from those two options is a place to draft and a place to echo, not a promise about capacity. Would I paste a production kubeconfig into a chat window just to save a few minutes of typing? I would not, even when the server costs nothing and the chat feels private enough for a quick paste. A free seat does not convert a secret into something safe to share with a drafting tool.&lt;/p&gt;

&lt;p&gt;If the rehearsal host can still reach production, I treat that host as production and I stop the drill. Isolation is a property I have to prove, not a feature I get to assume from a price of zero. Have you checked which credentials are already sitting in that host's environment before you call it safe? I stop the echo until that check is written down, because a memory of isolation is not isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I copy before I ask for an explanation
&lt;/h2&gt;

&lt;p&gt;I copy four fields into the scratch note, and I refuse to add color before those fields are complete. If any field is missing, I escalate with the raw page instead of asking a model to guess the gap. Guessing a timestamp feels helpful in the moment and becomes a lie the moment someone audits the note. Would you want your name beside a startsAt value that a chat invented from the word recently?&lt;/p&gt;

&lt;h3&gt;
  
  
  Four fields, nothing else
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;alertname&lt;/code&gt;, so the echo cannot quietly drift toward a sibling alert that shares a similar title.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fingerprint&lt;/code&gt;, so two similar series cannot impersonate each other inside an otherwise convincing note.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;startsAt&lt;/code&gt;, so a closed page cannot reuse a summary that was written for yesterday's incident.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;generatorURL&lt;/code&gt;, so I can see which rule produced the page before I trust any explanation of it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I leave annotations, suggested fixes, and dashboard screenshots out of that first copy on purpose. Those extras help later, but they tempt the draft to sound finished before any echo exists on disk. Have you noticed how a bright screenshot can make an unverified guess feel like a finished observation? I keep the screenshot in the ticket until the fingerprint match is already written under oncall-state.&lt;/p&gt;

&lt;h2&gt;
  
  
  First commands, and only these
&lt;/h2&gt;

&lt;p&gt;The first commands stay on my laptop until a minimal page file exists and a hash is written beside it. I am labeling the snippets as a proposal you should adapt, not as a transcript from a cluster I measured. Paths, probes, and field names will differ on your side, and that difference is your job to resolve. What should you do if your alert payload uses different names for the same four ideas?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ./oncall-state
jq &lt;span class="s1"&gt;'{alertname,fingerprint,startsAt,generatorURL}'&lt;/span&gt; page.json | &lt;span class="nb"&gt;tee&lt;/span&gt; ./oncall-state/page-min.json
jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.alertname and .fingerprint and .startsAt and .generatorURL'&lt;/span&gt; ./oncall-state/page-min.json
&lt;span class="nb"&gt;sha256sum&lt;/span&gt; ./oncall-state/page-min.json | &lt;span class="nb"&gt;tee&lt;/span&gt; ./oncall-state/page-min.sha256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On macOS I swap sha256sum for shasum with the 256 flag, and I keep the same output file name. If the field check fails, I do not open a model chat and I do not start a rehearsal session. I escalate with the raw page attached and the words fingerprint incomplete in the very first line. Would you ask a model to invent a startsAt value from a vague just now typed in chat?&lt;/p&gt;

&lt;p&gt;The rehearsal echo is a read-only probe that prints the same four fields and then exits cleanly. I run that probe only after the local minimal file has a hash I can compare later by eye. A free server helps only when that environment cannot reach production credentials or the paging API. If I cannot prove that isolation, I skip the server and escalate with the local file alone.&lt;/p&gt;

&lt;p&gt;I save the probe output as echo.json with a redirect, and I never let the model write that file for me. The redirect is boring, which is exactly what I want from the first commands on a live page. A boring command is easier to read aloud to a secondary who is only half awake. Can you explain a clever one-liner to that person without accidentally opening a second incident?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Proposal: run on the rehearsal host, not on production.&lt;/span&gt;
&lt;span class="c"&gt;# Replace the probe with your own read-only status command.&lt;/span&gt;
&lt;span class="c"&gt;# Save that probe output yourself as ./echo.json first.&lt;/span&gt;
jq &lt;span class="s1"&gt;'{alertname,fingerprint,startsAt,generatorURL}'&lt;/span&gt; ./echo.json | &lt;span class="nb"&gt;tee&lt;/span&gt; ./oncall-state/echo-min.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The comparison I refuse to skip
&lt;/h2&gt;

&lt;p&gt;I keep the comparison in a script so a tired shift cannot decide that two strings mostly match. A partial match is how the wrong series survives a glance and later becomes a restart. This script is a proposal, and I am not claiming I ran it against your pager or scored its misses. Read it, change the paths, and throw it away if your payload cannot supply stable fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# Proposal only. Do not point this at production credentials.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;PAGE_MIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?page-min.json&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;ECHO_MIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;:?echo-min.json&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;STATE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;./oncall-state&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;page_fp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'[.alertname,.fingerprint,.startsAt,.generatorURL] | join("|")'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PAGE_MIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;echo_fp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'[.alertname,.fingerprint,.startsAt,.generatorURL] | join("|")'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ECHO_MIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$page_fp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"null"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$echo_fp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"null"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$page_fp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'escalate: incomplete fingerprint'&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/decision.txt"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'frozen'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/prod.state"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$page_fp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$echo_fp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'escalate: echo mismatch'&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/decision.txt"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'frozen'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/prod.state"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;3
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"match: &lt;/span&gt;&lt;span class="nv"&gt;$page_fp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/decision.txt"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'echo-matched-still-frozen'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/prod.state"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Read the state aloud
&lt;/h3&gt;

&lt;p&gt;After the script exits, I read the state file aloud before I touch any other tool on the laptop. A nonzero exit means I stop drafting and I send the decision line to the secondary on call. A zero exit still leaves production frozen, which surprises people who treat a match as approval. Why would a matching string be enough to restart a service you have not otherwise checked?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash ./fingerprint-gate.sh ./oncall-state/page-min.json ./oncall-state/echo-min.json
&lt;span class="nb"&gt;cat&lt;/span&gt; ./oncall-state/prod.state
&lt;span class="nb"&gt;cat&lt;/span&gt; ./oncall-state/decision.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A drill that should fail closed
&lt;/h3&gt;

&lt;p&gt;This fixture should exit on a mismatch and leave the state file frozen, which is the result I want from a drill. If your copy prints a match, you swapped the fingerprints and the drill has not taught you anything yet. Run it locally before you point any probe at a rehearsal host, so a path bug does not look like an incident. Did the frozen state actually stop you from pasting the next command, or did you override it from habit?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ./oncall-state
jq &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; alertname LatencyHigh &lt;span class="nt"&gt;--arg&lt;/span&gt; fingerprint abc123 &lt;span class="nt"&gt;--arg&lt;/span&gt; startsAt 2026-10-08T09:00:00Z &lt;span class="nt"&gt;--arg&lt;/span&gt; generatorURL https://example.invalid/rule &lt;span class="s1"&gt;'{alertname:$alertname,fingerprint:$fingerprint,startsAt:$startsAt,generatorURL:$generatorURL}'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./oncall-state/page-min.json
jq &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; alertname LatencyHigh &lt;span class="nt"&gt;--arg&lt;/span&gt; fingerprint def456 &lt;span class="nt"&gt;--arg&lt;/span&gt; startsAt 2026-10-08T09:00:00Z &lt;span class="nt"&gt;--arg&lt;/span&gt; generatorURL https://example.invalid/rule &lt;span class="s1"&gt;'{alertname:$alertname,fingerprint:$fingerprint,startsAt:$startsAt,generatorURL:$generatorURL}'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./oncall-state/echo-min.json
bash ./fingerprint-gate.sh ./oncall-state/page-min.json ./oncall-state/echo-min.json &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'drill failed closed, as intended'&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; ./oncall-state/prod.state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Escalation when the echo will not match
&lt;/h2&gt;

&lt;p&gt;I escalate on three results, and I do not ask the model to repair any of them in the chat. A mismatch means I am holding two payloads, not a wording problem the draft can smooth over. An incomplete page means the rule or the receiver dropped a field I refuse to invent tonight. A rehearsal host that offers a write-capable shell is the wrong host, even when the text looks perfect.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send the secondary the two minimal JSON files and the decision line, not a generated essay about the page.&lt;/li&gt;
&lt;li&gt;Say whether the failure is incomplete, mismatched, or a write-capable shell you should abandon immediately.&lt;/li&gt;
&lt;li&gt;Stop drafting commands until a human owner names the next read-only probe you are allowed to run.&lt;/li&gt;
&lt;li&gt;Keep the original page open so nobody summarizes away the fingerprint while the call is still live.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the secondary is dark, I use the owner already printed in the service catalog and I wait for that person. I stay on the read-only probe until that person answers, even if the draft already shows a tempting command. I am not inventing a new rotation policy in this note, and I am not replacing your existing roster. I am only refusing to let a generated paragraph become the path that wakes the next person.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze, then a separate unfreeze line
&lt;/h2&gt;

&lt;p&gt;I keep production frozen while the state file is missing, reads frozen, or still reads echo-matched-still-frozen. A fingerprint match is not permission to change anything a user can already feel in production. A match only shows that the rehearsal host repeated the page I think I am holding right now. Would a matching string justify a restart by itself if you still have not checked capacity or scope?&lt;/p&gt;

&lt;p&gt;Unfreeze is a separate line I append by hand after that human check, and the model does not write it. The rehearsal host does not write it either, because a host that echoes data should not grant change rights. I want a name, a UTC time, and the fingerprint in that line so a later review can see who moved. If those three pieces are missing, the freeze continues and the draft remains a note, not a plan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Human only. Replace YOUR_NAME. Do not pipe model output into this file.&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"unfreeze YOUR_NAME 2026-10-08T09:30:00Z &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .fingerprint ./oncall-state/page-min.json&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ./oncall-state/unfreeze.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A decision table I can scan at 3 a.m.
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Echo result&lt;/th&gt;
&lt;th&gt;State file&lt;/th&gt;
&lt;th&gt;What I do next&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Incomplete fields&lt;/td&gt;
&lt;td&gt;frozen&lt;/td&gt;
&lt;td&gt;Escalate with the raw page and stop drafting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fingerprints differ&lt;/td&gt;
&lt;td&gt;frozen&lt;/td&gt;
&lt;td&gt;Escalate with both minimal files and stop drafting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fields match&lt;/td&gt;
&lt;td&gt;echo-matched-still-frozen&lt;/td&gt;
&lt;td&gt;Human reviews scope, then may write the unfreeze line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shell can write&lt;/td&gt;
&lt;td&gt;frozen&lt;/td&gt;
&lt;td&gt;Abandon that host and pick one without production credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I can scan that table without asking a model to reinterpret my own rule back to me. If the table and the script disagree, I trust the more restrictive result and I fix the script later. A 3 a.m. argument about which artifact is canonical is how freezes quietly evaporate on a tired team. Would you rather debate the table in the channel, or keep production still until a named human decides?&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations, and who should skip this
&lt;/h2&gt;

&lt;p&gt;This gate catches identity mix-ups, and it does not tell you whether the underlying alert is a true page. A bad exporter can stamp the same fingerprint on two different failures, and the script will happily match them. I have no measured false-match rate to offer, so please do not quote this note as a control study. Would you accept a teaching script as proof that your paging pipeline is safe enough for Friday?&lt;/p&gt;

&lt;p&gt;Free model access does not make the scratch note true, current, or allowed to approve a production change. A free server option does not prove isolation, network distance, or the absence of copied secrets on disk. You still have to check credentials yourself before the first probe, or the echo is just production with extra steps. If your organization forbids external models from seeing alert text, do not paste the page into one.&lt;/p&gt;

&lt;p&gt;Skip this approach if the alert payload has no stable fingerprint, start time, and generating rule URL. Skip it if you cannot place the echo on a host that is sealed off from production credentials entirely. Skip it during a life-safety incident where the existing human runbook already names the immediate action to take. Skip it if you wanted an assistant to act as approver, because this note never grants that role to anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I still do with the draft
&lt;/h2&gt;

&lt;p&gt;I still read the draft, because a clear sentence can help a tired person notice a label they missed. I just refuse to let that sentence travel until the echo matches and a human writes the unfreeze line. If you already have a free model seat and a free server you can truly isolate, try the gate on a drill. Watch whether a bad echo stops your hands before you ever trust the same habit on a live page.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>tutorial</category>
      <category>monitoring</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Keep the Assistant on a Scratch Host Until a Human Diff Unfreezes Prod</title>
      <dc:creator>Emery Huang</dc:creator>
      <pubDate>Thu, 24 Sep 2026 20:19:22 +0000</pubDate>
      <link>https://dev.to/appcpp_9071/keep-the-assistant-on-a-scratch-host-until-a-human-diff-unfreezes-prod-3m75</link>
      <guid>https://dev.to/appcpp_9071/keep-the-assistant-on-a-scratch-host-until-a-human-diff-unfreezes-prod-3m75</guid>
      <description>&lt;p&gt;I will not feed a live page to an assistant until a scratch host is named, isolated, and written into the freeze note. Production stays frozen while that host tries to reproduce the symptom with copied fixtures, not live credentials. A generated patch remains a suggestion until a human pastes a diff and signs the unfreeze line. If this feels fussy, ask yourself who you want holding the deploy key at 2 a.m.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I separate the model from the page
&lt;/h2&gt;

&lt;p&gt;The failure mode I am writing against is not a dumb suggestion, but a useful suggestion aimed at the wrong machine. Someone pastes the alert, the tool offers a patch, and the same shell session still has production environment variables loaded. Would you trust a completion that can also see your current kube context and your deploy role? I would not, because a single wrong directory is enough to turn a drill into an outage.&lt;/p&gt;

&lt;p&gt;I want the assistant where it can be wrong in public, on a host that cannot reach the production network path. That host can be small, temporary, and boring, as long as its name is in the note before the first prompt. The page still needs a human owner, because a model does not carry the pager and does not join the bridge. When the scratch host and the production host share a name, I stop and rename one of them before I continue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alerts I will actually accept
&lt;/h2&gt;

&lt;p&gt;I accept a page only when these four lines are filled, and I bounce it back when any line is blank. Each line is a fact I can check, not a vibe about how severe the chart looks. A blank line is a bounce, not a debate, because the bridge should not invent missing facts while the chart is red. Would you rather argue about severity, or spend those minutes filling the four lines I listed below?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The alert name, the firing time, and the service identifier are copied from the page, not retyped from memory.&lt;/li&gt;
&lt;li&gt;A symptom query is named, and that query is read-only, so the first action cannot mutate state.&lt;/li&gt;
&lt;li&gt;A scratch host name is reserved, and that name is different from every production host in the rotation sheet.&lt;/li&gt;
&lt;li&gt;Two clocks are written down: one for scratch reproduction, and one for a human diff review.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  First commands I run before any prompt
&lt;/h2&gt;

&lt;p&gt;I start on the production host with commands that only read, and I paste their output into the note before I open a chat. The goal is a symptom packet, not a fix, because the fix conversation can wait until the packet is boring and complete. If a command would change a file, restart a process, or roll a deployment, it does not belong in this first pass. I would rather look slow on the bridge than explain a write command I cannot undo after the fact.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the probe is allowed to do
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Unexecuted example. Run only on a host you already own.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eu&lt;/span&gt;
&lt;span class="nb"&gt;hostname
date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ
git &lt;span class="nt"&gt;-C&lt;/span&gt; /srv/app status &lt;span class="nt"&gt;--short&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;git &lt;span class="nt"&gt;-C&lt;/span&gt; /srv/app rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="c"&gt;# Read-only symptom probe. Replace with your service's real query.&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 5 http://127.0.0.1:8080/healthz &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"healthz_failed"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I copy that output into a local file called &lt;code&gt;symptom.txt&lt;/code&gt;, and I do not upload secrets that sometimes hide in environment dumps. A health check failure is enough context for a first prompt, and a stack trace can be trimmed before it leaves the host. Would you paste a database URL into a chat window just to save one tired minute? I hope your answer is no, because that saved minute is cheaper than rotating a leaked credential later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I refuse to paste into the prompt
&lt;/h2&gt;

&lt;p&gt;I keep the prompt smaller than the note, because the note can hold more context than a chat window should see. I paste the alert name, the trimmed health output, and the question I want answered, and I leave credentials out. A model that asks for a token, a kubeconfig, or a production hostname is asking me to break the freeze. I close that chat and continue with the local editor, because convenience is not an exception to the note.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt questions that stay useful
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Can you rewrite this gate so a missing freeze note exits before any append, and show the diff only?&lt;/li&gt;
&lt;li&gt;Can you list which lines in this health output are symptoms, and which lines are just timestamps?&lt;/li&gt;
&lt;li&gt;Can you draft a freeze-note template with scratch host, reviewer, and both clocks, without inventing a service name?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Escalation clocks that are not one vague minute
&lt;/h2&gt;

&lt;p&gt;I do not use one vague escalate-if-needed line, because that line is how pages stall while everyone waits for someone else. I set a reproduction clock at fifteen minutes, and I set a human-diff clock at thirty minutes from the page time. If the scratch host cannot replay the symptom by the first clock, I call the service owner instead of prompting harder. If the human diff is still missing at the second clock, I call a second engineer and keep production frozen.&lt;/p&gt;

&lt;p&gt;Those numbers are a starting proposal for a small team, not a benchmark I measured across companies. Change them in your runbook if your bridge already has a tighter rule, and write the chosen numbers into the note. The point is that both clocks exist before the assistant is opened, so the model cannot become the escalation path. Ask yourself who you call when the suggestion looks confident and the graph still looks wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze and unfreeze, with the assistant in the rule
&lt;/h2&gt;

&lt;p&gt;Freeze means no deploy, no config write, and no apply of generated code on any host whose name appears in the production inventory. Unfreeze means a named human has reviewed a diff that was produced on the scratch host and copied back as text. I do not unfreeze on a model claim that tests passed, because I did not watch those tests myself. The freeze note must name the scratch host, the symptom file, and the person who may sign the unfreeze line.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the scratch gate checks
&lt;/h3&gt;

&lt;p&gt;Here is the gate I want in the scratch session before anyone applies a patch, even on the scratch host. It is a proposal, not a tool I have certified, and you should read every line before you trust it. The script exits if it sees a production marker, because a scratch workflow that runs on prod is just an outage with extra steps. I keep it short so a tired reviewer can actually read it on a bridge call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# Proposal only. Not executed in this article. No network calls.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SCRATCH_HOST&lt;/span&gt;:?set&lt;span class="p"&gt; SCRATCH_HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FREEZE_NOTE&lt;/span&gt;:?set&lt;span class="p"&gt; FREEZE_NOTE to a local note path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUMAN_REVIEWER&lt;/span&gt;:?set&lt;span class="p"&gt; HUMAN_REVIEWER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PRODUCTION&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DEPLOY_ENV&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"refusing: production marker is set"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;this_host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;this_host&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SCRATCH_HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"refusing: hostname &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;this_host&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is not &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SCRATCH_HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;3
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FREEZE_NOTE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"refusing: freeze note missing"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;4
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"scratch_host=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SCRATCH_HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FREEZE_NOTE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"refusing: freeze note does not name this scratch host"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;5
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s host=%s reviewer=%s action=scratch_ok\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;this_host&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUMAN_REVIEWER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FREEZE_NOTE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"scratch gate passed; still frozen until a human diff is signed"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A three-step drill before the rotation
&lt;/h3&gt;

&lt;p&gt;I treat the script as untrusted until this drill passes on a host that has no production route. The drill is a proposal you can run in a shell, and I have not published timing numbers from it. If a step fails, fix the script or the note, and do not skip ahead to a model prompt. Would you trust a gate you have never watched fail on purpose during a quiet drill?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Export &lt;code&gt;PRODUCTION=1&lt;/code&gt; on the scratch host and confirm the gate script exits with status 2 before you unset the variable.&lt;/li&gt;
&lt;li&gt;Point &lt;code&gt;SCRATCH_HOST&lt;/code&gt; at a wrong name and confirm the script exits before it appends a line to the freeze note.&lt;/li&gt;
&lt;li&gt;Run the script with matching names and confirm it appends one &lt;code&gt;scratch_ok&lt;/code&gt; line and still prints that production remains frozen.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A small decision table for the bridge
&lt;/h2&gt;

&lt;p&gt;I keep this table in the runbook so the bridge does not invent a new policy while the graph is red. Read the row, do the action, and write the time next to the row you used. If two rows seem to match, I take the stricter row and I stay frozen until the note is updated. Would you rather argue about a row for ten minutes, or follow the stricter one and argue after the page is quiet?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;What I do&lt;/th&gt;
&lt;th&gt;Freeze state&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Symptom file is missing&lt;/td&gt;
&lt;td&gt;Run the read-only probe, save output, do not prompt&lt;/td&gt;
&lt;td&gt;Stay frozen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scratch host name matches a prod host&lt;/td&gt;
&lt;td&gt;Rename the scratch host and update the note&lt;/td&gt;
&lt;td&gt;Stay frozen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reproduction fails before the first clock&lt;/td&gt;
&lt;td&gt;Call the service owner with the symptom file&lt;/td&gt;
&lt;td&gt;Stay frozen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Patch exists but no human diff&lt;/td&gt;
&lt;td&gt;Wait, or call the second engineer at the second clock&lt;/td&gt;
&lt;td&gt;Stay frozen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human diff is signed and tests were watched&lt;/td&gt;
&lt;td&gt;Unfreeze only the named service, then watch the same probe&lt;/td&gt;
&lt;td&gt;Unfreeze that service&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where a free assistant fits, and where it does not
&lt;/h2&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;I want a second editor only for the scratch-host script, the freeze note, and the trimmed symptom summary. That is the only job I will give an assistant during a page, and I will not hand it the deploy role. MonkeyCode is relevant here as an open-source assistant only when free model access and a free server are still offered. I am not naming a token quota, a model, or hardware, because this draft does not cite a current pricing page.&lt;/p&gt;

&lt;p&gt;Check the project docs yourself before you plan a shift around any free tier you might have heard about. Treat a missing or unclear limit as a reason to stay on tools you already run locally. Would you bet a page on a free tier whose current cap you have not opened and read? I would not, so the runbook names the local editor as the fallback when the docs are silent.&lt;/p&gt;

&lt;p&gt;The free server option matters only as a candidate scratch host, and only after you confirm it cannot see production credentials or production networks. I would use it to rewrite the gate script, to draft the freeze note, or to replay a fixture I already copied by hand. I would not point it at a live cluster, and I would not paste deploy keys into the prompt to save a step. If the free server and the production VPC share a route, it is not a scratch host, no matter how convenient the signup felt.&lt;/p&gt;

&lt;p&gt;Remove the product name and the workflow still stands: isolate the editor, freeze production, demand a human diff, and escalate on two clocks. The assistant is a text worker on a disposable machine, not an on-call engineer and not a change-management system. If the free access disappears tomorrow, I still want the same gate script and the same table in the runbook. That is the test I use before I mention a tool in a page procedure at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits, and who should not copy this
&lt;/h2&gt;

&lt;p&gt;This approach assumes you can spare a separate host, a local note file, and a second human before the second clock expires. It is a poor fit for a solo operator who has no one to sign the diff, because the unfreeze rule then becomes theater. It is also a poor fit for a data-loss page that needs a vendor bridge in the first minutes. A scratch replay can waste the only useful minutes when the vendor already holds the timeline.&lt;/p&gt;

&lt;p&gt;I have not measured mean time to recovery with this gate, and I will not pretend a blog draft is an incident report. Do not use this script as a security boundary against a hostile insider, because it only checks a few environment markers and a hostname. Do not store customer data in the symptom file, and do not treat a model transcript as an audit log. If your regulator wants a signed change record, keep using that system and let this note point at it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do before the next page
&lt;/h2&gt;

&lt;p&gt;I would copy the gate script into a private drill repo and point it at a host that is already empty. I would then fail the script on purpose with a production marker, and I would keep the failure in the drill note. After that drill passes, I would add the decision table and pick both clocks with the people who carry the pager. The page gets safer when the freeze note names a host a human can still refuse, not because a tool was free.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>tutorial</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Name a Rollback File in the Handoff Packet Before You Touch the Alert</title>
      <dc:creator>Emery Huang</dc:creator>
      <pubDate>Wed, 23 Sep 2026 16:47:25 +0000</pubDate>
      <link>https://dev.to/appcpp_9071/name-a-rollback-file-in-the-handoff-packet-before-you-touch-the-alert-1m2f</link>
      <guid>https://dev.to/appcpp_9071/name-a-rollback-file-in-the-handoff-packet-before-you-touch-the-alert-1m2f</guid>
      <description>&lt;p&gt;I will not accept a production page until the handoff packet names a rollback file. That sounds stubborn on a noisy night, and it should, because unowned mitigations become the next incident. Alerts without a five-minute diagnostic budget turn into guesswork, and guesswork is how freeze rules get skipped. So the packet comes first, freeze comes second, and any assistant-generated command stays off production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed from agents is not permission to skip the packet
&lt;/h2&gt;

&lt;p&gt;People keep asking whether an agent should call production APIs without ever leaving the editor window. Have you watched a tool-calling demo and felt your pager twitch at the same time? I have, and that uneasy feeling is exactly why this handoff packet exists before diagnosis. Fast patches are useful on a scratch box, but they are reckless while the customer path stays unfrozen.&lt;/p&gt;

&lt;p&gt;The public conversation around agents and tool calling is loud right now, and on-call is the quiet counter-argument. An assistant that can draft a command is not an assistant that may run that command. I want command generation isolated from the cluster that is actually paging me right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fill the handoff packet before you type a diagnostic
&lt;/h2&gt;

&lt;p&gt;I keep a YAML packet next to the service, and I refuse to start diagnosis until every required field has a value. The packet is not a novel, and it is not a postmortem; it is a small transfer object. If I get hit by a second alert, the backup on-call should continue from this file alone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proposed example: oncall-handoff.packet.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oncall.packet/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HandoffPacket&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;alert_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example-ALERT-0000&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-api&lt;/span&gt;
  &lt;span class="na"&gt;opened_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-09-23T02:14:00Z&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;diagnostic_budget_seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
  &lt;span class="na"&gt;rollback_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy/rollback/checkout-api.last-good.txt&lt;/span&gt;
  &lt;span class="na"&gt;rollback_file_sha256&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pending&lt;/span&gt;
  &lt;span class="na"&gt;customer_visible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;freeze_scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-api-canary&lt;/span&gt;
  &lt;span class="na"&gt;unfreeze_owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;primary-oncall&lt;/span&gt;
  &lt;span class="na"&gt;escalation_target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payments-secondary&lt;/span&gt;
  &lt;span class="na"&gt;first_commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kubectl get deploy checkout-api -o wide&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kubectl logs deploy/checkout-api --tail=200 --since=10m&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl -sS https://status.internal.example/checkout-api/healthz&lt;/span&gt;
  &lt;span class="na"&gt;notes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proposed packet; do not treat hostnames as real inventory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Does that YAML look heavy for a single page, or does it look lighter than a wrong restart? Every field exists because I have watched pages drift when the rollback path lived only in someone's head. I treat missing fields as a failed start, not as optional documentation I can fill after mitigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fields I fill in a fixed order
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;I copy &lt;code&gt;alert_id&lt;/code&gt; from the page so the packet and the pager cannot silently diverge later.&lt;/li&gt;
&lt;li&gt;I point &lt;code&gt;rollback_file&lt;/code&gt; at a real artifact so mitigation is a file path, not a remembered command.&lt;/li&gt;
&lt;li&gt;I set &lt;code&gt;diagnostic_budget_seconds&lt;/code&gt; to three hundred so the first five minutes cannot quietly become an hour.&lt;/li&gt;
&lt;li&gt;I name &lt;code&gt;freeze_scope&lt;/code&gt; as a concrete surface, because freezing the whole company is not a strategy.&lt;/li&gt;
&lt;li&gt;I name &lt;code&gt;unfreeze_owner&lt;/code&gt; as one person, so thawing production is never an implied group decision at night.&lt;/li&gt;
&lt;li&gt;I name &lt;code&gt;escalation_target&lt;/code&gt; as a different person, so a spent budget already has a next human.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those six fields are the contract I accept with the rotation, and I will not bargain them down during the incident. If a field is unknown, I page the service owner instead of inventing a placeholder that looks complete. A pretty packet with fake owners is worse than a refused page, because it launders uncertainty into action.&lt;/p&gt;

&lt;p&gt;The rollback file itself is boring on purpose, and boring is what I want under a paging alert. I keep last-good image, replica count, and config identity in one text file beside the deploy path. If that file is missing, the validator below refuses the page before I touch a shell.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# proposed example: deploy/rollback/checkout-api.last-good.txt
image: checkout-api:sha-example
replicas: 3
configmap: checkout-api-2026-09-01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  First commands stay read-only even when a restart looks obvious
&lt;/h2&gt;

&lt;p&gt;I map each alert to commands that cannot change cluster state, and I paste them from the packet. If a command needs apply, delete, restart, or scale, it does not belong in the first_commands list. Ask yourself this: would I still run that command if the unfreeze owner were offline tonight?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# proposed read-only first commands for a latency page&lt;/span&gt;
kubectl get deploy checkout-api &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.spec.replicas}{"\n"}'&lt;/span&gt;
kubectl describe pod &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;checkout-api | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'1,80p'&lt;/span&gt;
kubectl get events &lt;span class="nt"&gt;--field-selector&lt;/span&gt; involvedObject.name&lt;span class="o"&gt;=&lt;/span&gt;checkout-api &lt;span class="nt"&gt;--sort-by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.lastTimestamp
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/healthz.json &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://status.internal.example/checkout-api/healthz
&lt;span class="nb"&gt;sha256sum &lt;/span&gt;deploy/rollback/checkout-api.last-good.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I record the output hashes in the packet notes before I argue about causes with anyone. Why bother hashing terminal output during a live page, when the logs are already scrolling away? Because the next person should see the same evidence I saw, not a vanished buffer.&lt;/p&gt;

&lt;p&gt;A freeze annotation is not a first command, even when the dashboard is screaming and the room wants motion. I will sketch it with &lt;code&gt;--dry-run=client&lt;/code&gt; after the budget expires, and I still will not apply it from an assistant session. Human ownership stays on the freeze, and the scratch box stays a scratch box.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# not a first command; only after budget expiry and named freeze_scope&lt;/span&gt;
&lt;span class="c"&gt;# proposed freeze annotation, human-owned, never assistant-owned&lt;/span&gt;
kubectl annotate deploy checkout-api oncall.packet/frozen&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Freeze only what the packet names, then keep unfreeze as a second signature
&lt;/h2&gt;

&lt;p&gt;The question is not whether freezing feels dramatic; the question is which named surface the packet is allowed to freeze. I freeze only the &lt;code&gt;freeze_scope&lt;/code&gt; listed in the file, and I do not freeze adjacent services because the dependency graph looks scary. Unfreeze requires the named &lt;code&gt;unfreeze_owner&lt;/code&gt;, a current packet, and a rollback file whose checksum still matches.&lt;/p&gt;

&lt;p&gt;If any of those three checks fail, production stays frozen, and I escalate instead of improvising a thaw. Is that slower than a hopeful restart on a red dashboard at two in the morning? Yes, and that slower path is the trade I want when the customer path is still burning.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Packet state&lt;/th&gt;
&lt;th&gt;Customer visible?&lt;/th&gt;
&lt;th&gt;Action I take&lt;/th&gt;
&lt;th&gt;Who may unfreeze&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;rollback_file missing&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;refuse the page, page the service owner&lt;/td&gt;
&lt;td&gt;nobody yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;budget still open&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;run first_commands only&lt;/td&gt;
&lt;td&gt;unfreeze_owner after budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;budget expired, cause unknown&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;freeze freeze_scope, escalate&lt;/td&gt;
&lt;td&gt;escalation_target after review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cause known, rollback_file present&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;optional freeze, then planned change&lt;/td&gt;
&lt;td&gt;unfreeze_owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;assistant drafted a patch&lt;/td&gt;
&lt;td&gt;either&lt;/td&gt;
&lt;td&gt;keep patch on scratch server&lt;/td&gt;
&lt;td&gt;never the assistant&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That table is the whole policy I will defend on the incident bridge without extra rows. If a situation is not a row in the table, I do not invent a fifth path while the alert is still firing. Improvisation belongs in the postmortem, and it does not belong in the unfreeze signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five-minute clock is a hard stop, not a suggestion
&lt;/h2&gt;

&lt;p&gt;I start a timer when the packet validates, and I do not extend it because the logs look interesting. Interesting logs after minute five belong in the escalation note, not in a longer local debug session. If the first commands did not name a cause, I freeze &lt;code&gt;freeze_scope&lt;/code&gt; and I stop typing.&lt;/p&gt;

&lt;p&gt;I also stop asking an assistant for one more hypothesis once the budget is gone. Extra hypotheses are cheap; extra mutations during freeze are how you write the next page. The clock exists to protect the freeze rule, not to make me look decisive on the bridge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where an isolated assistant belongs in this loop
&lt;/h2&gt;

&lt;p&gt;I still want help drafting the packet, especially when a new alert type shows up without a template. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I use MonkeyCode as a coding assistant with free model access and a free server option, kept off production. The product is in the loop only as a scratch drafting aid, never as the unfreeze owner.&lt;/p&gt;

&lt;p&gt;The useful loop is narrow, and I will not pretend it replaces the on-call human holding the pager. I paste a redacted alert description into the isolated session and ask for candidate read-only commands, then I reject anything that mutates state. The free server is a scratch place to run the validator below, not a jump host into the cluster that is paging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# proposed prompt I keep next to the packet (unexecuted example)
Redact customer data. Propose only read-only diagnostic commands.
Each command must name the binary, the resource, and the output I should save.
Do not propose kubectl apply, delete, rollout restart, or scale.
Do not propose unfreeze steps. Return a YAML fragment for first_commands only.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want a scratch box for that drafting loop, MonkeyCode's free server option is one isolated place I will try. I still copy the YAML back into the repository by hand, because the packet is an owned artifact, not a chat transcript. No generated fragment lands in git until I have read every command against the mutating-word list.&lt;/p&gt;

&lt;h2&gt;
  
  
  A validator I run before I accept the rotation
&lt;/h2&gt;

&lt;p&gt;This is a proposed Python check, not a production metric, and it fails closed when required fields are empty. Run it on the scratch server or on your laptop; do not run it as a cluster controller. I want a refuse-the-page exit code, not a dashboard that argues with the freeze owner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Proposed handoff packet validator. Example only; not production incident data.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;install pyyaml in the scratch environment, not on the frozen host&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;MUTATING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\b(apply|delete|restart|scale|cordon|drain|replace|patch)\b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;REQUIRED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alert_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rollback_file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;diagnostic_budget_seconds&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;freeze_scope&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unfreeze_owner&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;escalation_target&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_packet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;packet must be a mapping&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;spec&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;packet_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;oncall-handoff.packet.yaml&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pkt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_packet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;packet_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;REQUIRED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;pkt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;missing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;diagnostic_budget_seconds&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;diagnostic_budget_seconds must be an int between 1 and 900&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pkt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rollback_file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rollback_file does not exist: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rollback&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_bytes&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rollback_file sha256=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;first_commands&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;first_commands must contain at least one read-only command&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;MUTATING&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mutating first command: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pkt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unfreeze_owner&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;pkt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;escalation_target&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unfreeze_owner and escalation_target must be different people&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;REFUSE THE PAGE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;- &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;packet complete; freeze_scope may be applied by the named owner&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 validate_handoff_packet.py oncall-handoff.packet.yaml
&lt;span class="c"&gt;# expected for a complete packet: prints sha256 and a single success line&lt;/span&gt;
&lt;span class="c"&gt;# expected for a broken packet: prints REFUSE THE PAGE and exits 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the validator prints &lt;code&gt;REFUSE THE PAGE&lt;/code&gt;, I do not start first commands, and I do not ask an assistant to skip the check. The backup on-call can run the same script on their laptop and should get the same refusal. That repeatability is the only reason I bother checking the handoff packet in code at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Escalation is a packet transfer, not a feeling
&lt;/h2&gt;

&lt;p&gt;When the budget clock hits zero, I stop proposing new hypotheses and I freeze the named scope. I paste the packet, the command output hashes, and the validator result into the escalation note. Then I page &lt;code&gt;escalation_target&lt;/code&gt; and I stay on the call as a reader, not as a second unfreeze owner.&lt;/p&gt;

&lt;p&gt;What do I refuse to send during that handoff, even if the channel is already busy? I refuse secret-mixed chat logs, assistant-only patches, and freezes of services the packet never named. Escalation moves the packet, and it does not dump my anxiety onto the next engineer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations, and who should not copy this
&lt;/h2&gt;

&lt;p&gt;This workflow assumes you can name a rollback file before the page, which baby services and weekend prototypes often cannot. If you have no deploy artifact, no service owner, and no secondary, the validator will refuse every page, and that refusal is correct. Do not use this contract as a reason to skip paging humans who actually know the service.&lt;/p&gt;

&lt;p&gt;I also will not run assistant-drafted commands against production because a free server made the script look tidy. The isolated environment has no authority over freeze or unfreeze, and it should stay that way. Teams with a formal incident commander already owning freeze policy should not bolt this packet on as a second source of truth.&lt;/p&gt;

&lt;p&gt;If your alerts are purely informational, a five-minute budget is theater, and you should fix routing instead of filling YAML. This article is a proposed on-call contract, not a claim about latency numbers, token quotas, or model rankings I did not measure. Use it where ownership is real, and ignore it where the service cannot even name a rollback file.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>python</category>
      <category>productivity</category>
      <category>ai</category>
    </item>
    <item>
      <title>Keep Production Frozen Until the Runbook Names Blast Radius</title>
      <dc:creator>Emery Huang</dc:creator>
      <pubDate>Tue, 22 Sep 2026 15:03:57 +0000</pubDate>
      <link>https://dev.to/appcpp_9071/keep-production-frozen-until-the-runbook-names-blast-radius-1k51</link>
      <guid>https://dev.to/appcpp_9071/keep-production-frozen-until-the-runbook-names-blast-radius-1k51</guid>
      <description>&lt;p&gt;I will not unfreeze production until the runbook names blast radius and a hard command budget. A page without those two fields is just a loud ticket, and I treat it that way on every rotation. Can you point at the services that freeze with this alert, or are we still guessing in Slack? Guessing is how a careful read-only check becomes a write that nobody on the rotation can roll back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this runbook shape exists
&lt;/h2&gt;

&lt;p&gt;Most on-call docs I inherit list symptoms, then dump a wall of kubectl and curl without saying what they can break. They never say how many commands I may run before I must escalate to a human who owns the blast radius. Is that a runbook, or is it a wiki page wearing a pager costume at three in the morning? I want a contract that the alert, the first command, and the freeze rule can all satisfy without improvisation.&lt;/p&gt;

&lt;p&gt;The industry keeps handing us faster assistants and louder suggested patches, and tired engineers will paste those patches into prod. I still want drafting help, but I want it on a box that cannot touch production traffic or secrets. Does your current runbook even say where generated output is allowed to live during an incident? Mine says the draft lives offline, and the freeze stays up until a human signs the unfreeze.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four fields I require on every paging alert
&lt;/h2&gt;

&lt;p&gt;Every paging alert in the runbook gets four required fields before I will accept the next rotation. If a field is missing, I refuse the page and I keep the named services frozen. Why would I accept a rotation that cannot tell me what breaks when checkout-api pages at 3 a.m.? I would not, and I have started saying that out loud during the handoff, not after the page.&lt;/p&gt;

&lt;p&gt;Here is what I require, written as a list so reviewers cannot pretend they missed a field:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alert name and severity&lt;/strong&gt; — page, ticket, or ignore, with no silent severity drift between docs and the pager.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blast radius&lt;/strong&gt; — the services, queues, and data stores that freeze together with this alert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First command&lt;/strong&gt; — one read-only check with a timeout and a known-good output shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command budget&lt;/strong&gt; — how many commands I may run before escalation is mandatory and writes stay blocked.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A YAML contract I will actually merge
&lt;/h3&gt;

&lt;p&gt;The YAML I keep in git looks like this, and I reject pull requests that leave these keys empty.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# runbook/alerts/checkout-latency.yaml&lt;/span&gt;
&lt;span class="c1"&gt;# Proposed template — review before you attach it to a pager.&lt;/span&gt;
&lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CheckoutP99High&lt;/span&gt;
&lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;page&lt;/span&gt;
&lt;span class="na"&gt;customer_facing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;blast_radius&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;checkout-api&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;payments-worker&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;datastores&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;checkout-redis&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;queues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;checkout-jobs&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;first_command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;argv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-n"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checkout"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deploy,po,hpa"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;timeout_seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
  &lt;span class="na"&gt;mutates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;known_good&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;checkout-api&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pods&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Ready"&lt;/span&gt;
&lt;span class="na"&gt;command_budget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;max_commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;after_budget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;escalate&lt;/span&gt;
&lt;span class="na"&gt;escalation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-oncall&lt;/span&gt;
  &lt;span class="na"&gt;backup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payments-oncall&lt;/span&gt;
  &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#inc-checkout"&lt;/span&gt;
&lt;span class="na"&gt;freeze&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frozen&lt;/span&gt;
  &lt;span class="na"&gt;unfreeze_requires&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;blast_radius_named&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;budget_not_exceeded&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;human_signature&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;ai_may_unfreeze&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Does that look heavier than a markdown heading and a prayer at three in the morning? Good, because pager weight should hurt a little, or people will keep paging on pure noise. I would rather fight a noisy review than discover a missing blast radius while the pager is screaming. The file is the contract, and Slack is not a contract no matter how many emoji ACK the thread.&lt;/p&gt;

&lt;h2&gt;
  
  
  First commands that cannot mutate
&lt;/h2&gt;

&lt;p&gt;The first command is not a fix, and I will keep repeating that until the runbook files agree. The first command is a photograph of the blast radius, and it must finish inside the timeout. I want kubectl get, curl on a health path, or a read-only ping, never apply, never delete, never migrate. If your so-called first command needs a write flag, it is not first; it is an unfreeze request wearing a costume.&lt;/p&gt;

&lt;p&gt;Would I trust a generated snippet that restarts a deployment because p99 looks sad on a graph? Not before I have the photograph, and not before the freeze is already locked on the blast radius. The runbook must name the jumphost, and it must say the command is read-only without exception. If either line is missing, I keep production frozen and I ping the backup on-call instead of inventing a shell history.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Proposed read-only photograph. Do not point this article at production.&lt;/span&gt;
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; checkout get deploy,po,hpa &lt;span class="nt"&gt;-o&lt;/span&gt; wide
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; checkout get deploy checkout-api
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; checkout get po &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;checkout-api
curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 5 https://checkout.internal/healthz
redis-cli &lt;span class="nt"&gt;-h&lt;/span&gt; checkout-redis PING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Known-good output is part of the command
&lt;/h3&gt;

&lt;p&gt;I also want a known-good string in the YAML so a half-ready replica set cannot pass as healthy. The phrase all checkout-api pods Ready is boring, and boring is exactly what I want at 3 a.m. Fancy dashboards can wait until after the photograph matches, which is a rule I will not bargain. If the known-good line is missing, the first command is incomplete, and incomplete commands do not unlock an unfreeze.&lt;/p&gt;

&lt;h2&gt;
  
  
  Escalation after the command budget
&lt;/h2&gt;

&lt;p&gt;Three commands is my default budget, and I do not negotiate that number during the incident. After the photograph, one extra read, and one documented diagnostic, I escalate even if I almost see the bug. Have you noticed how almost is when people start typing writes from memory instead of paging backup? The budget exists to interrupt that reflex before it ships a worse outage on the same blast radius.&lt;/p&gt;

&lt;h3&gt;
  
  
  Role, backup, channel
&lt;/h3&gt;

&lt;p&gt;Escalation in this runbook is a role, a backup role, and a channel, not a feeling in the incident thread. I do not need a theatrical countdown; I need a named human who owns the blast radius when my budget is gone. If checkout-oncall does not answer, payments-oncall is already written down, and I do not invent a third person from the directory. The channel is #inc-checkout, and transcripts of the three commands go there before I page anyone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;After command 3:
  1. Post the photograph and the three command transcripts in #inc-checkout.
  2. Page checkout-oncall with blast_radius in the first line of the message.
  3. If no ACK, page payments-oncall with the same blast_radius line.
  4. Do not type a write. Freeze stays up.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Freeze and unfreeze are different signatures
&lt;/h2&gt;

&lt;p&gt;Freeze is the default when a customer-facing alert fires against a named blast radius in git. Unfreeze is a signature, and it is never automatic just because a dashboard turned green for thirty seconds. Can a model lift that freeze because it wrote a plausible patch in confident prose during the page? Not in this runbook, because ai_may_unfreeze false is a field the validator rejects if you delete it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the signer must restate
&lt;/h3&gt;

&lt;p&gt;I treat generated output as a draft comment on the incident thread, never as an unfreeze token for production. The human who signs must restate blast radius, confirm the command budget was not exceeded, and name the write. If they cannot restate those three things in one message, the freeze stays, and I am comfortable looking stubborn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drafting the runbook offline
&lt;/h2&gt;

&lt;p&gt;I still want help drafting these YAML files and the read-only first commands after a long shift. Writing them by hand when I am tired is exactly how blast radius and budget fields go missing. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I use MonkeyCode's free model access and free server option as an isolated drafting bench, not as a production jumphost.&lt;/p&gt;

&lt;p&gt;It is also not something that can unfreeze a service, and I will not give it that job. The drafting loop looks like this, and every step stays off the pager host on purpose. I want the model to propose fields I can reject, not a patch I can apply. That distinction is the whole reason the freeze exists in the YAML.&lt;/p&gt;

&lt;h3&gt;
  
  
  The offline loop
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Paste the alert name and the suspected services into the free server session.&lt;/li&gt;
&lt;li&gt;Ask the free model access to propose a blast_radius list and a read-only first_command.&lt;/li&gt;
&lt;li&gt;Reject any proposal that includes apply, delete, migrate, restart, or scale.&lt;/li&gt;
&lt;li&gt;Copy the surviving YAML into a local git branch and run the validator below.&lt;/li&gt;
&lt;li&gt;Only then open a runbook pull request for humans who own the blast radius.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Is this slower than letting a copilot patch live traffic during the page, while freeze is still down? Yes, and that slowness is the entire point of keeping the freeze in place on the blast radius. The free server is useful because it is not production, and I will not pretend it observes your metrics. If you draft runbooks the same way, that isolated bench is the only product role I am describing here.&lt;/p&gt;

&lt;p&gt;I will not claim token ceilings, model names, hardware, or how long a free plan lasts in this article. I have not verified those details from this desk, and outdated numbers help nobody on a rotation. Free model access and a free server are enough for this drafting loop as I described it. Anything beyond that should be checked on the project itself before you depend on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A validator you can run before you accept the rotation
&lt;/h2&gt;

&lt;p&gt;This is the artifact I actually want in CI, because reviewers miss empty keys at 5 p.m. on Friday. It does not page anyone, and it should never run against a production cluster or jumphost. It fails the build when the runbook is still a wiki page in disguise, which is the usual case. Can you merge a runbook that allows a model to unfreeze checkout-api through a missing field?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# validate-runbook.sh — proposed local check, not a production controller.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?usage:&lt;span class="p"&gt; validate-runbook.sh alerts/foo.yaml&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

need&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Eq&lt;/span&gt; &lt;span class="s2"&gt;"^&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"missing &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; in &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

need &lt;span class="s2"&gt;"alert"&lt;/span&gt;
need &lt;span class="s2"&gt;"severity"&lt;/span&gt;
need &lt;span class="s2"&gt;"blast_radius"&lt;/span&gt;
need &lt;span class="s2"&gt;"first_command"&lt;/span&gt;
need &lt;span class="s2"&gt;"command_budget"&lt;/span&gt;
need &lt;span class="s2"&gt;"escalation"&lt;/span&gt;
need &lt;span class="s2"&gt;"freeze"&lt;/span&gt;

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"mutates: false"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"first_command must set mutates: false"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"ai_may_unfreeze: false"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"AI must not be allowed to unfreeze"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Eiq&lt;/span&gt; &lt;span class="s2"&gt;"kubectl apply|kubectl delete|migrate|DROP TABLE"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"runbook contains a mutating command; refuse the rotation"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ok: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; names blast radius, a read-only first command, and a freeze AI cannot lift"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it on every file under runbook/alerts before you say yes to the next week of nights. The find pipeline below is the whole pre-rotation gate I want, and it belongs in CI rather than in a sticky note. If the script exits nonzero, I refuse the rotation, and I send the failure to the service owner. That refusal is cheaper than an unfreeze that nobody can explain later in the incident timeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find runbook/alerts &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.yaml'&lt;/span&gt; &lt;span class="nt"&gt;-print0&lt;/span&gt; | xargs &lt;span class="nt"&gt;-0&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt; ./validate-runbook.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decision table I keep next to the pager
&lt;/h2&gt;

&lt;p&gt;I keep this table next to the pager, and I treat a skipped row as a failed runbook, not a shortcut. Can a green graph override a missing blast_radius line in git during the first minute? It cannot, because the table says freeze and refuse, not eyeball the dashboard. I want the next on-call to make the same call I would make without a private Slack aside.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Freeze?&lt;/th&gt;
&lt;th&gt;Next action&lt;/th&gt;
&lt;th&gt;Unfreeze allowed?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alert missing &lt;code&gt;blast_radius&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Refuse the page; ping runbook owner&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First command would mutate&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Rewrite as read-only or escalate&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Command budget spent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Page primary, then backup&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model proposed a patch&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;File the draft offline only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human restates blast radius and the write&lt;/td&gt;
&lt;td&gt;Stays until signature&lt;/td&gt;
&lt;td&gt;Second on-call ACKs&lt;/td&gt;
&lt;td&gt;Yes, after ACK&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Would I skip a row because the dashboard looks fine for a minute during a noisy page? That is how silent data loss starts, so I do not skip rows even when graphs look friendly. The table is part of the runbook in git, not a slide I remember from last quarter's retro. If a situation is not in the table, I escalate rather than inventing a fifth kind of unfreeze.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not solve
&lt;/h2&gt;

&lt;p&gt;This workflow assumes you can freeze a blast radius without taking down the whole company, which is not always true. It also assumes git-hosted runbooks and a CI hook, which a two-person team on a shared cluster may not have. The validator is a grep net, not a policy engine, and a determined paste can still smuggle a write inside quotes. A drafting bench does not watch production metrics, and it does not replace an incident commander who can say no.&lt;/p&gt;

&lt;p&gt;I have not claimed latency numbers or model catalogs here because I cannot verify them from this desk today. If your compliance rules forbid sending alert names to any external drafting tool, skip the bench entirely. Keep the YAML rules anyway, because blast radius and command budget do not depend on a drafting product. The freeze still belongs to humans, and that part of the runbook should survive any tool change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;p&gt;Do not use this if you are already in an active incident and you still lack a freeze path. Do not use it if legal will not allow service lists in an external editor, even a free server. Do not use it as an excuse to page fewer people while you experiment with generated YAML on the jumphost. Those three cases need a human process first, not another file that pretends to be a runbook.&lt;/p&gt;

&lt;p&gt;If you cannot name blast radius, you do not get an unfreeze, and I will not pretend otherwise. If you cannot name a command budget, you do not get an unfreeze either, even when the graphs look calm. Everything else, including a helpful draft from an offline bench, waits until those two lines exist in git. That is the whole method, and it is intentionally boring on purpose for the people who get the page.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sre</category>
      <category>monitoring</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Refuse the Page Until the Runbook Names an Escalation Minute</title>
      <dc:creator>Emery Huang</dc:creator>
      <pubDate>Mon, 21 Sep 2026 13:16:09 +0000</pubDate>
      <link>https://dev.to/appcpp_9071/refuse-the-page-until-the-runbook-names-an-escalation-minute-36dl</link>
      <guid>https://dev.to/appcpp_9071/refuse-the-page-until-the-runbook-names-an-escalation-minute-36dl</guid>
      <description>&lt;p&gt;I refuse every new page until the runbook names an escalation minute, a first command, and a freeze owner. AI drafts can fill comments on a throwaway box, but they cannot sign those three fields for me. A document without a clock is a blog post, and I will not carry it overnight. Would you really take a rotation whose escalation path still says "use your judgment"?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the escalation minute comes first
&lt;/h2&gt;

&lt;p&gt;Alerts without a minute mark turn into private investigations that never escalate and never freeze writes. Night threads often show six people typing kubectl, and nobody can say when the next role should be paged. The runbook has to name that minute before I accept the rotation, not after the customer already felt the outage. If that escalation clock is missing from git, what exactly are you pretending to be on call for?&lt;/p&gt;

&lt;p&gt;I do not start from a chatbot essay about incident best practices, because essays do not page humans. I start from the alert name, then I write the first read-only command, then I write the minute, then I write who freezes. That strict order keeps generated text inside comments, where it cannot open a production shell tonight. Why would I hand a model the pager when it still cannot name the unfreeze owner?&lt;/p&gt;

&lt;h2&gt;
  
  
  The signed runbook shape I actually keep
&lt;/h2&gt;

&lt;p&gt;I keep one YAML file per service, and I treat unsigned keys as pages I will not accept. The file has to list alerts, first commands, escalation, and a freeze contract, because those are the only night-safe objects. Generated prose can live under a drafts key, and the validator must fail if that key is still unsigned. Does your repo still store the real procedure in a wiki that deploys independently of git?&lt;/p&gt;

&lt;p&gt;Here is a template, labeled as a proposal, not as a dump from some secret production cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proposal: service runbook, unsigned until owners.sign == true&lt;/span&gt;
&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payments-api&lt;/span&gt;
&lt;span class="na"&gt;owners&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sign&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;freeze&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sre-payments"&lt;/span&gt;
  &lt;span class="na"&gt;unfreeze&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sre-payments-lead"&lt;/span&gt;
&lt;span class="na"&gt;drafts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model-comment-only"&lt;/span&gt;
  &lt;span class="na"&gt;signed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;notes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;may&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;propose&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;here;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;it&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;must&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;never&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;become&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;command."&lt;/span&gt;
&lt;span class="na"&gt;alerts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PaymentsP99Latency&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;page&lt;/span&gt;
    &lt;span class="na"&gt;silence_minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
    &lt;span class="na"&gt;first_commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--context=readonly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;payments-api&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-o&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;wide"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--context=readonly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;logs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deploy/payments-api&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--tail=80"&lt;/span&gt;
    &lt;span class="na"&gt;escalation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;minute&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sre-payments-lead"&lt;/span&gt;
      &lt;span class="na"&gt;page_back_when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p99&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;300ms&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;10m&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AND&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;error_rate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1%"&lt;/span&gt;
    &lt;span class="na"&gt;freeze&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;on_clock_start&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;writes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deploy"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configmap"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rollout"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;unfreeze_evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page_back_when&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cleared&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;this&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;file"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fields I will not negotiate
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;alerts[].id&lt;/code&gt; must match the pager string, including punctuation, because fuzzy titles create the wrong first command.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;first_commands&lt;/code&gt; must be read-only invocations, and the wrapper below rejects anything that is not in this list.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;escalation.minute&lt;/code&gt; is an integer clock that starts at ack, not a feeling about how bad the graph looks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;page_back_when&lt;/code&gt; is a measurable condition, because "looks better" is how silent failures return at dawn.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;freeze.on_clock_start&lt;/code&gt; means writes stop when escalation starts, not after someone remembers the wiki.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;drafts.signed&lt;/code&gt; must stay false until a human copies accepted text into the signed keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  First commands are read-only or they do not ship
&lt;/h2&gt;

&lt;p&gt;I want the first five minutes to be evidence, not mutation, because mutation without a freeze owner is just another outage. Every first command in the file above is a get or a log read, and the allowlist wrapper will refuse a rollout. If a model suggests &lt;code&gt;kubectl delete&lt;/code&gt;, that line stays in &lt;code&gt;drafts.notes&lt;/code&gt; until a human rewrites it after unfreeze. Are you still pasting generated kubectl into production before the runbook even names the write blast?&lt;/p&gt;

&lt;p&gt;I keep a boring wrapper so tired humans cannot "just this once" run a stray apply.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# proposal: allowlist runner for first commands only&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;RUNBOOK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?runbook&lt;span class="p"&gt; yaml&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;ALERT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;:?alert&lt;span class="p"&gt; id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;shift &lt;/span&gt;2
&lt;span class="nv"&gt;CMD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

python3 - &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RUNBOOK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ALERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CMD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import sys, yaml
runbook, alert_id, cmd = sys.argv[1], sys.argv[2], sys.argv[3]
doc = yaml.safe_load(open(runbook))
if not doc.get("owners", {}).get("sign"):
    raise SystemExit("unsigned runbook: refuse to exec")
if doc.get("drafts", {}).get("signed") is True:
    raise SystemExit("drafts cannot be signed; copy text into alerts first")
alert = next(a for a in doc["alerts"] if a["id"] == alert_id)
if cmd not in alert["first_commands"]:
    raise SystemExit("command not in first_commands allowlist")
print("allow")
&lt;/span&gt;&lt;span class="no"&gt;PY

&lt;/span&gt;&lt;span class="c"&gt;# reached only after the allowlist printed allow&lt;/span&gt;
&lt;span class="c"&gt;# first_commands already pin --context=readonly&lt;/span&gt;
&lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$CMD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What this wrapper is allowed to do
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Read the signed YAML from git, not from a paste in Slack, before any shell starts.&lt;/li&gt;
&lt;li&gt;Refuse the whole night if &lt;code&gt;owners.sign&lt;/code&gt; is still false on the branch that can page.&lt;/li&gt;
&lt;li&gt;Compare the exact command string, because a nearby destructive cousin is still a miss.&lt;/li&gt;
&lt;li&gt;Leave writes to the freeze contract, because first-command territory ends when mutation begins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That wrapper is deliberately rude, and I actually like it that way during a noisy page. It will not run an unsigned file, and it will not run a command the alert did not name. If you need a write, you are no longer in first-command territory, and the freeze contract has to speak. Should a tired operator be able to skip the allowlist because a chatbot sounded confident?&lt;/p&gt;

&lt;h2&gt;
  
  
  Escalation is a clock with a page-back condition
&lt;/h2&gt;

&lt;p&gt;I write &lt;code&gt;escalation.minute: 10&lt;/code&gt; when ten minutes of read-only evidence is enough to stop being a lone hero. At that minute the named role is paged, freeze stays on, and I stop collecting extra screenshots for a thread nobody will read. The page-back condition has to be something a graph can answer, not a vibe about error budget remaining. Who is supposed to page you back if p99 only recovered because customer traffic already died?&lt;/p&gt;

&lt;h3&gt;
  
  
  Escalation ladder I paste into the service file
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Minute 0: ack the page, run only &lt;code&gt;first_commands&lt;/code&gt;, and start the freeze clock if the alert is a page.&lt;/li&gt;
&lt;li&gt;Minute 10: page &lt;code&gt;escalation.to&lt;/code&gt; with the command output attached, not with a novel about possible causes.&lt;/li&gt;
&lt;li&gt;Minute 20: if &lt;code&gt;page_back_when&lt;/code&gt; is still false, page the unfreeze owner, who is the only person allowed to lift writes.&lt;/li&gt;
&lt;li&gt;Any earlier recovery: leave freeze on until &lt;code&gt;page_back_when&lt;/code&gt; is recorded as cleared in git, not in chat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I want those minutes in YAML because chat messages vanish, and memories lie after adrenaline fades. The validator below fails the pull request when &lt;code&gt;minute&lt;/code&gt; is missing or when &lt;code&gt;page_back_when&lt;/code&gt; is an empty string. That failure is the whole point, because empty escalation is how teams pretend they have a runbook. Can you show me the minute in git, or only in a slide from last quarter?&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze starts when the clock starts
&lt;/h2&gt;

&lt;p&gt;I do not wait for a dramatic graph before I freeze writes, because drama arrives after the bad config already rolled. When the escalation clock starts, deploys, configmaps, and rollouts for that service are blocked until the unfreeze owner records evidence. Unfreeze is a different signature from freeze, and the YAML names two different people on purpose. If one person can freeze and unfreeze alone, what stops a panicked apply at minute nine?&lt;/p&gt;

&lt;p&gt;This is not a platform design essay; it is a git-enforced habit for a single service file. You can block writes with an admission policy, a deploy queue, or a locked pipeline without buying a new vendor. The runbook only has to name the rule so humans do not argue about it while the pager is screaming. Is your production freeze still just a Slack emoji from whoever happens to be awake?&lt;/p&gt;

&lt;h2&gt;
  
  
  Draft on a throwaway box, never on the bastion
&lt;/h2&gt;

&lt;p&gt;I will let a coding model propose YAML comments, but I will not let it propose a live shell on the production bastion. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I used MonkeyCode only as a drafting place, because free model access and a free server keep the skeleton off the pager host. The model fills &lt;code&gt;drafts.notes&lt;/code&gt;, I copy surviving lines into signed keys, and then I set &lt;code&gt;owners.sign&lt;/code&gt; on my laptop. If the draft still contains writes, why would I promote those lines into &lt;code&gt;first_commands&lt;/code&gt; tonight?&lt;/p&gt;

&lt;p&gt;The useful artifact remains the signed file in git, not the editor that proposed the comments. Remove the drafting box and the validator still protects you, which is how this workflow should work. Generated confidence is not an escalation path, and a free server is not a production replica. Do not paste a model rollout into the allowlist and call it a runbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  The validator I run in CI
&lt;/h2&gt;

&lt;p&gt;I run this as a proposal script in CI, and I have not baked it into a particular platform. It fails closed: unsigned owners, signed drafts, missing minutes, empty page-back, or a first command that looks like a write.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Proposal: fail CI unless the on-call runbook is night-safe.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;

&lt;span class="n"&gt;WRITE_HINTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(apply|delete|replace|patch|rollout\s+undo|scale|edit)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runbook invalid: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;owners&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owners&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sign&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owners.sign must be true before the file can page humans&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unfreeze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze and unfreeze owners must be named&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unfreeze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze and unfreeze must be different humans or roles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;drafts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drafts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;drafts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drafts.signed must stay false; copy text into alerts first&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;alerts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alerts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;at least one alert is required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;alert&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;aid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;missing&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;cmds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; has no first_commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;WRITE_HINTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; first command looks like a write: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;readonly&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; first command must pin a readonly context: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;esc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;escalation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;minute&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;minute&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; escalation.minute must be a positive integer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; escalation.to is empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page_back_when&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; page_back_when is empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;freeze&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;on_clock_start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; must freeze writes when the escalation clock starts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unfreeze_evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; unfreeze_evidence is empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runbook ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it like this, and keep the unsigned template out of the branch that can page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pyyaml
python3 validate_runbook.py payments-api.runbook.yaml
&lt;span class="c"&gt;# unsigned files must exit nonzero&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I want that nonzero exit more than I want a prettier wiki, because pretty docs do not block a rollout. If CI is green while &lt;code&gt;owners.sign&lt;/code&gt; is false, you built a linter that flatters the author. Should a pull request that cannot name a page-back condition be allowed to merge?&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations I will say out loud
&lt;/h2&gt;

&lt;p&gt;This file does not detect a bad metric, and it does not replace a real observability stack during an incident. The write hints are regular expressions, so a creative destructive command can still sneak through if you work at it. The YAML cannot freeze a cluster by itself, so you still need an admission policy or a locked pipeline. If your pager string does not match &lt;code&gt;alerts[].id&lt;/code&gt;, the allowlist will refuse, and that refusal is correct.&lt;/p&gt;

&lt;p&gt;It also does not make generated text trustworthy, which is the whole lesson hiding under the trend around AI-authored work. A model can outline comments quickly on a free server, and that speed is useful only because the validator remains hostile. I will not claim latency numbers, token quotas, or hardware details I cannot verify from here. If you need those numbers, measure them yourself on your own box before you change a night rotation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should skip this approach
&lt;/h2&gt;

&lt;p&gt;Skip it if you are a solo hobby project with no pager, because freeze owners and escalation minutes are ceremony you do not need. Skip it if your compliance team requires a vendor-signed runbook format that this YAML cannot satisfy. Skip it if you expected the drafting box to execute production commands, because that is the opposite of the allowlist. And skip it if you want a model to unfreeze writes, because I will not give any model that signature.&lt;/p&gt;

&lt;p&gt;I still want humans on the rotation, and I still want git to be ruder than Slack. Name the escalation minute, keep first commands read-only, and freeze writes when the clock starts. If you remember only one rule tonight, remember that unsigned drafts still do not page people. Would you actually merge this runbook file tonight, or are the escalation minutes still sitting blank?&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sre</category>
      <category>python</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Split Freeze From Unfreeze, and Map Each Alert to a Read-Only Command</title>
      <dc:creator>Emery Huang</dc:creator>
      <pubDate>Sun, 20 Sep 2026 11:23:04 +0000</pubDate>
      <link>https://dev.to/appcpp_9071/split-freeze-from-unfreeze-and-map-each-alert-to-a-read-only-command-94o</link>
      <guid>https://dev.to/appcpp_9071/split-freeze-from-unfreeze-and-map-each-alert-to-a-read-only-command-94o</guid>
      <description>&lt;p&gt;A paging channel without a freeze rule is just a group chat that happens to be loud. I will not treat an on-call runbook as live until every alert maps to a first command, an escalation tree names a human, and freeze is a different checklist from unfreeze. Can a model write fluent incident prose at two in the morning? Yes, and that is exactly why I keep it away from those fields.&lt;/p&gt;

&lt;p&gt;Quiet services still page someone, and that someone still needs a command that will not write. If you skip the human sections, the next page becomes a brainstorm with extra confidence. Have you watched a channel fill with suggested &lt;code&gt;kubectl&lt;/code&gt; lines while nobody names a freeze owner? I have, and I will not pretend a generated paragraph is a substitute for that owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why pretty runbooks still fail the first page
&lt;/h2&gt;

&lt;p&gt;Most half-finished runbooks fail in the same places, and none of those places are grammar. The alert does not name a service owner who can answer the phone. The first commands are write-heavy, so the first keystroke mutates production. The escalation path is a Slack handle that nobody pages after midnight. Freeze and unfreeze share one vague sentence, so nobody knows who may lift the gate.&lt;/p&gt;

&lt;p&gt;AI-assisted drafting makes those gaps prettier, not smaller. Why would a model know your after-hours phone tree? Why would it know which replica is allowed to restart without a freeze? It will invent confident steps unless you refuse to let it own those fields. The useful work is still naming owners, commands, and gates in daylight, then leaving the model a narrow drafting job.&lt;/p&gt;

&lt;p&gt;I still use a drafting box for symptom text, because writing that prose by hand is slow and easy to postpone. Disclosure: This article was prepared as part of MonkeyCode's product outreach. When I need a quiet place to draft non-authoritative sections, MonkeyCode's free model access and free server option are enough to generate candidate paragraphs that I then strip of any write action.&lt;/p&gt;

&lt;h2&gt;
  
  
  The human sections the pager actually needs
&lt;/h2&gt;

&lt;p&gt;Treat the runbook as a contract the pager can parse, not as a blog post about the service. I keep these sections in a checked-in file, and I reject drafts that leave any of them as prose-only comments.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Alert catalog&lt;/strong&gt; — each paging alert has an id, a service, a severity, and a human owner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First-command map&lt;/strong&gt; — each alert id points at read-only commands, with an explicit deny list for writes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalation tree&lt;/strong&gt; — primary, secondary, and manager, with a timeout that is a number, not a vibe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freeze checklist&lt;/strong&gt; — who may freeze writes, what evidence they record, and what stays allowed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unfreeze checklist&lt;/strong&gt; — a different owner path, a different evidence set, and a named blast check.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice freeze and unfreeze are not one section with a reversible verb. If they share a heading, people will unfreeze with the same confidence they used to freeze. Do you want that person to be a model that never saw your change window? I do not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Artifact: a runbook file the pager can fail closed
&lt;/h2&gt;

&lt;p&gt;This is a proposed schema, not a war story with fake metrics. Drop it in &lt;code&gt;runbooks/payments-api.yaml&lt;/code&gt; and refuse to page off a document that cannot load.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Proposed template — label every field human-owned or draft-only.&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oncall.example/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Runbook&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payments-api&lt;/span&gt;
  &lt;span class="na"&gt;owners&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary-oncall"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payments-tl"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;America/Los_Angeles&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;alerts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ALRT-PAY-429&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout_latency_p99&lt;/span&gt;
      &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;page&lt;/span&gt;
      &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payments-primary"&lt;/span&gt;
      &lt;span class="na"&gt;first_commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--context=prod-ro&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-n&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;payments&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;payments-api&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-o&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;wide"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--context=prod-ro&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-n&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;payments&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pods&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-l&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;app=payments-api"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-sS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://status.internal/payments/health&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;jq&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;
      &lt;span class="na"&gt;deny_commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;delete"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scale"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;apply"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;helm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;upgrade"&lt;/span&gt;
      &lt;span class="na"&gt;escalate_after_minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;escalation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payments-primary"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timeout_minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;10&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;secondary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payments-secondary"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timeout_minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;15&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;manager&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payments-tl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timeout_minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;20&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;never&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@here"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;random&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;chat"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;freeze&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;required_before_writes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;owner_role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incident-commander"&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alert_id"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_command_output_hash"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blast_radius_service_list"&lt;/span&gt;
    &lt;span class="na"&gt;allowed_during_freeze&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;capture"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;unfreeze&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;owner_role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incident-commander"&lt;/span&gt;
    &lt;span class="na"&gt;second_signer_role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service-owner"&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alert_id_cleared_or_mitigated"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;write_plan_printed"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rollback_command"&lt;/span&gt;
    &lt;span class="na"&gt;forbidden_if_missing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;second_signer"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rollback_command"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Would I let a model fill &lt;code&gt;deny_commands&lt;/code&gt; from memory of some other cluster? No, because that list is how you keep the first five minutes read-only. The model may draft the health-check narrative that sits under the YAML. It does not get to invent the owner role or the second signer.&lt;/p&gt;

&lt;h2&gt;
  
  
  A validator that fails before the rotation starts
&lt;/h2&gt;

&lt;p&gt;I want the runbook to fail in CI, not in the paging channel. The script below is a proposed check you can run locally; it does not talk to production and it does not claim a benchmark.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;validate_runbook.py — proposed structural check, not an incident replay.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pip install pyyaml&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;WRITE_TOKENS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scale&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apply&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upgrade&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;restart&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exec --&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;alerts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alerts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no alerts: a runbook that cannot match a page is a wiki page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;freeze&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;unfreeze&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unfreeze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;freeze&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;unfreeze&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze and unfreeze must both exist as mappings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner_role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner_role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;unfreeze&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner_role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Same person may hold both hats, but the checklists cannot be identical.
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;unfreeze&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze evidence must not equal unfreeze evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;unfreeze&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;second_signer_role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unfreeze needs a second signer role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;esc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;escalation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secondary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manager&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeout_minutes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;escalation.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; needs role and timeout_minutes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;alert&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;aid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;missing-id&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: alert has no human owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cmds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: no first commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;WRITE_TOKENS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: first command looks like a write: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deny_commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: deny_commands missing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; alerts)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage: python3 validate_runbook.py runbooks/payments-api.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it like this, on a laptop, before you accept a week of pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 validate_runbook.py runbooks/payments-api.yaml
&lt;span class="c"&gt;# expected: ok: runbooks/payments-api.yaml (1 alerts)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the validator prints &lt;code&gt;ok&lt;/code&gt; while &lt;code&gt;unfreeze.evidence&lt;/code&gt; still equals &lt;code&gt;freeze.evidence&lt;/code&gt;, I missed a check and I want you to fail the file by hand. Should a green script be enough to unfreeze production? No. It is only enough to prove the document is not empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  First commands stay read-only until freeze is named
&lt;/h2&gt;

&lt;p&gt;I keep a tiny wrapper so the tired person at the keyboard cannot “just scale it” from muscle memory. This is a proposed shell gate, not a cluster agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# firstcmd.sh — proposed read-only wrapper. Do not point this at a write kubecontext.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;ALERT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?alert&lt;span class="p"&gt; id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;RUNBOOK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;runbooks&lt;/span&gt;&lt;span class="p"&gt;/payments-api.yaml&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;CONTEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KUBE_RO_CONTEXT&lt;/span&gt;:?set&lt;span class="p"&gt; KUBE_RO_CONTEXT to a read-only context&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONTEXT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;prod-w&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONTEXT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;writable&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"refusing: context looks writable: &lt;/span&gt;&lt;span class="nv"&gt;$CONTEXT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"alert=&lt;/span&gt;&lt;span class="nv"&gt;$ALERT_ID&lt;/span&gt;&lt;span class="s2"&gt; runbook=&lt;/span&gt;&lt;span class="nv"&gt;$RUNBOOK&lt;/span&gt;&lt;span class="s2"&gt; context=&lt;/span&gt;&lt;span class="nv"&gt;$CONTEXT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"freeze_named=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FREEZE_NAMED&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;no&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FREEZE_NAMED&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;no&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"yes"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"writes remain blocked until FREEZE_NAMED=yes and the freeze checklist is signed"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What belongs in &lt;code&gt;first_commands&lt;/code&gt;? Gets, describes, logs with a tail limit, and health URLs that cannot mutate. What does not belong? Anything that restarts, scales, applies, deletes, or opens an interactive shell on a prod pod. If the model drafts a “quick restart” as step one, that is a defect in the draft, not a shortcut.&lt;/p&gt;

&lt;h2&gt;
  
  
  Escalation is a tree with minutes, not a mention
&lt;/h2&gt;

&lt;p&gt;I want numbers on the tree because “ping secondary if needed” is how pages rot in a thread. Primary has ten minutes. Secondary has fifteen. Manager has twenty. After that, you are not brainstorming; you are late.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not escalate to &lt;code&gt;@here&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Do not escalate to a model chat that cannot take the phone.&lt;/li&gt;
&lt;li&gt;Do not list a person who is already the freeze owner unless a second signer still exists for unfreeze.&lt;/li&gt;
&lt;li&gt;Do record the time you moved to the next role, even if the next role is you with a different hat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your team is two people, the tree can still exist. It just becomes honest about how thin the bench is. Is a thin bench a reason to skip the tree? It is a reason to write the tree larger than the bench, then staff it later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze and unfreeze as two checklists
&lt;/h2&gt;

&lt;p&gt;Use a decision table during the incident, on paper or in the channel topic. The table is the artifact that keeps freeze from becoming a mood.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Freeze&lt;/th&gt;
&lt;th&gt;Unfreeze&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who signs?&lt;/td&gt;
&lt;td&gt;Incident commander&lt;/td&gt;
&lt;td&gt;Commander plus service owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What evidence?&lt;/td&gt;
&lt;td&gt;Alert id, first-command output, blast list&lt;/td&gt;
&lt;td&gt;Alert mitigated, printed write plan, rollback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writes allowed?&lt;/td&gt;
&lt;td&gt;No, except documented break-glass&lt;/td&gt;
&lt;td&gt;Only the printed plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model output allowed as proof?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can the same human hold both hats?&lt;/td&gt;
&lt;td&gt;Yes, if the evidence sets differ&lt;/td&gt;
&lt;td&gt;Only with a second signer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I will freeze as soon as a write is tempting and the blast list is still unnamed. I will not unfreeze because a generated summary says the error rate “looks better.” Looks better than what baseline, captured by which command, against which alert id? If you cannot answer that, the freeze stays.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a drafting model is allowed, and where it is not
&lt;/h2&gt;

&lt;p&gt;Let the model propose symptom text, dashboard links you already host, and questions to ask the primary. Do not let it propose owners, phone numbers, deny lists, or unfreeze signers. Those fields are boring on purpose, because boring fields are the ones people skip when a page is loud.&lt;/p&gt;

&lt;p&gt;If you paste a generated runbook straight into the wiki, you have a document that reads finished and behaves empty. That is the failure mode I am trying to make expensive. The validator is cheap. The second signer on unfreeze is cheaper than a write that nobody can roll back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations, and who should not use this
&lt;/h2&gt;

&lt;p&gt;This approach assumes you already have paging, a read-only kube context or equivalent, and a place to store YAML that humans review. It does not measure latency, does not replay traffic, and does not prove the first command is the right command for a novel failure. The schema will happily accept a wrong-but-read-only &lt;code&gt;curl&lt;/code&gt; if you typed it.&lt;/p&gt;

&lt;p&gt;Do not use this if you are a solo hobby project with no pager and no production writes worth freezing. Do not use it as a reason to skip a real staging reproduction when you have time. Do not use a drafting server as the system of record for phone trees or credentials. Do not treat free model access as an on-call teammate; it cannot take the escalation slot, and it cannot sign unfreeze.&lt;/p&gt;

&lt;p&gt;If your incident process already requires two-person review for every production write, you may only need the alert-to-command map and the validator. That is fine. The point is not to collect headings. The point is to make the first five minutes of a page boring enough that nobody invents a write.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sre</category>
      <category>productivity</category>
      <category>learning</category>
    </item>
    <item>
      <title>Fill Four On-Call Lists Before You Accept the Rotation</title>
      <dc:creator>Emery Huang</dc:creator>
      <pubDate>Fri, 18 Sep 2026 07:48:12 +0000</pubDate>
      <link>https://dev.to/appcpp_9071/fill-four-on-call-lists-before-you-accept-the-rotation-2924</link>
      <guid>https://dev.to/appcpp_9071/fill-four-on-call-lists-before-you-accept-the-rotation-2924</guid>
      <description>&lt;p&gt;On-call is not a conversation with a model while the pager is still firing at you. A rotation is ready only when four lists exist: the alert catalog, the first commands, the escalation packet, and the freeze rule. I will not accept a week of pages that depend on a chat transcript, no matter how fluent that transcript looks. Would you really open a blank prompt at three in the morning and call that a procedure?&lt;/p&gt;

&lt;p&gt;Narrative runbooks feel complete in daylight, then collapse when two alerts land together. People scroll, argue about meaning, and invent write commands that nobody reviewed against a named surface. I want files I can grep, scripts that only print state, a clock that forces escalation, and a freeze line that does not depend on mood. Does a five-page story help you when the ID on the page is not even in the catalog?&lt;/p&gt;

&lt;p&gt;The useful question this week is not whether assistants can draft English. The question is whether you will call that draft an operating procedure before it survives a checker. I let a model fill comments. I reject the file until the four lists validate. That is the method, and chat is none of the lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four lists, named on purpose
&lt;/h2&gt;

&lt;p&gt;I keep the on-call file as four lists because each list answers a different failure. The catalog answers "what is this page." First commands answer "what do we look at before we speak." Escalation answers "who gets the evidence, and when." Freeze answers "which writes are illegal until a human writes a sentence." If any list is missing, you are improvising, and improvisation is not a runbook.&lt;/p&gt;

&lt;p&gt;Here is the order I actually enforce when a rotation starts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every page ID in the last month has a catalog row, including a "not this" note.&lt;/li&gt;
&lt;li&gt;Every catalog row points at a read-only first-command script that exists on disk.&lt;/li&gt;
&lt;li&gt;Escalation names roles, minute bounds, and the three fields in the packet.&lt;/li&gt;
&lt;li&gt;Unknown IDs freeze writes immediately; unfreeze is a filled sentence, not a vibe.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Would a generated paragraph satisfy those four gates? Only the parts that survive the YAML and the checker below. Everything else stays a comment in a draft branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  List 1: the alert catalog is a table, not a blog
&lt;/h2&gt;

&lt;p&gt;Every page needs a stable ID, a meaning, a severity, and a "not this" note. If the ID is missing from the catalog, the incident is already in freeze, and nobody invents a new class on the call. New classes are born in daylight, with a script on disk and a minute bound that a human typed. Why would you let a model mint an ID while customers are already waiting?&lt;/p&gt;

&lt;h3&gt;
  
  
  Catalog shape I keep in git
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# runbook/alerts.yaml — labeled template, not a live service file&lt;/span&gt;
&lt;span class="na"&gt;alerts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checkout.p95.latency"&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sev2&lt;/span&gt;
    &lt;span class="na"&gt;means&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p95&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;checkout&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;latency&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exceeded&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;burn&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;threshold&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;minutes"&lt;/span&gt;
    &lt;span class="na"&gt;not_this&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;batch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;jobs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reporting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;warehouse"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;synthetic&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;probes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;failing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;one&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;region"&lt;/span&gt;
    &lt;span class="na"&gt;first_command_set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency-read"&lt;/span&gt;
    &lt;span class="na"&gt;escalate_after_minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
    &lt;span class="na"&gt;write_allowed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payments.auth.5xx"&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sev1&lt;/span&gt;
    &lt;span class="na"&gt;means&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;auth&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;5xx&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;crossed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;budget&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;page"&lt;/span&gt;
    &lt;span class="na"&gt;not_this&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;timeouts&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;single&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ISP"&lt;/span&gt;
    &lt;span class="na"&gt;first_command_set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payments-read"&lt;/span&gt;
    &lt;span class="na"&gt;escalate_after_minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;
    &lt;span class="na"&gt;write_allowed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;write_allowed&lt;/code&gt; starts false on every row. A catalog row that defaults to writes is a loaded gun with the safety off. Unknown IDs do not get a row invented during the page; they get freeze, then escalation, then a daylight edit. I want the on-call person to grep, not to negotiate meaning in a thread.&lt;/p&gt;

&lt;h2&gt;
  
  
  List 2: first commands print state and then stop
&lt;/h2&gt;

&lt;p&gt;First commands print state. They do not restart, scale, mute, deploy, or patch. I want the output pasted into the incident doc before anyone discusses a mutation, because the paste is the only evidence the next human can trust. If the command is not in the set named by the alert, it does not run, even when someone swears it is "just a quick look." Would you skip the paste step because the output looked obvious?&lt;/p&gt;

&lt;h3&gt;
  
  
  Read-only command sets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# first-commands/latency-read.sh&lt;/span&gt;
&lt;span class="c"&gt;# labeled example: swap in your own read endpoints after review&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"UTC &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"alert_id=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ALERT_ID&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;unset&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS_URL&lt;/span&gt;&lt;span class="s2"&gt;/checkout/p95"&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 4096
&lt;span class="nb"&gt;echo
&lt;/span&gt;kubectl get deploy checkout &lt;span class="nt"&gt;-o&lt;/span&gt; wide
kubectl get hpa checkout &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'1,80p'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# first-commands/payments-read.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"UTC &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS_URL&lt;/span&gt;&lt;span class="s2"&gt;/payments/auth/5xx"&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 4096
&lt;span class="nb"&gt;echo
&lt;/span&gt;kubectl logs deploy/payments-auth &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10m &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'5[0-9]{2}|timeout'&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 40
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I keep these scripts tiny on purpose. A first-command file that contains &lt;code&gt;apply&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;restart&lt;/code&gt;, or &lt;code&gt;scale&lt;/code&gt; is not a first command; it is a change that snuck in through naming. The checker later treats those strings as failures, because I have watched "just restart it" show up in a reconnaissance folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  List 3: escalation is a clock plus a packet
&lt;/h2&gt;

&lt;p&gt;Escalation is not "ping someone if you feel stuck." It is a minute bound, a named role, and a packet of evidence that another human can read without joining your head. The packet is the alert ID, the first-command output path, and the freeze state. If any of those three is missing, you are not escalating; you are venting into a channel. What happens when the named person is asleep and the runbook only lists a nickname?&lt;/p&gt;

&lt;h3&gt;
  
  
  Escalation decision table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Clock&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Who you page&lt;/th&gt;
&lt;th&gt;Packet you must send&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;T+0&lt;/td&gt;
&lt;td&gt;Page fires&lt;/td&gt;
&lt;td&gt;primary on-call&lt;/td&gt;
&lt;td&gt;alert ID, catalog hit or miss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Catalog minute bound&lt;/td&gt;
&lt;td&gt;First commands cannot classify&lt;/td&gt;
&lt;td&gt;secondary and service owner&lt;/td&gt;
&lt;td&gt;output path, freeze=on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any time&lt;/td&gt;
&lt;td&gt;Alert ID missing from catalog&lt;/td&gt;
&lt;td&gt;incident commander&lt;/td&gt;
&lt;td&gt;freeze=on, no writes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After classify&lt;/td&gt;
&lt;td&gt;A write is proposed&lt;/td&gt;
&lt;td&gt;owner of the write surface&lt;/td&gt;
&lt;td&gt;command text and revert line&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I keep roles as functions, not heroic names. Put the rotation source next to the role so the packet can move when the human changes. A Slack handle with no rotation pointer is a hope, and hope is not an escalation path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# runbook/escalation.yaml — labeled template&lt;/span&gt;
&lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pagerduty://checkout-primary"&lt;/span&gt;
  &lt;span class="na"&gt;secondary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pagerduty://checkout-secondary"&lt;/span&gt;
  &lt;span class="na"&gt;service_owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pagerduty://checkout-owner"&lt;/span&gt;
  &lt;span class="na"&gt;incident_commander&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pagerduty://ic-weekday"&lt;/span&gt;
&lt;span class="na"&gt;freeze_on_unknown_alert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;require_packet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;alert_id&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;first_command_output_uri&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;freeze_state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  List 4: freeze unknown pages; unfreeze with a sentence
&lt;/h2&gt;

&lt;p&gt;Here is the freeze rule I will defend in a postmortem. If the alert ID is absent from the catalog, freeze writes immediately and escalate. If the first-command set cannot classify the symptom inside the catalog minute bound, freeze writes and escalate. Unfreeze requires the catalog ID, a classified symptom, a named write owner, and an explicit unfreeze block in the incident doc. Is that slower than a heroic restart? Yes, and that delay is the control.&lt;/p&gt;

&lt;p&gt;Unfreeze is a sentence, not a feeling in the room. I write it like this, and I refuse anything shorter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;UNFREEZE&lt;/span&gt; &lt;span class="err"&gt;checkout&lt;/span&gt; &lt;span class="err"&gt;writes&lt;/span&gt;
&lt;span class="py"&gt;alert_id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;checkout.p95.latency&lt;/span&gt;
&lt;span class="py"&gt;classified_as&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;db_pool_saturation&lt;/span&gt;
&lt;span class="py"&gt;write_owner&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;checkout-owner&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;increase checkout pool from 20 to 40&lt;/span&gt;
&lt;span class="py"&gt;revert&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;set pool back to 20&lt;/span&gt;
&lt;span class="py"&gt;expires&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;20m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you cannot fill those fields, you stay frozen. The revert line is not decoration; it is the only way the next on-call person knows how to undo your write without asking the chat. I have no interest in an unfreeze that cannot be read after the call ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Artifact: fail the runbook before the rotation starts
&lt;/h2&gt;

&lt;p&gt;I do not argue about completeness in the war room. A checker fails the tree on a laptop, or in CI, before anyone accepts the pager. The script below is a proposal you can run against the YAML shapes above. It does not prove production is healthy. It proves the four lists are present, writes are not smuggled into first commands, and unknown alerts are set to freeze.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;runbook_check.py — unlabeled fields fail the runbook.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;install pyyaml before running this checker&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;REQUIRED_ALERT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;means&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not_this&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_command_set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;escalate_after_minutes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;write_allowed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;REQUIRED_ROLES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secondary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service_owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incident_commander&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;REQUIRED_PACKET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alert_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_command_output_uri&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze_state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;WRITE_MARKERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl delete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl apply&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;restart&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scale &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAIL: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;alerts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runbook/alerts.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;esc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runbook/escalation.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alerts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alerts.yaml missing alerts list&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;REQUIRED_ALERT&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alert &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt; missing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duplicate alert id &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;write_allowed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; must not default write_allowed to true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first-commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;first_command_set&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.sh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing first-command script &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;WRITE_MARKERS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; looks like a write; first commands must be read-only&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;escalate_after_minutes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; escalate_after_minutes must be a positive int&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;roles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;roles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;escalation.yaml missing roles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;missing_roles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;REQUIRED_ROLES&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;missing_roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing roles &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missing_roles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze_on_unknown_alert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze_on_unknown_alert must be true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;require_packet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;REQUIRED_PACKET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;require_packet must be &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REQUIRED_PACKET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OK: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; alerts, freeze-on-unknown enabled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it like this on a toy tree before you point it at the real rotation files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 runbook_check.py &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;# expected on a complete tree:&lt;/span&gt;
&lt;span class="c"&gt;# OK: N alerts, freeze-on-unknown enabled&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the checker fails, the rotation does not start. That sounds harsh until you remember the alternative: a page whose first command does not exist, or a freeze rule that lives only in someone's memory. I would rather fail CI on Tuesday than invent YAML while the pager is already open.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a page should actually move
&lt;/h2&gt;

&lt;p&gt;When the page fires, I want a boring sequence, not a clever one. The primary copies the alert ID, greps the catalog, and runs only the named first-command script. Output goes into the incident doc before any theory gets typed. Classification either happens inside the minute bound or it does not; there is no third state called "we are pretty sure."&lt;/p&gt;

&lt;p&gt;If the ID is missing, freeze is already on, and the packet goes to the incident commander with freeze=on and no writes. If classification fails the clock, freeze stays on and secondary plus owner get the output path. Only after those gates does anyone draft a write, and that write still needs an unfreeze block with a revert line. Would you skip a gate because the room feels aligned? The gates exist for the room that feels aligned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a draft assistant fits, and where it does not
&lt;/h2&gt;

&lt;p&gt;I will let a model propose &lt;code&gt;means&lt;/code&gt; and &lt;code&gt;not_this&lt;/code&gt; text from an exported list of alert names. I will not let it invent IDs, minute bounds, role URIs, or write commands. The draft is a comment. The YAML is the contract. That split is how you use an assistant without pretending the assistant took the page.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. Comment drafts for those catalog sentences can sit in MonkeyCode, which has free model access and a free server option, and only fields that survive review get copied into &lt;code&gt;alerts.yaml&lt;/code&gt;. The checker does not care where the English came from. The free server is a draft bench, not an incident cluster, and generated &lt;code&gt;kubectl&lt;/code&gt; that never sat in &lt;code&gt;first-commands/&lt;/code&gt; stays unread.&lt;/p&gt;

&lt;p&gt;Would I point any assistant at a production kubeconfig to "save time"? No. Time saved on typing is worthless if the first write has no revert line. Keep the model on comments. Keep the pager on lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations you should not hand-wave
&lt;/h2&gt;

&lt;p&gt;This workflow assumes you already have paging, a place to paste output, and someone who can own a write. The checker is syntactic; it cannot prove a &lt;code&gt;curl&lt;/code&gt; target is actually read-only, and it cannot prove a role URI reaches a human who is awake. Minute bounds are guesses until you rehearse them with a game-day page. YAML that passes CI can still describe the wrong service, or a "not this" note that was true last quarter and is false tonight.&lt;/p&gt;

&lt;p&gt;Assistants hallucinate confident &lt;code&gt;not_this&lt;/code&gt; notes. If you paste those notes without a human who has taken that page, you will freeze the wrong subsystem or unfreeze too early. I treat every generated sentence as untrusted until a person who has carried the pager edits it. The four lists reduce improvisation. They do not replace judgment, and they do not replace a rehearsal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this shape
&lt;/h2&gt;

&lt;p&gt;Do not use this shape if you have no pager, no secondary, and no incident doc. Do not use it to justify running model output against production because the sentences sounded operational. Do not use a shared free server as the system of record for secrets, kubeconfigs, or customer data. Tiny internal tools with one operator may prefer a single shell script over four YAML files, and that is fine.&lt;/p&gt;

&lt;p&gt;If your culture rewards the fastest unmute, this freeze rule will feel like friction. That friction is the point of the runbook. I would rather explain a delayed write than explain an untracked restart that nobody can revert. The four lists are the runbook. Everything else is conversation, and conversation does not unfreeze writes.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sre</category>
      <category>productivity</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Will Not Unfreeze Prod Until the Runbook Hash Matches the Page</title>
      <dc:creator>Emery Huang</dc:creator>
      <pubDate>Thu, 17 Sep 2026 05:50:16 +0000</pubDate>
      <link>https://dev.to/appcpp_9071/i-will-not-unfreeze-prod-until-the-runbook-hash-matches-the-page-4p89</link>
      <guid>https://dev.to/appcpp_9071/i-will-not-unfreeze-prod-until-the-runbook-hash-matches-the-page-4p89</guid>
      <description>&lt;p&gt;I will not let an assistant invent the incident path while the pager is still screaming. A usable on-call runbook names the alert class, the first command, the escalation owner, and the freeze hash before anyone types. Chat logs are not runbooks, and generated shells are not change tickets, no matter how confident they sound. If those four fields are missing, I keep writes frozen and I keep the assistant on a scratch box.&lt;/p&gt;

&lt;p&gt;Why am I this stubborn about a document that looks like YAML? Because the last seven days of AI-coding talk keep confusing fluency with ownership, and on-call work punishes that mix. A model can draft a plausible restart in twenty seconds, then miss the replica that actually holds the lock. Would you rather argue with a chat transcript at 03:00, or execute a hashed runbook you already signed?&lt;/p&gt;

&lt;h2&gt;
  
  
  The contract I actually page against
&lt;/h2&gt;

&lt;p&gt;I treat every page as a four-field contract, not as a brainstorming session with extra urgency. The contract is boring on purpose, and boring is what I want when the graph is red. If a field cannot be filled from the alert payload and the repo, I do not improvise a fifth field in Slack.&lt;/p&gt;

&lt;p&gt;Here is the schema I keep in &lt;code&gt;runbooks/&amp;lt;service&amp;gt;.yml&lt;/code&gt;. Copy it, then refuse to run writes until &lt;code&gt;freeze.hash&lt;/code&gt; matches the file on disk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# runbooks/payments-api.yml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oncall.example.com/v1&lt;/span&gt;
&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payments-api&lt;/span&gt;
&lt;span class="na"&gt;owner_oncall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payments-primary&lt;/span&gt;
&lt;span class="na"&gt;escalation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;t_plus_15m&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payments-secondary&lt;/span&gt;
  &lt;span class="na"&gt;t_plus_30m&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payments-manager&lt;/span&gt;
  &lt;span class="na"&gt;t_plus_45m&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;incident-commander&lt;/span&gt;
&lt;span class="na"&gt;alert_classes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PAYMENTS_P99_LATENCY&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;page&lt;/span&gt;
    &lt;span class="na"&gt;observe_commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oc1&lt;/span&gt;
        &lt;span class="na"&gt;argv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-n"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payments"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deploy"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payments-api"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-o"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wide"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oc2&lt;/span&gt;
        &lt;span class="na"&gt;argv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-n"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payments"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;top"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pod"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-l"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app=payments-api"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;write_commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;   &lt;span class="c1"&gt;# stays empty until unfreeze&lt;/span&gt;
&lt;span class="na"&gt;freeze&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frozen&lt;/span&gt;
  &lt;span class="na"&gt;hash_of&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;runbooks/payments-api.yml&lt;/span&gt;
  &lt;span class="na"&gt;require&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;named_blast_radius&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;matching_git_sha&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;human_signature&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what is missing on purpose: there is no prompt, no vibe, and no "try whatever the model suggested." Observe commands are argv arrays, not English. Write commands start empty. The freeze block is data, not a pep talk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field 1: alert class, not a paragraph
&lt;/h2&gt;

&lt;p&gt;I bind the pager to a stable &lt;code&gt;alert_class&lt;/code&gt; id, because humans rename dashboards and models paraphrase titles. &lt;code&gt;PAYMENTS_P99_LATENCY&lt;/code&gt; is allowed to wake me; "the site feels slow" is not. If the alert cannot map onto one id in the YAML, I treat it as observe-only noise until a human classifies it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page-worthy classes get &lt;code&gt;severity: page&lt;/code&gt; and a short observe list.&lt;/li&gt;
&lt;li&gt;Ticket-worthy classes get &lt;code&gt;severity: ticket&lt;/code&gt; and never unfreeze writes.&lt;/li&gt;
&lt;li&gt;Unknown classes inherit freeze and a required escalation note.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Can your current alert rule print that id into the notification body? If it cannot, the runbook is already lying before you open a terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# example Alertmanager annotation, not a prompt
description: class=PAYMENTS_P99_LATENCY ns=payments deploy=payments-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Field 2: first commands stay read-only
&lt;/h2&gt;

&lt;p&gt;My first commands are inventory, not remediation. I want the replica count, the ready condition, the recent events, and the error budget burn, in that order. Anything that mutates pods, feature flags, or DNS waits behind the freeze hash. Have you ever watched a generated one-liner delete the healthy deployment because the label selector was almost right?&lt;/p&gt;

&lt;p&gt;I keep a tiny allowlist checker next to the YAML so the laptop refuses clever extra flags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tools/check_first_commands.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;

&lt;span class="n"&gt;ALLOWED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;describe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;logs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;top&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api-resources&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dig&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+short&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-sS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/dev/null&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alert_classes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;observe_commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;argv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;argv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;bin_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bin_&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALLOWED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: binary &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bin_&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not in observe allowlist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apply&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;patch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scale&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
                &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: mutating token in observe argv &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;write_commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: write_commands must be empty while frozen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frozen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze.state must start as frozen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; observe-only and frozen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it before the page, not during it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 tools/check_first_commands.py runbooks/payments-api.yml
&lt;span class="nb"&gt;sha256sum &lt;/span&gt;runbooks/payments-api.yml | &lt;span class="nb"&gt;tee &lt;/span&gt;runbooks/payments-api.yml.sha256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Field 3: escalation is a clock, not a group chat
&lt;/h2&gt;

&lt;p&gt;I write escalation as timestamps against the first page, because "ask in Slack" is how incidents grow extra owners and zero decisions. Fifteen minutes of observe with no named blast radius promotes to secondary. Thirty minutes promotes to the manager. Forty-five minutes asks for an incident commander who is not also typing kubectl. Who is allowed to unfreeze if the primary is the person who wrote the bad deploy?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;T+0 to T+15: primary runs observe commands only, pastes command ids into the incident doc.&lt;/li&gt;
&lt;li&gt;T+15: secondary joins, repeats the same observe ids, and challenges any extra argv.&lt;/li&gt;
&lt;li&gt;T+30: manager confirms customer impact and names the blast radius in one sentence.&lt;/li&gt;
&lt;li&gt;T+45: commander owns communication; primary still cannot unfreeze without the hash check.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I do not escalate because I feel nervous. I escalate because the clock in the runbook elapsed and a field is still blank.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field 4: freeze hash, then a signed unfreeze
&lt;/h2&gt;

&lt;p&gt;This is the rule that keeps assistants useful instead of dangerous. The freeze hash is &lt;code&gt;sha256&lt;/code&gt; of the runbook file at the git sha I am paging from. Unfreeze is a separate artifact, not a vibes-based "looks good." If the assistant rewrites the YAML during the call, the hash breaks, and writes stay frozen. Is that annoying? Yes. Is it worse than a silent &lt;code&gt;kubectl apply&lt;/code&gt; from a regenerated plan? Not even close.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# tools/unfreeze.sh — still a proposal until a human runs it on a signed laptop&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;RB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?runbook&lt;span class="p"&gt; yml&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;INCIDENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;:?incident&lt;span class="p"&gt; id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;BLAST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;3&lt;/span&gt;:?blast&lt;span class="p"&gt; radius sentence&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;SIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;:?path&lt;span class="p"&gt; to signature file&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;expected&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sha256"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$expected&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$actual&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"hash mismatch: freeze holds"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SIG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"no human signature"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;3&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BLAST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"blast radius unnamed"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;4&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'unfrozen\nincident=%s\nblast=%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INCIDENT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BLAST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.unfreeze"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"writes allowed only for commands listed after this file exists"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I still require write commands to be appended as argv arrays after unfreeze, never as free text. The assistant may propose a patch on a scratch clone. It does not get to flip &lt;code&gt;freeze.state&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a free coding assistant actually helps
&lt;/h2&gt;

&lt;p&gt;I will use a coding assistant to draft the YAML, generate the allowlist tests, and argue with my own escalation clock, but only on a throwaway box. MonkeyCode is relevant here because free model access and a free server option give me that scratch loop without borrowing a production jumphost. Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;I paste the schema, the checker, and a redacted alert sample, then I ask for missing observe commands that still fit the allowlist. I do not paste kubeconfigs, customer payloads, or unfreeze signatures. If the model invents a &lt;code&gt;rollout restart&lt;/code&gt;, the checker fails, and that failure is the lesson. The free server is for running &lt;code&gt;check_first_commands.py&lt;/code&gt;, not for talking to prod.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# labeled as a proposal I would run on a scratch server, not on prod
python3 tools/check_first_commands.py runbooks/payments-api.yml
python3 -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))" runbooks/payments-api.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Would I let the same session open a tunnel to the cluster? No. The runbook is the contract; the assistant is a typist with a linter.&lt;/p&gt;

&lt;h2&gt;
  
  
  A dry-run I actually practice
&lt;/h2&gt;

&lt;p&gt;I rehearse with a fake page so the first real page is not also the first parse of the YAML. The drill is short, scripted, and mean about extra commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;INCIDENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;inc-drill-2026-09-17
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ALERT_CLASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PAYMENTS_P99_LATENCY
python3 tools/check_first_commands.py runbooks/payments-api.yml
&lt;span class="c"&gt;# observe only — replace with your read-only kube context&lt;/span&gt;
kubectl &lt;span class="nt"&gt;--context&lt;/span&gt; scratch &lt;span class="nt"&gt;-n&lt;/span&gt; payments get deploy payments-api &lt;span class="nt"&gt;-o&lt;/span&gt; wide
kubectl &lt;span class="nt"&gt;--context&lt;/span&gt; scratch &lt;span class="nt"&gt;-n&lt;/span&gt; payments top pod &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;payments-api
&lt;span class="c"&gt;# stop here unless tools/unfreeze.sh succeeded on this same hash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the drill needs a command that is not in &lt;code&gt;observe_commands&lt;/code&gt;, I update the YAML in git and rehash. I do not "just this once" extend the path in the terminal. That exception is how freeze gates die.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations, and who should skip this
&lt;/h2&gt;

&lt;p&gt;This workflow assumes you can map alerts to ids, keep argv allowlists, and block writes in the default path. It will feel heavy if you are a solo hobby project with no pager, or if your platform cannot distinguish read kubectl from write kubectl. It also will not save you if the runbook hash is computed after the assistant edits the file. I am not claiming latency numbers, model rankings, or token budgets here, because those claims go stale and they are not the point.&lt;/p&gt;

&lt;p&gt;Do not use this approach to launder a generated production change behind a decorative freeze file. Do not store signatures, kubeconfigs, or customer data on a shared demo server. Do not skip escalation because the model sounded calm. If you cannot name the blast radius in one sentence, the hash check should keep failing.&lt;/p&gt;

&lt;p&gt;The core conclusion does not change when the models get more fluent. Encode the page as alert class, first command, escalation clock, and freeze hash, then make unfreeze a signed break of that hash. Everything else is optional commentary. If you want a scratch box to lint the YAML and the allowlist, MonkeyCode's free server option is enough to practice the checks; it is not an on-call seat.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sre</category>
      <category>productivity</category>
      <category>linux</category>
    </item>
    <item>
      <title>Copilot Patches Stay Read-Only Until You Sign the Unfreeze</title>
      <dc:creator>Emery Huang</dc:creator>
      <pubDate>Wed, 16 Sep 2026 04:04:15 +0000</pubDate>
      <link>https://dev.to/appcpp_9071/copilot-patches-stay-read-only-until-you-sign-the-unfreeze-2f1b</link>
      <guid>https://dev.to/appcpp_9071/copilot-patches-stay-read-only-until-you-sign-the-unfreeze-2f1b</guid>
      <description>&lt;p&gt;An AI copilot can draft a remediation in seconds, but it cannot own production blast radius. I still freeze writes until the alert class, first-read commands, and an escalation owner are named. If those three are missing, the patch stays in a scratch shell, not on the cluster. Would you really unfreeze kube-system just because a chat window sounded confident at 03:12?&lt;/p&gt;

&lt;h2&gt;
  
  
  The wrong debate for a page
&lt;/h2&gt;

&lt;p&gt;Feeds keep asking whether models already outcode most working engineers on ordinary tickets. That question is interesting at lunch, and it is honestly dangerous on a live pager. On-call is not a coding contest; it is a control problem with a clock and a blast radius. A fluent patch that restarts the wrong Deployment is still an incident amplifier, not a win.&lt;/p&gt;

&lt;p&gt;I treat every model suggestion as an untrusted diff sitting against a frozen write path. The runbook below is a proposal I keep in the repo, not a story about a specific outage. If your team already pastes chat output into kubectl, this is the gate I wish sat in front of that habit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I page on, and what I refuse to page on
&lt;/h2&gt;

&lt;p&gt;I only auto-page when the alert already carries a stable fingerprint, a service owner, and a read-only first command. Everything else can wait for a ticket, because a vague prompt is not an incident. Ask yourself: if the alert cannot name the workload, how can a model name a safe write?&lt;/p&gt;

&lt;h3&gt;
  
  
  Payload contract
&lt;/h3&gt;

&lt;p&gt;Minimum fields I want on the page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;alertname&lt;/code&gt; plus a fingerprint hash, not a prose summary&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;namespace&lt;/code&gt;, &lt;code&gt;workload_kind&lt;/code&gt;, and &lt;code&gt;workload_name&lt;/code&gt; when Kubernetes is involved&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;owner&lt;/code&gt; as a team alias that actually answers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;first_command&lt;/code&gt; that is strictly read-only&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;write_allowed&lt;/code&gt; defaulting to &lt;code&gt;false&lt;/code&gt; until unfreeze
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proposal: pager payload contract (not executed against a live cluster)&lt;/span&gt;
&lt;span class="na"&gt;alertname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;KubeDeploymentReplicasUnavailable&lt;/span&gt;
&lt;span class="na"&gt;fingerprint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a3f1c9e2"&lt;/span&gt;
&lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payments&lt;/span&gt;
&lt;span class="na"&gt;workload_kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;workload_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-api&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payments-oncall&lt;/span&gt;
&lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;page&lt;/span&gt;
&lt;span class="na"&gt;write_allowed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;first_command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;kubectl -n payments get deploy checkout-api -o jsonpath='{.status.conditions}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any of those keys are empty, I do not run writes, and I do not ask a model for a restart. I escalate with the incomplete payload, because guessing owners at 3 a.m. is how you page the wrong people. Can a copilot invent an owner alias that paging actually honors? It cannot, and I will not pretend otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  First commands are reads, and they are boring on purpose
&lt;/h2&gt;

&lt;p&gt;My first five minutes are observe-only, even when the copilot is already waving a Helm rollback. I want evidence that matches the fingerprint, not a soothing narrative that matches my anxiety. The commands below are the ones I type before anyone on the call talks about unfreeze.&lt;/p&gt;

&lt;h3&gt;
  
  
  The boring bundle
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# proposal: observe-only bundle, labeled unexecuted&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;payments &lt;span class="nv"&gt;APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;checkout-api &lt;span class="nv"&gt;KIND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deploy &lt;span class="nv"&gt;NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;checkout-api
&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"fingerprint=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FINGERPRINT&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;missing&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; get deploy,sts,po,ep &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; wide
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; describe &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KIND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'1,80p'&lt;/span&gt;
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; logs &lt;span class="s2"&gt;"deploy/&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="nt"&gt;--timestamps&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 40

&lt;span class="c"&gt;# still read-only: compare live object to last applied&lt;/span&gt;
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; get &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KIND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/tmp/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.live.yaml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice there is no rollout restart, no scale, and no force delete in that first bundle. If the model prints those, I copy them into a scratch file named &lt;code&gt;candidate.sh&lt;/code&gt;, and I leave production frozen. Why would a first command need a write if we still have not proved the alert is real?&lt;/p&gt;

&lt;h2&gt;
  
  
  Escalation is a lane, not a vibe
&lt;/h2&gt;

&lt;p&gt;I escalate when the fingerprint is real and the owner is not me, or when the read-only evidence disagrees with the model's story. Restarting just to see is more expensive than a two-minute handoff with a complete note. The three questions I send in the escalate note stay small on purpose, and they travel well.&lt;/p&gt;

&lt;h3&gt;
  
  
  The note I actually send
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Which fingerprint and workload did we actually observe?&lt;/li&gt;
&lt;li&gt;Which read-only commands already ran, with paste of the output hashes?&lt;/li&gt;
&lt;li&gt;What write is proposed, and what blast radius did we name?
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# proposal: escalate note
fingerprint: a3f1c9e2
observed: Deployment/checkout-api ns=payments replicas 1/3
reads_run: get,describe,logs (see incident/2026-09-16/reads.log)
proposed_write: kubectl -n payments rollout undo deploy/checkout-api
blast_radius: checkout-api only; payments-worker not in selector
owner_needed: payments-oncall
unfreeze: blocked
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the next engineer cannot answer those, we stay frozen through the next scheduled check-in. A model that cannot fill this note should not get a shell on any cluster. Does that slow you down on a scary page, and should it, when a fast write can still take the site down?&lt;/p&gt;

&lt;h2&gt;
  
  
  The freeze and unfreeze rule for copilot patches
&lt;/h2&gt;

&lt;p&gt;Here is the rule I want on a sticky note above the keyboard during every page. Production writes stay frozen until four signatures exist together in one unfreeze file on disk. Missing any signature means the copilot patch remains a candidate, even if the diff looks brilliant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four signatures
&lt;/h3&gt;

&lt;p&gt;Unfreeze requires all of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alert fingerprint matches the live object you just read&lt;/li&gt;
&lt;li&gt;Owner alias is a human who accepted the page&lt;/li&gt;
&lt;li&gt;Blast radius is a named workload list, not "the cluster"&lt;/li&gt;
&lt;li&gt;Candidate commands were dry-run in a scratch environment, not on prod
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# proposal: unfreeze gate&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;INCIDENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20260916-checkout
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;UNFREEZE_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./incident/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;INCIDENT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/unfreeze.json"&lt;/span&gt;

python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import json, os, sys, pathlib
p = pathlib.Path(os.environ["UNFREEZE_FILE"])
data = json.loads(p.read_text())
required = ["fingerprint", "owner", "blast_radius", "dry_run_ok"]
missing = [k for k in required if not data.get(k)]
if missing or data.get("write_allowed") is not True:
    print("FROZEN: missing", missing or ["write_allowed"])
    sys.exit(2)
print("UNFREEZE ok for", data["blast_radius"])
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example &lt;code&gt;unfreeze.json&lt;/code&gt; I will not apply until the checker exits zero:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fingerprint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a3f1c9e2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payments-oncall"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"blast_radius"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"deploy/checkout-api"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dry_run_ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"write_allowed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I do not source the candidate script until that checker exits zero on the unfreeze file. If you skip the file, you are not using a runbook; you are gambling with autocomplete. Would I make an exception for a Sev-1? Only with a named owner and a named blast radius.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dry-run the model where it cannot page you twice
&lt;/h2&gt;

&lt;p&gt;I want the copilot to argue with a replica of the failing object, not with production kube-apiserver. A scratch box, a kind cluster, or a disposable VM is enough for most Deployment pages. The goal is simple: does the proposed command even parse, and does it target the named blast radius?&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. I use MonkeyCode, an open source project, only as a scratch place with free model access and a free server option. That keeps the dry run off production credentials, which matters more than chat convenience during a page. I am not claiming a particular model name, a token ceiling, or a hardware spec, because those change and I will not invent them. If you need a disposable box for that dry run, the free server option is enough to keep the experiment off prod.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static allowlist
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# proposal: keep the model off prod kubeconfig&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/scratch/kind-incident.kubeconfig"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;INCIDENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"20260916-checkout"&lt;/span&gt;

&lt;span class="c"&gt;# reject any line that is not in a tiny allowlist&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^(kubectl |helm |echo |#)'&lt;/span&gt; candidate.sh &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null

python3 ./tools/assert_blast_radius.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--script&lt;/span&gt; candidate.sh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allow&lt;/span&gt; &lt;span class="nv"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;payments &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allow&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;checkout-api &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--forbid&lt;/span&gt; kube-system &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--forbid&lt;/span&gt; default

&lt;span class="c"&gt;# only after static checks: server-side dry-run on the scratch cluster&lt;/span&gt;
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; payments apply &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;server &lt;span class="nt"&gt;--validate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; candidate.yaml
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; payments diff &lt;span class="nt"&gt;-f&lt;/span&gt; candidate.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tools/assert_blast_radius.py — proposal checker, unexecuted against prod
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--script&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--allow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;append&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--forbid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;append&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubectl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forbid&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;namespace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: namespace &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not in blast radius&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bad&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bad&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--force&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delete ns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drain &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: forbidden verb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dry-run static checks passed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that checker fails, I do not unfreeze, and I do not negotiate with the model. I either rewrite the candidate by hand or I escalate with the failing line number. A free scratch server is useful here because I can burn the VM after the page, secrets and all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision table I keep next to the runbook
&lt;/h2&gt;

&lt;p&gt;The matrix below is what I actually glance at when the copilot starts sounding sure. It is deliberately blunt, because a clever exception is how frozen writes become thawed writes. Please read it once before you open a chat window on an active production page.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;First action&lt;/th&gt;
&lt;th&gt;Copilot allowed?&lt;/th&gt;
&lt;th&gt;Unfreeze?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fingerprint missing&lt;/td&gt;
&lt;td&gt;Reject page / convert to ticket&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fingerprint matches, reads clean&lt;/td&gt;
&lt;td&gt;Capture logs, wait&lt;/td&gt;
&lt;td&gt;Comments only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reads confirm bad rollout&lt;/td&gt;
&lt;td&gt;Draft undo in scratch&lt;/td&gt;
&lt;td&gt;Yes, files only&lt;/td&gt;
&lt;td&gt;After signatures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Owner unreachable&lt;/td&gt;
&lt;td&gt;Escalate, stay frozen&lt;/td&gt;
&lt;td&gt;No writes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Candidate touches extra NS&lt;/td&gt;
&lt;td&gt;Delete candidate.sh&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checker exit 0 + owner ack&lt;/td&gt;
&lt;td&gt;Run named write once&lt;/td&gt;
&lt;td&gt;No further prompts&lt;/td&gt;
&lt;td&gt;Yes, time-boxed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table is the article, if I am honest with the rest of the on-call rotation. Models are optional during a page, and they stay optional after the unfreeze file exists. The freeze file is not optional, and I will argue that in the incident review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations, and who should not copy this
&lt;/h2&gt;

&lt;h3&gt;
  
  
  When I throw the runbook away
&lt;/h3&gt;

&lt;p&gt;This workflow assumes you can freeze writes without making the outage worse, which is not true for every class of alert. Data corruption, certificate expiry in the next few minutes, and disk-full nodes sometimes need a human-owned write with no model in the loop. I also assume you will not paste secrets, kubeconfigs, or customer payloads into any chat, free or otherwise.&lt;/p&gt;

&lt;p&gt;Do not use this approach if you lack RBAC that can actually deny your own user. Skip it as well if your so-called scratch cluster still shares any credentials with production. Do not use it as an excuse to skip the owner, either, when the page is loud. A free server does not become a production control plane just because the model answered quickly.&lt;/p&gt;

&lt;p&gt;I am not publishing latency numbers, token quotas, or win rates, because I did not measure them for this piece. The only artifact I am defending here is the unfreeze gate, not a vendor benchmark. If your incident commander wants a different freeze policy, follow that policy and keep the checker as a pre-commit hook on runbook PRs.&lt;/p&gt;

&lt;p&gt;When the page is quiet again, I file the unfreeze file next to the read logs. That packet is what I want in the review, not a screenshot of a confident chat. Keep the model in the scratch lane, and keep production behind a signature you can explain.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sre</category>
      <category>linux</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
