v0.4.0rc1
Pre-release[0.4.0rc1] - 2026-09-03 (release candidate of 0.4.0)
v0.4.0rc1 · schema era 2
Schema era 2: the break-once release. A file written by this build
does not open on an older one, by design — PRAGMA user_version is 2
and a newer era is refused rather than half-read. The first open of an
era-1 file migrates it in place: additive columns and a millisecond ->
nanosecond rescale, crash-idempotent, with no other table data touched.
Era 2 is where the POSIX surface became real (ownership, hardlinks,
xattrs, fifos), where id generation moved out of SQL and into the host,
and where the cross-implementation contract grew teeth: the conformance
suite, the id and format vectors, and a spec that is asserted against
the implementation rather than describing it.
Added
- Pack format v2 (D-8). A pack now carries each node's uid, gid and
mode, its xattrs, and its retention policy, so a pack/unpack cycle no
longer silently strips the permissions FUSE set. v1 packs remain
readable forever and a pack from a newer build is refused rather
than half-read. Not carried, by decision: atime, ctime, and hardlink
identity (each placement restores as its own node). The codec lives
inaloelite/pack.pyand is pinned byte-for-byte by
conformance/vectors/pack-v2.json(writer) andpack-v1.json
(reader), generated byscript/gen_pack_vectors.pyand run by the
Python and Rust suites;scenarios/pack.yamlpins the round trip. - Spec: encryption parameters.
mount-api.yamlnow declares
chunk_size,pinandenc_modeoncreate_volume,pinon
mount, and thebad_key/encryption_requiredoutcomes on
mountandchange_pin— what the reference has taken since ENC-2
shipped, and what a port needs to run the encryption harnesses. rust/— the Rust workspace. Six crates on three targets (native,
wasm32-wasip2,wasm32-unknown-unknown).aloelite-corecompiles to
every target with zerocfg, and a CI job enforces that on every push
— including running the conformance suite in a headless browser.
aloelite-coreimplements the whole Mount API — schema and era
migration, the sixty-four templates as compile-time constants, the id
mint (D-1/D-2), the ENC-2 ladder, resolution, the streaming
descriptor, and every operation function-for-function with
operations.py— and passes all 94 conformance scenarios and every
vector natively and in a headless browser.doc/RUST_PORT.mdis the plan; D-7 is the storage
decision.aloelite-store: how a connection is opened, per target. Three
openers, each returning the same engine handle: a file path (native
and WASI, durability per transaction), a memory image loaded from and
checkpointed back to oneBlobStoreblob (every target; durability
per explicitcheckpoint()/close(), never on a timer, and a
snapshot is refused inside a transaction), and the browser's OPFS
pool from a Dedicated Worker, with export, import and delete. It is
the one crate in the port wherecfgis allowed. Tested natively, in
a browser, and in a dedicated worker; D-7 records why there is no
trait over the three.aloelite-wasm: the browser surface.Fs.call(op, args)runs
any Mount API operation by its spec name with the spec's parameter
names;serve(fs, self)turns that into the Worker protocol
({id, op, args}in,{id, ok}or{id, error: {code, message}}
out);Pool.install/Pool.open(name)is the OPFS pool, and
opentakes the Web Lock that makes a volume file single-writer
(busywhen another Worker has it, never a silent wait). One
dispatch table, held againstmount-api.yamlin both directions by
test. Integers cross asBigInt— timestamps are nanoseconds and do
not fit a double — and bytes asUint8Array. CI runs the tests
under Firefox and packages the ES module withwasm-bindgen.aloelite-fuse: the Linux FUSE daemon. A port of
aloelite/fuse.pyoverfuser, handler for handler, including the
write-handle model that keeps a second fd coherent with unflushed
writes (the shared per-inode dirty-extent overlay).doc/COMPATIBILITY.md
is re-established against a live kernel mount by
rust/aloelite-fuse/tests/mount.rs: hardlinks, symlinks, fifos,
user.*xattrs, real ownership and nanosecond times, sparse writes,
and device-node refusal (D-3). POSIX locks stay kernel-arbitrated per
mount, as in the reference; the cross-mount upgrade is D-4. CI
installsfuse3and runs the mount tests on every push.aloelite-cli, and a contract for the command. Thealoelite
command in Rust, verb for verb withaloelite/cli.py: the same
fifteen verbs, globals, flags, output lines and exit codes,put -r
/get -r, the three PIN sources with a terminal prompt. Porting it
wrote the contract the CLI never had,aloelite/config/cli.yaml,
whichtests/test_cli_contract.py(Python argparse) and
rust/aloelite-cli/tests/contract.rs(Rust verb table) now project
onto in both directions — a verb or a flag cannot exist in one
implementation alone; the file lists the few places the two
legitimately read differently. The Rust command also builds for
wasm32-wasip2and runs under wasmtime with the volume on a
preopened host directory; CI drives it there. With this the six
crates of the port are complete (D-7).- Releases are built by an action. Pushing a
v*tag now produces
the GitHub release itself: the Python wheel and sdist,aloelite
andaloelite-fusefor Linux (x86_64 glibc and musl, aarch64),
aloelitefor macOS (both architectures) and Windows, the WASI
component, the browser package, aSHA256SUMS, and the container
image on GHCR — with the release notes rendered from this file and
the pre-release flag derived from it. A candidate tag (v0.4.0rc1)
is recorded here under its entry'scandidatesrather than as an
entry of its own.doc/RELEASING.mdwalks a release end to end; the
existing PyPI publish is unchanged and still runs on the same tag. - Real POSIX metadata.
uid/gid/modecolumns,atime/ctime,
link(hardlinks, withnlinkderived from active placements rather
than stored),mknodfor fifos and sockets, symlinks as a first-class
node type, anduser.*xattrs. Device nodes are refused by decision
(D-3), not by omission. - Host-minted ids (D-1/D-2). Node and edge ids come from a per-mount
monotonic uuid7 mint fenced by the volume's high-water mark, replacing
SQL trigger minting. This removes theMAX(uuid7)read-back and with
it the single-owning-connection model, and it makes id generation one
unit-testable function per language instead of one per SQL dialect. - Mount access policy (D-4). Mounts carry
ro/rwand an optional
principal; by default at most onerwmount covers a subtree, with
overlap an explicit opt-in. Authorization layered above correctness,
not a correctness invariant. - The conformance suite is the port contract. 94 language-agnostic
scenarios plus byte vectors for the id mint and the encryption ladder.
tests/test_spec_projection.pyasserts the Python surface against
mount-api.yamlin both directions, including each operation's
locksflag, so an operation can no longer inherit a lock decision by
omission.
Changed
- All stored timestamps are nanoseconds (see Upgrading). The era-1
rescale is bounded by magnitude and is safe to re-run after a crash. pack'sverfield is now a gate. A blob from a newer build is
refused withunsupportedinstead of being read with this build's
field set and silently losing whatever the new version added; a blob
with no usable version iscorrupt. Older versions stay readable.- PI-1 narrowed to containers. Non-container leaves may hold several
active placements, which is what makes hardlinks possible; the
container graph is still a tree.
Fixed
unpackanswerscorruptfor a garbage or malformed pack blob (bad
MsgPack, a top-level array, a node without a type) instead of
escaping a raw decoder exception.- Mounting or resolving a volume BY NAME with duplicates now picks the
most recently created volume (created_at, then id). It picked the
greatest id, and volume ids are stateless uuid7s (D-1: no ordering
promise), so two same-named volumes created inside one millisecond
resolved by their random bits — a coin fliptest_path.pylost on a
fast CI runner. - Locks guard placement, not just content (ACC-11, D-6).
rename
andlinkwere outside the excluded set, so a same-directorymv
from another mount walked past a lock that a cross-directorymv
honoured — and a WebDAV client holding a LOCK could have its next PUT
land in a different node with no error anywhere.packandunpack
joined the set too. What a lock deliberately does NOT exclude —
ownership, times, xattrs,copy— is now recorded and pinned as
conformance data alongside what it does. copyandpackname each placement, not each node. A subtree
walk enumerates placements, and a hardlinked node may be named
differently at each one. Reading the node's own name gave every
placement the same name, so the copies collided and NODE-5 hid all but
one — a copy containing fewer visible files than its source, silently.
Upgrading
Timestamps are nanoseconds, not milliseconds (NODE-4). Anything
that renders an engine timestamp must divide by 1e9. This is the one
change that breaks code which never mentions it: three frontends have
been caught dividing by 1000 and rendering the year 56,672,999.
Era-2 files do not open on 0.3.x builds. Export before downgrading.