Skip to content

Ship the dark-only theme, the seller payee fix, and close the tools/list class - #706

Merged
MikeyPetrillo merged 7 commits into
mainfrom
claude/sweet-brown-i99jl3
Aug 7, 2026
Merged

Ship the dark-only theme, the seller payee fix, and close the tools/list class#706
MikeyPetrillo merged 7 commits into
mainfrom
claude/sweet-brown-i99jl3

Conversation

@MikeyPetrillo

Copy link
Copy Markdown
Owner

Five commits, already CI-green on the branch and (for the deploy-marked ones) live on prod.

  • Dark-only theme and the follow-up run after the Actions outage.
  • Raw JavaScript rendering at the top of every page - fixed.
  • A venue with many payees is not one seller: the index kept one payTo per network via a first-wins reduce, so a marketplace giving each author their own revenue split had 21 of 22 payees discarded, and any ranking aggregating by payTo attributed the whole origin's volume to whichever address was crawled first.
  • The tool we told agents to call was not in tools/list (about_agent402 tells agents to call request_tool; your live tools/list (17 tools) has no such tool #705): about_agent402 instructs agents to "Call request_tool" and request_tool was never listed, so only callers who already knew the name could reach it. Third instance of that one defect, and the first two were fixed by hand without a test, which is why this one was reported from outside.

The last commit is mostly the guard rather than the one-line fix. scripts/test-mcp-self-consistency.js reads five agent-facing surfaces and asserts that every tool name we tell an agent to call is advertised, every named slug exists, and every referenced route is registered - the class no functional test can see, because every functional test calls the tools whose names it already knows.

Route existence uses two independent oracles and reports missing only when both say no: a source scan (the only one that can see a POST-only route) and a live GET (the only one that can see template-literal routes). The live probe never touches /api or /v1, since those handlers execute under FREE_MODE.

Mutation-tested: removing request_tool from the listing fails 2 assertions independently, a planted fake tool name fails 1, a planted fake route fails 1. An earlier draft of the checker had a plausible-looking filter that skipped unknown names - i.e. exactly the defect being hunted - and a planted Call submit_wish passed a green run. That filter is gone.

MikeyPetrillo and others added 5 commits August 6, 2026 12:18
CSS and markup only, no new variable, so the upsert is a no-op and cannot race
the pinned deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous deploy run sat queued for hours through a GitHub Actions major
outage and never got a runner, so it does not self-start now that capacity is
back. Same change, fresh trigger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Shipped broken. The regex that removed the theme IIFE ate its `<script>`
OPENING tag, leaving the next function as bare text in <head> followed by an
orphaned `</script>`:

  <head>
  <meta charset="utf-8">
  function a402ToggleMenu(){...}</script>

Browsers hoist stray text out of <head> and into the body, so a wall of
JavaScript rendered at the top of every page on the site. Worse and quieter:
a402ToggleMenu was never DEFINED, so the mobile burger menu was dead on every
route - and the burger is the only navigation below 880px, where the nav links
are display:none.

Nothing caught it. Div balance was fine, the strict HTML parser was fine, all
three SEO strings were present, every route returned 200, and the theme
assertions all passed. The page was well-formed; it just had a script's worth
of source code printed on it.

Reported by Mike from looking at the live site, which is the part worth
remembering: a check that reads structure will not notice text that is
structurally valid and semantically absurd.

Guarded now, mutation-tested (removing the tag again fails 3):
- <script> open/close tags must balance, since an orphaned closing tag is the
  signature of a stripped opening one
- no bare `function` declaration may sit outside a script in <head>
- a402ToggleMenu must be defined INSIDE a script element
- the burger button must still be wired to it

Verified across ten routes: scripts balanced on every one, no leaked source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The index kept ONE payTo per network via a first-wins reduce, so every payee
after the first was discarded. Fine for a seller whose tools all pay the same
address; wrong for a marketplace that gives each author their own revenue
split.

Measured on a live seller 2026-08-06: 236 paid routes across 22 authors with
22 distinct payTo addresses, of which the index retained exactly one. Twenty
one payees were invisible, and any ranking that aggregates by payTo - ours
included - attributed that whole origin's on-chain volume to whichever address
happened to be crawled first.

This is also how I got the architecture wrong in conversation. I read one 402,
saw a single payTo, and concluded the origin shared one split across all its
authors. The seller had to correct me; their design was right and OUR INDEX was
the part flattening it.

`payToByNetwork` deliberately stays a single string per network. The router's
chain-derived proven-ness join, advertisedPayToEvidence, and the market pages
all index it directly, and changing its type would break them quietly.
`payTosByNetwork` carries the full set alongside, from one shared
allPayTosByNetwork() used by all three builders plus the http/https collapse -
which unions the arrays rather than spreading one object over the other, since
the two schemes of a host can advertise different payees.

Case-exact, because folding merges DISTINCT payees: base58/base32 addresses are
case-sensitive and EVM addresses are checksummed (same rule as src/payer.js).
Capped at 200 per network so a pathological origin cannot balloon a cached
index entry.

The test IMPORTS the reducer rather than mirroring it. The first draft
reimplemented it locally and would have stayed green with payTosByNetwork
deleted from the module outright - proving its own copy works, not the shipped
code. Mutation-tested: reverting to first-wins fails 5 assertions, case-folding
fails 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
about_agent402's missingATool field says "Call request_tool". tools/list did
not contain request_tool. The handler existed and worked, so anyone who
already knew the name could call it, and every agent who learned about it the
way we documented could not find it. The wish board is how we decide what to
build next, and the one tool whose job is to catch an agent that just failed
to find something was itself unfindable.

This is the THIRD instance of one defect. about_agent402 and top_x402_sellers
had the same shape - working handler, absent from the only surface an MCP
client can discover from - and both were fixed by hand, with a comment in
mcp-http.js explaining the class and no test pinning it. Fixing instances
three times without pinning the class is why this one was reported from
outside instead of found here.

So the fix is mostly the guard. test-mcp-self-consistency.js reads five
agent-facing surfaces (tools/list text, about_agent402, get_payment_info,
/llms.txt, /.well-known/x402) and asserts that every tool name in a
call-this position is advertised, every named catalog slug exists, and every
referenced route is registered - plus both parity directions, a handler branch
no advertised name can reach and a listed tool with no implementation. Nothing
functional could have caught this: every existing test calls the tools whose
names it already knows.

Route existence needs TWO oracles and reports missing only when both say no.
A source scan of app.<verb>("...") is the only one that can see a POST-only
route - a live GET 404 cannot tell "no such route" from "wrong method", and
that ambiguity is precisely why the reporter would not conclude /api/wish was
broken. A live GET is the only one that can see the chain pages, registered
through a template literal the scan cannot read. The live probe never touches
/api or /v1: in FREE_MODE those handlers execute, and a consistency check has
no business calling a tool that spends money. Matching is segment-aware, so
/api/wish is never satisfied by /api/wishes.

The first draft skipped any snake_case name that was neither listed nor
handled, on the theory that unknown words are probably not tool references.
That filter skipped exactly the defect being hunted: a planted "Call
submit_wish" passed a green run. It is gone. The only escape hatch is an
explicit NOT_A_TOOL set, currently one entry, for a tool that is real but
lives on the stdio package.

Mutation-tested: removing request_tool from the listing fails 2 assertions
(structural and prose, independently), a fake tool name fails 1, a fake route
fails 1. Extractors are proven against a planted control before any clean run
is believed, the same doctrine as the free-tier egress probe.

request_tool is annotated readOnlyHint:false, alone among the 18. It records a
wish row, and a client that trusts that hint to decide what it may call
unattended would be misled by a blanket true.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread scripts/test-theme.js Fixed
A published path like /demo.js was being truncated to /demo before the
lookup, which would report our own live zero-dependency demo as a route
that does not exist. Found by running the same check over README and the
wiki, where /demo.js is how we tell a new agent to start.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MikeyPetrillo
MikeyPetrillo marked this pull request as ready for review August 7, 2026 11:07
The balance assertion that catches a stripped <script> opening tag only
matched lowercase, so an uppercase <SCRIPT> would have gone uncounted and
reported a balance that was not there - a blind spot in the one check
standing between us and shipping a wall of raw JavaScript at the top of
every page again. Flagged by CodeQL (js/bad-tag-filter) on this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MikeyPetrillo
MikeyPetrillo merged commit 0e04013 into main Aug 7, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants