The canonical serialisation of a manifest, and its digest
Source:R/lib_manifest.R
manifest_canonical.Rdmanifest_canonical() renders a manifest as one line of JSON with
every object's keys in sorted order and every number at full double
precision. manifest_digest() is the SHA-256 of those bytes.
Arguments
- manifest
A manifest, as from
make_manifest().
Details
Why this is needed. Two manifests that record the same thing can easily
differ as bytes: R lists keep insertion order, so building meta
before results or the other way round gives different JSON, and a
signature over the JSON would then depend on the order a script happened
to assemble the list. Sorting the keys removes that. The precision
matters for a different reason: the default JSON writer emits four
significant digits, which is right for a human-readable report and wrong
for a record something will later be checked against, because 1/3
comes back as 0.3333 and no recomputation can match it.
Sign manifest_digest(), not the pretty
JSON. The digest is stable across the assembly order, across
pretty, and across a round trip through a file.
Full precision means full precision on every platform, which took more than writing enough digits. Seventeen significant digits recover any double exactly, but only through a reader that converts decimal to binary with correct rounding, and not every C library does – macOS arm64 reads the correct decimal for the largest double as infinity. So this package converts decimals itself rather than asking the platform, in integer arithmetic with a remainder that decides the rounding. A manifest written on one machine reads back bit-identically on another, and a caller does nothing to get that.
Examples
a <- make_manifest(list(b = 2, a = 1), environment = FALSE)
b <- make_manifest(list(a = 1, b = 2), environment = FALSE)
# the same content in a different order has the same digest
identical(manifest_digest(a), manifest_digest(b))
#> [1] TRUE
# full precision, so a recorded number can be checked later
m <- make_manifest(list(x = 1/3), environment = FALSE)
grepl("0.33333333333333331", manifest_canonical(m), fixed = TRUE)
#> [1] TRUE