<?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: FromZeroToShip</title>
    <description>The latest articles on DEV Community by FromZeroToShip (@fromzerotoship).</description>
    <link>https://dev.to/fromzerotoship</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%2F4009335%2Fb545e0a8-85ee-491a-bec0-9c98be7e95ed.png</url>
      <title>DEV Community: FromZeroToShip</title>
      <link>https://dev.to/fromzerotoship</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fromzerotoship"/>
    <language>en</language>
    <item>
      <title>I published a 60-second deploy tolerance on Monday. On Wednesday a deploy took 70, and my check called a healthy site broken.</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Thu, 06 Aug 2026 12:30:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/i-published-a-60-second-deploy-tolerance-on-monday-on-wednesday-a-deploy-took-70-and-my-check-53l6</link>
      <guid>https://dev.to/fromzerotoship/i-published-a-60-second-deploy-tolerance-on-monday-on-wednesday-a-deploy-took-70-and-my-check-53l6</guid>
      <description>&lt;p&gt;On Monday I published a piece admitting that my deploy verification tolerates sixty seconds of "not there yet" for a reason I couldn't defend. Three retries, twenty seconds apart. I picked twenty because it was the first interval where my false alarms stopped, my sample was about three deploys, and I had never once recorded how long propagation actually takes.&lt;/p&gt;

&lt;p&gt;I made three commitments in that piece. A birth certificate for the constant. A rule fixed before the run it judges. And the one that mattered most: &lt;strong&gt;emit the value, not just the verdict&lt;/strong&gt; — a check that prints only pass or fail hides the exact signal that would tell me it's miscalibrated.&lt;/p&gt;

&lt;p&gt;I did the third one that afternoon. Every deploy since writes down how long it took to go green.&lt;/p&gt;

&lt;p&gt;Three samples in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Aug 03   ( 1.7,  21.7 ]s
Aug 05   ( 0,     6.7 ]s
Aug 05   ( 40,   70   ]s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They're intervals rather than points because my poll spacing is twenty seconds. All I can honestly say is that green happened somewhere between the last failed check and the first successful one — a number I can't resolve finer than my own instrument.&lt;/p&gt;

&lt;h2&gt;
  
  
  The third one failed
&lt;/h2&gt;

&lt;p&gt;Not the deploy. The check.&lt;/p&gt;

&lt;p&gt;I shipped a post, ran verification, and got a clean red: page 404, hero missing, sitemap entry absent. Three attempts, twenty seconds apart, exactly as designed. By its own rules the deploy had failed.&lt;/p&gt;

&lt;p&gt;Nothing was wrong. A longer script came back 200 on everything. Total elapsed: somewhere between forty and seventy seconds, against a tolerance of sixty.&lt;/p&gt;

&lt;p&gt;So the false alarm I widened the interval to eliminate returned on the third recorded sample, four days after I published the sentence "my sample was about three deploys." I'd like to say I predicted this. I predicted the category, not the timing, and the timing is the part that stings.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I hadn't considered at all
&lt;/h2&gt;

&lt;p&gt;Here's what the red actually said, in order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;attempt 1   article 404 · hero missing · list page MISSING · sitemap missing
attempt 3   article 404 · hero missing · list page OK      · sitemap missing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The blog index had updated. The article page it links to had not.&lt;/p&gt;

&lt;p&gt;For about a minute the site was in a state where a reader could see the new post announced on one page and get a 404 by clicking it. Both facts were true at once, and my check has no way to say so, because it was built on the assumption that a deploy is a single transition: not there, then there.&lt;/p&gt;

&lt;p&gt;It isn't. It's a set of files arriving independently, and "partly arrived" is a real state my instrument cannot name. It has two words for three situations. Seeing an updated list page beside a missing article, it filed that under "not there yet" — the same bucket as a deploy that hasn't started propagating at all. Those are not the same reading. One is waiting. The other is a live inconsistency a visitor can walk into.&lt;/p&gt;

&lt;p&gt;That's the more interesting defect, and no adjustment to the retry count touches it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm not going to do
&lt;/h2&gt;

&lt;p&gt;Raise twenty to thirty.&lt;/p&gt;

&lt;p&gt;That's the move, and I can feel it pulling. It would make this week's red go away, it would take four seconds, and it would be indistinguishable from what I did the first time — widen until the alarm stops, then call the result engineering.&lt;/p&gt;

&lt;p&gt;The numbers don't support a bump anyway. Three samples spanning 6.7 to 70 seconds is a tenfold range. No single value in that spread avoids being wrong regularly: pick 20 and you get false alarms; pick 90 and your check is now ninety seconds slow at catching a deploy that genuinely failed. When the spread is that wide relative to what you're measuring, the problem isn't which point you choose. It's that you're choosing a point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually needs to change
&lt;/h2&gt;

&lt;p&gt;Two things, neither of them a threshold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Report elapsed time even on a pass.&lt;/strong&gt; The check currently prints one character for a deploy that resolved in six seconds and one that resolved in sixty-five. Different events, indistinguishable logs, no way to see the trend that would warn me before the next false alarm. This was Monday's commitment and I only half-kept it — I started recording the interval by hand, in a separate script, which is precisely the kind of thing that stops happening in about two weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And it needs a third verdict.&lt;/strong&gt; Not pass and fail but &lt;code&gt;INCONSISTENT&lt;/code&gt; — some resources updated, others not. That state means something different to a reader than either neighbour, and it's the only one where waiting is genuinely the right response, which is worth knowing at the moment you're deciding whether to panic.&lt;/p&gt;

&lt;p&gt;Three states instead of two. I've now arrived at that same shape from four unrelated directions in one week:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resolved   /  genuinely absent  /  unreachable
pass       /  fail              /  never ran
visible    /  removed           /  never existed
arrived    /  arriving          /  half arrived
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time, the missing third state was one I'd collapsed into a neighbour because I hadn't imagined it could happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest ending
&lt;/h2&gt;

&lt;p&gt;I still cannot tell you the right number.&lt;/p&gt;

&lt;p&gt;Three measurements with a tenfold spread is not a distribution. It's three measurements. What I have is a series that might become one, and that's more than I had a week ago — but only because I wrote the value down at the moment of the check instead of walking away with the impression that it felt fine.&lt;/p&gt;

&lt;p&gt;That's the whole difference, and it's smaller than it sounds. Monday's version of me had exactly the same experience of deploying. What he didn't have was a file.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://fromzerotoship.com/blog/#stolen" rel="noopener noreferrer"&gt;Stolen from the Feed&lt;/a&gt; — what a non-developer carries home from dev.to and actually builds. Sister series to From Zero to Ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>community</category>
    </item>
    <item>
      <title>My gate rejected the useless indicator instantly. Then it certified the worst one I own, at p=.001.</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Wed, 05 Aug 2026 12:30:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/my-gate-rejected-the-useless-indicator-instantly-then-it-certified-the-worst-one-i-own-at-p001-4j6e</link>
      <guid>https://dev.to/fromzerotoship/my-gate-rejected-the-useless-indicator-instantly-then-it-certified-the-worst-one-i-own-at-p001-4j6e</guid>
      <description>&lt;p&gt;A few weeks ago I killed an indicator of mine in public.&lt;/p&gt;

&lt;p&gt;I had been trying to work out how much of my audience was automated. One signal was whether an account had uploaded its own avatar. It fired on 100% of the accounts I was confident were people and 97% of the ones I suspected were not. That isn't a lenient signal. It isn't separating anything — it tracks something both groups share, and I had been counting its votes for weeks.&lt;/p&gt;

&lt;p&gt;I wrote that up. Named the defect, retired the indicator, moved on feeling like I'd learned something.&lt;/p&gt;

&lt;p&gt;Three days later I shipped another one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same hole, in a different shape
&lt;/h2&gt;

&lt;p&gt;I needed to check whether a comment on one of my posts was actually visible to readers — I'd found one the API returned and the comment count included, but that moderation had removed. So I wrote a check:&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;// v1 — passes for anyone with a second comment on the page. Zero separation.&lt;/span&gt;
&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// v2 — the only witness with jurisdiction over one comment.&lt;/span&gt;
&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two comments from the same account, one removed and one live, both came back visible under v1. I found it by accident, and only because I happened to compare against something else.&lt;/p&gt;

&lt;p&gt;Someone in a thread put the problem in a sentence I couldn't argue with: if the fix is "I noticed this one," the next indicator ships with the same blind spot in a different shape. Which is, word for word, what I had already written about the previous defect.&lt;/p&gt;

&lt;p&gt;Their prescription was structural. A labeled control set shouldn't be a diagnostic you run when something feels off. It should be a &lt;strong&gt;permanent seed every indicator has to clear a margin on before it's allowed to vote&lt;/strong&gt; — not just beat chance on the live population, which is exactly the condition that let the avatar signal pass silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building it
&lt;/h2&gt;

&lt;p&gt;Twenty-eight accounts. Fourteen labeled human, fourteen automated, and every label carries a provenance string saying how it was established — seven humans confirmed through long back-and-forth exchanges, the rest by a weaker proxy. That doesn't make a label true. It makes an invented one visible, which is a different and more achievable thing.&lt;/p&gt;

&lt;p&gt;Then a margin: 30 points of separation before an indicator votes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;indicator            human   automated   margin
github/twitter        64%       7%        57pt   admitted
avatar uploaded      100%     100%         0pt   rejected
location present      57%       0%        57pt   admitted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dead indicator died on contact. That's the class the gate was built for, and it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the threshold the way I tested the indicators
&lt;/h2&gt;

&lt;p&gt;Thirty was a number I picked because it sounded right. Having just written a whole piece about constants chosen by feel, I could hardly leave it there — and the same person named the obvious move: pressure-test the threshold exactly the way you pressure-tested the indicators.&lt;/p&gt;

&lt;p&gt;Shuffle the labels. Recompute every margin against the scrambled pairing. Twenty thousand times, with a fixed random seed so the result is reproducible rather than remembered.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    observed   null p95   null p99   p
  github/twitter       57pt       29pt       43pt    .0034
  avatar uploaded       0pt        0pt        0pt    1.000
  location present     57pt       29pt       43pt    .0011
  bio present          57pt       29pt       43pt    .0055
  website present      71pt       43pt       43pt    .0002
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thirty sits at the 96.4th percentile of that null. The 95th is 29 points, the 99th is 43. My number was approximately right by luck, and it's measured now instead.&lt;/p&gt;

&lt;p&gt;Except moving it to 43 changes nothing. Every observed margin is either 0 or between 57 and 71. Nothing lands in the gap. &lt;strong&gt;The constant is now calibrated and has still never decided anything&lt;/strong&gt; — it only starts mattering the first time an indicator falls between those values, and until then "calibrated" is a property I can claim without having tested it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I didn't see coming
&lt;/h2&gt;

&lt;p&gt;Location present: &lt;strong&gt;p = .0011.&lt;/strong&gt; Second strongest of the five.&lt;/p&gt;

&lt;p&gt;Location is the indicator I threw out weeks ago. An empty location field says "this account is three days old" just as well as it says "this account is automated." It was my original example when explaining why a test built from the assumption it's testing proves nothing.&lt;/p&gt;

&lt;p&gt;The gate certified it more confidently than it certified the signal I actually trust.&lt;/p&gt;

