pub const CACHE_VERSION: u32 = 29;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.
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.