flowchart LR T[target] --> H[handshake] --> Q[quic] --> C[cert] --> P[pq] --> D["deep (deep mode)"] --> E[error]
Record schema
The output contract: one schema-1 record per target per pass, streaming JSONL. The field lists below are the encoders’ contract — pinned by the goldens in the test tree, drift-checked against the published machine-readable schema in CI. Full prose context: the spec §5.7.
Block order
| Block | Fields |
|---|---|
target |
input, host, ip, port, ptr, sni_used |
handshake |
negotiated (group id), group_name, group_class, suite, version, alpn_neg |
quic |
attempted, state (negotiated · handshaked · refused · mismatch · skipped · udp_blocked), negotiated, group_name, group_class, discovered{alt_svc_h3, h3_direct} |
cert |
sig_alg, sig_pq, key_alg, key_pq, dual |
pq |
score (0–100), label (pq-ready ≥ 80, pq-ready-hybrid ≥ 60, pq-partial ≥ 40, pq-missing ≥ 1), hndl, summary |
deep |
deep mode only: mode, attempted, ok, rejected, findings[]{probe, result, detail} |
error |
the per-target error string |
The fast record (captured on google.com)
$ pqscan -u google.com -jsonl{"schema":1,"mode":"fast","target":{"input":"google.com","host":"google.com","ip":"","port":443,"ptr":"","sni_used":""},"handshake":{"negotiated":4588,"group_name":"X25519MLKEM768","group_class":"pq_hybrid","suite":"TLS_AES_128_GCM_SHA256","version":"tls-1.3","alpn_neg":"h2"},"quic":{"attempted":true,"state":"negotiated","negotiated":4588,"group_name":"X25519MLKEM768","group_class":"pq_hybrid","discovered":{"alt_svc_h3":true,"h3_direct":true}},"cert":{"sig_alg":"ecdsa-with-SHA256","sig_pq":false,"key_alg":"ECDSA","key_pq":false,"dual":false},"pq":{"score":40,"label":"pq-partial","hndl":false,"summary":"negotiated_pq_hybrid(+40)"},"error":""}The CSV projection
25 columns, header first; the order below is the contract:
target,host,port,ptr,sni,negotiated,group_name,group_class,suite,version,alpn,quic_state,quic_group,udp_blocked,cert_sig_alg,cert_sig_pq,cert_key_alg,cert_key_pq,cert_dual,pq_score,pq_label,pq_hndl,pq_summary,error,ts
Exit codes
| code | meaning |
|---|---|
| 0 | scan completed (per-target errors are records, not failures) |
| 1 | runtime error (engine/system) |
| 64 | EX_USAGE: a command-line mistake refused before any scan (flag-parse errors, no targets, unknown -mode/-deep/-sni, a bogus -shard spec, -resume without -o, -explain under a machine output format) |
| 130 | interrupted (drains before exit; WAL retained) |
Consumption recipes (jq views, the estate calendar, the adapters) live on the Recipes page; the human-readable man-format flag reference is USAGE.
How it stays honest
- The goldens (
tests/goldens/record.*) pin the byte-shape; a contract change = a visible golden diff in review. - The schema-sync check regenerates
docs/schema-v1.jsonfrom the goldens and requires byte equality — the published schema cannot drift from what the binary emits. - The usage-drift gate keeps USAGE.md honest against the flags.