<?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: Haley</title>
    <description>The latest articles on DEV Community by Haley (@haaaaaley).</description>
    <link>https://dev.to/haaaaaley</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%2F4022687%2F703ed06f-0209-4f1f-9ffb-c60d641ad45b.jpg</url>
      <title>DEV Community: Haley</title>
      <link>https://dev.to/haaaaaley</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/haaaaaley"/>
    <language>en</language>
    <item>
      <title>Rehearse the Hand-Back Before You Ship the Agent</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Sat, 17 Oct 2026 05:44:57 +0000</pubDate>
      <link>https://dev.to/haaaaaley/rehearse-the-hand-back-before-you-ship-the-agent-47n3</link>
      <guid>https://dev.to/haaaaaley/rehearse-the-hand-back-before-you-ship-the-agent-47n3</guid>
      <description>&lt;p&gt;Last week I watched a designer approve an agent's refund draft in four seconds.&lt;/p&gt;

&lt;p&gt;The policy clause sat behind a quiet little link. She never opened it. Nothing in the log recorded the skip.&lt;/p&gt;

&lt;p&gt;Who owned that decision? She did. What was the consequence? A refund policy might not allow. Where was the point of reversibility? Gone with one click.&lt;/p&gt;

&lt;p&gt;I build drills for that moment now. Not demos, not trust surveys. A drill is a small, boring harness that asks one question: can a real reviewer find the missing evidence and act before the agent ships?&lt;/p&gt;

&lt;p&gt;Below is the whole thing, zero to working. Every stage ends with a check you can run. If the check fails, stop there. Don't move on and hope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1 — Write the missing evidence into a file
&lt;/h2&gt;

&lt;p&gt;Thoughts are not testable. A JSON file is.&lt;/p&gt;

&lt;p&gt;Pick one decision, one deliberately missing field, and one correct first action. Here is my refund scenario.&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"refund-hold-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Approve a 42.00 EUR refund"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Draft ready, nothing sent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"interrupt_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"before send"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"missing_field_on_purpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Refund window: 30 days from delivery"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"correct_first_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open_policy_clause"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stop_condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Reviewer approves without opening the clause"&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;Look at &lt;code&gt;correct_first_action&lt;/code&gt;. That is what I measure. Not whether they felt confident. Not whether they liked the card. Whether they opened the clause before approving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check:&lt;/strong&gt; read the file aloud to a colleague. If they cannot say who decides and what a wrong click costs, rewrite the fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2 — Serve it, don't slide it
&lt;/h2&gt;

&lt;p&gt;Slides get skipped. Pages get clicked. I use only the Python standard library, so there is nothing to install.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;http.server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseHTTPRequestHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPServer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;

&lt;span class="n"&gt;SCEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pathlib&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scenario.json&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;LOG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sessions.jsonl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;PAGE&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="s"&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;html lang=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;lt;head&amp;gt;&amp;lt;meta charset=&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="s"&gt;&amp;gt;
&amp;lt;meta name=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;viewport&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; content=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;width=device-width,initial-scale=1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;
&amp;lt;title&amp;gt;Hand-back drill&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SCEN&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;decision&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;&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SCEN&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;agent_state&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;.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#policy&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="s"&gt;clause&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;Policy clause&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;p id=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;policy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; hidden&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SCEN&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;missing_field_on_purpose&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;&amp;lt;/p&amp;gt;
&amp;lt;button id=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;approve&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;Approve refund&amp;lt;/button&amp;gt;
&amp;lt;script&amp;gt;
const send = (name) =&amp;gt; fetch(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/log&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, {{method: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, body: JSON.stringify({{id: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SCEN&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="s"&gt;, action: name}})}});
document.getElementById(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;clause&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;).addEventListener(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;click&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, () =&amp;gt; {{ document.getElementById(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;policy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;).hidden = false; send(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;open_policy_clause&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;); }});
document.getElementById(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;approve&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;).addEventListener(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;click&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, () =&amp;gt; send(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;approve&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;));
&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;H&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseHTTPRequestHandler&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text/html&lt;/span&gt;&lt;span class="sh"&gt;'&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;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Length&lt;/span&gt;&lt;span class="sh"&gt;'&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="nf"&gt;len&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wfile&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;data&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;do_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/health&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ok&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;text/plain&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;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/verdict&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text/plain&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PAGE&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;do_POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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;Content-Length&lt;/span&gt;&lt;span class="sh"&gt;'&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="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rfile&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="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sa"&gt;b&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="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;LOG&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="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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;f&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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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="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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;logged&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;text/plain&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;verdict&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&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;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;LOG&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="nf"&gt;splitlines&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;LOG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&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;opened&lt;/span&gt; &lt;span class="o"&gt;=&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;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;action&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_policy_clause&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&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;approved&lt;/span&gt; &lt;span class="o"&gt;=&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;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;action&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;approve&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;approved&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;no decision yet&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;correct&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;opened&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;skipped&lt;/span&gt;&lt;span class="sh"&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="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;serve_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start it and check the health route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 drill.py &amp;amp;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; localhost:8000/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Check:&lt;/strong&gt; you must see &lt;code&gt;ok&lt;/code&gt;. If not, don't invite a reviewer yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3 — Log the skip, not the sentiment
&lt;/h2&gt;

&lt;p&gt;Every click lands in &lt;code&gt;sessions.jsonl&lt;/code&gt; with a timestamp. Two numbers come out: seconds to the correct first action, and skip rate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"id":"refund-hold-01","action":"approve"}'&lt;/span&gt; localhost:8000/log
curl &lt;span class="nt"&gt;-s&lt;/span&gt; localhost:8000/verdict
&lt;span class="nb"&gt;cat &lt;/span&gt;sessions.jsonl | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the verdict says &lt;code&gt;skipped&lt;/code&gt;, the link was too quiet or the copy was wrong. Don't blame the reviewer yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check:&lt;/strong&gt; reproduce a skip yourself on purpose. If the log cannot catch your own skip, it cannot catch anyone's.&lt;/p&gt;

&lt;p&gt;One caveat I learned the hard way. This verdict is global across the file. Delete &lt;code&gt;sessions.jsonl&lt;/code&gt; between sessions, or add a session id per run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4 — Read a script and keep your mouth shut
&lt;/h2&gt;

&lt;p&gt;Facilitators ruin drills. One hint and the data becomes theatre.&lt;/p&gt;

&lt;p&gt;Three lines, read cold. "This is a draft. Nothing is sent yet. Approve when you're ready." Then silence.&lt;/p&gt;

&lt;p&gt;Stop the session if the reviewer asks what the clause says and you answer. That is coaching, not evidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check:&lt;/strong&gt; write those three lines down before the session, then compare. Ad-libbing is a failed check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5 — Generate variants, then throw most away
&lt;/h2&gt;

&lt;p&gt;One scenario proves nothing. I generate ten and keep the ones that change the surface without changing the correct first action.&lt;/p&gt;

&lt;p&gt;This is where MonkeyCode's free model access helped me draft variant copy at volume. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The free-access and free-server claims are operator-supplied here, so check the current plan page yourself before relying on any limit.&lt;/p&gt;

&lt;p&gt;Keep the brief in a file so variants stay comparable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# prompts/variants.md&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Same decision, same correct first action.
&lt;span class="p"&gt;-&lt;/span&gt; Vary label wording, link prominence, field order.
&lt;span class="p"&gt;-&lt;/span&gt; Never paste the policy text into the card.
&lt;span class="p"&gt;-&lt;/span&gt; Output one JSON object per line.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then triage with &lt;code&gt;jq&lt;/code&gt; instead of reading ten drafts at midnight.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jq &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'select(.correct_first_action == "open_policy_clause")'&lt;/span&gt; variants.jsonl &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; keep.jsonl
&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; keep.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Check:&lt;/strong&gt; every kept variant must fail in the same direction when skipped. A variant that cannot be skipped tests nothing at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 6 — Put it on a real URL
&lt;/h2&gt;

&lt;p&gt;Localhost is a lab coat. People behave differently on a link they can open on a phone.&lt;/p&gt;

&lt;p&gt;I put my harness on MonkeyCode's free server option and opened it on my own phone first. Follow that host's own docs for ports and process binding; your deploy command is not mine.&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="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8000 python3 drill.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Check:&lt;/strong&gt; open the URL one-handed on a phone, sound off. If the approve button lands under your thumb and the clause link does not, you just found a real defect for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 7 — Accessibility before you count anything
&lt;/h2&gt;

&lt;p&gt;A drill that excludes reviewers produces confident, wrong data. I have shipped that mistake once.&lt;/p&gt;

&lt;p&gt;Four checks: keyboard-only path to the clause, visible focus, labels that survive a screen reader, and plain language on the button. "Approve" needs a noun. Approve what, for how much?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check:&lt;/strong&gt; tab through without a mouse. If the clause is reachable but focus is invisible, stop and fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision table I actually use
&lt;/h2&gt;

&lt;p&gt;After five sessions I put the evidence in one table and decide.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;th&gt;Ship the agent?&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0/5 skips, clause opened first&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Ship, keep logging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1/5 skips, recoverable before send&lt;/td&gt;
&lt;td&gt;Hold&lt;/td&gt;
&lt;td&gt;Rewrite the link, rerun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2+/5 skips or one irreversible click&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Redesign the hand-back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reviewer can't explain the consequence&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Cut the autonomy, not the copy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two skips in five is not a small number. It is a pattern wearing a small sample.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this drill cannot tell you
&lt;/h2&gt;

&lt;p&gt;Five sessions is a smell test, not a study. I don't compute significance on five people. I look for the same failure twice.&lt;/p&gt;

&lt;p&gt;The agent is mocked, so real failures stay messier than my file. This harness tests the hand-back, not the model.&lt;/p&gt;

&lt;p&gt;Don't use it for regulated sign-off. Don't use it instead of production telemetry. And don't run it if nobody can watch the sessions, because unsupervised drills become usability theatre with a CSV.&lt;/p&gt;

&lt;p&gt;It is also a poor fit for teams with no reversible moment at all. If every action is final, you have a different problem and a drill won't find it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question I keep asking
&lt;/h2&gt;

&lt;p&gt;Which missing evidence should stop an approval? Which extra detail only adds noise?&lt;/p&gt;

&lt;p&gt;You won't answer that in a design review. You answer it by watching one person, one click, one skipped clause. Then you fix the link and run it again.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>testing</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Build a Refusal Gate in Five Stages, Then Break It</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Fri, 16 Oct 2026 04:39:07 +0000</pubDate>
      <link>https://dev.to/haaaaaley/build-a-refusal-gate-in-five-stages-then-break-it-10ka</link>
      <guid>https://dev.to/haaaaaley/build-a-refusal-gate-in-five-stages-then-break-it-10ka</guid>
      <description>&lt;p&gt;Picture a support queue on a Tuesday morning. An agent reads one ticket and drafts a refund promise. The ticket is missing the order state. Nobody notices until the customer replies.&lt;/p&gt;

&lt;p&gt;That draft was a decision made with missing evidence. The decision owner was the support lead. The consequence landed on the customer. The point of reversibility was the send button.&lt;/p&gt;

&lt;p&gt;So I stopped asking whether the model was trustworthy. I started asking where the refusal should live. A gate is cheaper than an apology. Here is the harness, in five stages, each with its own check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: Write the stop condition as data, not prose
&lt;/h2&gt;

&lt;p&gt;Rules in a wiki get read once. Rules in a file get executed. Put yours in &lt;code&gt;stop_conditions.json&lt;/code&gt;.&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"refund-promise"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"trigger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"draft contains a refund commitment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required_evidence"&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;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order_state"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"refund_policy_id"&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;"support-lead"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowed_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"draft_only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"recovery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ask_customer_for_order_id"&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;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;Four fields matter most: the trigger, the evidence, the owner, the recovery. If you cannot name one owner, you have a policy, not a rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verification:&lt;/strong&gt; &lt;code&gt;python -m json.tool stop_conditions.json &amp;gt; /dev/null &amp;amp;&amp;amp; echo "schema ok"&lt;/code&gt; should print &lt;code&gt;schema ok&lt;/code&gt; and exit zero. If this file is empty, your gate is a rubber stamp.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: Stand up the harness on free model access
&lt;/h2&gt;

&lt;p&gt;I keep a scratch server for gate work. The point is a machine you can break without breaking production.&lt;/p&gt;

&lt;p&gt;MonkeyCode, an open-source project I use for this kind of experiment, advertises free model access and a free server option. Those are operator-supplied availability claims, and I treat them as availability, not as a promise. Check the current terms before you depend on them.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh you@your-free-server
python3 &lt;span class="nt"&gt;-V&lt;/span&gt;                 &lt;span class="c"&gt;# 3.9+ for the stdlib calls below&lt;/span&gt;
git clone &amp;lt;your-fork-url&amp;gt; stop-gate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;stop-gate
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; .venv/bin/activate
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MONKEYCODE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;from your console&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MODEL_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;from your console&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MODEL_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;pick one your console lists&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am not naming models here, because the list changes and my naming it would age badly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verification:&lt;/strong&gt; &lt;code&gt;python -c "import os; print(bool(os.environ['MODEL_BASE_URL']))"&lt;/code&gt; prints &lt;code&gt;True&lt;/code&gt;. One working transport call is enough for the whole harness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: Put the gate in front of the model
&lt;/h2&gt;

&lt;p&gt;The gate checks evidence before any model call. A refusal therefore costs zero tokens.&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 — reference implementation, stdlib only
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="n"&gt;STOP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stop_conditions.json&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;transport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&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;model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fill in the request shape your console documents. This is a placeholder.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;base&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;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&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;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&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;Bearer &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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&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;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;r&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;json&lt;/span&gt;&lt;span class="p"&gt;.&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;r&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;coverage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;have&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;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required_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="n"&gt;case&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;f&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;have&lt;/span&gt;&lt;span class="p"&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;rule&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required_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;have&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_model&lt;/span&gt;&lt;span class="o"&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;for&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;STOP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_&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="nf"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&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;REFUSE&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;rule&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rule&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;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;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="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="n"&gt;rule&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;recovery&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recovery&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;model_called&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&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;call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case&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;call_model&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&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;DRAFT&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;rule&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing&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;output&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="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model_called&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;call_model&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;None&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the part people skip. Prove the refusal never reaches the model.&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;# test_gate.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;gate&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;decide&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;boom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&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;AssertionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model must not be called on a refusal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;case&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;customer_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;c-88&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;draft&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;we will refund you&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;boom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&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;REFUSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing&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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_state&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;refund_policy_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;out&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;refusal gate ok:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&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="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recovery&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;&lt;strong&gt;Verification:&lt;/strong&gt; &lt;code&gt;python test_gate.py&lt;/code&gt; prints &lt;code&gt;refusal gate ok: support-lead ask_customer_for_order_id&lt;/code&gt;. When a refusal still calls the model, you have not built a gate. You have built a comment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: Keep the discarded information in the record
&lt;/h2&gt;

&lt;p&gt;A refusal is data, not silence. Log what was missing, who owns it, and how a human recovers.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record&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;case_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule&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="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recovery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;case_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;case_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rule&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;decision&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="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recovery&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;recovery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;with&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="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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;fh&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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verification:&lt;/strong&gt; &lt;code&gt;jq -r 'select(.decision=="REFUSE") | .missing[]' decisions.jsonl | sort | uniq -c | sort -rn&lt;/code&gt; groups the blockers by count.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 14 order_state
  9 refund_policy_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fourteen refusals, one field. Fix the intake form instead of the prompt. That is the whole argument for logging last.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5: Show the refusal card to a human
&lt;/h2&gt;

&lt;p&gt;The card has four lines, in this order: what the agent wanted to do, which evidence is missing, who can unblock it, what approving anyway means. Focus order must match that reading order. No color-only signals. Plain words, no system names.&lt;/p&gt;

&lt;p&gt;My review protocol is small and repeatable. Two reviewers read one card aloud, one of them with a screen reader. I count hesitations and time how long it takes each to name the owner and the recovery path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Success measure:&lt;/strong&gt; every refusal card names exactly one owner and one recovery path within ten seconds. &lt;strong&gt;Stop measure:&lt;/strong&gt; any approval that proceeds while a required field is empty. When that happens, halt the release and fix the rule. Never relax the fixture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this harness does not prove
&lt;/h2&gt;

&lt;p&gt;It does not prove the model is safe, only that one class of action waits for evidence. Free access is availability, not a guarantee, and terms can change.&lt;/p&gt;

&lt;p&gt;Skip this if a regulator requires a contractual model version pinned to a document. Skip it for sub-second interactive flows, because the gate writes a record. Skip it if your team cannot name a single decision owner. Anonymous refusals teach nobody anything.&lt;/p&gt;

