Repository navigation
Releases
Releases · replikativ/boring
0.1.31
Seqs round-trip as seqs, via the tag-27 marker clojure/seq (#29)
* feat: seqs round-trip as seqs, via the tag-27 marker clojure/seq
A Clojure seq used to encode as a plain CBOR array and decode as a vector, so
`'(count ?v)` came back as `[count ?v]`. That is `=` to the original but not
`seq?`, which is how datahike#1145 read a query call as a lookup ref. It also
has a different hasch content address, so a value did not survive its own
round trip.
Every `seq?` value (lists, lazy seqs, conses, ranges, syntax-quoted forms) is
now written as 27(["clojure/seq", <array>]) under :clojure and :archival. The
payload is byte for byte the array the bare encoding writes, so a reader that
does not know the name still finds the elements. It decodes to a seq over the
decoded vector, or () when empty. That is what nippy returns for every seq that
is not a PersistentList: one object on top of the array decode, chunked, O(1)
count. A seq never offered indexed access, so the decoded value does not
either.
:interop, :canonical and :canonical-rfc7049 lock the new `:seqs` option off and
still write a plain array, because those profiles promise agreement with
encoders that have no seqs. `{:seqs false}` turns it off under :clojure.
- Java and ClojureScript writers and readers; canonical scratch writers inherit
the flag. `clojure/seq` is a reserved name, so nav realises it as it does
queues.
- The payload must be an array: null, a set or a map is :boring/bad-tag-content
on both platforms.
- Golden corpus: five new entries, no existing bytes changed.
- The Python and Rust reference readers unwrap the marker to a plain list; the
interop fixture gains a seq.
- Fixtures, the defaults freeze and the nippy widening test move seqs out of
"collapses to a vector".
Readers must be upgraded before writers: an older boring decodes the marker as
an UnknownRecord. Data written before this change still decodes to vectors.
* fuzz-nav: read data-len off the blob's footer, not a re-encode
The nav fuzzer took the value item's length from (encode v) with stringref
on. When nothing clears :index-min, encode-indexed returns the plain encoding
with stringref OFF and no frame, so any value that repeats a string
re-encoded shorter than the blob. The fuzzer's "frame-only" mutations then
landed in the data and were reported as FRAME-CHANGED-DECODE.
The bug was there before #29, but generated seeds almost never repeated a
string. clojure/seq makes every value with two seqs repeat one, and CI went
red. data-len now comes from frame/footer-start, the same way boring.edit
finds the data region.
0.1.30
docs: clarify CBOR motivation and rewrite core guides (#28)
0.1.29
doc: register-record's example gave a name that can never match (#27)
`register-record` takes the wire name as a STRING, and its example gave the JVM
CLASS name:
(boring/register-record "my.ns.Point" map->Point)
The writer emits `namespace/Name`. `boring.data/record-type-name` is explicit
about why — a dot is ambiguous, `a.b.c.D` could split either way — and returns
the slash form on both platforms. So every registration written from that
example silently did nothing.
Silently is the point. An unregistered record decodes to an `UnknownRecord`
carrying the same name and fields and re-encodes to identical bytes, which is a
property worth having; it also means a wrongly-FORMED registration is
indistinguishable from a type nobody registered. Nothing raises, and any
`instance?` check the caller makes on the decoded value answers false — so
whatever they meant to do with that type quietly does not happen.
Found downstream: datahike registered `"datahike.migrate.cbor.SysRef"` from this
example. Its `SysRef` — which marks a ref value pointing at a system entity —
therefore never round-tripped, `resolve-sysrefs` never fired, and such a value
was imported as an opaque record instead of the target's entity id. It had been
that way for as long as the registration existed, with no symptom until someone
added a second record type and used the first as a control.
The prose above the example was stale in the same direction, still describing
the JVM class name and ClojureScript munging down to match it. That is what
`record-type-name` used to do and deliberately stopped doing ("Every lossy step
breaks a symmetry, and this one was not forced").
Fixed here:
* all four examples use `my.ns/Point` — `register-record` (clj and cljs),
`register-navigable-record`, and `nav-conformance`;
* the prose describes what `record-type-name` actually returns;
* the recommended idiom is to DERIVE the name from `record-type-name` rather
than write it out, which removes the class of mistake instead of correcting
one instance of it;
* the silent-failure mode is stated, since it is the reason a wrong name costs
so much to find.
`register-record-name-test` pins all three: the wire name is namespace/Name and
not the class name; a derived registration round-trips; and a class-name
registration silently yields an `UnknownRecord` whose re-encoded bytes are
identical.
No behaviour change and no format change — the wire has always carried the slash
form. Verified by deliberately breaking an assertion and watching the suite fail,
because a test that is never run is worse than none.
Measured: 549 tests, 28204 assertions, 0 failures.
0.1.28
bench: commit the tier-matched hako-ab table and wire it into the sui…
0.1.27
doc: make the CBOR connection explicit in the tagline and name (#24) A reader pointed out that 'boring' does not, on its own, signal CBOR. Put CBOR in the tagline and own the name-is-the-joke in the first lines -- CBOR is the boring, standard, universally-readable format, which is the whole point -- so the connection lands immediately instead of six paragraphs in. Pairs with a repo description and topics that now say CBOR.
0.1.26
feat: container-type path dispatch + automatic same-length poke (#23) Paths now resolve on the CONTAINER like clojure.core/get-in, not on the key's type: a map keyed by integers is read as a map instead of being mistaken for an array, so nested integer-map-key paths work (they previously resolved to the wrong container or threw path-absent). child-offset drives both the value locator and the structural (parent re-encode) path, which is rewritten off the offset layer to match. update-in-bytes and boring.mmap/splice! now detect a same-length result (d==0) and take the poke path automatically -- overwrite in place, leave the frame untouched, skip the tail move and reindex -- so a caller gets poke speed without knowing to ask. The explicit poke* fns remain for callers who want a length change to be a typed error. Docs: README + doc/EDITING.md use keyword-keyed examples and document the container dispatch and the automatic poke. Tests: nested integer-map-key replace/add/dissoc, non-container-parent, same-length-keeps-the-frame, splice! same-length -> [old old]. 546 tests / 28197 assertions, lint clean.
0.1.25
In-place / splice editing: update-in an encoded value without decodin…
0.1.24
docs: sharpen README motivation — ride the open format, and speak it …
0.1.23
ci: make the index-mutants gate robust to a dep/compile flake (#21)
A docs-only PR turned the `mutants` job red. Root cause: on a dependency-cache
MISS the parallel CI jobs each cold-resolve from the network, and `mutants`
drew a transient malformed-POM response (aether validateEffectiveDependency),
so the classpath never built and `clojure -M:test` never ran. bin/index-mutants
derives coverage by grepping the suite output for 'N failures'; with no output
it read ${n:-0}=0 and reported all four index paths as 'no test depends on it'
-- a real coverage gate turned into a FALSE coverage regression.
Two fixes:
- bin/index-mutants: a run_suite helper that returns NORUN when the suite emits
no failures line at all (compile/dep error, OOM, timeout), distinct from
'0 failures'. A baseline runs the UNMUTATED suite first and aborts loudly with
the environment error if it did not run or is not green -- so a broken
environment can never again masquerade as missing coverage. Verified locally:
NORUN -> clear abort; green suite -> 33/2/8/2, ok.
- .circleci/config.yml: restore-deps now resolves once with a 3x retry, so a
momentary repo/POM hiccup on a cold cache does not fail the job.
No behaviour change to the format or the library.
0.1.22
test: keep appendix-a to Appendix A, and stop the header misleading (…