Skip to content

Latest commit

 

History

History
152 lines (127 loc) · 25.6 KB

ROADMAP.md

File metadata and controls

152 lines (127 loc) · 25.6 KB

Roadmap

Strategy: observe first, then manage (D-004). Each work package (WP) is sized for one focused session; a merged WP (D-054, D-055) names its split points (Split:), parts that each fit one, and every split point is a checkpoint with the clear-or-keep test (AGENTS.md directive 2). The next milestones have detailed acceptance criteria; later ones are refined just in time, and the first task of every milestone is to refine its WPs. Legend: [x] done · [ ] to do · (S/M/L/XL) size, where XL is two L-sized parts · → dependencies

M0 — Foundation: walking skeleton and delivery pipeline

Goal: a real (if empty) product ships. Every push to main builds, tests and releases binaries plus a scratch image for 4 architectures.

  • M0.1 Plan & directives (M): README, AGENTS/CLAUDE, docs, license, GitHub repo.
  • M0.2 Rust workspace + server skeleton (M) → M0.1
    • Cargo workspace with crates/server (package uniwrt, bin uniwrt), rust-toolchain.toml pinned to stable, workspace lints, and the release profile from BUILD.md.
    • uniwrt serve (axum): GET /api/v1/health returns {status, version, commit, built_at}. Serves the embedded UI with precompressed assets and an SPA fallback. When web/dist is absent, a placeholder page is embedded instead, so the backend builds without Node.
    • uniwrt healthcheck (exit code 0/1, for Docker) and uniwrt version. Config through flags or env: UNIWRT_HTTP_LISTEN (default 0.0.0.0:8080) and UNIWRT_DATA_DIR (default ./data). Tracing, and graceful shutdown on SIGTERM/SIGINT.
    • Tests: the health handler, asset serving (content-encoding negotiation, cache headers) and the SPA fallback.
  • M0.3 Web skeleton (M) → M0.1
    • web/: bun, Vite, Solid 1.9, strict TypeScript, Tailwind v4 (tokens.css with dark and light themes), Biome, Vitest with Testing Library.
    • Placeholder app shell (wordmark, sidebar skeleton, a health badge fed by /api/v1/health) and a Vite proxy to the backend.
    • At least one component test. The build writes hashed assets to web/dist/assets/ (BUILD.md, Embedded UI) and uses no inline scripts or styles (the CSP in security.md).
    • The health badge's type is generated from the Rust DTO (crates/server/src/http/health.rs) with ts-rs (D-017); M0.4 wraps the export in just gen.
  • M0.4 Build tooling & image (M) → M0.2, M0.3
    • justfile (dev, check, test, fmt, lint, build, cross, image, map, plus a demo stub) and scripts/gen-map.sh → docs/MAP.md.
    • Cross builds for the 4 musl targets with cargo-zigbuild; locally they run inside the cargo-zigbuild container image.
    • FROM scratch Dockerfile per BUILD.md: prebuilt binary chosen by TARGETARCH/TARGETVARIANT, UID 65532, /data, HEALTHCHECK.
    • Local verification: the image runs and /api/v1/health answers. Record binary sizes in STATUS.
  • M0.5 CI/CD (M) → M0.4
    • .github/workflows/ci.yml per BUILD.md: version → web → rust → build ×4 (with qemu smoke tests) → image (GHCR, SBOM and provenance, cosign) → release (tag, assets, notes) on main. PRs only build and test. CI fails if docs/MAP.md is stale.
    • scripts/next-version.sh with a test script, cargo-deny config, Dependabot (cargo, npm, actions), and CI/release badges in the README.
    • Acceptance: a push to main produces release v0.1.0 with 4 tarballs and SHA256SUMS plus a 4-platform image, and docker run --platform linux/arm/v7 ghcr.io/robertzas/uniwrt:latest version works.

M1 — Core engine and read-only adoption ("see your network")

