Skip to content

Feat/paid api docs smoke tests health stats - #424

Merged
Emmy123222 merged 5 commits into
Emmy123222:mainfrom
nonso7:feat/paid-api-docs-smoke-tests-health-stats
Sep 3, 2026
Merged

Feat/paid api docs smoke tests health stats#424
Emmy123222 merged 5 commits into
Emmy123222:mainfrom
nonso7:feat/paid-api-docs-smoke-tests-health-stats

Conversation

@nonso7

@nonso7 nonso7 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Closes #225
Closes #226
Closes #227
Closes #228

.github/workflows/ci.yml carried unresolved conflict markers committed
to main in 7804e9c, which made the file invalid YAML. GitHub Actions
could not parse it, so none of the CI jobs ran at all — including any
job a branch protection rule might require.

Resolve by keeping both sides: the real coverage-gate steps (npm ci plus
threshold verification) replace the placeholder echo, and the
supply-chain SBOM and vulnerability job is retained.
The README and DocsPage covered /search only, while /images and /news
were served by Express and proxied by the MCP tools with no public
documentation of their parameters, limits, 402 challenge, paid response,
or result fields.

Document both in the README — per-route count bounds and freshness
enums, an error table, the 402 challenge, paid response bodies, and
ImageResult/NewsResult field tables — and in DocsPage, with curl
examples that URL-encode queries via --data-urlencode rather than
inlining them. Record explicitly that /images and /news are Express and
MCP only, with no Vercel serverless equivalent.

Documenting the limits required making them true. Emmy123222#188 added
src/lib/paramValidation.ts and a full test matrix but never wired the
contract into a route, so /images and /news silently clamped count
(count=999 became 10) instead of rejecting it, and the README already
described rejection that did not happen. Wire the shared contract in as
middleware registered ahead of the x402 middleware, so an invalid count
or freshness is refused with 400 without consulting the payment adapter
or issuing a payment challenge — on Express and on Vercel /api/search
alike. Verified against the running server: /images?q=x&count=999 now
returns 400 "count must be between 1 and 10" rather than a 402.

Also correct the payment headers shown in the DocsPage flow. The
middleware emits the challenge on PAYMENT-REQUIRED, not
X-Payment-Required, and takes the signed payload on PAYMENT-SIGNATURE,
with X-PAYMENT retained for x402 v1 clients.
Pull requests validated nothing about an actual deployment. Serverless
routing, CORS, environment wiring, static assets, and the SPA rewrite
only exist once Vercel has built a preview, so unit tests cannot see
them and a routing or CORS regression reached main unnoticed.

Add scripts/smoke.mjs, a 12-check suite covering those, plus the shape
of the x402 402 challenge: scheme exact, a stellar network, an integer
stroop amount rather than a decimal dollar figure, a Soroban C...
asset rather than USDC:ISSUER, and a G... payTo. That last check is the
one a bad deploy silently breaks.

The suite is non-secret by construction: no repository secrets, no
Vercel token, no wallet, no signing material, and it never settles a
payment, so a run costs 0 USDC. It is dependency-free plain ESM, so CI
runs it straight from a checkout without npm ci.

Add .github/workflows/preview-smoke.yml, triggered by the
deployment_status event Vercel's GitHub integration emits, so no Vercel
token is needed. Failures name the exact endpoint three ways: a
::error:: annotation per failed endpoint, a job-summary report carrying
filtered headers and a truncated body, and an uploaded artifact.

Add vercel.json, which did not exist, defining the serverless routing,
SPA rewrites, and CORS headers the suite validates.

Response artifacts are header-filtered before they are written:
authorization, set-cookie, x-api-key, and the payment headers are never
captured, and bodies are truncated to 2 KB.
Closes Emmy123222#226

The serverless health schema omitted totalQueries, totalUsdcSettled,
avgLatencyMs and uptime while the browser coalesced them away
(data.totalQueries ?? 0), so a Vercel deployment rendered
"0 queries, $0.00 settled, 0ms" beside a pulsing green SERVER ONLINE
indicator. None of that was a measurement; it was missing data presented
as fact. The MCP get_search_stats tool fared worse — it called
stats.totalQueries.toLocaleString() on the absent field, threw a
TypeError, and surfaced it to the user as a misleading
"Failed to fetch server stats".

Durable serverless counters are not reachable without external storage,
and an in-memory counter in a function that scales to zero would
describe one warm instance rather than the deployment. So take the other
option and declare the gap. src/lib/serverHealth.ts holds one contract
shared by all four runtimes: Express declares statsSupported true, the
Vercel handler declares the four fields unsupported with a reason, and
omits them rather than reporting zeros.

Consumers now read through resolveStat(), which keeps a measured zero
distinct from an unmeasured field. StatsGrid renders unmeasured metrics
as n/a — dimmed, without the pulsing dot that signals a live reading,
with the reason on hover and in screen-reader text, and one panel-level
note — while a freshly started Express server still shows a genuine 0,
because having served no queries is a real measurement. MCP prints
"not reported" plus a single reason line instead of throwing. The
preview smoke suite now fails a deployment that omits the counters
without declaring them, that claims support but omits the values, or
that leaves a stale value on a field declared unsupported.

Additive and backward compatible: no existing field changed type or
meaning, and resolveStat tolerates deployments predating the contract by
trusting present values and reporting absent ones as undeclared rather
than as zero. Reporting only — the paid routes and their verified x402
settlement semantics are untouched.
The documented tree predated the server helpers, serverless routes, UI
components, tests and workflow files now in the repo, and listed
components at paths they no longer occupy.

Rebuild it from the actual file listing, and mark which code is shared
versus runtime-specific: everything under src/lib is imported by the
browser bundle, the Express server, the Vercel functions and the MCP
server alike, so a change there has to keep all four aligned, while
server/, api/ and mcp-server/ are each one runtime's implementation.

Add a table mapping every test suite to the runtime it covers, so the
Express, Vercel, browser and MCP boundaries are visible from the tree
rather than inferred from filenames.
@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

@nonso7 is attempting to deploy a commit to the Emmanuel's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Sep 2, 2026

Copy link
Copy Markdown

@nonso7 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Emmy123222
Emmy123222 merged commit fc68f29 into Emmy123222:main Sep 3, 2026
0 of 2 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

2 participants