Skip to content

VM campaign — Ubuntu 24.04 and 26.04 by unit, every profile whole on three targets

Run overnight 2026-09-02 → 03 with scripts/vm_campaign.py. Two campaigns per unit on the Ubuntu targets at engine 118b237 (main, after the node backend merged), and three campaigns that install every profile whole from a clean snapshot — the shape an operator actually uses — on Kali, Parrot and Debian 13. The whole-profile runs are new to the harness (--whole-profiles --reset-each DOMAIN) and they found what the per-unit runs could not: two profiles that planned clean and failed partway.

Standing

Target Campaign Result
Ubuntu 24.04.4 243 units by name 224 installed+confirmed, 0 failed, 19 refused at plan time
Ubuntu 26.04.1 243 units by name 234 installed+confirmed, 0 failed, 9 refused at plan time
Debian 13 15 profiles whole 13 installed+confirmed, 1 stopped at its consent gate, 1 refused
Parrot 7.3, pass 1 15 profiles whole 8 confirmed, 1 consent gate, 1 refused, 5 failed at the first apt command
Parrot 7.3, pass 2 (D-038) 15 profiles whole 13 installed+confirmed, 1 consent gate, 1 refused
Kali rolling digital-modes whole failed after 44 commands, then confirmed after the fix below; 1.28 GB measured

"Confirmed" is the engine's own bar: every command completed and the re-probe found the software (D-031). The consent gate is rf-research stopping on a non-interactive stdin, which is the gate working — the campaign never affirms one (D-021). The refusal is workstation, whose code and codium needed the third-party-repository backend that was the maintainer's decision to make — made, and built, on 2026-09-03 (D-040); both editors moved to an opt-in editors profile the same day, so workstation is now seven archive-only packages.

Zero failures on either Ubuntu is the headline. The engine's 24.04 and 26.04 coverage was untested before this run — the capability matrix has never carried a 24.04 target, only Linux Mint 22.3 on the same base — and every unit that did not confirm was refused before anything ran, with a reason that is true of the archive and not of the engine.

What Ubuntu refuses, and why each is honest

