FIANGSHIcensus 2026-11-09 02:57 UTC · 90-day window · providers live · settlement dry-run

Docs/How it works/Architecture

On this page
  1. The map
  2. See it working
  3. What each step does
  4. Outside services
  5. Storage
  6. Security and trust model
  7. Budgets and limits
  8. Configuration
  9. Failure modes
  10. What it does not do

Fiangshi architecture

This page explains how Fiangshi is built, so an integrator or auditor can trust it. It covers every component, every outside service it talks to, where data is kept, and what happens when each part fails. Every threshold below is copied from the source file named next to it. If this page and the code disagree, the code is right, and that is a bug in this page.

The map

Figure 1 · live modelThe map
MCP clientsClaude, Codex, Grok…HTTP clientscurl, xAI, DeepSeekBrowserthis site, no scriptMCP serversrc/mcp/server.tsFIANGSHI · src/api/server.ts1Probesrc/adapters/probe.tsone unpaid GET, read the 402 terms2Null-payee gatesrc/core/v6.tsdead-end payTo? BLOCK now, $0.003Signalssrc/census.ts · src/cold.tsage, funder, payers, evidence quotes4Scoresrc/core/score.ts25M + 20P + 20R + 15I + 10E + 10C5Model advicesrc/adapters/serv.ts · dolphin.tsstrict JSON, every quote checked6Six rulessrc/core/vows.tscan only lower the band7Decidesrc/core/decide.tslowest of score, model and rules8Receiptsrc/core/receipt.tssha256, chained to the one beforeSellerx402 endpointSurfBase on-chain dataSERVadvisesDolphinreads namesLedgerdata/ledger.jsonlCensus800 payees, freeBLOCK · $0.00no data, no modelverdict + quoted evidence + receipt idback to the agent; its own wallet pays, or notAn AI agent asks before it pays: by MCP, by HTTP, or on this site.1 · One unpaid request to the seller: the 402 gives the price and the wallet.2 · A dead-end wallet is blocked here, for free (red branch).3 · Facts about the wallet: from the census, or live from Surf.4 · A score from 0 to 100, from six weighted parts.5 · SERV advises with quotes; Dolphin reads names. Neither decides.6–7 · Six fixed rules, then the lowest band wins.8 · A receipt is sealed and chained into the ledger.The verdict goes back. Fiangshi never touches the money.Clients → 8 steps inside Fiangshi → verdict. Services right, census left.

Follow the dot. A question comes in from an AI tool or the site, passes through eight steps inside Fiangshi, and the verdict goes back. Each step names the file that does it. The boxes on the right are the outside services a step uses; the red branch is the free BLOCK for a dead-end wallet.

In words: an AI tool (through MCP or HTTP) or a person (on the site) asks about a paid URL. Inside the Fiangshi server the request goes through eight steps: probe, null-payee gate, signals, score, model advice, six rules, decide and receipt. The seller, Surf, SERV, Dolphin and the ledger are each used by one step. The verdict, its evidence and a receipt id go back, and the agent's own wallet pays, or does not.

See it working

The pictures on this page are working models: they move through the same steps the code takes. On the site at /architecture they are animated; in the repository the text describes them.

Figure 2 · live modelThree checks, live
1 PROBE2 GATE3 SIGNALS4 SCORE5 MODEL6 VOWS7 DECIDE8 RECEIPTapi.cleave.fi/x402/gas · payTo 0x0000…0000BLOCK · $0.00rule I (null payee) at probe time: no data call, no model call, receipt still sealedlab control C3 · mature wallet, many repeat payersALLOW · 86score 86.43, SERV agrees and its quotes match the evidence, no rule fireslab case B6 · a required signal is missingREVIEWrule VI (silence): a required signal is missing, so it fails closed

Three requests go through the same eight steps. A dead-end payment address stops at step 2, for free. A strong seller goes all the way to ALLOW. A seller with missing data stops at the rules and gets REVIEW, because Fiangshi never guesses.

