<?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: Jeremy Longshore</title>
    <description>The latest articles on DEV Community by Jeremy Longshore (@jeremy_longshore).</description>
    <link>https://dev.to/jeremy_longshore</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%2F3842419%2Ff5d02b54-daf0-4520-9aef-118fbd0c24ac.jpeg</url>
      <title>DEV Community: Jeremy Longshore</title>
      <link>https://dev.to/jeremy_longshore</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeremy_longshore"/>
    <language>en</language>
    <item>
      <title>Do Not Blindly Restart: Designing a Self-Healing Watchdog That Stays Honest</title>
      <dc:creator>Jeremy Longshore</dc:creator>
      <pubDate>Wed, 22 Jul 2026 10:28:31 +0000</pubDate>
      <link>https://dev.to/jeremy_longshore/do-not-blindly-restart-designing-a-self-healing-watchdog-that-stays-honest-58hm</link>
      <guid>https://dev.to/jeremy_longshore/do-not-blindly-restart-designing-a-self-healing-watchdog-that-stays-honest-58hm</guid>
      <description>&lt;p&gt;A self-healing system that restarts blindly is an outage generator with extra steps.&lt;/p&gt;

&lt;p&gt;On 2026-07-20 a burn-in watchdog guarding the SigNoz observability stack hard-latched and paged. The stack was fine. The breach was a phantom. That failure exposed two design defects that show up in almost every monitoring system built in a hurry: a watchdog that restarts blindly, and a monitor that lies about its own success. Both come back to the same rule: infrastructure that watches for failure has to be more honest about failure than the thing it watches.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: a phantom latch
&lt;/h2&gt;

&lt;p&gt;The burn-in watchdog exists to prove a freshly deployed container is stable before promotion. It polls pressure-stall (PSI) metrics for memory and io, and if a resource crosses a ceiling it stops the burn-in, latches, and pages a human. That last part matters: a latch is a hard stop. Nothing recovers on its own after a latch. Someone gets woken up.&lt;/p&gt;

&lt;p&gt;The 2026-07-20 page fired on io PSI. Root cause: a ClickHouse background-merge io burst, sub-10-seconds long, against a baseline io of roughly 0.00. The check that caught it looked like this:&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;# The bad check: single sample, no duration, hard ceiling&lt;/span&gt;
&lt;span class="nv"&gt;psi_io&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;read_psi_io_avg10&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s2"&gt;"BEGIN{exit !(&lt;/span&gt;&lt;span class="nv"&gt;$psi_io&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt;= 1.0)}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;latch &lt;span class="s2"&gt;"io PSI breach: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;psi_io&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;   &lt;span class="c"&gt;# hard stop + page&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One sample. One threshold. No sense of time. A transient merge that lasted less than ten seconds tripped a hard stop on a stack that was never in trouble. The watchdog was not measuring saturation. It was measuring a spike and calling it saturation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just raise the threshold
&lt;/h2&gt;

&lt;p&gt;The obvious fix is to bump &lt;code&gt;1.0&lt;/code&gt; to something higher and move on. That is the wrong fix, and it is worth being precise about why.&lt;/p&gt;

&lt;p&gt;A blanket raise trades one failure mode for a worse one. Raise the io ceiling high enough that a merge burst no longer trips it, and you have also raised it past the point where a real, sustained io saturation would trip it. You do not get fewer false pages. You get false pages replaced by missed real incidents, which is the failure mode a watchdog exists to prevent. Raising the number makes the monitor quieter, not more correct. A quiet monitor that misses saturation is worse than a noisy one, because you trust it.&lt;/p&gt;

&lt;p&gt;The actual problem was never the height of the ceiling. It was that the check had no concept of duration. A spike and a sustained load produce the same instantaneous PSI reading. The only thing that separates them is time. So the fix is time, applied per resource:&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;# Duration-tolerant io check: a spike is logged and ignored,&lt;/span&gt;
&lt;span class="c"&gt;# sustained pressure confirms and trips.&lt;/span&gt;
&lt;span class="nv"&gt;psi_io_avg10&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;read_psi_io_avg10&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;psi_io_avg60&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;read_psi_io_avg60&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s2"&gt;"BEGIN{exit !(&lt;/span&gt;&lt;span class="nv"&gt;$psi_io_avg10&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt;= 15.0)}"&lt;/span&gt; &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;awk&lt;/span&gt; &lt;span class="s2"&gt;"BEGIN{exit !(&lt;/span&gt;&lt;span class="nv"&gt;$psi_io_avg60&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt;= 5.0)}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;breach &lt;span class="s2"&gt;"io PSI sustained: avg10=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;psi_io_avg10&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% avg60=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;psi_io_avg60&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;log &lt;span class="s2"&gt;"io PSI transient ignored: avg10=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;psi_io_avg10&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The io ceiling now requires &lt;code&gt;avg10 &amp;gt;= 15%&lt;/code&gt; AND &lt;code&gt;avg60 &amp;gt;= 5%&lt;/code&gt;. Yes, the avg10 number itself moved from 1.0 up to 15, and that is not the blanket raise the last section warned against. The 1.0% single-sample line was never a saturation boundary in the first place, it sat down in the noise floor where a routine merge lives, so it was destined to fire on nothing. Raising it alone would still miss a real event. The avg60 term is what makes the higher ceiling honest instead of just quieter: it is the confirmation window. A sub-10-second merge cannot move a 60-second average past 5%, so it gets logged and ignored. A genuine io saturation holds both windows and trips. The fix was never a bigger number, it was a bigger number that a spike cannot reach.&lt;/p&gt;

&lt;p&gt;Memory PSI kept its single-sample &lt;code&gt;avg10 &amp;lt; 1.0%&lt;/code&gt; rule on purpose. Memory baseline is 0.00 and a memory floor breach is the kind of thing you want to latch fast, so the fast latch there is deliberate, filed as an advisory item rather than "fixed." That is the point of per-resource ceilings: io needs duration tolerance, memory does not, and a blanket policy would get one of them wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The redesign: classify, then bound, then latch
&lt;/h2&gt;

&lt;p&gt;Raising thresholds was never going to be enough, because the deeper defect was structural. The old watchdog had exactly one response to any breach: stop and page. It could not tell a recoverable hiccup apart from a permanent fault, so it treated everything as permanent. The redesign gives it a decision: classify the breach, restart if it is on a known-safe allowlist, latch if it is anything else, and stop restarting before restarting becomes the outage.&lt;/p&gt;

&lt;p&gt;Four conditions landed, each closing a specific way a self-healer can hurt you.&lt;/p&gt;

&lt;h3&gt;
  
  
  C1: Fail-closed classifier
&lt;/h3&gt;

&lt;p&gt;RECOVERABLE is an allowlist, not a denylist. Exactly two reasons are recoverable: &lt;code&gt;c8-unhealthy&lt;/code&gt; and &lt;code&gt;staging-restart-anomaly&lt;/code&gt;. Everything else latches immediately: a RAM floor, disk, the data tree, a memory-PSI breach, a prod-unhealthy signal, an unexpected listener (that is a security event), and critically any reason the classifier has never seen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;classify_breach&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
    &lt;/span&gt;c8-unhealthy|staging-restart-anomaly&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"RECOVERABLE"&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"NON_RECOVERABLE"&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;esac&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default arm is the whole design. A denylist would restart on anything it forgot to name. This allowlist latches on anything it does not explicitly bless. When someone adds a new breach reason next quarter and forgets to classify it, the watchdog latches and pages instead of restarting into an unknown state. That is fail-closed: the unknown path is the safe path. Do not blindly restart.&lt;/p&gt;

&lt;h3&gt;
  
  
  C2: Slow-flap guard
&lt;/h3&gt;

&lt;p&gt;A container that restarts, looks healthy, then breaches again an hour later can loop for days, each cycle a small outage. &lt;code&gt;BI_MAX_LIFETIME_RECOVERIES=3&lt;/code&gt; caps lifetime recoveries per burn-in. After the cap, a recoverable breach latches instead of restarting forever. Recovery is bounded, not infinite.&lt;/p&gt;

&lt;h3&gt;
  
  
  C3: Health poll, not snapshot
&lt;/h3&gt;

&lt;p&gt;A restarted container is not healthy the instant it comes up. Verifying health with a single snapshot can latch a container that is still starting. &lt;code&gt;verify_healthy&lt;/code&gt; polls up to &lt;code&gt;BI_HEALTH_TIMEOUT=120s&lt;/code&gt;, requiring staging active AND C8 healthy, before it decides anything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;verify_healthy&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;deadline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;SECONDS &lt;span class="o"&gt;+&lt;/span&gt; BI_HEALTH_TIMEOUT&lt;span class="k"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; SECONDS &amp;lt; deadline &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if &lt;/span&gt;staging_active &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; c8_healthy&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then return &lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi
    &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;5
  &lt;span class="k"&gt;done
  return &lt;/span&gt;1   &lt;span class="c"&gt;# never latched on a single still-starting snapshot&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  C4: Reset hygiene and honest outcome
&lt;/h3&gt;

&lt;p&gt;A new &lt;code&gt;burn-in-reset.sh&lt;/code&gt; clears &lt;code&gt;recovery.json&lt;/code&gt; and &lt;code&gt;incident.json&lt;/code&gt;. RECOVERED is logged only after a verified-healthy poll passes, never optimistically at restart time. The first breach is preserved exactly once in &lt;code&gt;incident.json&lt;/code&gt;, so the original signal survives even after a successful recovery. &lt;code&gt;recovery_count&lt;/code&gt; is surfaced in &lt;code&gt;status.json&lt;/code&gt;, and recovery pages carry severity &lt;code&gt;info&lt;/code&gt;, because a recovery is not an incident and should not read like one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification is the product
&lt;/h2&gt;

&lt;p&gt;A self-healer is only as trustworthy as the failures you can prove it survives. The redesign shipped with four planted scenarios, each run 20 times, 20 for 20:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;transient-recovery&lt;/strong&gt;: breach, restart, verify healthy, log RECOVERED.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;persistent-failure&lt;/strong&gt;: breach latches immediately, zero restarts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;notification-failure&lt;/strong&gt;: recovery proceeds, logs an honest "receipt non-zero," fires no false page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;retry-exhaustion&lt;/strong&gt;: exactly MAX_RESTARTS attempts, then latch, no infinite loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two that matter most are the ones that assert the watchdog does nothing. Persistent-failure must latch with zero restarts, because a fault that restarting cannot fix must never be restarted. Retry-exhaustion must stop at the cap and latch, because the whole point of a bound is that it holds under a fault that never clears. A test that only proves the happy path (breach, restart, healthy) proves nothing about safety. The proof that a self-healer is safe is the set of scenarios where it correctly refuses to act.&lt;/p&gt;

&lt;p&gt;All three scripts are shellcheck clean. The scenarios are wired into &lt;code&gt;validate:signoz-staging&lt;/code&gt;, and &lt;code&gt;pnpm check&lt;/code&gt; exits 0. Guardian review returned approve-with-conditions, and the four conditions above are those blocking conditions, landed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parallel honesty story
&lt;/h2&gt;

&lt;p&gt;The same day, a second system taught the same lesson from a different angle. The governed alert chain went live on both hosts, and the trap was sitting in plain sight: &lt;code&gt;notify.sh&lt;/code&gt; exits 0 unconditionally. It always reports success. Reuse it as the alert transport and you recreate a prior defect where 108 alerts were false-delivered: reported sent, never sent.&lt;/p&gt;

&lt;p&gt;An alert transport that always exits 0 is not a monitor. It is a monitor-shaped object that lies. The fix was a new &lt;code&gt;vps-slack-transport.sh&lt;/code&gt; that does an honest webhook POST, classifying the HTTP status and treating a non-2xx as a real failure instead of trusting the exit code. &lt;code&gt;notify.sh&lt;/code&gt; was left untouched for its other callers, so the honesty lives in the transport that needs it.&lt;/p&gt;

&lt;p&gt;That verification was as ruthless as the watchdog's: six drills, a bidirectional rollback rehearsal that succeeded in each direction, and a 26-case proof covering 22 failure modes plus 4 non-delivered outcomes, with live green sweeps captured on both hosts. The dev box also got the migrated scorecardecho uptime producer, replacing a stale pre-adoption copy that cron had been quietly running since 07-16, a real docs-versus-reality gap, plus an external dead-man's-switch ping. The guardian loop stayed honest too: round one blocked on an untracked liveness-bypass exclusion, which became a filed follow-up, and round two approved.&lt;/p&gt;

&lt;p&gt;Both systems converge on one rule. A monitor that always reports success is worse than no monitor, because no monitor at least tells you nothing is watching. A lying monitor tells you everything is fine. The watchdog's fail-closed classifier and the transport's 2xx check are the same idea wearing different clothes: make the honest outcome the default outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also shipped
&lt;/h2&gt;

