{
  "what": "This service's defect catalogue as one flat list, so a test harness can consume it without scraping eight family indexes. One row per behaviour: where it lives, what to send to observe it, what the defect is, which document says so, and whether the bytes are stable enough to pin a digest on. A few rows are deliberately CONTROLS rather than defects — /headers and /echo say \"Not a defect\" in their own defect field, and every family index names its correct-behaviour flavor — because a fixture set with no known-good case cannot tell a broken client from a broken server. What is absent is documented behaviour that is not part of the catalogue at all: see what_is_not_a_row.",
  "what_is_not_a_row": "Two kinds of documented behaviour are deliberately absent, because a defect catalogue that includes them is diluted rather than complete. (1) The template explainers: request a documented URL template literally, braces intact (/sse/{flavor}), and you get a 200 with the placeholder's valid values — correct, useful, and not a defect. (2) Every surface that describes this service rather than misbehaving: the eight family indexes (/badjson, /sse, /range, /etag, /cookies, /auth, /compress, /402), the discovery and bookkeeping documents (/openapi.json, /llms.txt, /sitemap.xml, robots.txt, /favicon.svg, /health, /books, /books.json, /corpus, /corpus.jsonl, /clients, /clients.jsonl, /license), and the domain verification file /.well-known/402index-verify.txt. Until 2026-09-08 this index promised \"every documented behaviour\", which all of those made false; the promise was corrected rather than the file padded with rows demonstrating nothing.",
  "jsonl": "http://badhttp.dev/corpus.jsonl",
  "format": "application/x-ndjson — one JSON object per line, newline-terminated",
  "count": 150,
  "by_layer": {
    "message-framing": 2,
    "timing": 2,
    "diagnostic": 2,
    "status-codes": 1,
    "redirects": 12,
    "body-syntax": 16,
    "event-streams": 14,
    "range-requests": 14,
    "conditional-requests": 10,
    "cookies": 19,
    "authentication": 18,
    "content-coding": 21,
    "payment": 19
  },
  "license": "CC0-1.0",
  "license_url": "http://badhttp.dev/license",
  "rfc9112_message_syntax": {
    "emits_syntax_violations": false,
    "probed": "2026-09-06",
    "method": "151 documented endpoints captured over TLS with ALPN http/1.1, each response head parsed byte by byte and judged against RFC 9112 §2.2 (bare CR/LF), §4 (status-line: HTTP-version, 3DIGIT status-code, reason-phrase charset), §5 (field-line shape), §5.1 (field-name is a token; no whitespace before the colon), §5.2 (obs-fold), §5.5 (field-value charset) and §6 (Content-Length and Transfer-Encoding agreement, duplicate and non-numeric Content-Length).",
    "result": "0 of 151 responses violated RFC 9112 message syntax.",
    "reproduce": "Capture any endpoint's response head as literal bytes and read it against the sections above: curl --http1.1 --raw -sS -D - --output - https://badhttp.dev/status/418 | sed -n '1,/^\\r$/p' | xxd (that prints the whole header section, up to and including the terminating CRLF, which is what the grammar is about). Every row of /corpus.jsonl carries the same capture command in its `curl` field. What you are checking is the head's grammar, not the body: the claim is about message syntax only, and the two §6.3 completeness violations named in where_we_do_misbehave are the exception it does not cover.",
    "why": "This Worker runs behind Cloudflare, which re-serializes every response before it reaches you. Neither workerd nor the edge will emit a malformed start-line, a malformed field-line, an obs-fold, a bare CR or LF in the header section, or a Content-Length that disagrees with a Transfer-Encoding. A header value set to a string with a CR in it does not reach the wire as one. This is a property of the platform, not a design choice, and it is not something this service can opt out of.",
    "what_this_means_for_you": "If you are testing a parser against the RFC 9112 grammar or the request-smuggling surface, badhttp.dev is not a source of that traffic and capturing from it will teach you nothing: you would collect 151 well-formed messages. Worse, a corpus of clean messages can read as evidence that a checker has no false negatives. Serve those cases from a raw socket you control (a listener writing literal bytes), not from anything behind a CDN.",
    "where_we_do_misbehave": "One layer up. RFC 9112 §6.3 completeness: /truncate and /sse/drop declare a Content-Length and then deliver fewer bytes and close (verified 2026-09-06: 1000 declared / 500 delivered, and 565 declared / 53 delivered). Everything else in this catalogue is a semantic defect inside a well-formed message — a wrong status code, a lying Content-Range, a corrupt gzip member, a malformed auth challenge, a cookie attribute no jar should accept."
  },
  "how_to_use": [
    "GET /corpus.jsonl once. Each row is self-contained and carries a ready-to-run curl.",
    "Send the request_headers. On /compress they are load-bearing — see the capture note.",
    "Capture what you receive; this service ships recipes and defect descriptions, never pre-recorded bytes, because a CDN sits between this Worker and you and the honest artifact is the one you captured yourself.",
    "No row asserts what a correct client should do with the response: for almost all of them no specification says. For what six real clients DID do with /compress, see /clients.",
    "deterministic_bytes tells you whether the SAME request returns the same body bytes — repeat it and pin a digest. It is not a promise about tomorrow: see self_check.what_this_does_not_check. varies_by names what moves the response.",
    "Nothing here requires attribution. See /license."
  ],
  "capture_notes": {
    "compress": "THE ONE THAT BITES: what you receive depends on your Accept-Encoding, because the edge removes one coding layer it recognizes unless your normalized set lists it — no-transform notwithstanding. Send Accept-Encoding: gzip to capture the flavor as this server sends it. Send no Accept-Encoding to capture what the edge does to it, which is the second half of the documentation and a real defect class of its own. Both are worth having; they are different fixtures and a corpus should say which one it holds.",
    "auth": "Digest flavors carry a nonce derived from a 5-minute clock bucket, so their heads are not byte-stable across captures.",
    "crosshost": "THE ONE THAT BITES: these rows are STARTING points, and the interesting response is the one after the redirect, so capture with a client that follows (curl -L) and record BOTH hops. Two rows do not start on https://badhttp.dev at all — from-subdomain starts on alt.badhttp.dev, and scheme-upgrade starts over plaintext http — because the direction and the scheme ARE the measurement. Use each row url exactly as published. Every badhttp response carries x-badhttp-version; a response without it came from the edge (a rate-limit 429), not from this server, and is not an observation."
  },
  "stability_legend": {
    "deterministic_bytes": "true means the same request returns the same body bytes. It never means the response HEAD is byte-stable: Date, cf-ray and Server-Timing move on every response and are the edge's, not ours.",
    "varies_by": "What changes the response: request headers you control, the clock, or timing."
  },
  "self_check": {
    "method": "Two checks run against a live badhttp on every deploy. scripts/corpus-verify.sh replays ALL rows with their own url, method and request_headers and fails if any answers 400, 404, 405 or nothing at all — the guard that a row addresses the endpoint it publishes. scripts/corpus-assert.sh then re-derives the machine-readable fields: it fetches TWICE, with an identical request, every row claiming deterministic_bytes:true and fails if the body bytes differ; and it checks statically, across all rows, that deterministic_bytes:false names something in varies_by, that rfc9112_completeness_violation is on exactly the two endpoints named above, and that the published curl string reconstructs from the row's own url, method and headers.",
    "script": "scripts/corpus-verify.sh and scripts/corpus-assert.sh in https://github.com/auzroz/badhttp; the method above is the whole of what they do, and every assertion in it is one you can re-run against this service with curl and a hash.",
    "rows_checked": 150,
    "rows_double_fetched": 64,
    "last_run": "Every deploy. This document is regenerated per request from the same tables the checks run against, so it cannot report a date the code did not ship with; the deploy that published this text is the one that passed, and badhttp_version on every row of /corpus.jsonl says which deploy that was.",
    "result": "Introduced 2026-09-08, when it found one: /range/if-range-ignored had published deterministic_bytes:true since the corpus existed, while it mints a fresh generation stamp per request. That row now reads false. No other field disagreed with the wire.",
    "what_this_does_not_check": [
      "That bytes stay stable over weeks. Two requests seconds apart cannot see a body that turns over hourly or at midnight UTC; deterministic_bytes is a claim about repeating the same request, not about permanence.",
      "That varies_by is exhaustive. It is checked for being non-empty where a row admits its bytes move, never for naming everything that moves them.",
      "Whether a row's prose is true. `defect` is written by this project about its own endpoint, and no script can tell you it describes the response honestly — that is what /clients exists to make checkable, by publishing what independent implementations actually did.",
      "What a correct client SHOULD do. Nineteen rows carry an `expect` field, but it describes what this server intends to happen, not a conformance requirement, and it is neither machine-checkable nor asserted by anything here — see how_to_use."
    ]
  },
  "not_covered": {
    "rfc9112-message-syntax": "If you are testing a parser against the RFC 9112 grammar or the request-smuggling surface, badhttp.dev is not a source of that traffic and capturing from it will teach you nothing: you would collect 151 well-formed messages. Worse, a corpus of clean messages can read as evidence that a checker has no false negatives. Serve those cases from a raw socket you control (a listener writing literal bytes), not from anything behind a CDN.",
    "request-smuggling": "Same reason. This service cannot emit conflicting framing headers; the edge normalizes them away.",
    "http/2 and http/3 framing": "Both are served, but the frame layer is Cloudflare's, not ours; nothing here misbehaves at it on purpose."
  }
}
