ocaml-cbor: rewrite Obj/Obj_int as nox-json-style pipeline, drop Obj.magic
The old [(o, a) mem] GADT was monadic with [cont : 'x -> ('o, 'a) mem],
which forced [member_names] / [build_decoders] to walk the chain by
calling [cont (Obj.magic ())] — a fake value the continuations were
trusted not to inspect. That trust was implicit, type-system-evading,
and exactly what you don't want in a security-focused codec.
Replace it with [Json.Codec.Object]'s pipeline shape:
Cbor.Obj.map (fun a b c -> { a; b; c })
|> Cbor.Obj.mem "a" (fun r -> r.a) Cbor.string
|> Cbor.Obj.mem "b" (fun r -> r.b) Cbor.int
|> Cbor.Obj.mem_opt "c" (fun r -> r.c) Cbor.string
|> Cbor.Obj.seal
Each [mem] consumes one argument of the curried constructor and reifies
the field as a record carrying its name plus typed encode/decode
closures. [seal] enumerates field names and a name -> field dispatch
table directly from the field list — no chain walking, no [Obj.magic].
The implementation still uses [Stdlib.Obj.repr] / [Stdlib.Obj.obj] to
hold heterogeneously-typed decoded values in a single name-keyed table,
but only with values whose static type is fixed by the field that
produced them (the same [Hmap]-style universal-table contract); never
[Obj.magic ()].
Other changes:
- Apply the same rewrite to [Cbor.Obj_int] (integer-keyed records used
by COSE / CWT).
- Drop [let*], [return], [map], [both], [let+], [and+] from the API —
the pipeline form supersedes them.
- Update mli docs and the README quick-start to the new shape.
- Migrate every caller in the workspace
(ocaml-cbor/test/{test_cbor,test_value}, ocaml-mst/test/test_mst).
[dune build] is clean; [Cbor.encode_string] / [decode_string]
roundtrip remains green across all 298 cbor tests and the 41 mst
tests. [merlint ocaml-cbor] now reports 0 issues — every Obj.magic
flagged by E100 is gone.