Skip to content

Releases: Aloecraft-org/aloelite

Release list

v0.4.0rc1

v0.4.0rc1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 03 Sep 02:17
bc96d9b

[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
    in aloelite/pack.py and is pinned byte-for-byte by
    conformance/vectors/pack-v2.json (writer) and pack-v1.json
    (reader), generated by script/gen_pack_vectors.py and run by the
    Python and Rust suites; scenarios/pack.yaml pins the round trip.
  • Spec: encryption parameters. mount-api.yaml now declares
    chunk_size, pin and enc_mode on create_volume, pin on
    mount, and the bad_key / encryption_required outcomes on
    mount and change_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-core compiles to
    every target with zero cfg, and a CI job enforces that on every push
    — including running the conformance suite in a headless browser.
    aloelite-core implements 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.md is 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 one BlobStore blob (every target; durability
    per explicit checkpoint() / 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 where cfg is 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
    open takes the Web Lock that makes a volume file single-writer
    (busy when another Worker has it, never a silent wait). One
    dispatch table, held against mount-api.yaml in both directions by
    test. Integers cross as BigInt — timestamps are nanoseconds and do
    not fit a double — and bytes as Uint8Array. CI runs the tests
    under Firefox and packages the ES module with wasm-bindgen.
  • aloelite-fuse: the Linux FUSE daemon. A port of
    aloelite/fuse.py over fuser, 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
    installs fuse3 and runs the mount tests on every push.
  • aloelite-cli, and a contract for the command. The aloelite
    command in Rust, verb for verb with aloelite/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,
    which tests/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-wasip2 and 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
    and aloelite-fuse for Linux (x86_64 glibc and musl, aarch64),
    aloelite for macOS (both architectures) and Windows, the WASI
    component, the browser package, a SHA256SUMS, 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's candidates rather than as an
    entry of its own. doc/RELEASING.md walks a release end to end; the
    existing PyPI publish is unchanged and still runs on the same tag.
  • Real POSIX metadata. uid/gid/mode columns, atime/ctime,
    link (hardlinks, with nlink derived from active placements rather
    than stored), mknod for fifos and sockets, symlinks as a first-class
    node type, and user.* 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 the MAX(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/rw and an optional
    principal; by default at most one rw mount 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.py asserts the Python surface against
    mount-api.yaml in both directions, including each operation's
    locks flag, 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's ver field is now a gate. A blob from a newer build is
    refused with unsupported instead of being read with this build's
    field set and silently losing whatever the new version added; a blob
    with no usable version is corrupt. 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

  • unpack answers corrupt for 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 flip test_path.py lost on a
    fast CI runner.
  • Locks guard placement, not just content (ACC-11, D-6). rename
    and link were outside the excluded set, so a same-directory mv
    from another mount walked past a lock that a cross-directory mv
    honoured — and a WebDAV client holding a LOCK could have its next PUT
    land in a different node with no error anywhere. pack and unpack
    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.
  • copy and pack name 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.