&lt;p&gt;The reason is in how the labels were made. My automated label means &lt;em&gt;followed me within a day of signing up&lt;/em&gt;. Empty location tracks account newness. Those aren't two proxies for the same hidden thing — they're the same proxy wearing two names. The margin is real, the p-value is honest, and both are about the confound.&lt;/p&gt;

&lt;p&gt;I would have read .0011 as evidence the indicator was good, if someone hadn't told me this first, in advance, in writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I can't stratify my way out
&lt;/h2&gt;

&lt;p&gt;My instinct was to fix it afterward: split the human label by how it was established and see whether the circular indicators wobble when the label source changes.&lt;/p&gt;

&lt;p&gt;They didn't. Location moved zero points across that split. The indicator I trust most moved 43. With seven accounts per arm the comparison is noise, and I'd have reported whichever direction it went as a finding.&lt;/p&gt;

&lt;p&gt;The deeper problem isn't sample size. Stratifying after the fact means conditioning on the very thing I'm trying to detect. A bigger n makes the noise quieter and the answer no more valid. The confound was installed the moment I decided what "automated" meant.&lt;/p&gt;

&lt;p&gt;What the seed needs is an automated signal &lt;strong&gt;independent of account age by construction&lt;/strong&gt; — not a window measured from signup. I tried one: regularity in timing between accounts, which doesn't depend on how old any single account is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;confirmed-human accounts, gap before their follow (minutes):
  1.0   1.4   3.7   12.5   36.1   48.9        population median: 22.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It spans the entire suspicious band. It may still be a population-level pattern, but it can't carry a per-account label, and a per-account label is the whole job.&lt;/p&gt;

&lt;p&gt;Cross-account content duplication is the candidate left standing — nothing about being new explains two accounts posting the same text. That's the next build, and I don't know yet whether it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I'd hand back
&lt;/h2&gt;

&lt;p&gt;A gate that catches indicators with no signal is the easy half. Those are cheap to find once you look: one number beside another number, and the defect is visible.&lt;/p&gt;

&lt;p&gt;The hard half is an indicator with &lt;em&gt;plenty&lt;/em&gt; of signal, about the wrong thing. No amount of statistics run on a confounded seed will find it, because every test you run keeps confirming it. The arithmetic is correct. The p-value is honest. It's answering a question you didn't ask.&lt;/p&gt;

&lt;p&gt;So here's the one I'd take to your own labels. It takes about a minute:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How was this label generated, and could the thing you're testing have produced it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is yes, your seed and your indicator are measuring the same thing, and every result from that pairing will look like confirmation.&lt;/p&gt;

&lt;p&gt;Mine did, at p = .001, for an indicator I had already thrown in the bin.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://fromzerotoship.com/blog/#stolen" rel="noopener noreferrer"&gt;Stolen from the Feed&lt;/a&gt; — what a non-developer carries home from dev.to and actually builds. Sister series to From Zero to Ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>community</category>
    </item>
    <item>
      <title>I quoted 133 resolved, 0 absent, 340 unreachable. My log had one entry — and it wasn't that run.</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Tue, 04 Aug 2026 12:30:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/i-quoted-133-resolved-0-absent-340-unreachable-my-log-had-one-entry-and-it-wasnt-that-run-4567</link>
      <guid>https://dev.to/fromzerotoship/i-quoted-133-resolved-0-absent-340-unreachable-my-log-had-one-entry-and-it-wasnt-that-run-4567</guid>
      <description>&lt;p&gt;Here is a sentence I wrote to someone in a thread last week:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Built it. The refusal now writes a record instead of just exiting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And here are the numbers I quoted alongside it: 133 resolved, 0 genuinely absent, 340 unreachable. Coverage 28%, below my threshold, so the script refused to print a statistic and said so.&lt;/p&gt;

&lt;p&gt;Today I opened the log file. It has one entry. It isn't that run.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it got that way
&lt;/h2&gt;

&lt;p&gt;I'd written a script to check something about my own audience — it looks up a few hundred profiles and reports two percentages. The first version did what most of my scripts do: it dropped whatever it couldn't fetch and printed confident numbers from what survived. Someone in a thread pointed out that this makes the percentages meaningless. A rate computed over an unknown denominator isn't a sample. It's an accident that happens to be formatted like a measurement.&lt;/p&gt;

&lt;p&gt;So I rebuilt it with three states instead of two.&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="nx"&gt;r&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;return&lt;/span&gt; &lt;span class="nx"&gt;r&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="c1"&gt;// resolved&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;r&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;===&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;)&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;backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&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="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                   &lt;span class="c1"&gt;// genuinely absent — a fact worth having&lt;/span&gt;
&lt;span class="c1"&gt;// ...falling out of the retry loop:&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                              &lt;span class="c1"&gt;// unreachable — NOT the same thing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Resolved.&lt;/strong&gt; &lt;strong&gt;Genuinely absent&lt;/strong&gt; — the profile is gone, which is itself a fact worth having. &lt;strong&gt;Unreachable&lt;/strong&gt; — the request failed, which is not the same thing as absence and must never be allowed to become it. If coverage fell below ninety percent, the script would refuse to print anything at all.&lt;/p&gt;

&lt;p&gt;Then a different stranger — the one I'm correcting here — said the refusal itself was worth keeping:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I would preserve the refusal artifact rather than discard the run: counts by state, the threshold that failed, and the rate-limit context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was right, and I built it. Counts by state, the threshold, the backoff policy, the concurrency, and a &lt;code&gt;reopensIf&lt;/code&gt; field naming the condition under which the question becomes answerable again — not a date, a condition, because a date only measures my own diligence.&lt;/p&gt;

&lt;p&gt;What I did not notice was the order of events. &lt;strong&gt;The run I quoted happened before the artifact existed.&lt;/strong&gt; Its numbers were never written anywhere. They were in my head, and my head handed them over in the same breath as a sentence about how I now write things down.&lt;/p&gt;

&lt;p&gt;I built an instrument specifically to stop me from reporting unrecorded numbers, and reported unrecorded numbers while describing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one entry
&lt;/h2&gt;

&lt;p&gt;Same script. Same key. Same rate limit. Same concurrency. Nothing touched.&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;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REFUSED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"threshold"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"coverage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.7092&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"counts"&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;"resolved"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;395&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"gone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;"failed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"population"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;557&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;"cellsInterpretableAlone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reopensIf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"an access path that yields &amp;gt;=90% coverage"&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;Two things fall out of that, and both are worse for me than the slip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I qualified my claim three times and was still one short.&lt;/strong&gt; When I said the data path couldn't support the statistic, I was careful about it: this path, this key, this rate limit. I remember feeling good about that caution. It needed a fourth qualifier — &lt;em&gt;on this run&lt;/em&gt;. Coverage moved from a remembered 28% to a recorded 71% with nothing changed between them. A single refusal does not describe the boundary of an instrument. It describes one attempt at it, on one day, under conditions I didn't control and didn't record. I had been treating those as the same claim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The zero became a 2.&lt;/strong&gt; The stranger had warned me about that specific cell:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The zero in "genuinely absent" also should not be allowed to read as evidence that absence is rare while the unreachable category remains larger than the resolved category.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I agreed with that the way you agree with something sensible you don't expect to need. Then the number moved. Absence was never rare — it was unobserved, and the only reason I can tell those two apart is that the states were kept separate instead of collapsed into a truthy check. That one design decision is why today produced a fact instead of a shrug.&lt;/p&gt;

&lt;h2&gt;
  
  
  What preserving it actually bought
&lt;/h2&gt;

&lt;p&gt;I expected the artifact to preserve a result. That isn't what it did.&lt;/p&gt;

&lt;p&gt;What it did was make the gap visible. I have one record and one recollection, and I cannot properly compare them. I &lt;em&gt;suspect&lt;/em&gt; the boundary is unstable, on the strength of one number I can read and one I can only remember — which is precisely the epistemic position the artifact was supposed to get me out of, and it can't, because it arrived one run too late.&lt;/p&gt;

&lt;p&gt;That's the honest state and I'd rather hold it than round it off. Not "the rate limit varies." Not "28 to 71." One measurement, one memory, and no license to draw a line between two points when only one of them is a point.&lt;/p&gt;

&lt;p&gt;The record earns its keep going forward, not backward. Four runs happened before it existed. Those four are gone, and no amount of care applied now retrieves them.&lt;/p&gt;

&lt;h2&gt;
  
  
  It happened again the same day
&lt;/h2&gt;

&lt;p&gt;Different instrument. Same shape.&lt;/p&gt;

&lt;p&gt;I was going through comments on my own posts and found one I'd never replied to, from a week earlier — a real person, thoughtful, describing a path close to mine. I drafted a reply. Then I opened the post and the comment wasn't there.&lt;/p&gt;

&lt;p&gt;The API returns it. The comment count includes it. No reader can see it: moderation had removed it, and the index kept it. The comment's own permalink returns 404.&lt;/p&gt;

&lt;p&gt;So &lt;em&gt;the API says there is a comment&lt;/em&gt; and &lt;em&gt;a reader can see a comment&lt;/em&gt; turned out to be different claims, and I'd been using one as evidence for the other for as long as I'd had the script.&lt;/p&gt;

&lt;p&gt;Then I wrote a check for it, and got the check wrong too:&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;// v1 — passes for anyone with a second comment on the page. Zero discriminating power.&lt;/span&gt;
&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// v2 — the only witness with jurisdiction over one comment.&lt;/span&gt;
&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two comments from the same account, one removed and one live, both came back visible under v1. Only the comment's own id separated them.&lt;/p&gt;

&lt;p&gt;I have made this exact error before, in a different script, on a different field: an indicator that returned the same answer whether the condition held or not. That isn't a lenient check. It isn't a check.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;the thing said&lt;/th&gt;
&lt;th&gt;what it could actually testify to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;my quoted run&lt;/td&gt;
&lt;td&gt;what I remembered, not what the log recorded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;the comment count&lt;/td&gt;
&lt;td&gt;the index still lists it, not that anyone can read it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;the username match&lt;/td&gt;
&lt;td&gt;this account posted on this page at all&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The part I'd hand back
&lt;/h2&gt;

&lt;p&gt;Not "be more careful." I was being careful. I qualified the claim three separate ways and it was still wrong, and the carefulness is part of how it survived — a hedged sentence reads as rigor and stops attracting questions.&lt;/p&gt;

&lt;p&gt;Here's the one I'd actually use. Find a number you quoted recently, in a standup or a postmortem or a comment thread. Ask one thing about it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you said it, were you reading it or remembering it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you can't tell now, that's the answer. It isn't a memory problem — recalled numbers feel identical to read ones from the inside, which is the entire difficulty. It's that nothing in the setup forced the difference to be visible at the moment you spoke.&lt;/p&gt;

&lt;p&gt;Mine didn't either. The log exists now. It has one entry, and that one entry is worth more to me than the four runs I can only describe.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://fromzerotoship.com/blog/#stolen" rel="noopener noreferrer"&gt;Stolen from the Feed&lt;/a&gt; — what a non-developer carries home from dev.to and actually builds. Sister series to From Zero to Ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>community</category>
    </item>
    <item>
      <title>My deploy check waits 60 seconds. My outage alarm waits 5. I measured neither.</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Mon, 03 Aug 2026 12:30:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/my-deploy-check-waits-60-seconds-my-outage-alarm-waits-5-i-measured-neither-3cid</link>
      <guid>https://dev.to/fromzerotoship/my-deploy-check-waits-60-seconds-my-outage-alarm-waits-5-i-measured-neither-3cid</guid>
      <description>&lt;p&gt;Two numbers from my own systems, side by side.&lt;/p&gt;