&lt;p&gt;So which missing piece of evidence should stop your approval this week? Write that one rule, run the fixture, and let the gate refuse for you.&lt;/p&gt;

&lt;p&gt;If you want a scratch machine for the fixture, the free server option in MonkeyCode will run this harness end to end.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>testing</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Show the First Refusal Before You Retry</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Mon, 12 Oct 2026 22:46:50 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-the-first-refusal-before-you-retry-59ng</link>
      <guid>https://dev.to/haaaaaley/show-the-first-refusal-before-you-retry-59ng</guid>
      <description>&lt;p&gt;Picture this review with me, as a teaching scenario. The design lead's cursor hovered over the Approve button. The second model pass agreed with our preferred label.&lt;/p&gt;

&lt;p&gt;The first pass had refused that same recovery modal. I asked who owned the click if a user got stuck. The scenario says the design lead owned the decision.&lt;/p&gt;

&lt;p&gt;I asked where we could still undo the approval. The room went quiet in that familiar review-room way. A cheap retry can make agreement feel like proof.&lt;/p&gt;

&lt;p&gt;Agreement is not the same thing as evidence. Have you watched a second pass sand down a first no? It shows up inside this kind of teaching scenario.&lt;/p&gt;

&lt;p&gt;The dissent vanishes, and the card looks clean. That is the decision I want to slow down. The owner is the design lead, not the model.&lt;/p&gt;

&lt;p&gt;The consequence is a recovery path that hides refusal. The point of reversibility sits before the card is marked approved. After that click, engineering may treat the copy as settled.&lt;/p&gt;

&lt;p&gt;Can you still walk that approval back after the click? Retry debates keep surfacing in public developer threads. I am not treating those posts as proof of anything.&lt;/p&gt;

&lt;p&gt;I am treating the urge to retry as the design problem. A nod is a mood. A stored refusal is a record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Name the decision before a model speaks
&lt;/h2&gt;

&lt;p&gt;Start from zero, even if the file already exists. Write the decision in one sentence a stranger can read. Do not start this stage by prompting a model.&lt;/p&gt;

&lt;p&gt;I use this sentence in the worked scenario. May we replace the refused recovery label with the team's preferred label? The owner, the user, and the harm must all appear.&lt;/p&gt;

&lt;p&gt;Here is the card I ask the team to save first.&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;"decision_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"recovery-label-014"&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;"design-lead"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_moment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account-lockout-recovery"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proposed_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"replace refused label with team label"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"harm_if_wrong"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user cannot tell refusal from success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reversible_until"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"review-card-status-approved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"first_refusal"&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="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kept_verbatim"&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;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Label reads as success. Undo path missing. Reason missing."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source_handle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pass-1-output.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"overwritable"&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;span class="nl"&gt;"stops"&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="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"missing verbatim first refusal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"retry output drops the undo gap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"retry output drops the refusal reason"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"no named decision owner"&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;span class="nl"&gt;"noise"&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="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"model confidence score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"a third wording variant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"praise for the team label"&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;span class="nl"&gt;"evidence"&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;"stored first pass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lockout flow notes"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hypotheses"&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;"team label feels clearer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"accessible name wording"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retry_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;Verification for this stage is blunt on purpose. Read the sentence aloud without the model in the room. If you cannot name the owner, you stop.&lt;/p&gt;

&lt;p&gt;If you cannot name the harm, you stop. A missing field should fail that command outright. A pretty sentence should not rescue a missing field.&lt;/p&gt;

&lt;p&gt;Did the command exit clean, or did you shrug? A green exit is the only pass I accept here. A shrug means the stage is not done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.owner and .harm_if_wrong and .reversible_until'&lt;/span&gt; review-card.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the flow I want on the wall, not in someone's head. The user is in lockout, then asks for a recovery path. The refusal is stored before any retry is even offered.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lockout
  -&amp;gt; recovery request
  -&amp;gt; first refusal stored and locked
  -&amp;gt; stop check
  -&amp;gt; optional retry challenge on a disposable run
  -&amp;gt; owner approves or hands back
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The optional run comes after the stop check, never before it. Approval sits with the owner at the end. A model nod is not a box on this flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lock the first refusal
&lt;/h2&gt;

&lt;p&gt;The first pass said the label sounded like success. It named a missing undo path and a missing reason. That dissent is the evidence, not a draft to polish.&lt;/p&gt;

&lt;p&gt;I paste the refusal into the card before anyone hits retry. I do not paraphrase it into something kinder. Kind paraphrases are how eager agreement gets hired.&lt;/p&gt;

&lt;p&gt;Now check that the refusal is present and locked. If that check fails, the retry is not a review. It is an eraser wearing a review badge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.first_refusal.overwritable == false and .first_refusal.kept_verbatim == true'&lt;/span&gt; review-card.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Would you approve a design that deletes its own critique? I would not. The deleted line is usually the one the user needed.&lt;/p&gt;

&lt;p&gt;I separate evidence from the hunch in the next fields. Evidence is the stored first pass and the flow notes. The hunch is that the team label feels clearer.&lt;/p&gt;

&lt;p&gt;Do not merge those two lines into one. A hunch can guide the next research question. A hunch cannot fill an empty evidence slot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set the stops before a cheap retry
&lt;/h2&gt;

&lt;p&gt;Free retries tempt teams to sample until the model nods. I was told MonkeyCode offers free model access and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;I am not quoting a token total, a model list, or an uptime promise. I have not checked a primary source for those figures this week. Availability is not a quality certificate, and I will not pretend it is.&lt;/p&gt;

&lt;p&gt;So the free lane is only the place we might rerun a bounded check. That lane is not the approver on this card. It is not allowed to overwrite the first refusal.&lt;/p&gt;

&lt;p&gt;Here are the stops I want on the card. A missing verbatim refusal stops the retry cold. A second pass that drops the undo gap stops approval.&lt;/p&gt;

&lt;p&gt;A confidence number without a cited flow is noise, not a key. Which missing evidence should stop this approval cold? For me, a dropped undo gap or a dropped reason stops it.&lt;/p&gt;

&lt;p&gt;Which extra line would only add noise here? A score that flatters the preferred label is noise. A third adjective is noise too.&lt;/p&gt;

&lt;p&gt;Verify the stops exist before you spend a free run. If you cannot say the stop out loud, do not open the model. Curiosity is not a stop condition by itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.stops | length &amp;gt;= 3'&lt;/span&gt; review-card.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A deadline is not a stop condition either. Boredom is not a stop condition on this card. Only a named gap in the refusal can stop the run.&lt;/p&gt;

&lt;p&gt;Save this checker beside the card. It is a proposed contract, not a lab result I already ran.&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;# Proposed checker. Not executed against a live product.
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&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;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;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="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;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;harm_if_wrong&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;reversible_until&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_refusal&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;stops&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&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&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="n"&gt;required&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;card&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;k&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;refusal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;card&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_refusal&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;refusal&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;overwritable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;missing&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;locked_first_refusal&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;card&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;retry_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;and&lt;/span&gt; &lt;span class="n"&gt;missing&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry allowed while evidence is missing&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;missing&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stop: &lt;/span&gt;&lt;span class="sh"&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;, &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;missing&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;card has the minimum refusal record&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 check_review_card.py review-card.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the refusal is unlocked, the script should exit on a stop. If the minimum record is present, it should print the minimum line. Treat that print as the expected contract, not a lab result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenge the label, do not rewrite the no
&lt;/h2&gt;

&lt;p&gt;I point the second pass at the stored refusal, not at a blank page. The job is to challenge our label against that refusal. The job is not to make us feel right.&lt;/p&gt;

&lt;p&gt;This command is a proposed check, not a live call I executed. Swap in whatever client your team already trusts. Keep the second output in a new file.&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 sequence. Do not overwrite pass-1-output.txt.&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;review-card.json pass-1-output.txt &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; retry-brief.txt
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; pass-2-output.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"no second pass yet"&lt;/span&gt;
&lt;span class="c"&gt;# After your free model lane writes pass-2-output.txt:&lt;/span&gt;
diff &lt;span class="nt"&gt;-u&lt;/span&gt; pass-1-output.txt pass-2-output.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the diff only after the second file exists. Until then, the test line should admit the gap. A missing second file is a stop, not a crash to ignore.&lt;/p&gt;

&lt;p&gt;When a reply exists, I diff the claims, not the vibes. I then mark each surviving claim as evidence or hypothesis. If the second pass agrees and drops the undo gap, retry stays blocked.&lt;/p&gt;

&lt;p&gt;Agreement with a hole is a failed retry. The jq line below is the failed-retry hand-back, not the happy path. The design lead still owns the actual decision.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jq &lt;span class="s1"&gt;'.retry_allowed = false'&lt;/span&gt; review-card.json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; review-card.tmp
&lt;span class="nb"&gt;mv &lt;/span&gt;review-card.tmp review-card.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model does not get a quieter vote. Can a free server make this safer by itself? No, a free server cannot make the decision safer alone.&lt;/p&gt;

&lt;p&gt;A free server can make the run cheaper to discard. Cheap discard is useful only if the first no stays put. Otherwise the team only bought a cleaner mistake.&lt;/p&gt;

&lt;p&gt;If the retry survives, the owner may edit the flag by hand. Do not let a script flip that flag to true. Automated approval is how eager agreement gets the last word.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the card the way we read the product
&lt;/h2&gt;

&lt;p&gt;The review card is an interface in its own right. If dissent is only a red dot, some people will miss it. I want the word Refusal in text, early in the reading order.&lt;/p&gt;

&lt;p&gt;I tab through the card before I trust the layout. The refusal block must be reachable without a pointer. The Approve control must expose why it is disabled.&lt;/p&gt;

&lt;p&gt;I also read it with a screen reader in the scenario. Or I ask a teammate who will actually do it. The stop reason should be announced with the disabled button.&lt;/p&gt;

&lt;p&gt;Color alone cannot carry a do-not-retry message. Here is the accessible name I expect on the blocked action. The wording is still a hypothesis until someone hears it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt; &lt;span class="na"&gt;aria-describedby=&lt;/span&gt;&lt;span class="s"&gt;"stop-reason"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Approve recovery label
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"stop-reason"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Stopped. First refusal still names a missing undo path.
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the association before you call the pattern done. If either grep is empty, the block is a visual hint. Visual hints fail quietly for a lot of people.&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;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'aria-describedby="stop-reason"'&lt;/span&gt; review-card.html
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'id="stop-reason"'&lt;/span&gt; review-card.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quiet failure is how bad recovery copy ships. The stored refusal and the flow notes are the only evidence here. The accessible names are still a design hypothesis.&lt;/p&gt;

&lt;p&gt;They stay a hypothesis until someone actually hears the stop. I also check the product flow, not only the card. The user hits lockout, asks for recovery, and must see refusal as refusal.&lt;/p&gt;

&lt;p&gt;They need a reason, a next step, and a way back. If our preferred label skips any of those, the first no stands. A smoother sentence is not an accessible sentence.&lt;/p&gt;

&lt;p&gt;Would you know you had just been refused? If the answer is a pause, the card is not ready. Smooth copy can still abandon someone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run three scenarios, then stop generalizing
&lt;/h2&gt;

&lt;p&gt;I do not call this protocol a finding. It is a protocol you can run tomorrow. Run it on three scenarios before you generalize.&lt;/p&gt;

&lt;p&gt;In the first scenario, the shorter label still lacks a reason. Success means the reason stays visible or approval stays blocked. Stop if the retry calls brevity a fix.&lt;/p&gt;

&lt;p&gt;In the second scenario, the retry adds a reason but drops undo. Success means both the reason and the undo survive. Stop if the card celebrates a partial save.&lt;/p&gt;

&lt;p&gt;In the third scenario, the two passes disagree on severity. Success means both statements stay on the card for the owner. Stop if you delete the harsher line to reduce noise.&lt;/p&gt;

&lt;p&gt;Noise can look like a third decorative adjective. Noise can also look like a naked score. Noise can sound like a polished model compliment.&lt;/p&gt;

&lt;p&gt;None of those tell the locked-out person what to do next. I would not use this approach on a live incident bridge. I would not use it if you cannot store the first pass.&lt;/p&gt;

&lt;p&gt;I would not use it as a substitute for someone else's code fix. I would not use it to bless a quota or a chip. I would not use it to bless a forever-free claim.&lt;/p&gt;

&lt;p&gt;I was not given those facts in a form I can check. Please do not invent them in the review notes either. Unchecked numbers age fast, and this card has no room for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leave the no on the table
&lt;/h2&gt;

&lt;p&gt;Leave the first refusal in the record even if you later reject it. A rejection is still a real human decision. Deletion looks like the model was right all along.&lt;/p&gt;

&lt;p&gt;The flow is simple if you say it slowly. Capture the no, then lock that no in place. Name the stop before you challenge the label.&lt;/p&gt;

&lt;p&gt;Challenge the label, and do not rewrite the no. Then let the owner approve or hand the work back. The model can speak twice and still not own the click.&lt;/p&gt;

&lt;p&gt;If a free model lane already sits on your bench, use it here. If a free server is there too, keep the run disposable. Keep the first refusal in the file the retry cannot touch.&lt;/p&gt;

&lt;p&gt;Then ask whether the missing undo path is still on the card. If it is gone, the retry failed, no matter how smooth the label sounds. Would you hand that card to the locked-out person?&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>design</category>
      <category>ai</category>
      <category>ux</category>
    </item>
    <item>
      <title>Show the Measured Pair Before You Approve the Page</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Sun, 11 Oct 2026 19:53:40 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-the-measured-pair-before-you-approve-the-page-38d</link>
      <guid>https://dev.to/haaaaaley/show-the-measured-pair-before-you-approve-the-page-38d</guid>
      <description>&lt;p&gt;I open a Thursday review and the page already looks finished. A side panel holds a free draft of the hero. It claims the contrast passes, and it wrote the alt text.&lt;/p&gt;

&lt;p&gt;I ask who measured the color pair on the real screen. Nobody answers, and the publish button is still armed. Would you press it on a sentence you cannot trace?&lt;/p&gt;

&lt;p&gt;That quiet is the decision I care about today. I own the call, and a keyboard user may miss contact. I can still hold the URL before it goes public.&lt;/p&gt;

&lt;p&gt;People are arguing again about generated pages and real gates. I will not settle that argument here with a slogan. I will stay on the moment where the evidence is missing.&lt;/p&gt;

&lt;p&gt;This is a scenario I use to teach the gate. It is not a customer study, and it has no success rate. Each stage below has a check you can fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hold the decision before the draft talks
&lt;/h2&gt;

&lt;p&gt;I write one line before I read any model output. The line says whether this page may go public today. I name the owner, the harm, and the last reversible moment.&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;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"publish_page"&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;"design_reviewer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"harm_if_wrong"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keyboard_user_misses_contact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reversible_until"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"before_public_url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blocked"&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 verify the card by reading every field out loud. The owner must be a person sitting in the review. The reversible moment must name a real page state.&lt;/p&gt;

&lt;p&gt;If the owner field is blank, I stop the review. If the reversible moment only says later, I stop. A mood note about the palette would only add noise.&lt;/p&gt;

&lt;p&gt;Who is allowed to move status from blocked to ready? If you cannot name them, you do not have a gate. A tool name in that field is not an owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capture the pair a person saw
&lt;/h2&gt;

&lt;p&gt;I refuse the model's contrast sentence as if it were proof. I record the foreground, background, ratio, tool, and human. I also mark the alt source and the focus record.&lt;/p&gt;

&lt;p&gt;The numbers below are fixture data for the tutorial. They are not a lab result from a shipped page. Treat them as a shape to copy, not as a finding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pair: hero_heading
fg: "#1A1A1A"
bg: "#F7F4EF"
ratio: 14.8
tool: contrast_checker
seen_by: reviewer
image_alt_source: missing
focus_path_recorded: no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I verify the file by checking the seen_by field carefully. That name must be a person, not the drafting model. The ratio must come from the tool a person ran.&lt;/p&gt;

&lt;p&gt;A calm note about cream paper is only a hypothesis. The stored ratio is the evidence I am willing to defend. Feelings can stay in the notes, but they cannot unlock publish.&lt;/p&gt;

&lt;p&gt;I point the room at W3C contrast and text-alternative guidance. I am not asking a model to remember the criterion for me. If your team uses a stricter bar, write that bar in.&lt;/p&gt;

&lt;p&gt;Do you already see the two locks in this file? Alt source is missing, and the focus path is unmarked. Those locks matter more than a smoother hero sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Say which gap stops you
&lt;/h2&gt;

&lt;p&gt;I ask one research question before anyone admires the copy. Which missing field should block a publish in this review? Which extra note should we ignore on purpose today?&lt;/p&gt;

