Repository navigation
Add a CBOR module that reads and writes TLA+ values - #136
Conversation
Proposed IANA registration for tag 33000Tags from 32768 up are First Come First Served. IANA asks for the four-line template from RFC 8949 section 9.2 plus a URL that describes the semantics. Before I file it, here is what I would send, so that the number, the data item, and the wording can be agreed in this PR. The description text lives in a gist, https://gist.github.com/younes-io/8bbc63cffd4811f6aa2776c7301eb360, which I will keep in step with this thread; its current text is also below. The module header gets the registry link when the row appears. Update 2026-10-08. The request went to IANA with the template below and both contacts. Update 2026-10-09. IANA registered tag 33000 on 2026-10-08, with the template below as filed: https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml (row 33000, template https://www.iana.org/assignments/cbor-tags/template/33000). The module header and Template Description of semantics A function in TLA+ is a finite table from arguments to values. Its arguments can be integers, strings, booleans, model values, tuples, sets, records, or other functions. CBOR maps allow such keys, but the native dictionaries of several host languages do not. Go cannot use an array as a map key, and a JavaScript object turns every key into a string. Tag 33000 therefore writes a function as an array of pairs, which every language reads. The tagged data item is an array. Each element is an array of exactly two data items, the argument first and then the value at that argument.
The producer of this tag, the Examples. The function The function on the tuples Open points for reviewers
|
|
First reaction: Could the Python testing be adapted and merged into the current Java test suite? |
Yes (shared my email privately). |
|
IANA registered tag 33000 today. The row is live at https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml, with the semantics as proposed above and both of us as contacts. Thanks, @lemmy. |
lemmy
left a comment
There was a problem hiding this comment.
LGTM (this is ready (modulo readme file) to be experimented with more broadly).
|
@lemmy btw, I don't see the merge button |
build.xml compiled the overrides with source and target 1.8, but IOUtils already calls Files.readString and Files.writeString, which Java 11 added, and the tla2tools.jar that the build downloads is class version 55 and needs Java 11 to run. The build has needed Java 11 since 2021, when IOUtils adopted those calls. The javac task now passes release 11 instead. With release, javac checks every call against the Java 11 class library rather than the library of the JDK that runs the build, so a call to a newer API fails to compile instead of surfacing in review. README.md said Java 9 or higher and now says Java 11 or higher. [Build] Signed-off-by: younes-io <git@younes.io>
ToCBOR returns the CBOR (RFC 8949) encoding of a finite TLA+ value as a sequence of integers in 0..255, and FromCBOR decodes such a sequence back into a TLA+ value. CBORSerialize and CBORDeserialize do the same through a file. Harnesses in Go, Rust, TypeScript and Python can then read TLC values without the type loss of JSON. Sets, model values and functions with non-string domains keep their TLA+ meaning. Sets use tag 258 and model values tag 39. A function with domain 1..n is an array, a function with a non-empty domain of strings is a map, and any other function uses tag 33000, which IANA registered on 2026-10-08 as "TLA+ function as an array of [argument, value] pairs". Equal values encode as identical bytes. The encoder picks a function's encoding from its domain, not from its Java class, and sorts set elements and keys by their encoded bytes instead of by TLC's intern order. The decoder rejects duplicate set elements and keys, tags it does not know, and model values that the running TLC does not define. CBORTests holds each golden as inline hex literals and requires ToCBOR and FromCBOR to agree with them, and it tests every input the module refuses. CBORGoldenTest, run by ant test, checks the same goldens against a second encoder that shares no code with CBOR.java. tlaplus/tlaplus#1467 [Feature] Signed-off-by: younes-io <git@younes.io>



Why
tlaplus/tlaplus#1467 asks for a trace format that harnesses in Rust, Go, TypeScript, and Python can read without losing TLA+ types. JSON cannot tell a set from a sequence, a model value from a string, or a function from a record. This PR adds a
CBORmodule (RFC 8949) withToCBORandFromCBORfor byte sequences andCBORSerializeandCBORDeserializefor files, implemented as TLC overrides in the style ofJson. TLC's-dumpTracecan later be a thin layer on top of it.What changed
modules/CBOR.tladocuments the four public operators and the encoding.tlc2.overrides.CBORimplements them, and the Javadoc of itsEncoderandDecoderstates the exact rules for writing and reading.tests/CBORTests.tlapins the bytes of every value kind, andtests/java/tlc2/overrides/CBORGoldenTest.java, a second encoder with no shared code, checks those bytes fromant test.The encoding
Integers, booleans, and strings are the CBOR types of the same name. A model value is tag 39 (Identifier) around its name. A set is tag 258 (Mathematical finite set) around an array of its elements. A function whose domain is
1..nis an array, which covers sequences, tuples, and the empty function. A function whose domain is a non-empty set of strings is a map, which covers records. Any other function is tag 33000 around an array of[argument, value]pairs.Map keys use the unsigned lexicographic encoded-byte ordering in RFC 8949 section 4.2.1. This module also applies that ordering to set elements and function arguments. That additional contract gives supported TLC-equal values identical bytes regardless of their Java representation or string interning order.
Why a new tag
Tags 39 and 258 were already registered with IANA. Tag 33000 was registered for this module on 2026-10-08. It is needed because a TLA+ function can have integers, model values, tuples, sets, or records as arguments, and a CBOR map with such keys does not survive every decoder. Go's
fxamacker/cborrejects a map whose key is an array, and a JavaScript object has only string keys. An array of pairs avoids requiring function arguments to fit a host language's native map keys. Consumers still need to interpret the tags. Tag 259 does not help, because it marks a map and leaves the key problem in place.Before this registration, no registered tag described a function or map encoded as an array of pairs. The closest are 259 and 275, which tag maps, and 281, which tags Lisp cons cells. 33000 is in the First Come First Served range. A comment below holds the registration template as filed; the registry row is at https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml.
Scope
StackOverflowError; increasing JVM-Xsscan allow deeper values.ToCBORcan export mixed sets such as{1, "a"}, butFromCBORrejects them because TLC cannot compare their elements. The header ofCBOR.tlastates this round-trip restriction.-dumpTrace cboroption in TLC, and atomic file replacement, whichJsondoes not do either.Tradeoffs
Deciding the form by the value, not by TLC's Java class, is what makes equal values byte-identical. The cost is the shape change above. A typed reader needs a small custom decoder for functions. The alternative, writing by Java class, would make
<<>>and the empty record differ on the wire while TLC says they are equal.Impact on existing code
The new module is registered in
TLCOverrides.javaand included inAllTests.tla. A separate first commit makes the build and the README state Java 11 as the floor, which the build already needed:IOUtilsuses Java 11 file APIs and the tla2tools.jar it downloads is built for Java 11.ant testruns one JUnit test before TLC.Verification
ant testpasses on the current source. The build downloaded the v1.8.0tla2tools.jar, which reports version2026.10.13.011403, revision210df3a, and ran on Homebrew Java 17.0.20.1 on macOS. This run does not cover the other CI platforms.CBORTests.tlacontains 119 assumptions covering byte-exact encodings, equivalent TLC representations, round trips over exhaustive small value spaces, malformed-input rejection, file operations, and nesting beyond the former fixed limits.CBORGoldenTestpasses all 31 golden cases using a second encoder with no shared implementation code. It runs before TLC inant test.cbor2and Go'sfxamacker/cbor, including a counterexample written from aPOSTCONDITION. Those interoperability checks have not been rerun for the latest revision.