&lt;p&gt;When I deploy, a check confirms the pages are actually live. It retries three times, twenty seconds apart, so it tolerates up to a minute of "not there yet" before calling anything wrong.&lt;/p&gt;

&lt;p&gt;When my monitor decides whether production is &lt;strong&gt;down&lt;/strong&gt;, it waits five seconds and retries once.&lt;/p&gt;

&lt;p&gt;The check that guards the more consequential claim is the more impatient one. I did not decide that. I never compared them. Until last week I had never seen those two numbers in the same place, and neither had anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the numbers came from
&lt;/h2&gt;

&lt;p&gt;The deploy one has an origin story I'd have told you proudly a week ago.&lt;/p&gt;

&lt;p&gt;I shipped nine pages, then checked the URLs immediately instead of trusting the CLI's success message. Four returned 404. Nothing was broken — CDN propagation — and twenty seconds later all nine were 200. A single check at the wrong moment would have told me, with total confidence, that a perfectly good deploy was broken.&lt;/p&gt;

&lt;p&gt;So I wrapped it in a retry loop. Three attempts, twenty seconds apart. Problem solved, and it even sounds like engineering.&lt;/p&gt;

&lt;p&gt;Here is the part that isn't. &lt;strong&gt;I picked twenty because it was the first interval where the false alarms stopped.&lt;/strong&gt; My sample was about three deploys. I have never recorded how long propagation actually takes. I widened the tolerance until the red went away, and then I wrote about it as if I'd learned something.&lt;/p&gt;

&lt;p&gt;Someone in a thread named this before I saw it: a tolerance chosen that way is the same muting I'd been criticizing, relocated inside the assertion where it reads as rigor instead of avoidance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test I was given, and the answer I didn't want
&lt;/h2&gt;

&lt;p&gt;In that same thread I speculated that my deploy tolerance was probably leaking into my outage detector through a shared helper. It sounded plausible and I said it like a finding.&lt;/p&gt;

&lt;p&gt;The reply was sharper than the guess: that's a falsifier, not evidence. Here's the concrete test — &lt;strong&gt;do the two checks consume the same retry policy or threshold configuration?&lt;/strong&gt; If yes, two different claims have already collapsed into one mechanism.&lt;/p&gt;

&lt;p&gt;So I ran it. The answer is no. Nothing is shared.&lt;/p&gt;

&lt;p&gt;Which turned out to be worse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy verification   3 attempts × 20s     a shell loop I retype by hand each time
outage monitor        1 retry after 5s     written inline at three call sites in one file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No shared configuration means no leak. It also means nothing versioned, nothing reviewable, nothing anyone could pre-register. A shared helper would at least have put the number somewhere a person could find it and argue with it.&lt;/p&gt;

&lt;p&gt;Then I widened the search across the codebase:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Timeout / retry literals found&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40+&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distinct values&lt;/td&gt;
&lt;td&gt;3s, 5s, 8s, 10s, 12s, 15s, 75s, 90s, 120s, 600s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Written inline at the call site&lt;/td&gt;
&lt;td&gt;all of them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Derived from a measurement&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;none&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Forty-plus numbers, each typed by me at the moment I needed one, each chosen by how it felt, none of them ever compared to another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two different ways a constant is wrong
&lt;/h2&gt;

&lt;p&gt;Someone else in these threads drew a line I'd been blurring, and it changed what I think the fix is.&lt;/p&gt;

&lt;p&gt;Their system had a signup limit: five accounts per hour per IP. Written when users arrived one at a time from search — a completely sensible number. Then the link went into a group chat, and an entire office behind one NAT would have been locked out after five.&lt;/p&gt;

&lt;p&gt;Their earlier examples were constants calibrated to a rhythm that later changed — a reminder window sized for a tournament, then applied to a nine-month league season. That's drift, and drift is catchable: record what was true when you set the number, and the day reality diverges you have something to compare against.&lt;/p&gt;

&lt;p&gt;The rate limit isn't that. It wasn't correct-then and stale-now. It encoded an assumption about identity — one IP means roughly one person — and that was never true. Nothing drifted. It had simply never been exercised.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So some constants rot, and some were wrong at birth and merely unexercised.&lt;/strong&gt; A dated note catches the first kind. Only something adversarial catches the second, and the tell for that family is a key that stands in for identity rather than measuring it.&lt;/p&gt;

&lt;p&gt;Mine are mostly the first kind. That doesn't make them better; it makes them the kind I had the tools to catch and didn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things a threshold needs
&lt;/h2&gt;

&lt;p&gt;Not "pick better numbers." I can't pick better numbers — that's the whole problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A birth certificate.&lt;/strong&gt; When it was set, what the distribution looked like at that moment, and how far it's allowed to drift before something complains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CAL_SINCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-07-27&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CAL_BASELINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CAL_DRIFT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's from the one place I already do this. A machine computes the divergence and prints it. Compare that to a comment saying "20s seems fine," which requires me to reread it and be alarmed, and I have demonstrated that I won't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A rule fixed before the run it judges.&lt;/strong&gt; This is the correction I'd have missed on my own. Deriving the threshold from measured data still lets me revise until the red disappears — only now with a distribution available to rationalize the revision. The rule has to be versioned and fixed in advance, so that changing it becomes an event with a record rather than a quiet adjustment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The value, not just the verdict.&lt;/strong&gt; My deploy check currently emits pass or fail. A deploy that resolves in two seconds and one that takes thirty-nine produce identical output, which means the check is hiding exactly the signal that would tell me it's miscalibrated. A slow deploy should stay visible even when it passes. Until it does, I have no series to derive anything from — which is why I still can't tell you what the right number is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tell
&lt;/h2&gt;

&lt;p&gt;Here's what I'd check in your own systems, and it takes about a minute.&lt;/p&gt;

&lt;p&gt;Find a timeout or a retry count in your code. Ask where the number came from. If the honest answer is "it stopped failing at that value," it isn't a measurement. It's a preference, and it's currently deciding when you get woken up.&lt;/p&gt;

&lt;p&gt;Mine are five seconds and sixty seconds, guarding claims of very different consequence, in the opposite order from what you'd choose on purpose.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://fromzerotoship.com/blog/#stolen" rel="noopener noreferrer"&gt;Stolen from the Feed&lt;/a&gt; — what a non-developer carries home from dev.to and actually builds. Sister series to From Zero to Ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>community</category>
    </item>
    <item>
      <title>'Deploy succeeded.' Four of my nine pages were still 404.</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Sun, 02 Aug 2026 07:43:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/deploy-succeeded-four-of-my-nine-pages-were-still-404-5bke</link>
      <guid>https://dev.to/fromzerotoship/deploy-succeeded-four-of-my-nine-pages-were-still-404-5bke</guid>
      <description>&lt;p&gt;I deployed nine new pages this week. The CLI said what it always says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✨ Success! Uploaded 14 files
Deployed  (5.61 sec)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twenty seconds later I checked the actual URLs, because someone in a thread here had spent six rounds teaching me not to trust a report about the wrong thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;synology&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;htaccess&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;does&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;nothing&lt;/span&gt;        &lt;span class="mf"&gt;404&lt;/span&gt;
&lt;span class="k"&gt;php&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;74&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;str&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;starts&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;fatal&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;    &lt;span class="mf"&gt;200&lt;/span&gt;
&lt;span class="k"&gt;pwa&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;still&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;showing&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;old&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;version&lt;/span&gt;         &lt;span class="mf"&gt;404&lt;/span&gt;
&lt;span class="k"&gt;php&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;curl&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;errno&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;28&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;ipv&lt;/span&gt;&lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;timeout&lt;/span&gt;        &lt;span class="mf"&gt;200&lt;/span&gt;
&lt;span class="k"&gt;synology&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;cron&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;php&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;running&lt;/span&gt;         &lt;span class="mf"&gt;404&lt;/span&gt;
&lt;span class="k"&gt;mysql&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;emoji&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;group&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;merged&lt;/span&gt;           &lt;span class="mf"&gt;200&lt;/span&gt;
&lt;span class="k"&gt;javascript&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;toisostring&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;wrong&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;date&lt;/span&gt;     &lt;span class="mf"&gt;200&lt;/span&gt;
&lt;span class="mf"&gt;405&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;method&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;allowed&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;post&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;php&lt;/span&gt;       &lt;span class="mf"&gt;200&lt;/span&gt;
&lt;span class="k"&gt;agreement&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;was&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;the&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;                &lt;span class="mf"&gt;404&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four of nine. Not broken — &lt;strong&gt;not there yet&lt;/strong&gt;. Edge propagation. Twenty seconds later they were all 200, and if I'd checked once and walked away at the wrong moment, I'd have concluded the deploy was broken. If I'd never checked at all, I'd have concluded it worked, and been right by luck.&lt;/p&gt;

&lt;p&gt;Neither of those is knowing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jurisdiction
&lt;/h2&gt;

&lt;p&gt;The phrase I stole for this comes from a long exchange about monitoring: &lt;strong&gt;a witness testifies only within its jurisdiction.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My deploy tool answers exactly one question: &lt;em&gt;did the upload complete and did the platform accept it?&lt;/em&gt; That's a true and useful answer. It is not an answer to &lt;em&gt;are the pages reachable&lt;/em&gt;, and it is definitely not an answer to &lt;em&gt;is what's live what I meant.&lt;/em&gt; Three different questions, and I had been reading the first as if it covered all three.&lt;/p&gt;

&lt;p&gt;Once you have the word for it, you start seeing it everywhere:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The thing says&lt;/th&gt;
&lt;th&gt;What it actually testifies to&lt;/th&gt;
&lt;th&gt;What it says nothing about&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;✨ Deploy succeeded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the platform accepted the upload&lt;/td&gt;
&lt;td&gt;whether any URL responds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Task completed, exit 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a process was launched and returned&lt;/td&gt;
&lt;td&gt;whether your script did its work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;200 OK&lt;/code&gt; from the API&lt;/td&gt;
&lt;td&gt;the server answered&lt;/td&gt;
&lt;td&gt;whether the answer is complete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tests passed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the assertions that ran, ran green&lt;/td&gt;
&lt;td&gt;assertions that never ran at all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;No findings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the rules that fired, found nothing&lt;/td&gt;
&lt;td&gt;rules that can no longer fire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;$?&lt;/code&gt; after `cmd \&lt;/td&gt;
&lt;td&gt;tail`&lt;/td&gt;
&lt;td&gt;the exit status of &lt;strong&gt;&lt;code&gt;tail&lt;/code&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every row is a true statement being used to support a claim it doesn't reach.&lt;/p&gt;

&lt;p&gt;That last row happened to me while I was writing this post, which is why it's in the table.&lt;/p&gt;