&lt;p&gt;In this scenario I choose three gaps as hard stops. I stop for no human ratio on the measured pair. I stop for no alt source tied to a seen image.&lt;/p&gt;

&lt;p&gt;I stop when nobody has recorded the keyboard path yet. I ignore a second paraphrase of the same hero line. I ignore a confidence score beside the drafted alt text.&lt;/p&gt;

&lt;p&gt;I ignore a longer rationale that adds no new fact. Two fixtures make this split something you can repeat. The stop fixture omits the recorded focus path on purpose.&lt;/p&gt;

&lt;p&gt;The pass fixture includes owner, ratio, human alt, and path. I verify the protocol by scoring both fixtures before we meet. If the stop fixture can still publish, the question was theater.&lt;/p&gt;

&lt;p&gt;Success means the stop fixture stays blocked after the review. It does not mean the page has passed an access audit. A pass fixture only shows that notes exist, not that they are true.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walk the flow in the order harm happens
&lt;/h2&gt;

&lt;p&gt;I narrate the flow so we do not skip straight to copy. A person opens the page and tabs toward the contact control. Focus dies on the way, or the image alt tells a lie.&lt;/p&gt;

&lt;p&gt;They leave, and we cannot undo that visit later. The recovery point has to sit before the public URL. After publish, the repair flow gets slower and colder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;open page -&amp;gt; tab toward contact -&amp;gt; focus dies or alt lies -&amp;gt; visitor leaves
hold the URL before it is public
after public -&amp;gt; unpublish -&amp;gt; restore human version -&amp;gt; tell owner -&amp;gt; rerun gate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I verify the flow by pointing at one state per beat. If a beat has no screen state, it is only a story. Would your current mock survive that kind of pointing test?&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a free draft for words only
&lt;/h2&gt;

&lt;p&gt;I do use a narrow helper, so I show the relationship first. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I was told the trial includes free model access for drafts.&lt;/p&gt;

&lt;p&gt;I was also told a free server option exists for a sandbox. I will not certify a token quota or a machine size. I will not promise how long that free option remains available.&lt;/p&gt;

&lt;p&gt;Read the live product page before you plan a study around it. I will not treat license or repo status as settled here either. I place only the evidence file on that free server sandbox.&lt;/p&gt;

&lt;p&gt;I do not place private participant notes on a free host. I ask the free model for candidate alt text and nothing more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;task: draft_alt_words
input: evidence_file_only
forbid: new_ratios, pass_or_fail, unseen_claims
mark: candidate_not_evidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I verify the model reply with a plain text search. I look for any ratio that was absent from the file. I also look for passes, compliant, and the word accessible.&lt;/p&gt;

&lt;p&gt;A hit means I discard the draft and keep the file. The sandbox helped only if I can throw the output away. Should a free draft ever flip the card status to ready?&lt;/p&gt;

&lt;p&gt;I say no, because the person who saw the screen owns that. Skip this stage when you need a formal conformance audit. Skip it when the page is already sitting on a public URL.&lt;/p&gt;

&lt;p&gt;Skip it when your team will treat a free option as uptime. A free helper can suggest words after the locks are visible. It cannot become the evidence those locks were missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run a stop script on the two fixtures
&lt;/h2&gt;

&lt;p&gt;I want the boredom of a check that can fail out loud. This script is a proposal I have not executed for you. It only checks that required notes exist in the files.&lt;/p&gt;

&lt;p&gt;It does not judge whether the stored ratio is true. Copy it as a starting gate, then point it at your own fixtures. If your filenames differ, change the command, not the stop rule.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&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;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;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="n"&gt;ev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;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;2&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="n"&gt;stops&lt;/span&gt; &lt;span class="o"&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;card&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="n"&gt;stops&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;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;if&lt;/span&gt; &lt;span class="n"&gt;ev&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;ratio&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="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&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="n"&gt;stops&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;ratio&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;ev&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;image_alt_source&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="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&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;missing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;stops&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;alt_source&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;ev&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;focus_path_recorded&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="n"&gt;stops&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;focus_path&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;stops&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;STOP&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;stops&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="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;EVIDENCE_PRESENT&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;I verify this stage with two commands and two expected results. The stop fixture must print STOP and exit code two. The pass fixture must print EVIDENCE_PRESENT and then exit clean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 gate.py stop_card.json stop_evidence.json
python3 gate.py pass_card.json pass_evidence.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the first command prints EVIDENCE_PRESENT, the gate is theater. If the second command prints STOP, the pass fixture is incomplete. I fix the files or the script before we discuss tone.&lt;/p&gt;

&lt;p&gt;A green result is not an accessibility research finding yet. I say that aloud so a proposal is never filed as proof. The script supports the recommendation only as a field check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check access without asking the model
&lt;/h2&gt;

&lt;p&gt;I close the draft and walk the page with a keyboard. I tab from the top until I reach the contact control. I watch whether the focus ring stays visible on each stop.&lt;/p&gt;

&lt;p&gt;I compare the alt text with the image I can actually see. A draft that guessed a mood is not a match for me. A decorative image needs an empty alternative, not a poem.&lt;/p&gt;

&lt;p&gt;I verify the pass by writing a path note beside the file. The note lists controls in order and confirms a visible ring. If I cannot write that note, the status stays blocked.&lt;/p&gt;

&lt;p&gt;Color alone must not carry blocked status on our card. The word blocked must appear as text beside a visible label. Our own gate should be readable without guessing from color.&lt;/p&gt;

&lt;p&gt;My tab pass can catch a dead path, and that is all. It cannot replace disabled people testing with their own tools. If we claim a full access review, we are overreaching badly.&lt;/p&gt;

&lt;p&gt;What did my eyes miss because I never heard the page? A quiet control name can still block a screen reader user. I write that limit into the review so the claim stays small.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put the page back when someone shipped
&lt;/h2&gt;

&lt;p&gt;Sometimes the URL is already live when I join the review. I do not start by polishing the model's alt text. I unpublish the page or I hide the broken block first.&lt;/p&gt;

&lt;p&gt;I restore the last version a human had actually approved. I archive the bad draft so the miss stays in the record. I name who may have been blocked, and how they retry.&lt;/p&gt;

&lt;p&gt;I verify recovery before anyone calls the incident fully closed. The public URL must show the held state, not the draft. The archive must still contain the discarded model wording too.&lt;/p&gt;

&lt;p&gt;The gate must fail until the new evidence file is complete. Would deleting the draft make the team feel cleaner today? It would also hide the exact miss we need to remember.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this gate will not do
&lt;/h2&gt;

&lt;p&gt;This tutorial will not make a generated page accessible by itself. It will not replace W3C guidance or assistive technology testing. It will not prove a portfolio matters because a human published.&lt;/p&gt;

&lt;p&gt;I would not use this gate for a legal conformance claim. I would not use it when nobody can see the built screen. I would not store private research notes on a free server.&lt;/p&gt;

&lt;p&gt;The recommendation rests on evidence you can point to today. Show the measured pair before you approve the page for publish. Show a seen alt source and a recorded keyboard path too.&lt;/p&gt;

&lt;p&gt;Those three gaps should stop the approval in the room. A smoother paraphrase should not unlock the publish decision here. Missing proof stops the call, and extra wording is only noise.&lt;/p&gt;

&lt;p&gt;If you hold one unpublished screen, run the card first. Make the bad fixture fail before a free draft writes words. Then decide, with the owner still named, whether it may ship.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>design</category>
      <category>ai</category>
      <category>ux</category>
    </item>
    <item>
      <title>Show the Withheld Context Before You Admit a Draft</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Sat, 10 Oct 2026 16:06:51 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-the-withheld-context-before-you-admit-a-draft-3ice</link>
      <guid>https://dev.to/haaaaaley/show-the-withheld-context-before-you-admit-a-draft-3ice</guid>
      <description>&lt;p&gt;Picture a Tuesday critique with six tired people. I am facilitating, and the clock is already rude. Someone pastes a generated empty-state line into the deck.&lt;/p&gt;

&lt;p&gt;The line sounds kind, calm, and almost finished. Did anyone record what the model was allowed to read? I ask that, and the room goes quiet.&lt;/p&gt;

&lt;p&gt;That quiet is the consent gap I refuse to skip. The decision owner is the critique facilitator, not the model. A false line could enter the shared component notes.&lt;/p&gt;

&lt;p&gt;You can still throw the line away before anyone copies it. I treat this scene as a tutorial, not a field study. No metric here comes from a shipped product.&lt;/p&gt;

&lt;p&gt;You can replay every step on your own laptop. Bring a public guideline, not a private transcript. If you cannot say the prompt aloud, you are not ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Name the decision before any prompt
&lt;/h2&gt;

&lt;p&gt;Start with a paper decision, not a clever prompt. Who may admit this draft into critique today? What actually breaks if the line is wrong?&lt;/p&gt;

&lt;p&gt;I write those answers before I open any generator. A missing owner stops the session before any prompt. A vague consequence is also a hard stop.&lt;/p&gt;

&lt;p&gt;If you cannot name the undo point, wait. You are collecting hope, not a decision record. Hope is a poor ticket at the kitchen pass.&lt;/p&gt;

&lt;p&gt;Create a folder and a ledger file first. Run the commands, then check that the file exists. This check is the first verification, not a formality.&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; critique-gate/fixtures
&lt;span class="nb"&gt;cd &lt;/span&gt;critique-gate
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; fixtures/ledger.json
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; fixtures/ledger.json &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ledger file exists"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The echo line is your verification for this stage. If you do not see that message, stop here. A missing file is already a stop condition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep evidence off the guess pile
&lt;/h2&gt;

&lt;p&gt;Here is the analogy I use with design partners. A critique deck is a kitchen pass, not a junk drawer. The cook may plate a dish, but the expediter checks the ticket.&lt;/p&gt;

&lt;p&gt;Evidence is something a person can point to. A hypothesis is what we hope the line will do. Those two must not share a field in the ledger.&lt;/p&gt;

&lt;p&gt;That mix is the failure this protocol is built to catch. I keep the ledger shape below as a proposal. It is not a production schema for your company.&lt;/p&gt;

&lt;p&gt;Change the words, but do not drop the refusal list. Save that object as &lt;code&gt;fixtures/ledger.json&lt;/code&gt; on your disk. Then confirm that the refusal array is present.&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;"decision_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;"critique facilitator"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consequence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"empty-state line may enter component notes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reversibility_point"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"before notes are copied into the system"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowed_context"&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;"public empty-state guideline"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"refused_context"&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;"support transcripts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"private user quotes"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&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;"guideline says name the next action"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hypotheses"&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;"shorter line may reduce abandonment"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"draft_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"human_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"admit"&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"const l=require('./fixtures/ledger.json'); if(!l.refused_context.length) process.exit(1); console.log('refusal list present')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the words refusal list present. If the script exits with an error, stop. Why require a named refusal before any prose?&lt;/p&gt;

&lt;p&gt;Silence about withheld context is not a boundary. It is just an empty chair at the table. Empty chairs do not protect anyone in the room.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a local gate with no network
&lt;/h2&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. The outreach brief describes MonkeyCode as an open-source project. It also describes free model access for practice drafts.&lt;/p&gt;

&lt;p&gt;The same brief names a free server option. It states a free allotment of ten million tokens. I have not rechecked that number in a live console.&lt;/p&gt;

&lt;p&gt;Confirm the current project page before you plan around it. I would use that access only to mint a disposable draft. I would not store the consent ledger on that server.&lt;/p&gt;

&lt;p&gt;The ledger stays in the local folder you just made. If the free option vanishes, this gate should still run. A practice bench is not your system of record.&lt;/p&gt;

&lt;p&gt;Would you bet a design-system note on a changing limit? The flow I want is short, and it stays local at the ends. Read it once before you write the checker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public guideline
  -&amp;gt; local consent ledger
  -&amp;gt; gate prints hold or admit-ready
  -&amp;gt; optional public draft on a free bench
  -&amp;gt; human sets admit
  -&amp;gt; critique may still reject
refused context never enters the prompt
ledger never moves to the free server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write &lt;code&gt;gate.js&lt;/code&gt; as a local checker with no network call. It only reads the ledger you pass on the command line. That limit is the whole point of the exercise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;blank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;blank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decision_owner&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;decision_owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;blank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;consequence&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;consequence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;blank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reversibility_point&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reversibility_point&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refused_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refused_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;refused_context&lt;/span&gt;&lt;span class="dl"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;evidence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;leaked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidence&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;may &lt;/span&gt;&lt;span class="dl"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leaked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;evidence_looks_like_hypothesis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hold&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;missing&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;process&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="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admit-ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the script never sets &lt;code&gt;admit&lt;/code&gt; for you. A complete record is not permission to ship. Check the syntax before you trust a green line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--check&lt;/span&gt; gate.js &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"syntax ok"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the words syntax ok printed. If Node prints a parse error, fix the file. Do not paper over a syntax error with a manual note.&lt;/p&gt;

&lt;p&gt;This script is a teaching example until you run it. It is not a judge of design quality or tone. It only checks whether a human recorded the boundary.&lt;/p&gt;

&lt;p&gt;The may check is only a crude hint, not a parser. A real guideline might say users may retry. That phrase would trip this crude filter today.&lt;/p&gt;

&lt;p&gt;Rename the hint or drop it before you rely on it. I would rather a false hold than a silent mix. You can delete that filter after the lesson lands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail the gate on purpose
&lt;/h2&gt;

&lt;p&gt;Copy the ledger and strip the refusal list. Then run the gate and welcome the failure. A green light here would mean the gate is fake.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"const l=require('./fixtures/ledger.json'); delete l.refused_context; require('fs').writeFileSync('fixtures/bad.json', JSON.stringify(l, null, 2));"&lt;/span&gt;
node gate.js fixtures/bad.json&lt;span class="p"&gt;;&lt;/span&gt; &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;I expect a hold message and an exit code of 1. If you see admit-ready, fix the script before you trust it. A gate that smiles at a missing refusal is theater.&lt;/p&gt;

&lt;p&gt;Now run the same command against the good file. Keep &lt;code&gt;admit&lt;/code&gt; false until a human reads the line. The script does not need that flag to judge completeness.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node gate.js fixtures/ledger.json&lt;span class="p"&gt;;&lt;/span&gt; &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;You should see admit-ready and an exit code of 0. That result means the record is complete enough to discuss. It does not mean the sentence is good, kind, or true.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask for one public line
&lt;/h2&gt;

&lt;p&gt;Only now do I ask a model for draft text. I paste the allowed context, never the refused context. I ask for one empty-state line and a non-use note.&lt;/p&gt;

&lt;p&gt;If you try the free model access, keep the prompt public. Do not paste transcripts, tokens, or private user quotes. A free server is still a server someone else operates.&lt;/p&gt;

&lt;p&gt;Would you read that prompt aloud in the critique? If the answer is no, the prompt is already too private. Shrink it until the answer becomes a bored yes.&lt;/p&gt;

&lt;p&gt;When a draft returns, paste it into &lt;code&gt;draft_text&lt;/code&gt; by hand. Do not let the generator edit the admit field. The human flips that field only after reading the card.&lt;/p&gt;

&lt;p&gt;Rerun the gate after you save the hand edit. Exit code 0 means the record is ready for discussion. Critique can still reject the words without apology.&lt;/p&gt;

&lt;p&gt;Put that rejection in &lt;code&gt;human_note&lt;/code&gt; on the same file. Discarded wording should stay beside the refusal list. If you delete the loser, the next critique repeats the quiet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"const p='fixtures/ledger.json'; const l=require('./'+p); if(!l.draft_text){console.error('draft missing'); process.exit(1)} if(l.admit!==false){console.error('admit flipped too early'); process.exit(1)} console.log('draft parked, admit still false')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see draft parked, admit still false. If admit is already true, a tool jumped the human. Put that flag back before the critique starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the card before the shiny line
&lt;/h2&gt;

&lt;p&gt;The gate output is ugly on purpose, and that is fine. A critique still needs a card a person can read aloud. I speak the pattern instead of hiding status in color.&lt;/p&gt;

&lt;p&gt;The card title is the decision, not the draft. The first line names the owner in plain text. The next line names the consequence in the same voice.&lt;/p&gt;

&lt;p&gt;Then I read the refusal list before the draft text. Why that order, when the sentence looks finished? Because the shiny line will steal the room.&lt;/p&gt;

&lt;p&gt;A screen reader user should reach Refuse without a mouse. The refuse control needs a visible name, not an icon alone. Status must not depend on red or green alone.&lt;/p&gt;

&lt;p&gt;I write the hold reason as a full sentence. We did not record what the model must not read. Consent gap may confuse a new teammate in the room.&lt;/p&gt;

