diff --git a/CHANGELOG.md b/CHANGELOG.md index 396bb3b..248774c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,14 +126,49 @@ pass. - **`.clj-kondo/hooks/jolt_ffi.clj` rewrites `jolt.ffi/defcfn` into an equivalent `defn`** so clj-kondo and clojure-lsp can see through the FFI macro. Without it every `gtk-*`/`g-*` binding reports as unresolved. -- **Never gate CI on `jolt `**: a `deps.edn` `:tasks` entry does not - propagate its child process's exit status (verified against jolt v0.6.3), so - `jolt test` prints failures and still exits 0. Use `jolt -M:` or a - `bb.edn` task. +- **Never gate CI on `jolt `**: on jolt v0.6.3 a `deps.edn` `:tasks` + entry did not propagate its child process's exit status, so `jolt test` + printed failures and still exited 0. Jolt fixed this in v0.7.28 and the task + form now exits with the command's status (measured as 7 on v0.7.29), but the + advice stands for a different reason: glitter's floor is v0.7.24, so the task + form is unreliable on part of the range it supports. Use `jolt -M:` or + a `bb.edn` task. - The codebase is uniformly `clojure-lsp`-formatted, including the ten files ported verbatim from Replicant. An earlier decision exempted those to preserve upstream diffability; it was reversed in favour of one project-wide style. +- **`deps.edn` declares `:jolt/min-version "0.7.24"`**, the oldest Jolt glitter + runs on, so the floor that was already documented in prose is now + machine-readable. The key is + [jolt#804](https://github.com/jolt-lang/jolt/pull/804), and a runtime below a + declared floor refuses to load rather than run. It does not fire today: the + Jolt that reads the key is v0.8.0 or newer, well above the floor, so a Jolt + old enough to have the UTC-date bug ignores it. It is declared for the next + break, which is why upstream shipped the key ahead of needing it. +- **Jolt's `jolt.ffi` arena release ([#802](https://github.com/jolt-lang/jolt/pull/802)) + does not affect glitter, checked rather than assumed.** That release moved + `ffi/write`'s value ahead of its offset, a change no runtime check can + distinguish since both spellings are integers, and flipped a fixed array + descriptor to `[:array element-type count]`. glitter has no `ffi/write` call + site and no `[:array ...]` descriptor anywhere in `src`, `test` or + `examples`, so neither can reach it. Nothing was renamed or removed in that + release either, so all 223 `defcfn` bindings, `foreign-callable` and the + `free-callable` retain/release bookkeeping stand unchanged; the new + arena-owned `ffi/callback` is an option, not a replacement. Verified against + `jolt v0.7.29-25-gd4e92a43`, and again on `v0.8.0-45-g5e0e7a96`, with the + unit suite and all twenty-seven live-GTK smokes green. +- **The pinned `jolt-lang/time` SHA moves two commits past `v0.0.7`, to pick up + its `:jolt/provides` declaration.** jolt + [#813](https://github.com/jolt-lang/jolt/pull/813) (RFC 0014) moved the + host-class provider table out of the runtime: a library now declares the JDK + classes it implements in its own `deps.edn`, and a class nobody claims fails + to resolve. Until + [jolt-lang/time#11](https://github.com/jolt-lang/time/pull/11) landed, nothing + claimed `java.time.ZoneId`, so on a jolt carrying #813 `jolt flights` died + outright and `glitter.nexus.action-log-test` was silently dropped from the + suite: 29 tests down to 26, 78 assertions to 67, exit 0. A green `bb test` + did not catch that; only the changed count did. The pin is deliberately ahead + of the `v0.0.7` tag because no release carries the declaration yet. ### Known gaps diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c3b5e0..a6bffe1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ this repo, but the pull request build is the authority. You need Jolt and a native GTK4: ```sh -jolt --version # developed against v0.6.3; re-verified on v0.7.24 +jolt --version # developed against v0.6.3; re-verified on v0.7.24 and v0.7.29 brew install gtk4 # macOS; on Linux use your distro's gtk4 + glib dev packages ``` @@ -37,6 +37,13 @@ from ~1.15ms to ~13.6us. See for the measurements. On an older Jolt the demo still runs, it just defaults its departure field to the UTC date. +`deps.edn` states that floor as `:jolt/min-version "0.7.24"`, so it is +machine-readable rather than only written down here. A Jolt below a declared +floor refuses to load the project instead of running it. Note this does not +change anything for an older Jolt today: the runtime that reads the key is +v0.8.0 or newer, comfortably above the floor, so a Jolt old enough to have the +date bug ignores the key entirely. It is there for the next breaking change. + `deps.edn`'s `:jolt/native` declares `glib-2.0`, `gobject-2.0`, `gio-2.0` and `gtk-4`, with Homebrew paths for darwin and `.so` names for linux. @@ -101,10 +108,13 @@ Each of these was a real bug at some point. Most are explained at length in [`docs/guide/`](docs/guide/index.md); the short forms are here so a reviewer can point at a number. -1. **Never gate CI on `jolt `.** A `deps.edn` `:tasks` entry doesn't - propagate its child process's exit status (verified against jolt v0.6.3), - so `jolt test` prints failures and still exits 0. Use `jolt -M:`, or - a `bb.edn` task; those already do it correctly. +1. **Never gate CI on `jolt `.** On jolt v0.6.3 a `deps.edn` `:tasks` + entry didn't propagate its child process's exit status, so `jolt test` + printed failures and still exited 0. Jolt fixed that in v0.7.28, and on + v0.7.29 the task form exits correctly (measured as 7). Keep using + `jolt -M:` or a `bb.edn` task anyway: glitter runs on jolt from + v0.7.24, and the fix only lands at v0.7.28, so the task form can still + swallow a failure on a supported version. 2. **`insert-before` covers two different cases**: a genuinely new child, and repositioning an *already-parented* child (a keyed reorder). diff --git a/README.md b/README.md index 53d3f81..33aa07e 100644 --- a/README.md +++ b/README.md @@ -124,11 +124,13 @@ smokes, each of which mounts a real window and exits non-zero on failure. **In CI, invoke the alias form, not the task form**: `jolt -M:test`, -`jolt -M:keyed`, and so on. Verified against jolt v0.6.3: a -deps.edn `:tasks` entry does not propagate its child process's exit status, so -`jolt test` reports failures on stdout and still exits 0, while `jolt -M:test` -correctly exits 1. The task shorthand is fine interactively; it cannot gate a -build. +`jolt -M:keyed`, and so on. On jolt v0.6.3 a deps.edn `:tasks` entry did not +propagate its child process's exit status, so `jolt test` reported failures on +stdout and still exited 0. Jolt fixed that in v0.7.28 and the task form now +exits with the command's status, measured as 7 on v0.7.29. The alias form is +still what CI should use, because glitter supports jolt from v0.7.24 and the +fix only arrives at v0.7.28, so the task form is unreliable across part of the +range this project runs on. ### Or via `bb` diff --git a/deps.edn b/deps.edn index cd80997..745e24a 100644 --- a/deps.edn +++ b/deps.edn @@ -1,5 +1,57 @@ {:paths ["src"] + ;; The oldest jolt glitter runs on, declared so a runtime below it refuses + ;; to load rather than run wrong (jolt #804). glitter is a library, and a + ;; library is the natural declarer here: it knows what its bindings need + ;; and the app pulling it in does not. + ;; + ;; 0.7.24 is the release that made (t/today) answer the machine's zone + ;; instead of UTC: the TZ-restore fix and the memoization that makes it + ;; affordable. Both are absent from v0.7.23. Without them + ;; examples/glitter/flights.clj opens its departure field on YESTERDAY for + ;; the first 10 hours of every AEST day, silently. That is the exact shape + ;; of failure this key turns into a message. See docs/guide/nexus.md. + ;; + ;; This does NOT fire today, and it is worth being honest about why. The key + ;; is only honoured by a jolt that reads it, and the runtime that reads it + ;; (#804) first shipped in v0.8.0, far above this floor. So every jolt that + ;; can enforce the floor already clears it, and every jolt old enough to have + ;; the date bug ignores the key the way it ignores any key it does not know. + ;; Until v0.8.0 shipped on 2026-09-01 the sharper form was "no released jolt + ;; reads the key at all"; that is no longer true, and the conclusion is + ;; unchanged. The declaration is forward protection, in place for the next + ;; break, which is the whole reason upstream shipped the key before needing + ;; it rather than after. + ;; + ;; Think twice before RAISING this, because a floor is enforced on consumers, + ;; not just here. A project that declares no floor of its own still gets + ;; refused at resolve-project when a dependency declares one it cannot meet, + ;; and the message names the dependency (:jolt/required-by). Measured against + ;; a :local/root dep declaring "0.9.0": the consumer is stopped before any of + ;; its code runs. So whatever glitter declares becomes a hard stop for + ;; glitter-gl, glitter-uikit and anyone else downstream, over a constraint + ;; they never chose. Raise it only for something glitter genuinely needs, and + ;; only to a version that is actually released. + ;; + ;; Deliberately NOT raised to 0.7.29 for jolt #802's ffi/write argument-order + ;; break: glitter has no ffi/write call site and no [:array ...] layout + ;; descriptor, so that break cannot reach it. Raising the floor for a change + ;; glitter does not use would refuse working runtimes for nothing, and a + ;; library's floor propagates to every consumer. + ;; + ;; And a correction worth keeping, because jolt's own changelog invites the + ;; opposite conclusion by using ffi/write as this key's motivating example: + ;; NO floor value protects an ffi/write consumer. Not "0.7.29", not "0.8.0", + ;; none. The populations are disjoint by construction, and that is provable + ;; rather than merely likely: #804 (which added the code that READS this key) + ;; has #802's merge commit as its SOLE parent, ahead by exactly one, so no + ;; jolt exists that reads the key and still carries the old write order. A + ;; runtime with the old order predates the reader and skips the key. Measured + ;; elsewhere: released v0.7.29 against a project declaring "0.8.0" ran 19 of + ;; 37 tests, dropped two namespaces silently, and exited clean. The key + ;; guards breaks from #804 FORWARD, and nothing before it. + :jolt/min-version "0.7.24" + ;; jolt-lang/time provides the java.time.* surface as a pure-Clojure ;; host shim, with juxt/tick (pulled transitively via jolt-lang/time's ;; own deps.edn) as the idiomatic date/time API on top — used by @@ -10,10 +62,20 @@ ;; just JVM Clojure) as of when they were pinned — re-verify live ;; before bumping either. ;; - ;; The time SHA is v0.0.7, and it is a FLOOR, not just a snapshot: it is - ;; the release that made (t/today) answer the machine's zone instead of - ;; UTC. Pinning anything older silently puts flights.clj's departure - ;; field back on the UTC date. See docs/guide/nexus.md. + ;; The time SHA is a FLOOR, not just a snapshot, and it now sits two commits + ;; PAST the v0.0.7 tag rather than on it. Two separate reasons, both of which + ;; break silently if the pin moves back: + ;; + ;; v0.0.7 itself is what made (t/today) answer the machine's zone instead of + ;; UTC. Pinning older puts flights.clj's departure field back on the UTC date. + ;; + ;; 33c5e98 is jolt-lang/time#11, which declares the java.time classes the + ;; library provides (jolt #813 / RFC 0014). Without it, a jolt carrying #813 + ;; cannot resolve java.time.ZoneId at all: `jolt flights` dies, and + ;; glitter.nexus.action-log-test is silently dropped from the suite, 29 tests + ;; down to 26 with a clean exit. It is untagged because no release carries it + ;; yet, so this pin is ahead of v0.0.7 deliberately. Move it to the tag once + ;; one ships. See docs/guide/nexus.md. ;; ;; The two use different Maven-coordinate conventions on purpose: ;; io.github.jolt-lang/time is a normal git-coordinate dep, but @@ -26,7 +88,7 @@ :git/sha "b6b12097ba973141fe87c1b4d6a27ddcce5d5929"} io.github.jolt-lang/time {:git/url "https://github.com/jolt-lang/time" - :git/sha "b11798fb597a0b685c960056e03f1bd2fee229c2"} + :git/sha "33c5e9831c046cfcbe68bc0c9c33e92568eaebb1"} org.clojure/tools.logging {:git/url "https://github.com/jolt-lang/logging" :git/sha "ec6747e55583118d881faf4ab41e68d7e587ce4c"}} diff --git a/docs/guide/examples.md b/docs/guide/examples.md index 6b61c2c..f04ac3d 100644 --- a/docs/guide/examples.md +++ b/docs/guide/examples.md @@ -173,9 +173,11 @@ invisible to something: 4. **If it's a smoke**, add it to `bb.edn`'s `smokes` list: otherwise `bb smokes` won't run it and CI-by-hand won't catch a regression in it. -A smoke must exit non-zero on failure. Do **not** gate it on `jolt `: -a `deps.edn` `:tasks` entry doesn't propagate its child's exit status, so -`jolt smoke` prints failures and still exits 0. Use `jolt -M:` or a +A smoke must exit non-zero on failure. Do **not** gate it on `jolt `. +On jolt before v0.7.28 a `deps.edn` `:tasks` entry didn't propagate its +child's exit status, so `jolt smoke` printed failures and still exited 0. That +is fixed now, but glitter's floor is v0.7.24, so the task form still can't be +trusted across every version this project supports. Use `jolt -M:` or a `bb.edn` task. See [`testing-and-tasks.md`](testing-and-tasks.md) for the full rationale and [`CONTRIBUTING.md`](https://github.com/jlt-commons/glitter/blob/main/CONTRIBUTING.md) for the invariant list. diff --git a/docs/guide/nexus.md b/docs/guide/nexus.md index 9a8d7cc..55419e6 100644 --- a/docs/guide/nexus.md +++ b/docs/guide/nexus.md @@ -583,3 +583,46 @@ v0.7.24 carries both: (t/today), jolt v0.7.23-12 (#712, no memo) ~1.15 ms (t/today), jolt v0.7.24 (#712 + #716) ~13.6 us ``` + +### The floor is declared, not just written down + +`deps.edn` carries `:jolt/min-version "0.7.24"`, so the floor above is +machine-readable and not only prose. The key comes from +[jolt#804](https://github.com/jolt-lang/jolt/pull/804): a runtime below the +declared floor refuses to load the project instead of running it, and the +floor is read from every dependency's `deps.edn` as well as the project's. A +library is the natural place to declare one, because it knows what its +bindings need and the app pulling it in does not. + +Be clear about what this buys today, though, because it is less than it +sounds. The key is honoured only by a jolt that reads it, and the runtime +that reads it first shipped in v0.8.0, which sits far above 0.7.24, so every +jolt able to enforce the floor already clears it, and every jolt old enough to +carry the UTC-date bug ignores the key the way it ignores any key it does not +recognise. Nothing +changes for an older Jolt: `jolt flights` still runs there and still defaults +its departure field to the UTC date. + +So the declaration is forward protection rather than a guard that fires now. +That is the same reason upstream shipped the key before it was needed instead +of at the next break, and it is why the value here is the floor glitter +actually has rather than the newest version around. + +One limit of the mechanism is worth stating, because jolt's own changelog +points the other way by using `ffi/write`'s moved argument order as the +motivating example for the key. **No floor value protects an `ffi/write` +consumer.** The code that reads `:jolt/min-version` arrived in +[#804](https://github.com/jolt-lang/jolt/pull/804), whose sole parent is the +merge commit of [#802](https://github.com/jolt-lang/jolt/pull/802), the change +that flipped the argument order. One commit, no siblings. So the two +populations are disjoint by construction: no jolt reads the key and still +carries the old order, and any runtime that does carry it predates the reader +and skips the key in silence. A sibling project measured it: released v0.7.29 against a +project declaring `"0.8.0"` ran 19 of 37 tests, dropped two namespaces +silently, and exited clean. + +That does not weaken the floor above, because the TZ-restore boundary is the +case the mechanism does handle: runtimes on both sides of v0.7.24 can read the +key. It only means a floor cannot be used to guard a break older than the +reader, which is worth knowing before reaching for one to solve a +write-order-shaped problem. diff --git a/docs/guide/testing-and-tasks.md b/docs/guide/testing-and-tasks.md index b72ecea..759bffa 100644 --- a/docs/guide/testing-and-tasks.md +++ b/docs/guide/testing-and-tasks.md @@ -119,12 +119,21 @@ hanging, no manual window-close needed to run these in CI. ## The exit-code trap: `jolt ` vs `jolt -M:` -**Verified against jolt v0.6.3**: a deps.edn `:tasks` entry does not +**Verified against jolt v0.6.3**: a deps.edn `:tasks` entry did not propagate its child process's exit status. `jolt test` (the task form) -prints failures to stdout and still exits 0; `jolt -M:test` (the alias -form) correctly exits non-zero. This isn't a glitter-specific quirk to -work around; it's how the `:tasks` wrapper behaves, and it applies to -every task in `deps.edn`, not just `test`. +printed failures to stdout and still exited 0; `jolt -M:test` (the alias +form) correctly exited non-zero. This was never a glitter-specific quirk; +it was how the `:tasks` wrapper behaved, for every task in `deps.edn`. + +**Jolt fixed it in v0.7.28**, under "A failing `:tasks` shell command +exited 0". Re-measured on v0.7.29-25-gd4e92a43 with a task whose command +exits 7: both `jolt -M:fail` and `jolt fail` answer 7. + +The alias form is still the right thing for a gate, for a narrower reason +than the original one. glitter declares `:jolt/min-version "0.7.24"`, and +the fix only arrives at v0.7.28, so anyone running a supported jolt in +between still gets a task form that swallows the failure. The alias form +is correct across the whole supported range, which is what a gate needs. **Always use `-M:` (or a `bb.edn` task, which already does this) to gate a build.** The task shorthand (`jolt test`, `jolt keyed`, ...) is fine