Files
boocode/openspec/changes/boocontrol/artifacts/p1-impl-validation.md
indifferentketchup b18de2a331 chore: snapshot working tree - pty_exited notifications + in-flight inference WIP
feat(booterm): structured pty_exited WS notifications. Plan-validated, impl-validated, code-reviewed green (contracts build clean, contracts test 29/29, booterm + web typecheck clean).

wip: in-progress inference/provider refactor (agents.ts, provider.ts, new llama-providers.ts, removed llama-args-validator), plus arena, dispatcher, compaction, schema changes.

openspec: pty-exit-notifications complete; x-agent-flags planned (not yet implemented).
2026-06-14 12:48:47 +00:00

8.6 KiB

Validation: boocontrol (implementation mode)

Date: 2026-06-12 Mode: Implementation (all P1 tasks checked [x]) Size: Large (10-phase program, 15 P1 tasks)

Verdict

PASS-WITH-FINDINGS

openspec validate

Skipped (pre-spec-format acceptance; validation against openspec CLI format not applicable to accepted spec per implementation-plan.md).

Verification commands

All four verification commands passed:

  • pnpm -C packages/contracts build -- PASS
  • pnpm -C packages/contracts test -- PASS (29 tests)
  • pnpm -C apps/control build -- PASS
  • pnpm -C apps/control test -- PASS (32 passed, 2 skipped DB-integration)
  • pnpm -C apps/server build -- PASS
  • pnpm -C apps/server test -- PASS (575 passed, 11 skipped)
  • npx tsc -p apps/web/tsconfig.app.json --noEmit -- PASS (no errors)

Traceability

Task Claim Evidence Status
P1.1 Scaffold apps/control: Fastify, TS NodeNext, .env.example, port 9503, /api/health, systemd unit apps/control/package.json:1 (deps), apps/control/src/index.ts:199 (Fastify), :227-234 (/api/health), apps/control/boocontrol.service, apps/control/.env.example TRUE
P1.2 db.ts with applySchema + waitForTable (poll information_schema, throw on timeout) apps/control/src/db.ts:29-45 (waitForTable with exponential backoff, throws on timeout), :47-51 (applySchema), apps/control/src/index.ts:218 (waitForTable called before applySchema) TRUE
P1.3 schema.sql: all tables with correct UNIQUE constraints, NO source column, V11 indexes apps/control/src/schema.sql:6-16 (control_hosts), :19-23 (seed ON CONFLICT DO NOTHING), :26-43 (control_requests UNIQUE(provider_id, swap_entry_id, ts)), :45-46 (idx), :49-58 (control_perf_samples UNIQUE + idx), :61-67 (control_perf_rollup_5m UNIQUE), :70-80 (control_model_events UNIQUE + idx). Grep for source in schema.sql: 0 matches. TRUE
P1.4 Fleet connector: SSE + backoff+jitter+circuit-breaker, connected/reconnecting/down state, reconcile ON CONFLICT DO NOTHING, gap_suspected no-overlap fleet-connector.ts:19-23 (addJitter 0-50%), :43-51 (reconnectDecision), :33-37 (6 max attempts), index.ts:44-98 (handleLlamaSweepEvent ON CONFLICT DO NOTHING), :102-154 (handleReconcile gap detection: oldest reconcile vs newest persisted), fleet-state.ts:13 (liveness type) TRUE
P1.5 Perf poller: 5s, /api/performance?after=, watermark MAX(ts), NULL watermark omits after index.ts:158-193 (pollPerformance), :168-169 (MAX(ts)), :172 (null watermark omits afterParam), :265-273 (setInterval 5000) TRUE
P1.6 In-memory fleet state + per-host monotonic seq + WS snapshot-on-join + seq-stamped deltas + restart rebuild from DB ws.ts:15-56 (snapshot on join), fleet-state.ts:11-17 (HostState with seq), index.ts:33-36 (incrementSeq). Note: restart rebuild is commented but not implemented -- fleet starts empty. TRUE (partial)
P1.7 Retention: rollup idempotent upsert + chunked delete + activity prune + capture cap + configurable windows retention.ts:34-67 (runRollup ON CONFLICT DO UPDATE), :73-90 (pruneRawSamples chunked), :95-100 (pruneActivity), :105-110 (pruneModelEvents), :115-121 (trimCapture), config.ts:9-13 (configurable defaults), index.ts:276-285 (daily timer) TRUE
P1.8 5 frame types in WsFrameSchema + KNOWN_FRAME_TYPES + web strict union ws-frames.ts:492-552 (5 Control*Frame in WsFrameSchema), :761-765 (5 in KNOWN_FRAME_TYPES), apps/web/src/api/types.ts:539-595 (5 frame types defined), :801-805 (5 in WsFrame union) TRUE
P1.9 Server proxy: registerControlProxy + BOOCONTROL_URL + keep-in-sync comments control-proxy.ts:19-88 (registerControlProxy), index.ts:282-283 (BOOCONTROL_URL), control-proxy.ts:16 (keep-in-sync), coder-proxy.ts:16 (keep-in-sync) TRUE
P1.10 /control route, nav entry, Control.tsx shell, useControlStream singleton + context App.tsx:139 (Route /control), ProjectSidebar.tsx:567-577 (nav entry Radio icon), Control.tsx:1-53 (Fleet+Activity tabs), useControlStream.tsx:129-226 (ControlProvider context + WS singleton) TRUE
P1.11 Fleet tab: host cards, state chips with color/glow, VRAM/temp/power, TTL rings HostCard.tsx:11-18 (STATE_COLORS), :48-179 (motion layout), VramGauge.tsx (gauge), TtlRing.tsx (TTL rings), FleetTab.tsx TRUE
P1.12 Activity feed: react-virtuoso tail-follow, followOutput=bottom, filter chips, pause-on-scroll ActivityTab.tsx:166-184 (Virtuoso followOutput), :28-48 (filter chips), :146-161 (pause toggle) TRUE
P1.13 ECharts via echarts/core modular imports + buildEChartsTheme from CSS vars buildEChartsTheme.ts:1-25 (getComputedStyle), PerfChart.tsx:1-14 (modular imports), VramGauge.tsx:1-8, TtlRing.tsx:1-8 TRUE
P1.14 acquireHostAccess no-op seam in host-access.ts host-access.ts:13-18 (returns {ok: true}, V1 no-op, P8 seam) TRUE
P1.15 Tests: connector + liveness + retention + seq + DB tests fleet-connector.test.ts (10 tests), liveness.test.ts (7), retention.test.ts (4), seq-logic.test.ts (6), reconcile.test.ts (2, skipped w/o DB), fleet-state.test.ts (5) TRUE

