Skip to content

Latest commit

 

History

History
11 lines (9 loc) · 3.62 KB

File metadata and controls

11 lines (9 loc) · 3.62 KB

serde — notes

Hand-written. ../harvested/serde.md is what the documents say; this file is what the implementation found, and it wins. Each entry names the harvested entry it answers by that entry's stable key.

Reference

  • A decoded JSON String can be UTF-8-tagged and invalid, and neither layer that produces it raises. Beside serde/b5e5efc8, which puts the strictness burden on the witness: a witness asserting String cannot catch this, because an invalid-UTF-8 String is a String. Measured on Ruby 3.2.11, 3.3.12, 3.4.10 and 4.0.6 against json 2.19.9 (the gemspec floor) and 3.0.2 (the bundle's): JSON.parse(%Q({"a":"\xff"}).b)["a"] returns a String whose #encoding is UTF-8, whose #bytes is [255] and whose #valid_encoding? is false. Phase 3a's #read_utf8 and #read_string(encoding) retag and apply no replacement policy, by their own stated contract, so nothing between the wire and the witness validates. Phase 7a's Dexpace::Serde::JSON::Codec#load therefore calls #valid_encoding? on the drained text and raises Dexpace::Serde::DeserializationError before parsing (7a P7-6). A transcode is the wrong repair: "\xc3\xa9".b.encode(::Encoding::UTF_8, ::Encoding::BINARY) raises Encoding::UndefinedConversionError for a valid two-byte é, because BINARY has no character semantics to convert from — so the retag-then-transcode recipe of docs/knowledge/notes/io-and-byte-streams.md (its key io-and-byte-streams/6eb5155f, a note and not a harvested rule) is retag, then validate, and the transcode step applies only when a declared charset is not UTF-8. review · docs/work/mvp/phase7/phase7a/2026-09-10-phase7a-serialization-design.md · high · sha:manual-phase7a-utf8-validation
  • JSON::Coder's constructor and its tolerance of an unknown option both changed between json 2.19.9 and 3.0, so an adapter that forwards caller options verbatim is configured differently on the two, silently on one and loudly on the other. Adds to serde/d3bef411 and serde/b36d4403, which describe the private-engine and fresh-factory contract the Coder makes satisfiable literally (7a P7-4), and to serde/d15ade64, the reason the floor is a gemspec line: measured on 2026-09-20 on 3.4.10 with json 2.19.9 and on 3.2.11 and 4.0.6 with json 3.0.2, JSON::Coder.instance_method(:initialize).parameters is [[:opt, :options], [:block, :as_json]] on 2.19.9 and [[:key, :object_class], [:key, :array_class], [:key, :on_load], [:keyrest, :options], [:block, :as_json]] on 3.0.2; Coder.new(max_nestng: 4).dump([1]) returns "[1]" on 2.19.9 and raises ArgumentError: unknown keyword: max_nestng on 3.0.2; Coder.new(encoders: {}) is accepted on 2.19.9 and refused on 3.0.2; Coder.new(nil) works on 2.19.9 and raises on 3.0.2; a duplicate key is accepted last-wins on 2.9.1, a warning on 2.19.9 and a ParserError on 3.0.2, while Coder.new(allow_duplicate_key: false) raises ParserError on both floored versions; and Coder.new.dump(Object.new) and .dump(Time.at(0)) raise GeneratorError on both without strict: true — the Coder is strict on its own account, and strict: true is documentation of intent rather than a switch. Phase 7a's codec therefore validates options against its own allowlist, constructs the Coder with keywords only, fixes allow_duplicate_key: false unless the caller opts in, and never forwards encoders:. Design open question 4's premise ("Coder.new accepts unknown options silently") is true at the floor and false at 3.0. review · docs/work/mvp/phase7/phase7a/2026-09-10-phase7a-serialization-checklist.md · high · sha:manual-phase7a-coder-option-drift