Files
AetherForge/PROBLEMS.md
AetherForge 415b5dc6a3
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Release validation: tests green, USB pack, fleet UX and API hardening.
Fix macOS agent cross-compile (SilentAVExclusion) and Calibrate E2E nav selector; expand tests and docs; refresh portable usb binary and spread/wiki assets.
2026-06-06 16:57:39 -07:00

663 lines
29 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### Open — medium / by design
| Issue | Notes |
|-------|-------|
| **`bof_execute` disabled** | Agent returns explicit error — in-memory BOF execution disabled for safety (`client.go`). |
| **Process hollowing AMSI/ETW** | Relocation patching done; Defender/ETW detection causes ~50% failure — bypass not implemented (`hollow_windows.go`). |
| **Cloudflared on Linux server** | In-process tunnel start is Windows-only; Linux/macOS builds use no-op stub — use external connector (`AF_TUNNEL_EXTERNAL`) or add cross-platform launcher. |
| **macOS camera / GPU miner** | Stubs return "not supported"; Linux has V4L2 + nvidia-smi path. |
| **Agent WireGuard auto-download** | Windows `ensureWGExe()` downloads WireGuard on first Path Tracer use — heavy side effect; pre-install recommended. |
| **Non-Windows Path Tracer agent parity** | `pathtracer_stub.go` returns error on `wg_setup`; Linux/macOS agents cannot join WireGuard chains. |
| **KEV heuristics** | Non-Windows agents return catalog with `Status: n/a` — Windows-only CVE matching.
### Open — large / deferred
| Issue | Notes |
|-------|-------|
| **Non-Windows agent parity** | `pathtracer_stub.go` returns error JSON on `wg_setup`; Linux/macOS cannot join chains. UI filters platform; server does not validate `platform` field. |
| **In-memory sessions only** | `TraceSession` map in handler RAM — server restart loses session state while agents may still hold tunnels; no DB persistence or startup sweep. |
| **WireGuard auto-download** | Windows `ensureWGExe()` silently downloads/installs WireGuard on first use — heavy, needs admin, no progress UI. |
| **No PathTracerPage Vitest** | Page logic (polling, chain selection, QR modal) untested; only `uiHelp.test.ts` covers help keys. |
| **No agent-side pathtracer tests** | `pathtracer_windows.go` / stub have zero Go tests. |
| **NAT / symmetric UDP** | UPnP + DB IP fallback may still yield unreachable endpoints; no STUN/TURN or connectivity probe after configure. |
| **Fixed listen port 51820** | All hops use same UDP port — multiple agents behind one NAT may conflict; no per-hop port allocation. |
| **Agent display name vs hostname** | WS auth `UpsertAgent` overwrites `name` with hostname on every connect — operator-renamed fleet labels may not appear in Path Tracer unless re-saved after connect. |
### Open — medium / UX
| Issue | Notes |
|-------|-------|
| **Visual island (green vs deck)** | Page uses `#00ffaa` WireGuard chrome while `data-operator-deck='pathtracer'` sets blue accent (`operatorDeck.css`); intentional branding split (see DV-07). |
| **Status polling only** | UI polls `/status` every 2s — no WebSocket push for hop progress; acceptable latency but stale during orchestration. |
| **Error path leaves tracing UI** | On `status.error`, `tracing` clears but session remains server-side until operator clicks End or TTL — hops show failed state in chain panel. |
---
### Open — large / deferred
| Issue | Notes |
|-------|-------|
| **No CI-automated HTTP forge** | `scripts/e2e-validate.ps1 -ForgeAgent` still manual (multipart fusion, long compile). Live compile gated behind `LIVE_FORGE=1` + `-tags liveforge`. |
| **PathForge test gaps** | Only `TestPathForgePlacedExcludesHintFile`; no skipped-counter, lock-original, or frontend UI tests. |
| **Forge cancellation / batch races** | Cancel token API tested in isolation; concurrent batch forge + mid-compile cancel not covered. |
| **Non-Windows forge host limits** | PE disguise (`disguise_stub.go`) and Windows signing (`sign_stub.go` / osslsigncode) are platform-constrained by design. |
| **Mac PathForge runtime dependency** | `.command` launchers curl `/api/download/agent-mac` at runtime — requires reachable `server_url` and pre-placed agent binary. |
### Open — low
| Issue | Notes |
|-------|-------|
| `pathforge_test.go` dead loop | Lines 66–72 iterate `entry.Files` but assert nothing — harmless test noise. |
### Open (document-only / deferred)
| Issue | Notes |
|-------|--------|
| Dual storage without sync policy | Complex cross-tab sync — session preferred over local; `aetherforge-auth` event on logout |
| Flaky progress simulation vs. real compile time | Cosmetic — stage timeline caps at 94% until server responds (45 min client timeout) |
| Path Forge / batch fusion test gaps | Cancellation, partial batch failure, cancel-token races — needs dedicated tests |
| DashboardPage tests emit ECONNREFUSED stderr | Failure-path tests; happy-dom hits `localhost:3000`; tests pass |
| DownloadButton mock aliasing pattern | Document for new download helpers — shared mock fn already in `components.test.tsx` |
--
### Open (2026-06-06 audit — LARGE)
- **`tunnel_stream` not implemented** — server-side TCP reverse relay documented as future work (`README.md`).
- **Server `cloudflared` launcher no-op on non-Windows** — Linux server deploy cannot auto-launch tunnels (`cloudflared/launcher_stub.go`).
### Low (open)
- **`mergeConfig` partial-PUT `UseTLS` legacy behavior** — omitted keys may not reset bools; see `config_test.go`.
### High (open)
- **Non-Windows Path Tracer agent parity** — `pathtracer_stub.go` returns error on `wg_setup`; Linux/macOS agents cannot join WireGuard chains.
### Medium (open)
- **Agent WireGuard auto-download:** Windows agent `ensureWGExe()` downloads and silently installs WireGuard from `download.wireguard.com` on first Path Tracer use if not already present (`agent/client/pathtracer_windows.go`). Heavy side effect; no server-side fix — operator should pre-install WireGuard on fleet hosts or accept first-run download latency.
- Mac PathForge `.command` still depends on `server_url` + `/api/download/agent-mac` at runtime (now validated at forge time).
| Op | Command | Notes |
|----|---------|-------|
| SMB share enumeration | `smb_shares` | Windows + Remote Aggressive; ARP/subnet hosts ? `net view` JSON |
| Spread status | `spread_status` | In-memory last sweep (`deploy/spread_status.go`); read-only |
| Credential names | `credential_vault_list` | Win Credential Manager / macOS Keychain / Linux secret-tool + `~/.ssh` paths — names only |
| Secure wipe | `secure_wipe` | Overwrite-then-delete folder; system-root guards; confirm in UI |
| Port-forward matrix | `tunnel_ssh_forward` × N | `CruciblePortForwardMatrix` — multi-row grid on selected Windows nodes |
UI: Phase C controls in `CrucibleExpandedOps.tsx` Fleet Maintenance (replaces “coming soon” stubs).
---
### Linux / macOS parity
| Area | Status |
|------|--------|
| **Mining + hashrate** | RandomX pure-Go engine works on Linux/macOS; stats loop sends `hashrate_15s/1m/15m` + shares over WS. |
| **Idle schedule guard** | **Fixed** — `SystemCPUPercent` was always 0 on Unix (`reporter_unix.go`), blocking idle-mode mining; Linux uses `/proc/stat`, macOS uses `sysctl kern.cp_time`. |
| **Screenshot** | Linux: scrot / ImageMagick `import` / gnome-screenshot. macOS: `screencapture`. |
| **Camera** | Linux V4L2 via ffmpeg/fswebcam (`camera_linux.go`). macOS: stub. |
| **File ops** | Cross-platform (`file_ops_unix.go` / `file_ops_windows.go`). |
| **Posture** | Unix collectors return firewall/AV/patch data (`posture_unix.go`), not all n/a. |
| **Spread** | SSH path on Linux/macOS (`autospread_unix.go`); SMB/WinRM Windows-only by design. |
| **Firewall ops** | Linux ufw/iptables (`firewall_linux_ops.go`); **macOS pf + socketfilterfw** (`firewall_darwin_ops.go`); other Unix stubs. |
| **GPU miner** | Windows-only T-Rex path; Linux/macOS stub with detect-only. |
| **Docker E2E** | `docker/docker-compose.yml` — isolated agent + server; see `docker/README.md`. |
### Open
- **Client:** WebSocket/beacon paths integration-only in CI (Docker Tier 2 closes Linux slice).
- **macOS:** camera, GPU miner — stubs or partial; firewall aggressive ops implemented (see Backend BE-04).
- **Linux screenshot:** headless containers need `xvfb` + scrot or custom `command` field.
- **`bof_execute` permanently disabled** — handler always fails (`client.go`); product/safety decision.
- **Linux/macOS GPU RVN mining broken** — `detectGPU()` may find NVIDIA, but `spec()` downloads Windows `.exe` miners (`gpu_miner.go`, `gpu_detect_stub.go`).
- **Mesh P2P without `-tags p2p`** — default build reports 0 peers; UI exposes `mesh_status` with re-forge hint (`mesh_p2p_stub.go`).
- **Process hollowing AMSI/ETW bypass not implemented** — documented ~50% failure rate (`hollow_windows.go`).
- **KEV exposure scan non-Windows** — all CVEs marked `n/a` (`cve_scan_stub.go`).
- **Unknown Unix CPU stats stub returns 0** — can break idle-mining guard on exotic platforms (`cpu_stub.go`).
### Open (document-only / deferred)
| Issue | Notes |
|-------|--------|
| Dual storage without sync policy | Complex cross-tab sync — session preferred over local; `aetherforge-auth` event on logout |
| Flaky progress simulation vs. real compile time | Cosmetic — stage timeline caps at 94% until server responds (45 min client timeout) |
| Path Forge / batch fusion test gaps | Cancellation, partial batch failure, cancel-token races — needs dedicated tests |
| DashboardPage tests emit ECONNREFUSED stderr | Failure-path tests; happy-dom hits `localhost:3000`; tests pass |
| DownloadButton mock aliasing pattern | Document for new download helpers — shared mock fn already in `components.test.tsx` |
| `server/webroot` not auto-synced on `npm run build` | Manual step via `devrun.bat` or copy; stale webroot served old hashed assets |
| Vitest stderr noise | `FleetTopologyMap` three.js tags warn in happy-dom — tests pass |
---
### Open (needs product copy or broader pass)
| ID | Issue | Notes |
|----|-------|-------|
| UH-01 | Crucible expanded ops (spread/tunnels/recon buttons) | 40+ buttons still rely on `title=` only — no `HelpTip` on each `CrucibleExpandedOps` action |
| UH-02 | Fleet Roster / Agents page bulk toolbar | Filter chips and bulk actions lack inline help |
| UH-03 | Emberwake / War Room campaign widgets | Funnel stages need `HelpTip` parity with Command Deck funnel |
| UH-04 | Mission Deck | Page has minimal operator guidance |
| UH-05 | Builder mission wizard chips | Inline blurbs exist on chips; not all advanced forge sections have `HelpTip` (see `docAnchors.test` gap list) |
| UH-06 | Settings tabs beyond Calibrate/Forge | Alerts, webhooks, desktop push sections partially covered |
### Open (visual / UX debt)
| ID | Issue | Files / notes |
|----|-------|----------------|
| DV-01 | **Neon cyan fragmentation** — canonical token is `--neon-cyan: #00e8f5` but components hard-code `#00f5ff`, `#00e5ff`, and `#0ff` fallbacks | `AgentRemoteActions.css`, `HashrateChart.tsx`, `BuildManagerPage.tsx`, `ProtocolTunnelPanel.css`, `FileManager.css` (fixed), sacred geometry SVGs |
| DV-02 | **Page header patterns diverge** — most pages use `deck-hero` + eyebrow; Build Manager uses `bm-header` / `bm-title`; Path Tracer uses green `.pt-title` (`#00ffaa`); Forge says “The Forge” vs nav “Forge” | `BuildManagerPage.tsx/css`, `PathTracerPage.css`, `BuilderPage.tsx`, `Layout.tsx` NAV |
| DV-03 | **`SacredPageHeader` unused** — component + `.page-header--sacred` CSS exist but no page imports it; dead design path | `SacredPageHeader.tsx`, `sacred-geometry.css` |
| DV-04 | **`Pages.css` duplicate / conflicting rules** — two `.empty-state` blocks (L192 vs L1078); second `.page-header h1` block overrides `visual-polish` gradient when page CSS loads after global polish | `Pages.css`; load order via per-page imports |
| DV-05 | **Default dashboard subtitle** — out-of-box copy is “security is just an emotion” until Calibrate overrides `dashboard_subtitle`; reads as placeholder to new operators | `DashboardPage.tsx` L70; `SettingsPage.tsx` L131 |
| DV-06 | **Path Tracer buried in nav** — desktop sidebar lists it last; mobile hides it under “More” while Crucible/Forge are primary tabs | `Layout.tsx` MOBILE_PRIMARY vs MOBILE_MORE |
| DV-07 | **Path Tracer visual island** — green WireGuard aesthetic (`#00ffaa`) does not use operator-deck page classes or brass/neon deck chrome | `PathTracerPage.css`, `PathTracerPage.tsx` (missing `operator-deck-page`) |
| DV-08 | **Chart lazy-load placeholder is invisible** — `ChartPlaceholder` is an empty div at 35% opacity; advanced charts pop in with layout shift | `DashboardPage.tsx` L51–53 |
| DV-09 | **Dead chart badge styles** — `.chart-live.sample` / `.blend` in wealth-deck CSS; `resolveChartSeries` no longer emits sample mode | `wealth-deck.css`; `chartSampleData.ts` |
| DV-10 | **Docs vs in-app naming drift** — wiki says “Calibrate (Settings)”, “Command Deck”, “Forge / Builder”; nav uses “Calibrate”, “Command Deck”, “Forge”; Emberwake route was `/spread` redirect | `public/docs/index.html`, `Layout.tsx`, `App.tsx` |
| DV-11 | **Public spread landing vs Emberwake** — `/spread/` static kit uses `aether.css` deck tokens (good) but typography/spacing differs slightly from in-app Emberwake cards | `public/spread/assets/aether.css`, `EmberwakePage.css` |
| DV-12 | **Emoji in status bar / actions** — ?? DOCS pill, agent action buttons (?? ? ?) inconsistent with otherwise SVG-icon nav | `SystemStatusBar.tsx`, `AgentRemoteActions.tsx` |
| DV-13 | **No light theme** — entire product is dark-only; docs wiki matches but no `prefers-color-scheme` path | global styles |
| DV-14 | **Sidebar version hard-coded** — footer shows `v0.0.1` regardless of server build | `Layout.tsx` L321 |
| DV-15 | **Mission Deck formatting** — source file has excessive blank lines (likely formatter artifact); harder to maintain, no runtime impact | `MissionDeckPage.tsx` |
### Remaining doc gaps (need product copy / user input)
| Topic | Notes |
|-------|-------|
| Default dashboard subtitle | “security is just an emotion” until Calibrate override — marketing copy decision (DV-05) |
| SocGholish / fake-update lander | Documented in spread playbook `#third-party` tab; no shipped branded HTML template |
| OAuth redirect abuse playbook | Research only — no Entra app wizard in Emberwake |
| Earnings USD quote | `TEST_RESULTS.md` notes low priority; not wired |
| Screenshot placeholders in wiki | `[Screenshot: …]` divs — need real captures from operator deck |
| Sidebar version `v0.0.1` | Hard-coded in `Layout.tsx` — should read server build version (DV-14) |
| Light theme | Dark-only documented; no `prefers-color-scheme` path (DV-13) |
---
### Deferred — needs refactor, heavy mocks, or external deps
| Area | Why deferred |
|------|--------------|
| `agent/cmd/mine-validate` | Standalone CLI (`main` package); exercises RandomX + live Stratum; run manually or in mining CI |
| Cloudflared `Start` with real token | Spawns/downloads binary + network; only empty-token path covered |
| `FleetScheduler.Start` loop | 1-minute ticker; logic covered via direct `tickInterval`/`tickCron` in same-package tests |
| CruciblePage full integration | Large page; helpers + `CrucibleExpandedOps` + `FileManager` tested separately |
| Fleet panel widgets | Partially covered via `components.test.tsx`; full panel flows need WS mocks |
| Path Forge / batch fusion cancel races | Needs dedicated cancel-token harness |
| Playwright E2E (`server/web/e2e/`) | Phase 8 of test-suite; requires build + temp server |
| Docker mining E2E | `.github/workflows/ci-docker-mining.yml` — separate tier |
| Mesh P2P | Requires `-tags p2p` build tag |
| Platform-specific fusion launchers | OS-gated; crypto path covered in `media_crypto_test.go` |
---
| Suite | Result | Notes |
|-------|--------|-------|
| `go test ./...` (repo root) | **N/A** | No root `go.mod`; use per-module dirs (documented in Integration audit). |
| `agent/` | **PASS** | All packages ok (`client` ~3s). |
| `server/` | **PASS** | `internal/api` ~69s, `internal/builder` ~32s. |
| `fusion/` | **PASS** | Includes `media_crypto_test.go` (untracked in git at time of pass). |
| `server/web` Vitest | **PASS** | 66 files / 587 tests (3 consecutive full runs). |
| Item | Notes |
|------|-------|
| Vitest stderr `ECONNREFUSED 127.0.0.1:3000` | Failure-path / bulk-command tests in `DashboardPage`, `AgentsPage`, etc. (see Dashboard section). |
| `FleetTopologyMap` three.js ref warnings | happy-dom; cosmetic stderr. |
| Vite build chunk size warnings | `three` / vendor bundles > 500 kB; not a test failure. |
---
## Bugs — Builder/Security (Bug Team 4 audit, 2026-06-06)
*Scope: `server/internal/builder/`, `fusion/`, `server/config.go`, cross-cutting API input validation and auth edge cases.*
### Fixed in this pass
| ID | Fix |
|----|-----|
| BLD-01 | **PowerShell injection in uninstaller** — `processName` and `persistenceKey` were embedded in single-quoted PS1 strings without escaping apostrophes. A `WorkerName` like `foo'; Invoke-Expression …; '` would break out of the string. Now uses `strings.ReplaceAll(…, "'", "''")` for both fields, consistent with how `installRel` was already escaped (`uninstall.go`). |
| BLD-02 | **JSON build request body unbounded** — non-multipart `POST /builder/build` decoded `r.Body` without a size limit; a 1 GiB JSON body would buffer entirely. Fixed: `http.MaxBytesReader` capped to 512 KiB before `json.Decode` (`handler.go`). |
| BLD-03 | **Unbounded backup pool/URL arrays** — `BackupServerURLs`, `BackupPools`, and `RVNBackupPools` from user input are concatenated verbatim into the generated Go source (`generateBuiltinConfig`). A request with 10 000 entries would produce a multi-MB `.go` file, slowing or crashing `go build`. Fixed: arrays truncated to 10 entries each in `normalizeRequest` (`handler.go`). |
| BLD-04 | **Partial build dir not cleaned on failure** — when `compileWorker`, `buildFusionFromRequest`, `writeUninstallScript`, or `MkdirAll` fail mid-build, the entire `builds/<uuid>/` tree (containing a copy of agent source + uploaded fusion payload) was left on disk. Added `cleanupBuild()` closure that calls `os.RemoveAll(buildDir)` on each failure return path (`handler.go`). |
| BLD-05 | **Weak random password entropy** — `generateRandomPassword()` used only 4 random bytes (8 hex chars, 32-bit entropy), guessable in ~4 billion attempts. Increased to 8 bytes (16 hex chars, 64-bit entropy) (`router.go`). Test updated. |
| BLD-06 | **Malformed `config.json` silently ignored** — `json.Unmarshal` failure was swallowed; operator saw no indication their config was rejected and defaults were running instead. Added `fmt.Fprintf(os.Stderr, …)` warning on parse failure (`config.go`). |
### Deferred / large
| ID | Severity | Location | Description |
|----|----------|----------|-------------|
| **BLD-D1** | High | `server/internal/builder/pathforge.go` | **No server-side restriction on `root_path`.** `PathForgeRequest.RootPath` is passed directly to `filepath.WalkDir` without any whitelist or prefix check. An authenticated operator (or compromised session) can supply any absolute path (e.g. `C:\Windows\System32`, `/etc/`) and the server will (a) enumerate every matching file and (b) write `.exe`, `.bat`, and `.command` companion files next to them. Fix: validate `RootPath` against an operator-configured allow-list (e.g. specific USB/NAS mount points), or at minimum reject absolute paths that escape a configured `data_dir`. |
| **BLD-D2** | Medium | `server/internal/builder/pathforge.go` `batContent` / `macContent` | **Filename injection in generated scripts.** `d.Name()` (raw filesystem filename) is interpolated into `.bat` and `.command` scripts via `fmt.Sprintf`. On Windows, a filename containing `%VAR%` expands the BAT variable; a filename with `"` breaks the quoted argument. On macOS/Linux, a filename with `'` breaks single-quoted shell strings, and a `server_url` containing `'` would allow shell command injection in the generated `curl` line. Fix: escape `"` and `%` for BAT content; escape `'` ? `'\''` for shell content. |
| **BLD-D3** | Low | `server/internal/builder/handler.go` `buildUniversalAgent` | **No partial build cleanup for universal builds.** The `cleanupBuild` fix (BLD-04) covers single-platform `buildAgent`. The corresponding `buildUniversalAgent` / `finishSpreadKit` / `finishUniversalFusion` do not clean up `buildDir` on internal failures. Extend the same pattern. |
---
## Bugs — Server (Bug Team 1 audit, 2026-06-06)
### Deferred / large
| ID | Severity | Location | Description |
|----|----------|----------|-------------|
| ~~**SRV-B1**~~ | ~~High~~ | `server/main.go` | ~~**No graceful shutdown on SIGINT/SIGTERM.**~~ **Fixed.** `http.ListenAndServe` replaced with `http.Server` + goroutine; `signal.NotifyContext(syscall.SIGINT, syscall.SIGTERM)` drives a `srv.Shutdown(ctx)` with 10 s timeout on signal, allowing all `defer` calls (`cloudflared.Stop()`, `database.Close()`, `maintenance.StopRetentionJobs()`, `fleetSched.Stop()`) to run cleanly. |
| ~~**SRV-B2**~~ | ~~Medium~~ | `server/internal/db/sqlite.go` | ~~**SQLite max-connections not configured.**~~ **Fixed.** `db.SetMaxOpenConns(1)` added immediately after `sql.Open`; the single-connection pool eliminates concurrent-writer WAL-lock contention and `SQLITE_BUSY` errors under load. |
| ~~**SRV-B3**~~ | ~~Medium~~ | `server/internal/scheduler/fleet_scheduler.go` | ~~**O(tasks × agents) DB queries per minute.**~~ **Fixed.** `BulkLastFleetTaskRuns` added to the `db` package; `tickInterval` now pre-fetches all relevant `fleet_task_runs` rows in a single query and checks an in-memory `map[string]time.Time` (keyed `"agentID:taskID"`) in the nested loop — 1 query per tick instead of tasks × agents. |
| ~~**SRV-B4**~~ | ~~Medium~~ | `server/internal/api/websocket.go` `broadcastDashboard` | ~~**Stale-conn cleanup races with `HandleDashboardWS` teardown.**~~ **Fixed.** `broadcastDashboard` no longer spawns a goroutine to delete the dashboard map entry on write failure. It only closes the connection; `HandleDashboardWS` already owns all map cleanup via its existing `defer`, so the double-delete and the spurious `presence_update{online:false}` are eliminated. |
| ~~**SRV-B5**~~ | ~~Low~~ | `server/internal/api/agent_ws_limiter.go` | ~~**Rate-limiter map never purges zero-entry keys.**~~ **Fixed.** `delete(agentWSRateLim.attempts, clientIP)` called when `len(filtered) == 0` after the expiry sweep; map keys are reclaimed as IP addresses churn out of the window. |
---
## Backend
- **`db.New` / `MkdirAll`:** Already returns error on failure (`server/internal/db/sqlite.go`); remove stale “Low (open)” note in Server API section when editing that doc block.
---
## Frontend
- **`RemoteDirBrowser` removal:** No broken imports. UI logic lives in `FileManager.tsx` + `src/help/remoteDirBrowser.ts`; `remoteDirBrowser.test.ts` covers helpers. Deleted `RemoteDirBrowser.tsx` / `.css` are not referenced elsewhere.
- **`FileManager.test.tsx`:** Untracked but picked up by Vitest (`src/**/*.test.{ts,tsx}`); 10 tests pass — add to git when committing Fleet work.
- **Download test mocks:** Prefer separate `vi.fn()` per export when mocking `api/download` (real module aliases `downloadApiFile` to `downloadAuthedFile`; shared mock caused order-dependent flakes).
---
### Open — large / architecture
| Issue | Notes |
|-------|-------|
| **Monolithic WebSocket context** | Every `useWebSocket()` consumer re-renders on any WS state change. Split into `FleetContext` / `EventsContext` or selector hook (`useAgents()`) for true isolation. |
| **`CruciblePage` size (~2k lines)** | Single component owns terminal, fleet list, tabs, file manager — hard to memoize subtrees; consider section components + `React.memo` boundaries. |
| **Per-agent `stats_update` broadcast** | Backend sends one dashboard message per agent stats tick; no batching/coalescing in `websocket.go`. Fleet of N agents ? N JSON parses/frame on client. |
| **`SystemStatusBar` REST poll duplicates WS** | Polls `listAgents` every 15s though fleet already streams via WebSocket — wire readout to WS or drop agent poll. |
| **Vite chunk size** | `three` (~600 kB) and vendor bundles trigger build warnings; FleetTopologyMap loads three on Dashboard — already lazy but still heavy first open. |
| **No terminal virtualization** | DOM cap at 400 lines helps; full virtual list (react-window) needed for 2000-line scrollback without mount cost. |
| **MatrixRain always mounted in Layout** | Runs on every route including mobile (hidden sidebar but component still mounts on desktop). Consider `content-visibility` or route-gated mount. |
| **Path Tracer 2s REST poll** | No WS push for hop progress; acceptable but adds load during orchestration. |
### Open — medium
| Issue | Notes |
|-------|-------|
| **Emberwake war room double feed** | 15s client poll + 30s server `runWarRoomBroadcast` — redundant; prefer WS-only with poll fallback. |
| **CursorFire + SacredGeometry on all routes** | Desktop-only effects still mount with Layout; gate on `VisualEffectsContext` or route. |
| **Earnings estimate on every hashrate change** | Dashboard debounces via `totalHashrate` effect — could share chart sampler interval. |
---
### Deferred — large / complex
| Issue | Location | Notes |
|-------|----------|-------|
| ~~**WebSocketProvider async race**~~ | `context/WebSocketProvider.tsx` | **Fixed (2026-06-06).** Added `openingRef` (`useRef(false)`) in-flight guard -- a second `connect()` call while a ticket fetch is in progress returns early. Added `AbortController` (`ticketAbortRef`) to cancel any prior in-flight fetch; aborted invocations bail before creating a `WebSocket`. `openingRef` is always reset in a `finally` block. |
| ~~**`useVisibleInterval` calls `fn` on every dep change**~~ | `hooks/usePageVisible.ts` | **Fixed (2026-06-06).** `fn` is now stored in a `fnRef` (`useRef`). The effect depends only on `ms`, `enabled`, and `visible` -- an unstable `fn` reference no longer re-runs the effect or triggers an extra immediate call. The interval always invokes `fnRef.current()` so callers always see the latest `fn` without extra renders. |
---
### Deferred — large / medium
| ID | Issue | Location | Notes |
|----|-------|----------|-------|
| ~~BA-03~~ | ~~**`write()` has no write deadline**~~ | `client/client.go``write()` | **Fixed.** `c.conn.SetWriteDeadline(time.Now().Add(15*time.Second))` is now called immediately before `WriteJSON` (and cleared afterward), so a stalled TCP socket cannot hold `c.mu` indefinitely and deadlock share submission, stats, and command-result goroutines. |
| ~~BA-04~~ | ~~**SSH/SCP spread commands have no overall timeout**~~ | `deploy/autospread_unix.go` — `attemptSSHSpread()` | `ConnectTimeout=3` limits only the TCP handshake; after a successful connection, `scp.Run()` and `ssh … start.Run()` have no deadline. A slow or unresponsive host stalls the goroutine indefinitely, holding a slot in `spreadSem` (16 total). With 16 such hangs in flight, all future spread goroutines block waiting on the semaphore. Fix: use `exec.CommandContext` with a ~30 s deadline wrapping the whole SCP + SSH sequence. **Fixed:** `exec.CommandContext` with `context.WithTimeout(30s)` wraps the full SCP + SSH sequence; a hung host releases its semaphore slot after 30 s. |
| ~~BA-05~~ | ~~**GPU miner binary download has no HTTP timeout or body-size cap**~~ | `client/gpu_miner.go` — `downloadAndExtract()` | `http.Get(url)` with no timeout and `io.ReadAll(resp.Body)` with no size limit. A slow redirect or a response that trickles bytes forever will hang the goroutine; a gigabyte-scale response could OOM the agent. Fix: use an `http.Client` with a 5-min overall timeout, and wrap the body in `io.LimitReader(resp.Body, 512<<20)`. **Fixed:** `http.Client{Timeout: 5*time.Minute}` + `io.LimitReader(resp.Body, 512<<20)`. |
| BA-06 | **`CollectFullSysCheck` blocks for 45+ s on empty subnets** | `client/syscheck.go` — `CollectFullSysCheck()` | Calls `deploy.ScanLocalSubnet(56)` synchronously in the command handler goroutine. `ScanLocalSubnet` probes up to 56 hosts × ~0.8 s timeout = ~45 s on an empty /24. During this time the agent cannot process incoming WebSocket messages (it is running in a command goroutine, but the read loop is separate; however if the server has a short reply timeout the session may drop). Consider running the subnet scan in a background goroutine and returning a "pending" token, or reducing the default `maxHosts` for syscheck. |
---
## Infrastructure
- **Root `fix.py`, `fix3.py`, `fix4.py`:** Untracked one-off Python string/regex editors targeting `CruciblePage.tsx` (ProtocolTunnel `onDispatch`, GPU hashrate label). Intended changes appear already applied in `CruciblePage.tsx`. Safe to delete after review; not run in CI — do not rely on them for builds.
- **CI:** Only `.github/workflows/ci-docker-mining.yml` (Docker mining); no root Makefile test target found.
- **`data/login-credentials.json`, `data/users.json`:** Untracked; do not commit (secrets/local data).