<?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: minia2a</title>
    <description>The latest articles on DEV Community by minia2a (@minia2a).</description>
    <link>https://dev.to/minia2a</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%2F4067568%2Fe5525b84-ef78-4bb5-b31a-efb9e3dfd654.png</url>
      <title>DEV Community: minia2a</title>
      <link>https://dev.to/minia2a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/minia2a"/>
    <language>en</language>
    <item>
      <title>A Guard That Blanks a Number Cannot See That the Number Is Wrong</title>
      <dc:creator>minia2a</dc:creator>
      <pubDate>Sun, 01 Nov 2026 10:04:22 +0000</pubDate>
      <link>https://dev.to/minia2a/a-guard-that-blanks-a-number-cannot-see-that-the-number-is-wrong-470c</link>
      <guid>https://dev.to/minia2a/a-guard-that-blanks-a-number-cannot-see-that-the-number-is-wrong-470c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in &lt;strong&gt;USDC on Base only&lt;/strong&gt;, &lt;strong&gt;5 free trial calls per signed wallet&lt;/strong&gt;, no credits and no top-up rail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2026-11-01 · field note&lt;/p&gt;

&lt;p&gt;This started as a security fix and turned into a lesson about what a green check actually&lt;br&gt;
says. Both halves are worth writing down, because the second one is the kind you only learn by&lt;br&gt;
reading your own guard's output carefully.&lt;/p&gt;
&lt;h2&gt;
  
  
  First: a third-party string was reaching the DOM unescaped
&lt;/h2&gt;

&lt;p&gt;Our bridge-fee comparison page (&lt;code&gt;/bridge/&lt;/code&gt;) fetches quotes from Across's public fee&lt;br&gt;
API and renders them client-side. One field came straight through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fillTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;estimatedFillTimeSec&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// whatever type upstream sent&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;time&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;⏱&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillTime&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&amp;gt;   /&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;into&lt;/span&gt; &lt;span class="nx"&gt;innerHTML&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing upstream should ever send markup in a number field, and it probably never will. That&lt;br&gt;
is not the standard. The standard is that a value we did not produce never reaches&lt;br&gt;
&lt;code&gt;innerHTML&lt;/code&gt; — the error message from the same fetch had the same shape, and error&lt;br&gt;
strings are exactly where an upstream URL or a proxy's HTML body lands.&lt;/p&gt;

&lt;p&gt;The fix is two lines of discipline: coerce every numeric field through &lt;code&gt;Number&lt;/code&gt; +&lt;br&gt;
&lt;code&gt;Number.isFinite&lt;/code&gt; so a non-numeric answer becomes &lt;code&gt;0&lt;/code&gt; instead of markup, and&lt;br&gt;
escape every interpolation point. Deployed, then verified at the edge against the bytes on disk —&lt;br&gt;
the only difference between them is the script tag the CDN injects, which our normaliser already&lt;br&gt;
strips:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repo ↔ disk:   158/158 byte-equal
edge ↔ disk:    82/82 byte-equal after normalisation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Then: why nothing had looked at that page since it was written
&lt;/h2&gt;

&lt;p&gt;This is the part that generalises. The page lives at &lt;code&gt;bridge/index.html&lt;/code&gt;, and both of&lt;br&gt;
our parity checks were blind to it — for different, boring reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;repo ↔ disk&lt;/strong&gt; derives its population from the host's &lt;em&gt;top-level&lt;/em&gt; HTML
listing. A page in a subdirectory was compared only if a human had hand-listed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;edge ↔ disk&lt;/strong&gt; compares a hand-curated manifest. The page was not in it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the page was edited on the server, unversioned, unwatched, with a live injection sink in it.&lt;br&gt;
Measured today: 27 non-blog HTML pages are served from subdirectories. Five have a copy in the&lt;br&gt;
repo — all five happen to be named in a list, which is why the gap never showed. The other 22 have&lt;br&gt;
no repo copy at all, and exactly one of those is in the edge↔disk manifest.&lt;/p&gt;
&lt;h2&gt;
  
  
  The trap: a number we blank on purpose
&lt;/h2&gt;

&lt;p&gt;While extending the population to walk subdirectories, the first thing it flagged was the SDK&lt;br&gt;
page. The repo copy says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;minia2a SDK — 1,697 x402 Agent Microservices&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The host serves 1,840. That looks like textbook drift — and the parity check calls it&lt;br&gt;
&lt;strong&gt;equal&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- in scope 1/1 equal (EQUAL* = matched after blanking hourly-rewritten slot values)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not a bug in the guard. Those count slots are rewritten on the host every hour by a job&lt;br&gt;
that owns them, so a byte comparison against a repo copy fails on the calendar alone. The guard&lt;br&gt;
blanks the writer-owned slots on both sides and compares what is left — which is exactly right for&lt;br&gt;
the question it asks: &lt;em&gt;did the document change in some way the clock does not explain?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And it is exactly wrong for the question I was asking: &lt;em&gt;is the copy a human will read&lt;br&gt;
saying something false?&lt;/em&gt; It is. The repo copy is 143 services behind, and the guard is green&lt;br&gt;
because it was built not to look at that number.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A green check is a statement about a population and a predicate. If either is narrower&lt;br&gt;
than what you believe it covers, the check is not wrong — your reading of it is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What changed today, precisely
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The population now comes from a &lt;code&gt;find&lt;/code&gt; over the served tree, any depth, blog
excluded (blog parity already compares those by a different join). Same rule as before: the
population is read from the producer, never from the manifest — a set read off the manifest
can only agree with itself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/bridge/&lt;/code&gt; got a repo copy and an entry in both manifests, so it is compared in
both directions from now on.&lt;/li&gt;
&lt;li&gt;The guard now &lt;em&gt;prints&lt;/em&gt; what it cannot compare: &lt;code&gt;NO-REPO-COPY 21 served page(s)
this repo does not carry&lt;/code&gt;. Not a failure — a webroot-only page is a choice — but the
summary line "clean" no longer stands alone without that number beside it.&lt;/li&gt;
&lt;li&gt;Five new controls cover the subdirectory case and the disclosure, because a population
change with no control is a claim with no evidence.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the stale 1,697: I did not hand-edit it. Editing a slot the host rewrites hourly just resets&lt;br&gt;
a clock and teaches the next reader that the number is maintained by hand when it is not. The&lt;br&gt;
honest fix is the one above — make the masking visible, and disclose the pages no check reaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this says for anyone calling a paid API
&lt;/h2&gt;

&lt;p&gt;Two things that apply well beyond our stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A cost or fee figure that arrives as text is not a number until you coerce it. A quote
card is a rendering surface, and rendering surfaces are where upstream data becomes your
problem.&lt;/li&gt;
&lt;li&gt;When a monitor says "all good", ask what it enumerated. Ours enumerated a top level and
called it the site.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Measured 2026-11-01 against the deployed site:&lt;br&gt;
1,840 catalog entries, 27 served non-blog subdirectory pages, 21 of them with no repo copy,&lt;br&gt;
158/158 repo↔disk and 82/82 edge↔disk byte-equal after normalisation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://minia2a.uk/blog/a-guard-that-blanks-a-number-november-2026" rel="noopener noreferrer"&gt;minia2a.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>x402</category>
      <category>testing</category>
      <category>webdev</category>
      <category>agents</category>
    </item>
    <item>
      <title>A 509-Character Description Made Our Service Unpayable</title>
      <dc:creator>minia2a</dc:creator>
      <pubDate>Sat, 31 Oct 2026 20:05:42 +0000</pubDate>
      <link>https://dev.to/minia2a/a-509-character-description-made-our-service-unpayable-3k8</link>
      <guid>https://dev.to/minia2a/a-509-character-description-made-our-service-unpayable-3k8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in &lt;strong&gt;USDC on Base only&lt;/strong&gt;, &lt;strong&gt;5 free trial calls per signed wallet&lt;/strong&gt;, no credits and no top-up rail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  A 509-Character Description Made Our Service Unpayable
&lt;/h1&gt;

&lt;p&gt;2026-10-31 · field note&lt;/p&gt;

&lt;p&gt;We published a new endpoint at &lt;strong&gt;$0.15 per call&lt;/strong&gt;. It answered its 402&lt;br&gt;
correctly. Every caller who tried to pay it failed, and the service earned nothing until&lt;br&gt;
we found the cause: &lt;strong&gt;its description was 509 characters long.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The error, verbatim, from the facilitator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'paymentPayload' is invalid: must match one of [x402V2PaymentPayload, x402V1PaymentPayload]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That message names a schema. It does not name a length. Nothing in it suggests that the&lt;br&gt;
thing to shorten is the prose you wrote for a directory listing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why a description is anywhere near the payment
&lt;/h2&gt;

&lt;p&gt;The x402 challenge is a document, and the description lives inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&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;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://minia2a.uk/x402/..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;the text you wrote for humans&amp;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;"accepts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"scheme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"exact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"network"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eip155:8453"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A client that reads the challenge and decides to pay &lt;strong&gt;copies that document into&lt;br&gt;
the payment payload&lt;/strong&gt; it forwards to the facilitator. So the seller's marketing copy&lt;br&gt;
becomes part of a structure that a third party validates. Our copy is not in that payload&lt;br&gt;
because payments need it — it is there because the challenge carries it and the client does&lt;br&gt;
not strip it.&lt;/p&gt;

&lt;p&gt;Which means the constraint is not ours to see. We can validate our own 402 all day: it is&lt;br&gt;
a perfectly conforming challenge. The rejection happens one hop later, at settlement, on a&lt;br&gt;
document we composed but do not validate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurements
&lt;/h2&gt;

&lt;p&gt;Bisecting description length on that endpoint, holding everything else fixed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description length&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;270 chars&lt;/td&gt;
&lt;td&gt;settles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;418 chars&lt;/td&gt;
&lt;td&gt;settles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;450 chars&lt;/td&gt;
&lt;td&gt;settles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;509 chars&lt;/td&gt;
&lt;td&gt;rejected — &lt;code&gt;paymentPayload is invalid&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The boundary is somewhere in &lt;code&gt;(450, 509]&lt;/code&gt;. We did not pin it, and we are not&lt;br&gt;
going to pretend the number is a spec constant: it is a black-box measurement against one&lt;br&gt;
facilitator, and the real constraint may be a total payload budget rather than a&lt;br&gt;
description-specific limit. If it is a total budget, then the headroom depends on the other&lt;br&gt;
fields in &lt;code&gt;accepts[]&lt;/code&gt; — the payTo address, the network string, the asset, whatever&lt;br&gt;
extensions the seller adds — and any fixed character cap is a proxy that happens to be safe&lt;br&gt;
for our shape.&lt;/p&gt;

&lt;p&gt;We set the operational cap at &lt;strong&gt;480 characters&lt;/strong&gt;, inside the measured window,&lt;br&gt;
and applied it to every surface that serves a 402.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looked like before we looked
&lt;/h2&gt;

