<?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: Alexey Spinov</title>
    <description>The latest articles on DEV Community by Alexey Spinov (@alex_spinov).</description>
    <link>https://dev.to/alex_spinov</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%2F3975624%2F45a8ae00-5171-4172-8040-15cbfbbb4916.jpg</url>
      <title>DEV Community: Alexey Spinov</title>
      <link>https://dev.to/alex_spinov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alex_spinov"/>
    <language>en</language>
    <item>
      <title>Lost Update: When Two AI Agents Edit One File, One Silently Wins</title>
      <dc:creator>Alexey Spinov</dc:creator>
      <pubDate>Thu, 30 Jul 2026 02:29:50 +0000</pubDate>
      <link>https://dev.to/alex_spinov/lost-update-when-two-ai-agents-edit-one-file-one-silently-wins-21n3</link>
      <guid>https://dev.to/alex_spinov/lost-update-when-two-ai-agents-edit-one-file-one-silently-wins-21n3</guid>
      <description>&lt;p&gt;A lost update between AI agents: two agents edit the same resource, both writes return success, and one is silently gone with nothing in the system saying a word. It happens when both read the same version and the last writer overwrites the rest. The fix is a pre-write compare-and-set gate, not a bigger log.&lt;/p&gt;

&lt;p&gt;I ran it. With 5 agents committing to one shared file under a worst-case schedule, where every agent reads before anyone writes, 4 of the 5 contributions vanished from the final state, and every one of those 5 writes had been acknowledged. The final file held exactly one agent's work. The other four were paid for, ACKed, and silently overwritten. And that worst case is not rare: in a fair sample of interleavings at N=5, some write was lost in essentially 100% of runs.&lt;/p&gt;

&lt;p&gt;This is the &lt;a href="https://en.wikipedia.org/wiki/Isolation_%28database_systems%29" rel="noopener noreferrer"&gt;lost update&lt;/a&gt; anomaly, and it is older than AI agents by decades. What is new is that each vanished write burned real model tokens, and the loss leaves no error, no exception, no red log line. So I built a small offline simulator to make the loss countable, and to test the one class of fix that actually stops it: a version check before the write lands.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI disclosure:&lt;/strong&gt; I wrote &lt;code&gt;lostwrite_sim.py&lt;/code&gt; with an AI assistant and ran it myself, offline, on Python 3.13.5, standard library only. No network, no keys, no threads, no funds. Concurrency is simulated by explicit interleaving schedules, not real parallelism, so the output is reproducible. Every number and hex string below is pasted from a real local run. Three runs produced byte-identical STDOUT with sha256 &lt;code&gt;1df08c5e38894622314a2b052684303853b9b8fa9508eaabdfd877970c88983b&lt;/code&gt;. The linked specs and articles are other people's work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When N agents read the same version of a resource, each does its work, and each writes back, the last writer wins and everyone in between is silently overwritten. In the worst case at N=5 that was 4 lost writes out of 5, all acknowledged.&lt;/li&gt;
&lt;li&gt;Whether it happens at all depends on the schedule. In a fair sample of interleavings the loss hit 75.0% of runs at N=2 and effectively 100% by N=5, but a quarter of N=2 runs serialized and lost nothing. So one green test run proves nothing.&lt;/li&gt;
&lt;li&gt;An append-only log records every write, so you can prove each write happened. It does not help you recover one. Reading the current value still returns a lost update, because logging preserves the fact of a write, not the presence of its change.&lt;/li&gt;
&lt;li&gt;A pre-write compare-and-set (check the version has not moved since you read it) drops lost updates to 0 by construction. Measured, it lost nothing in all 46,333 fair interleavings I sampled, at every N.&lt;/li&gt;
&lt;li&gt;The gate does not save money. It costs more. In the worst case at N=5 it added 4 work-units of retries on top of 5, so total work went from 5 to 9. It buys integrity and a countable collision, not a refund.&lt;/li&gt;
&lt;li&gt;Turn the version check off inside the same gate and the loss comes straight back (4 again). That falsifier is the proof that the compare, not the scaffold around it, is doing the work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a toy concern anymore. The moment you run two sub-agents, or a swarm, or a retrying planner that spawns workers, you have multiple actors writing shared state: a plan file, a scratchpad, a database row, a GitHub issue. &lt;a href="https://dev.to/opsveritas/one-agent-times-out-three-more-agents-dont-notice-o61"&gt;@opsveritas wrote up the paired version of this&lt;/a&gt;: one agent times out, three more do not notice, and work gets paid for that no one will ever read. Lost update is the same failure seen from the write side.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a lost update between two AI agents?
&lt;/h2&gt;

&lt;p&gt;A lost update happens when two actors read the same version of a resource, both compute a new value from what they read, and both write it back. The second write is based on a snapshot that predates the first write, so committing it erases the first. Neither actor sees an error. The classic definition lives in database isolation theory, and agents inherit it the instant they share a resource without a write precondition.&lt;/p&gt;

&lt;p&gt;The mental model I used in the simulator is deliberately concrete: N agents each append their own section to one shared file. The correct final file contains all N sections. A lost update is a section that made it into an acknowledged write and then disappeared from the final file.&lt;/p&gt;

&lt;p&gt;Here is the store. It holds a version number and the set of contributions currently present.&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;class&lt;/span&gt; &lt;span class="nc"&gt;VersionedStore&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;__init__&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozenset&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;wal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;  &lt;span class="c1"&gt;# append-only log of every accepted write
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_lww&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;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Last-writer-wins: overwrite unconditionally, always ACK.&lt;/span&gt;&lt;span class="sh"&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozenset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_value&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;version&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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;wal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_id&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;ACK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_cas&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;expected_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enforce&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Commit only if the version has not moved since this agent read.&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;enforce&lt;/span&gt; &lt;span class="ow"&gt;and&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;version&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expected_version&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;CAS_FAIL&lt;/span&gt;&lt;span class="sh"&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozenset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_value&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;version&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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;wal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_id&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;ACK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each agent runs three steps: &lt;code&gt;READ&lt;/code&gt; the store, &lt;code&gt;WORK&lt;/code&gt; (build &lt;code&gt;snapshot | {my_id}&lt;/code&gt; and spend one unit), then &lt;code&gt;WRITE&lt;/code&gt;. Concurrency is not threads. It is an explicit interleaving, and I run it two ways on purpose. The first is a deterministic &lt;strong&gt;worst case&lt;/strong&gt;: every agent reads before anyone writes, so every read is stale. That is the adversarial upper bound, not a typical run, and I use it for the headline because it is the clearest picture of the failure. The second is a &lt;strong&gt;fair sampler&lt;/strong&gt;: at each tick pick one still-ready agent uniformly at random (&lt;code&gt;random.Random&lt;/code&gt;, seeded, so the whole run is reproducible to the byte), and draw tens of thousands of independent interleavings to get a distribution. The worst case shows the failure; the fair sample shows how often it actually bites.&lt;/p&gt;

&lt;p&gt;A note on that, because the first version of this tool got it wrong and it matters. I originally picked agents with a hand-rolled linear congruential generator and &lt;code&gt;next() % n&lt;/code&gt;, which reads the least-random low bits. At N=2 that strictly alternates for every seed, so it silently pinned the worst case and printed it as if it were a seeded sample. &lt;code&gt;random.Random.randrange&lt;/code&gt; draws from the full generator state and does not have that bias. The fair numbers below come from the fixed version.&lt;/p&gt;

&lt;p&gt;One thing I want to be honest about up front, because it decides how you read every number here. The costs in this tool are counts, not dollars. &lt;code&gt;COST_UNIT&lt;/code&gt; is an accounting constant set to 1, labelled in the source as "not dollars, not a measured price." Every money figure is an integer counter times that constant. If you want your dollars, multiply the run counts by your own measured per-run token cost. I did not measure a token price, and I am not going to pretend a synthetic schedule tells you one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anomaly at its worst: N=5
&lt;/h2&gt;

&lt;p&gt;5 agents, the worst-case schedule (everyone reads before anyone writes), gate off versus gate on. This is the headline block straight from the run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WORST CASE  N=5, maximum contention (every agent reads stale)
--------------------------------------------------------------------
                              NO_GATE (LWW)      GATE (compare-and-set)
  writes acknowledged         5                  5
  contributions in final      1                  5
  contributions expected      5                  5
  lost_updates (ACKed, gone)  4                  0
  integrity_ok (present==all) False              True
  cas_failures                0                  4
  retries                     0                  4
  refusals                    0                  0
  total_runs (work executed)  5                  9
  total_cost (runs*COST_UNIT) 5                  9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the left column first. Five writes acknowledged, one contribution in the final state, four lost. The &lt;code&gt;lost_updates&lt;/code&gt; number is not printed from a flag that says "no gate, so print 4." It is re-derived: the tool takes the set of agents whose write was ACKed at least once, subtracts the set actually present in the final value, and counts what is left. Four writes said success and are not in the file.&lt;/p&gt;

&lt;p&gt;The right column is the same schedule with a version check on the write. Zero lost. All five contributions present. And I need to be blunt about why that zero is a zero, because it would be dishonest to sell it as a surprising discovery: it is true by construction. A compare-and-set write only commits when the version has not moved, which means it commits on top of a snapshot that already contains every prior commit. It cannot drop one. The interesting, measured part of the right column is not the zero. It is the price of the zero, which I will get to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking is not control
&lt;/h2&gt;

&lt;p&gt;Here is the part I keep having to relearn. "Just log every write" feels like it should help. It does not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRACKING IS NOT CONTROL  (NO_GATE, worst case, N=5)
--------------------------------------------------------------------
  writes recorded in append-only log (WAL) : 5
  WAL entry shape                          : (version, agent_id), no value
  contributions surviving in final state   : 1
  final state contains contributions       : (4,)
  writes logged and ACKed but gone on read : 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The append-only log has all five writes. Every one. It proves that five agents each wrote, and it tells you which agent and at what version. That is enough to know you were charged for five and kept one. It is not enough to hand any of the four back: my WAL entry is &lt;code&gt;(version, agent_id)&lt;/code&gt;, the fact of the write, not the value it carried. A richer log that stores the content, like the separate git-refs &lt;a href="https://dev.to/dipankar_sarkar/two-coding-agents-editing-the-same-issue-no-merge-conflict-here-is-how-git-refs-make-that-work-325k"&gt;@dipankar_sarkar described for two coding agents editing the same issue&lt;/a&gt;, keeps both versions and reconciles after, and for a merge-later workflow it is the right tool.&lt;/p&gt;

&lt;p&gt;But look at the last line, and notice it holds no matter how fat the log gets. The log knows about all five writes, and reading the current value still returns four lost updates. Recording a write and preventing its loss are different operations at different times. The log is a receipt written after the fact. It cannot un-overwrite the file, any more than &lt;a href="https://finops.spinov.online/blog/model-receipt-probe/" rel="noopener noreferrer"&gt;a receipt read after the fact&lt;/a&gt; can recover a field that was never stored. This is the same franchise as &lt;a href="https://finops.spinov.online/blog/a-47k-agent-loop-spend-cap/" rel="noopener noreferrer"&gt;a spend cap that counts tokens but cannot stop the loop&lt;/a&gt;: tracking tells you what happened, control changes what is allowed to happen. Only a write that runs before the commit gets to reject the stale one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate, and its honest price
&lt;/h2&gt;

&lt;p&gt;The fix is a precondition on the write. Optimistic concurrency, the lock-free version: read a version, do your work, and at commit time refuse if the version moved. It has a formal name, &lt;a href="https://en.wikipedia.org/wiki/Optimistic_concurrency_control" rel="noopener noreferrer"&gt;optimistic concurrency control&lt;/a&gt;, and the primitive under it is &lt;a href="https://en.wikipedia.org/wiki/Compare-and-swap" rel="noopener noreferrer"&gt;compare-and-swap&lt;/a&gt;. A pessimistic lease or lock would also work; it just makes the agent wait instead of retry. Either way the shared idea is the same: no write lands on state you did not read.&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="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_cas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enforce&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;disable_version_check&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;res&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;acked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&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;cas_failures&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;READ&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;      &lt;span class="c1"&gt;# re-read the latest, re-work, re-write
&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;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refused&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;   &lt;span class="c1"&gt;# fail-closed: nothing silently applied
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the cost, which is the whole reason this post is not called "how the gate saves you money." It does not. Look again at the headline: NO_GATE ran 5 work-units, GATE ran 9. The gate added 4 work-units of retries, because each stale writer had to re-read fresh state and redo its work before it could land. The delta is positive, and it is positive by design:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Delta cost of the gate      = 9 - 5 = 4 work-units (&amp;gt;=0: the gate
                                costs MORE, it does not save tokens)
  Spend destroyed without gate= lost_updates * COST_UNIT = 4 work-units
                                (already paid, silently gone from state)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two numbers, two different things, and it is easy to blur them into a false savings pitch. The 4 destroyed work-units under NO_GATE are money you already spent on work that then vanished. The gate does not refund them. It cannot reach back into a run that already happened. What it does is stop the next four from vanishing, and it charges you 4 units of retries to do it. If your worry is the token bill, the gate is a cost, not a saving. If your worry is that four agents did real work and the result quietly disappeared, the gate is the thing that keeps that from being silent.&lt;/p&gt;

&lt;p&gt;That is the contrarian bit, and I will state it as a falsifiable claim: a pre-write concurrency gate on shared agent state increases token spend and is still worth it, because it converts a silent unbounded loss into a visible, counted, retryable event. If your data shows a gate that reduces total spend under contention, I would genuinely like to see the workload, because my own run says the opposite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the gate doing the work, or is my test scaffold cheating?
&lt;/h2&gt;

&lt;p&gt;This is the failure mode I trust least in myself, so the tool has a falsifier for it. The classic mistake is to build a "gate" whose zero comes from the test scaffold rather than the mechanism. So F1 keeps the entire gate code path and disables one thing: the version check inside &lt;code&gt;write_cas&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FALSIFIER F1  gate code path, version-check DISABLED (worst case, N=5)
--------------------------------------------------------------------
  lost_updates with the compare removed    : 4  (was 0 with it on)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four again. Same as no gate. The retry loop, the counters, the refusal path, all still there. Remove only the comparison of expected version to current version, and the loss returns in full. That is the proof I actually care about: the compare is the mechanism. Everything else is plumbing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does it get worse with more agents?
&lt;/h2&gt;

&lt;p&gt;Two questions live here, and they have different answers. How bad can it get, and how often does it get bad at all. The worst case answers the first. The fair sample answers the second, and it is the more useful number.&lt;/p&gt;

&lt;p&gt;First the worst case, swept over N. Every agent reads before anyone writes, so all but the last writer are clobbered. The N=1 row is the negative control: one writer, no one to overwrite, zero loss.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WORST-CASE SWEEP over N (maximum contention, every read stale)
----------------------------------------------------------------------------
  N | ng.lost ng.integ | g.casfail g.retry g.refuse g.lost g.integ | dCost
  --------------------------------------------------------------------------
  1 |      0 True    |        0       0        0      0 True    |     0  (degenerate: 1 writer)
  2 |      1 False   |        1       1        0      0 True    |     1
  3 |      2 False   |        2       2        0      0 True    |     2
  5 |      4 False   |        4       4        0      0 True    |     4
  8 |      7 False   |        7       7        0      0 True    |     7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the worst case the loss is exactly &lt;code&gt;N-1&lt;/code&gt;: everyone but the last writer is gone, and the gate pays one retry per clobbered writer to fix it. But nobody schedules their agents to lose. The honest question is what a fair draw of interleavings does, so I sampled &lt;code&gt;40000 // N&lt;/code&gt; of them per N and counted the distribution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAIR SWEEP  uniform random interleavings (random.Random, Mersenne Twister)
----------------------------------------------------------------------------
  base_seed=20260728, trials per N = 40000 // N
  N | trials | NO_GATE loses&amp;gt;=1 | serializes (0 lost) | mean lost | max | GATE loses&amp;gt;=1
  --------------------------------------------------------------------------
  2 |  20000 |   75.0% (14994) |   25.0% ( 5006)  |     0.75 |   1 | 0
  3 |  13333 |   97.3% (12972) |    2.7% (  361)  |     1.59 |   2 | 0
  5 |   8000 |  100.0% ( 7999) |    0.0% (    1)  |     3.35 |   4 | 0
  8 |   5000 |  100.0% ( 5000) |    0.0% (    0)  |     6.14 |   7 | 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the number I would actually put in a design doc. At N=2 a quarter of interleavings serialize on their own and lose nothing, which is exactly the trap: two agents, run it a few times, watch it pass, ship it. Then it loses data in three runs out of four. By N=3 the safe fraction is down to 2.7%, and by N=5 it is one interleaving in eight thousand. So the anomaly is not universal on small N, and it is close to certain past a handful of writers. That is a worse story than "it always happens," not a better one, because "usually fine" is the thing that gets shipped.&lt;/p&gt;

&lt;p&gt;The gate column is the point of the whole exercise. Across all 46,333 fair interleavings I drew, over every N, compare-and-set lost an update exactly zero times. That is a measured claim, not a hand-wave: the by-construction argument says a CAS write cannot drop a prior commit, and 46,333 independent draws agree. NO_GATE integrity is False on every row with a second writer; GATE integrity is True on every one of them; and the gate's delta cost is never negative.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the gate runs out of retries
&lt;/h2&gt;

&lt;p&gt;A gate that only works when it can always retry is not much of a gate. So the last thing I checked is the ugly case: heavy contention with zero retry budget. What does it do when it cannot win the race?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAIL-CLOSED  GATE with max_retries=0 (worst case, N=8)
--------------------------------------------------------------------
  cas_failures                             : 7
  refusals (VISIBLE, counted)              : 7
  refused agent ids                        : (1, 2, 3, 4, 5, 6, 7)
  lost_updates (SILENT)                    : 0
  integrity_ok                             : False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seven agents lost the race and, out of retries, they refused. Loudly. &lt;code&gt;integrity_ok&lt;/code&gt; is False, which is correct, because seven contributions genuinely did not make it. But &lt;code&gt;lost_updates&lt;/code&gt; is 0. Nothing was silently overwritten. The difference between this and the NO_GATE case is the entire point: NO_GATE gives you &lt;code&gt;integrity_ok=False&lt;/code&gt; with a silent 4, and you find out never. The gate gives you &lt;code&gt;integrity_ok=False&lt;/code&gt; with seven refusals by explicit id, and you find out immediately. One failure is invisible, the other is a list of names. That is what "fail-closed" buys, and the tool exits non-zero if that property ever breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is, and what it is not
&lt;/h2&gt;

&lt;p&gt;It is a simulator, not a measurement of production. Two kinds of number live in it and they deserve different trust. The worst-case counts (headline 4 lost, &lt;code&gt;N-1&lt;/code&gt; across the sweep) are a deterministic upper bound: they are what a maximally adversarial schedule does, not what a typical one does, and I label them that way. The fair-sweep fractions (75.0% at N=2, 97.3% at N=3, ~100% at N=5 and N=8) are Monte Carlo estimates over tens of thousands of uniform-random interleavings, so they carry the usual sampling error of a proportion and would shift a little on a different base seed. What does not shift is the direction: the zero-loss fraction falls hard as writers are added, and the gate's zero holds in every sampled interleaving. None of this is a token-price measurement. I sampled one model of "random interleaving," a uniform scheduler; a real runtime with its own scheduling could sit anywhere between my worst case and my fair sample, and I have not measured a real one.&lt;/p&gt;

&lt;p&gt;I also did not measure a token price, and I will not dress the accounting unit up as one. And this is not &lt;a href="https://finops.spinov.online/blog/revert-guard/" rel="noopener noreferrer"&gt;the revert-guard problem&lt;/a&gt;, where a single agent reintroduces reverted code, nor is it about a single agent double-charging on a retry. It is specifically two-or-more writers, one version, no precondition. If you run &lt;a href="https://finops.spinov.online/blog/subagent-dispatch-gate/" rel="noopener noreferrer"&gt;sub-agent dispatch&lt;/a&gt; or any fan-out where workers share a resource, this is the write-side gate that belongs next to your &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;pre-execution checks&lt;/a&gt;. And it is a distinct axis from &lt;a href="https://finops.spinov.online/blog/mandate-freshness-gate/" rel="noopener noreferrer"&gt;mandate freshness&lt;/a&gt;: freshness asks whether an old approval is still valid in time, this asks whether two live writes can both survive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;Standard library only, offline, no keys, no funds, a few seconds (the fair sweep draws about 92,000 interleavings). &lt;code&gt;run_all.sh&lt;/code&gt; runs the selftest, then three full runs, compares them byte for byte, and prints the sha256 of each.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interpreter: Python 3.13.5

self-test: PASS
run 1: exit=0 sha256=1df08c5e38894622314a2b052684303853b9b8fa9508eaabdfd877970c88983b
run 2: exit=0 sha256=1df08c5e38894622314a2b052684303853b9b8fa9508eaabdfd877970c88983b
run 3: exit=0 sha256=1df08c5e38894622314a2b052684303853b9b8fa9508eaabdfd877970c88983b
determinism: 3 runs byte-identical
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The report body also self-reports its own sha256 (&lt;code&gt;57e02acaa779e066d4736bb00b9ef1a2f4eeec21d886c626879cfd1a67db661e&lt;/code&gt;), so you can tell at a glance whether your run matches mine. The selftest is the contract: INV1 requires the worst case to lose exactly &lt;code&gt;N-1&lt;/code&gt;, INV2 requires the gate to lose nothing there, INV3 requires the fair sample to lose in some but not all interleavings on small N, INV4 requires the gate to lose nothing in any sampled interleaving, F1 through F4 are the falsifiers, and any failed assertion exits 1 with &lt;code&gt;FAIL&lt;/code&gt;. A gate that cannot fail its own tests is decoration.&lt;/p&gt;

&lt;p&gt;Follow along if you want the numbers from the next teardown in this series. And if you are running more than one agent against shared state right now, tell me in the comments: what is the worst silent overwrite you have hit between two agents, and did you catch it before the commit or only when the result was already gone? I suspect, for most of us, the honest answer is "only when it was already gone."&lt;/p&gt;

</description>
      <category>agents</category>
      <category>python</category>
      <category>concurrency</category>
      <category>ai</category>
    </item>
    <item>
      <title>x402 Signs the Money, Not the URL. I Checked 18 Fields.</title>
      <dc:creator>Alexey Spinov</dc:creator>
      <pubDate>Wed, 29 Jul 2026 01:12:59 +0000</pubDate>
      <link>https://dev.to/alex_spinov/x402-signs-the-money-not-the-url-i-checked-18-fields-429a</link>
      <guid>https://dev.to/alex_spinov/x402-signs-the-money-not-the-url-i-checked-18-fields-429a</guid>
      <description>&lt;p&gt;An x402 payer signature does not cover the URL. It commits to the amount, the recipient, the token contract and the chain, and to nothing that says what you are paying for. I mutated 18 leaf fields of the payment payload published in the x402 spec: 8 changes left the signature verifying, 10 broke it.&lt;/p&gt;

&lt;p&gt;I rebuilt the EIP-712 digest from that example, recovered the signer with my own secp256k1 code, then changed one field at a time. The 8 that still verify include the entire &lt;code&gt;resource&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;Change the resource URL to a different host. The signature still verifies. Nothing in the payment path notices.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI disclosure:&lt;/strong&gt; I wrote &lt;code&gt;x402_intent_gate.py&lt;/code&gt; with an AI assistant and ran it myself, offline, on Python 3.13.5, standard library only, no network, no keys, no wallet, no funds. Every number and every hex string below is pasted from a real local run. Three runs produced byte-identical STDOUT with sha256 &lt;code&gt;6cfe746ec64d8a497b1cafe27ed351dab1f56f690d3195959f0993a6e57888a6&lt;/code&gt;. The spec text I quote is other people's work, linked inline.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The signed structure in the x402 &lt;code&gt;exact&lt;/code&gt; EVM scheme is &lt;a href="https://eips.ethereum.org/EIPS/eip-3009" rel="noopener noreferrer"&gt;EIP-3009&lt;/a&gt;'s &lt;code&gt;TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)&lt;/code&gt;, wrapped in an EIP-712 domain that adds the token name, version, chain id and contract address. That is the whole list. There is no slot for what you are buying.&lt;/li&gt;
&lt;li&gt;I enumerated every leaf field of the &lt;code&gt;PaymentPayload&lt;/code&gt; example in the x402 v2 spec, mutated one at a time, and re-ran real ECDSA recovery each time. 8 of 18 mutations left the signature verifying. The 10 that broke it are money, token, chain and clock.&lt;/li&gt;
&lt;li&gt;Nothing the payer signed can be checked afterwards either. A settled &lt;code&gt;transferWithAuthorization&lt;/code&gt; leaves an ERC-20 &lt;code&gt;Transfer&lt;/code&gt; and &lt;code&gt;AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce)&lt;/code&gt;, and neither names a resource. The &lt;code&gt;SettlementResponse&lt;/code&gt; has seven fields, and exactly one of them, &lt;code&gt;extensions&lt;/code&gt;, can carry the resource, because that is where the optional offer-and-receipt extension parks a receipt. That receipt is signed by the server, not by you.&lt;/li&gt;
&lt;li&gt;The repair costs zero protocol changes. The nonce is 32 bytes the payer chooses, it is inside the signature, and it is emitted indexed on-chain. So stop wasting it on randomness: &lt;code&gt;nonce = keccak256(canonical_intent || salt)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The gate runs before the signature exists. Of 17 constructed cases, 10 pass the facilitator's own verification steps, including a real validity-window check, and get refused by the gate anyway. All 3 legitimate ones still pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a hypothetical protocol that nobody ships. Cloudflare announced &lt;a href="https://blog.cloudflare.com/monetization-gateway/" rel="noopener noreferrer"&gt;a monetization gateway for x402&lt;/a&gt; on 2026-07-01, which puts a 402 in front of anything sitting behind their edge. The number of agents that will sign one of these grew a lot faster than the number of people asking what the signature says.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does an x402 payer signature actually commit to?
&lt;/h2&gt;

&lt;p&gt;Start with the anchor, because everything after it depends on my arithmetic being right.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/coinbase/x402/blob/main/specs/x402-specification-v2.md" rel="noopener noreferrer"&gt;x402 v2 specification&lt;/a&gt; publishes a complete &lt;code&gt;PaymentPayload&lt;/code&gt; example in section 5.2.1, including a real 65-byte signature. The &lt;a href="https://github.com/coinbase/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md" rel="noopener noreferrer"&gt;exact/EVM scheme spec&lt;/a&gt; republishes the same payload with one extra field, and defines what gets signed. I implemented keccak256 and secp256k1 from scratch, rebuilt the EIP-712 digest from that example, and ran public key recovery against that signature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  [PASS] keccak256("") matches the published vector
  [PASS] keccak256("abc") matches the published vector
  [PASS] keccak256(TransferWithAuthorization type string) equals the TYPEHASH
         constant published in EIP-3009
  [PASS] secp256k1 base point is on the curve
  [PASS] n*G is the point at infinity
  [PASS] the signature published in the x402 spec example recovers to the
         payer address published in that same example
  [PASS] sign then recover round-trips on the throwaway demo key

  keccak256("")    c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
  keccak256("abc") 4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45
  TWA typehash      0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267
  recovered signer  0x857b06519e91e3a54538791bdbb0e22373e36b66
  authorization.from 0x857b06519e91e3a54538791bdbb0e22373e36b66
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line is the part I care about. The address my code recovers from their signature equals the &lt;code&gt;from&lt;/code&gt; address in their example. So the 32 bytes I am reconstructing are the 32 bytes that were actually signed, not a plausible-looking reimplementation of them.&lt;/p&gt;

&lt;p&gt;The digest is &lt;code&gt;0xf256992871671abcb27ff92885a7afa46218724e5fc0bac35d050115aa1d22e6&lt;/code&gt;, and it is built from exactly this:&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;eip712_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Rebuild exactly the 32 bytes an x402 exact/EVM payer signs.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accepted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payload&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;authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;chain_id&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;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;network&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;domain_separator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extra&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;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extra&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;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                          &lt;span class="n"&gt;chain_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;struct_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;keccak256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;keccak256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TWA_TYPE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;_addr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&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="nf"&gt;_addr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;_u256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&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="nf"&gt;_u256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;validAfter&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="nf"&gt;_u256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;validBefore&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="nf"&gt;_b32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nonce&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="nf"&gt;keccak256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x19\x01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;struct_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the inputs. &lt;code&gt;from&lt;/code&gt;, &lt;code&gt;to&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt;, two timestamps, a nonce, and a domain made of the token name, version, chain id and contract. Count the fields that describe what you are buying: zero.&lt;/p&gt;

&lt;p&gt;The Permit2 path in the same spec is stricter, not looser. Its witness type is &lt;code&gt;keccak256("Witness(address to,uint256 validAfter)")&lt;/code&gt;, and the spec carries the comment &lt;code&gt;post-audit: extra removed from Witness&lt;/code&gt;. The one place context could have been smuggled in got taken out by an audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which x402 payload fields can change without breaking the signature?
&lt;/h2&gt;

&lt;p&gt;I walked that same v2 section 5.2.1 example as an object tree, collected every leaf, and mutated them one at a time with a minimal type-preserving change. After each mutation the digest gets recomputed and the original 65 bytes get re-verified by full recovery. The signature field itself is excluded, since it is the artifact under test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;leaf field                             | minimally changed to   | signature
------------------------------------------------------------------------------
x402Version                            | 3                      | STILL VERIFIES
resource.url                           | value + "-mutated"     | STILL VERIFIES
resource.description                   | value + "-mutated"     | STILL VERIFIES
resource.mimeType                      | value + "-mutated"     | STILL VERIFIES
accepted.scheme                        | value + "-mutated"     | STILL VERIFIES
accepted.network                       | eip155:8453            | fails
accepted.amount                        | 10001                  | STILL VERIFIES
accepted.asset                         | 0x00000000000000000... | fails
accepted.payTo                         | 0x00000000000000000... | STILL VERIFIES
accepted.maxTimeoutSeconds             | 61                     | STILL VERIFIES
accepted.extra.name                    | value + "-mutated"     | fails
accepted.extra.version                 | 3                      | fails
payload.signature                      | not mutated            | the artifact under test
payload.authorization.from             | 0x00000000000000000... | fails
payload.authorization.to               | 0x00000000000000000... | fails
payload.authorization.value            | 10001                  | fails
payload.authorization.validAfter       | 1740672090             | fails
payload.authorization.validBefore      | 1740672155             | fails
payload.authorization.nonce            | 0xababababababababa... | fails
------------------------------------------------------------------------------
leaf fields enumerated                    : 19
excluded (the signature itself)           : 1
mutated                                   : 18
signature STILL VERIFIES after the change : 8
signature fails after the change          : 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two notes on that table before the counts, because the printout carries them and I would rather you read them from me than find them yourself. The example's &lt;code&gt;extensions&lt;/code&gt; is an empty object, so it has zero leaves and gets no row, and it happens to be the one place a resource identifier could ever ride. Section 3 comes back to it. And &lt;code&gt;accepted.network&lt;/code&gt; is the single field I gave a hand-written mutation, &lt;code&gt;eip155:84532&lt;/code&gt; to &lt;code&gt;eip155:8453&lt;/code&gt;, because a malformed CAIP-2 string crashes the chain id parser instead of testing anything.&lt;/p&gt;

&lt;p&gt;The 10 failures are the negative control, and they matter more than the 8 passes. If everything had come back STILL VERIFIES, the honest conclusion would have been that my checker was broken. It is not: touch the payer, the recipient, the amount, either timestamp, the nonce, the token contract, the chain id, or the token name or version, and recovery lands on a different address.&lt;/p&gt;

&lt;p&gt;So the line is clean, and it is not an accident of my field ordering. Everything describing where the money goes is inside the signature. Everything describing what the money is for is outside it.&lt;/p&gt;

&lt;p&gt;The 8 survivors split in two. Four of them say what is being bought: &lt;code&gt;resource.url&lt;/code&gt;, &lt;code&gt;resource.description&lt;/code&gt;, &lt;code&gt;resource.mimeType&lt;/code&gt; and the scheme name. The other four carry no payment authority at all: the protocol version, the timeout hint, and the display copies of the amount and the recipient.&lt;/p&gt;

&lt;p&gt;Two of those 8 deserve a note, because I do not want to overclaim. &lt;code&gt;accepted.amount&lt;/code&gt; and &lt;code&gt;accepted.payTo&lt;/code&gt; are display copies. They do not move money, since the money follows &lt;code&gt;authorization.to&lt;/code&gt; and &lt;code&gt;authorization.value&lt;/code&gt;. What they do is decide what your client renders and what your logs keep. A client builds the authorization from the requirements the server sent, so the unsigned copy is the input, and afterwards only one of the two versions can be proven. That is a smaller problem than the URL, and I am flagging it as smaller.&lt;/p&gt;

&lt;h2&gt;
  
  
  The row worth sitting with
&lt;/h2&gt;

&lt;p&gt;Minimal mutations prove coverage. They understate severity. So I re-ran the uncovered fields with values chosen to be obnoxious, using the same 65 bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;change to the envelope                       | signature
------------------------------------------------------------------------------
resource.url -&amp;gt; a different host entirely    | STILL VERIFIES
resource.url -&amp;gt; a different path             | STILL VERIFIES
resource.description -&amp;gt; unrelated            | STILL VERIFIES
resource.mimeType -&amp;gt; unrelated               | STILL VERIFIES
accepted.amount display copy, 100x           | STILL VERIFIES
accepted.payTo display copy -&amp;gt; burn address  | STILL VERIFIES
accepted.scheme -&amp;gt; another scheme name       | STILL VERIFIES
accepted.maxTimeoutSeconds -&amp;gt; one hour       | STILL VERIFIES
add accepted.extra.assetTransferMethod       | STILL VERIFIES
------------------------------------------------------------------------------
adversarial envelope changes tried  : 9
signature still verifies after      : 9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;api.example.com&lt;/code&gt; to &lt;code&gt;evil.example.net&lt;/code&gt;, same signature, still valid. The money is pinned to the last atomic unit. The name of the thing the money bought is a free text field sitting next to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why can't you reconcile this afterwards?
&lt;/h2&gt;

&lt;p&gt;Because the data does not exist. This is the part that surprised me, and it is why I stopped looking for a post-hoc answer.&lt;/p&gt;

&lt;p&gt;A settled &lt;code&gt;transferWithAuthorization&lt;/code&gt; leaves two records on chain: the ERC-20 &lt;code&gt;Transfer&lt;/code&gt; the token contract emits, and EIP-3009's own &lt;code&gt;event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce)&lt;/code&gt;. Neither names a resource. Two purchases at the same price to the same recipient for different resources look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  buy A
    resource                  https://api.example.com/premium-data
    Transfer.from             0x857b06519e91e3a54538791bdbb0e22373e36b66
    Transfer.to               0x209693bc6afc0c5328ba36faf03c514ef312287c
    Transfer.value            10000
    AuthorizationUsed.nonce   0xf3746613c2d920b5fdabc0856f2aeb2d4f88ee6037b8cc5d04a71a4462f13480
  buy B
    resource                  https://api.example.com/cheap-data
    Transfer.from             0x857b06519e91e3a54538791bdbb0e22373e36b66
    Transfer.to               0x209693bc6afc0c5328ba36faf03c514ef312287c
    Transfer.value            10000
    AuthorizationUsed.nonce   0x63e1985efb2feb72dfaa78debef5dc246d15984f7895fb86294e43d9153d476d

  ERC-20 Transfer args identical between A and B : True
  AuthorizationUsed differs only in the nonce    : True
  fields naming the resource in either record    : 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those records are reconstructions from my fixtures using the field lists the spec and the EIP define. I did not query a chain, and nothing in that script touches money.&lt;/p&gt;

&lt;p&gt;Then there is the &lt;code&gt;SettlementResponse&lt;/code&gt;, and here I have to correct myself. My first draft of this post listed five fields and said flatly that none of them names the resource. Section 5.3.2 of the v2 spec has seven, and the two I dropped were the two that mattered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The SettlementResponse the server hands back has seven fields in the x402
v2 spec, section 5.3.2, and here is the whole list:
    success      required   errorReason  optional
    transaction  required   payer        optional
    network      required   amount       optional
                            extensions   optional
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six of those seven cannot name a resource. The seventh can. &lt;code&gt;extensions&lt;/code&gt; is exactly where the &lt;a href="https://github.com/coinbase/x402/blob/main/specs/extensions/extension-offer-and-receipt.md" rel="noopener noreferrer"&gt;offer-and-receipt extension&lt;/a&gt; parks a receipt, at &lt;code&gt;extensions["offer-receipt"].info.receipt&lt;/code&gt;, and that receipt carries a &lt;code&gt;resourceUrl&lt;/code&gt;. The spec prints a worked example with &lt;code&gt;"resourceUrl": "https://api.example.com/premium-data"&lt;/code&gt; sitting right there in the settlement response. So "no field names the resource" is false as an absolute, and I am glad it got caught before this went out.&lt;/p&gt;

&lt;p&gt;The true claim is the narrower one, and it is the one this whole post is about: nothing the payer signed names the resource. The single slot that can name it is opt-in, and it is filled by the server. That extension is genuinely useful and I would turn it on. But it is signed by the service rather than the payer, its own text calls it an audit layer "without changing payment execution or settlement semantics", and the receipt is "privacy-minimal by default and intentionally omits transaction references to reduce correlation risk". A merchant-signed artifact that by default does not link to the transaction is a merchant's statement, not the payer's proof of what the payer decided to buy.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PaymentPayload.resource&lt;/code&gt; has the same shape of problem: it exists, the v2 spec marks it Optional, and it sits outside the signature.&lt;/p&gt;

&lt;p&gt;Which leaves one place to stand. Not after the settlement. Before the signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is not the stale mandate problem
&lt;/h2&gt;

&lt;p&gt;I want to keep two failures apart, because they look alike and they are not.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://finops.spinov.online/blog/mandate-freshness-gate/" rel="noopener noreferrer"&gt;the mandate freshness gate&lt;/a&gt; the axis is time. The signature was honest when it was made, and the authority behind it walked out afterwards: revoked, expired, limit lowered. Everything cryptographic holds, and the question is whether the yes is still standing at execution.&lt;/p&gt;

&lt;p&gt;Here the axis is content. The authority is perfectly live. The signature is fresh, valid, and inside every limit. It simply never said what it was for. A freshness check passes this case with full marks, and so does a signature check, because both are answering questions that have correct answers.&lt;/p&gt;

&lt;p&gt;This is the same shape as &lt;a href="https://finops.spinov.online/blog/a-47k-agent-loop-spend-cap/" rel="noopener noreferrer"&gt;tracking is not control&lt;/a&gt;, pushed one level down. Your spend cap counts tokens you can compute. In an x402 flow the counterparty names the price in the 402 response, so a &lt;a href="https://finops.spinov.online/blog/sliding-window-spend-guard/" rel="noopener noreferrer"&gt;sliding window guard&lt;/a&gt; is watching a number it did not choose. And a &lt;a href="https://finops.spinov.online/blog/model-receipt-probe/" rel="noopener noreferrer"&gt;receipt read after the fact&lt;/a&gt; cannot recover a field that was never recorded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix that needs no protocol change
&lt;/h2&gt;