&lt;p&gt;Outside the watchdog incident, three smaller things landed the same day.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;diagnostic-pro (PR #23)&lt;/strong&gt;: added the in-repo MiniMax advisory PR-review workflow (defect lane plus adversarial-claims lane), the same reviewer already running in claude-code-plugins and intent-os. Runs on &lt;code&gt;pull_request&lt;/code&gt; (never &lt;code&gt;pull_request_target&lt;/code&gt;), same-repo head guard so fork PRs never see the API key, gated behind an &lt;code&gt;ENABLE_MINIMAX_REVIEW&lt;/code&gt; kill switch, advisory only. Enforcement travels with the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;claude-code-plugins&lt;/strong&gt;: fixed a heredoc bug in the MiniMax A-grade-coach lane. A &lt;code&gt;head -c 16000&lt;/code&gt; byte cut landed mid-line, leaving no trailing newline, so the closing &lt;code&gt;__MINIMAX_EOF__&lt;/code&gt; delimiter fused onto the last content line and GitHub Actions aborted the step with "Matching delimiter not found." Fix: force a trailing newline plus a per-run random delimiter. Only triggered on PRs big enough to fill the 16KB cap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;learn-intent-solutions-hub&lt;/strong&gt;: ported the CPN cohort hub off Cloudflare onto Intent Solutions infra. Four runtime seams swapped: Workers &lt;code&gt;export default app&lt;/code&gt; to &lt;code&gt;@hono/node-server&lt;/code&gt;; D1 &lt;code&gt;drizzle(c.env.DB)&lt;/code&gt; to a &lt;code&gt;better-sqlite3&lt;/code&gt; file; &lt;code&gt;c.env.ASSETS.fetch()&lt;/code&gt; to Caddy &lt;code&gt;file_server&lt;/code&gt;; &lt;code&gt;c.env.EXPORT_TOKEN&lt;/code&gt; to &lt;code&gt;process.env&lt;/code&gt;. Added &lt;code&gt;/api/health&lt;/code&gt; for a real post-deploy smoke. Product behavior, schema, and routes unchanged.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/exit-0-is-not-success/"&gt;Exit 0 Is Not Success&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/a-green-recovery-drill-can-still-be-lying/"&gt;A Green Recovery Drill Can Still Be Lying&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/liveness-without-health-is-theater/"&gt;Liveness Without Health Is Theater&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>observability</category>
      <category>devops</category>
      <category>automation</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Passing Is Not Validating: A Green Check With No Teeth</title>
      <dc:creator>Jeremy Longshore</dc:creator>
      <pubDate>Wed, 22 Jul 2026 10:28:29 +0000</pubDate>
      <link>https://dev.to/jeremy_longshore/passing-is-not-validating-a-green-check-with-no-teeth-ob7</link>
      <guid>https://dev.to/jeremy_longshore/passing-is-not-validating-a-green-check-with-no-teeth-ob7</guid>
      <description>&lt;p&gt;A validator gate returned green on every check. Nineteen rule checks across four rules, all passing. And most of them were exercising nothing at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contract
&lt;/h2&gt;

&lt;p&gt;Intent Solutions built a repository-identity contract to canonically identify GitHub repositories in a registry. The question: what is the single source of truth for a repo's identity when repos get renamed and transferred between owners and orgs?&lt;/p&gt;

&lt;p&gt;The design decision was sound. Use the immutable GitHub database ID (the numeric &lt;code&gt;source_repository_id&lt;/code&gt;), never the mutable &lt;code&gt;owner/name&lt;/code&gt; pair. Owner changes on transfer. Name changes on rename. Those belong in an &lt;code&gt;identity_history&lt;/code&gt; list, not the primary key.&lt;/p&gt;

&lt;p&gt;The schema enforced this intent.&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;"$schema"&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://json-schema.org/draft-07/schema#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"source_repository_id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&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;"source_repository_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="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"minimum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The ONLY identity key. A string here is an invalid record by construction."&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;"current_owner"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&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;"current_name"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&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;"identity_history"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"items"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"properties"&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;"owner"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"changed_at"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"date-time"&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="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;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A separate validator rule-gate existed to prove the identity rules held in practice. Four rules. Nineteen check statements total. All green.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Symptom
&lt;/h2&gt;

&lt;p&gt;The code-review lane weakened the schema as a regression test. It changed &lt;code&gt;source_repository_id&lt;/code&gt; from &lt;code&gt;{"type": "integer"}&lt;/code&gt; to &lt;code&gt;{"type": ["integer", "string"]}&lt;/code&gt;. That should break the whole identity model. A string could now be the identity key. The validation rules should go red.&lt;/p&gt;

&lt;p&gt;They stayed green.&lt;/p&gt;

&lt;p&gt;That is the proof: a check has no teeth. You sabotage the exact thing it exists to reject, and it does not notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bug
&lt;/h2&gt;

&lt;p&gt;The first rule, R1, was supposed to validate rename and transfer survival. This is what it looked like.&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;test_r1_rename_transfer_survival&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Construct test records
&lt;/span&gt;    &lt;span class="n"&gt;renamed&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;source_repository_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;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_name&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;new-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;transferred&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;source_repository_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;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;other-org&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;current_name&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;old-name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Arithmetic check (never validates against schema)
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;renamed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source_repository_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;transferred&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source_repository_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;assert&lt;/span&gt; &lt;span class="n"&gt;renamed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;original-owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# GREEN. Tests nothing.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It constructed its own data. It did arithmetic on that data. It never called the actual jsonschema validator. It was testing the test, not the system.&lt;/p&gt;

&lt;p&gt;This is a tautological test: it builds its own input, asserts something about that input, and never runs the system under test. It cannot fail for the reason it exists, because the thing it guards is never exercised.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;The teeth check: deliberately weaken the invariant and assert the rule goes red.&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;test_r1_rename_transfer_survival&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Valid snapshot 1: rename
&lt;/span&gt;    &lt;span class="n"&gt;renamed&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;source_repository_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;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_name&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;new-name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Valid snapshot 2: transfer
&lt;/span&gt;    &lt;span class="n"&gt;transferred&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;source_repository_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;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;other-org&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;current_name&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;old-name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Positive control: both snapshots must pass the real schema
&lt;/span&gt;    &lt;span class="n"&gt;validator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonschema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Draft7Validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&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;renamed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&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;transferred&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;renamed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source_repository_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;transferred&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source_repository_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Teeth check: weaken the schema (allow strings as source_repository_id)
&lt;/span&gt;    &lt;span class="n"&gt;weakened&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepcopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;weakened&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source_repository_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;type&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;integer&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;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# With the weakened schema, a malformed record using owner as identity should be REJECTED
&lt;/span&gt;    &lt;span class="n"&gt;invalid_by_string_id&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;source_repository_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;original-owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Wrong: string, not integer
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# TEETH CHECK: the original validator rejects the string, the weakened one accepts it
&lt;/span&gt;    &lt;span class="n"&gt;weak_validator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonschema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Draft7Validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weakened&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;weak_validator&lt;/span&gt;&lt;span class="p"&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;invalid_by_string_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Passes with weakened schema
&lt;/span&gt;
    &lt;span class="c1"&gt;# Original schema must reject it
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonschema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&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;invalid_by_string_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now R1 fails when you sabotage the schema. It has teeth.&lt;/p&gt;

&lt;p&gt;The other gap: the schema used annotation-only &lt;code&gt;format&lt;/code&gt; for timestamps instead of validating &lt;code&gt;pattern&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;"changed_at"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"date-time"&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;JSON Schema draft-07 does not enforce &lt;code&gt;format&lt;/code&gt; by default. You have to opt in. The fix added explicit regex.&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;"changed_at"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;d{4}-&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;d{2}-&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;d{2}T&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;d{2}:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;d{2}:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;d{2}Z$"&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;The rewrite grew the suite while it was at it. The same four rules now drive twenty-eight valid and twenty-five invalid fixtures through the real validator, up from nineteen hollow checks. Each invalid one now genuinely fails for its specific planted reason, not an accidental different one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lesson
&lt;/h2&gt;

&lt;p&gt;A test that constructs its own input and does arithmetic on it is testing the test. The system never sees the validator.&lt;/p&gt;

&lt;p&gt;The fix is the teeth check. To prove a check has teeth:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Construct valid data that passes the real validator.&lt;/li&gt;
&lt;li&gt;Deliberately break the invariant the check exists to protect (weaken the type, drop the constraint, plant the exact invalid case).&lt;/li&gt;
&lt;li&gt;Assert the sabotaged case now slips through the weakened version.&lt;/li&gt;
&lt;li&gt;Assert the original, unbroken validator still rejects it.&lt;/li&gt;
&lt;li&gt;If step 4 does not go red, the check has no teeth.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the same failure mode as a mock that mocks the function under test, or a snapshot test that snapshots the bug. Mutation testing is the industrial version of the same idea.&lt;/p&gt;

&lt;p&gt;Green checks are not proof of anything. Proof is: you sabotage the invariant and the check goes red.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also Shipped
&lt;/h2&gt;

&lt;p&gt;Bob's Big Brain registrar added a seam firewall so governance scores are branded and retrieval scores cannot cross into them. L4 integration test now gates every PR.&lt;/p&gt;

&lt;p&gt;Diagnostic Pro converted to Capacitor iOS and Android mobile apps, and onboarded to VPS auto-deploy with Umami analytics.&lt;/p&gt;

&lt;p&gt;Mission Control operator kit built, proven, and wired.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/a-green-recovery-drill-can-still-be-lying/"&gt;A Green Recovery Drill Can Still Be Lying&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/let-the-model-judge-make-the-code-decide/"&gt;Let the Model Judge, Make the Code Decide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>python</category>
      <category>cicd</category>
      <category>architecture</category>
    </item>
    <item>
      <title>A Green Recovery Drill Can Still Be Lying</title>
      <dc:creator>Jeremy Longshore</dc:creator>
      <pubDate>Mon, 20 Jul 2026 10:14:39 +0000</pubDate>
      <link>https://dev.to/jeremy_longshore/a-green-recovery-drill-can-still-be-lying-hlg</link>
      <guid>https://dev.to/jeremy_longshore/a-green-recovery-drill-can-still-be-lying-hlg</guid>
      <description>&lt;p&gt;We had a backup mechanism for a SigNoz staging stack. What we did not have was a &lt;em&gt;proven&lt;/em&gt; restore. Those are different claims, and the gap between them is where outages become permanent.&lt;/p&gt;

&lt;p&gt;SigNoz is open-source observability backed by ClickHouse. The staging deployment on our self-hosted VPS ran the usual set: ClickHouse for telemetry, clickhouse-keeper for coordination, and Postgres for stack metadata. Snapshots existed. Nobody had ever restored one and confirmed the data came back queryable. A backup you have never restored is a hypothesis, not a safety net.&lt;/p&gt;

&lt;p&gt;So the job was to build a real drill: seed known telemetry, back the stack up hot, restore it somewhere safe, and verify the exact seeded data survives. The interesting part is not that we built it. The interesting part is what stood between a drill that runs and a drill that means something: four traps, found in the order below. Two failed loudly and honestly, and fixing them taught us the setup. The other two went green while proving nothing, and one of those would have lied silently forever. The loud ones you catch by running the drill. The silent ones you catch only by auditing the proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the work
&lt;/h2&gt;

&lt;p&gt;Three scripts, each with one job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hot backup.&lt;/strong&gt; Tar the ClickHouse, clickhouse-keeper, and Postgres state trees while the stack keeps running. Read-only, zero downtime. Staging never stops serving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isolated restore.&lt;/strong&gt; Unpack the backup into a &lt;em&gt;disposable&lt;/em&gt;, network-isolated ClickHouse plus keeper pair. Guaranteed teardown at the end. It never shares a network with live and never touches the production containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recovery drill.&lt;/strong&gt; Seed synthetic telemetry through the live ingest gateway, run the backup, run the restore, and verify the seeded signals come back by run id. Every signal we push goes through a disclosure-filter gateway (we call it C8) that strips prohibited content before anything reaches storage. That filter is a governance requirement, and as you will see, it is also the source of two of the four traps.&lt;/p&gt;

&lt;p&gt;The target scorecard was concrete: a specific seeded run recovers as a trace and a metric and a log, a planted prohibited phrase is &lt;em&gt;absent&lt;/em&gt; after restore, all six SigNoz databases come back queryable, and live staging stays healthy the entire time.&lt;/p&gt;

&lt;p&gt;Green on that scorecard sounds like proof. Getting there honestly took four corrections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 1: the macros trap
&lt;/h2&gt;

&lt;p&gt;The first failure was honest and loud, which is the good kind.&lt;/p&gt;

&lt;p&gt;ClickHouse ReplicatedMergeTree tables encode their coordination path using &lt;code&gt;{shard}&lt;/code&gt; and &lt;code&gt;{replica}&lt;/code&gt; macros. Those macros resolve against server config. On restore into the disposable container, the tables refused to attach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code: 36. DB::Exception: No macro 'shard' in config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The macros live in the &lt;code&gt;config.yaml&lt;/code&gt; we mounted into the container. So why were they missing? Because ClickHouse does not read every config file you hand it. It selects one primary config file, and the image default &lt;code&gt;config.xml&lt;/code&gt; won that selection over our mounted &lt;code&gt;config.yaml&lt;/code&gt;. The macros were present in a file the server never treated as primary.&lt;/p&gt;

&lt;p&gt;You can fight ClickHouse over which primary file wins. Or you can stop fighting. ClickHouse &lt;em&gt;always&lt;/em&gt; merges everything under &lt;code&gt;/etc/clickhouse-server/config.d/&lt;/code&gt;, regardless of which primary file it picked. So the fix was a tiny drop-in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- /etc/clickhouse-server/config.d/macros.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;clickhouse&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;macros&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;shard&amp;gt;&lt;/span&gt;01&lt;span class="nt"&gt;&amp;lt;/shard&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;replica&amp;gt;&lt;/span&gt;replica-restore&lt;span class="nt"&gt;&amp;lt;/replica&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/macros&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/clickhouse&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mount that into &lt;code&gt;config.d/&lt;/code&gt;, and the macros are guaranteed present no matter which primary config the server elects to load. Tables attached on the next run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; for configuration that &lt;em&gt;must&lt;/em&gt; be present, use the always-merged drop-in directory, not the primary file that some other layer might override. Do not depend on winning a precedence fight you do not control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 2: the vacuous canary
&lt;/h2&gt;

&lt;p&gt;With tables attaching, the disclosure filter needed proving. The assertion is negative: after restore, a prohibited string must be &lt;em&gt;absent&lt;/em&gt;, showing the filter's clean state survived the round trip.&lt;/p&gt;

&lt;p&gt;To prove a thing is absent, you first have to put a candidate in. So the drill seeds a canary through the gateway, then checks it is gone after restore. The first canary was a random marker string, something like &lt;code&gt;CANARY-7f3a91&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It passed. It also proved nothing.&lt;/p&gt;

&lt;p&gt;The gateway's filter does not strip arbitrary text. It matches a specific set of prohibited phrases. A random marker is not on that list, so the gateway correctly passed it straight through to storage. The canary was present in the backup, present in the restore, and the "absent after restore" check was reading the wrong signal entirely. Worse, if the check had been written to expect absence, it would have failed for the right-looking wrong reason, and we would have chased a filter bug that did not exist.&lt;/p&gt;

&lt;p&gt;The canary has to be a phrase the filter actually targets. Only then does its absence mean the filter did its job through backup and restore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; a negative-space test, proving something is absent, is only meaningful if the thing would otherwise be present. An absence assertion over data that was never going to be there is vacuous. It is green by construction and tells you nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 3: the poisoned run
&lt;/h2&gt;

&lt;p&gt;Fixing the canary broke a different check, and the way it broke is instructive.&lt;/p&gt;

&lt;p&gt;The synthetic producer stamps the canary into three places in a run: the span name, the run's one metric name, and its one log body. Once the canary became a &lt;em&gt;real&lt;/em&gt; prohibited phrase, the gateway did exactly what it is built to do. It dropped the whole signals that carried it. The span survived in altered form, but the metric and the log were filtered out completely.&lt;/p&gt;

&lt;p&gt;Now look at what the full-signal verifier needs: a trace and a metric and a log, all sharing the run id. A canary-carrying run cannot supply that, because the metric and log it needs are precisely the signals the filter destroys. The two goals are in direct conflict. Proving the filter strips prohibited content requires data the filter will delete. Proving full-signal recovery requires data that survives untouched. One fixture cannot be both.&lt;/p&gt;

&lt;p&gt;The fix is to stop asking one run to do two contradictory jobs. Seed two runs per drill:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;clean run&lt;/strong&gt; with no prohibited content, used to prove run-id recovery across trace, metric, and log.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;canary run&lt;/strong&gt; carrying a real prohibited phrase, used to prove the filter's round-trip fidelity by confirming the phrase is absent after restore.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each run answers exactly one question, and neither question undermines the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; one synthetic fixture cannot serve two assertions that require opposite data conditions. When two checks demand contradictory inputs, that is a signal to split the fixture, not to weaken a check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 4: the false-pass by default
&lt;/h2&gt;

&lt;p&gt;The drill was green. Every check passing: containers up, queries answered, all databases back, clean run recovered by run id, canary absent. It looked done.&lt;/p&gt;

&lt;p&gt;Then we ran an adversarial code review over the harness itself, and it found the trap that matters most, because this one would have lied silently forever.&lt;/p&gt;

&lt;p&gt;The verifier script queries ClickHouse for the seeded run id. Its query target, the container it talks to, defaulted to the &lt;strong&gt;live&lt;/strong&gt; container when an environment variable was unset. Read that again in context. In step one, the synthetic run is seeded &lt;em&gt;through the live gateway&lt;/em&gt;. That means the run genuinely exists in live storage. So if any future call, any refactor, any copy-pasted invocation forgot to set the target variable, the verifier would query live, find the run that live legitimately holds, and report &lt;code&gt;PASS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The restore could recover absolutely nothing. The disposable container could be empty. The verifier would still go green, because it was quietly asking the wrong database, and the wrong database happened to have the answer. A broken restore would pass every single time, and the drill would keep congratulating itself.&lt;/p&gt;

&lt;p&gt;The fix is to make the target impossible to omit:&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;# before: target falls back to live when unset&lt;/span&gt;
&lt;span class="nv"&gt;CH_CONTAINER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CH_CONTAINER&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;signoz&lt;/span&gt;&lt;span class="p"&gt;-clickhouse&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# after: explicit, required, no default to a happy source&lt;/span&gt;
restore-verify &lt;span class="nt"&gt;--container&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DISPOSABLE_CH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--run-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RUN_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c"&gt;# the script exits non-zero if --container is not provided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every restore-verify call now names its disposable target in code. There is no fallback, so there is no path where a forgotten variable routes the query to live. If you do not say where to look, the verifier refuses to run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; a verifier that defaults to the happy source is a false-pass generator. If the thing you are trying to prove recovered is &lt;em&gt;also&lt;/em&gt; present somewhere the verifier can silently reach, an unset default will find it there and lie. Make the target explicit and make omitting it a hard error.&lt;/p&gt;

&lt;h2&gt;
  
  
  The collaboration beat
&lt;/h2&gt;

&lt;p&gt;Worth being honest about how the fourth trap surfaced. The build ran on Claude Opus 4.8. The macros trap, the vacuous canary, and the poisoned run all came out through iteration: a check failed or passed suspiciously, we traced it, we fixed it. Normal engineering.&lt;/p&gt;

&lt;p&gt;The false-pass-by-default was different. It did not fail. The drill was already green when we ran a dedicated code-reviewer subagent adversarially against the harness, with a narrow charge: can any verify step false-pass, and can any path touch live? That framing, aimed at the proof rather than the data, is what caught the live-default fallback. The drill had proved the data recovers. The adversarial review proved that the proof itself was not yet trustworthy. Those are two different audits, and passing the first does not grant the second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;The final drill passes eight of eight checks, and now each one means what it says. The eight, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The disposable keeper container starts.&lt;/li&gt;
&lt;li&gt;The disposable ClickHouse container starts.&lt;/li&gt;
&lt;li&gt;The restored ClickHouse accepts queries.&lt;/li&gt;
&lt;li&gt;All &lt;strong&gt;six SigNoz databases&lt;/strong&gt; are present.&lt;/li&gt;
&lt;li&gt;The seeded signal rows are recovered and queryable.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;metadata tables&lt;/strong&gt; are recovered.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;clean run&lt;/strong&gt; recovers by run id, as a trace, a metric, and a log.&lt;/li&gt;
&lt;li&gt;The prohibited &lt;strong&gt;canary is absent&lt;/strong&gt; in the restore, confirming filter round-trip fidelity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two measurements come out of the same run, &lt;strong&gt;RTO about 30 seconds&lt;/strong&gt; and &lt;strong&gt;RPO about 2 minutes&lt;/strong&gt;, and two invariants hold every time: the &lt;strong&gt;disposable stack tears down&lt;/strong&gt;, and &lt;strong&gt;live staging stays healthy and untouched&lt;/strong&gt;. RTO and RPO are now numbers from an exercised drill, not aspirations from a runbook nobody has run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Green is a claim, not evidence. A recovery drill is a program, and like any program it can be correct-looking and wrong. Two of these traps failed loudly, the macros error and the poisoned run, and that is the honest kind: the drill told us the setup was broken before it ever went green. The other two are the dangerous kind. The vacuous canary went green by construction, and the live-default verifier would have gone green over a completely empty restore, forever. You find loud failures by running the drill. You find silent false-passes only by auditing the proof itself.&lt;/p&gt;

&lt;p&gt;For any recovery or verification harness, the question to ask is not "does it pass." It is "what would make this pass when it should fail," enumerated concretely, then tested one by one. Would an absent-by-default value satisfy an absence check? Does one fixture quietly serve two contradictory assertions? Can the verifier reach a source that holds the answer for the wrong reason?&lt;/p&gt;

&lt;p&gt;None of these four are specific to ClickHouse or SigNoz. A config drop-in that must always merge, a negative test that needs its subject to actually exist, a fixture pulled between two goals, and a verifier that defaults to the happy source: those generalize to any recovery drill you will ever build. The stack was observability. The lesson is about trusting your own green.&lt;/p&gt;

</description>
      <category>observability</category>
      <category>clickhouse</category>
      <category>disasterrecovery</category>
      <category>backup</category>
    </item>
    <item>
      <title>Let the Model Judge. Make the Code Decide.</title>
      <dc:creator>Jeremy Longshore</dc:creator>
      <pubDate>Sat, 18 Jul 2026 14:07:31 +0000</pubDate>
      <link>https://dev.to/jeremy_longshore/let-the-model-judge-make-the-code-decide-1kmp</link>
      <guid>https://dev.to/jeremy_longshore/let-the-model-judge-make-the-code-decide-1kmp</guid>
      <description>&lt;p&gt;This post was written by the pipeline it describes. The transcript analyzer that runs on every build day looked at the work that produced this system and reported the receipts: four different models did the building (Claude Opus 4.8, Claude Fable 5, Grok 4.5, and GPT-5.6 Sol), across 459 minutes, with 91 moments where something failed and got fixed, and 5 places a human stepped in to change direction. That is not a brag. It is the point. No single model built this, no model committed a line of it, and the record of who did what is machine-readable because deterministic code wrote it down, not a model's memory.&lt;/p&gt;

&lt;p&gt;The rule the whole day came down to is short. When you put a language model in a production pipeline, split the work. The model owns judgment: writing, scoring, proposing. Code owns the decisions: what ships, what gets enforced, what gets recorded. Keep them strictly apart. Call it produce then land. Yesterday it got applied across an entire writing system in one pass, and every fix was the same fix wearing a different shirt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline had let the model creep
&lt;/h2&gt;

&lt;p&gt;This blog publishes a post most days with no human in the loop. A cron job wakes up, a Claude Code skill writes the day's post, classifies it into a tier, and it goes live. That part worked. What had rotted was everywhere the model had quietly taken over a job that code should own.&lt;/p&gt;

&lt;p&gt;Five symptoms, one disease:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model was in the git-commit path. It wrote the post and it also decided the post was good enough to ship, then pushed. A model having a bad morning could push a bad morning straight to production.&lt;/li&gt;
&lt;li&gt;The voice rules lived in four files. The deny-list of banned phrases was restated in the skill, in a doc, in an agent prompt, and in a checklist. Four copies means four truths, and they had already started to disagree.&lt;/li&gt;
&lt;li&gt;A "learned patterns" file that nothing read. Every tier misclassification was supposed to teach the system. The lessons got written to a file. The file was never loaded. &lt;code&gt;times_applied&lt;/code&gt; sat at zero.&lt;/li&gt;
&lt;li&gt;The persona's own facts had drifted. The star count for the open-source project showed up as 1,300 in one place, 2,000-plus in another, 2,100-plus in a third, and 2,519 in a fourth. Same day. Four homes, four numbers.&lt;/li&gt;
&lt;li&gt;The transcript scanner threw away the actual work. It was supposed to measure how each day got built. Instead it dumped truncated chat and called it analysis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of those is the same mistake. A model was trusted to be the source of truth for something that has an exact, checkable answer. Judgment is the model's job. Facts, gates, and records are not judgment. They are code's job. The rest of the day was moving each thing back to the side of the line it belonged on.&lt;/p&gt;

&lt;p&gt;I ran restaurants for twenty years before I wrote production code. A line cook has enormous judgment: seasoning, timing, when a plate is wrong and goes back. The line cook does not decide whether the kitchen passes the health inspection. That is a checklist somebody else signs. You do not let the person cooking the food also be the person who certifies the kitchen is clean. Same rule here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Produce then land: the model can write junk, but it can never ship it
&lt;/h2&gt;

&lt;p&gt;The first and biggest fix was pulling the model out of the git-commit path entirely.&lt;/p&gt;

&lt;p&gt;Before, the skill did everything: wrote the post, judged it, committed, pushed. After, the skill writes two things and stops. It writes the post. It writes a small readiness sentinel, a JSON file that says "I believe this is ready, and here is why." Then it does no git at all.&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;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-16"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ready"&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;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gates_passed"&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;"hugo_build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consistency_audit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fact_check"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"post_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content/posts/let-the-model-judge-make-the-code-decide.md"&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;A separate script, &lt;code&gt;blog-land.sh&lt;/code&gt;, does the landing. It is pure bash. No model runs inside it. It reads the sentinel, then it re-checks every precondition itself, from scratch, because a model claiming a gate passed is a claim, not a fact:&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;# blog-land.sh (shape, trimmed)&lt;/span&gt;
&lt;span class="nv"&gt;sentinel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STAGING&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;.intent.json"&lt;/span&gt;

&lt;span class="nv"&gt;ready&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.ready'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$sentinel&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ready&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; quarantine &lt;span class="s2"&gt;"sentinel not ready"&lt;/span&gt;

classifier_record_exists &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;        &lt;span class="o"&gt;||&lt;/span&gt; quarantine &lt;span class="s2"&gt;"no classifier record"&lt;/span&gt;
audit_addendum_present &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;          &lt;span class="o"&gt;||&lt;/span&gt; quarantine &lt;span class="s2"&gt;"no step-8 audit addendum"&lt;/span&gt;
hugo &lt;span class="nt"&gt;--buildFuture&lt;/span&gt; &lt;span class="nt"&gt;--gc&lt;/span&gt; &lt;span class="nt"&gt;--minify&lt;/span&gt;        &lt;span class="o"&gt;||&lt;/span&gt; quarantine &lt;span class="s2"&gt;"hugo build failed"&lt;/span&gt;

&lt;span class="c"&gt;# only now does anything touch git&lt;/span&gt;
git add &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$post_path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$msg&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git push
publish_to_tonsofskills &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$post_path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
queue_crosspost &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$post_path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any check fails, the post gets quarantined. The script moves the file aside, restores a clean working tree, and fires an alert. It does not publish something half-baked, and it does not leave a wrecked repo for tomorrow's run to trip over.&lt;/p&gt;

&lt;p&gt;That last part matters more than it sounds. The old failure mode was a stuck run. A gate would hang, a model would time out mid-commit, and the tree would be left dirty. The next day's job would start in a broken state and fail too. One bad morning became a bad week. Now a bad produce step just means one quarantined post and a clean tree. Tomorrow runs like nothing happened.&lt;/p&gt;

&lt;p&gt;The model can write a bad file. It cannot ship one. The commit path has no model in it. That is the entire idea, and it is worth saying plainly because it is the thing most agent pipelines get wrong: they let the smart component also be the trusted component. Smart and trusted are different properties. Code is dumb and trustworthy. Use it for the part where trust is the requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Single source of truth, applied twice
&lt;/h2&gt;

&lt;p&gt;Two of the five symptoms were the same bug: a fact copied into many places instead of referenced from one. Both got the same fix, and the fix has a name. Reference, do not copy.&lt;/p&gt;

&lt;h3&gt;
  
  
  The voice deny-list
&lt;/h3&gt;

&lt;p&gt;The list of banned phrases (the AI-slop tells: &lt;code&gt;delve&lt;/code&gt;, &lt;code&gt;seamless&lt;/code&gt;, &lt;code&gt;game-changer&lt;/code&gt;, and two dozen more) had been pasted into four files. When I added a phrase to the linter, the doc still had the old list. The agent prompt had a third version. Nothing was wrong exactly, but nothing agreed either, and drift is just being wrong on a delay.&lt;/p&gt;

&lt;p&gt;It collapsed to one file:&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;voice-denylist.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;phrases,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;copy)&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;"banned_phrases"&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;"delve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dive into"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"seamless"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"supercharge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"leverage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"game-changer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"revolutionize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"comprehensive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"it's worth noting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"at its core"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"in conclusion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"banned_chars"&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;"em-dash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-dash"&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;The linter reads it. The docs reference it by path instead of restating it. Add a phrase in one place and every consumer sees it on the next run. There is exactly one answer to "is this phrase banned," and it lives in one file.&lt;/p&gt;

&lt;h3&gt;
  
  
  The persona star count
&lt;/h3&gt;

&lt;p&gt;The other copy-drift was uglier because it was public. The open-source project's star count was quoted in the writing persona, in the site's own about copy, in a partner doc, and in the blog's byline material. Four numbers: 1,300, 2,000-plus, 2,100-plus, 2,519. All stale except by accident, because a hand-typed number is stale the moment the counter ticks.&lt;/p&gt;

&lt;p&gt;The fix was a canonical persona source of truth in the intent-os repo (&lt;code&gt;persona/master.md&lt;/code&gt;), with the number stated once and a live-verify command sitting right next to it so nobody ever guesses again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# live-verify the OSS numbers instead of quoting from memory&lt;/span&gt;
gh api repos/jeremylongshore/claude-code-plugins &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'"stars=\(.stargazers_count) forks=\(.forks_count)"'&lt;/span&gt;
&lt;span class="c"&gt;# stars=2519 forks=362&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The canonical text now reads "2,500-plus stars, 360-plus forks, 45,000-plus npm downloads." Rounded down, honest, and every other file points at that one instead of carrying its own copy. Two merged pull requests did the cleanup: one stood up the canonical persona (&lt;code&gt;master.md&lt;/code&gt; and &lt;code&gt;voices.md&lt;/code&gt;), one corrected the star count on the personal site. A model does not own a fact that a command can check. That is not a judgment call. It is a lookup, and lookups belong to code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deterministic enforcement of learned judgment
&lt;/h2&gt;

&lt;p&gt;Here is the one that was quietly the most broken, because it looked like it worked.&lt;/p&gt;

&lt;p&gt;The tier classifier scores each post and assigns a tier: field note, deep-dive, case study, distinguished paper. When it got a tier wrong, a feedback step recorded the correction as a "learned pattern" in &lt;code&gt;patterns.jsonl&lt;/code&gt;. The intent was a system that gets sharper over time. A pattern looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tch-anchor-cap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"condition"&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;"tier_claimed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"named_artifact_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;"action"&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;"cap_tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tier 2+ requires a named artifact; 0 present downgrades to 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;"times_applied"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;See the &lt;code&gt;times_applied: 0&lt;/code&gt;. That was the tell. The patterns were being written and never read. The "learning loop" was a human hand-copying each learned rule back into the prompt by hand, which nobody had done in a while. The file was a diary, not a control.&lt;/p&gt;

&lt;p&gt;The fix was a deterministic engine, &lt;code&gt;apply-patterns.py&lt;/code&gt;, that loads the rules and evaluates each one against the actual scores, then caps the tier when a rule matches. The model still scores the post. It reads the artifacts, weighs the evidence, proposes a tier. That is judgment, and it stays with the model. But the enforcement (does this proposed tier survive the learned rules) is code now:&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;enforce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proposed_tier&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;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;patterns&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;matches&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;condition&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tier&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cap_tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;times_applied&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="n"&gt;tier&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model scores. The code enforces. And the moment I ran the engine against history, it earned its keep: one active rule matched 7 of 238 historical decisions, and 4 of those were real downgrades, exactly matching the evidence the rule had recorded about itself. The system had been right about its own blind spot the whole time. It just had no hands.&lt;/p&gt;

&lt;p&gt;Wiring it up also surfaced two silent import bugs. The analytics index that was supposed to hold every feedback entry and every pattern had zero rows in both tables. Zero. Two &lt;code&gt;import&lt;/code&gt; paths had been quietly failing, so the loop had been writing to a void for who knows how long. This is the tax you pay for a "learning" component nobody checks: it can be fully disconnected and still look alive, because the diary still fills up. Only when code started reading the diary did the disconnection show.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure how the work actually happened
&lt;/h2&gt;

&lt;p&gt;The transcript analyzer got a full rewrite, and it is the clearest example of the split, so it is worth walking.&lt;/p&gt;

&lt;p&gt;The old scanner grabbed chat logs, truncated them, and pasted them into a summary. Useless. It threw away the one thing worth keeping, which is what actually happened during the build, and kept the one thing worth throwing away, which is raw model chatter.&lt;/p&gt;