&lt;p&gt;Keep both the short label and the slower sentence. If the card truncates the refusal list, call it a failure. Hidden withheld context is the same quiet we started with.&lt;/p&gt;

&lt;p&gt;Would you approve a ticket you cannot fully scroll? I would not, and I would send the card back. Another person should name the refusal without your help.&lt;/p&gt;

&lt;p&gt;That spoken check is the verification for this stage. If they cannot name it, the card failed. Fix the card before you discuss the sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rehearse three scenes, then stop
&lt;/h2&gt;

&lt;p&gt;I rehearse three scenes before I trust the habit. Scene one uses only a public empty-state guideline. The gate should print admit-ready for that scene.&lt;/p&gt;

&lt;p&gt;Scene two omits the owner and keeps everything else. The gate should hold, and the session should stop. No draft is worth an unnamed decision owner.&lt;/p&gt;

&lt;p&gt;Scene three puts a private quote in allowed context. I stop by policy even if the script stays green. The checker does not understand privacy, but you do.&lt;/p&gt;

&lt;p&gt;Add a human scan for names, tickets, and raw quotes. Do that scan before you set admit to true. A green script cannot see a name you forgot to hide.&lt;/p&gt;

&lt;p&gt;Success here is modest, and I want it that way. A facilitator can name the owner, the harm, and the undo. A missing refusal list blocks admission every time.&lt;/p&gt;

&lt;p&gt;A hypothesis never sits inside the evidence array. Stop the method if nobody will own the decision. Stop if the draft touches health, finance, or account data.&lt;/p&gt;

&lt;p&gt;Stop if your team treats exit code 0 as taste approval. This checker cannot see a bad or biased sentence. It cannot score the accessibility of the product screen.&lt;/p&gt;

&lt;p&gt;Pair the gate with a separate read of the real interface. The protocol does not replace that interface read. It only stops a draft from entering critique naked.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the exit codes do not prove
&lt;/h2&gt;

&lt;p&gt;I am not claiming a usability study or a benchmark. I have no completion rate and no customer quote. Please do not cite this article as either one.&lt;/p&gt;

&lt;p&gt;The recommendation rests on a failure you can reproduce. The supporting evidence is the exit codes you just watched. The critique scene is a teaching frame, not a dataset.&lt;/p&gt;

&lt;p&gt;One design hypothesis sits beside that evidence. A visible consent ledger may reduce silent pastes into critique. Test that with your own team before you write a rule.&lt;/p&gt;

&lt;p&gt;Five facilitated sessions would be a humble start. A hallway opinion is not evidence for a system rule. Label that hypothesis until those sessions actually exist.&lt;/p&gt;

&lt;p&gt;Do not use this flow for production audit logs. Do not use it as a legal record of consent. Do not use it where counsel expects a retention proof.&lt;/p&gt;

&lt;p&gt;Skip it if you need named models, latency, or a permanent quota. Those facts change, and this draft does not certify them. Who else should leave this method on the shelf?&lt;/p&gt;

&lt;p&gt;Skip it when your prompt cannot be read aloud. Skip it when you need a private region or a retention note. The brief's free access is a practice bench, not a vault.&lt;/p&gt;

&lt;p&gt;People who want the model to approve its own copy should walk away. Teams without a named facilitator should walk away too. The gate will not invent an owner for you.&lt;/p&gt;

&lt;p&gt;If a disposable bench would help, read the current MonkeyCode notes. Try the free model access on public text only. Keep the ledger on your machine either way.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>a11y</category>
      <category>ux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Show the Source Gap Before You Publish</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Fri, 09 Oct 2026 11:28:14 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-the-source-gap-before-you-publish-4h74</link>
      <guid>https://dev.to/haaaaaley/show-the-source-gap-before-you-publish-4h74</guid>
      <description>&lt;p&gt;I watched a designer hover over Publish last Tuesday. The portfolio line looked clean, calm, and confident. It claimed the checkout flow had lifted completion. No study, no date, and no owner sat beneath that line. Would you still ship that line to the public web?&lt;/p&gt;

&lt;p&gt;That scene is a composite rehearsal, not a client metric. I use it when a team treats a smooth sentence as proof. The decision owner is the publishing designer, not the model. The consequence is a public claim a stranger can quote. The point of reversibility is the moment before the publish control fires.&lt;/p&gt;

&lt;p&gt;Think of the line as a coat with no receipt. You can still like the cut of that coat. You still should not swear that you paid for it. The missing receipt is the source gap I care about. Extra praise about the fabric is just noise.&lt;/p&gt;

&lt;p&gt;This walkthrough is a from-zero research gate for one claim. You will freeze one line, file a card, and fail closed. Each stage ends with a check you can run. I am not asking you to restyle the whole portfolio. One risky line is enough to learn the habit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze one risky line
&lt;/h2&gt;

&lt;p&gt;Start in an empty folder on your own machine. Make a home for the rehearsal, then stop. If the folder is not empty, you picked a messy desk. Would you review a claim inside a junk drawer?&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; source-gap-rehearsal
&lt;span class="nb"&gt;cd &lt;/span&gt;source-gap-rehearsal
git init
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"Lifted checkout completion after the redesign."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; line.txt
&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; line.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification: &lt;code&gt;wc&lt;/code&gt; should print a small word count, not an error. Read &lt;code&gt;line.txt&lt;/code&gt; aloud before you touch the wording. If you cannot point to a study, the gap is real. A ticket or a dated note can also be the pointer. Do not edit the line into something safer yet. You need the risky version for the test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write the card before any model speaks
&lt;/h2&gt;

&lt;p&gt;The card is the receipt stub, not a trust essay. Fill only the fields a human can defend. Leave the model out until the stub exists. A charming rewrite can wait in the hall.&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;"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;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lifted checkout completion after the redesign."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_pointer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"date_window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reversibility"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"before publish control fires"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model_role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hypothesis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publish_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Publish blocked. Source pointer is missing."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stop_not_color_only"&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;"keyboard_order"&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;"source_pointer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"publish"&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="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save that object as &lt;code&gt;card.json&lt;/code&gt; in the folder. Verification is visual before any script runs. Open the file and confirm &lt;code&gt;owner&lt;/code&gt; is an empty string. Confirm that &lt;code&gt;source_pointer&lt;/code&gt; is an empty string too. If you filled them from memory, you skipped the point. Those empty fields are the missing evidence on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail closed on blanks
&lt;/h2&gt;

&lt;p&gt;Paste this checker into &lt;code&gt;check-source-gap.mjs&lt;/code&gt; in that folder. It is a proposed local script, not a product feature. It tests field presence, not the truth of the claim. A full pointer can still be a lie. Hold that limit in your head while you run it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;source_type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;source_pointer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;date_window&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reversibility&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;model_role&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model_role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;evidence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;STOP: model output is not evidence.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&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="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;STOP: owner cannot be the model.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&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;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`STOP: missing &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;missing&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&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="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PASS: required source fields are present.&lt;/span&gt;&lt;span class="dl"&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 the checker against that still-empty card now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node check-source-gap.mjs card.json
&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;You want a STOP line and an exit code of 1. If you see PASS, the card was already filled. That is a failed rehearsal, not a win. Fix the card back to blanks and run it again.&lt;/p&gt;

&lt;p&gt;Now fill only the fields a human can point at. Use a real pointer you control, even a local note path. Then run the same command and read the exit code. A pass prints the PASS sentence and exit code 0. If the exit code is 2 or 3, stop and reread the owner. You let the model pretend to be the owner or the proof. Back up and ask who signs the sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let a draft speak, then demote it
&lt;/h2&gt;

&lt;p&gt;Here is the only product note in this rehearsal. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I mention it because a free model can draft the line. A free server can also host a scratch review. I am not naming a model, a quota, or a duration. I am not naming a machine shape either. Those details were not verified for this piece. So I will not invent them in this note.&lt;/p&gt;

&lt;p&gt;Ask the free model for two phrasings of &lt;code&gt;line.txt&lt;/code&gt;. Tell it to mark any number it cannot source. Save the reply as &lt;code&gt;draft.md&lt;/code&gt; on disk. Do not paste that reply into the source pointer.&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;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"HYPOTHESIS ONLY"&lt;/span&gt; | &lt;span class="nb"&gt;cat&lt;/span&gt; - draft.md &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; labeled.md
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"HYPOTHESIS ONLY"&lt;/span&gt; labeled.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification: &lt;code&gt;grep&lt;/code&gt; should print a line number for the label. If the label is missing, you mixed a draft with a receipt. Would you staple a guess onto an expense report?&lt;/p&gt;

&lt;p&gt;The model may sound sure of a number. A sure tone is not a source type. A confidence badge is noise, not a gate. A prettier verb is also noise in this gate. The pointer, the owner, and the date window can stop you. The reversibility line can stop the click too. If any one of those is blank, approval stops. Extra adjectives do not earn the publish click.&lt;/p&gt;

&lt;p&gt;What supports that stop rule? You can observe a blank field, and the script can exit. What does not support it? I have no study showing that this card changes publish rates. Treat the behavior hope as a design hypothesis. Keep it far from the PASS line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Park the review, do not crown the host
&lt;/h2&gt;

&lt;p&gt;If a free server option is already available, use it lightly. Treat that host as a scratch pad only. Put &lt;code&gt;labeled.md&lt;/code&gt; and the checker output where a teammate can read them. Then leave that host alone after the upload. The record of truth stays in the folder you can diff.&lt;/p&gt;

&lt;p&gt;I will not give you a vendor command here. I do not have a verified setup sequence. A made-up flag would be a false manual. Use a plain host you already trust if the free option is unclear. The gate is the script, not the address bar.&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; review-export
&lt;span class="nb"&gt;cp &lt;/span&gt;card.json labeled.md review-export/
node check-source-gap.mjs card.json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; review-export/check.txt
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; review-export/check.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"export ok"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification: &lt;code&gt;export ok&lt;/code&gt; means the check text exists. It also means that file is not empty. Open &lt;code&gt;check.txt&lt;/code&gt; before anyone else on the team reads it. If it says STOP, do not upload a publishable page. Upload the stop text, or upload nothing at all.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  A[Risky line] --&amp;gt; B[Source card]
  B --&amp;gt; C{Checker pass?}
  C --&amp;gt;|No| D[Hand back with missing fields]
  C --&amp;gt;|Yes| E[Human reads pointer]
  E --&amp;gt; F{Pointer still true?}
  F --&amp;gt;|No| D
  F --&amp;gt;|Yes| G[Publish with owner named]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Read that flow like a hallway, not a mood. The model never sits in the pass box. The signing human sits there with the pointer. If you remove the hand-back arrow, you built a slide. A slide is not a review you can defend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the stop hearable
&lt;/h2&gt;

&lt;p&gt;A red border alone is not an explanation. I want the publish control to say why it is blocked. That reason should live in text, not hue. The accessible name can be the &lt;code&gt;publish_name&lt;/code&gt; field. Keyboard order should hit the missing field before the button. Color may support the stop, but it cannot be the stop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--input-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;module &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
import { readFileSync } from "node:fs";
const card = JSON.parse(readFileSync("card.json", "utf8"));
const name = String(card.publish_name || "");
const lowered = name.toLowerCase();
if (!lowered.includes("blocked") &amp;amp;&amp;amp; !lowered.includes("missing")) {
  console.error("STOP: publish name hides the gap");
  process.exit(4);
}
if (card.stop_not_color_only !== true) {
  console.error("STOP: color-only stop");
  process.exit(5);
}
const order = card.keyboard_order || [];
if (order.indexOf("source_pointer") &amp;gt; order.indexOf("publish")) {
  console.error("STOP: publish comes before the gap");
  process.exit(6);
}
console.log("A11Y PASS: stop is named, textual, and ordered.");
'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the A11Y PASS sentence in the terminal. Exit 4 means the button hides the gap. That fails a screen reader user in a hurry. It also fails a keyboard user who never sees the hue. Fix the accessible name, then rerun the check. Do not skip this because the mock looks obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recovery when someone already shipped
&lt;/h2&gt;

&lt;p&gt;Sometimes that hover becomes a real public click. I do not pretend the web has an undo button. Recovery is a correction note beside the line. The named owner writes it, not the model. It names the missing source in plain words. It does not quietly swap an adjective and leave.&lt;/p&gt;

&lt;p&gt;Hand the line back with the STOP text attached. The writer fills the pointer or cuts the claim. A cut claim stays in the review record. Deleting the argument is how the silence returns. Would you trust a history that only keeps the smile?&lt;/p&gt;

&lt;p&gt;If the pointer later breaks, run the checker again. Do not polish the prose and call that recovery. Replace the source pointer, or unpublish the line. A free host going away is not a reason to keep a false line. The line's home is the claim, not the scratch server.&lt;/p&gt;

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

&lt;p&gt;The checker proves a field is non-empty on your machine. It does not prove that the study happened. It does not prove the number is fair. It does not measure whether designers behave better next week. That behavior claim is only a design hypothesis. I have not run that study with a real team. So I will not decorate it with a percent.&lt;/p&gt;

&lt;p&gt;Do not use this gate for medical or legal claims. Do not use it for safety-critical public claims either. Do not use it to let an agent approve its own click. Do not use it if you cannot name a human owner. Do not use it as permission to pull private data into a portfolio. Presence checks are a porch light, not a courthouse.&lt;/p&gt;

&lt;p&gt;Free model access and a free server can change. Build the habit so the script still matters later. The scratch tools may not stay available for you. If a quota is not in your current docs, leave it out. A duration or a hardware promise belongs in those docs too. If they are missing, keep them out of the card.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should actually stop you
&lt;/h2&gt;

&lt;p&gt;Stop when the owner is blank, fictional, or the model. Stop when the source pointer is only a memory. Stop when another model's sentence is the only pointer. Stop when the date window is still missing. Stop when reversibility is already behind the click. Also stop when nobody has a correction plan. Stop when the publish name hides the gap.&lt;/p&gt;

&lt;p&gt;Let the extra noise stay noise on the card. Tone notes and synonym lists can sit in the file. Confidence badges can sit in that file too. They must not flip the exit code to success. I would rather see a blunt gap than a charming paragraph.&lt;/p&gt;

&lt;p&gt;Which missing field would pull your hand off Publish? Which extra sentence would only make the card louder? Write those two answers in a sibling note. Then run the checker once more before you close the laptop.&lt;/p&gt;

&lt;p&gt;Use the free options only as a scratch pair. Discard the host when the rehearsal is done. Keep the card, because the pass is local. A teammate should be able to hear the stop.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>design</category>
      <category>a11y</category>
      <category>testing</category>
    </item>
    <item>
      <title>Name Who Owns Each Case-Study Sentence</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Thu, 08 Oct 2026 09:04:23 +0000</pubDate>
      <link>https://dev.to/haaaaaley/name-who-owns-each-case-study-sentence-45n0</link>
      <guid>https://dev.to/haaaaaley/name-who-owns-each-case-study-sentence-45n0</guid>
      <description>&lt;p&gt;Picture the design review I want us to walk. A designer wants the portfolio to ship tonight. An agent wrote those case studies late last night.&lt;/p&gt;

&lt;p&gt;We ask who owns each sentence on the page. That ownership question becomes our whole review gate. The decision owner is the person named on the page.&lt;/p&gt;

&lt;p&gt;The consequence is a public claim they may have to defend. The reversible moment is before any write hits the live repo. I will walk this as the review owner.&lt;/p&gt;

&lt;p&gt;I am not claiming a real client result. I am showing the protocol I want on the table.&lt;/p&gt;

&lt;p&gt;Think of the review like a kitchen pass. The plate should not leave without a ticket. The agent is the cook in this analogy.&lt;/p&gt;

&lt;p&gt;You are the pass, not a hungry diner. Smooth fluency is only the plating, not the proof. The source note is the ticket that matters.&lt;/p&gt;

&lt;p&gt;Does the source ticket still match the plate? A fluent plate with no ticket should wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze the decision
&lt;/h2&gt;

&lt;p&gt;You start this tutorial from an empty folder. You do not open the agent chat yet. You name the decision in a file first.&lt;/p&gt;

