Skip to main content

max / alloy

docs: measure the postgres and glibc premises Phase 0 left open Three things Phase 0 asked for and none of them needed the machine down. Prod postgres is confirmed rather than inferred. 16.14 on Ubuntu 24.04.4 LTS, one cluster, read over tailscale ssh. fw13 is 16.14 and astra 16.13, so the fleet is uniform on 16 and the Fedora 43 mismatch is exactly two majors. What that buys the 0.2 decision is a constraint instead of an unknown: whatever answer it reaches has to keep a 16 in reach of the migration_dry_run gate. The glibc claim in 0.2 was marked a reading and does not survive being measured. The premise holds, the image is glibc 2.42 against fw13's 2.39. The conclusion does not. The same source compiled on both hosts floors at GLIBC_2.34, and adding process spawning takes both to GLIBC_2.39 from the same two symbols, pidfd_getpid and pidfd_spawnp. The deployed makenotwork binary built on fw13 today already requires those. The floor tracks which symbols the code uses, not the host's glibc version, so building the apps natively on Alloy would not raise it above where fw13 already puts it. Recorded with its limits: it measures Rust std, not C dependencies built against Fedora headers, and not what linuxdeploy bundles alongside the executable. The cargo-installed tool set is written down for the first time. Eleven entries from .crates2.json, none of them rebuilt by clone-tree.sh and none of them listed in bootstrap. Same class as the signing keys in 0.3: it survives a wipe nowhere. sccache is called out because blocker 0.1 means nothing under ~/Code compiles until it is back, and sqlx-cli because it carries a non-default feature set that a reinstall from the bare name would drop. No code changed. Nothing pushed.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-21 18:44 UTC
Signed with PGP, not checked
Commit: 5edae137143ce087fdacd40e15440419a353c21e
Parent: b147a34
1 file changed, +45 insertions, -4 deletions
@@ -52,7 +52,9 @@
52 52
53 53 **node and npm are absent, and no `LANGS` value supplies them.** Sando's `code_smoke` gate builds two frontends with `npm run build` before it creates a database or boots anything, and the config text says outright that this is the one place a frontend failure is fatal. It is fatal there because the MNW server's `build.rs` deliberately is not: with no node it emits a warning and lets the Rust build succeed against whatever `static/dist/` already holds, which would silently ship the previous build's JavaScript. The `LANGS` validator accepts only rust, go, python and zig, so this is not a mint-time flag away.
54 54
55 - **No PostgreSQL server or client, and the versions do not match.** Sando's `migration_dry_run` and `cargo_test` gates restore prod dumps into a local scratch cluster over the unix socket. Alloy ships neither the server nor `psql`. Two problems, not one: the package is absent, and Fedora 43 offers 18.4 while fw13 and astra both run 16. A PG18 scratch cluster stops testing MNW against the version production runs, which is most of what the gate is for. Confirm what prod runs before sizing this.
55 + **No PostgreSQL server or client, and the versions do not match.** Sando's `migration_dry_run` and `cargo_test` gates restore prod dumps into a local scratch cluster over the unix socket. Alloy ships neither the server nor `psql`. Two problems, not one: the package is absent, and Fedora 43 offers 18.4 while fw13 and astra both run 16. A PG18 scratch cluster stops testing MNW against the version production runs, which is most of what the gate is for.
56 +
57 + Prod is confirmed, measured 2026-08-21 over `tailscale ssh root@alpha-west-1`: PostgreSQL 16.14 on Ubuntu 24.04.4 LTS, one cluster, client and server both 16.14. fw13 is 16.14 and astra 16.13, so all three hosts sit on 16 today and the fleet is uniform. That sizes the mismatch at two majors rather than at some unknown, and it means the gate's value is specific: it is the only place MNW's migrations meet the version prod will actually apply them on. Whatever answer 0.2 reaches has to keep a 16 available to the gate, whether that is a versioned Fedora package, a container, or the gate running somewhere other than fw13.
56 58
57 59 **The Tauri Linux build environment is absent.** goingson and balanced_breakfast release through `cargo tauri build`, which needs webkit2gtk-4.1, gtk3, libsoup3 and javascriptcoregtk at build time. fw13 carries the whole set as host packages today. The image carries none of them and its pkgconfig directory has no matching files, so the link step cannot resolve. Largest single gap by weight, and the one most obviously suited to a container rather than to the image.
58 60
@@ -64,7 +66,13 @@
64 66 2. It belongs in a distrobox container with an exported wrapper.
65 67 3. It is a workflow change that gets absorbed, as the absence of tmux already is.
66 68
67 - Two notes on the container answer. distrobox 1.8 is unconditional in both profiles and mounts the host home, so `~/Code` paths and `target/` directories are identical inside and out, and Bento's collect finds artifacts where it expects them. That makes it a genuine answer for node, postgres and the webkit deps. Against it: bentod's topology declares fw13 with `ssh = "local"`, so it runs cargo on the host itself, and routing those into a box needs a Bento change rather than a config edit. Pointing the other way, an AppImage links its build host's glibc, and Fedora 43's is newer than Ubuntu 24.04's, so building natively on Alloy would raise the glibc floor for everyone who downloads a release while building in a Debian-family box keeps it where it is. That last point is a reading about how AppImage works, not a measurement.
69 + Two notes on the container answer. distrobox 1.8 is unconditional in both profiles and mounts the host home, so `~/Code` paths and `target/` directories are identical inside and out, and Bento's collect finds artifacts where it expects them. That makes it a genuine answer for node, postgres and the webkit deps. Against it: bentod's topology declares fw13 with `ssh = "local"`, so it runs cargo on the host itself, and routing those into a box needs a Bento change rather than a config edit. Pointing the other way, an AppImage links its build host's glibc, and Fedora 43's is newer than Ubuntu 24.04's, so building natively on Alloy would raise the glibc floor for everyone who downloads a release while building in a Debian-family box keeps it where it is.
70 +
71 + **That last point was a reading, and the measurement does not support it.** Measured 2026-08-21 by compiling the same source on both hosts and reading `objdump -T`. The Alloy image is glibc 2.42 (`glibc-2.42-13.fc43`) against fw13's 2.39, so the premise about the hosts is right. The conclusion is not. A Rust binary exercising fs, threads, time, net and collections floors at GLIBC_2.34 built either place, identically. Add process spawning, which is what pulls the newest symbols the tree actually uses, and both hosts produce GLIBC_2.39 from the same two symbols, `pidfd_getpid` and `pidfd_spawnp`. The deployed `makenotwork` binary built on fw13 today already requires GLIBC_2.39 for exactly those two, plus `hypotf` at 2.35.
72 +
73 + So the floor is set by which symbols the code uses and by what rustc's own feature detection reaches for, not by the build host's glibc version, and on this code both hosts reach the same place. Building the apps natively on Alloy would not raise the floor above where fw13 already puts it.
74 +
75 + Two limits on that, stated so nobody over-reads it. It measures Rust std only: a C dependency compiled by `cc` against Fedora 43 headers could still pick up a newer symbol, and none of `openssl-sys`, `libsqlite3-sys` or the webkit glue was tested. And it says nothing about the libraries linuxdeploy bundles into the AppImage, which is a separate question from the executable's own floor. What it does settle is that the strongest stated argument for keeping the Tauri build in a Debian-family box does not hold in the form it was written.
68 76
69 77 ### 0.3 The six that degrade rather than block
70 78
@@ -81,11 +89,18 @@
81 89
82 90 The image build takes over an hour and can only be done on an x86_64 host, which today means fw13 itself. Build and mint before the machine goes down, not during.
83 91
84 - - Get the `rust-stage` witchbroom cell live on astra (infra `d3c88c6c`). It guards the failure class that has actually been biting: a `SHOP_REV` bump silently adding a system build dependency and taking the image build with it. This is the one item that stops the recurring failure rather than working around it.
92 + - The `rust-stage` witchbroom cell is live on astra. Infra task `d3c88c6c` completed 2026-08-11 and the cell ran in astra's 2026-08-21 sweep, so this is closed, not open. It guards the failure class that has actually been biting: a `SHOP_REV` bump silently adding a system build dependency and taking the image build with it. That guard is the one thing here that stops the recurring failure rather than working around it, which is why it stays written down after being closed.
85 93 - Build the image with whatever Phase 0.1 decided, on `main`.
86 94 - Write the ISO to the SanDisk. **Standing drive rule: the SanDisk is the install-media and scratch drive, the Samsung T9 is never a write target.**
87 95 - Record which commit the medium was minted from. A boot test against a stale image answers a question nobody asked.
88 96
97 + **The medium premise regressed, measured 2026-08-21.** An earlier reading dated 2026-08-11 said the SanDisk already carried an installer image, so Phase 1 could be run without minting anything. That is no longer a claim this file can make, on two counts.
98 +
99 + - There is no `output/*.iso` on fw13. `output/` holds `disk.raw` and `manifest-raw.json`, both dated 2026-08-14. The only ISO left in the tree is `output.prev/install.iso`, also 2026-08-14, which is a previous run's artifact and is not the mint of record for anything.
100 + - The SanDisk is not attached. `lsblk` shows the internal WD_BLACK SN7100 and the Samsung T9, and nothing else. What the drive holds cannot be checked while it is unplugged, so the 2026-08-11 reading is unverifiable rather than wrong.
101 +
102 + Practical effect on Phase 1: plan on an image build and a fresh mint, and treat any content already on the SanDisk as unknown until the drive is plugged in and read. The drive rule is unchanged by this. The SanDisk is the install-media and scratch drive and the one that gets written; the T9 is never a write target, whatever is or is not on the SanDisk.
103 +
89 104 ### 0.5 Make the rebuild real
90 105
91 106 `_private/infra/bootstrap/` already holds `repos.toml`, `clone-tree.sh` and `generate-manifest.sh`, and the cargo config is a tracked file with a symlink at the live path. Rebuilding the tree is cloning `_private` from astra and running one script. Before Phase 3:
@@ -94,6 +109,22 @@
94 109 - Push every repo to the remotes its policy allows. An unpushed commit on fw13 is the single most losable thing in this whole operation.
95 110 - Extend the bootstrap README to cover the `$HOME` material from 0.3, or at minimum to say where it lives.
96 111 - Dry-run `clone-tree.sh` into a scratch root and confirm it still completes.
112 + - Write down the cargo-installed tool set, which nothing in this file or in
113 + `_private/infra/bootstrap/` currently lists. It is `$HOME` material of the same class as
114 + the signing keys in 0.3: it survives a wipe nowhere, and it is not one of the things
115 + `clone-tree.sh` rebuilds. Read from `~/.cargo/.crates2.json` on 2026-08-21, eleven entries:
116 + `cargo-audit`, `cargo-deny`, `cargo-fuzz`, `cargo-mutants`, `cargo-sweep`, `samply`,
117 + `sccache`, `sqlx-cli` (built `--no-default-features --features postgres`), `tauri-cli`,
118 + and the two path installs `spag-mcp` and `spaghetti`, which come from the tree rather than
119 + from crates.io and so have to follow the clone rather than precede it.
120 +
121 + Three of these are load-bearing in a way worth naming. `sccache` is blocker 0.1: nothing
122 + under `~/Code` compiles until it is back, so it is the first install after the toolchain
123 + and before the first build. `tauri-cli` is what 0.2's Tauri question is actually about on
124 + the cargo side, and it is already a `cargo install` rather than a system package, so
125 + whatever 0.2 decides about the webkit libraries does not move this one. `sqlx-cli` carries
126 + a non-default feature set, so reinstalling it from the bare name silently produces a
127 + different binary.
97 128
98 129 ## Phase 1: the no-write boot test
99 130
@@ -173,12 +204,22 @@
173 204
174 205 Split deliberately, because machine time is the scarce half.
175 206
176 - **Can be done ahead, with fw13 fully in service:** every item in Phase 0. Closing the blockers, the selection decisions, the image build, minting the medium, getting the sweep cell live on astra, regenerating the manifest, pushing every repo, writing down where the `$HOME` material lives, and the dry-run rebuild.
207 + **Can be done ahead, with fw13 fully in service:** every item in Phase 0. Closing the blockers, the selection decisions, the image build, minting the medium, regenerating the manifest, pushing every repo, writing down where the `$HOME` material lives, and the dry-run rebuild. The sweep cell on astra was on this list and is done.
177 208
178 209 **Has to happen at the machine:** the Phase 1 boot test and its three judgments, the Phase 2 shrink, the Phase 3 install and unlock, fingerprint enrollment, and the display and suspend readings that only exist under a running session.
179 210
180 211 **Happens over time and cannot be compressed:** Phase 4.
181 212
213 + ## astra is not covered here
214 +
215 + This runbook covers fw13 and only fw13. There is no equivalent for astra, and the gap is worth naming so nobody reads a converted fw13 as a converted tree.
216 +
217 + Where an astra runbook starts is the astra role census: the same exercise Phase 0.2 runs for fw13, listing what the machine is actually asked to do before anyone decides what an image has to carry. Without it, the selection discipline has nothing to select against and every missing package becomes an argument for adding it.
218 +
219 + Converting astra is a different problem from converting fw13, and a larger one. fw13 is a build host: its state is caches, checkouts and `$HOME` material, and the caches regenerate. astra is a service host with state. It runs the git mirrors, the sweep, aarch64 builds and CI, and it is the machine the fw13 plan itself leans on for restore. Downtime there is not one person's laptop being unavailable for an evening, and the way-back at each step cannot be "reboot into the old install" the way it can here.
220 +
221 + Detail and the sequencing live in wiki `alloy-astra-migration`. Do not try to answer astra's questions inside this file.
222 +
182 223 ## Open questions this runbook does not answer
183 224
184 225 - What prod runs for PostgreSQL, which decides how much of the version mismatch matters.