---
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).
