nexus/.planning/phases/30-hardware-detection-mode-selection/30-01-SUMMARY.md
Nexus Dev 949f09ac54 docs(30-01): complete hardware detection + nexus settings plan
- Add 30-01-SUMMARY.md with execution record and deviation docs
- Update STATE.md: plan advanced to 2/2, progress 50%, decisions logged
- Update ROADMAP.md: phase 30 progress updated (1/2 plans complete)
- Update REQUIREMENTS.md: ONBD-01, ONBD-02, ONBD-03 marked complete
2026-04-04 03:55:49 +00:00

142 lines
7.8 KiB
Markdown

---
phase: 30-hardware-detection-mode-selection
plan: 01
subsystem: api
tags: [hardware-detection, systeminformation, settings-persistence, ollama, model-catalog, zod]
# Dependency graph
requires:
- phase: 28-ollama-integration
provides: ollama service, model catalog, getRecommendedModel
provides:
- hardwareService with Apple Silicon / GPU / cpu_only tier detection and 5-min cache
- nexusSettingsService with Zod-validated file-backed mode persistence
- Unauthenticated GET /api/system/providers endpoint for pre-auth hardware probe
- GET /api/system/providers/recommendation — catalog filtered by detected tier
- Board-auth-gated GET/PATCH /api/nexus/settings for mode persistence
- Extended ollama-model-catalog.json with tier arrays on all variants and qwen3 family
- getRecommendedModel supports optional hardwareTier parameter for tier-aware filtering
affects: [30-02-onboarding-ui, 31-cloud-providers, 33-personal-ai-assistant]
# Tech tracking
tech-stack:
added: [systeminformation@5]
patterns:
- Promise.race with timeout for unreliable system calls (GPU detection)
- vi.hoisted() for mock functions referenced in vi.mock() factories
- File-backed JSON settings with Zod schema and graceful default fallback
- Unauthenticated route mounted before boardMutationGuard by placing before api = Router()
key-files:
created:
- server/src/services/hardware.ts
- server/src/services/nexus-settings.ts
- server/src/routes/hardware.ts
- server/src/routes/nexus-settings.ts
- server/src/__tests__/30-hardware-detection.test.ts
modified:
- server/src/services/ollama.ts
- server/src/data/ollama-model-catalog.json
- server/src/app.ts
key-decisions:
- "Use Promise.race with 3000ms timeout for si.graphics() — failure degrades to cpu_only tier, never blocks startup"
- "Apple Silicon detected via process.platform === 'darwin' && cpuModel.startsWith('Apple') — does NOT call si.graphics()"
- "Hardware routes mounted with app.use('/api', hardwareRoutes()) before const api = Router() to bypass boardMutationGuard"
- "nexusSettingsService uses Zod z.enum(NEXUS_MODES).default('both') — validation at write time, default at read time"
- "Model catalog tier arrays: cpu_only models serve all tiers, apple_silicon excludes large GPU-only models, gpu tier gets all"
patterns-established:
- "Promise.race timeout pattern: pair any unreliable external call with a reject-after-N-ms promise"
- "vi.hoisted() pattern: use for mock function references needed inside vi.mock() factory callbacks"
- "File-backed settings: mkdirSync recursive + writeFileSync in set(), graceful default in get() catch block"
requirements-completed: [ONBD-02, ONBD-03, ONBD-01]
# Metrics
duration: 15min
completed: 2026-04-02
---
# Phase 30 Plan 01: Hardware Detection, Nexus Settings, Model Catalog Summary
**Hardware tier detection (Apple Silicon/GPU/CPU-only) via systeminformation with 3s timeout, file-backed mode persistence via Zod-validated nexus-settings service, extended model catalog with tier arrays, and unauthenticated /api/system/providers endpoint**
## Performance
- **Duration:** 15 min
- **Started:** 2026-04-02T23:16:00Z
- **Completed:** 2026-04-02T23:22:00Z
- **Tasks:** 2
- **Files modified:** 8
## Accomplishments
- Hardware detection service with Apple Silicon / discrete GPU / CPU-only tiers, 3-second timeout on GPU probe, 5-minute cache
- Nexus settings service persisting mode to `data/nexus-settings.json` with Zod validation and graceful default
- Extended model catalog with `tier` arrays on all 11 variants plus new qwen3:8b family (12 variants total)
- `getRecommendedModel` upgraded with optional `hardwareTier` parameter for tier-aware filtering
- Unauthenticated `GET /api/system/providers` endpoint mounted before boardMutationGuard
- Board-auth-gated `GET/PATCH /api/nexus/settings` for mode persistence
- 13 passing unit tests covering all hardware tiers, settings CRUD, catalog validation, and tier filtering
## Task Commits
1. **Task 1: Hardware service, nexus-settings service, model catalog, and tests** - `766460a1` (feat)
2. **Task 2: Hardware and nexus-settings routes, app.ts mounting** - `86e30e5c` (feat)
## Files Created/Modified
- `server/src/services/hardware.ts` - hardwareService factory with Apple Silicon/GPU/cpu_only detection and cache
- `server/src/services/nexus-settings.ts` - nexusSettingsService with Zod validation and file persistence
- `server/src/routes/hardware.ts` - Unauthenticated GET /system/providers and /system/providers/recommendation
- `server/src/routes/nexus-settings.ts` - Board-auth-gated GET/PATCH /nexus/settings
- `server/src/__tests__/30-hardware-detection.test.ts` - 13 unit tests using vi.hoisted() pattern
- `server/src/services/ollama.ts` - Added tier?: string[] to CatalogVariant, hardwareTier param to getRecommendedModel
- `server/src/data/ollama-model-catalog.json` - Added tier arrays to all variants, added qwen3 family
- `server/src/app.ts` - Imported and mounted hardwareRoutes (before api router) and nexusSettingsRoutes (on api router)
## Decisions Made
- Used `Promise.race` with a 3000ms timeout for `si.graphics()` — this matches the plan spec and ensures GPU probe never blocks the server for more than 3 seconds
- Apple Silicon path intentionally skips `si.graphics()` entirely — unified memory means GPU VRAM concept doesn't apply
- Hardware routes placed before `const api = Router()` to remain outside `boardMutationGuard` — this is the correct Express mounting position for unauthenticated endpoints
- Used `vi.hoisted()` to fix the Vitest mock hoisting issue — `mockGraphicsFn` must be initialized before `vi.mock()` factory runs
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Fixed vi.mock() hoisting issue in test file**
- **Found during:** Task 1 (TDD test implementation)
- **Issue:** `const mockGraphicsFn = vi.fn()` declared before `vi.mock()` but Vitest hoists `vi.mock()` calls, causing "Cannot access 'mockGraphicsFn' before initialization" error
- **Fix:** Used `vi.hoisted()` to initialize `mockGraphicsFn` in a hoisted context, and updated all test references to use it directly instead of `vi.mocked(si.default.graphics)`
- **Files modified:** server/src/__tests__/30-hardware-detection.test.ts
- **Verification:** All 13 tests pass
- **Committed in:** 766460a1 (Task 1 commit)
**2. [Rule 2 - Missing Critical] Added _resetHardwareCache export for test isolation**
- **Found during:** Task 1 (TDD test implementation)
- **Issue:** Module-level cache in hardware.ts bleeds between tests — hardware tier tests would return stale cached values
- **Fix:** Exported `_resetHardwareCache()` function from hardware.ts, called in `beforeEach` in test file
- **Files modified:** server/src/services/hardware.ts, server/src/__tests__/30-hardware-detection.test.ts
- **Verification:** Each test runs with fresh detection state
- **Committed in:** 766460a1 (Task 1 commit)
---
**Total deviations:** 2 auto-fixed (1 bug, 1 missing critical)
**Impact on plan:** Both fixes necessary for correct test behavior. No scope creep.
## Issues Encountered
- Existing test suite has 18 pre-existing failures across 5 test files (skill-registry-routes, agent-permissions-routes, heartbeat-workspace-session, app-hmr-port, plugin-worker-manager) — confirmed pre-existing by stash test, not caused by this plan's changes.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- Hardware probe API ready for consumption by Plan 02 onboarding UI
- Mode persistence API ready — onboarding can set mode via PATCH /api/nexus/settings
- Model catalog with tier arrays ready — onboarding can show tier-appropriate model recommendations
- `GET /api/system/providers/recommendation` combines hardware detection + catalog filtering in one call
---
*Phase: 30-hardware-detection-mode-selection*
*Completed: 2026-04-02*