Skip to content

feat: centralised TX kill-switch URL poller (PR 3/3) - #128

Merged
M0LTE merged 1 commit into
masterfrom
feat/tx-gate-pr3-poller
May 6, 2026
Merged

feat: centralised TX kill-switch URL poller (PR 3/3)#128
M0LTE merged 1 commit into
masterfrom
feat/tx-gate-pr3-poller

Conversation

@M0LTE

@M0LTE M0LTE commented May 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

PR 3 of 3 for the master TX-stop work. Fills the `ITxKillSwitchSignal` seam from #127 with a real `BackgroundService` that polls a configurable URL and gates TX on the result.

  • `SystemOptions` adds `TxKillSwitchUrl` (empty = disabled), `TxKillSwitchPollSeconds` (default 60, min 5), `TxKillSwitchFailOpen` (default true), `TxKillSwitchStaleSeconds` (default 600, min 30). All round-trip through `SystemOptionsStore`.
  • `TxKillSwitchPoller` is the `ITxKillSwitchSignal` (singleton) and a hosted service. Reads `IOptionsMonitor` on every tick so flipping URL or interval via `/Config` takes effect without restart.

Wire shape

{ "txAllowed": false, "reason": "contest QRM", "appliesTo": ["M0LTE-*", "GB7RDG"] }

`appliesTo` is a glob-ish callsign filter so one URL can stop one site without affecting the whole fleet:

  • `"*"` matches every node (the natural default).
  • `"M0LTE-*"` matches every SSID under `M0LTE` (case-insensitive).
  • `"M0LTE-2"` matches exactly that callsign.

Empty / missing list = applies to everyone.

Fail modes

HTTP / parse failure keeps the last successful state until `StaleSeconds` elapses, then applies `FailOpen`:

  • Fail-open (default): network outage doesn't gag a working node; the kill-switch is for active intervention, not graceful degradation.
  • Fail-closed: opt-in for paranoid mode; if we can't confirm "go", don't TX.

Errors are swallowed by design - poller crash mustn't take down the daemon.

Test plan

  • 20 new unit tests covering the JSON contract, glob-style callsign targeting, fail-open / fail-closed staleness, never-fetched-yet boot states, transient-fail recovery
  • Full unit suite: 639/639 green
  • Manual end-to-end smoke: `dotnet watch` + a Python `http.server` hosting a static JSON file. Gate flips to BLOCK within one poll cycle (5s configured); the dashboard banner shows the remote reason and notably has no Resume button (only the remote closed the gate, the local toggle didn't); flipping the JSON back to `txAllowed: true` reverses on the next poll.

Stacked on PRs 1-2

#126 and #127 land first; this branch is rebased on master post-merge.

🤖 Generated with Claude Code

PR 3 of the master TX-stop work. Fills the ITxKillSwitchSignal seam
introduced in PR 2 with a real BackgroundService that polls a
configurable URL and gates TX on the result.

- SystemOptions adds TxKillSwitchUrl (empty=disabled), PollSeconds
  (default 60, min 5), FailOpen (default true), StaleSeconds
  (default 600, min 30). Round-trip through SystemOptionsStore.
- TxKillSwitchPoller IS the ITxKillSwitchSignal (singleton) AND a
  hosted service. Reads IOptionsMonitor on every tick so a /Config
  flip of the URL takes effect without restart.
- Wire shape: {"txAllowed": bool, "reason": string?, "appliesTo": ["*"|"M0LTE-*"|"M0LTE-2"]}.
  appliesTo is a glob-ish callsign filter so one URL can stop one
  site without affecting the whole fleet. Empty/missing = applies
  to everyone.
- Fail modes: HTTP/parse failure keeps the last successful state
  until StaleSeconds elapses, then applies FailOpen (default
  permits TX so a network outage doesn't gag a working node;
  operators can flip to fail-closed).
- Errors swallowed - same posture as UpdateChecker.

Manual smoke: dotnet watch + python http.server hosting a JSON
file confirms the gate flips to BLOCK within one poll, banner
shows the remote reason without a Resume button (since only the
remote closed the gate), and reverses on its own when the JSON
is flipped back.

Test suite: 639/639 green (20 new poller tests covering JSON
contract, callsign targeting, staleness fail-open/fail-closed,
never-fetched-yet boot states).
@M0LTE
M0LTE merged commit ced0b87 into master May 6, 2026
4 checks passed
@M0LTE
M0LTE deleted the feat/tx-gate-pr3-poller branch May 6, 2026 09:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant