Own the network: details, VPN, enterprise Wi-Fi, and a firewall that can also allow

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-24 16:31:52 -04:00
parent aba2d16ffa
commit b30bf40407
29 changed files with 4452 additions and 241 deletions
@@ -596,3 +596,126 @@ schema itself:
F9` against the **real compositor** with an isolated `XDG_CONFIG_HOME`. That
was true before this phase and is unchanged, but it is the one contract in
this wave that writes to the running keymap, so it wants a quiet moment.
## Phase 9 (Network & Sharing) — append below
Spec: `2026-08-24-network-sharing-redesign.md`. Connections became a full
network manager (per-connection details, forget, autoconnect, MAC
randomization, VPN import, hotspot, enterprise Wi-Fi, airplane mode, system
proxy), Firewall grew its add side and a zone browser, Printers gained per-
printer defaults and job hold/release, and Sharing got copy and structure work.
The last two GNOME punt rows in the app died with the "Owned by Fedora" card.
**Nothing here was run against a live harness.** Three agents edited the tree
concurrently. What *was* verified is listed per contract below.
### New contracts (1)
`quickshell/network-tools-contract`. The README count line moves **169 → 170**;
`setup/readme-contract` was run and passes ("170 contracts, as documented"),
counted the same way `panama test` collects the suite.
### Verified
- **`quickshell/network-tools-contract` — RUN END TO END, PASS.** It is safe to
run because it never reaches the machine: `env -i` with a stub directory first
on `PATH`, stubs for nmcli/gsettings/rfkill plus every D-Bus client, a
PyGObject stand-in on `PYTHONPATH` whose `require_version` always raises, and
both D-Bus bus addresses pointed at sockets that do not exist. It asserts the
first two of those before running the helper at all, and refuses to continue
if `nmcli`, `gsettings` or `rfkill` resolves anywhere but the stub directory.
Checked with a debug copy that it is doing real work rather than passing
vacuously: the stub's terse listing drives the shipped parse all the way to
`{"ip4":"192.168.7.42/24","gateway":"192.168.7.1","dns":["192.168.7.1",
"1.1.1.1"],"mac":"AA:BB:CC:DD:EE:FF","macRandomized":true,…}` while the three
secret properties in the same listing are absent from it.
- The libnm/D-Bus branch of `join-enterprise` is deliberately **not**
exercised — running it would add a real connection profile. It is covered
statically instead, by an AST walk that fails if any command list in the
helper carries a password-shaped identifier. The nmcli fallback is what the
dynamic half drives, and there the password is asserted present on the
editor's **stdin** and absent from **argv**, stdout, stderr and disk.
- **`quickshell/connectivity-contract` — static half run, and it found a bug.**
See "Still open" below. The new enum check reads the member list out of the
installed `quickshell-network.qmltypes` rather than a hand-kept list, so it
cannot drift the way the two bugs it catches did.
- **`quickshell/firewall-contract` — static half run, PASS** (everything up to
the first live `firewall-cmd` read).
- **`quickshell/printers-contract` — static half run, PASS.**
- **`quickshell/sharing-contract` — static half run, PASS.**
- **`quickshell/gnome-handoff-contract` — RUN END TO END, PASS** (source-only):
"ok (12 handoffs checked against 39 pages)" with `network` and `wifi` now in
OWNED.
- **`quickshell/health-ui-contract` — static half run, PASS.**
- **`PANAMA_SETTINGS_STATIC_ONLY=1 settings-pages-contract` — PASS.**
- **`setup/readme-contract` — RUN END TO END, PASS.**
- `bash -n` on every changed contract; every embedded `python3` heredoc compiled
separately with `py_compile`.
### Still open before the run
- **`Connectivity.qml:204` names an enum member that does not exist.**
`ConnectionFailReason.Authentication` is not a member; the real ones are
`NoSecrets`, `Unknown`, `WifiAuthTimeout`, `WifiClientDisconnected`,
`WifiClientFailed`, `WifiNetworkLost`. QML resolves it to `undefined`, the
switch arm never matches, and a wrong password is reported with the
fallthrough text. `NoSecrets` is almost certainly the intended member.
`connectivity-contract`'s new enum check fails on exactly this line and passes
on a copy with it corrected, so the check is confirmed working and the fix is
a one-word edit in a service file this phase's contract owner does not own.
**This is the one item blocking a clean run.**
- **The enum check is scoped to `Connectivity.qml`.** The same class of bug —
a plausible enum member that does not exist, resolving to `undefined` in
silence — can live anywhere that imports `Quickshell.Networking`, including
`modules/quicksettings/WifiList.qml` and the bar's status cluster. Widening
the sweep to every QML file that imports the module is a small change and the
obvious next one. Owner: whoever holds `connectivity-contract` next.
- **`network-tools-contract` cannot see the libnm branch.** That is a deliberate
trade, but it means the branch that actually runs on this machine (NM 1.56,
bindings present) is only ever checked by reading. A machine without PyGObject
would exercise the fallback for real; nothing here has one.
- **Two contracts still pin prose.** `firewall-contract` requires the add flow
to say "permanent" and "ask for your password", and `sharing-contract`
requires "never passes through Panama", "install it and this becomes a switch"
and "does not install software". The spec names all five phrases, so they are
pinned deliberately, but they are the needles a copy edit will trip.
- **`printers-contract` pins the option vocabulary exactly** — `media` ∈
{Letter, A4, Legal}, `sides` ∈ {one-sided, two-sided-long-edge,
two-sided-short-edge}. Adding a third settable option is meant to be a
deliberate act that updates this contract, but it will read as a surprise the
first time somebody tries.
- **`connectivity-contract`'s live half is unchanged** and still starts a
harness beside the running session, so it wants the same quiet moment it
always did. `PANAMA_CONNECTIVITY_STATIC_ONLY=1` runs the new source-only half
alone.
- Run order for this phase: the source-only contracts first
(`gnome-handoff-contract`, `PANAMA_NETWORK_STATIC_ONLY=1
network-tools-contract`, `PANAMA_CONNECTIVITY_STATIC_ONLY=1
connectivity-contract`, `PANAMA_SETTINGS_STATIC_ONLY=1
settings-pages-contract`, `setup/readme-contract`), then
`network-tools-contract` in full — it is hermetic, so it can run at any time —
then the read-only system contracts (`firewall-contract`,
`printers-contract`, `sharing-contract`), then the harness contracts
(`connectivity-contract`, `health-ui-contract`, `settings-search-contract`),
and `settings-pages-contract` last, as before.
### Docs updated in the same wave
- `services/SettingsSearch.qml` — the three "Managed by GNOME Settings"
connectivity entries are gone, and with them the duplicate **Printers** entry
that routed to `connectivity` rather than to the Printers page. Ten entries
replace them, all routing to `connectivity`: **Wi-Fi**, **Bluetooth**, **VPN**,
**Import a VPN**, **Hotspot**, **Airplane mode**, **Network proxy**, **IP
address**, **Forget a Wi-Fi network**, **Enterprise Wi-Fi**. Wi-Fi and
Bluetooth were rewritten rather than deleted: the spec lists eight additions,
none of which contains the word "Bluetooth", so deleting the lying entry
outright would have made a switch the page has unreachable by search.
- Checked against `settings-search-contract`'s pinned query list: none of its 21
ranked queries, nor the nine leaf-routing queries, matches any new or changed
entry, so no pinned top result moves. `connectivity` is a leaf in
`SettingsRoutes`, so the routing sweep holds.
- No settings docs or launcher commands were regenerated: this phase added no
schema keys, so there is nothing for `panama-settings-docs` or
`panama-settings-commands` to pick up. Verified by reading the diff — every
new setting here is system state (NetworkManager, firewalld, CUPS, gsettings),
not a Panama preference.