Release validation: tests green, USB pack, fleet UX and API hardening.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Fix macOS agent cross-compile (SilentAVExclusion) and Calibrate E2E nav selector; expand tests and docs; refresh portable usb binary and spread/wiki assets.
This commit is contained in:
AetherForge
2026-06-06 16:57:39 -07:00
parent 5229854f00
commit 415b5dc6a3
119 changed files with 7005 additions and 3082 deletions

View File

@@ -1,224 +1,662 @@
## Builder / Forge
*Scope: B-01B-13 (2026-06-04 pass). `go test ./internal/builder/... ./internal/api/...` — run after changes.*
### Fixed in this pass
| ID | Fix |
|----|-----|
| B-01 | `checkBuildSizeFile` enforced on universal/spread-kit and universal-fusion ZIP outputs (`limits.go`, `build_universal.go`). |
| B-02 | `signExecutable` runs on spread-kit workers and universal-fusion runners when `sign_build` is set (`build_universal.go`). |
| B-03 | Dropper `/get` resolves `DownloadURL` artifact paths before `FilePath` (`dropper_handler.go`; `dataDir` on handler). |
| B-04 | Fusion launcher compile respects `shouldObfuscate` / garble (`fusion_media.go`). |
| B-05 | Fusion estimate uses paired/embedded sizing; removed stale `"video"` kind branch (`estimate.go`). |
| B-06 | Estimate adds `signingToolMissingNote` when cert configured but signtool/osslsigncode absent (`estimate.go`, `sign_*.go`). |
| B-07 | Universal fusion README `RunnerName` uses `disguisedRunnerName(payloadBase)` (`build_universal.go`). |
| B-08 | `publishFusionDeliverable` falls back to `dataDir` when `projectRoot` empty (`fusion_media.go`). |
| B-09 | PathForge `Placed` excludes hint file from count (`pathforge.go`). |
| B-10 | Multipart parse limit raised to `multipartMaxMemory` (2 GiB + headroom) (`limits.go`, `handler.go`). |
| B-11 | Dropper PS1 ZIP handler also tries `start.bat` / `deploy.bat` (`dropper_handler.go`). |
| B-12 | `DownloadBuild` Content-Disposition uses `FileName` (`handler.go`). |
| B-13 | `resolveToolPaths` probes bundled `toolchain/gopath/bin` for garble and go-winres (`winres.go`). |
### Open
*(none in B-01B-13 scope)*
### 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. |
---
## Dashboard (React/Vite)
*Audit fixes: 2026-06-04. `npm run test -- --run` in `server/web` — pass.*
### Fixed in this pass
| Fix | Area |
|-----|------|
| SessionGate distinguishes 401 vs transport errors; network blip keeps saved credentials with degraded banner | `SessionGate.tsx` |
| Session expiry message when 401 clears auth mid-session (`consumeAuthExpiredFlag`) | `auth.ts`; `client.ts`; `SessionGate.tsx` |
| Download timeouts + AbortError messaging: `downloadAuthedFile`, `downloadAgentLog`, `downloadBackup` (5 min / 10 min) | `download.ts`; `client.ts` |
| Agent log `refresh=1` uses 90s timeout (long-poll) | `client.ts` |
| Re-forge requires confirmation before compile; fusion prep picker highlights on missing payload | `BuilderPage.tsx` |
| BuildManager delete/pin errors surfaced; dropper copy clarifies pinned vs latest; `serverBase` prefers `suggested_url` via parallel `getServerInfo` | `BuildManagerPage.tsx` |
### 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 |
|-------|--------|
| Emberwake public-build toggle in Builds UI | API `PUT /builds/{id}/public` exists; Builds page checkbox not wired yet — use API or Calibrate `public_builds_enabled` |
| 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 |
| 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 API — deeper issues only
Source: `server/internal/api` audit (2026-06-04). **API-D01D10 addressed 2026-06-04** (`go test ./internal/api/... -count=1` PASS).
### Fixed (2026-06-04)
| ID | Fix |
|----|-----|
| API-D01 | `POST /api/v1/auth/ws-ticket` issues 2min one-time tickets; dashboard WS prefers `?ticket=`; legacy `?token=` retained as fallback. |
| API-D02 | Per-IP agent WS upgrade rate limit (429) + 45s pre-auth read deadline before disconnect. |
| API-D03 | `MarkBeaconSeen` / `EnqueueBeaconCommand` require agent row in DB; beacon upsert runs before mark. |
| API-D04 | CoinGecko fetch checks HTTP status; retries 429/5xx up to 3 attempts with backoff. |
| API-D05 | `POST /api/v1/users` validates username (332, alnum/`_`/`-`) and password (4128); **409** on existing username. |
| API-D06 | Agent WS read loop logs unknown `msg.Type` in `default` branch. |
| API-D07 | `notifyCmdCallback` uses blocking channel send (no drop on full buffer). |
| API-D08 | `GET /agents/{id}/stats` returns **404** when agent missing (parity with `GET /agents/{id}`). |
| API-D09 | **By design**`/api/download/agent-*` and dropper `/get` stay unauthenticated; URL knowledge is the gate. |
| API-D10 | Legacy `?token=` WS auth uses `authSessionCache` (bcrypt skip on cache hit), same as REST. |
---
| DownloadButton mock aliasing pattern | Document for new download helpers — shared mock fn already in `components.test.tsx` |
### Fixed (2026-06-04)
--
### 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`).
- **`mergeConfig`:** now merges `server.fleet_secret` and `tunnel_defaults` (file load still uses `mergeConfigExplicit` for bool safety).
- **`pool.Manager`:** `poolKey` includes `PaymentID` to avoid integrated-address proxy collisions.
- **`GetSpreadFunnelStats`:** `new_connects_today` now uses caller `since` (aligned with `by_build` window).
- **`LAUNCH.bat` / `devrun.bat`:** read `port` from `data/config.json` for display; launch without `-port` so config wins.
### Low (open)
- `db.New` ignores `MkdirAll` failure.
- **`mergeConfig` partial-PUT `UseTLS` legacy behavior** — omitted keys may not reset bools; see `config_test.go`.
## Fusion / PathForge
*Audit: fusion pathforge (2026-06-04). Builder + API tests — pass.*
### Fixed (2026-06-04)
- **F1 — Path Tracer WireGuard peer topology:** `buildHopPeers` adds client peer on hop 1 (`10.66.0.1/32`); single-hop no longer gets empty `peers`; multi-hop adds reverse peers on middle/exit hops. Tests: `pathtracer_handler_test.go` (`TestBuildHopPeers*`, `TestPathTracerOrchestrationConfigurePeers`).
- **Path Tracer session expiry:** background cleanup goroutine removes sessions after 2h and sends `wg_teardown`. Test: `TestPathTracerSessionExpiry`.
- **PathForge `Skipped` counter:** incremented for non-matching files during walk.
- **PathForge `dataDir`:** used as fallback search path in `findAgentBinary`.
- **Mac PathForge `server_url`:** required when `target_mac` is enabled (400 if missing).
- **Tests added:** `pathforge_test.go` (HTTP validation, skipped counter, placement); `pathtracer_handler_test.go` (peer topology + mock-agent orchestration).
### High (open)
- `fusion/` package has no tests (coverage only in `server/internal/builder/fusion_*_test.go`).
- **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.
- **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).
---
## Crucible (Remote Operations Theater)
*Expansion pass: 2026-06-04. `npm run test -- --run` in `server/web`; `go test ./agent/client/...`.*
### Phase A — wired (done)
| Op | Command / API | Notes |
|----|----------------|-------|
| Connectivity Probe | `connectivity_probe` | JSON to terminal |
| Listen Ports / Patch Status | `listen_ports`, `patch_status` | Individual chips (Deep Scan still runs both) |
| Firewall suite | `firewall_punch`, `firewall_off`, `firewall_on`, `firewall_profiles`, `firewall_remove` | `canRunAggressiveAction` gates |
| UPnP | `hole_punch_status`, `hole_punch_close` | Hole Punch forge flag |
| Bulk Tunnel Stop | `tunnel_stop` + `all` | Selected online nodes |
| Mesh Peers | `mesh_status` | Mesh P2P forge flag |
| Persistence | `bits_persist`, `host_binary_persist` | Win + Remote Aggressive |
| Fleet Upgrade | build picker → `upgrade` | `listBuilds` download URL |
| Registry panel | `registry_read` / `write` / `delete` | Single or bulk Win (confirm) |
| Live Desktop | `screenshot` poll 3s | Single-node toggle |
| Wake-on-LAN | `POST /agents/{id}/wol` | Works offline |
UI: collapsible **Network**, **Persistence**, **Fleet Maintenance** groups in `CrucibleExpandedOps.tsx`; styling in `CruciblePage.css`.
### Phase B — partial
| Op | Status |
|----|--------|
| `arp_neighbors` | **Done**`deploy.ArpNeighborIPs()` JSON |
| `camera_list` + picker | **Done** — device via `command` on `camera_snapshot` |
| `persistence_audit` | **Done** — read-only Run/tasks/systemd/launchd JSON |
| `kill_process` | **Done**`{ command: pid }` |
| `delete_path` / `move_path` | **Done** — file_ops guards (no dirs/system roots) |
### Phase C — done (2026-06-04)
| 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).
|----|---------|-------|
| 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).
---
## Agent (Go)
### Linux / macOS parity (2026-06-04 pass)
### 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`. |
| **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 still stub. |
| **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`. |
| **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:** firewall aggressive ops, camera, GPU miner — stubs or partial.
- **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.
### Fixed (2026-06-04)
- **`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 |
| Area | Fix |
|------|-----|
| **Mesh** | Relay path uses `write()` under `AgentClient.mu` (no direct `conn` read); `MeshNode.Stop()` tears down mDNS/host; one-way relay documented; unit tests in `client/mesh_test.go` and `client/mesh_p2p_test.go` (`-tags p2p`). |
| **Miner** | `HashAtNonce` returns `ErrEngineNotReady` / `ErrBlobTooShort` instead of empty+nil; edge-case tests updated in `miner/engine_test.go`. |
| **Spread** | Shared `deploy/subnet.go`: IPv6 local IPs + /64 prefix matching, IPv4-only active sweep; SSH/SMB prerequisites documented in `subnet.go` and autospread entrypoints. |
---
## Integration audit (2026-06-04)
*Postalive-UI wave verification. Commands: `go test ./...` in `agent/` and `server/`; `go test ./internal/api/... ./internal/db/...`; `npm run test -- --run` + `npm run build` in `server/web`.*
### 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
| Item | Fix |
|------|-----|
| Stale `server/webroot` | Clean-synced from `server/web/dist` after `npm run build` (59 files; includes Mission Deck chunks, wiki search, spread landing). Run `devrun.bat` or `xcopy dist → webroot` after each frontend build. |
| `fleetModules.ts` fallbacks | UI fallbacks aligned with embedded `ModuleManifest` packs in `modules.go` (capabilities + descriptions). |
| `data/modules/*.json` | Verified consistent with embedded manifests (signatures computed at load time). |
### Verified (no code change needed)
| Item | Status |
|------|--------|
| `modules_test.go`, `fleet_policy_test.go`, `campaign.go` | Compile + tests **PASS** (`go test ./internal/api/... ./internal/db/...`) |
| `operatorDeck.css` import | `Layout.tsx` imports `../../styles/operatorDeck.css` — build passes |
| `/mission-deck` route | Registered in `App.tsx`; `App.test.tsx` + `MissionDeckPage.test.tsx` pass |
| `/docs` wiki search | `public/docs/index.html` + `wiki.js` ship in `dist/docs/`; search indexes `h3`/`h4` + body blocks |
| Root `go test ./...` | **N/A** no root `go.mod`; run per-module (`agent/`, `server/`) |
| 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
### Open (document-only / ops)
| Issue | Notes |
|-------|--------|
| `server/webroot` not auto-synced on `npm run build` | Manual step via `devrun.bat` or copy; stale webroot served old hashed assets (e.g. missing `MissionDeckPage-*` chunks) |
| Vitest stderr noise | `FleetTopologyMap` three.js tags warn in happy-dom — tests pass (63 files / 562 tests) |
| `fusion/` package tests | Still none — coverage only in `server/internal/builder/fusion_*_test.go` |
|-------|-------|
| **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).