solve-engine 1.0.0
The first stable release.
The work that got here was not adding features. It was nine agents trying to break the engine, a fuzzer, and a differential harness comparing every expression we could find against the previous release. They found sixty-odd defects. The ones that mattered were not gaps: they were confident wrong answers, and five inputs that killed the host process.
Wrong answers, now right
| Expression | Before | Now |
|---|---|---|
1 km > 500 m |
false | true |
2024-01-01 + 1 year |
Dec 31, 2024 | Jan 1, 2025 |
-40 C in F |
-104 F | -40 F |
1 cup in fl oz |
2.36e14 | 8 |
true == false |
true | false |
5 > 3 and 2 > 1 |
false | true |
sum(x*10, [5]) |
5 | 50 |
1.5 in French or Spanish |
150 | 1,50 |
12 - 25 - 2023 |
Christmas 2023 | -2036 |
$490 rounded to nearest hundred |
500 | $500.00 |
24:00 |
0 | an error |
[1,2;3,4]^2 |
0 | [7,10;15,22] |
Most of these were one decision applied incompletely, with the correct pattern already sitting next door. EQ/NEQ unified units while the four ordering operators did not, under a comment claiming they did. addBusinessDays walked the calendar correctly while + N days added milliseconds and landed on the wrong day across a daylight-saving boundary. fact refused past 170 while permutation, immediately beside it, would loop a trillion times.
It cannot take your process down
Five inputs used to kill the host outright. Each had a different mechanism, and none was catchable by the existing limits, because every one of those is checked between bytecode instructions while all of this work happened inside one:
sum(x, 1:100000000) heap fatal
transpose(a) * a heap fatal, from two individually legal operands
today + 100000000 workdays 13 seconds, then "Invalid Date"
gcd(4, arccos(2)) nine characters, an uninterruptible infinite loop
There is now an allocation budget that bounds user-controlled allocation in one place. Matrices are charged at birth, so instructions not yet written inherit the bound, and a matrix product is refused from its shape before anything is allocated. It costs 3.5% on the hot path.
Eight configuration fields that were declared as safety limits and read nowhere are now wired up or deleted. A limit that does nothing is worse than no limit, because it invites false confidence.
Precedence follows mathematics and C
2 ^ 3 ^ 2 is 512. Exponentiation is right-associative, which both BindingPower.ts and PrecedenceParser.ts already claimed in their comments while the code did the opposite.
Bitwise and shift operators take their C and JavaScript levels, so 1 + 2 << 3 is 24 rather than 17. Both parse tiers now read the same named constants, so they cannot silently diverge again.
This changes results in existing documents. It is the largest behavioural change in the release.
How this was verified
A fuzzer, seeded and shrinking, against both the expression grammar and the bytecode VM. 2.6 million cases, zero process deaths. It found what review had not: an instruction missing from the operand-width table, which desynchronised every bytecode scanner after a date literal and let a raw TypeError escape to the host; nine raw exceptions reachable through the public vm export; and a nine-character infinite loop. Every finding is shrunk to a minimal reproducer and committed to a corpus that replays on every test run. Run it with npm run fuzz.
A differential harness comparing 40,368 expressions against 1.0.0-beta.6, drawn from the documented examples, every string literal in the test suite, the parity corpus, and generated expressions. Zero process deaths against the previous release's 68. Of 1,942 differences, every one was classified; five were a regression, and it was fixed.
That regression is worth naming, because the test suite had concealed it. A conversion error was being swallowed by a following conversion, so 60 km/h in m/s answered 0.00 /s, a plausible number in a unit with no numerator. Value.toNumber() returns 0 for errors, so 52 sites computed with a zero they could not tell from a real one. A test.failing covering that exact expression had an assertion quietly flip from failing to passing while the test stayed red on a later line. Every such test is now a single assertion, with a scanner that fails the run if a new one is not.
6,733 tests across 283 suites.
Breaking changes
- Operator precedence and associativity changed as described above.
unitOfMeasurementResult.unitNamesis removed. Both branches of its ternary were identical, so it never did anything. Deriving unit names automatically is not sound:CandKshare a ratio and are distinguished only by an offset table, so a reverse index renders20 Cas "20 kelvins".parseTimeoutMs,executionTimeoutMsand thediceconfiguration section are removed. A wall-clock timeout is undeliverable in a synchronous engine, and the dice fields described notation the engine does not implement.maxDocumentLinesnow defaults to 100,000, raised from a declared-but-unenforced 10,000, because the repository's own tests already exceed that.- String literals no longer keep their quote characters in their value.
- Unpadded dates parse again, and a date is now distinguished from a subtraction chain by whether it is written as one run:
2024-5-3is a date,2024 - 5 - 3is 2016.
Security
The README now documents the posture, and every claim in it was checked against the source. There is no eval, no Function constructor and no code generation anywhere. The engine reads no files and spawns no processes. It does make network requests, and the honest version is more specific than "opt in": two packages registered by default reach out on their own, so 100 USD in GBP fetches a rate and weather in london calls a geocoder with no host configuration. The docs show how to build a package list that really is offline. There is one runtime dependency.
Also in this release
The symbolic solver was reworked while this release was being prepared.
solve(x^5-x=0, x) now returns all five roots, [-1, 0, 1, -i, i], where it
previously invented duplicates and dropped the complex pair; solve(x^3-x=0, x)
is exactly [-1, 0, 1] rather than carrying floating-point noise.
Known limits
- Two projected years in the inflation table await a real data source rather than invented numbers.
- Stock and knowledge data require a host-supplied provider. Currency and weather reach the network on their own unless you exclude them.