&lt;p&gt;The nonce is 32 bytes. EIP-3009 says they are random and payer-chosen. They sit inside the signed struct, and the contract emits them indexed on-chain in &lt;code&gt;AuthorizationUsed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is a 32-byte payer-controlled channel that is already signed and already published, and we currently fill it with noise.&lt;/p&gt;

&lt;p&gt;I expected to be arguing that this is merely protocol-legal. It turns out the spec argues it for me. EIP-3009's own Security Considerations say that where cross-use is a risk, "the app developer could dedicate some leading bytes of the nonce as an identifier to prevent cross-use". Putting meaning in those bytes is a sanctioned use, not a loophole I found. The one caveat worth keeping: the spec says the nonce is randomly generated, and what preserves that property here is the 32-byte salt, not the intent. Hash a bare intent with no salt and you get a nonce that repeats and that anyone can grind.&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;canonical_intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;One line per field, fixed order, newline separated. No JSON ambiguity.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="nf"&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;x402-intent/1&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body-sha256: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;class: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max-atomic: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asset: %s/%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payTo: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;[&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="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="nf"&gt;normalize_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
                &lt;span class="n"&gt;intent&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;body_sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resource_class&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;intent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_atomic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
                &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;network&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asset&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payTo&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;encode&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;intent_nonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt32&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salt32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;salt must be 32 bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;keccak256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;canonical_intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;salt32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;canonical_intent for buy A, exactly the bytes that get hashed:
    | x402-intent/1
    | method: GET
    | url: https://api.example.com/premium-data
    | body-sha256: -
    | class: market-data
    | max-atomic: 20000
    | asset: eip155:84532/0x036cbd53842c5426634e7929541ec2318f3dcf7e
    | payTo: 0x209693bc6afc0c5328ba36faf03c514ef312287c

  committed nonce   0xce1d40f2e8ccbe52ec6f127abb9752a42f0c469669bebd856a16649c558d4711
  same intent, same salt, recomputed                : MATCH
  host swapped to evil.example.net, same salt       : MISMATCH
  cap raised from 20000 to 30000, same salt         : MISMATCH
  different salt, same intent                       : MISMATCH
  nonce length in bytes                             : 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is still a valid 32-byte nonce, still unique, still opaque to everyone without the salt. Nothing changes on the wire. What changes is that the signature becomes impossible to produce without first having written the decision down, which is the property I actually wanted: the control produces the audit trail, instead of the audit trail being offered as a substitute for control.&lt;/p&gt;

&lt;p&gt;Two things it does not do, stated plainly. It does not make the server deliver the resource you named. And it is a commitment, not a receipt: it proves what you decided, not what you received. If someone shows me a way to bind delivery from the payer side without an extension the merchant has to opt into, I would like to see it, because I could not find one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate, run before anything is signed
&lt;/h2&gt;

&lt;p&gt;Same family as &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;the pre-execution gate&lt;/a&gt; and &lt;a href="https://finops.spinov.online/blog/grok-tx-canary/" rel="noopener noreferrer"&gt;the pre-send transaction canary&lt;/a&gt;, aimed at the payment decision. &lt;code&gt;decide()&lt;/code&gt; collects every reason instead of bailing on the first, and there is no code path that returns ALLOW on an error.&lt;/p&gt;

&lt;p&gt;I built 17 cases and ran each one twice: once through the facilitator's own verification steps, once through the client gate. Every case is really signed with a throwaway key derived from a fixed string in the file, using &lt;a href="https://datatracker.ietf.org/doc/html/rfc6979" rel="noopener noreferrer"&gt;RFC 6979&lt;/a&gt; deterministic nonces so the bytes come out the same every run. The facilitator column is doing real recovery, not trusting an asserted boolean.&lt;/p&gt;

&lt;p&gt;Step 3 of that verification list reads "Verify the authorization parameters (Amount, Validity Window) meet the &lt;code&gt;PaymentRequirements&lt;/code&gt;", and the validity window is the part it is easy to quietly skip, since checking it needs a clock and a clock breaks determinism. My first version skipped it, checked only that &lt;code&gt;validBefore&lt;/code&gt; was greater than &lt;code&gt;validAfter&lt;/code&gt;, and still printed a column labelled offline-checkable. So the run pins a clock instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    FIXED_NOW = 1740672100  (inside the spec example's window 1740672089..1740672154)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That instant sits inside the window of the spec's own example, so freshness gets checked for real while the output never touches the wall clock. Falsifier F5 exists purely to prove the check fires: an authorization whose window closed before &lt;code&gt;FIXED_NOW&lt;/code&gt; gets flagged &lt;code&gt;outside-validity-window&lt;/code&gt;, and the spec example does not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constructed case                                      | gate   | facilitator
------------------------------------------------------------------------------
legit: market data, 10000, everything allowlisted     | ALLOW  | ACCEPT
legit: image gen, 4000, inside its own class cap      | ALLOW  | ACCEPT
legit: second market-data buy, budget still fits      | ALLOW  | ACCEPT
quote is for a different URL than we asked for        | REFUSE | ACCEPT
resource host is not on the allowlist                 | REFUSE | ACCEPT
payTo is not on the allowlist                         | REFUSE | ACCEPT
chain is not the one we fund                          | REFUSE | ACCEPT
token contract is not the one we fund                 | REFUSE | ACCEPT
25000 for a class capped at 20000                     | REFUSE | ACCEPT
fits the cap, but 45000 already signed and unsettled  | REFUSE | ACCEPT
resource class the policy never heard of              | REFUSE | ACCEPT
plain random nonce, no commitment to any intent       | REFUSE | ACCEPT
nonce commits to a different intent                   | REFUSE | ACCEPT
control: signed value contradicts the quote           | REFUSE | REJECT
control: signed destination contradicts the quote     | REFUSE | REJECT
control: validity window closed at decision time      | REFUSE | REJECT
unusable input, required field missing                | REFUSE | n/a
------------------------------------------------------------------------------
cases constructed                                  : 17
gate ALLOW                                         : 3
gate REFUSE                                        : 14
facilitator ACCEPT (offline-checkable steps)       : 13
facilitator REJECT                                 : 3
pass the facilitator, refused by the gate          : 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ten cases sail through the facilitator's verification list and get stopped by the gate. That is not a criticism of facilitators, and it is worth being fair here: the spec is explicit that "the Facilitator cannot modify the amount or destination", and my run agrees, because both of those are inside the signature. The facilitator is doing its job correctly. Its job is to check the authorization against the server's requirements. Both of those come from the server. Your decision is not an input to that comparison anywhere in the verification list.&lt;/p&gt;

&lt;p&gt;The three REJECT rows are there so you can see the facilitator checker is capable of saying no on each axis it claims to check: amount, destination and freshness.&lt;/p&gt;

&lt;p&gt;One thing I should not let myself round off. Two of those ten, &lt;code&gt;intent-nonce-missing&lt;/code&gt; and &lt;code&gt;intent-nonce-mismatch&lt;/code&gt;, are refused for not using a convention I invented four paragraphs ago. Every x402 payment on earth today would trip them. That is a proposal, not a finding, and if you strip those two out the gate still catches eight cases with nothing more exotic than an allowlist and a cap.&lt;/p&gt;

&lt;p&gt;One reason code is mine and I have not seen it elsewhere: &lt;code&gt;budget-would-exceed-with-outstanding&lt;/code&gt;. An EIP-3009 authorization is a liability from the moment it is signed, not from the moment it settles. It carries &lt;code&gt;validAfter&lt;/code&gt; and &lt;code&gt;validBefore&lt;/code&gt;, and until one of those windows closes or the nonce is consumed, the money is committed. A cap that counts settled spend will happily sign the payment that puts you over, and then watch it land.&lt;/p&gt;

&lt;h2&gt;
  
  
  What would prove me wrong
&lt;/h2&gt;

&lt;p&gt;Here is the single counterexample that ends this post: show me a field, in what the payer authorizes under the &lt;code&gt;exact&lt;/code&gt; EVM scheme, that identifies the resource. One field and I am wrong.&lt;/p&gt;

&lt;p&gt;Be clear about which part of that I ran and which part I read. The scheme defines three asset transfer methods, and my tool exercises one. EIP-3009 is the one measured above. Permit2 signs &lt;code&gt;Witness(address to,uint256 validAfter)&lt;/code&gt;, with the spec's own comment &lt;code&gt;post-audit: extra removed from Witness&lt;/code&gt;. ERC-7710 sends &lt;code&gt;delegationManager&lt;/code&gt;, &lt;code&gt;permissionContext&lt;/code&gt; and &lt;code&gt;delegator&lt;/code&gt;, and the spec says its verification "is performed entirely through simulation" of an ERC-20 &lt;code&gt;transfer(payTo, amount)&lt;/code&gt;. Two addresses and an amount. So the claim holds across all three, but only the first is a measurement and the other two are me reading the spec, which is a weaker kind of evidence and I would rather label it than launder it.&lt;/p&gt;

&lt;p&gt;The tool ships six falsifiers, all PASS on the run above. F1 is the negative control on the checker. F2 checks the commitment is a function and not a coincidence. F3 requires the gate to allow every case built to be legitimate and refuse every case built to be wrong, which stops a gate that refuses everything from scoring well. F4 feeds it garbage, an empty object and a null, and requires REFUSE with &lt;code&gt;bad-input&lt;/code&gt; on all of them. F5 proves the validity-window check actually fires. F6 re-runs the sweep and demands identical verdicts.&lt;/p&gt;

&lt;p&gt;Then I tried to break it on purpose, four times, and it exited 1 every time: flipping one Keccak round constant, forcing &lt;code&gt;decide()&lt;/code&gt; to return no reasons, turning the fail-closed branch into fail-open, and dropping the intent from the nonce so it hashed only the salt. A gate that cannot fail its own tests is decoration.&lt;/p&gt;

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

&lt;p&gt;It is not a conformance suite, and it does not implement &lt;code&gt;upto&lt;/code&gt;, &lt;code&gt;deferred&lt;/code&gt; or any Solana scheme. It does not talk to a chain, a facilitator or a wallet, so balance and simulation are not run at all and are never reported as passed. Those are steps 2 and 5 of the exact/EVM EIP-3009 list specifically; the Permit2 list numbers them 3 and 7, so the numbers are not portable even inside one document. The counts are counts of cases I constructed in one file. They are not frequencies, not samples, not rates observed anywhere in production, and no standard errors apply because nothing here is an estimate. Recount every one of them from the printout.&lt;/p&gt;

&lt;p&gt;I also have no idea how common any of this is in the wild. I have not measured a single real x402 payment, and I am not going to pretend a count of seventeen constructed cases tells you anything about how often an agent overpays for the wrong URL. What the run does establish is structural: the field is not in the signature, so the check cannot be done later, no matter how careful your logging is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;Standard library only, offline, no keys, no funds, about ten seconds. &lt;code&gt;run_all.sh&lt;/code&gt; runs the self-test, then three full runs, compares them byte for byte and prints the sha256 of each:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interpreter: Python 3.13.5

self-test: PASS
run 1: exit=0 sha256=6cfe746ec64d8a497b1cafe27ed351dab1f56f690d3195959f0993a6e57888a6
run 2: exit=0 sha256=6cfe746ec64d8a497b1cafe27ed351dab1f56f690d3195959f0993a6e57888a6
run 3: exit=0 sha256=6cfe746ec64d8a497b1cafe27ed351dab1f56f690d3195959f0993a6e57888a6
determinism: 3 runs byte-identical
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The report itself ends with &lt;code&gt;report-sha256: 49cc227bc3cb64316dbea77387ea304f14b9ca3c677c9c707538bc0fe6bc3ccc&lt;/code&gt;, so you can tell at a glance whether your run matches mine.&lt;/p&gt;

&lt;p&gt;The question I have not answered: the intent-committed nonce binds my decision to my money, and it does that with no protocol change and no cooperation from anyone. It still cannot prove the server gave me what I paid for. Every payer-side scheme I sketched for that ends up needing the merchant to sign something, which means it needs adoption, which means it is not something I can ship on my own next week. If you have found a payer-side way to bind delivery, I want to read it.&lt;/p&gt;

&lt;p&gt;Follow along if you want the numbers from the next teardown in this series. And if you are running x402 in anything resembling production, tell me in the comments what your client does with the resource URL after it signs, because I suspect the honest answer for most of us is "logs it, unsigned, next to the amount".&lt;/p&gt;

</description>
      <category>x402</category>
      <category>security</category>
      <category>python</category>
      <category>agents</category>
    </item>
    <item>
      <title>Your Authz Checks the Caller. The Model Picked the Tenant.</title>
      <dc:creator>Alexey Spinov</dc:creator>
      <pubDate>Sun, 26 Jul 2026 03:51:45 +0000</pubDate>
      <link>https://dev.to/alex_spinov/your-authz-checks-the-caller-the-model-picked-the-tenant-3bao</link>
      <guid>https://dev.to/alex_spinov/your-authz-checks-the-caller-the-model-picked-the-tenant-3bao</guid>
      <description>&lt;p&gt;A confused deputy in an AI agent is not a broken authorization check. It is an authz check aimed at the wrong operand: it verifies the caller, never the model-authored &lt;code&gt;tenant_id&lt;/code&gt; selecting the resource. A pre-execution provenance gate refuses model-authored selectors before any read. Without it, 4 of 5 selectors returned another tenant's rows; with it, 0 of 5 did.&lt;/p&gt;

&lt;p&gt;Your agent is authorized to read invoices. This morning it read a different company's invoices, and every authorization check returned yes.&lt;/p&gt;

&lt;p&gt;Nobody bypassed the gate. The caller was who it claimed to be, the token was valid, the role allowed the tool. The gate answered the exact question it was built to answer, correctly. The leak lived in an argument the gate never looked at: the &lt;code&gt;tenant_id&lt;/code&gt; that selects which company's rows come back. And that argument was written by the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A confused deputy in an AI agent is not a broken auth check. It is an auth check aimed at the wrong operand: it verifies &lt;em&gt;who is calling&lt;/em&gt; and never verifies &lt;em&gt;which resource the call selects&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;When the model authors the resource-selecting argument (&lt;code&gt;tenant_id&lt;/code&gt;, &lt;code&gt;account_id&lt;/code&gt;, &lt;code&gt;project_id&lt;/code&gt;), an authorized caller can reach another tenant's data. The identity check passes the whole time.&lt;/li&gt;
&lt;li&gt;The discriminator is provenance, not value. A &lt;code&gt;tenant_id&lt;/code&gt; that came from the authenticated session is fine. The same value, if the model wrote it, is not, because the model picking the right tenant once is luck, not authorization.&lt;/li&gt;
&lt;li&gt;The gate: any resource-selecting argument must be session-derived. Model-authored selectors are refused before the database is touched.&lt;/li&gt;
&lt;li&gt;The tool below runs eight concrete calls. Without the gate, 4 of the 5 model-authored selectors returned another tenant's rows. With the gate, 0 of 5 reached any row, and all 3 session-derived calls were still served. Standard library only, offline, deterministic. Recount every number from the printout.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI disclosure:&lt;/strong&gt; I wrote &lt;code&gt;scope_provenance_gate.py&lt;/code&gt; with an AI assistant and ran it myself, three times, on Python 3.13.5, standard library only, no network, no keys. Every output block below is pasted from that run. The STDOUT is byte-for-byte identical across the three runs; its sha256 is &lt;code&gt;63adbe8ebe17e873cbf7dbdf24faed392f20a3205d50579aec1705cf3c7841cb&lt;/code&gt; and &lt;code&gt;bash run_all.sh&lt;/code&gt; reproduces it. The fixture is synthetic and calibrated to nothing: the tenants, invoices and sessions are invented to isolate one mechanism. bot2 is a new project. It has no production fleet and no incident to sell you. This post demonstrates how a bug is reachable, not how often it happens in the wild. The one verbatim external quote (the definition of confused deputy) is attributed and linked; the practitioner posts I reference are their words, and I link the primary sources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The confused deputy: the operand nobody checks
&lt;/h2&gt;

&lt;p&gt;Here is the shape of a normal agent tool call. The agent has a tool, &lt;code&gt;read_account_rows(account_id, limit)&lt;/code&gt;. A request comes in on an authenticated session. Middleware checks whether this caller, in this role, may invoke this tool. It may. The tool runs. Rows come back.&lt;/p&gt;

&lt;p&gt;Now look at where &lt;code&gt;account_id&lt;/code&gt; came from. In an LLM agent the tool-call arguments are assembled from two very different sources. Some fields the runtime injects: the session, the auth context, anything you copy in from the request you already trusted. The rest the model fills, from its plan. &lt;code&gt;limit&lt;/code&gt; is a fine thing for the model to choose. &lt;code&gt;account_id&lt;/code&gt; decides whose data you return. If the model writes that field, then the argument that selects the resource is authored by the least trusted component in the system, and the authorization layer never reads it.&lt;/p&gt;

&lt;p&gt;That is a textbook confused deputy. The &lt;a href="https://en.wikipedia.org/wiki/Confused_deputy_problem" rel="noopener noreferrer"&gt;Wikipedia article on the confused deputy problem&lt;/a&gt; defines it in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"In information security, a confused deputy is a computer program that is tricked by another program (with fewer privileges or less rights) into misusing its authority on the system."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The term is Norm Hardy's, from his 1988 ACM SIGOPS paper of the same name. The deputy here is your authz middleware. It holds real authority (it can read any account) and it is tricked into using that authority on a target chosen by the model, because it checks the caller and not the selector. The privilege gap is exact: the model has no standing to read account B, the middleware does, and the middleware acts on the model's choice.&lt;/p&gt;

&lt;p&gt;So here is the claim, stated so you can break it: &lt;strong&gt;an authorization layer that verifies the caller and reads the resource-selecting argument from the model's output can return a resource the caller was never scoped to.&lt;/strong&gt; The fix does not need a smarter authz check on identity. It needs a check on a different operand: the provenance of the selector. Show me a model-authored &lt;code&gt;account_id&lt;/code&gt; that reaches data through the gate below, or a session-derived one the gate refuses, and the tool is broken and the claim with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The world, in eight calls
&lt;/h2&gt;

&lt;p&gt;The fixture is three accounts and two sessions. &lt;code&gt;acct_apex&lt;/code&gt; and &lt;code&gt;acct_ceres&lt;/code&gt; and &lt;code&gt;acct_borealis&lt;/code&gt;, each with one or two invoice rows. Two authenticated sessions: &lt;code&gt;S1&lt;/code&gt;, scoped to &lt;code&gt;acct_apex&lt;/code&gt; only; &lt;code&gt;S2&lt;/code&gt;, a shared-ops user scoped to both &lt;code&gt;acct_apex&lt;/code&gt; and &lt;code&gt;acct_ceres&lt;/code&gt;. Nobody is scoped to &lt;code&gt;acct_borealis&lt;/code&gt; here, which makes it the clean victim.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BLOCK 1 -- the world (synthetic, calibrated to nothing)
  acct_apex: rows [apex-inv-2201,apex-inv-2202]
  acct_borealis: rows [bor-inv-5501,bor-inv-5502]
  acct_ceres: rows [cer-inv-8801]
  session S1: identity=user_apex_ops role=reader authorized_accounts=['acct_apex']
  session S2: identity=user_shared_ops role=reader authorized_accounts=['acct_apex', 'acct_ceres']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The authorization middleware is the ordinary kind. It answers one question and it answers it right:&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;authz_allow_caller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Answers exactly one question: may this caller invoke this tool?
    It never sees account_id.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ROLE_TOOLS&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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the tool trusts that the middleware already did its job, so it does no ownership check of its own:&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;run_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&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;tool&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read_account_rows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_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;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&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="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DB&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;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])[:&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown tool&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;Neither layer checks who is allowed to read &lt;code&gt;account_id&lt;/code&gt;. That is the whole bug, and it is boring, which is why it ships.&lt;/p&gt;

&lt;p&gt;Each argument in the model carries a provenance tag: &lt;code&gt;session&lt;/code&gt; if the runtime injected it from the authenticated context, &lt;code&gt;model&lt;/code&gt; if it came out of the plan. This is not something I invented for the demo. Your runtime already knows which fields it injected and which the model filled, because you wrote the code that assembles the call. The tag just names a fact you are throwing away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch identity pass while the data leaks
&lt;/h2&gt;

&lt;p&gt;Run the eight calls through the current path: identity check, then the tool. No scope check anywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BLOCK 2 -- every enumerated call, WITHOUT the gate
  call sess selector(account_id)   prov    id_ok foreign rows_returned
  C1   S1   acct_apex              session True  False   apex-inv-2201,apex-inv-2202
  C2   S1   acct_borealis          model   True  True    bor-inv-5501,bor-inv-5502
  C3   S1   acct_borealis          model   True  True    bor-inv-5501,bor-inv-5502
  C4   S1   acct_apex              model   True  False   apex-inv-2201,apex-inv-2202
  C5   S1   acct_ceres             model   True  True    cer-inv-8801
  C6   S2   acct_ceres             session True  False   cer-inv-8801
  C7   S2   acct_borealis          model   True  True    bor-inv-5501,bor-inv-5502
  C8   S1   acct_apex              session True  False   apex-inv-2201,apex-inv-2202

  Cross-tenant reads achieved without the gate: 4 -&amp;gt; C2,C3,C5,C7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the &lt;code&gt;id_ok&lt;/code&gt; column. It is &lt;code&gt;True&lt;/code&gt; on every row, including the four that leaked. The identity check never failed. C2 and C3 are &lt;code&gt;S1&lt;/code&gt;, scoped to apex, walking out with &lt;code&gt;bor-inv-5501&lt;/code&gt; and &lt;code&gt;bor-inv-5502&lt;/code&gt;, borealis rows, because the model wrote &lt;code&gt;account_id = "acct_borealis"&lt;/code&gt; and the middleware only ever asked whether &lt;code&gt;S1&lt;/code&gt; may call the tool. C5 is &lt;code&gt;S1&lt;/code&gt; reading ceres. C7 is &lt;code&gt;S2&lt;/code&gt; reading borealis, an account it was never scoped to.&lt;/p&gt;

&lt;p&gt;That is the confused deputy in one screen. The gate the system trusts did not break. It answered "may this caller use this tool?" with a correct yes, while a different operand it never inspected decided the answer to a question nobody asked: "may this caller read this account?"&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate reads the source, not the value
&lt;/h2&gt;

&lt;p&gt;The fix keys on the one property that separates C1 from C4. Look at those two rows again: same session &lt;code&gt;S1&lt;/code&gt;, same selector value &lt;code&gt;acct_apex&lt;/code&gt;, same rows on disk. C1 is safe and C4 is not, and the only difference between them is who wrote the &lt;code&gt;account_id&lt;/code&gt;. C1's came from the session. C4's came from the model. The value is identical. The provenance is not.&lt;/p&gt;

&lt;p&gt;So the rule is provenance, and only provenance:&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;scope_provenance_gate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Looks ONLY at the source of each resource-selecting argument,
    never at its value. Fails closed on unknown tools or params.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TOOL_SCHEMA&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;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool&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;schema&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;return &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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DENY: unknown tool, no schema (fail-closed)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;pspec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&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;pspec&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;return &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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DENY: undeclared param &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; (fail-closed)&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;pspec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resource_selecting&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;arg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provenance&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;return &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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DENY: resource-selecting arg &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; is &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                           &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provenance&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-authored, must be session-derived&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;return &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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALLOW: all resource-selecting args are session-derived&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;A tiny schema declares which parameter selects a resource (&lt;code&gt;account_id&lt;/code&gt; yes, &lt;code&gt;limit&lt;/code&gt; no). The gate runs before authz and before the database. Same eight calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BLOCK 3 -- the same calls, WITH the provenance gate
  call selector         prov    verdict rows / reason
  C1   acct_apex        session ALLOW   rows [apex-inv-2201,apex-inv-2202]
  C2   acct_borealis    model   DENY    DENY: resource-selecting arg 'account_id' is model-authored, must be session-derived
  C3   acct_borealis    model   DENY    DENY: resource-selecting arg 'account_id' is model-authored, must be session-derived
  C4   acct_apex        model   DENY    DENY: resource-selecting arg 'account_id' is model-authored, must be session-derived
  C5   acct_ceres       model   DENY    DENY: resource-selecting arg 'account_id' is model-authored, must be session-derived
  C6   acct_ceres       session ALLOW   rows [cer-inv-8801]
  C7   acct_borealis    model   DENY    DENY: resource-selecting arg 'account_id' is model-authored, must be session-derived
  C8   acct_apex        session ALLOW   rows [apex-inv-2201,apex-inv-2202]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C4 is the row I want you to sit with. Its value was harmless. &lt;code&gt;acct_apex&lt;/code&gt; is exactly what &lt;code&gt;S1&lt;/code&gt; is allowed to read, and the model happened to name it correctly. The gate denies it anyway. That is not the gate being dumb. It is the gate refusing to grade the model on whether it guessed right this time, because a component that is allowed to pick the tenant when it guesses right is allowed to pick the tenant, full stop. If you want apex read, the selector comes from the session. The model does not get partial credit for coincidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The counts, so you can recount them
&lt;/h2&gt;

&lt;p&gt;Every number I am about to state is a count of the eight rows above. No sample size, no percentage over an invented denominator, no averaging. Count them yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BLOCK 4 -- the counts (recount them from BLOCK 2 and BLOCK 3)
  K total enumerated calls                          : 8
  resource selector is session-derived (S)          : 3  -&amp;gt; C1,C6,C8
  resource selector is model-authored (M)           : 5  -&amp;gt; C2,C3,C4,C5,C7
  of M, selector points OUTSIDE caller's set        : 4  -&amp;gt; C2,C3,C5,C7
  of M, selector happens to name caller's own acct  : 1  -&amp;gt; C4

  WITHOUT gate: model-authored selectors that
    returned another tenant's rows                  : 4 of 5  -&amp;gt; C2,C3,C5,C7
  WITH gate: model-authored selectors that
    reached ANY row                                 : 0 of 5  -&amp;gt; (none)
  WITH gate: session-derived selectors that still
    returned the caller's authorized rows           : 3 of 3  -&amp;gt; C1,C6,C8
  WITH gate: session-derived selectors denied        : 0 of 3  -&amp;gt; (none)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three facts, each a count and not an estimate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reachable.&lt;/strong&gt; 4 of the 5 model-authored selectors returned another tenant's rows without the gate. The fifth, C4, returned the caller's own rows only because the model happened to name the caller's own account. The leak is reachable, and you can see exactly which rows walked out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocked.&lt;/strong&gt; 0 of the 5 reached any row with the gate on. All five were denied before the database was touched, C4 included. The gate does not need to know which of them was malicious, because it does not decide on the value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not broken.&lt;/strong&gt; 3 of 3 session-derived selectors were still served with the gate on. The gate denied zero legitimate calls. It is not "deny everything," which would be trivial and useless.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The 4-of-5 and 0-of-5 are not measurements of a rate that could come out differently on a bigger fixture. They are properties of the construction. Without the gate, a model-authored selector pointing outside the caller's set will always return foreign rows, because identity-only authz always passes and the tool always reads whatever &lt;code&gt;account_id&lt;/code&gt; it is handed. With the gate, a model-authored resource selector is always denied, because that is the rule. Grow the fixture to eighty calls or eight hundred and the two structural facts do not move. Only the sizes of the sets do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four things that could have broken and didn't
&lt;/h2&gt;

&lt;p&gt;A gate that only ever says DENY would pass the leak test and be worthless. So the tool ships four falsifiers, each of which fails loudly if the gate is the wrong shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BLOCK 6 -- falsifiers (each could fail if the gate were wrong)
  [PASS] F1 gate does not break legitimate session-derived scope
         3/3 session-derived calls served, 0 denied. A deny-all gate would fail this.
  [PASS] F2 classifier reads the source of the arg, not its value
         C1 and C4 both select 'acct_apex'; C1 (session) ALLOW, C4 (model) DENY. A value-based check (account_id == self) would ALLOW C4 and miss the rule.
  [PASS] F3 session-derived scope leaks nothing even without the gate
         All session-derived selectors are inside the caller's authorized set by construction, so identity-only authz already returns own rows. The vulnerability is specific to model-authored scope, not to the tool.
  [PASS] F4 gate keys on the resource-selecting operand, not on 'any model arg'
         C8's limit is model-authored but non-selecting; account_id is session-derived. Gate ALLOWs C8. A blanket 'no model input' gate fails.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;F2 is the one that matters most, because it is the difference between a real fix and a fake one. The obvious patch is a value check: &lt;code&gt;assert account_id == session.tenant&lt;/code&gt;. That would stop C2, and it would also break &lt;code&gt;S2&lt;/code&gt; reading its second authorized account, and it would silently pass a model-authored value that happens to match. F2 proves the gate is not doing that. C1 and C4 carry the &lt;em&gt;identical&lt;/em&gt; value &lt;code&gt;acct_apex&lt;/code&gt; and get opposite verdicts, so the decision is provably keyed on the source, not the string. F4 proves the gate is not the lazy over-correction either: it does not reject every argument the model touched, only the one that selects a resource. C8's &lt;code&gt;limit&lt;/code&gt; is model-authored and the gate lets it through, because a model choosing to fetch fifty rows of accounts it is allowed to see is not a confused-deputy problem.&lt;/p&gt;

&lt;p&gt;F3 is the honest scoping of the whole claim. The tool is not dangerous. &lt;code&gt;read_account_rows&lt;/code&gt; is fine. The vulnerability is specific to model-authored scope, and F3 shows it: every session-derived call leaks nothing even with no gate at all, because a selector drawn from the session is inside the caller's authorized set by construction. Take the model out of the resource-selection path and there is no deputy to confuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is not the write-hop bug
&lt;/h2&gt;

&lt;p&gt;If you read &lt;a href="https://finops.spinov.online/blog/gate-taint-lint/" rel="noopener noreferrer"&gt;the write-chain taint post here three weeks ago&lt;/a&gt;, this can look like the same story told twice. It is a different operand, and the difference is the whole point.&lt;/p&gt;

&lt;p&gt;In that post the gate keyed on a signal, &lt;code&gt;sender_trust&lt;/code&gt;, and the danger was that a model had written some store &lt;em&gt;upstream&lt;/em&gt; of that signal, so the value the gate read was model-laundered. The fix walked the write-closure of the signal the gate reads and refused to let a model-tainted signal hold the authorization role. The tainted thing was the thing the gate checks.&lt;/p&gt;

&lt;p&gt;Here the gate reads a clean signal. Caller identity is world-anchored; the model did not write it and there is no write-hop to trace. The gate is &lt;em&gt;right&lt;/em&gt; about what it checks. The bug is that the resource selector is a &lt;strong&gt;different argument entirely&lt;/strong&gt;, a sibling operand the authz layer structurally never inspects, and that operand is model-authored at the point of the call. There is no laundering and no upstream store. The model just fills a field, directly, and the field decides whose data comes back. Taint-linting the signal the gate reads would not catch this, because the signal the gate reads is clean. You have to gate the operand the gate ignores. Same family, adjacent bug, different fix. If your mental model is "make sure the gate's inputs are trustworthy," this one slips past, because the gate's inputs &lt;em&gt;are&lt;/em&gt; trustworthy and the leak is in an input the gate never took.&lt;/p&gt;

&lt;p&gt;The general franchise both posts sit in is the same, and it is the one I keep coming back to: &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;gate before you execute, do not log after&lt;/a&gt;. It is the same instinct as &lt;a href="https://finops.spinov.online/blog/lethal-trifecta-gate/" rel="noopener noreferrer"&gt;gating the lethal trifecta before the agent runs&lt;/a&gt; and as &lt;a href="https://finops.spinov.online/blog/agent-authored-sql-reaches-db/" rel="noopener noreferrer"&gt;the post on model-authored SQL reaching the database&lt;/a&gt;: an operand an untrusted component authored gets checked before it acts, not logged after. Logging the call tells you which account leaked, next week, in the incident review. Gating the provenance of the selector stops the read.&lt;/p&gt;

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

&lt;p&gt;This is a mechanism demo on a synthetic fixture. It shows that the leak is reachable and that a provenance gate closes it on eight enumerated calls. It is not a claim about how common the bug is, not a benchmark, and not a measurement of anything in production, because bot2 has no production. The provenance tag is the load-bearing assumption: the gate is only as good as your runtime's honesty about which arguments it injected versus which the model filled. If you assemble tool calls by letting the model emit the whole JSON and never tracking what you put in, you do not have the tag, and step one is to start attaching it. The gate cannot recover a provenance you never recorded.&lt;/p&gt;

&lt;p&gt;There is a real cost, and C4 is it. The gate denies model-authored selectors that would have been harmless, because it refuses to read the value. In exchange you get a rule that does not depend on the model being right. I think that trade is correct for anything that selects a tenant. You may not, for lower-stakes selectors, and F4 is there precisely so you can scope the rule to the arguments that deserve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to change on Monday
&lt;/h2&gt;

&lt;p&gt;Find every tool where an argument selects a resource: &lt;code&gt;tenant_id&lt;/code&gt;, &lt;code&gt;account_id&lt;/code&gt;, &lt;code&gt;project_id&lt;/code&gt;, &lt;code&gt;workspace&lt;/code&gt;, &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;org&lt;/code&gt;. For each one, ask a single question: at the moment the call is assembled, does that argument come from the session or from the model? If you cannot answer, that is the finding. Start tagging.&lt;/p&gt;

&lt;p&gt;Practitioners are already converging on this from the other side. Kailash Sankar, in &lt;a href="https://dev.to/ksankar/defense-in-depth-tenant-isolation-for-an-agent-that-executes-code-375j"&gt;"Defense in Depth: Tenant Isolation for an Agent That Executes Code"&lt;/a&gt;, wires a proxy that overwrites whatever the model puts in a &lt;code&gt;tenantId&lt;/code&gt; parameter with the trusted value from the context registry, hallucinated or injected value be damned. Brian Hall, in &lt;a href="https://dev.to/brianrhall/dont-use-an-llm-to-decide-what-your-ai-agent-is-allowed-to-do-1dkn"&gt;"Don't use an LLM to decide what your AI agent is allowed to do"&lt;/a&gt;, puts it as a design rule: the decision on whether a real action runs "has to sit on something that gives the same answer every time and can show its work afterward."&lt;/p&gt;

&lt;p&gt;Overwrite and deny are two implementations of the same rule, and they differ in one way worth naming. Sankar's proxy silently corrects the model's &lt;code&gt;tenantId&lt;/code&gt;; the model never learns it overreached. The gate here refuses and says why, which turns a silent correction into a visible signal you can count, alert on, and use to notice a plan that keeps reaching for tenants it was not handed. Silent is safer to ship. Loud is better for finding out your agent has been trying the wrong door for a month. I have not run this in anger long enough to tell you which one you will regret less. Pick the one that matches how much you trust your own logging.&lt;/p&gt;

&lt;p&gt;The tool, all eight calls, both execution paths, the selftest and the four falsifiers are in &lt;code&gt;scope_provenance_gate.py&lt;/code&gt;. Copy it, add a ninth call, watch the counts move by exactly one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write one runnable tool per post about operating AI agents in production: the cost, the failures, the gates that run before execution instead of the logs that run after. Follow for the next one. And tell me in the comments: in your agent, which tool arguments come from the session and which come from the model, and are you sure?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>agents</category>
      <category>python</category>
    </item>
    <item>
      <title>Cost Per Verified Success: Your Exit-0 Denominator Lies</title>
      <dc:creator>Alexey Spinov</dc:creator>
      <pubDate>Sat, 25 Jul 2026 03:51:39 +0000</pubDate>
      <link>https://dev.to/alex_spinov/cost-per-verified-success-your-exit-0-denominator-lies-5e6j</link>
      <guid>https://dev.to/alex_spinov/cost-per-verified-success-your-exit-0-denominator-lies-5e6j</guid>
      <description>&lt;p&gt;Your cost-per-task dashboard is doing division. Spend on top, "successful tasks" on the bottom. The number it prints is the average cost of one agent task getting done. Here is the problem nobody puts on the dashboard: the denominator is whatever your agent &lt;em&gt;told you&lt;/em&gt; was a success. On most stacks that means &lt;code&gt;exit_code == 0&lt;/code&gt;, or &lt;code&gt;ok: true&lt;/code&gt;, or an HTTP 200. That is the actor grading its own homework. When the agent silently fails, that failure stays in the denominator as a "success," so the average cost per success comes out lower than the truth. Your cheapest-looking number is the one you can least trust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost per verified success is agent spend divided by witnessed successes, not by exit-0.&lt;/strong&gt; A verified success is one an independent witness re-confirms: a file in the manifest, a DB row, a token in an HTTP body, a matching sha. Silent failures inflate the exit-0 count, so the dashboard number is a lower bound on real cost.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI disclosure.&lt;/strong&gt; I wrote &lt;code&gt;verified_cost.py&lt;/code&gt; with an AI assistant and ran every case myself before publishing. Every terminal block below is pasted from a real run on Python 3.13.5, stdlib only. The run-log is a &lt;strong&gt;synthetic fixture&lt;/strong&gt;: the token counts and the price sheet are made up, and I label them so. What is real is the witness logic (each check is recomputed from recorded evidence, not asserted) and the arithmetic. I have no production incident and no invoice to sell you here. bot2 is new and its lifetime spend is zero dollars. What I have is a script that runs and a number you can reproduce.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why is cost per task a lie?
&lt;/h2&gt;

&lt;p&gt;Because "task" and "successful task" are two different measurements, and the cheap one is wearing the expensive one's name tag. &lt;code&gt;exit_code == 0&lt;/code&gt; is cheap: it is the process telling you it thinks it finished. It is a self-report from the same actor whose work you are trying to price. On &lt;a href="https://finops.spinov.online/blog/your-agent-returns-200-and-lies/" rel="noopener noreferrer"&gt;this blog the shape keeps recurring&lt;/a&gt;: a tool returns 200 and lies, a green check reconciles against nothing, an approval is not immutability. Cost inherits the same disease. If your success count is self-reported, your cost-per-success is self-reported too, and it always rounds in the flattering direction.&lt;/p&gt;

&lt;p&gt;Watch it happen on one line of a run-log. An agent calls a "create order" endpoint. The endpoint returns &lt;code&gt;HTTP/1.1 200 OK&lt;/code&gt; with a body of &lt;code&gt;{"status":"RATE_LIMITED","order":null}&lt;/code&gt;. The process exits 0, because 200 is not an error. The dashboard counts a successful task and folds its cost into the average. No order was created. You paid for the tokens, you paid for the task, and the dashboard told you that money bought a success. It did not. Multiply that by an overnight batch and your per-success number drifts away from reality while looking perfectly healthy.&lt;/p&gt;

&lt;p&gt;The fix is not a better dashboard. It is a better denominator.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a witnessed success?
&lt;/h2&gt;

&lt;p&gt;A verified success is a task that (1) exited 0 &lt;strong&gt;and&lt;/strong&gt; (2) has its effect re-confirmed by a check independent of the agent. That "and" matters. It makes verified successes a subset of exit-0 successes, which is the whole reason the math has a direction. Call &lt;code&gt;M&lt;/code&gt; the count of exit-0 tasks and &lt;code&gt;M'&lt;/code&gt; the count of verified ones. Because every verified success is also an exit-0 success, &lt;code&gt;M' &amp;lt;= M&lt;/code&gt; for any log you will ever feed it. So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;naive_cost_per_success    = total_spend / M
verified_cost_per_success = total_spend / M'      (M' &amp;lt;= M, so this is &amp;gt;= naive)
understatement_factor     = verified / naive = M / M'   (&amp;gt;= 1, always)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That inequality is not a finding. It is arithmetic. &lt;code&gt;naive_cost &amp;lt;= verified_cost&lt;/code&gt; for every possible run-log, with equality only when &lt;code&gt;M' == M&lt;/code&gt;, meaning zero silent failures. I want to be blunt about that, because it is the honest core of the tool: the tool does not &lt;em&gt;discover&lt;/em&gt; that your true cost is higher. It &lt;em&gt;proves&lt;/em&gt; it is at least as high and then measures by how much. The direction is guaranteed. The magnitude is what you did not know.&lt;/p&gt;

