diff --git a/plan.md b/plan.md index 98d8065..53a9b54 100644 --- a/plan.md +++ b/plan.md @@ -4,7 +4,7 @@ Living planning document. Aim is to get DAPPS into the hands of node operators w ## Where we are now -The protocol is fully specified (`README.md`'s "On-air protocol" section, including F4 versioning policy). The implementation matches the spec; the on-air format is byte-validated against real BPQ in CI via `m0lte/linbpq` (Testcontainers-managed). Local apps talk to DAPPS via MQTT (durable, idempotent on `dapps-id`) or REST. TTL forwarding works across multi-hop topologies. F1 end-to-end source tracking, F2 multi-part messages, F3 `rev` polling (opportunistic + scheduled) all done. B5 passive-learning routing + B5.1 MeshCore-flavoured DSR alternative both shipped. B6.1 connected-mode probe-and-map with transitive `peers` discovery (probe results now feed B5's learned-route graph too), B6.2 HF-NVIS solicit-and-listen (on-demand + scheduled cadence), B7 single-counter discovery airtime budget with probe strategies, all live. **H3 RHPv2 bearer** for XRouter is done (DAPPS_NODE_BEARER=rhpv2; mainline BPQ RHP still awaiting upstream support). C1 single-file native binaries publish via CI on five platforms; C2 env-var seeding plus `dapps --show-config`; C3 `/Health`, `/Operational`, decision-events ring + structured journal mirror, MQTT heartbeat; C4 install/upgrade docs in the README; C5.1 update-availability banner + C5.2 triggered self-update via privileged `dapps-updater.service`. D1-D4 dashboard MVP. E1-E4 developer guide complete. Phase M (#81-#85) - MCP server at `/mcp` with 26 operator-facing tools. +The protocol is fully specified (`README.md`'s "On-air protocol" section, including F4 versioning policy). The implementation matches the spec; the on-air format is byte-validated against real BPQ in CI via `m0lte/linbpq` (Testcontainers-managed). Local apps talk to DAPPS via MQTT (durable, idempotent on `dapps-id`) or REST. TTL forwarding works across multi-hop topologies. F1 end-to-end source tracking, F2 multi-part messages, F3 `rev` polling (opportunistic + scheduled) all done. B5 passive-learning routing + B5.1 MeshCore-flavoured DSR alternative both shipped. B6.1 connected-mode probe-and-map with transitive `peers` discovery (probe results now feed B5's learned-route graph too), B6.2 HF-NVIS solicit-and-listen (on-demand + scheduled cadence), B7 single-counter discovery airtime budget with probe strategies, all live. **H3 RHPv2 bearer** for XRouter is done (DAPPS_NODE_BEARER=rhpv2; mainline BPQ RHP still awaiting upstream support). C1 single-file native binaries publish via CI on five platforms; C2 env-var seeding plus `dapps --show-config`; C3 `/Health`, `/Operational`, decision-events ring + structured journal mirror, MQTT heartbeat; C4 install/upgrade docs in the README; C5.1 update-availability banner + C5.2 triggered self-update via privileged `dapps-updater.service`. D1-D4 dashboard MVP. E1-E4 developer guide complete. Phase M (#81-#85) - MCP server at `/mcp` with 26 operator-facing tools. **Master TX kill-switch** - operator stop button + dev-time centralised URL signal, both enforced at the bearer chokepoints (AGW frame / RHP open / UDP send), removed before 1.0 (see "Open tasks" entry). What's left is in the "Suggested ordering" near the bottom plus the open H bearer-integration phase. The biggest remaining items are external-blocked (H1/H2 MeshCore hardware; H3 mainline-BPQ RHP awaiting upstream support - the XRouter half of H3 has shipped), parked (C5.3 scheduled auto-update - banner + one-click + `trigger_update` MCP cover today's operator population; F5 signing - design parked, see the F5 section for the recommended shape when it gets pulled forward), or not-yet-prioritised (Phase G second-language reference impl, scratchpad phone messenger app). @@ -39,6 +39,8 @@ What's left is in the "Suggested ordering" near the bottom plus the open H beare - **Inject `TimeProvider` so time-dependent code is testable** *(done)*. `TimeProvider.System` is registered as a singleton; `Database`, `OperationalMetrics`, `OutboundForwarderService`, `TtlSweeperService`, `UpdateChecker`, `ProbeSchedulerService`, `DiscoveryService`, `NodeProber`, `UdpDatagramListener`, and `UpdaterOrchestrator` all take it via DI. Every `DateTime.UtcNow` in those classes routes through it; every `Task.Delay(span, ct)` uses the `Task.Delay(span, TimeProvider, ct)` overload; `TtlSweeperService`'s `PeriodicTimer` uses the new `(TimeSpan, TimeProvider)` constructor. The `OutboundForwarderService` test-only ctor is gone - replaced with init-only `TickInterval` / `StartupDelay` setters. Tests in `TimeProviderInjectionTests` exercise the wiring end-to-end with `FakeTimeProvider.Advance(...)` (e.g. saving a row, advancing the clock by 90 s, asserting `DeleteExpired` soft-deletes it). 430/430. +- **Master TX kill-switch - operator toggle + dev-time centralised** *(done)*. Two safety mechanisms composed through one seam. (1) An operator master TX-stop button rendered on every dashboard page: `SystemOptions.TxEnabled` (default true) persisted in the systemoptions table, flipped via POST `/TxControl/{stop,resume}`. When closed, a sticky red banner shows on every Razor page with the block reason and a Resume button. (2) A centralised URL kill-switch the project author controls during the development phase: `TxKillSwitchPoller : BackgroundService, ITxKillSwitchSignal` polls a hardcoded HTTPS URL (`https://compute.oarc.uk/storage/public/folders/4803/dapps-devtime-killswitch.json`) every 5 minutes and stops TX if the JSON says so. Wire shape `{"txAllowed":bool,"reason":string?,"appliesTo":["*"|"M0LTE-*"|"M0LTE-2"]}` with glob-ish callsign targeting so one URL can pause one site without affecting the fleet. **Deliberately not configurable**: URL, cadence, 30-minute staleness window, and fail-open behaviour are all `const`s on `TxKillSwitchPoller`; operators cannot disable the polling, repoint it, or relax the timings. The whole subsystem is removed (not made configurable per-fleet) before 1.0; `docs/dev-time-tx-kill-switch.md` documents this transparently and is linked from `getting-started.md`. **Bearer-level enforcement** is the architectural commitment - `IDappsTxGate` is consulted at the four chokepoints where DAPPS bytes meet on-air emissions: `AgwFrameTransport.WriteFrameAsync` (gates RF Kinds C/v/c/D/M/V/K, lets admin / disconnect through so the BPQ session stays usable), `Rhpv2OutboundTransport.ConnectAsync` (before active OpenAsync = AX.25 SABM) and its `writeOutgoing` lambda (I-frames), `Rhpv2InboundService` writeOutgoing (replies on accepted-inbound sessions), and `UdpDatagramBackhaul.SendAsync`. Discovery, probes, polls, forwards and floods all flow through these chokepoints, so no application-layer service had to be touched. **TLS hardening**: the named `tx-kill-switch` `HttpClient` pins a fresh `SocketsHttpHandler` with default `SslOptions` (system trust store + chain / hostname / expiry validation) so any future tweak to the default handler can't silently weaken kill-switch validation; `PollOnce` adds a runtime guard that refuses to fetch over plaintext if the URL ever drifts off `https://`. Belt-and-braces over .NET's already-validating default. **Audit trail**: both signals write `tx-control` rows symmetrically - `bearer="ui"` for the local toggle (every press, including no-op presses, so operator intent is logged), `bearer="remote"` for remote-driven flips (first-poll-says-BLOCK and ALLOW↔BLOCK transitions; steady-state polls and failed polls don't audit). Files: `dapps.client/Tx/IDappsTxGate.cs`, `dapps.core/Services/SystemOptionsBackedTxGate.cs`, `dapps.core/Services/TxKillSwitchPoller.cs`, `dapps.core/Controllers/TxControlController.cs`. Tests: 27 across `TxGateTests`, `SystemOptionsBackedTxGateTests`, `TxControlControllerTests`, `TxKillSwitchPollerTests`, `TxKillSwitchPollerAuditTests`. PRs #126 (bearer seam), #127 (UI toggle + banner), #128 (poller, originally configurable), #129 (URL hardcoded + transparent docs), #131 (5-min cadence + TLS hardening + softer wording), #132 (symmetric audit). 644/644. + **Goal:** DAPPS core talks in terms of forwarding durable DAPPS units to neighbours, not in terms of opening a stream and speaking one specific session protocol. The current factoring around `IDappsOutboundTransport` + `Stream` was the right move to get AGW under an interface, but it is still too transport-shaped if DAPPS is going to support a datagram bearer such as MeshCore without contorting the rest of the system. This is high priority while the code is still in progress.