Figure 3 · live modelWho talks to whom, and where the money goes
money: the agent's own wallet → the seller. It never passes through Fiangshi.AI agentabout to payFiangshiadvises onlySellerx402 endpointSurfBase on-chain dataSERVreasoning modelDolphinnames onlycheck this URLunpaid GET402 + termsSQLwallet factsadvice, quotes checkedALLOW + receipt$VERDICT SEALED1 · The agent asks Fiangshi about a paid URL before paying.2 · Fiangshi makes one unpaid request and reads the 402 payment terms.3 · It looks up the wallet that would be paid: age, funder, payers.4 · SERV advises; every quote must match the stored evidence. Dolphin reads names.5 · The rules decide. The verdict and a hash-chained receipt go back.6 · Only on ALLOW does the agent pay, from its own wallet.Agent → Fiangshi → seller, Surf, SERV, Dolphin → verdict → the agent pays, or not.

Fiangshi sits beside the payment, not in it. It reads the seller's terms, the wallet's on-chain history and a model's advice, then answers. The money goes from the agent's own wallet to the seller, never through Fiangshi.

What each step does

1. Probe

src/adapters/probe.ts sends one GET with no payment header and reads the HTTP 402 answer: the headers and the JSON body. It accepts x402 v1 and v2 shapes. It checks each payment option on its own, so one malformed option cannot hide a valid Base option. The terms are hashed as canonical JSON (sorted keys, src/core/canonical.ts), giving terms_hash. A probe that times out, returns something other than 402, or has no parseable Base option gives no verdict (probe failed), never ALLOW.

2. Null-payee gate (rule I, null_payee)

src/core/v6.ts runs before anything is spent. It blocks when the payTo address is:

A blocked endpoint costs $0.00. No data call or model call is made, and a receipt is still sealed, so the BLOCK can be verified later.

3. Signals: where the facts come from

sourcewhencostfile
censusthe payee is one of the 800 Base payees measured on 9 Nov (90-day window)0 credits at request timesrc/census.ts, fixtures/audit/
cold checka Base payee not in the census, on the paid route2 Surf SQL calls (8 credits), then cached 72 hsrc/cold.ts, src/cold-surf.ts
funder labelsexchange, relayer, mixer, or unknownmeasured once, committedfixtures/funders.json, census checkpoints

The signals are: wallet age, transaction count, first funder, how many other catalog sellers share that funder (funder_fanout), distinct payers, repeat payers, total received, the top payer's share, dormancy and identity claims. Each one carries a quoted line of evidence. A value that is negative, not a number, in the future, or self-funding is turned into "missing" (sanitize in src/core/score.ts), and a missing value triggers rule VI (silence), which gives REVIEW. A data error never gives BLOCK.

4. Score

src/core/score.ts. Each component is in [0, 1]. A missing optional component (identity) has its weight removed and the rest re-scaled, so having no claim is never a penalty.

partweighthow it is computed
M maturity25half log-scaled age (capped at the window), half log-scaled transaction count (cap 1,000)
P provenance20exchange funder 1.0 · fresh funder or relayer 0.6 · funder shared with other sellers 0.2 · mixer 0
R revenue200.6 × log-scaled distinct payers (cap 100) + 0.4 × log-scaled repeat payers (cap 50)
I identity15no claim or unconfirmed claim: weight removed · one independent source 0.5 · two or more 0.75
E exposure10breadth min(1, log10(payers+1)/2) × concentration 1 − max(0, top_share − 0.30)/0.70
C corroboration10share of evidence fields that have a quote

Bands from the score: 80 or more is ALLOW, 50 to 79 is REVIEW, under 50 is BLOCK.

5. Model advice

Two models run in parallel and neither can decide alone.

Model replies are cached by a hash of the exact evidence they saw, so the same evidence never pays twice, and changed evidence asks again.

