Files
gstack/design/test
Garry Tan 14f3ab570c feat(design): introduce design daemon — multi-board persistent server
Adds design/src/daemon.ts: a Bun.serve daemon that hosts many boards
under /boards/<id>/ instead of one server per `$D compare --serve` call.
Spawned by daemon-client (next commit); for now wired only via tests.

Endpoint table:
  GET  /health                       liveness + version + counts (unauth)
  GET  /                             index of recent boards
  POST /api/boards                   publish; daemon derives sourceDir
                                     from realpath(html). body sourceDir
                                     IGNORED (Codex trust-boundary fix).
  POST /shutdown                     graceful; refuses if active boards
                                     exist (Codex data-loss fix)
  GET  /boards/<id>                  301 → /boards/<id>/ (trailing slash
                                     is load-bearing — relative URLs in
                                     board JS resolve against pathname)
  GET  /boards/<id>/                 render board HTML
  GET  /boards/<id>/api/progress     state machine status (no idle reset)
  POST /boards/<id>/api/feedback     submit/regen; writes feedback.json
                                     or feedback-pending.json with
                                     boardId + publishedAt augmented in
  POST /boards/<id>/api/reload       swap HTML; per-board allowedDir
                                     guard rejects traversal, directories,
                                     out-of-allowed-dir symlinks

Lifecycle:
- 24h idle timeout (DESIGN_DAEMON_IDLE_MS for tests).
- Idle with active boards extends 1h up to 4x, then force-shuts (Codex).
- LRU cap 50 boards; evicts done before non-done; 503 when 50 non-done.
- Per-board async mutex serializes feedback POST vs reload POST.
- SIGTERM/SIGINT/uncaughtException → graceful shutdown, state file unlink.
- Stdout: DAEMON_STARTED port=<N> (the line the client parses).

Shared utilities live in design/src/daemon-state.ts: atomic state-file
write/read (mode 0o600), fs.openSync('wx') lock, isProcessAlive, cmdline
identity verification (/proc on Linux, ps on macOS), CMDLINE_MARKER
constant. Modeled on browse/src/cli.ts lock + spawn patterns.

design/test/daemon.test.ts: 30 tests, all green. Covers every endpoint,
both error paths and happy paths, cross-board feedback isolation, the
trailing-slash redirect, the directory-not-file reload rejection, LRU
preferring done over non-done, /shutdown refusal with active boards,
all path-traversal guards. Uses the exported fetchHandler in-process
(no spawn) so the suite runs in ~70ms.

design/test/daemon-tests-fixtures.ts: shared helpers — req() builder,
tmp-dir helpers, daemon reset, and a spawnDaemonForTest() helper used
by the next commit's discovery tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 15:24:05 -07:00
..