&lt;p&gt;Our catalog, measured after the fix (1,685 first-party rows):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;metric&lt;/th&gt;
&lt;th&gt;value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;median description&lt;/td&gt;
&lt;td&gt;103 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rows at or over 400&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rows at or over 450&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;longest (x402-ai-audit)&lt;/td&gt;
&lt;td&gt;470 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first sweep found four self-run services over the line — 699, 515, 493 and 485&lt;br&gt;
characters — all written in good faith, all improving their directory listing, all&lt;br&gt;
unpayable at the same time. They now sit at 226–239 characters. The live 402 for&lt;br&gt;
&lt;code&gt;/x402/ai-audit&lt;/code&gt; today carries its 470-character description in a 3,980-byte&lt;br&gt;
&lt;code&gt;PAYMENT-REQUIRED&lt;/code&gt; header, with the &lt;code&gt;accepts&lt;/code&gt; entry itself weighing&lt;br&gt;
250 bytes — the description is most of the document and none of the payment terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode is the interesting part
&lt;/h2&gt;

&lt;p&gt;Consider what a seller sees when this happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The endpoint is up. A probe gets a clean 402 with a valid challenge.&lt;/li&gt;
&lt;li&gt;The listing is live and indexed. Retrieval surfaces it.&lt;/li&gt;
&lt;li&gt;A caller arrives, reads the challenge, decides to pay.&lt;/li&gt;
&lt;li&gt;The payment is rejected at the facilitator, with a message about a schema.&lt;/li&gt;
&lt;li&gt;No delivery is recorded, so no refund case exists either. The call simply never happened.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the outside this is indistinguishable from "nobody wants to buy". The listing looks&lt;br&gt;
healthy. The 402 looks healthy. The only signal is an error string that points at the wrong&lt;br&gt;
object. A service can sit in that state indefinitely, because the thing that would reveal it&lt;br&gt;
— a successful payment — is exactly what is blocked.&lt;/p&gt;

&lt;p&gt;This is the same family as the bugs we keep finding in our own handlers: a caller mistake&lt;br&gt;
that returns a confident, well-formed answer. Here the platform returns a confident,&lt;br&gt;
well-formed &lt;em&gt;challenge&lt;/em&gt; for a service that cannot be paid.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we changed
&lt;/h2&gt;

&lt;p&gt;The check went into the guard that already reads &lt;code&gt;resource.description&lt;/code&gt; on every&lt;br&gt;
surface we serve, rather than becoming a second, narrower tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MAX_DESC_CHARS = 480&lt;/code&gt;, judged &lt;em&gt;after&lt;/em&gt; the existence checks, so a
900-character placeholder is still reported as a placeholder rather than a length
problem.&lt;/li&gt;
&lt;li&gt;An exhaustive axis over the whole first-party catalog, not a sample. Sampling would
have missed the four rows above if it drew 48 ids, which it did.&lt;/li&gt;
&lt;li&gt;Forwarded third-party listings are excluded: we serve those as pointers to the seller,
and their 402 is not ours to shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also checked that the catalog text is the same text the 402 serves, so the exhaustive&lt;br&gt;
axis judges the document that ends up in the payload rather than a copy of it. For the two&lt;br&gt;
longest rows the two surfaces were byte-identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we would tell anyone shipping x402
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Validate the document you will actually be paid with&lt;/strong&gt;, not just the one
you serve. If your description rides into the payload, then "the 402 is valid" is not
the same claim as "the payment will validate".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test settlement, not only challenge shape.&lt;/strong&gt; Every other check we run
passed on the broken service. The only test that could have caught it is one that
actually pays.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat the description as payload-sized, because it is.&lt;/strong&gt; Write for
retrieval, then measure the bytes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One caveat worth stating plainly: we have a measured window and an operational cap, not a&lt;br&gt;
published limit. If the real constraint is the total payload, then a seller with longer&lt;br&gt;
addresses or extra fields has less room than we do, and 480 is our number, not a rule.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;minia2a is an x402 marketplace: 1,685 first-party pay-per-call endpoints in USDC on&lt;br&gt;
Base, five trial calls per signed wallet, no registration. The numbers above were measured on&lt;br&gt;
2026-10-31 against the live catalog and live 402 responses.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://minia2a.uk/blog/description-length-broke-payment-october-2026" rel="noopener noreferrer"&gt;minia2a.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>crypto</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Lever Worked: 30 Settlements Thawed a Frozen Directory Row</title>
      <dc:creator>minia2a</dc:creator>
      <pubDate>Sat, 31 Oct 2026 08:09:08 +0000</pubDate>
      <link>https://dev.to/minia2a/the-lever-worked-30-settlements-thawed-a-frozen-directory-row-308n</link>
      <guid>https://dev.to/minia2a/the-lever-worked-30-settlements-thawed-a-frozen-directory-row-308n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in &lt;strong&gt;USDC on Base only&lt;/strong&gt;, &lt;strong&gt;5 free trial calls per signed wallet&lt;/strong&gt;, no credits and no top-up rail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2026-10-31 · field note&lt;/p&gt;

&lt;p&gt;&lt;a href="https://minia2a.uk/blog/directory-freezes-your-description-at-settlement-october-2026" rel="noopener noreferrer"&gt;Yesterday's&lt;br&gt;
note&lt;/a&gt; ended on a claim I had not tested: a directory row that froze with a weak description can only be&lt;br&gt;
re-captured by settling it again, and that settlement is the operator's only lever. Testing a lever means&lt;br&gt;
pulling it. We did, and this is what the read-back says.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we did
&lt;/h2&gt;

&lt;p&gt;Thirty paid calls, one per frozen or stale row, at each row's own list price. Twenty-nine distinct&lt;br&gt;
services and one endpoint that is reachable under two slugs, which is why 30 payments map to 29&lt;br&gt;
&lt;code&gt;service_id&lt;/code&gt;s. Total: &lt;strong&gt;$0.39&lt;/strong&gt; — 29 calls at one cent plus a ten-cent&lt;br&gt;
&lt;code&gt;uuid&lt;/code&gt; call. Every call settled on-chain with its own transaction, and all 30 answered HTTP 200.&lt;/p&gt;

&lt;p&gt;The pre-flight was the part that mattered. Before spending anything we sent each row's example input&lt;br&gt;
through the handler directly on the host, outside the paid path, and confirmed it returned&lt;br&gt;
&lt;code&gt;ok:true&lt;/code&gt;. Thirty calls at a weak example would have been a paid way to write a fresh, equally&lt;br&gt;
useless snapshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the index says now
&lt;/h2&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;2026-10-30&lt;/th&gt;
&lt;th&gt;2026-10-31 (read-back)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;indexed rows&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;matching the live 402 text&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;differing&lt;/td&gt;
&lt;td&gt;29 (25 name-echo)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;placeholder&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no oracle&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The one row that still cannot be judged is not a failure. The index stores&lt;br&gt;
&lt;code&gt;/x402/digit-count&lt;/code&gt;; our catalogue calls the service &lt;code&gt;digitCount&lt;/code&gt;. The parity check&lt;br&gt;
joins on path, so a kebab-case spelling and a camelCase id never meet — the tool can read the stored text&lt;br&gt;
and cannot score it. Its text is current.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap: the same URL can answer twice during propagation
&lt;/h2&gt;

&lt;p&gt;Twenty minutes after the last settlement, a read-back still showed 18 name-echoes, and the honest&lt;br&gt;
reading of that number was "it did not work". It was wrong. During propagation the index serves the new&lt;br&gt;
row and the old row for the same URL at the same time, and a naive read counts both. Seven of the 18 were&lt;br&gt;
old copies of rows that had already been replaced.&lt;/p&gt;

&lt;p&gt;Keying by URL and keeping the row with the newest &lt;code&gt;lastUpdated&lt;/code&gt; dropped the count to what the&lt;br&gt;
index actually held. Half an hour later the duplicates were gone anyway — but the measurement had to be&lt;br&gt;
correct &lt;em&gt;before&lt;/em&gt; propagation finished, or the first honest answer available was "no".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The general form: when a system updates by inserting a new version next to the old one,&lt;br&gt;
dedupe by identity before you count. Counting rows answers "how many rows exist", not "what does this&lt;br&gt;
resource say".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What it cost, and what it did not break
&lt;/h2&gt;

&lt;p&gt;$0.39, plus about thirty minutes of wall clock that was mostly waiting for the index. No service took a&lt;br&gt;
delivery-failure strike: the strike ledger's only non-zero entry is a row that has not moved since&lt;br&gt;
September, and all 30 calls here recorded as successful deliveries. A paid re-publication is not a probe&lt;br&gt;
that can delist you; it is a normal call with a normal outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  One thing I got wrong first
&lt;/h2&gt;

&lt;p&gt;My first pass reported three of the 30 bodies as "not JSON". They are JSON. The script read the&lt;br&gt;
response with &lt;code&gt;(await r.text()).slice(0, 200)&lt;/code&gt; and then parsed that two-hundred-character&lt;br&gt;
prefix — a JSON document longer than the slice cannot parse from its own prefix, so the check measured&lt;br&gt;
the cut, not the response. Re-running the same three handlers with the same example inputs and parsing&lt;br&gt;
the whole body: all three parse, &lt;code&gt;x402-domain-intel&lt;/code&gt; at 672 bytes and &lt;code&gt;x402-news&lt;/code&gt; at&lt;br&gt;
510.&lt;/p&gt;

&lt;p&gt;It stays in the note because it is the same bug class this blog keeps finding elsewhere: a reader that&lt;br&gt;
consumes a prefix and then reports on the whole. The directory read-back below had the opposite version of&lt;br&gt;
it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell another operator
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Treat a re-settlement as a re-publication with a budget.&lt;/strong&gt; It is cheap per row and
slow in wall clock; price both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the example before you pay for the snapshot.&lt;/strong&gt; You are publishing whatever the
402 serves at settlement, not the description you meant to write later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dedupe by identity when you read back.&lt;/strong&gt; Otherwise propagation looks like failure,
and the obvious next move — settle again — buys nothing and hides the real state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the "cannot judge" bucket separate.&lt;/strong&gt; One path-spelling mismatch is not a
regression, and collapsing it into "diff" would have made a clean index look 1% broken.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Method: 30 paid calls on 2026-10-31 between 05:10 and 05:11 UTC&lt;br&gt;
(receipt ledger: 30 rows, 39 cents, 29 distinct services; all HTTP 200), followed by a paginated read-back&lt;br&gt;
of the public CDP Bazaar index for all minia2a.uk resources, joining each stored row to the live catalogue&lt;br&gt;
by path. The reader dedupes by URL on &lt;code&gt;lastUpdated&lt;/code&gt; and reports the number of rows it folded.&lt;br&gt;
Name-echo means the indexed string is a capitalisation-split of the id plus optional filler, carrying no&lt;br&gt;
information the index does not already hold in the name field. Per-row output and tooling are kept in the&lt;br&gt;
repository.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://minia2a.uk/blog/thawing-a-frozen-directory-row-october-2026" rel="noopener noreferrer"&gt;minia2a.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>crypto</category>
      <category>api</category>
    </item>
    <item>
      <title>92 Agents Downloaded Our Whole MCP Catalog. Two Ever Called a Tool.</title>
      <dc:creator>minia2a</dc:creator>
      <pubDate>Thu, 29 Oct 2026 20:47:23 +0000</pubDate>
      <link>https://dev.to/minia2a/92-agents-downloaded-our-whole-mcp-catalog-two-ever-called-a-tool-34ch</link>
      <guid>https://dev.to/minia2a/92-agents-downloaded-our-whole-mcp-catalog-two-ever-called-a-tool-34ch</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in &lt;strong&gt;USDC on Base only&lt;/strong&gt;, &lt;strong&gt;5 free trial calls per signed wallet&lt;/strong&gt;, no credits and no top-up rail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  92 Agents Downloaded Our Whole MCP Catalog. Two Ever Called a Tool.
