Skip to content

Spec 010 T006–T007: the Java-against-Java parity harness - #507

Merged
adamjohnwright merged 6 commits into
mainfrom
java/parity-harness
Oct 17, 2026
Merged

adamjohnwright merged 6 commits into
mainfrom
java/parity-harness

Conversation

@adamjohnwright

Copy link
Copy Markdown
Contributor

tools/java-parity sends the same requests to a reference (what answers today) and a candidate (what replaces it). It compares status, the headers clients read, and the SHA-256 of the body after a declared normaliser. That normaliser is the only way a difference can be hidden, and it is named in each run.

  • Privacy: reports hold digests and verdicts, never bodies. --redact drops the request as well, because some paths are tokens that anyone holding them can read.
  • --calibrate: asks the reference twice. A request whose reference disagrees with itself (live PSICQUIC data) is counted as unstable, not as a difference. --simultaneous sends both at once.
  • Normalisers: identity, and an analysis result (or its gzipped download) with the token removed, since a token holds the time and a per-process counter.

Each comparison was shown failing by mutation: status, headers, body, calibration, redaction.

The PDF normaliser (T008) will come with the first PDF comparison (T030), since it needs qpdf in a container.

🤖 Generated with Claude Code

adamjohnwright and others added 6 commits October 17, 2026 13:37
tools/java-parity sends the same requests to a reference (what answers
today) and a candidate (what replaces it) and compares status, the
headers clients read, and the SHA-256 of the body after a declared
normaliser -- the only way a difference may be hidden, by name.

- Reports hold digests and verdicts, never bodies (some answers hold
  people's records); --redact drops the request too, since some paths
  are tokens anyone holding them can read.
- --calibrate asks the reference twice and counts a request whose
  reference disagrees with itself (live PSICQUIC data) as unstable, not
  as a difference.
- --simultaneous sends both at once, for live data.
- Normalisers: identity; an analysis result, or its gzipped download,
  without the token (it holds the time and a per-process counter).

Each comparison shown failing by mutation (status, headers, body,
calibration, redaction). The PDF normaliser (T008) comes with the first
PDF comparison: it needs qpdf in a container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…udly

- Over node:http, not fetch: fetch dropped a caller's Host, so each side
  saw its own address and answers built from Host differed for no real
  reason; it also hid Transfer-Encoding.
- Every response header is compared except named noise (Date,
  Connection, Keep-Alive, Server): a redirect's Location, Vary, ETag,
  Set-Cookie and every CORS header were not compared before.
- The token normaliser blanks the token's bytes and nothing else. The
  JSON round trip it replaces lost digits past 2^53, folded -0 into 0,
  dropped duplicate keys and reordered integer-like keys -- differences
  R and Python clients see.
- A failure is an `error` verdict carrying only its kind (a message can
  quote a body or a token), the run goes on, and each request has a
  timeout.
- Exit 0 only when something was compared and all of it was identical;
  errors, unstable requests or an empty run exit 2.
- Request bodies can be bytes (bodyBase64, bodyFile), for uploads.

Each fix shown failing by mutation first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…timeout

- A base URL's context path is kept. Dropped, both sides went to the
  same wrong resource and matching 404s compared as a pass, exit 0.
- The scheme is honoured (https supported) or refused, never ignored; a
  base with a query or fragment is refused; IPv6 hosts work.
- The timeout covers the whole request: a body trickled a byte at a time
  never went quiet and held the run open.
- Headers come from rawHeaders, every copy kept: the parsed map kept one
  Content-Type of two.
- A status difference is reported even when the body cannot be
  normalised; a token normaliser that finds no token is an error; errors
  name what failed (network, body decoding, normaliser).
- --ignore-header declares a header to skip for the run, and the report
  lists it.

Each shown failing first; the suite passes three runs in three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A response reset part way through clears its timer: the run went on,
  but the process stayed alive until the timeout fired (two minutes by
  default) for anyone importing the harness.
- An unusable base URL or a dangling --ignore-header exits 3 (usage),
  not 1, which means "differences".
- A status difference whose error page claims an encoding it lacks is
  compared as sent, and reported as a difference rather than an error.

Each shown failing first; the timer test measures timers armed by the
run itself, not the test runner's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adamjohnwright
adamjohnwright merged commit 0d96e97 into main Oct 17, 2026
7 checks passed
@adamjohnwright
adamjohnwright deleted the java/parity-harness branch October 17, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant