- Changed
- A new quote rule, R14f: an x402 challenge carried inside a JSON-RPC response is read as the quote it is. An MCP server charging for a tool answers an unpaid `tools/call` with HTTP 200 and the PaymentRequired object inside the JSON-RPC result (`result.structuredContent`, a JSON text part of `result.content`, or `error.data`). Version 6 looked for the challenge at the body's top level and nowhere deeper, and graded every such response `Answered, no quote`. Measured before the change on the 52 MCP tool rows the November list carries, read live on 2026-10-16: of 46 reachable, 12 carried the price inside the result and read as no quote; 11 more carried it there and in the `PAYMENT-REQUIRED` header, and already read as quoted from the header. The new rule runs after the header, body and MPP rules, so a response any of those read keeps its rule and source; the row's sub-code says which carrier held the challenge. In the same change the prober's request to an MCP tool row carries `Accept: application/json, text/event-stream`, the MCP transport's own requirement: 16 of the 46 answered 406 to the previous header and were graded `Blocked or challenged` — a statement about the request's header, not about the operator. An HTTP route's request is byte-identical to before.
- Effect on existing records
- No existing observation is rewritten and the archive is not migrated: every observation taken before this version still says `rubric_version: 6`, permanently, and records the rules that graded it. No seed row of any version before 7 was an MCP tool row, so no archived series is expected to move; the 21 MCP-shaped rows the seed held before were read by hand on 2026-10-13 and none of their `Answered, no quote` readings carried a challenge inside a JSON-RPC result. Should a re-read of the archive find one, it is disclosed as a correction record, never edited.
Rubric
Rules are evaluated in the order below; the first match wins. Every observation records the rule id that decided it.
- Rubric version
- 7
- Governing rule
- `Blocked or challenged` requires positive evidence — a vendor-identifying header, a body signature, or a content-type flip. Ambiguity defaults to `Answered, no quote` or `Unreachable` plus a sub-code, never to a blocked claim. Misclassifying a bare 403 as blocked is recoverable; publishing a blocked claim we cannot support is not.
Outcomes
- Quoted
- Answered, no quote
- Blocked or challenged
- Unreachable
- Not probed
- Note
- There is no value anywhere in this rubric, or anywhere in the schema, in which `dead` or `down` could be written. That is deliberate.
Rules
| ID | Outcome | Says |
|---|---|---|
| R0 | Not probed | Our own vantage failed this cycle — every reference endpoint we check ourselves against was unreachable. Nothing in the cycle is a claim about anyone's endpoint. |
| R1 | Unreachable | DNS did not resolve (NXDOMAIN or SERVFAIL). |
| R2 | Unreachable | The TCP connection timed out or was refused. |
| R3 | Unreachable | The connection was reset, or the TLS handshake failed. The cause is undecidable from one vantage and we do not guess at it. |
| R3b | Unreachable | The request failed before any response, in a way we could not name. We record it as unnamed rather than guessing at a timeout — medium confidence, because the outcome is solid and the cause is not. |
| R4 | Blocked or challenged | Cloudflare's own `cf-mitigated: challenge` header. Vendor-stated, definitive. |
| R5 | Blocked or challenged | AWS WAF's `x-amzn-waf-action` header said challenge or captcha. Vendor-stated, definitive. |
| R6 | Blocked or challenged | A DataDome block: its cookie, its script marker, or its captcha host. |
| R7 | Blocked or challenged | A HUMAN/PerimeterX block: its JSON block payload or its cookie. |
| R8 | Blocked or challenged | A Cloudflare 1xxx error code from the block family, read out of the response body — and only when the response also carries an error status and a header Cloudflare's own edge sets. A page that merely discusses the error is prose, not evidence. |
| R9 | Blocked or challenged | Rate limited — HTTP 429, which is self-describing; or Cloudflare error 1015 in the body, under the same edge-corroboration rule as R8. Any `Retry-After` is recorded verbatim, and its absence is recorded too. |
| R10 | Blocked or challenged | HTTP 451: unavailable for legal reasons. |
| R11 | Blocked or challenged | HTTP 406 with no other signal — a WAF shape we grade at medium confidence, not high. Never applied when a valid payment quote parsed. |
| R12 | Blocked or challenged | A challenge page served with any status code, including 200, identified by vendor markers plus a content-type flip away from the JSON we asked for. Never applied when a valid payment quote parsed. |
| R13 | Blocked or challenged | A 403 whose body says in words that we were rate limited. Medium confidence; confirmable from the series. Never applied when a valid payment quote parsed. |
| R14a | Quoted | A well-formed x402 v2 challenge in the `PAYMENT-REQUIRED` header — checked regardless of status code. |
| R14b | Quoted | A well-formed x402 v1 challenge in the response body. |
| R14c | Quoted | Both x402 transports on one response. Recorded once, never double-counted. |
| R14d | Quoted | An MPP challenge in `WWW-Authenticate: Payment` carrying at least an amount or a recipient — read either from the challenge's own parameters or from the base64url `request` parameter that carries them on almost every live MPP endpoint. The payment method the challenge names (tempo, stripe, lightning, solana and five others we have seen) is recorded as the quote's scheme. We test the auth scheme token, never the header's presence (see R18b) — and a bare scheme with no price and no recipient is not a quote. |
| R14e | Quoted | Both rails on one response: an x402 challenge AND an MPP challenge. Which rails an endpoint serves is its own fact. |
| R14f | Quoted | A well-formed x402 challenge carried inside a JSON-RPC response — in `result.structuredContent`, in a JSON text part of `result.content`, or in `error.data` — which is how an MCP server answers an unpaid `tools/call`: HTTP 200, the price in the result. The same object the header and body rules read, from a different carrier; the sub-code names the carrier. Read after the header, the body and the MPP rules, where none of them found a challenge, so no earlier reading changes. A JSON-RPC result with no challenge in it is not a quote. |
| R15 | Answered, no quote | HTTP 402 carrying nothing parseable in any transport — including an MPP scheme token with no amount and no recipient, and an MPP `request` parameter we could not decode. Alive, asking to be paid, not payable as specified. The parse that was attempted is on the record: a challenge we could not read is never recorded as an endpoint that published no price. |
| R16 | Answered, no quote | A CDN-attributed origin failure. Either a 520-527 status, which is Cloudflare's own and self-describing, or a 5xx carrying a Cloudflare origin error code in the body — and the body form requires a header Cloudflare's edge sets, under the same corroboration rule as R8 and R9. Affirmative evidence the edge is alive and the origin is not; never a block claim. |
| R17 | Answered, no quote | A domain-parking page. Evidence the operator left, recorded at medium confidence. |
| R18 | Answered, no quote | A bare 403 with no vendor evidence. Deliberately not a block: it is byte-identical to an origin's own 403, and AWS WAF's default block looks exactly like it. |
| R18b | Answered, no quote | A credential wall — 401/407 whose offered schemes contain no `Payment` challenge. |
| R19 | Answered, no quote | Any other live response. The class is high-confidence; what it means is left to the series. |
| R20 | Not probed | We did not look, for a reason about us: policy-excluded, not yet live, a scheduler gap, our own fault, a live robots.txt rule, a backoff we owed the operator after being rate-limited or blocked, a request we refuse to send because we hold no body for it or its URL is still a template, or the collection cycle running out of its wall-clock budget before it reached this endpoint, or our own archive not taking the write. |
Quote guarded rules
Rules
- R11
- R12
- R13
- Says
- These three rules are medium-confidence and run before the quote rules. None of them can fire when a well-formed payment challenge parsed — an endpoint that is quoting is recorded as quoting, whatever else its response carries.
Probe classes
Classes
- executed
- challenge-only
- Default
- executed
- Note
- A record that does not name a class is an executed probe: the request we sent was the operator's own published example, or no body was needed. The archive has carried `challenge-only` records since 31 Aug 2026, 14:00 UTC, when the class was introduced; every record before that instant is `executed` by absence.
Challenge only
- In force
- In force since 31 Aug 2026, 14:00 UTC.
- Says
- A knock: an unpaid, empty `{}` POST to a route whose operator publishes no example we may send. It executes nothing — an x402 endpoint answers its payment wall before its handler — and it exists so that a route we cannot call is still recorded as alive or not, instead of as nothing.
- Claim scope
- A challenge-only observation grades the payment wall and nothing past it. Where the endpoint answered with a parsed payment challenge, or with HTTP 402, the record is published as a grade of the operator — liveness, and the quote where one parsed — disclosed as challenge-only and never counted as an executed probe. Where it answered anything else, the answer may be about our own empty request rather than about the endpoint, so the observation is archived in full and is NOT published as a grade of that operator.
- Inputs
- Classification reads status + headers + content-type + body together. Status alone is insufficient and is the specific mistake that produces a corrupt health dataset.
Confidence
- definitive
- high
- medium
- Changelog note
- Every rule change bumps the version above and lands in the changelog with what it changed and what it did to records already held. An observation records the version that graded it, so a grade is re-runnable - and a version with no changelog entry would make that promise unkeepable, so every version has an entry.
Changelog
- Changed
- R14d (an MPP challenge in `WWW-Authenticate: Payment`) now reads the charge out of the base64url `request` parameter as well as out of the flat auth-params. Measured over the archive's own retained captures before the change: of 525 MPP payment challenges read across 384 captures and 162 hosts, 519 put the amount, the currency and the recipient inside `request` and only 4 put them in the flat parameters the rule had read since v2. Nine payment providers publish that way - tempo, evm, stripe, solana, lightning, exact, x402, asterpay and usdc - and the provider is now recorded as the quote's scheme. A `request` that cannot be decoded is recorded as an attempted parse that failed and still lands on R15; it is never read as an endpoint that published no price.
- Effect on existing records
- No existing observation was rewritten, and the archive was not migrated. Every observation taken before this version still says `rubric_version: 5`, permanently, and still says `Answered, no quote` - it records the rules that actually graded it. What DID change is our reading of those rows, and it is disclosed rather than edited: a correction record scopes the affected endpoints from their first capture to 2026-09-03 and says the rows understate those endpoints, which were quoting the whole time. Re-deriving the archive under today's rules therefore does NOT reproduce every pre-v6 row byte-for-byte, and the reach is wider than the refusals. Measured over the WHOLE archive on 2026-09-08 — 981 observation files, 114,155 capture rows, every row re-derived from the raw captures it was graded from: 581 files carrying 5,241 rows grade differently under this version, 398 files re-derive byte-for-byte, 2 sit across an older view-schema boundary, and the number of rows that move for a reason this change does not name is ZERO. Which FIELDS move was attributed row by row over the archive's opening week (160 closed ticks, 5,052 rows, 2026-08-21 to 08-27), the slice a complete replay carries a correct running state for: of 889 rows that move there, 740 ALREADY carried a quote — their outcome is unchanged and their rule id, sub-codes and quote source move, because a parsed MPP arm now reaches the classifier, which returns early at R14 with a fixed sub-code list — and the other 149 are the refusals themselves becoming `Quoted`. Every field that may move is named in advance by the re-grade record this change carries, so our regeneration check reports those rows as re-graded rather than as a disagreement, and a row that moves anywhere else is still reported as one.
- Changed
- R16 (CDN-attributed origin failure) now requires a header Cloudflare's edge sets before it will read a 1xxx origin code out of a response BODY - the same corroboration R8 and R9 got in v4. The 520-527 status arm is unchanged: those codes are Cloudflare's own. Before this, a 500 page that merely mentioned "error code 1016" was published as evidence the edge was alive and the origin was not, at high confidence.
- Effect on existing records
- No existing observation changes outcome, and no existing record was rewritten. Measured before the change was made: R16 had never fired on the live archive. Observations taken before this version still say `rubric_version: 4`, permanently - they record the rules that actually graded them, and the boundary is visible in the data as a series break rather than only in this changelog. Corrected 2026-08-21: this entry first said the derived dataset must be re-derived once for the version stamp to agree. It did not. Regeneration was stamping a module constant instead of reading the version the archive already records on every capture row; re-deriving would have flattened the v4/v5 boundary out of the data.
- Changed
- A Cloudflare 1xxx code read out of a BODY now needs an error status AND an edge header (R8, R9's 1015 arm). And an MPP challenge needs at least an amount or a recipient: a bare `WWW-Authenticate: Payment` is no longer a quote, and lands on R15 with the parse attempt on the record.
- Effect on existing records
- None. Made before go-live, against an empty archive - which is why it was free.
- Changed
- Two fixes, found by reading the classifier against the method page. (1) The quote guard reached no further than R12. R11 (a 406 read as a suspected WAF block) and R13 were unguarded, so a response carrying a well-formed 402 challenge could still be graded `Blocked or challenged` at medium confidence - blocked-vs-dead getting the wrong answer on an endpoint that was quoting normally, which is the product's core claim. All three medium rules now require that no payment quote parsed. (2) `transport-unknown` was added as a named sub-code at medium confidence: a transport failure we could not name was previously reported as one we could, at high confidence.
- Effect on existing records
- None: the archive did not exist yet. Bumped 20 Aug 2026, 21:12 UTC; the collector went live at 21 Aug 2026, 08:00 UTC - fifteen hours before the first tick.
- Changed
- The first rubric that ever ran, as amended after its review: the quote rule split into R14a–R14e (header, body, both transports and MPP), the credential-wall rule pointed at the authentication scheme because MPP challenges also use WWW-Authenticate, and a rule added so a valid MPP challenge reads as Quoted rather than as a credential wall.
- Effect on existing records
- None: the archive did not exist yet. Bumped 20 Aug 2026, 18:28 UTC, fourteen hours before the first tick.
- Changed
- There is no version 1. The version number was raised to 2 while the rubric was drafted, before it was committed, so no response was ever graded under version 1.
- Effect on existing records
- None. No code stamped v1 was ever run.