&lt;p&gt;I'd built a script that deliberately refuses to print a result when it can't reach enough of its data — it reports what it missed and exits non-zero. Good guard. It fired correctly: it reached only 28% of the records and quit with status 1.&lt;/p&gt;

&lt;p&gt;My tooling told me the job &lt;strong&gt;succeeded&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node script.mjs | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-8&lt;/span&gt;   &lt;span class="c"&gt;# &amp;lt;-- the pipe is the problem&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;                     &lt;span class="c"&gt;# 0, even though node exited 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shell reports the exit status of the &lt;em&gt;last&lt;/em&gt; command in a pipeline. &lt;code&gt;tail&lt;/code&gt; ran fine, so the pipeline "succeeded." My guard did exactly what I built it to do, said so out loud, and the plumbing between it and me quietly relabeled it a success. (&lt;code&gt;set -o pipefail&lt;/code&gt; fixes it; not having it is the default.)&lt;/p&gt;

&lt;p&gt;So the alarm was fine and the wire was the problem — which is its own lesson, and one I'd written about a week earlier without connecting it to my own shell.&lt;/p&gt;

&lt;p&gt;I hit three of those five this month. The scheduled task on my NAS reported &lt;strong&gt;completed, exit 0&lt;/strong&gt; for weeks while the PHP script it launched died instantly on a missing extension — DSM was honestly reporting that it had started a process, which is a genuinely different fact from the one I wanted. And a script of mine fetched 473 records, silently dropped 402 that failed on a rate limit, and printed confident percentages from the 71 that survived. Every HTTP call it &lt;em&gt;made&lt;/em&gt; returned 200. The 200s were true. The number was not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cheap fix is to ask the question you actually have
&lt;/h2&gt;

&lt;p&gt;None of this requires better tooling. It requires asking a second question, out loud, in a place that isn't the first tool.&lt;/p&gt;

&lt;p&gt;After a deploy, don't read the deploy log — request the pages:&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;for &lt;/span&gt;s &lt;span class="k"&gt;in &lt;/span&gt;page-one page-two page-three&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%-34s %s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;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="s2"&gt;"https://example.com/&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And because "not there yet" and "broken" look identical in a single sample, poll instead of glancing:&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;for &lt;/span&gt;try &lt;span class="k"&gt;in &lt;/span&gt;1 2 3&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;bad&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
  &lt;span class="k"&gt;for &lt;/span&gt;s &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$PAGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&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;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="s2"&gt;"https://example.com/&lt;/span&gt;&lt;span class="nv"&gt;$s&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="o"&gt;=&lt;/span&gt; 200 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;bad&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
  &lt;span class="k"&gt;done&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$bad&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"all live (attempt &lt;/span&gt;&lt;span class="nv"&gt;$try&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;break
  echo&lt;/span&gt; &lt;span class="s2"&gt;"propagating, retrying in 20s"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;20
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole technique. It cost me four lines and it's the only reason I know the four 404s were propagation rather than a broken build — a single check would have given me a confident wrong answer in either direction.&lt;/p&gt;

&lt;p&gt;For the scheduled job, the equivalent is making the run leave evidence it couldn't produce without doing the work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// last statement in the job — a partial run can't fake a complete one&lt;/span&gt;
&lt;span class="nb"&gt;file_put_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/last-run.txt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;" ok rows=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$updated&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a second machine can read a timestamp and a row count, instead of asking the scheduler whether the scheduler is happy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What made this hard to see
&lt;/h2&gt;

&lt;p&gt;It isn't that the tools lie. Every one of those green messages was factually correct. That's exactly what made them so easy to over-read: &lt;strong&gt;there's no moment of doubt, because nothing wrong ever happens.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'd been treating "the tool reported success" as a general-purpose statement about the world, when it's a narrow statement about one step of one process. The gap between those two never announces itself. You only find it by naming what the tool can &lt;em&gt;see&lt;/em&gt; — and then noticing how much of what you care about is outside that.&lt;/p&gt;

&lt;p&gt;So the question I'd hand over, and the one I now ask before trusting any green light:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is this thing actually in a position to know?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the honest answer is narrower than the claim I'm about to make, I need a second witness — and it has to be one that doesn't sit downstream of the first. My deploy tool cannot tell me the pages are live. Only a request to the pages can, and that request has to come from somewhere the deploy doesn't control.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://fromzerotoship.com/blog/#stolen" rel="noopener noreferrer"&gt;Stolen from the Feed&lt;/a&gt; — what a non-developer carries home from dev.to and actually builds. Sister series to From Zero to Ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>community</category>
    </item>
    <item>
      <title>Two of my projects were about to jump a grade. Nobody had fixed anything.</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:41:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/two-of-my-projects-were-about-to-jump-a-grade-nobody-had-fixed-anything-2med</link>
      <guid>https://dev.to/fromzerotoship/two-of-my-projects-were-about-to-jump-a-grade-nobody-had-fixed-anything-2med</guid>
      <description>&lt;p&gt;I have a scanner that grades my own projects. It reads the code, applies a list of rules, subtracts points per finding, and hands each project a letter grade. It exists because I build with AI and needed something that wasn't me deciding whether my work was fine.&lt;/p&gt;

&lt;p&gt;Someone in a thread here pointed at a flaw in it that I couldn't argue with.&lt;/p&gt;

&lt;p&gt;His point: my rules overlap. If two rules fire on the same underlying defect, the project is charged twice for one mistake. And — this is the part that made me sit up — &lt;strong&gt;that error can only land on projects that have the defect.&lt;/strong&gt; A clean project is never double-charged, because there's nothing to charge. So the penalty is regressive: it pushes the worst projects further down and leaves the good ones untouched, which means my grade distribution is stretched at the bottom by a bug rather than by reality.&lt;/p&gt;

&lt;p&gt;That's a real argument. My instinct was to fix it that evening.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did instead
&lt;/h2&gt;

&lt;p&gt;I've been burned enough times this month by fixes that felt obviously right. So before changing the scoring, I wrote something to measure whether the thing he described is actually happening in my data.&lt;/p&gt;

&lt;p&gt;The measurement is simple: for every finding, record which rule fired and where. Then count how often two different rules fire at the same file and line.&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;// count co-occurrence of different rule keys at the same file:line&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                       &lt;span class="c1"&gt;// "file:line" -&amp;gt; Set(ruleKey)&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;f&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;findings&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;at&lt;/span&gt; &lt;span class="o"&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&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;f&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="s2"&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;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;seen&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;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;Set&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;seen&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;at&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;f&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pairs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&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;keys&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&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;let&lt;/span&gt; &lt;span class="nx"&gt;i&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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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;let&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&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;ks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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;ks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&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;pairs&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;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pairs&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;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="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;Then: recompute every project's score with duplicates collapsed, and compare.&lt;/p&gt;

&lt;h2&gt;
  
  
  What came back
&lt;/h2&gt;

&lt;p&gt;72 projects. Ten rule pairs that ever co-occur. Here are the top four:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule pair (co-firing at the same line)&lt;/th&gt;
&lt;th&gt;Times&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;missing social preview + missing standard footer&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;SELECT *&lt;/code&gt; + no performance diagnostics&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;suspected N+1 query + no performance diagnostics&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;suspected N+1 query + &lt;code&gt;SELECT *&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Look at the top one. "No social preview tag" and "no standard footer" are not one defect seen twice. They're two genuinely separate omissions that happen to live in the same file, because that file is the page template. Collapsing them wouldn't remove a double charge — it would stop charging for a real second problem.&lt;/p&gt;

&lt;p&gt;The same is true further down. &lt;code&gt;SELECT *&lt;/code&gt; and missing diagnostics co-occur because both are symptoms of a page written quickly, not because one implies the other.&lt;/p&gt;

&lt;p&gt;Then I checked his actual prediction — that the correction should lift the &lt;em&gt;worst&lt;/em&gt; projects most:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Original grade band&lt;/th&gt;
&lt;th&gt;Projects&lt;/th&gt;
&lt;th&gt;Avg. points gained by deduplicating&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;F (0–59)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D (60–69)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;+1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C (70–79)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+1.50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B (80–89)&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;+0.82&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A (90–100)&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;+0.39&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The uplift peaks in the &lt;strong&gt;middle&lt;/strong&gt;, not at the bottom. If the regressive error he described were the dominant effect in my data, that F row should be the largest number in the table. It's the second smallest.&lt;/p&gt;

&lt;p&gt;Two projects would have moved from B to A. Not because they improved. Because I applied a correction for a problem I hadn't confirmed I had.&lt;/p&gt;

&lt;p&gt;So I didn't ship it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I have to admit
&lt;/h2&gt;

&lt;p&gt;Here's what stops this from being a story about being clever.&lt;/p&gt;

&lt;p&gt;Look at the sample sizes. F is &lt;strong&gt;two projects&lt;/strong&gt;. D is &lt;strong&gt;one&lt;/strong&gt;. The bottom of that table, where the entire argument lives, is three data points. "The uplift peaks at C" is a sentence I can write and cannot really defend.&lt;/p&gt;

&lt;p&gt;So the honest conclusion isn't "he was wrong." It's narrower and less satisfying: &lt;strong&gt;I don't have enough bad projects to know whether he's right, and shipping the fix would have changed real grades based on a theory I couldn't test.&lt;/strong&gt; The measurement didn't prove him wrong. It proved I was about to act on faith and call it engineering.&lt;/p&gt;

&lt;p&gt;If my next twenty projects include eight bad ones, that F row becomes meaningful and I'll rerun it. The script is committed, the baseline is stored, and it compares against that baseline on every run — so the day the answer changes, something tells me instead of waiting for me to wonder.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually took from it
&lt;/h2&gt;

&lt;p&gt;Three things, and only one of them is about scoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A good argument is not a measurement.&lt;/strong&gt; His reasoning was sound and might still be correct. Sound reasoning about your data is a hypothesis, and it costs about an hour to find out which one you have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixes have a direction, and you should predict it before you look.&lt;/strong&gt; The reason this was checkable at all is that his claim made a prediction: the correction should help the worst projects most. That gave me something to compare against. Advice that doesn't predict anything can only be accepted or ignored — never tested.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Report what the measurement can't say.&lt;/strong&gt; My first draft of this post ended at the table, with the uplift peaking at C, sounding conclusive. Adding "n=2" to that row changed my own conclusion while I was writing it.&lt;/p&gt;

&lt;p&gt;So, genuinely: thank you for the flaw. It's on the list, with the measurement attached and a condition that will tell me when it starts to matter. That's a better outcome than shipping it would have been — and it took longer to write this post than to run the test.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://fromzerotoship.com/blog/#stolen" rel="noopener noreferrer"&gt;Stolen from the Feed&lt;/a&gt; — what a non-developer carries home from dev.to and actually builds. Sister series to From Zero to Ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>community</category>
    </item>
    <item>
      <title>My bot detector said 95% fake. It agreed with me instantly — that was the tell.</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Fri, 31 Jul 2026 07:28:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/my-bot-detector-said-95-fake-it-agreed-with-me-instantly-that-was-the-tell-3j6a</link>
      <guid>https://dev.to/fromzerotoship/my-bot-detector-said-95-fake-it-agreed-with-me-instantly-that-was-the-tell-3j6a</guid>
      <description>&lt;p&gt;Two days ago I closed a thread here with a line I meant as honest humility:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The next thing will ship with the same defect in a fresh costume.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Someone replied: &lt;strong&gt;"See you when it does."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It took two days. And the thing I got wrong wasn't the timing. It was that I spent those two days watching the wrong room.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I was guarding
