Skip to main content
Glama
README.md
# vet402

[![ci](https://github.com/kzmttkc/vet402/actions/workflows/ci.yml/badge.svg)](https://github.com/kzmttkc/vet402/actions/workflows/ci.yml)

**Independent Verification of the x402 Agent-Payment Economy**

*vet402 buys. It settles. It publishes the measurements.*

## Chains vet402 buys on

vet402 pays x402 sellers on ten chains with its own funds and publishes every attempt. Counts below were read from https://vet402.com/api/v1/observatory/state at 2026-10-23 09:10 UTC (whole ledger). Avalanche C-Chain started on 2026-10-26, after that read, so it has no row here; its count is in `l1.byChain` of the same URL. They grow daily, so read the live page for current numbers.

| Chain | Network id | Asset paid | Paid attempts | Settled | Settled and answered 2xx |
|---|---|---|---:|---:|---:|
| Base | eip155:8453 | USDC | 15,208 | 9,616 | 8,912 |
| Solana | solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp | USDC | 983 | 724 | 683 |
| Tempo | eip155:4217 | USDC.e | 312 | 282 | 79 |
| XRPL | xrpl:0 | RLUSD | 195 | 81 | 61 |
| Celo | eip155:42220 | USDC | 168 | 140 | 135 |
| Arc | eip155:5042 | USDC | 138 | 68 | 68 |
| Stellar | stellar:pubnet | USDC | 77 | 70 | 70 |
| BNB Chain | eip155:56 | USD1 | 59 | 41 | 38 |
| Arbitrum One | eip155:42161 | USDC | 30 | 14 | 14 |

**BNB Chain.** vet402 pays sellers that accept USD1 (`0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d`) by EIP-3009 on eip155:56, and every settled BNB Chain row is bound to the nonce vet402 signed. To list them:

```bash
curl -sL "https://vet402.com/api/v1/observatory/export.csv?days=90" | grep "eip155:56"
```

## Arbitrum Open House Singapore (vet402 /rwa)

Paste a Robinhood Chain wallet and see its Stock Token track record rebuilt from public chain data, including look-alike tokens it was sent and the corporate actions (share multiplier changes) it held through. Live at https://vet402.com/rwa . Start with [docs/rwa/README.md](docs/rwa/README.md) (30-second path, verify-it-yourself, what is not trustless).

## ETHGlobal Tokyo 2026 (ENS and Intercepta partner prizes)

For Tokyo, an agent pays an x402 seller only while vet402's draft ENSIP-29 attestation in the seller's ENSv2 name still matches the seller's offer. Check it with one line (Node 22.18 or later, no key):

```bash
git clone --depth 1 --branch tokyo-2026-submission https://github.com/kzmttkc/vet402 && cd vet402/examples/tokyo-2026-demo && npm ci && npm run verify -- seller-a.eth
```

- Page from the event, where a judge could change one character of a seller's offer on-chain and watch the agent refuse: <https://vet402.com/tokyo?name=seller-a.eth>
- Status on 2026-11-02: since 2026-10-01 the page and the verify command end at step 1 with `ens_evidence_unavailable` for any valid name, because ENS redeployed on Sepolia and the two contracts the SDK reads (UniversalResolver and UniversalHelper) now report different root registries. The demo's attestations, valid for seven days, also expired on 2026-10-03. No seven-step result is produced. The recorded demo and the transactions in [for-reviewers](./docs/tokyo-2026/for-reviewers.md) are from before that date.
- Demo, commands and the Intercepta screening notes: [`examples/tokyo-2026-demo/README.md`](./examples/tokyo-2026-demo/README.md)
- Every requirement, transaction and one-line check: [`docs/tokyo-2026/for-reviewers.md`](./docs/tokyo-2026/for-reviewers.md)
- What existed before the event: vet402 and `@vet402/sdk`, up to the tag `pre-tokyo-2026`. Everything in `git log pre-tokyo-2026..tokyo-2026-submission` was made during it. Who did what, human and AI: [`AI_USAGE.md`](./AI_USAGE.md#ethglobal-tokyo-2026) and [`docs/tokyo-2026/human-log.md`](./docs/tokyo-2026/human-log.md)

## Judges: 60 seconds (ETHOnline 2026, September)

```bash
# Node 24.x (package.json engines; Node 22 cancels the timeout tests)
git clone https://github.com/kzmttkc/vet402 && cd vet402
npm run judge-check   # sdk → mcp-server → demo → root → ab: builds, tests, mutation runs. No key, nothing live. About 25–30 s on a clean clone (24–27 s across three measurements on 2026-09-07, warm npm cache)
GRAPH_API_KEY=… node examples/ethonline-2026-demo/src/run.ts judge https://kronossignals.com/api/v1/price/btc
```

- Line 2 prints one table, one exit code per step, and `unset`s every key before it starts — a green run is proof that none is needed.
- Line 3 reads the seller's real 402 challenge, vet402's `/decision` and The Graph's x402 Base subgraph (`_meta.block.number`, live) side by side, then prints `verdict: ALLOW|REFUSE`, `reason_codes[]`, `signed: false`. Dry run — the signing module is never loaded. About 4 s.
- Keys: one free Graph key from <https://thegraph.com/studio> → *API Keys*. `/decision` answers key-less (10/min per IP); nothing else is required.
- Everything else a judge can run, in dependency order, with the recorded output: [`SKILL.md`](./SKILL.md).
- **Continuity Track boundary** — what predates the hackathon window, what does not, and the git commands that separate them: [`docs/ethonline-2026/README_CONTINUITY_SECTION.md`](./docs/ethonline-2026/README_CONTINUITY_SECTION.md).
- **Language.** Source comments are written in Japanese, my working language; commit subjects are Japanese, and **the rule from 20:00 JST on 2026-09-08 is to write new ones in English** (`docs/ethonline-2026/GIT_RULES.md`) — a rule going forward, not a property of the log behind it. It took effect mid-evening, not at the start of that day, and work already in flight kept landing in Japanese for a few commits after. Nothing is rewritten to tidy this up, so count it rather than take my word: `TZ=Asia/Tokyo git log origin/main --since='2026-09-08 20:00:00 +0900' --pretty=%s`. The English route through the work is [`SKILL.md`](./SKILL.md), [`AI_USAGE.md`](./AI_USAGE.md) and [`docs/ethonline-2026/CHANGED_FILES.md`](./docs/ethonline-2026/CHANGED_FILES.md). **Seven** source files carry an English header above the Japanese one — the SDK's decision and payment path, the MCP bridge, and the two example front-ends: `packages/sdk/src/pay-or-refuse.ts`, `packages/sdk/src/subgraph-evidence.ts`, `packages/sdk/src/x402-pay.ts`, `packages/mcp-server/src/pay-if-trusted.ts`, `examples/ethonline-2026-demo/src/run.ts`, `examples/ethonline-2026-demo/src/render.ts`, `examples/ethonline-2026-ab/src/mcp.mjs`. Count them yourself: `grep -rln "English header for judges" packages/ examples/ | grep -v dist`.

vet402 buys from x402 endpoints, re-reads each payment on-chain, records what came back, checks it against the seller's output declaration when there is one, and publishes the results.

> **Formerly Vouch.** The repository is <https://github.com/kzmttkc/vet402> (renamed 2026-08-18). The npm scope is `@vet402/*`; the API key prefix (`vouch_`) and some internal identifiers retain the old name for backward compatibility — renaming them would break every key already issued.

**Site:** <https://vet402.com> · **Live demo:** <https://vet402.com/playground> · **API reference:** <https://vet402.com/docs/api> · **Accuracy ledger:** <https://vet402.com/accuracy>

> 日本語の概観は [docs/ja/README.md](./docs/ja/README.md)。

This repository is the source of the vet402 service and of the three npm packages published from `packages/`.

<a id="ethonline-2026-continuity--what-is-ours-from-this-window"></a>

## ETHOnline 2026 (Continuity) — what I claim from *this* window

The boundary tag `pre-ethonline-2026` is commit `c42daca`, cut **2026-09-04 00:05:36 UTC**, and it is
pushed, so anyone can verify the split themselves. The window opened at **2026-09-04 16:00 UTC**
(ETHGlobal's published schedule, `hacking-begins`), so the tag sits 15 h 54 min *before* the start, not
five minutes after it as this file said until 2026-09-08. **Three commits in the range I claim were made
before the window opened** — their SHAs, times and contents, and the command that lists them, are in
[`docs/ethonline-2026/DISCLOSURE_2026-09-05.md`](./docs/ethonline-2026/DISCLOSURE_2026-09-05.md), which
also carries the message I sent ETHGlobal on **2026-09-05** disclosing the volume of work that predates
the tag, and why its commit count reads 207 where this file says 214.

**One caveat.** `main` is also this product's production branch, so the range
`pre-ethonline-2026..main` contains work that has nothing to do with this submission — grant
applications for other programmes, SEO and marketing, a secret rotation, operational fixes. **I am not
claiming all of it.** What I submit is the payment gate and what it needed:

```bash
git log pre-ethonline-2026..main -- packages/sdk packages/mcp-server \
    examples/ethonline-2026-demo examples/ethonline-2026-ab \
    SKILL.md AI_USAGE.md docs/ethonline-2026    # the submission
git log pre-ethonline-2026..main                # everything on main in the same days
```

Both commands are worth running. The first is my claim; the second is the full picture, including the
parts I am not claiming.

Who wrote the code, by area and representative file — and what I did: [`AI_USAGE.md`](./AI_USAGE.md). An excerpt of the instructions I gave, by day (Japanese; the folder's README opens in English):
[`docs/ethonline-2026/PROMPTS/`](./docs/ethonline-2026/PROMPTS/). The payment gate itself:
[`SKILL.md`](./SKILL.md).

**Start here** — four doors, all reachable from this page:

- <https://vet402.com/ethonline> — the judge's landing page: one command to run, what to read in which order, the continuity disclosure, The Graph.
- [`docs/ethonline-2026/COMMITS_EN.md`](./docs/ethonline-2026/COMMITS_EN.md) — every commit in the window in English, derived from `git log pre-ethonline-2026..main` by [`scripts/ethonline-commits-en.mjs`](./scripts/ethonline-commits-en.mjs) (✔ marks what I claim; `--check` fails if the file is not what the script produces).
- [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/kzmttkc/vet402?quickstart=1) — one click: Node 24 with SKILL.md's build order already run ([`.devcontainer/devcontainer.json`](./.devcontainer/devcontainer.json)), so `npm run judge-check` works as written.
- [`skills/pay-or-refuse/SKILL.md`](./skills/pay-or-refuse/SKILL.md) (Agent Skill) and [`.claude-plugin/plugin.json`](./.claude-plugin/plugin.json) (Claude Code plugin) — the same gate, packaged the way The Graph's own skills repositories are.

**Try it** (clean clone; the first line needs no API key):

- `npm run judge-check` — one command from the repo root: sdk → mcp-server → demo → root `npm ci` → A/B harness, every test, exit codes recorded per step (`scripts/judge-check.sh`).
- [`examples/ethonline-2026-demo`](./examples/ethonline-2026-demo/) — `refuse` (two sources side by side, no signature), `pay` (dry run: fetches the real 402, signs nothing), `judge <url>` (bring your own x402 URL).
- [`examples/ethonline-2026-ab`](./examples/ethonline-2026-ab/) — the Bazantic A/B harness: Recipe vs no Recipe, mock run without keys, live run with a model.

## Install

```bash
npm i @vet402/sdk          # TypeScript API client
npm i @vet402/middleware   # x402 transaction gate (Express / Next.js / Hono)
npm i @vet402/mcp-server   # MCP tools for Cursor / Claude Desktop
```

> **Use the scoped names exactly as written above.** The unscoped npm package `vouch-sdk` is an unrelated project published by a different vendor and has nothing to do with vet402. Only `@vet402/*` packages are vet402's.

> **On npm since 2026-09-12 (06:41 UTC, the day before the 2026-09-13 16:00 UTC deadline).** `@vet402/sdk` is 0.6.0 (published 06:41:38 UTC) and exports `payOrRefuse`; `@vet402/mcp-server` is 0.3.0 (06:45:12 UTC) and answers `tools/list` with 7 tools, `pay_if_trusted` among them. The releases that stood through the window contained neither: the `@vet402/sdk@0.5.0` tarball (2026-08-25) ships `index` and `spend-guard` only, and `@vet402/mcp-server@0.2.0` (2026-08-24) answers `tools/list` with 5 tools. Publishing is not part of the claimed work; what is judged is the clone, not the tarball. To read or run this window's code from source, clone this repository and build from `packages/` ([`SKILL.md`](./SKILL.md) → *Install*).

- [@vet402/sdk](https://www.npmjs.com/package/@vet402/sdk)
- [@vet402/middleware](https://www.npmjs.com/package/@vet402/middleware)
- [@vet402/mcp-server](https://www.npmjs.com/package/@vet402/mcp-server)

```typescript
import { createVouchClient } from "@vet402/sdk";
```

Get a free API key at <https://vet402.com/signup> (1,000 lookups/month, no card required).

## Verification levels

A result never moves up a level: an L0 probe cannot report settlement, and an L3 opinion is never folded into an L0–L2 fact.

| Level | Question | How | Output |
|---|---|---|---|
| L0 | Liveness — does the endpoint answer correctly? | Probe, no purchase | pass / fail / unverified |
| L1 | Settle-through — does the payment settle, and does a non-empty 2xx come back? | Real purchase | n of m settled, latency |
| L2 | Conformance — does the response match the seller's own declaration? | Purchase + machine diff | conform / mismatch / undeclared / not_checked |
| L3 | Quality — is the content any good? | Not built | none |

The 0–100 trust score this API returns today (banded `ALLOW` / `WARN` / `BLOCK`) predates these levels. It stays available to API and SDK callers during the transition, and is never reported as an L0–L2 result. Methodology: <https://vet402.com/#methodology>.

Since 2026-09-02 (product spec v1.0), the canonical integration is `GET /api/v1/resources/{resource_id}/decision`: the L0–L2 facts and the `ALLOW` / `WARN` / `BLOCK` recommendation arrive in the same document, with `reason_codes`, `freshness`, `evidence` and the `rules_version` that produced it. `GET /api/v1/resolve?q=<url|domain|address|tx>` turns whatever you hold into the §5 object ids. The score endpoints remain as a thin compatibility layer.

## Docs

Start here, in this order:

- [PRODUCT.md](./PRODUCT.md) — two products, verb glossary, plans
- [DESIGN.md](./DESIGN.md) — public RFC paper vs dashboard operate app
- [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) — system map: data flow, money safety, module boundaries ([日本語](./docs/ja/ARCHITECTURE.md))
- [CONTRIBUTING.md](./CONTRIBUTING.md) — full-stack setup (Docker or local), PR checklist, first issues
- [docs/README.md](./docs/README.md) — which remaining file to open (do not load the rest by default)

Grant / hackathon reviewers: [docs/applications/](./docs/applications/) has the one-page impact summary and per-ecosystem materials.

Customer-facing: [Quickstart](./docs/quickstart.md) · [OpenAPI](./docs/openapi.yaml) · [Deployment](./docs/deployment.md)


# Self-hosting and development

The rest of this file covers running the service yourself. Using vet402 as a customer needs none of it — see [Install](#install) above.

## Stack

- Next.js 16 (App Router) + TypeScript
- viem (Base mainnet)
- Drizzle ORM + PostgreSQL (Neon)
- ERC-8004 Identity & Reputation Registry

## Quick start

```bash
cp .env.example .env.local
# Set DATABASE_URL, secrets, DEV_API_KEY, BASE_RPC_URL

npm install
./scripts/dev-setup.sh   # local Postgres + db:push (Docker or Homebrew)
npm run dev
```

### Health check

```bash
curl http://localhost:3000/api/health
```

### Score an agent (dev)

```bash
curl -H "Authorization: Bearer $DEV_API_KEY" \
  http://localhost:3000/api/v1/agents/1/score
```

### Score by wallet (x402 integration path)

```bash
curl -H "Authorization: Bearer $DEV_API_KEY" \
  http://localhost:3000/api/v1/wallets/0x1234567890123456789012345678901234567890/score
```

### Attest settlement

```bash
curl -X POST -H "Authorization: Bearer $DEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"wallet":"0x...","txHash":"0x..."}' \
  http://localhost:3000/api/v1/payments/x402
```

### Resolve, then decide (product spec §7.3 / §9.1)

```bash
# what is this URL? → resource_id, endpoint_id, payee_id (key-less)
curl -L "http://localhost:3000/api/v1/resolve?q=https://api.example.com/v1/quote"

# should my agent pay it? facts + recommendation in one document
curl -L -H "Authorization: Bearer $DEV_API_KEY" \
  "http://localhost:3000/api/v1/resources/<resource_id>/decision?role=payer"

# should this seller serve this payer? (seller mode, post-settlement)
curl -L -H "Authorization: Bearer $DEV_API_KEY" \
  "http://localhost:3000/api/v1/resources/<resource_id>/decision?role=payee&payer=0x..."

# The rest of the §7.3 / §9.1 family — all key-less:
#   GET /api/v1/resources/<resource_id>                    the Resource, payees, links
#   GET /api/v1/endpoints/<endpoint_id>                    the Endpoint (sha256 id or observatory uuid)
#   GET /api/v1/endpoints/<endpoint_id>/payees             endpoint → payees[]
#   GET /api/v1/payees/<address>/endpoints                 payee → endpoints[]
#   GET /api/v1/observatory/endpoints/<id>/facts           L0–L2 seller facts, no score
#   GET /api/v1/census/summary?chain=&window=7d|30d        settlements raw and real, never merged
#   GET /api/v1/observatory/corrections                    the correction log as JSON
```

## Database setup (M2)

```bash
# Set DATABASE_URL in .env.local, then push schema
npm run db:push

# Create a production API key
npm run api-key:create -- --plan free --name "my app"
```

**Production:** set `APP_ENV=production` explicitly (do not rely on `NODE_ENV` alone).

**Production required env vars:** `DATABASE_URL`, `API_KEY_PEPPER`, `DASHBOARD_SESSION_SECRET`, `ADMIN_SECRET` (min 32 chars, no placeholders).  
Set `PROXY_HEADER_SOURCE` to name the proxy in front of the app (`vercel` | `generic` | `none`); per-IP rate limits depend on it, and the default `none` believes no forwarded header.  
**Never set in production:** `DEV_API_KEY`, `SKIP_CHAIN_READS`.

Deep health check: `GET /api/health?deep=1` with `Authorization: Bearer $ADMIN_SECRET` (development allows unauthenticated deep checks).

Dashboard uses **httpOnly session cookies only** (Bearer API keys are not accepted on dashboard routes).

Whitelist override: responses include `manualOverride: true` when **customer** WL/BL changed the outcome. Global operator blacklist is applied opaquely (`blockReason: operator_policy`, `signals.manual.list: none`). WL is **not applied** when sybil risk is `high`.

Customer WL/BL lists are shared across all API keys under the same owner account.

Score cache invalidation uses DB-backed epochs (`cache_epochs` table) so list changes propagate across serverless replicas.

After enabling `API_KEY_PEPPER`, recreate API keys (hashes change).

API keys are stored as SHA-256 hashes. Monthly quotas are enforced **per owner** (all API keys under the same account share one quota). Up to **10 active API keys** per owner.

| Plan | Monthly limit |
|---|---|
| free | 1,000 |
| pro | 50,000 |
| scale | 500,000 |

Responses include `X-RateLimit-Limit`, `X-RateLimit-Used`, and `X-RateLimit-Remaining` headers.

## Dashboard (M3)

```bash
npm run dev
# Open http://localhost:3000/dashboard
# Sign in with a database-backed API key (not DEV_API_KEY)
```

Dashboard features:
- **Overview** — monthly usage and plan quota
- **Lookup** — agent / wallet score search (counts against API quota)
- **WL/BL** — customer whitelist/blacklist + CSV import (max 500 rows)
- **Logs** — recent query history
- **API Keys** — create keys (inherits current plan only) and revoke

Sign-in uses httpOnly session cookies (API key is not stored in the browser).

**Signup:** `/signup` creates a free account + API key (invite code required when `BETA_INVITE_CODE` is set).

**Billing:** Dashboard → Billing (Stripe Checkout for Pro/Scale).

## Funder indexer (F-03)

Background job populates `funder_wallets` for sybil cluster detection (read-only from scoring path):

```bash
npm run indexer:funders
# or Vercel cron: GET /api/cron/index-funders (daily 04:00 UTC)
```

## Owner agent indexer (sybil F-03)

Indexes ERC-8004 `Registered` / `Transfer` events into `owner_agents` for `multi_agent_owner` sybil checks:

```bash
npm run indexer:owners
# or Vercel cron: GET /api/cron/index-owners (daily 05:00 UTC)
```

**Partial sync is supported** — scores ship while the indexer catches up. Responses include `dataCoverage.ownerIndexer` (`synced` only at tip; otherwise `partial` + `staleRisk`). Sybil `multi_agent_owner` uses ERC-721 `balanceOf` (authoritative) cross-checked with `max(index, balanceOf)`. Full catch-up can take weeks; do not gate product launch on it.

Monitor critical outages: `GET /api/cron/monitor-health` (503 = env/DB/RPC only). Indexer lag is reported in the payload without forcing 503.

## Log retention

`trust_events` are purged by plan: **90 days** (free) / **1 year** (pro, scale). Expired dashboard sessions and stale IP rate-limit buckets are also cleaned.

```bash
npm run cron:purge-logs
# or Vercel cron: GET /api/cron/purge-logs (daily 03:00 UTC)
```

## MCP Server (M4 / M7)

```bash
cd packages/mcp-server
npm install && npm run build
```

Tools: `check_agent_trust`, `check_wallet_trust`, `check_payee_trust`, `explain_trust_score`, `attest_x402_payment`, `check_resource_decision`, `pay_if_trusted`, `diagnose_x402_url`

See [MCP setup](./docs/mcp-setup.md) for Cursor / Claude Desktop configuration.

## TypeScript SDK (M7)

```bash
cd packages/sdk && npm install && npm run build
```

```typescript
import { createVouchClient } from "@vet402/sdk";
```

## x402 sample middleware (M4)

```bash
cd examples/x402-trust-gate
npm install
export VOUCH_API_KEY=your_key
npm run demo
```

Express middleware that blocks `BLOCK` recommendations before serving paid routes. See [x402 integration guide](./docs/x402-integration.md).

## Project structure

```
src/
  app/api/v1/          # REST API routes
  app/docs/api/        # Hosted API reference page
  lib/
    chain/             # viem client, ERC-8004 reads, wallet metrics, agent resolver
    scoring/           # Score engine, sybil detection
    db/                # Drizzle schema
    api/               # Auth, rate limits (M2)
docs/
  README.md            # which remaining file to open
  quickstart.md
  deployment.md
  openapi.yaml
packages/
  mcp-server/          # @vet402/mcp-server — MCP tools for Cursor / Claude
  sdk/                 # @vet402/sdk — thin TypeScript API client
  middleware/          # @vet402/middleware — x402 gate (Express / Next.js / Hono)
examples/
  x402-trust-gate/     # Express middleware sample
```

## Milestones

| Phase | Status | Scope |
|---|---|---|
| M0 | ✅ Done | Base RPC, ERC-8004 reads, API skeleton |
| M1 | ✅ Done | Score engine v1, sybil standard, cache |
| M2 | ✅ Done | API keys, rate limits, DB persistence |
| M3 | ✅ Done | Dashboard, WL/BL |
| M4 | ✅ Done | MCP server, x402 sample, docs |
| M5 | ✅ Done | Closed β deploy, funder indexer, signup + Stripe, log retention |
| M6 | ✅ Done | x402 payment attestations + 10% score weight |
| M7 | ✅ Done | Parallel channels: SDK, MCP attest, settlements UI, API docs |

## License

[MIT](./LICENSE) © KIZUNA Creation. The published `@vet402/*` packages carry the same license.

The **measurements** are licensed separately: [CC BY 4.0](./LICENSE-DATA) — the observatory
snapshot (`/api/v1/observatory/state`), the daily series (`/api/v1/observatory/history`), the
L1 purchase ledger (`/api/v1/observatory/export.csv`), the L0 export
(`/api/v1/observatory/l0/export.csv`) and the sellers export (`/api/v1/sellers/export.csv`) may be redistributed and built on,
commercially or not, provided the source is named:

> KIZUNA Creation. vet402 observatory. Dataset, retrieved YYYY-MM-DD.
> https://vet402.com/api/v1/observatory/state

The retrieval date is part of the citation — every one of these numbers moves. The JSON carries
`license` / `retrievedAt` / `cite` in the body; the CSV carries them in `x-vet402-*` and `Link`
response headers.

TDQS

A3.6/5.0

Scored across 7 tools

Disambiguation2/5

Three tools — check_agent_trust, check_wallet_trust, and check_payee_trust — all perform essentially the same address/agent trust lookup, and their descriptions repeat the identical "decide on decision and safe_to_pay" boilerplate rather than drawing a clear boundary. check_resource_decision also overlaps with all three while adding policy, leaving an agent unsure which entry point to use. Only explain_trust_score, attest_x402_payment, and pay_if_trusted are clearly distinct.

Naming Consistency4/5

All names are snake_case with a leading verb (check_*, explain_*, attest_*, pay_if_*), which reads predictably. Minor deviations like pay_if_trusted (conditional phrasing) and attest_x402_payment (embedded protocol token) are still legible and do not break the pattern.

Tool Count5/5

Seven tools is well within a comfortable range for a trust-gating server, and each remaining tool (explain, attest, decide, pay) maps to a distinct stage of the x402 flow. The only count concern is redundancy among the three check_* tools, not raw volume.

Completeness4/5

The surface covers check → explain → attest → decide → pay, which is the full payment-gating lifecycle. Gaps exist around reading back attestations or querying score history, but an agent can work around them via the check tools.

Maintenance

ActivityActive
ResponsivenessSyncing