Skip to content

v0.2.0: adversarial hardening — uniform rejection contract across all 7 lineages - #1

Merged
SaMullinsJr merged 2 commits into
mainfrom
harden-v0.2.0
Jul 15, 2026
Merged

v0.2.0: adversarial hardening — uniform rejection contract across all 7 lineages#1
SaMullinsJr merged 2 commits into
mainfrom
harden-v0.2.0

Conversation

@SaMullinsJr

Copy link
Copy Markdown
Member

Summary

Hardening round driven by the new 3-layer conformance stack (pinned corpus, deterministic differential probe, seeded adversarial fuzzer). The first 8,000-case fuzz campaign found 82 agreement failures that the corpus, probe, and four GPT review rounds had all missed; every one is closed here.

Breaking change: v0.2.0 rejects inputs v0.1.3 accepted. The accepted domain is now strictly narrower and uniform across all seven lineages:

  • Plain-decimal number domain only: no exponent tokens, integers in the closed interval [−2^53, +2^53], fractions in [1e-6, 1e21)
  • Invalid UTF-8 rejected everywhere (Go previously replaced with U+FFFD and hashed the replacement — silent collision class)
  • Raw control bytes in strings and non-whitespace control bytes between tokens rejected
  • Strict number grammar (no leading zeros, trailing dots, attached junk)
  • Malformed \u escapes, case-variant literals, and parser extensions (comments, unquoted keys, NaN/Infinity) rejected
  • ES-262 shortest-digits float formatting byte-identical in all 7 (incl. GHC even-mantissa boundary fix)

Verification

  • Fuzz: 0/8,000 (seed 20260715) + 0/4,000 (fresh seed 424242)
  • Differential probe: 0/234
  • Verifier: 7/7 lineages byte-identical on 24 accept + 31 reject corpus rows
  • Pristine-clone gate: fresh clone → build_all 7/7 → verifier PASS → probe PASS

🤖 Generated with Claude Code

SaMullinsJr and others added 2 commits July 15, 2026 12:47
…-data conformance

- Plain-decimal number domain in all 7 CLIs (lexical): no exponent tokens,
  integers limited to [-2^53, +2^53] by digit-string compare, fractions in
  [1e-6, 1e21). '100' accepted, '1E2' rejected.
- ES-262 ToString float formatting in every lineage (shortest round-trip,
  plain decimal, zero -> "0"): C/OCaml %.*e+strtod loop, C++ to_chars,
  Rust std Display, D %.*e loop, Haskell floatToDigits. The whole fraction
  band [1e-6, 1e21) is now byte-identical across all seven.
- Go: lone-surrogate escapes rejected (backslash-run parity), -0.0 -> "0".
- OCaml: lone LOW surrogate rejected (yojson only caught the high half).
- D: strict single-document parse (trailing garbage, concatenated docs,
  trailing comma, empty input all rejected; std.json accepted all four).
- C/C++: leading UTF-8 BOM rejected. C++: over-uint64 integer tokens
  rejected (nlohmann routes them to number_float).
- Corpus-as-data: conformance/accept.jsonl (23 pinned) + reject.jsonl (19)
  generated by gen_corpus.py, which refuses to pin divergent cases;
  verify_all_lineages.sh is corpus-driven; differential_probe.py sweeps 165
  generated danger-zone cases in CI (agreement-only).
- CI: actions pinned to commit SHAs; package versions -> 0.2.0; README
  states the precise domain (closed interval +/-2^53, plain-decimal floats,
  single-document rule) and prerequisites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… controls, lax tokens; ES-262 shortest-digits fix

Third conformance layer added: conformance/fuzz_agreement.py, a seeded
randomized fuzzer (structured boundary-biased documents, byte mutations of
valid documents, raw garbage) asserting seven-way agreement per input.
Campaign seed 20260715, 8000 cases: initially 82 failures, now 0.

Divergence classes closed (all lexical pre-parse checks; cores untouched):
- invalid UTF-8 (stray continuation, truncated, overlong, encoded surrogate,
  >U+10FFFF): C/D/OCaml accepted verbatim, Go replaced with U+FFFD and hashed
  the replacement (silent cross-lineage collision) — now rejected in all 7
- raw control bytes in string literals (C, and pinned in Haskell/OCaml);
  non-whitespace control bytes between tokens (C skipped <=0x20; D skipped
  VT/FF via isWhite) — JSON whitespace is only TAB/LF/CR/space
- number-token grammar: leading zeros (C, D), bare trailing dot (C),
  junk attached to number tokens '2-' (D)
- malformed backslash-u escapes with <4 hex digits (C)
- case-insensitive literals nuLl/falSe (D); unquoted keys, // and /**/
  comments, NaN/Infinity/-Infinity (OCaml/yojson)
- HASH SPLIT (worst class): Haskell emitted 17-digit exact value instead of
  ES-262 shortest digits for even-mantissa boundary doubles beyond 2^53
  (GHC floatToDigits uses open-interval comparisons); shortening pass added,
  latent exact-integer fast path at 2^53-adjacent .0 tokens removed

Corpus grown to 24 accept (pins big-float shortest-digits) + 31 reject rows;
differential probe grown to 234 deterministic cases (raw-byte payload support
for invalid-UTF-8 cases); README domain contract updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SaMullinsJr
SaMullinsJr merged commit fb5f0c2 into main Jul 15, 2026
8 checks passed
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