&lt;/h2&gt;

&lt;p&gt;Last month was a hardening month. I planted known-bad files to prove my scanner could still see them. I built a drill that deliberately breaks each guard one at a time and requires it to go red &lt;em&gt;for its own reason&lt;/em&gt;, not just to fail. When it turned out nothing was checking whether the drill still ran, I put a separate machine on a separate schedule to watch for its proof of life.&lt;/p&gt;

&lt;p&gt;All of it aimed at one thing: my code.&lt;/p&gt;

&lt;p&gt;So when I said the defect would come back in a fresh costume, I pictured a costume made of code. Some new guard that looked solid and wasn't. A green light I'd trust for the wrong reason.&lt;/p&gt;

&lt;p&gt;It didn't come back there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number that agreed with me
&lt;/h2&gt;

&lt;p&gt;Some context. I've been writing here, and I'd been watching my follower count climb fast while my view counts stayed flat — hundreds of followers, posts getting fewer than 25 reads. Something was obviously off, and I had a theory: the followers weren't real.&lt;/p&gt;

&lt;p&gt;So I did the thing I've trained myself to do. I didn't guess, I measured. Pulled every follower through the public API and wrote a small classifier:&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;// my first version — read it and see if you spot the problem&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;suspect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;follower&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;daysBeforeFollow&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;follower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;joined_at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="nx"&gt;e3&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;daysBeforeFollow&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;        &lt;span class="c1"&gt;// signed up, immediately followed me&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;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="c1"&gt;// no location&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;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website_url&lt;/span&gt;         &lt;span class="c1"&gt;// no website&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;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// no bio&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The answer came back fast and clean. &lt;strong&gt;About 95% fake.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It matched my theory exactly. It explained every anomaly I'd been staring at for a week. I wrote the number down as a fact and moved on.&lt;/p&gt;

&lt;p&gt;That should have been the tell. A measurement that agrees with you on the first run hasn't been tested against anything — it's only been asked to confirm. I know this. I had just spent a month building machinery whose entire purpose is to distrust a green light.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a control group did to it
&lt;/h2&gt;

&lt;p&gt;The only reason I caught it is that I happened to have, sitting right there, a small group of accounts I &lt;em&gt;knew&lt;/em&gt; were real — people I'd had long technical back-and-forths with. Fourteen of them. So I ran the same classifier against them as a control.&lt;/p&gt;

&lt;p&gt;Here's what came back:&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;Known-real (14)&lt;/th&gt;
&lt;th&gt;Suspect (30)&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Avatar uploaded&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;97%&lt;/td&gt;
&lt;td&gt;measures nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Has a website&lt;/td&gt;
&lt;td&gt;79%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;circular&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Has a location&lt;/td&gt;
&lt;td&gt;57%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;circular&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Has ≥1 post&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;27%&lt;/td&gt;
&lt;td&gt;circular&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub linked&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;survives&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Twitter linked&lt;/td&gt;
&lt;td&gt;36%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;survives&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things fell out, and neither is what the raw gaps suggest.&lt;/p&gt;

&lt;p&gt;The first was instant and embarrassing — it's the top row. One of my indicators scored &lt;strong&gt;100% on the known-real group and 97% on the suspect group.&lt;/strong&gt; It wasn't discriminating at all. It was measuring nothing, and I'd been reading its 97% as corroboration. It had been "passing" the whole time, in exactly the way a check that can only ever be green passes.&lt;/p&gt;

&lt;p&gt;The second was worse, because it wasn't a bug — it was the design. My strongest signals were &lt;em&gt;empty location, empty website, empty bio&lt;/em&gt;. But an empty profile means "automated" &lt;strong&gt;or&lt;/strong&gt; "signed up yesterday," and every account I was suspicious of had signed up yesterday. I couldn't separate those two, because I had built the test out of the same assumption I was testing.&lt;/p&gt;

&lt;p&gt;The classifier wasn't wrong, exactly. It was me, restated in JavaScript, and then cited as if it were a second opinion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one signal that survived
&lt;/h2&gt;

&lt;p&gt;Exactly one indicator held up under the control, and it held up for a reason worth naming.&lt;/p&gt;

&lt;p&gt;Sign-up path — whether the account is linked to a GitHub or Twitter identity. That value gets written the moment the account is created, &lt;em&gt;by the act of creating it a particular way&lt;/em&gt;. It isn't a field anyone fills in later, so "the account is new" can't explain its absence. In my control group, half had it. In the suspect group, essentially none did.&lt;/p&gt;

&lt;p&gt;Which is the same rule I'd been repeating all month in a different context: &lt;strong&gt;evidence has to be something that couldn't exist unless the work was actually done.&lt;/strong&gt; I'd applied it carefully to my test suite. It never occurred to me that it applied to a number I was about to publish about my own audience.&lt;/p&gt;

&lt;p&gt;And I nearly did publish it. "95.6% bots" is a great sentence.&lt;/p&gt;

&lt;p&gt;The honest version, after the control, is messier. 390 of 408 followed within a day of creating their account. Of a sample of those, one in ten had a linked developer identity, against half of my known-real group. So: most of them, probably the large majority, with a band in the middle I genuinely cannot classify — and fourteen people I'm certain about.&lt;/p&gt;

&lt;p&gt;Less quotable. Considerably more true.&lt;/p&gt;

&lt;h2&gt;
  
  
  The costume was a different room
&lt;/h2&gt;

&lt;p&gt;Here's the part I actually want to hand over, because I'd have argued with it a week ago.&lt;/p&gt;

&lt;p&gt;I assumed my blind spot lived in my code, so I built increasingly good instruments and pointed all of them at my code. The recurrence showed up in analytics — a domain where I had no fixtures, no drill, no seeded failures and no habit of doubt, because I'd never filed it under "engineering." It was just a script. A quick answer to a question I had.&lt;/p&gt;

&lt;p&gt;Every safeguard I own was in the other room.&lt;/p&gt;

&lt;p&gt;So I had "fresh costume" too narrow. The defect doesn't come back as a different bug in the same place. It comes back &lt;strong&gt;in the place you don't think of as a place&lt;/strong&gt; — the throwaway script, the number in the status update, the estimate you gave someone in a meeting. Rigor doesn't travel on its own, because rigor lives in tooling, and tooling is built where you already knew to be afraid.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a control group actually is
&lt;/h2&gt;

&lt;p&gt;The fix cost about ten minutes, which is the annoying part.&lt;/p&gt;

&lt;p&gt;A control group isn't a statistical nicety. It's the one part of an experiment &lt;strong&gt;you don't get to design.&lt;/strong&gt; You choose the hypothesis, the method, the thresholds, the indicators — and then the control sits there, assembled by reality, free to disagree with all of it.&lt;/p&gt;

&lt;p&gt;Someone in a thread here put it better than I can: when you don't have a senior colleague across the table, you build one that can't be argued with. I'd been reading that as advice about tooling. It's actually a definition. &lt;strong&gt;What the person across the table gives you isn't expertise — it's non-participation.&lt;/strong&gt; They didn't help build the assumption, so they're free to reject it. Anything with that property will do. A control group has it. The AI agents I brief with my own framing do not.&lt;/p&gt;

&lt;p&gt;So, the question I'm handing back:&lt;/p&gt;

&lt;p&gt;Where do you make confident numeric claims with no control?&lt;/p&gt;

&lt;p&gt;Probably not in your code. Your code has tests, because someone taught you to be afraid there. I'd look at the metrics dashboard, the conversion number, the one-line summary at the top of your status report. Those get quoted far more often than your test suite does, and most of them have never once been checked against a population somebody else assembled.&lt;/p&gt;

&lt;p&gt;Mine hadn't. It took a stranger's &lt;em&gt;"see you when it does"&lt;/em&gt; to make me go look.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://fromzerotoship.com/blog/#stolen" rel="noopener noreferrer"&gt;Stolen from the Feed&lt;/a&gt; — what a non-developer carries home from dev.to and actually builds. Sister series to From Zero to Ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>community</category>
    </item>
    <item>
      <title>My AI reviewer found nothing for months. I read that as good news.</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Thu, 30 Jul 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/my-ai-reviewer-found-nothing-for-months-i-read-that-as-good-news-3f4o</link>
      <guid>https://dev.to/fromzerotoship/my-ai-reviewer-found-nothing-for-months-i-read-that-as-good-news-3f4o</guid>
      <description>&lt;p&gt;I build a hospital's internal tools as a non-developer, which means AI writes most of the code. So of course I did the responsible thing: I had AI review it too.&lt;/p&gt;

&lt;p&gt;Not casually. I set up separate roles — one agent implements, another runs the tests, another reads the code specifically for security problems. Different instructions, different focus, different pass over the same work. It felt like a review process. For months, the security reviewer flagged almost nothing.&lt;/p&gt;

&lt;p&gt;I read that as good news.&lt;/p&gt;

&lt;p&gt;Then, over three days on this site, strangers found eight real defects in the same systems. Not one of them came from my reviewer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number that made me stop
&lt;/h2&gt;

&lt;p&gt;Here's the honest tally from that stretch. A check that asserted &lt;em&gt;something&lt;/em&gt; was detected rather than &lt;em&gt;the specific thing&lt;/em&gt; it was supposed to detect. An exclusion guard that verified a pattern could recognize a folder, but never that the shipping run actually excluded it — which had been silently mis-scoring six clean files for weeks. A blind spot in the opposite direction, where widening that same pattern could swallow real code and produce a perfectly green "zero problems." An exception list printed in the report and excluded from the pass/fail entirely. A drill I performed by hand, once, months ago, whose result had quietly expired. A scheduled job that was supposed to run all of this daily and had never been registered at all.&lt;/p&gt;

&lt;p&gt;Eight, roughly, depending on how you count. Every single one found by someone outside my setup, usually in a comment, usually aimed at something I had just written about proudly.&lt;/p&gt;

&lt;p&gt;My reviewer agent had been reading that same code for months. It never mentioned any of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I had backwards
&lt;/h2&gt;

&lt;p&gt;My first instinct was that the reviewer needed better instructions. That's the comfortable conclusion, because it's fixable with a prompt.&lt;/p&gt;

&lt;p&gt;Then someone in a thread said the thing that actually explained it: &lt;em&gt;two models agreeing is often one failure mode with two signatures.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'd been treating role separation as independence. It isn't. My implementer and my reviewer are the same model underneath, given different jobs. Different hats, one prior. Whatever the implementer was structurally unable to notice, the reviewer was unable to notice for exactly the same reason — not because it was careless, but because it shares the shape of the blind spot. Asking it to check the work is asking the same mind to reconsider its own assumption, which is the thing minds are worst at.&lt;/p&gt;

&lt;p&gt;There's a second, worse layer. My agents are not outside the conversation about my systems. &lt;strong&gt;They are the conversation.&lt;/strong&gt; They hold the context I gave them, the framing I chose, the vocabulary I use for my own tools. A stranger reading my post has none of that. They aren't smarter — they're just standing somewhere I can't stand, and neither can anything I briefed.&lt;/p&gt;

