# Runtime observation and verification boundary `runtime` defines public, read-only observation values, a bounded Linux WireGuard Generic Netlink reader, and a pure comparison between an observed interface and the authoritative saved server document. The reader accesses only the configured interface and never changes the running interface. ## Observation values An observation records an optional UTC sample time and distinguishes an unknown interface, an unavailable read, a missing interface, and a present interface. A present interface carries its observed interface name and peers keyed by `PublicKey`; duplicate keys are retained as an explicit ambiguity signal. The comparison requires the currently configured interface name and rejects a different observed interface, even when its peer set happens to match. The peer set states whether it is unknown, unavailable, partial, or complete. Only a complete set can prove that a saved peer is absent or that an extra runtime peer exists. Each peer has AllowedIPs and statistics with their own availability. A valid zero byte counter is `Available(0)`. A peer with no recorded handshake is `Available(None)`. Unavailable statistics do not become zero or a disconnected state. Counters are current values from the server interface's perspective and may reset after restart; they are not history and do not establish online or offline status. ## Saved technical projection `SavedPeerProjection::from_document` includes every `[Peer]` section from the byte-preserving `ConfigDocument`, including valid routes unsupported by the narrow `domain::Client` address model and legacy IPv4-only peers. Routes are normalized and compared as complete sets, independent of ordering and duplicate entries. Aliases and managed creation dates are not technical runtime fields; metadata anomalies alone do not prevent comparison. Missing or ambiguous keys, malformed routes, structural document issues, and duplicate identities remain unverifiable and cannot be omitted to obtain a matching reduced set. `compare_peers(saved, expected_interface, runtime)` checks interface presence and identity, the complete public-key set, and each peer's complete AllowedIPs set. It reports a wrong interface, missing, extra, route-mismatched, duplicate, partial, and unverifiable cases. An empty saved peer set can match only a present expected interface with an explicitly complete empty runtime peer set. Handshake values, counters (including resets), endpoint changes, ordering, and sample time do not affect technical equivalence. The comparison returns only a technical result; it does not create online status, applied state, or history. This result is separate from M4's `domain::Snapshot` and applied marker. A successful technical comparison is necessary evidence for applying state, but it is not itself a durable M4 transition. Application restart orchestration joins saved aliases and representable addresses by public key only after full technical equivalence succeeds. If any authoritative peer cannot be safely represented for that narrower durable view, verification remains unverifiable and reservations are retained instead of supplying a fabricated subset. ## WireGuard Generic Netlink decoder `runtime::netlink::GetDeviceDecoder` is a pure decoder for one WireGuard `GET_DEVICE` dump. It consumes caller supplied datagrams and expected request sequence, local netlink port ID, family ID, and interface name. It has no socket or host access. The transport rejects truncated datagrams, enforces its monotonic receive deadline, and wipes its receive buffer when its lifetime ends. The decoder checks every netlink and attribute length/alignment, message type, sequence and port ID, Generic Netlink command/version, configured interface, nested peer and AllowedIPs records, duplicate fields and aggregate limits. It returns data only after an explicit `NLMSG_DONE` with an embedded zero error code. It rejects `NLMSG_ERROR`, interrupted/overrun dumps, malformed or incomplete data, and limits exceeded. Peer fragments are coalesced only when a repeated public key is adjacent across multipart messages and the fragment contains only that key and AllowedIPs; duplicate routes remain invalid. A complete empty peer list is valid. Server private keys and peer preshared keys are length-checked through borrowed input slices and are neither copied into decoder-owned structures nor returned. The result contains only public keys, AllowedIPs, last-handshake timestamps and current received/transmitted counters. Errors carry stable categories without raw message data. The decoder uses configurable aggregate limits with bounded defaults; future transport code must set request-specific limits explicitly. Protocol details follow the [WireGuard Generic Netlink specification](https://docs.kernel.org/netlink/specs/wireguard.html) and the [Linux Netlink message specification](https://docs.kernel.org/6.1/userspace-api/netlink/intro.html). ## Systemd restart job tracking boundary `service::RestartJobTracker` is the pure event state machine for one resolved primary systemd unit ID. The bounded zbus 5.19.0 adapter is internal and crate-private; public runtime mutation is exposed only through `Application::restart`, after an explicit restart choice. The adapter installs `JobRemoved` and systemd name-owner observation, calls Manager `Subscribe`, and resolves the configured target with read-only Manager `GetUnit` followed by the unit's `Id` property. It then submits `RestartUnit` using the original configured target and tracks the returned job object path together with the resolved primary ID, which is the unit name reported by `JobRemoved`. If `GetUnit` cannot resolve a loaded unit, or reading its ID fails, the operation stops with a redacted resolution failure; it does not load a unit or guess its canonical name. The signal stream is installed before resolution and restart, so a fast completion queued before the method reply remains available once the reply supplies the path. The pure tracker also supports a fixed bound of early events for transports that deliver events before the method reply; overflow fails closed. Only systemd's `done` job result is successful. Failed, canceled, timed-out, skipped, dependency, or unknown job results remain failures. Bus disconnection, manager owner change, subscription failure, and permission/request failures are separate redacted outcomes. `RestartDeadline` carries the operation's monotonic start and timeout so connection, match registration, Subscribe, unit resolution, RestartUnit, job wait, and later runtime verification can share one budget. The adapter drives zbus async futures with a standard-library thread waker and parks only for the remaining budget. zbus's `async-io` backend continues socket I/O on its own executor; its connection is short lived and is dropped after the adapter returns. Deadline expiry stops the application's wait; a submitted job may still complete. The adapter never cancels or retries the job, checks unit active state, or verifies the WireGuard interface. Ordinary adapter tests inject a fake event transport and never connect to the system bus. Real D-Bus behavior remains for the disposable systemd VM acceptance at M9. Protocol references: [systemd Manager D-Bus API](https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.systemd1.html) and [zbus 5.19.0 API](https://docs.rs/zbus/5.19.0/zbus/). ## Application restart orchestration `Application::preview_restart` binds a read-only preview to the current saved generation and resolves the configured interface, systemd unit, and timeout from that generation. `Application::restart` accepts an explicit choice. Cancel returns before backup, service, runtime, or persistence adapters run. A confirmed operation completes a `pre-restart` backup, submits a systemd restart, waits for a successful terminal job, reads and compares the complete runtime peer set within the shared monotonic deadline, then writes verification state through the saved-file journal. Only after that write succeeds does in-memory applied state advance and deleted-client reservations release. Failure reports identify the stage, whether a restart was submitted, whether it may still complete, the backup path when available, and whether manual recovery is required. A timeout does not cancel a submitted systemd job. A runtime mismatch or verification-state write failure does not roll back the VPN or release reservations. Commit and restore remain saved-file operations and never invoke the runtime or service adapters. Application unit tests use private fake adapters and an injected private monotonic clock. Public integration tests exercise cancellation, preview, restore, and saved-setting selection without confirming a host restart. These are synthetic fixture results. M7 implemented the TUI and management startup wiring, with synthetic workflow and PTY coverage. M8 also validated the Linux Rustix transport against a real WireGuard interface in the isolated Alpine fixture, including IPv4 peer, route, handshake, and counter observations (see [M8 evidence](evidence/milestone-8.md)). That fixture does not exercise the real zbus/systemd path: systemd/D-Bus behavior and end-to-end restart acceptance still require the disposable VM at M9. Real IPv6 capability and traffic remain unverified. ## Bounded Linux transport `runtime::transport::observe_interface` resolves the WireGuard Generic Netlink family through `nlctrl`, then sends only `WG_CMD_GET_DEVICE` for the supplied interface. It uses rustix 1.1.5's safe nonblocking socket, `poll`, and datagram APIs; no `SET_DEVICE`, subprocess, unsafe block, or host socket is used by the default tests. Family lookup and the complete multipart dump share one monotonic deadline. The datagram size is capped, `MSG_TRUNC` is detected using the returned full datagram length, the sender socket address must be kernel port 0, and decoder aggregate limits apply across the dump. Linux's Generic Netlink reply header `nlmsg_pid` carries the requester port ID (the destination); the actual kernel sender port ID is 0 in the datagram address. The transport validates both identities. A complete read is returned only after the decoder consumes successful `NLMSG_DONE`; kernel errno values map to distinct no-device and permission outcomes. Malformed data, transport errors, timeouts, and limit excess remain separate, redacted categories. Receive buffers are zeroized on every return path because device replies may include private and preshared key attributes, although the decoder borrows and does not retain those attributes. Runtime observation does not itself perform saved-peer comparison or mark state applied. M8 exercised this Rustix reader against a real Alpine WireGuard interface for IPv4 observations; real IPv6 capability and traffic remain unverified. See [M8 evidence](evidence/milestone-8.md) for the accepted scope.