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

IDOutcomeSays
R0Not probedOur 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.
R1UnreachableDNS did not resolve (NXDOMAIN or SERVFAIL).
R2UnreachableThe TCP connection timed out or was refused.
R3UnreachableThe connection was reset, or the TLS handshake failed. The cause is undecidable from one vantage and we do not guess at it.
R3bUnreachableThe 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.
R4Blocked or challengedCloudflare's own `cf-mitigated: challenge` header. Vendor-stated, definitive.
R5Blocked or challengedAWS WAF's `x-amzn-waf-action` header said challenge or captcha. Vendor-stated, definitive.
R6Blocked or challengedA DataDome block: its cookie, its script marker, or its captcha host.
R7Blocked or challengedA HUMAN/PerimeterX block: its JSON block payload or its cookie.
R8Blocked or challengedA 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.
R9Blocked or challengedRate 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.
R10Blocked or challengedHTTP 451: unavailable for legal reasons.
R11Blocked or challengedHTTP 406 with no other signal — a WAF shape we grade at medium confidence, not high. Never applied when a valid payment quote parsed.
R12Blocked or challengedA 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.
R13Blocked or challengedA 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.
R14aQuotedA well-formed x402 v2 challenge in the `PAYMENT-REQUIRED` header — checked regardless of status code.
R14bQuotedA well-formed x402 v1 challenge in the response body.
R14cQuotedBoth x402 transports on one response. Recorded once, never double-counted.
R14dQuotedAn 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.
R14eQuotedBoth rails on one response: an x402 challenge AND an MPP challenge. Which rails an endpoint serves is its own fact.
R14fQuotedA 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.
R15Answered, no quoteHTTP 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.
R16Answered, no quoteA 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.
R17Answered, no quoteA domain-parking page. Evidence the operator left, recorded at medium confidence.
R18Answered, no quoteA 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.
R18bAnswered, no quoteA credential wall — 401/407 whose offered schemes contain no `Payment` challenge.
R19Answered, no quoteAny other live response. The class is high-confidence; what it means is left to the series.
R20Not probedWe 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

Version7Date2026-10-16
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.
Version6Date2026-09-03
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.
Version5Date2026-08-21
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.
Version4Date2026-08-21
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.
Version3Date2026-08-20
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.
Version2Date2026-08-20
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.
Version1Date(never shipped)
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.