Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e327455
[test][deploy] Fix WALLET_BLOCKLIST never matching Solana/Stellar/Alg…
MikeyPetrillo Aug 16, 2026
2fc67c4
[test][deploy] Rename uptimeSeconds -> processUptimeSeconds
MikeyPetrillo Aug 16, 2026
98adc40
[test][deploy] Bump @x402 family 2.16 -> 2.22 - closes a live payment…
MikeyPetrillo Aug 16, 2026
d20bc4f
[test][deploy] Fix --faint contrast to clear WCAG AA (was 3.15-3.66:1)
MikeyPetrillo Aug 16, 2026
5d2005f
[test][deploy] Fix /contact's dead success-state markup
MikeyPetrillo Aug 16, 2026
4571da1
[test][deploy] Add buyer-diversity trend line to /revenue's Buyers me…
MikeyPetrillo Aug 16, 2026
d25af77
[test][deploy] CRITICAL: fix CSS parse break from invalid // comment …
MikeyPetrillo Aug 16, 2026
e030819
[test][deploy] Fix /sell registration form accessibility + feedback
MikeyPetrillo Aug 16, 2026
b3de8d7
[test][deploy] Fix missing focus-visible styles on 4 search/demo inputs
MikeyPetrillo Aug 16, 2026
50a8457
[test][deploy] Fix nested <main> landmark on 4 pages
MikeyPetrillo Aug 16, 2026
a0e5729
[test][deploy] Add horizontal-scroll wrapper to /compare's 4 tables
MikeyPetrillo Aug 16, 2026
d289d84
[test][deploy] Add JSON 405 + Allow header for wrong-method catalog r…
MikeyPetrillo Aug 16, 2026
985bf66
[test][deploy] Add data-reveal-eager opt-in for a second above-fold s…
MikeyPetrillo Aug 16, 2026
579e42e
[test][deploy] Fix hamburger menu aria-label to toggle with aria-expa…
MikeyPetrillo Aug 16, 2026
7a8d0c2
[test][deploy] Dedup /.well-known/x402 resources[] by URL
MikeyPetrillo Aug 16, 2026
ccc3944
[test][deploy] Declare X-Pow-Solution/Idempotency-Key as headers in o…
MikeyPetrillo Aug 16, 2026
dfab67b
[test][deploy] Add operator-only rail revenue visibility + top-by-rev…
MikeyPetrillo Aug 16, 2026
31f2e2e
[test][deploy] Add seller_registrations table for /sell conversion/ch…
MikeyPetrillo Aug 16, 2026
0091194
[test][deploy] Add 'how to read our 402' note to llms.txt (issue #794)
MikeyPetrillo Aug 16, 2026
ec0b3fe
[test][deploy] Verify MPP_CHALLENGE_NETWORKS default against real mpp…
MikeyPetrillo Aug 16, 2026
8f2735e
[test][deploy] Fix flaky test-operator-revenue-visibility.js in share…
MikeyPetrillo Aug 16, 2026
f878cde
[test][deploy] Derive llms.txt route-execute pricing from EXEC_TIERS
MikeyPetrillo Aug 16, 2026
4a85007
[test][deploy] Auto-deprecate superseded MCP Registry versions on pub…
MikeyPetrillo Aug 16, 2026
b260f45
[test][deploy] Redis-back the payment-nonce replay guard across replicas
MikeyPetrillo Aug 16, 2026
055252d
[test][deploy] Fix reveal-on-scroll content permanently invisible on …
MikeyPetrillo Aug 16, 2026
540ff7a
[test][deploy] Remove 'unsafe-inline' from CSP script-src
MikeyPetrillo Aug 16, 2026
5d5ad7c
[test][deploy] Fix test-buyers-trend.js pointing at a script location…
MikeyPetrillo Aug 16, 2026
1c3540a
[test][deploy] Fix test-catalog-page.js checking the page HTML for a …
MikeyPetrillo Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 79 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,9 @@ jobs:
- name: Discovery & trust surfaces (/.well-known/x402, /api/reliability)
run: node scripts/test-discovery.js

- name: Manifest resources[] dedup (a tool registered under 2 HTTP methods lists its URL once, not twice)
run: node scripts/test-manifest-resources-dedup.js

- name: Revenue detector classification (real x402 buys vs funding/tests)
run: node scripts/test-revenue-scan.js

Expand Down Expand Up @@ -881,6 +884,9 @@ jobs:
- name: /openapi.json coverage (every CATALOG tool has a path + operationId + x-price)
run: node scripts/test-openapi-coverage.js

- name: /openapi.json header parameters (Idempotency-Key on every op, X-Pow-Solution only where PoW-eligible)
run: node scripts/test-openapi-header-params.js

- name: /.well-known/x402 manifest contract (envelope + payment + capabilities + discovery)
run: node scripts/test-x402-manifest.js

Expand All @@ -893,6 +899,12 @@ jobs:
- name: /llms.txt content lock (tool count + discovery URLs + PoW/MCP/wallet signals)
run: node scripts/test-llms-txt.js

- name: /llms.txt MPP/x402 dual-header reading note (issue #794 guidance, offline)
run: node scripts/test-llms-mpp-header-note.js

- name: /llms.txt route-execute tier pricing derived from EXEC_TIERS (a future 5th tier can't go silently missing — offline)
run: node scripts/test-llms-route-execute-tiers.js

- name: Rails-copy lock (rails.js ↔ payments.js + every key page renders every rail)
run: TARGET_URL=http://localhost:3000 node scripts/test-rails.js

Expand Down Expand Up @@ -984,6 +996,30 @@ jobs:
- name: Reveal-on-scroll no hero flash (the hero must never hide behind opacity:0 waiting on the observer)
run: node scripts/test-reveal-no-hero-flash.js

- name: CSS tokens resolve (every :root custom property must actually resolve in a real browser, not just exist as text)
run: TARGET_URL=http://localhost:3000 node scripts/test-css-tokens-resolve.js

- name: Focus-visible on search/demo inputs (real browser check - inline style can silently defeat a stylesheet :focus rule)
run: TARGET_URL=http://localhost:3000 node scripts/test-focus-visible.js

- name: Single <main> landmark per page (no page-level renderer may nest a second <main> inside ledgerShell's own)
run: TARGET_URL=http://localhost:3000 node scripts/test-single-main-landmark.js

- name: /compare tables scroll independently on narrow viewports (real browser check - scrollWidth>clientWidth alone can't prove overflow-x is enabled)
run: TARGET_URL=http://localhost:3000 node scripts/test-compare-scroll.js

- name: Wrong-method 405 with Allow header (a known catalog path hit with the wrong verb no longer looks like a dead route)
run: TARGET_URL=http://localhost:3000 node scripts/test-wrong-method-405.js

- name: Reveal-on-scroll data-reveal-eager opt-in (a second above-fold section can be exempted explicitly, never leaks to unmarked pages)
run: TARGET_URL=http://localhost:3000 node scripts/test-reveal-eager.js

- name: Reveal-on-scroll skip-over safety net (a single fast scroll jump must never leave a section permanently invisible — real browser, found live on /marketplace 2026-08-16)
run: TARGET_URL=http://localhost:3000 node scripts/test-reveal-scroll-skip.js

- name: Hamburger menu aria-label toggles with aria-expanded (real browser click, not a static string check)
run: TARGET_URL=http://localhost:3000 node scripts/test-burger-aria-label.js

- name: Self-listing exclusion (crawled agent402.tools origin never appears as an "external" seller — offline)
run: node scripts/test-self-listing-exclusion.js

Expand All @@ -1005,6 +1041,9 @@ jobs:
- name: Revenue buyers series (distinct-count semantics; seeded ledger — offline)
run: node scripts/test-revenue-buyers.js

- name: Revenue buyers trend (rolling 14d recent-vs-prior comparison — offline)
run: node scripts/test-buyers-trend.js

- name: Settle-fallback chain (PayAI -> Solvador; double-settle gate between fallbacks — offline)
run: node scripts/test-settle-fallback.js

Expand Down Expand Up @@ -1071,9 +1110,15 @@ jobs:
- name: MPP dual-stack shim (real mppx client buys over WWW-Authenticate/Authorization wire; single settle; x402 pass-through; HMAC tamper — offline)
run: node scripts/test-mpp-shim.js

- name: MPP_CHALLENGE_NETWORKS default verified against installed mppx registry (drift guard — offline)
run: node scripts/test-mpp-shim-mppx-registry.js

- name: HEAD paywall bypass closed (HEAD on paid GET routes 402s with challenges + empty body; free surfaces untouched — offline)
run: node scripts/test-head-paywall.js

- name: Wildcard route bypass closed (@x402/core 2.21.0 dotAll fix — percent-encoded line terminators can't skip payment on /api/convert/* — offline)
run: node scripts/test-wildcard-route-bypass.js

- name: Boot /supported guard (a dead facilitator costs ONE rail, not every paid route — the 2026-08-01 Celo outage; probe-driven drop, fail-open on total blindness, escape hatch — offline)
run: node scripts/test-supported-guard.js

Expand All @@ -1094,6 +1139,11 @@ jobs:
REDIS_URL: redis://127.0.0.1:6379
run: node scripts/test-redis-integration.js

- name: "Replay guard, cross-replica (REAL redis — two guard instances prove shared claim/settle/release; fails open to local state on a redis outage — 2026-08-16)"
env:
REDIS_URL: redis://127.0.0.1:6379
run: node scripts/test-replay-guard-redis.js

- name: Well-known store (operator-published domain-verification docs; traversal impossible, reserved names refused, no shadowing of /.well-known/x402 — offline)
run: node scripts/test-well-known-store.js

Expand Down Expand Up @@ -1174,6 +1224,9 @@ jobs:
- name: MCP outer transport limits (R-11 — per-IP cap + concurrency ceiling bound floods before server/transport allocation — spawns child)
run: node scripts/test-mcp-limits.js

- name: MCP Registry find-latest-version helper (used by the publish job to auto-deprecate a superseded version — offline)
run: node scripts/test-mcp-find-latest-version.js

- name: PostHog analytics proxy abuse controls (R-17 — method allowlist + per-IP cap before the upstream call — spawns child)
run: node scripts/test-posthog-proxy.js

Expand Down Expand Up @@ -1401,9 +1454,15 @@ jobs:
- name: Operator auth (A402-07 no ?token= query; session cookie + header; hardened cookie flags — spawns child)
run: node scripts/test-operator-auth.js

- name: Operator revenue visibility (zero-revenue rail flagging + top-by-revenue vs top-by-calls, offline)
run: node scripts/test-operator-revenue-visibility.js

- name: Index self-serve registration tests (offline)
run: node scripts/test-index-register.js

- name: Seller registration conversion/churn table (first_seen immutable, last_settled_seen sticky — offline)
run: node scripts/test-seller-registrations.js

- name: Index openapi-fallback crawl tests (payment-signal gate, Bazaar merge, ranking — offline)
run: node scripts/test-openapi-fallback.js

Expand Down Expand Up @@ -1490,6 +1549,7 @@ jobs:
node scripts/test-encoding-kit.js
node scripts/test-math-kit.js
node scripts/test-theme.js
node scripts/test-faint-contrast.js
node scripts/test-reveal-on-scroll.js
node scripts/test-validation-kit.js
node scripts/test-text-analysis-kit.js
Expand Down Expand Up @@ -2307,9 +2367,17 @@ jobs:
# "mcpName" to the server.json name and confirming the npm package is
# published — so this runs after the npm publish above.
VER=$(node -p "require('./mcp/package.json').version")
if curl -s "https://registry.modelcontextprotocol.io/v0/servers?search=io.github.MikeyPetrillo/agent402" | grep -q "\"version\":\"$VER\""; then
SEARCH_RESULT=$(curl -s "https://registry.modelcontextprotocol.io/v0/servers?search=io.github.MikeyPetrillo/agent402&limit=100")
if echo "$SEARCH_RESULT" | grep -q "\"version\":\"$VER\""; then
echo "io.github.mikeypetrillo/agent402@$VER already in the MCP Registry — nothing to do"; exit 0
fi
# Capture whichever version is isLatest RIGHT NOW - it's about to
# become stale the moment the new publish below lands. Deprecation
# tooling gap found 2026-08-16: nothing ever marked a superseded
# version deprecated, so 24 of 25 published versions were still
# "active" in the registry. Best-effort: a lookup failure here
# leaves PREV_LATEST empty and the deprecate step below just skips.
PREV_LATEST=$(echo "$SEARCH_RESULT" | node scripts/mcp-find-latest-version.js || true)
asset="mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz"
if [ -n "${MCP_PUBLISHER_VERSION:-}" ]; then base="https://github.com/modelcontextprotocol/registry/releases/download/$MCP_PUBLISHER_VERSION"; else base="https://github.com/modelcontextprotocol/registry/releases/latest/download"; fi
# Download to disk and fail fast (no curl|tar of unverified bytes).
Expand Down Expand Up @@ -2352,6 +2420,16 @@ jobs:
../mcp-publisher login github-oidc
../mcp-publisher publish
echo "published io.github.mikeypetrillo/agent402@$VER → https://registry.modelcontextprotocol.io"
# Deprecate the version this publish just superseded. Only ever
# touches the ONE immediately-previous version - never a bulk
# rewrite of history - and never fails the job: a registry hiccup
# here is a missed housekeeping step, not a publish failure.
if [ -n "$PREV_LATEST" ] && [ "$PREV_LATEST" != "$VER" ]; then
../mcp-publisher status --status deprecated --message "Superseded by $VER" \
io.github.MikeyPetrillo/agent402 "$PREV_LATEST" \
&& echo "deprecated superseded version $PREV_LATEST" \
|| echo "::warning::failed to deprecate superseded version $PREV_LATEST (non-fatal)"
fi

probe:
needs: markers
Expand Down
128 changes: 128 additions & 0 deletions assets/js/api-explorer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
(function(){
var wrapEl=document.querySelector('.ae-wrap');
var BASE=wrapEl?(wrapEl.getAttribute('data-base')||''):'';
var list=document.getElementById('aeList');
var search=document.getElementById('aeSearch');
var countEl=document.getElementById('aeCount');
var catsEl=document.getElementById('aeCats');
var endpoints=[];
var activeCategory='all';

function el(tag,cls,text){
var e=document.createElement(tag);
if(cls)e.className=cls;
if(text)e.textContent=text;
return e;
}

fetch(BASE+'/openapi.json').then(function(r){return r.json()}).then(function(spec){
var paths=spec.paths||{};
Object.keys(paths).sort().forEach(function(p){
var methods=paths[p];
['get','post','put','delete','patch'].forEach(function(m){
if(!methods[m])return;
var op=methods[m];
endpoints.push({method:m.toUpperCase(),path:p,name:op.summary||op.operationId||'',desc:op.description||'',category:(op.tags&&op.tags[0])||'other',schema:op.requestBody&&op.requestBody.content&&op.requestBody.content['application/json']&&op.requestBody.content['application/json'].schema||null,params:op.parameters||[]});
});
});
renderCats();
renderList();
}).catch(function(){list.textContent='Failed to load API spec.';});

function renderCats(){
while(catsEl.firstChild)catsEl.removeChild(catsEl.firstChild);
var cats={};
endpoints.forEach(function(e){cats[e.category]=true;});
var allBtn=el('button','ae-cat-btn active','All');
allBtn.setAttribute('data-cat','all');
catsEl.appendChild(allBtn);
Object.keys(cats).sort().forEach(function(c){
var btn=el('button','ae-cat-btn',c);
btn.setAttribute('data-cat',c);
catsEl.appendChild(btn);
});
catsEl.addEventListener('click',function(ev){
var btn=ev.target.closest('.ae-cat-btn');
if(!btn)return;
activeCategory=btn.getAttribute('data-cat');
catsEl.querySelectorAll('.ae-cat-btn').forEach(function(x){x.classList.remove('active');});
btn.classList.add('active');
renderList();
});
}

function renderList(){
var q=search.value.toLowerCase().trim();
while(list.firstChild)list.removeChild(list.firstChild);
var shown=0;
endpoints.forEach(function(ep){
if(activeCategory!=='all'&&ep.category!==activeCategory)return;
if(q&&ep.method.toLowerCase().indexOf(q)===-1&&ep.path.toLowerCase().indexOf(q)===-1&&ep.name.toLowerCase().indexOf(q)===-1&&ep.desc.toLowerCase().indexOf(q)===-1)return;
shown++;

var div=el('div','ae-endpoint');
var head=el('div','ae-ep-head');
head.appendChild(el('span','ae-method '+ep.method,ep.method));
head.appendChild(el('span','ae-path',ep.path));
head.appendChild(el('span','ae-ep-name',ep.name));
head.addEventListener('click',function(){div.classList.toggle('open');});
div.appendChild(head);

var body=el('div','ae-ep-body');

if(ep.desc){
var sec=el('div','ae-section');
sec.appendChild(el('div','ae-section-title','Description'));
var p=el('p','',ep.desc);
p.style.cssText='font-size:.9rem;color:var(--muted);margin:0';
sec.appendChild(p);
body.appendChild(sec);
}

if(ep.params.length){
var sec2=el('div','ae-section');
sec2.appendChild(el('div','ae-section-title','Parameters'));
var schema=el('div','ae-schema');
ep.params.forEach(function(pm){
var row=el('div','ae-prop');
row.appendChild(el('span','ae-prop-name',pm.name));
row.appendChild(document.createTextNode(' '));
row.appendChild(el('span','ae-prop-type',pm.in||'query'));
if(pm.description){row.appendChild(document.createTextNode(' '));row.appendChild(el('span','ae-prop-desc',pm.description));}
schema.appendChild(row);
});
sec2.appendChild(schema);
body.appendChild(sec2);
}

if(ep.schema&&ep.schema.properties){
var sec3=el('div','ae-section');
sec3.appendChild(el('div','ae-section-title','Request Body'));
var schema2=el('div','ae-schema');
Object.keys(ep.schema.properties).forEach(function(k){
var prop=ep.schema.properties[k];
var row=el('div','ae-prop');
row.appendChild(el('span','ae-prop-name',k));
row.appendChild(document.createTextNode(' '));
row.appendChild(el('span','ae-prop-type',prop.type||'any'));
if(prop.description){row.appendChild(document.createTextNode(' '));row.appendChild(el('span','ae-prop-desc',prop.description));}
schema2.appendChild(row);
});
sec3.appendChild(schema2);
body.appendChild(sec3);
}

var sec4=el('div','ae-section');
var tryBtn=el('a','ae-try-btn','Try in Playground →');
tryBtn.href='/playground';
sec4.appendChild(tryBtn);
body.appendChild(sec4);

div.appendChild(body);
list.appendChild(div);
});
countEl.textContent=shown+' endpoint'+(shown===1?'':'s');
}

search.addEventListener('input',renderList);
})();
25 changes: 25 additions & 0 deletions assets/js/catalog-search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
(function() {
var search = document.getElementById('cat-search');
var rows = document.querySelectorAll('.cat-row');
var empty = document.getElementById('cat-empty');
var emptyQ = document.getElementById('cat-empty-q');
function applyFilter() {
var q = (search.value || '').toLowerCase().trim();
var visible = 0;
rows.forEach(function(row) {
var label = row.querySelector('th a').textContent.toLowerCase();
var blurb = row.querySelector('.cat-blurb').textContent.toLowerCase();
var match = !q || label.indexOf(q) !== -1 || blurb.indexOf(q) !== -1;
row.style.display = match ? '' : 'none';
if (match) visible++;
});
empty.style.display = visible === 0 ? 'block' : 'none';
if (visible === 0) emptyQ.textContent = search.value;
}
search.addEventListener('input', applyFilter);
try {
var params = new URLSearchParams(window.location.search);
var q0 = params.get('q');
if (q0) { search.value = q0; applyFilter(); }
} catch (e) {}
})();
25 changes: 25 additions & 0 deletions assets/js/copy-buttons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
(function(){
// Shared "copy code snippet" button handler for every page that has one:
// adapter docs (.ml-adp-copy), badges (.bdg-copy), contribute (.ct-copy),
// quickstart (.qs-copy). All four copy their own <code> sibling's text and
// give the same "Copied!" feedback - unified into one file (CSP hardening,
// 2026-08-16) instead of four near-identical inline scripts. .ml-adp-copy
// originally used inline style.color instead of a "copied" CSS class;
// both behaviors are preserved exactly, keyed by which class matched, so
// no page's visual behavior changed.
function wire(selector, feedback){
document.querySelectorAll(selector).forEach(function(btn){
btn.addEventListener("click",function(){
var code=btn.parentElement.querySelector("code");
var text=code.textContent;
navigator.clipboard.writeText(text).then(function(){
btn.textContent="Copied!";
feedback(btn, true);
setTimeout(function(){btn.textContent="Copy";feedback(btn, false);},1500);
});
});
});
}
wire(".ml-adp-copy", function(btn, on){ btn.style.color = on ? "var(--accent)" : ""; });
wire(".bdg-copy, .ct-copy, .qs-copy", function(btn, on){ btn.classList.toggle("copied", on); });
})();
30 changes: 30 additions & 0 deletions assets/js/docs-sidebar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
(function(){
var toggle=document.getElementById('ml-docs-mobile-toggle');
var side=document.getElementById('ml-docs-side');
if(!toggle||!side)return;
toggle.addEventListener('click',function(){
var open=side.classList.toggle('ml-docs-side-open');
toggle.setAttribute('aria-expanded',open?'true':'false');
});
})();
(function(){
var input=document.getElementById('ml-docs-search-input');
if(!input)return;
var side=input.closest('.ml-docs-side');
if(!side)return;
input.addEventListener('input',function(){
var q=input.value.trim().toLowerCase();
var lists=side.querySelectorAll('.ml-docs-side-ul');
lists.forEach(function(ul){
var anyVisible=false;
ul.querySelectorAll('li').forEach(function(li){
var match=!q||(li.textContent||'').toLowerCase().indexOf(q)>-1;
li.classList.toggle('ml-docs-side-hidden',!match);
if(match)anyVisible=true;
});
var h=ul.previousElementSibling;
if(h&&h.classList.contains('ml-docs-side-h'))h.classList.toggle('ml-docs-side-hidden',!anyVisible);
ul.classList.toggle('ml-docs-side-hidden',!anyVisible);
});
});
})();
Loading