&lt;p&gt;The human equivalent is two colleagues who've worked together for years: still two people, still two signatures, but their disagreements have already been resolved into a shared way of seeing. Same trap. AI just gets there instantly, on day one, by construction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The silence I misread
&lt;/h2&gt;

&lt;p&gt;Here's what stings, because I'd already written two posts about this exact mistake in a different costume.&lt;/p&gt;

&lt;p&gt;A reviewer that finds nothing and a reviewer that isn't really looking produce &lt;strong&gt;identical output&lt;/strong&gt;: nothing. I know this. I've built dead-man switches on this principle — checks that alarm on the &lt;em&gt;absence&lt;/em&gt; of a fresh signal, precisely because silence and health look the same from outside. And I still read months of quiet from my reviewer as evidence the code was clean.&lt;/p&gt;

&lt;p&gt;I never once seeded it. I never handed it a file with a known planted flaw to confirm it could still see. I'd done exactly that for my automated scanner — planted ten known-bad patterns, discovered it caught seven — and it never occurred to me to do the same for the reviewer, because a reviewer feels like a colleague rather than an instrument. That's the whole error in one sentence. &lt;strong&gt;It's an instrument. I'd just never held a match under it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The tower I built instead
&lt;/h2&gt;

&lt;p&gt;Looking back, everything I've built this month is a monument to not trusting the previous layer's silence.&lt;/p&gt;

&lt;p&gt;I stopped trusting my own judgment about the code, so I had an AI review it. I stopped trusting the review, so I built a mechanical scanner with explicit rules. I stopped trusting the scanner's green light, so I planted known-bad fixtures to prove it could see. I stopped trusting that those fixtures still meant anything, so I built a drill that deliberately breaks each guard and confirms it goes red for its own reason. And then I stopped trusting the drill, because it turned out nothing was checking whether the drill itself still ran — so a separate machine, on a separate schedule, now watches for its proof of life.&lt;/p&gt;

&lt;p&gt;Five layers. Each one exists because the layer below it could go quiet without telling me.&lt;/p&gt;

&lt;p&gt;And here's the thing I can't build my way out of: &lt;strong&gt;every one of those layers was designed by me and written by the same AI.&lt;/strong&gt; They fail in different ways, which genuinely helps — a rule regression, a dead gate, and a stopped scheduler don't happen for the same reason on the same day. But diversity of failure mode is not independence of origin. The whole tower shares an author.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually stands outside
&lt;/h2&gt;

&lt;p&gt;The only thing in this entire setup that has ever reliably stood outside is other people.&lt;/p&gt;

&lt;p&gt;Not because they're better engineers than my agents — several of the sharpest corrections came in three sentences from someone who never saw a line of my code. They worked because they weren't in the room when the assumption was made. They read what I was pleased with, and &lt;em&gt;pleased-with&lt;/em&gt; turns out to be a reliable marker for &lt;em&gt;under-tested&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So the practice I've landed on, stated plainly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agreement inside the room is not evidence.&lt;/strong&gt; If the second opinion comes from the same model, the same session, or the same framing I supplied, it's one opinion with two signatures. It's useful for catching slips. It is worthless for catching assumptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anything that can only stay silent is not a check.&lt;/strong&gt; Including a reviewer. Seed it, or admit you're reading its quiet as proof of something it never claimed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The genuinely external check has to cost something to obtain.&lt;/strong&gt; A stranger's attention, a published mistake, a post that invites people to aim at your best work. It doesn't scale, it doesn't run nightly, and it's the only layer I have that isn't downstream of my own assumptions.&lt;/p&gt;

&lt;p&gt;That's an uncomfortable conclusion for someone whose entire toolkit is AI-built. I'm not going to pretend it resolves neatly. But five layers of automated checking, and the thing that found the live bug was a stranger with a spare five minutes and no stake in my being right.&lt;/p&gt;

&lt;p&gt;Consider that the fence I'm handing back. If you're building with AI and having AI check the work: when did your reviewer last tell you something you didn't want to hear? And if the answer is "it doesn't really find much" — that's the same silence I spent months reading as good news.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://fromzerotoship.com/blog/#stolen" rel="noopener noreferrer"&gt;Stolen from the Feed&lt;/a&gt; — what a non-developer carries home from dev.to and actually builds. Sister series to From Zero to Ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>community</category>
    </item>
    <item>
      <title>#S5 The Stranger Who Rewrote My Test Four Times</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Tue, 28 Jul 2026 12:32:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/s5-the-stranger-who-rewrote-my-test-four-times-1ola</link>
      <guid>https://dev.to/fromzerotoship/s5-the-stranger-who-rewrote-my-test-four-times-1ola</guid>
      <description>&lt;p&gt;Last post ended on a line I meant sincerely and had not yet paid for: don't hold the pen alone.&lt;/p&gt;

&lt;p&gt;This one is the receipt. One stranger, three replies over two days, and four versions of the same check — each version breaking the thing I had just been proud of. By the fourth, we'd found a bug that had been quietly live for weeks.&lt;/p&gt;

&lt;p&gt;They never saw my code. They didn't need to. They only needed to see what I was pleased with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 1: the one I bragged about
&lt;/h2&gt;

&lt;p&gt;Here's where it starts. I run a scanner over my own code that looks for security problems — hardcoded secrets, unescaped output, that family of mistakes. I'd finally done the obvious thing and tested whether it could actually see: planted ten known-bad patterns on purpose and ran it.&lt;/p&gt;

&lt;p&gt;Seven of ten. I wrote rules for the three it missed, ran it again, got ten of ten.&lt;/p&gt;

&lt;p&gt;That was the version I wrote about. Detector seeded, blind spots closed, receipts in hand. I felt like I'd graduated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 1 → Version 2: "watch it fail for the &lt;em&gt;right&lt;/em&gt; reason"
&lt;/h2&gt;

&lt;p&gt;The reply came with three points, and the third one is the one that changed code that night.&lt;/p&gt;

&lt;p&gt;Their argument: my test asserted that &lt;em&gt;a finding fired&lt;/em&gt;, not that &lt;em&gt;the finding I planted&lt;/em&gt; fired. So a fixture that went red because I'd fat-fingered a path, then green after I "fixed" some unrelated rule, would sail through looking like a passing test — red and green both for reasons that had nothing to do with what I meant to check. Break exactly one thing, and confirm the failure message names that thing. Otherwise you've just watched a different gate fail and called it proof.&lt;/p&gt;

&lt;p&gt;They were right, and it was worse than they knew: my report printed the exceptions — the known blind spots, the known false alarms — and excluded them from the pass/fail entirely. I'd built a readout and wired it to nothing that could fail.&lt;/p&gt;

&lt;p&gt;So version 2: assert the &lt;em&gt;specific&lt;/em&gt; pattern, not just "something fired." Assert the fixture count so a silently deleted seed can't turn into a flattering "9 out of 9, 100%." Split a miss into &lt;em&gt;the rule regressed&lt;/em&gt; versus &lt;em&gt;nothing was even loaded&lt;/em&gt;, so a red says which kind it is.&lt;/p&gt;

&lt;p&gt;I ran it. Green. I replied, pleased with myself again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 2 → Version 3: the trap inside my fix
&lt;/h2&gt;

&lt;p&gt;The second reply is where this stops being a tidy story about improvement.&lt;/p&gt;

&lt;p&gt;I had added a guard to confirm my scanner was excluding the folder of planted fixtures — you don't want deliberately-broken test files scored as real findings. My guard checked that the exclusion pattern &lt;em&gt;recognized&lt;/em&gt; the folder. It passed. I moved on.&lt;/p&gt;

&lt;p&gt;Their point: that's the self-inclusion trap again, one level up. Pointing the checker at the fixture folder proves the checker can see the folder when told to. The exclusion that can break silently governs a &lt;em&gt;different invocation&lt;/em&gt; — the production run over the whole tree. Two claims, and only one of them ships. &lt;strong&gt;Write the predicate on the run that ships.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I did. I stopped asking "does the pattern recognize this folder" and started asking "does the actual production scan actually exclude it."&lt;/p&gt;

&lt;p&gt;It didn't.&lt;/p&gt;

&lt;p&gt;The pattern matched &lt;code&gt;tests/seed&lt;/code&gt; but not &lt;code&gt;tests/seed-clean&lt;/code&gt; — the trailing &lt;code&gt;-clean&lt;/code&gt; fell just outside the boundary the regex anchored on. One character's worth of gap. Six clean fixtures had been scored as real findings in every run for weeks, and every one of those runs came back green, because the run and the report were written by the same thing in the same breath.&lt;/p&gt;

&lt;p&gt;I want to be precise about what happened there, because it's the whole point of this post: the drill didn't prevent that bug. It performed the autopsy. A gate that's silently broken doesn't just fail to catch things — it removes the possibility of catching them early, because "no failure reported" is exactly what it produces while broken.&lt;/p&gt;

&lt;p&gt;And I would not have found it. My guard was green, and a fluent green never asks to be opened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 3 → Version 4: the danger my fix created
&lt;/h2&gt;

&lt;p&gt;Here's the part I didn't see coming, and the reason a single round of feedback wouldn't have been enough.&lt;/p&gt;

&lt;p&gt;To fix the leak I &lt;em&gt;widened&lt;/em&gt; the exclusion pattern. That's the obvious repair, and the third reply aimed straight at it: an exclude that fails OPEN produces findings — noisy, catchable. An exclude that gets BROADER produces silence. If the pattern was fragile enough to fail once, the fix that widens it can start swallowing real source paths, and "zero findings under the seed folder" stays trivially true while the run scans half of what it used to.&lt;/p&gt;

&lt;p&gt;Their instruction: assert the file &lt;em&gt;count&lt;/em&gt; the production run actually scanned, not only what it found. Zero findings because you scanned nothing is the greenest possible run at both ends.&lt;/p&gt;

&lt;p&gt;So version 4 asserts the denominator. Specific files that must be present in every scan, and a floor under the total number scanned. Findings alone can't distinguish "nothing wrong" from "nothing looked at."&lt;/p&gt;

&lt;p&gt;They also caught something subtler in the same reply, which I'll compress: when I pinned the set of rules each fixture was allowed to trip, I'd quietly changed what the assertion &lt;em&gt;meant&lt;/em&gt; — from "this fixture proves the scanner catches X" to "this is what my scanner currently does." The first legitimate new rule turns that red, the reflex is to re-pin, and that's the moment the fixture stops being a test. Keep the two claims apart: the intended rule is the coverage claim and must never be re-pinned away; the rest is a change detector.&lt;/p&gt;

&lt;h2&gt;
  
  
  What came after: making the match automatic
&lt;/h2&gt;

&lt;p&gt;One more turn, and it came from a different direction — from noticing that my own habit had the same defect I'd been diagnosing all week.&lt;/p&gt;

&lt;p&gt;My planted fixtures re-run automatically on every change. But the &lt;em&gt;drill&lt;/em&gt; — deliberately breaking a gate to confirm it still goes red — was something I did by hand, once, when I happened to think of it. Which makes the drill the one unprotected part of the whole setup, by exactly the argument I'd been making for days.&lt;/p&gt;

&lt;p&gt;In test-driven development the red is a one-time event: you see it before the code exists, and afterward the test carries itself. A gate's red is not an event. It's a perishable state. It proves the check worked on the day you looked, and says nothing about whether the condition that makes it fire is still reachable next month. Mine rotted precisely there — falsifiable by design, unreachable in practice, so "no failure observed" stayed true for the wrong reason.&lt;/p&gt;

&lt;p&gt;So now every guard gets broken automatically, on every run, and has to go red &lt;em&gt;for its own reason&lt;/em&gt; — matching the expected message, not just returning a nonzero exit, because exit codes alone let one gate's failure pass as another's proof. Then everything is restored, and the restore is itself verified against a snapshot.&lt;/p&gt;

&lt;p&gt;The first time I ran it, I broke one guard on purpose to see whether the drill could detect a dead one. It reported: &lt;em&gt;you broke this and it still passed — this guard is dead.&lt;/em&gt; Which is the only reason I now believe the other seven.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why none of this was possible alone
&lt;/h2&gt;

&lt;p&gt;Four versions. Each one fixed a real defect. Each one created or exposed the next.&lt;/p&gt;

&lt;p&gt;And here's the thing I keep turning over: none of the three replies required knowing my codebase. They required standing outside it. Every single time, the flaw was in &lt;strong&gt;the thing I had just built and was still proud of&lt;/strong&gt; — and that's the exact object a maker can't examine. The newest fix is the least suspected and has the least evidence behind it. I don't audit what I finished an hour ago; I audit what I've grown suspicious of, and suspicion takes time I hadn't spent yet.&lt;/p&gt;

&lt;p&gt;A stranger has none of that history. They read what I was pleased with, and pleased-with is a reliable marker for under-tested.&lt;/p&gt;

&lt;p&gt;I'd have stopped at version 1. Version 1 was checking that a regex could read a folder name. It would have stayed green for months while six clean files were scored as broken, and I'd have gone on telling people I'd seeded my detector — which was true, and which was not the same as it working.&lt;/p&gt;

&lt;p&gt;That's what "you can't hold the pen alone" actually looks like when you write it down. Not a principle. Three replies from someone who owed me nothing, each one aimed at the last thing I'd bragged about.&lt;/p&gt;

&lt;p&gt;Consider this post the fence I'm returning. If you're running a check you're proud of: what would happen if you broke it on purpose — and would it fail for the reason you expect, or just fail?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://fromzerotoship.com/blog/#stolen" rel="noopener noreferrer"&gt;Stolen from the Feed&lt;/a&gt; — what a non-developer carries home from dev.to and actually builds. Sister series to From Zero to Ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>community</category>
    </item>
    <item>
      <title>#S4 Who Holds the Pen: The One Check You Can't Run on Yourself</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Mon, 27 Jul 2026 13:16:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/s4-who-holds-the-pen-the-one-check-you-cant-run-on-yourself-c05</link>
      <guid>https://dev.to/fromzerotoship/s4-who-holds-the-pen-the-one-check-you-cant-run-on-yourself-c05</guid>
      <description>&lt;p&gt;Two posts ago I told you five strangers audited my systems in a single day and didn't know it. I was pleased with that line. It made me the clever one — the guy who mines the feed for other people's hard-won lessons and quietly ships them into his own tools.&lt;/p&gt;

&lt;p&gt;This is the post where the feed audited &lt;em&gt;me&lt;/em&gt; back.&lt;/p&gt;

&lt;p&gt;The same week I published a piece bragging that I'd finally learned to steal the check instead of the confidence, a stranger read it and — without meaning to — walked me straight into the one check I'd never run: the one on the checker itself. What they left me with is the question this whole series had been circling and I kept not asking out loud. &lt;em&gt;Who checks the checker?&lt;/em&gt; And the answer, which cost me an embarrassing week to actually believe: not you. Not alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pen
&lt;/h2&gt;

&lt;p&gt;Start with the pen, because that's the whole problem in one image: who's holding it when the verdict gets written.&lt;/p&gt;

&lt;p&gt;The failure that opened all this was dumb and quiet. I have a scanner that reads my own code for problems, and it's supposed to skip a folder of deliberately-broken test files so it doesn't score those fixtures as real findings. For weeks it had been scoring six &lt;em&gt;clean&lt;/em&gt; files as broken — a one-character gap in the exclusion rule — and every single run came back green, because the run and the report were written by the same thing in the same breath.&lt;/p&gt;

&lt;p&gt;Someone in the comments put the general form of it better than I could: &lt;em&gt;the receipt is as forgeable as the work, because it has the same author.&lt;/em&gt; A worker that can invent the result can invent the proof of it in the same response. My scanner said "passed," and "passed" was a sentence it wrote about itself. The question was never whether the check is clever. It's &lt;em&gt;who controls the channel the result comes back on.&lt;/em&gt; If the thing being graded is holding the pen, the grade is a self-portrait.&lt;/p&gt;

&lt;h2&gt;
  
  
  The recursion
&lt;/h2&gt;

&lt;p&gt;Fine, I thought. Don't let the checked thing grade itself. Put a checker outside it.&lt;/p&gt;

&lt;p&gt;So I did — the scanner now runs its own independent query instead of reading a status the code wrote about itself. Then, because I'd just been burned, I asked the next question: what checks &lt;em&gt;that&lt;/em&gt;? I added a dead-man's switch — a separate job that screams if the checker ever goes quiet. Now the guard has a guard.&lt;/p&gt;

&lt;p&gt;And you can see the cliff coming. What guards the guard's guard? The recursion doesn't terminate on its own. It has to stop at a human eventually — at me, looking. I told myself that was the floor. The whole tower of checking bottoms out at the one party in the building with a conscience: me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The human holds a pen too
&lt;/h2&gt;

&lt;p&gt;A stranger kicked the floor out from under that.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Your scanner has a conscience because you're the one forced to look at it,&lt;/em&gt; they wrote, &lt;em&gt;not because it does. Take you out and it flatters itself clean by Monday.&lt;/em&gt; I started typing a rebuttal — I'm the external check, that's the entire point — and stopped, because it wasn't true. I hadn't looked. Six files leaked for weeks and I never once opened the report, because it was green, and a fluent green never asks to be opened. My conscience only fires on what visibly demands it. The failure that's dressed as success walks right past it, smiling.&lt;/p&gt;

&lt;p&gt;That's the part that took a full week to land: &lt;em&gt;a healthy-looking control and a silently dead one are indistinguishable from the outside.&lt;/em&gt; I &lt;em&gt;was&lt;/em&gt; the outside. And from the outside, the dead one looked exactly like health. Being human hadn't put me outside the loop. Trusting the green had put me inside it, wearing a lab coat. I was holding a pen too — I'd just written my verdict by &lt;em&gt;not&lt;/em&gt; writing one, by letting the silence stand as a pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you can't do it alone
&lt;/h2&gt;

&lt;p&gt;So if the checker can't check itself, and I can't be trusted to check the checker just because I'm the human in the room — what's actually outside?&lt;/p&gt;

&lt;p&gt;Three things, and I stole all three this month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One: a hand with a different motive — not just a second hand.&lt;/strong&gt; Someone pointed out that two sign-offs buy you nothing if the same incentive reaches both signers: &lt;em&gt;you haven't added an independent party, you've added a second instance of the same interested party.&lt;/em&gt; Where I build — a hospital — that isn't abstract. It's two people on the same shift who both benefit, or one quietly covering for the other. And the independence isn't even stable: decorrelated on Monday, correlated by Friday after a reorg drops both signers under the same manager's number. A second signature only re-prices the forgery if the second hand can't be bought by the first one's reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two: a gate that calls me whether I'm looking or not.&lt;/strong&gt; &lt;em&gt;A gate you haven't seen fail is not a gate.&lt;/em&gt; &lt;em&gt;A test that passes against a broken implementation is worse than no test — it's a false sense of safety.&lt;/em&gt; The only way I now trust any check is the failure-then-pass cycle: deliberately break the exact thing the gate exists to catch, confirm it goes red &lt;em&gt;for that reason&lt;/em&gt;, then repair it and watch it go green. A check I've only ever seen pass is a check I've never seen work. This week I put a match under every gate I owned — and the exclusion gate, the one that had been green for weeks, refused to go red for the boundary I'd never tested. That's how I finally saw the leak: not by looking harder, but by building the gate that could shove the red in front of me on the days I'd stopped paying attention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three: other people.&lt;/strong&gt; Which is the whole reason this series exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the recursion actually stops
&lt;/h2&gt;

&lt;p&gt;Here's the turn I didn't see coming when I named this series.&lt;/p&gt;

&lt;p&gt;Every earlier post was me stealing from the feed — carrying home a stranger's lesson and building it into my tools. This post is the feed stealing back. The leak, the dead-man's switch, the match under every gate, the entire realization that I cannot be my own last check — none of it is mine. It came in through the comments on the posts you've already read. A stranger who told me my conscience was really just my attention. Another who named the forgeable receipt and asked who owns the channel. Another who showed me that independence drifts. Another who made me break my own gates and watch what the dashboard did. I didn't write this post. The people who audited the last three did. I just held the pen while they moved my hand.&lt;/p&gt;

&lt;p&gt;That's the real answer to &lt;em&gt;who checks the checker.&lt;/em&gt; Not a person — a person holds a pen, and any single pen writes its own verdict in the end. It's a mesh: independent hands with different reasons, gates rigged to fail loudly enough to reach you when you've stopped looking, and strangers who owe you nothing reading your green lights back to you out loud. The recursion doesn't bottom out at me. It bottoms out at everyone who &lt;em&gt;isn't&lt;/em&gt; me.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's not a heist if the door's open
&lt;/h2&gt;

&lt;p&gt;I've been closing every post in this series with that line as a note about generosity — take a line, leave a line, that's the economy. It turns out it was also the load-bearing wall the whole time.&lt;/p&gt;

&lt;p&gt;The open door is the only reason anyone was standing outside my systems to see what I structurally couldn't. A closed workshop isn't safe — it's just a room where you grade your own homework in perfect, silent, confident green, with no one on the outside to tell you the control went dead in July. The generosity and the verification are the same act. You leave the door open, and the person who walks through it becomes the one check you were never able to run on yourself.&lt;/p&gt;

&lt;p&gt;So that's the series, and this is where it lands.&lt;/p&gt;

&lt;p&gt;Steal the check, not the confidence — that was the last one. Here's the one underneath it, the last thing I've got: &lt;strong&gt;don't hold the pen alone.&lt;/strong&gt; Build the gates that fail in front of you. Add the hand with the other motive. And leave the door open, because the stranger who walks through it can see the green light you've already stopped reading.&lt;/p&gt;

&lt;p&gt;Four posts of stolen goods. The bag was never really mine — it was the room's. Thank you for leaving the door open.&lt;/p&gt;

