--- id: "0130b" title: "Backend Go kanban_cpp v2: schema + handlers + watcher + SSE" status: pendiente type: app domain: - apps-infra - dev-ux scope: app-scoped priority: alta depends: - "0130a" blocks: - "0130c" related: - "0130" created: 2026-05-22 updated: 2026-05-22 tags: [service, kanban, go, sqlite, sse] flow: "0130" --- # 0130b — Backend Go kanban_cpp v2 **Status:** pendiente ## Por que Servicio HTTP local que sirve los issues + flows del proyecto al frontend C++. Es un wrapper fino sobre las funciones del registry de 0130a + SQLite cache + watcher. ## Estructura ``` apps/kanban_cpp/backend/ app.md # tag service go.mod main.go # entry: flags + run db.go # open + apply migrations + upsert helpers handlers.go # endpoints REST sse_hub.go # broadcaster watcher.go # bind a watch_dir_fsnotify + re-ingesta + emit SSE ingest.go # scan → upsert; usa 0130a migrations/ 001_init.sql operations.db # creada en runtime ``` ## Endpoints | Verbo | Path | Notas | |---|---|---| | GET | `/api/health` | `{ok:true, version, count_issues, count_flows}` | | GET | `/api/issues` | filtros: `status`, `domain`, `priority`, `tag`, `scope` | | GET | `/api/issues/{id}` | issue + body | | PATCH | `/api/issues/{id}` | partial update frontmatter → `write_issue_md` + re-ingesta + SSE | | GET | `/api/flows` | filtros: `status`, `kind` | | GET | `/api/flows/{id}` | flow + body | | GET | `/api/meta` | enums leidos de `dev/TAXONOMY.md` | | GET | `/api/sse` | stream `{type, id, path}` | CORS abierto local (`*`). Logger middleware. ## Schema (migrations/001_init.sql) ```sql CREATE TABLE IF NOT EXISTS issues ( id TEXT PRIMARY KEY, title TEXT NOT NULL, status TEXT NOT NULL, type TEXT, scope TEXT, priority TEXT, domain_json TEXT NOT NULL DEFAULT '[]', tags_json TEXT NOT NULL DEFAULT '[]', depends_json TEXT NOT NULL DEFAULT '[]', blocks_json TEXT NOT NULL DEFAULT '[]', related_json TEXT NOT NULL DEFAULT '[]', flow_id TEXT, body TEXT NOT NULL DEFAULT '', file_path TEXT NOT NULL, mtime_ns INTEGER NOT NULL, created_at TEXT, updated_at TEXT, completed INTEGER NOT NULL DEFAULT 0 -- 1 si vive en completed/ ); CREATE INDEX IF NOT EXISTS idx_issues_status ON issues(status); CREATE INDEX IF NOT EXISTS idx_issues_priority ON issues(priority); CREATE TABLE IF NOT EXISTS flows ( id TEXT PRIMARY KEY, title TEXT NOT NULL, status TEXT, kind TEXT, tags_json TEXT NOT NULL DEFAULT '[]', body TEXT NOT NULL DEFAULT '', file_path TEXT NOT NULL, mtime_ns INTEGER NOT NULL ); ``` ## DoD - `curl http://localhost:8487/api/health` devuelve 200 + counts. - `curl http://localhost:8487/api/issues | jq 'length' >= 90`. - `curl -X PATCH /api/issues/0130 -d '{"status":"in-progress"}'` reescribe `dev/issues/0130-*.md` (status updated, body intacto). - Despues del PATCH, suscriptor SSE recibe evento `{type:"updated", id:"0130"}`. - Tras `mv dev/issues/0130-*.md dev/issues/completed/`, watcher actualiza fila (`completed=1`). - `go test ./...` verde. ## Anti-scope - No expone proposals ni capabilities (eso es MCP registry). - No autentica (local-only por ahora). - No persiste estado UI (eso lo hace el frontend).