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

flowchart LR
  T[target] --> H[handshake] --> Q[quic] --> C[cert] --> P[pq] --> D["deep (deep mode)"] --> E[error]

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.json from 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.