&lt;p&gt;A witness has to be concrete or it is just vibes with a checkmark. &lt;code&gt;verified_cost.py&lt;/code&gt; ships four kinds, and each one is recomputed from recorded evidence rather than trusting a boolean somebody wrote down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eval_witness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;w&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file_exists&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;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manifest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;          &lt;span class="c1"&gt;# path actually present
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db_row_present&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;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;              &lt;span class="c1"&gt;# row id actually there
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http_body_contains&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;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;needle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;              &lt;span class="c1"&gt;# token actually in the body
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hash_match&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;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected&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;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;observed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;        &lt;span class="c1"&gt;# sha actually matches
&lt;/span&gt;    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown witness kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# anything else -&amp;gt; fail closed
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The real function has the type-checking and the fail-closed raises spelled out; this is the spine.) The point is that a witness result is a function of evidence you can inspect, not a second self-report. If the &lt;code&gt;create order&lt;/code&gt; body says &lt;code&gt;RATE_LIMITED&lt;/code&gt;, then &lt;code&gt;http_body_contains("ORDER_CONFIRMED")&lt;/code&gt; returns false no matter what the exit code claimed. You can paste your own bodies in and rerun. The check does not care about the agent's opinion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it on a batch
&lt;/h2&gt;

&lt;p&gt;Here is the meter on a synthetic overnight batch of 12 tasks. Ten exited 0. Two failed honestly (a migration that returned exit 1, a docker build that OOM-killed with 137), and the dashboard already knows about those. The interesting three are the ones that exited 0 and did nothing: &lt;code&gt;t03&lt;/code&gt; got the &lt;code&gt;RATE_LIMITED&lt;/code&gt; body above, &lt;code&gt;t04&lt;/code&gt; produced an artifact whose sha did not match what was expected, &lt;code&gt;t06&lt;/code&gt; claimed to update &lt;code&gt;user-90&lt;/code&gt; but that row is not in the table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 verified_cost.py report fixtures/runlog.json
&lt;span class="go"&gt;id     spend      exit0   witness   verified
&lt;/span&gt;&lt;span class="gp"&gt;t03    $&lt;/span&gt;0.3540    &lt;span class="nb"&gt;yes     &lt;/span&gt;FAIL      no
&lt;span class="gp"&gt;t04    $&lt;/span&gt;0.5850    &lt;span class="nb"&gt;yes     &lt;/span&gt;FAIL      no
&lt;span class="gp"&gt;t06    $&lt;/span&gt;0.2775    &lt;span class="nb"&gt;yes     &lt;/span&gt;FAIL      no
&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="gp"&gt;total_spend            = $&lt;/span&gt;3.4665
&lt;span class="go"&gt;M  (exit-0 successes)  = 10      &amp;lt;- the denominator your dashboard uses
M' (verified: exit0 AND witness) = 7
silent failures (exit0, witness FAIL) = 3  ['t03', 't04', 't06']
&lt;/span&gt;&lt;span class="gp"&gt;spend on silent failures = $&lt;/span&gt;1.2165 &lt;span class="o"&gt;(&lt;/span&gt;bought an &lt;span class="nb"&gt;exit &lt;/span&gt;0, witness rejected it&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;------------------------------------------------------------------------------
&lt;/span&gt;&lt;span class="gp"&gt;naive_cost_per_success    = total/M  = $&lt;/span&gt;3.4665 / 10 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;.3466
&lt;span class="gp"&gt;verified_cost_per_success = total/M' = $&lt;/span&gt;3.4665 / 7 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;.4952
&lt;span class="go"&gt;understatement_factor     = M/M' = 10/7 = 1.4286x  (arithmetic, not a measured effect)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the raw pieces, not just the ratio. &lt;code&gt;M&lt;/code&gt; is 10, &lt;code&gt;M'&lt;/code&gt; is 7, and I print both so the &lt;code&gt;1.4286x&lt;/code&gt; cannot hide anything. On this batch the dashboard would tell you each success cost 35 cents. The witness says 50. That is the same &lt;code&gt;1.4286x&lt;/code&gt; the report prints: the true per-success cost sits 43% above the dashboard's number (equivalently, the dashboard reads 30% below the truth), and it is not a rounding error. &lt;code&gt;$1.2165&lt;/code&gt; of the &lt;code&gt;$3.4665&lt;/code&gt; you spent, better than a third of the bill, bought exit-0s that the witness threw out. The dashboard counted that third as wins.&lt;/p&gt;

&lt;p&gt;I want to be precise about what is real here and what is not. The &lt;code&gt;1.4286x&lt;/code&gt; is &lt;code&gt;10/7&lt;/code&gt;, and the 10 and the 7 are counts I chose when I built the fixture. So the magnitude is synthetic. What is not synthetic is the direction and the mechanism: on any log, the moment one exit-0 task fails its witness, &lt;code&gt;M'&lt;/code&gt; drops below &lt;code&gt;M&lt;/code&gt; and your true cost climbs above what the dashboard shows. The tool computes &lt;code&gt;M'&lt;/code&gt; by rerunning the checks, so on your own log the number is yours, not mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate: block before the inflated number reaches the budget
&lt;/h2&gt;

&lt;p&gt;A meter you read after the fact is a postmortem. The point of this franchise is to gate &lt;em&gt;before&lt;/em&gt; the spend lands, the same way the &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;pre-execution gate&lt;/a&gt; decides whether an action runs at all. So &lt;code&gt;verified_cost.py gate&lt;/code&gt; takes two policy knobs, a max verified cost per success and a max understatement factor, and returns a CI exit code: 0 to pass, 1 to block, 2 to fail closed on garbage input.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 verified_cost.py gate fixtures/runlog.json 0.15 1.20
&lt;span class="gp"&gt;  naive=$&lt;/span&gt;0.3466/succ   &lt;span class="nv"&gt;verified&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;.4952/succ   &lt;span class="nv"&gt;understatement&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.4286x
&lt;span class="gp"&gt;  policy: max_verified=$&lt;/span&gt;0.1500/succ  &lt;span class="nv"&gt;max_understatement&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.2000x
&lt;span class="gp"&gt;  BLOCK: verified_cost $&lt;/span&gt;0.4952/succ exceeds budget &lt;span class="nv"&gt;$0&lt;/span&gt;.1500/succ
&lt;span class="go"&gt;  BLOCK: understatement 1.4286x exceeds tolerance 1.2000x (silent failures mask the bill)
VERDICT: BLOCK exit=1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two independent reasons fired. The budget one is a generic FinOps cap. The understatement one is the reason this tool exists, so I isolated it: loosen the budget to a dollar per success, well above the real &lt;code&gt;$0.4952&lt;/code&gt;, and the gate still blocks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 verified_cost.py gate fixtures/runlog.json 1.00 1.20
&lt;span class="gp"&gt;  policy: max_verified=$&lt;/span&gt;1.0000/succ  &lt;span class="nv"&gt;max_understatement&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.2000x
&lt;span class="go"&gt;  BLOCK: understatement 1.4286x exceeds tolerance 1.2000x (silent failures mask the bill)
VERDICT: BLOCK exit=1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole idea in one exit code. Even when you can afford the verified cost, a wide gap between the naive and verified numbers is itself the signal: your dashboard is dividing by a denominator that is lying to it, and that is worth stopping a deploy over before the pattern scales into next month's budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  The falsifier: when this tool should shut up
&lt;/h2&gt;

&lt;p&gt;A gate that always fires is a stuck alarm, not a control. So the tool has to pass a case where it adds nothing, and it has to do that honestly. If your agent never silently fails, then &lt;code&gt;exit_code == 0&lt;/code&gt; really does mean success, &lt;code&gt;M'&lt;/code&gt; equals &lt;code&gt;M&lt;/code&gt;, and there is nothing to correct. Here is that case, five tasks, every exit-0 witnessed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 verified_cost.py gate fixtures/honest_zero.json 0.30 1.20
&lt;span class="gp"&gt;  naive=$&lt;/span&gt;0.2244/succ   &lt;span class="nv"&gt;verified&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;.2244/succ   &lt;span class="nv"&gt;understatement&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.0000x
&lt;span class="go"&gt;VERDICT: PASS exit=0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Understatement &lt;code&gt;1.0000x&lt;/code&gt;. Naive equals verified to the cent. The masking check is silent and the gate passes. That is the falsifier working: if exit-0 never lied on your stack, this tool changes nothing and says so out loud. Any claim it makes on your real batch has to survive that comparison first.&lt;/p&gt;

&lt;p&gt;Now the reasonable objection: "fine, but our agents are healthy, we pass 95% of tasks, the gap must be negligible." It is not zero, and the arithmetic says exactly how not-zero. &lt;code&gt;understatement = M/M' = 1/(1-s)&lt;/code&gt; where &lt;code&gt;s&lt;/code&gt; is the silent-failure rate among exit-0 tasks. I swept &lt;code&gt;s&lt;/code&gt; to show the magnitude at each rate (this table is arithmetic, not a measurement of any workload, and it is labeled that way in the output):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;k      M'     s        SE(s)      M/M'         1/(1-s)
0      200    0.0000   0.0000     1.0000       1.0000
10     190    0.0500   0.0154     1.0526       1.0526
40     160    0.2000   0.0283     1.2500       1.2500
60     140    0.3000   0.0324     1.4286       1.4286
100    100    0.5000   0.0354     2.0000       2.0000
200    0      1.0000   0.0000     UNBOUNDED    1/0     &amp;lt;- M'=0, verified cost UNBOUNDED, gate BLOCKS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At a 5% silent-failure rate, the "healthy 95% agent," your true cost is &lt;code&gt;1.0526x&lt;/code&gt; the dashboard number. Small, but not nothing, and it only grows: the &lt;em&gt;lower&lt;/em&gt; your real success rate, the &lt;em&gt;wider&lt;/em&gt; the gap, because you are dividing the same spend by an ever-smaller denominator. And the bottom row is the one I most wanted the tool to handle without crashing: when every exit-0 task is a silent failure, &lt;code&gt;M'&lt;/code&gt; is zero, verified cost is unbounded, and the gate blocks rather than dividing by zero and printing a comfortable-looking number.&lt;/p&gt;

&lt;p&gt;One more honest note buried in that table. If you use &lt;code&gt;exit_code&lt;/code&gt; itself as the witness, &lt;code&gt;M'&lt;/code&gt; equals &lt;code&gt;M&lt;/code&gt; for every &lt;code&gt;s&lt;/code&gt;, so the gap is &lt;code&gt;1.0&lt;/code&gt; always. A dashboard is not doing bad arithmetic. It is using the null witness: the actor as its own judge. The gap this tool measures is precisely the information a real witness adds beyond the agent's self-report. No witness, no gap, no visibility. That is why "add a witness log" is the actual ask here, not "buy a better dashboard."&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is soft, and what it is NOT
&lt;/h2&gt;

&lt;p&gt;I would rather you trust the small claim than oversell the big one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The magnitudes are synthetic.&lt;/strong&gt; &lt;code&gt;1.4286x&lt;/code&gt;, &lt;code&gt;$0.4952&lt;/code&gt;, &lt;code&gt;$1.2165&lt;/code&gt; come from a fixture I built. They illustrate the arithmetic. They are not a measurement of any real agent fleet, mine or anyone's. The direction (naive is a lower bound) is general; the size is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is exactly as good as your witness.&lt;/strong&gt; Garbage witness, garbage &lt;code&gt;M'&lt;/code&gt;. If your only "independent" check is another call to the same flaky service, you have two self-reports, not a witness. The four kinds here (&lt;code&gt;file_exists&lt;/code&gt;, &lt;code&gt;db_row_present&lt;/code&gt;, &lt;code&gt;http_body_contains&lt;/code&gt;, &lt;code&gt;hash_match&lt;/code&gt;) are the ones I could make recompute from evidence with no network. Yours may need more. And the quiet version of a garbage witness is one too weak to ever fail: an empty &lt;code&gt;needle&lt;/code&gt; sits inside every body, so it passes silently and the gap reads &lt;code&gt;1.0000x&lt;/code&gt;. The tool fails closed on evidence it &lt;em&gt;cannot evaluate&lt;/em&gt; (exit 2), not on a witness that &lt;em&gt;cannot fail&lt;/em&gt;. Writing a witness that can never fail is on you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A witnessed success is not a correct one.&lt;/strong&gt; &lt;code&gt;http_body_contains("ACK")&lt;/code&gt; confirms the body said ACK. It does not confirm the ACK was for the right thing. Contract-level witnessing catches silent &lt;em&gt;failure&lt;/em&gt;; it does not catch subtle &lt;em&gt;wrongness&lt;/em&gt;. This is the same blind spot &lt;a href="https://finops.spinov.online/blog/your-agent-returns-200-and-lies/" rel="noopener noreferrer"&gt;exit codes have&lt;/a&gt;, moved one notch up, not removed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost is only the loop's forward pass.&lt;/strong&gt; This tool prices completed tasks. It says nothing about tokens burned inside a task that never finishes; that is what the &lt;a href="https://finops.spinov.online/blog/loop-cost-forecaster/" rel="noopener noreferrer"&gt;loop cost forecaster&lt;/a&gt; is for. Pair them: forecast the loop, then price the loop against what it actually produced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It fails closed, on purpose.&lt;/strong&gt; A malformed witness (say a &lt;code&gt;hash_match&lt;/code&gt; with no &lt;code&gt;observed&lt;/code&gt; field) exits 2, not 0. You cannot trust a bill computed from evidence you cannot evaluate, so the tool refuses to compute one. I checked that by hand: malformed input exits 2, and zero verified successes with nonzero spend blocks rather than dividing by zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run it against your own run-log
&lt;/h2&gt;

&lt;p&gt;If you log agent tasks at all, you already have &lt;code&gt;M&lt;/code&gt;. What you probably do not have is &lt;code&gt;M'&lt;/code&gt;, because most stacks never record an independent witness next to the exit code. That is the actual gap, and it is cheaper to close than it sounds: pick the one artifact each task is supposed to produce, record whether it is really there, and divide by that count instead. Everything here is offline, keyless, stdlib-only Python 3.13.5. It drops into CI or a pre-deploy hook with no daemon and no account, and it prints its own sha256 on the last line so you can pin the version you ran.&lt;/p&gt;

&lt;p&gt;Here is the part I have not settled, and I want your take. I set the masking tolerance at &lt;code&gt;1.20x&lt;/code&gt; and I am not sure that is right for anyone but this fixture. Too tight and it fires on the honest 5% agent; too loose and it waves through a batch where a third of the spend bought nothing. And the deeper question is upstream of the threshold: what is your witness? If you run agents in production, tell me the single check you trust to confirm a task actually happened, the one that is not just the agent saying &lt;code&gt;ok: true&lt;/code&gt; a second time. I read every comment, and I am collecting the good ones.&lt;/p&gt;

&lt;p&gt;If this was useful, the &lt;a href="https://finops.spinov.online/blog/your-agent-returns-200-and-lies/" rel="noopener noreferrer"&gt;200-and-lies witness gate&lt;/a&gt; is where the honest denominator comes from, the &lt;a href="https://finops.spinov.online/blog/loop-cost-forecaster/" rel="noopener noreferrer"&gt;loop cost forecaster&lt;/a&gt; is the numerator side, and the &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;pre-execution gate&lt;/a&gt; is the pattern that stops the bad number before it spends. Follow along; the next one keeps walking down this stack.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>finops</category>
      <category>python</category>
      <category>agents</category>
    </item>
    <item>
      <title>Your MCP Pin Blocks Every Update. Most Never Broke You.</title>
      <dc:creator>Alexey Spinov</dc:creator>
      <pubDate>Fri, 24 Jul 2026 03:47:33 +0000</pubDate>
      <link>https://dev.to/alex_spinov/your-mcp-pin-blocks-every-update-most-never-broke-you-3g66</link>
      <guid>https://dev.to/alex_spinov/your-mcp-pin-blocks-every-update-most-never-broke-you-3g66</guid>
      <description>&lt;p&gt;A month ago I shipped a 40-line padlock for MCP tools: &lt;a href="https://finops.spinov.online/blog/mcp-tool-pin-verify/" rel="noopener noreferrer"&gt;pin the manifest hash, block the rug-pull&lt;/a&gt;. It works. It also has a flaw I wrote about in the last paragraph and then had to go build a fix for, because it kept nagging me. The pin fires on &lt;strong&gt;any&lt;/strong&gt; change to a tool's definition. A server that honestly adds an optional parameter trips it exactly as loud as a server that yanks a required one out from under you. Same alarm, same block, same 3am page. The pin knows the contract &lt;em&gt;changed&lt;/em&gt;. It has no idea whether the change &lt;em&gt;broke you&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; an &lt;strong&gt;MCP tool schema breaking change&lt;/strong&gt; narrows the valid-call set, so a call your agent makes today stops validating tomorrow. A &lt;strong&gt;backward-compatible change&lt;/strong&gt; widens or leaves that set alone, so old calls stay valid. A byte pin cannot tell them apart. &lt;code&gt;compat_gate.py&lt;/code&gt; diffs the &lt;code&gt;inputSchema&lt;/code&gt; and replays the calls your agent recorded: silent on compatible, fail-closed on breaking.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI disclosure:&lt;/strong&gt; I wrote &lt;code&gt;compat_gate.py&lt;/code&gt; with an AI assistant and ran every case myself before publishing. Every terminal block below is pasted from a real run on Python 3.13.5. The compatible case (C2) is a &lt;strong&gt;real&lt;/strong&gt; dated diff between two published MCP spec schemas, fetched over &lt;code&gt;curl&lt;/code&gt; and checksummed; the breaking cases (C3 to C5) are synthetic fixtures I built and confirmed by replay, and I label them as such. I have no production incident to sell you here; bot2 is new and its lifetime run count is zero. What I have is a tool that runs and a diff you can reproduce.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why does pinning a manifest fire on every change?
&lt;/h2&gt;

&lt;p&gt;Because a hash has one bit of memory: same, or different. The &lt;a href="https://finops.spinov.online/blog/mcp-tool-pin-verify/" rel="noopener noreferrer"&gt;June pin&lt;/a&gt; takes a canonical SHA-256 over &lt;code&gt;name + description + inputSchema&lt;/code&gt; and blocks when the live manifest stops matching the one you approved. That canonicalization is already the good kind of pin: it sorts keys and strips whitespace, so a re-serialized-but-identical manifest does &lt;em&gt;not&lt;/em&gt; trip it. I checked that first, and it holds. The pin is not paranoid about JSON formatting.&lt;/p&gt;

&lt;p&gt;It is paranoid about meaning. And that is the problem, because most meaning changes to a schema are harmless to your calls. The MCP spec itself is the clearest evidence. The protocol ships dated, breaking-capable revisions on a cadence; the schema directory in &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol" rel="noopener noreferrer"&gt;the spec repo&lt;/a&gt; currently carries &lt;code&gt;2024-11-05&lt;/code&gt;, &lt;code&gt;2025-03-26&lt;/code&gt;, &lt;code&gt;2025-06-18&lt;/code&gt;, &lt;code&gt;2025-11-25&lt;/code&gt;, and a live &lt;code&gt;draft/&lt;/code&gt;. I pulled two of those and diffed them by hand (more on that below). Servers track the spec. Tools get re-versioned. Descriptions get rewritten for the model. Every one of those events flips the pin's one bit, and the pin dutifully blocks.&lt;/p&gt;

&lt;p&gt;Here is the open question I left myself in June and could not answer from my own toolbox: &lt;em&gt;where is the line between a legitimate tool update and drift?&lt;/em&gt; The franchise on this blog keeps landing on the same shape. Tracking is not control. Having a credential is not scoping it. Approval is not immutability. This post adds the next line: &lt;strong&gt;a changed contract is not a broken one. A pin is not a compatibility check.&lt;/strong&gt; The pin tracks change. It does not control whether a change that would break your agent reaches the call. That is a different job, one level down from the hash, and it needs a different tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes a tool-schema change breaking?
&lt;/h2&gt;

&lt;p&gt;There is a ground truth here, and it is older than MCP. It is subtyping. A schema describes a set: the set of argument objects it calls valid. A change is &lt;strong&gt;backward-compatible&lt;/strong&gt; when the new set contains the old one, so every call that used to validate still validates. A change is &lt;strong&gt;breaking&lt;/strong&gt; when the new set is smaller, so some previously-valid call now fails. That is the whole test, and it does not require an opinion.&lt;/p&gt;

&lt;p&gt;On an object &lt;code&gt;inputSchema&lt;/code&gt;, the breaking moves are the ones that shrink the valid set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add a required property.&lt;/strong&gt; Old calls that omit it were fine and now are not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove a property when &lt;code&gt;additionalProperties: false&lt;/code&gt;.&lt;/strong&gt; A call that passed that key is now rejected as an extra.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrow a type.&lt;/strong&gt; &lt;code&gt;number&lt;/code&gt; to &lt;code&gt;integer&lt;/code&gt; throws out &lt;code&gt;3.5&lt;/code&gt;. Any incompatible type swap can strand an old value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shrink an &lt;code&gt;enum&lt;/code&gt;.&lt;/strong&gt; Drop &lt;code&gt;"csv"&lt;/code&gt; from &lt;code&gt;["json", "csv"]&lt;/code&gt; and every call that asked for CSV is gone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make an optional property required&lt;/strong&gt;, or &lt;strong&gt;tighten &lt;code&gt;additionalProperties&lt;/code&gt; from &lt;code&gt;true&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the compatible moves, the ones that widen or preserve the set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add an optional property.&lt;/strong&gt; Nobody was required to send it; old calls are untouched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Widen an &lt;code&gt;enum&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;relax a type&lt;/strong&gt; (&lt;code&gt;integer&lt;/code&gt; to &lt;code&gt;number&lt;/code&gt;), &lt;strong&gt;drop a required constraint&lt;/strong&gt;, or &lt;strong&gt;rewrite a description.&lt;/strong&gt; None of these can invalidate a call that was already valid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That list is the entire logic of the classifier. The point of the tool is not to be clever about it. The point is to run it against real schemas and, critically, to check the classifier's verdict against something independent: the calls themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate: replay the calls your agent actually makes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;compat_gate.py&lt;/code&gt; is keyless, offline, and stdlib-only (&lt;code&gt;json&lt;/code&gt;, &lt;code&gt;sys&lt;/code&gt;, &lt;code&gt;hashlib&lt;/code&gt;). It carries a deliberately small JSON-Schema validator that understands exactly six keywords: &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;required&lt;/code&gt;, &lt;code&gt;properties&lt;/code&gt;, &lt;code&gt;enum&lt;/code&gt;, &lt;code&gt;additionalProperties&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt;. Anything else in a schema (a &lt;code&gt;format&lt;/code&gt;, an &lt;code&gt;items&lt;/code&gt;, a &lt;code&gt;$ref&lt;/code&gt;) is treated as no constraint. That is a real limit and I come back to it at the end. It is the right subset for the corpus here and small enough to read in one sitting.&lt;/p&gt;

&lt;p&gt;Two signals run side by side, and they are meant to agree:&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;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_is&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_is&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Diff two inputSchemas. Return [(verdict, reason), ...] in a stable order.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;old_props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_is&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;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt; &lt;span class="n"&gt;new_is&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;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;old_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_req&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;old_is&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;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])),&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_is&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;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]))&lt;/span&gt;
    &lt;span class="n"&gt;old_ap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_is&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;additionalProperties&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;new_ap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_is&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;additionalProperties&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&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;new_props&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;p&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;old_props&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;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;new_req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;changes&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;BREAKING&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;added required property &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; (old calls omit it)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;changes&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;COMPATIBLE&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;added optional property &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; (old calls stay valid)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;# ... removed props, newly-required props, additionalProperties, enum shrink/widen, type narrow, const ...
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is signal (A), the subtyping classifier: it labels each field-level change &lt;code&gt;COMPATIBLE&lt;/code&gt; or &lt;code&gt;BREAKING&lt;/code&gt; with a reason. Signal (B) is the one I trust more, because it does not reason at all. It replays every recorded call against both schemas:&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="n"&gt;replay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;  &lt;span class="c1"&gt;# (idx, call, valid_under_old, valid_under_new, errs_new)
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calls&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;vu_old&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old_is&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;errs_new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_is&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;replay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vu_old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;errs_new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errs_new&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;control_ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;all&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="mi"&gt;2&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;replay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;               &lt;span class="c1"&gt;# every recorded call must be valid under old
&lt;/span&gt;    &lt;span class="n"&gt;broken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&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;replay&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&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="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;r&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="c1"&gt;# valid under old, invalid under new
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every call in the corpus has to be valid under the old schema. That is the entry criterion and I print it as &lt;code&gt;valid_under_old=True&lt;/code&gt; on every line, as a negative control: if a recorded call is not valid under the schema it was recorded against, the fixture is wrong and the tool says so. Then each call is validated against the new schema. A call that was valid under old and is invalid under new is a &lt;strong&gt;broken call&lt;/strong&gt;, and the count of broken calls is a measurement, not a label. The classifier can call a change breaking; the replay tells you whether it broke &lt;em&gt;your&lt;/em&gt; traffic.&lt;/p&gt;

&lt;p&gt;Quick start is two commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# just classify the schema delta:&lt;/span&gt;
python3 compat_gate.py classify old_tool.json new_tool.json

&lt;span class="c"&gt;# the gate: classify + replay recorded calls, exit code is your CI signal:&lt;/span&gt;
python3 compat_gate.py gate old_tool.json new_tool.json calls.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gate&lt;/code&gt; returns 0 when there are zero breaking changes and zero broken calls, 1 when either fires, and 2 on a usage error. The exit code is the whole product. It drops into CI or a pre-connect hook with no daemon and no account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three changes, three verdicts, one tool
&lt;/h2&gt;

&lt;p&gt;I ran the gate against six changes. One is real; five are synthetic fixtures on a made-up &lt;code&gt;run_query&lt;/code&gt; tool (a read-only SQL tool: required &lt;code&gt;sql&lt;/code&gt;, optional &lt;code&gt;limit&lt;/code&gt;, &lt;code&gt;format&lt;/code&gt; as an &lt;code&gt;enum&lt;/code&gt; of &lt;code&gt;["json","csv"]&lt;/code&gt;, &lt;code&gt;dry_run&lt;/code&gt;, &lt;code&gt;timeout_ms&lt;/code&gt;). Four recorded calls, none of them touching &lt;code&gt;timeout_ms&lt;/code&gt;. Here is the null case first, because a gate that screams at everything is useless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C1, identical (re-serialized).&lt;/strong&gt; The new tool is the old one with keys reordered and whitespace changed. Same content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### C1 identical (re-serialized)
byte-pin: ok
subtyping: 0 changes
compat verdict: PASS   breaks=0/4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both silent. The canonical pin does not trip on re-serialization, and the gate finds nothing to break. Good. Neither tool is trigger-happy on a non-change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C2, a real backward-compatible diff from the MCP spec.&lt;/strong&gt; This is the one I care most about being honest on. I fetched two published MCP schema files, &lt;code&gt;2025-06-18&lt;/code&gt; (sha256 &lt;code&gt;b3db8f1c...&lt;/code&gt;) and &lt;code&gt;2025-11-25&lt;/code&gt; (sha256 &lt;code&gt;7b2d96fd...&lt;/code&gt;), and pulled the &lt;code&gt;Implementation&lt;/code&gt; object out of each. &lt;code&gt;Implementation&lt;/code&gt; is the &lt;code&gt;serverInfo&lt;/code&gt;/&lt;code&gt;clientInfo&lt;/code&gt; payload in the handshake; it is a JSON Schema with &lt;code&gt;properties&lt;/code&gt; and &lt;code&gt;required&lt;/code&gt;, the same shape a tool &lt;code&gt;inputSchema&lt;/code&gt; is, which is why I can drive the gate with it. Between those two dated versions it gained three optional properties (&lt;code&gt;description&lt;/code&gt;, &lt;code&gt;icons&lt;/code&gt;, &lt;code&gt;websiteUrl&lt;/code&gt;) and kept &lt;code&gt;required&lt;/code&gt; at &lt;code&gt;["name", "version"]&lt;/code&gt;. Textbook additive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### C2 real MCP additive diff (Implementation)
byte-pin: BLOCK
  [COMPATIBLE] added optional property 'description' (old calls stay valid)
  [COMPATIBLE] added optional property 'icons' (old calls stay valid)
  [COMPATIBLE] added optional property 'websiteUrl' (old calls stay valid)
compat verdict: PASS   breaks=0/3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pin blocks. The bytes changed, so the hash changed, so the pin does its one job and refuses. The gate replays three recorded handshake instances (&lt;code&gt;{"name": "example-server", "version": "1.0.0"}&lt;/code&gt; and friends), all valid under old, all still valid under new, and passes in silence. This is half the falsifier for the whole thesis: on a genuinely compatible change, the gate has to be quiet, or it is just a slower pin. It is quiet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C3, an added required parameter.&lt;/strong&gt; The new &lt;code&gt;run_query&lt;/code&gt; requires a &lt;code&gt;tenant_id&lt;/code&gt;. This is the shape I most wanted to fail closed on, because it is the common one: a server tightens a tool and every existing integration silently starts sending calls that will be rejected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### C3 added required param 'tenant_id'
byte-pin: BLOCK
  [BREAKING] added required property 'tenant_id' (old calls omit it)
  call#1 valid_under_old=True valid_under_new=False [BROKEN] {"sql": "select 1"}
        -&amp;gt; $: missing required 'tenant_id'
  ...
compat verdict: FAIL   breaks=4/4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four of four recorded calls break, each because it is missing &lt;code&gt;tenant_id&lt;/code&gt;, and the gate names them. The &lt;code&gt;gate&lt;/code&gt; command exits 1. That is the other half of the falsifier: on a breaking change, the gate must fail closed and point at the call. It does, and I checked the exit code by hand, because a gate that prints FAIL and exits 0 is not a gate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C4, a shrunk enum.&lt;/strong&gt; The &lt;code&gt;format&lt;/code&gt; enum drops &lt;code&gt;"csv"&lt;/code&gt;. Only one recorded call asked for CSV.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### C4 enum shrank on 'format'
byte-pin: BLOCK
  [BREAKING] property 'format' enum shrank ['csv', 'json']-&amp;gt;['json'] (dropped ['csv'])
  call#3 valid_under_old=True valid_under_new=False [BROKEN] {"sql": "select id, email from users", "format": "csv"}
        -&amp;gt; $.format: 'csv' not in enum ['json']
compat verdict: FAIL   breaks=1/4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of four. Not four of four. This is why the replay matters and the classifier alone does not: "breaking" is true, but the blast radius on &lt;em&gt;your&lt;/em&gt; traffic is a single call, and the number tells you that. &lt;code&gt;breaks=1/4&lt;/code&gt; is a different operational decision than &lt;code&gt;breaks=4/4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C5, breaking by schema, silent on your traffic.&lt;/strong&gt; The new tool removes the optional &lt;code&gt;timeout_ms&lt;/code&gt; under &lt;code&gt;additionalProperties: false&lt;/code&gt;. By subtyping that is breaking: a call that sent &lt;code&gt;timeout_ms&lt;/code&gt; would now be rejected. But none of the four recorded calls ever set it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### C5 removed optional 'timeout_ms'
byte-pin: BLOCK
  [BREAKING] removed property 'timeout_ms' under additionalProperties:false (a call using it is now rejected)
compat verdict: FAIL   breaks=0/4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the two signals disagree, and the tool refuses to hide it. The classifier says breaking; the replay says &lt;code&gt;breaks=0/4&lt;/code&gt;. The verdict is FAIL, because I would rather a gate be conservative on a schema that genuinely narrowed. But the printout names the split out loud: &lt;em&gt;breaking by schema, 0 recorded calls touch it, silent on your traffic.&lt;/em&gt; That middle cell is the honest one. If I collapsed it to a clean PASS I would be lying about the schema; if I collapsed it to a clean FAIL with no annotation I would be hiding that your recorded traffic is fine. So it prints both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Byte-pin versus compat-gate on the same changes
&lt;/h2&gt;