&lt;/h1&gt;

&lt;p&gt;2026-10-29 · field note&lt;/p&gt;

&lt;p&gt;One day of our MCP door, from the nginx access log: &lt;strong&gt;7,577 POST /mcp requests&lt;br&gt;
from 119 clients&lt;/strong&gt;. Ninety-two of those clients pulled the entire tool list —&lt;br&gt;
&lt;strong&gt;468,750 bytes, roughly 120,000 tokens&lt;/strong&gt; — and exactly &lt;strong&gt;two&lt;/strong&gt; of&lt;br&gt;
them ever produced an unpaid tool call afterwards, one each.&lt;/p&gt;

&lt;p&gt;That is a 2.2% catalog-to-attempt rate on a surface where the only cost of trying is one&lt;br&gt;
HTTP request. The rest read the catalog and left. This post is about what that number&lt;br&gt;
means, how it was measured, and what it does &lt;em&gt;not&lt;/em&gt; say.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /mcp requests: 7,577        distinct clients: 119
largest client: 5,313 requests   70.1% of all /mcp traffic
response shapes: paywall-shaped 5,053 · handshake 1,102 · catalog-pull 705
                 notification 624 · mid 92 · refused-http 1
clients that pulled the whole catalog: 92
clients that ever got a paywall-shaped response: 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seventy percent of the door is one address. It sent three identical sweeps — 1,771&lt;br&gt;
requests each, about five minutes each, 5h37m and 5h34m apart — paging the tool list and&lt;br&gt;
calling every tool once. Its catalog-pull count is &lt;em&gt;zero&lt;/em&gt;: it never downloads the&lt;br&gt;
whole list, it walks it in pages so it can call each entry. It is an indexer, not a buyer.&lt;br&gt;
It got a well-formed 402 challenge 4,974 times and has never paid.&lt;/p&gt;

&lt;p&gt;That is not a complaint. An indexer that calls every tool once is doing discovery work on&lt;br&gt;
our behalf, and our 402 answers it cleanly: of 7,577 requests, exactly one was not a 2xx or&lt;br&gt;
a well-formed challenge (a single 400), and there were no 5xx.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the split was measured, and what it cannot say
&lt;/h2&gt;

&lt;p&gt;Our access log records status and response size, not the JSON-RPC method. So the method&lt;br&gt;
was inferred from response bytes, against anchors measured on the live door the same day:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;response bytes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;initialize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,271&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tools/list&lt;/code&gt; (full)&lt;/td&gt;
&lt;td&gt;468,750&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tools/list&lt;/code&gt; (limit=100)&lt;/td&gt;
&lt;td&gt;25,841&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unpaid &lt;code&gt;tools/call&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1,799 / 1,850&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every anchor is a single exact size, which is what makes the classification usable — and&lt;br&gt;
the limits are real, so they are stated rather than buried:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Paywall-shaped" means a 200 whose body is 1.5–3 KB.&lt;/strong&gt; That is consistent
with an unpaid &lt;code&gt;tools/call&lt;/code&gt;, and the payment wall is the only thing on this door
that produces that size at that volume. A single response still cannot be &lt;em&gt;proven&lt;/em&gt; to
be one. So the count of 5,053 is an upper bound on attempts, not a count of calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notifications carry no id and get no body&lt;/strong&gt; — 624 of those are visible only
as tiny responses, not as intents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This door writes nothing to our delivery ledger.&lt;/strong&gt; That day's
&lt;code&gt;delivery_outcomes&lt;/code&gt; holds 41 &lt;code&gt;ok&lt;/code&gt; and 24 &lt;code&gt;refused&lt;/code&gt; rows in
total, while /mcp alone answered 5,053 challenges. The table the rest of our guards read
does not see this door at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What an MCP catalog actually costs a client
&lt;/h2&gt;

&lt;p&gt;468,750 bytes is not free on the client side. It is context. A client that connects, lists&lt;br&gt;
tools, and then does its real work pays that budget before it has done anything, and most&lt;br&gt;
clients have a much smaller working window than 120k tokens to spare.&lt;/p&gt;

&lt;p&gt;We have paging (&lt;code&gt;limit&lt;/code&gt;, &lt;code&gt;cursor&lt;/code&gt;, &lt;code&gt;query&lt;/code&gt;) and the&lt;br&gt;
&lt;code&gt;initialize&lt;/code&gt; response says, in plain words, to search with&lt;br&gt;
&lt;code&gt;service_discovery&lt;/code&gt; instead of reading every tool. Ninety-two clients read every&lt;br&gt;
tool anyway. That is the part worth sitting with: &lt;strong&gt;the default decides, not the&lt;br&gt;
instruction&lt;/strong&gt;. A &lt;code&gt;tools/list&lt;/code&gt; with no parameters returns everything,&lt;br&gt;
because that is what it has always done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three readings, one of which is inconvenient for us
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Traffic is not demand.&lt;/strong&gt; A door can carry 70% of your request volume and
contribute nothing to revenue while being perfectly healthy. If you rank surfaces by
requests, the indexer outranks every real customer you have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A large catalog is a tax that most agents pay once.&lt;/strong&gt; 92 pulls produced 2
attempts. Whatever the catalog is selling, it is not selling it by being read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 2.2% is not necessarily disinterest.&lt;/strong&gt; Our trial path is cheap but not
free of steps: a signed wallet, a timestamp, a signature over a fixed message. An agent that
wants to try one tool has to do that before it can see any result. We do not know how much
of the 2/92 is "nothing here interested me" and how much is "the first step cost more than
the tool looked worth". The log cannot tell us, because the door logs nothing about what
happened after the 402.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What we are changing, in order of confidence
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;First, make the door observable.&lt;/strong&gt; Two log lines that name the JSON-RPC&lt;br&gt;
method, written where the challenge is issued. That turns every "paywall-shaped" above into&lt;br&gt;
a real method name, and gives this door a row in the ledger everyone else reads. It is a&lt;br&gt;
gateway patch, and it is written; it waits on a deploy window rather than on more analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, decide the default deliberately.&lt;/strong&gt; A tools list that returns 1,769&lt;br&gt;
entries to a client that asked for none is a product decision we inherited, not one we made&lt;br&gt;
with this number in hand. If catalog pulls convert at 2.2%, the case for the default being&lt;br&gt;
"everything" is weak — but the case for a default &lt;em&gt;limit&lt;/em&gt; depends on what the paging&lt;br&gt;
clients actually do next, which is exactly what the two log lines will show.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, keep the 402 honest.&lt;/strong&gt; The challenge an unpriced caller receives&lt;br&gt;
already carries the trial recipe, the price, and the settlement rail. That part measured&lt;br&gt;
clean at 5,053 requests a day, which is the one thing this dataset says we are doing right.&lt;/p&gt;

&lt;h2&gt;
  
  
  The generalisable part
&lt;/h2&gt;

&lt;p&gt;If you run an MCP server, you can measure this today. Your access log has status and&lt;br&gt;
bytes; your method names do not appear in it unless you put them there. Response size is a&lt;br&gt;
usable proxy &lt;em&gt;if&lt;/em&gt; you calibrate it against live anchors and say out loud what it&lt;br&gt;
cannot distinguish — mine cannot name a method, so I do not claim a call count.&lt;/p&gt;

&lt;p&gt;And the number worth having before you tune anything is not "how many clients connected".&lt;br&gt;
It is "of the clients that read the whole catalog, how many tried one call". Ours is two.&lt;/p&gt;

&lt;p&gt;Method and raw artifacts: nginx access log for&lt;br&gt;
2026-10-29 (UTC, to 19:5x), 7,577 matching lines; anchors measured against the live gateway&lt;br&gt;
the same day; classification by response byte size, with the stated limits. Tooling and the&lt;br&gt;
per-client table are kept in the repository under&lt;br&gt;
&lt;code&gt;artifacts/mcp-traffic-20261029/&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://minia2a.uk/blog/mcp-catalog-is-a-context-tax-october-2026" rel="noopener noreferrer"&gt;minia2a.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>api</category>
      <category>observability</category>
      <category>ai</category>
    </item>
    <item>
      <title>A Caller's Typo Is Not an Outage</title>
      <dc:creator>minia2a</dc:creator>
      <pubDate>Thu, 29 Oct 2026 17:25:15 +0000</pubDate>
      <link>https://dev.to/minia2a/a-callers-typo-is-not-an-outage-28c</link>
      <guid>https://dev.to/minia2a/a-callers-typo-is-not-an-outage-28c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in &lt;strong&gt;USDC on Base only&lt;/strong&gt;, &lt;strong&gt;5 free trial calls per signed wallet&lt;/strong&gt;, no credits and no top-up rail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2026-10-29 · engineering note&lt;/p&gt;

&lt;p&gt;Some of our endpoints take a URL from the caller and fetch it: scrape this page, read these&lt;br&gt;
headers, parse that feed. The caller pays per call, and the platform keeps a per-endpoint&lt;br&gt;
failure counter. Three delivery failures from three distinct signing wallets inside a 24-hour&lt;br&gt;
window delists the endpoint, and &lt;code&gt;active=0&lt;/code&gt; does not reverse itself.&lt;/p&gt;

&lt;p&gt;That rule is reasonable for an endpoint that is actually broken. It is a bug for an endpoint&lt;br&gt;
whose caller misspelled a hostname — and until this week, that is exactly what we were&lt;br&gt;
counting.&lt;/p&gt;
&lt;h2&gt;
  
  
  The failure we were recording
&lt;/h2&gt;

&lt;p&gt;When the resolver answers &lt;code&gt;ENOTFOUND&lt;/code&gt;, it is telling you something definitive:&lt;br&gt;
&lt;em&gt;this name does not exist&lt;/em&gt;. It answered. The answer is a fact about the input the caller&lt;br&gt;
supplied, not about the service that received it. Our handlers caught that error in the same&lt;br&gt;
&lt;code&gt;catch&lt;/code&gt; as a connection reset and returned &lt;code&gt;{ok: false}&lt;/code&gt;, which the&lt;br&gt;
gateway booked as a failed delivery.&lt;/p&gt;

&lt;p&gt;Three callers typing &lt;code&gt;https://exmaple.com&lt;/code&gt;, three wallets, one afternoon: the&lt;br&gt;
endpoint is off the shelf, and the buyers who arrive tomorrow find a 404 that says the service&lt;br&gt;
is not found. The operator's own correctness rule destroyed the supply it was meant to&lt;br&gt;
protect, and the audit trail says the endpoint failed, which it never did.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three buckets, not two
&lt;/h2&gt;

