--- name: backend-tool-builder description: Genera un backend FastAPI con endpoints que WxO importa como tools vía OpenAPI (patrón Dun). Incluye spec filter, audit_logs, write_audit, webhook validator HMAC, watchdog para casos atascados. Usar cuando el caso requiere lógica de negocio compleja, persistencia, y observabilidad. --- # Backend Tool Builder Tu trabajo es **escribir un backend FastAPI** que WxO va a importar como herramientas vía OpenAPI. Este es el patrón **Dun** — más robusto que el patrón Cotemar (mocks Python). ## Cuándo usar este patrón vs mocks Python | Situación | Recomendado | |---|---| | PoC / demo simple | mock-builder (Python wrappers a mocks) | | Lógica de negocio real | **backend-tool-builder** (esto) | | Persistencia necesaria | **backend-tool-builder** | | Múltiples casos en paralelo con state | **backend-tool-builder** | | Audit log / observabilidad fuerte | **backend-tool-builder** | ## Estructura del backend ``` backend/ ├── Dockerfile ├── app/ │ ├── __init__.py │ ├── main.py ← FastAPI app + routers │ ├── audit.py ← write_audit() │ ├── orchestrate/ │ │ ├── spec_filter.py ← /orchestrate-tools-spec.json │ │ └── webhook_validator.py ← HMAC-SHA256 │ ├── api/v1/ │ │ ├── __init__.py │ │ ├── cases.py ← endpoints "tools" │ │ ├── auth.py ← login / token │ │ └── webhooks.py ← receiver │ ├── models/ ← Pydantic schemas con coerción │ ├── db/ ← SQLAlchemy models + session │ └── settings.py └── requirements.txt ``` ## Patrones obligatorios ### 1. `write_audit` en cada handler ```python from app.audit import write_audit @router.post("/cases/run") def run_case(input: RunCaseInput, x_orchestrate_token: str = Header(...)): write_audit(input.case_id, "tool.run_case.started", {"input": input.dict()}) result = do_the_work(input) write_audit(input.case_id, "tool.run_case.completed", {"result": result}) return result ``` ### 2. Endpoint público filtrado ```python from .orchestrate.spec_filter import build_public_spec @app.get("/api/v1/orchestrate-tools-spec.json") def public_spec(): return build_public_spec(app) ``` Con allowlist `PUBLIC_TOOLS` definida en el módulo. ### 3. Webhook validator HMAC Usar el template `wxo/tools/openapi/_webhook_validator.py`. ### 4. Watchdog en GET /cases/{id} ```python @router.get("/cases/{case_id}") def get_case(case_id: str, db: Session = Depends()): case = db.get(Case, case_id) if case.status == "executing": if (datetime.utcnow() - case.updated_at) > timedelta(seconds=90): case.status = "paused" db.commit() write_audit(case_id, "watchdog.paused", {}) return case ``` ### 5. Async tasks con wrapper safe ```python async def _start_run_safe(case_id, ...): try: await orchestrate.start_run(...) except Exception as e: logger.exception("start_run failed") await db.update_case(case_id, status="failed", error=str(e)) # En el endpoint: await _start_run_safe(case_id, ...) ``` ### 6. Coerción en schemas Cada input model con `@field_validator(mode="before")` para int/list/bool. Usar helpers de `wxo/tools/python/_coercion_helpers.py`. ### 7. ExecutionStep con output_payload Para granular retry — cada substep persiste sus outputs. ### 8. Healthcheck Dockerfile ```dockerfile HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ CMD curl -sf http://localhost:8000/health || exit 1 ``` ## Entrada esperada ``` backend_name: endpoints: [ { method, path, summary, description, request_schema, response_schema, requires_auth: true/false } ] db_schema: [ { table, fields, indexes } ] auth_method: api_key_header # X-Orchestrate-Token audit_events: [ "tool.case_run.started", "tool.case_run.step_X", ... ] ``` ## Validación - [ ] `/health` responde 200 - [ ] `/api/v1/orchestrate-tools-spec.json` devuelve OpenAPI con `description` per-op y `security` per-op - [ ] `write_audit` se llama en TODOS los handlers expuestos - [ ] Pydantic models tienen coerción - [ ] Webhook validator implementado si recibe webhooks de WxO - [ ] Watchdog en GET /cases/{id} si hay state long-running - [ ] Dockerfile con healthcheck `curl -sf` o `wget -qO-` - [ ] No declara labels Traefik manuales en docker-compose (issue I-009)