6. The six rules (vows)

src/core/vows.ts. They run after the model and can only lower the band. When several fire, the most severe one is reported; ties go to the order below.

#rulefires whenband
Inull_payeepayTo is zero, a burn address, the token contract, or fails EIP-55 (also run at probe time)BLOCK
IIagewallet age under 48 h (AGE_MIN_H)BLOCK
IIIkinshipfirst funder shared with 3 to 20 other catalog payees (KINSHIP_MIN, KINSHIP_REVIEW_AT), not an exchange or relayerBLOCK
IIIkinshipthe same, with 21 or more and the funder unlabelled: flag kinship_suspected_exchangeREVIEW
IVhistoryno distinct payers and price above $0.50 (HISTORY_PRICE_USD)REVIEW
Videntitya claimed name is contradicted by a label naming the wallet's operatorBLOCK
VIsilenceany required signal is missingREVIEW

7. Decide

src/core/decide.ts: final band = the lowest of the score band, the model band and the deciding rule's band. ALLOW needs all three to agree. The model can only lower the band, never raise it.

Figure 4 · live modelThe lowest band wins
SCORE BANDMODEL BANDRULE BANDFINAL = LOWEST→Case 1 · everything agrees86 → ALLOWSERV: ALLOWno rule firedALLOWALLOW needs all three.Case 2 · a rule beats the model84 → ALLOWSERV: ALLOWage: 31 h oldBLOCKThe wallet is under 48 hours old. The model cannot overrule it.Case 3 · the model alone cannot block81 → ALLOWSERV: BLOCK → REVIEWno rule firedREVIEWA model BLOCK is capped at REVIEW: it is not a machine-checkable reason.A rule beats the model84 → ALLOWSERV: ALLOWage: 31 h oldBLOCK

The score, the model and the rules each give a band, and the final verdict is the lowest of the three. A rule can overrule the model. A model on its own can lower a verdict to REVIEW, never to BLOCK, and never raise one. The numbers shown are examples.

8. Receipt and ledger

src/core/receipt.ts writes a receipt with: payee, url, terms_hash, price, band, score, vow_fired, evidence quotes, model verdict and degraded flag, snapshot_id, checked_at, chain: "eip155:8453", prev_hash, receipt_hash and settlement. receipt_hash is the sha256 of the canonical JSON of every other field, and prev_hash links it to the receipt before it in data/ledger.jsonl. Changing any field breaks GET /v1/verify.

Figure 5 · live modelThe receipt chain catches an edit
RECEIPT 1prev 0000…00band ALLOWhash 9f3a…c1RECEIPT 2prev 9f3a…c1band BLOCKhash 4be0…77RECEIPT 3prev 4be0…77band hash d21c…08RECEIPT 4prev d21c…08band ALLOWhash 7a95…e3REVIEWALLOW ✎✓ valid✓ valid✓ valid✓ valid✓ valid✗ hash mismatch✓ valid✗ link brokenGET /v1/verify recomputes every sha256 and every link: valid.One word changed in receipt 3: its hash no longer matches, and the chain breaks after it.Each receipt carries the hash of the one before. Change one word and verify fails from there on.

Every receipt holds the hash of the receipt before it. Verify recomputes them all. Edit one word in one receipt and its hash stops matching, and every link after it breaks. The hashes shown are shortened examples.

Receipts are tamper-evident through the hash chain. They are not signed with a private key: Fiangshi holds no keys at all. Anyone can recompute a hash.

src/ledger/drift.ts flags a payee whose band crosses 50 or 80, whose payee address rotates, whose funder changes, whose price moves more than 10% (PRICE_DRIFT), or which wakes up after 60 or more dormant days (DORMANCY_DAYS). A verdict older than 72 h (FIANGSHI_POLICY_TTL_HOURS) is stale and must be checked again.

Outside services