&lt;p&gt;Can you point to the human decision owner? If you cannot name them, stop the review.&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; voice-gate/&lt;span class="o"&gt;{&lt;/span&gt;inbox,record,discard,checks&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'decision: publish case-study sentences'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; voice-gate/inbox/decision.txt
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'owner: named designer on the page'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; voice-gate/inbox/decision.txt
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'live_write: false'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; voice-gate/inbox/decision.txt
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s1"&gt;'live_write: false'&lt;/span&gt; voice-gate/inbox/decision.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'freeze_ok'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You verify that freeze before you move on. The live write flag must still read false. A true flag means you already lost the reversible moment.&lt;/p&gt;

&lt;p&gt;You should see freeze_ok in the terminal. If you do not, fix the file first. You do not negotiate with the unfinished draft.&lt;/p&gt;

&lt;h2&gt;
  
  
  Split evidence from guesses
&lt;/h2&gt;

&lt;p&gt;I treat the agent paragraphs like unlabeled jars. Some jars hold quotes from the designer notes. Some jars hold smooth guesses with no witness.&lt;/p&gt;

&lt;p&gt;Mixing them is how a portfolio starts lying. Would you defend a sentence you did not live?&lt;/p&gt;

&lt;p&gt;Write a small record before any rewrite starts. Each sentence needs a source, a speaker, and a status. Use status values of evidence, hypothesis, or discard.&lt;/p&gt;

&lt;p&gt;An empty source field means you must stop. A fluent guess dressed up as evidence is worse.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; voice-gate/record/sentences.json &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "decision_owner": "Amina Rahman",
  "page": "portfolio/case-study-checkout",
  "sentences": [
    {
      "id": "s1",
      "text": "I interviewed eight cashiers before changing the error copy.",
      "source": "research-notes/2026-09-12.md#cashiers",
      "speaker": "Amina Rahman",
      "status": "evidence"
    },
    {
      "id": "s2",
      "text": "The redesign cut checkout time in half.",
      "source": "",
      "speaker": "agent-draft",
      "status": "evidence"
    }
  ]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import json; json.load(open("voice-gate/record/sentences.json")); print("record_ok")'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Amina is a stand-in name for this protocol. Do not paste a real teammate until they consent. The empty source on s2 is the point.&lt;/p&gt;

&lt;p&gt;A bold result with no note is a hypothesis. It must not ship as a research finding. Marking it as evidence is the bug we want the gate to catch.&lt;/p&gt;

&lt;p&gt;You should see record_ok before the stop check. A parse error means the record is not ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the stop checker
&lt;/h2&gt;

&lt;p&gt;Here is the verifier I keep beside the review. It is a proposal script, not a production service. It fails closed when the record is thin.&lt;/p&gt;

&lt;p&gt;A missing owner should fail this gate immediately. A hypothesis marked as evidence also fails 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="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; voice-gate/checks/stop_gate.py &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;'
#!/usr/bin/env python3
"""Proposal checker. Not a live product test."""
import json
import sys

path = sys.argv[1]
data = json.load(open(path, encoding="utf-8"))
errors = []
owner = data.get("decision_owner", "").strip()
if not owner or owner == "agent-draft":
    errors.append("missing_owner")
for row in data.get("sentences", []):
    sid = row.get("id", "?")
    status = row.get("status")
    if status not in {"evidence", "hypothesis", "discard"}:
        errors.append(f"{sid}: bad_status")
    if status == "evidence" and not str(row.get("source", "")).strip():
        errors.append(f"{sid}: evidence_without_source")
    if row.get("speaker") == "agent-draft" and status == "evidence":
        errors.append(f"{sid}: agent_voice_marked_evidence")
if errors:
    print("stop")
    print("&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;".join(errors))
    sys.exit(2)
print("review_may_continue")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;python3 voice-gate/checks/stop_gate.py voice-gate/record/sentences.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command above writes the checker and runs it. On the sample above, you should get stop.&lt;/p&gt;

&lt;p&gt;The second sentence still has an empty source. Its speaker is the agent draft, not Amina. That stop is a pass of the protocol.&lt;/p&gt;

&lt;p&gt;It is not a failure of the tool. Change s2 to discard or attach a real source.&lt;/p&gt;

&lt;p&gt;Run the checker again after you edit it. Only the review_may_continue line lets you draft.&lt;/p&gt;

&lt;p&gt;What missing evidence should stop an approval here? An unnamed owner should always stop the approval. A result with no note should stop it too.&lt;/p&gt;

&lt;p&gt;A sentence the named person will not say aloud should stop it. What extra information would only add review noise?&lt;/p&gt;

&lt;p&gt;A second paraphrase of the same note is noise. A tone score with no rubric is noise. A model ranking you cannot reproduce is noise.&lt;/p&gt;

&lt;p&gt;I rehearse three scenarios before I trust the gate. First, a metric sentence with no study note. Its stop condition is an empty source field.&lt;/p&gt;

&lt;p&gt;Second comes a quote from a real participant. Stop if that person never consented to be named. Third, an accessibility boast with no pass file.&lt;/p&gt;

&lt;p&gt;Stop until checks/a11y.md exists and names a tester. Success here is narrower than a pretty page. Success means the owner can say every kept line.&lt;/p&gt;

&lt;p&gt;Success also means every discard still has a reason. A pretty draft without that reason is not success.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bound the free draft
&lt;/h2&gt;

&lt;p&gt;The outreach brief calls MonkeyCode an open-source project. Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;I am not pasting a repo URL I have not opened. Use the official project page you can verify. I use a free model only after the gate passes.&lt;/p&gt;

&lt;p&gt;I also use the free server option only then. Both availability claims come from the outreach brief. I am not printing a token quota here.&lt;/p&gt;

&lt;p&gt;I am not printing hardware or a duration. Allowances change, so the live product page wins. Copy the live figure into inbox/allowance.txt first.&lt;/p&gt;

&lt;p&gt;Do that before you plan any run size. If the page disagrees with me, trust the page.&lt;/p&gt;

&lt;p&gt;The server job should not hold publish keys. Give it the notes, not the live repo. Ask for a draft that labels every sentence.&lt;/p&gt;

&lt;p&gt;You should refuse any line that invents a metric. A made-up timing cut is a false public claim.&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; voice-gate/inbox/notes
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'Eight cashier interviews informed the error copy. No timing study was logged.'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; voice-gate/inbox/notes/cashier-note.md
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; voice-gate/inbox/notes/cashier-note.md &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'notes_ok'&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; voice-gate/inbox/run-brief.md &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Role: draft assistant. You may not publish.
Inputs: only files in voice-gate/inbox/notes.
Output: voice-gate/record/draft.json
Rules:
- Every sentence needs source, speaker, and status.
- If the note lacks a number, do not invent one.
- Mark guesses as hypothesis.
- If a sentence needs a life you cannot see, use discard.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; voice-gate/inbox/live_portfolio_key &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'no_live_key_in_workspace'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a note the agent is allowed to read. Keep the missing timing study visible in that note. You should see notes_ok before any draft call.&lt;/p&gt;

&lt;p&gt;Verify the bound before you paste that brief. No live key should sit in the workspace. The brief forbids invented numbers and fake names.&lt;/p&gt;

&lt;p&gt;If setup asks for a deploy token, stop. You are in the wrong stage for that. Remove the token and start the gate over.&lt;/p&gt;

&lt;p&gt;I will not claim I timed this run. I have not attached any benchmark to this. If you run it, record your own result.&lt;/p&gt;

&lt;p&gt;Put that result beside the allowance file too. Do not borrow my silence as a score.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the discarded lines
&lt;/h2&gt;

&lt;p&gt;People delete the awkward sentence and lose the reason. I keep the discard in a concrete record. That record is the recovery path you will need.&lt;/p&gt;

&lt;p&gt;Six months later, someone will ask about the boast. Can you answer them without the discard file?&lt;/p&gt;

&lt;p&gt;Set s2 status to discard, then hold the doubt. Hold the doubt even if the line sounds sharp.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&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
path = "voice-gate/record/sentences.json"
data = json.load(open(path, encoding="utf-8"))
for row in data["sentences"]:
    if row["id"] == "s2" and not str(row.get("source", "")).strip():
        row["status"] = "discard"
        row["speaker"] = "agent-draft"
json.dump(data, open(path, "w", encoding="utf-8"), indent=2)
held = [r for r in data["sentences"] if r["status"] in {"discard", "hypothesis"}]
json.dump(held, open("voice-gate/discard/held.json", "w", encoding="utf-8"), indent=2)
print("held=%d" % len(held))
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;python3 voice-gate/checks/stop_gate.py voice-gate/record/sentences.json
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; voice-gate/discard/held.json &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'discard_kept'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that held.json exists after you run this. It should not be empty when a hypothesis existed. An empty discard plus a shiny draft is a smell.&lt;/p&gt;

&lt;p&gt;You threw the doubt away far too early. Put the doubt back into the discard record. You should also see review_may_continue after the status edit.&lt;/p&gt;

&lt;p&gt;If you still see stop, read the error before you continue. You should see discard_kept beside that continue line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review access before ship talk
&lt;/h2&gt;

&lt;p&gt;I do not implement the component in this article. A frontend owner can build the control later. I review the pattern a person must pass.&lt;/p&gt;

&lt;p&gt;The publish control needs a visible text name. Color alone must never carry the status meaning. Please use evidence, hypothesis, or discard in text.&lt;/p&gt;

&lt;p&gt;Screen reader users need the owner name nearby. It must sit in the same view as approve. If approve sits on another route, say so.&lt;/p&gt;

&lt;p&gt;Say that in text, not in a tooltip. Tooltips often fail on touch and on keyboard. I treat that failure as a block, not polish.&lt;/p&gt;

&lt;p&gt;Check the reading load before you praise fluency. A case study only a model can parse is not inclusive. Read the kept sentences aloud before any approval.&lt;/p&gt;

&lt;p&gt;If you stumble, a participant might stumble too. Cut that sentence and record the cut reason.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&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
rows = json.load(open("voice-gate/record/sentences.json", encoding="utf-8"))["sentences"]
long_rows = [r["id"] for r in rows if len(r["text"].split()) &amp;gt; 24]
if long_rows:
    print("revise_length " + " ".join(long_rows))
else:
    print("a11y_text_pass")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This length check is only a rough proxy. It does not prove contrast or focus order. It also does not prove captions or labels.&lt;/p&gt;

&lt;p&gt;Pair it with a short manual accessibility pass. Use keyboard only, then two hundred percent zoom. Add a screen reader spot check on approve.&lt;/p&gt;

&lt;p&gt;Write the result in checks/a11y.md before approval. No written file means no approval from me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hand the voice back
&lt;/h2&gt;

&lt;p&gt;Approval is not the end of this protocol. The named owner must hear their own sentences. I send the kept lines and the discard file.&lt;/p&gt;

&lt;p&gt;I ask which line they would not say in an interview. A no on any line reopens the gate. The agent does not get a vote then.&lt;/p&gt;

&lt;p&gt;If the free server run fails, you still have notes. A timeout does not invent a new truth. You do not rebuild truth from the last paragraph.&lt;/p&gt;

&lt;p&gt;You return to the notes and rerun the checker. You do not publish what you merely remember.&lt;/p&gt;

&lt;p&gt;Who should skip this approach and walk away? Skip it when you need production component code. That build work belongs with the frontend owner.&lt;/p&gt;

&lt;p&gt;Skip it if no human will defend the page. Skip it if you need a measured model comparison. This protocol does not produce that kind of comparison.&lt;/p&gt;

&lt;p&gt;Skip it for covert collection of private text. Consent lives in the owner field for a reason. No consent means no sentence enters the record.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I will claim
&lt;/h2&gt;

&lt;p&gt;Would you let a fluent boast survive an interview? Each recommendation here rests on a visible check.&lt;/p&gt;

&lt;p&gt;It does not rest on a merely confident vibe. The owner field is the accountability check I trust. The source field splits evidence from a hypothesis.&lt;/p&gt;

&lt;p&gt;The stop script acts as the mechanical refusal. The discard file becomes the recovery record later. The accessibility notes stay a manual gate here.&lt;/p&gt;

&lt;p&gt;Remember that the word-count script is only a proxy. I am explicit where the evidence is thin. I did not run a live free-server job here.&lt;/p&gt;

&lt;p&gt;I did not verify a token total either. Please confirm both on the current MonkeyCode docs. Do that before a study plan depends on them.&lt;/p&gt;

&lt;p&gt;If you rehearse this, wait for review_may_continue first. Read the live allowance with your own eyes. Keep the discard file when the draft sounds fluent.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>design</category>
      <category>ai</category>
      <category>ux</category>
    </item>
    <item>
      <title>Show the Silence Note Before You Ship the Claim</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Thu, 08 Oct 2026 06:19:54 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-the-silence-note-before-you-ship-the-claim-16id</link>
      <guid>https://dev.to/haaaaaley/show-the-silence-note-before-you-ship-the-claim-16id</guid>
      <description>&lt;p&gt;I keep one staging scene for this kind of review. The hero line says the page is fully accessible. A model wrote that line while the team slept.&lt;/p&gt;

&lt;p&gt;Would you ship that sentence on a Thursday? I am the decision owner if I approve it. A stranger can test the promise after publish.&lt;/p&gt;

&lt;p&gt;The reversible moment sits on the review card. After publish, that promise is already a public fact. I want the undo to be a held button, not an apology.&lt;/p&gt;

&lt;p&gt;A recent public write-up poked the same bruise. Some models may notice a problem and stay quiet. I will not borrow that author's numbers as mine.&lt;/p&gt;

&lt;p&gt;So what belongs on the card before approval? I want the gap the draft swallowed, in writing. Call that gap the silence note, and keep it visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hallway I make people walk
&lt;/h2&gt;

&lt;p&gt;Think of two doors in a short hallway. One door is polish, and it feels kind. The other door is evidence, and it feels rude.&lt;/p&gt;

&lt;p&gt;Teams pick the kind door when the clock bites. I walk them through the rude door first. Why reward a smooth sentence that cannot stand?&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  claim[Claim on the page] --&amp;gt; notice[Ask what was noticed]
  notice --&amp;gt; silence[Write the silence note]
  silence --&amp;gt; card[Review card beside the claim]
  card --&amp;gt; stop{Stop condition hit?}
  stop --&amp;gt;|yes| hold[Hold publish and recover]
  stop --&amp;gt;|no| human[Named human approves]
  human --&amp;gt; ship[Ship with the record kept]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;That picture is a pattern, not a study result. I have not scored a cohort with it. You can run it on one claim today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze one claim
&lt;/h2&gt;

&lt;p&gt;I start with one sentence, not the whole page. A claim is a promise a stranger can test. Friendly tone is a mood, not a claim.&lt;/p&gt;

&lt;p&gt;Why polish a line you may have to kill? I freeze the words in a local file first. The model does not get a vote yet.&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; silence-note &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;silence-note
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; claim.txt &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
This portfolio is fully accessible.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; claim.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is boring, which is how I like it. The word count should stay a single claim. If an and hides a second promise, split the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Split evidence from guesses
&lt;/h2&gt;

&lt;p&gt;I sort the desk before any model speaks. Evidence is a record a person can open. A hypothesis is only a guess about trust.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; buckets.json &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "claim": "This portfolio is fully accessible.",
  "evidence": [],
  "hypotheses": [
    "Readers may trust a badge more than the page."
  ],
  "owner": "reviewing-designer",
  "reversible_until": "before-publish"
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool buckets.json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"buckets-ok"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The check should print buckets-ok, then stop. If the JSON breaks, fix the shape first. A broken card makes a draft look finished.&lt;/p&gt;

&lt;p&gt;What record would actually support this claim? A keyboard pass with a name and a date. A polished model paragraph is not that record.&lt;/p&gt;

&lt;p&gt;What supports this stop, besides my own taste? Accessibility standards already warn against casual conformance claims. I am stating a design rule, not a new user study.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask for silence, not shine
&lt;/h2&gt;

&lt;p&gt;Now a free drafting pass can earn its place. It may draft the note, not bless the claim. Can a cheaper draft still hide the gap?&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is the open-source project this outreach covers. I mention it here because the sandbox step uses it.&lt;/p&gt;

&lt;p&gt;The outreach states free model access and a free server. It also states a free pool of 10 million tokens. I did not re-measure those terms for this piece.&lt;/p&gt;

&lt;p&gt;Confirm the live terms before you plan around them. Offers move, and a tutorial should not freeze them. I will not call them permanent or unlimited.&lt;/p&gt;

&lt;p&gt;Use the free model only to draft the silence note. Use the free server only as a review sandbox. Would you paste private interview notes in there?&lt;/p&gt;

&lt;p&gt;I would not, and you should not either. Keep raw research transcripts off that server. A sandbox is not a vault with a badge.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; silence-prompt.txt &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
You are drafting a silence note for a human reviewer.
Claim: This portfolio is fully accessible.
Do not rewrite the claim.
Return JSON with keys: noticed, cannot_verify, would_hide_if_rushed, stop_approval.
If you lack a source, write "missing" and stop.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That prompt is a protocol, not a logged run. I am not publishing scores from a secret trial. You should save the raw reply untouched.&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;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; model-silence.json &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool model-silence.json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"reply-json-ok"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that check only after you save the raw reply. Four keys must exist, or you discard the file. If the model rewrote the claim, discard it too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seat the note beside the claim
&lt;/h2&gt;

&lt;p&gt;A chat tab is where silence wins the argument. The approver must see the claim and the note. Side by side, or the polish door opens again.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; review-card.json &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "claim": "This portfolio is fully accessible.",
  "silence_note": {
    "noticed": ["The phrase asserts conformance."],
    "cannot_verify": ["No keyboard pass is linked.", "No contrast record is linked."],
    "would_hide_if_rushed": ["An AI-reviewed badge can sound like certification."],
    "stop_approval": true
  },
  "evidence": [],
  "owner": "reviewing-designer",
  "recovery": "Replace the claim with the tested scope, or hold publish."
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wrote that sample by hand to show fields. It is not model output and not a test result. Treat it like a form, not a finding.&lt;/p&gt;

&lt;p&gt;If you host the card, keep the audience tiny. One named approver is enough for this pass. Why point the public at a sandbox at all?&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail closed with a local check
&lt;/h2&gt;

&lt;p&gt;I want a command that refuses to be nice. Missing silence should block the word approved. Pretty copy should not sneak past a green check.&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;Local check for a silence-note review card. Unexecuted until you run it.&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;json&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;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;claim&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;silence_note&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;evidence&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;recovery&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;note_keys&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;noticed&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;cannot_verify&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;would_hide_if_rushed&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;stop_approval&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&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="n"&gt;required&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&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;card&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;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;STOP missing fields:&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;missing&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="n"&gt;note&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;silence_note&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;note&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="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;STOP silence_note is not an object&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="n"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&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="n"&gt;note_keys&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&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;note&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;gap&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;STOP silence note incomplete:&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;gap&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;note&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stop_approval&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="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;card&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;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;STOP approval blocked: silence note fired and 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="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;3&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;card&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;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;STOP no decision 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;2&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;CARD SHAPE OK&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;Owner must still decide. This script is not approval.&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;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;Save the checker as check_card.py before you run it. Then read the exit code out loud with a teammate. A quiet pass is not the same as a true claim.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 check_card.py review-card.json
&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;You should see the stop line and exit code 3. That means the guard worked, not that the claim passed. If you see a cheerful all-clear, the script is wrong.&lt;/p&gt;

&lt;p&gt;Which extra field would only add noise here? A vibe score, or a fake confidence percent. Those numbers calm people, then they ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the card as a tired person
&lt;/h2&gt;

&lt;p&gt;The review card can shut people out too. I read it late, with one hand on the keyboard. I read it as if English is my second language.&lt;/p&gt;

&lt;p&gt;Can I reach the stop reason without a hover? Is the stop state text, not only a color? Does recovery say the next action in plain words?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&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
card = json.load(open("review-card.json"))
assert "color" not in card
assert str(card["recovery"]).strip()
assert str(card["silence_note"]["cannot_verify"]).strip()
print("a11y-text-ok")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command only checks that stop is not a color key. It cannot see your layout, so you still read it. Did the print say a11y-text-ok, or did you skip the read?&lt;/p&gt;

&lt;p&gt;A red badge alone is not a silence note. If the note sits below a long fold, I fail it. The pattern failed, even if the JSON was perfect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Say the stop out loud
&lt;/h2&gt;

&lt;p&gt;I ask the approver to read four lines aloud. They name the claim, the gap, the stop, and recovery. If they stumble, the card is not ready.&lt;/p&gt;

&lt;p&gt;Does the sentence still sound true after that reading? If your voice drops on accessible, hold the ship. Embarrassment is a signal, not a style problem.&lt;/p&gt;

&lt;p&gt;I compare this to a kitchen handoff at closing. The door note matters more than the garnish. A clean plate can still hide a missing allergen card.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the discarded sentence
&lt;/h2&gt;

&lt;p&gt;Suppose the claim already sat on a staging URL. Recovery is not a brighter slogan with a wink. Recovery is a smaller true sentence plus the record.&lt;/p&gt;

&lt;p&gt;I keep the discarded claim inside the card. Next week someone will paste it back from memory. The record is the memory you can audit.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; recovery.txt &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Removed claim: This portfolio is fully accessible.
Replacement: Keyboard path not yet recorded. This page is not a conformance statement.
Record: review-card.json stays in the decision log.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; recovery.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"recovery-recorded"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The check should print recovery-recorded, then rest. The old claim must remain quoted in the file. Deleting the quote is how the false promise returns.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I will not pretend
&lt;/h2&gt;

&lt;p&gt;A silence note is not a window into weights. The model may omit a gap it never held. It may also invent a gap to look careful.&lt;/p&gt;

&lt;p&gt;That is why empty evidence still blocks approval. The human owns the stop, not the script. The script only refuses to pretend you looked.&lt;/p&gt;

&lt;p&gt;Do not use this for a legal conformance audit. Do not use it on private research transcripts. Do not host production traffic on the free server.&lt;/p&gt;

&lt;p&gt;Skip it if no named person will read the card. A queue of unread cards is a slower publish button. Who benefits from a ritual nobody finishes?&lt;/p&gt;

&lt;p&gt;Free model access can make one draft cheap to try. The stated token pool is a budget for retries. It is not a reason to skip the human reader.&lt;/p&gt;

&lt;p&gt;I am not reporting a measured token cost. I did not run that measurement, so I will not fake it. If the project page drops those terms, re-scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  One claim, then a decision
&lt;/h2&gt;

&lt;p&gt;Pick one claim you meant to ship this week. Run the local check and read the note beside it. Then decide while the stop reason is still visible.&lt;/p&gt;

&lt;p&gt;If you want the drafting sandbox, read the current docs. Match this protocol to what the project offers today. A kind hallway still needs a locked evidence door.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>design</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Show the Page State Before Approving an Agent Click</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Wed, 23 Sep 2026 15:54:57 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-the-page-state-before-approving-an-agent-click-4emh</link>
      <guid>https://dev.to/haaaaaley/show-the-page-state-before-approving-an-agent-click-4emh</guid>
      <description>&lt;p&gt;This is a composite scene from design reviews. I almost approved a click I could not see. The agent wanted Confirm on a live refund form. I had no picture of the page the person actually faced.&lt;/p&gt;

&lt;p&gt;Who owned that decision in that second? I did, as the human still holding the gate. Money would leave an account after one click. The only reversible moment sat before that click, not after.&lt;/p&gt;

&lt;p&gt;Browser agents make this feel ordinary now. A model reads a page, then asks to act. Designers still treat the preview as proof. Is a tidy button enough evidence for irreversible action?&lt;/p&gt;

&lt;p&gt;I do not think so. A preview is a hypothesis wearing a screenshot costume. The missing piece is the visible page state at decision time. Without that state, approval is theater.&lt;/p&gt;

&lt;p&gt;I am proposing a from-zero tutorial, not a completed study. Treat every protocol below as unexecuted until you run it. I will still write it as a working path you can verify stage by stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Name the decision before you name a model
&lt;/h2&gt;

&lt;p&gt;Start with the human choice, not the stack. Write one sentence that names owner, consequence, and reversibility. Mine reads like this, on purpose, in plain language.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Decision: approve or refuse an in-page agent click.
Owner: the on-call designer or reviewer.
Consequence: a refund submits on the user's behalf.
Reversible until: the click is sent to the page.
Stop if: visible page state is missing, stale, or unmatched.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Does that sentence still work if the model is free and noisy? It must. Cheap generation does not cheapen the click. It only makes confident copy easier to mint.&lt;/p&gt;

&lt;p&gt;When I need a scratch space for that sentence, I draft on MonkeyCode. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The project is open source, with free model access and a free server option. I use that pair to generate candidate review cards. I do not use it as evidence that a click is safe.&lt;/p&gt;

&lt;p&gt;Verify this stage before you go on. The card must name a person, not a model. If the owner field says "the AI," stop. Rewrite until a human can refuse.&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;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"Owner:|Reversible until:|Stop if:"&lt;/span&gt; decision.txt
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; decision.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"stage0: decision file is not empty"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If grep returns nothing, you do not have a decision. You have a vibe. Do not proceed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capture the visible page state, not the model's summary
&lt;/h2&gt;

&lt;p&gt;The agent will offer a summary. Summaries are polite. They also hide the broken checkbox, the unread fee, and the already-selected annual plan. I want the state the user could have seen.&lt;/p&gt;

&lt;p&gt;Think of it like a night-shift nurse. You do not accept "patient is fine." You want the last charted numbers. An agent click is a medical order. Chart first.&lt;/p&gt;

&lt;p&gt;Here is the proposed capture object. Label it proposed. I have not shipped this schema to production.&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;"review_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"refund-click-017"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"click"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target_role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Confirm refund"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"page_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.local/refund"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"viewport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1280x720"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"visible_text_excerpt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Refund $48.00 to Visa ending 4412. This cannot be undone."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"visible_warnings"&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;"This cannot be undone."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dom_digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:pending"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"captured_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-23T15:04:11Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stale_after_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model_summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User wants to confirm the refund."&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;Notice the last field. I keep the model summary. I refuse to approve from it. Which field should stop approval if blank? &lt;code&gt;visible_text_excerpt&lt;/code&gt; and &lt;code&gt;visible_warnings&lt;/code&gt;. Extra chrome like model confidence only adds noise.&lt;/p&gt;

&lt;p&gt;Verify the capture before drafting copy. A tiny check is enough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"const fs=require('fs'); const c=JSON.parse(fs.readFileSync('page-state.json','utf8')); if(!c.visible_text_excerpt || !c.captured_at) process.exit(1); if(c.model_summary &amp;amp;&amp;amp; !c.visible_warnings) process.exit(2); console.log('stage1: page state has excerpt and timestamp');"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code two means you trusted a summary without warnings. That is the trap. Stop there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Draft the review card from evidence, then from a free model
&lt;/h2&gt;

&lt;p&gt;Now you may ask a free model to help. Ask it to format the card. Do not ask it to decide. The server is a desk, not a judge.&lt;/p&gt;

&lt;p&gt;I paste the capture object. I ask for three fields only: &lt;code&gt;what_the_user_can_see&lt;/code&gt;, &lt;code&gt;what_the_click_will_do&lt;/code&gt;, and &lt;code&gt;what_would_make_me_refuse&lt;/code&gt;. If the draft invents a fee you did not capture, throw the draft away.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are formatting a review card, not approving a click.
Use only the JSON I paste.
If a fact is missing, write MISSING, never guess.
Return markdown with those three fields, nothing else.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That prompt is a fence. Free models hop fences when you praise them. Do not praise them here. Verify the draft against the capture, word by word.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"const fs=require('fs'); const card=fs.readFileSync('review-card.md','utf8'); const state=JSON.parse(fs.readFileSync('page-state.json','utf8')); if(!card.includes(state.visible_text_excerpt.slice(0,24))) process.exit(1); if(/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="nv"&gt;$[&lt;/span&gt;&lt;span class="s2"&gt;0-9]/.test(card) &amp;amp;&amp;amp; !state.visible_text_excerpt.includes('&lt;/span&gt;&lt;span class="nv"&gt;$'&lt;/span&gt;&lt;span class="s2"&gt;)) process.exit(2); console.log('stage2: card quotes visible money, invents none');"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did the card mention a dollar the page never showed? Kill it. Generated helpfulness is still a lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Research the approval with stop conditions
&lt;/h2&gt;

&lt;p&gt;I do not run a vibe check with five friends. I run four scenarios that try to make a reasonable person approve too early. This is a protocol, not a finding. Say that out loud to your team.&lt;/p&gt;

&lt;p&gt;Scenario A: the excerpt is stale by thirty seconds. Scenario B: the warning sits below the fold. Scenario C: the target name is Confirm, the visible text says trial convert. Scenario D: the digest changes after the card renders.&lt;/p&gt;

&lt;p&gt;Success is ugly and specific. Reviewers refuse A, C, and D every time. They pause B and scroll before any approve control enables. If anyone approves A, the pattern failed. You do not ship a nicer tooltip. You stop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// proposed/stop-conditions.test.js — unexecuted example&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;missingExcerpt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible_text_excerpt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;stale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;captured_at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stale_after_seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;nameMismatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible_text_excerpt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;refund&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="sr"&gt;/refund/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target_name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;digestDrift&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dom_digest&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dom_digest&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shouldEnableApprove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;liveDigest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;missingExcerpt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;stops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;stops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nameMismatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digestDrift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dom_digest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;liveDigest&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify this stage with the four scenarios, not with a demo smile. Record refuses. Record the exact missing field. If a reviewer says "I just trusted the summary," that is a stop, not a coaching note.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node proposed/stop-conditions.test.js &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"stage3: stop conditions are not green yet"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Check accessibility at the pattern, not the palette
&lt;/h2&gt;

&lt;p&gt;An approve button that appears beside missing evidence is an accessibility bug. Keyboard users will hit it because it is the next control. Screen reader users will hear "Approve" without the excerpt. That is not inclusive. That is a trap with good contrast.&lt;/p&gt;

&lt;p&gt;I want the approve control disabled until the excerpt is in the same reading order. The warning must be announced before the button name. Focus must land on the missing-evidence text when a stop fires. Can a person who cannot see the screenshot still refuse for the same reason I refuse?&lt;/p&gt;

&lt;p&gt;If the answer is no, you designed a visual ritual. You did not design a decision. Fix the reading order before you polish motion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- proposed review region, not production markup --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"review-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"review-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Review this click&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"excerpt"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Refund $48.00 to Visa ending 4412. This cannot be undone.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"stop"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Approve is unavailable. Visible page state is stale.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;aria-describedby=&lt;/span&gt;&lt;span class="s"&gt;"excerpt stop"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Approve click&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify with a keyboard-only pass and a screen reader pass. If Approve is reachable while disabled text is unread, fail the stage. Do not argue about icons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rehearse hand-back before you need it
&lt;/h2&gt;

&lt;p&gt;Approval is not the end of the flow. Refusal is a product path. When the digest drifts, the agent must hand the page back with the last good excerpt still visible. The person should not hunt through a chat scroll for the warning they already earned.&lt;/p&gt;

&lt;p&gt;The recovery copy has to quote the stop, not soothe it. "I cannot click Confirm. The form changed after I read it." That sentence is a brake. "Let me try again" is a shove. Which one belongs at the reversible moment?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hand-back record
Stop: digestDrift
Last visible excerpt: Refund $48.00...
User-facing line: I cannot click Confirm. The form changed after I read it.
Next human action: recapture page state, or take the click yourself.
Discarded line: Looks good, submitting now.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the discarded line in the record. Future you will try to reuse it. Future you is tired. The record is the adult in the room.&lt;/p&gt;

&lt;p&gt;Verify recovery by forcing a drift on purpose. Change one visible price. Confirm Approve stays dark. Confirm the status text matches the stop name. If the agent still says submitting, you failed recovery, not copywriting.&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 drift rehearsal — run against a local fixture only&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;page-state.json page-state.before.json
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt;.bak &lt;span class="s1"&gt;'s/48.00/72.00/'&lt;/span&gt; page-state.json
node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"const a=require('./page-state.before.json'); const b=require('./page-state.json'); if(a.visible_text_excerpt===b.visible_text_excerpt) process.exit(1); console.log('stage5: drift detected, approve must stay blocked');"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;This walkthrough is not a benchmark of free models. It is not a claim that in-browser agents are ready. It is not permission to skip legal review on refunds. I have no participant counts to flash at you. I have a decision mechanism you can test on Tuesday.&lt;/p&gt;

&lt;p&gt;Do not use this if you cannot recapture page state. Do not use it if no human owns refusal. Do not use it to launder a click through a pretty card. Designers who need a model to invent the fee should close the laptop.&lt;/p&gt;

&lt;p&gt;The analogy I keep using is a signed check. The model can fill the memo line. The page state is the amount. You still hold the pen. Would you sign a check because the memo sounded confident?&lt;/p&gt;

&lt;p&gt;Ask one last pair of questions before you enable Approve. Which missing evidence should stop the click today? Which extra token of model self-praise would only add noise? If your team cannot answer both, you are not researching trust. You are decorating a guess.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>a11y</category>
      <category>ux</category>
      <category>design</category>
    </item>
    <item>
      <title>Show the Barge-In Transcript Before Approving a Voice Tool Call</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Tue, 22 Sep 2026 14:13:16 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-the-barge-in-transcript-before-approving-a-voice-tool-call-15og</link>
      <guid>https://dev.to/haaaaaley/show-the-barge-in-transcript-before-approving-a-voice-tool-call-15og</guid>
      <description>&lt;p&gt;Last Tuesday the product lead hovered on Approve. The voice agent offered to book a room. The visible transcript looked complete and strangely calm.&lt;/p&gt;

&lt;p&gt;Did anyone open the barge-in log then? Nobody on that review call opened it. The user had already said wait clearly.&lt;/p&gt;

&lt;p&gt;The calendar tool still fired after that. That is the decision I refuse to rubber-stamp. Who owns this moment on your team?&lt;/p&gt;

&lt;p&gt;The designer who ships the review card owns it. The consequence is a side effect someone tried to stop. Reversibility exists only if the interrupt sits in the record first.&lt;/p&gt;

&lt;p&gt;I do not start with a trust workshop. I start with a file you can fail. Can we show the barge-in transcript before any tool call?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 0: name the irreversible moment
&lt;/h2&gt;

&lt;p&gt;I write one sentence onto the card. May this voice turn invoke a tool? That sentence needs an owner, a stop, and a hand-back.&lt;/p&gt;

&lt;p&gt;If I cannot name those three parts, I cancel. Two teammates must agree on the owner. Disagreement means the protocol is not ready.&lt;/p&gt;

&lt;p&gt;Verification is spoken, not decorative copy. I read the sentence aloud in critique. If owners diverge, we stop before recruiting anyone.&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="na"&gt;decision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;voice_turn_may_invoke_tool&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;session_reviewer&lt;/span&gt;
&lt;span class="na"&gt;consequence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external_side_effect&lt;/span&gt;
&lt;span class="na"&gt;reversibility&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;only_if_barge_in_captured&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is that card too small for leadership slides? Good. Small cards get honest refusals. Large decks hide the empty log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: put evidence fields on the card
&lt;/h2&gt;

&lt;p&gt;I do not ask for confidence scores here. Confidence is almost always extra noise. I ask whether the interrupt existed in time.&lt;/p&gt;

&lt;p&gt;Here is the card I actually project. Empty fields are missing proofs, not polish. If a field cannot stop Approve, I delete it.&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;"session_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"voice-2026-09-22-014"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_visible_transcript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"barge_in_transcript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"barge_in_timestamp_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tool_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"create_event"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tool_paused"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hand_back_utterance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stop_condition_hit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reviewer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hold"&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;Which missing evidence should stop approval right now? For me it is &lt;code&gt;barge_in_transcript&lt;/code&gt;, &lt;code&gt;tool_paused&lt;/code&gt;, and &lt;code&gt;hand_back_utterance&lt;/code&gt;. Which extra information would only add noise?&lt;/p&gt;

&lt;p&gt;Model nicknames, token tallies, and vibe scores add noise. I leave them off this card. The human decides with less, not more.&lt;/p&gt;

&lt;p&gt;Verification fails closed on purpose. I run a tiny checker before anyone clicks Approve. Kind checkers are how incidents get a green button.&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;# validate_barge_in_card.py
# Proposed checker. Label: unexecuted example, not product code.
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&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;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;barge_in_transcript&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;tool_paused&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;hand_back_utterance&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;stop_condition_hit&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;reviewer&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="k"&gt;with&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&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;handle&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;card&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;in&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="bp"&gt;None&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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP approval. Missing evidence: &lt;/span&gt;&lt;span class="sh"&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;, &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;missing&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;Review card has the stop fields.&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 validate_barge_in_card.py session.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it prints STOP, the human does not approve. That is the product behavior I want. A warning toast is not a stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: draw the interrupt, not the whole agent
&lt;/h2&gt;

&lt;p&gt;I keep the flow tiny on purpose. The agent is still speaking. The user cuts in while a tool is pending.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  A[Voice agent speaking] --&amp;gt; B{User barge-in?}
  B --&amp;gt;|No| C[Continue the turn]
  B --&amp;gt;|Yes| D[Write barge-in transcript]
  D --&amp;gt; E{Tool call pending?}
  E --&amp;gt;|No| F[Hand back to the user]
  E --&amp;gt;|Yes| G[Pause the tool]
  G --&amp;gt; H[Review card with interrupt]
  H --&amp;gt; I{Stop condition hit?}
  I --&amp;gt;|Yes| J[Refuse the tool call]
  I --&amp;gt;|No| K[Human approves or rejects]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Look hard at the write step. If that step is missing, pause labels are theater. Have you approved a paused tool with a blank log?&lt;/p&gt;

&lt;p&gt;I have watched that movie in design reviews. The pause was a badge. The transcript field was empty.&lt;/p&gt;

&lt;p&gt;Verification is a finger on the diagram. I point at the write step during critique. If nobody can quote a sample string, the flow fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: research three stoppable scenarios
&lt;/h2&gt;

&lt;p&gt;I do not recruit for feelings about voice assistants. I recruit for one decision under pressure. Each scenario carries a stop measure you can end on.&lt;/p&gt;

&lt;p&gt;Scenario A starts mid-sentence on purpose. The user says wait. The agent talks two more seconds, then calls &lt;code&gt;create_event&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Success means the barge-in transcript contains wait. Stop means the tool fired before the card appeared. We end the study there, and we do not collect leftover praise.&lt;/p&gt;

&lt;p&gt;Scenario B involves interrupted speech, not a joke accent. A stammer is not consent. The agent must not treat a pause as a yes.&lt;/p&gt;

&lt;p&gt;Success means &lt;code&gt;stop_condition_hit&lt;/code&gt; is true while speech is incomplete. Stop means reviewers blame the speaker. That halt is ethics, not taste.&lt;/p&gt;

&lt;p&gt;Scenario C is a shared room with two voices. A roommate says don't. The intended user said nothing at all.&lt;/p&gt;

&lt;p&gt;Success means the card shows speaker uncertainty. Stop means we cannot tell speakers apart, yet we still approve. Diarization charts would only add noise here.&lt;/p&gt;

&lt;p&gt;One uncertainty flag is enough for the decision. I keep discarded turns in the same record. Why throw away don't because another person said it?&lt;/p&gt;

&lt;p&gt;That discarded line is the evidence. Hide it and the reviewer invents a clean user. Clean users are a design hypothesis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# research/voice-barge-in/session-notes.md&lt;/span&gt;
&lt;span class="gh"&gt;# Proposed note file. Label: unexecuted protocol.&lt;/span&gt;

&lt;span class="gu"&gt;## Scenario A&lt;/span&gt;
heard: wait
shown_to_reviewer: yes / no
tool_paused_before_side_effect: yes / no
approve: hold

&lt;span class="gu"&gt;## Discarded turns&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; wait at 00:12.4 — kept
&lt;span class="p"&gt;-&lt;/span&gt; cough at 00:12.9 — discarded, reason: non-speech
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a grep, not a memory. If the path is empty, the notes are decoration. Decoration does not become a finding.&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; research/voice-barge-in
&lt;span class="nb"&gt;touch &lt;/span&gt;research/voice-barge-in/session-notes.md
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"shown_to_reviewer"&lt;/span&gt; research/voice-barge-in/session-notes.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Empty grep means I do not file findings. Findings without a shown field are hypotheses in costume. Costume is how we ship the calendar event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: split evidence from design guesses
&lt;/h2&gt;

&lt;p&gt;Evidence is an interrupt string the reviewer saw. Evidence is a tool that did or did not pause. Evidence is a hand-back the user could hear.&lt;/p&gt;

&lt;p&gt;A red banner will raise trust is a hypothesis. A waveform is more honest is a hypothesis. Those do not live in the evidence column.&lt;/p&gt;

&lt;p&gt;When someone says the model sounded careful, I ask for the barge-in field. Careful is not a field I can audit. If it is not in the card, it did not happen for approval.&lt;/p&gt;

&lt;p&gt;I still need hostile fixtures for empty states. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I use MonkeyCode's free model access and free server option to draft counter-transcripts for the card, and I treat those lines as fixtures, not as user proof.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# fixtures/generate_counter_turns.txt
Write five barge-in lines a user might say
while a voice agent is about to call create_event.
Include wait, don't, that is wrong, and a silence marker.
Do not invent a successful booking.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I paste each line into &lt;code&gt;barge_in_transcript&lt;/code&gt;. Then I run the validator again on disk. If Approve still lights up with a blank pause flag, the surface failed.&lt;/p&gt;

&lt;p&gt;The fixture did not fail. The review card failed. Verification requires three fixtures to trip STOP before I trust the checker.&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_barge_in_card.py fixtures/wait.json
python3 validate_barge_in_card.py fixtures/dont.json
python3 validate_barge_in_card.py fixtures/silence.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If none trip, I made the validator kind. Kind validators ship incidents. I would rather fail a dry run than a live booking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5: review accessibility on the pattern
&lt;/h2&gt;

&lt;p&gt;Voice is not a decorative extra channel. It is speech, motor control, and attention. I check the card itself, not a separate mode.&lt;/p&gt;

&lt;p&gt;Can a deaf reviewer still read the barge-in transcript? A waveform-only proof excludes them. Can a keyboard user reach Refuse before Approve?&lt;/p&gt;

&lt;p&gt;If Approve is the first tab stop, we already chose. Can interrupted speech recover without becoming consent? Scenario B already asked that out loud.&lt;/p&gt;

&lt;p&gt;Can captions disagree with the barge-in field? Then I trust neither until a human types the conflict. The missing evidence is the accessibility bug.&lt;/p&gt;

&lt;p&gt;Verification uses a static mock, not a full app. I tab through it once, slowly. Refuse comes first, and the transcript is text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Proposed pattern mock. Not production UI. --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Barge-in transcript
    &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"barge_in_transcript"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"decision"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"refuse"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Refuse tool call&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"decision"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"approve"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Approve&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Approve stays disabled until the validator passes. That is the responsible default I will defend. Not a toast. A dead control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 6: recover without a sorry paragraph
&lt;/h2&gt;

&lt;p&gt;If the tool already fired, the card arrived late. Recovery is a different object on purpose. I still keep the discarded interrupt beside the side effect.&lt;/p&gt;

&lt;p&gt;You said wait. We still created the event. Here is Undo, if Undo exists for that tool.&lt;/p&gt;

&lt;p&gt;If that tool cannot reverse, I write not reversible on the card. Then I revoke auto-calls for the rest of the session. Future turns do not inherit a broken default.&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="na"&gt;recovery_state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tool_already_fired&lt;/span&gt;
&lt;span class="na"&gt;interrupt_was_present&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;reversible&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;next_session_default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tools_require_explicit_tap&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do we need a longer audit PDF after that? Usually we do not. The next reviewer needs the interrupt, the side effect, and the new default.&lt;/p&gt;

&lt;p&gt;Verification is one debrief question, asked twice. What would have stopped approval? If they answer a better model, the study failed.&lt;/p&gt;

&lt;p&gt;The answer must name a field we refused to leave empty. Fields stop people. Vibes do not.&lt;/p&gt;

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

&lt;p&gt;This is not a latency benchmark in disguise. This is not a bake-off between voice models. This is not consent to record a household.&lt;/p&gt;

&lt;p&gt;Do not use it to justify always-on microphones. Do not use it when the tool is only a local filter. There is no irreversible moment there.&lt;/p&gt;

&lt;p&gt;Teams without a human reviewer should not fake the card. An empty reviewer field is itself a stop. I mean that literally, and the checker should too.&lt;/p&gt;

&lt;p&gt;Generated fixtures are not live research. Real sessions still need consent on the record. They need a stop phrase the participant already knows, and a hand-back a person can hear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leave the question on the card
&lt;/h2&gt;

&lt;p&gt;Which missing evidence should stop approval? For this flow I want three: no barge-in transcript, no pause proof, no hand-back line.&lt;/p&gt;

&lt;p&gt;Which extra information would only add noise? Glow scores, pet names for models, token tallies, a paragraph about being helpful.&lt;/p&gt;

&lt;p&gt;Show the interrupt. Then ask whether the tool may run. Not the other way around.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>a11y</category>
      <category>ux</category>
      <category>design</category>
    </item>
    <item>
      <title>Show the Hand-Back Sample Before Approving an Agent Score</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Mon, 21 Sep 2026 12:15:14 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-the-hand-back-sample-before-approving-an-agent-score-3pcd</link>
      <guid>https://dev.to/haaaaaley/show-the-hand-back-sample-before-approving-an-agent-score-3pcd</guid>
      <description>&lt;p&gt;The score arrived before the user did. Nine point one, for a hand-back that never happened. The design lead hovered over Approve. She owned that decision. The consequence was live traffic. After a failed tool call, the agent would keep talking. Reversibility lived in one thin place. Attach the sample the model never scored, or lose the claim.&lt;/p&gt;

&lt;p&gt;Have you watched a number outrun a person? I have. It feels like grading a fire drill from the parking lot. The siren looks fine. Nobody opened the door.&lt;/p&gt;

&lt;p&gt;We keep using yesterday’s tests on today’s agents. Checklists still smile. Heuristic scores still look scientific. The agent now writes the score itself. That is not research. That is a hypothesis wearing a decimal.&lt;/p&gt;

&lt;p&gt;I do not let that decimal into a critique until it survives a calibration sample. The sample is a known hand-back. A human already judged it. The agent must match that judgment, or the score dies. Harsh? Good. A free model will happily grade its own exit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 0. Write the decision before you open a model
&lt;/h2&gt;

&lt;p&gt;Do not start in the prompt box. Start in a file. Name the owner. Name the harm. Name the last moment you can reverse.&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;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"approve_handback_score"&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;"design_lead"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"surface"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent_failure_banner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consequence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent_continues_after_tool_error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reversible_until"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"score_is_cited_in_release_notes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hypothesis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"users can take control after a failed call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence_fields"&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;"sample_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"human_verdict"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"control_visible"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"time_to_handback"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"noise_fields"&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;"model_confidence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"word_count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sentiment"&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="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the card. Missing fields mean you are not ready.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.owner and .consequence and .reversible_until'&lt;/span&gt; decision-card.json
jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.evidence_fields | index("human_verdict")'&lt;/span&gt; decision-card.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did the second command print a number? Then a human verdict is required. If it prints &lt;code&gt;null&lt;/code&gt;, stop. Would you approve a score with no human in the loop?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1. Show the hand-back sample, not the live task
&lt;/h2&gt;

&lt;p&gt;The live task is contaminated. The agent has already seen your newest copy. The calibration sample must be older, smaller, and judged by a person.&lt;/p&gt;

&lt;p&gt;I use a kitchen-timer metaphor here. You do not trust a new clock until it matches one you already trust. The sample is that trusted clock. Strike it first.&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;"sample_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"handback-tool-timeout-02"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_job"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stop the agent after a silent tool failure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"control_copy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Take over"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"control_was_visible"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time_to_handback_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"known_human_verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"known_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no visible control after 8 seconds of silence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_must_not_fill"&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;"time_to_handback_ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_quote"&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="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it like a ritual, not a vibe check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.known_human_verdict == "fail"'&lt;/span&gt; handback-sample.json
jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.control_was_visible == false'&lt;/span&gt; handback-sample.json
jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.agent_must_not_fill | index("user_quote")'&lt;/span&gt; handback-sample.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the control was not visible, a high score is a lie. What exactly did the agent watch? A banner it invented? A user it never met?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2. Split evidence from the design hypothesis
&lt;/h2&gt;

&lt;p&gt;This is where most reviews go soft. The hypothesis sounds kind. The evidence is thin. I make the review card refuse to blend them. Evidence must be observed. Hypothesis must not gate approval.&lt;/p&gt;

&lt;p&gt;The evidence side is small on purpose. I want &lt;code&gt;sample_id&lt;/code&gt;, a human verdict, whether the control was visible at failure, and time-to-handback or an explicit &lt;code&gt;unknown&lt;/code&gt;. The hypothesis side can hold feelings. Safer. Friendlier. Generally good at UX. None of that may unlock Approve.&lt;/p&gt;

&lt;p&gt;A tiny checker helps. Treat it as a bouncer, not a product. This is a proposed script, not a lab result.&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;# verify_score_card.py — proposal for a review gate
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&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;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;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="n"&gt;hyp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;card&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;hypothesis&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="n"&gt;blocked&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;feel&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;friendlier&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;generally good&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;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hyp&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;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;blocked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;card&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;human_verdict&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hypothesis is leaking into a score with no human verdict&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;evidence and hypothesis are still split&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 against the card you will actually present.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python verify_score_card.py review-card.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it exits nonzero, you caught a story pretending to be a measurement. Are you still going to paste 9.1 into the release notes?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3. Declare stop conditions before the model speaks
&lt;/h2&gt;

&lt;p&gt;I ask two questions out loud in the room. Which missing evidence should stop approval? Which extra field is only noise?&lt;/p&gt;

&lt;p&gt;For hand-back scores, missing &lt;code&gt;control_visible&lt;/code&gt; stops me. Missing &lt;code&gt;human_verdict&lt;/code&gt; stops me. An extra &lt;code&gt;model_confidence&lt;/code&gt; field is noise. It does not tell me whether a person could take the wheel. Token counts are noise too. They measure the essay, not the exit.&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;"stop_if_missing"&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;"human_verdict"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"control_visible"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sample_id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"treat_as_noise"&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;"model_confidence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"token_count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sentiment"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success_measure"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent_verdict_matches_human_on_calibration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fail_measure"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent_assigns_pass_when_control_was_invisible"&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;Verify the stops exist before anyone types a prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.stop_if_missing | length &amp;gt;= 3'&lt;/span&gt; stops.json
jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.success_measure'&lt;/span&gt; stops.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No success measure? Then you will negotiate with the score. Negotiation is how 9.1 survives a silent failure. I have watched that negotiation. It always sounds reasonable. It is still a skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4. Review the score card like an interface
&lt;/h2&gt;

&lt;p&gt;The card is UI. Treat it that way. Color-only pass or fail fails accessibility. A green badge with no text fails. A score a screen reader cannot speak is decoration, not a finding.&lt;/p&gt;

&lt;p&gt;I ask four questions of the card itself. Is the verdict written as fail or pass in text? Can I reach Reject without a pointer? Does &lt;code&gt;sample_id&lt;/code&gt; stay on screen when the score refreshes? Is the stop reason a sentence, not a red dot? If any answer is no, the card is not reviewable.&lt;/p&gt;

&lt;p&gt;I am not citing a lab study for this exact widget. I am applying named controls and non-color indicators. That evidence is about the card. It is not about users in the product. Do not mix those layers. Mixing them is how teams quote contrast ratios as if they were task success.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5. Rehearse the hand-back, then verify the log
&lt;/h2&gt;

&lt;p&gt;Now you may call a model. Not before. I want the failure in a log I can read later. The discarded sample stays in the record. If the agent passes a case a human failed, the log must show the mismatch. Hiding the mismatch is how scores outgrow the tests we still recite.&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; records
&lt;span class="nb"&gt;cp &lt;/span&gt;handback-sample.json records/sample.json
&lt;span class="nb"&gt;cp &lt;/span&gt;decision-card.json records/decision.json
&lt;span class="c"&gt;# candidate-score.json is the rehearsal output, not a finding&lt;/span&gt;
jq &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--slurpfile&lt;/span&gt; s records/sample.json &lt;span class="nt"&gt;--slurpfile&lt;/span&gt; a candidate-score.json &lt;span class="s1"&gt;'
  if $s[0].known_human_verdict != $a[0].verdict
  then "STOP: agent verdict does not match calibration"
  else "ok to discuss, not to ship"
  end
'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that last string again. “Ok to discuss” is not “ok to ship.” Say it in the room. The analogy I use is a spare key. You may hold it. You do not leave it in the lock.&lt;/p&gt;

&lt;p&gt;When I need a cheap rehearsal, I run the same cards against a free model on a free server. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode’s free model access and free server option are enough to generate a candidate score I can fail in public. The card still decides. The model still does not.&lt;/p&gt;

&lt;p&gt;I am not claiming a quota, a model name, or a benchmark here. I only need a disposable place to be wrong. If the free server is slow, that is fine. Latency is not evidence of quality. Speed is not coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flow I actually use
&lt;/h2&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  A[Decision card with owner] --&amp;gt; B[Attach hand-back sample]
  B --&amp;gt; C{Human verdict present?}
  C --&amp;gt;|no| X[Stop approval]
  C --&amp;gt;|yes| D[Split evidence from hypothesis]
  D --&amp;gt; E{Stop fields missing?}
  E --&amp;gt;|yes| X
  E --&amp;gt;|no| F[a11y review of the score card]
  F --&amp;gt; G[Rehearse candidate score]
  G --&amp;gt; H{Matches calibration?}
  H --&amp;gt;|no| X
  H --&amp;gt;|yes| I[Discuss only. Do not ship the number.]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Notice the last node. Matching calibration is not a ship gate for the product. It is a ship gate for the claim. The product still needs people. The number still waits in the hallway.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;If missing&lt;/th&gt;
&lt;th&gt;If extra&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;human_verdict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stop. No owner of the judgment.&lt;/td&gt;
&lt;td&gt;Do not average two verdicts to look certain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;control_visible&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stop. You cannot score a hand-back.&lt;/td&gt;
&lt;td&gt;A screenshot without a timestamp is still noise.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sample_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stop. The live task is contaminated.&lt;/td&gt;
&lt;td&gt;Extra samples without verdicts only add theater.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model_confidence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ignore. Never a stop.&lt;/td&gt;
&lt;td&gt;Treat as noise. It inflates the decimal.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That table is a stop map, not a dashboard. I will not add a composite score on top of it. Composites hide the hole you needed to see.&lt;/p&gt;

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

&lt;p&gt;This protocol does not replace participant research. It does not turn a free model into a rater you can cite. It does not give you a severity number you can paste into a bug tracker. If you need statistically valid usability scores, do not use this. If you are writing regulated medical or financial copy, do not use a free server as your review room. If you cannot name a human owner, do not run the model at all.&lt;/p&gt;

&lt;p&gt;I also will not pretend a passing calibration means the live hand-back works. The sample is a tuning fork. It is not the concert. Your production silence may last longer. Your control copy may wrap. Your user may be on a phone with one thumb. None of that is in the fork.&lt;/p&gt;

&lt;p&gt;So which missing evidence should stop you this week? For me it is a missing human verdict on a known silent failure. Extra confidence scores are noise. They make the decimal look taller. They do not put a control on the screen.&lt;/p&gt;

&lt;p&gt;Keep the sample in the record. Keep the owner on the card. Let the number wait.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>a11y</category>
      <category>ux</category>
      <category>design</category>
    </item>
    <item>
      <title>Show Consent Scope Before an AI Moderator Follows Up</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Sun, 20 Sep 2026 10:26:16 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-consent-scope-before-an-ai-moderator-follows-up-27ai</link>
      <guid>https://dev.to/haaaaaley/show-consent-scope-before-an-ai-moderator-follows-up-27ai</guid>
      <description>&lt;p&gt;I almost clicked approve on the interview packet. The script looked tidy. The calendar hold was already sent. Then I asked a quieter question. What had the participant actually agreed to discuss?&lt;/p&gt;

&lt;p&gt;The research lead owned that click. A free-model moderator would run the session. One extra follow-up could leave the consent fence. The only clean reverse sat before the first unscripted probe, not after the transcript landed in a shared drive.&lt;/p&gt;

&lt;p&gt;That is the decision I now freeze. Not the insight readout. Not the highlight reel. The moment a model wants one more question.&lt;/p&gt;

&lt;p&gt;Why this moment, and not later? Because later is theater. You cannot un-ask a probe. You can only hide it, which is worse. Hidden probes still shape the quotes you trust.&lt;/p&gt;

&lt;p&gt;Think of consent as a fence around a yard. The scripted questions live inside. Follow-ups are the dog that sprints the perimeter. If you never mark the fence, the dog decides the yard.&lt;/p&gt;

&lt;p&gt;This walkthrough is a from-zero packet. I treat it as a proposed research ops tutorial, not a completed study. I do not present sample chats as findings. I do not invent participant quotes. The artifact is the consent card, the probe ceiling, and a local checker you can run before anyone joins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 0: Name the owner and the reverse
&lt;/h2&gt;

&lt;p&gt;I start a folder before I touch a model. The first file is not a prompt. It is a decision stub.&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; study/&lt;span class="o"&gt;{&lt;/span&gt;consent,probes,runs,handback&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/consent/decision.yaml &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
decision: approve_ai_moderated_interview
owner: research_lead
consequence: off_protocol_probe_enters_evidence
reverse_point: before_first_unscripted_follow_up
status: blocked_until_scope_verified
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is boring on purpose. Does the owner field name a human? Does reverse_point sit before speech, not after coding? If either answer is no, I stop. A model cannot be the owner. A transcript cannot be the reverse.&lt;/p&gt;

&lt;p&gt;Who gets hurt if we skip this? The participant, first. Then the study. Then the product claim that will quote a question nobody agreed to hear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: Write the consent scope as fields, not vibes
&lt;/h2&gt;

&lt;p&gt;I refuse paragraphs of “we may ask related topics.” Related to what? Related is how a model wanders. I write fields a checker can fail.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/consent/scope.yaml &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
study_id: checkout-recovery-2026-09
consent_topics:
  - recovering a failed payment
  - what the error message meant
  - whether they tried again the same day
out_of_scope:
  - household income
  - other household members
  - medical or disability detail
  - employer names
max_followups_per_topic: 1
recording: audio_only
identifiable_quotes: prohibited_until_redaction_review
participant_can_stop: say_stop_or_leave_call
hand_back_to: human_moderator
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a field count, not a vibe check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&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 yaml
from pathlib import Path
scope = yaml.safe_load(Path("study/consent/scope.yaml").read_text())
required = ["consent_topics","out_of_scope","max_followups_per_topic","hand_back_to"]
missing = [k for k in required if not scope.get(k)]
assert not missing, missing
assert scope["max_followups_per_topic"] == 1
assert scope["hand_back_to"] != "model"
print("scope_ok")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that print never appears, there is no session. Would a longer policy PDF help here? Usually it only adds noise. The missing evidence is the topic list the participant saw, not a legal novel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: Separate evidence from design hypotheses
&lt;/h2&gt;

&lt;p&gt;I keep two files on purpose. Mixing them is how a probe becomes a leading question.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/consent/evidence.yaml &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
evidence:
  - field: last_shown_error_copy
    status: unknown_until_session
  - field: participant_restated_consent_topics
    status: unknown_until_session
hypotheses:
  - users blame themselves for gateway timeouts
  - retry copy feels like a threat
rule: hypotheses_may_not_become_probes
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hypothesis file is a warning label. It is not a question bank. If a designer wants to “just check” self-blame, that check waits for a human moderator after hand-back. Why? Because a free model will turn a hypothesis into a why-did-you question. That why is not on the fence.&lt;/p&gt;

&lt;p&gt;Verification asks one rude question. Can I grep a hypothesis noun in the probe file? If yes, I delete the probe. I do not soften it. Softening is how leading questions survive review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: Cap the probes before the model sees the packet
&lt;/h2&gt;

&lt;p&gt;I write every allowed follow-up in advance. Improvisation is the product risk, not the feature. The model may reorder silence. It may not invent a third question.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/probes/allowed.yaml &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
scripted:
  - What did you think the payment error was asking you to do?
  - Did you try again that same day?
followups_allowed:
  - Can you say that last part in your own words?
forbidden_stems:
  - why did you
  - how much
  - does anyone else
  - are you disabled
  - where do you work
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the original checker. It is small. That is the point. A research gate should fail closed without a dashboard.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/probes/check_probes.py &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
from pathlib import Path
import re, sys, yaml

scope = yaml.safe_load(Path("study/consent/scope.yaml").read_text())
probes = yaml.safe_load(Path("study/probes/allowed.yaml").read_text())
hyp = yaml.safe_load(Path("study/consent/evidence.yaml").read_text())

errors = []
if len(probes.get("followups_allowed", [])) &amp;gt; scope["max_followups_per_topic"]:
    errors.append("followup_ceiling_exceeded")

blob = " ".join(probes.get("scripted", []) + probes.get("followups_allowed", [])).lower()
for stem in probes.get("forbidden_stems", []):
    if stem.lower() in blob:
        errors.append(f"forbidden_stem:{stem}")

for item in hyp.get("hypotheses", []):
    key = re.sub(r"[^a-z]+", " ", item.lower())
    for token in key.split():
        if len(token) &amp;gt; 5 and token in blob:
            errors.append(f"hypothesis_leaked:{token}")
            break

if errors:
    print("FAIL")
    print("&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;".join(errors))
    sys.exit(1)
print("probes_ok")
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;python3 study/probes/check_probes.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this exits nonzero, the packet is not shy. It is invalid. Do we need model-quality scores before this gate? No. Scores would only add noise. The missing evidence is still the fence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: Dry-run on a free lane, never on a person
&lt;/h2&gt;

&lt;p&gt;I needed a staging lane that was not a live participant. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I used MonkeyCode's free model access and free server option as that staging lane for the moderator packet. I still ran the local checker first. A free lane does not bless a leaky probe.&lt;/p&gt;

&lt;p&gt;The dry-run rule is simple. The model sees scope.yaml and allowed.yaml only. It never sees hypotheses. It never sees real names. The output is a rehearsal transcript, labeled rehearsal.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/runs/dry_run_prompt.txt &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
You are a research moderator in rehearsal, not a live session.
Ask only scripted questions from allowed.yaml.
You may use at most one follow-up from followups_allowed.
If the user mentions an out_of_scope topic, say:
"I will hand this back to a human moderator."
Then stop.
Do not invent questions.
Do not treat this chat as evidence.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification after the dry run is a grep, not a feeling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&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
text = Path("study/runs/rehearsal.txt").read_text().lower() if Path("study/runs/rehearsal.txt").exists() else ""
flags = []
for stem in ["why did you", "how much", "where do you work"]:
    if stem in text:
        flags.append(stem)
print("dry_run_flags:", flags or "none")
assert not flags, flags
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No rehearsal file yet? Then there is nothing to approve. An empty dry run is a stop, not a pass. Would a longer chat log make me safer? Only if I am hunting stems. Extra witty turns are noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5: Accessibility review of the consent moment
&lt;/h2&gt;

&lt;p&gt;The fence fails if the participant cannot perceive it. I review the consent screen as a pattern, not as a component library. Can a screen-reader user hear the topic list as a list of topics, not a blob? Can they get the stop phrase before the call starts? Is the language short enough to reread without a timer?&lt;/p&gt;

&lt;p&gt;I keep a review card next to the YAML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONSENT PATTERN REVIEW
Decision owner: research_lead
What the participant must hear: the three consent_topics
Stop control: say stop or leave the call, equally valid
Time pressure: none; start is participant-started
Reading load: topics as separate items, not one paragraph
Audio alternative: same topics spoken, then confirmed
Failure: if they cannot restate one topic, do not start the model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a restatement test. In rehearsal I ask a colleague to say the topics back. If they invent a fourth topic, the copy failed. If they miss an out-of-scope warning, the copy failed. I do not “clarify live” with the model. Clarifying live is a new probe.&lt;/p&gt;

&lt;p&gt;What if the consent copy is beautiful and still silent on recording? Then it is not consent. Beauty is noise. The missing evidence is whether recording was named.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 6: Hand-back and recovery before any quote is coded
&lt;/h2&gt;

&lt;p&gt;When a probe hits the fence, the session is not “mostly fine.” The turn is contaminated. I write the hand-back packet before the call, because mid-call invention is how teams keep going.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/handback/card.yaml &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;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
trigger:
  - out_of_scope_topic_mentioned
  - model_asks_unlisted_question
  - participant_says_stop
  - participant_cannot_restate_consent
action:
  - stop_model_speech
  - mark_turn_contaminated
  - do_not_code_quote
  - human_moderator_joins_or_ends
record_kept:
  - the unasked follow-up that was refused
  - the reason for refusal
reverse: discard_contaminated_turn_not_the_whole_day_by_default
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I keep refused follow-ups in the record. Why keep a question nobody asked? Because the discarded probe shows the fence worked. If we only store answers, the log pretends the model never reached. That pretence is how later readers think the protocol was looser than it was.&lt;/p&gt;

&lt;p&gt;Verification is a file, not a standup story.&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;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; study/handback/card.yaml &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;handback_ok
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What should stop approval, and what is noise
&lt;/h2&gt;

&lt;p&gt;Missing evidence that must stop me: no named human owner, no topic list the participant will hear, no probe ceiling, no forbidden stems, no restatement check, no hand-back owner, no dry-run grep. Extra information that only adds noise: model brand chat, elo scores, a second color pass on the consent button, a longer empathy paragraph in the script, a promise that the model is “careful.”&lt;/p&gt;

&lt;p&gt;Careful is not a control. A ceiling is a control.&lt;/p&gt;

&lt;p&gt;This method will not fit every team. Do not use it for clinical, legal, or investigative interviews that need a licensed human from the first word. Do not use it if you cannot name a research lead who will discard a turn. Do not use a free-model rehearsal as if it were a participant. Do not ship product copy from a dry run. The free lane is a fence rehearsal. It is not a person.&lt;/p&gt;

&lt;p&gt;I still catch myself wanting one clever follow-up. That urge is the tell. The model is outgrowing the test when the test is “did the interview happen.” The test I want is narrower. Did every spoken probe stay inside the consent the participant could restate?&lt;/p&gt;

&lt;p&gt;If you also stage packets before a live hour, a free server lane can hold the rehearsal files. The consent card remains the product. The model remains the dog at the fence.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>a11y</category>
      <category>ux</category>
      <category>design</category>
    </item>
  </channel>
</rss>
