53a3cdbda9
- .claude/rules/registry_calls.md - apps/dag_engine/README.md - apps/dag_engine/app.md - docs/capabilities/INDEX.md - docs/capabilities/systemd.md - docs/execution_standard.md - dev/proposals_e2e_checks_0121/ - docs/capabilities/backends.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
259 lines
11 KiB
Markdown
259 lines
11 KiB
Markdown
# Capability: backends
|
|
|
|
Catalogo de stacks backend usados en `apps/`. Para que un agente que va a construir un backend nuevo sepa **que stack elegir**, **que funciones del registry componer** y **que esqueleto copiar** sin reinventar nada.
|
|
|
|
No es un grupo `tag:` del registry — es una guia transversal (los stacks atraviesan dominios). Complementa `cpp_apps.md` (frontend C++) y `deploy.md` (entrega).
|
|
|
|
## Stacks soportados
|
|
|
|
| Stack | Lang | Cuando elegir | Apps de referencia |
|
|
|---|---|---|---|
|
|
| **Go net/http stdlib + SQLite** | go | **Default.** API HTTP local o detras de Traefik. Persistencia SQLite con migraciones embed.FS. | `sqlite_api`, `services_api`, `registry_api`, `kanban`, `deploy_server`, `dag_engine`, `agent_runner_api`, `call_monitor` |
|
|
| **Go MCP stdio/http** | go | Tool server para Claude/agentes. Lectura/escritura registry. | `registry_mcp` |
|
|
| **Go bubbletea TUI** | go | CLI interactiva sin HTTP. Pipeline launcher, docker manager. | `pipeline_launcher`, `docker_tui`, `dev_console` |
|
|
| **Go mautrix bot** | go | Bot Matrix con E2EE, LLM tools, comandos. | `agents_and_robots` |
|
|
| **Python httpx + YAML** | py | Cliente declarativo de API REST externa (Metabase, Gitea, etc.). Pull/push contra disco. | `auto_metabase`, `metabase_registry` |
|
|
| **Bash docker-compose** | bash | Stack multi-container (Synapse + Element + LiveKit, PostGIS + Valhalla). | `element_matrix_chat`, `footprint_geo_stack` |
|
|
| **C++ ImGui frontend** | cpp | NO es backend — cliente HTTP/WS de los Go services. Ver `cpp_apps.md`. | `services_monitor`, `registry_dashboard`, ... |
|
|
|
|
## Decision tree
|
|
|
|
```
|
|
¿Necesitas HTTP API?
|
|
├─ si → ¿Es para Claude/agentes?
|
|
│ ├─ si → MCP server (Go) → copiar `registry_mcp`
|
|
│ └─ no → Go net/http stdlib + SQLite + embed.FS migrations → copiar `services_api`
|
|
│
|
|
└─ no → ¿Interactivo terminal?
|
|
├─ si → Go bubbletea → copiar `pipeline_launcher`
|
|
└─ no → ¿Cliente de API externa?
|
|
├─ si → Python httpx → copiar `auto_metabase`
|
|
└─ no → ¿Stack de containers?
|
|
├─ si → Bash + docker-compose.yml → copiar `element_matrix_chat`
|
|
└─ no → reevalua, probablemente sea funcion del registry no app
|
|
```
|
|
|
|
## Esqueleto canonico: Go net/http stdlib + SQLite (el default)
|
|
|
|
### Layout
|
|
|
|
```
|
|
apps/<name>/
|
|
app.md # frontmatter + service: block (issue 0105)
|
|
main.go # flags + listener + signal handling
|
|
server.go # http.ServeMux + routes + middleware chain
|
|
db.go # sql.Open con WAL + FK + apply migrations
|
|
handlers_*.go # 1 archivo por recurso
|
|
migrations/
|
|
001_init.sql # CREATE TABLE IF NOT EXISTS (idempotente)
|
|
002_*.sql # aditivo. NUNCA modificar 001 ya commiteado
|
|
operations.db # SQLite local. Gitignored.
|
|
```
|
|
|
|
### Frontmatter app.md
|
|
|
|
```yaml
|
|
---
|
|
name: my_service
|
|
lang: go
|
|
domain: infra # ver list_domains
|
|
version: 0.1.0
|
|
description: "1 linea: que hace + por que existe."
|
|
tags: [service, api, http, sqlite]
|
|
uses_functions:
|
|
- sqlite_apply_versioned_migrations_go_infra
|
|
- http_serve_go_infra
|
|
- http_json_response_go_infra
|
|
- http_parse_body_go_infra
|
|
- http_router_go_infra
|
|
- http_cors_middleware_go_infra
|
|
- http_logger_middleware_go_infra
|
|
- logger_go_infra
|
|
uses_types: []
|
|
framework: "net/http"
|
|
entry_point: "main.go"
|
|
dir_path: "apps/my_service"
|
|
service: # OBLIGATORIO si tag service. issue 0105
|
|
port: 8500
|
|
health_endpoint: /api/health
|
|
health_timeout_s: 3
|
|
systemd_unit: my_service.service
|
|
systemd_scope: user
|
|
restart_policy: always # NUNCA on-failure (ver gotcha cpp_apps.md)
|
|
runtime: systemd-user
|
|
pc_targets: [home-wsl]
|
|
is_local_only: false
|
|
---
|
|
```
|
|
|
|
### main.go (minimo viable)
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"log"
|
|
"os/signal"
|
|
"syscall"
|
|
)
|
|
|
|
func main() {
|
|
bind := flag.String("bind", "127.0.0.1:8500", "addr to listen on")
|
|
dbPath := flag.String("db", "operations.db", "sqlite path")
|
|
flag.Parse()
|
|
|
|
ctx, cancel := signal.NotifyContext(context.Background(),
|
|
syscall.SIGINT, syscall.SIGTERM)
|
|
defer cancel()
|
|
|
|
db, err := openDB(*dbPath) // db.go — usa sqlite_apply_versioned_migrations
|
|
if err != nil { log.Fatal(err) }
|
|
defer db.Close()
|
|
|
|
srv := newServer(db) // server.go — http.ServeMux + rutas
|
|
log.Printf("listening on %s", *bind)
|
|
if err := serve(ctx, *bind, srv); err != nil { log.Fatal(err) }
|
|
}
|
|
```
|
|
|
|
### Build + service unit
|
|
|
|
```bash
|
|
CGO_ENABLED=1 go build -tags fts5 -o my_service .
|
|
systemctl --user enable --now my_service.service # tras generar unit
|
|
```
|
|
|
|
## Esqueleto canonico: MCP server Go
|
|
|
|
Copia `registry_mcp`:
|
|
- `main.go` parsea flags (`--stdio` o `--http`).
|
|
- Cada tool = funcion Go con schema JSON y handler.
|
|
- Schema generation con `jsonschema` reflect.
|
|
- Read-only por default; `--enable-run` / `--enable-write` gating.
|
|
|
|
Funciones del registry relevantes: cualquier de `mcp__registry__fn_*` para entender shape. Patron de gating en `apps/registry_mcp/main.go`.
|
|
|
|
## Esqueleto canonico: Python httpx cliente declarativo
|
|
|
|
Copia `auto_metabase`:
|
|
- `python/.venv/bin/python3` como interprete.
|
|
- Importa wrappers del registry (`from infra import metabase_auth, metabase_get_dashboard, ...`).
|
|
- YAML manifest define estado deseado.
|
|
- Pull = volcar a YAML. Push = enviar a API.
|
|
- NUNCA `requests.post(...)` directo — siempre via wrapper que ya hace auth + retry + telemetria.
|
|
|
|
```python
|
|
import sys, os
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "python", "functions"))
|
|
from infra import metabase_auth, metabase_list_dashboards
|
|
session = metabase_auth(host, user, pwd)
|
|
for d in metabase_list_dashboards(session):
|
|
...
|
|
```
|
|
|
|
## Esqueleto canonico: bubbletea TUI
|
|
|
|
Copia `pipeline_launcher`:
|
|
- `tea.Model` con `Init/Update/View`.
|
|
- Sin HTTP. Estado local + acciones sobre disco/registry.
|
|
- Tag `launcher` si debe aparecer en el Pipeline Launcher (function_tags.md).
|
|
|
|
## Esqueleto canonico: Bash docker-compose stack
|
|
|
|
Copia `element_matrix_chat`:
|
|
- `docker-compose.yml` + `.env` + scripts `up.sh`/`down.sh`.
|
|
- Sin codigo Go/Py — solo composicion de imagenes.
|
|
- Tag `service` + `runtime: docker-compose` en `service:`.
|
|
- Deploy via `docker_compose_remote_deploy_bash_infra` (ver capability `deploy`).
|
|
|
|
## Funciones del registry — paleta backend Go
|
|
|
|
| ID | Para |
|
|
|---|---|
|
|
| `sqlite_apply_versioned_migrations_go_infra` | Aplicar `migrations/*.sql` con embed.FS al arrancar |
|
|
| `sqlite_open_*_go_infra` | Abrir SQLite con WAL + foreign keys + ping |
|
|
| `http_serve_go_infra` | Listener con graceful shutdown via ctx |
|
|
| `http_router_go_infra` | `*http.ServeMux` desde `[]Route` declarativo |
|
|
| `http_json_response_go_infra` | Escribir JSON + status code |
|
|
| `http_error_response_go_infra` | Errores con shape consistente |
|
|
| `http_parse_body_go_infra` | Decode JSON con maxBytes (anti-DoS) |
|
|
| `http_cors_middleware_go_infra` | CORS para frontend ImGui/web |
|
|
| `http_logger_middleware_go_infra` | Log estructurado por request |
|
|
| `http_middleware_chain_go_infra` | Componer middlewares |
|
|
| `http_session_cookie_middleware_go_infra` | Sesiones cookie con TTL |
|
|
| `http_session_cookie_set_go_infra` / `clear` / `extract` | CRUD cookie sesion |
|
|
| `jwt_middleware_go_infra` | Auth JWT (alternativa a cookies) |
|
|
| `crud_generate_handlers_go_infra` | 5 handlers REST a partir de `CRUDResource` |
|
|
| `crud_register_routes_go_infra` | Registrar `GET/POST/PUT/DELETE /resource[/id]` en mux |
|
|
| `file_serve_go_infra` | Static files con `Cache-Control: max-age` |
|
|
| `health_check_http_go_infra` | Polling de URL hasta 2xx (smoke en tests) |
|
|
| `logger_go_infra` | Logger estructurado (consumido por log_window apps C++) |
|
|
| `notify_telegram_go_infra` | Alertas a chat (opcional) |
|
|
| `ssh_exec_go_infra` | Ejecutar comandos en host remoto (services cross-PC) |
|
|
|
|
Buscar mas: `mcp__registry__fn_search query="http" lang="go" tag="service"`.
|
|
|
|
## Patron CRUD declarativo
|
|
|
|
Si el backend expone una entidad CRUD plana (cards, jobs, deploys), evita escribir 5 handlers a mano:
|
|
|
|
```go
|
|
res := infra.CRUDResource{
|
|
Table: "cards",
|
|
PKColumn: "id",
|
|
Columns: []string{"id","title","status","created_at"},
|
|
}
|
|
mux := http.NewServeMux()
|
|
infra.CRUDRegisterRoutes(mux, "/api/cards", res, db)
|
|
// GET /api/cards → list
|
|
// GET /api/cards/{id} → get
|
|
// POST /api/cards → create
|
|
// PUT /api/cards/{id} → update
|
|
// DELETE /api/cards/{id} → delete
|
|
```
|
|
|
|
## Service: block (issue 0105) — checklist obligatorio
|
|
|
|
Toda app con `tag: service` declara:
|
|
|
|
- `port` (null si stdio).
|
|
- `health_endpoint` (ruta GET 2xx → sano).
|
|
- `health_timeout_s` (default 3).
|
|
- `systemd_unit` si `runtime` empieza con `systemd-`.
|
|
- `systemd_scope`: `user` | `system`.
|
|
- `restart_policy`: **`always`** (no `on-failure` — ver gotcha en `function_tags.md`).
|
|
- `runtime`: `systemd-user` | `systemd-system` | `docker-compose` | `stdio` | `manual`.
|
|
- `pc_targets[]`: pc_ids de `pc_locations`.
|
|
|
|
Auditar: `fn doctor services-spec`.
|
|
|
|
## Gotchas comunes
|
|
|
|
| Gotcha | Causa | Solucion |
|
|
|---|---|---|
|
|
| Service muere en `SIGTERM` y systemd no reinicia | `Restart=on-failure` en unit | Cambiar a `Restart=always`. `sqlite_api` cayo 20h asi (2026-05-17) |
|
|
| `database is locked` al escribir desde 2 procesos | SQLite sin WAL | Abrir con `?_journal_mode=WAL&_foreign_keys=on` (usar `sqlite_open_*`) |
|
|
| Frontend ImGui no recibe CORS preflight | Falta `http_cors_middleware` | Anadir middleware antes del router |
|
|
| Body POST llega vacio | `json.NewDecoder(r.Body)` sin maxBytes — falla silenciosa | Usar `http_parse_body_go_infra` con `maxBytes=10MB` |
|
|
| Migracion 002 borra datos en otros PCs al hacer `fn sync` | Migracion destructiva | Solo aditivo. Ver `db_migrations.md` |
|
|
| MCP tool no aparece en Claude tras anadirla | Schema no regenerado | Rebuild + `claude mcp remove/add` o reset cache |
|
|
|
|
## Fronteras (que NO cubre esta capability)
|
|
|
|
- **Apps C++ frontend** → `cpp_apps.md` + capability `cpp-dashboard-viz`.
|
|
- **Deploy** del backend a VPS → capability `deploy` (Docker+Traefik, systemd, rsync).
|
|
- **Frontend web Vite/React/Mantine** consumido por el backend → capability `mantine`.
|
|
- **Migraciones de schema** → regla `db_migrations.md`.
|
|
- **Telemetria de calls** del agente al registry → `registry_calls.md` + `call_monitor` app.
|
|
|
|
## Cuando promover a pipeline / extraer al registry
|
|
|
|
Si tras construir el N-esimo Go service detectas patron repetido (>2 apps):
|
|
|
|
1. Buscar funcion existente en registry (`mcp__registry__fn_search`).
|
|
2. Si no existe → spawn `fn-constructor` con tag de grupo (`http`, `service`, etc.).
|
|
3. Migrar las apps existentes para consumirla.
|
|
4. Capability page se actualiza sola via `fn doctor capabilities`.
|