serviceused forhowwhen it fails
the seller's endpointthe 402 termsone unpaid GET, 15 s limitno verdict (probe failed)
Surf (surf CLI, agent.base_transfers)on-chain facts for the census and cold checksSQL over Base transfers, always filtered on block_date, batched, budget-guardedREVIEW with surf_error, budget_guard or unavailable on the receipt
SERV Reasoning (SERV_BASE_URL/chat/completions)advice with quoted evidencestrict JSON schema, 15 s limit, one stricter retrydegraded, capped at REVIEW
Dolphin (DOLPHIN_BASE_URL)identity fields from prose12 s, one retry on timeoutenrichment: incomplete, band unchanged
x402 payer (your agent)pays Fiangshi for the paid routesX-PAYMENT header, checked by src/adapters/x402.ts402 again; settlement in this build is dry-run

The HTTP server itself never calls Surf for the census: the census is built from committed files at startup. Only the cold check calls Surf at request time, and only on the paid route.

Storage

pathwhatcommitted
fixtures/recorded 402s, census data, funder labels, model replies, lab casesyes
data/ledger.jsonllive receipts, one JSON per line, hash-chainedno (data/ is ignored)
data/cold-cache.jsoncold-check signals, 72 hno
data/numbers.jsonevery headline number, written by scripts/numbers.tsno (rebuilt)
artifacts/*.mdaudit, matrix, costs, sensitivity, win-rate, historyyes

There is no database server. There are no accounts and no user records.

Security and trust model

Budgets and limits

limitvaluewhere
Surf calls per run60 (FIANGSHI_MAX_SURF_CALLS)budget guard
Surf floor before a cold check32 credits (FIANGSHI_SURF_FLOOR)src/cold.ts
Surf addresses per label call20 at mostscripts/surf/labels.ts
SERV calls / Dolphin calls100 / 200 per processbudget guard
model timeout15 s (FIANGSHI_MODEL_TIMEOUT_MS)src/adapters/serv.ts
verdict lifetime72 hFIANGSHI_POLICY_TTL_HOURS
pricescached check $0.05 · cold check $0.15 · payee record $0.02.env.example

Configuration

variablemeaning
SERV_REASONING_API_KEY, SERV_BASE_URL, SERV_MODELSERV Reasoning access
DOLPHIN_API_KEY, DOLPHIN_BASE_URL, DOLPHIN_MODELDolphin access
SURF_API_KEY, SURF_BINSurf CLI (scripts and cold checks)
FIANGSHI_PAYTOyour Base wallet for payments to the paid routes; paid routes are off without it
FIANGSHI_REPLAY=1fixture mode: recorded replies through the real code, no keys, no network
FIANGSHI_OFFLINE, MOCK_MODELSmock models for tests
FIANGSHI_URLwhere the MCP server finds the HTTP server (default http://127.0.0.1:8787)
HOST, PORTserver bind (default 127.0.0.1:8787)
FIANGSHI_DATA_DIRwhere the ledger and caches go (default data)

The full list, with defaults, is in .env.example.

Failure modes

what goes wrongwhat you getwhy
seller endpoint down or not a 402no verdict, probe failednothing to judge
payTo is a dead endBLOCK, $0.00rule I at probe time
payee not in census, free routeno_recordthe free route never spends credits
payee not on BaseREVIEW, network_not_basethe data source covers Base only
Surf budget below the floorREVIEW, budget_guardnever overspend
Surf error or timeoutREVIEW, surf_errorfail closed
a required signal missingREVIEW, rule VIfail closed
SERV invalid twiceREVIEW, degraded: truethe model cannot be trusted this time
SERV says BLOCK, rules say nothingREVIEWa model alone is not a machine-checkable reason
SERV says ALLOW, a rule says BLOCKBLOCKrules override the model
Dolphin times outband unchanged, enrichment: incompleteidentity is optional
receipt editedvalid: false on verifyhash mismatch

What it does not do