Reason 24.04 26.04
No apt candidate in this release's archive aethersdr, gtk-meshtastic-client, m2kcli, mlat-client-adsbfi, odr-audioenc, python3-meshtastic, pythonprop (and its voacapl), readsb, rtl-ais, satdump, sdrangel, voacapl aethersdr, gr-gsm, soapysdr-module-rfspace
No install block declares this target arduino-cli, soapysdr-module-plutosdr arduino-cli, soapysdr-module-plutosdr
Third-party apt repository (unimplemented, the maintainer's call) code, codium code, codium
Node floor (D-037): the archive's nodejs is 18.19, the unit needs 20.19 openhamclock —
Retired on evidence: the NOAA APT satellites went off the air 2025-11-09 noaa-apt noaa-apt
Vendor .deb collides with the installed wsjtx-data (from jtdx, installed earlier in the same campaign) wsjtx-improved wsjtx-improved

The 26.04 gaps are already in capability-matrix.md's measured sweep; the 24.04 list above is that target's first measurement and is the record for it. Two are worth a second look by whoever next touches them: soapysdr-module-rfspace has left Ubuntu's archive between 24.04 and 26.04 (Kali lacks it too), and sdrangel remains the binary-backend unit on every target that does not package it.

The slowest confirmed units, for the runbook's timeouts: qlog 977 s and 1108 s, hamclock-next 872 s and 925 s, wsjtx 475 s and 459 s, js8call 413 s on 26.04. Nothing reached the 1800 s per-unit budget; the whole-profile runs used 3600 s and their longest was 585 s.

Profiles that do not install whole, per target

Every refusal above was checked against the profiles that list it. The plan resolves the whole transaction or refuses it (D-016), so one refused member withholds the profile:

Profile Ubuntu 24.04 Ubuntu 26.04 Debian 13 Parrot
electronics (11) m2kcli — — —
listening (23) readsb, rtl-ais, satdump, mlat-client-adsbfi — — —
propagation (12) openhamclock, voacapl, pythonprop — — —
satellite (4) satdump — — —
rf-research (2) — gr-gsm — —
workstation (9) code, codium code, codium code, codium code, codium

Five of fifteen on 24.04, two on 26.04, one on Debian and Parrot. Whether a member the archive does not carry should withhold the profile (D-016) or be deferred by name the way a config file is (D-035) was Q-017; the table is its evidence, and it was resolved on 2026-09-03 as D-039: the m2kcli, readsb, rtl-ais, satdump, mlat-client-adsbfi, openhamclock, voacapl, pythonprop and gr-gsm rows now defer by name and the rest of each profile installs. The workstation row did not — its members were refused for an engine gap, not an archive gap — until the apt_repos backend landed as D-040 and the two editors moved to editors. Whether this table now installs whole on the VMs is the next campaign's measurement, not this one's claim.

Measured 2026-09-03, Ubuntu 24.04, engine at 05f3db0, each profile from the clean-baseline snapshot (~/.local/state/hammunition-campaigns/ ubuntu2404-deferral-2026-09-03.md):

Profile Outcome Deferred, by the plan's own words Seconds
satellite installed+confirmed satdump — no candidate 101
electronics installed+confirmed m2kcli — no candidate 177
propagation installed+confirmed openhamclock — nodejs 18.19.1 below the 20.19 floor; pythonprop, voacapl — no candidate 819
listening installed+confirmed mlat-client-adsbfi, readsb, rtl-ais, satdump — no candidate 233
workstation refused (plan) — code, codium: engine gap, as the classification requires 1

Four of the five that refused whole on 2026-09-02 now install whole, with every deferral printed under Will NOT happen naming the member, the reason and the by-name command that shows the refusal in full. The fifth refused for the reason D-039 says it must. The deferred names are from a --dry-run of each profile on the same VM after the campaign, because the campaign report records outcomes and seconds, not plans.

What the whole-profile runs found, and what was fixed

digital-modes on Kali planned clean and failed after forty-four commands. The plan checked every package for a candidate and every .deb for a conflict with what was installed — on a clean machine, nothing. It did not ask what the transaction's own apt step would pull in: jtdx brings wsjtx-data, and the wsjtx-improved vendor .deb ships the same pixmap. fldigi and WSJT-X had built by the time dpkg refused. Two fixes: the plan now refuses that pairing by name, and the profile no longer lists wsjtx-improved (its page says how to install it alone). Re-run whole from the clean snapshot: 45 commands, confirmed, 1.28 GB installed measured as df before minus after with the 0.9 GB of build trees and the apt cache subtracted.

Five Parrot profiles planned clean and failed at the first apt command, in one second each. Parrot's clean baseline installs 197 of its 3,801 packages from parrot-backports; main's -dev packages depend on their runtime at an exact version the machine no longer has, and apt will not downgrade the runtime to build against it. The same skew was seen on 2026-08-30 (vm-campaign-digital-modes.md: glfer and xwefax, the GTK dev chain) and worked around by hand that night. This time it was measured across every failed line and put in the engine as D-038: the plan asks apt-get install --simulate for every transaction with apt work, reads the package apt refused to downgrade and the release it was installed from, retries once with --target-release naming that release, and lists in the plan every package that release alone supplies. Pass 2 on the same clean snapshot: digital-modes 585 s, electronics 93 s, listening 185 s, logging 427 s, propagation 515 s, all confirmed.

Debian 13 had no surprises: thirteen of fifteen whole, the other two the gate and the repository refusal, and it is the target that proved the Parrot failures were Parrot's — the same five profiles installed there untouched (digital-modes 554 s, propagation 505 s, logging 441 s).

Two harness fixes came out of it: the CLI line-buffers stdout so a redirected install log fills as it runs (the first campaign's reports buried every error above the tail window), and the campaign report files a declined consent gate in its own bucket rather than as a failure.

An engine gap the campaign named but did not close

Both partway failures had the same shape: the plan is complete about what apt knows, and anything apt only learns by trying still surfaces after the machine has been touched. D-038's simulate step closes the apt half. The .deb half is still open — apt-get install ./file.deb does not simulate a file that has not been fetched, and the engine fetches during execution. Fetching and verifying every artefact, and simulating every .deb against the post-apt state, before the first system modification would make "the plan passed" mean what an operator reads it to mean. It is a reordering of execute.py, not a new backend, and it is recorded here so it is built on evidence and not forgotten. Built 2026-09-03: every fetch now runs first, and a plan holding a .deb simulates it together with the apt step after the fetch and before either installs.

Measured the same day, Debian 13: install wsjtx-improved --yes ran the fetch first (67,711,336 bytes from SourceForge, 45 minutes at that mirror's pace), then apt-get install --simulate --yes -- <cached .deb> (exit 0), then the install-deb action; wsjtx 3.2.0 installed, three steps confirmed. The transaction record exposed one more D-031 gap: it read verified: true with checks: []. A vendor .deb's package is not in the apt step and the binaries check skips deb formats, so nothing had asked apt whether the package landed. verify_effects now probes each .deb's declared deb_package; the uninstall-and-reinstall that followed recorded {'kind': 'package', 'subject': 'wsjtx', 'confirmed': True, 'detail': 'installed 3.2.0'}.

The repository backend, measured on 2026-09-03

The one refusal every target shared — code and codium behind an apt_repos block this engine could not act on — closed as D-040 and was run on two of the VMs the same day. The rows below are the engine's own bar again: every command completed and the re-probe found the software.

Target Run Result
Ubuntu 24.04, apt 2.8.3 install code, no variable, no terminal exit 3, nothing written — the gate stopping
Ubuntu 24.04 install code, variable =1 exit 3, refusal names the fingerprint it should hold
Ubuntu 24.04 install code --yes, variable = fingerprint confirmed, 7 commands; 640-byte binary key from Microsoft's armored .asc; code 1.136.0 from packages.microsoft.com
Ubuntu 24.04 install code again 0 commands — already installed, repository already ours
Ubuntu 24.04 uninstall code --yes confirmed, 4 commands; both files gone, apt-cache policy code has no candidate
Debian 13, apt 3.0.3 install editors --yes, both variables confirmed, 11 commands; VSCodium's binary pub.gpg kept at 2256 bytes; code 1.136.0 and codium 1.126.04524
Debian 13 uninstall editors --yes confirmed, 6 commands, four files removed
Debian 13 install codium --dry-run with a hand-written vscodium.sources present exit 2, refused as foreign, nothing changed
Parrot 6.x install codium --yes, package removed first, no variable confirmed, 1 command, from deb.parrot.sh echo/main; note not added (D-022); no gate, no files, no consent record

workstation itself is now seven archive-only packages: install workstation --yes on the same Ubuntu 24.04 VM planned minicom and tio (the other five were already present from earlier profiles), one apt command, confirmed, and the serial-terminal suggestion respected the screen already there.

Left with the maintainer

  • ~~Q-017, above.~~ Resolved as D-039, 2026-09-03.
  • ~~The third-party-repository backend (code, codium on every target).~~ Built as D-040, 2026-09-03.
  • Ubuntu 24.04 as a declared target in containers/targets.yaml and the capability matrix, now that it is measured — or Linux Mint 22.3 as its stand-in, which is the current state.