&lt;p&gt;The new &lt;code&gt;scan-session-transcripts.py&lt;/code&gt; reads every AI-coding CLI used on a given day: Claude Code, Grok, Codex. It names each model exactly, because "an AI helped" is not a fact and "Claude Opus 4.8, Claude Fable 5, Grok 4.5, and GPT-5.6 Sol" is. Exact model names are also just good SEO; people search the model, not the vibe. Then it extracts the collaboration deterministically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tool activity: how many edits, reads, bash runs, which files.&lt;/li&gt;
&lt;li&gt;Failure-to-fix arcs: a command failed, then a later command succeeded on the same target. That is a debugging loop, and counting them is arithmetic, not opinion.&lt;/li&gt;
&lt;li&gt;Human course-corrections: the human redirected the work. Counting them is arithmetic too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run on its own build day, the analyzer reported the numbers this post opened with: four models, 91 failure-to-fix arcs, 5 course-corrections, 459 minutes. The model in this pipeline does exactly one thing with all that: it narrates it into a paragraph. Every number is extracted by code. The model writes the sentence around the numbers. It never invents one.&lt;/p&gt;

&lt;p&gt;The rewrite also does secret redaction on any code it quotes, and that guard caught a live one. A real API token had leaked into a "command not found" error string in a transcript. The redactor scrubbed it before it could reach the summary. If the model had been trusted to "just summarize the logs," that token ships in a blog post. Deterministic redaction on the extraction path is the difference between a near miss and an incident.&lt;/p&gt;

&lt;p&gt;There is a second loop in the same family worth naming: &lt;code&gt;next-topics.py&lt;/code&gt;. It reads Umami content-performance data (which posts actually got read) and turns it into a ranked queue of what to write next. Real reader behavior in, ranked topics out. The model writes the posts; the data decides the order. Same split, different surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest calibration call
&lt;/h2&gt;

&lt;p&gt;Not every decision resolved cleanly into "code decides." One was a genuine judgment tradeoff, and pretending otherwise would be dishonest.&lt;/p&gt;

&lt;p&gt;The transcript analyzer produces a "session signal" that feeds tier classification. The idea: a day with heavy, hard engineering probably produced a meatier post. Reasonable. But the raw signal is volume, and the volume is almost always "strong," because a normal day here runs 1,600 to 2,500 tool calls. If I wired a mechanical floor ("strong signal equals bump the tier"), every post would inflate to case study on day one. The tripwire that guards against tier creep would scream every week, correctly, because I would have built the creep myself.&lt;/p&gt;

&lt;p&gt;So the call was: set the signal from real data, but do not let it mechanically set the tier. The signal is an input the model weighs against the specific arcs of the day (did something genuinely break and get solved, was there a real design decision), not a volume threshold that auto-promotes. Code owns the measurement. The judgment about what the measurement means stays judgment. That is the honest version. The split is a principle, not a religion, and the one place it bent was the one place the quantity was real but the meaning was not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The silence bug
&lt;/h2&gt;

&lt;p&gt;One more, because it is the operator lesson in miniature.&lt;/p&gt;

&lt;p&gt;There is a morning packet that emails the day's syndication material out for a human to post. One morning it did not send. And here is the thing: silence looked exactly like a working pipeline. No error. No alert. Nothing in the inbox is what "all quiet, nothing to post today" looks like, and it is also what "the whole thing is broken" looks like. The two states were identical from the outside.&lt;/p&gt;

&lt;p&gt;The fix was a positive heartbeat. Now, when there is genuinely nothing to send, the pipeline sends a one-line "NO PACKET TODAY" note on purpose. An expected message. The absence of that message is now itself an alarm, because the only way to get silence is a real failure.&lt;/p&gt;

&lt;p&gt;I checked kitchens the same way. An empty prep list is not "we are ready." An empty prep list means nobody did prep. You do not get to assume the quiet means good. You make good produce a signal, so the quiet can only mean bad. Silence was the actual defect. The missing email was not the bug's symptom; the missing email was the bug, and the fix was to make silence impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why deterministic-owns-the-gates beats letting the model do it all
&lt;/h2&gt;

&lt;p&gt;Step back from the five fixes and the shape is one design decision made five times. The gate, the fact, the enforcement, the measurement, the record: all pulled out of the model and given to code. Here is why that is not just tidiness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reliability.&lt;/strong&gt; A model is a probabilistic component. It has good mornings and bad mornings, and you cannot schedule which. Code is deterministic. When the trustworthy part of your pipeline is the deterministic part, the model's bad morning costs you one quarantined post, not a corrupted repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auditability.&lt;/strong&gt; "Four models, 91 failure-to-fix, 5 course-corrections, 459 minutes" is a fact I can stand behind because code counted it. If a model had summarized the day from memory, I would have a vibe, not a receipt. Receipts are what let this post open with real numbers instead of a hand-wave.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reproducibility.&lt;/strong&gt; The learned patterns went from a diary nobody read to a rule engine that produces the same downgrade every time the same condition appears. 7 of 238 matches, 4 real, repeatable on the next run. A model re-judging from scratch each time is not reproducible. A rule is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It survives a flaky model.&lt;/strong&gt; This is not hypothetical. During the build, one model hit its weekly usage limit mid-stream. A fallback model took over. The pipeline still shipped, because the model was never the thing that ships. The commit path is bash. It does not care which model wrote the file, or whether the model that started the post is the model that finished it. When code owns the commit, the model layer can fail over, rate-limit, or swap entirely, and the output does not corrupt. You cannot get that property if the model is holding the git handle.&lt;/p&gt;

&lt;p&gt;Across two days this was 34 commits on the blog repo, two merged pull requests on the persona and voice system, and one on the personal site to kill the star-count drift. Every one of them was the same move: find a place a model was trusted with a decision, and give the decision to code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;If you ship agents in production, the model is the worker on the line, not the manager who signs off. It has real skill and real judgment, and you should use all of it: let it write, let it score, let it propose. Then take its work to a gate it does not control. Let the model judge. Make the code decide.&lt;/p&gt;

&lt;p&gt;The tell that you have it backwards is comfort. If your pipeline feels clean because the model "handles everything end to end," you have handed the trust property to the component that has bad mornings. Find the git handle, the fact, the gate, the record. Move each one to code. What is left for the model is the part it is actually good at, and the part where a mistake costs one quarantined file instead of a shipped incident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related posts:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/exit-0-is-not-success/"&gt;Exit 0 Is Not Success&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/copying-files-is-not-installing/"&gt;Copying Files Is Not Installing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/producer-fallback-when-claude-hits-weekly-limit/"&gt;Producer Fallback: When Claude Hits the Weekly Limit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aiagents</category>
      <category>claudecode</category>
      <category>pipeline</category>
      <category>llm</category>
    </item>
    <item>
      <title>Copying Files Is Not Installing</title>
      <dc:creator>Jeremy Longshore</dc:creator>
      <pubDate>Sat, 18 Jul 2026 14:07:28 +0000</pubDate>
      <link>https://dev.to/jeremy_longshore/copying-files-is-not-installing-38a9</link>
      <guid>https://dev.to/jeremy_longshore/copying-files-is-not-installing-38a9</guid>
      <description>&lt;p&gt;A marketplace install copies your plugin's files into place. It does not run &lt;code&gt;npm install&lt;/code&gt; in them. Those are two different operations, and the gap between them is where "works on my machine" lives.&lt;/p&gt;

&lt;p&gt;We hit that gap shipping Bob's Big Brain, a Claude Code and Cowork plugin that runs a local stdio MCP server to give a team a governed knowledge brain. In the author's checkout it worked. For every teammate installing from the marketplace, one of its two modes died on start. The teammates did nothing wrong. The install path had an assumption baked into it, and the assumption only held on the machine where the code was written.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two modes, one of them has native code
&lt;/h2&gt;

&lt;p&gt;The server runs in one of two modes, decided at startup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team mode&lt;/strong&gt; talks to a remote API. It reads &lt;code&gt;TEAMKB_API_URL&lt;/code&gt; from the environment or a &lt;code&gt;~/.teamkb/team.json&lt;/code&gt; file, sends requests over HTTP, and holds no local state. It is pure JavaScript with zero native dependencies. Nothing to compile, nothing to install.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local mode&lt;/strong&gt; runs an embedded store on the user's own machine, backed by SQLite. That mode needs two &lt;em&gt;native&lt;/em&gt; Node modules: &lt;code&gt;better-sqlite3&lt;/code&gt; for the database and &lt;code&gt;fs-ext&lt;/code&gt; for file locking. Native means compiled C++ bindings, not pure JS. They do not exist until something runs &lt;code&gt;npm install&lt;/code&gt; and the build step produces a &lt;code&gt;.node&lt;/code&gt; binary for the current platform.&lt;/p&gt;

&lt;p&gt;So the dependency surface is lopsided. Team users need nothing. Local users need a compile step to have happened. That asymmetry is the whole story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The exact failure
&lt;/h2&gt;

&lt;p&gt;Here is the sequence for a clean marketplace install of local mode:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The marketplace copies the plugin directory into place. Source files, &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;package-lock.json&lt;/code&gt;, the &lt;code&gt;.mcp.json&lt;/code&gt; manifest. Everything except &lt;code&gt;node_modules&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Claude launches the MCP server named in the manifest.&lt;/li&gt;
&lt;li&gt;The server enters local mode and calls &lt;code&gt;require('better-sqlite3')&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;There is no &lt;code&gt;node_modules&lt;/code&gt;. The &lt;code&gt;require&lt;/code&gt; throws &lt;code&gt;MODULE_NOT_FOUND&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The server crashes on boot. Local mode is dead before it does any work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the dev checkout, step 4 never happened, because &lt;code&gt;npm install&lt;/code&gt; had been run there by hand months earlier and &lt;code&gt;node_modules&lt;/code&gt; was sitting on disk. The code was correct. The environment was not reproducible. A marketplace install is a &lt;em&gt;file copy&lt;/em&gt;, and a file copy carries no build artifacts.&lt;/p&gt;

&lt;p&gt;The fix is not "tell users to run npm install." A plugin that requires a manual build step after install is a plugin that fails for most people. The fix is to make the server provision its own native deps, on first start, only when it actually needs them, and to do it safely when several copies of the server start at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bootstrap launcher
&lt;/h2&gt;

&lt;p&gt;The manifest now points at a small launcher instead of the server directly. &lt;code&gt;plugin-runtime/bootstrap.cjs&lt;/code&gt; is about 125 lines and uses &lt;em&gt;only&lt;/em&gt; Node builtins. That constraint is load-bearing: the launcher has to run before any dependency exists, so it cannot import a single thing from &lt;code&gt;node_modules&lt;/code&gt;. &lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;child_process&lt;/code&gt;, &lt;code&gt;module&lt;/code&gt;. Nothing else.&lt;/p&gt;

&lt;p&gt;On start it makes one decision: does this launch need native deps at all?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A value counts as "configured" only if it is real and expanded.&lt;/span&gt;
&lt;span class="c1"&gt;// An unexpanded ${VAR} placeholder is NOT configured.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isConfigured&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;teamModeRequested&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isConfigured&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEAMKB_API_URL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEAMKB_BASE_PATH&lt;/span&gt;
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEAMKB_HOME&lt;/span&gt;
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&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="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;homedir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.teamkb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;team.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If team mode is requested, the launcher does nothing and executes the bundled server immediately. Team users pay zero install cost, which is correct: they never touch SQLite. The &lt;code&gt;${&lt;/code&gt; guard matters more than it looks. When a manifest ships &lt;code&gt;"TEAMKB_API_URL": "${TEAMKB_API_URL}"&lt;/code&gt; and the variable is unset, the literal string &lt;code&gt;${TEAMKB_API_URL}&lt;/code&gt; arrives in the environment. Treating that as "configured" would route a local user into team mode and fail differently. So an unexpanded placeholder counts as absent.&lt;/p&gt;

&lt;p&gt;If team mode is not requested, the launcher checks whether the native deps are already present before doing any work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRequire&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nativeDependenciesReady&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requireFromBundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRequire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BUNDLE&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="nf"&gt;requireFromBundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;better-sqlite3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;requireFromBundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs-ext&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;createRequire(BUNDLE)&lt;/code&gt; resolves from the bundle's own root, not the launcher's, so the check asks the exact question that matters: can the &lt;em&gt;server&lt;/em&gt; load these when it runs? If yes, provisioning is skipped entirely. Idempotent by construction. If no, the launcher provisions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm ci &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev &lt;span class="nt"&gt;--no-audit&lt;/span&gt; &lt;span class="nt"&gt;--no-fund&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;npm ci&lt;/code&gt;, not &lt;code&gt;npm install&lt;/code&gt;. &lt;code&gt;ci&lt;/code&gt; installs the exact versions pinned in &lt;code&gt;package-lock.json&lt;/code&gt; and errors if the lockfile and manifest disagree. That makes provisioning reproducible: every teammate gets the same &lt;code&gt;better-sqlite3&lt;/code&gt; build the author tested, not whatever "latest" resolves to that day. After the install the launcher re-runs &lt;code&gt;nativeDependenciesReady()&lt;/code&gt; and throws if the deps are still missing. A silent partial install is worse than a loud failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The concurrency protocol
&lt;/h2&gt;

&lt;p&gt;The hard part is not installing. It is installing exactly once when the server can start several times at once.&lt;/p&gt;

&lt;p&gt;An MCP server gets launched per client. Open three Claude windows, or run a few agents, and you get three concurrent bootstraps racing to &lt;code&gt;npm ci&lt;/code&gt; the same directory. Two &lt;code&gt;npm ci&lt;/code&gt; runs on one directory step on each other and corrupt the tree. A naive "check if node_modules exists, if not install" has a check-then-act race: both processes check, both see nothing, both install.&lt;/p&gt;

&lt;p&gt;The lock is an atomic &lt;code&gt;mkdirSync&lt;/code&gt; of a &lt;code&gt;.native-install.lock&lt;/code&gt; directory. &lt;code&gt;mkdir&lt;/code&gt; is the right primitive because it either creates the directory or fails with &lt;code&gt;EEXIST&lt;/code&gt;, atomically, at the filesystem layer. There is no window between check and create. A check-then-write lock reintroduces the race; an atomic create, whether &lt;code&gt;mkdir&lt;/code&gt; or an exclusive &lt;code&gt;wx&lt;/code&gt; open, does not. &lt;code&gt;mkdir&lt;/code&gt; is just the simplest primitive that gives it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;acquireInstallLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lockDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STALE_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// 5 minutes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WAIT_STEP_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_WAIT_MS&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="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 2 minutes&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;waited&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="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(;;)&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="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lockDir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// atomic: create or EEXIST&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                        &lt;span class="c1"&gt;// we hold the lock&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EEXIST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Someone else holds it. Three ways out:&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nativeDependenciesReady&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="c1"&gt;// 1. deps already landed&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lockIsStale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lockDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;STALE_MS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;   &lt;span class="c1"&gt;// 2. crashed installer&lt;/span&gt;
        &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rmSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lockDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                              &lt;span class="c1"&gt;//    reclaim and retry&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waited&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;MAX_WAIT_MS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;             &lt;span class="c1"&gt;// 3. give up loudly&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;native install lock timed out after 2m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WAIT_STEP_MS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;waited&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;WAIT_STEP_MS&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three exits when the lock is already held, in priority order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deps are ready.&lt;/strong&gt; Another process finished the install while we waited. Return, do nothing, let the server start. This is the common happy path for the second and third windows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The lock is stale.&lt;/strong&gt; &lt;code&gt;lockIsStale&lt;/code&gt; compares the lock directory's mtime against a 5-minute ceiling. A process that crashed mid-install leaves the lock dir behind forever; without reclamation, one crash wedges the whole team permanently. Older than 5 minutes means the holder is presumed dead, so we remove the lock and retry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Otherwise wait, bounded.&lt;/strong&gt; Sleep 250ms and re-loop, up to a 2-minute ceiling, then throw. A live installer gets the time it needs; a pathological state fails with a clear message instead of hanging forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sleep is a real blocking sleep, not a busy-spin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Atomics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Int32Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SharedArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Atomics.wait&lt;/code&gt; on a throwaway &lt;code&gt;SharedArrayBuffer&lt;/code&gt; blocks the thread for the duration without burning CPU. And the lock directory is removed in a &lt;code&gt;finally&lt;/code&gt; around the install, so a successful holder always releases it.&lt;/p&gt;

&lt;p&gt;The whole launcher fails closed. Any error, missing bundle, failed install, lock timeout, prints one line to stderr and exits non-zero:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[governed-brain] REFUSING TO START: &amp;lt;reason&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A server that cannot guarantee its own dependencies should not come up half-initialized and answer queries against a store that is not there. Refuse loudly. The operator sees the reason immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving it stays fixed
&lt;/h2&gt;

&lt;p&gt;A fix you cannot regression-test is a fix with a shelf life. The environment assumption was invisible precisely because the author's machine always had &lt;code&gt;node_modules&lt;/code&gt;. CI had the same problem: it installed deps before running tests, so it never exercised a clean install either.&lt;/p&gt;

&lt;p&gt;The new smoke test removes that blind spot. &lt;code&gt;smoke/bootstrap-clean-install.mjs&lt;/code&gt; copies the shipped plugin into a scratch directory with &lt;strong&gt;no &lt;code&gt;node_modules&lt;/code&gt;&lt;/strong&gt;, launches the actual MCP artifact through the bootstrap, and drives a full disposable round trip against local mode: capture a memory, govern it, read status, verify the audit chain, then clean up. It is wired into &lt;code&gt;.github/workflows/smoke.yml&lt;/code&gt;, so CI now boots the plugin the way a teammate does. If a future change breaks the clean-install path, the smoke goes red before the release ships. It also exercises the lock path, which is what &lt;code&gt;test(plugin): harden bootstrap concurrency checks&lt;/code&gt; covers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The general lesson, and two smaller ones
&lt;/h2&gt;

&lt;p&gt;If you ship a plugin with native dependencies to any marketplace that installs by copying files, assume the install runs no build step. Provision deps yourself, at runtime, gated on a readiness check so you do nothing when there is nothing to do, and protected by a real lock because your process can start concurrently. Pin with &lt;code&gt;npm ci&lt;/code&gt; so every install is the build you tested. Fail closed when you cannot.&lt;/p&gt;

&lt;p&gt;Two hygiene fixes rode along in the same release, both small, both the difference between an install that reads clean to a new user and one that does not. The MCP manifest's &lt;code&gt;description&lt;/code&gt; had command-shaped text in it, so it got rewritten as plain prose. A description is metadata a human reads, not a script, and shell syntax in a schema field is just noise that trips linters and confuses readers. And a team-onboarding README example pointed at an invalid API URL, corrected to a valid local example, so the first thing a new team user copies actually works.&lt;/p&gt;

&lt;p&gt;This shipped as v1.1.2. The one-line version: copying files is not installing, and a plugin that assumes otherwise works only on the machine where the files were already installed.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>plugins</category>
      <category>mcp</category>
      <category>node</category>
    </item>
    <item>
      <title>Producer Fallback: When Claude Hits the Weekly Limit, the Pipeline Still Ships</title>
      <dc:creator>Jeremy Longshore</dc:creator>
      <pubDate>Thu, 16 Jul 2026 16:18:30 +0000</pubDate>
      <link>https://dev.to/jeremy_longshore/producer-fallback-when-claude-hits-the-weekly-limit-the-pipeline-still-ships-1pde</link>
      <guid>https://dev.to/jeremy_longshore/producer-fallback-when-claude-hits-the-weekly-limit-the-pipeline-still-ships-1pde</guid>
      <description>&lt;p&gt;A daily content pipeline that dies because one model returns a rate-limit error is measuring the wrong success criterion. The job is not "Claude ran." The job is "yesterday has a post on the live site."&lt;/p&gt;

&lt;p&gt;On 2026-07-15 that distinction stopped being theoretical.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke at 04:00
&lt;/h2&gt;

&lt;p&gt;Two failures, one after the other:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dirty tree preflight.&lt;/strong&gt; Closing a bead the night before left one uncommitted line in &lt;code&gt;.beads/interactions.jsonl&lt;/code&gt;. &lt;code&gt;preflight_branch_normalize&lt;/code&gt; correctly refused to run on a dirty &lt;code&gt;master&lt;/code&gt;. No produce step. No post for 2026-07-14.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude weekly limit.&lt;/strong&gt; After the tree was cleaned and the run re-fired, &lt;code&gt;claude -p /blog-backfill&lt;/code&gt; exited in three seconds with: weekly limit, resets Jul 19. Land saw no post file and reported &lt;code&gt;NO-POST (rc=20)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the same shape as automation assurance on Intent-OS: exit codes and "the scheduled job ran" are not outcome verification. The outcome is a live URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix one: do not brick the cron on beads interaction noise
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.beads/interactions.jsonl&lt;/code&gt; is an append-only session audit log. Any &lt;code&gt;bd close&lt;/code&gt; dirties it without committing. Treating that as "human has uncommitted feature work" is wrong; treating real content dirt the same as always is right.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;preflight_branch_normalize&lt;/code&gt; now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Still &lt;strong&gt;FATAL&lt;/strong&gt; on any tracked dirt that is not interactions-only.&lt;/li&gt;
&lt;li&gt;If the &lt;strong&gt;only&lt;/strong&gt; dirty path is &lt;code&gt;.beads/interactions.jsonl&lt;/code&gt;, auto-commit it with a mechanical message and continue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That keeps the clean-tree invariant for posts and methodology files without letting a late-night bead close silence the morning publish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix two: hard voice lint (already on master)
&lt;/h2&gt;

&lt;p&gt;Separately, the produce path now fails closed on AI voice fingerprints: em dash / en dash hard ban, expanded slop phrase list, deterministic &lt;code&gt;lint-post-voice.py&lt;/code&gt; in the skill and again in &lt;code&gt;blog-land.sh&lt;/code&gt;. Phrase checks mask fenced code and URLs so repo names do not false-positive. Historical posts are not bulk-rewritten; only the post being landed is gated.&lt;/p&gt;

&lt;p&gt;That gate is what made the recovered 2026-07-14 post ship without the em-dash density that had become the house default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix three: Grok as producer fallback
&lt;/h2&gt;

&lt;p&gt;Claude remains the primary producer. It is not the only producer.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;blog-backfill-daily.sh&lt;/code&gt; now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tries &lt;code&gt;claude -p /blog-backfill&lt;/code&gt; (same as before).&lt;/li&gt;
&lt;li&gt;If Claude fails &lt;strong&gt;and&lt;/strong&gt; no post exists for the target date, runs a &lt;strong&gt;Grok headless&lt;/strong&gt; producer with the same contract: write the post, append &lt;code&gt;decisions.jsonl&lt;/code&gt;, write &lt;code&gt;.blog-staging/DATE.intent.json&lt;/code&gt; with &lt;code&gt;ready:true&lt;/code&gt; only after gates (including voice lint). No git.&lt;/li&gt;
&lt;li&gt;Always runs &lt;code&gt;blog-land.sh&lt;/code&gt; afterward (verify → commit → push → dual-publish → queue, or quarantine).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Env knobs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Values&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BLOG_PRODUCER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;auto&lt;/code&gt; (default), &lt;code&gt;claude&lt;/code&gt;, &lt;code&gt;grok&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Which producer(s) to run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GROK_BIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;path&lt;/td&gt;
&lt;td&gt;Defaults to &lt;code&gt;~/.grok/bin/grok&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BLOG_GROK_MAX_TURNS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;Headless turn cap (default 120)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;auto&lt;/code&gt; is the production setting until Claude is healthy again, and it stays useful after: a weekly limit or API blip should not equal a missed day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually shipped for 2026-07-14
&lt;/h2&gt;