&lt;p&gt;It's your turn to rob me.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the finale of &lt;a href="https://fromzerotoship.com/blog/#stolen" rel="noopener noreferrer"&gt;Stolen from the Feed&lt;/a&gt; — what a non-developer carries home from dev.to and actually builds. Sister series to From Zero to Ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>ai</category>
      <category>productivity</category>
      <category>community</category>
    </item>
    <item>
      <title>#S3 The Loot Report, Vol. 2: Four Lines That Fixed the Boring Parts</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Sun, 26 Jul 2026 13:28:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/s3-the-loot-report-vol-2-four-lines-that-fixed-the-boring-parts-1ajb</link>
      <guid>https://dev.to/fromzerotoship/s3-the-loot-report-vol-2-four-lines-that-fixed-the-boring-parts-1ajb</guid>
      <description>&lt;p&gt;Last Loot Report was a single day's haul. This one is slower — four lines I lifted over a few weeks, each one aimed at the least glamorous corner of my systems: the checks that were supposed to be watching and quietly weren't. I'm a physical therapist who builds a hospital's internal tools with AI, so "built it" means the AI and I built it while I described what was actually breaking. Every one started as somebody else's sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loot #1: a match for the smoke detector
&lt;/h2&gt;

&lt;p&gt;Someone ran a free security scanner against ten vulnerabilities they'd planted on purpose. It caught three. Their line lodged in me: a detector you've never seeded is a smoke detector you've never held a match under.&lt;/p&gt;

&lt;p&gt;I had exactly that — a static security scanner watching my own codebase — and I had never once tested whether it could actually see. So I planted ten known-bad patterns and ran it. Seven of ten. I wrote rules for the three it missed, ran it again, got ten of ten. Then the part that made me laugh, because it happened to them too: the moment I added the "hardcoded secret" rule, it fired on &lt;em&gt;real&lt;/em&gt; code — three live API keys I'd have sworn weren't there, in programs I'd already "reviewed." Seeding the detector was quietly also an audit of everything it had been failing to catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loot #2: a guard for the guard
&lt;/h2&gt;

&lt;p&gt;Someone wrote about a monitoring hook that sat silently dead for 23 days. Nobody noticed, because zero warnings looked exactly like healthy. Their line: a dead guard produces the same visible result as a clean pass — nothing.&lt;/p&gt;

&lt;p&gt;I'd just made my systems' health-check smarter: it now runs a real database query instead of trusting that a page loaded. I was proud of it for about a day, until that post showed me the hole. I'd made the health-check smarter, but &lt;em&gt;nothing was watching the health-check.&lt;/em&gt; If its scheduler quietly stopped firing, my dashboard would stay green out of pure absence — and I'd have read that silence exactly the way their team did. So I added a dead-man's switch: a separate job that raises the alarm if the health-check didn't leave a fresh proof-of-life behind. Now the guard has a guard. (The recursion has to stop at a human eventually — but one layer up from where I was.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Loot #3: the one you're reading right now
&lt;/h2&gt;

&lt;p&gt;Someone built a hook that stops an AI agent from doing all the research and then punting the decision back to you — "so which do you prefer?" — instead of committing to a recommendation.&lt;/p&gt;

&lt;p&gt;I gave the little team of AI agents I run a standing rule: recommend one option, give a one-line reason, and name the assumption that would make it wrong. Felt good. Then the author said something that stuck: don't trust that it's working — count the leak. So I actually counted. I went back over one long working session and tallied every point where a decision got handed back to me. Full punts were zero of six. But in two of six, I'd quietly dropped the "here's what would make this wrong" line — and both times it was on calls I'd filed as &lt;em&gt;yours to make anyway.&lt;/em&gt; The exemption I'd given myself was the exact place it leaked, and I'd never have seen it without counting.&lt;/p&gt;

&lt;p&gt;Now here's the theft you didn't notice. The way I've been making recommendations across this whole series — "here's my pick, here's the reason, here's the assumption that would make it wrong" — I stole that too. From that post. You've been reading stolen goods this entire time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loot #4: an eye that can't lie
&lt;/h2&gt;

&lt;p&gt;Someone building a design tool wrote the sentence I needed most: my eyeballs were wrong 40 percent of the time; a short script was right every time.&lt;/p&gt;

&lt;p&gt;I'd been approving the color contrast in my tools by eye for months — squinting at a button, deciding it looked fine, moving on. That afternoon I wrote a tiny script that checks contrast against the accessibility standard, and ran my whole palette through it. The colors I'd agonized over were fine. But a link color I'd approved by eye months earlier was sitting below the readable threshold — quietly failing, invisibly, the whole time, for anyone whose eyes weren't as forgiving as mine. I'd never have caught it by looking. That's the point: looking was the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What all four have in common
&lt;/h2&gt;

&lt;p&gt;Line them up and they're the same lesson wearing four coats. The scanner that says it ran. The monitor that says it's up. The agent that says it's done. The eye that says it looks fine. Every one is a thing grading its own homework, and every fix was the same move: &lt;strong&gt;steal the check, not the confidence.&lt;/strong&gt; Put something between you and the thing's opinion of itself — a planted failure, a proof-of-life, a tally, a script that doesn't have feelings about being right.&lt;/p&gt;

&lt;p&gt;I didn't arrive at that principle. I stole it four times from four strangers before I noticed it was one idea. (And if you want the deep version of where that idea goes — who checks the checker, and why you can't do it alone — that's the next post.)&lt;/p&gt;

&lt;h2&gt;
  
  
  It's not a heist if the door's open
&lt;/h2&gt;

&lt;p&gt;Here's what keeps this from being theft: nothing here is one-directional. My count of my own leak went back to the author of that hook, and it patched a blind spot his own tool couldn't see from inside. My notes on Korean typography went into another author's product roadmap. You take a line, you leave a line. That's the whole economy.&lt;/p&gt;

&lt;p&gt;That's what the feed actually is, once you stop treating it as a broadcast channel — a shared workshop where everyone's tools are lying out on the bench, and "I'm stealing that" is just how we say thank you.&lt;/p&gt;

&lt;p&gt;So: thank you. The bag's full. See you next post with a fuller one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://fromzerotoship.com/blog/#stolen" rel="noopener noreferrer"&gt;Stolen from the Feed&lt;/a&gt; — what a non-developer carries home from dev.to and actually builds. Sister series to From Zero to Ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>ai</category>
      <category>productivity</category>
      <category>community</category>
    </item>
    <item>
      <title>#22 Tool #23 Is the Blog You're Reading Right Now</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Sat, 25 Jul 2026 13:25:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/22-tool-23-is-the-blog-youre-reading-right-now-3ha0</link>
      <guid>https://dev.to/fromzerotoship/22-tool-23-is-the-blog-youre-reading-right-now-3ha0</guid>
      <description>&lt;p&gt;The story I told across the last twenty-one posts ended with twenty-two tools running at my hospital.&lt;/p&gt;

&lt;p&gt;This post is what happened next. Tool number twenty-three is the thing you're looking at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a blog, of all things
&lt;/h2&gt;

&lt;p&gt;When the story was finished, I wanted it to travel farther than my own building. So I did the thing that would have sounded absurd to me two years ago: I bought a domain and built a website. Myself.&lt;/p&gt;

&lt;p&gt;The domain cost $10.45 a year — about two cups of coffee. Registering it took ten minutes. A person who once didn't know what code was now owns a corner of the internet, and the entry fee was pocket change. That fact alone still feels strange to type.&lt;/p&gt;

&lt;p&gt;The site itself is a static site builder called Astro, hosted on Cloudflare — &lt;a href="https://fromzerotoship.com/blog/the-first-time-i-went-outside/" rel="noopener noreferrer"&gt;the same Cloudflare from the story&lt;/a&gt;, just wearing a different hat. One command builds everything; a push publishes it. Twenty-one posts went up. A newsletter box went in. So far, nothing here would surprise anyone who read &lt;a href="https://fromzerotoship.com/blog/my-actual-tool-stack/" rel="noopener noreferrer"&gt;the tool list&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then I wanted a comment box, and it got interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comment box problem
&lt;/h2&gt;

&lt;p&gt;The ready-made options all had the same flaw. The popular ones require readers to log in with a developer account — fine for developers, exactly wrong for the people this blog is for, the ones who were told they couldn't code. The other kind is a free widget that pays for itself with ads and tracking. I didn't want either.&lt;/p&gt;

&lt;p&gt;What I actually wanted was the way old Korean forums did it: pick any nickname, type any password, say your piece. No account. No permission needed. It turns out almost nobody offers that as a service anymore.&lt;/p&gt;

&lt;p&gt;And there it was again, the question that starts everything: &lt;em&gt;is there really no other way?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I built it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What building it actually looked like
&lt;/h2&gt;

&lt;p&gt;In roughly a day, this blog got: a small database, a tiny program running on Cloudflare's edge that receives and serves comments, a captcha so the spam bots stay outside, an email that lands in my inbox the moment anyone comments, emoji reactions, replies, edit and delete by password — and a hidden admin page where I can see everything at once.&lt;/p&gt;

&lt;p&gt;I still don't hand-write code. The process was &lt;a href="https://fromzerotoship.com/blog/my-own-navigation/" rel="noopener noreferrer"&gt;the same loop as always&lt;/a&gt;: describe the behavior in plain words, let the AI write it, test it, tell it exactly what broke, repeat.&lt;/p&gt;

&lt;p&gt;And in the spirit of honesty this blog runs on, the missteps were included, as usual:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I first installed a ready-made comment system, shipped it, and only then noticed it forced readers to log in. It was live for about an hour before I tore it out and started over. Shipping the wrong thing taught me what the right thing was — &lt;a href="https://fromzerotoship.com/blog/what-you-actually-need-to-build-software/" rel="noopener noreferrer"&gt;a paid lesson, not a loss&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Twice I deployed changes, saw nothing update, and briefly panicked. The culprit both times: a cache serving the old page. The fix was to wait. The real lesson was to stop assuming I'd broken something.&lt;/li&gt;
&lt;li&gt;My emoji reactions "didn't work" in testing — until it turned out the tool was fine and my &lt;em&gt;terminal&lt;/em&gt; was mangling the emoji before they ever left my machine. The bug is usually somewhere dumber than you think.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this required becoming a developer first. It required describing what I wanted, refusing the first "can't be done," and testing like a suspicious customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this actually means
&lt;/h2&gt;

&lt;p&gt;Here is the part I keep coming back to. A working comment system — database, server, spam protection, notifications — used to be the kind of thing you hired someone for, or did without.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The distance between "I wish this existed" and "it exists" has gotten so short that the only meaningful cost left is deciding to start.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That was true for a parking-lot lookup tool at a hospital in Korea, and it's true for the page you're reading. Same person, same method, no credentials acquired in between.&lt;/p&gt;

&lt;h2&gt;
  
  
  The demo is right below
&lt;/h2&gt;

&lt;p&gt;So here's the ending this post was always going to have.&lt;/p&gt;

&lt;p&gt;The comment box under this article &lt;em&gt;is&lt;/em&gt; tool twenty-three, or at least its newest part. No login. Any nickname, any password, and a captcha the bots can't pass. Type "hi" and hit post — that's enough.&lt;/p&gt;

&lt;p&gt;When you do, an email will land in my inbox within seconds, because I wired that too, without knowing how to code, in an afternoon.&lt;/p&gt;

&lt;p&gt;Go ahead. Leave the proof.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://fromzerotoship.com" rel="noopener noreferrer"&gt;From Zero to Ship&lt;/a&gt; — the honest logbook of a non-developer who shipped 20+ working tools with AI. If this resonated, the rest of the series is there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>ai</category>
      <category>productivity</category>
      <category>career</category>
    </item>
  </channel>
</rss>