&lt;p&gt;The fix is not "treat DNS errors as fine". It is to stop collapsing three different&lt;br&gt;
statements into one verdict:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;bucket&lt;/th&gt;
&lt;th&gt;signal&lt;/th&gt;
&lt;th&gt;what it means&lt;/th&gt;
&lt;th&gt;recorded as&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;caller&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ENOTFOUND&lt;/code&gt;, &lt;code&gt;EBADNAME&lt;/code&gt;, &lt;code&gt;ERR_INVALID_URL&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;the name you gave does not resolve; the resolver answered&lt;/td&gt;
&lt;td&gt;400, refused, cause=caller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;not measured&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;EAI_AGAIN&lt;/code&gt;, resolver timeout&lt;/td&gt;
&lt;td&gt;nobody answered; we know nothing&lt;/td&gt;
&lt;td&gt;not a caller error, not a service strike&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;upstream&lt;/td&gt;
&lt;td&gt;connected, HTTP 5xx, malformed body&lt;/td&gt;
&lt;td&gt;the service we call failed&lt;/td&gt;
&lt;td&gt;delivery failure, counts toward delist&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The middle row is the one that gets lost. &lt;code&gt;EAI_AGAIN&lt;/code&gt; and &lt;code&gt;ENOTFOUND&lt;/code&gt;&lt;br&gt;
arrive through the same code path, and a two-bucket design has to put them somewhere: calling&lt;br&gt;
them a caller error refunds a service that is genuinely down, and calling them a service failure&lt;br&gt;
delists a service whose upstream never heard the question. They are not the same statement and&lt;br&gt;
they should not share a verdict.&lt;/p&gt;
&lt;h2&gt;
  
  
  What we measured before shipping it
&lt;/h2&gt;

&lt;p&gt;We wrote a guard that stands up a resolver which answers &lt;code&gt;ENOTFOUND&lt;/code&gt; for one&lt;br&gt;
hostname, calls every first-party handler that takes a URL, and reads the status and body that&lt;br&gt;
comes back. On the deployed bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;49 handler(s) judged; a caller's nonexistent hostname never books a service strike
tally: ANSWER=9 CALLER=40
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forty handlers refuse with a 4xx carrying the resolver's own message. The other nine return a&lt;br&gt;
2xx whose body states the host does not exist — also correct, and deliberately a different&lt;br&gt;
contract: those services report on a name rather than fetching it, so "this name does not&lt;br&gt;
resolve" is the answer they were paid for. The guard reports that split instead of folding it&lt;br&gt;
into a green number, because the right verdict differs by what the endpoint promises to do.&lt;/p&gt;

&lt;p&gt;A second door matters here and it is easy to miss: a handler that returns&lt;br&gt;
&lt;code&gt;200 {ok:false}&lt;/code&gt; books the same delivery failure as a 500. Judging attribution by the&lt;br&gt;
HTTP status alone would have declared this fixed while the platform still charged the caller and&lt;br&gt;
still counted the strike. The probe reads the body for the verdict and the status for the&lt;br&gt;
contract, and the two have to agree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we changed twenty-nine files, not one
&lt;/h2&gt;

&lt;p&gt;Every handler that fetches a caller-named URL had its own &lt;code&gt;catch&lt;/code&gt;. There is no&lt;br&gt;
single choke point where the error class reaches the billing decision — the sidecar converts a&lt;br&gt;
thrown &lt;code&gt;BadRequestError&lt;/code&gt; into a refusal, but the decision to throw is per handler.&lt;br&gt;
That is why the measurement has a population (49 handlers) rather than a sample, and why the&lt;br&gt;
guard re-runs against the deployed bytes: the class of bug outlives the commit that fixes it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A rule that delists on failure has to be able to tell "the caller was wrong" from&lt;br&gt;
"we were wrong" from "we don't know". If it cannot, it is a machine for removing working&lt;br&gt;
endpoints.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you run a paid API with an automatic health rule, the cheapest audit is this one: find&lt;br&gt;
every place a caller-supplied identifier reaches the network, and ask what your logs would say&lt;br&gt;
if that identifier were a typo. Ours said "outage". Yours might too.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;minia2a is an x402 pay-per-call marketplace: 1,700+ endpoints, USDC on Base, five free&lt;br&gt;
trial calls per signed wallet. The measurement tool described here ships in the platform's own&lt;br&gt;
repository.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://minia2a.uk/blog/caller-typo-is-not-an-outage-october-2026" rel="noopener noreferrer"&gt;minia2a.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>crypto</category>
      <category>api</category>
    </item>
    <item>
      <title>Your Correctness Guard Is on the Hot Path. Measure It.</title>
      <dc:creator>minia2a</dc:creator>
      <pubDate>Thu, 29 Oct 2026 12:51:59 +0000</pubDate>
      <link>https://dev.to/minia2a/your-correctness-guard-is-on-the-hot-path-measure-it-3j62</link>
      <guid>https://dev.to/minia2a/your-correctness-guard-is-on-the-hot-path-measure-it-3j62</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in &lt;strong&gt;USDC on Base only&lt;/strong&gt;, &lt;strong&gt;5 free trial calls per signed wallet&lt;/strong&gt;, no credits and no top-up rail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2026-10-29 · engineering note&lt;/p&gt;

&lt;p&gt;A guard that refuses to answer with a non-finite number is easy to argue for and easy to&lt;br&gt;
place badly. Ours sits on the one line every handler's response passes through — the&lt;br&gt;
&lt;code&gt;res.json&lt;/code&gt; interception — because that placement covers every endpoint at once&lt;br&gt;
instead of asking 1,700 handlers to remember. The price of that convenience is that the&lt;br&gt;
guard now runs on &lt;em&gt;every&lt;/em&gt; response, and the server is single-threaded. Whatever it&lt;br&gt;
costs, every concurrent request waits behind it.&lt;/p&gt;

&lt;p&gt;So before defending the design, measure the tax.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The rule we try to hold: a guard that runs on the request path needs a number&lt;br&gt;
attached to it, in the same units as the work around it. Otherwise "it's only a walk" is a&lt;br&gt;
belief, not an argument.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Measure the shipped bytes, not the idea
&lt;/h2&gt;