&lt;p&gt;While Claude was dark, the 2026-07-14 post was produced manually under the same land contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/exit-0-is-not-success/"&gt;Exit 0 Is Not Success: Automation Assurance That Verifies Outcomes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Voice lint PASS, Hugo PASS, dual-publish to tonsofskills + field-notes, live liveness OK&lt;/li&gt;
&lt;li&gt;Ezekiel packet re-sent with real social copy after the first send degraded (voice-gen still hits Claude)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The content thesis of that post and the ops lesson of this one are the same sentence: &lt;strong&gt;exit 0 is not success; verify the outcome.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Also on the board today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Release bumps and README/changelog sync after the voice-lint and preflight commits landed on &lt;code&gt;master&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;IAM Bob packaging release noise (&lt;code&gt;v2.1.6&lt;/code&gt;) from the overnight auto-release path: docs-only, not the story of the day.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Producer outages will happen. Rate limits, model downtime, auth glitches. The pipeline should treat them like any other dependency failure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do not fail open (publish half-baked).&lt;/li&gt;
&lt;li&gt;Do not fail silent (alert + log).&lt;/li&gt;
&lt;li&gt;Do not single-home the produce step on one vendor when a second local producer can honor the same artifact contract.&lt;/li&gt;
&lt;li&gt;Keep land deterministic and dumb: it does not care which model wrote the markdown.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tomorrow's 04:00 run can try Claude, fall back to Grok, and still land. That is the bar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/exit-0-is-not-success/"&gt;Exit 0 Is Not Success: Automation Assurance That Verifies Outcomes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/liveness-without-health-is-theater/"&gt;Liveness Without Health Is Theater&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/empty-is-not-clean/"&gt;Empty Is Not Clean: Five Fail-Open Bugs in an AI Agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>automation</category>
      <category>devops</category>
      <category>claudecode</category>
      <category>blogging</category>
    </item>
    <item>
      <title>Exit 0 Is Not Success: Automation Assurance That Verifies Outcomes</title>
      <dc:creator>Jeremy Longshore</dc:creator>
      <pubDate>Thu, 16 Jul 2026 16:18:27 +0000</pubDate>
      <link>https://dev.to/jeremy_longshore/exit-0-is-not-success-automation-assurance-that-verifies-outcomes-2a4n</link>
      <guid>https://dev.to/jeremy_longshore/exit-0-is-not-success-automation-assurance-that-verifies-outcomes-2a4n</guid>
      <description>&lt;p&gt;A cron that exits zero and produces nothing is not healthy. It is a silent failure wearing a green badge.&lt;/p&gt;

&lt;p&gt;That distinction drove most of 2026-07-14 on Intent-OS. Phase-2 landed a stack of fail-closed controls, and the load-bearing one for operations was &lt;strong&gt;automation execution assurance&lt;/strong&gt;: a registry, an engine, and active drills that refuse to treat "process finished cleanly" as "the work got done."&lt;/p&gt;

&lt;h2&gt;
  
  
  The lie exit codes tell
&lt;/h2&gt;

&lt;p&gt;Schedulers are good at answering a narrow question: &lt;em&gt;did the process start and did it return 0?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;They are bad at the question operators actually care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the backup finish with a restorable artifact?&lt;/li&gt;
&lt;li&gt;Did the heartbeat file get a fresh timestamp?&lt;/li&gt;
&lt;li&gt;Did the export write the expected tables, not an empty stub?&lt;/li&gt;
&lt;li&gt;Did the watchdog that was supposed to catch all of the above still run?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your only gate is the process exit code, every one of those can fail while the dashboard stays green. We already ship that lesson in other forms (&lt;a href="https://dev.to/posts/empty-is-not-clean/"&gt;empty data that passed as clean&lt;/a&gt;, &lt;a href="https://dev.to/posts/liveness-without-health-is-theater/"&gt;liveness without health&lt;/a&gt;, &lt;a href="https://dev.to/posts/every-safety-gate-has-a-failure-direction/"&gt;gates that fail open&lt;/a&gt;). Automation was the next place the same shape showed up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we shipped: independent outcome verification
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PR I&lt;/strong&gt; on Intent-OS (&lt;code&gt;feat(automation): execution assurance&lt;/code&gt;) makes the rule explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An exit code of 0 is &lt;strong&gt;not&lt;/strong&gt; business success.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The design is deliberately boring and mechanical:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;registry.json&lt;/code&gt;&lt;/strong&gt; lists each automation's full lifecycle policy: schedule, lateness, max runtime, heartbeat, retry/backoff, catch-up, overlap, idempotency, expected output, &lt;strong&gt;outcome verification&lt;/strong&gt;, owner, and runbook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;assure.py&lt;/code&gt;&lt;/strong&gt; runs a fail-closed status ladder. A completed exit-0 run is classified &lt;code&gt;exit0-no-outcome&lt;/code&gt; unless an independent check verifies the expected output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watchdog + watchdog-of-watchdog&lt;/strong&gt;: catching dead jobs is not enough if the catcher itself dies. Escalation covers the uncovered gap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;drills.sh&lt;/code&gt;&lt;/strong&gt; injects the minimum failure set on disposable/synthetic targets and asserts detect → receipt → recover.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first-run scorecard for 2026-07-14 recorded &lt;strong&gt;34 predicates, 0 failures&lt;/strong&gt;, including a real process kill (stuck heartbeat), a real exit-0 with no output (&lt;code&gt;exit0-no-outcome&lt;/code&gt;), timer disabled, scheduler unavailable, credential missing, stale output, and watchdog failure. The scorecard field that matters for humans is simple: &lt;code&gt;no_false_green=True&lt;/code&gt;. Exit-0 is healthy only with a verified outcome.&lt;/p&gt;

&lt;p&gt;That is the same discipline as two-marker liveness on the blog fleet (&lt;code&gt;.beat&lt;/code&gt; vs &lt;code&gt;.ok&lt;/code&gt;): &lt;em&gt;ran&lt;/em&gt; is not &lt;em&gt;succeeded&lt;/em&gt;. The automation package just names the failure modes and drills them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not trust the job?
&lt;/h2&gt;

&lt;p&gt;The tempting alternative is "the job already knows if it failed; just check its exit code harder." That collapses three different things into one bit:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;What it proves&lt;/th&gt;
&lt;th&gt;What it hides&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Process started&lt;/td&gt;
&lt;td&gt;Scheduler fired&lt;/td&gt;
&lt;td&gt;Outcome quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exit 0&lt;/td&gt;
&lt;td&gt;No crash path&lt;/td&gt;
&lt;td&gt;Empty success, skipped body, wrong target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heartbeat fresh&lt;/td&gt;
&lt;td&gt;Something is looping&lt;/td&gt;
&lt;td&gt;Correct work product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outcome check&lt;/td&gt;
&lt;td&gt;Expected artifact exists and is plausible&lt;/td&gt;
&lt;td&gt;(this is the bar)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Independent verification is not cynicism about the job author. It is an admission that the success path is where silent failure hides. Crashes are loud. Empty green is quiet.&lt;/p&gt;

&lt;p&gt;We also fixed a &lt;code&gt;kill 0&lt;/code&gt; process-group hazard in the drill trap while we were there: drills that accidentally widen their blast radius are worse than no drills. Disposable targets only; no production automation was touched in the first-run pack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail-closed neighbors on the same day
&lt;/h2&gt;

&lt;p&gt;Assurance did not land alone. The Phase-2 foundation batch on Intent-OS put the same fail-closed posture in several adjacent places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Disclosure policy (PR A / C8):&lt;/strong&gt; canonical fail-closed enforcement at every sink. The irreversible control is the one that refuses to emit when the policy cannot be evaluated cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability failure-detection spine (PR H):&lt;/strong&gt; registry + engine + disposable drills for detection, not just dashboards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent gateway (PR J):&lt;/strong&gt; narrow tools, default-deny, signed receipts. Governed agentic ops with an allowlist, not an open shell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert floor (PR C):&lt;/strong&gt; one authoritative source plus drift enforcement so routing cannot silently rot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watchtower scan-gate (PR D):&lt;/strong&gt; resolve + scan + gate before promote.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supply-chain CI baseline (PR G):&lt;/strong&gt; normalize + policy gate + schemas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The audit trail for the day closed with a Phase-2 re-audit after D88 residuals: conformant with explicit gaps, not "green because we stopped looking."&lt;/p&gt;

&lt;h2&gt;
  
  
  Also shipped
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;claude-code-plugins / Freshie inventory.&lt;/strong&gt; Several production-truth fixes: refuse to export incomplete discovery runs to Dolt; gate public exports behind a table allowlist; stop &lt;code&gt;--fix-agents&lt;/code&gt; from stripping required color; floor curated wipes; propagate &lt;code&gt;populate-db&lt;/code&gt; failures; harden external-sync supply chain after an ops review. Incomplete data no longer pretends it is a finished inventory snapshot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DiagnosticPro.&lt;/strong&gt; Instrument-grade frontend redesign (less template, quieter nav), real Open Graph images, whiteglove PDF customer reports, and live e2e journeys with free-coupon seed cases. Public surface stopped looking like a stock SaaS shell.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bob's Big Brain rename.&lt;/strong&gt; Umbrella and plugin directories aligned to &lt;code&gt;bobs-big-brain-*&lt;/code&gt; on disk and remote; skill docs tighten the search ladder (keywords first, then wider scope) and marketplace author schema so install stops failing on a CLI type check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blog pipeline (this site).&lt;/strong&gt; Hard ban on em dashes and AI-slop phrases via &lt;code&gt;lint-post-voice.py&lt;/code&gt;, skill write rules, and a land-step gate. Phrase checks mask fenced code and URLs so repo names do not false-positive. That work merged after the dirty-tree preflight story below, but the lesson matches the day: soft style guidance without a hard gate is theater.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dirty-tree lesson (same failure shape)
&lt;/h2&gt;

&lt;p&gt;This post itself almost did not exist. The 04:00 backfill aborted because &lt;code&gt;.beads/interactions.jsonl&lt;/code&gt; had one uncommitted line from a late bead close. Preflight correctly refused a dirty &lt;code&gt;master&lt;/code&gt;. The content pipeline never ran.&lt;/p&gt;

&lt;p&gt;That is &lt;code&gt;exit0-no-outcome&lt;/code&gt; in human form: the machine "did something" (bd wrote a log line), then left the system in a state where the real job (publish a post) could not start. Fix was twofold: commit the audit line, and teach preflight to auto-commit &lt;strong&gt;only&lt;/strong&gt; when interactions.jsonl is the sole tracked dirt. Any other dirt still fails closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is still honest ABSENT
&lt;/h2&gt;

&lt;p&gt;Automation assurance is proven on disposable drills. Wiring live schedulers (systemd, cron, GitHub Actions receipts, borg/R2 lifecycle, the real &lt;code&gt;ops/verify&lt;/code&gt; stream) as observation sources, and deploying the off-host watchdog-of-watchdog, is still change-control-gated production work. Shipping the engine without claiming the fleet is fully under it is part of the same honesty rule: do not paint partial coverage as complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;If your automation dashboard only counts exit codes, you are measuring crashes, not success. Put the success criterion in a separate check of the artifact you actually needed. Drill the failure modes on disposable targets until &lt;code&gt;no_false_green&lt;/code&gt; is true. Then wire production under change control.&lt;/p&gt;

&lt;p&gt;Exit 0 means the process finished. Outcome verification means the work finished. Only one of those is worth a green light.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/empty-is-not-clean/"&gt;Empty Is Not Clean: Five Fail-Open Bugs in an AI Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/liveness-without-health-is-theater/"&gt;Liveness Without Health Is Theater&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/every-safety-gate-has-a-failure-direction/"&gt;Every Safety Gate Has a Failure Direction&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>automation</category>
      <category>devops</category>
      <category>observability</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Empty Is Not Clean: Five Fail-Open Bugs in an AI Agent</title>
      <dc:creator>Jeremy Longshore</dc:creator>
      <pubDate>Wed, 15 Jul 2026 13:07:49 +0000</pubDate>
      <link>https://dev.to/jeremy_longshore/empty-is-not-clean-five-fail-open-bugs-in-an-ai-agent-5fd8</link>
      <guid>https://dev.to/jeremy_longshore/empty-is-not-clean-five-fail-open-bugs-in-an-ai-agent-5fd8</guid>
      <description>&lt;p&gt;A policy said &lt;code&gt;deny&lt;/code&gt; anything under &lt;code&gt;/etc&lt;/code&gt;. A Bash call read &lt;code&gt;/etc/shadow&lt;/code&gt; and came back &lt;code&gt;{allow, rule: 'trust-bash'}&lt;/code&gt;. No human in the loop. The deny rule was still there, still first in the list, still scoped exactly the way an operator would write it. It just never fired.&lt;/p&gt;

&lt;p&gt;Two cold-contributor reviewers reproduced it independently on this policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deny&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pathPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/etc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto_approve&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trust-bash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// Bash → /etc/shadow  ⇒  { allow: true, rule: 'trust-bash' }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gate is the policy engine inside &lt;code&gt;claude-code-slack-channel&lt;/code&gt; — an MCP-mediated Slack agent whose whole reason to exist is that it can &lt;em&gt;prove&lt;/em&gt; what a tool did: a signed, offline-verifiable audit journal plus a per-tool-call policy engine. If the deny rule can be walked past, none of that matters. This is the story of that bug, and the four other bugs that turned out to be the same bug wearing different clothes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern: absence read as success
&lt;/h2&gt;

&lt;p&gt;Every one of these had the same shape — the same fail-open bug. When the data a check needs to make its decision was &lt;em&gt;absent&lt;/em&gt;, the check defaulted to the permissive answer — allow, pass, clean, covers-all — instead of the safe one. A missing argument. An empty log. A property that isn't there. A chain link that got sheared off. A scope field left unset.&lt;/p&gt;

&lt;p&gt;The principle that closes all five: &lt;strong&gt;when a check has nothing to check, it must fail closed.&lt;/strong&gt; Default-deny, not default-allow. Absence is not a green light. Absence is the alarm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disguise 1 — empty args made the deny rule vanish
&lt;/h2&gt;

&lt;p&gt;The engine's &lt;code&gt;evaluate()&lt;/code&gt; takes a &lt;code&gt;ToolCall&lt;/code&gt; and walks the rules. &lt;code&gt;matchApplies()&lt;/code&gt; decides whether a rule's scope — &lt;code&gt;pathPrefix&lt;/code&gt;, &lt;code&gt;argEquals&lt;/code&gt; — matches the call. The exploit lived one level up, in the &lt;em&gt;only&lt;/em&gt; production caller.&lt;/p&gt;

&lt;p&gt;The MCP &lt;code&gt;permission_request&lt;/code&gt; handler in &lt;code&gt;server.ts&lt;/code&gt; never receives structured tool arguments. It gets an &lt;code&gt;input_preview&lt;/code&gt; string for display and nothing else. So it built the call like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.ts — the sole production caller&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolCall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;                      &lt;span class="c1"&gt;// ← no structured args available here&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;deny&lt;/code&gt; scoped by &lt;code&gt;pathPrefix: '/etc'&lt;/code&gt; asks: does &lt;code&gt;input.path&lt;/code&gt; start with &lt;code&gt;/etc&lt;/code&gt;? Against &lt;code&gt;input: {}&lt;/code&gt; the answer is no. &lt;code&gt;matchApplies()&lt;/code&gt; returns no-match, the deny is &lt;em&gt;silently skipped&lt;/em&gt;, and the next broad &lt;code&gt;auto_approve&lt;/code&gt; for Bash swallows the call. The scoped rule the operator wrote was invisible precisely because the engine couldn't see what it was scoped on.&lt;/p&gt;

&lt;p&gt;The fix does not try to reconstruct the missing args. It can't — they aren't there. Instead, when the gate cannot see the input a scoped &lt;code&gt;deny&lt;/code&gt;/&lt;code&gt;require_approval&lt;/code&gt; rule needs, it stops trusting the fall-through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// If a scoped deny/require rule COULD apply but we lack the input&lt;/span&gt;
&lt;span class="c1"&gt;// to know, escalate to a human instead of falling through to auto_approve.&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scopedRuleNeedsInput&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;inputIsUnavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;escalate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;require_approval&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input-unavailable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not knowing whether a deny applies is treated as "it might" — and "it might deny" routes to a human, never to auto-approve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disguise 2 — a wiped audit log verified clean
&lt;/h2&gt;

&lt;p&gt;The journal's verifier walks the event chain and reports broken links. Zero events means zero broken links. So an &lt;em&gt;empty&lt;/em&gt; &lt;code&gt;audit.log&lt;/code&gt; verified clean. An attacker who truncates the entire log to nothing gets a green check — the strongest possible signal from the weakest possible file.&lt;/p&gt;

&lt;p&gt;The fix is a floor. &lt;code&gt;--min-events N&lt;/code&gt; makes verification &lt;em&gt;fail&lt;/em&gt; when the log holds fewer events than expected:&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;verify audit.log &lt;span class="nt"&gt;--min-events&lt;/span&gt; 500
&lt;span class="go"&gt;FAIL: expected ≥500 events, found 0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero is no longer "clean." Zero is "someone deleted the evidence." The flag parser is deliberately fail-closed too: a malformed value throws rather than silently disabling the floor, because a floor you can turn off by fat-fingering it is not a floor. (A later fix routed a space-form negative like &lt;code&gt;--v2-floor-seq -5&lt;/code&gt; to the value validator so it throws the accurate "must be a non-negative integer" instead of a misleading "missing value.")&lt;/p&gt;

&lt;h2&gt;
  
  
  Disguise 3 — a prototype key past a truthiness gate, and the near-miss
&lt;/h2&gt;

&lt;p&gt;Channel-policy reads (&lt;code&gt;access.channels[id]&lt;/code&gt;) were scattered across seven call sites with inconsistent guards. A bare index read inherits &lt;code&gt;Object.prototype&lt;/code&gt; members: ask for the channel &lt;code&gt;'constructor'&lt;/code&gt; or &lt;code&gt;'toString'&lt;/code&gt; and you get back a truthy function that never belonged to anyone's config — enough to slip past a truthiness-based gate.&lt;/p&gt;

&lt;p&gt;Not exploitable today; Slack channel ids match &lt;code&gt;[CD][A-Z0-9]+&lt;/code&gt; and never take prototype-key forms. But it's a footgun class, and seven inconsistent guards is seven chances for one to drift wrong. The fix routes every read through one chokepoint that returns a policy only for an &lt;em&gt;own&lt;/em&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getChannelPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;access&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Access&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ChannelPolicy&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;access&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;access&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One helper, seven callers, no drift. And this is where a fail-closed refactor nearly shipped a fail-open crash.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;access.channels&lt;/code&gt; is &lt;em&gt;typed&lt;/em&gt; as always-present. It isn't. An &lt;code&gt;Access&lt;/code&gt; loaded from disk can arrive without it, and &lt;code&gt;Object.hasOwn(undefined, id)&lt;/code&gt; &lt;strong&gt;throws&lt;/strong&gt;. A Gemini audit flagged it HIGH. The old scattered code used &lt;code&gt;access.channels?.[id]&lt;/code&gt; — optional chaining that safely returns &lt;code&gt;undefined&lt;/code&gt; when the object is missing. The refactor, in the name of hardening, would have converted a &lt;em&gt;defined-safe&lt;/em&gt; absence into a thrown exception on every channel read. Whether an unhandled throw at the gate ends up allowing or denying depends on which handler catches it — and "depends on the handler" is exactly the uncertainty a security gate must not have. Defined behavior became undefined behavior at the one point that has to stay defined.&lt;/p&gt;

&lt;p&gt;The guard belongs &lt;em&gt;inside&lt;/em&gt; the one helper, so it can't be forgotten at any of the seven sites:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getChannelPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;access&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Access&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ChannelPolicy&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;access&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;channels&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A fail-closed accessor must fail closed. It must not throw. Same discipline, one recursion deeper — the fix for a fail-open bug has to itself fail closed on &lt;em&gt;its&lt;/em&gt; missing input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why one chokepoint instead of hardening seven sites?
&lt;/h3&gt;

&lt;p&gt;Hardening each of the seven reads in place would have worked on the day it was written. It also would have created seven independent copies of a subtle guard, and the eighth read — the one added six months from now by someone who copied the &lt;em&gt;simplest&lt;/em&gt; nearby example — would skip it. A chokepoint is not just less code. It's the only version where the correct behavior is the &lt;em&gt;default&lt;/em&gt; behavior. You can't call the channel-policy lookup wrong because there's only one way to call it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disguise 4 — a sheared, renumbered chain recomputes clean
&lt;/h2&gt;

&lt;p&gt;The genesis-seq pin from an earlier hardening pass was documented as defeating head-truncation. It does — but only on &lt;em&gt;signed&lt;/em&gt;, key-verified (v2) chains. On an unsigned v1 chain the links are bare keyless SHA-256:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevHash&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;canonicalJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A writer-capable attacker shears the head off, renumbers the survivors so the new first event is &lt;code&gt;seq=1&lt;/code&gt;, and recomputes every hash forward. No secret is required — the formula is public and the file contains everything it needs. The sheared file verifies clean because it &lt;em&gt;is&lt;/em&gt; internally consistent. It's a smaller, valid-looking journal that never happened.&lt;/p&gt;

&lt;p&gt;The close is two optional verification anchors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;pinnedGenesisHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a1b2…&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// event[0].prevHash MUST equal this operator-held value&lt;/span&gt;
  &lt;span class="na"&gt;v2FloorSeq&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="c1"&gt;// every event at seq ≥ N MUST be v2/signed&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;pinnedGenesisHash&lt;/code&gt; is the key move — and it's a different mechanism from the older genesis-&lt;em&gt;seq&lt;/em&gt; pin. That earlier pin fixed the sequence number, which a renumbering attacker just recomputes. &lt;code&gt;pinnedGenesisHash&lt;/code&gt; fixes the first event's &lt;code&gt;prevHash&lt;/code&gt; against a value the operator captured &lt;em&gt;outside&lt;/em&gt; the file. When an attacker renumbers the chain, the new first event's &lt;code&gt;prevHash&lt;/code&gt; no longer matches the pinned value — and there's no way to forge a match, because the attacker can't reach into the operator's out-of-band record to change it. That's also why it holds on an unsigned chain: the protection comes from the anchor being external, not from a key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not a self-describing anchor?
&lt;/h3&gt;

&lt;p&gt;The obvious instinct is to make the file carry its own anchor — stamp the "true" genesis hash into a header, or sign a manifest of the chain's own metadata. That fails for a reason worth stating plainly: &lt;strong&gt;the only forgery a file can reproduce from itself is one an in-file anchor also validates.&lt;/strong&gt; If the attacker can rewrite the chain, the attacker can rewrite any anchor stored alongside it. A self-attested anchor gets forged in the same edit as everything it was supposed to protect. A &lt;em&gt;signed&lt;/em&gt; in-file manifest is only better if the signing key lives somewhere the attacker can't reach — at which point the key is the out-of-band secret, and you've relocated the external-fact requirement, not removed it. The anchor has to be a fact the attacker &lt;em&gt;cannot&lt;/em&gt; reproduce — and on an unsigned chain the only such fact is one that was never in the file to begin with.&lt;/p&gt;

