diff --git a/docs/superpowers/plans/2026-08-20-recon-reports-enterprise-stability.md b/docs/superpowers/plans/2026-08-20-recon-reports-enterprise-stability.md new file mode 100644 index 0000000..53ef3cf --- /dev/null +++ b/docs/superpowers/plans/2026-08-20-recon-reports-enterprise-stability.md @@ -0,0 +1,365 @@ +# Recon, Reports, Enterprise Certificates, and PineAP Stability Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make Recon status truthful, enrich offline HTML reports with signal maps, move Handshakes under PineAP, add self-signed Evil Enterprise certificate generation, and contain known `pineapd` crash loops. + +**Architecture:** Preserve the existing single-file Python backend and vanilla-JavaScript SPA conventions. Add small pure helpers around existing Recon, report, enterprise, and health boundaries, with backend-generated state replacing frontend inference. Implement each subsystem test-first and deploy only after the complete local suite passes. + +**Tech Stack:** Python 3.11 standard library and device `python3-light`, BusyBox/OpenWrt commands, OpenSSL CLI, vanilla JavaScript, inline CSS/SVG, `unittest`. + +## Global Constraints + +- Support Pineapple Pager firmware `24.10.1` and daemon SHA-256 `e2cf0453d7e7d2e08bf31a242289e480d44649923a5199eed954d73df0cf1da5`. +- Do not add Python packages, frontend packages, a build system, remote assets, or inline report JavaScript. +- Do not patch or replace `/usr/sbin/pineapd`. +- Keep pool broadcast disabled on known affected and unknown builds. +- Preserve existing authentication, same-origin checks, and API error conventions. +- Use argv command lists, never shell interpolation, for user-supplied certificate fields. +- Preserve unrelated worktree changes and do not stage `loot/` or untracked existing certificate files unless a task explicitly requires them. + +--- + +### Task 1: Truthful Recon Coverage State + +**Files:** +- Modify: `payload/user/remote_access/pager-webui/server.py:66-79, 1335-1765` +- Modify: `payload/user/remote_access/pager-webui/www/js/views.js:1680-1910, 2672-2783` +- Test: `tests/test_recon.py:165-512` +- Test: `tests/test_env_check.py:202-324` + +**Interfaces:** +- Produces: `_recon_coverage(iface, active) -> dict` with `state`, `reason`, and `channel`. +- Produces: scan-state keys `baseline_scan_id`, `scan_id`, `settling_until`, and `result_warning`. +- Produces: `/api/recon/status` fields `coverage_24`, `scan_id`, and `result_warning`. +- Consumes: existing `_recon_hopper_preflight`, `_iface_associated`, `_dummy_sta_borrowable`, `recon_scans_data`, and `_recon_scan_state` lock. + +- [ ] **Step 1: Add failing backend coverage tests** + +Add tests that assert an enabled but unassociated dummy STA is not diagnosed as starvation, a successfully borrowed dummy reports `full`, a failed busy probe reports `current_channel_only` or `pinned` with its observed reason, and hopper interfaces are empty after loop cleanup. Mock `_iface_associated`, `_iface_admin_up`, `_run`, and channel state so each test controls runtime evidence rather than UCI alone. + +- [ ] **Step 2: Run the focused coverage tests and verify failure** + +Run: `python3 -m unittest tests.test_recon.ReconHopperTest tests.test_env_check.EnvCheckTest -v` + +Expected: new assertions fail because status has no `coverage_24` object and completed hopper state retains interfaces. + +- [ ] **Step 3: Implement backend coverage state** + +Add a pure status helper that reports `idle` when no scan is active, `full` only when `wlan0mon` is in the active hopper, `unavailable` when the monitor is absent/down, and otherwise uses the preflight skip reason/current channel to report `pinned` or `current_channel_only`. Store preflight per-interface outcomes in `_recon_hop_state`; do not derive the result solely from `_sta_uplink_enabled()`. + +Clear `_recon_hop_state['ifaces']` in the hopper loop's `finally` block while preserving the last coverage reason long enough for final status polling. + +- [ ] **Step 4: Add failing scan identity tests** + +Add tests that mock the existing maximum scan ID before start, return a newer row after start, and assert status tracks that exact ID. Add a timeout test using a mocked clock that advances 15 seconds beyond duration and asserts `result_warning` is set instead of selecting the prior scan. + +- [ ] **Step 5: Run scan-state tests and verify failure** + +Run: `python3 -m unittest tests.test_recon.ReconScanStateTest -v` + +Expected: failures because `_recon_scan_state` has no baseline/result identity or settlement state. + +- [ ] **Step 6: Implement result association and settlement** + +Before native start, query the maximum scan ID. After acceptance, store it as `baseline_scan_id`. During watchdog/status updates, query for the first row with a greater ID and set `scan_id`. At elapsed duration, allow a 15-second settlement window; after it expires without a row, set `result_warning` and end the run. Reset all identity fields on explicit reset/start. + +- [ ] **Step 7: Update Recon frontend state** + +Render backend `coverage_24` copy instead of the `wlan0Sta && !hop24` fallback. During auto-follow, select `status.scan_id` even when its counts are zero. Show completion only after a tracked result exists, or show the backend result warning after settlement failure. Keep existing styling and ASCII copy. + +- [ ] **Step 8: Run Recon and environment tests** + +Run: `python3 -m unittest tests.test_recon tests.test_env_check -v` + +Expected: PASS. + +- [ ] **Step 9: Commit Recon correctness** + +```bash +git add payload/user/remote_access/pager-webui/server.py payload/user/remote_access/pager-webui/www/js/views.js tests/test_recon.py tests/test_env_check.py +git commit -m "fix: report observed recon coverage and scan results" +``` + +--- + +### Task 2: Firmware-Gated pineapd Containment + +**Files:** +- Modify: `payload/user/remote_access/pager-webui/server.py:4432-4616, 4667-4814, 6982-6992` +- Modify: `payload/user/remote_access/pager-webui/www/js/app.js:516-546` +- Modify: `payload/user/remote_access/pager-webui/README.md:112-130` +- Modify: `README.md:54-61, 112-130` +- Test: `tests/test_health.py` +- Test: `tests/test_env_check.py` +- Test: `tests/test_robustness.py` + +**Interfaces:** +- Produces: `_pineapd_build() -> dict` with `firmware`, `sha256`, and `policy` (`affected` or `unknown`). +- Produces: `_reconcile_pineapd_safety() -> dict` listing changed keys and errors. +- Produces: health-state keys `events`, `quarantined`, `stable_since`, `build`, and `last_reconcile`. +- Produces: `GET /api/health/diagnostics` as a JSON download with no secrets. + +- [ ] **Step 1: Add failing build-policy and reconciliation tests** + +Test the exact affected firmware/hash pair, an unknown hash, the five required safe UCI values, no unrelated UCI writes, one commit only when values changed, and no command-socket ping. Assert unknown builds are reported but not automatically rewritten. + +- [ ] **Step 2: Run focused tests and verify failure** + +Run: `python3 -m unittest tests.test_health tests.test_env_check -v` + +Expected: failures because build policy and production reconciliation do not exist. + +- [ ] **Step 3: Implement build detection and safe reconciliation** + +Read firmware through the existing release parser and hash `/usr/sbin/pineapd` with `sha256sum`. Match the exact affected tuple. On affected builds, compare and set only: + +```text +pineapd.@ssidpool[0].disable=1 +pineapd.wlan2mon.disable=1 +pineapd.wlan2mon.hop=0 +pineapd.wlan1mon.bands=5 +pineapd.wlan0mon.bands=2 +``` + +Commit `pineapd` once if needed. Invoke reconciliation before Mark VIII explicitly starts/restarts the daemon, not continuously while a healthy daemon is running. + +- [ ] **Step 4: Add failing circuit-breaker tests** + +Use a mocked clock and process/SIGSEGV samples to assert three crashes in two minutes enter quarantine, Mark VIII issues no further restart, five stable minutes clear quarantine, and explicit recovery reruns reconciliation. Add pool tests for preserving 64 entries/4096 bytes and quarantining then clearing data above either limit only during a correlated crash loop. + +- [ ] **Step 5: Implement bounded health recovery** + +Track timestamped PID/SIGSEGV events in a bounded in-memory list. Enter quarantine at three crashes in 120 seconds. Stop Mark VIII restart calls while quarantined. Clear after 300 stable seconds or explicit recovery. For an oversized pool during the threshold-crossing event, write a timestamped diagnostic copy under `/root/loot/pineapd-diagnostics/`, clear through the existing safe pool command, and make one reconciled restart attempt. + +- [ ] **Step 6: Add diagnostics endpoint tests** + +Assert the download contains firmware/hash, policy, relevant UCI, monitor state, pool metrics, bounded event history, and health actions. Assert it excludes certificate private-key contents, enterprise secrets, cookies, and daemon binary data. + +- [ ] **Step 7: Implement diagnostics and UI health detail** + +Register `GET /api/health/diagnostics`. Reuse `Download` with JSON content and attachment naming. Expand `/api/health` with build/quarantine/reconcile data and display concise affected/unknown/quarantined state in the existing top-bar health treatment. + +- [ ] **Step 8: Correct stability documentation** + +Document automatic affected-build reconciliation, unknown-build behavior, passive checks, thresholds, and diagnostic preservation. Remove claims that normal health checks always clear the pool. + +- [ ] **Step 9: Run health, environment, and robustness tests** + +Run: `python3 -m unittest tests.test_health tests.test_env_check tests.test_robustness -v` + +Expected: PASS. + +- [ ] **Step 10: Commit containment changes** + +```bash +git add payload/user/remote_access/pager-webui/server.py payload/user/remote_access/pager-webui/www/js/app.js payload/user/remote_access/pager-webui/README.md README.md tests/test_health.py tests/test_env_check.py tests/test_robustness.py +git commit -m "fix: contain affected pineapd crash loops" +``` + +--- + +### Task 3: Self-Contained Report Signal Maps + +**Files:** +- Modify: `payload/user/remote_access/pager-webui/server.py:1921-2174` +- Test: `tests/test_recon.py:991-1191` + +**Interfaces:** +- Produces: `_report_channel_maps(aps) -> str` containing zero or more escaped inline SVG sections. +- Produces: `_report_channel_map(aps, band) -> str` for one of `2.4`, `5`, or `6`. +- Consumes: AP dictionaries from `recon_scan_data` and existing `_esc_html`, `band_of`, and report CSS. + +- [ ] **Step 1: Add failing report-map tests** + +Add representative 2.4, 5, and 6 GHz APs and assert one SVG per populated band, expected labels, all four signal colors, raised-cosine path data, and escaped SSID/vendor metadata inside `