Goal: adopt the owner's existing nodes read-only and watch them live in a polished app shell.

  • M1.1 core, model (facts), store (M) → M0
    • core: IDs, MacAddr, network types, error conventions, a Clock trait. model::facts: NodeFacts, Capabilities, RadioFacts, StationFacts and InterfaceFacts (serde, with fixtures).
    • store: SQLite in WAL mode, embedded migrations, a batching writer actor and repository traits. Tests use a temporary DB.
  • M1.2 SSH transport (L) → M1.1
    • node crate: russh client; a controller ed25519 key (generated and kept in a vault stub); TOFU host-key pinning; a NodeLink trait (call/exec/stream/upload/events); a connection actor with keepalive and jittered backoff; jump hosts through direct-tcpip.
    • Tests against an in-process russh server, plus dropbear in an OpenWrt rootfs container (CI job).
    • Also done here (D-032): the agent envelope in proto.rs (requests, answers, events, ticks, hello, the protocol range) and the RPC multiplexer, which NodeLink::call needs.
  • M1.3 node-agent v0: core, bootstrap, system facts (L) → M1.2
    • A ucode agent with two loop modes (D-034): hello and caps, ping, collect (sets board, system, capabilities, interfaces, devices), ubus, exec (argv, timeout, bounded output), read_file. The ops are typed in proto/ops.rs and mirrored in proto.uc/ops.uc; the agent exits when its stdin closes.
    • Upload-by-hash bootstrap into /tmp/uniwrt/agent; parsing into model::facts, tested with fixtures captured from rootfs containers and with the canonical gateway.
    • Agent tests in OpenWrt rootfs containers booted with procd (24.10 and 25.12; the CI job agent).
  • M1.3b node-agent: wireless, clients and mesh sets (L) → M1.3 (split from M1.3; the streams moved to M1.3c)
    • Collect sets phys, radios, stations, leases, neighbors (with the bridge FDB) and batman, with their model types and parsers: nl80211 through ucode-mod-nl80211, netifd and hostapd over ubus, rtnetlink with a /proc fallback, dnsmasq and odhcpd, and batctl's JSON queries (D-035).
    • The hwsim lab (just test-wifi): virtual radios and batman-adv in OpenWrt 24.10 and 25.12 containers. Its sanitized captures are the wireless fixtures until the owner's nodes are captured.
  • M1.3c node-agent: streams (M) → M1.3b
    • Under the event loop (D-036): subscribe/unsubscribe (the topics hostapd, netifd and dhcp over ubus, a subscriber per object, re-subscribing as objects appear), log_follow (logd polled by id by default, or logread -f streamed) and set_interval ticks. The synchronous mode answers them unsupported, and the engine polls instead.
    • node::events parses the pushes into ticks, model::events (stations, devices, interfaces, leases) and model::log entries, tested against a lab capture (fixtures/events/).
    • Tested in the agent containers (ticks, both log modes, netifd) and the hwsim lab (a kicked client, its lease, a radio restart).
  • M1.4 engine (M) → M1.3c
    • A NodeActor per node, the live state store, the event bus, subscription-driven adaptive intervals, snapshot persistence, and node status (online/degraded/offline) with reasons (D-037).
    • Pushed events refresh the sets they touch; roams are inferred across nodes; logs are followed on demand. serve watches the adopted nodes over SSH links. Tested over a scripted link on tokio's paused clock, and against the agent containers in both loop modes.
  • M1.5 Simulator + demo mode (M) → M1.4
    • sim crate implementing NodeLink: a gateway, 3 APs and about 30 clients, with roaming, signal drift, events and logs. Deterministic seed. uniwrt serve --demo.
    • Done (D-038): a seeded world (path loss and fading, 802.11r roams, clients that leave and come back, bursty traffic, lease renewals, a wired AP and a multi-hop batman mesh, AP reboots) rendered in the agent's raw shapes, so the real parsers run; SimLink answers the event-loop agent's ops and pushes events, ticks and both log modes. serve --demo [--demo-seed] runs the engine on FleetLink over a fresh database in the data directory, deleted at exit; just demo. Tested through the node crate's parsers and with the engine on tokio's paused clock.
  • M1.6 Vault and TLS (M) → M1.4 (split from M1.6, owner's choice: auth moved to M1.6b, REST and WS to M1.6c)
    • The real vault: XChaCha20-Poly1305 under the master key (master.key, UNIWRT_MASTER_KEY_FILE or UNIWRT_MASTER_KEY), resealing the stub's alg = 'plain' rows (D-032). HTTPS on 8443 with a certificate the user can download and trust.
    • Done (D-039): a name-constrained local CA and the leaf it signs (rcgen), both in the vault; rustls with ring, HTTP/2 and a hot-swapped leaf renewed by a daily check; GET /api/v1/tls and /api/v1/tls/ca.crt; plain HTTP redirects except the health check and --trusted-proxies; --tls-names. Tested with webpki (chains, name constraints, validity), a TLS handshake, and the binary in CI's smoke test.
  • M1.6b api crate, auth and sessions (M) → M1.6
    • The api crate (the routes, problem+json errors and security headers move there from server/src/http), first-run admin setup, login and sessions (Argon2id), CSRF defenses (SameSite, Origin, a custom header), login rate limiting and lockout, and the audit of logins (features/security.md). ConnectInfo<Peer> gives the client's address; behind a trusted proxy, X-Forwarded-For and X-Forwarded-Proto.
    • Done (D-040): crate uniwrt-api (Api, router, ApiError, Client, CurrentUser, the Certificate trait for /v1/tls); setup within 10 minutes of a start without users (owner's choice); /v1/auth, /setup, /login, /logout, /me; migration 0003 (users, sessions, audit) and their repos; the listeners set Client (forwarded headers only from trusted proxies). API tokens are left for later. Tested through the router over a temporary store and with the binary.
  • M1.6c REST and the WS hub (M) → M1.6b
    • Node and fleet routes over Engine<FleetLink>, a WS hub with topics and deltas (at most 10 per second per topic), and ts-rs codegen (just gen) for the DTOs; tested against serve --demo.
    • Done (D-041): uniwrt_engine::Fleet (the owner's choice) and Engine::interests; GET v1/nodes, GET v1/nodes/{id}; /v1/ws with the topics fleet, node:<id> (holds focus) and logs?node=<id>&min=… (follows the log), snapshots then merged deltas every 100 ms, resync after lag, closing on logout, silence, stalls and shutdown, over HTTP/1.1 and HTTP/2 (extended CONNECT); the ts feature of core and model, so the DTOs carry the model's types. Tested with unit tests of the merging, over loopback sockets against the simulated fleet (HTTP/1.1 and HTTP/2), and with serve --demo over HTTPS.
  • M1.7 Adopt existing node (observe mode) (M) → M1.6c (split, owner's choice: discovery and batch adoption moved to M1.7b, the wizard to M1.8b)
    • API and job: IP + root password → confirm the host-key fingerprint → install the key → inventory and config backup → live view.
    • Done (D-042): crate uniwrt-jobs (Jobs, the adopt job, Dial/Remote/SshDial), jobs kept as history (no resume); migration 0004 (jobs, node_inventory, node_backups sealed in the DB, audit.detail); POST v1/adopt/probe, POST v1/adopt, GET v1/jobs[/{id}], the WS topic jobs; adopted nodes are watched at once. Tested over a scripted dial and a temporary store, and by adopting OpenWrt 24.10 and 25.12 containers, checking that only authorized_keys changes.
  • M1.7b Discovery and batch adoption (S) → M1.7
    • Discovered nodes from the gateway's DHCP leases (and batman originators), adopted in one go with one password.
    • Acceptance (with M1.8b): the owner's 4 nodes are adopted, with zero config changes other than authorized_keys.
    • Done (D-044): GET v1/discovered, built from the adopted nodes' leases, LAN neighbors (owner's choice) and batman meshes, with SSH banner and host-key probes (uniwrt_node::banner, Dial::banner; 4 at a time, held 10 minutes); POST v1/adopt/batch (one password, a job per node); at most 4 adoptions at once. Tested over the gateway fixture with a scripted dial, through the router (batches, demo mode), and the banner against dropbear (just test-dropbear).
  • M1.8 App shell & design system v1 (L) → M1.6c (split: sign-in, the API and WS clients, the Devices list and the live Device overview moved to M1.8c)
    • Decision gates: is Solid 2.0 GA, and is the ecosystem ready? Settle the UI direction (O-5) by showing the owner 3 rendered mockups.
    • Tokens and primitives (Button, Card, Badge, virtualized Table, Tabs, Dialog, Drawer, Tooltip, Toast, Stat, Sparkline), layout, sidebar, top bar, ⌘K palette, theme toggle.
    • Done (D-045, D-046): Solid 2 RC with router 2, solid-query 6 and a patched Kobalte 2 (owner's choice after a spike); three skins (Graphite pro, Midnight glass, UniFi-familiar) × light and dark, chosen in the sidebar's Appearance menu or ⌘K and applied before the first paint; tokens.test.ts checks WCAG contrast for every skin and theme; primitives in web/src/ui/ (Icon over lucide data, a Table virtualized above 200 rows with @tanstack/virtual-core); the shell with an icon rail (UniFi-familiar), a mobile drawer, the ⌘K palette (pages, skins, themes) and a page error boundary; the dev-only gallery at /dev/ui?skin=…&theme=…. Tested with Vitest (67 tests, the Kobalte patch included) and by screenshots of the gallery in each skin.
  • M1.8c Sign-in and live devices (M) → M1.8
    • First-run setup and login screens over /v1/auth (sessions, CSRF header), with the shell shown only when signed in; lib/api for the node routes and lib/ws (the fleet and node:<id> topics, deltas applied once per animation frame).
    • A Devices list (the Table, live status, ⌘K finds devices by name, MAC or IP) and a live Device overview (stats, radios, clients) against serve --demo; a Playwright smoke test in CI against demo mode.
    • Done (D-047): app/App.tsx gates the shell on /v1/auth (setup, setup closed, login with 401/429 explained; a 401 or a 4401 close returns to sign-in), an account menu with sign-out; lib/ws (LiveSocket: one socket, ref-counted topics, jittered reconnect, a flush per frame or per second when hidden), lib/live (the fleet store merged in place, createNodeView with patches and CPU/memory history), lib/session, lib/format, ui/TextField; features/auth, features/devices (list, overview with stats, radios, details and clients named from the gateway's leases). Tested with Vitest (94 tests) and Playwright against serve --demo in the runner's Chrome (just e2e, CI job e2e, which the image waits for).
  • M1.8b Adoption wizard (M) → M1.8c, M1.7b
    • Devices → Adopt: address and password → the fingerprint to confirm → the job's steps live (WS jobs) → the node's live view; Discovered nodes with batch adoption; failures explained by their code. A simulated adoption in demo mode.
    • Done (D-048): the demo leaves its last access point unadopted (owner's choice) and adopts it through the real job and discovery over DemoDial, the simulated node answering adoption's commands; the wizard is a dialog over Devices (owner's choice, ?adopt=1&host=…, ⌘K), with failures explained and acted on by code; the Discovered card under the list (selection, batch dialog with a progress bar per device); lib/jobs for the jobs topic, ui/Checkbox. Tested with Vitest (105 tests), the demo's adoption in the server's tests, and Playwright adopting the demo's access point through the wizard. The owner's acceptance (4 nodes adopted, only authorized_keys changed) waits for their hardware.
  • M1.9 Dev accelerators (S) → M1.6b
    • just new-feature <area> (scaffolds the files in the feature recipe) and just arch-check (enforces the layering rules from ARCHITECTURE.md), both wired into just check and CI.
    • Done (D-050): scripts/arch-check.sh (crates per the table in ARCHITECTURE.md, read from the doc; pure L0 trees and sources; web import directions; in just check and CI, tested on fixture trees) and scripts/new-feature.sh (model, API, web and, with --telemetry, the collector and parser, wired in and compiling; tested end to end in CI by scripts/new-feature.test.sh). Dialog and Drawer now return focus to their opener.

M2 — Observability ("understand your network")

  • M2.1 Topology engine (M): the pure topo crate builds the graph from facts; API and WS topic; fixture-based tests.
  • M2.2 Topology view (L): Canvas renderer, d3-force in a worker, tree/RF/clients views, live flows, pin/drag, details panel, 24 h replay slider.
  • M2.3 Clients (M): inventory, OUI vendor, device icons, history and roams, experience score v1, rename; a Signal tab from an hour of samples in memory. Kick and block moved to M5.1 (D-053, D-054).
  • M2.4 Metrics & dashboard (L, D-056, D-057, D-058): rollups, retention, LTTB downsampling, uPlot charts, dashboard v1 with a topology mini-map (the canvas renderer moves to web/src/viz/topology), and the Metrics explorer (/insights/metrics: any entity's series over a range). Split: a storage & API ✓ | b charts on device and client pages (the client's Traffic tab; uPlot replaces LineChart.tsx) and the Metrics explorer ✓ | c dashboard and mini-map ✓.
  • M2.5 Logs, events & alerts (XL; merged M2.5 and M2.6, D-055): syslog receiver (UDP/TCP), logread fallback, FTS5 search, live-tail UI, pattern annotations (log-patterns.toml); the WS logs topic's snapshot (empty since M1.6c) then comes from storage; event timeline, alert rules, notifiers (ntfy, webhook and email first). Split (D-059): a1 log ingest, storage, annotations and API ✓ | a2 the logs UI (/insights/logs: live tail, filters, search, histogram, saved searches, context; D-060) ✓ | b1 events end to end (derivation, storage, API and WS topic, /insights/events, the dashboard's card, logs around an event; D-061) ✓ | b2 alert rules over metrics and events, notifiers (in-app, ntfy, webhook, email over SMTP) and their settings (D-061); split (D-062): b2a alert rules and in-app alerts end to end, itself split: b2a1 the backend (rules, evaluator, storage, engine, API and WS topic alerts) ✓ | b2a2 the UI (/insights/alerts with Active, History, Rules and Maintenance, the top bar's bell, the rule editor, maintenance windows and snooze, active alerts on the dashboard) ✓ | b2b notifiers end to end (ntfy, webhooks with templates, email; Settings › Notifications; a test send; D-063), split: b2b1 the backend (the notify crate, storage and the vault, dispatch with reminders and digests, the API and the test send) ✓ | b2b2 Settings › Notifications (the list, an editor per kind, the filter, the template editor, the test button, the link address) ✓.
  • M2.6 RF insight (M, D-064): channel surveys, scheduled scans (every 4 h while idle), the inter-node RSSI matrix, DFS and congestion events, the spectrum view (/insights/rf) and the dashboard's RF card.

M3 — Config engine ("change safely")

  • M3.1 Desired-state model v1, revisions, vault (L; D-065). Split: a | b.
    • M3.1a Backend: SiteConfig v1 (every section; M3.3's fields), validation, structural diff, schema migrations with fixtures; the revisions table with secrets sealed per value in the vault; v1/site and v1/revisions (restore, audited reveal); the defaults as revision 1.
    • M3.1b History page: a read-only Settings › Revisions page: the list (author, time, message, changes), a revision's structural diff from its parent or any other, newest first with paging.
  • M3.2 uci crate (M; D-066): AST, parser, serializer, semantic diff; proptest round-trips; fixtures from real configs.
  • M3.3 render v1 & system test harness (XL; merged M3.3 and M3.7, D-055): system, network (DSA and swconfig), dhcp, firewall and wireless basics; capability-based; golden tests; OpenWrt x86-64 images (24.10, 25.12) under QEMU or as rootfs containers in CI, validating rendered configs (uci, fw4 check, netifd, wifi on mac80211_hwsim). Split: render | harness.
    • M3.3a Render (D-067): the board's layout in the facts (board.json), the render crate (system, network on DSA, swconfig and plain NICs with one VLAN-aware br-lan, the WAN, dhcp, firewall zones from the purpose matrix, wireless, the mesh basics), values carried over from the node, raw sections, golden tests; the embedded time-zone table.
    • M3.3b Harness (D-068): rendered configs accepted by real OpenWrt 24.10 and 25.12 (uci, fw4 check, netifd up, Wi-Fi and the mesh on mac80211_hwsim), in CI; settles the ⚠️ assumptions in docs/openwrt/REFERENCE.md (Stock packages, board.json, Render's assumptions).
  • M3.4 Transactions, jobs & review UI (XL; merged M3.4 and M3.6, D-055; D-069): agent transaction and boot guard, controller verify/confirm, the apply job (persisted as history; jobs don't resume, D-042), multi-node ordering with a fleet-wide two-phase commit; pending-changes tray, per-node diffs, impact summary, live progress, history and rollback. Split: a1 | a2 | b.
    • M3.4a1 Node transactions & management (D-069): the transaction tool and the boot guard (node-agent/txn/: stage, check, apply with a dead-man timer under procd, confirm, extend, rollback, rollback at boot), uniwrt_node::txn, the manage and unmanage jobs, PUT v1/nodes/{id}/management, the simulated tool; container tests (confirm, rollback on request, at the deadline and at boot, refusals).
    • M3.4a2 Apply job: render → fresh fetch → semantic diff per node → plan (leaves → gateway) → stage and check everywhere → apply everywhere with timers armed (extend while the others apply) → verify each over a new connection (role health checks: WAN and DNS on the gateway, the controller reaches every node, mesh neighbors, radios) → confirm all, or let all roll back; the applied revision per node; the API to plan and start an apply; the simulated transactions; an end-to-end apply on scripts/test-system.sh's topology.
    • M3.4b Review & apply UI: pending-changes tray, per-node diffs (secrets masked), impact summary, live progress, history and rollback; management on/off on the device page.
  • M3.5 Brownfield import & drift (L): lossless import, raw overrides, per-node managed-package toggles, drift detection; the review UI shows raw overrides and drift.

M4 — Gateway: WAN, LAN and firewall ("own the primary router")

  • M4.1 WAN, SQM & failover (L; merged M4.1 and M4.5, D-054): ISP preset format and seed set; WAN types including VLAN, 6rd and DHCPv6-PD; wizard; live connectivity test; cake with speed-test autotuning; optional mwan3 failover whose health checks reuse the connectivity test. Split: presets, WAN & wizard | SQM & failover.
  • M4.2 Networks & firewall (L; merged M4.2 and M4.3, D-054): carving the 10.0.0.0/16 space (O-1), networks/VLANs, DHCP pools and reservations, local DNS; a zone per network, the inter-network matrix seeded by purpose, port forwards, UPnP. Split: IPAM & networks | firewall.
  • M4.3 Factory gateway bootstrap (M): first-run wizard with make-before-break renumbering.

M5 — Wi-Fi and mesh ("build the mesh")

  • M5.1 WLANs & client actions (M; took in M3.8, D-054): security modes, VLAN binding, bands, node scoping, guest isolation, schedules; kick (hostapd del_client with a ban time) and block/unblock (MAC filter) on managed nodes, enabling the client page's disabled buttons (from M2.3, D-053). Split: WLANs | client actions.
  • M5.2 Roaming & steering (L; merged M5.2 and M5.5, D-054): 802.11r (PSK and SAE key distribution), controller-pushed 11k neighbor reports, 11v, management of the full wpad package; usteer profiles (O-2) and a steering/roaming timeline. Split: roaming | steering.
  • M5.3 Mesh backhaul (L): batman-adv over 802.11s and wired uplinks (with an MTU probe), BLA/DAT, VLANs over the mesh, mesh health.
  • M5.4 Adoption paths (L): ZTP via ASU with the pending-adoption inbox, the adoption port, the one-liner, the apk/opkg abstraction, dumb-AP conversion.

M6 — RF optimization ("optimize the air")

  • M6.1 RF planner & apply (XL; merged M6.1 and M6.2, D-055): rf crate with constraints, scoring, search and explanations; simulation tests; rolling and synchronized cutovers with rollback; a nightly optimizer with hysteresis. Split: planner | apply & scheduler.
  • M6.2 Floor plans & heatmap (L): floor-plan editor and a calibrated path-loss heatmap.

M7 — Lifecycle ("keep it current")

  • M7.1 Firmware & rolling upgrades (XL; merged M7.1 and M7.2, D-055): firmware catalog & ASU, the controller update notice (from M7.4, D-054); leaf → gateway ordering, health gates, soak periods, canary, pause/resume, pipeline UI. Split: catalog & ASU | rolling upgrades.
  • M7.2 Backups, restore & replace (L; merged M7.3 and M7.4, D-054): node and controller backups, site export/import; replace, factory reset & re-adopt, decommission. Split: backups & restore | replace & decommission.

M8 — Services and troubleshooting ("never SSH again")

  • M8.1 Network services & packages (L; merged M8.1 and M8.3, D-054): DNS suite, DDNS, NTP, the other services, and the package manager UI. Split: DNS, DDNS & NTP | other services & packages.
  • M8.2 WireGuard VPN (M)
  • M8.3 Diagnostics & troubleshooting (XL; merged M8.3 and M8.4, D-055): diagnostics toolkit, web terminal, UCI explorer; connection doctor, WAN quality monitor, support bundle. Split: toolkit, terminal & UCI explorer | doctor, WAN monitor & support bundle.

M9 — Polish and 1.0

  • M9.1 UX polish, accessibility, mobile, onboarding (L)
  • M9.2 Performance budgets enforced in CI; profiling (M)
  • M9.3 Security review, fuzzing, threat model (M)
  • M9.4 User guide, screenshots from demo mode, 1.0 release (M)

Backlog (unscheduled; see the open options in DECISIONS.md)

AI troubleshooting assistant (O-7) · PPSK (O-8) · Home Assistant/MQTT presence (O-9) · Prometheus exporter · multi-site · captive portal (openNDS) · controller package for gateways (O-6) · DNS query analytics · parental-controls UI · PWA push notifications · secondary gateways and multi-WAN at APs.