&lt;p&gt;The tradeoff is honest and belongs in the docs: the anchors are only as strong as the operator's out-of-band capture. A key rotation that forgets to record the new head hash loses the cross-file link. Anchors are opt-in — absent, verification is byte-identical to before. You buy real protection with real operational discipline, and the system says so out loud rather than pretending the file can bootstrap its own trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disguise 5 — a thread-scoped rule silently covered every thread
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;matchSubsetOrEqual()&lt;/code&gt; — the subset check behind both &lt;code&gt;detectShadowing()&lt;/code&gt; and &lt;code&gt;checkMonotonicity()&lt;/code&gt; — compared tool, channel, actor, &lt;code&gt;pathPrefix&lt;/code&gt;, and &lt;code&gt;argEquals&lt;/code&gt;. It never compared &lt;code&gt;thread_ts&lt;/code&gt;. So a rule scoped to one Slack thread was treated as a rule covering &lt;em&gt;every&lt;/em&gt; thread.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before: thread_ts absent from the comparison ⇒ "matches all threads"&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;matchSubsetOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;sameTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;sameChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;sameActor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;subsetPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;subsetArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// thread_ts never checked&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The absent field defaulted to "matches all" when the safe default is "matches none." A narrowly-scoped rule got read as maximally broad — the same failure shape as the deny that ignored &lt;code&gt;/etc&lt;/code&gt;, one layer up in the analysis code instead of the enforcement path. The fix honors &lt;code&gt;thread_ts&lt;/code&gt; in the subset check, and an unset scope now correctly covers nothing rather than everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the fail-open bug
&lt;/h2&gt;

&lt;p&gt;Line them up and it's one bug five times. Empty args → skip the deny. Empty log → verified clean. Missing property → truthy prototype value (plus a thrown exception the fix itself almost added). Sheared chain → recomputes valid. Absent thread scope → covers all threads. Every one defaults to &lt;em&gt;allow / pass / clean / covers-all&lt;/em&gt; when the discriminating data is missing. The permissive branch is the one that runs when there's nothing to run against.&lt;/p&gt;

&lt;p&gt;The fix never changes. Make absence fail closed. A gate that can't see its input escalates to a human. A verifier with too few events fails. An accessor with no property returns &lt;code&gt;undefined&lt;/code&gt; and doesn't throw. A chain proves itself against a fact it can't forge. A scope with no value covers nothing. Same principle, five layers. The one honest exception is the sheared chain: when the discriminating fact can only live &lt;em&gt;outside&lt;/em&gt; the file, fail-closed becomes something the operator opts into by capturing that fact out-of-band — and the system says so plainly instead of pretending a file can bootstrap its own trust.&lt;/p&gt;

&lt;p&gt;None of these surfaced from writing new code. They surfaced from adversarial review pointed at the load-bearing core: a hardening review of the signed journal, two cold-contributor reviewers who reproduced the &lt;code&gt;/etc/shadow&lt;/code&gt; walk-past, a Gemini audit that caught the crash-on-absent-channels the &lt;em&gt;fix&lt;/em&gt; almost introduced. Every one was proven revert-to-red — revert the guard and a test goes red, so the guard is doing work no other line does. The suite sits at ~1304 tests, ~96.6% line / 98.2% function coverage, nine gates green.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Disguise&lt;/th&gt;
&lt;th&gt;What was absent&lt;/th&gt;
&lt;th&gt;Permissive default&lt;/th&gt;
&lt;th&gt;Fail-closed fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Empty args&lt;/td&gt;
&lt;td&gt;Structured tool input to scope on&lt;/td&gt;
&lt;td&gt;Deny rule skipped; &lt;code&gt;auto_approve&lt;/code&gt; wins&lt;/td&gt;
&lt;td&gt;Escalate to a human when a scoped rule lacks its input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wiped audit log&lt;/td&gt;
&lt;td&gt;Any events in the file&lt;/td&gt;
&lt;td&gt;Empty log verifies clean&lt;/td&gt;
&lt;td&gt;Fail below a &lt;code&gt;--min-events&lt;/code&gt; floor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing property&lt;/td&gt;
&lt;td&gt;An own property on the config&lt;/td&gt;
&lt;td&gt;Prototype key returns a truthy function&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Object.hasOwn&lt;/code&gt; chokepoint; return &lt;code&gt;undefined&lt;/code&gt;, never throw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sheared chain&lt;/td&gt;
&lt;td&gt;An out-of-band genesis fact&lt;/td&gt;
&lt;td&gt;Renumbered chain recomputes valid&lt;/td&gt;
&lt;td&gt;Pin the genesis hash externally; a mismatch fails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unset thread scope&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;thread_ts&lt;/code&gt; in the subset check&lt;/td&gt;
&lt;td&gt;Rule covers every thread&lt;/td&gt;
&lt;td&gt;Compare &lt;code&gt;thread_ts&lt;/code&gt;; no scope covers none&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Also shipped
&lt;/h2&gt;

&lt;p&gt;Alongside the fail-open work, a set of transport-contract fixes in the same spirit: start the MCP stdio transport &lt;em&gt;before&lt;/em&gt; Socket Mode so the protocol handshake never blocks on Slack; route all structured logs to stderr because stdout &lt;em&gt;is&lt;/em&gt; the MCP channel and one stray log line corrupts the protocol; and extract the socket-start and manifest-identity guards into testable seams. A decision-record spike (ADR-004) settled a build-vs-keep question: this system's job is to &lt;em&gt;prove&lt;/em&gt; what an agent did — offline, self-hosted, deterministic per tool call — so the signed journal is what matters, not prettier approval cards.&lt;/p&gt;

&lt;p&gt;That's the throughline for all of it. Guardrails for an AI agent are not features you add on top — they're the property that lets the agent hold a tool at all. An agent you can trust with &lt;code&gt;Bash&lt;/code&gt; is one whose deny rule fires even when the caller forgot to pass the arguments. Fail-closed discipline is what earns the trust; empty is not clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/every-safety-gate-has-a-failure-direction/"&gt;Every Safety Gate Has a Failure Direction&lt;/a&gt; — the underlying principle: every gate fails in &lt;em&gt;some&lt;/em&gt; direction, and you have to choose it on purpose.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/adversarial-review-before-team-rollout/"&gt;Adversarial Review Before Team Rollout&lt;/a&gt; — all five of these bugs came from pointing hostile reviewers at the core before anyone depended on it.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/relevance-score-broke-cite-or-refuse-gate/"&gt;When a Relevance Score Broke the Cite-or-Refuse Gate&lt;/a&gt; — another fail-open gate, another case of absence quietly reading as permission.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aiagents</category>
      <category>typescript</category>
      <category>authentication</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The Kernel Must Not Import Its Agents</title>
      <dc:creator>Jeremy Longshore</dc:creator>
      <pubDate>Tue, 14 Jul 2026 10:23:16 +0000</pubDate>
      <link>https://dev.to/jeremy_longshore/the-kernel-must-not-import-its-agents-5d72</link>
      <guid>https://dev.to/jeremy_longshore/the-kernel-must-not-import-its-agents-5d72</guid>
      <description>&lt;p&gt;There is a moment in the life of every framework where an application-shaped thing has grown up inside it. It started as a demo, a reference implementation, a "let's just prove the primitives work" spike. Then it kept running. Now it has cron entries, an operator surface, a test pack, and traceability rows — and it lives in the same repository as the primitives it was only ever meant to exercise.&lt;/p&gt;

&lt;p&gt;On 2026-07-12 we did the extraction. We pulled a stateful watcher agent out of a governance kernel and gave it its own leaf repository. The interesting part is not that we moved files. It is the single rule that made the move safe, and that made the resulting kernel worth trusting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The kernel must not import its agents.&lt;/strong&gt; When you split an agent out of its governance kernel, the dependency must point from the agent to the kernel — never the reverse.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the agent-framework restatement of a much older law: &lt;em&gt;the framework must not depend on the application&lt;/em&gt;, and &lt;em&gt;the composition root lives in the leaf&lt;/em&gt;. Everything below is one concrete execution of that law.&lt;/p&gt;

&lt;p&gt;In practice the agent repo imports the kernel's published contracts as a pinned dependency, composing governance primitives like the trigger interface and policy engine. The kernel has zero knowledge of the agent's existence — no import, no reference, no coupling. That one-way edge is what keeps the kernel small, independently verifiable, and trustworthy as a standalone governance system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two systems
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;agent-governance-plane&lt;/code&gt; (AGP)&lt;/strong&gt; is the governance &lt;em&gt;kernel&lt;/em&gt;. It owns the durable governance primitives and nothing else: a policy engine, a Docker sandbox for tool execution, Slack human-in-the-loop (HITL) approval, and an Ed25519-signed, hash-chained audit journal. Its doctrine line is worth memorizing because it dictates the whole shape:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The model proposes; the deterministic system decides and records.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every autonomous tool call passes through the same governed path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trigger → policy gate → (if risky) human approval → sandboxed exec → signed journal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;bob-the-intendant&lt;/code&gt;&lt;/strong&gt; is the new &lt;em&gt;leaf&lt;/em&gt; product repo. It composes AGP as a pinned dependency and owns the agent/composition layer. Its product category is "governed background agents" — long-lived processes that wake on a trigger and want to do something a human might not want them to do unsupervised. AGP is what keeps them honest. Bob is what makes them useful.&lt;/p&gt;

&lt;p&gt;Before the extraction, the watcher lived inside AGP. After it, AGP has zero knowledge that the watcher exists, and Bob owns it outright.&lt;/p&gt;

&lt;h2&gt;
  
  
  The invariant we were protecting
&lt;/h2&gt;

&lt;p&gt;The boundary has two halves, and they are not the same rule. The headline half is the thesis: &lt;strong&gt;the kernel imports nothing from the leaf&lt;/strong&gt; — it does not know the agents that compose it exist. The mirror half constrains the leaf: &lt;strong&gt;the leaf may import only the kernel's frozen contracts, never the daemon's internals&lt;/strong&gt; — where "the daemon" is the kernel's live governance loop (&lt;code&gt;runMediated()&lt;/code&gt;) and the machinery around it. The composed agent composes the contracts the kernel publishes; it never reaches past them into implementation.&lt;/p&gt;

&lt;p&gt;Put together, the two halves define one edge: it runs from agent (leaf) to kernel, touches only the published surface, and never runs the other way. A governance kernel that imports the agents it governs breaks the acyclic-dependency rule — it is a governance kernel you cannot reason about. You can no longer point at it and say "this small thing decides and records; here is its trust boundary" — because its &lt;a href="https://dev.to/posts/govern-at-merge-untrusted-union/"&gt;trust boundary&lt;/a&gt; now includes every agent that ever leaked into it.&lt;/p&gt;

&lt;p&gt;So the extraction had two acceptance tests, both structural:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After extraction, &lt;strong&gt;AGP has zero knowledge of the watcher.&lt;/strong&gt; No import, no CLI command, no template, no traceability row.&lt;/li&gt;
&lt;li&gt;After extraction, &lt;strong&gt;Bob has zero references to removed AGP modules.&lt;/strong&gt; It composes contracts that still exist; it points at nothing that was deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What moved out, what stayed in
&lt;/h2&gt;

&lt;p&gt;The discipline of the move is entirely in &lt;em&gt;where the cut runs&lt;/em&gt;. The cut runs exactly along the contract boundary — the bounded context between kernel and leaf: agent-specific composition leaves, primitives stay.&lt;/p&gt;

&lt;p&gt;Moved &lt;strong&gt;out&lt;/strong&gt; of the kernel and into the leaf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/triggers/github-watcher/     # the trigger-woken GitHub watcher agent
src/cli/commands/watch.ts        # `agp watch` — the operator surface a human runs
templates/github-watcher/        # the per-agent template test packs
RTM REQ-043 / 045 / 046 / 047 / 048 / 049   # this agent's traceability rows
Journey J2 + the "run-governed-watch" persona flow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stayed &lt;strong&gt;in&lt;/strong&gt; the kernel, because these are the primitives the agent &lt;em&gt;composes&lt;/em&gt;, not the agent itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trigger-source contract (RTM REQ-050)   # the frozen interface the agent fires through
daemon runMediated() loop (REQ-042)     # the governance loop itself
journal cross-chain causal-pointer (REQ-044)   # a journal primitive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the requirements-traceability discipline here, because it is the part most teams skip. REQ-042 and REQ-044 were &lt;em&gt;re-anchored to the retained code&lt;/em&gt; — the rows didn't get deleted, they got re-pointed at the primitives that stayed. The agent's own rows (REQ-043/045/046/047/048/049) followed the agent into the leaf's docs. Traceability is a graph, and when you cut the code graph you have to cut the requirements graph along the same line or your "signed audit of everything" claim quietly develops holes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dependency edge
&lt;/h2&gt;

&lt;p&gt;This is the entire point of the exercise, in one picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        ┌───────────────────────────────────────────────┐
        │  bob-the-intendant  (LEAF — the application)   │
        │                                                │
        │   • github-watcher agent                       │
        │   • `bob watch` operator CLI                   │
        │   • template test packs                        │
        │   • J2 / run-governed-watch persona flow       │
        │                                                │
        │        imports  agp/src/... (contracts only)   │
        └───────────────────────┬───────────────────────┘
                                 │   pinned dependency
                                 │   ONE WAY — leaf → kernel
                                 ▼
        ┌───────────────────────────────────────────────┐
        │  agent-governance-plane  (KERNEL — frozen)     │
        │                                                │
        │   • trigger-source contract   (REQ-050)        │
        │   • runMediated() gov. loop   (REQ-042)        │
        │   • policy engine · Docker sandbox             │
        │   • Slack HITL · Ed25519 hash-chained journal  │
        │                                                │
        │        knows NOTHING about the watcher         │
        └───────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bob consumes AGP as a &lt;strong&gt;pinned dependency&lt;/strong&gt;. The watcher's imports were rewired from in-tree relative paths to &lt;code&gt;agp/src/...&lt;/code&gt;. There is no arrow back up. The kernel cannot see the leaf, cannot import the leaf, and does not know the leaf exists. That is not an accident of the current file layout — it is the property we spent the day buying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three tradeoffs, and why each went the way it did
&lt;/h2&gt;

&lt;p&gt;Every real extraction is a sequence of forks in the road. Three of them carried the design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Clean removal + a cross-repo pinned-dependency edge — over keeping the watcher as a flag-gated in-tree spike.&lt;/strong&gt; The tempting cheap move is to leave the watcher in AGP behind a feature flag and call it "disabled." We chose the harder cut. A flag-gated agent left in-tree keeps the boundary muddy: the code is still &lt;em&gt;there&lt;/em&gt;, still importable, still one careless &lt;code&gt;import&lt;/code&gt; away from the kernel depending on its own agent. The flag protects runtime behavior; it does nothing for the dependency graph. We wanted the kernel to physically shrink to contracts, so that the invariant is enforced by &lt;em&gt;absence&lt;/em&gt;, not by a boolean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Composing AGP as a pinned dependency — over vendoring or forking the kernel.&lt;/strong&gt; The frozen contracts and the trigger-source interface must be authored in exactly one place: AGP. If the leaf vendored or forked the kernel, those contracts would exist in two copies, and two copies of a contract drift — slowly, silently, and always at the worst time. Importing a pinned version means the leaf reads the contracts, never owns them. One author, one source of truth, one thing to version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Owning the watcher locally in the leaf — over importing &lt;code&gt;agp/src/triggers&lt;/code&gt;.&lt;/strong&gt; This is the mirror image of tradeoff 2, and it is what makes the cut &lt;em&gt;symmetric&lt;/em&gt;. After extraction the kernel no longer has the watcher modules, so the leaf cannot import them from AGP — they aren't there anymore. The leaf must own the watcher outright to build against the post-extraction kernel. Contracts: imported. Agent: owned. That split &lt;em&gt;is&lt;/em&gt; the boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it was a breaking change on both sides
&lt;/h2&gt;

&lt;p&gt;Extraction that touches a published surface is a breaking change by definition, and honest versioning has to say so.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On the kernel:&lt;/strong&gt; &lt;code&gt;agp watch&lt;/code&gt; was &lt;strong&gt;removed&lt;/strong&gt; from the AGP CLI. &lt;code&gt;BREAKING CHANGE&lt;/code&gt; — the governed watcher now ships in the leaf. Anyone who typed &lt;code&gt;agp watch&lt;/code&gt; has to move.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On the leaf:&lt;/strong&gt; the CLI was renamed &lt;code&gt;intendants → bob&lt;/code&gt;; the npm package was renamed &lt;code&gt;@intentsolutions/intendants → @intentsolutions/bob-the-intendant&lt;/code&gt;. &lt;code&gt;BREAKING CHANGE&lt;/code&gt; on both the binary name and the package name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You cannot smuggle a change like this through as a patch. The whole value of the extraction is that consumers can &lt;em&gt;reason&lt;/em&gt; about the kernel independently — and that reasoning depends on the kernel's version number telling the truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part everyone underestimates: it was a live migration
&lt;/h2&gt;

&lt;p&gt;Here is the detail worth teaching, because it is the one that bites. The watcher was not a dormant module. It was &lt;strong&gt;running in production&lt;/strong&gt; on a cron: &lt;code&gt;~/bin/intendants-release-watch.sh&lt;/code&gt;, firing on a schedule, in a governed loop, on a real repo.&lt;/p&gt;

&lt;p&gt;Extracting a &lt;em&gt;running&lt;/em&gt; component is a live-migration problem, not a code move. If you merge the kernel PR that removes &lt;code&gt;agp watch&lt;/code&gt; while production cron still calls &lt;code&gt;agp watch&lt;/code&gt;, you have — for however long it takes to notice — a production job pointed at &lt;a href="https://dev.to/posts/every-safety-gate-has-a-failure-direction/"&gt;a command that no longer exists&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So the order was inverted deliberately:&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;# 1. Repoint the production cron to the LEAF's command FIRST,&lt;/span&gt;
&lt;span class="c"&gt;#    while `agp watch` still exists in the merged kernel.&lt;/span&gt;
&lt;span class="c"&gt;#    ~/bin/intendants-release-watch.sh  →  `bob watch ...`&lt;/span&gt;

&lt;span class="c"&gt;# 2. THEN merge the kernel PR that removes `agp watch`.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At no instant was production pointed at a command that didn't exist. The cutover of the running process precedes the removal of the thing it used to call. This is the same expand/contract pattern database migrations use — you add the new path, move traffic, and only then remove the old path — applied to a CLI command instead of a column.&lt;/p&gt;

&lt;p&gt;The releases were coordinated to match:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AGP cut &lt;code&gt;v0.1.100&lt;/code&gt;&lt;/strong&gt; — the post-extraction clean kernel.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The leaf cut &lt;code&gt;v0.0.1&lt;/code&gt;, then &lt;code&gt;v0.0.2&lt;/code&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;During the work, the leaf pinned the post-extraction &lt;em&gt;commit&lt;/em&gt; of AGP; once the kernel PR merged, it &lt;strong&gt;repinned to the &lt;code&gt;v0.1.100&lt;/code&gt; release tag.&lt;/strong&gt; Pin to a commit while the edge is in flight; pin to a tag once the edge is real.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verification: link the evidence, don't say "verified"
&lt;/h2&gt;

&lt;p&gt;The whole claim of a governance kernel is auditability, so the extraction has to be auditable too. Both sides landed green.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kernel branch (post-extraction clean AGP):&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;bun run typecheck      0 errors
bun test               360 pass / 8 skip / 0 fail
coverage (lines)       94.46%   (gate ≥ 90)
coverage (funcs)       91.80%   (gate ≥ 88)
lint                   clean
claim-scan             PASS
doc-drift              PASS
audit-harness verify   OK
escape-scan            REFUSE=0 / CHALLENGE=0 / FLAG=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Leaf, built against the post-extraction kernel:&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;bun run typecheck      0 errors
bun test               52 pass / 0 fail  (10 files)
references to removed AGP modules:   ZERO
  (no `agp/src/triggers`, no `agp/src/cli/commands/watch`)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line is the acceptance test made mechanical. "Bob has zero references to removed AGP modules" is not a vibe — it is a grep that returns nothing. The kernel's side has the mirror check: nothing in AGP mentions the watcher. Two structural assertions, both green, and the invariant is proven rather than asserted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The generalizable principle
&lt;/h2&gt;

&lt;p&gt;Strip away the specific repos and here is what is left, for anyone building an agent framework, a policy layer, or any governance kernel that other things compose.&lt;/p&gt;

&lt;p&gt;If your governance kernel imports the agents it governs, three things break at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You cannot ship or version the kernel independently.&lt;/strong&gt; Its releases are hostage to every agent tangled into it; a change in one agent forces a kernel release, and the kernel's version number stops meaning anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You cannot reason about the kernel's trust boundary in isolation.&lt;/strong&gt; "Here is the small deterministic thing that decides and records" is only a true sentence if the small thing has a fixed edge. Import an agent and the boundary now wraps that agent's code too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every new agent widens the kernel's attack and verification surface.&lt;/strong&gt; The set of things you must audit to trust the kernel grows with every agent you add — which is exactly backwards, because the kernel is supposed to be the &lt;em&gt;fixed point&lt;/em&gt; you audit once. The watcher we just extracted was one such agent; every agent we &lt;em&gt;don't&lt;/em&gt; extract would fold exactly this cost back into the kernel, which is why the extraction is a repeatable discipline and not a one-off cleanup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Extracting agents into leaves keeps the kernel a small, independently-verifiable dependency with a stable trust boundary. That is not architectural neatness for its own sake — it is the entire reason to have a governance kernel in the first place. The one-way edge, agent → kernel, is what makes "a signed audit log of every tool call" a claim you can verify by looking at the kernel &lt;em&gt;alone&lt;/em&gt;, without first reading every agent that happens to use it. The direction of a single dependency arrow is the difference between a trust boundary and a wish.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>agents</category>
      <category>dependencymanagement</category>
      <category>governance</category>
    </item>
    <item>
      <title>Making a Fire-and-Forget Writer Safe Under Failure</title>
      <dc:creator>Jeremy Longshore</dc:creator>
      <pubDate>Mon, 13 Jul 2026 10:22:33 +0000</pubDate>
      <link>https://dev.to/jeremy_longshore/making-a-fire-and-forget-writer-safe-under-failure-521j</link>
      <guid>https://dev.to/jeremy_longshore/making-a-fire-and-forget-writer-safe-under-failure-521j</guid>
      <description>&lt;p&gt;There is a hook that distills a finished Claude Code session into a few durable learnings and proposes them to a shared team brain. It runs on &lt;code&gt;SessionEnd&lt;/code&gt;. Nobody watches it. That last sentence is the entire problem.&lt;/p&gt;

&lt;p&gt;The manual counterpart — a &lt;code&gt;/brain-save&lt;/code&gt; command a teammate types when they want to keep something — has been safe for months. Not because the write path was safe, but because a human was standing over it. A human notices when a capture fails. A human doesn't hit save twice in a panic. A human sees the error toast and shrugs. The forgiving hands were attached to a person, and the person was the safety system.&lt;/p&gt;

