Lumbridge is Apache-2.0, and decision 0023 quietly made it link
GPL-3.0-or-later. `crates/gpui/Cargo.toml` at the pinned revision carries
`ztracing.workspace = true` -- unconditional, not optional, not behind a
feature -- and `ztracing` is GPL-3.0-or-later, as are the `zlog` and
`ztracing_macro` it pulls. `sum_tree` asks for it too. The path is the ordinary
Linux build, not an `--all-features` artefact and not a dev-dependency:
lumbridge -> gpui_platform -> gpui_linux -> gpui -> ztracing -> zlog
-> ztracing_macro
`cargo deny check licenses` failed on it, exit 4.
This is the second thing decision 0023 got wrong by reading the manifests of the
crates it added instead of resolving the graph; the first was believing there
were two Zed Git sources when there are five. Both were found by a gate that had
never been run.
There was no feature to turn off, so the choice was to relax the licence policy,
drop the framework, or stop compiling the crate. `ztracing` is now redirected by
a `[patch]` table at `crates/lumbridge-ztracing-shim`, a first-party
zero-dependency no-op under Apache-2.0. `zlog` and `ztracing_macro` were
reachable only through it and leave the lockfile with it.
The shim is small because the usage is: nine `#[instrument(skip_all)]` sites
across `gpui/src/svg_renderer.rs`, `sum_tree/src/sum_tree.rs` and
`sum_tree/src/cursor.rs`, and nothing else. Upstream's own crate compiles to
almost exactly this whenever the `ztracing` cfg is off, which is every build
that is not a Tracy profiling build, so no shipping behaviour is lost. It is a
proc-macro crate deliberately: such a crate can export nothing but proc macros,
so an upstream revision that starts using `ztracing::Span` or
`ztracing::info_span!` fails to compile and names the shim, rather than
resolving to something plausible.
`scripts/workspace-guard.sh` gained a third gate asserting, against Cargo.lock
rather than the manifest, that no `ztracing`, `zlog` or `ztracing_macro`
resolves to a Zed source and that the patch table is still present. `cargo deny`
already checks this, and the duplication is the point: `scripts/ci.sh`
downgrades a missing cargo-deny to a warning unless `LUMBRIDGE_CI_STRICT=1`, and
that is how the licence closure went ungated once already. A `[patch]` is an
unusually quiet thing to lose -- delete the table and everything still compiles,
still passes, and is GPL again.
Removing the three GPL rejections exposed a fourth that had been sitting beside
them and was never reported separately: `libbz2-rs-sys` under `bzip2-1.0.6`,
reached through async-compression <- http_client <- gpui. It is BSD-style and
permissive with no copyleft, and is allowed in `deny.toml` with that reasoning
written down. `cargo deny check licenses sources` is exit 0 for the first time.
The Git-source allowances in `deny.toml` are all still needed; the patched
crate's own source was `zed.git`, which `gpui` still requires.
Decision 0025 records the whole of it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPYebLiN2w4TqnHUYGdECq
156 lines
8.5 KiB
Markdown
156 lines
8.5 KiB
Markdown
# 0023: The GPUI dependency moves to a pinned Zed revision, and the accessibility adapter is written before it moves
|
|
|
|
Status: accepted. Supersedes the dependency choice in decision 0017; that
|
|
record's disposition of Floem, its account of the licence closure, and its
|
|
reasoning about accent colour all stand.
|
|
|
|
Decision 0017 chose published `gpui 0.2.2` from crates.io over a Zed git
|
|
revision, accepted a known-unmet accessibility gate as the price, and described
|
|
the way back: "Reversing this decision means changing one dependency line and
|
|
one adapter implementation." Both halves of that sentence have since been
|
|
falsified. The published crate is not a release line that can be migrated from
|
|
later, and the adapter that was supposed to make the migration cheap was never
|
|
written.
|
|
|
|
## The published crate is not a channel
|
|
|
|
Measured against the crates.io API and Zed's `main` on 2026-09-01:
|
|
|
|
- `gpui`'s newest version is `0.2.2`, published **2025-10-22**. Every non-yanked
|
|
release was published in a seventeen-day window in October 2025, and nothing
|
|
has been published in the ten months since.
|
|
- Zed's `main` still declares `version = "0.2.2"` in `crates/gpui/Cargo.toml`.
|
|
Ten months of development, no version bump: nobody upstream is preparing a
|
|
publish.
|
|
- The platform layer has since been split into `gpui_platform`, `gpui_linux`,
|
|
`gpui_macos`, `gpui_windows`, `gpui_wgpu`, `gpui_web` and `gpui_apple`. All of
|
|
them inherit `publish = false` from Zed's workspace; only `crates/gpui` sets
|
|
`publish = true`. On `main` the `x11` and `wayland` features of `gpui` itself
|
|
are now empty markers, because the backends they used to select live in
|
|
`gpui_linux`.
|
|
- Published 0.2.2 has no `accesskit` dependency at all, and
|
|
`docs.rs/gpui/0.2.2/gpui/enum.Role.html` is a 404. At `ce48461e`,
|
|
`crates/gpui/Cargo.toml:50` carries `accesskit.workspace = true`.
|
|
- Published 0.2.2 renders through `blade-graphics`. Upstream rendering has moved
|
|
to `gpui_wgpu`.
|
|
|
|
So the choice was never "0.2.2 now, a newer published version later". Even if
|
|
Zed published tomorrow, the publishable crate no longer contains a Linux
|
|
platform backend. Staying means freezing permanently on an October-2025
|
|
artifact, and permanently failing the hard gate in `UX_VERTICAL_SLICE.md` that
|
|
says the accessibility tree must name each pane, its selected state, its
|
|
execution target, and its waiting state.
|
|
|
|
## A pinned revision is not an unpinned upstream
|
|
|
|
Decision 0017's objection was "a git dependency on an unpinned upstream". That
|
|
is not what is adopted here. With a full-SHA `rev`, a committed `Cargo.lock`,
|
|
and `deny.toml` naming the permitted Git sources, a git dependency is *more*
|
|
pinned than `gpui = "0.2.2"`, which is a caret requirement that would silently
|
|
accept a `0.2.3` nobody reviewed. The real cost is that bumping the revision
|
|
becomes a deliberate scheduled act. That cost is accepted and named here rather
|
|
than avoided.
|
|
|
|
The target revision is `ce48461eaadd16c65c31f835511ab96bd3b6e746`
|
|
(2026-08-31), which is the revision `spikes/gpui-accessibility-probe` already
|
|
pins and already proves compiles with stable accessibility IDs, workspace/pane
|
|
roles, selected state, and real `EntityInputHandler`/`ElementInputHandler`
|
|
implementations. Its toolchain is Rust 1.97.1, moving the workspace from 1.94.1.
|
|
|
|
## The adapter comes first, and it does not exist yet
|
|
|
|
Decision 0017 said the mitigation was "a narrow `a11y` adapter trait, introduced
|
|
now while there are few call sites", so that the migration would be "a swap
|
|
rather than a rewrite". No such trait, module, or file exists anywhere in the
|
|
workspace. The only trace is `apps/lumbridge/src/sidebar/model.rs`, where
|
|
`describe()` produces the screen-reader sentence, is tested, is called by
|
|
nothing, and carries an `#[allow(dead_code, reason = "wired up with the
|
|
accessibility adapter, decision 0017")]`.
|
|
|
|
Meanwhile the call sites stopped being few: `apps/lumbridge/src` now builds 147
|
|
elements and gives only 20 of them a stable identity.
|
|
|
|
Writing the adapter is therefore the first step and happens **before** the
|
|
dependency changes, on 0.2.2, where its bodies are no-ops. That keeps the
|
|
dependency swap a swap, as 0017 intended, instead of paying for the swap and
|
|
the wiring in one unreviewable change. The `#[allow(dead_code)]` is deleted by
|
|
that step rather than carried.
|
|
|
|
## Order, and why each step is alone
|
|
|
|
Each stage lands separately because each fails differently, and a combined
|
|
failure is unattributable:
|
|
|
|
1. **The `a11y` adapter, still on 0.2.2.** No-op bodies; wire the elements that
|
|
carry identity; give `describe()` a caller.
|
|
2. **The toolchain, still on 0.2.2.** Move `rust-toolchain.toml`, the workspace
|
|
`rust-version`, and the toolchain named in `.gitea/workflows/ci.yml` — which
|
|
hardcodes it twice rather than reading the file — to 1.97.1. Three minor
|
|
versions of new `clippy::pedantic` lints arrive as hard errors under
|
|
`-D warnings`. Isolating this stage is what stops a lint avalanche being
|
|
mistaken for a GPUI API break.
|
|
3. **`deny.toml`.** Add the Zed Git sources to `allow-git`. This record
|
|
originally said there were two, `zed-industries/zed` and
|
|
`zed-industries/font-kit`. **That was wrong: there are five.** The graph also
|
|
carries `xim-rs` (X11 input methods, feeding `gpui_linux` -- the IME path,
|
|
so load-bearing for a hard gate rather than incidental), `wasm_thread`
|
|
(reached twice, through `gpui_web` and through `scheduler` <- `gpui`), and
|
|
`scap`, which resolves in the lockfile without being reachable in this
|
|
target's graph and so is named before some later platform's build finds it
|
|
first.
|
|
|
|
The error is worth keeping visible rather than quietly corrected. It was made
|
|
by reading the manifests of the two crates being added, which is exactly the
|
|
reasoning that misses a transitive Git source; only resolving the graph
|
|
answers the question. `cargo deny check sources` is what caught it, on the
|
|
commit after the allowance was written -- which is the argument for running
|
|
that check at all.
|
|
4. **The dependency swap.** `gpui` and `gpui_platform` at the pinned rev;
|
|
`wayland`/`x11` requested on `gpui_platform`, where they still mean
|
|
something; and `Application::new()` — which does not exist at that revision —
|
|
becomes `gpui_platform::application()`.
|
|
5. **Real semantics in the adapter**, and the probe's AccessKit tree test ported
|
|
into `apps/lumbridge`.
|
|
6. **The probe changes job**: it stops proving AccessKit exists and starts
|
|
proving the next revision still compiles.
|
|
|
|
The renderer swap from blade to wgpu in stage 4 was called the least
|
|
characterised risk here, and at build time it cost nothing: the whole closure
|
|
compiled on the existing `libxkbcommon-x11` shim, with no Vulkan or GL loader
|
|
problem and no new native dependency. That is not the same as the risk being
|
|
disproven. Nothing in `cargo check`, `clippy`, or the test suite opens a window,
|
|
so the renderer has still never run. The risk moved from build time to first
|
|
launch; it did not go away.
|
|
|
|
The rest of stage 4 was smaller than expected. The entire API delta across the
|
|
ten-month jump was three signatures over nineteen sites: `Window::focus` taking
|
|
the `App` it used to reach through the window, `flex_shrink` taking the factor
|
|
it used to assume, and `Application::new` becoming
|
|
`gpui_platform::application()`. No crate outside `apps/lumbridge` needed any
|
|
change, because none of them imports a gpui type -- the narrow boundary
|
|
`ARCHITECTURE.md` insisted on is the reason this was an afternoon.
|
|
|
|
## What is knowingly still unmet
|
|
|
|
A passing unit test over an AccessKit `TreeUpdate` is not an assistive-technology
|
|
claim. The gate is not met until AT-SPI/Orca on Linux and VoiceOver on macOS
|
|
have been driven by hand. Stage 5 makes the semantics real; it does not close
|
|
the gate, and nothing in this record should be read as closing it.
|
|
|
|
## Enforcement
|
|
|
|
This record is not self-enforcing, and the previous one was not obeyed. Three
|
|
mechanical checks carry it instead:
|
|
|
|
- `scripts/workspace-guard.sh` asserts the gpui source and version in
|
|
`Cargo.lock` — the lockfile, because a manifest states an intent while the
|
|
lockfile states what would actually compile. It runs in the fast headless job,
|
|
which is not `continue-on-error`, so it gates every push.
|
|
- `deny.toml`'s `unknown-git = "deny"` plus an explicit `allow-git` list makes a
|
|
fourth Git source a CI failure. `scripts/ci.sh` now actually runs
|
|
`cargo deny check sources`; it previously ran only `check licenses`, and the
|
|
source policy had never once been executed.
|
|
- `AGENTS.md` names the gpui dependency, `rust-toolchain.toml`, the workspace
|
|
`rust-version`, and `deny.toml` as things a contributor or agent may not
|
|
change without a decision record.
|