Axiom 0.3.4
0.3.4 — 2026-08-26
Changed
-
The release published the whole changelog as its notes, and the
changelog outgrew GitHub.release.ymlpassedCHANGELOG.md
entire to--notes-file, and on this release that hit the ceiling:
HTTP 422: body is too long (maximum is 125000 characters)against
a 132,366-character file. All three platform builds had already
succeeded, so the failure was three sets of correct artifacts and no
release.It was always the wrong content as well — a reader opening
v0.3.4
wantsv0.3.4's notes, not every note since0.2.0. The publish
step extracts this version's own section now (7,032 characters), and
refuses to publish an empty body: a missing or empty section
stops the release rather than shipping notes that say nothing, which
is the same rule this repository applies to a golden with no
content. Checked against every released version — 0.2.0 through
0.3.4 all extract, the largest being 0.3.0 at 78,160.
No gate was added for this release: the thirty-seven gates that build
the compiler under test through gate_build_axc already cover it,
check-compat.sh holding the nine declared breaks and
run-stdlib-tests.sh the behaviour at 82 fixtures.
-
stdlib/Sys.axstops answering an Int that is sometimes an
errno. Twelve raw wrappers answer(Result Int Error)through
sysResult: the seven filesystem calls (sysWriteFile,
sysAppendFile,sysUnlink,sysMkdir,sysRmdir,sysRename,
sysFileSize) and five process calls (sysSpawn,sysWaitPid,
sysRun,sysRunPath,sysRandomBytes).IO.ax's wrappers
re-wrap rather than convert —Syshas the errno,IOhas the
path, so the code carries through and only the message is rebuilt.sysRunis the one that mattered. Its contract was three answers in
oneInt: an exit code,128+nfor a signal, and a negated errno
meaning the child never ran. It is split by the type now —Err
is the spawn failing,Okcarries whatever the child answered —
which is the distinctionSys.ax's own comment says a driver must
not lose, "could not start llc" against "llc rejected the
module".Census:
Sys.ax13 → 3, the library 30 → 13. Nine breaks
declared against0.3.4;runToolunwraps at the stdlib boundary to
127 becauseself_host/is a different slice.The reason this was deferred did not exist.
0.3.3recorded that
Syssits belowErrin the dependency order.Errimports only
Str;StrimportsMemandVec. There is no cycle, and
(import Err)inSys.axcompiles first try. An ordering claim
about an import graph is checkable in one command.
Added
-
A parameterised type answered through a bare
Intis refused.
Intis this language's universal heap handle and the tree relies on
it —mkSpandeclaresIntand answers aSpan— which is why
tyReprClashnames onlyBoolandFloat, and why the 2026-08-10
attempt at the general rule reported 21 of 271 files that were all
correct.A parameterised constructor is different in kind: the handle
keeps the address and throws the type arguments away, so nothing
downstream can recover what it holds. Swept 0 over 516 files, and
probed the other way —Result- andOption-through-Intrefused,
while a monomorphic struct handle, a monomorphic data handle, an
honestIntand a correctly declaredResultare all still
accepted.tests/diagnostics/498-param-through-int.axcarries the
defect and that control in one file.It closes a class that produced two silent defects during the
migration itself:IO.makeDirreturned a heap address where an
errno belonged withcheckprintingOK, anddriver.runTooldid
the same one layer up and surfaced asAX4003at run time.
Fixed
-
unwrapOrwith a constant is not a port, and it cost three
defects. Wherever a sentinel carried which failure, a fallback
value destroys it and nothing complains:Job.jobSubmit— a failed spawn became pid0, which is not< 0,
so the pool counted it live andsysWaitPid 0waited for any
child in the process group.302-jobhung rather than failed;
checkprintedOKthroughout.993-filesystem-verbs—(== (unwrapOr … 0) -2)is silently false,
so the case counted one fewer success and exited 1 where its own
first line says 77.305-path-search— printed the fallback for every failure, which is
the vacuous pass that fixture's own comment exists to prevent.All three are
matchnow, and the golden for305matches
unchanged — the assertion survived rather than being re-blessed
around. The rule, indocs/error-model.md:unwrapOris safe only
where the fallback is genuinely equivalent to the error. -
The stdlib boundary collapsed a
Resultonto the wrong constant,
and a missingoptstopped being survivable.runToolunwrapped
a tool that could not be started to127— "command not found", the
shell's convention, and the wrong answer here. Every caller in
driver.axseparates tool missing from tool ran and failed by
sign:(if (< rc 0)prints "optnot found on PATH; building
without mid-level optimisation" and continues, while any non-zero
non-negativercfails the build.127is positive, so CI went red
on all three platforms.check-driver.shexists to hold exactly this
— aPATHwithllcandccbut nooptmust still build — and it
did.The boundary collapses
Errto the negated errno now: the exact
contract every caller already tests. This was the fourth
unwrapOr-with-a-constant defect in the migration, and the first
introduced after the rule against it was written down. -
An assertion that counted a failure as a success. Auditing all 71
unwraps rather than waiting for the next gate found
(== (unwrapOr (sysFileSize f) 0) 0)in993-filesystem-verbs: the
expected size is0and the fallback is0, so a failed call and an
empty file were indistinguishable. Amatchkeeps them apart. The
other 69 are fire-and-forget cleanup, where the value is discarded,
or assertions against a non-zero value, where a failure surfaces as a
failed assertion. -
The generated API reference documented a contract that no longer
existed. CI caughtdocs/stdlib-api.mdstale on all three
platforms. Regenerating alone would have been worse than the
failure: the reference pulls its prose from each source's first
comment, and those still said "or-errno" — so it would have
shown the new signature above a description of the contract it
replaced. Four comments rewritten with it.
Found, not fixed
-
Twenty-nine of the thirty sentinels left are not failures, and two
ofERR-ADOPT-1's five slices are specified against the wrong
distinction. Classified by what the sentinel means — §5's own
split between expected failure and programmer error — slice 4's 25
declarations inself_host/are 21 absence (namedFieldIndex
answers "or -1";structFieldOfanswers "0 when the struct does not
declare the name"; they are lookups, and a lookup that finds nothing
has not failed) against one genuine failure, already bounded. Of the
13 left instdlib/, eight are absence and all five failures are
already decided —writeStrand the threenetcalls sit on hot
paths where an(Ok n)block allocates per call or per poll wake and
check-net.shasserts memory ratios on exactly that server.So the
Resultmigration is complete. What remains is a
different one: 29 lookups wantingOption, which is built in and
needs no import. Recorded asdocs/error-model.md§10.1 rather than
acted on, because renumbering the slices is a decision about that
document.compat/SENTINELSsays why the count stops at 13.