&lt;p&gt;An unattended hook has no hands. It fires when the session ends, whether or not the network is up, whether or not the same session already fired, whether or not one teammate's token has gone berserk. So the hook could not ship until the write path underneath it earned five properties. Skip any one and "convenient automation" becomes silent data loss, a duplicate flood, or governance drift nobody can see. Here is each property, why the obvious version of it was wrong, and the fifth one that governs the on-switch itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five properties
&lt;/h2&gt;

&lt;p&gt;Before an unattended capture hook is safe to turn on, its write path needs all five:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Idempotent intake&lt;/strong&gt; — retries collapse into a single record, never a duplicate flood.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable outbox&lt;/strong&gt; — a network blip queues the write instead of losing it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-actor backpressure&lt;/strong&gt; — one runaway identity can't flood the queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atomic governance receipts&lt;/strong&gt; — a state flip and its audit record commit together or not at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consent-gated opt-in&lt;/strong&gt; — the on-switch stays a deliberate human decision.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Property 1 — Idempotent intake
&lt;/h2&gt;

&lt;p&gt;A retry must not fan out into duplicates. This has two halves, because the client and the server can each betray you.&lt;/p&gt;

&lt;p&gt;The client half was the id. Team mode was minting candidate ids with &lt;code&gt;randomUUID()&lt;/code&gt;. A re-send — a flaky network retry, a hook that fires twice, a drained backlog item going back out — arrived as a brand-new row every time. The fix is to make the id a pure function of the content:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;// UUIDv5 over (tenant, title, content) — same input, same id, forever.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deriveCandidateId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6ba7b810-9dad-11d1-80b4-00c04fd430c8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// DNS namespace&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;content&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&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="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0x0f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mh"&gt;0x50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// version 5&lt;/span&gt;
  &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&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="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0x3f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mh"&gt;0x80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// RFC 4122 variant&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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;8&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&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="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deriving from &lt;code&gt;node:crypto&lt;/code&gt; SHA-1 keeps team mode dependency-free — no &lt;code&gt;uuid&lt;/code&gt; package pulled in for one function. The handler was extracted into an exported &lt;code&gt;capture()&lt;/code&gt; alongside &lt;code&gt;deriveCandidateId&lt;/code&gt;, &lt;code&gt;outboxDir&lt;/code&gt;, and &lt;code&gt;drainOutbox&lt;/code&gt;, so the whole path is unit-testable instead of buried in a request handler.&lt;/p&gt;

&lt;p&gt;The server half is the guard behind the id. &lt;code&gt;CandidateService.intake&lt;/code&gt; now calls &lt;code&gt;findByContentHashAndTenant(contentHash, tenantId)&lt;/code&gt; before it inserts. On a hit it returns the existing candidate and skips writing a second &lt;code&gt;proposed&lt;/code&gt; receipt, instead of inserting a duplicate row. The id and the server's content hash are two layers, not one key: the UUIDv5 guarantees a &lt;em&gt;retry&lt;/em&gt; re-sends the byte-identical row, while the tenant-scoped content-hash lookup is the server's own backstop, collapsing same-content proposals within a tenant even if two ids ever diverged. The id dedups retries; the hash dedups content.&lt;/p&gt;

&lt;p&gt;Why not reuse the repo's existing &lt;code&gt;findByContentHash&lt;/code&gt;? Because that one is cross-tenant — &lt;code&gt;WHERE content_hash = ? LIMIT 1&lt;/code&gt;, no tenant clause. Dedup against it and tenant A's re-send matches tenant B's identical row, leaking cross-tenant state and deduping to the wrong record. The new lookup is tenant-scoped on purpose. The behavior change is intended and narrow: a same-content re-send &lt;strong&gt;within&lt;/strong&gt; a tenant returns the existing candidate (201); identical content across two tenants stays two independent rows. The old test that asserted "POST accepts duplicate candidates" was rewritten to assert the new idempotent contract. store 224 and api 323 tests pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Property 2 — A durable outbox
&lt;/h2&gt;

&lt;p&gt;A network blip must not silently lose the write. Team mode had no spool fallback. Local mode already wrote to a spool via &lt;code&gt;writeToSpool&lt;/code&gt;; team mode, under an unattended hook, would throw on a dropped connection and the capture would evaporate. That was the one true "captures lost" blocker — a hard prerequisite for turning the hook on.&lt;/p&gt;

&lt;p&gt;Now a &lt;code&gt;fetch&lt;/code&gt; throw or a 5xx queues the candidate to a flat &lt;code&gt;~/.teamkb-outbox/&lt;/code&gt; directory (mode 700, override with &lt;code&gt;TEAMKB_OUTBOX_DIR&lt;/code&gt;) and reports &lt;code&gt;queued&lt;/code&gt; rather than an error. The next successful capture drains the backlog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;postCandidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&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;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;server-5xx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// 4xx: real rejection, surface it&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;drainOutbox&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                             &lt;span class="c1"&gt;// success → flush the backlog&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HttpError&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// never queue a 4xx&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;network&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;             &lt;span class="c1"&gt;// throw / 5xx → durable spool&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The direction of the split is the whole design. A 5xx or a thrown socket is transient — keep it and retry. A 4xx is a real validation, auth, or disclosure rejection — queuing it would loop forever against a request that will never be accepted, so it is surfaced immediately. Drop-on-4xx, keep-on-5xx. A permanently-invalid item can never wedge the outbox, and a transient outage never loses a capture.&lt;/p&gt;

&lt;p&gt;The two properties compose because of the id. Since the queued row carries its UUIDv5, the drain re-sends the &lt;em&gt;exact same&lt;/em&gt; row — same id — and Property 1's server guard collapses it back to one candidate. The outbox can retry as aggressively as it likes and never manufactures a near-duplicate. Six new tests in &lt;code&gt;remote-server.test.ts&lt;/code&gt; pin it down: id determinism and format, same-id-on-retry, outbox-on-throw, queue-on-5xx-not-4xx, drain-on-next-success, drain-stops-while-down. Full suite: 69 pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Property 3 — Per-actor backpressure
&lt;/h2&gt;

&lt;p&gt;One runaway identity must not flood the queue. A new capture-quota middleware — an &lt;code&gt;onRequest&lt;/code&gt; hook registered after auth on the intake path — caps how many candidates a single token identity (&lt;code&gt;request.actor&lt;/code&gt;) may POST to &lt;code&gt;/api/candidates&lt;/code&gt; per window. Default 60 per 60s, tunable via &lt;code&gt;captureQuotaMax&lt;/code&gt; and &lt;code&gt;captureQuotaWindowMs&lt;/code&gt;. Intake only; promote and reject stay admin-gated and uncapped; reads are untouched.&lt;/p&gt;

&lt;p&gt;The obvious answer is "there's already a rate limiter, use that." The existing limiter is per-source-IP, and on a tailnet that is worthless. Every teammate device is its own &lt;code&gt;100.x&lt;/code&gt; address, so a per-IP cap gives every token effectively its own private quota — there was no real per-token limit at all. One compromised token, one runaway loop, or one over-eager auto-capture hook could bury the inbox, and the only tripwire was a DB-size canary that doesn't warn until 200 rows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;onRequest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/candidates&lt;/span&gt;&lt;span class="dl"&gt;"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                         &lt;span class="c1"&gt;// follows the TOKEN, not the IP&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;quota&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="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;windowMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;capture quota&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;quota&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="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Making the limit follow the token instead of the network address is the fix. The test proves the (N+1)th intake gets a 429 while reads and other paths never touch the counter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Property 4 — Atomic governance receipts
&lt;/h2&gt;

&lt;p&gt;A state flip and its audit record must commit together or not at all. The nightly auto-govern sweep writes a single batch &lt;code&gt;governed&lt;/code&gt; receipt covering the candidates it processes. Some of those flips — &lt;code&gt;quarantine&lt;/code&gt; and &lt;code&gt;duplicate&lt;/code&gt; — have no individual receipt; the batch event is the &lt;em&gt;only&lt;/em&gt; on-chain record they exist. And they were autocommitting inside the loop, while the batch receipt got inserted afterward in a try/catch that logged to stderr on failure.&lt;/p&gt;

&lt;p&gt;Walk that failure through. The loop flips a candidate to &lt;code&gt;quarantine&lt;/code&gt; and commits. The batch receipt insert throws. The catch logs a line to stderr. Now the candidate is out of the inbox with no on-chain record of why it left. That is chain-invisible governance — silent drift, discoverable only by noticing something is missing.&lt;/p&gt;

&lt;p&gt;The fix defers the receipt-less flips and applies them in one non-swallowing transaction with the receipt insert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deferred&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="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;promoted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;applyPromotion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// already carries its own transactional receipt + memory&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="nx"&gt;deferred&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;             &lt;span class="c1"&gt;// quarantine / duplicate — no individual receipt&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;insertGovernedReceipt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batchEvent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// the ONLY record of the deferred flips&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;deferred&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;applyFlip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// commit them WITH the receipt&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;                                          &lt;span class="c1"&gt;// throws → everything rolls back, error propagates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;promoted&lt;/code&gt; stays in-loop deliberately, because a promoted row already writes its own transactional &lt;code&gt;promoted&lt;/code&gt; receipt and curated memory — it is never chain-invisible. On a receipt failure the quarantine and duplicate flips roll back, those candidates stay &lt;code&gt;inbox&lt;/code&gt; and get retried idempotently on the next run, the promoted rows persist with their receipts, and the error propagates so &lt;code&gt;runGovern&lt;/code&gt; surfaces it and the nightly wrapper's fail-loud path alerts. Atomic rollback over best-effort retry: a governance action you can't record is a governance action you don't take.&lt;/p&gt;

&lt;h2&gt;
  
  
  Property 5 — Consent-gated opt-in
&lt;/h2&gt;

&lt;p&gt;Four properties made the write path safe. The fifth governs the switch, and it is the one thing you cannot automate.&lt;/p&gt;

&lt;p&gt;The hook (&lt;code&gt;hooks/session-end-capture.mjs&lt;/code&gt;) is the automatic twin of manual &lt;code&gt;/brain-save&lt;/code&gt;. It carries three hard guards, and any unmet guard is a silent no-op: an opt-in marker file &lt;code&gt;~/.teamkb/autocapture.enabled&lt;/code&gt;, team mode configured, and not a recursive child. It runs the distiller detached — it never blocks or fails the session — and the distiller holds a &lt;strong&gt;member&lt;/strong&gt; token, so it can only propose to the inbox, never write durable state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;markerPath&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="c1"&gt;// (a) opt-in marker absent → no-op&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;teamModeConfigured&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="c1"&gt;// (b) team mode off → no-op&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEAMKB_RECURSIVE&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="c1"&gt;// (c) recursive child → no-op&lt;/span&gt;
&lt;span class="nf"&gt;spawnDetached&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;distiller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;memberTokenEnv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// detached, member-scoped, never blocks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why isn't this just a &lt;code&gt;plugin.json&lt;/code&gt; hooks entry?&lt;/strong&gt; Because a &lt;code&gt;plugin.json&lt;/code&gt;-declared hook auto-installs the instant the plugin installs. That is exactly the silent push that was forbidden — a teammate updates the plugin and their sessions start shipping distilled learnings to a shared brain they never agreed to feed. The constraint that shaped the design was blunt: &lt;em&gt;"I don't want them to ship it and be like wtf."&lt;/em&gt; So the hook is deliberately not plugin-declared. Installing the plugin enables nothing.&lt;/p&gt;

&lt;p&gt;The only way to turn it on is a consent-gated enable script (&lt;code&gt;hooks/enable-autocapture.mjs&lt;/code&gt;) that prints the full disclosure — what it does, what it does not do, how to pause, how to purge, and why — then requires typing &lt;code&gt;I CONSENT&lt;/code&gt;. Only then does it register the hook in the teammate's own &lt;code&gt;~/.claude/settings.json&lt;/code&gt; (backing the file up first) and write the marker. &lt;code&gt;--off&lt;/code&gt; pauses, &lt;code&gt;--off --purge&lt;/code&gt; deletes local logs, &lt;code&gt;--status&lt;/code&gt; shows state.&lt;/p&gt;

&lt;p&gt;Consent is the property you cannot make idempotent or durable. It has to be a deliberate human act, performed once, by the person whose sessions will be read. Even then the hook stays a silent no-op unless all three guards pass — and the distiller it spawns only does real work on a real transcript, so a hook that fires on an empty session burns nothing. A 5/5 hook-guard smoke test pins that whole no-op envelope against a stub &lt;code&gt;claude&lt;/code&gt; on PATH, with zero model invocation. Verification across the change: &lt;code&gt;node --check&lt;/code&gt; on both scripts, typecheck + lint + build exit 0, and &lt;code&gt;remote-server.test.ts&lt;/code&gt;'s 69 tests green — with store 224 / api 323 still clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boring bugs that would have eaten the captures anyway
&lt;/h2&gt;

&lt;p&gt;Two footguns fixed the same day, both the kind that lose data quietly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default DB path drift.&lt;/strong&gt; The API defaulted to &lt;code&gt;~/.teamkb/data/teamkb.db&lt;/code&gt; while the plugin and brain used &lt;code&gt;~/.teamkb/teamkb.db&lt;/code&gt;. A no-override deploy would write every teammate capture to a database the govern sweep never reads. Aligned the default via &lt;code&gt;resolveTeamKbPath('teamkb.db')&lt;/code&gt; — chosen over assert-and-warn, because a warning still loses the capture. The live deploy already overrides &lt;code&gt;TEAMKB_DB_PATH&lt;/code&gt;, so it was never hit; the next fresh deploy would have been.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No-op anchor skip.&lt;/strong&gt; &lt;code&gt;appendAnchor&lt;/code&gt; now returns the last record unchanged when both the chain head and the row count match the last anchor. A nightly run that changed nothing was still appending to the tamper-evidence log and to git history for zero evidentiary gain. A real new head still anchors — the never-rehash-the-chain rule holds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Also shipped
&lt;/h2&gt;

&lt;p&gt;The same "make an unattended watcher safe to cron" theme ran through the secondary repos. &lt;strong&gt;agent-governance-plane&lt;/strong&gt; got a release-watcher meaningfulness filter (draft and prerelease releases dropped by default, &lt;code&gt;includePrereleases&lt;/code&gt; to opt in — the skip runs &lt;em&gt;before&lt;/em&gt; tag validation, so a tagless GitHub draft is skipped rather than crashing the watcher), a notify-mode one-way webhook that needs no human in the loop, and a one-command installer plus an extraction-readiness ADR, with three irreversible doors — public flip, npm publish, external reply — kept explicitly human-gated. &lt;strong&gt;intentsolutions-vps-runbook&lt;/strong&gt; and &lt;strong&gt;intent-os&lt;/strong&gt; got a torn-datadir backup fix: consistent DB dumps plus a restore-verification drill, correcting an over-optimistic single-host restore claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern behind safe unattended writes
&lt;/h2&gt;

&lt;p&gt;A best-effort, fire-and-forget writer is a distributed system wearing a convenience costume. The moment the human leaves the loop, every property the human was silently providing has to be built into the write path: dedup so retries collapse, a spool so blips don't vanish, a per-actor cap so one identity can't flood, atomic receipts so governance can't drift invisibly, and a consent gate so the on-switch stays a human decision. Four of those you can engineer. The fifth you have to refuse to engineer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/adversarial-review-before-team-rollout/"&gt;Adversarial Review: The Six Lenses That Halted a Rollout&lt;/a&gt; — the governance review that gated this same team-brain rollout.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/every-safety-gate-has-a-failure-direction/"&gt;Every Safety Gate Has a Failure Direction&lt;/a&gt; — why drop-on-4xx / keep-on-5xx is a direction choice, not a detail.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/liveness-without-health-is-theater/"&gt;Liveness Without Health Is Theater&lt;/a&gt; — the sibling lesson for unattended systems: running is not the same as working.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;{&lt;br&gt;
  "&lt;a class="mentioned-user" href="https://dev.to/context"&gt;@context&lt;/a&gt;": "&lt;a href="https://schema.org" rel="noopener noreferrer"&gt;https://schema.org&lt;/a&gt;",&lt;br&gt;
  "@type": "BlogPosting",&lt;br&gt;
  "headline": "Making a Fire-and-Forget Writer Safe Under Failure",&lt;br&gt;
  "description": "Five properties an unattended capture hook needs before you turn it on: idempotent intake, durable outbox, backpressure, atomic receipts, consent gate.",&lt;br&gt;
  "author": {&lt;br&gt;
    "@type": "Person",&lt;br&gt;
    "name": "Jeremy Longshore"&lt;br&gt;
  },&lt;br&gt;
  "publisher": {&lt;br&gt;
    "@type": "Organization",&lt;br&gt;
    "name": "Start AI Tools"&lt;br&gt;
  },&lt;br&gt;
  "datePublished": "2026-07-11T10:00:00-05:00",&lt;br&gt;
  "dateModified": "2026-07-11T10:00:00-05:00",&lt;br&gt;
  "url": "&lt;a href="https://startaitools.com/posts/making-fire-and-forget-capture-safe-under-failure/" rel="noopener noreferrer"&gt;https://startaitools.com/posts/making-fire-and-forget-capture-safe-under-failure/&lt;/a&gt;",&lt;br&gt;
  "keywords": ["architecture", "idempotency", "ai-agents", "claude-code", "distributed-systems"],&lt;br&gt;
  "articleSection": "Technical Deep-Dive"&lt;br&gt;
}&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>idempotency</category>
      <category>aiagents</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>Liveness Without Health Is Theater</title>
      <dc:creator>Jeremy Longshore</dc:creator>
      <pubDate>Mon, 13 Jul 2026 10:21:03 +0000</pubDate>
      <link>https://dev.to/jeremy_longshore/liveness-without-health-is-theater-4hdl</link>
      <guid>https://dev.to/jeremy_longshore/liveness-without-health-is-theater-4hdl</guid>
      <description>&lt;p&gt;Every team that runs scheduled work eventually ships the same bug, and it does&lt;br&gt;
not look like a bug. A cron job, a nightly workflow, a backup timer — something&lt;br&gt;
that is supposed to run on a clock and quietly do useful work. To keep an eye on&lt;br&gt;
it, someone adds a heartbeat: each time the job runs, it touches a file, pings a&lt;br&gt;
status endpoint, or emits a "still alive" event. A sweep watches those beats and&lt;br&gt;
pages when one goes stale. The dashboard is green. Everyone moves on.&lt;/p&gt;

&lt;p&gt;Then one morning the useful work has not happened for three weeks, and the&lt;br&gt;
dashboard is still green.&lt;/p&gt;
&lt;h2&gt;
  
  
  The monitor that lies
&lt;/h2&gt;

&lt;p&gt;Liveness proves a scheduled job &lt;em&gt;ran&lt;/em&gt;. Health proves it &lt;em&gt;succeeded&lt;/em&gt;. A&lt;br&gt;
liveness-only heartbeat fires on every invocation regardless of outcome, so it&lt;br&gt;
hides the one failure that matters most: a job that runs on schedule and&lt;br&gt;
silently does nothing useful. Two independent markers restore the missing&lt;br&gt;
distinction — is the system stopped, failing, or actually working?&lt;/p&gt;

&lt;p&gt;The failure is structural, not incidental. A heartbeat that fires on &lt;em&gt;every&lt;/em&gt;&lt;br&gt;
invocation — before the work runs, or regardless of how the work exits —&lt;br&gt;
collapses two very different facts into one signal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The job ran&lt;/strong&gt; (the scheduler fired, the process started).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The job succeeded&lt;/strong&gt; (the work it exists to do actually happened).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A liveness-only heartbeat attests only to the first. So the single worst failure&lt;br&gt;
mode of any scheduled system — &lt;em&gt;a job that runs forever while silently doing&lt;br&gt;
nothing useful&lt;/em&gt; — is precisely the mode it is blind to: the process starts on&lt;br&gt;
schedule, the beat fires like clockwork, and inside it the real work is throwing,&lt;br&gt;
short-circuiting, or writing to the wrong place. Liveness is perfect, health is&lt;br&gt;
zero, and the monitor reports the metric it can measure while staying silent on&lt;br&gt;
the one that matters.&lt;/p&gt;

&lt;p&gt;This is not a monitoring gap. A gap implies something is unwatched. This is&lt;br&gt;
worse: the thing is watched, the watch is green, and the green is a lie. That is&lt;br&gt;
theater — the &lt;em&gt;appearance&lt;/em&gt; of oversight standing in for the substance of it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why heartbeats lie
&lt;/h2&gt;

&lt;p&gt;The flaw is in &lt;em&gt;where&lt;/em&gt; the beat is written. Touch the heartbeat first and&lt;br&gt;
unconditionally — &lt;code&gt;touch job.heartbeat; do_the_work&lt;/code&gt;, with nobody checking &lt;code&gt;$?&lt;/code&gt; —&lt;br&gt;
and the beat is decoupled from the outcome. The exit code, the one piece of&lt;br&gt;
information that distinguishes "worked" from "ran," is thrown away. To the sweep,&lt;br&gt;
a job that succeeds and a job that fails emit the identical signal. Writing the&lt;br&gt;
beat in a &lt;code&gt;finally&lt;/code&gt; block, or last thing before exit, has the same effect.&lt;/p&gt;

&lt;p&gt;The fix is not a better heartbeat. It is a &lt;em&gt;second&lt;/em&gt; marker.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two markers, not one
&lt;/h2&gt;

&lt;p&gt;Separate the two facts into two files. Emit a &lt;strong&gt;liveness beat&lt;/strong&gt; on every&lt;br&gt;
invocation. Write a &lt;strong&gt;health mark&lt;/strong&gt; only when the work exits clean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c"&gt;# run-job.sh — wrap any scheduled job with a two-marker protocol.&lt;/span&gt;
&lt;span class="nv"&gt;STATE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;STATE_DIR&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;/var/lib/job-markers&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;shift

&lt;/span&gt;&lt;span class="nv"&gt;beat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$job&lt;/span&gt;&lt;span class="s2"&gt;.beat"&lt;/span&gt;
&lt;span class="nv"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$job&lt;/span&gt;&lt;span class="s2"&gt;.ok"&lt;/span&gt;

&lt;span class="c"&gt;# Liveness: fires on EVERY run, before we know the outcome.&lt;/span&gt;
&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%s &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$beat&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Do the actual work and capture its verdict.&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;