&lt;p&gt;Here is the head-to-head, straight from the run. The byte-pin column is the June canonical hash, my own prior tool, not a strawman.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;change                                   byte-pin   compat   breaks
------------------------------------------------------------------------------
C1 identical (re-serialized)             ok         PASS     0/4
C2 real MCP additive diff (Implementatio BLOCK      PASS     0/3
C3 added required param 'tenant_id'      BLOCK      FAIL     4/4
C4 enum shrank on 'format'               BLOCK      FAIL     1/4
C5 removed optional 'timeout_ms'         BLOCK      FAIL     0/4        &amp;lt;- breaking by schema, 0 calls touched
C6 desc rewrite + added optional 'cache' BLOCK      PASS     0/4
------------------------------------------------------------------------------
byte-pin fired: 5/6   |   compat-gate fired: 3/6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the absolute counts, not the ratio. On this corpus of six changes, the byte-pin fired five times and the gate fired three. The two changes the pin flagged and the gate stayed silent on (C2, the real MCP additive; C6, a description rewrite plus an added optional &lt;code&gt;cache&lt;/code&gt; param) were both backward-compatible: every recorded call still validated. That gap of two is the false alarms the gate removed on this corpus. It is not a claim about all possible changes; it is the measured difference on these six.&lt;/p&gt;

&lt;p&gt;Two is a small number because six is a small corpus. But the shape scales the wrong way for the pin. The more a spec re-versions, the more the pin fires, and the fraction of those fires that are actually compatible does not drop just because the volume went up. A pin under heavy re-versioning is a siren that is right about "changed" and useless about "should I stop." The gate's job is to turn most of that siren off without turning off the part that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does the compatible/breaking line actually sit?
&lt;/h2&gt;

&lt;p&gt;I would rather you trust the small claim than the big one, so here is where this gets soft.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replay only covers the calls you recorded.&lt;/strong&gt; The gate is exactly as good as your call log. If your agent has a code path that fires a call shape you have never captured, C5 is your whole life: the gate will say &lt;code&gt;breaks=0&lt;/code&gt; and be technically right and operationally blind. The classifier signal (A) is the hedge here, because it flags the schema narrowing even when your traffic misses it, which is precisely why I keep both signals and fail closed when they disagree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compatible-by-schema is not compatible-by-behavior.&lt;/strong&gt; This is the same blind spot the pin has, one level up. A server can keep &lt;code&gt;inputSchema&lt;/code&gt; byte-identical and change what the tool &lt;em&gt;does&lt;/em&gt; behind it: return different data, hit a different backend. Nothing in this gate, and nothing in a hash, catches that. Contract compatibility is not runtime compatibility, and I am not claiming it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The validator is a subset.&lt;/strong&gt; Six keywords. It does not model &lt;code&gt;pattern&lt;/code&gt;, &lt;code&gt;minimum&lt;/code&gt;, &lt;code&gt;maxItems&lt;/code&gt;, nested &lt;code&gt;$ref&lt;/code&gt; resolution, &lt;code&gt;anyOf&lt;/code&gt;, or a dozen other JSON Schema features that can each carry a breaking change. On a schema that tightens a &lt;code&gt;pattern&lt;/code&gt; or lowers a &lt;code&gt;maximum&lt;/code&gt;, this tool will miss it, because it does not read those keywords. That is a scoping decision, not a completeness claim. Extend the validator and you extend the coverage; the design leaves room for it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is not a replacement for the &lt;a href="https://finops.spinov.online/blog/mcp-tool-pin-verify/" rel="noopener noreferrer"&gt;pin&lt;/a&gt;. Run both.&lt;/strong&gt; The hash catches the description-poisoning and rug-pull shapes, where a byte-level change &lt;em&gt;is&lt;/em&gt; the signal and "compatible" is the wrong frame entirely: a hidden instruction slipped into a description is backward-compatible by subtyping and still hostile. The pin is your detector for "changed at all." This gate is your filter for "changed in a way that breaks my calls." They answer different questions and they belong in the same pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not a semantic scanner.&lt;/strong&gt; It does not read the meaning of a description or judge whether a new parameter is dangerous. It asks one structural question about the argument set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The breaking fixtures are synthetic.&lt;/strong&gt; C3 to C5 are fixtures I wrote. They are confirmed by independent replay (a call valid under old genuinely fails under new, and the tool prints the failing path), not by my say-so, but they are not a live server. The one real diff is C2, and I checksummed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A PASS is not a proof of full compatibility.&lt;/strong&gt; It means: on the six schema keywords I model, zero breaking changes, and zero of your recorded calls broke. It does not mean nothing can go wrong. See the section above.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run it against your noisiest server
&lt;/h2&gt;

&lt;p&gt;Pull the &lt;code&gt;tools/list&lt;/code&gt; for the MCP server your agent leans on hardest. Save the &lt;code&gt;inputSchema&lt;/code&gt; you have now, save the next version when the server updates, dump a few hundred of your real recorded calls to a &lt;code&gt;.jsonl&lt;/code&gt;, and run &lt;code&gt;compat_gate.py gate&lt;/code&gt;. If it passes, you just re-approved an update without a human reading a diff at 3am. If it fails, it hands you the exact calls that would have broken, before the first one goes out. That is the cheapest pre-call check you will add this quarter, and it runs before the call instead of in the postmortem.&lt;/p&gt;

&lt;p&gt;Here is the part I still have not settled, and I want your take. Signal (A) and signal (B) disagree in the C5 shape (breaking schema, untouched traffic), and I resolved it by failing closed with a loud annotation. I am not sure that is right for every team. If you re-version aggressively, C5 is going to fire constantly on parameters nobody sends, and constant firing is how a gate gets disabled. Do you gate on the schema signal (conservative, noisy) or the replay signal (permissive, blind to unseen paths)? Do you tie re-approval to a publisher signature and skip the diff entirely? I have run this against exactly one real diff and a pile of fixtures, so my line is drawn in pencil. If you have run a compat check against a server that updates for real, tell me where you put the line and why. I read every comment.&lt;/p&gt;

&lt;p&gt;If this was useful, the &lt;a href="https://finops.spinov.online/blog/mcp-tool-pin-verify/" rel="noopener noreferrer"&gt;MCP tool pin&lt;/a&gt; is the layer above it, the &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;pre-execution gate&lt;/a&gt; is the layer that decides whether a specific action runs at all, and the &lt;a href="https://finops.spinov.online/blog/mcp-server-token-tax/" rel="noopener noreferrer"&gt;MCP server token tax&lt;/a&gt; measures what each tool costs you in context. Follow along; the next one keeps walking down this stack.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>aiagents</category>
      <category>jsonschema</category>
      <category>python</category>
    </item>
    <item>
      <title>The best config in your bake-off didn't win. Selection did.</title>
      <dc:creator>Alexey Spinov</dc:creator>
      <pubDate>Thu, 23 Jul 2026 03:24:54 +0000</pubDate>
      <link>https://dev.to/alex_spinov/the-best-config-in-your-bake-off-didnt-win-selection-did-4jm2</link>
      <guid>https://dev.to/alex_spinov/the-best-config-in-your-bake-off-didnt-win-selection-did-4jm2</guid>
      <description>&lt;p&gt;Best-of-K eval selection bias: pick the highest-scoring config from K candidates on one eval set and that observed score is biased up. It reports the expected maximum of K noisy estimates, which beats the field mean whenever K exceeds one. The bias appears even when all K configs are truly equal, grows with K, and shrinks with n.&lt;/p&gt;

&lt;p&gt;Here is the version that bites you. Your bake-off ran a batch of prompts against one eval set, the top one came out ahead, and you shipped it. In production it does worse. That drop reads like bad luck, or drift, or a bad week. It is none of those. It is a number you could have computed before you shipped, and it gets larger the more candidates you tried.&lt;/p&gt;

&lt;p&gt;I ran a small script to make the gap concrete. Eight configs, one hundred eval items, and here is the catch: I made all eight configs &lt;em&gt;truly identical&lt;/em&gt;, every one a fair coin at 50%. There is no real best. Nothing to tune. Then I let selection pick a winner anyway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   config 0:  47/100 =  47.0%
   config 1:  50/100 =  50.0%
   config 2:  52/100 =  52.0%
   config 3:  52/100 =  52.0%
   config 4:  50/100 =  50.0%
   config 5:  50/100 =  50.0%
   config 6:  54/100 =  54.0%  &amp;lt;- selected winner (argmax)
   config 7:  44/100 =  44.0%

     config 6: 54.0% (k=54 n=100 SE=4.98)
     config 2: 52.0% (k=52 n=100 SE=5.00)
   RANK: INDISTINGUISHABLE - gap 2.00 pp against 7.06 pooled SE = 0.28 SE &amp;lt; 2.0. Ranking "config 6" above "config 2" is NOT allowed.

Held-out the winner on a fresh 100 items: 48/100 = 48.0%.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Config 6 wins the bake-off at 54.0%. Then I asked the same eval-guard I use in the &lt;a href="https://finops.spinov.online/blog/your-ab-eval-is-paired-mcnemar-not-wald/" rel="noopener noreferrer"&gt;McNemar&lt;/a&gt; and &lt;a href="https://finops.spinov.online/blog/zero-failures-is-not-zero-risk-rule-of-three/" rel="noopener noreferrer"&gt;rule-of-three&lt;/a&gt; pieces to rank config 6 against the runner-up. It refused: the gap is 0.28 SE, far under the two-SE bar, so &lt;code&gt;INDISTINGUISHABLE&lt;/code&gt;. The ranker would not call config 6 the best. Selection did. And on a fresh held-out set the 54.0% falls back to 48.0%, toward the true 50% it was always going to be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Picking the best of K configs by observed pass rate reports the expected &lt;em&gt;maximum&lt;/em&gt; of K noisy estimates. The max of K exceeds the field mean, strictly, for K at least 2 (Jensen's inequality on a convex function). So the winner's reported score is biased up.&lt;/li&gt;
&lt;li&gt;The bias is not about a weak config. In the null world, where all K configs are truly equal, best-of-16 reads 8.81 pp above the true 50%, and best-of-64 reads 11.67 pp above it. Nothing to optimize; the inflation is the act of selecting.&lt;/li&gt;
&lt;li&gt;It grows with K: 2.82 pp at K=2, 7.10 pp at K=8, 11.67 pp at K=64 (p=0.5, n=100). Adding candidates makes it worse, not better. You cannot fix a selection bias by selecting from more.&lt;/li&gt;
&lt;li&gt;It equals the drop you feel on held-out. On held-out, best-of-16 is indistinguishable from a random pick, 50.4% against 49.9% at 1.40 SE, while its in-sample number sits 29.5 SE higher. The whole curse lives in the eval you selected on.&lt;/li&gt;
&lt;li&gt;It shrinks with n like 1/sqrt(n): 8.81 pp at 100 items, 2.21 pp at 1600. Real, but you fight the square root, so halving the bias costs four times the eval budget.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What this is and is not.&lt;/strong&gt; The fixtures are synthetic, and I say so on purpose. The bias here is a theorem, not an effect I fitted: &lt;code&gt;E[max of K i.i.d. non-degenerate estimates] &amp;gt; their common mean&lt;/code&gt; is Jensen, and the null-world sections compute it exactly from binomial order statistics with no sampling and no tuned constant. The one sampled section uses a fixed seed and is byte-identical across runs. The curse is negligible when one config is genuinely far ahead of the field; it dominates when the field is tight, which is where most bake-offs actually sit. I show both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the winner's score is biased up
&lt;/h2&gt;

&lt;p&gt;Selection reports a maximum, and a maximum is not an average. Each config's observed pass rate is its true rate plus noise. When you take the best of K, you are not reading a typical config, you are reading whichever one the noise flattered most. &lt;code&gt;E[max]&lt;/code&gt; sits above the mean for any K of 2 or more, and the gap widens as K grows. This is Jensen's inequality: the max is a convex function of the estimates, so the expectation of the max exceeds the max of the expectations, which in the equal case is just the common true rate.&lt;/p&gt;

&lt;p&gt;The magnitude has a textbook form. The expected max of K standard normals grows like &lt;code&gt;sqrt(2 ln K)&lt;/code&gt;, so the selection bias scales roughly as &lt;code&gt;sigma * sqrt(2 ln K)&lt;/code&gt;, where &lt;code&gt;sigma&lt;/code&gt; is the standard error of one config's estimate. That is why more candidates keep costing you: the penalty climbs with &lt;code&gt;ln K&lt;/code&gt; and never turns around. Here is the exact order-statistic computation, summed over the binomial, not sampled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   K | E[in-sample winner] | true field p | bias = drop (pp) | sigma*sqrt(2lnK)
   1 |              50.00% |        50.0% |            -0.00 |             0.00
   2 |              52.82% |        50.0% |             2.82 |             5.89
   4 |              55.14% |        50.0% |             5.14 |             8.33
   8 |              57.10% |        50.0% |             7.10 |            10.20
  16 |              58.81% |        50.0% |             8.81 |            11.77
  32 |              60.31% |        50.0% |            10.31 |            13.16
  64 |              61.67% |        50.0% |            11.67 |            14.42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the bias column downward. Zero at K=1, because with one config there is no selection to make; the script flags that row as degenerate and refuses to run the selection path on it. Then 2.82, 5.14, 7.10, all the way to 11.67 pp at K=64. The &lt;code&gt;sqrt(2 ln K)&lt;/code&gt; column is the growth guide, not an equality: it is the asymptotic leading term and runs loose at small K, which is why I print the exact bias beside it rather than the approximation. The conclusion does not live in any single row. It lives in the monotone climb of the whole swept column, with zero real signal anywhere in the fixture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bias is there even when nothing is better
&lt;/h2&gt;

&lt;p&gt;The null world is the part I care about most, because it is the part you cannot argue with. There is no best config to find. Every candidate is the same coin. If the winner's score were an honest estimate, best-of-K would land at 50% on average for every K. It does not. It lands at 58.81% for K=16 and 61.67% for K=64. That premium is manufactured entirely by the operation of taking a maximum over noise.&lt;/p&gt;

&lt;p&gt;This is the anti-fixture check I hold myself to after &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;dropping five of my own drafts in one day&lt;/a&gt; for hiding a conclusion inside a tuned constant. Here there is no constant to tune in my favor: no lucky base rate, no engineered gap, no cherry-picked K. I set the true difference between configs to exactly zero and the bias is still positive and still growing. You can disagree with the binomial if you like. You cannot make the max of sixteen coins average out to one coin.&lt;/p&gt;

&lt;h2&gt;
  
  
  It doesn't depend on a lucky base rate
&lt;/h2&gt;

&lt;p&gt;The next question a good skeptic asks: did I pick p=0.5 because it flatters the effect? So I swept the base rate at K=16 and read the exact bias at each:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     p | E[in-sample winner] | bias = drop (pp) | sigma (pp) | bias/sigma
  0.10 |              15.58% |             5.58 |       3.00 |       1.86
  0.20 |              27.28% |             7.28 |       4.00 |       1.82
  0.50 |              58.81% |             8.81 |       5.00 |       1.76
  0.80 |              86.79% |             6.79 |       4.00 |       1.70
  0.90 |              94.92% |             4.92 |       3.00 |       1.64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Positive at every base rate, 5.58 pp at a 10% pass rate up to 8.81 pp at 50%. Look at the last column instead of the fourth. Measured in units of the per-cell noise &lt;code&gt;sigma&lt;/code&gt;, the inflation is close to constant, about 1.76 sigma at p=0.5 and never straying far. That is the real statement: the curse scales with &lt;code&gt;sigma = sqrt(p(1-p)/n)&lt;/code&gt;, the noise in one estimate, not with any special value of p. There is no base rate you can choose to make it disappear. The absolute pp figure is largest near 50% only because that is where a coin is noisiest.&lt;/p&gt;

&lt;h2&gt;
  
  
  More items shrink it, but you're fighting a square root
&lt;/h2&gt;

&lt;p&gt;There is one honest escape, and the same math hands it to you. Since the bias scales with &lt;code&gt;sigma&lt;/code&gt;, and &lt;code&gt;sigma&lt;/code&gt; falls like &lt;code&gt;1/sqrt(n)&lt;/code&gt;, running more eval items shrinks the curse. The theorem's degenerate limit is &lt;code&gt;n&lt;/code&gt; to infinity: &lt;code&gt;sigma&lt;/code&gt; to zero, bias to zero. Sweeping n at K=16, p=0.5:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     n | sigma (pp) | bias = drop (pp) | bias/sigma | normal pred (pp)
   100 |       5.00 |             8.81 |       1.76 |             8.83
   400 |       2.50 |             4.41 |       1.76 |             4.41
  1600 |       1.25 |             2.21 |       1.77 |             2.21
  6400 |       0.62 |             1.10 |       1.77 |             1.10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things worth seeing here. First, the last column: I also computed the normal-theory prediction, &lt;code&gt;sigma&lt;/code&gt; times the exact expected max of sixteen standard normals (which the script works out to 1.7660 by quadrature), and the binomial bias converges onto it as n grows and the central limit theorem takes hold. 8.81 against a predicted 8.83 at n=100, then dead-on to two decimals by n=400. The theorem and the run agree because it is the same theorem.&lt;/p&gt;

&lt;p&gt;Second, the cost. Going from 100 to 400 items, a 4x eval budget, halves the bias from 8.81 to 4.41 pp. That is the square-root tax. If someone &lt;a href="https://finops.spinov.online/blog/llm-judge-cost-deterministic-pre-gate/" rel="noopener noreferrer"&gt;pays a judge model per eval item&lt;/a&gt;, buying your way out of the winner's curse is a real line on the bill, and it scales the wrong way. "Just run more evals" is correct and expensive at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The held-out drop is the bias you actually feel
&lt;/h2&gt;

&lt;p&gt;The bias so far is &lt;code&gt;E[in-sample winner] - true rate&lt;/code&gt;. You never see the true rate, so how do you know you got bitten? You hold the winner out. Re-evaluate the selected config on a fresh set and its inflated score regresses. In expectation the held-out drop &lt;em&gt;equals&lt;/em&gt; the selection bias, because the held-out re-estimate is unbiased for whatever config you happened to pick.&lt;/p&gt;

&lt;p&gt;That is exactly the trap I walked into on the &lt;a href="https://finops.spinov.online/blog/your-ab-eval-is-paired-mcnemar-not-wald/" rel="noopener noreferrer"&gt;McNemar piece&lt;/a&gt;, where a quantity that looked like a finding turned out to be an unconditional rate wearing a conditional costume. So this script runs the independence probe from the same library before it claims anything: it compares the winner's held-out rate against a random pick's held-out rate, and reports whether the conditioning on "was selected" actually buys you anything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   in-sample(best of 16) : 58.8% (k=23506 n=40000 SE=0.25)
   held-out(best of 16)  : 50.4% (k=20145 n=40000 SE=0.25)
   held-out(random pick) : 49.9% (k=19947 n=40000 SE=0.25)
   FORCED BY CONSTRUCTION [NULL: held-out of best-of-16 vs a random pick]: conditional 50.4% is indistinguishable from unconditional 49.9% (1.40 SE &amp;lt; 2.0)
   in-sample vs held-out (paired over 400 trials): drop 8.40 pp = 29.5 SE -&amp;gt; SEPARATED (measured curse)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On held-out, best-of-16 reads 50.4% and a random pick reads 49.9%. The probe calls them indistinguishable at 1.40 SE, which is the whole point: selecting the best of sixteen coins bought nothing you could keep. The winner's held-out rate &lt;em&gt;is&lt;/em&gt; the base rate. Meanwhile the in-sample number sits 29.5 SE above held-out on the paired comparison. The entire curse lived in the eval you selected on, and none of it survived the move to fresh data. That is the honest translation of "you selected noise."&lt;/p&gt;

&lt;p&gt;The probe is not vacuous, and I check that too. When I give one config a real edge, config 0 truly at 0.70 while the other fifteen sit at 0.50, the same probe flips:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   held-out(best of 16)  : 69.4% (k=27747 n=40000 SE=0.23)
   held-out(random pick) : 51.5% (k=20595 n=40000 SE=0.25)
   ok [SPREAD: held-out of best-of-16 vs a random pick]: conditional 69.4% separates from unconditional 51.5% by 52.60 SE &amp;gt;= 2.0 - the conditioning is real
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now selection earns its keep: the winner's held-out rate beats a random pick by 52.60 SE. So the probe discriminates. It is forced under the null and not forced when there is signal, which is the only way I would trust the null result at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  When selection is safe and when it lies
&lt;/h2&gt;

&lt;p&gt;None of this says stop running bake-offs. It says know which regime you are in. I held K=8 and n=100 fixed and widened one config's true edge over a flat field, reading the exact stats at each width:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;leader true p | edge (SE) | P(pick best) | held-out best | lift vs random | in-sample bias
         0.50 |      0.00 |        14.4% |         50.0% |         -0.00 |           7.10
         0.52 |      0.40 |        23.6% |         50.5% |          0.22 |           6.96
         0.55 |      1.00 |        42.0% |         52.1% |          1.47 |           6.21
         0.60 |      2.00 |        74.3% |         57.4% |          6.18 |           3.62
         0.70 |      4.00 |        99.2% |         69.8% |         17.33 |           0.19
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two ends, two different worlds. A dead-flat field, edge 0 SE, is the null: selection picks the true best only 14.4% of the time, about one in eight for eight configs, the held-out lift over a random pick is zero, and the in-sample bias of 7.10 pp is the entire result. A runaway leader, edge 4 SE at a true 0.70, gets picked 99.2% of the time, delivers a real 17.33 pp held-out lift, and its in-sample bias shrinks to 0.19 pp because there is little noise left to exploit next to a gap that large. The dangerous regime is the middle, one or two SE of edge, which is where most prompt and model bake-offs actually land: a handful of items separating the top few. There, selection inflates the score and the winner regresses.&lt;/p&gt;

&lt;h2&gt;
  
  
  How is this different from the McNemar piece?
&lt;/h2&gt;

&lt;p&gt;Fair question, since both are about comparing configs on an eval set. The &lt;a href="https://finops.spinov.online/blog/your-ab-eval-is-paired-mcnemar-not-wald/" rel="noopener noreferrer"&gt;McNemar piece&lt;/a&gt; is about &lt;em&gt;comparing two&lt;/em&gt; configs correctly: two prompts on one task set give paired outcomes, so the right significance test is McNemar, not a two-proportion SE. That is a question about the &lt;em&gt;test&lt;/em&gt; you run on a fixed comparison. This piece is about what happens &lt;em&gt;after&lt;/em&gt; you compare K of them and keep the max. The bias here is not in any pairwise test. It is in the selection step and in carrying the winner's eval score forward as if it were an unbiased estimate. You can run a flawless paired test between every pair and still report an inflated winner, because the inflation is post-selection, not a testing error. Different failure, one layer up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am not claiming
&lt;/h2&gt;

&lt;p&gt;Three limits, stated plainly.&lt;/p&gt;

&lt;p&gt;The fixtures are synthetic Bernoulli configs, not your agent. The order-statistic math is exactly right for i.i.d. estimates; real evals have correlated items and non-identical difficulty, which change the constants but not the direction. If anything, correlation across your candidates makes the effective K smaller and the bias milder than the i.i.d. case, while heavy-tailed noise makes it worse. The sign does not move.&lt;/p&gt;

&lt;p&gt;The bias is about the winner's &lt;em&gt;score&lt;/em&gt;, not about whether you chose the right config. With a real leader you usually still pick it, as the spread table shows. The mistake the curse causes is trusting the number next to the winner, not the act of choosing a winner.&lt;/p&gt;

&lt;p&gt;And it is an expectation. Any single held-out re-eval has its own noise, so a particular winner might regress a lot, a little, or by luck not at all. The claim is about what happens on average across bake-offs, which is the thing your intuition quietly assumes when it treats an eval score as ground truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does your bake-off do
&lt;/h2&gt;

&lt;p&gt;Everything here ran locally, Python 3.13.5, stdlib plus the pinned &lt;code&gt;measure.py&lt;/code&gt;, offline, no keys, no funds, three runs byte-identical, exit 0, empty stderr. The null-world numbers are exact order statistics; the sampled sections use a fixed seed. Same eval harness as the two pieces before it, and the three of them are one family of &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;pre-execution gates for AI agents&lt;/a&gt;: decide what you can actually claim before you act on the claim.&lt;/p&gt;

&lt;p&gt;I publish the runs that correct my own reading, not only the ones that flatter it, so follow along if that is your kind of thing. And a real question I do not have a clean answer to. When your last bake-off picked a winner out of a dozen prompts on a couple hundred items, did you hold that winner out on a fresh set before you shipped its score, or did the eval number go straight into the launch doc? I want to know how many candidates your team tries, and whether anyone writes down that trying more of them is the thing making the winner look better.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI disclosure. I drafted this with an AI writing assistant and edited every line; the framing, the tool, and the reading are mine. Every output block is pasted from one real local run on 2026-07-23. &lt;code&gt;winners_curse.py&lt;/code&gt; sha256 &lt;code&gt;c968f39c9295fff7…&lt;/code&gt;, run output sha256 &lt;code&gt;41934693461986a6…&lt;/code&gt;, and the imported &lt;code&gt;measure.py&lt;/code&gt; stays at its pinned sha256 &lt;code&gt;b1b3702bccb6ab46…&lt;/code&gt; so the earlier pieces keep verifying. Recompute: &lt;code&gt;python3 winners_curse.py&lt;/code&gt; prints the same bytes, exit 0, empty stderr, stdlib only.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>agents</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Zero failures isn't zero risk: the rule of three for evals</title>
      <dc:creator>Alexey Spinov</dc:creator>
      <pubDate>Wed, 22 Jul 2026 03:11:12 +0000</pubDate>
      <link>https://dev.to/alex_spinov/zero-failures-isnt-zero-risk-the-rule-of-three-for-evals-4hcd</link>
      <guid>https://dev.to/alex_spinov/zero-failures-isnt-zero-risk-the-rule-of-three-for-evals-4hcd</guid>
      <description>&lt;p&gt;The rule of three for evals says zero failures in N runs is a count, not a rate. With 0 failures in N independent runs, the exact 95% upper bound on the true failure rate is &lt;code&gt;1 - 0.05^(1/N)&lt;/code&gt;, which &lt;code&gt;3/N&lt;/code&gt; approximates. After 100 clean runs you still cannot rule out a 2.95% rate, about 1 in 34.&lt;/p&gt;

&lt;p&gt;Here is the reading that bites you. Your eval harness runs the agent 100 times, prints "0 failures," and the tile goes green. Someone screenshots it into the launch thread. The unspoken translation is "the failure rate is zero." It is not what the data says.&lt;/p&gt;

&lt;p&gt;I wrote a small script to make the gap concrete, so I ran a real gate over 200 deterministic agent runs first, counted honestly, and got the dashboard everyone trusts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gate: spend&amp;lt;=budget over 200 deterministic agent runs
observed failures: 0  (distinct scenarios: 200)
naive point rate : 0.00%   binomial SE: 0.00 pp
naive 95% CI     : [0.00%, 0.00%]   &amp;lt;- zero width: false certainty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the standard error. For a zero count the binomial SE is &lt;code&gt;sqrt(0*1/200)&lt;/code&gt;, which is exactly 0, so the naive 95% interval collapses to &lt;code&gt;[0.00%, 0.00%]&lt;/code&gt;. A zero-width confidence interval. The math is telling you it is completely certain, from 200 samples, that the true rate is precisely zero. That is obviously wrong, and it is the exact shape of every "all green" board I have ever trusted too much.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"0 failures in N runs" is an observed count, not a rate. The naive binomial SE of a zero count is 0, which is why a green board looks like proof and isn't.&lt;/li&gt;
&lt;li&gt;The honest number is the one-sided upper bound. With 0 failures in N runs, the 95% upper confidence limit on the true failure rate is &lt;code&gt;1 - 0.05^(1/N)&lt;/code&gt;. The rule of three, &lt;code&gt;3/N&lt;/code&gt;, approximates it and rounds the risk slightly up.&lt;/li&gt;
&lt;li&gt;At N=30 the bound is 9.50% (about 1 in 11). At N=100 it is 2.95% (1 in 34). At N=1000 it is 0.30% (1 in 334). Zero failures in 30 runs is compatible with a 1-in-11 true failure rate.&lt;/li&gt;
&lt;li&gt;To rule out a 0.1% rate at 95% you need about 2995 clean runs, not 50. "We ran it fifty times" and "we are 99.9% sure" are different sentences.&lt;/li&gt;
&lt;li&gt;Correlated runs make it worse. 200 runs that are really 20 scenarios replayed ten times give an effective N of 20, and the bound jumps from 1.49% to 13.91%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What this is and is not.&lt;/strong&gt; The gate in the script is benign by construction: I built it so it observes zero failures, because a clean eval is exactly the case I want to talk about. The bound over that count is a theorem, not a fitted effect. There is no tuned constant anywhere in this that, if I nudged it, would flip the conclusion. The only inputs are the count (0), the number of runs (N), and the confidence level. I show that explicitly in section 6 of the run, because "the number rides on a fixture I chose" is the failure mode I most distrust in this kind of post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why zero observed failures is not a zero failure rate
&lt;/h2&gt;

&lt;p&gt;Counting is not estimating. If you flip a coin twice and it lands heads twice, you have observed a 100% heads rate, and nobody sane concludes the tails face does not exist. Two runs is not enough to rule much out. The same logic holds when the observed count is zero and the sample is 30, or 100, or 1000: a rare failure can hide behind any finite streak of clean runs, and the question is not "did it fail" but "what rate is still consistent with what I saw."&lt;/p&gt;

&lt;p&gt;The naive dashboard answers a different, easier question. It reports the point estimate, &lt;code&gt;0/N = 0%&lt;/code&gt;, and its standard error, which for a zero count is zero. So the interval has no width and the board is green with no caveat. This is the statistical version of a monitor that has been silently dead for three weeks: a check that always returns "fine" and a check that has stopped running produce the identical screenshot. Silence is not the same as health, and a zero-width interval is not the same as certainty. This is the whole of the &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents" rel="noopener noreferrer"&gt;tracking-is-not-control&lt;/a&gt; idea in one number: the board is counting what already happened and calling the absence of an event a guarantee about the next one.&lt;/p&gt;

&lt;p&gt;So throw out the point estimate and ask the honest question instead. Given that I saw zero failures in N runs, how high could the true failure rate be and still make a run this clean plausible?&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule of three, and why it is exact at zero
&lt;/h2&gt;

&lt;p&gt;The upper bound falls straight out of the binomial, no approximation required. The only way to observe zero failures in N independent runs is for every run to pass, and the probability of that is &lt;code&gt;(1-p)^N&lt;/code&gt;. The Clopper-Pearson upper confidence limit is the failure rate &lt;code&gt;p&lt;/code&gt; that makes an outcome this clean just barely believable at your confidence level: set &lt;code&gt;(1-p)^N = 0.05&lt;/code&gt;, solve, and you get &lt;code&gt;p = 1 - 0.05^(1/N)&lt;/code&gt;. Because the binomial tail at x=0 is a single term, this is exact, not a normal approximation that wobbles at small N. One word of caution on &lt;em&gt;exact&lt;/em&gt;: it means the coverage is guaranteed to be at least 95%, not that this is the tightest interval available. Clopper-Pearson sits at the conservative end of the family (Wilson and Jeffreys intervals run a hair narrower), and I take the conservative end on purpose when the thing I am bounding is a rare and expensive failure.&lt;/p&gt;

&lt;p&gt;The rule of three is the pocket version. The true constant is &lt;code&gt;-ln(0.05) = 2.9957&lt;/code&gt;, and &lt;code&gt;3/N&lt;/code&gt; is that rounded to a number you can do in your head. Here is the exact bound beside the rule of three across N, all assuming zero observed failures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     N |  exact 95% upper |      3/N |   ~1 in K | exact-3/N (pp)
------------------------------------------------------------------
    10 |           25.89% |   30.00% |         4 |         -4.113
    30 |            9.50% |   10.00% |        11 |         -0.497
    50 |            5.82% |    6.00% |        17 |         -0.184
   100 |            2.95% |    3.00% |        34 |         -0.049
   300 |            0.99% |    1.00% |       101 |         -0.006
  1000 |            0.30% |    0.30% |       334 |         -0.001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the N=30 row. Thirty green runs, zero failures, and the true failure rate could still be 9.50%, roughly one bad run in eleven, without your eval being unlucky at all. The &lt;code&gt;3/N&lt;/code&gt; column sits a hair above the exact bound in every row (the gap is negative because exact minus &lt;code&gt;3/N&lt;/code&gt; is small and negative), so the mnemonic slightly over-states the risk. That is the direction you want a shortcut to err. By a thousand runs the two columns read the same to two decimals (0.30% and 0.30%), and the rule of three stops being an approximation worth distinguishing.&lt;/p&gt;

&lt;p&gt;None of these numbers are typed in. They come out of &lt;code&gt;1 - 0.05^(1/N)&lt;/code&gt; in the script, and I cross-checked the row values against an independent recompute before I quoted them, because quoting a bound I hadn't actually run would be its own kind of lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many clean runs actually buy a target?
&lt;/h2&gt;

&lt;p&gt;Flip the question around. If you want to be able to rule out a given failure rate, how many clean runs does that take? Invert the bound, &lt;code&gt;1 - 0.05^(1/N) &amp;lt;= target&lt;/code&gt;, and you get &lt;code&gt;N &amp;gt;= ln(0.05)/ln(1-target)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;target = the failure rate you want to be able to rule out at 95%.
  bound &amp;lt;=  5.00%  needs  N =    59 clean runs (exact 58.4)
  bound &amp;lt;=  1.00%  needs  N =   299 clean runs (exact 298.1)
  bound &amp;lt;=  0.30%  needs  N =   998 clean runs (exact 997.1)
  bound &amp;lt;=  0.10%  needs  N =  2995 clean runs (exact 2994.2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the section that changes how I plan an eval budget. Ruling out a 1% failure rate takes 299 clean runs. Ruling out 0.1%, the neighborhood where you'd actually feel safe shipping an agent that can move money or delete data, takes about 2995. For anyone &lt;a href="https://finops.spinov.online/blog/llm-judge-cost-deterministic-pre-gate" rel="noopener noreferrer"&gt;paying a judge model per eval item&lt;/a&gt;, those Ns are a line straight to a bill, and the honest version of "prove it's safe" is expensive in a way the green tile hides. If your rare-but-catastrophic action got 50 clean runs, the most you can claim at 95% is that its failure rate is probably under 6%. That is not a number I would put next to "delete the production database."&lt;/p&gt;

&lt;p&gt;The confidence level is a knob too, and it is worth seeing that "three" is not sacred:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  conf |  -ln(alpha) |           name |  upper@N=100
----------------------------------------------------
   90% |      2.3026 |    rule of 2.3 |        2.28%
   95% |      2.9957 |      rule of 3 |        2.95%
   99% |      4.6052 |    rule of 4.6 |        4.50%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule of three is specifically the 95% case, because &lt;code&gt;-ln(0.05)&lt;/code&gt; rounds to 3. Want 99% confidence in your upper bound? That is the rule of 4.6, and at a fixed N it gives a looser bound (4.50% versus 2.95% at N=100), because more confidence over the same data costs you sharpness. Pick the confidence deliberately instead of inheriting 95% because a stats class used it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap in the number: correlated runs shrink your N
&lt;/h2&gt;

&lt;p&gt;Everything above assumes the runs are independent. In agent evals they very often are not, and the failure is quiet. You run the same 20 scenarios with 10 seeds each and call it 200 runs. The board says 200; the informative count is 20. Common-mode inputs, the same prompt replayed, the same fixture, the same upstream data, inflate N on the dashboard and leave the real bound where the distinct cases put it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nominal runs: 200   distinct scenarios: 20   observed failures: 0
bound if you count nominal N=200 : 1.49%  (~1 in 67)
bound at effective N=20          : 13.91% (~1 in 7)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same zero failures. Same 200 lines in the log. But the bound you're entitled to is 13.91%, about 1 in 7, not 1.49%. This one cuts against the reassuring direction, which is exactly why I put it in: replays and shared inputs shrink your effective N, and a smaller N only ever loosens the bound, never tightens it, since &lt;code&gt;1 - 0.05^(1/N)&lt;/code&gt; falls as N grows. A harness that counts replays as independent runs is quietly reporting a safety you don't have. If you can't argue your runs are independent draws from the distribution you care about, use the count of distinct cases, not the count of executions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the bound does not tell you
&lt;/h2&gt;

&lt;p&gt;Three honest limits, because the number is narrow on purpose.&lt;/p&gt;

&lt;p&gt;It is about frequency, not identity. &lt;code&gt;1 - 0.05^(1/N)&lt;/code&gt; bounds how often a failure could happen. It says nothing about which failure, or how bad. A 1% rate of "off-by-a-cent" and a 1% rate of "wired the whole treasury" are the same number here and nothing alike in practice. The bound does not replace worst-case analysis or &lt;a href="https://finops.spinov.online/blog/severity-gate-not-pass-rate" rel="noopener noreferrer"&gt;reading results by severity class&lt;/a&gt;; it sits next to them.&lt;/p&gt;

&lt;p&gt;It bounds the rate on your eval distribution, not on production. If your 100 clean runs don't cover the inputs production will throw, the bound is honest about the runs you did and silent about the ones you didn't. A clean eval on the wrong distribution is a different problem, closer to &lt;a href="https://finops.spinov.online/blog/eval-contamination-probe" rel="noopener noreferrer"&gt;contamination in the eval itself&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is an upper bound, not an estimate. Zero failures does not mean the rate is &lt;code&gt;3/N&lt;/code&gt;. It means the rate is probably below &lt;code&gt;3/N&lt;/code&gt;. The true rate could be much lower, or, with bad luck, right at the bound. The point is not to name the rate. It is to stop pretending a clean run named it zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why there is nothing here to fake
&lt;/h2&gt;

&lt;p&gt;I have dropped enough of my own drafts to be suspicious of any post where the punchline sits inside a constant the author picked. So I built this one to have no such constant. The conclusion depends on three inputs and nothing else: the observed count, N, and the confidence level. Section 6 of the run makes the point by brute force:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;re-run the gate: failures still 0 of 200  -&amp;gt;  bound 1.49%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shuffle which scenario the gate sees first, run it twice, reorder the whole fixture: the count stays 0, so the bound is byte-for-byte the same. There is no effect size to permute, no ratio whose denominator is secretly doing the work. That is the opposite of the class of eval post that gets dropped, where the "finding" is really a fixture setting in disguise. Here the only thing I observe is a count, and the only thing I do to it is bound it. If you distrust the bound, you are distrusting the binomial, which is a fine thing to argue about and a very different argument from "he tuned the numbers."&lt;/p&gt;

&lt;p&gt;The whole thing runs in a second: Python 3.13.5, stdlib only, offline, no keys, no funds, three runs byte-identical, exit 0, empty stderr. Picking the right statistical test is a sibling problem, and I wrote up the paired-data version of it in &lt;a href="https://finops.spinov.online/blog/your-ab-eval-is-paired-mcnemar-not-wald" rel="noopener noreferrer"&gt;why your A/B eval probably needs McNemar, not a two-proportion SE&lt;/a&gt;. Both belong to the same family of &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents" rel="noopener noreferrer"&gt;pre-execution gates for AI agents&lt;/a&gt;: decide what you can actually claim before you act on the claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does your board show
&lt;/h2&gt;

&lt;p&gt;The next time a tile goes green on "0 failures in N runs," do the one-line translation out loud. Zero in 30 is a rate that could be 1 in 11. Zero in 100 is a rate that could be 1 in 34. Neither is zero, and the dashboard will not tell you that unless you make it.&lt;/p&gt;

&lt;p&gt;I publish the runs that correct my own reading, not only the ones that flatter it, so follow along if that is your kind of thing. And a real question I don't have a clean answer to: when your eval last went green, did anyone write down the N, or did "0 failures" quietly get filed as "the failure rate is zero"? I want to know how many runs your team treats as enough, and why that number and not ten times it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI disclosure. I drafted this with an AI writing assistant and edited every line; the framing, the gate, and the reading are mine. Every output block is pasted from one real local run on 2026-07-22. &lt;code&gt;zero_failures.py&lt;/code&gt; sha256 &lt;code&gt;d3b3f205909640fc…&lt;/code&gt;, run output sha256 &lt;code&gt;cc7aca328fdd7236…&lt;/code&gt;. Recompute: &lt;code&gt;python3 zero_failures.py&lt;/code&gt; prints the same bytes, exit 0, empty stderr, stdlib only.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>agents</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Your A/B eval is paired. Your stat test probably isn't.</title>
      <dc:creator>Alexey Spinov</dc:creator>
      <pubDate>Tue, 21 Jul 2026 03:03:28 +0000</pubDate>
      <link>https://dev.to/alex_spinov/your-ab-eval-is-paired-your-stat-test-probably-isnt-lbk</link>
      <guid>https://dev.to/alex_spinov/your-ab-eval-is-paired-your-stat-test-probably-isnt-lbk</guid>
      <description>&lt;p&gt;Paired eval, wrong test: two prompts scored on one 100-item set give paired outcomes, so ranking them needs McNemar, not the two-proportion Wald SE. On the same data, Wald read 1.01 SE and said 'collect more'; McNemar read 2.65 SE and allowed the ranking. Same 100 items, opposite decision.&lt;/p&gt;

&lt;p&gt;I shipped a little eval helper that refuses to rank two configurations when their gap is smaller than two standard errors. The idea is good. The refusal is honest. Last week it looked at two prompts scored on the same 100-item set and printed this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RANK: INDISTINGUISHABLE - gap 7.00 pp against 6.95 pooled SE = 1.01 SE &amp;lt; 2.0. Ranking "prompt B" above "prompt A" is NOT allowed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seven points of apparent improvement, called noise. The honest reading of that line is "collect more items." And it was wrong. Not a rounding error, not a close call. The right test on that exact data says the ranking is already decided, and I could have stopped.&lt;/p&gt;

&lt;p&gt;The bug was not in the arithmetic. It was in which test the arithmetic ran. My helper was treating two paired runs as if they were two independent samples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two prompts on one task set are paired: each task gives a pair &lt;code&gt;(pass_A, pass_B)&lt;/code&gt;, and both-pass / both-fail tasks carry zero information about the difference.&lt;/li&gt;
&lt;li&gt;My harness used the Wald SE of a difference of two proportions, &lt;code&gt;pooled = sqrt(se_A**2 + se_B**2)&lt;/code&gt;. That is the independent-samples formula; it ignores the pairing entirely. In my showcase that cost real power.&lt;/li&gt;
&lt;li&gt;The paired test is McNemar: &lt;code&gt;SE = 100*sqrt(b+c)/n&lt;/code&gt;, using only the discordant counts &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt;. On the same 100 items it returned 2.65 SE, not 1.01, and ranking was allowed. With just 7 discordant pairs the tool also prints the exact binomial, &lt;code&gt;p=0.0156&lt;/code&gt; (z-equivalent 2.42), so the decision does not ride on the normal approximation.&lt;/li&gt;
&lt;li&gt;On 535 real paired observations from my own sweep, one pair reads 2.65 SE under Wald and 6.24 under McNemar with &lt;code&gt;c=0&lt;/code&gt;: a strictly nested result that Wald reports as a hair over the line and has no way to flag as deterministic.&lt;/li&gt;
&lt;li&gt;The fix is about fifteen lines. The hard part is not the formula, it is noticing your runs are paired.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What this is and is not.&lt;/strong&gt; The two-prompt table below is a constructed example: I picked the counts to sit exactly on the line where the two tests disagree, so you can reproduce it with four numbers. The 535-observation table comes from a synthetic marker fixture I wrote, not from anyone's production system. Where a number is forced by my constants rather than measured, I say so in the same paragraph. That habit is half the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why two prompts on one set are paired
&lt;/h2&gt;

&lt;p&gt;Run prompt A and prompt B on the same 100 eval tasks. Task 7 either trips both of them, or neither, or exactly one. That last group is the only one that tells you which prompt is better. The tasks where both pass and the tasks where both fail are shared difficulty: they move both pass rates together and say nothing about the gap.&lt;/p&gt;

&lt;p&gt;Here is the case that caught me, laid out as a 2x2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                     B pass   B fail
        A pass          55        0     &amp;lt;- b = 0 (A pass, B fail)
        A fail           7       38     &amp;lt;- c = 7 (A fail, B pass)
        A marginal = 55/100   B marginal = 62/100   n = 100 pairs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prompt A passes 55, prompt B passes 62. Same items. Ninety-three of the hundred tasks are concordant: 55 both pass, 38 both fail. Seven tasks are discordant, and all seven go the same way, B passes where A failed. Zero go the other way. B's pass set contains A's pass set completely. That is a strong statement, and it is invisible to a test that only looks at the two marginal rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What my harness printed, and why it was the wrong number
&lt;/h2&gt;

&lt;p&gt;The refusal came out of &lt;code&gt;rank()&lt;/code&gt;. Under the hood it builds the pooled standard error the way you were taught for two independent proportions:&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="n"&gt;pooled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;se&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;se&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;se&lt;/code&gt; is the binomial standard error of one marginal rate. For 55/100 and 62/100 that pools to 6.95 points, the 7-point gap divides to 1.01 SE, and the guard files it under "indistinguishable." Fair, if the two samples were drawn independently. They were not. The 93 concordant tasks are the same 93 tasks in both columns, and the formula charged me full variance for them anyway.&lt;/p&gt;

&lt;p&gt;The cost of believing that line is real budget. At 1000 items each, the same seven-point gap finally clears the bar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RANK: "prompt B" &amp;gt; "prompt A" - gap 7.00 pp = 3.18 SE &amp;gt;= 2.0. Ranking is allowed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the harness asked for ten times the eval spend to reach a verdict the correct test already had at 100 items. For anyone &lt;a href="https://finops.spinov.online/blog/llm-judge-cost-deterministic-pre-gate" rel="noopener noreferrer"&gt;paying per token to run a judge model&lt;/a&gt; over a set, that is a straight line from a stats mistake to a bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  What McNemar does instead
&lt;/h2&gt;

&lt;p&gt;McNemar's test (Quinn McNemar, Psychometrika, 1947, the standard test for paired nominal data) throws away the concordant pairs and looks only at &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt;. The standard error of the difference becomes &lt;code&gt;100*sqrt(b+c)/n&lt;/code&gt;, and the test statistic is &lt;code&gt;|c-b|/sqrt(b+c)&lt;/code&gt;. I added it to the same library as &lt;code&gt;mcnemar(name_a, b, name_c, c, n)&lt;/code&gt;. On the exact same 100 items:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;McNEMAR: discordant pairs prompt A=0, prompt B=7 (concordant 93 of n=100).
  SE = 2.65 pp (100*sqrt(b+c)/n); marginal gap 7.00 pp = 2.65 SE &amp;gt;= 2.0 -&amp;gt; ranking "prompt B" over "prompt A" is allowed.
  small discordant count (b+c=7 &amp;lt; 25): the normal approximation is anti-conservative here. Exact two-sided binomial p=0.0156 (z-equiv 2.42), continuity-corrected z=2.27. all three clear the 2.0 bar; the decision is unchanged.
  NESTED (b=0 or c=0): every discordant item favours the same side; the difference is deterministic (strict nesting on this sample), not a coin-flip margin.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.65 SE against the same 2.0 threshold. Allowed. Same data, opposite decision. And note the third line. With only 7 discordant pairs, 2.65 is a normal approximation, and the tool will not let me lean on it: beside it sit the exact two-sided binomial, &lt;code&gt;p=0.0156&lt;/code&gt; (z-equivalent 2.42), and the continuity-corrected z at 2.27. All three clear the 2.0 bar. Same decision, reached without trusting the largest number in the row. The &lt;code&gt;NESTED&lt;/code&gt; line fires because one discordant count is zero, which means the two prompts never disagreed in both directions: on this sample B dominates A item by item. That is a qualitatively different thing from a noisy 7-point margin, and a test that pools marginals cannot see it.&lt;/p&gt;

&lt;p&gt;One honesty note, because it matters. &lt;code&gt;NESTED&lt;/code&gt; is not a license to rank anything with a zero in it. A pair with &lt;code&gt;b=0, c=1&lt;/code&gt; is also nested, and the tool prints it at 1.00 SE, nowhere near the bar. The ranking decision still rides on the z value. In this case the z is 2.65 and the nesting is strict, so both agree. I flag them separately on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same divergence on 535 real observations
&lt;/h2&gt;

&lt;p&gt;The constructed example is clean but you should distrust anything I can tune to land on the line. So here is the same phenomenon on data I did not hand-pick: a sweep of a monotone-but-not-total marker fixture, &lt;code&gt;P_PATHS=12 W_PER_TICK=3 T_TICKS=400 SEEDS=20&lt;/code&gt;, where a false reject is a landed write that a single-integer witness wrongly rejected.&lt;/p&gt;

&lt;p&gt;The cells of that sweep share one underlying draw. Path and outcome come off the LCG independently of the axis knobs, so the raw &lt;code&gt;(tick, path, outcome)&lt;/code&gt; at each sampled position is identical across cells. The run checks that before it does anything else, across all 33 sampled points per seed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(tick,path,outcome) skew 6:6 vs 7:5            identical: True  (33 sampled points)
(tick,path,outcome) streams 8 vs 4             identical: True  (33 sampled points)
ALL PAIRED (raw observations identical across cells): True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same observations, only the verdict moves. That is the definition of paired, and it is why the two-proportion SE is the wrong tool here too. Cross-tabbing the false-reject indicators gives real &lt;code&gt;b/c&lt;/code&gt; counts. Both tests, side by side:&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;pair                     | b / c / n        | Wald (rank)    | McNemar (paired)
------------------------------------------------------------------------------
&lt;/span&gt;6:6 vs 7:5               | 142 / 128 / 535  | 0.87 SE        | 0.85 SE
7:5 vs 8:4               | 39 / 3 / 535     | 2.31 SE        | 5.55 SE
8:4 vs 9:3               | 39 / 0 / 535     | 2.65 SE        | 6.24 SE NESTED
streams=8 vs streams=4   | 91 / 32 / 535    | 3.98 SE        | 5.32 SE
streams=2 vs streams=1   | 229 / 0 / 535    | 20.01 SE       | 15.13 SE NESTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the third row. 8:4 versus 9:3 is 39 discordant pairs, all in one direction, &lt;code&gt;c=0&lt;/code&gt;. Strict nesting again, on real counts. Both tests clear the 2.0 bar here, so both allow the ranking. But Wald reports 2.65, a hair over the line, while McNemar reports 6.24 and flags NESTED. One of those tells you the result is deterministic on this sample; the other cannot tell "barely over the threshold by luck" apart from "decided." Here is that pair with both guards printing in full:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PAIR 8:4 vs 9:3   marginal FR: 8:4=172/535  9:3=133/535
    8:4: 32.1% (k=172 n=535 SE=2.02)
    9:3: 24.9% (k=133 n=535 SE=1.87)
  RANK: "8:4" &amp;gt; "9:3" - gap 7.29 pp = 2.65 SE &amp;gt;= 2.0. Ranking is allowed.
  McNEMAR: discordant pairs 8:4=39, 9:3=0 (concordant 496 of n=535).
    SE = 1.17 pp (100*sqrt(b+c)/n); marginal gap 7.29 pp = 6.24 SE &amp;gt;= 2.0 -&amp;gt; ranking "8:4" over "9:3" is allowed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two honest observations about that table. First, the two tests never disagree on direction: whichever version each one ranks higher, they agree, on all five pairs. Second, at this 2.0 threshold none of the five flip the decision. Row one, 6:6 vs 7:5, is 0.87 under Wald and 0.85 under McNemar, both below the bar, both refused. Rows two through five sit above the bar for both. What moves is the reported SE, sometimes a lot, 2.31 against 5.55, and whether the &lt;code&gt;c=0&lt;/code&gt; nesting gets named at all.&lt;/p&gt;

&lt;p&gt;And the gap runs both ways, which is why "McNemar is always more powerful" would be the wrong lesson. On 6:6 vs 7:5 Wald reads 0.87 against McNemar's 0.85, and on streams=2 vs streams=1 it reads 20.01 against 15.13: when the discordant pairs are many and lopsided, the independent-samples SE is not the conservative one, it understates the variance instead. The rule is not that one test wins, it is that you owe your data the test its design calls for.&lt;/p&gt;

&lt;p&gt;So where is the decision flip? Near the bar. This particular sweep happens to spread its pairs away from the 2.0 line, so the two tests agree on every call even while their SEs diverge. The constructed 100-item example sits right on the line, which is where the difference between the tests stops being cosmetic and turns into a yes or a no. I did not engineer that to cheat; it is where most config bake-offs I have watched actually get decided, on a handful of items either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  One thing I am deliberately not claiming
&lt;/h2&gt;

&lt;p&gt;You may have noticed those false-reject levels, 75.9%, 64.9%, and want me to say something about them. I will not, and the same library is why. Run the construction-independence probe on those cells and it comes back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FORCED BY CONSTRUCTION [streams=8]: conditional 75.9% (k=406 n=535 SE=1.85) is indistinguishable from unconditional 74.7% (k=493 n=660 SE=1.69) (0.48 SE &amp;lt; 2.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The level equals the unconditional pass-the-gate rate, 74.7% at &lt;code&gt;n=660&lt;/code&gt;, because the fixture draws outcome and stamp from separate LCG steps. So the level is an artifact of my constants, not a measurement, and I quote none of them as findings. What survives that probe is the discordance structure, &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt;, which is a genuine between-cell comparison. The McNemar inputs are real; the levels they sit next to are not. Running the probe and reporting the result is the only reason I trust the distinction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix, in about fifteen lines
&lt;/h2&gt;

&lt;p&gt;There is nothing clever in it. It is the discordant count and a square root:&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="n"&gt;disc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
    &lt;span class="n"&gt;conc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;disc&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;se&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;disc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="n"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="n"&gt;n_se&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;disc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# = gap / se, n cancels
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;c&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="c1"&gt;# NESTED: strict set-nesting, deterministic on this sample
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Truncated by me: the wrapper handles &lt;code&gt;b==c&lt;/code&gt;, the no-discordant case, and localized output; full function in &lt;code&gt;measure.py&lt;/code&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The formula is the easy part. The part that actually protects you is upstream and it is not in this function at all: the library cannot know your two runs are paired. You have to establish that yourself, by confirming the observations are the same items in the same order and only the outcome moved. That is the check the sweep runs before it calls &lt;code&gt;mcnemar()&lt;/code&gt; at all, and if the raw observations had differed it would have refused to treat them as paired. A paired test on unpaired data is its own mistake.&lt;/p&gt;

&lt;p&gt;The limits, plainly. &lt;code&gt;SE = 100*sqrt(b+c)/n&lt;/code&gt; is the normal approximation to McNemar; for a handful of discordant pairs you want the exact binomial instead, which is why the function prints it automatically once &lt;code&gt;b+c&lt;/code&gt; drops below 25, and I would not read the plain SE without it. The z threshold of 2.0 is a convention I carried over from the rest of the harness, not a law. And none of this touches the failure mode that actually costs you money, a silent false accept, because that is a question about your ground truth, not your arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does yours do
&lt;/h2&gt;

&lt;p&gt;Everything here ran locally on a synthetic fixture: Python 3.13.5, stdlib only, offline, no keys, no funds, three runs byte-identical, exit 0, empty stderr. Two earlier pieces work the same eval harness from other angles: &lt;a href="https://finops.spinov.online/blog/severity-gate-not-pass-rate" rel="noopener noreferrer"&gt;reading eval results by severity class instead of a flat pass rate&lt;/a&gt; and &lt;a href="https://finops.spinov.online/blog/eval-contamination-probe" rel="noopener noreferrer"&gt;a static probe that found contamination points without running the agent&lt;/a&gt;. Both belong to the same family of &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents" rel="noopener noreferrer"&gt;pre-execution gates for AI agents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I publish the runs that corrected my own reading, not only the ones that confirmed it. Follow along if that is your kind of thing. And a real question I do not have a clean answer to: when your last A/B put two agent versions at 76 and 74 out of 100 on the same set, did your harness run a paired test, or did it pool two marginals and quietly ask you for more data? I would like to know what yours does.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI disclosure. I drafted this with an AI writing assistant and edited every line; the test choice, the sweep, and the reading are mine. Every output block is pasted from one real local run on 2026-07-21. &lt;code&gt;measure.py&lt;/code&gt; sha256 &lt;code&gt;b1b3702bccb6ab46…&lt;/code&gt;, &lt;code&gt;paired_test.py&lt;/code&gt; sha256 &lt;code&gt;fa687e05e8e69512…&lt;/code&gt;, run output sha256 &lt;code&gt;d8e4e521b1f35ac0…&lt;/code&gt;. The library default stays Russian so the sha256 of two already-published runs keeps verifying; English is opted into explicitly.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>agents</category>
      <category>datascience</category>
    </item>
    <item>
      <title>A Spend Cap That Stops Counting Is Already Fail-Open</title>
      <dc:creator>Alexey Spinov</dc:creator>
      <pubDate>Sun, 19 Jul 2026 01:53:12 +0000</pubDate>
      <link>https://dev.to/alex_spinov/a-spend-cap-that-stops-counting-is-already-fail-open-4mi</link>
      <guid>https://dev.to/alex_spinov/a-spend-cap-that-stops-counting-is-already-fail-open-4mi</guid>
      <description>&lt;p&gt;Two of the five ways a spend cap can handle a missing price produce the &lt;strong&gt;exact same decision stream&lt;/strong&gt; — same sha256, byte for byte. One of them is the thing everybody calls fail-open. The other is the thing everybody recommends instead of it: fall over to a free local model.&lt;/p&gt;

&lt;p&gt;Let me say what that hash is and isn't before it does any work. It covers &lt;code&gt;(seq, label, admitted, charge)&lt;/code&gt; and deliberately drops the human-readable reason strings, so two policies that print different words hash the same when they decide the same. Once you see that, the collision is a theorem rather than a discovery: a free fallback charges zero, fail-open charges zero, and a ledger built out of charges cannot tell them apart because there is nothing there to tell apart. The sha256 proves only that my implementation doesn't quietly cheat.&lt;/p&gt;

&lt;p&gt;The reason it's still worth a post is that nobody ships them as the same policy. One is the bug you apologise for; the other is the fix you recommend in the thread. On the axis that matters they are one policy, and one of them has better branding.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI disclosure.&lt;/strong&gt; I wrote &lt;code&gt;blind_spend_cap.py&lt;/code&gt; with AI assistance and ran it myself. Every number and hash below is pasted from a real run: offline, stdlib only, no network, no keys, no funds. The oracle is injected, so runs are deterministic. I ran it three times; the output was byte-identical each time. Code sha256 &lt;code&gt;ddc42590…&lt;/code&gt;, output sha256 &lt;code&gt;9ebe1b4a…&lt;/code&gt;. External figures are linked and labeled, and I say clearly which ones I did not reproduce.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A spend cap needs a cost-oracle to price the next action. The oracle has its own outage.&lt;/li&gt;
&lt;li&gt;The usual framing (fail-open vs fail-closed) is the wrong axis. The real split: &lt;strong&gt;does the ledger keep moving while the oracle is quiet?&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Strategies that charge a price the remaining budget can still absorb keep the ledger alive and re-trip the cap. Charge zero and &lt;code&gt;spent&lt;/code&gt; freezes forever. Charge more than fits and you've written &lt;code&gt;refuse&lt;/code&gt; with extra steps — the harness proves that one on itself.&lt;/li&gt;
&lt;li&gt;A free local fallback charges zero. In my harness it produces a decision stream identical to plain fail-open: same sha256.&lt;/li&gt;
&lt;li&gt;But a moving ledger is a floor, not a certificate. Any positive fiction satisfies it — price a &lt;code&gt;$0.05&lt;/code&gt; call at &lt;code&gt;$0.01&lt;/code&gt; and your cap is quietly five times the one you configured. The real axis is the &lt;strong&gt;bias of your estimator&lt;/strong&gt;; zero is just where that bias hits −100%.&lt;/li&gt;
&lt;li&gt;The headline number you'd expect me to use here (34 extra actions) is arithmetic, not evidence. I take it apart below rather than sell it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The branch nobody writes down
&lt;/h2&gt;

&lt;p&gt;Every spend cap I've shipped has the same shape. Price the action, compare against a budget, allow or block. The pricing step assumes the oracle answers.&lt;/p&gt;

&lt;p&gt;It doesn't always. CoinGecko 429s. A usage endpoint times out. A token meter sits behind a gateway returning 526. In that moment your cap takes a decision that probably isn't in your code review notes, because it isn't in your code: what to do with an action it cannot price.&lt;/p&gt;

&lt;p&gt;I know it's unwritten because I shipped it that way. On June 8, 2026 I published &lt;a href="https://finops.spinov.online/blog/a-47k-agent-loop-spend-cap/" rel="noopener noreferrer"&gt;SpendGuard&lt;/a&gt;, a 40-line pre-execution cap. It works, and it never declared this branch. The oracle call sits inside &lt;code&gt;cost_fn&lt;/code&gt; on line 121, and &lt;code&gt;eth_price_usd()&lt;/code&gt; calls &lt;code&gt;raise_for_status()&lt;/code&gt; before it returns anything. So on a 429 the exception blows straight past the gate and out of the wrapper. Accidentally fail-closed, by way of an uncaught exception that takes the caller down instead of returning a verdict you can count.&lt;/p&gt;

&lt;p&gt;Copy the demo loop from that same post and you get the opposite. It prices once &lt;em&gt;before&lt;/em&gt; the loop and reuses that number for every round. A mid-loop outage is invisible. Accidental fail-open.&lt;/p&gt;

&lt;p&gt;Same file, two wirings, two opposite behaviors, and I declared neither.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five strategies, one fork
&lt;/h2&gt;

&lt;p&gt;So I built the smallest thing that isolates the branch. &lt;code&gt;blind_spend_cap.py&lt;/code&gt; runs one Analyzer/Verifier ping-pong through one budget gate, under five strategies that are identical everywhere except the &lt;code&gt;quote is None&lt;/code&gt; block:&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;else&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;strategy&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;_v&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK&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;no quote: refuse&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;priced&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;strategy&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admit-unpriced&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="nf"&gt;_v&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ADMIT&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;no quote: admit, charge 0 (ledger frozen)&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;priced&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;strategy&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stale&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;last_known&lt;/span&gt; &lt;span class="ow"&gt;is&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;return&lt;/span&gt; &lt;span class="nf"&gt;_v&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK&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;no quote and no last-known price: refuse&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;priced&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;est&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_known&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stale &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pessimistic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;est&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;per_action_cap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pessimistic &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fallback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;est&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fallback_cents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fallback &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two design choices worth stating, because both cut against the result I might have wanted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A BLOCK does not stop the loop.&lt;/strong&gt; A real runaway retries. My earlier harness gave the refusing policy a free &lt;code&gt;break&lt;/code&gt;, which quietly handed it the win: it "stopped the runaway" because I wrote the loop that way. Here the gate stops the &lt;em&gt;spend&lt;/em&gt;, not the work, and the loop keeps hammering. There's an &lt;code&gt;--on-block halt&lt;/code&gt; flag for the single-shot shape, and I sweep both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The budget has no clock&lt;/strong&gt;, so I call it a per-run budget rather than a daily one. Calling it daily would be a lie in a file with no time in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The split isn't admit-vs-block. It's counting-vs-not.
&lt;/h2&gt;

&lt;p&gt;Here's the outage run, oracle down from step 6, straight from &lt;code&gt;output.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SCENARIO B — oracle down from step 6, loop retries after a BLOCK
  refuse           admitted=6   spent=$0.30  unpriced=0   unaccounted=0   ledger-moved=False exit(conv)=1 exit(strict)=1
  admit-unpriced   admitted=40  spent=$0.30  unpriced=34  unaccounted=34  ledger-moved=False exit(conv)=0 exit(strict)=2
  stale            admitted=10  spent=$0.50  unpriced=4   unaccounted=0   ledger-moved=True  exit(conv)=1 exit(strict)=2
  pessimistic      admitted=6   spent=$0.30  unpriced=0   unaccounted=0   ledger-moved=False exit(conv)=1 exit(strict)=1
  fallback:0c      admitted=40  spent=$0.30  unpriced=34  unaccounted=34  ledger-moved=False exit(conv)=0 exit(strict)=2
  fallback:1c      admitted=26  spent=$0.50  unpriced=20  unaccounted=0   ledger-moved=True  exit(conv)=1 exit(strict)=2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the &lt;code&gt;spent&lt;/code&gt; column, not the &lt;code&gt;admitted&lt;/code&gt; one.&lt;/p&gt;

&lt;p&gt;Exactly two strategies end at &lt;code&gt;$0.50&lt;/code&gt;: &lt;code&gt;stale&lt;/code&gt; and &lt;code&gt;fallback:1c&lt;/code&gt;. That's the budget, tripped, doing its job. &lt;code&gt;admit-unpriced&lt;/code&gt; and &lt;code&gt;fallback:0c&lt;/code&gt; end at &lt;code&gt;$0.30&lt;/code&gt; and stay there — not because the run was cheap, but because after step 6 nothing was ever added to the ledger again.&lt;/p&gt;

&lt;p&gt;Now the row that breaks the tidy version of this claim, which I had written as "every strategy that charges &lt;em&gt;something&lt;/em&gt; ends at &lt;code&gt;$0.50&lt;/code&gt;" until the table two lines above told me otherwise. &lt;code&gt;pessimistic&lt;/code&gt; charges the most of anybody and still ends at &lt;code&gt;$0.30&lt;/code&gt;. Charging something isn't sufficient. The something has to &lt;em&gt;fit&lt;/em&gt;. &lt;code&gt;pessimistic&lt;/code&gt; prices every un-priced call at the &lt;code&gt;$0.25&lt;/code&gt; per-action cap, only &lt;code&gt;$0.20&lt;/code&gt; of budget remains after step 6, so every un-priced call is blocked on arrival: &lt;code&gt;admitted=6&lt;/code&gt;, &lt;code&gt;unpriced=0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Which changes how you read the &lt;code&gt;unaccounted&lt;/code&gt; column. It counts admissions where the oracle gave no quote and nothing was charged — defined by the fact of a missing quote, not by the name of the policy, so it can accuse any strategy including the ones I like. &lt;code&gt;stale&lt;/code&gt; and &lt;code&gt;fallback:1c&lt;/code&gt; sit at zero because they kept counting. &lt;code&gt;pessimistic&lt;/code&gt; and &lt;code&gt;refuse&lt;/code&gt; sit at zero because they never admitted a blind call in the first place. Same number, two different stories, and I'd been reading the flattering one into both.&lt;/p&gt;

&lt;p&gt;Push it to the limit and the failure gets loud. Oracle dead from step 0:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 blind_spend_cap.py &lt;span class="nt"&gt;--strategy&lt;/span&gt; admit-unpriced &lt;span class="nt"&gt;--oracle-fails-from&lt;/span&gt; 0
&lt;span class="gp"&gt;admit-unpriced   admitted=40  spent=$&lt;/span&gt;0.00  &lt;span class="nv"&gt;unpriced&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;40  &lt;span class="nv"&gt;unaccounted&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;40  ledger-moved&lt;span class="o"&gt;=&lt;/span&gt;False &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;conv&lt;span class="o"&gt;)=&lt;/span&gt;0 &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;strict&lt;span class="o"&gt;)=&lt;/span&gt;2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forty actions admitted. Ledger says zero dollars. Exit code zero, under the mapping most gates actually ship.&lt;/p&gt;

&lt;p&gt;That's the thing worth internalizing. A blind cap doesn't report danger. It reports &lt;strong&gt;innocence&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number I'm not putting in the headline
&lt;/h2&gt;

&lt;p&gt;You'd expect the pitch to be "fail-open admitted 34 more actions." The tool does print it. I'm going to argue against it anyway, because I got burned by exactly this number last time.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;M&lt;/code&gt; is the gap in admitted actions between &lt;code&gt;admit-unpriced&lt;/code&gt; and &lt;code&gt;refuse&lt;/code&gt;. In the run above it's 34. Sweep the outage step across the whole parameter space and you get this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  on-block=retry
        K:    0    5    6    9   10   11   12   20   39   40
        M:   40   35   34   31   30   29   28   20    1    0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(That's the &lt;code&gt;retry&lt;/code&gt; half of the sweep with the &lt;code&gt;unacc&lt;/code&gt; row dropped for now — under &lt;code&gt;retry&lt;/code&gt; it's identical to &lt;code&gt;M&lt;/code&gt; anyway. The full block, both loop shapes, is two sections down.)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;M = WANTS − K&lt;/code&gt;, exactly, everywhere. I picked &lt;code&gt;WANTS = 40&lt;/code&gt;. If I'd picked 1000, the headline would read 994. It isn't a property of any policy, it's a property of how long I let the loop want things. A number I chose, dressed up as a number I found.&lt;/p&gt;

&lt;p&gt;Which is why the strategy table above leads with &lt;code&gt;spent&lt;/code&gt; and &lt;code&gt;unaccounted&lt;/code&gt;, and why &lt;code&gt;M&lt;/code&gt; is buried in a scenario that tells you to go read the sweep before quoting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pressing my own kill switch
&lt;/h2&gt;

&lt;p&gt;The honest question isn't whether my metric works in the run I picked. It's where it stops working. So here's the same sweep under both loop shapes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  on-block=retry
        K:    0    5    6    9   10   11   12   20   39   40
        M:   40   35   34   31   30   29   28   20    1    0
    unacc:   40   35   34   31   30   29   28   20    1    0
    -&amp;gt; M = 0 in 1/41 of K (2%); unaccounted = 0 in 1/41 (2%)
  on-block=halt
        K:    0    5    6    9   10   11   12   20   39   40
        M:   40   35   34   31   30    0    0    0    0    0
    unacc:   40   35   34   31   30    0    0    0    0    0
    -&amp;gt; M = 0 in 30/41 of K (73%); unaccounted = 0 in 30/41 (73%)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under &lt;code&gt;halt&lt;/code&gt; semantics, everything I've argued dies in &lt;strong&gt;73% of the parameter space&lt;/strong&gt;. Not weakens. Dies, to identically zero, both metrics at once.&lt;/p&gt;

&lt;p&gt;The reason is dull and important. The budget is &lt;code&gt;$0.50&lt;/code&gt;, a healthy call is &lt;code&gt;$0.05&lt;/code&gt;, so a healthy run hits the cap at step 10. If the oracle only falls over at step 11 or later, the loop is already stopped by money. The un-priced branch is never reached. Nothing to measure, nothing to argue about.&lt;/p&gt;

&lt;p&gt;So the applicability condition, which my previous draft never stated and which I'm stating plainly now: &lt;strong&gt;this post is about outages that start before your budget would have stopped the loop anyway&lt;/strong&gt; — &lt;code&gt;K &amp;lt;= budget // unit_cost&lt;/code&gt;, which is &lt;code&gt;K &amp;lt;= 10&lt;/code&gt; here — &lt;strong&gt;or about loops that retry after being refused.&lt;/strong&gt; Outside those two cases the whole thing is a non-event.&lt;/p&gt;

&lt;p&gt;That boundary was off by one in the tool until this morning: the summary line said the metrics collapse once &lt;code&gt;K &amp;gt;= 10&lt;/code&gt;, when &lt;code&gt;K = 10&lt;/code&gt; is the last K where they're still alive and &lt;code&gt;K = 11&lt;/code&gt; is the first dead one. The sweep table underneath it was right the whole time. A decent argument for printing the table and not just the conclusion drawn from it.&lt;/p&gt;

&lt;p&gt;I think retrying is the common case, because runaway agent loops are usually retry loops. That's a judgement about the world, not a measurement, and I'm flagging it as one.&lt;/p&gt;

&lt;h2&gt;
  
  
  A free fallback is fail-open with better branding
&lt;/h2&gt;

&lt;p&gt;Now the equivalence. Same outage, and I hash the decision stream — the &lt;code&gt;(seq, label, admitted, charge)&lt;/code&gt; tuples, deliberately excluding the human-readable reason strings, so two strategies that make the same decisions hash the same even when they print different words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SCENARIO C — is a free fallback a distinct strategy, or a renamed fail-open?
  admit-unpriced   sha=53b01d22a232f1fee833a76c7cd1ed810d1945da2e7620c8a1a17a9302b4df79
  fallback:0c      sha=53b01d22a232f1fee833a76c7cd1ed810d1945da2e7620c8a1a17a9302b4df79
  stale            sha=0dcbd560f59adcf2b1eec3ca111dc7f89c3e7ac08569fe165d88ec7af77b4311
  fallback:5c      sha=0dcbd560f59adcf2b1eec3ca111dc7f89c3e7ac08569fe165d88ec7af77b4311
  refuse           sha=7df1f34491edfde21648d36e9a8eda1db7306d12efda4c29364fa8f54ad3b04a
  pessimistic      sha=7df1f34491edfde21648d36e9a8eda1db7306d12efda4c29364fa8f54ad3b04a
  -&amp;gt; fallback:0c  == admit-unpriced : True
  -&amp;gt; fallback:5c  == stale          : True  (needs &amp;gt;=1 real quote before the outage, and a constant oracle price)
  -&amp;gt; pessimistic  == refuse         : True  (at THESE caps: $0.25 never fits what is left of $0.50)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three collisions in that block, and the third one costs me a third of my own recommendation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;fallback:0c == admit-unpriced&lt;/code&gt;&lt;/strong&gt; is the headline, and — as I said up top — a theorem. Both charge zero, the hash covers the charge. I couldn't break it by moving the budget, the unit cost, the per-action cap, the loop shape or the outage step; it isn't a coincidence of the parameters I picked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;fallback:5c == stale&lt;/code&gt;&lt;/strong&gt; is real but conditional, and I stated it as a general truth in an earlier pass. It needs two things I'd left unsaid. There has to be at least one successful quote before the outage — with &lt;code&gt;--oracle-fails-from 0&lt;/code&gt; there's no last-known price at all, &lt;code&gt;stale&lt;/code&gt; refuses, and the equality collapses. And the oracle's price has to actually hold still; point the harness at a varying price and the two decision streams separate immediately. The honest version is narrower: &lt;em&gt;a fallback pinned to the real rate is stale pricing, as long as the real rate isn't moving.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;pessimistic == refuse&lt;/code&gt;&lt;/strong&gt; was sitting in my own main table and I walked past it twice. Same &lt;code&gt;7df1f344…&lt;/code&gt;. A &lt;code&gt;$0.25&lt;/code&gt; estimate never fits the &lt;code&gt;$0.20&lt;/code&gt; left after step 6, so &lt;code&gt;pessimistic&lt;/code&gt; blocks every un-priced call — which is &lt;code&gt;refuse&lt;/code&gt;, decision for decision. Run &lt;code&gt;--strategy fallback --fallback 25 --oracle-fails-from 6&lt;/code&gt; and you get &lt;code&gt;7df1f344…&lt;/code&gt; as well. Three names, one behavior.&lt;/p&gt;

&lt;p&gt;That forces an admission about my own advice. Further down I tell you to charge a stale price, or a conservatively biased estimate, or the per-action cap. At the parameters in my own demo that last one is bit-identical to the refusal I declined to recommend — which is why it now ships with that caveat attached instead of posing as a third independent door. It doesn't make the advice wrong. Refusing is defensible, and I spend a whole section on its bill.&lt;/p&gt;

&lt;p&gt;The price sweep is where this gets concrete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SCENARIO D — sweep the fallback price (the whole argument hangs on it)
  fallback:0c      admitted=40  spent=$0.30  unpriced=34  unaccounted=34  ledger-moved=False exit(conv)=0 exit(strict)=2
  fallback:1c      admitted=26  spent=$0.50  unpriced=20  unaccounted=0   ledger-moved=True  exit(conv)=1 exit(strict)=2
  fallback:2c      admitted=16  spent=$0.50  unpriced=10  unaccounted=0   ledger-moved=True  exit(conv)=1 exit(strict)=2
  fallback:5c      admitted=10  spent=$0.50  unpriced=4   unaccounted=0   ledger-moved=True  exit(conv)=1 exit(strict)=2
  fallback:25c     admitted=6   spent=$0.30  unpriced=0   unaccounted=0   ledger-moved=False exit(conv)=1 exit(strict)=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the clean line I wanted to write — "the runaway terminates if and only if the price is above zero" — is not true, and the bottom row is the counterexample. &lt;code&gt;fallback:25c&lt;/code&gt; prices well above zero and &lt;code&gt;spent&lt;/code&gt; still sticks at &lt;code&gt;$0.30&lt;/code&gt; with the ledger frozen. What a price above zero actually buys is a ledger that keeps &lt;strong&gt;moving&lt;/strong&gt;, and only while that price still fits what's left of the budget. Under that band you get fail-open. Over it you get refusal wearing a price tag. The usable range is narrower than "not zero", and where it sits depends on caps I picked.&lt;/p&gt;

&lt;p&gt;Worth being exact about the edge, because I rounded it off in an earlier pass. The price has to clear &lt;code&gt;min(per-action cap, budget − spent when the outage begins)&lt;/code&gt; — here &lt;code&gt;min($0.25, $0.20)&lt;/code&gt;, so the collision with &lt;code&gt;refuse&lt;/code&gt; actually starts at &lt;code&gt;$0.21&lt;/code&gt;, and &lt;code&gt;$0.25&lt;/code&gt; is just the row I happened to print. Which of the two terms binds depends on when the oracle dies: at step 6 it's the leftover budget, at step 4 it's the per-action cap. So "too expensive to fit" isn't a property of the price alone — it's the price measured against however much budget the outage left you.&lt;/p&gt;

&lt;p&gt;This matters because the free version is the one people ship. On July 15, 2026, a developer publishing as &lt;a href="https://dev.to/ddhh/i-built-a-tiny-llm-circuit-breaker-when-the-budget-runs-out-it-fails-over-to-a-local-model-30ka"&gt;@ddhh released a small LLM circuit breaker&lt;/a&gt; with a clean statement of the instinct: &lt;em&gt;"When I'm about to overspend, don't fail and don't keep paying — fall through to a free local model and keep working."&lt;/em&gt; The config in the post marks the tier &lt;code&gt;# local, free, always-on fallback&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I want to be precise about his design rather than convenient, so I read the &lt;a href="https://github.com/qkrehgk1-wq/llm-circuit-breaker" rel="noopener noreferrer"&gt;repo&lt;/a&gt; instead of the headline. The gate I'm comparing against is his &lt;strong&gt;budget&lt;/strong&gt; gate: &lt;code&gt;_tier_order()&lt;/code&gt; switches to local-only tiers once &lt;code&gt;_budget_exhausted()&lt;/code&gt;, and if no local tier is configured it raises &lt;code&gt;BudgetExceeded&lt;/code&gt; rather than continuing. That gate keys on accumulated spend against a limit — not on a missing quote. That's a declared branch with a hard stop, which is more than my June code had.&lt;/p&gt;

&lt;p&gt;Provider failure is handled too, just not by that gate. His post opens on exactly that problem — &lt;em&gt;"Paid API quotas dying mid-run. One provider 429s, and the whole run falls over"&lt;/em&gt; — and &lt;code&gt;complete()&lt;/code&gt; wraps each tier in an &lt;code&gt;except … continue&lt;/code&gt; (&lt;code&gt;breaker.py:121-131&lt;/code&gt;, commented "a failed tier should never crash the caller"), so a 429 on a paid tier falls through to the next one, local included. Two triggers, one ordered failover. The reason I'm separating them carefully is that the thing I care about is which signal moves the ledger, and on both paths the local tier reports the same number: &lt;code&gt;ollama_tier._call&lt;/code&gt; ends with &lt;code&gt;return text, 0.0&lt;/code&gt; (&lt;code&gt;providers.py:167&lt;/code&gt;), which &lt;code&gt;_record&lt;/code&gt; writes into the JSONL ledger as &lt;code&gt;cost_usd: 0.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For his stated goal that is &lt;strong&gt;correct&lt;/strong&gt;, and I'll say so flatly: a free local call really does cost zero dollars, so his ledger is accurate and his dollar spend genuinely cannot grow past the limit. He solved the problem he set out to solve.&lt;/p&gt;

&lt;p&gt;The pattern risk lives one step to the side. Once every call costs zero, the dollar ledger can never stop anything again — and if the thing that pushed you over budget was a non-converging loop rather than an expensive model, dollars were never the binding constraint. Wall-clock, local GPU, rate-limited downstream endpoints and side effects all keep accruing, and the ledger reports &lt;code&gt;$0.00&lt;/code&gt; while they do. Exactly the &lt;code&gt;fallback:0c&lt;/code&gt; row above.&lt;/p&gt;

&lt;p&gt;None of that is a defect in his breaker. It's what the graceful-degradation instinct does when you port it from "budget exhausted" to "cost unknown" without noticing the axis changed.&lt;/p&gt;

&lt;p&gt;For scale on why any of this is worth an afternoon: on July 16, 2026 &lt;a href="https://dev.to/royanannya/my-multi-agent-ai-cost-1847-in-one-weekend-heres-the-fix-that-cut-it-82-3mi4"&gt;@royanannya published a postmortem&lt;/a&gt; of a multi-agent loop that billed &lt;strong&gt;$1,847 in one weekend&lt;/strong&gt;, fixed by pushing decisions off the LLM layer and cutting per-game cost from $1.95 to $0.35. Their number, their run; I didn't reproduce it. Their fix was architectural, not a spend cap, and I'm not going to pretend otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where my own metric stops meaning anything
&lt;/h2&gt;

&lt;p&gt;I've been leaning on &lt;code&gt;unaccounted&lt;/code&gt; as though it measures how accurately you're counting. It doesn't. It's a binary test — &lt;em&gt;did an admitted call with no quote get charged zero?&lt;/em&gt; — and the counterexample is sitting in my own output.&lt;/p&gt;

&lt;p&gt;The harness has no notion of what an action actually costs. Every admit really executes, and a real call here is &lt;code&gt;$0.05&lt;/code&gt;. Multiply the &lt;code&gt;admitted&lt;/code&gt; column by that and set it beside the ledger:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;strategy&lt;/th&gt;
&lt;th&gt;ledger says&lt;/th&gt;
&lt;th&gt;actually spent&lt;/th&gt;
&lt;th&gt;vs the &lt;code&gt;$0.50&lt;/code&gt; budget&lt;/th&gt;
&lt;th&gt;&lt;code&gt;unaccounted&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stale&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;1.0x&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fallback:5c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;1.0x&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fallback:1c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1.30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.6x&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fallback:0c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$0.30&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;4.0x&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;fallback:1c&lt;/code&gt; passes my metric cleanly. &lt;code&gt;unaccounted=0&lt;/code&gt;, &lt;code&gt;ledger-moved=True&lt;/code&gt;, budget tripped on schedule — and I wrote "that's the budget, tripped, doing its job" about that exact row. It also pushed 26 real calls through a ten-call budget. Pricing a &lt;code&gt;$0.05&lt;/code&gt; action at &lt;code&gt;$0.01&lt;/code&gt; under-counts by 5x, and a cap that under-counts by 5x is a cap five times larger than the one you configured.&lt;/p&gt;

&lt;p&gt;So "keep the ledger moving" is satisfied by any positive fiction. The real axis isn't zero versus non-zero — it's the &lt;strong&gt;bias of your estimator&lt;/strong&gt;. Zero is simply the point where the bias hits −100% and the cap stops existing at all. It's the worst case and it's the common case, which is why it earns a post, but &lt;code&gt;unaccounted=0&lt;/code&gt; is a floor, not a certificate. If your fallback price is a comfortable number rather than a conservative one, you haven't fixed the runaway. You've slowed it down and moved it out of view.&lt;/p&gt;

&lt;p&gt;There's a second place my instrument lies, and I found it checking this piece rather than writing it. The &lt;code&gt;ledger-moved&lt;/code&gt; column samples &lt;code&gt;spent&lt;/code&gt; &lt;em&gt;after&lt;/em&gt; each blind decision, so what it actually asks is "did the ledger move more than once?" A price that fits exactly once — try &lt;code&gt;--fallback 15&lt;/code&gt; — pushes &lt;code&gt;spent&lt;/code&gt; from &lt;code&gt;$0.30&lt;/code&gt; to &lt;code&gt;$0.45&lt;/code&gt; and still prints &lt;code&gt;ledger-moved=False&lt;/code&gt;, the same value fail-open gets. None of the rows in this post are affected; they sit at 0c, 1c, 2c, 5c and 25c. But if you sweep the price yourself you'll walk into it, and it's the same conflation I've spent the whole post complaining about, sitting in my own column. Trust &lt;code&gt;spent&lt;/code&gt;. The boolean is a convenience and I got it wrong.&lt;/p&gt;

&lt;p&gt;One caveat on that table, because it cuts against my own framing: it assumes an un-priced call costs what a healthy one costs. If your fallback genuinely is a free local model, the dollar figure really is zero and the &lt;code&gt;fallback:0c&lt;/code&gt; row overstates the dollars. That's precisely &lt;a class="mentioned-user" href="https://dev.to/ddhh"&gt;@ddhh&lt;/a&gt;'s case — and precisely why what escapes there is wall-clock, GPU and downstream rate limits rather than dollars.&lt;/p&gt;

&lt;h2&gt;
  
  
  What refusing actually costs you
&lt;/h2&gt;

&lt;p&gt;I've been describing the failure mode of not counting. Refusing has its own bill, and my last draft skipped it, which was the honest complaint against it.&lt;/p&gt;

&lt;p&gt;First, the recovery case. Outages end. Oracle down from step 6, back at step 15:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SCENARIO F — the outage ENDS: oracle down 6..14, back at 15
  loop retries after a BLOCK (a real runaway does):
  refuse           admitted=10  spent=$0.50  unpriced=0   unaccounted=0   ledger-moved=False exit(conv)=1 exit(strict)=1
  same run, but the loop HALTS on the first BLOCK (single-shot shape):
  refuse           admitted=6   spent=$0.30  unpriced=0   unaccounted=0   ledger-moved=n/a   exit(conv)=1 exit(strict)=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The scenario prints &lt;code&gt;admit-unpriced&lt;/code&gt; and &lt;code&gt;stale&lt;/code&gt; rows too; I've kept only &lt;code&gt;refuse&lt;/code&gt; here, because it's the policy on trial.)&lt;/p&gt;

&lt;p&gt;Under retry, refusing costs nothing: the run resumes and completes the same ten actions it would have anyway. Under halt, it ends the run at step 6 and never sees the oracle come back. Same policy, same outage, and whether refusing is free or expensive depends entirely on a property of your caller that isn't in the cap at all.&lt;/p&gt;

&lt;p&gt;Three more costs, none of which my harness measures:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You hand your stop button to a third party.&lt;/strong&gt; If your gate refuses whenever CoinGecko is unreachable, then CoinGecko's rate limiter is now your kill switch, and anyone who can degrade it can halt your agent remotely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refusing is not automatically safe.&lt;/strong&gt; The fail-closed doctrine arrives from authorization, where denial is the safe default. Spending isn't authorization. Stopping halfway through a non-idempotent sequence — and SpendGuard was written for ETH and gas — can be worse than admitting one un-priced call. If your actions aren't safely interruptible, "refuse" is not the free option it looks like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The escape hatch never gets closed.&lt;/strong&gt; Any &lt;code&gt;--allow-unpriced&lt;/code&gt; flag will be added to a systemd unit at 3am during an incident and will still be there next year. If you build one, give it an expiry.&lt;/p&gt;

&lt;p&gt;Which is why the recommendation of this post isn't "fail closed." It's narrower: &lt;strong&gt;declare the branch, and keep the ledger moving with a price you'd defend out loud.&lt;/strong&gt; Charge a stale price. Charge an estimate biased high rather than convenient. Charge the per-action cap if you accept what my own harness showed above — that on a tight budget this is refusal under a different name. Just never charge zero and call it accounting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two exit codes, and which one is an opinion
&lt;/h2&gt;

&lt;p&gt;The tool prints exits under two mappings, because the difference is the trap:&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="n"&gt;EXIT_CONVENTIONAL&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;PASS&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ADMIT&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERROR&lt;/span&gt;&lt;span class="sh"&gt;"&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;EXIT_STRICT&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;PASS&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ADMIT&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERROR&lt;/span&gt;&lt;span class="sh"&gt;"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;conventional&lt;/code&gt; is what most gates ship: an admitted action is a success. Under it, the blind run exits &lt;strong&gt;0&lt;/strong&gt; while 34 actions went through un-priced. Your orchestrator sees green and moves on.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;strict&lt;/code&gt; treats "admitted without a quote" as its own outcome. Under it no strategy exits 0 during an outage — refusing gets a 1, admitting blind gets a 2. Nobody gets a clean run when the oracle is down, which seems right to me.&lt;/p&gt;

&lt;p&gt;That second mapping is &lt;strong&gt;my opinion, and I'm labeling it as one.&lt;/strong&gt; My last attempt at this put &lt;code&gt;ADMIT: 0&lt;/code&gt; in the table, then acted amazed that fail-open exited green — I'd assumed the conclusion and called it a finding. The counts are the evidence. The exit code is a choice, and you should make your own.&lt;/p&gt;

&lt;p&gt;The tool also exits worst-wins in every mode including the demo, so the default run exits &lt;code&gt;3&lt;/code&gt;. It contains a deliberate oracle fault. A file about failures laundered into green zeros doesn't get to launder its own.&lt;/p&gt;

&lt;p&gt;The oracle-fault path catches magnitude, not just sign, since a cents-versus-dollars mixup is the classic cost-oracle bug:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      seq 3: oracle-untrusted: quote 500 is 100x last known 5 (unit error, not a price)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;100x&lt;/code&gt; jump is caught as a broken oracle instead of an expensive action. The threshold is &lt;code&gt;20x&lt;/code&gt; and it's a judgement call, and it can't fire on the first quote of a run because there's nothing to compare against yet.&lt;/p&gt;

&lt;p&gt;It's also one-sided, which I only noticed while writing this up. It catches a quote that's too big and sails straight past the mirror-image bug: dollars arriving as cents, every action priced at a hundredth of what it costs. For a spend cap that's the more dangerous direction — it under-counts instead of blocking, which is the same failure as everything else in this post — and my check doesn't cover it. It's marked as a known gap in the source rather than quietly left there.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A benchmark of your bill.&lt;/strong&gt; The constants ($0.05 a call, a $0.50 budget, 40 rounds) exist to make the branch legible. The transferable part is the shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proof that outages and runaways co-occur.&lt;/strong&gt; I believe they do, because a runaway hammering a rate-limited endpoint is often the thing knocking its own price feed over. A synthetic harness can't show that, and I'm not claiming it does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A verdict on anyone's library.&lt;/strong&gt; The equivalence is about the pattern of pricing a fallback at zero. It reproduces with &lt;code&gt;--strategy fallback --fallback 0 --oracle-fails-from 6&lt;/code&gt; in twenty lines of my own code. (Leave the outage flag off and you get a healthy-oracle run where the branch never fires — which proves nothing, as Scenario A says out loud.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A correctness proof.&lt;/strong&gt; The sha256s show determinism, nothing more. A wrong program reproduces byte-for-byte just as well as a right one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A model of how commercial budget systems fail.&lt;/strong&gt; AWS Budgets, GCP billing and most usage endpoints don't go quiet — they lag, then reconcile, and what you couldn't see gets billed to you later. My gate has no true-up: an estimate charged during the outage is never corrected when the oracle comes back. And for LLM calls there's no honest pre-call price at all, since output tokens aren't known until the call is finished — so a real LLM cap lives permanently in &lt;code&gt;pessimistic&lt;/code&gt;/&lt;code&gt;fallback&lt;/code&gt; and never earns the &lt;code&gt;PASS&lt;/code&gt; row my harness prints. What this models cleanly is a price feed, which is the case I actually shipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sits on a different axis from the &lt;a href="https://finops.spinov.online/blog/sliding-window-spend-guard/" rel="noopener noreferrer"&gt;sliding-window guard&lt;/a&gt;, which is about cheap calls that &lt;em&gt;sum&lt;/em&gt; to a runaway, and from the &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;pre-execution gate&lt;/a&gt;, which is about gating before you execute rather than after. Today's axis is oracle &lt;em&gt;availability&lt;/em&gt;, and neither of those touched it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one I'm still stuck on
&lt;/h2&gt;

&lt;p&gt;Stale pricing keeps the ledger alive, which is the whole recommendation, and it's also quietly a lie: you're charging against a number you know might be wrong. So how long is a cost estimate allowed to live before "cached" becomes "guessing"? For gas that moves in seconds it might be five seconds. For a token price it might be an hour. I don't have a principled way to set that TTL, and I suspect it's per-oracle rather than a general rule.&lt;/p&gt;

&lt;p&gt;If you've drawn that line in production — the point where a cached cost stops being a fact — I'd like to hear where you put it and what made you move it.&lt;/p&gt;

&lt;p&gt;Run it yourself: stdlib, offline, and it prints hashes you can diff against mine. Then go look at your own cap and answer one question. When it can't price the next call, does the number in your ledger keep moving?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Follow for the next teardown in this series, and tell me the worst thing your agent ever did while your dashboard showed $0.00. I read every comment.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written with AI assistance and reviewed/edited by a human. The Python in this post was run offline (stdlib only, no network, no keys, no funds) on 2026-07-19; every number and hash in the output blocks is from a real deterministic run, repeated three times byte-for-byte. Code sha256 &lt;code&gt;ddc425908d070c07a6765810e6115f649c17312b8e754d034227d37c982357e8&lt;/code&gt;, output sha256 &lt;code&gt;9ebe1b4ab3459eb78c1d3aeea7eaa16ee0c2286e1d8d57b09e0eeb6451a189f1&lt;/code&gt;. The $1,847 figure belongs to &lt;a class="mentioned-user" href="https://dev.to/royanannya"&gt;@royanannya&lt;/a&gt; and was not reproduced here; the circuit-breaker design described is &lt;a class="mentioned-user" href="https://dev.to/ddhh"&gt;@ddhh&lt;/a&gt;'s, quoted from their post and their MIT-licensed repository.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>python</category>
      <category>llm</category>
    </item>
    <item>
      <title>One compaction, four actions, one block: compaction safety is a property of the pair</title>
      <dc:creator>Alexey Spinov</dc:creator>
      <pubDate>Sat, 18 Jul 2026 01:06:26 +0000</pubDate>
      <link>https://dev.to/alex_spinov/one-compaction-four-actions-one-block-compaction-safety-is-a-property-of-the-pair-5a77</link>
      <guid>https://dev.to/alex_spinov/one-compaction-four-actions-one-block-compaction-safety-is-a-property-of-the-pair-5a77</guid>
      <description>&lt;p&gt;&lt;strong&gt;Context compaction&lt;/strong&gt; is safe or unsafe only against a specific proposed action. Not in general. The compactor decides what your agent forgets before it knows what your agent will do, so at compaction time "did this lose anything important?" has no answer yet. The lie, if there is one, comes into existence later, when the agent proposes to act.&lt;/p&gt;

&lt;p&gt;That sounds like philosophy. It has an exit code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI disclosure.&lt;/strong&gt; I wrote &lt;code&gt;compaction_omission_gate.py&lt;/code&gt; with an AI assistant and ran it myself: Python 3.13.5, offline, standard library only, no network, no keys, no funds. Every verdict, exit code and hash below is pasted from a real local run. I ran the whole demo twice and the two &lt;code&gt;output.txt&lt;/code&gt; files are byte-for-byte identical (&lt;code&gt;sha256 2f0ae2c6d54b1d35caefb3da12af3a4189aff05e5d8c59321b63150f27eef376&lt;/code&gt;). The fixtures are synthetic and mine. The numbers I quote from Anannya Roy Chowdhury, Prasad T and the Ratel team are &lt;strong&gt;their&lt;/strong&gt; claims from their own posts, attributed inline; I did not reproduce their systems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Freeze the compaction. Vary only the action. Same 52 records, same 16 kept (&lt;code&gt;kept-sha256=3bfe38c85fcf&lt;/code&gt; on every row), same 36 dropped, and the same rule missing from all four: &lt;strong&gt;1 BLOCK, 3 PASS&lt;/strong&gt;. Safety moved without the compaction moving at all.&lt;/li&gt;
&lt;li&gt;So "a good compactor" is not a thing you can have. There is only "this compaction is safe for that action."&lt;/li&gt;
&lt;li&gt;The check is arithmetic, not vibes: &lt;code&gt;amount 5000&lt;/code&gt; passes, &lt;code&gt;amount 5001&lt;/code&gt; blocks. Same words, one unit apart.&lt;/li&gt;
&lt;li&gt;This is a &lt;strong&gt;partial no&lt;/strong&gt; to a question I asked publicly two days ago. The action-blind version of the check blocks &lt;strong&gt;4 of 4&lt;/strong&gt;, where the gate blocks 1 of 4. Three of those four blocks are wrong, and nothing recovers them.&lt;/li&gt;
&lt;li&gt;Two of my own designs died on this post: one to a number in someone else's post, one to my reviewer. Details below.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The gap three people found in the same week
&lt;/h2&gt;

&lt;p&gt;Anannya Roy Chowdhury published &lt;a href="https://dev.to/royanannya/my-multi-agent-ai-cost-1847-in-one-weekend-heres-the-fix-that-cut-it-82-3mi4"&gt;&lt;em&gt;My Multi-Agent AI Cost $1,847 in One Weekend&lt;/em&gt;&lt;/a&gt; on July 16. Her numbers: $1,847 burned, 82% cut, context compressed "from 12,000 tokens to 340." That is 97% of the context gone. I asked her in the comments whether she'd found a way to catch a bad compression &lt;em&gt;before&lt;/em&gt; the turn runs on it, or whether you find out from the win rate.&lt;/p&gt;

&lt;p&gt;Her reply named the hole better than my question did:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A bad compressor can absolutely 'lie' by omission, which is arguably more dangerous than naive replay because &lt;strong&gt;the failure is silent&lt;/strong&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;She called it the core challenge of her Part 2, which was not out when I wrote this. So this post is not a rebuttal of anything. It is an attempt to answer the question she agreed was open.&lt;/p&gt;

&lt;p&gt;Two other people hit the same wall in the same week. Prasad T's &lt;a href="https://dev.to/prasadt1/teaching-a-qwen-agent-to-forget-5bgb"&gt;&lt;em&gt;Teaching a Qwen agent to forget&lt;/em&gt;&lt;/a&gt; put a &lt;code&gt;superseded_by&lt;/code&gt; link on memory records so a contradicted fact is dropped from recall but kept in an audit trail. The line I keep coming back to, from the thread under his post: forgetting you can't inspect is just data loss. And the &lt;a href="https://news.ycombinator.com/item?id=48936491" rel="noopener noreferrer"&gt;Ratel&lt;/a&gt; Show HN (19 points, 18 comments when I checked) does progressive tool disclosure with BM25/embeddings/hybrid and claims up to 81% fewer tokens. Different clothes, same problem: something decides what the model doesn't see, and nothing checks that decision against what the model is about to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number that killed my first design
&lt;/h2&gt;

&lt;p&gt;My first design was the obvious one: run the decision on the full context, run it on the compacted context, and if the decision changes, BLOCK. Compare against the truth.&lt;/p&gt;

&lt;p&gt;Prasad's post killed it. His FAMA measurement (his numbers, not mine) reports &lt;strong&gt;recency-only = 0.64&lt;/strong&gt; and &lt;strong&gt;never-forgets = 0.64&lt;/strong&gt;. Identical. Never-forgets &lt;em&gt;is&lt;/em&gt; the full context. So the "truth" I wanted to compare against is itself wrong 36% of the time. An oracle that lies in a third of cases is not an oracle, and any reader who has read his post gets to close my tab in one line.&lt;/p&gt;

&lt;p&gt;Those same two numbers kill a second thing, which is the framing I would have reached for by reflex: &lt;em&gt;compaction hurts because you lose volume.&lt;/em&gt; If throwing almost everything away and throwing nothing away both land on 0.64, volume is not the variable. Prasad says it plainly: &lt;em&gt;"the win isn't about trimming context; it's about knowing what's stale."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So the gate has no oracle. It never asks what the model would have decided. It resolves &lt;strong&gt;your&lt;/strong&gt; declared predicates against &lt;strong&gt;your&lt;/strong&gt; stored records. That is a structural fact, not a prediction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the compaction omission gate actually ask?
&lt;/h2&gt;

&lt;p&gt;One closed question: &lt;em&gt;among the records the compactor dropped, is there one that binds this action?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Input is the full pre-compaction context, the ids the compactor &lt;strong&gt;kept&lt;/strong&gt;, and a concrete action. The dropped set is computed, never supplied. I don't trust anyone's "here's what I removed" list. Records are typed: &lt;code&gt;policy&lt;/code&gt; (scope, predicate, effect), &lt;code&gt;fact&lt;/code&gt; (field, value, optional &lt;code&gt;supersedes&lt;/code&gt;), &lt;code&gt;chatter&lt;/code&gt;. Ordering is a declared &lt;code&gt;seq&lt;/code&gt;, never a wall clock, so the verdict can't drift with the time of day.&lt;/p&gt;

&lt;p&gt;The whole resolution step is this small:&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;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;facts&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.params wins; otherwise the newest stored fact for that field.&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;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&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;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&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;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&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;facts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&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="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a field resolves nowhere, the gate fails closed rather than assuming harmless. There is no scoring anywhere in the verdict path. PASS, BLOCK, ERROR. No percentages. That's deliberate: on July 16 a Show HN called &lt;a href="https://news.ycombinator.com/item?id=48941051" rel="noopener noreferrer"&gt;ReasonGate&lt;/a&gt; shipped a "gate" built on regexes, and Simon Willison took it apart in the thread within the hour. Recall of &lt;em&gt;"76-96%... never 100%"&lt;/em&gt; is not a gate, and &lt;em&gt;"this list of regular expressions does not inspire confidence."&lt;/em&gt; A gate that guesses is a filter with good PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is context compaction safe? Freeze it and vary the action
&lt;/h2&gt;

&lt;p&gt;Here is the claim as an experiment, and it needs nothing to compare against. One compaction. Four actions.&lt;/p&gt;

&lt;p&gt;The fixture is a 52-record support thread. The refund request opens it, the resolution closes it, and the middle carries the boring stuff: internal notes, a tier update, and one dry line at &lt;code&gt;seq 11&lt;/code&gt;: &lt;code&gt;Refunds over 5,000 need a human approver.&lt;/code&gt; The compaction is head+tail, keep the first 8 and the last 8, which is the shape most windowing schemes converge on. That rule sits at position 11 of 52. It is dropped. In all four rows.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Proposed action&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Exit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;refunds.create {amount: 8400}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;BLOCK &lt;code&gt;dropped-binding-constraint&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;refunds.create {amount: 120}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;refunds.create {amount: 5000}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notes.append {ticket: 7741}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Same compaction" is a claim, and claims about counts are cheap, so the tool prints a fingerprint of the kept &lt;strong&gt;set&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    compaction: 52 records, 16 kept, 36 dropped (retention=given, kept-sha256=3bfe38c85fcf)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That digest is identical on all four rows. Same 52 records, same 16 kept, record for record, same 36 dropped. One blocks. Nothing about the compaction moved, and the answer to "is this compaction safe?" moved anyway. That is the whole thesis, and there is no baseline in it, because the claim isn't that some other tool does worse. The claim is that the question is malformed until you name the action.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;120&lt;/code&gt; row prints a line I like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;note      : permissive: dropped policy 'f_020' (allow) resolves TRUE here (amount = 120 lt 500)
            but losing a permission cannot license an action; not a block reason
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A dropped &lt;code&gt;allow&lt;/code&gt; rule is not danger. It costs you an auto-approval, nothing more. The gate says so instead of blocking on principle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"A good compactor" is not a thing you can have.&lt;/strong&gt; There is only "this compaction is safe for that action."&lt;/p&gt;

&lt;h2&gt;
  
  
  The boundary, because "it resolves the predicate" is also a claim
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;5000&lt;/code&gt; row is the one worth poking at. &lt;code&gt;5000 gt 5000&lt;/code&gt; is FALSE, so the rule wouldn't have fired, so its absence changes nothing. But how would you know the gate computed that rather than pattern-matched its way there? Walk the boundary:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;amount&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4999&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5000&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5001&lt;/td&gt;
&lt;td&gt;BLOCK &lt;code&gt;dropped-binding-constraint&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8400&lt;/td&gt;
&lt;td&gt;BLOCK &lt;code&gt;dropped-binding-constraint&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;5000&lt;/code&gt; and &lt;code&gt;5001&lt;/code&gt; are the same word to any tokenizer worth the name. The verdicts differ because the gate resolved &lt;code&gt;amount&lt;/code&gt; out of the action's params and did arithmetic against a stored predicate. Lexical similarity has no way to know that &lt;code&gt;8400 &amp;gt; 5000&lt;/code&gt;, because that is not a fact about words.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part where I answer my own question with "no"
&lt;/h2&gt;

&lt;p&gt;Two days ago I asked publicly whether you can catch a bad compression &lt;em&gt;before the turn runs&lt;/em&gt;. The honest answer, which I did not want, is &lt;strong&gt;mostly no&lt;/strong&gt;, and I can measure the cost of pretending otherwise.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--precheck&lt;/code&gt; takes the action away from the verdict and asks the general question instead: could any dropped record bind &lt;em&gt;any&lt;/em&gt; reachable call of these tools?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 compaction_omission_gate.py &lt;span class="nt"&gt;--precheck&lt;/span&gt; &lt;span class="nt"&gt;--retention&lt;/span&gt; given fixtures/fx1_refund_thread.json
&lt;span class="go"&gt;    action    : (the verdict below never consults it -- that is the experiment)
    compaction: 52 records, 16 kept, 36 dropped (retention=given, kept-sha256=3bfe38c85fcf)
    tools     : refunds.create
    verdict   : BLOCK (action-blind) exit 1
&lt;/span&gt;&lt;span class="gp"&gt;    reason    : may-bind-some-action: dropped policy 'f_011' scopes to refunds.create;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;some
&lt;span class="go"&gt;                reachable call of that tool satisfies (amount gt 5000). Which one? Unknown
                here: no action was supplied
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it against each of the four actions and you get the same line four times, because it never reads them. The gate blocks &lt;strong&gt;1 of 4&lt;/strong&gt;. Precheck blocks &lt;strong&gt;4 of 4&lt;/strong&gt;. Three of those four blocks are false, and they are not fixable by being cleverer: nothing recovers &lt;code&gt;120 &amp;gt; 5000 = FALSE&lt;/code&gt; without the 120.&lt;/p&gt;

&lt;p&gt;So the control point sits one step later than I hoped, and one step earlier than it costs money. The turn is already spent: the model has read the mutilated context and produced a proposal. Nothing has &lt;em&gt;executed&lt;/em&gt;. That's the line my &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;pre-execution gate&lt;/a&gt; has always drawn: before the action, not before the thinking. Reading the dropped records to check them costs zero tokens, since compaction governs what gets sent to the model, not what's on your disk. (Money is a different post: &lt;a href="https://finops.spinov.online/blog/context-tax-measure-transcript-rebill/" rel="noopener noreferrer"&gt;the re-billing curve is here&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The hazard is not symmetric, and my gate got that wrong
&lt;/h2&gt;

&lt;p&gt;Here's the bug my reviewer found in my own tool, and it is the mirror of the one I was proud of.&lt;/p&gt;

&lt;p&gt;I had a rule: &lt;em&gt;losing a permission cannot license an action.&lt;/em&gt; True, and the gate leans on it. A dropped &lt;code&gt;allow&lt;/code&gt; never blocks. But I filtered the &lt;strong&gt;kept&lt;/strong&gt;-rule check by the same logic, and that filter was wrong. A kept &lt;code&gt;allow&lt;/code&gt; rule is a permission you still have. If the compactor drops the &lt;em&gt;value that permission reads&lt;/em&gt;, the permission fires on a value you already took back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 compaction_omission_gate.py &lt;span class="nt"&gt;--retention&lt;/span&gt; given fixtures/fx6_stale_permit.json
&lt;span class="go"&gt;    verdict   : BLOCK (dropped-superseder-stale-permit) exit 1
    reason    : dropped-superseder-stale-permit: kept policy 'f_003' (allow) reads
                'customer.tier'. The compactor kept 'f_002' (customer.tier = gold, seq 2) and
                dropped 'f_011' (customer.tier = standard, seq 11) and declares supersedes
                'f_002'. The rule's own predicate (customer.tier eq gold) is TRUE on the kept
                value and FALSE on the newest one: the agent will license this call with a
                value you already retracted. Losing a permission cannot license an action, but
                KEEPING one while its input is retracted can
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before the fix, that fixture returned &lt;strong&gt;PASS exit 0&lt;/strong&gt;. Silent. A 4,000 refund auto-approved on a &lt;code&gt;gold&lt;/code&gt; tier that was revoked twenty records ago, and my gate, whose entire pitch is "the failure is silent, so catch it," said nothing. I had written the general principle and then applied it to the wrong noun.&lt;/p&gt;

&lt;p&gt;Fixing it forced a distinction I had been sloppy about. A stale value has two directions and they are &lt;strong&gt;not&lt;/strong&gt; the same event:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kept rule &lt;strong&gt;permits&lt;/strong&gt; on the stale value, wouldn't on the newest: the agent acts where your records say stop. &lt;strong&gt;Hazard. BLOCK.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Kept rule &lt;strong&gt;denies&lt;/strong&gt; on the stale value, wouldn't on the newest: the agent stops where your records say go. &lt;strong&gt;Cost. PASS, with a note.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second one is a real fixture too (&lt;code&gt;fx7&lt;/code&gt;), and the gate declines to block it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    note      : stale value, safe direction: kept policy 'f_003' (deny) reads 'customer.tier'.
                [...] The rule's own predicate (customer.tier eq restricted) is TRUE on the kept
                value and FALSE on the newest one. This compaction makes the agent more cautious
                than your records warrant: a cost, not a block reason
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A gate that blocks whenever &lt;em&gt;anything&lt;/em&gt; changed is just an alarm wired to the whole house. The direction is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is there a "correct" retention scheme? I measured. No.
&lt;/h2&gt;

&lt;p&gt;Same fixtures, same &lt;code&gt;k&lt;/code&gt;, two action-independent schemes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;fixture&lt;/th&gt;
&lt;th&gt;head_tail&lt;/th&gt;
&lt;th&gt;recency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;refund thread (rule in the middle)&lt;/td&gt;
&lt;td&gt;BLOCK binding&lt;/td&gt;
&lt;td&gt;BLOCK binding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;superseded fact&lt;/td&gt;
&lt;td&gt;BLOCK stale-kept&lt;/td&gt;
&lt;td&gt;BLOCK binding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;genuinely safe&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;retracted tier, kept &lt;code&gt;allow&lt;/code&gt; rule&lt;/td&gt;
&lt;td&gt;BLOCK stale-permit&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;retracted tier, kept &lt;code&gt;deny&lt;/code&gt; rule&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I predicted recency would survive the supersession fixture. It doesn't. I was wrong, and the way it's wrong is more interesting than my guess: head+tail pins the &lt;strong&gt;stale&lt;/strong&gt; &lt;code&gt;customer.tier = standard&lt;/code&gt; at &lt;code&gt;seq 2&lt;/code&gt; and drops the correction at &lt;code&gt;seq 20&lt;/code&gt;, so the agent applies a kept rule to a retracted value. Recency keeps the correction and drops &lt;strong&gt;the rule itself&lt;/strong&gt;. Two popular schemes, opposite failures, same input. "Switch to recency" doesn't fix the failure, it relocates it.&lt;/p&gt;

&lt;p&gt;I won't tell you no correct scheme exists; five synthetic fixtures can't carry that. The structural argument is stronger than the table anyway. Safety depends on the action, which the freeze-and-vary run above shows without appealing to any scheme at all. Neither of these schemes can see the action. So neither can be safe in general, and the row where recency wins is luck about where the record sat, not virtue.&lt;/p&gt;

&lt;h2&gt;
  
  
  "But a relevance compactor would keep the rule"
&lt;/h2&gt;

&lt;p&gt;Probably, sometimes. And this is where I have to report a section that isn't here.&lt;/p&gt;

&lt;p&gt;An earlier draft of this post had a better headline and a fifth act: my gate against a relevance-based compactor, IDF-weighted cosine, keep-top-k, the state of the art, confidently certifying a compaction that dropped a binding rule. The reviewer who gates my drafts took the code apart instead of reading the prose, and found that I had serialized the action as &lt;strong&gt;structure&lt;/strong&gt; (&lt;code&gt;refunds.create amount 8400 currency USD customer c_889&lt;/code&gt;) while indexing every record as &lt;strong&gt;prose&lt;/strong&gt; (&lt;code&gt;Refunds over 5,000 need a human approver.&lt;/code&gt;). Same JSON on both sides. My gate read six fields off that record. My baseline read one.&lt;/p&gt;

&lt;p&gt;Whatever that comparison showed, it was not a fact about relevance ranking. It was a fact about what I handed each side. So the ranker is gone, all ninety-odd lines of tokenizer and cosine that existed to lose a fight I had arranged. The thesis never needed it, which I'd have noticed sooner if the number hadn't been flattering.&lt;/p&gt;

&lt;p&gt;The honest version of the question is harder. A ranker that sees the action doesn't produce &lt;em&gt;one&lt;/em&gt; compaction you can freeze and audit; it produces a different compaction per action. That may well be the right engineering. It also means there is no fixed artifact to check, and "is this compaction safe" stops being a question you can ask once and cache. I don't have a clean answer for that shape. If you do, I'd like to hear it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prose cannot be certified
&lt;/h2&gt;

&lt;p&gt;One fixture is rolling summarization output: the middle of the thread replaced by eight prose blobs typed as &lt;code&gt;summary&lt;/code&gt;. The gate doesn't recognize that kind, can prove nothing about it, and blocks with &lt;code&gt;dropped-unevaluable-record&lt;/code&gt;. Be precise about what fired, though: the &lt;em&gt;unrecognized kind&lt;/em&gt;, not the prose itself. Retype those same eight blobs as &lt;code&gt;chatter&lt;/code&gt; — a known kind the gate reads as your own declaration that a record is throwaway — and it believes you, drops them silently, and returns PASS. I checked; the verdict flips. So the honest version is narrower than &lt;em&gt;prose fails closed&lt;/em&gt;: the gate fails closed on a kind it can't read, and trusts the one you've labelled inconsequential. Same knife as the schema limit further down — it holds you to your typing. There's an &lt;code&gt;--allow-unevaluable&lt;/code&gt; flag that turns the unrecognized-kind block into PASS exit 0 with &lt;code&gt;[--allow-unevaluable: you chose to fly blind]&lt;/code&gt; in the report. Not a fix. A signed decision.&lt;/p&gt;

&lt;p&gt;This is the uncomfortable one, because rolling summarization is exactly what my own &lt;a href="https://finops.spinov.online/blog/context-tax-measure-transcript-rebill/" rel="noopener noreferrer"&gt;context-tax post&lt;/a&gt; prescribes as the cure. I'm auditing my own prescription. If your memory is a pile of summarized text, this gate has nothing to work with, and neither does anything else. That's not a gap in the gate. It's a gap in the store. Prasad's &lt;code&gt;superseded_by&lt;/code&gt; link is the cheapest version of the fix.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not an authorizer.&lt;/strong&gt; It says the compaction hid nothing. Whether the action is allowed is your policy engine's job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a benchmark.&lt;/strong&gt; Seven synthetic fixtures I wrote, shaped to have the property I'm describing. No claim about anyone's production system, and specifically no claim about Anannya's. Her numbers are hers, from her post.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a compactor.&lt;/strong&gt; It doesn't fix your retention. It tells you when this retention can't carry this action.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not proof your agent is safe.&lt;/strong&gt; It closes one hole: a dropped record that binds the proposed call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not usable on unstructured memory&lt;/strong&gt;, as above.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How is this different from my other gates?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://finops.spinov.online/blog/mandate-freshness-gate/" rel="noopener noreferrer"&gt;&lt;code&gt;mandate-freshness-gate&lt;/code&gt;&lt;/a&gt; expires &lt;strong&gt;authority&lt;/strong&gt;: the permission was revoked, and nothing is missing from the context. Here a &lt;strong&gt;fact&lt;/strong&gt; is retracted and the record is gone. &lt;a href="https://finops.spinov.online/blog/checkpoint-skip-gate/" rel="noopener noreferrer"&gt;&lt;code&gt;checkpoint-skip-gate&lt;/code&gt;&lt;/a&gt; replays a recorded trajectory to find a &lt;strong&gt;step&lt;/strong&gt; the agent skipped; the culprit is the agent, and the check is post-hoc. Here nothing is skipped: the infrastructure quietly ate a &lt;strong&gt;fact&lt;/strong&gt;, and the check runs before execution. &lt;a href="https://finops.spinov.online/blog/agent-memory-tax-and-backdoor/" rel="noopener noreferrer"&gt;&lt;code&gt;agent-memory-tax-and-backdoor&lt;/code&gt;&lt;/a&gt; argued &lt;em&gt;retention is not relevance&lt;/em&gt;, about what your store keeps. This one is about what your window throws away in the same breath, and the verdict never scores anything. &lt;a href="https://finops.spinov.online/blog/sliding-window-spend-guard/" rel="noopener noreferrer"&gt;&lt;code&gt;sliding-window-spend-guard&lt;/code&gt;&lt;/a&gt; treats the window as a spend problem, where eviction is bookkeeping. Here eviction is a correctness problem and the verdict has no cost term in it at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it
&lt;/h2&gt;

&lt;p&gt;Drop the tool and the fixtures in a directory and run &lt;code&gt;bash run_demo.sh&lt;/code&gt;. Three stdlib imports (&lt;code&gt;json&lt;/code&gt;, &lt;code&gt;hashlib&lt;/code&gt;, &lt;code&gt;sys&lt;/code&gt;), no network, no keys, no model. The full sweep ends &lt;code&gt;2 PASS, 4 BLOCK, 1 ERROR -&amp;gt; overall exit 1&lt;/code&gt;, malformed input exits 2 and never 0, and every report ends with a sha256 of its own body. Run it twice and diff. If it isn't byte-identical, I'd want to know.&lt;/p&gt;

&lt;p&gt;Every command in the demo passes &lt;code&gt;--retention given&lt;/code&gt; explicitly even though it's the default, because an earlier version of that script quietly recomputed the compaction per action while the prose above it claimed the compaction was frozen. Two of the numbers I'd written were false and I hadn't run the command my own paragraph described. Spelling the flag out makes the sentence and the command the same object.&lt;/p&gt;

&lt;p&gt;The schema is the price of admission. &lt;code&gt;policy&lt;/code&gt; needs a scope, a predicate and an effect; &lt;code&gt;fact&lt;/code&gt; needs a field, a value and ideally a &lt;code&gt;supersedes&lt;/code&gt;. If your ingestion layer labels a binding rule as chatter, the gate believes it, and nothing saves you. It holds you to your own typing. That's the honest limit.&lt;/p&gt;

&lt;p&gt;So: if compaction safety is a property of the pair rather than of the compactor, where does this check belong in your harness: inside the compactor, in the store, or at the execution gate? I lean toward the execution gate, because it's the only place that knows the action, but that's the place least likely to have the dropped records in hand. And what do you do when your context is prose with no schema? Pay to type it, or fly blind and admit it?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Follow for the numbers from the next gate I build and break. If you've watched an agent lose an instruction to auto-compact and only find out from the outcome, tell me what the dropped record was. I read every comment.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>python</category>
      <category>llm</category>
    </item>
    <item>
      <title>Codex encrypted its sub-agent prompts. Gate the spawn plan.</title>
      <dc:creator>Alexey Spinov</dc:creator>
      <pubDate>Thu, 16 Jul 2026 00:48:38 +0000</pubDate>
      <link>https://dev.to/alex_spinov/codex-encrypted-its-sub-agent-prompts-gate-the-spawn-plan-47j7</link>
      <guid>https://dev.to/alex_spinov/codex-encrypted-its-sub-agent-prompts-gate-the-spawn-plan-47j7</guid>
      <description>&lt;p&gt;&lt;strong&gt;Pre-dispatch authorization for AI sub-agents&lt;/strong&gt; means checking a child spawn's grant envelope (its role, tools, path scope and token budget) against the parent's policy in the last plaintext moment before handoff, not by reading a trace afterwards. When the orchestrator encrypts the handoff, the after-view goes blind. The before-view does not, because it sits earlier on the timeline.&lt;/p&gt;

&lt;p&gt;On July 14, "Codex starts encrypting sub-agent prompts" hit &lt;a href="https://news.ycombinator.com/item?id=48905028" rel="noopener noreferrer"&gt;408 points and 240 comments on Hacker News&lt;/a&gt; in a day. The tracking bug behind it is filed as &lt;a href="https://github.com/openai/codex/issues/28058" rel="noopener noreferrer"&gt;openai/codex#28058&lt;/a&gt;, titled &lt;em&gt;"Regression: encrypted MultiAgentV2 messages remove readable task audit trail."&lt;/em&gt; A change encrypted the orchestrator-to-sub-agent payload, and the plaintext task record humans used to read after the fact turned into ciphertext. People who had been inspecting what their sub-agents were told, after dispatch, could no longer read it.&lt;/p&gt;

&lt;p&gt;That is a good thing to notice, and a worse thing to fix by asking for the plaintext back.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI disclosure.&lt;/strong&gt; I wrote &lt;code&gt;subagent_dispatch_gate.py&lt;/code&gt; with an AI assistant and ran it myself: Python 3.13.5, offline, standard library only, no network, no keys, no funds. Every number, exit code and sha256 below is pasted from a real local run. I ran the whole demo twice and the two &lt;code&gt;output.txt&lt;/code&gt; files are byte-for-byte identical (&lt;code&gt;sha256 5af48191642d66f7c364c429c50d2ad1a021f09004f5566ba878c7be87fcaaf1&lt;/code&gt;). The one synthetic part is clearly marked: &lt;code&gt;encrypt_artifact()&lt;/code&gt; models the &lt;em&gt;observable consequence&lt;/em&gt; of encryption (opaque bytes you cannot parse back into fields), not Codex's real crypto. And every fact about Codex here comes from that HN thread and that issue. I did not reproduce their system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The issue people filed asks to restore the readable audit copy. That restores &lt;em&gt;tracking&lt;/em&gt;. It does not restore &lt;em&gt;control&lt;/em&gt;, and I think the two got conflated in the thread.&lt;/li&gt;
&lt;li&gt;Post-hoc inspection genuinely worked the day before the change. In my contrast run it recovered a plan with &lt;strong&gt;4 fields&lt;/strong&gt; when the artifact was plaintext, then &lt;strong&gt;0 fields&lt;/strong&gt; once the artifact was the opaque blob. The only variable is encryption. No strawman.&lt;/li&gt;
&lt;li&gt;A pre-dispatch gate reads the plan the parent still holds in-process, before the handoff boundary. Same spawn, both worlds, &lt;strong&gt;children=1&lt;/strong&gt;, invariant to whether the artifact is later encrypted.&lt;/li&gt;
&lt;li&gt;The gate is not a tautology. One plan granting &lt;code&gt;fs.write&lt;/code&gt;, a &lt;code&gt;fs:*&lt;/code&gt; wildcard, &lt;code&gt;shell.run&lt;/code&gt;, a path of &lt;code&gt;~&lt;/code&gt; and a 5,000,000-token budget produces &lt;code&gt;PASS: 1 BLOCK: 1&lt;/code&gt;, &lt;strong&gt;exit 1&lt;/strong&gt;. The same two roles scoped read-only inside the workspace under cap produce &lt;code&gt;PASS: 2 BLOCK: 0&lt;/code&gt;, &lt;strong&gt;exit 0&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;An absent or empty plan is &lt;strong&gt;exit 2&lt;/strong&gt;, never a silent pass. A gate that treats "no plan found" as "nothing to block" fails open, and that is the failure mode I most wanted to kill.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What is pre-dispatch authorization for AI sub-agents?
&lt;/h2&gt;

&lt;p&gt;It is a check that runs on the parent, before the parent hands a task to a child agent. The parent holds a spawn plan: for each child, a role, a set of tool grants, a set of paths it may touch, and a token budget. Pre-dispatch authorization compares each of those against a policy the parent already owns, and refuses the spawn if any child asks for more than the policy allows. It happens while the plan is still a plain object in memory. Nothing has been sent, nothing has been encrypted, nothing has run.&lt;/p&gt;

&lt;p&gt;The distinction that matters: this is authorization of a &lt;em&gt;spawn&lt;/em&gt;, not authorization of an &lt;em&gt;action&lt;/em&gt;, and not reconciliation of a &lt;em&gt;trace&lt;/em&gt;. It is the same family as a &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;pre-execution gate for AI agents&lt;/a&gt;, moved up one level, from "may this action run" to "may this child exist with these powers."&lt;/p&gt;

&lt;h2&gt;
  
  
  The incident: the audit trail went dark, and the thread asked for it back
&lt;/h2&gt;

&lt;p&gt;Read issue #28058 in its own words. A PR added encryption to the MultiAgentV2 message payloads. The intent was reasonable: the model-facing channel between orchestrator and sub-agent should not sit around in plaintext on disk. The side effect was that the local rollout history, the thing a human opened to review &lt;em&gt;what task got delegated to which sub-agent&lt;/em&gt;, became unreadable. The issue's ask is to keep a human-readable audit copy alongside the encrypted delivery.&lt;/p&gt;

&lt;p&gt;I understand the ask. I also think it quietly gives up the game. If your control over what a sub-agent may do lives in your ability to &lt;em&gt;read the transcript afterwards&lt;/em&gt;, then your control was always downstream of the work. The sub-agent already ran. The tokens already burned. The &lt;code&gt;shell.run&lt;/code&gt; already happened. Restoring the readable copy lets you write a better postmortem. It does not stop the next spawn.&lt;/p&gt;

&lt;p&gt;This is the line I keep coming back to across this whole cluster: tracking is not control. A trace tells you what happened. It does not decide what is allowed. When the trace is plaintext you can pretend the two are the same, because you can always go read it and intervene. Encryption breaks the pretense. It takes away the after-view and forces the question: if you could not read the trace at all, where would your control actually live?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is post-hoc dead while pre-dispatch survives?
&lt;/h2&gt;

&lt;p&gt;Here is the honest version of that question, run as code. Same spawn, two worlds, one variable.&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;encrypt_artifact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan_obj&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 the at-rest / handoff artifact AFTER the orchestrator encrypts it.
    Not real crypto: models the observable consequence -- bytes a reader without
    the key cannot parse back into fields. sha256 stands in for &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ciphertext&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="n"&gt;raw&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="n"&gt;plan_obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&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;ENC:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;posthoc_inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;artifact&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 session inspector reading the artifact AFTER dispatch.
    Plaintext JSON -&amp;gt; recovers the plan (worked yesterday).
    Opaque ENC blob -&amp;gt; recovers nothing (blind today).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;artifact&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="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;artifact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ENC:&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="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;            &lt;span class="c1"&gt;# blind: zero fields recoverable
&lt;/span&gt;    &lt;span class="n"&gt;obj&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;artifact&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;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;artifact&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;artifact&lt;/span&gt;
    &lt;span class="k"&gt;if&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;obj&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="ow"&gt;and&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;obj&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;plan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&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;c&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;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plan&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;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point of &lt;code&gt;posthoc_inspect&lt;/code&gt; is that it is not rigged. Hand it plaintext and it does its job. Run &lt;code&gt;python3 subagent_dispatch_gate.py --contrast&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONTRAST: post-hoc inspection vs pre-dispatch gate (same spawn)
  world=PLAINTEXT  post-hoc: recovered plan=yes fields=4
  world=ENCRYPTED  post-hoc: recovered plan=no fields=0  &amp;lt;- BLIND
  pre-dispatch reads parent's in-process plan: children=1 (invariant to artifact encryption)
  =&amp;gt; post-hoc control SURVIVES encryption: no
  =&amp;gt; pre-dispatch control survives encryption: yes
sha256(contrast)=0abca3747138eaf4260ed298ed001c1df6fd7ac67e89ce56f60c7b5fe8b9ef11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yesterday's inspector recovered 4 fields. Today's recovers 0. It did not get worse at its job. The world moved out from under it. The pre-dispatch view reads the plan the parent constructs in memory, before &lt;code&gt;encrypt_artifact&lt;/code&gt; ever touches it, so it reports the same &lt;code&gt;children=1&lt;/code&gt; in both worlds. That is the whole argument in five lines of output: encryption is fatal to the after-view and irrelevant to the before-view, and I am not shaving points off post-hoc to get there. It honestly recovers the plan when the plan is readable.&lt;/p&gt;

&lt;p&gt;The reason the before-view can exist at all is boring and load-bearing: the parent has to build the plan in plaintext to send it. Even Codex's own encryption sits &lt;em&gt;around&lt;/em&gt; delivery and at-rest storage. The payload is assembled in the clear first. There is a moment, every single spawn, when the full plan is a plain object the parent is holding. That moment is where the gate goes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate: authorize the spawn plan, not the trace
&lt;/h2&gt;

&lt;p&gt;Four checks, one policy, one child at a time. Nothing clever.&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="n"&gt;WILDCARD_PATH_MARKERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~&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;$HOME&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="n"&gt;WRITE_CAPS&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;fs.write&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;fs.delete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shell.run&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;wallet.transfer&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;_escapes_workspace&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;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;True if `path` is not provably inside `root` (fail-closed on ambiguity).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&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="c1"&gt;# a non-string path spec is not provably safe
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;root_norm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;WILDCARD_PATH_MARKERS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="c1"&gt;# home / wildcard touches everything
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;..&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;             &lt;span class="c1"&gt;# parent-traversal (absolute OR relative)
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&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="c1"&gt;# filesystem root
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;                &lt;span class="c1"&gt;# absolute: must be inside root
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;root_norm&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root_norm&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="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;allowed_tools&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;policy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allowed_tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;cap&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;budget_cap_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workspace_root&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;allow_write&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policy&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;allow_write&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="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;   &lt;span class="c1"&gt;# only a real True grants write
&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;child&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;tools&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;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;reasons&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;child declares no tools (fail-closed: unauthorizable)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tools&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;t&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allowed_tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;reasons&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;tool &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; not in parent allowlist (deny-by-default)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;t&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;WRITE_CAPS&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;allow_write&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;reasons&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;tool &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; is a write/destructive capability but &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                               &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;policy allow_write=false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;child&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;paths&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;paths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;reasons&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;child.paths must be a list&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;paths&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;_escapes_workspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;reasons&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;path &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; escapes workspace root &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%s&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt; &lt;span class="o"&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="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;child&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;budget_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;reasons&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;budget_tokens missing or non-integer (unbounded spawn)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;reasons&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;budget_tokens %d over parent cap %d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;reasons&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;budget_tokens %d must be positive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;budget&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;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;no-role&amp;gt;&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;PASS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;reasons&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;BLOCK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reasons&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two design choices are doing the real work. First, &lt;code&gt;fs.write&lt;/code&gt; is &lt;em&gt;in&lt;/em&gt; the allowlist and still gets blocked, because it is a write capability and the policy says &lt;code&gt;allow_write=false&lt;/code&gt;. Allowlisting a tool and granting write are two different decisions, and collapsing them is how "read-only agent" quietly becomes "agent that can write." Second, &lt;code&gt;_escapes_workspace&lt;/code&gt; is deny-by-default on ambiguity. I do not try to be clever about what &lt;code&gt;~&lt;/code&gt; might resolve to. If a path is not &lt;em&gt;provably&lt;/em&gt; inside the root, it escapes. I got this wrong in the first draft: my wildcard list literally contained &lt;code&gt;"/"&lt;/code&gt;, so the substring check flagged every absolute path, and the legitimate &lt;code&gt;PASS&lt;/code&gt; fixture came back as &lt;code&gt;exit 1&lt;/code&gt;. The live run is what caught it. That is the entire reason I run these before I write about them.&lt;/p&gt;

&lt;p&gt;The scope side of this is a sibling problem to scoring a single API key's &lt;a href="https://finops.spinov.online/blog/blast-radius-ai-agent-api-key/" rel="noopener noreferrer"&gt;blast radius&lt;/a&gt;: same "how wide is this grant" axis, different object and different output. This gate is a binary refusal on a whole spawn envelope, not a 0-100 score on one credential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it in sixty seconds
&lt;/h2&gt;

&lt;p&gt;Here is the child that should never be allowed to spawn. Save it as &lt;code&gt;plan_block.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="nl"&gt;"policy"&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;"allowed_tools"&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;"fs.read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fs.write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http.get"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"workspace_root"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/repo/workspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"budget_cap_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allow_write"&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;"plan"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"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;"home-cleaner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tools"&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;"fs.write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fs:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shell.run"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"paths"&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;"~"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/repo/workspace/task-1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"budget_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000000&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;span class="nl"&gt;"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;"doc-reader"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nl"&gt;"tools"&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;"fs.read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http.get"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"paths"&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;"/repo/workspace/docs"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"budget_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40000&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;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;&lt;code&gt;home-cleaner&lt;/code&gt; is the kind of spawn you do not want dispatched sight-unseen: broad file powers, a shell, a home-directory reach, an absurd budget. Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 subagent_dispatch_gate.py plan_block.json
&lt;span class="go"&gt;SUBAGENT-DISPATCH-GATE REPORT
policy: 3 tool(s) allowed, root=/repo/workspace, budget_cap=200000, allow_write=False
children in spawn plan: 2
&lt;/span&gt;&lt;span class="gp"&gt;  - home-cleaner -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;BLOCK
&lt;span class="go"&gt;      x tool 'fs.write' is a write/destructive capability but policy allow_write=false
      x tool 'fs:*' not in parent allowlist (deny-by-default)
      x tool 'shell.run' not in parent allowlist (deny-by-default)
      x tool 'shell.run' is a write/destructive capability but policy allow_write=false
      x path '~' escapes workspace root '/repo/workspace'
      x budget_tokens 5000000 over parent cap 200000
&lt;/span&gt;&lt;span class="gp"&gt;  - doc-reader -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASS
&lt;span class="go"&gt;PASS: 1   BLOCK: 1
VERDICT: 1 subagent spawn(s) refused BEFORE dispatch
sha256(report)=afed5edb835afdafeac5496dd299770581701fb4489232b7e2baf469fa311812
exit=1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note &lt;code&gt;doc-reader&lt;/code&gt; passes in the same plan. The gate is not a wall that blocks everything. It refused one child on six specific grounds and let the scoped one through. Now flip only the scope, keep the two roles. In &lt;code&gt;plan_pass.json&lt;/code&gt;, &lt;code&gt;home-cleaner&lt;/code&gt; is &lt;code&gt;fs.read&lt;/code&gt; on &lt;code&gt;/repo/workspace/task-1&lt;/code&gt; with a 40,000-token budget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 subagent_dispatch_gate.py plan_pass.json
&lt;span class="go"&gt;SUBAGENT-DISPATCH-GATE REPORT
policy: 3 tool(s) allowed, root=/repo/workspace, budget_cap=200000, allow_write=False
children in spawn plan: 2
&lt;/span&gt;&lt;span class="gp"&gt;  - home-cleaner -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASS
&lt;span class="gp"&gt;  - doc-reader -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PASS
&lt;span class="go"&gt;PASS: 2   BLOCK: 0
&lt;/span&gt;&lt;span class="gp"&gt;VERDICT: all spawns within policy;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dispatch authorized
&lt;span class="go"&gt;sha256(report)=de81c4d8f75cd4683796b9715c9501f81b77bd618286f0ebbf172aad8f21a5d9
exit=0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit 1 to exit 0, decided on the real difference between the two plans. If a gate cannot produce both outcomes it is a decoration, not a control. This one produces &lt;code&gt;afed5edb…&lt;/code&gt; for the refusal and &lt;code&gt;de81c4d8…&lt;/code&gt; for the authorization, and both hashes are stable across runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does bad input fail closed?
&lt;/h2&gt;

&lt;p&gt;The failure I care about most is the quiet one. A gate that returns "all clear" when it was handed nothing is worse than no gate, because it launders "I did not check" into "I approved." So an absent or empty plan is exit 2, and it says why:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 subagent_dispatch_gate.py plan_empty.json      &lt;span class="c"&gt;# "plan": []&lt;/span&gt;
&lt;span class="go"&gt;ERROR: spawn plan is empty (fail-closed: nothing to authorize is not the same as everything authorized)
exit=2

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 subagent_dispatch_gate.py plan_missing.json    &lt;span class="c"&gt;# no "plan" key at all&lt;/span&gt;
&lt;span class="go"&gt;ERROR: no spawn plan present (fail-closed: absence of a plan is NOT authorization -- a gate that passes an empty plan fails open)
exit=2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same reflex runs inside each child. A spawn that declares no tools is blocked, not waved through as harmless. A &lt;code&gt;budget_tokens&lt;/code&gt; that is missing or non-integer is blocked, because an unbounded spawn is the one that quietly forks children in a loop and bills you for all of them. Empty is not safe. Empty is unknown, and unknown fails closed. A previous tool in this series died in review for getting this backwards, treating a zero-byte input as a pass, and I would rather over-correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this sits next to the rest
&lt;/h2&gt;

&lt;p&gt;This is one spoke of a cluster, and the edges matter more than usual here.&lt;/p&gt;

&lt;p&gt;The near neighbor is &lt;a href="https://finops.spinov.online/blog/authz-gate-trace-vs-allowed/" rel="noopener noreferrer"&gt;an authz gate that reconciles the trace against what was allowed&lt;/a&gt;. That gate &lt;em&gt;assumes the trace exists&lt;/em&gt;. It reads a span log after the fact and checks each recorded action against policy. Issue #28058 is the moment that assumption dies: encrypt the payload and there is no trace to reconcile. So the control does not disappear, it relocates earlier on the timeline, from the span log that is now ciphertext to the spawn plan that is still plaintext. If you have been running post-hoc reconciliation, that article is where you were, and this one is where you go when the log goes dark.&lt;/p&gt;

&lt;p&gt;It is also a &lt;a href="https://finops.spinov.online/blog/lethal-trifecta-gate/" rel="noopener noreferrer"&gt;pre-run manifest check like the lethal-trifecta gate&lt;/a&gt;, which asks a different question of the same manifest: can untrusted input reach private data and then reach an exfiltration channel? Same "decide from the declared plan before anything runs," different property. And it is a new spoke on the &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;pre-execution gate&lt;/a&gt; pillar: authorize the child, not the action.&lt;/p&gt;

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

&lt;p&gt;I would rather you find the holes than a commenter find them for me.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;I did not reproduce Codex.&lt;/strong&gt; Every claim about their system comes from HN 48905028 and issue #28058. The encryption there covers delivery and the at-rest copy; the sub-agent's &lt;em&gt;output&lt;/em&gt; is a separate matter, and MultiAgentV2 is an experimental, off-by-default path. I am reasoning about the shape of the problem, not their internals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This is not a decryptor or a bypass.&lt;/strong&gt; The gate never touches ciphertext. It stands on the parent's own boundary and reads the plaintext plan the parent is already holding, before handoff. If your orchestrator does not expose that pre-dispatch moment, this tool has nothing to read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;encrypt_artifact()&lt;/code&gt; is a model, not AES.&lt;/strong&gt; It produces opaque bytes to stand in for "ciphertext you cannot parse back into fields." It demonstrates the &lt;em&gt;consequence&lt;/em&gt; of encryption for a reader without the key. It is not a reproduction of any real cipher, and I am not claiming it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not a runtime enforcer.&lt;/strong&gt; It statically authorizes a manifest and returns an exit code. To actually stop a spawn you need a pre-dispatch hook inside your orchestrator that consults this verdict and refuses the handoff. This shows &lt;em&gt;what&lt;/em&gt; the hook should refuse and why. It does not install the hook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The budget check is a declared integer, not a tokenizer.&lt;/strong&gt; It compares &lt;code&gt;budget_tokens&lt;/code&gt; to a cap. It trusts the plan's own declared number and does not estimate real token cost. If a child lies about its budget, the gate believes the lie. Treat it as a ceiling on the &lt;em&gt;ask&lt;/em&gt;, not a measurement of the spend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The policy language is a toy.&lt;/strong&gt; An allowlist, a workspace root, a budget cap, an &lt;code&gt;allow_write&lt;/code&gt; flag. That is not OPA, not a real PDP/PEP, not a capability system. It is the smallest thing that makes the argument concrete and runnable. Real deployments will want a real policy engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A blocked plan is not a safe plan.&lt;/strong&gt; The gate checks the four things it checks. A child that stays inside all four can still do something dumb with a legitimate grant. Passing this gate means "within the envelope you declared," nothing stronger.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I would do on Monday
&lt;/h2&gt;

&lt;p&gt;Find the point in your orchestrator where the parent has assembled the child's task and grants but has not sent them yet. That point exists, because the parent has to build the thing in the clear to hand it over. Emit the plan as a plain object there, one dict per child with its tools, paths and budget. Run those four checks against the parent's policy. Refuse the handoff on any BLOCK, and log the reasons. Then, separately, keep whatever after-the-fact audit you have. The audit is still useful for the postmortem. It just cannot be the place your control lives, because one encryption change already proved it can be taken away.&lt;/p&gt;

&lt;p&gt;Here is the question I actually want answered, and I do not have a clean answer myself. For anyone running a multi-agent orchestrator in anger: where does authorization of the &lt;em&gt;spawn&lt;/em&gt; live in your stack right now? Is it a real check before the parent hands off, or is it a trace you read afterwards and hope you get to in time? Because if it is the trace, issue #28058 is a preview of the day it goes dark.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write about pre-execution control for AI agents: gates that decide before the work runs, not dashboards that explain it after. Every post ships a tool you can run offline, with no keys. Follow along if that is your kind of thing, and if your orchestrator has a pre-dispatch story, put it in the comments. I am especially interested in anyone who has bolted an authorization check onto a spawn and had it survive contact with a real agent loop.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>python</category>
      <category>security</category>
    </item>
    <item>
      <title>AI Agent Cost Drift: 0.35%/day Is Invisible to Your Dashboard</title>
      <dc:creator>Alexey Spinov</dc:creator>
      <pubDate>Wed, 15 Jul 2026 00:47:52 +0000</pubDate>
      <link>https://dev.to/alex_spinov/ai-agent-cost-drift-035day-is-invisible-to-your-dashboard-1734</link>
      <guid>https://dev.to/alex_spinov/ai-agent-cost-drift-035day-is-invisible-to-your-dashboard-1734</guid>
      <description>&lt;p&gt;&lt;strong&gt;AI agent cost drift&lt;/strong&gt; is the slow growth of your input floor — system prompt, tool schemas, CLAUDE.md, MCP servers — that a rolling baseline never catches, because the baseline climbs with it. &lt;code&gt;drift_anchor_gate.py&lt;/code&gt; pins a frozen canary on day 0 and compares: a 0.35%/day creep raised &lt;strong&gt;zero alarms in 60 days&lt;/strong&gt;; the anchor blocked it on &lt;strong&gt;day 9&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your fleet dashboard fires when today's average run jumps 20 percent above last week. Your context floor grows 0.35 percent a day. Those two numbers never meet. I built six 60-day worlds and ran four rolling detectors over them, each at the tightest threshold that stays quiet on a flat fleet, and the slow creep raised &lt;strong&gt;zero alarms in all four, across 60 days&lt;/strong&gt;, while the floor went up 22.6 percent. A frozen anchor caught the same creep on &lt;strong&gt;day 9&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI disclosure:&lt;/strong&gt; I wrote &lt;code&gt;drift_anchor_gate.py&lt;/code&gt; and &lt;code&gt;make_worlds.py&lt;/code&gt; with an AI assistant and ran them myself: Python 3.13.5, offline, standard library only, no network, no keys. Every number, exit code and sha256 below is pasted from a real local run. I ran the whole demo twice from a clean &lt;code&gt;rm -rf worlds&lt;/code&gt;, and the two &lt;code&gt;output.txt&lt;/code&gt; files are byte-for-byte identical (&lt;code&gt;sha256 1772e695cb75f79d9e3f162ed4c49477a329703610cdf5ddff54cec2cc4da62a&lt;/code&gt;). The series are synthetic, and I say exactly how they are generated below. The one thing that is not synthetic is the arithmetic, and it is the part that does the work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A baseline computed from your own recent history (rolling median, rolling mean, EWMA over your fleet) is a detector of &lt;strong&gt;speed&lt;/strong&gt;. The invoice is charged on &lt;strong&gt;level&lt;/strong&gt;. Those are different quantities, and slow cost drift lives in the gap between them.&lt;/li&gt;
&lt;li&gt;The blind band is arithmetic, not a finding: a window of length &lt;code&gt;w&lt;/code&gt; firing at ratio &lt;code&gt;T&lt;/code&gt; cannot see any uniform daily growth below &lt;code&gt;g* = T^(2/w) - 1&lt;/code&gt;. For &lt;code&gt;w=7, T=1.20&lt;/code&gt; that is &lt;strong&gt;5.35 percent a day&lt;/strong&gt;. Real context creep runs at a tenth of that.&lt;/li&gt;
&lt;li&gt;Measured on my fixtures: a 0.35 percent/day creep produced &lt;strong&gt;0 alarms&lt;/strong&gt; from all four rolling detectors in 60 days. The frozen anchor blocked it on &lt;strong&gt;day 9&lt;/strong&gt;, at a 2 percent tolerance, with 0 false alarms on a flat fleet.&lt;/li&gt;
&lt;li&gt;The demo that matters: a world where &lt;strong&gt;every local file is byte-for-byte identical to day 0&lt;/strong&gt; (&lt;code&gt;diff -rq&lt;/code&gt; prints nothing, &lt;code&gt;git diff&lt;/code&gt; would print nothing), the vendor quietly bumped the harness scaffold, the fleet dashboard's short-window detectors stayed silent (the long window fired once, on day 58 — 46 days late), and the anchor blocked on day 12 with the growth attributed to &lt;code&gt;vendor: +2600 B&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The dashboard wins a world too, and I show it: when the &lt;strong&gt;work&lt;/strong&gt; per run doubles and the floor never moves, the anchor passes all 60 days and the rolling detector fires on day 30. Keep both.&lt;/li&gt;
&lt;li&gt;Where this article is wrong: when the floor is about &lt;strong&gt;90 percent or more&lt;/strong&gt; of a typical request, the rolling detector does see the creep. Sweep included.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The comment I owed a measurement to
&lt;/h2&gt;

&lt;p&gt;On July 13, &lt;a href="https://dev.to/alex_spinov/comment/3b3eb"&gt;Dipankar Sarkar&lt;/a&gt; replied to my post on &lt;a href="https://finops.spinov.online/blog/delivered-but-unbilled-stream-gate/" rel="noopener noreferrer"&gt;usage logs that report zero tokens for delivered text&lt;/a&gt; with a sharp correction. His argument: a provider tokenizer bump and real suppression look identical if you only watch a threshold, so the disambiguator should be population, not threshold. "So the block condition isn't 'delta widened past X,' it's 'this stream's delta diverged from the fleet's,' per-stream anomaly against a rolling baseline of everyone else."&lt;/p&gt;

&lt;p&gt;He is right, and I said so. Then I argued the thing that became this article: a rolling baseline adapts, so anything that grows slower than the adaptation window never diverges from the fleet. It walks the fleet along with it. Boiling frog. What you need is a second anchor that is frozen at a known-good date, and for a solo developer with no fleet, a canary request with deterministic input is a population of one.&lt;/p&gt;

&lt;p&gt;And then I ended &lt;a href="https://dev.to/alex_spinov/comment/3b3hf"&gt;my own comment&lt;/a&gt; with this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"To be straight about status, this is a design argument and not something I have measured. The re-tokenized floor I have run. The fleet-versus-canary discrimination I have not."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was two days ago. This post is me paying that off. The domain moved (I am measuring input cost drift, not usage suppression) but the structure of the question is the same one Dipankar and I were arguing about: &lt;strong&gt;can a baseline built from your own recent history see a thing that moves your own recent history?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the arithmetic, before any data exists
&lt;/h2&gt;

&lt;p&gt;This part is not a discovery. It is a derivative, and it is worth doing on paper before you touch a fixture, because it tells you what the measurement is allowed to find.&lt;/p&gt;

&lt;p&gt;A rolling window compares today against a baseline built from the previous &lt;code&gt;w&lt;/code&gt; days. That baseline is centred roughly &lt;code&gt;w/2&lt;/code&gt; days back. Under uniform daily growth &lt;code&gt;g&lt;/code&gt;, today's level over the baseline's level is about &lt;code&gt;(1+g)^(w/2)&lt;/code&gt;. The alarm fires when that ratio exceeds the threshold &lt;code&gt;T&lt;/code&gt;. Solve for &lt;code&gt;g&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;g* = T^(2/w) - 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any growth slower than &lt;code&gt;g*&lt;/code&gt; is invisible &lt;strong&gt;forever&lt;/strong&gt;. Not "for a while". The ratio the detector computes stops rising, because the baseline is climbing at the same rate as the signal. Here is what the tool prints before it has read a single byte of data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  window   threshold   invisible below   compounds to, over 30 days
  w=7      T=1.15         4.07%/day        x3.31
  w=7      T=1.20         5.35%/day        x4.77
  w=14     T=1.20         2.64%/day        x2.18
  w=28     T=1.20         1.31%/day        x1.48
  w=28     T=1.10         0.68%/day        x1.23
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the right-hand column. A seven-day window at a 20 percent threshold is blind to any drift that multiplies your input cost by &lt;strong&gt;4.77x over a month&lt;/strong&gt;. That is the prediction. The measurement is not the formula, it is the question of where real context creep lands relative to that band, and whether you can pick a &lt;code&gt;T&lt;/code&gt; low enough to close it without the alarm screaming every Tuesday.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fixtures, declared before I ran them
&lt;/h2&gt;

&lt;p&gt;Sixty days. Forty runs per day, &lt;strong&gt;fixed&lt;/strong&gt;. I fixed the run count on purpose: variable traffic adds noise to a daily average, and noise is the thing that pushes the dashboard's threshold up. Removing it is a handicap in the dashboard's favour, and I would rather hand the dashboard its best case than be accused of rigging it.&lt;/p&gt;

&lt;p&gt;The floor at pin time is 61,060 bytes:&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;bytes&lt;/th&gt;
&lt;th&gt;origin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tools_json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;26,000&lt;/td&gt;
&lt;td&gt;local file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;system_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12,000&lt;/td&gt;
&lt;td&gt;local file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;instructions&lt;/code&gt; (CLAUDE.md)&lt;/td&gt;
&lt;td&gt;9,000&lt;/td&gt;
&lt;td&gt;local file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;harness_scaffold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6,000&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;vendor&lt;/strong&gt;, not a local file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mcp_a&lt;/code&gt;, &lt;code&gt;mcp_b&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;4,000 each&lt;/td&gt;
&lt;td&gt;local files (&lt;a href="https://finops.spinov.online/blog/mcp-server-token-tax/" rel="noopener noreferrer"&gt;the MCP token tax&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;session_id&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;36 + 24&lt;/td&gt;
&lt;td&gt;runtime, declared volatile&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Work per run is a lognormal draw, median 18,000 bytes, sigma 0.8, multiplied by a daily job-mix factor (lognormal, sigma 0.15). So a typical run is 86,033 bytes and the floor is &lt;strong&gt;71.0 percent&lt;/strong&gt; of it. Every world shares one noise realization and one anchor: only the floor trajectory differs. The dashboard is never handed a harder draw than the anchor gets.&lt;/p&gt;

&lt;p&gt;The six worlds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;world&lt;/th&gt;
&lt;th&gt;what happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;control&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;nothing changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;creep&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;one policy section appended to CLAUDE.md every day, a 3rd MCP server on day 14, a 4th on day 33, 12 tool schemas on day 40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;slowcreep&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;one policy section every third day, one MCP server on day 33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spike&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;someone pastes a 40 KB architecture doc into CLAUDE.md on day 30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;harness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;no local file changes at all.&lt;/strong&gt; The vendor bumps its scaffold on days 12, 31 and 48&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;workload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;the floor never moves.&lt;/strong&gt; Work per run doubles on day 30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each policy section is uniquely generated, 380 to 620 bytes, with its own heading and body. I mention that because my last cost article was dropped in review for a fixture that repeated one paragraph thirty times, which was a fair hit.&lt;/p&gt;

&lt;p&gt;Growth rates that come out of that: &lt;code&gt;creep&lt;/code&gt; runs at &lt;strong&gt;+1.044 percent/day&lt;/strong&gt; and reaches 112,700 bytes. &lt;code&gt;slowcreep&lt;/code&gt; runs at &lt;strong&gt;+0.347 percent/day&lt;/strong&gt; and reaches 74,885 bytes, up 22.6 percent. Nobody would notice either one by looking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six worlds, one anchor, one command
&lt;/h2&gt;

&lt;p&gt;Every rolling detector gets its own best deal: the tool walks a threshold ladder from 1.05 upward and picks &lt;strong&gt;the lowest threshold that raises zero false alarms on the flat world&lt;/strong&gt;. That is the most generous calibration you can honestly give an alerting rule, because the first thing that happens to a noisy rule in real life is that somebody raises the threshold until it shuts up.&lt;/p&gt;

&lt;p&gt;Here is what came back, condensed from the real output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;world&lt;/th&gt;
&lt;th&gt;rolling median w=7&lt;/th&gt;
&lt;th&gt;rolling mean w=7&lt;/th&gt;
&lt;th&gt;EWMA a=0.3&lt;/th&gt;
&lt;th&gt;rolling median w=28&lt;/th&gt;
&lt;th&gt;frozen anchor, tol 2%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;control&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0 alarms (T=1.20)&lt;/td&gt;
&lt;td&gt;0 (T=1.20)&lt;/td&gt;
&lt;td&gt;0 (T=1.20)&lt;/td&gt;
&lt;td&gt;0 (T=1.15)&lt;/td&gt;
&lt;td&gt;PASS, all 60 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;creep&lt;/code&gt; (1.04%/d)&lt;/td&gt;
&lt;td&gt;1, on &lt;strong&gt;day 40&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;1, day 40&lt;/td&gt;
&lt;td&gt;1, day 40&lt;/td&gt;
&lt;td&gt;15, from day 30&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;BLOCK day 3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;slowcreep&lt;/code&gt; (0.35%/d)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;BLOCK day 9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;harness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1, day 58&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;BLOCK day 12&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spike&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4, day 30&lt;/td&gt;
&lt;td&gt;2, day 30&lt;/td&gt;
&lt;td&gt;2, day 30&lt;/td&gt;
&lt;td&gt;15, day 30&lt;/td&gt;
&lt;td&gt;BLOCK day 30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;workload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4, day 30&lt;/td&gt;
&lt;td&gt;2, day 30&lt;/td&gt;
&lt;td&gt;2, day 30&lt;/td&gt;
&lt;td&gt;14, day 30&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PASS, all 60 days&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Look at the &lt;code&gt;creep&lt;/code&gt; row for the short windows. One alarm each, and it lands on &lt;strong&gt;day 40&lt;/strong&gt;, the day twelve tool schemas were installed. Not on the creep. On the step. The detector is doing exactly what it is built to do, which is notice a discontinuity, and the 51 KB the floor gained by walking there is invisible to it.&lt;/p&gt;

&lt;p&gt;Now the long window, &lt;code&gt;w=28&lt;/code&gt; at &lt;code&gt;T=1.15&lt;/code&gt;. The arithmetic said it is blind below &lt;strong&gt;1.00 percent/day&lt;/strong&gt;. &lt;code&gt;creep&lt;/code&gt; runs at 1.044 percent/day, just above the line, and the detector fires 15 times starting on day 30. &lt;code&gt;slowcreep&lt;/code&gt; runs at 0.347 percent/day, below the line, and it fires &lt;strong&gt;zero&lt;/strong&gt; times in 60 days. The formula predicted both the hit and the miss before the data existed. That is the whole argument in two rows, and it is why I put the arithmetic first.&lt;/p&gt;

&lt;p&gt;The long window is not free, by the way. It paid for that sensitivity with 15 alarms on &lt;code&gt;spike&lt;/code&gt; and 14 on &lt;code&gt;workload&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The demo that made me build this: git diff is empty and you are paying more
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;harness&lt;/code&gt; world is the one I would put in front of a skeptic.&lt;/p&gt;

&lt;p&gt;Nothing in the repository changes. Not one byte. The demo runs &lt;code&gt;diff -rq&lt;/code&gt; between a day-0 snapshot and a day-59 snapshot of every local file that feeds the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;diff -rq repo_day000 repo_day059: no differences. git diff would print nothing.

sha256 of every local file, day 0 vs day 59:
  d0  063d5a8a0fe8267fc4093041701996f728662aab22b0763dd0225de767bff895  ./CLAUDE.md
  d0  e3b98852646c31827f303b3762410a3ca42f3a51c2a2201614e21b76fae51e70  ./mcp/a.json
  d0  07acba3d89f17bc9467bcbac3f2bcc72cfff4c07ad65694d86691a66a080f80c  ./mcp/b.json
  d0  25cb751f2526dafd5e0f7273fe29a7abac797424f442cfff28ea82ca013bf8cf  ./system_prompt.txt
  d0  3dc0f40d1489590ef5a98c464c11378f97e4133aa1a6fcc984fa5d386525440c  ./tools.json
  d59 063d5a8a0fe8267fc4093041701996f728662aab22b0763dd0225de767bff895  ./CLAUDE.md
  d59 e3b98852646c31827f303b3762410a3ca42f3a51c2a2201614e21b76fae51e70  ./mcp/a.json
  d59 07acba3d89f17bc9467bcbac3f2bcc72cfff4c07ad65694d86691a66a080f80c  ./mcp/b.json
  d59 25cb751f2526dafd5e0f7273fe29a7abac797424f442cfff28ea82ca013bf8cf  ./system_prompt.txt
  d59 3dc0f40d1489590ef5a98c464c11378f97e4133aa1a6fcc984fa5d386525440c  ./tools.json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meanwhile the vendor added 2,600 bytes of tool-call protocol preamble to every request on day 12, another 3,100 on day 31, another 3,400 on day 48. By day 59 the rendered request carries 14.9 percent more floor than it did at pin time, on every single call, forever.&lt;/p&gt;

&lt;p&gt;Three instruments looked at that world. &lt;code&gt;git diff&lt;/code&gt; reported nothing. The rolling detectors on the usage log reported nothing (the long window eventually woke up on day 58, which is 46 days late and only after the third bump). The anchor reported this, on day 12, from the same command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;drift_anchor_gate.py check worlds/anchor.json worlds/harness/canary/day012.json
&lt;span class="go"&gt;anchor     61060 B   pinned day 0   tolerance 2.0%
today      63660 B   day 12           drift +4.26%

  harness_scaffold      +2600 B   vendor
  unchanged (same sha256): instructions, mcp_a, mcp_b, session_id, system_prompt, timestamp, tools_json

  from local files: +0 B      from the vendor: +2600 B

&lt;/span&gt;&lt;span class="gp"&gt;verdict: BLOCK  -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;1   &lt;span class="o"&gt;(&lt;/span&gt;drift +4.26% is above the 2.0% tolerance&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;exit=1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same command, the same anchor, the same day, in the control world:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;drift_anchor_gate.py check worlds/anchor.json worlds/control/canary/day012.json
&lt;span class="go"&gt;today      61060 B   day 12           drift +0.00%
&lt;/span&gt;&lt;span class="gp"&gt;verdict: PASS   -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0   &lt;span class="o"&gt;(&lt;/span&gt;drift +0.00% is within the 2.0% tolerance&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;exit=0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line &lt;code&gt;from local files: +0 B      from the vendor: +2600 B&lt;/code&gt; is the reason the anchor measures the &lt;strong&gt;rendered request&lt;/strong&gt; and not the files you remembered to list. Your repo is not the thing you pay for. The rendered request is the thing you pay for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the dashboard wins and my anchor is blind
&lt;/h2&gt;

&lt;p&gt;I would not trust this article if it did not have this section, so here it is.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;workload&lt;/code&gt; world: the floor never changes, and on day 30 the work per run doubles (retrieval starts returning twice the chunks, users paste bigger inputs, whatever). The rolling detector fires on day 30 and it is completely correct to do so. The anchor &lt;strong&gt;passes all 60 days&lt;/strong&gt;, because the frozen canary task is not doing more work. Its input really did not drift. The anchor is blind to the entire class of cost growth that lives in the workload (&lt;a href="https://finops.spinov.online/blog/context-tax-measure-transcript-rebill/" rel="noopener noreferrer"&gt;the context tax, where every step re-bills the whole transcript&lt;/a&gt;), and that class is real and often bigger than the floor.&lt;/p&gt;

&lt;p&gt;Now put &lt;code&gt;spike&lt;/code&gt; and &lt;code&gt;workload&lt;/code&gt; side by side in that results table. Look at the rolling columns. Same detector, same day 30, near-identical alarm counts (4, 2, 2 on the short windows, 15 vs 14 on the long one). From inside the usage log those two worlds are &lt;strong&gt;indistinguishable&lt;/strong&gt;. One of them is a 40 KB doc pasted into an instruction file that you will now pay for on every call until someone deletes it. The other is your product doing more work, which is what you wanted. The dashboard raises the identical alarm for both.&lt;/p&gt;

&lt;p&gt;The anchor tells them apart: BLOCK on the paste, PASS on the workload. That is not the anchor being more sensitive. It is the anchor answering a different, narrower question, and answering it cleanly.&lt;/p&gt;

&lt;p&gt;So the honest summary is a division of labour, not a replacement. Keep the rolling detector. It catches the paste, the step, and the workload change. Add the anchor. It catches the year.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this article is wrong
&lt;/h2&gt;

&lt;p&gt;There is one parameter that can flip the whole result, so I swept it and I am publishing the sweep: &lt;strong&gt;how much of a typical request is floor?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The noise in a fleet average comes from the &lt;strong&gt;work&lt;/strong&gt;, not from the floor. The floor is the same on every call. So the less work your runs carry, the quieter your fleet average is, the lower a threshold you can run without false alarms, and the closer your dashboard gets to being a canary all by itself. Here is the sweep on the &lt;code&gt;slowcreep&lt;/code&gt; world, with the &lt;code&gt;w=7&lt;/code&gt; detector recalibrated to its own zero-false-alarm threshold at each work level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  work_median  floor share   w=7 clean T   blind below   alarms on slowcreep   first
  wm002000      95.8%       T=1.05          1.40%/day    4 &amp;lt;- sees the creep  33
  wm005000      89.8%       T=1.05          1.40%/day    6 &amp;lt;- sees the creep  12
  wm010000      81.8%       T=1.15          4.07%/day    0                    -
  wm018000      71.7%       T=1.15          4.07%/day    0                    -
  wm030000      59.2%       T=2.00         21.90%/day    0                    -
  wm060000      41.7%       T=2.00         21.90%/day    0                    -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At a floor share around 90 percent and above, &lt;strong&gt;the dashboard sees the creep and this article's headline does not apply to you&lt;/strong&gt;. The flip in my run happens between 81.8 percent (blind) and 89.8 percent (sees). If your agent's requests are almost entirely system prompt and tool schemas with a tiny user turn on top, you already own a canary. You did not have to freeze anything.&lt;/p&gt;

&lt;p&gt;For everyone else, notice what happens at the bottom of that table. At a 42 percent floor share, the ladder had to climb to &lt;code&gt;T=2.00&lt;/code&gt; to stay quiet on a flat fleet. A threshold of 2.00 means "wake me when input cost doubles day over day", and its blind band is 21.9 percent a day. Nobody ships that alert. That is the mechanism in one line: &lt;strong&gt;the noisier your work, the higher your threshold must go, and the wider the door you leave open for drift.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I swept the day-to-day noise too, sigma from 0.05 to 0.25. At sigma 0.05, a very quiet fleet, the &lt;code&gt;w=7&lt;/code&gt; detector does catch one alarm on &lt;code&gt;slowcreep&lt;/code&gt;, on day 36 (27 days after the anchor). At every other noise level: zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bytes, not tokens, not dollars
&lt;/h2&gt;

&lt;p&gt;The tool counts bytes. That is deliberate, and it is also the answer to the first objection I would raise if I were reading this.&lt;/p&gt;

&lt;p&gt;Drift is a &lt;strong&gt;ratio&lt;/strong&gt;, and a ratio survives any linear rescale: &lt;code&gt;(a*x2) / (a*x1) = x2 / x1&lt;/code&gt;. Whatever your bytes-per-token constant is, whatever your price per token is, it cancels. So I do not need a tokenizer to say the floor grew 22.6 percent, and I refuse to multiply by a made-up constant to make the number look like money.&lt;/p&gt;

&lt;p&gt;Caching is the interesting version of this objection. A stable prefix is cacheable, and a cache read can cost about a tenth of a fresh write, so caching &lt;strong&gt;changes the level&lt;/strong&gt; of what you pay by a large factor. It does not change the ratio, so it does not change the verdict. The floor still grew 22.6 percent, you are still paying the drift, and you are paying it on every call. There is a nastier detail underneath: editing an instruction file &lt;strong&gt;invalidates the prefix&lt;/strong&gt;, so on the day someone appends a policy section you pay a full cache write on top (&lt;a href="https://finops.spinov.online/blog/cache-break-detector/" rel="noopener noreferrer"&gt;the cache-break detector&lt;/a&gt; is about exactly that failure). Slow creep is not one cheap event. It is a cheap event that also throws away your cache, repeatedly.&lt;/p&gt;

&lt;p&gt;If you want dollars, multiply. Just do not let the multiplication happen before the comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this differs from the two gates I already shipped
&lt;/h2&gt;

&lt;p&gt;Two of my own posts are close enough to this one that I owe you a direct answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://finops.spinov.online/blog/sliding-window-spend-guard/" rel="noopener noreferrer"&gt;Sliding-Window Spend Guard&lt;/a&gt; also uses a window. The difference is where the threshold comes from: there, the cap is &lt;strong&gt;absolute&lt;/strong&gt; ("$X per window, refuse the next call"), and the window is only a way to aggregate. Here, the whole subject is a threshold derived &lt;strong&gt;from your own history&lt;/strong&gt;, which is the thing that fails. An absolute cap is immune to this failure mode by construction. It is also the reason the window guard is a good design and a fleet-relative alert is a trap: one of them has a baseline that cannot be moved by the drift it is supposed to catch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://finops.spinov.online/blog/mcp-tool-pin-verify/" rel="noopener noreferrer"&gt;MCP Tool Pin Verify&lt;/a&gt; also pins a hash. The difference is what the pin is for: there it protects the &lt;strong&gt;semantic integrity&lt;/strong&gt; of a tool description against a malicious rug-pull. Here the pin protects a &lt;strong&gt;level&lt;/strong&gt;, and the adversary is not an attacker, it is your own team, being helpful, one policy section at a time. Same mechanism, different failure. Pinning tool manifests does not tell you your floor grew, and this gate does not tell you a tool description turned hostile. Run both.&lt;/p&gt;

&lt;p&gt;Both belong to the same family: &lt;a href="https://finops.spinov.online/blog/pre-execution-gate-for-ai-agents/" rel="noopener noreferrer"&gt;a pre-execution gate&lt;/a&gt; that decides before the money is spent instead of a chart that explains the money after.&lt;/p&gt;

&lt;h2&gt;
  
  
  The exit contract, and the bug I refuse to ship again
&lt;/h2&gt;

&lt;p&gt;A cost gate has exactly one bug it must never have: reporting "all clear" because the artifact it was supposed to measure went missing. My previous gate had that bug (a zero-byte baseline made a division produce &lt;code&gt;0.0&lt;/code&gt;, which was below the threshold, which was a pass, on 2.25 million tokens). It was caught in review and the article was dropped. Fair.&lt;/p&gt;

&lt;p&gt;So this one has three exits and a rule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0 PASS&lt;/code&gt; drift within tolerance, canary frozen, every declared field present and non-empty.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1 BLOCK&lt;/code&gt; drift above tolerance.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2 STRUCTURAL&lt;/code&gt; the input is unusable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;There is no path where a missing, emptied or truncated artifact returns 0.&lt;/strong&gt; (A record that faithfully reports a genuinely &lt;em&gt;smaller&lt;/em&gt; request is a PASS — that is the whole point of measuring level, not a fail-open. What cannot happen is the measurement vanishing, or a field dropping out, and being scored as "no drift.") Nine degenerate inputs, all from the real run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- dropped_field ---
STRUCTURAL: declared field missing from today's record: tools_json
STRUCTURAL: a bytes-only check would have called this -42.6% drift and passed it
verdict: STRUCTURAL  -&amp;gt;  exit 2   (fail-closed: unusable input is never a pass)
exit=2

--- zero_byte_field ---
STRUCTURAL: declared field rendered zero bytes: mcp_b
exit=2

--- unknown_field ---
STRUCTURAL: undeclared field in today's record: mcp_c
STRUCTURAL: you do not know everything your harness is sending; re-pin deliberately
exit=2

--- task_changed ---
STRUCTURAL: canary task changed: task_sha256 c81db92e6a9f... != pinned 7439b8b0addf...
STRUCTURAL: a canary that drifts is not a canary
exit=2

--- volatile_grew ---
STRUCTURAL: volatile field session_id changed length: 36 B pinned, 96 B today
STRUCTURAL: a volatile field with a moving length is not volatile, it is drift in disguise
exit=2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus &lt;code&gt;missing&lt;/code&gt;, &lt;code&gt;empty&lt;/code&gt;, &lt;code&gt;truncated&lt;/code&gt;, and &lt;code&gt;inconsistent_total&lt;/code&gt; (the parts do not sum to the stated total). Nine cases, nine exit 2s. The &lt;code&gt;dropped_field&lt;/code&gt; line is the one to stare at: &lt;strong&gt;a naive bytes-only comparison reads that record as 42.6 percent cheaper than the anchor and passes it.&lt;/strong&gt; That is the fail-open shape, and it is why the manifest is a manifest and not a number.&lt;/p&gt;

&lt;p&gt;And on the typical scenario, the one this tool exists for, it fails closed in the direction that costs someone a conversation: &lt;code&gt;slowcreep&lt;/code&gt; day 8 passes at +1.80 percent, day 9 blocks at +2.79 percent, exit 1, attribution &lt;code&gt;instructions +1706 B local:CLAUDE.md&lt;/code&gt;. Your CI goes red because somebody added four paragraphs of rules over nine days. That is the intended behaviour, and if it annoys you, re-pin the anchor. Deliberately. In a commit. With your name on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anchor
&lt;/h2&gt;

&lt;p&gt;It is 1,726 bytes and there is nothing clever in 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;"canary_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;"floor-canary-v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pinned_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;"day 0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tolerance_pct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task_sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7439b8b0addf5009c942bb2f1c42c0c4e3489b13990957f8a47253aaa429a8c8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rendered_bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;61060&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest"&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;"tools_json"&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="nl"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;26000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3dc0f40d..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local:tools.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nl"&gt;"volatile"&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;"system_prompt"&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="nl"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"25cb751f..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local:system_prompt.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"volatile"&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;"instructions"&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="nl"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;9000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"063d5a8a..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local:CLAUDE.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;"volatile"&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;"harness_scaffold"&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="nl"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;6000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"39b235eb..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vendor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="nl"&gt;"volatile"&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;"mcp_a"&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="nl"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e3b98852..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local:mcp/a.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nl"&gt;"volatile"&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;"mcp_b"&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="nl"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"07acba3d..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local:mcp/b.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nl"&gt;"volatile"&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;"session_id"&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="nl"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"96311dd8..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"runtime"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nl"&gt;"volatile"&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;"timestamp"&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="nl"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2956a7fc..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"runtime"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nl"&gt;"volatile"&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="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;(Hashes truncated here for width. They are full-length in the file.) One detail worth pausing on: &lt;code&gt;instructions&lt;/code&gt; is pinned at &lt;code&gt;063d5a8a...&lt;/code&gt;, and that is the same digest &lt;code&gt;shasum&lt;/code&gt; printed for &lt;code&gt;CLAUDE.md&lt;/code&gt; on day 59 of the harness world. The file is provably untouched, and the request still grew.&lt;/p&gt;

&lt;p&gt;Today's record is the same shape: per-field byte counts and digests for the rendered request. Byte counts and hashes, not payloads, which is the shape a privacy-conscious request log already has. If your harness can dump the request it is about to send, you can produce this. If it cannot, that is a finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;Three files. All of it, because a reproducibility claim you cannot check is just a claim.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;drift_anchor_gate.py&lt;/code&gt;:&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;drift_anchor_gate.py - offline, keyless, zero-network, read-only, stdlib-only (Python 3.13).

One question: has the INPUT COST of a known-good run gone up since a known-good day?

A rolling baseline (median / mean / EWMA over your fleet) answers that question by
comparing today against your own recent history. It is a detector of SPEED. The
invoice is charged on LEVEL. This tool answers the same question against a baseline
that does not move: an anchor pinned on day 0 to a FROZEN canary task, so the only
thing that can change between then and now is what your harness wraps around it.

  check      one day against the anchor. Exit 0 PASS / 1 BLOCK / 2 STRUCTURAL.
  replay     N days, anchor and rolling detectors side by side on the same log.
  sweep      the two parameters that could flip the result: floor share, day noise.
  blindband  pure arithmetic, no data: the growth rate a rolling window cannot see.

Exit contract (fail-closed):
  0 PASS        drift within tolerance, canary frozen, every declared field present and non-empty
  1 BLOCK       drift above tolerance
  2 STRUCTURAL  input unusable: unreadable, empty, truncated, a declared field missing or
                rendered zero bytes, an undeclared field present, the canary task changed,
                a volatile field that changed length, a record whose parts do not sum to its total.

There is no path where a missing, emptied or truncated artifact returns 0, and a
dropped or zeroed field fails closed too. A gate that answers &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no drift detected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
because the artifact vanished is worse than no gate.

This tool never writes, never opens a socket, never imports anything outside the stdlib.
&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;hashlib&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;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;statistics&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;LADDER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;FAMILIES&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;rolling median w=7&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;median&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&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;rolling mean   w=7&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;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&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;EWMA alpha=0.3&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;ewma&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&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;rolling median w=28&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;median&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;ALPHA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;


&lt;span class="c1"&gt;# ---------------------------------------------------------------- the gate
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_json&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;what&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%s not found: %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;what&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;if&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getsize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%s is zero bytes: %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;what&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;try&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="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="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;fh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;OSError&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;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%s is not readable JSON: %s (%s)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;what&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;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__class__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&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;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="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;STRUCTURAL: %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;msg&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;verdict: STRUCTURAL  -&amp;gt;  exit 2   (fail-closed: unusable input is never a pass)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quiet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return (verdict, drift_pct, lines). verdict in PASS / BLOCK / STRUCTURAL.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;man&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manifest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;today&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;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="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;fields&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="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;fields&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;STRUCTURAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;no fields in today&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s record&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;today&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;canary_id&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;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;canary_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STRUCTURAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;canary_id mismatch: %r vs pinned %r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                                   &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today&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;canary_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;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;canary_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;today&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;task_sha256&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;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STRUCTURAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;canary task changed: task_sha256 %s... != pinned %s...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                                   &lt;span class="o"&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="n"&gt;today&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;task_sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))[:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;12&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 canary that drifts is not a canary&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;man&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;fields&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;unknown&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;fields&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;man&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;empty&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&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;v&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;bytes&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="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="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;naive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rendered_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STRUCTURAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;declared field missing from today&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s record: %s&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="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;a bytes-only check would have called this %+.1f%% drift and passed it&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;naive&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&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;unknown&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;STRUCTURAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;undeclared field in today&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s record: %s&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="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;you do not know everything your harness is sending; re-pin deliberately&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;empty&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;STRUCTURAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;declared field rendered zero bytes: %s&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="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;empty&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;man&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&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;v&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;volatile&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;fields&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&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;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STRUCTURAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;volatile field %s changed length: %d B pinned, %d B today&lt;/span&gt;&lt;span class="sh"&gt;"&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&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;a volatile field with a moving length is not volatile, it is drift in disguise&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&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;total&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;today&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;rendered_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STRUCTURAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;record does not sum: fields %d B, rendered_bytes %d B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                                   &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;today&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;rendered_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;

    &lt;span class="n"&gt;drift&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rendered_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;tol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tolerance_pct&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="mf"&gt;100.0&lt;/span&gt;
    &lt;span class="n"&gt;deltas&lt;/span&gt; &lt;span class="o"&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;fields&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&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;man&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;man&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&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;local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;deltas&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;man&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;local&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;vendor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;deltas&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;man&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;local&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;quiet&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="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;anchor  %8d B   pinned %s   tolerance %.1f%%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                   &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rendered_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pinned_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tolerance_pct&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="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;today   %8d B   day %-3s          drift %+.2f%%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                   &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;today&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;day&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="n"&gt;drift&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&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="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="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;deltas&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;d&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="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;  %-18s %+8d B   %s&lt;/span&gt;&lt;span class="sh"&gt;"&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;man&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="n"&gt;same&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;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;deltas&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&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;man&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&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;same&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="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;  unchanged (same sha256): %s&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="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;same&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="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="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="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;  from local files: %+d B      from the vendor: %+d B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK&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;drift&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tol&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;PASS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drift&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&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;def&lt;/span&gt; &lt;span class="nf"&gt;cmd_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anchor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;today&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s record&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drift&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;today&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;verdict&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STRUCTURAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ln&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&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;STRUCTURAL: %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;ln&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;verdict: STRUCTURAL  -&amp;gt;  exit 2   (fail-closed: unusable input is never a pass)&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;body&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&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="n"&gt;body&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="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK&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;verdict: BLOCK  -&amp;gt;  exit 1   (drift %+.2f%% is above the %.1f%% tolerance)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drift&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tolerance_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="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;verdict: PASS   -&amp;gt;  exit 0   (drift %+.2f%% is within the %.1f%% tolerance)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drift&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tolerance_pct&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;report-sha256: %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;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="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;


&lt;span class="c1"&gt;# ---------------------------------------------------------- the dashboard
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;daily_means&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;if&lt;/span&gt; &lt;span class="ow"&gt;not&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;path&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="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getsize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage log missing or empty: %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&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;days&lt;/span&gt; &lt;span class="o"&gt;=&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="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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&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;r&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;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&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;day&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;append&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;input_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage log has no records: %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&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;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d&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;d&lt;/span&gt; &lt;span class="ow"&gt;in&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;days&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;alarms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Days on which today&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s mean exceeds the baseline built from the PREVIOUS w days by factor t.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;ew&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;series&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;w&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;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;median&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ew&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ewma&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;ew&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ew&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;ALPHA&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ALPHA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ew&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calibrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;The lowest threshold on the ladder that raises zero false alarms on a flat world.&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;LADDER&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;alarms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&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;t&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;blind_band&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Uniform daily growth g is invisible while (1+g)^(w/2) &amp;lt;= T. Arithmetic, not a finding.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cmd_blindband&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="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;blind band of a rolling window: it compares today against a baseline centred&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;about w/2 days back, so a uniform daily growth g only trips it when (1+g)^(w/2) &amp;gt; T.&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;solve for g:   g* = T^(2/w) - 1     any growth slower than g* is invisible forever.&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="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;  window   threshold   invisible below   compounds to, over 30 days&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.10&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;blind_band&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&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;  w=%-4d   T=%.2f       %6.2f%%/day        x%.2f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&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="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="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;this is arithmetic. it is true before you collect a single byte of data.&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;the measurement is where real context creep lands relative to that band.&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;def&lt;/span&gt; &lt;span class="nf"&gt;cmd_replay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&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;args&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;anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_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;path&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anchor.json&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;anchor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_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;path&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;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events.json&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;events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;series&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;daily_means&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;path&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;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage.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;control&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;daily_means&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;path&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;ctrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage.jsonl&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;world: %s   days: %d   runs/day: fixed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&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;series&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;floor0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rendered_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;typical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# a flat fleet, so this is the work profile, not the drift
&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;floor at pin time %d B   typical run %d B   floor share %.1f%%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;floor0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;typical&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;floor0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;typical&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="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;  detector              threshold   alarms   first   on the day of&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;FAMILIES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calibrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&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;t&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  %-20s   none clean on a flat world&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;alarms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&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;events&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="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nothing: this one is about the creep&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;first&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="k"&gt;else&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="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;  %-20s   T=%.2f       %2d      %-5s   %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&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;hits&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first&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="k"&gt;else&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;ev&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;blind_band&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&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;  %-20s   blind below %.2f%%/day (x%.2f over 30 days)&lt;/span&gt;&lt;span class="sh"&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;first_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first_struct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drift_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;final&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="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;floor0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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;series&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_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;path&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;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;canary&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;day%03d.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;record&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drift&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quiet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;first_block&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;first_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drift_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drift&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STRUCTURAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;first_struct&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;first_struct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;
        &lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rendered_bytes&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="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first_block&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;first_struct&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  frozen anchor tol=%.1f%%     PASS on all %d days: the frozen input never drifted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tolerance_pct&lt;/span&gt;&lt;span class="sh"&gt;"&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;series&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;first_block&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;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;  frozen anchor tol=%.1f%%     BLOCK on day %d (drift %+.2f%%)  -&amp;gt; exit 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tolerance_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;first_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drift_at&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;first_struct&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;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;  frozen anchor tol=%.1f%%     STRUCTURAL on day %d (%s) -&amp;gt; exit 2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tolerance_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;first_struct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;events&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="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_struct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the set of fields changed&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;  floor on the last day %d B (%+.1f%% against the anchor)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;floor0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first_block&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="nf"&gt;else &lt;/span&gt;&lt;span class="p"&gt;(&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;first_struct&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="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cmd_sweep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;rstrip&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="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sweep root not found: %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_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;path&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anchor.json&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;anchor&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;  %-10s  floor share   w=7 clean T   blind below   alarms on slowcreep   first&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;label&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;name&lt;/span&gt; &lt;span class="ow"&gt;in&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&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;path&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;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ctrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;daily_means&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;path&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;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;control&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;usage.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;slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;daily_means&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;path&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;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slowcreep&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;usage.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;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calibrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;median&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;alarms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;median&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;share&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rendered_bytes&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;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;blind_band&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&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;  %-10s  %6.1f%%       T=%.2f        %6.2f%%/day   %2d %-18s %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&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;hits&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;- sees the creep&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;hits&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hits&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;-&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="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;  neither parameter reaches the frozen anchor. It reads a deterministic input, so it&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;  carries no work and no day-to-day noise: floor share 100%%, tolerance %.1f%%, same&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tolerance_pct&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;  BLOCK day in every row above. The rows only move the DASHBOARD.&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="n"&gt;USAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;usage:
  drift_anchor_gate.py check     &amp;lt;anchor.json&amp;gt; &amp;lt;today.json&amp;gt;
  drift_anchor_gate.py replay    &amp;lt;world_dir&amp;gt; &amp;lt;control_dir&amp;gt;
  drift_anchor_gate.py sweep     &amp;lt;sweep_root&amp;gt; &amp;lt;label&amp;gt;
  drift_anchor_gate.py blindband
&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="n"&gt;cmds&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;check&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cmd_check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replay&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cmd_replay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sweep&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cmd_sweep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blindband&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cmd_blindband&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="ow"&gt;or&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="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&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;USAGE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;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="n"&gt;cmds&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;make_worlds.py&lt;/code&gt;, the fixture builder. Seeded, and the only thing here that writes anything:&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;make_worlds.py - build the fixtures for drift_anchor_gate.py.

Offline, keyless, zero-network, stdlib-only, seeded. This is the only script here
that writes anything. The gate itself never writes.

Six worlds share ONE anchor (day 0 is byte-identical everywhere) and ONE noise
realization per (work_median, sigma_day) pair. Only the floor trajectory differs
between worlds, so the dashboard is never handed a harder noise draw than the
anchor. Runs per day are FIXED at 40 on purpose: that removes run-count noise,
which is a handicap in the dashboard&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s favour.
&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;hashlib&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;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shutil&lt;/span&gt;

&lt;span class="n"&gt;HERE&lt;/span&gt; &lt;span class="o"&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HERE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worlds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;DAYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
&lt;span class="n"&gt;RUNS_PER_DAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;
&lt;span class="n"&gt;BASE_SEED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20260715&lt;/span&gt;
&lt;span class="n"&gt;WORK_MEDIAN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;18_000&lt;/span&gt;      &lt;span class="c1"&gt;# bytes of real per-run work: the user turn, retrieved chunks, history
&lt;/span&gt;&lt;span class="n"&gt;WORK_SIGMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;          &lt;span class="c1"&gt;# lognormal spread of work across runs
&lt;/span&gt;&lt;span class="n"&gt;SIGMA_DAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;          &lt;span class="c1"&gt;# lognormal spread of the daily job mix
&lt;/span&gt;&lt;span class="n"&gt;TOL_PCT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;

&lt;span class="n"&gt;TASK&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;canary task: read the pinned fixture file, return its word count as JSON. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;do not call tools. do not browse. answer in one line.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Floor at pin time, in bytes, by field.
&lt;/span&gt;&lt;span class="n"&gt;FLOOR0&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;harness_scaffold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# vendor-injected: tool-call protocol preamble + safety wrapper
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system_prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools_json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;26_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instructions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# CLAUDE.md / AGENTS.md
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp_a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp_b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;VOLATILE&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;session_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;ORIGIN&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;harness_scaffold&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;vendor&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;system_prompt&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;local:system_prompt.txt&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;tools_json&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;local:tools.json&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;instructions&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;local:CLAUDE.md&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;mcp_a&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;local:mcp/a.json&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;mcp_b&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;local:mcp/b.json&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;mcp_c&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;local:mcp/c.json&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;mcp_d&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;local:mcp/d.json&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;session_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;runtime&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;timestamp&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;runtime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;WORDS&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;agent budget cache canary context deploy drift enforce escalate fixture gate harness &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ingest invariant ledger manifest observe payload pin policy prompt quota render replay &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry rollback runbook schema scope session snapshot spend threshold token trace vendor &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;window workspace approve reject timeout latency shard cursor batch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;SUBJECTS&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;retry budget&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 allowlist&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;pii redaction&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;escalation path&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;commit policy&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;sandbox scope&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeout ladder&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;cache warmup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rollback drill&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;on-call handoff&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;schema review&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;cost ceiling&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;log retention&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;vendor swap&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;eval cadence&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;prompt review&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;secret handling&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;shell allowlist&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;diff size cap&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;branch policy&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;text_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&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="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Deterministic ASCII text of exactly n bytes.&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;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tag&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="n"&gt;size&lt;/span&gt; &lt;span class="o"&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;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORDS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&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;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&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;parts&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&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="o"&gt;*&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;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rule_section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;One appended policy section. Unique subject, unique body, 380..620 bytes.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;380&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;620&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;head&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\n&lt;/span&gt;&lt;span class="s"&gt;## Rule %03d: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SUBJECTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&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;SUBJECTS&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;head&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;text_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&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;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rationale-%03d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# ---- static content, generated once so it is stable across days and worlds ----
&lt;/span&gt;&lt;span class="n"&gt;_c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_SEED&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="mh"&gt;0xF100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;BASE&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="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&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="k"&gt;for&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;FLOOR0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="n"&gt;_s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_SEED&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="mh"&gt;0x5EC7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;SECTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;rule_section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;EXTRA_TOOLS&lt;/span&gt; &lt;span class="o"&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="nf"&gt;text_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1_150&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-schema-%02d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 13,800 B
&lt;/span&gt;&lt;span class="n"&gt;MCP_C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;text_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4_200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp_c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;MCP_D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;text_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3_800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp_d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;SPIKE_DOC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;text_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pasted-architecture-doc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;BUMP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2_600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scaffold-bump-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3_100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scaffold-bump-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3_400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scaffold-bump-3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="n"&gt;EVENTS&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;control&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;creep&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="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp: +3rd server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp: +4th server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools: +12 schemas&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;slowcreep&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="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp: +3rd server&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;spike&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="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instructions: +40 KB doc pasted&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;harness&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="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vendor: scaffold bump 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vendor: scaffold bump 2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vendor: scaffold bump 3&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;workload&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="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;work per run doubles (floor untouched)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;WORLDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EVENTS&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;render_floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Field -&amp;gt; content string for this world on this day. Day 0 is identical in every world.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE&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;world&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;creep&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;day&lt;/span&gt;                                  &lt;span class="c1"&gt;# one policy section per day
&lt;/span&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slowcreep&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;                             &lt;span class="c1"&gt;# one policy section every third day
&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;n&lt;/span&gt; &lt;span class="o"&gt;=&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;n&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instructions&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;BASE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instructions&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="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;SECTIONS&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;creep&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;day&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools_json&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;BASE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools_json&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;EXTRA_TOOLS&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;14&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp_c&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;MCP_C&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;33&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp_d&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;MCP_D&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slowcreep&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;33&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp_c&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;MCP_C&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spike&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instructions&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;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instructions&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;SPIKE_DOC&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;harness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="o"&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;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&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;BUMP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&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;day&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;d&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;harness_scaffold&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;BASE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;harness_scaffold&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;add&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;work_scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;The only world that changes the WORK per run rather than the floor.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workload&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;&amp;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;else&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&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;dump_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Rendered-request record: per-field byte count + sha256. The shape of a privacy-safe request log.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;rec&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="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;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&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;v&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;VOLATILE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;rec&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="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;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&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;rec&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;total_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&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;v&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VOLATILE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&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;noise_matrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;work_median&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Per-run work bytes. Identical across worlds for a given (work_median, sigma_day).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DAYS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;dm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lognormvariate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma_day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;work_median&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lognormvariate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WORK_SIGMA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;dm&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;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RUNS_PER_DAY&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;rows&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_usage&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;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noise&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;usage.jsonl: one line per run, the log a fleet dashboard already has.&lt;/span&gt;&lt;span class="sh"&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;w&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DAYS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;floor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;total_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;render_floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;work_scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&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;run&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RUNS_PER_DAY&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;day&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;run&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_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;floor&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;noise&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k&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;def&lt;/span&gt; &lt;span class="nf"&gt;main&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdir&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="n"&gt;shutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rmtree&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makedirs&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="n"&gt;day0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;render_floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;control&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;anchor&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;canary_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;floor-canary-v1&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;pinned_at&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;day 0&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;tolerance_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TOL_PCT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TASK&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rendered_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;total_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manifest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&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;rec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;dump_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manifest&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&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;sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&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;origin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ORIGIN&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volatile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;VOLATILE&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&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;anchor.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor&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="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;noise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;noise_matrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORK_MEDIAN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SIGMA_DAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BASE_SEED&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;world&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;WORLDS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;wd&lt;/span&gt; &lt;span class="o"&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;path&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;OUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;world&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="nf"&gt;makedirs&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;path&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;wd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;canary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DAYS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;render_floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;rec&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;canary_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;floor-canary-v1&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;day&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TASK&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rendered_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;total_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dump_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
            &lt;span class="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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;canary&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;day%03d.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&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="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;write_usage&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;path&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;wd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage.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;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noise&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;EVENTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;items&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="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Repo snapshots for the harness world: the local files a `git diff` would look at.
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="ow"&gt;in&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="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&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;path&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;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;harness&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;repo_day%03d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;day&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="nf"&gt;makedirs&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;path&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;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;render_floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;harness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system_prompt.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system_prompt&lt;/span&gt;&lt;span class="sh"&gt;"&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLAUDE.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instructions&lt;/span&gt;&lt;span class="sh"&gt;"&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools_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;open&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;path&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;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp&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;a.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp_a&lt;/span&gt;&lt;span class="sh"&gt;"&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp&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;b.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp_b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# Degenerate inputs. Every one of these must fail closed.
&lt;/span&gt;    &lt;span class="n"&gt;dg&lt;/span&gt; &lt;span class="o"&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;path&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;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;degenerate&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;base&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&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;control&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;canary&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;day000.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;variant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;r&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;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;base&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;mutate&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;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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dg&lt;/span&gt;&lt;span class="p"&gt;,&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;.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&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="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;drop_tools&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="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;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools_json&lt;/span&gt;&lt;span class="sh"&gt;"&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;rendered_bytes&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="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&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;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;values&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;zero_mcp&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="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;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;mcp_b&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;bytes&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sha&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;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;rendered_bytes&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="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&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;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;values&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;unknown_field&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="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;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;mcp_c&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;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4_200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MCP_C&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;rendered_bytes&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="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&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;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;values&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;task_changed&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="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;task_sha256&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="nf"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TASK&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; and also list the files you can see&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;volatile_grew&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="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;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;session_id&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;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;96&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;rendered_bytes&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="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&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;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;values&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;bad_sum&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="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;rendered_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;61_060&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;20_000&lt;/span&gt;

    &lt;span class="nf"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dropped_field&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drop_tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zero_byte_field&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zero_mcp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown_field&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unknown_field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_changed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_changed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volatile_grew&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;volatile_grew&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inconsistent_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bad_sum&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empty.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;close&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;truncated.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;canary_id&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="s"&gt;floor-canary-v1&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="s"&gt;day&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 0,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Sweeps: the two parameters that could flip the result. control + slowcreep share a seed.
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60_000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BASE_SEED&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1_000_003&lt;/span&gt;
        &lt;span class="n"&gt;nz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;noise_matrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SIGMA_DAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&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;world&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;control&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;slowcreep&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&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;path&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;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;sweep_floorshare&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;wm%06d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;world&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="nf"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;write_usage&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;path&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;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage.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;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&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;path&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;OUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events.json&lt;/span&gt;&lt;span class="sh"&gt;"&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;path&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;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events.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;for&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BASE_SEED&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;WORK_MEDIAN&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;sig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1_000_003&lt;/span&gt;
        &lt;span class="n"&gt;nz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;noise_matrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORK_MEDIAN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&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;world&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;control&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;slowcreep&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&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;path&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;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;sweep_sigma&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;s%03d&lt;/span&gt;&lt;span class="sh"&gt;"&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;sig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;world&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="nf"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;write_usage&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;path&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;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage.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;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&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;path&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;OUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events.json&lt;/span&gt;&lt;span class="sh"&gt;"&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;path&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;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events.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;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;worlds built: %s&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;WORLDS&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;anchor rendered_bytes: %d B  tolerance: %.1f%%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rendered_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;TOL_PCT&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;world&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;WORLDS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;total_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;render_floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nf"&gt;total_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;render_floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;a&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="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;  %-10s floor day0 %7d B  -&amp;gt;  day59 %7d B   (x%.2f, %+.3f%%/day)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;run_demo.sh&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# run_demo.sh - rebuild the fixtures from scratch and run every claim in the post.&lt;/span&gt;
&lt;span class="c"&gt;# Offline, keyless, no network, stdlib only. Nothing here reads a clock, so stdout is&lt;/span&gt;
&lt;span class="c"&gt;# byte-for-byte reproducible: run it twice, diff the two outputs, they are identical.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

hr&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'\n%s\n%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'=%.0s'&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 &lt;span class="k"&gt;${#&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="si"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

hr &lt;span class="s2"&gt;"0. THE PREDICTION (arithmetic, before any data exists)"&lt;/span&gt;
python3 drift_anchor_gate.py blindband

hr &lt;span class="s2"&gt;"1. BUILD THE WORLDS (seeded, deterministic, no network)"&lt;/span&gt;
python3 make_worlds.py

hr &lt;span class="s2"&gt;"2. KILLER DEMO: the harness world. Every local file is byte-identical to day 0."&lt;/span&gt;
diff &lt;span class="nt"&gt;-rq&lt;/span&gt; worlds/harness/repo_day000 worlds/harness/repo_day059 &lt;span class="se"&gt;\&lt;/span&gt;
  &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;"diff -rq repo_day000 repo_day059: no differences. git diff would print nothing."&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"sha256 of every local file, day 0 vs day 59:"&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;worlds/harness/repo_day000 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f | &lt;span class="nb"&gt;sort&lt;/span&gt; | xargs shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256 &lt;span class="o"&gt;)&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^/  d0  /'&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;worlds/harness/repo_day059 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f | &lt;span class="nb"&gt;sort&lt;/span&gt; | xargs shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256 &lt;span class="o"&gt;)&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^/  d59 /'&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--- same anchor, same day 12, two worlds ---"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt; drift_anchor_gate.py check worlds/anchor.json worlds/control/canary/day012.json"&lt;/span&gt;
python3 drift_anchor_gate.py check worlds/anchor.json worlds/control/canary/day012.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;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt; drift_anchor_gate.py check worlds/anchor.json worlds/harness/canary/day012.json"&lt;/span&gt;
python3 drift_anchor_gate.py check worlds/anchor.json worlds/harness/canary/day012.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;

hr &lt;span class="s2"&gt;"3. SIX WORLDS, ONE ANCHOR, ONE LOG. Each rolling detector gets its own best threshold."&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;w &lt;span class="k"&gt;in &lt;/span&gt;control creep slowcreep harness spike workload&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
  python3 drift_anchor_gate.py replay &lt;span class="s2"&gt;"worlds/&lt;/span&gt;&lt;span class="nv"&gt;$w&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; worlds/control
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  replay exit=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;hr &lt;span class="s2"&gt;"4. THE TYPICAL SCENARIO, AT THE BOUNDARY. The gate must fail CLOSED on slow creep."&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt; drift_anchor_gate.py check worlds/anchor.json worlds/slowcreep/canary/day008.json"&lt;/span&gt;
python3 drift_anchor_gate.py check worlds/anchor.json worlds/slowcreep/canary/day008.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;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt; drift_anchor_gate.py check worlds/anchor.json worlds/slowcreep/canary/day009.json"&lt;/span&gt;
python3 drift_anchor_gate.py check worlds/anchor.json worlds/slowcreep/canary/day009.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;

hr &lt;span class="s2"&gt;"5. DEGENERATE INPUTS: every one of these must fail closed."&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in &lt;/span&gt;missing empty truncated dropped_field zero_byte_field unknown_field task_changed volatile_grew inconsistent_total&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--- &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt; ---"&lt;/span&gt;
  python3 drift_anchor_gate.py check worlds/anchor.json &lt;span class="s2"&gt;"worlds/degenerate/&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;.json"&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;span class="k"&gt;done
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--- clean day 0 (the control: it must still pass) ---"&lt;/span&gt;
python3 drift_anchor_gate.py check worlds/anchor.json worlds/control/canary/day000.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;

hr &lt;span class="s2"&gt;"6. SWEEP A: the one parameter that flips the result. How much of the request is floor?"&lt;/span&gt;
python3 drift_anchor_gate.py sweep worlds/sweep_floorshare work_median

hr &lt;span class="s2"&gt;"7. SWEEP B: day-to-day noise. Does the dashboard wake up at any of these?"&lt;/span&gt;
python3 drift_anchor_gate.py sweep worlds/sweep_sigma sigma_day
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it: &lt;code&gt;bash run_demo.sh &amp;gt; output.txt&lt;/code&gt;. It takes a few seconds, touches no network, needs no keys, and produces the same bytes every time. Mine hashes to &lt;code&gt;1772e695cb75f79d9e3f162ed4c49477a329703610cdf5ddff54cec2cc4da62a&lt;/code&gt;.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The time series are synthetic.&lt;/strong&gt; I do not have a production fleet, and I am not going to pretend I do. The builder is in this post, the shape is declared (how many bytes a policy section adds, what day an MCP server lands), and the seed is fixed. Change the parameters and the numbers move. What is not synthetic: the blind-band arithmetic, and the fact that the gate runs on your real &lt;code&gt;usage.jsonl&lt;/code&gt; and your real request dumps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The anchor does not see workload growth.&lt;/strong&gt; Demonstrated above, in the one world where it passes for 60 days while cost genuinely rises. If your bill grew because your product got busier, this tool will tell you nothing and your dashboard will tell you everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The anchor only sees the rendered request it is handed.&lt;/strong&gt; No request dump, no gate. It reads nothing from the network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not tell you whether the drift was worth it.&lt;/strong&gt; A third MCP server may well be worth its 4 KB on every call. The gate converts a silent accumulation into a decision that somebody signs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model-side drift is out of scope.&lt;/strong&gt; If the vendor changes the tokenizer or the model gets chattier, the input payload can be identical while the bill moves. This gate reads the input payload only. That is a real hole, and it is Dipankar's original point coming back around.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 2 percent tolerance is not a measured optimum.&lt;/strong&gt; On my fixtures the control world drifts by exactly 0.00 percent, so any positive tolerance gives zero false alarms. I picked 2 percent as a real-world allowance for things like a version string that changes length. My data did not force that number, and I am not going to dress it up as though it did.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I would do on Monday
&lt;/h2&gt;

&lt;p&gt;Dump the rendered request for one frozen, deterministic task. Save the byte counts and hashes as an anchor with today's date. Add one CI step that re-renders that same task and compares. Set the tolerance somewhere near 2 percent, because a deterministic input has no noise to hide in, and let it fail the build.&lt;/p&gt;

&lt;p&gt;Then keep your dashboard exactly as it is. It is watching for the paste and the traffic step, and it is good at that. It was never watching for the year.&lt;/p&gt;

&lt;p&gt;One thing I have not settled, and I would genuinely like a second opinion: &lt;strong&gt;the re-pin.&lt;/strong&gt; Every legitimate change (a new MCP server, a needed rule) forces a re-pin, and a re-pin resets the baseline. Do it a dozen times and you have reinvented a rolling baseline with extra steps, just with human latency in the loop. My current answer is that the re-pin has to carry the cumulative drift since the &lt;strong&gt;original&lt;/strong&gt; pin, so the number you are approving is "the floor is now 47 percent above where it was in March", not "plus 4 percent since last week". I have not built that yet. If you have run a pinned baseline in production for longer than a quarter, I want to know how you kept the pin honest.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write about pre-execution control for AI agents: gates that decide before the money moves, not charts that explain it afterwards. Every post ships a tool you can actually run, offline, with no keys. Follow along if that is your kind of thing, and if your context floor has a story, put it in the comments. I am especially interested in anyone who has watched a vendor bump a harness without telling them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>finops</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