Findings

F1: Hardcoded oklch colors in ECharts components (Advisory)

  • Location: apps/web/src/components/control/VramGauge.tsx:35-37, TtlRing.tsx:40-42
  • Evidence: Six oklch() color literals for gauge progress (green/amber/red based on thresholds).
  • Impact: Task spec says "no hardcoded colors in components/control." These are ECharts inline color values for dynamic gauge progress that changes based on a computed threshold. ECharts requires explicit color values for series itemStyle; CSS vars are not consumed by ECharts config objects. The rest of the components correctly use CSS custom properties. The oklch values are the design S9 state-color tokens (green/amber/red glow). Not blocking.

F2: Snapshot rebuild from DB not implemented (Advisory)

  • Location: apps/control/src/index.ts:15-16 (fleet starts empty), apps/control/src/routes/ws.ts:13 (comment documents intent)
  • Evidence: On restart, createFleetState() returns empty hosts Map. The WS endpoint serves this empty state. The ws.ts comment documents the rebuild intent but no DB-rebuild code exists. JD20's claim was "rebuild fleet state from DB before serving snapshots."
  • Impact: After a BooControl restart, connected clients see empty fleet state until the next SSE event arrives and repopulates. Functional for a single-user dev setup; the SSE reconcile catches up within seconds. Not blocking for P1.

F3: Reconcile test is a placeholder (Advisory)

  • Location: apps/control/src/services/tests/reconcile.test.ts:9-27
  • Evidence: Both tests contain expect(true).toBe(true) with TODO comments describing what the real test would do. The test file is gated with describe.runIf(!!DATABASE_URL) and skipped without DB, but even with DB the assertions are no-ops.
  • Impact: The gap detection logic in index.ts:102-154 is untested. The pure helpers for jitter, reconnect, liveness, seq, and retention ARE tested. Not blocking for P1 but should be addressed before P2.

F4: SSE event parsing is fragile (Advisory)

  • Location: apps/control/src/services/fleet-connector.ts:155-173
  • Evidence: The SSE line parser uses trimmed.split(':')[0] to extract the event type. llama-swap SSE events may have colons in the event type line itself (e.g. event: modelStatus). The parser relies on the first colon split, which works for simple event names but is fragile if the SSE format changes.
  • Impact: Works for the current llama-swap SSE format. Not blocking for P1.

Claims I did not verify

  • Deploy docs in root CLAUDE.md for boocontrol (P1.1 claim mentions "deploy docs in root CLAUDE.md include BOOCONTROL_URL for apps/server proxy, DATABASE_URL for shared boochat DB") -- not checked; this is documentation, not code conformance.
  • The drift test extended to cover five new frames (P1.8 claim in implementation-plan.md says "extend the contracts drift test to cover the five new frames") -- the existing ws-frames.test.ts checks KNOWN_FRAME_TYPES vs WsFrameSchema alignment, which implicitly covers the 5 new frames since they are in both. There is no explicit per-frame test case for control frames, but the drift test at line 119-135 iterates all KNOWN_FRAME_TYPES entries. The plan noted "web strict union sync is manual" and added a comment in the test noting this limitation; that comment is not present in the test file.
  • @fastify/websocket in dependencies (JD5 claim) -- verified in package.json:16, TRUE.
  • Capture 256KB per-row cap enforced in application code (JD6 claim) -- verified in retention.ts:115-121 (trimCapture), TRUE.
  • 50MB default capture budget via CAPTURE_BUDGET_MB env (JD15 claim) -- verified in config.ts:13 (default 50), TRUE.