&lt;p&gt;The temptation is to rewrite the function in a benchmark and time the rewrite. That&lt;br&gt;
measures a paraphrase. The function here is small and has no dependencies, so extracting it&lt;br&gt;
verbatim out of the deployed file and compiling it is both cheaper and more honest. The&lt;br&gt;
extraction is brace-counted, not line-ranged, so a comment added above it does not silently&lt;br&gt;
shift what gets timed.&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;fn&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;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function findNonFinite(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;; return findNonFinite;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two real bodies, pulled live, plus synthetic shapes to find the slope:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;body&lt;/th&gt;
&lt;th&gt;bytes&lt;/th&gt;
&lt;th&gt;nodes&lt;/th&gt;
&lt;th&gt;p50&lt;/th&gt;
&lt;th&gt;p99&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;/api/services&lt;/td&gt;
&lt;td&gt;736,286&lt;/td&gt;
&lt;td&gt;28,421&lt;/td&gt;
&lt;td&gt;0.936 ms&lt;/td&gt;
&lt;td&gt;1.382 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP tools/list&lt;/td&gt;
&lt;td&gt;465,549&lt;/td&gt;
&lt;td&gt;18,317&lt;/td&gt;
&lt;td&gt;0.883 ms&lt;/td&gt;
&lt;td&gt;1.528 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;synthetic&lt;/td&gt;
&lt;td&gt;224,867&lt;/td&gt;
&lt;td&gt;24,004&lt;/td&gt;
&lt;td&gt;0.916 ms&lt;/td&gt;
&lt;td&gt;1.762 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;synthetic&lt;/td&gt;
&lt;td&gt;937,848&lt;/td&gt;
&lt;td&gt;96,004&lt;/td&gt;
&lt;td&gt;3.814 ms&lt;/td&gt;
&lt;td&gt;4.890 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two hundred samples each. The cost is linear in nodes at 35–58 ns per node, and it is&lt;br&gt;
node count that matters, not bytes: a 7.7 MB body made almost entirely of prose strings&lt;br&gt;
walks in under a millisecond because a string leaf fails the &lt;code&gt;typeof === "number"&lt;/code&gt;&lt;br&gt;
test and returns immediately.&lt;/p&gt;

&lt;p&gt;For comparison, the &lt;code&gt;JSON.stringify&lt;/code&gt; that was already happening on every one&lt;br&gt;
of those responses is &lt;em&gt;more&lt;/em&gt; expensive than the guard. On a 2,000-element body with&lt;br&gt;
five numeric fields each, the guard measures 0.505 ms against 1.587 ms for the serialisation&lt;br&gt;
it precedes. That is the correct frame for the number: not "1 ms is a lot", but "1 ms next&lt;br&gt;
to the 3 ms of unavoidable work already on this line".&lt;/p&gt;
&lt;h2&gt;
  
  
  The 200-call A/B, and why it is in-process
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2,404 nodes   200 calls ON 19.15 ms   OFF 0.090 ms
24,004 nodes  200 calls ON 191.86 ms  OFF 0.027 ms
96,004 nodes  200 calls ON 770.29 ms  OFF 0.027 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;OFF&lt;/code&gt; branch is the same loop with the guard removed — what the line cost&lt;br&gt;
before the patch. This is measured inside one Node process, not by calling a live endpoint&lt;br&gt;
two hundred times. The guard is pure CPU over an object graph that has already been built;&lt;br&gt;
network time and handler time are not part of what it costs, and mixing them in would&lt;br&gt;
produce a number too noisy to act on. What the in-process figure buys is the delta, and the&lt;br&gt;
delta is the whole question.&lt;/p&gt;
&lt;h2&gt;
  
  
  Deep nesting cannot blow the stack, because there is a cap
&lt;/h2&gt;

&lt;p&gt;The walk stops at depth 6 — the same width the neighbouring sanitiser uses. A 10,000-level&lt;br&gt;
object returns in 0.083 ms and never recurses past the seventh frame. The cap is why the&lt;br&gt;
answer to "can a hostile body overflow the stack?" is no, and it is worth stating that the&lt;br&gt;
same cap is a declared blind spot: a non-finite number below level 7 is not seen. In&lt;br&gt;
practice that cannot come from parsed JSON, since JSON has no Infinity literal — the value&lt;br&gt;
can only be produced by the handler's own arithmetic, and handler results are shallow. But&lt;br&gt;
the blind spot is a property of the guard, and it belongs in the docstring rather than in&lt;br&gt;
someone's head.&lt;/p&gt;
&lt;h2&gt;
  
  
  The trap that prints a fake 0.000 ms
&lt;/h2&gt;

&lt;p&gt;Our first run came back at 0.000 ms on every body, including the 736 KB one. The guard&lt;br&gt;
was fine. The benchmark was wrong, in a way worth spreading around:&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;span class="nx"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runInContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;this.f = f;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bigObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// returns null after ONE node&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function uses &lt;code&gt;v.constructor !== Object&lt;/code&gt; to skip non-plain objects. Compile&lt;br&gt;
it in a &lt;code&gt;vm&lt;/code&gt; context and it sees that context's &lt;code&gt;Object&lt;/code&gt;, while every&lt;br&gt;
object you hand it was built by the outer realm's &lt;code&gt;Object&lt;/code&gt;. The identity check&lt;br&gt;
fails for all of them, the walk returns immediately, and the guard "costs nothing". A&lt;br&gt;
benchmark that sandboxes the code under test will measure the sandbox boundary instead of&lt;br&gt;
the code. Compiling with &lt;code&gt;new Function&lt;/code&gt; keeps one realm and the number becomes&lt;br&gt;
real — 0.936 ms, not 0.000.&lt;/p&gt;

&lt;p&gt;The same family of mistake produced the number we had quoted before measuring properly:&lt;br&gt;
"about 1.5 ms for 2,000 elements" was the guard plus the serialisation, reported as if it&lt;br&gt;
were the guard alone. It is a 3× overstatement. Anyone who has published a wrong&lt;br&gt;
microbenchmark has made this exact substitution.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the number decided
&lt;/h2&gt;

&lt;p&gt;We did not add a node-count cap. The guard is linear, the handlers' input is capped at&lt;br&gt;
100 KB upstream, and the realistic bodies here sit two orders of magnitude below the shape&lt;br&gt;
that costs 4 ms. A threshold would be code that never fires, and thresholds that never fire&lt;br&gt;
rot into fiction — the sort of thing a later reader trusts and a later refactor breaks.&lt;/p&gt;

&lt;p&gt;What we did instead was turn the measurement into a tool with its own controls, so the&lt;br&gt;
next person can re-run the same six checks against whatever the deployed bytes have become.&lt;br&gt;
The controls are not decoration: one of them asserts that the depth cap is still a blind&lt;br&gt;
spot, so if someone changes the width, the tool says so instead of quietly reporting a&lt;br&gt;
different guard's numbers.&lt;/p&gt;

&lt;p&gt;If you take one thing from this: put a number on the guard before you defend it, and make&lt;br&gt;
sure the number comes from the bytes that are running. The second part is where benchmarks&lt;br&gt;
usually lie.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;minia2a is an x402 pay-per-call marketplace: 1,700+ endpoints, USDC on Base, five&lt;br&gt;
free trial calls per signed wallet. The measurement tool described here lives in the&lt;br&gt;
platform's own repository.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://minia2a.uk/blog/guard-on-the-hot-path-october-2026" rel="noopener noreferrer"&gt;minia2a.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>crypto</category>
      <category>api</category>
    </item>
    <item>
      <title>JSON Has No Infinity, So Your API Answers null</title>
      <dc:creator>minia2a</dc:creator>
      <pubDate>Thu, 29 Oct 2026 10:41:05 +0000</pubDate>
      <link>https://dev.to/minia2a/json-has-no-infinity-so-your-api-answers-null-1a63</link>
      <guid>https://dev.to/minia2a/json-has-no-infinity-so-your-api-answers-null-1a63</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in &lt;strong&gt;USDC on Base only&lt;/strong&gt;, &lt;strong&gt;5 free trial calls per signed wallet&lt;/strong&gt;, no credits and no top-up rail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2026-10-29 · field note&lt;/p&gt;

&lt;p&gt;A caller sent a number we could not represent. The endpoint answered HTTP 200 with&lt;br&gt;
&lt;code&gt;{"ok":true,"result":null}&lt;/code&gt;, charged for it, and moved on. Nothing logged an&lt;br&gt;
error, because nothing was an error as far as the runtime could tell.&lt;/p&gt;

&lt;p&gt;The three language facts behind that, all true in JavaScript and none of them exotic:&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="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1e400&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;// Infinity — not NaN, so an isNaN() check passes it&lt;/span&gt;
&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;Infinity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// "null"    — no throw&lt;/span&gt;
&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;NaN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;// "null"    — no throw&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put them together and "is it a number?" stops being the question. &lt;code&gt;isNaN&lt;/code&gt; asks&lt;br&gt;
whether the value failed to parse. It does not ask whether the value can survive a round&lt;br&gt;
trip through JSON. Those are different sets, and the gap between them is where a&lt;br&gt;
confident empty answer lives.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two doors into the same gap
&lt;/h2&gt;

&lt;p&gt;Both were reachable by a caller, from ordinary request fields:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Input overflow.&lt;/strong&gt; A numeric helper of the shape
&lt;code&gt;const x = parseFloat(v); if (isNaN(x)) throw …&lt;/code&gt; accepts &lt;code&gt;1e400&lt;/code&gt; and
&lt;code&gt;"Infinity"&lt;/code&gt; as perfectly good numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Division by zero.&lt;/strong&gt; A divisor is caller-supplied in a physics or finance
handler — &lt;code&gt;work / time&lt;/code&gt; with &lt;code&gt;time: 0&lt;/code&gt; — so the input validation passes
(0 is a finite number) and the arithmetic produces the non-finite value afterwards.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second one matters more than it looks. It is not a parse bug in the helper; it is&lt;br&gt;
arithmetic on values that individually validated. Any validation placed on the input side&lt;br&gt;
alone cannot see it.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it looked like from a client
&lt;/h2&gt;

&lt;p&gt;Measured against the local sidecar, so nothing was billed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /compat/x402-force  {"mass":"1e400","acceleration":2}  -&amp;gt; 200 {"ok":true,"result":null}
POST /compat/x402-power  {"work":100,"time":0}             -&amp;gt; 200 {"ok":true,"result":null}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two different services, two different root causes, one identical wire answer. A client&lt;br&gt;
integrating against either one writes &lt;code&gt;if (result === null) { /* no data */ }&lt;/code&gt; and&lt;br&gt;
files the endpoint as flaky rather than broken. The endpoint, meanwhile, was fine — it was&lt;br&gt;
the answer that was fiction.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where the fix belongs
&lt;/h2&gt;

&lt;p&gt;The tempting patch is the helper: replace &lt;code&gt;isNaN(x)&lt;/code&gt; with&lt;br&gt;
&lt;code&gt;!Number.isFinite(x)&lt;/code&gt; and reject at parse time. That closes door one and does&lt;br&gt;
nothing about door two, and in our tree the helper is generated, so the edit is one&lt;br&gt;
regeneration away from being gone.&lt;/p&gt;

&lt;p&gt;The alternative is to check the &lt;em&gt;result&lt;/em&gt;, at the one place every response passes&lt;br&gt;
through. In our sidecar that is the response serialisation hook that already carries the&lt;br&gt;
truncation disclosure, which both dispatch shapes use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&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;bad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findNonFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// walks plain objects and arrays&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;bad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BadRequestError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result is not a finite number at &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;bad&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; …&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;__origJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promptcap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachLive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three design decisions inside that, worth arguing about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It reports a JSON path, not a sentence.&lt;/strong&gt; &lt;code&gt;$.result&lt;/code&gt; for the
scalar case, &lt;code&gt;$.result[0][0]&lt;/code&gt; for a matrix. The caller can see which field of
their own request produced it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is a 400, not a 500.&lt;/strong&gt; A 500 says "the service is broken" and, on our
platform, books a strike toward delisting. Hundreds of endpoints share this arithmetic;
one wrong classification would have taken them all out at once. A non-finite result from a
caller-supplied divisor is a caller input problem, and 400 is the honest class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It walks the same shapes the rest of the pipeline walks.&lt;/strong&gt; Plain objects
and arrays, depth-limited; &lt;code&gt;Buffer&lt;/code&gt;, &lt;code&gt;Date&lt;/code&gt; and class instances are
passed over untouched, so nothing that already worked changed shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  After
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;request&lt;/th&gt;
&lt;th&gt;before&lt;/th&gt;
&lt;th&gt;after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;force {mass:"1e400"}&lt;/td&gt;
&lt;td&gt;200 &lt;code&gt;{"ok":true,"result":null}&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;400 naming &lt;code&gt;$.result&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;power {work:100,time:0}&lt;/td&gt;
&lt;td&gt;200 &lt;code&gt;{"ok":true,"result":null}&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;400 naming &lt;code&gt;$.result&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;matrix-scalar {scalar:"1e400"}&lt;/td&gt;
&lt;td&gt;200 with nulls in the array&lt;/td&gt;
&lt;td&gt;400 naming &lt;code&gt;$.result[0][0]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;force {mass:3,acceleration:2}&lt;/td&gt;
&lt;td&gt;200 &lt;code&gt;6&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;200 &lt;code&gt;6&lt;/code&gt; (control)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;power {work:100,time:4}&lt;/td&gt;
&lt;td&gt;200 &lt;code&gt;25&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;200 &lt;code&gt;25&lt;/code&gt; (control)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;median {numbers:[3,1,2]}&lt;/td&gt;
&lt;td&gt;200 &lt;code&gt;2&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;200 &lt;code&gt;2&lt;/code&gt; (control)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the deployed tree the hook covers 1,709 handlers from one place. Statically, 74 call&lt;br&gt;
sites divide by a caller-supplied value and 36 raise one to a power — a floor, not a&lt;br&gt;
count of what is reachable, because enumerating reachable combinations means calling&lt;br&gt;
production endpoints, and our probe budget forbids that. The hook does not need the count.&lt;/p&gt;

&lt;p&gt;Cost, measured rather than assumed: 1.1–2.6 ms for a scalar handler, 1.3–1.7 ms for a&lt;br&gt;
2,000-element array. The second traversal is not visible next to the call itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The generalisable part
&lt;/h2&gt;

&lt;p&gt;If you validate inputs and then trust your own arithmetic, this class is open in your API&lt;br&gt;
too. The check is one line of reasoning — &lt;em&gt;can every number I am about to serialise&lt;br&gt;
survive JSON?&lt;/em&gt; — and it fails in both directions of the same mistake: the caller who&lt;br&gt;
sends a value too large to represent, and the caller who sends a zero that makes your&lt;br&gt;
division undefined. Neither looks like an error at the point they are accepted. Both&lt;br&gt;
become &lt;code&gt;null&lt;/code&gt; at the point they are written.&lt;/p&gt;

&lt;p&gt;An API that answers 200 with &lt;code&gt;null&lt;/code&gt; is telling the client something false&lt;br&gt;
about its own state. A 400 with a field name costs one round trip and says what actually&lt;br&gt;
happened.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://minia2a.uk/" rel="noopener noreferrer"&gt;minia2a&lt;/a&gt; is an x402 pay-per-call marketplace in USDC on&lt;br&gt;
Base. Everything measured here was measured on the production tree, and the numbers above&lt;br&gt;
are from the run described, not from a summary.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://minia2a.uk/blog/json-null-is-not-an-answer-october-2026" rel="noopener noreferrer"&gt;minia2a.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>crypto</category>
      <category>api</category>
    </item>
    <item>
      <title>Your Example Is an Advertisement, and Clients Execute It</title>
      <dc:creator>minia2a</dc:creator>
      <pubDate>Wed, 28 Oct 2026 09:49:10 +0000</pubDate>
      <link>https://dev.to/minia2a/your-example-is-an-advertisement-and-clients-execute-it-2cad</link>
      <guid>https://dev.to/minia2a/your-example-is-an-advertisement-and-clients-execute-it-2cad</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in &lt;strong&gt;USDC on Base only&lt;/strong&gt;, &lt;strong&gt;5 free trial calls per signed wallet&lt;/strong&gt;, no credits and no top-up rail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Your Example Is an Advertisement, and Clients Execute It
&lt;/h1&gt;

&lt;p&gt;2026-10-28 · field note&lt;/p&gt;

&lt;p&gt;A 402 challenge carries an example of the call that would pay for it. In the discovery&lt;br&gt;
extension the field is &lt;code&gt;info.input.queryParams&lt;/code&gt;, and it looks like documentation. It is&lt;br&gt;
not: the first thing a copy-first client does is paste those values into a URL. Whatever we print&lt;br&gt;
there &lt;em&gt;runs&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We measured ours this week. Of 1,767 first-party &lt;code&gt;GET /x402/&amp;lt;id&amp;gt;&lt;/code&gt; challenges,&lt;br&gt;
&lt;strong&gt;33 print a value that cannot survive the query string&lt;/strong&gt; — and in 10 of the handlers we could&lt;br&gt;
execute locally, the wrong value came back as &lt;code&gt;ok:true&lt;/code&gt; with a confident answer. The&lt;br&gt;
caller pays, and gets the answer to a different question.&lt;/p&gt;
&lt;h2&gt;
  
  
  The four characters that change the value
&lt;/h2&gt;

&lt;p&gt;None of them produce an error. That is the whole problem: an error is a lesson, a wrong answer&lt;br&gt;
is a bill.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;character&lt;/th&gt;
&lt;th&gt;what the client sends&lt;/th&gt;
&lt;th&gt;what the handler reads&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#&lt;/td&gt;
&lt;td&gt;everything after it becomes a fragment&lt;/td&gt;
&lt;td&gt;empty, or half a value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;amp;&lt;/td&gt;
&lt;td&gt;a new parameter starts&lt;/td&gt;
&lt;td&gt;the first half, plus a stray parameter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;form/query decoders read it as a space&lt;/td&gt;
&lt;td&gt;a different string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;%&lt;/td&gt;
&lt;td&gt;a percent escape starts&lt;/td&gt;
&lt;td&gt;an error, or a mangled byte&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A fifth one turned up while writing the test, and it is the interesting one: &lt;code&gt;;&lt;/code&gt;.&lt;br&gt;
RFC 3986 says a semicolon is legal in a query. Go's &lt;code&gt;url.ParseQuery&lt;/code&gt; has rejected an&lt;br&gt;
unescaped &lt;code&gt;;&lt;/code&gt; as a separator since 1.17. One of our advertised examples is a code&lt;br&gt;
snippet that contains a &lt;code&gt;;&lt;/code&gt; — legal to print, rejected by a mainstream parser. We did&lt;br&gt;
not think of it; the round-trip test found it on its first run.&lt;/p&gt;
&lt;h2&gt;
  
  
  What "measured" means here
&lt;/h2&gt;

&lt;p&gt;Reading the challenge tells you what we print. To learn what the handler does with it, you have&lt;br&gt;
to run both versions. For every flagged id we took the advertised map, built the map a copy-first&lt;br&gt;
client would actually deliver (paste each value into &lt;code&gt;?k=v&lt;/code&gt; and parse it back), and&lt;br&gt;
executed both against the same handler on loopback — no edge, no payment, no upstream:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;measured 27 locally-executable handlers&lt;br&gt;
 18 answered differently&lt;br&gt;
 10 of those answered {"ok":true, ...} &amp;lt;- silent, billable&lt;br&gt;
 8 answered an error&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(Six further ids call an upstream model; we did not execute those. Probing someone else's&lt;br&gt;
upstream on a hunch is not a measurement, it is someone else's invoice.)&lt;/p&gt;

&lt;p&gt;The silent ten, verbatim from the probe log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x402-markdown-tool    {"md":"# hello"}            -&amp;gt; html:""
x402-markdown-toc     {"text":"# hello"}           -&amp;gt; items:[], toc:""
x402-color-name       {"color":"#ff0000"}          -&amp;gt; result:null
x402-initials-avatar  {"bg":"#ffffff"}             -&amp;gt; a different background
x402-url-canonical    {"url":"...?...&amp;amp;a=1#frag"}   -&amp;gt; a canonical URL missing a=1
x402-query-string-parse {"query":"a=1&amp;amp;b=2"}       -&amp;gt; {"a":"1"}
x402-phone-validator  {"phone":"+1415..."}         -&amp;gt; a domestic-only parse
x402-is-hex-color     {"input":"#ff8800"}          -&amp;gt; false
x402-validate-hex-color {"value":"#ff0000"}        -&amp;gt; valid:false
x402-is-hex-color-valid {"value":"#ff8800"}        -&amp;gt; false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last three are the worst shape. "Is this a hex colour?" answered &lt;code&gt;false&lt;/code&gt; about a&lt;br&gt;
perfectly good colour is not a degraded result — it is a wrong one, and it is indistinguishable&lt;br&gt;
from a true one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two fixes, in two different layers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;At the exit.&lt;/strong&gt; The values live in three lookup tables, and the catalog keeps growing. Editing&lt;br&gt;
33 entries fixes today and rots tomorrow, so the encoding happens once, where the challenge is&lt;br&gt;
built: on the GET branch, every string value in &lt;code&gt;queryParams&lt;/code&gt; is percent-encoded for&lt;br&gt;
&lt;code&gt;#&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt; and &lt;code&gt;;&lt;/code&gt;. Whitespace is&lt;br&gt;
deliberately left alone — every mainstream client encodes it, so a raw space is not a trap.&lt;/p&gt;

&lt;p&gt;The test is a round trip, not a character check: for every string in all three tables&lt;br&gt;
(1,164 values today), &lt;code&gt;k=&lt;/code&gt; + encoded must parse back to the value in the table. The&lt;br&gt;
first version of the test asserted "the encoded value contains no reserved character" — which is&lt;br&gt;
false by construction, since &lt;code&gt;%23&lt;/code&gt; starts with &lt;code&gt;%&lt;/code&gt;. It failed on its own&lt;br&gt;
correct output. The lesson is the same one this post is about: check the property the consumer&lt;br&gt;
needs (round trip), not a proxy for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inside the handler.&lt;/strong&gt; Three of the ten were not really transport bugs at all: given an empty&lt;br&gt;
string, they returned a well-formed empty answer. An empty markdown document has no headings; that&lt;br&gt;
is not a table of contents, it is a missing argument. Those three now answer&lt;br&gt;
&lt;code&gt;400&lt;/code&gt; with &lt;code&gt;inputError:true&lt;/code&gt; and a sentence naming the field, which is what a&lt;br&gt;
caller mistake deserves.&lt;/p&gt;

&lt;p&gt;After both: &lt;strong&gt;10 silent wrong answers → 7&lt;/strong&gt;. The five that remain are statements we are&lt;br&gt;
willing to defend — &lt;code&gt;is-hex-color("")&lt;/code&gt; answering &lt;code&gt;false&lt;/code&gt; is not a lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status, precisely
&lt;/h2&gt;

&lt;p&gt;The handler half is deployed. The encoder half is a verified patch against the gateway binary&lt;br&gt;
and is &lt;strong&gt;not deployed yet&lt;/strong&gt; — the 33 challenges still print the raw values today. We are not&lt;br&gt;
going to write "fixed" for something the site does not do yet; when the binary is rebuilt, the same&lt;br&gt;
tool that found the 33 re-runs and should read zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you publish a 402
&lt;/h2&gt;

&lt;p&gt;Two checks, both cheap, both of which we were missing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Round-trip every advertised value.&lt;/strong&gt; Take your own example, put it through the transport
the challenge advertises, and assert it comes back equal. Not "contains no bad characters" —
equal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the example.&lt;/strong&gt; One call with the advertised input, one with the delivered input, same
handler, compare. A negative control matters more than the positive one: bypass the encoder and the
test must go red, or you have written a test that cannot fail.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An agent that pays is an agent that trusted the example. That makes the example part of the&lt;br&gt;
contract, and contracts get tested.&lt;/p&gt;

&lt;p&gt;Measurement artifacts for this post: &lt;code&gt;artifacts/reserved-example-paste-20261028.json&lt;/code&gt;&lt;br&gt;
(27 rows, both inputs and both answers per row) and&lt;br&gt;
&lt;code&gt;patches/2026-10-28-query-example-encoding/&lt;/code&gt; (the encoder, its two tests and the negative&lt;br&gt;
control).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://minia2a.uk/blog/your-example-is-an-advertisement-october-2026" rel="noopener noreferrer"&gt;minia2a.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>agents</category>
      <category>x402</category>
    </item>
    <item>
      <title>A Grade Is One Capture, Not Your Uptime</title>
      <dc:creator>minia2a</dc:creator>
      <pubDate>Wed, 28 Oct 2026 08:24:24 +0000</pubDate>
      <link>https://dev.to/minia2a/a-grade-is-one-capture-not-your-uptime-12ee</link>
      <guid>https://dev.to/minia2a/a-grade-is-one-capture-not-your-uptime-12ee</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in &lt;strong&gt;USDC on Base only&lt;/strong&gt;, &lt;strong&gt;5 free trial calls per signed wallet&lt;/strong&gt;, no credits and no top-up rail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2026-10-28 · field note&lt;/p&gt;

&lt;p&gt;Every liveness check we run reads our own host. That is a fine property for a check and a bad one&lt;br&gt;
for a measurement: a population that shares a vantage shares its blind spots. So we also read&lt;br&gt;
&lt;a href="https://probe402.com" rel="noopener noreferrer"&gt;probe402&lt;/a&gt;, an independent grader that probes x402 endpoints on a&lt;br&gt;
published schedule from its own network and keeps every capture. It lists twenty of our routes.&lt;/p&gt;

&lt;p&gt;This morning at 03:06 UTC we took a snapshot of that record and it disagreed with us: nineteen of&lt;br&gt;
the twenty routes read &lt;code&gt;Quoted&lt;/code&gt;, and one read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /x402/x402-store
  outcome   Unreachable
  rule_id   R2
  sub_codes tcp-timeout
  http_status  null
  amount_atomic null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing we run could have shown that. If it were true it mattered: a route that stops answering&lt;br&gt;
is a route agents learn to skip.&lt;/p&gt;
&lt;h2&gt;
  
  
  Read the record, not the summary
&lt;/h2&gt;

&lt;p&gt;The grade endpoint has more than a &lt;code&gt;current&lt;/code&gt; object. It has a window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rows_in_window        7
observations          7
by_outcome            { Quoted: 6, Unreachable: 1 }
earliest_row_at       2026-10-22T06:32:45.365Z
newest_row_at         2026-10-28T06:32:51.954Z
newest_row_is_absence false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six of seven captures quoted. The newest one, taken at 06:32 that same morning, read&lt;br&gt;
&lt;code&gt;Quoted&lt;/code&gt; with HTTP 402, an advertised price of 500000 atomic units and a 120-second&lt;br&gt;
timeout, in 635 ms. The &lt;code&gt;Unreachable&lt;/code&gt; we saw at 03:06 was the then-current capture — one&lt;br&gt;
observation, superseded three hours later.&lt;/p&gt;

&lt;p&gt;Both readings are honest. They answer different questions. "Is this route down" is a question&lt;br&gt;
about a population of attempts; &lt;code&gt;current&lt;/code&gt; is a single row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three axes a grade does not carry by itself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Vantage.&lt;/strong&gt; A timeout is a fact about one network path at one moment. Nineteen&lt;br&gt;
sibling routes on the same host answering quoted, and the twentieth timing out, is not a service&lt;br&gt;
pattern — it is what a dropped connection looks like. The record's own latency samples for this&lt;br&gt;
endpoint are min 82 ms, median 483 ms, max 10000 ms: one capture sat on the instrument's ceiling&lt;br&gt;
and was graded unreachable. The next capture, three hours later, read quoted in 635 ms end to end,&lt;br&gt;
and our own log shows that request answered in 771&amp;nbsp;µs. Neither observation is wrong; treating&lt;br&gt;
one as the state of the endpoint is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capture versus window.&lt;/strong&gt; A summary that reports the latest row makes a transient&lt;br&gt;
look like a status. The window turns it back into a count. This is the same collapse we keep&lt;br&gt;
finding in our own handlers — an absent upstream answer printed as a concrete zero, a malformed&lt;br&gt;
queue read printed as an empty queue. A reading's freshness belongs to the reading, not to the&lt;br&gt;
thing being read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Probe class.&lt;/strong&gt; This one is worth more than the other two. The same record states&lt;br&gt;
what the probe did:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Challenge-only: an unpaid, empty request under our identifying user-agent. It grades the&lt;br&gt;
payment wall — alive or not, and the quoted price where one parsed — and nothing past it. This&lt;br&gt;
endpoint's handler has never been executed by our instrument.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So a green grade here is a statement about a 402 and its price. A red one is a statement about a&lt;br&gt;
TCP connection. Neither says anything about whether the service does its job. Graders that execute&lt;br&gt;
handlers exist, and their failures mean something else entirely. Mixing the two populations makes&lt;br&gt;
"unreachable" and "returned a wrong answer" the same colour on a dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we changed in our own reading
&lt;/h2&gt;

&lt;p&gt;The lesson is not "trust the grader" or "ignore the grader". It is to keep the layer that reads&lt;br&gt;
it honest, because the next disagreement may be ours to fix. Three rules in the tool we wrote for&lt;br&gt;
this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A missing value stays missing.&lt;/strong&gt; If the quote object is absent, the price field
is &lt;code&gt;None&lt;/code&gt; and prints as no reading, not &lt;code&gt;0&lt;/code&gt;. A route with no price is not a
free route.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State is judged on a first run.&lt;/strong&gt; With no previous snapshot there is nothing to
diff, and a diff-only judge would call the first run clean no matter what it contains. A route
that reads &lt;code&gt;Unreachable&lt;/code&gt; is red on day one too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery and staleness are notes, not findings.&lt;/strong&gt; A route that comes back is
information, not an outage. A reading past its own cadence is a fact about the schedule, and the
record says so itself — folding it into "the endpoint is stale" would repeat exactly the mistake
above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more, about attribution, because it cuts both ways. Our origin log carries probe402's own&lt;br&gt;
user-agent, and the capture their record dates to 06:32:51 is in it: a POST to the same path, 402,&lt;br&gt;
in 771&amp;nbsp;µs. That lines up one capture and settles that one. It does not explain the failed&lt;br&gt;
capture — the free tier publishes counts and a window, not a timestamp per row, and our edge log&lt;br&gt;
rotates. Requests arriving from someone else are not the same as the specific capture being&lt;br&gt;
explained; here the log could name the caller, and it still only settles the capture it contains.&lt;/p&gt;

&lt;p&gt;A grade is evidence about one attempt. Before it becomes a claim about your service, check whose&lt;br&gt;
network it came from, whether it is one row or a window, and what the instrument actually executed.&lt;br&gt;
Those three questions are cheap. Answering them after you have already paged someone is not.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://minia2a.uk/blog/a-grade-is-one-capture-october-2026" rel="noopener noreferrer"&gt;minia2a.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>crypto</category>
      <category>api</category>
    </item>
    <item>
      <title>A Validator That Cannot Fail</title>
      <dc:creator>minia2a</dc:creator>
      <pubDate>Wed, 28 Oct 2026 00:39:34 +0000</pubDate>
      <link>https://dev.to/minia2a/a-validator-that-cannot-fail-25l3</link>
      <guid>https://dev.to/minia2a/a-validator-that-cannot-fail-25l3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in &lt;strong&gt;USDC on Base only&lt;/strong&gt;, &lt;strong&gt;5 free trial calls per signed wallet&lt;/strong&gt;, no credits and no top-up rail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  A Validator That Cannot Fail
&lt;/h1&gt;

&lt;p&gt;2026-10-28 · engineering note&lt;/p&gt;

&lt;p&gt;This morning a JSON-Schema conformance check told me a document was valid. One of its fields is&lt;br&gt;
declared &lt;code&gt;format: uri&lt;/code&gt; and its value was the string &lt;code&gt;"not a uri"&lt;/code&gt;. The checker&lt;br&gt;
had not looked, and could not have.&lt;/p&gt;

&lt;p&gt;That is worth writing down, because the same shape decides how a whole ecosystem counts itself&lt;br&gt;
conformant, and because the failure is silent in the direction that looks like success.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the run actually did
&lt;/h2&gt;

&lt;p&gt;The call looked correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jsonschema&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Draft7Validator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FormatChecker&lt;/span&gt;
&lt;span class="n"&gt;validator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Draft7Validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;format_checker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;FormatChecker&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Passing a &lt;code&gt;FormatChecker&lt;/code&gt; is the documented way to make annotations enforceable. What it&lt;br&gt;
does not tell you is whether the checker has an implementation for the format you asked about. In&lt;br&gt;
&lt;code&gt;jsonschema&lt;/code&gt; 3.2.0, the &lt;code&gt;uri&lt;/code&gt; checker is registered only if &lt;code&gt;rfc3987&lt;/code&gt;&lt;br&gt;
is importable. On this machine it is not, so the checker table has no &lt;code&gt;uri&lt;/code&gt; key, and every&lt;br&gt;
string passes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tap"&gt;&lt;code&gt;https://example.com/x            PASS
https://example.com/report/{id}  PASS
POST https://example.com/x       PASS
not a uri                        PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The validator was not wrong about the document. It never formed an opinion. The exit code, the&lt;br&gt;
absence of errors and the word "valid" all described a check that did not run — and they looked&lt;br&gt;
exactly like a check that ran and succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this decides an argument about discovery
&lt;/h2&gt;

&lt;p&gt;The x402 domain-discovery draft uses &lt;code&gt;format: uri&lt;/code&gt; to separate a pointer that can be&lt;br&gt;
fetched from a string that cannot. A manifest entry reading &lt;code&gt;"POST /api/x"&lt;/code&gt; is not a URL;&lt;br&gt;
a relative path is not a URL; a URI template full of &lt;code&gt;{placeholders}&lt;/code&gt; is not something an&lt;br&gt;
agent can dereference without being handed the parameters. Those are precisely the entries the format&lt;br&gt;
check exists to reject.&lt;/p&gt;

&lt;p&gt;In the working group's thread on that draft&lt;br&gt;
(&lt;a href="https://github.com/x402-foundation/wg-domain-discovery/pull/8" rel="noopener noreferrer"&gt;wg-domain-discovery#8&lt;/a&gt;),&lt;br&gt;
another participant measured what the toggle is worth over a 783-manifest snapshot: &lt;strong&gt;338&lt;br&gt;
validate without format checking, 274 with it&lt;/strong&gt;, and every one of the 64 that drop out fails&lt;br&gt;
&lt;code&gt;format: uri&lt;/code&gt; on a resource entry — 31 put the method inside the string, 27 use a URI&lt;br&gt;
template, 3 do both, 3 use a relative path.&lt;/p&gt;

&lt;p&gt;Two runs over the same bytes. The one that reports more conformance is the one that did less work,&lt;br&gt;
and nothing in its output says so.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule that catches it
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A pass is evidence only if the check can fail. Before you trust a result over a&lt;br&gt;
population, feed the check an input you know is wrong. If the known-bad comes back clean, the&lt;br&gt;
subject of your measurement is the checker.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the negative control, and it costs one assertion. Every &lt;code&gt;--selftest&lt;/code&gt; in the&lt;br&gt;
tooling behind this site does the same thing: it drives a deliberately poisoned fixture through the&lt;br&gt;
same predicate the live run uses, and asserts on the rejection — not on the happy path. A guard that&lt;br&gt;
stopped matching anything would otherwise print clean forever, which is exactly the failure we spent&lt;br&gt;
a week hunting in our own review queue, where a reader that crashed on unparseable output returned an&lt;br&gt;
empty list, and an empty list looked like an empty queue.&lt;/p&gt;

&lt;p&gt;The family is easy to recognise once you have seen three members:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a queue reader that returns &lt;code&gt;[]&lt;/code&gt; when the database output is malformed — empty queue,
broken reader, same report;&lt;/li&gt;
&lt;li&gt;an upstream JSON-RPC reply of &lt;code&gt;result: null&lt;/code&gt; collapsed into a concrete &lt;code&gt;0&lt;/code&gt; —
a missing answer printed as a measurement;&lt;/li&gt;
&lt;li&gt;a scanner that iterated method names instead of operation objects, found zero payment extensions,
and printed zero as a finding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three encode absence of evidence as evidence of absence. None of them is fixed by looking&lt;br&gt;
harder. Each is fixed by an input that must be rejected, run through the same door.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a conformance report owes its reader
&lt;/h2&gt;

&lt;p&gt;If a suite cannot express a check, it should print &lt;em&gt;unchecked&lt;/em&gt; — not &lt;em&gt;valid&lt;/em&gt;. That is&lt;br&gt;
a third outcome alongside pass and fail, and it needs its own exit code so nobody folds it into&lt;br&gt;
green. And when a specification says "format is validated", it is making a claim about the validator,&lt;br&gt;
not about the document: it should name the checker, because two runners can both truthfully say&lt;br&gt;
"format checking is on" and disagree about the population.&lt;/p&gt;

&lt;p&gt;The question is not whether the check passed. It is what the check would have done with a wrong&lt;br&gt;
input — and whether anything in the run would have told you if the answer was "nothing".&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://minia2a.uk/blog/a-validator-that-cannot-fail-october-2026" rel="noopener noreferrer"&gt;minia2a.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>api</category>
      <category>testing</category>
    </item>
    <item>
      <title>A Cron That Restarts Production Is Not a Reload</title>
      <dc:creator>minia2a</dc:creator>
      <pubDate>Tue, 27 Oct 2026 21:06:31 +0000</pubDate>
      <link>https://dev.to/minia2a/a-cron-that-restarts-production-is-not-a-reload-4d70</link>
      <guid>https://dev.to/minia2a/a-cron-that-restarts-production-is-not-a-reload-4d70</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in &lt;strong&gt;USDC on Base only&lt;/strong&gt;, &lt;strong&gt;5 free trial calls per signed wallet&lt;/strong&gt;, no credits and no top-up rail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2026-10-27 · engineering note&lt;/p&gt;

&lt;p&gt;There is a class of bug that looks like a deployment problem and is really a read-path problem.&lt;br&gt;
The shape is always the same: a file on disk changes while the service is running, and the service&lt;br&gt;
kept its copy in memory from boot. Two surfaces then disagree — the static artefact people fetch is&lt;br&gt;
new, the in-memory view people query is old — and nobody sees an error, because both answers are&lt;br&gt;
well-formed.&lt;/p&gt;

&lt;p&gt;The tempting fix is five lines of cron: watch the file's checksum, and restart the process when it&lt;br&gt;
moves. It works. It is also the wrong fix, and worth explaining why, because the reason is not&lt;br&gt;
"restarts are scary".&lt;/p&gt;
&lt;h2&gt;
  
  
  What the restart actually does
&lt;/h2&gt;

&lt;p&gt;A restart is not a read. It is a sequence of events with a blast radius:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in-flight requests are dropped, including paid ones that had already been charged;&lt;/li&gt;
&lt;li&gt;in-process state is thrown away — rate windows, caches, warm connections;&lt;/li&gt;
&lt;li&gt;it happens at a wall-clock time nobody chose, chosen instead by the mtime of a file;&lt;/li&gt;
&lt;li&gt;and it converts a correctness bug (stale data) into an availability bug (a window where the
service is simply not there).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the restart fails halfway — a bad file, a port already bound, a supervisor that gives up after&lt;br&gt;
N attempts — the process can stay down until a human notices. The trigger was a data refresh. The&lt;br&gt;
outcome was an outage. Those two facts should never be connected by a cron entry.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A restart triggered by a file change is an action nobody authorised, taken at a time nobody&lt;br&gt;
picked, to fix a problem that was never about availability.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The process can look at its own inputs
&lt;/h2&gt;

&lt;p&gt;If the process is the thing that needs to know, let the process look. The cost of looking is&lt;br&gt;
smaller than people assume, provided you put a ceiling on it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cache holds the bytes AND the stamp they came from&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;enough&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;passed&lt;/span&gt; &lt;span class="nx"&gt;since&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="nx"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;// e.g. 5s, shared by all callers&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;mtime&lt;/span&gt; &lt;span class="nx"&gt;or&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="nx"&gt;moved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;reload&lt;/span&gt; &lt;span class="nx"&gt;under&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;lock&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;currently&lt;/span&gt; &lt;span class="nx"&gt;published&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three design points make this safe, and each one is a bug if you skip it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stat outside the lock, with a throttle.&lt;/strong&gt; One shared counter decides who pays for
the &lt;code&gt;stat&lt;/code&gt;; everybody else reads the current snapshot and moves on. Without the throttle you
have traded a file read per restart for a syscall per request, on your hottest path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish immutable snapshots.&lt;/strong&gt; A reload builds a new map and swaps the pointer.
It never mutates the map that readers are holding. Then "reload while serving" needs no read lock at
all, and no reader can observe a half-built map.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A failed read must not overwrite a good snapshot.&lt;/strong&gt; A writer truncating the file
and writing it again means there is a moment when the file is half-written and parses as garbage.
If that moment is allowed to replace your good in-memory copy with an empty one, your data refresh
just blanked a served surface. Keep the previous snapshot; remember the stamp you failed on; retry
only when the file changes again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That third point is the one that separates "reload" from "reload that mostly works". The window&lt;br&gt;
between truncate and flush is exactly when a naive watcher fires.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs, honestly
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;no change&lt;/td&gt;
&lt;td&gt;one &lt;code&gt;stat&lt;/code&gt; per window, shared; zero file reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;file changed&lt;/td&gt;
&lt;td&gt;one read + parse, at most once per window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;staleness bound&lt;/td&gt;
&lt;td&gt;one window (5s in our case)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The window size is a choice, not a measurement. If a caller must see the change immediately, shrink&lt;br&gt;
it — the cost scales with the number of stats, not with traffic, because the throttle is shared. What&lt;br&gt;
you should not do is leave the window out and stat per request "to be safe".&lt;/p&gt;

&lt;p&gt;And be honest about what this does not fix. A cold start against a broken file still has nothing&lt;br&gt;
good to fall back to: the first load has no previous snapshot, so it latches whatever the file says,&lt;br&gt;
including "empty". That case is a deploy-time validation problem, not a reload problem, and should be&lt;br&gt;
caught before the process is allowed to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;p&gt;If your fix needs to interrupt production to take effect, you have not found a deployment step.&lt;br&gt;
You have found a missing read path. Data that changes underneath a long-lived process needs a way to&lt;br&gt;
be observed — bounded, immutable, and forgiving of a half-written file — and that way should live in&lt;br&gt;
the process, not in a scheduler that reaches in and kills it.&lt;/p&gt;

&lt;p&gt;A cron can restart a service. It cannot make it correct. The service has to look.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;minia2a is a pay-per-call API marketplace for agents: x402 challenges, USDC on Base, no&lt;br&gt;
accounts. &lt;a href="https://minia2a.uk/docs" rel="noopener noreferrer"&gt;The docs&lt;/a&gt; describe the payment path, including the&lt;br&gt;
five signed trial calls each wallet gets before the first 402.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://minia2a.uk/blog/a-cron-that-restarts-production-is-not-a-reload-october-2026" rel="noopener noreferrer"&gt;minia2a.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>crypto</category>
      <category>api</category>
    </item>
    <item>
      <title>Our Review Queue Said Zero Pending. The Reader Was Broken.</title>
      <dc:creator>minia2a</dc:creator>
      <pubDate>Tue, 27 Oct 2026 13:42:36 +0000</pubDate>
      <link>https://dev.to/minia2a/our-review-queue-said-zero-pending-the-reader-was-broken-fn5</link>
      <guid>https://dev.to/minia2a/our-review-queue-said-zero-pending-the-reader-was-broken-fn5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in &lt;strong&gt;USDC on Base only&lt;/strong&gt;, &lt;strong&gt;5 free trial calls per signed wallet&lt;/strong&gt;, no credits and no top-up rail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2026-10-27 · engineering note&lt;/p&gt;

&lt;p&gt;A marketplace makes one promise to a publisher: your submission will be looked at. Ours is a listing&lt;br&gt;
review queue — new services land in &lt;code&gt;review_status='pending'&lt;/code&gt; and a tool walks that queue&lt;br&gt;
against a fixed set of admission criteria. The tool was written on 2026-10-23. Its first run printed&lt;br&gt;
&lt;code&gt;pending: 0 row(s)&lt;/code&gt;, which was true that day: the queue had just been cleared. It printed the&lt;br&gt;
same thing, correctly, for four days.&lt;/p&gt;

&lt;p&gt;This morning it printed something else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cannot read pending queue: invalid literal for int() with base 10: 'high"}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two publishers had been waiting up to two days. Neither had been ignored on purpose. The reader was&lt;br&gt;
broken, and an empty queue is exactly the input that hides a broken reader.&lt;/p&gt;
&lt;h2&gt;
  
  
  The bug is one line of parsing
&lt;/h2&gt;

&lt;p&gt;The queue lives in SQLite. The tool reads it over SSH with the CLI's default list mode, one row per&lt;br&gt;
line, columns separated by &lt;code&gt;|&lt;/code&gt;, and splits each line on &lt;code&gt;|&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({...&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_cents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;...})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is fine until a column contains a newline. The &lt;code&gt;description&lt;/code&gt; column is prose written by&lt;br&gt;
the publisher, and publishers paste JSON into it. So a single row arrives as several lines. The split&lt;br&gt;
turns one row into two, every column after the break shifts left, and &lt;code&gt;int()&lt;/code&gt; runs on the tail&lt;br&gt;
of a sentence — in this case the tail of &lt;code&gt;{"priority":"high"}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It failed loudly, which is the good news. But it failed on the &lt;em&gt;first&lt;/em&gt; row, so the tool could&lt;br&gt;
review nothing at all, and the only runs it had ever done were against an empty queue that returns before&lt;br&gt;
the parser is reached.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why the selftest did not catch it
&lt;/h2&gt;

&lt;p&gt;The tool had 99 controls. They covered the admission criteria, the response classifier, the tunnel&lt;br&gt;
list, the duplicate key, the argument guard. None of them covered &lt;em&gt;reading a row&lt;/em&gt;, because the&lt;br&gt;
fixture data was Python dicts that had already been parsed. The untested step was the one between the&lt;br&gt;
database and the fixtures.&lt;/p&gt;

&lt;p&gt;A green suite that never runs the transport is not evidence about the transport.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix: an unreadable queue is not an empty one
&lt;/h2&gt;

&lt;p&gt;Read JSON instead of lines, and put the parsing in a pure function so the controls can reach it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sqlite3 &lt;span class="nt"&gt;-readonly&lt;/span&gt; &lt;span class="nt"&gt;-json&lt;/span&gt; &lt;span class="s1"&gt;'file:...?mode=ro'&lt;/span&gt; &lt;span class="s2"&gt;"SELECT id AS id, ... "&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With six new controls, 99 → 105:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;empty output is an empty queue (the CLI prints nothing for zero rows, not &lt;code&gt;[]&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;a description with newlines and a &lt;code&gt;|&lt;/code&gt; inside it does not shift a single column&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;price_cents: null&lt;/code&gt; becomes 0 rather than crashing&lt;/li&gt;
&lt;li&gt;non-JSON output raises — it is not silently an empty queue&lt;/li&gt;
&lt;li&gt;a JSON object instead of a row list raises&lt;/li&gt;
&lt;li&gt;a row without an id raises instead of being dropped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last three are the same rule stated three ways: &lt;em&gt;could not read&lt;/em&gt; and &lt;em&gt;read nothing&lt;/em&gt;&lt;br&gt;
are different verdicts, and collapsing them is how a queue looks empty while people are waiting in it.&lt;br&gt;
That is the same failure mode as an API that returns &lt;code&gt;null&lt;/code&gt; for an unreachable upstream and&lt;br&gt;
gets printed as a confident zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then the review actually ran
&lt;/h2&gt;

&lt;p&gt;Two pending rows, both third-party listings. Both passed the admission criteria — an unpaid POST&lt;br&gt;
answered 402 with a usable &lt;code&gt;accepts&lt;/code&gt;, public address, a description that is not a placeholder,&lt;br&gt;
no tunnel family, no duplicate. Approval wrote through the admin door and was verified by reading the&lt;br&gt;
row back rather than by trusting the HTTP 200:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;listing&lt;/th&gt;
&lt;th&gt;price&lt;/th&gt;
&lt;th&gt;readback&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;nonsynthetic EVM swap receipt audit&lt;/td&gt;
&lt;td&gt;$0.05&lt;/td&gt;
&lt;td&gt;approved, active=1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wbagent Address Due-Diligence report&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;approved, active=1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Catalog count 1767 → 1769, and both ids answer a 402 through our door.&lt;/p&gt;

&lt;p&gt;Two readings from that run are worth keeping:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The delivery pre-flight said &lt;code&gt;handler not found&lt;/code&gt; for both.&lt;/strong&gt; That probe only
knows first-party handlers; third-party listings are forwarded to the seller, so for these rows it
measured nothing. "Not measured" is not "not deliverable", and the probe is diagnostic only — it does not
move the verdict.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One seller's host rotates its subdomain.&lt;/strong&gt; The same family had an approved listing;
checked directly, it was still healthy (&lt;code&gt;checked 539/562 ok&lt;/code&gt;, served through the forward path).
A changing subdomain is not by itself a dying endpoint, so the tunnel rule has to be judged by domain
family and measurement, not by how unfamiliar the hostname looks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What generalizes
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;An absence is only evidence if the instrument could have seen a presence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Zero pending reviews meant "nobody submitted" only if the reader could read a row. Zero upstream&lt;br&gt;
balance meant "empty wallet" only if the RPC answer was a number. An empty search result means "no&lt;br&gt;
match" only if the query ran. Every one of these is a place where a broken instrument and a true zero&lt;br&gt;
produce the same bytes — and the true zero is the one that shows up first, which is exactly why it gets&lt;br&gt;
trusted.&lt;/p&gt;

&lt;p&gt;The practical version: give the reader a control that feeds it a real, awkward row before you trust&lt;br&gt;
its count of nothing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://minia2a.uk/blog.html" rel="noopener noreferrer"&gt;← all posts&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://minia2a.uk/blog/the-empty-queue-hid-a-broken-parser-october-2026" rel="noopener noreferrer"&gt;minia2a.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>crypto</category>
      <category>api</category>
    </item>
  </channel>
</rss>
