pub const CACHE_VERSION: u32 = 35;Expand description
Cache version - increment when format changes.
v1: Initial release with string-based Decimal/NaiveDate
v2: Binary Decimal (16 bytes) and NaiveDate (i32 days)
v3: Fixed account type defaults in CachedOptions
v4: Hash algorithm switched from SHA-256 to BLAKE3 — same 32-byte
output so the header layout is unchanged, but old hashes won’t
match new files. Bumping the version short-circuits stale
caches at the header check instead of paying the rkyv
deserialize cost only to fail the hash compare.
v5: Transaction.postings: Vec<Posting> became
Vec<Spanned<Posting>> (#1151). The inner posting bytes
gained a Span + file_id per entry, so old cache files
would rkyv-deserialize into the new type as junk. Header
check forces a rebuild instead.
v6: The #1163 newtype slices (#1169 Currency, #1171 Account,
#1172 Tag, #1173 Link, #1174 MetaValue) swapped variant
payload types from InternedStr/String to typed newtypes.
The archived layout coincidentally matches AsInternedStr
in most cases, but MetaValue::{Account,Currency,Tag,Link}
and Transaction.tags/links (plus the parallel Document
fields) changed their archive wrappers. Bumping the version
forces regeneration so we don’t risk rkyv reading old bytes
into a structurally-different ArchivedMetaValue.
v7: PriceAnnotation refactored from 6-variant enum to
{ kind: PriceKind, amount: Option<IncompleteAmount> }
(#1167). Old cache bytes for the enum’s discriminant would
deserialize as nonsense in the new struct layout.
v8: CostSpec.{number_per,number_total}: Option<Decimal> collapsed
into CostSpec.number: Option<CostNumber> where CostNumber is
a 3-variant enum (PerUnit, Total, PerUnitFromTotal)
(#1164). The archived layout is structurally different
(Option<Decimal> + Option<Decimal> → Option<discriminant + payload>); reading v7 bytes into the v8 layout would produce
garbage cost numbers. Bumping forces regeneration.
Subsequent #1164 follow-up commits converted CostNumber’s
variants from tuple form (PerUnit(Decimal)) to struct form
(PerUnit { value: Decimal }) so serde could apply
tag = "kind" for cross-boundary wire unification. The rkyv-
archived layout for a single-field struct variant is byte-
identical to the tuple variant (both pack Archived<Decimal>
positionally) — verified against rkyv 0.8.16 — so this change
does NOT require a separate version bump. If a future rkyv
version changes that encoding, OR if CostNumber gains
additional fields, bump CACHE_VERSION to the next value.
v13: CostNumber gained the Compound { per_unit, total } variant
(#1700) and the parser now emits it for {a # b} cost specs —
exactly the “gains additional fields” case the v12 note calls
out. Without the bump, a cache written by a pre-#1700 binary
serves the old misparse (Total{b} / PerUnit{b}) to fixed
binaries, resurrecting the bug for any previously-loaded ledger.
v14: green compound-cost conversion now retries past unparsable
pre-/post-hash NUMBER tokens like red (#1713); inputs with garbage
around {a # b} parse to different CostNumber values than v13
cached them as.
v15: EOF now terminates a line in the parser’s error-recovery walkers, so a
malformed FINAL line without a trailing newline emits its diagnostic
(#1884). Previously such a file parsed to zero errors and rledger check exited 0 on it. The cached errors differ, and a cache written
by a pre-fix binary would serve the silent-pass result to a fixed one —
resurrecting exactly the bug for every ledger already in the cache,
which is the worst case since the symptom is “no error reported”.
v16: a sign separated from its operand is no longer dropped. - 7.50 USD
(valid beancount) parsed as +7.50, and -,123.00 USD (malformed)
parsed as +123.00 with no diagnostic, because the sign landed
outside the AMOUNT node where nothing read it. Cached postings from a
pre-fix binary therefore hold the WRONG SIGN, and the malformed case
also holds an empty errors list. Serving either to a fixed binary
reproduces the original bug on every ledger already in the cache — and
a flipped sign is silent, so nothing downstream would flag it.
v17: balance / price values are no longer read as “the first NUMBER
token”. Parenthesized arithmetic evaluated to the leading operand
((1 + 5) / 2.1 USD asserted against 1, not 2.857…), and a split
numeral did the same (1,23,4.50 USD -> 1, a thousandfold error).
Cached directives from a pre-fix binary therefore hold those wrong
VALUES, and the malformed cases also hold an empty errors list — both
silent, so nothing downstream would flag them.
v9: CachedOptions gained a set_options: Vec<String> field
(#1340). It was previously dropped, so a cache hit lost the
record of which options the file explicitly set — making
resolve_effective_booking_method re-book FIFO/LIFO ledgers as
STRICT. The new trailing field changes the archived layout, so
old bytes must be regenerated.
v10: String literals are now escape-decoded at parse (\"->", etc.);
the stored narration/payee/meta/etc. bytes differ from the old raw
form, so a cache hit would serve stale, still-escaped strings.
v11: MetaValue gained an Int(i64) variant (appended last). Integer
metadata literals (key: 42) now archive as Int rather than
Number, and the new discriminant changes the enum’s archived
layout, so old bytes must be regenerated.
v12: CachedOptions gained display_precision, use_precise_interpolation,
and plugin_processing_mode — previously dropped, so a cache hit
silently ignored option "display_precision" "USD:0.0001" (formatting
fell back to inferred precision) and the other two settings. New fields
change the archived layout, so old bytes must be regenerated.
v18: arithmetic in a COST SPEC is now evaluated rather than truncated to
its first operand (#1939). {10.00 * 3 USD} previously archived a
cost of 10.00; it now archives 30.00. The layout is unchanged, so
nothing here would REJECT the old bytes — which is exactly why the
bump is required: a stale cache would keep serving the truncated cost
basis, and the file would keep failing to balance, on a build that has
the fix. Verified by hitting precisely that during development.
v19: arithmetic is now evaluated in METADATA values and BALANCE
TOLERANCES as well as cost specs (#1944). key: 2 * 3 archives 6 not
2, and ~ 0.005 * 2 archives 0.010 not 0.005. Values again, not
layout — and again the reason the bump is mandatory: a stale cache
would keep serving the truncated tolerance and keep rejecting a file
the fixed build accepts.
v20: account names accept any NON-ASCII character inside a component
(#1930). A ledger that previously failed to parse now yields
directives, and one that parsed may gain account names it did not
have. Layout unchanged, so old bytes would be accepted and a cached
PARSE FAILURE served on a build that can read the file.
v21: a #tag / ^link on a directive that does not take one is now a
parse error (#1949). A file that previously loaded clean can now carry
errors, so a stale cache would serve the old clean parse on a build
that objects.
v22: a metadata key now needs at least two characters, as in beancount
(#1955). A file using k: 42 previously loaded clean and now carries a
parse error, so a stale cache would serve the old clean parse on a
build that objects.
v23: a ^link is no longer accepted as a metadata VALUE (#1954), so a
file using ref: ^x moves from clean to erroring. A stale cache would
serve the old clean parse.
v24: tags and links are no longer accepted as custom / pushmeta
values (#1958), so a file using them moves from clean to erroring and
a stale cache would serve the old clean parse.
v25: transaction headers beancount’s grammar rejects are now parse errors
(#2008) - a third header string, a string after a tag/link, or junk
after the narration. Same reasoning as v24: those files move from
clean to erroring, and this was observed for real - the first
rledger check run against the fixtures after the change reported
only the old downstream E1001s, because the cache still held the
permissive parse.
v26: malformed cost-spec component lists are now parse errors (#2008 cases
1 and 2) - an empty comma-delimited component, or a token after a
component is already complete. Same clean-to-erroring move as v25, and
a separate version because a cache written between the two lands is
stale for this change even though it carries v25.
v27: a cost spec with no number the author wrote now archives number: None instead of an invented zero (#2008). { # CCY} used to become
Compound { per_unit: 0, total: 0 }, and a malformed spec had a number
scraped out of it. Both changed the ARCHIVED CostSpec, so a stale cache
would serve the invented number to a build that no longer produces one.
v28: a literal -0.00 now parses to an UNSIGNED zero (matching
beancount, whose parser yields Decimal('0.00')), where the green
path’s bare -n previously archived a signed zero. That is parser
OUTPUT, so a stale cache would keep serving -0.00 from a build that
no longer produces one.
v29: Posting::cost and Posting::price are boxed. rkyv mirrors the
in-memory layout, so ArchivedPosting changed shape — a v28 file read
as v29 would interpret an inline CostSpec as a relative pointer.
This is a layout change rather than a content change, so unlike the
entries above nothing about the ledger’s meaning moved.
v30: options declared in an INCLUDED file are no longer applied unless
they accumulate across includes (#2151). Options is part of the
cached payload, so a v29 cache replays the old resolution and
resurrects the behavior: a sub-ledger’s booking_method still
changing lot selection, a sub-ledger’s inferred_tolerance_default
still letting an unbalanced transaction pass. Caught exactly that way
while testing the fix – the matrices kept diverging until the stale
caches were cleared.
v31: Note gained tags and links (#2160). The parser always accepted
them on a note header and threw them away; now it keeps them, so the
archived layout has two more fields. Without the bump a cache written
by an older binary deserializes into the new struct and every note
comes back with no tags – the exact bug, resurrected from disk.
v32: a blank line before a note or document no longer eats its tags
and links (#2160 review). Parser OUTPUT, not layout: the archived
shape is v31’s, but a v31 cache holds the empty tag lists the old
parser produced, and this build would serve them as fact.
v33: ~ ends a balance’s amount expression, so 0.25 + 0.75 ~ 0.01 USD
parses instead of erroring; and a price now refuses a tolerance it
used to accept and half-read (#2191). Parser OUTPUT in BOTH
directions: a v32 cache holds the error for the balance and the
truncated 1.10 for the price, and this build produces neither.
v34: a query carrying a tag or link is now diagnosed (#2194). The
DIRECTIVE is unchanged – reject_tags_and_links records an error and
conversion still emits the Query, as it does for the other seven
directives that refuse tags. What moves is the error list, and that is
enough: a file with a tagged query used to parse clean and was
therefore cacheable, so a v33 blob exists for it, and replaying that
blob skips the parse that would now complain. Measured rather than
assumed: a pre-#2194 binary run on a tagged query reports 0 errors and
writes a cache, and this build reading that same blob reports 0 errors
WITHOUT this bump and 2 WITH it. The bump is what surfaces the
diagnostic.
v35: a balance tolerance whose currency disagrees with the amount’s, or which carries a second juxtaposed number, is diagnosed instead of read in part and discarded in part (#2193). Like v34 the DIRECTIVE is unchanged and the error list is what moves — and like v34 that is enough, because such a file used to parse clean and was therefore cacheable, so replaying its v34 blob skips the parse that now complains.
Public so rustledger-wasm can pin its own cache version against this one.
Both caches archive the same Vec<Directive>, so a parser change that
alters PARSER OUTPUT has to bump both — and on #1942 only this one was
bumped, which review caught rather than any test. See
loader_cache_version_is_pinned in rustledger-wasm/src/cache.rs.