Skip to content

Releases

0.1.31

Choose a tag to compare

@TimoKramer TimoKramer released this 09 Nov 20:34
b1a3953
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

Choose a tag to compare

@TimoKramer TimoKramer released this 06 Sep 14:47
b837dda
docs: clarify CBOR motivation and rewrite core guides (#28)

0.1.29

Choose a tag to compare

@TimoKramer TimoKramer released this 23 Aug 00:58
d14ad50
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

Choose a tag to compare

@TimoKramer TimoKramer released this 21 Aug 09:11
f44823a
bench: commit the tier-matched hako-ab table and wire it into the sui…

0.1.27

Choose a tag to compare

@TimoKramer TimoKramer released this 16 Aug 04:25
8f16e66
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

Choose a tag to compare

@TimoKramer TimoKramer released this 16 Aug 04:24
7f30333
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

Choose a tag to compare

@TimoKramer TimoKramer released this 16 Aug 03:34
ff160c6
In-place / splice editing: update-in an encoded value without decodin…

0.1.24

Choose a tag to compare

@TimoKramer TimoKramer released this 15 Aug 06:02
537ce19
docs: sharpen README motivation — ride the open format, and speak it …

0.1.23

Choose a tag to compare

@TimoKramer TimoKramer released this 15 Aug 06:01
1380d93
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

Choose a tag to compare

@TimoKramer TimoKramer released this 12 Aug 22:23
313d4b4
test: keep appendix-a to Appendix A, and stop the header misleading (…