&lt;span class="c"&gt;# Health: refreshed ONLY when the work truly succeeded.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$rc&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%s &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ok&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$rc&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sweep no longer reads a single file — it reads the &lt;strong&gt;pair&lt;/strong&gt;, and the&lt;br&gt;
relationship between the two ages is the diagnosis:&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;# For each job, the two ages TOGETHER are the verdict (stale = older than one hour).&lt;/span&gt;
&lt;span class="nv"&gt;beat_age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; now &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$job&lt;/span&gt;&lt;span class="s2"&gt;.beat"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;
&lt;span class="nv"&gt;ok_age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; now &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$job&lt;/span&gt;&lt;span class="s2"&gt;.ok"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;0&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$beat_age&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stale&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nv"&gt;verdict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;STOPPED   &lt;span class="c"&gt;# not running at all&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ok_age&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;   &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stale&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nv"&gt;verdict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;FAILING   &lt;span class="c"&gt;# running, not succeeding&lt;/span&gt;
&lt;span class="k"&gt;else                                    &lt;/span&gt;&lt;span class="nv"&gt;verdict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;HEALTHY    &lt;span class="c"&gt;# ran AND worked&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole point lives in that state table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Beat marker&lt;/th&gt;
&lt;th&gt;Health marker&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;stale&lt;/td&gt;
&lt;td&gt;(any)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;STOPPED&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The scheduler is not firing the job at all.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fresh&lt;/td&gt;
&lt;td&gt;stale&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;FAILING&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The job runs on schedule but keeps exiting non-zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fresh&lt;/td&gt;
&lt;td&gt;fresh&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;HEALTHY&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;It ran &lt;em&gt;and&lt;/em&gt; it worked.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The middle row is the whole point: &lt;code&gt;fresh beat + stale ok&lt;/code&gt; is the silent failure&lt;br&gt;
made loud — a job running forever doing nothing, now a first-class alert. The&lt;br&gt;
same signal-pair instinct shows up wherever automation has to police itself: a&lt;br&gt;
&lt;a href="https://startaitools.com/posts/bot-loop-circuit-breaker-multi-agent-slack/" rel="noopener noreferrer"&gt;circuit breaker for a runaway bot loop&lt;/a&gt;&lt;br&gt;
trips on the gap between "still active" and "still making progress."&lt;/p&gt;
&lt;h3&gt;
  
  
  The seeding gotcha
&lt;/h3&gt;

&lt;p&gt;One rollout trap: the day the protocol ships, every job has a beat file but no&lt;br&gt;
&lt;code&gt;.ok&lt;/code&gt; yet, so the first sweep reads every health age as infinite and flags&lt;br&gt;
&lt;em&gt;everything&lt;/em&gt; FAILING — a wall of false alarms that trains everyone to ignore the&lt;br&gt;
new alert on day one. Seed each health marker from the beat that already exists&lt;br&gt;
(&lt;code&gt;[ -f "$ok" ] || cp "$beat" "$ok"&lt;/code&gt;), on the assumption that currently-live jobs&lt;br&gt;
are healthy until proven otherwise.&lt;/p&gt;
&lt;h2&gt;
  
  
  Watch the watchers
&lt;/h2&gt;

&lt;p&gt;Two markers fix the job. But the sweep, the alert path, and the dashboard are&lt;br&gt;
themselves software — usually written once and never touched again. An unwatched&lt;br&gt;
watcher is not oversight; it is a more expensive blind spot. Meta-monitoring —&lt;br&gt;
observability turned on the watchers themselves — means treating every layer of&lt;br&gt;
the stack as something that can also fail silently.&lt;/p&gt;
&lt;h3&gt;
  
  
  Canary the alert path
&lt;/h3&gt;

&lt;p&gt;The most dangerous single point of failure is the notification channel, because&lt;br&gt;
its failure is invisible by construction: a broken alert path produces &lt;em&gt;no&lt;br&gt;
alerts&lt;/em&gt;, which is indistinguishable from &lt;em&gt;nothing wrong&lt;/em&gt;. The only way to know a&lt;br&gt;
page can get out is to send one and require proof it arrived. Probe the&lt;br&gt;
credentials, POST to the chat webhook, and demand a real &lt;code&gt;200&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;#!/bin/sh&lt;/span&gt;
&lt;span class="c"&gt;# alert-canary.sh — prove a page can actually get out.&lt;/span&gt;
&lt;span class="c"&gt;# Run this UNDER run-job.sh, so its own success/failure is two-marked.&lt;/span&gt;
: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WEBHOOK_URL&lt;/span&gt;:?alert&lt;span class="p"&gt; webhook not configured&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{http_code}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WEBHOOK_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"text":"alert-path canary — ignore"}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$code&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"alert path DOWN (HTTP &lt;/span&gt;&lt;span class="nv"&gt;$code&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1   &lt;span class="c"&gt;# non-zero → run-job.sh leaves .ok stale → the sweep sees a dead path&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recursion is deliberate. The canary is wrapped by the same two-marker&lt;br&gt;
runner, so a failed canary refuses to refresh its own health mark, and the sweep&lt;br&gt;
that watches everything else now watches the watcher. On platforms with a service&lt;br&gt;
manager, back it with a native failure hook so a crash that never reaches the&lt;br&gt;
canary still escalates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# backup.service.d/onfailure.conf — escalate straight from the service manager.
&lt;/span&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="c"&gt;# alert@%n.service is a templated oneshot unit that runs the notifier.
# NOTE: systemd has no inline comments — everything after '=' is the value,
# so this explanation must live on its own line, never trailing the directive.
&lt;/span&gt;&lt;span class="py"&gt;OnFailure&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;alert@%n.service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reconcile the registry
&lt;/h3&gt;

&lt;p&gt;A sweep can only watch jobs it knows about, and the list of "everything that&lt;br&gt;
should be running" is itself state that drifts as jobs get added, renamed, and&lt;br&gt;
retired. A reconcile job closes the gap: diff the live inventory against the&lt;br&gt;
declared registry and report orphans both ways — things running that nobody&lt;br&gt;
registered, and things registered that no longer run.&lt;/p&gt;

&lt;p&gt;A healthy first run is &lt;em&gt;ugly&lt;/em&gt;. One such diff, run against a live fleet, surfaced&lt;br&gt;
61 orphaned jobs among 138 live automations — things executing for months with&lt;br&gt;
no registry entry, watched by nothing. That is the normal starting state, not an anomaly;&lt;br&gt;
driving it to zero is what makes "the sweep watches everything" true instead of&lt;br&gt;
assumed. The same instinct grows the sweep over time — one system's check count&lt;br&gt;
climbed from 19 to 32 as each new class of silent failure earned its own&lt;br&gt;
assertion.&lt;/p&gt;
&lt;h3&gt;
  
  
  Assert log freshness
&lt;/h3&gt;

&lt;p&gt;A status dashboard that &lt;code&gt;cat&lt;/code&gt;s a log and renders it is another liveness-only lie:&lt;br&gt;
it proves the log &lt;em&gt;exists&lt;/em&gt;, not that it is &lt;em&gt;current&lt;/em&gt;. A pipeline can wedge at&lt;br&gt;
2 a.m. and the dashboard will display yesterday's final line forever. Assert the&lt;br&gt;
log's freshness, not merely its contents:&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;# A dashboard must prove the log is CURRENT, not just that it has bytes.&lt;/span&gt;
&lt;span class="nv"&gt;log&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/pipeline.log
&lt;span class="nv"&gt;max_age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;900   &lt;span class="c"&gt;# 15 minutes&lt;/span&gt;

&lt;span class="nv"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; %Y &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$log&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$age&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$max_age&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"STALE: newest log line is &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;age&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s old — the dashboard is quoting a corpse"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Two root causes worth naming
&lt;/h2&gt;

&lt;p&gt;Two independent production incidents made the abstract argument concrete, and&lt;br&gt;
both traced to causes that generalize well beyond any one system.&lt;/p&gt;
&lt;h3&gt;
  
  
  Deploy-by-reference into mutable working trees
&lt;/h3&gt;

&lt;p&gt;Both incidents shared one mechanism: a cron job executed &lt;em&gt;whatever branch a&lt;br&gt;
shared git working tree happened to be parked on&lt;/em&gt; — a bare symlink into a&lt;br&gt;
directory that humans and other automation also checked out and reset. In one, a&lt;br&gt;
customer-facing uptime monitor ran an old, unarmed version of itself and posted&lt;br&gt;
false positives every day, because the tree sat on a stale branch — and it&lt;br&gt;
exited zero the whole time, so both a liveness beat &lt;em&gt;and&lt;/em&gt; an exit-code health&lt;br&gt;
mark would have called it perfectly healthy while it lied to users. In another, a&lt;br&gt;
static-site publishing pipeline only &lt;em&gt;appeared&lt;/em&gt; to run nightly — its lone&lt;br&gt;
"heartbeat" was the tree being parked on a feature branch overnight; the run it&lt;br&gt;
seemed to complete never happened. A missed run that was never missed, because&lt;br&gt;
the signal proving it ran was an artifact of the deploy mechanism, not the work.&lt;/p&gt;

&lt;p&gt;The rule that falls out is blunt: &lt;strong&gt;crons run deployed copies or&lt;br&gt;
verified-main paths — never a bare symlink into a working tree.&lt;/strong&gt; A working tree&lt;br&gt;
is mutable by definition; anything that reads code from one has no idea what code&lt;br&gt;
it is about to run.&lt;/p&gt;
&lt;h3&gt;
  
  
  Inherited file descriptors are state
&lt;/h3&gt;

&lt;p&gt;The second cause is subtler and produced weeks of silent underperformance with&lt;br&gt;
zero errors. A push job serialized itself with &lt;code&gt;flock&lt;/code&gt; on a lock file held open&lt;br&gt;
as a file descriptor, then spawned a long-lived daemon that &lt;em&gt;inherited that&lt;br&gt;
descriptor&lt;/em&gt; — and with it the lock, held for the daemon's entire multi-hour&lt;br&gt;
lifetime. A job scheduled every 20 minutes was throttled to roughly once every&lt;br&gt;
two hours — a sixfold collapse in cadence — silently, for weeks. Nothing failed:&lt;br&gt;
every blocked run exited cleanly on "another run holds the lock." The fix is one&lt;br&gt;
character of intent — close the fd on the child:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c"&gt;# push-cron.sh — serialize with flock, but DON'T leak the lock to children.&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;9&amp;gt;/var/lock/push.lock
flock &lt;span class="nt"&gt;-n&lt;/span&gt; 9 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"another run holds the lock; exiting"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# The daemon must NOT inherit fd 9, or it holds the lock for its whole&lt;/span&gt;
&lt;span class="c"&gt;# lifetime and throttles every future run of this cron.&lt;/span&gt;
long_lived_daemon 9&amp;gt;&amp;amp;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lesson is the thesis restated from a different angle: &lt;strong&gt;silence is not&lt;br&gt;
success.&lt;/strong&gt; A job that exits zero on a lock it should never have been blocked by&lt;br&gt;
looks identical, to a liveness monitor, to a job doing perfect work — and a&lt;br&gt;
health mark keyed only to the exit code has the very same blind spot here, since&lt;br&gt;
every throttled run's &lt;code&gt;rc&lt;/code&gt; was zero. Catching this one needs the third leg from&lt;br&gt;
the last section: assert that the &lt;em&gt;work product&lt;/em&gt; is fresh — that the push&lt;br&gt;
actually landed — not merely that the process exited clean. It is the same&lt;br&gt;
discipline behind &lt;a href="https://startaitools.com/posts/crash-durable-replies-loss-proof/" rel="noopener noreferrer"&gt;durable replies that survive a crash&lt;/a&gt;:&lt;br&gt;
prove the work persisted; never infer it from a clean return code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The discipline
&lt;/h2&gt;

&lt;p&gt;None of this is exotic — it is one idea applied consistently across every layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring is software.&lt;/strong&gt; It has bugs, it rots, and it fails silently like
any other code. Test it, canary it, and give it the same
&lt;a href="https://startaitools.com/posts/adversarial-review-before-team-rollout/" rel="noopener noreferrer"&gt;adversarial skepticism&lt;/a&gt;
you would aim at the systems it watches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every layer must distinguish &lt;em&gt;ran&lt;/em&gt; from &lt;em&gt;succeeded&lt;/em&gt;.&lt;/strong&gt; One marker answers
"did it run?" A second, written only on a clean exit, answers "did it work?"
The gap between them is where the expensive failures hide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor the monitors.&lt;/strong&gt; Canary the alert path so a dead pager pages itself.
Reconcile the registry so the sweep watches everything, not just what someone
remembered to register. Assert log freshness so a dashboard cannot quote a
corpse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do this and the word &lt;em&gt;green&lt;/em&gt; changes meaning. It stops saying "the cron fired"&lt;br&gt;
and starts saying "the work happened." That is the only definition of green&lt;br&gt;
worth trusting — and the only one a heartbeat, alone, can never give you.&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "&lt;a class="mentioned-user" href="https://dev.to/context"&gt;@context&lt;/a&gt;": "&lt;a href="https://schema.org" rel="noopener noreferrer"&gt;https://schema.org&lt;/a&gt;",&lt;br&gt;
  "@type": "BlogPosting",&lt;br&gt;
  "headline": "Liveness Without Health Is Theater",&lt;br&gt;
  "description": "A heartbeat that fires on every run proves a job ran, never that it succeeded — the fix is two markers plus meta-monitoring the watchers.",&lt;br&gt;
  "url": "&lt;a href="https://startaitools.com/posts/liveness-without-health-is-theater/" rel="noopener noreferrer"&gt;https://startaitools.com/posts/liveness-without-health-is-theater/&lt;/a&gt;",&lt;br&gt;
  "datePublished": "2026-07-10T08:00:00-05:00",&lt;br&gt;
  "author": { "@type": "Person", "name": "Jeremy Longshore" },&lt;br&gt;
  "publisher": { "@type": "Organization", "name": "Start AI Tools" },&lt;br&gt;
  "mainEntityOfPage": {&lt;br&gt;
    "@type": "WebPage",&lt;br&gt;
    "&lt;a class="mentioned-user" href="https://dev.to/id"&gt;@id&lt;/a&gt;": "&lt;a href="https://startaitools.com/posts/liveness-without-health-is-theater/" rel="noopener noreferrer"&gt;https://startaitools.com/posts/liveness-without-health-is-theater/&lt;/a&gt;"&lt;br&gt;
  },&lt;br&gt;
  "articleSection": "DevOps",&lt;br&gt;
  "keywords": "monitoring, reliability, devops, observability, cron"&lt;br&gt;
}&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>reliability</category>
      <category>devops</category>
      <category>observability</category>
    </item>
    <item>
      <title>Adversarial Review: The Six Lenses That Halted a Rollout</title>
      <dc:creator>Jeremy Longshore</dc:creator>
      <pubDate>Sun, 12 Jul 2026 10:21:51 +0000</pubDate>
      <link>https://dev.to/jeremy_longshore/adversarial-review-the-six-lenses-that-halted-a-rollout-2im5</link>
      <guid>https://dev.to/jeremy_longshore/adversarial-review-the-six-lenses-that-halted-a-rollout-2im5</guid>
      <description>&lt;p&gt;"We shipped the safety work" is a feeling, not a fact. Before you hand a shared, governed system to a team, the only thing that converts that feeling into the truth is a structured adversarial review that verifies claims against live state — not against the design doc, and not against the diff.&lt;/p&gt;

&lt;p&gt;On 2026-07-09 that review ran against an internal team knowledge system being prepared to open to six people all at once. It validated two judgment calls, broke three, and produced a list of eighteen real risks the plan had not named. The verdict: do not go all-at-once, and do not email anyone a token, until the first gate clears.&lt;/p&gt;

&lt;p&gt;That halt was the correct output. This is the story of the method that produced it, the three assumptions it demolished, and the fixes that shipped the same day — every one of which enforces a single boundary: &lt;strong&gt;the model proposes; the deterministic system owns identity, trust, and durable state.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The system, in one paragraph
&lt;/h2&gt;

&lt;p&gt;The subject is a "second brain": a governed team knowledge base. Content is captured, run through a deterministic govern pipeline, and — if it survives the gates — promoted into durable memory. Every state change writes a receipt into an append-only, hash-chained ledger, so the store is tamper-evident: you can recompute the chain and detect any row that was altered out of band. Two surfaces front it. A private, tailnet-bound HTTP API is the control plane — it holds the write-gate, the tenant guard, and the audit-actor stamp. A Claude Code plugin (an MCP server) is how people actually talk to it from their editor. Six teammates were about to get access: two admins, four members.&lt;/p&gt;

&lt;h2&gt;
  
  
  What shipped, and why it wasn't enough
&lt;/h2&gt;

&lt;p&gt;A "safety before people" pass had already landed. It was real work, not theater: bearer tokens were hashed at rest instead of stored as plaintext; per-user tokens were minted so each teammate carried a distinct identity; an onboarding runbook was written; several hardening items were verified against the running service. On paper, the box marked "make it safe for a team" was checked.&lt;/p&gt;

&lt;p&gt;The instinct to stop there is the trap. A safety pass tells you what you &lt;em&gt;did&lt;/em&gt;. It does not tell you what you &lt;em&gt;have&lt;/em&gt; — the residual risk, the assumptions you smuggled in, the interactions between a fix and the rest of the system. Those only surface when someone hostile to the plan goes and looks at the live thing. So before flipping the switch, that is exactly what happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The review that verified against live state
&lt;/h2&gt;

&lt;p&gt;An adversarial review checks claims against the running system, not the design doc and not the diff. Independent reviewers each inspect the live service, its databases, and its deployment through a different lens — security, operations, reliability, integrity, rollout, threat model — so an assumption that is locally true but globally false has nowhere to hide.&lt;/p&gt;

&lt;p&gt;Six independent engineer agents ran in parallel on the Fable model. Each got the same design brief and one distinct lens. The instruction that made the difference was not "review the plan." It was &lt;strong&gt;verify against real code and live state&lt;/strong&gt;: read the repos, query the live databases, recompute the hash chain yourself, check org membership yourself, inspect the running service unit yourself. A review that only reads a diff can confirm the diff does what it says. It cannot catch a claim that is locally true and globally false — and all three of the broken assumptions were exactly that shape.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lens&lt;/th&gt;
&lt;th&gt;Question it was forced to answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data integrity &amp;amp; correctness&lt;/td&gt;
&lt;td&gt;Recompute every hash. Does the store actually verify? Can any path write a durable row without its receipt?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security &amp;amp; secrets&lt;/td&gt;
&lt;td&gt;Where do the still-live secrets exist, in every location, including backups?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment &amp;amp; operations&lt;/td&gt;
&lt;td&gt;What does the service actually run from? What happens on crash-restart or rollback?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliability &amp;amp; concurrency&lt;/td&gt;
&lt;td&gt;Who can write concurrently, and what lock — if any — serializes them?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollout completeness &amp;amp; teammate UX&lt;/td&gt;
&lt;td&gt;What does a real member experience on a bad token, a dead API, or off-network?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adversarial / threat model&lt;/td&gt;
&lt;td&gt;Assume a leaked member token. What can it forge, escalate, or clear?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Independence is the other half of the method, and it is not optional. One reviewer with a six-item checklist shares one set of blind spots across all six items — if their mental model of the system is wrong in a given place, it is wrong for every lens they apply. Six agents that never see each other's work cannot collude into a shared assumption. The security lens does not know the reliability lens exists, so it has no reason to defer to it, and that is exactly why one of them looked at the backups while another looked at the write lock. Deduplication happens &lt;em&gt;after&lt;/em&gt; independent discovery, never before.&lt;/p&gt;

&lt;p&gt;The integrity lens recomputed all 2,186 candidate content hashes rather than trusting the "verified" label. The security lens enumerated every place a live secret's plaintext could exist rather than trusting "we hashed the file."&lt;/p&gt;

&lt;p&gt;That is the entire discipline: independent lenses, each grounded in the running system. It cost six agent-runs and the humility to let them contradict the plan. It was worth it, because it broke three things everyone believed were done.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the review left standing
&lt;/h2&gt;

&lt;p&gt;Before the breakage, credit where the design earned it — because a review that only ever confirms your fears is as miscalibrated as one that only ever flatters you. The review's two headline validations were calls of &lt;em&gt;restraint&lt;/em&gt;: it endorsed holding the riskiest feature — the auto-govern path that drives broken call #3 below — as design-only rather than rushing it into the rollout, and it blessed the sequencing that checked migration safety before restarting the live service. In both cases, not shipping yet was the correct instinct.&lt;/p&gt;

&lt;p&gt;Two foundational design bets also held up under every lens, and they are worth naming because the fixes below lean on them.&lt;/p&gt;

&lt;p&gt;The first is &lt;strong&gt;per-user token identities&lt;/strong&gt;. Minting a distinct token per teammate, rather than a shared team secret, looked like extra onboarding friction at the time. It is what makes the entire server-side intake override (below) possible: every request carries a resolvable actor, so the server has something to re-derive trust and authorship &lt;em&gt;from&lt;/em&gt;. A shared secret would have left it nothing to distinguish — you cannot own identity you cannot tell apart. The friction bought the security model.&lt;/p&gt;

&lt;p&gt;The second is the &lt;strong&gt;append-only, hash-chained receipt ledger&lt;/strong&gt; itself. The integrity lens recomputed the whole chain and confirmed it does what it claims: alter any promoted row out of band and the recomputation detects it. The atomicity fix that follows does not replace that design — it &lt;em&gt;protects&lt;/em&gt; it, by closing the one window where a durable row could exist without its receipt. A weaker audit design would have had nothing worth protecting.&lt;/p&gt;

&lt;p&gt;Naming what held is not politeness. It is calibration: it tells you the review's "broken" verdicts are signal, not a reviewer reflexively torching everything to look thorough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three calls the review broke
&lt;/h2&gt;

&lt;p&gt;This is the spine of the story. Two shipped decisions survived scrutiny; three did not — and the three that fell are the transferable lessons, because each was a locally reasonable call that the live state proved wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Broken call #1: hashing in place is not rotation
&lt;/h3&gt;

&lt;p&gt;The safety pass hashed the existing tokens &lt;em&gt;in place&lt;/em&gt;. It kept the same secret values and just stored their hashes instead of their plaintext, specifically to avoid the churn of re-issuing tokens to everyone. Locally, that reasoning is sound: the credential file no longer holds plaintext, so a read of that file at rest yields nothing usable.&lt;/p&gt;

&lt;p&gt;The security lens went and looked at &lt;em&gt;every&lt;/em&gt; place those secret values lived. Retained encrypted backups — one local, one off-host, and at least one target with no retention limit — already held the &lt;strong&gt;plaintext&lt;/strong&gt; of those same still-live secrets, captured before the hashing change. Hashing the file protected it going forward, at rest, in one location. It did nothing for a secret whose plaintext sits in a backup you can restore.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Re-hashing a value you have already exposed is not protection. Retiring an exposed secret requires &lt;strong&gt;credential rotation&lt;/strong&gt; — a genuinely new value — &lt;strong&gt;plus purging the exposure&lt;/strong&gt;. If the plaintext still exists anywhere you can restore from, the old secret is live, and hashing its current home is a false sense of safety.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The fix was not "hash harder." It was rotate the tokens to new values and treat the pre-hash backups as compromised. Hash-at-rest was necessary; it was not sufficient, and believing it was sufficient is precisely the failure a live-state review exists to catch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Broken call #2: local mode is a single-writer design
&lt;/h3&gt;

&lt;p&gt;To let the system's founder query the brain from any editor session, the plugin was enabled in user-scope &lt;em&gt;local mode&lt;/em&gt;. Convenient, and locally reasonable: one person, their own machine, direct access. The reliability lens asked the question the plan never did — what does local mode actually &lt;em&gt;do&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Local mode runs in-process as owner/admin. It does not go through the HTTP API. That means it bypasses the entire control plane: the write-gate, the tenant guard, and the audit-actor stamp all live on the API surface, and local mode is a side door around all three. Worse, "any editor session" is not one writer. Up to roughly eleven concurrent interactive sessions became &lt;strong&gt;unlocked writers&lt;/strong&gt; — none of them taking the single-writer file lock that the nightly cron jobs depend on to serialize their writes.&lt;/p&gt;

