From 838d56852e66b894a0835cf5b0e193bead8f7fc7 Mon Sep 17 00:00:00 2001 From: Paul Hammant Date: Sun, 9 Aug 2026 19:19:32 +0100 Subject: [PATCH] docs(CHANGELOG): backfill 0.506.0-0.509.0, which shipped unrecorded [skip actions] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VERSION said 0.509.0 while the newest CHANGELOG heading was [0.505.0]: four releases shipped with no section at all. The cause is structural. The release pipeline renames `## [current]` to the next version number, so a merge that adds no `[current]` section means the release records NOTHING — silently, with no warning. Of the five feature PRs merged in that window only #1464 carried a CHANGELOG entry; #1465, #1467, #1469 and #1472 did not. That is my omission across three of them. Reconstructed from the merge log rather than from memory. Each release window turned out to contain exactly one PR, so attribution is unambiguous: 0.506.0 #1465 nightly install refresh + git fetch --tags 0.507.0 #1467 Windows make install dropped the .exe suffix 0.508.0 #1469 fs.read / fs.read_binary error detail (+ the directory false-success bug found while testing it) 0.509.0 #1472 contrib/avcodec + the contrib-check link wiring Entries are written from the shipped code, not transcribed from commit subjects, and each keeps the detail that makes it useful later: why `ae --version` cannot detect a broken Windows install, why a sandbox refusal needs different wording from an I/O error, why the nightly's stale-toolchain failure reads like a GCC 16 bug and is not. Additions only — 122 lines, no existing section touched. Verified afterwards that VERSION now matches the newest heading and that none of the four new headings duplicate. (The pre-existing duplicate [0.435.0] and [0.497.0] headings are untouched; they are the same pipeline bug showing up earlier and are worth a separate fix so the rename cannot collide with an existing version.) Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b502734..6de1c27cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,128 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `main`, the release pipeline automatically replaces `[current]` with the next version number before tagging the release. +## [0.509.0] + +### Added + +- **`contrib/avcodec`** — in-process video decode, so a caller no longer needs + an intermediate file. A thin FFmpeg veneer following `contrib/sqlite`: C shim + plus `module.ae`, a catalogue entry with a pkg-config probe, and nothing + vendored — user programs link + `-laether_avcodec -lavcodec -lavformat -lavutil -lswscale` via `aether.toml`. + `make contrib` builds it where FFmpeg's dev libraries are present and SKIPs + cleanly where they are not. + + Motivated by aether-ui, which was spawning `ffmpeg` to transcode a whole clip + to raw RGBA on disk and reading frames back with `fs.pread`: 27.6 MB for six + seconds of 320x240, roughly 1.5 GB per minute of 1080p, and no workaround at + all for a *live* source such as a camera or network stream, where there is no + file to read. That intermediate is gone entirely; 300 frames of 640x480 + decode in 0.426s including compile time. + + The surface is deliberately narrow — open, take the next frame as packed + RGBA8888, close. Video only: audio, seeking and stream selection are future + work and none are needed to feed a renderer. Two ways to take a frame, + mirroring sqlite's blob accessors: `next_frame` allocates a fresh owned + string, while `next_frame_into` writes into a caller-owned buffer and + allocates nothing per frame — at 1080p30 that is 250 MB/s of copying avoided. + `fps()` returns a *ratio* rather than a float, because 30000/1001 does not + survive a float round-trip and a presentation-timestamp model needs the exact + value. + +### Fixed + +- **`make contrib-check` could not run any test backed by a system library** — + the runner builds each contrib test with `ae build --extra `, which + compiles the shim but has no way to pass `-l` flags, so such a test compiled + and then died at link with `undefined reference to avcodec_receive_frame` on + a box with every FFmpeg dev library installed. The gate was wired in but + structurally incapable of running. The test table gained an optional fifth + column naming the pkg-config modules an entry must link against; when set, + the runner stages an `aether.toml` workspace (the same shape + `tests/integration/sqlite_roundtrip` uses) so the flags reach gcc via + `get_link_flags()`, and SKIPs the entry when pkg-config cannot find them — an + absent system library is a provisioning gap, not a test failure. Contrib + modules with native dependencies are now genuinely gated rather than + nominally. + +## [0.508.0] + +### Fixed + +- **`fs.read` / `fs.read_binary` now name the path and the cause** — both + collapsed every failure into a bare constant (`"cannot read file"`, + `"cannot open file"`) with no path and no errno, so six distinct causes — + including sandbox denial and silent truncation — were indistinguishable. No + program could treat a missing optional file as benign while treating a + permission error as fatal. Reported from the aeb line, where a 79-target + parallel build reported `cannot read file` with no path, making an + intermittent content-hash failure undiagnosable. + + Failures now read `"/tmp/x: No such file or directory"`, `"/tmp: Is a + directory"`, `"/etc/shadow: Permission denied"`. A sandbox refusal gets its + own wording — `"blocked by sandbox policy (no fs_read grant for this path)"` + — because it is a policy decision, not a filesystem failure, and reporting it + as one sends whoever is debugging a grant list looking at the disk. The + `(bytes, length, err)` arity is unchanged, so existing callers keep working. + The error string is borrowed from thread-local storage and is valid until + that thread's next failed read; copy it with `string.concat(err, "")` to hold + it longer. + +- **`fs.read` on a directory reported success with empty content** — the + seekable fast path in `file_read_all_raw` never checked `ferror`, so a failed + read returned an empty string *as success*. Reading a directory is the + everyday trigger: `fopen("/tmp","r")` succeeds on Linux and `ftell` reports a + positive size, so control lands there and `fread` fails with `EISDIR`. Same + class as the #1116 silent-truncation bug, which fixed only the streaming + branch. A genuinely short read (the file shrank mid-read) still returns what + was read. + +## [0.507.0] + +### Fixed + +- **Windows `make install` dropped the `.exe` suffix, leaving a toolchain that + could not compile** — the install read the built artifacts *with* the + extension and wrote them *without* it, but `ae` looks for its compiler as + `aetherc.exe` next to itself. So `ae` could not find `aetherc` even though + the two sat in the same directory. `ae --version` kept reporting a healthy, + correctly-versioned toolchain throughout, because that path never needs the + compiler — the failure surfaced downstream as 57 aether-ui spec suites red + with 0 pass / 0 fail each, which reads as a harness fault rather than a + packaging one, and the accompanying `set AETHER_HOME=...` advice is a red + herring since the problem is a filename, not a search root. + + Fixed by installing with `$(EXE_EXT)` on both sides (a no-op on POSIX), and + `tools/ae.c`'s `/usr/local/bin` fallback now appends it too. `make install` + additionally **compiles a test program** with the installed `ae` rather than + only running `ae version` — the check that could not catch this, since it + never touches the compiler. + +## [0.506.0] + +### Fixed + +- **The CachyOS nightly built and tested a stale toolchain** — two causes, both + presenting as "the newer compiler on that box is broken" and neither being + that. First, a few tests link the *installed* artifacts rather than the build + tree, and the install had drifted three weeks behind, failing with + `undefined reference to aether_unwind_forget` — the nightly log showed only + `gcc --coverage link failed` without the linker's line, which reads like a + GCC 16 problem. `make install PREFIX=$HOME/.local` now runs as a pipeline + step (never sudo) with a freshness check, so drift is impossible by + construction. Second, the Makefile takes the highest **git tag** as the + authoritative version and falls back to the `VERSION` file only for tarballs, + so fetching commits without tags left the tree at current HEAD but the + previous tag and the build correctly stamped the *older* version; the sync is + now `git fetch --tags --prune`. + + Note for anyone reading versions by hand on that box: `ae --version` reports + the version *manager's* active install (`~/.aether/active_version`), not the + version compiled into the binary, so a correctly built 0.509.0 binary can + still say 0.417.0. Both the sweep guard and the install check use `strings` + against the compiled-in value for this reason. + ## [0.505.0] ### Fixed