# AER-1 Java port notes

Single-file-dependency-free Java port of the ARION AER-1 verifier
(`../verify.js`, 951 lines) and corpus runner (`../run_vectors.js`),
re-verified semantics against draft-zambo-aer1-10 / kit v1.4.4 @ 5e3f078.

## Files

- `Json.java` — hand-rolled strict JSON parser (RFC 8259). Objects are
  `LinkedHashMap` (insertion order; duplicate keys keep first position /
  last value, like JS `JSON.parse`). Numbers are `Json.Num` carrying the
  **raw lexeme** plus the double value — required because the corpus pins
  `entry_count: 5.0` as a lexical-float rejection while `seq: 2.0` is
  accepted (`Number.isInteger(2.0) === true`).
- `Aer1Verify.java` — the verifier. Public API:
  `Aer1Verify.verifyText(String rawJson, Aer1Verify.Opts)` →
  `List<String>` of failure reasons (empty = conformant), mirroring
  verify.js reason strings verbatim. Also `verifyJsonText` (returns
  `Result{kind, List<Failure{check,reason}>}`), `verifyDocument` (parsed
  value), `verifyReceiptText(text, strict, profile, anchor)`, and all the
  granular checks (`checkReceipt`, `checkReferenceProfile`,
  `checkDisinterestedTier`, `verifyChainV07`, `verifyChainV06`,
  `verifyChainCommitment`, `checkFinalEntryCommitment`, `merkleRoot`,
  `verifyWorkflowSteps`, `checkWorkflow`). Includes a CLI `main`
  equivalent to `node verify.js`.
- `RunKit.java` — port of run_vectors.js; same group order, verdict and
  expected-failure matching, and output format.
- `run_host.sh` — self-locating build+run script.

## Java version assumptions

- Target **Java 11+** (`List.of`, `String.isEmpty`, `var`-free). Uses only
  `java.base`: `java.security.MessageDigest` (SHA-256),
  `java.util.Base64`, `java.nio.charset`, `java.util.regex`, collections.
- Compile: `javac -d out Json.java Aer1Verify.java RunKit.java`
- Run: `java -cp out RunKit /path/to/kit-snapshot`
- Or simply `./run_host.sh` (expects kit at `../kit-snapshot`; override
  with `./run_host.sh /path/to/kit`).

## Porting decisions / deviations

- **Number lexemes.** JS `JSON.parse` erases int/float; the -10 kit runner
  patches this by regex-rewriting `"entry_count": <\d+\.\d+>` into a
  `{"__float__": N}` marker before parsing. This port keeps the raw lexeme
  on `Json.Num` instead; `Json.Num.isMarkedFloat()` implements exactly the
  kit's `\d+\.\d+` marker rule (unsigned, no exponent), so `5.0` is
  rejected while `5e0` or `-5.0` would behave as in JS (integer-valued,
  unmarked → accepted).
- **JS `===` / truthiness.** `undefined`, `null`, `false`, `0`, `""` are
  modeled: Java `null` = absent/undefined, `Json.NULL` = JSON null, plus a
  `truthy()` helper used where run_vectors.js does `doc ? ...` or
  `doc.timeline || doc.entries`. An `UNDEFINED` sentinel in RunKit keeps
  `expectedVerdict === null` vs `=== undefined` distinct (as in JS).
- **JSON `||` on objects.** `doc.timeline || doc.entries` is ported as a
  truthiness check, not just presence, matching JS.
- **UTF-8.** Strict decode uses `CharsetDecoder` with
  `CodingErrorAction.REPORT` (fails closed like `TextDecoder{fatal}`).
  Encoding for hashing uses an encoder replacing malformed input with
  U+FFFD — Node's `Buffer.from(s,"utf8")` behaviour on lone surrogates
  (Java's default replacement is `?`). Corpus contains no lone surrogates;
  this is belt-and-braces parity.
- **Base64.** Surface check `^[A-Za-z0-9+/]*={0,2}$` + length % 4, then
  `Base64.getDecoder()` which — like Node `Buffer.from(s,"base64")` — does
  not verify canonical pad bits. Trailing-newline tolerance was removed in
  draft -10 and is not reintroduced.
- **Regexes.** All kit regexes are ASCII-only; `Pattern` + `matches()`
  reproduces JS `^...$` anchoring. `verify_url` `^https?://` is implemented
  as `startsWith("http://") || startsWith("https://")` (equivalent).
- **RFC 3339.** Shape regex + calendar math (proleptic leap-year rule;
  1900-02-29 rejected). Offset bounds `< 24h` total, `hh<=23`, `mm<=59`.
  The `s.replace("Z","+00:00")` first-occurrence quirk is ported.
- **Canonical JSON (§7.1).** Keys sorted by UTF-16 code unit (Java
  `String.compareTo` ≡ JS `<` on strings), escapes: `\",\"\\,\b,\f,\n,\r,\t`
  plus `\uXXXX` (lowercase) for any unit `<0x20` or `>=0x7F` (includes DEL
  and surrogate halves — non-BMP chars serialize as surrogate-pair escapes).
- **Number printing.** `jsNumToString` implements ECMA-262
  Number::toString (shortest round-trip via `BigDecimal.valueOf`, decimal
  for `1e-6 <= |v| < 1e21`, `e±` exponential outside). Needed so `seq`
  values like `2.0` canonicalize as `2` in the entry digest.
- **Duplicate JSON keys.** Not specially detected — JS `JSON.parse`
  last-wins semantics are kept (LinkedHashMap put).
- **Runner summary.** Byte-compatible with run_vectors.js, including the
  fuzz corpus's unscored line and the summary entry
  `fuzz-corpus (strict, unlabeled) (unscored): A/1000 accepted/1000`.
- **Known intentional loosenesses:** parse-error text differs from V8's
  messages (only the "not valid JSON" verdict matters to scoring);
  `verifyFile` in Aer1Verify's CLI reports `not valid JSON: <msg>` like
  verify.js but the runner path uses the constant `not valid JSON`.

## Expected result on the snapshot

```
== SUMMARY ==
  ... (all groups N/N)
vector checks: 186 passed, 0 failed
fuzz: 59 accepted, 941 rejected (59 unparseable)
```
