nabla v2.3.0
Two things this release does that no earlier one could.
The workbook holds the numbers its own formulas produce
An .xlsx stores a formula and the answer Excel last got from it, and nothing keeps the two in step. The build edits the workbook as XML with no formula engine, so every cell downstream of a value it changes kept the answer it had before. Shifting the sample dates forward two years left 3,193 cached cells across 43 of the 50 sheets holding numbers their own formulas no longer produce; nb.IsOccurrenceDateλ alone had 978.
Nobody ever saw one, because Excel recalculates on open and replaces the lot. That is what made it worth finding: the file could be wrong in a way only a second tool could see, and everything that reads an .xlsx without a formula engine, from a diff to a converter to a web preview, reads the cached answer.
tools/refresh_cache.py recalculates the workbook in Excel and saves it. fullCalcOnLoad comes off with the stale values, so the workbook now opens reading correctly without recalculating, and Excel no longer asks to save a file you never edited. Nothing about the functions changes, measured rather than assumed: all 4,506 worksheet formula cells byte-identical, all 130 defined names byte-identical, the Advanced Formula Environment store byte-identical. The file grows by 247 bytes.
It no longer carries a path off the machine that built it
Excel stamps xl/workbook.xml with the directory a file was last saved from, so every release since v1.2.0 published one. The build strips it, the refresh strips it again because Excel puts it back on every save, and it joins the banned tokens so CI fails rather than trusting either step to have run.
What a full audit found
Thirteen readers across the build, the gates, the workbook, the LAMBDA sources, the prose and the CI, with every finding put to a second pass that tried to refute it.
nb.ScheduleValuesByItemsλandnb.ScheduleRatesByItemsλcompared a raw date against a converted one, the defect their siblings had until v2.2.0. The check added then could not see them: each reads its conversion, but only to hand it to the recursive call, so the first row came from the raw argument and every row below it from the converted one. With text period bounds the first returned 0 where the answer is 100, and the second the 20 January rate where the 1 January rate applies.nb.DebtSculptVariableLRVλ's worked example called its sibling and dropped its closing bracket.verify_signatureswas added in v2.1.0 for exactly this defect and structurally could not see it: it skipped the text on theEXAMPLESrow itself, leaving 43 of the 119 example blocks unread. It now reads all 119.functions.csvdisagreed with the workbook it is generated from:nb.Depreciateλ's signature cut off mid-parameter list, 31 descriptions carrying the raw OOXML escape for a line break, two starting with the help table's delimiter.docProps/app.xmldescribed a 49-sheet workbook that has 50.ATTRIBUTION.mdstill called the depreciation helpers ATO methods, which v2.1.0 retracted everywhere else.- Nine comment banners named or described the wrong function, and
nb.MaxColsλtold the reader it returns the minimum.nb.CountColsλpublished its signature with an empty second argument.
Gates
Six now, four in CI and two local. tools/verify_cache.py is new: it compares all 20,221 cached values against what the formulas produce, refuses to pass on fewer than 15,000 comparisons, and fails when any sheet contributes none. verify_workbook gained the sheet-list and build-path checks and stopped demanding fullCalcOnLoad. verify_previous_names now checks the two functions.csv columns nothing checked. CI dropped a dependency nothing imported, cancels superseded runs, and asks for read-only access.
All six pass, including 152 Excel assertions. Two cached cells moved against v2.2.0, both corrected help text on its own demonstration sheet.
Known and not fixed
nb.PeriodStartλreturns a date in the wrong month for month-end anchors:("31/1/2026", 1, "5/3/2026")gives 2 March where 28 February is the period start. The fix is anEDATErewrite, which changes results.nb.TimelineOffsetλdivides by zero on a daily or weekly timeline, andnb.Amortiseλcalls it unconditionally.nb.Depreciateλcarries a second copy of the same inference. Which intervals these suites support is a decision, not a repair.- The repository still states no licence for its own tooling.
ATTRIBUTION.mdis correct about the derived workbook and silent about everything else.
Full detail in CHANGELOG.md. nabla.xlsx is attached below.