&lt;p&gt;The concrete failure: a backup taken mid-write, with no lock held, can capture a torn state. Restore it and the brain's own tamper-evidence machinery reports &lt;strong&gt;TAMPER DETECTED&lt;/strong&gt; — not because anyone tampered with anything, but because the hash chain was snapshotted between two writes. The safety mechanism fires on its own operator.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A design built for one writer and one user silently becomes a concurrency-and-authorization surface the instant you make it multi-session. The reach goal — "query from anywhere" — was right. Routing it &lt;em&gt;around&lt;/em&gt; the single writer was wrong. It should have gone &lt;em&gt;through&lt;/em&gt; the API's single writer, inheriting the write-gate, the tenant guard, the audit stamp, and the lock.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Broken call #3: deleting durable rows needs a data-classification check and an all-consumers review
&lt;/h3&gt;

&lt;p&gt;A planned feature — the next thing on the roadmap — would DELETE candidate proposals after they were governed. The reasoning read cleanly if you looked only at the write path: the candidate has been governed, its outcome is recorded, so the row is spent; delete it to keep the table lean.&lt;/p&gt;

&lt;p&gt;The integrity lens read the &lt;em&gt;data-classification doc&lt;/em&gt; and &lt;em&gt;every consumer&lt;/em&gt; of that table, not just the code that does the delete. The candidates table is documented as insert-only, immutable, and a &lt;strong&gt;non-reproducible source of truth&lt;/strong&gt;. Three consumers depend on that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Provenance.&lt;/strong&gt; Remote captures write nowhere else. The candidate row is the &lt;em&gt;only&lt;/em&gt; copy of what was seen. Delete it and the provenance back-link points at nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The human-review queue&lt;/strong&gt; depends on flagged and rejected candidates &lt;em&gt;staying in place&lt;/em&gt; so a person can adjudicate them later. Delete-on-govern empties the queue out from under the reviewer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-ingest idempotency.&lt;/strong&gt; The nightly re-ingest path dedupes against that table. Delete a governed row and the next night re-ingests it as new, re-rejects it, and deletes it again — a permanent nightly re-ingest/re-reject loop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The plan proposed a "second run is a no-op" test to prove safety. That test would have &lt;strong&gt;passed while missing the loop entirely&lt;/strong&gt;, because it seeded the &lt;em&gt;table&lt;/em&gt; rather than the upstream spool files — so the dedupe it exercised was not the dedupe the loop breaks. A green test against the wrong fixture is worse than no test; it launders the bug.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A design that deletes durable rows must be checked against the data-classification doc &lt;em&gt;and&lt;/em&gt; every consumer of that data — review queue, re-ingest idempotency, provenance back-links — not just the code path that performs the write. "Is this row still needed by the thing that wrote it?" is the wrong question. "Who else reads this row, and what is the only copy of it?" is the right one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The 18-risk register, gated
&lt;/h2&gt;

&lt;p&gt;Deduplicated across the six engineers, the findings collapsed to eighteen distinct risks. Where two lenses hit the same issue, severity was taken as the &lt;strong&gt;max&lt;/strong&gt; across engineers — you do not average a "critical" with a "medium" and call it "high." A flat wall of eighteen findings is not actionable; it is a demoralizing to-do list with no critical path. So the register was &lt;strong&gt;gated&lt;/strong&gt; into three tiers, and the gating is what made it usable.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gate&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Representative risks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gate 0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Must clear &lt;em&gt;before any teammate onboards or any token email is sent&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;Rotate exposed secrets + purge backups; hash tokens at rest; immutable deploy with lockout guard; durable revoke-by-actor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gate 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Must clear &lt;em&gt;before the auto-govern feature ships&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;Server-side candidate-intake override; atomic promotion; the delete-on-govern redesign&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gate 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hardening — soon, but non-blocking&lt;/td&gt;
&lt;td&gt;Additional plugin observability; expiry policy; ancillary rate limits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Gating converts "here are eighteen problems" into "here is the one blocking set standing between you and a safe rollout." It also makes the halt legible: the verdict "do not go all-at-once and do not email tokens until Gate 0 clears" is a statement about a &lt;em&gt;specific&lt;/em&gt; tier, not a vague unease. No emails were sent. No teammate was onboarded. That was the correct state to be in, and the gate model is what let everyone agree on it in one sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fixes that shipped
&lt;/h2&gt;

&lt;p&gt;Gate 0 and the first Gate 1 items landed the same day. Each is a small, well-reasoned change. Shown below are the ones where the design decision is the point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tokens hashed at rest, with a safe fallback
&lt;/h3&gt;

&lt;p&gt;The token registry now accepts an already-salted &lt;code&gt;scrypt$salt$hash&lt;/code&gt; value in a record's token field and uses it verbatim, so the credential file can store hashes instead of plaintext bearer secrets. scrypt is a deliberately expensive, memory-hard key-derivation function — a stolen hash is costly to brute-force, unlike a bare SHA-256 digest. Plaintext still works, for operator convenience — and a value that merely &lt;em&gt;looks&lt;/em&gt; hashed but has non-hex segments falls back to being hashed as plaintext rather than being trusted as a hash.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A registry record's token field may already be a hashed secret.&lt;/span&gt;
&lt;span class="c1"&gt;// Accept a well-formed scrypt$salt$hash verbatim; otherwise hash it as plaintext.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resolveToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;field&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isHashed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;parts&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;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scrypt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9a-f&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;   &lt;span class="c1"&gt;// salt segment must be hex&lt;/span&gt;
    &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9a-f&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&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="c1"&gt;// hash segment must be hex&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isHashed&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;hashToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Necessary — but, per broken call #1, &lt;strong&gt;not sufficient on its own.&lt;/strong&gt; This fix protects the file at rest. It does nothing about the plaintext already sitting in backups. Rotation plus purge was the other half, and shipping the hash without the rotation would have been the exact false-safety the review flagged. The tension is the lesson: a real fix and an incomplete fix can look identical in a diff.&lt;/p&gt;

&lt;h3&gt;
  
  
  Immutable, tag-pinned deploy with a lockout floor-guard and auto-rollback
&lt;/h3&gt;

&lt;p&gt;The service had been running from a &lt;em&gt;mutable working checkout&lt;/em&gt;. That is three latent failures at once: any rebuild in that repo mutates the live service, a crash-restart can relaunch from a torn or feature-branch build, and there is no immutable artifact to roll back &lt;em&gt;to&lt;/em&gt;. And there was a sharper trap hiding inside it. Rolling the checkout back &lt;em&gt;past&lt;/em&gt; the token-hashing change would rebuild the pre-hash registry, which would then double-hash the now-hashed credential file — and lock out all six users at once.&lt;/p&gt;

&lt;p&gt;The fix builds immutable, self-contained release directories behind an atomic &lt;code&gt;current&lt;/code&gt; symlink, and refuses to deploy anything older than the migration that would cause the lockout.&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;# Floor guard: never deploy a commit that predates the lockout-inducing token-hash migration.&lt;/span&gt;
&lt;span class="nv"&gt;FLOOR_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"token-hash-floor"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; git merge-base &lt;span class="nt"&gt;--is-ancestor&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FLOOR_TAG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGET_REF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"REFUSE: &lt;/span&gt;&lt;span class="nv"&gt;$TARGET_REF&lt;/span&gt;&lt;span class="s2"&gt; predates &lt;/span&gt;&lt;span class="nv"&gt;$FLOOR_TAG&lt;/span&gt;&lt;span class="s2"&gt; — deploying it would rebuild the"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="s2"&gt;"pre-hash registry, double-hash the credential file, and lock out every user."&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Build an immutable, self-contained release dir: no .git, frozen deps, built once.&lt;/span&gt;
&lt;span class="nv"&gt;REL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/brain/releases/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y%m%dT%H%M%SZ&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGET_REF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
git archive &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGET_REF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; install_frozen_deps &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; build &lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Lockout preflight: the built artifact MUST contain the hash parser, or it will double-hash.&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s1"&gt;'scrypt'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REL&lt;/span&gt;&lt;span class="s2"&gt;/dist/token-registry.js"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"REFUSE: hash parser missing from build"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;PREV_REL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /opt/brain/current &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;   &lt;span class="c"&gt;# capture the current target FIRST, for rollback&lt;/span&gt;
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sfn&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; /opt/brain/current                       &lt;span class="c"&gt;# atomic promotion via symlink swap&lt;/span&gt;
systemctl restart brain-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a health gate with auto-rollback, so a bad release un-ships itself:&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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 5 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HEALTH_ENDPOINT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"post-restart health gate failed — rolling back to previous release"&lt;/span&gt;
  &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sfn&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PREV_REL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; /opt/brain/current
  systemctl restart brain-api
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;git merge-base --is-ancestor &amp;lt;floor-tag&amp;gt; &amp;lt;target&amp;gt;&lt;/code&gt; check is a &lt;strong&gt;named, transferable ops pattern&lt;/strong&gt;: a &lt;em&gt;floor guard against a lockout-inducing rollback&lt;/em&gt;. Any time a migration makes older code actively dangerous to redeploy — not just wrong, but destructive — pin a floor tag at the migration and refuse to deploy beneath it. Immutability gives you a rollback target; the floor guard makes sure the rollback target can't itself be the disaster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Durable revoke-by-actor with a persisted revocation list
&lt;/h3&gt;

&lt;p&gt;Tokens had no expiry, and the only revoke path was in-memory — lost on restart. And now that tokens are hashed at rest, an admin no longer holds any teammate's plaintext bearer secret, so "revoke by value" is impossible for the realistic incident: &lt;em&gt;a teammate's laptop was stolen.&lt;/em&gt; You cannot revoke a secret you deliberately no longer possess.&lt;/p&gt;

&lt;p&gt;So revocation keys off the &lt;strong&gt;audit identity the token already carries&lt;/strong&gt;, and persists to an append-only file read at boot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Tokens are hashed at rest, so no admin holds a plaintext bearer secret to revoke by value.&lt;/span&gt;
&lt;span class="c1"&gt;// Revoke by the audit identity the token carries; persist it so it survives a restart.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;revokeByActor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;revoked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                                   &lt;span class="c1"&gt;// in-memory guard, effective immediately&lt;/span&gt;
  &lt;span class="nf"&gt;appendFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;REVOCATION_LIST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                       &lt;span class="c1"&gt;// append-only ban-list: durable + audit trail&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// At boot, replay the ban-list so a revoked actor stays revoked across restarts.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadRevocations&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="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;readLines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;REVOCATION_LIST&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;revoked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The design choice worth naming: a &lt;strong&gt;separate append-only ban-list file&lt;/strong&gt; was chosen over mutating the source token file on every incident. Rewriting the credential store during an active incident — the highest-stress moment — is how you fat-finger a lockout. Append-only is safer than rewrite, and it doubles as a revocation audit trail: who was revoked, when, and why, in order, forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server-side candidate-intake override with a provenance receipt
&lt;/h3&gt;

&lt;p&gt;This is the clearest instance of the whole thesis. In team mode the &lt;em&gt;client&lt;/em&gt; built the entire candidate object, and the server trusted it verbatim. That means a member — or a leaked member token — could self-assert &lt;code&gt;trustLevel: 'high'&lt;/code&gt; to clear a minimum-trust gate, forge the &lt;code&gt;author&lt;/code&gt;, set an arbitrary &lt;code&gt;tenant&lt;/code&gt;, and clear the "potential secret" flag on their own content. And intake wrote no audit event, so none of it left a trace.&lt;/p&gt;

&lt;p&gt;The fix: the server re-derives the fields that decide trust, authorship, and tenancy from the &lt;strong&gt;bearer-token identity&lt;/strong&gt;, ignoring the body for exactly those fields, and writes a provenance receipt on every proposal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TEAM MODE: the client builds the whole candidate, but the server trusts NONE of it&lt;/span&gt;
&lt;span class="c1"&gt;// for the fields that decide trust, authorship, and tenancy. Re-derive from the token.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;intake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reqBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;reqBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;trustLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trustLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// NOT reqBody — client cannot self-assert 'high'&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;               &lt;span class="c1"&gt;// NOT reqBody — no forging provenance&lt;/span&gt;
    &lt;span class="na"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// NOT reqBody — no cross-tenant writes&lt;/span&gt;
    &lt;span class="na"&gt;potentialSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reqBody&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;    &lt;span class="c1"&gt;// server re-scans; client cannot clear the flag&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;writeReceipt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;candidate.intake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// every proposal leaves a receipt&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two alternatives were considered and rejected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reject any candidate that asserts non-default fields.&lt;/strong&gt; Brittle. The client always sends a full body, including those fields, on every legitimate proposal — so this would reject every real submission, not just malicious ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trim the client body to a minimal DTO at the boundary.&lt;/strong&gt; A bigger cross-surface contract change — new schema, new client code, new failure modes — for &lt;em&gt;no additional safety&lt;/em&gt;, because the override already neutralizes every one of those fields. More work, more blast radius, same result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The override wins because it is the smallest change that makes the guarantee true: the client proposes a candidate; the server decides what that candidate &lt;em&gt;is&lt;/em&gt;. Identity, trust, and tenant are server-owned. That sentence is the whole security model in miniature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Atomic promotion
&lt;/h3&gt;

&lt;p&gt;The promotion path — moving a governed candidate into durable memory — did roughly five separate autocommits: a supersession update and its event, the memory insert, the graph-edge links, and the "promoted" receipt. A kill mid-promote — the compile cron hitting its timeout, or a plain SIGTERM — could leave a promoted memory with &lt;strong&gt;no "promoted" receipt&lt;/strong&gt;. A durable row without its audit receipt violates the product's core promise — every state change has a receipt — and it never self-heals, because nothing re-derives a receipt for a row that already exists.&lt;/p&gt;

&lt;p&gt;The fix wraps the whole write block in one &lt;a href="https://www.sqlite.org/lang_transaction.html" rel="noopener noreferrer"&gt;&lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt;&lt;/a&gt; transaction — SQLite's mode for taking the write lock up front instead of deferring it to the first write statement — so the memory and its receipt commit together or not at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Memory row + its "promoted" receipt must commit atomically, or the&lt;/span&gt;
&lt;span class="c1"&gt;// append-only-receipts promise breaks and never self-heals.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;promote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidateId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDb&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;               &lt;span class="c1"&gt;// read-only getter over the shared connection&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BEGIN IMMEDIATE&lt;/span&gt;&lt;span class="dl"&gt;'&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="nf"&gt;supersedePrior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidateId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// supersession update + its event&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;memId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;insertMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidateId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;linkGraphEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;memId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;writeReceipt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;promoted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;memId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// nested audit inserts degrade to SAVEPOINTs here&lt;/span&gt;
    &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMMIT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ROLLBACK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;e&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two design details carry their own rationale. First, the shared DB connection was exposed via a &lt;strong&gt;read-only getter&lt;/strong&gt; rather than threading a new &lt;code&gt;db&lt;/code&gt; handle through the constructor — so every existing caller's signature stays unchanged, keeping the blast radius small. Second, the nested audit inserts each open their own &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt;; inside the outer transaction those degrade to &lt;strong&gt;savepoints&lt;/strong&gt;, which preserves the audit chain's anti-fork guarantee rather than fighting it. And the negative control matters: with the outer transaction bypassed, the atomicity tests &lt;em&gt;fail&lt;/em&gt; — the memory is orphaned — and with it in place they pass. The test catches the real regression, not a tautology that would pass either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plugin hardening, shipped alongside
&lt;/h3&gt;

&lt;p&gt;The plugin got its own Gate 0 work, because it is the surface six people will actually touch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local-mode writers now take the same single-writer &lt;a href="https://man7.org/linux/man-pages/man2/flock.2.html" rel="noopener noreferrer"&gt;&lt;code&gt;flock(2)&lt;/code&gt;&lt;/a&gt; advisory lock the cron jobs use&lt;/strong&gt; — a real kernel &lt;code&gt;flock(2)&lt;/code&gt; on the same file, so it interoperates with the cron's &lt;code&gt;flock(1)&lt;/code&gt;. A PID-lockfile library was rejected precisely because it shares no kernel lock with &lt;code&gt;flock(1)&lt;/code&gt;; two "locks" that don't see each other are not a lock.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Local-mode writers take the SAME kernel lock the cron holds (flock(2) &amp;lt;-&amp;gt; flock(1)).&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;openSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LOCK_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;r&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;flockSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ex&lt;/span&gt;&lt;span class="dl"&gt;'&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;await&lt;/span&gt; &lt;span class="nf"&gt;writeToBrain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;flockSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;un&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Team-mode search now surfaces errors instead of swallowing them into an empty result.&lt;/strong&gt; Previously a bad token, a dead API, and being off-network all rendered identically as "the brain found nothing." For a tool whose entire value is &lt;em&gt;trust by receipts&lt;/em&gt;, silently returning empty on failure is the worst possible failure mode — it teaches the user the brain has no answer when the truth is it never got the question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A new read-only &lt;code&gt;brain_status&lt;/code&gt; probe&lt;/strong&gt; answers "am I connected, in which mode, and do I have a token?" — the three things a confused teammate needs before they can even ask for help.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The plugin got its first unit test, lint, and CI gate — &lt;em&gt;before&lt;/em&gt; it received the review's riskiest changes.&lt;/strong&gt; You do not hand new safety-critical code to a surface with no test wall.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The tradeoffs
&lt;/h2&gt;

&lt;p&gt;None of this was free, and pretending otherwise would undercut the point.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The rollout got slower.&lt;/strong&gt; All-at-once for six people became "one person proves the path end-to-end, Gate 0 clears, then six." That is the right call, but it is a real delay against a plan that was a switch-flip away from done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is more operational machinery to maintain now.&lt;/strong&gt; Immutable release directories, a floor tag to keep current, a revocation-list file to back up, a health gate and rollback path. Every one of those is a thing that can itself break or drift. Determinism and durability cost surface area; the bet is that the surface is cheaper than the incidents it prevents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hash-at-rest was a false sense of security until rotation happened.&lt;/strong&gt; This is the honest one. For a window, the credential file looked safe while the live secrets sat in plaintext in backups. Anyone reading only the "we hashed the tokens" line would have believed the job was done. The review is the only reason that gap was named before it was a breach instead of after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The review itself has a cost, and it is not just compute.&lt;/strong&gt; Six independent agent-runs is the cheap part. The expensive part is the discipline to accept a halt the day before launch — to let a review overrule a plan that felt finished, hold the line on "no tokens emailed until Gate 0 clears," and eat the schedule slip. A review you are unwilling to act on is theater with a receipt. The value was realized only because the verdict was allowed to stop the rollout.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The boundary underneath all of it
&lt;/h2&gt;

&lt;p&gt;Read the Gate 0 and Gate 1 fixes together and they are not five unrelated patches. They are five expressions of one boundary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server derives identity, trust, and tenant from the token, &lt;strong&gt;not&lt;/strong&gt; the request body. &lt;em&gt;(intake override)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Receipts commit atomically with the data they describe. &lt;em&gt;(atomic promotion)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Deploys are immutable artifacts, &lt;strong&gt;not&lt;/strong&gt; a mutable checkout. &lt;em&gt;(tag-pinned release + floor guard)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Revocation is durable and keyed to identity, &lt;strong&gt;not&lt;/strong&gt; to a plaintext value you no longer hold. &lt;em&gt;(revoke-by-actor)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Credentials live at rest as hashes the system re-derives, &lt;strong&gt;not&lt;/strong&gt; as plaintext a disk or a backup can hand back. &lt;em&gt;(hash-at-rest, paired with rotation)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The model — or the client, or the convenient side door — proposes. The deterministic system owns identity, trust, and durable state.&lt;/strong&gt; Every place the old design let the proposer also decide what its proposal &lt;em&gt;was&lt;/em&gt;, the review found a hole, and every fix closed it by moving that decision back to the deterministic side. That boundary is not specific to this system. It is the load-bearing wall of any governed AI system where something upstream is allowed to be creative and something downstream has to be trustworthy.&lt;/p&gt;

&lt;p&gt;And there is a standing invariant the review left behind, worth more than any single fix: all-at-once is the right rollout &lt;em&gt;for six people&lt;/em&gt; — but only after one person has walked the entire path end-to-end and Gate 0 is clear. Confidence at team scale is earned by one proof at individual scale, not asserted by a completed checklist.&lt;/p&gt;

&lt;p&gt;The meta-lesson is the method itself. Six independent lenses, each forced to &lt;strong&gt;verify against live state&lt;/strong&gt; rather than read the design, are what caught "the backup still holds the plaintext" and "local mode runs as admin, in-process, unlocked." A review that only read the diff would have blessed all three broken calls — because in the diff, all three looked done. The difference between shipping the safety work and &lt;em&gt;knowing&lt;/em&gt; you shipped it is whether someone went and looked at the running thing with hostile intent, before the people arrived.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/the-moat-is-the-trust-layer-nexus-byok-rag/"&gt;The Moat Is the Trust Layer: Turning a Local-RAG App into a BYOK Document-Intelligence Platform&lt;/a&gt; — where adversarially reviewing the &lt;em&gt;graders&lt;/em&gt; caught the eval suite lying green.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/every-safety-gate-has-a-failure-direction/"&gt;Every Safety Gate Has a Failure Direction&lt;/a&gt; — why one gate crashes fail-closed on bad data while a swallowed error lets another pass fail-open.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/noise-robust-signed-llm-judge-evals/"&gt;Noise-Robust LLM-Judge Evals: Don't Sign a Coin Flip&lt;/a&gt; — on not trusting a measurement you haven't proven can go red.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;{&lt;br&gt;
  "&lt;a class="mentioned-user" href="https://dev.to/context"&gt;@context&lt;/a&gt;": "&lt;a href="https://schema.org" rel="noopener noreferrer"&gt;https://schema.org&lt;/a&gt;",&lt;br&gt;
  "@type": "BlogPosting",&lt;br&gt;
  "headline": "Adversarial Review: The Six Lenses That Halted a Rollout",&lt;br&gt;
  "description": "A six-lens adversarial review checked a team knowledge system against live state, broke three shipped assumptions, and gated 18 risks to halt the rollout.",&lt;br&gt;
  "author": { "@type": "Person", "name": "Jeremy Longshore" },&lt;br&gt;
  "publisher": {&lt;br&gt;
    "@type": "Organization",&lt;br&gt;
    "name": "Start AI Tools",&lt;br&gt;
    "logo": { "@type": "ImageObject", "url": "&lt;a href="https://startaitools.com/favicon.ico" rel="noopener noreferrer"&gt;https://startaitools.com/favicon.ico&lt;/a&gt;" }&lt;br&gt;
  },&lt;br&gt;
  "url": "&lt;a href="https://startaitools.com/posts/adversarial-review-before-team-rollout/" rel="noopener noreferrer"&gt;https://startaitools.com/posts/adversarial-review-before-team-rollout/&lt;/a&gt;",&lt;br&gt;
  "datePublished": "2026-07-09",&lt;br&gt;
  "keywords": "adversarial review, security review, governed AI system, team rollout, authentication, claude-code",&lt;br&gt;
  "articleSection": "Architecture"&lt;br&gt;
}&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>security</category>
      <category>architecture</category>
      <category>authentication</category>
    </item>
  </channel>
</rss>
