mirror of
https://github.com/jmcorgan/fips.git
synced 2026-09-14 00:45:08 +00:00
The observability work shipped as JSON only, which left the operator's live
view saying `up` for a transport bound to nothing. That is the exact shape
of the bug the whole mechanism exists to end: the original OpenWrt failure
was expensive because the 802.11s link formed regardless, so nothing anyone
could see said the node was deaf. Putting the data in show_transports and
not in fipstop reproduced that at one remove.
The list gains real columns. Instance name and the thing a transport is
bound to are separate facts about separate columns, so they get separate
columns: `Bound to` answers one question with different answers per
transport type — a netdev for the interface-bound ones, the bound socket
address for UDP and TCP, a truncated onion for Tor, a remote MAC for a link
row. Packed into one label they left the netdev names ragged down the list,
and that is the column an operator scans to find the interface they are
looking for.
The State column carries presence for an interface-bound transport rather
than the lifecycle state. `up` is true from the moment the transport starts
and stays true while its interface is missing, so it is precisely the wrong
answer in the one case someone is scanning that column for; there is no room
to show both and only one of them is news.
A Policy column reads required or optional, and severity follows it: an
absent interface whose absence is normal — a dock adapter that is not
plugged in, a radio this board never had — is yellow, while an absent
interface the config says to expect is red. That is the same split the
daemon makes between staying Full and reporting Degraded; painting both red
would train the operator to ignore red.
Ordering is no longer arbitrary. show_transports iterated a HashMap, so the
array order was whatever the hash seed produced, different on every daemon
restart. Both render sites sort by ascending transport id, which is creation
order, so the list groups by transport type for free — and it fixes fipsctl
output as well as the view, which sorting in fipstop alone would not have.
The identifying columns sit at the left. The first column was Min, so it
absorbed every spare column of a wide terminal and shoved Instance and
Bound-to into the middle, away from the names being scanned. It is
fixed-width now, sized for the widest label that actually lives in it — a
link's tree glyph and direction — with Peer taking the slack.
The detail pane gains an Interface block: netdev, presence and how long it
has been held, carrier, absence policy spelled out as its consequence rather
than its config key, bind count (flagged when it has rebound) and failed
binds when there are any. Carrier is spelled out because presence is IFF_UP
— a bound interface with no carrier is a bridge with nothing plugged into
it, which is normal and should not have to be inferred from silence — and
the two counters separate an interface that is flapping from one that is
there and refusing to bind.
test(control): pin the show_transports interface block
docs/reference/control-socket.md states the schema of each query response is
pinned by the snapshots in src/control/snapshots/. For the interface block it
was not: show_transports.json is `{"transports": []}`, because build_test_node
keeps every runtime list empty, so the only thing pinned was the empty form.
show_routing.json was regenerated for req_own_loopback in the same series,
which is what makes the omission look accidental rather than considered.
The block is emitted by two hand-duplicated sites — the live handler and the
read-handle variant — that agree today with nothing enforcing it.
A separate node and a separate snapshot rather than a richer build_test_node,
so the nineteen existing snapshots keep the empty-state determinism they were
built for. The fixture starts a real transport on an interface no host has, so
presence is deterministically absent and carrier deterministically false
everywhere this runs, and the captured shape is the one an operator actually
meets: state `up` with the interface absent. That pairing is the whole reason
the block exists, so it is worth having a committed artifact that shows it.
since_secs joins VOLATILE_KEYS — it is elapsed time, so redaction pins the
key's presence without pinning a value that changes between runs.
insert_transport_for_test mirrors isolate_peer_acl_for_test: the snapshot
tests live in crate::control and cannot reach Node's private transports map,
and a narrow hook keeps the fixture honest rather than hand-authoring JSON
that nothing in the daemon produces.
fix(fipstop): make the transports table fit an 80-column terminal
The table's fixed columns sum to 90, plus six single-column gaps: 96 against
the ~77 usable inside an 80-column terminal's border and scrollbar. Ratatui
resolves an over-subscribed layout by shrinking every column proportionally,
so the overflow does not clip the rightmost column — it clips all of them, and
`mesh0 (optional)` rendered as `mesh0 (optio`. The marker is the one thing on
that row worth reading, and the absence of it is what says `required`.
80x24 is the OpenWrt serial console and the xterm/tmux default, so this is the
deployment target rather than an edge case.
Below 100 columns the table drops Tx and Rx and sizes the rest down. Those two
are the only columns whose absence costs nothing an operator is scanning this
table to find — they are byte counters, and the detail pane carries them in
full — while Instance keeps 18 because `mesh0 (optional)` is 16, and Bound-to
keeps 17 because that is a full MAC. Both row shapes carry the same seven
cells in the same order, so the narrow variant is the same list with its tail
cut, keeping one place where the column count is decided rather than two that
have to agree.
The detail view stacks instead of splitting below 110 columns. A 40% split of
an 80-column terminal leaves the table 32 columns for a layout needing 65 even
narrow, and ratatui spends all of it on the trailing columns — rendering
Transport, Instance, Bound-to and State at width zero, so the operator gets
blank rows. Stacking keeps both panes readable instead of both unreadable.
The regression test renders at 80 and asserts the marker. That is precisely
the gap that let this through: the existing test asserting `mesh0 (optional)`
renders at 110, and the one rendering at 80 asserted only the netdev name, so
between them neither covered the width where the layout breaks. Verified
against the defect — with the narrow tier disabled, the new test fails and the
other two still pass.
The insert_transport_for_test hook now lands here rather than two commits
earlier. Nothing called it before this commit's snapshot fixture, so the
commits in between failed cargo clippy --all-targets -- -D warnings on dead
code. It also carries the target gate its only caller has, so the definition
and the call are present on the same platforms.
Changelog entry for the view.
Document the view in docs/reference/cli-fipstop.md. The reference page
described the Transports tab as a tree of instances with per-link children and
said nothing about an interface block, so the page and the tab disagreed the
moment this commit landed. The tab-table row now points at a new Interface
block section covering Interface, Presence, Carrier, Bound to, On absence and
the two bind counters.
Reference
Information-oriented technical descriptions for lookup on demand. Reference content describes what is: wire formats, configuration keys, command-line flags, control-socket commands, default values, file paths, exit codes. It is consulted, not read end-to-end.
Reference is austere by design: minimal narrative, no opinions, no guidance on when to use a feature. The "why" lives in design/; the "how do I accomplish X" lives in how-to/.
Available Reference
| Document | Scope |
|---|---|
| wire-formats.md | All FMP and FSP message byte layouts, encapsulation walkthrough |
| configuration.md | Full YAML configuration reference for the daemon and gateway |
| security.md | nftables baseline, peer ACL, cryptographic primitives, rekey defaults, threat-resistance matrix |
| nostr-events.md | Kind 37195 advert, Kind 21059 traversal signaling, Kind 10050 inbox relays |
| transports.md | Per-transport statistics counter inventory |
| control-socket.md | Line-delimited JSON control protocol for the daemon and gateway |
| native-api.md | Native datagram API: the Rust surface, addressing and ports, errno table, ceilings, line protocol, command reference |
| cli-fips.md | fips daemon CLI: options, exit codes, environment, files |
| cli-fipsctl.md | fipsctl control-client: subcommands, options, exit codes |
| cli-fipstop.md | fipstop live-status TUI: tabs, keybindings |
| cli-fips-gateway.md | fips-gateway service CLI: options, exit codes, files |