NAC-76: Brun-E Backend - Implementacion ejecutable (A+B)
Linear: NAC-76 · Estimacion: 40h · Estado: En progreso
1) Objetivo de implementacion
Implementar sesiones de voz Brun-E en backend con OpenAI Realtime, garantizando:
- inicio seguro por token efimero (nunca API key en cliente),
- control de elegibilidad y sesion activa unica,
- sideband para tools de negocio (Fase B),
- cierre idempotente con persistencia y evento unico a E-map.
2) Trazabilidad documental (source of truth)
Este plan ejecuta y aterriza decisiones ya cerradas en:
- Diseno tecnico:
tech-brun-e-realtime-design.md - DRF negocio:
12-brun-e-sesiones-voz.md
Regla de precedencia:
- DRF define comportamiento de negocio y alcance funcional.
- Diseno tecnico define contratos, arquitectura y reglas operativas.
- Este documento define el orden de ejecucion y entregables.
3) Dependencias
- NAC-75 (configuracion Brun-E por entorno).
- Auth multi-tenant existente (JWT con
userId,organizationId,nivel). OPENAI_API_KEYen entorno servidor.BRUNE_PROMPT_IDyBRUNE_PROMPT_VERSIONunicos paradev|prod(decision vigente: mismo prompt en ambos ambientes).
4) Contratos cerrados a implementar
Decisión vigente C-01: Brun-E adopta formato JSON:API en respuestas y errores para consistencia con backend.
Decisión vigente C-02: exactly-once E-map se implementa con outbox transaccional + worker publicador y dedupe por session_id.
Decisión vigente C-03: single active session se garantiza con indice unico parcial en DB + mapeo de conflicto a 409 brun_e.session_active.
Decisión vigente C-04: handshake sideband usa Authorization Bearer JWT en upgrade WS + ownership por session_id + rechazo canonico.
Decisión vigente C-05: tool sideband con schemas estrictos v1 por funcion (arguments/result, additionalProperties: false).
4.1 POST /brun-e/start
- Auth:
Bearer JWT. - Orden obligatorio de validaciones:
- usuario autenticado,
- no tiene sesion activa (
409), - elegibilidad/consumo (
403o429). - Response
200:
{
"session_id": "uuid",
"ephemeral_key": "string",
"expires_at": "2026-03-17T20:15:30.000Z",
"prompt": {
"id": "pmpt_xxx",
"version": "3"
},
"sideband": {
"url": "wss://api.example.com/brun-e/sideband",
"protocol": "brun-e.sideband.v1"
}
}
- Errores minimos:
401,403,409,429,503.
4.2 POST /brun-e/:sessionId/complete (Fase B)
- Auth:
Bearer JWT. - Cierre idempotente:
- primera llamada:
200 { completed: true, already_completed: false } - repetidas:
200 { completed: true, already_completed: true } - Ownership obligatorio por
session_id+ claims JWT. - Evento a E-map: emitir una sola vez por sesion.
5) Reglas operativas minimas (obligatorias)
- API key nunca sale del backend.
- No loggear
ephemeral_key, tokens ni prompts sensibles. - Sideband con deduplicacion por
(session_id, request_id)y TTL. - Solo funciones permitidas:
record_answerlookup_methodologyget_user_contextend_sessionargumentsvalidado por JSON schema por funcion.- Sin stack traces ni secretos en payload de sideband.
5.1 Politica minima de PII/retencion (I-03)
Decision vigente I-03 (opcion 2):
- persistir
transcript_eventsyfinal_report, - redaccion de PII en logs siempre,
- retencion por tipo de dato y purge automatico,
- control de acceso por rol +
organization_id+ auditoria.
Retencion inicial:
transcript_events: 30 dias,final_report: 180 dias,- logs tecnicos: 30 dias con redaccion.
Variables de configuracion:
BRUNE_PII_LOG_REDACTION_ENABLED=trueBRUNE_TRANSCRIPT_STORE_ENABLED=trueBRUNE_TRANSCRIPT_RETENTION_DAYS=30BRUNE_FINAL_REPORT_RETENTION_DAYS=180BRUNE_PII_PURGE_CRON=0 3 * * *BRUNE_PII_ACCESS_ROLES=admin,coach_supportBRUNE_PII_DELETE_ON_REQUEST_ENABLED=true
5.2 Observabilidad MVP (I-04)
Decision vigente I-04 (opcion 1, salida rapida):
- metricas minimas via
/metricspara Prometheus, - logs estructurados JSON con correlacion (
session_id,request_id,organization_id), - alertas minimas de disponibilidad/latencia/schema invalid,
- sin tracing distribuido en v1.
Variables de configuracion:
BRUNE_METRICS_ENABLED=trueBRUNE_METRICS_PATH=/metricsBRUNE_OBS_LOG_STRUCTURED=trueBRUNE_OBS_ALERTS_ENABLED=trueBRUNE_OBS_TRACING_ENABLED=false
5.3 Stack sideband WS v1 (I-05)
Decision vigente I-05 (opcion 1, salida rapida):
- sideband en
NestJS WebSocketGatewayin-process, - balanceador con sticky sessions obligatorio,
- dedupe de requests por
(session_id, request_id)en store persistente.
Limites operativos iniciales:
- conexiones concurrentes por pod: 500,
- mensajes por sesion por minuto: 120,
- tamano maximo de mensaje: 32 KB,
- heartbeat: 15s,
- ventana de reconexion: 60s.
Variables de configuracion:
BRUNE_WS_STICKY_SESSIONS_REQUIRED=trueBRUNE_WS_MAX_CONNECTIONS_PER_POD=500BRUNE_WS_MAX_MESSAGES_PER_MINUTE=120BRUNE_WS_MAX_MESSAGE_SIZE_KB=32BRUNE_WS_HEARTBEAT_SECONDS=15BRUNE_WS_RECONNECT_WINDOW_SECONDS=60
6) Elegibilidad baseline temporal (hasta cierre con cliente)
Implementar baseline configurable por env, marcado como temporal.
Decision vigente I-02:
- "por dia" se calcula en UTC (
00:00:00Za23:59:59Z), -
reglas de elegibilidad quedan parametrizadas para calibracion posterior sin deploy.
-
una sesion activa por usuario (
409 session_active), - cooldown minimo entre sesiones (
429 cooldown), - maximo diario por usuario (
403 eligibility_denied).
Orden de validacion obligatorio:
- sesion activa (
409), - elegibilidad estructural (
403), - cooldown (
429), - maximo diario UTC (
403).
Variables de configuracion:
BRUNE_ELIGIBILITY_DAY_TZ=UTCBRUNE_COOLDOWN_MINUTES=10BRUNE_MAX_SESSIONS_PER_DAY=3BRUNE_SESSION_MAX_MINUTES=8BRUNE_ELIGIBILITY_SCOPE=user_orgBRUNE_ELIGIBILITY_REQUIRE_PLAN=falseBRUNE_ELIGIBILITY_ALLOWED_LEVELS=(CSV opcional)BRUNE_ELIGIBILITY_ALLOWED_COURSE_IDS=(CSV opcional)
7) Plan maestro por SP (tarjeta completa)
Total estimado: 63 SP
Objetivo: dividir la ejecucion por bloques pequenos, con dependencias claras y Definition of Done (DoD) por SP.
7.1 Gate de arranque (P0 obligatorio) - 14 SP
| SP | Puntos | Objetivo | Dependencias | Entregable | DoD |
|---|---|---|---|---|---|
| SP-01 | 3 | Cerrar formato REST final (consistencia con backend real) | Ninguna | Decision formal + DTO/Swagger alineado | Contrato /start y /complete sin ambiguedad |
| SP-02 | 3 | Garantizar single active session bajo concurrencia | SP-01 | Indice unico parcial DB + manejo transaccional en start | Sin doble sesion activa en pruebas de carrera |
| SP-03 | 3 | Exactly-once de evento E-map | SP-01 | Patron tecnico (outbox/dedupe) implementable | Un evento maximo por session_id |
| SP-04 | 3 | Cerrar handshake sideband seguro | SP-01 | Auth header JWT + ownership + rechazo canonico + TTL reconexion | Conexiones invalidas rechazadas, validas autenticadas |
| SP-05 | 2 | Definir schemas tool sideband | SP-04 | JSON schema estricto v1 de arguments/result para 4 tools |
Dispatcher valida schema por funcion y version |
7.2 Fase A funcional (inicio sesion) - 15 SP
| SP | Puntos | Objetivo | Dependencias | Entregable | DoD |
|---|---|---|---|---|---|
| SP-06 | 3 | Crear modulo modules/brun-e hexagonal |
SP-01 | Estructura domain/application/infrastructure | Modulo compila y respeta puertos |
| SP-07 | 5 | Implementar POST /brun-e/start |
SP-02, SP-06 | Handler + controller + validaciones en orden DRF | Casos 200/401/403/409/429/503 operativos |
| SP-08 | 3 | Adapter OpenAI client_secrets |
SP-06 | IOpenAIRealtimePort implementado |
Emite ephemeral_key sin exponer API key |
| SP-09 | 2 | Elegibilidad baseline configurable | SP-07 | IEligibilityValidator por env |
Reglas baseline activas sin hardcode |
| SP-10 | 2 | Hardening seguridad start | SP-07, SP-08 | Masking logs + rate limit + ownership | Sin secretos en logs y rate limit aplicado |
7.3 Persistencia y cierre - 13 SP
| SP | Puntos | Objetivo | Dependencias | Entregable | DoD |
|---|---|---|---|---|---|
| SP-11 | 5 | Prisma modelo Brun-E | SP-06 | Tablas sessions, transcript_events, idempotency_keys |
Migraciones aplican sin conflicto |
| SP-12 | 4 | POST /brun-e/:sessionId/complete idempotente |
SP-03, SP-11 | Complete handler + ownership + 404 |
Repeticion retorna already_completed: true |
| SP-13 | 2 | Publicacion evento E-map exactly-once | SP-03, SP-12 | Publisher integrado al cierre | Evento unico por sesion, incluso retries |
| SP-14 | 2 | Expiracion y saneamiento | SP-11, SP-12 | Job por expires_at + cierre parcial seguro |
Sesion vencida cierra y publica segun regla |
7.4 Sideband Fase B - 15 SP
| SP | Puntos | Objetivo | Dependencias | Entregable | DoD |
|---|---|---|---|---|---|
| SP-15 | 4 | Gateway WS v1 + handshake auth | SP-04, SP-06 | WebSocketGateway brun-e sideband |
Handshake validado con JWT y ownership |
| SP-16 | 3 | Dispatcher + errores canonicos | SP-05, SP-15 | Router por tool + error model uniforme | function_not_allowed y invalid_arguments cubiertos |
| SP-17 | 4 | Implementar 4 handlers de tools | SP-16, SP-11 | record_answer, lookup_methodology, get_user_context, end_session |
Respuesta canonica por tool |
| SP-18 | 2 | Heartbeat, timeout, reconexion | SP-15 | Control de vida de conexion | Expiracion/control inactividad funcionando |
| SP-19 | 2 | Dedupe (session_id, request_id) con TTL |
SP-16, SP-11 | Store idempotencia sideband | Reintentos no duplican efecto |
7.5 Calidad de salida (release interno) - 6 SP
| SP | Puntos | Objetivo | Dependencias | Entregable | DoD |
|---|---|---|---|---|---|
| SP-20 | 2 | Unit tests core | SP-07, SP-12, SP-16, SP-17 | Suites unit para handlers/dispatcher | Cobertura de caminos felices y errores |
| SP-21 | 2 | Integration tests contratos REST/WS | SP-19 | Pruebas E2E de auth, ownership, idempotencia | Contratos cumplen codigo y docs |
| SP-22 | 1 | Contract tests sideband/output schema | SP-17 | Validadores de request/result/error + output schema | Cambios rompedores detectados en CI |
| SP-23 | 1 | Observabilidad minima | SP-07, SP-17 | Metricas base + alarmas iniciales | SLO inicial medible en dashboard |
8) Orden de ejecucion (firme)
- Bloque 7.1 (P0 obligatorio).
- Bloque 7.2 (Fase A funcional).
- Bloque 7.3 (persistencia y cierre).
- Bloque 7.4 (sideband Fase B).
- Bloque 7.5 (calidad de salida).
9) Propuesta de sprints por capacidad
| Sprint | Capacidad sugerida | SP objetivo |
|---|---|---|
| Sprint 1 | 16 SP | SP-01..SP-06 |
| Sprint 2 | 17 SP | SP-07..SP-11 |
| Sprint 3 | 16 SP | SP-12..SP-17 |
| Sprint 4 | 14 SP | SP-18..SP-23 |
Nota: si el equipo trabaja a 20+ SP/sprint, se puede compactar a 3 sprints.
10) Trazabilidad operativa (continuidad entre chats)
10.1 Regla de uso
Antes de empezar un SP en cualquier chat:
- Revisar tabla
10.2y tomar el primer SP entodocuyas dependencias esten endone. - Marcar ese SP como
in_progresscon fecha. - Al cerrar el trabajo, actualizar
estado,evidenciaysiguiente. - Si queda bloqueado, marcar
blockedy completarbloqueado_por.
Estados permitidos:
todoin_progressblockeddone
10.2 Matriz de checks por SP
| SP | Estado | Owner | Ultima actualizacion | Bloqueado por | Evidencia | Siguiente |
|---|---|---|---|---|---|---|
| SP-01 | done | agent | 2026-03-19 | - | DTOs JSON:API creados: StartBrunESessionDTO (@Resource('brun-e-session-start')), CompleteBrunESessionDTO, CompleteBrunESessionRequestDTO. Controller con Swagger completo (/start y /:sessionId/complete). |
Contract tests en SP-20 |
| SP-02 | done | agent | 2026-03-19 | SP-01 ✅ | Indice unico parcial implementado en migracion 20260319173000_add_brun_e_core_tables (uq_brune_active_session_per_user). Repositorio Prisma captura conflicto de unicidad y handler de start lo mapea a 409 brun_e_session_active en carrera. |
Validar con integration test de concurrencia en SP-21 |
| SP-03 | done | agent | 2026-03-19 | SP-01 ✅ | Outbox exactly-once implementado: modelo Prisma/migracion brun_e_outbox_events con uq_brune_outbox_aggregate_event; CompleteBrunESessionHandler ahora ejecuta cierre + enqueue en la misma transaccion (UnitOfWorkService); SessionCompletionOutboxAdapter inserta BrunESessionCompletedEvent idempotente; BrunEOutboxWorkerService procesa pending con lease, retries y backoff. |
Continuar con SP-12 (/complete idempotente completo) y SP-13 (publisher real E-map) |
| SP-04 | done | agent | 2026-03-20 | SP-01 ✅ | Handshake sideband seguro implementado en BrunESidebandGateway: valida Authorization: Bearer <JWT>, session_id en query, Sec-WebSocket-Protocol=brun-e.sideband.v1, ownership por session_id y estado activo/no expirada. Rechazos canónicos: 4401 brun_e.unauthorized, 4403 brun_e.forbidden_session_owner, 4404 brun_e.session_not_found, 4409 brun_e.session_not_active. |
Continuar con SP-05 (schemas tool sideband v1) |
| SP-05 | done | agent | 2026-03-20 | SP-04 ✅ | Schemas estrictos v1 publicados para 4 tools (record_answer, lookup_methodology, get_user_context, end_session) con additionalProperties: false en arguments/result; validador JSON Schema versionado (BrunESidebandSchemaValidatorService, v1) y dispatcher base (BrunESidebandDispatcherService) validan por name+version y emiten errores canónicos (brun_e.function_not_allowed, brun_e.invalid_arguments). |
Continuar con SP-15 (Gateway WS v1 + auth handshake operativo end-to-end) |
| SP-06 | done | agent | 2026-03-19 | SP-01 ✅ | Modulo hexagonal creado: modules/brun-e/ con domain (entity+enums+exceptions), application (commands+dtos+ports+mappers+factories), infrastructure (http+db+exceptions). Puertos: IBrunESessionRepository, IOpenAIRealtimePort, IEligibilityValidator, ISessionCompletionPublisher. Wiring: brun_e.module.ts, app.module.ts importa BrunEModule, tsconfig+jest alias @BrunE/*. i18n: es/en/pt brun_e.json. |
Conectar adapters reales en SP-07/SP-08 |
| SP-07 | done | agent | 2026-03-19 | SP-02, SP-06 ✅ | /brun-e/start operativo end-to-end con casos 200/401/403/409/429/503: handler mantiene orden de validacion, repositorio Prisma real y wiring en BrunEModule. |
Cubrir contratos con tests unit/integration en SP-20/SP-21 |
| SP-08 | done | agent | 2026-03-19 | SP-06 ✅ | Adapter OpenAIRealtimeAdapter implementado sobre POST /v1/realtime/client_secrets con OPENAI_API_KEY, parse de ephemeral_key/expires_at y mapeo a IOpenAIRealtimePort. |
Hardening de secretos/rate-limit en SP-10 |
| SP-09 | done | agent | 2026-03-19 | SP-07 ✅ | EligibilityValidatorService implementado por env: chequeo estructural usuario/org + allowed levels + cooldown + max diario UTC (defaults 10/3), usando brun_e_sessions. |
Afinar reglas avanzadas de negocio en calibracion con cliente |
| SP-10 | done | agent | 2026-03-19 | SP-07, SP-08 ✅ | Hardening aplicado en start: guard BrunEStartRateLimitGuard con limite por usuario/IP (BRUNE_START_RATE_LIMIT_PER_MINUTE, default 30), nueva excepcion 429 brun_e_rate_limited, y redaccion global de metadata sensible en LoggerService (token/secret/api_key/authorization/ephemeral/prompt). |
Validar limites efectivos en carga en SP-21 |
| SP-11 | done | agent | 2026-03-19 | SP-06 ✅ | Prisma Brun-E implementado: schema prisma/models/brun_e.prisma + migracion 20260319173000_add_brun_e_core_tables con tablas brun_e_sessions, brun_e_transcript_events, brun_e_idempotency_keys e indices operativos. npx prisma generate ejecutado y repositorio BrunESessionPrismaRepository conectado en modulo. |
Continuar con SP-07 y SP-08 |
| SP-12 | done | agent | 2026-03-20 | SP-03, SP-11 ✅ | /brun-e/:sessionId/complete cerrado idempotente con ownership+404: CompleteBrunESessionHandler usa transaccion + lock de fila (findByIdForUpdate con FOR UPDATE) para evitar doble cierre concurrente; llamadas repetidas retornan already_completed: true; cierre persiste sesion y encola outbox en la misma UoW. |
Continuar con SP-13 (publisher E-map exactly-once) |
| SP-13 | done | agent | 2026-03-20 | SP-03, SP-12 ✅ | Publisher real integrado en worker outbox: BrunEOutboxWorkerService publica BrunESessionCompletedEvent en EventBus interno (opcion A), con parse/validacion estricta de payload antes de publicar; lifecycle exactly-once mantiene claim+markPublished y retries/backoff ante fallo. |
Continuar con SP-14 (job expiracion y saneamiento) |
| SP-14 | done | agent | 2026-03-20 | SP-11, SP-12 ✅ | Job de expiracion y saneamiento implementado: BrunEExpiryWorkerService procesa sesiones ACTIVE vencidas por expires_at en lotes, con lock transaccional (findNextExpiredActiveForUpdate + FOR UPDATE SKIP LOCKED), transiciona a EXPIRED, persiste y encola evento de cierre via outbox en la misma UoW. |
Volver a P0 pendiente: SP-04 (handshake sideband seguro) |
| SP-15 | done | agent | 2026-03-20 | SP-04, SP-06 ✅ | Gateway BrunESidebandGateway conectado end-to-end al dispatcher v1 (function_call): valida payload base y session_id ligado al socket autenticado, ejecuta BrunESidebandDispatcherService.validateFunctionCall (schema/tool/version), y responde en contrato canónico function_result con errores controlados (brun_e.invalid_arguments, brun_e.function_not_allowed, brun_e.forbidden_session_owner, fallback brun_e.service_unavailable) sin exponer stack/secretos. |
Continuar con SP-16 (router por tool + modelo uniforme de errores) |
| SP-16 | done | agent | 2026-03-20 | SP-05, SP-15 ✅ | BrunESidebandDispatcherService evolucionado a router por name con registro de handlers (BRUN_E_SIDEBAND_FUNCTION_HANDLERS) y contrato uniforme function_result (ok=true/false) para todos los caminos. Maneja errores canónicos de schema (brun_e.function_not_allowed, brun_e.invalid_arguments) y fallback controlado brun_e.service_unavailable; valida también schema de result antes de responder. BrunESidebandGateway delega al dispatcher y emite resultado canónico end-to-end. |
Continuar con SP-17 (implementar handlers de 4 tools) |
| SP-17 | done | agent | 2026-03-20 | SP-16, SP-11 ✅ | Implementados handlers sideband para record_answer, lookup_methodology, get_user_context, end_session y registrados en BRUN_E_SIDEBAND_FUNCTION_HANDLERS. record_answer persiste en brun_e_transcript_events; get_user_context retorna contexto de sesión + respuestas previas; lookup_methodology responde catálogo inicial; end_session cierra vía CompleteBrunESessionCommand manteniendo idempotencia/outbox. |
Continuar con SP-18 (heartbeat, timeout, reconexión) |
| SP-18 | done | agent | 2026-03-20 | SP-15 ✅ | Lifecycle sideband operativo en BrunESidebandGateway: heartbeat server->client cada 15s (heartbeat), timeout de inactividad a 45s con warning técnico y cierre de socket, y ventana de reconexión de 60s por session_id; si no reconecta, expira sesión en transacción (session.expire() + save) y encola evento via SESSION_COMPLETION_PUBLISHER para cierre consistente. Configuración por env añadida: BRUNE_WS_HEARTBEAT_SECONDS, BRUNE_WS_RECONNECT_WINDOW_SECONDS. |
Continuar con SP-19 (dedupe (session_id, request_id) con TTL) |
| SP-19 | done | agent | 2026-03-20 | SP-16, SP-11 ✅ | Dedupe sideband persistente en BrunESidebandDispatcherService usando brun_e_idempotency_keys: claim transaccional por (session_id, request_id), reutilización segura cuando key expirada, bloqueo de reintentos activos con error canónico brun_e.duplicate_request, y persistencia de response_hash SHA-256 para trazabilidad. TTL configurable por env BRUNE_WS_DEDUPE_TTL_SECONDS (default 120s). |
Continuar con SP-20 (unit tests core de handlers/dispatcher) |
| SP-20 | done | agent | 2026-03-20 | SP-07, SP-12, SP-16, SP-17 ✅ | Unit tests core implementados (7 suites, 19 tests) para StartBrunESessionHandler, CompleteBrunESessionHandler, BrunESidebandDispatcherService y handlers record_answer, lookup_methodology, get_user_context, end_session. Ejecucion validada con npm run test -- src/modules/brun-e (verde). |
Continuar con SP-21 (integration tests REST/WS) |
| SP-21 | done | agent | 2026-03-20 | SP-19 ✅ | Integration tests REST/WS agregados: brun_e_http.integration.spec.ts (auth 401, ownership 403, idempotencia 200 + already_completed), brun_e_sideband.gateway.integration.spec.ts (handshake auth 4401, ownership 4403, dedupe brun_e.duplicate_request). Ejecucion validada con npm run test -- src/modules/brun-e (9 suites, 27 tests). |
Continuar con SP-22 (contract tests sideband/output schema) |
| SP-22 | done | agent | 2026-03-20 | SP-17 ✅ | Contract tests sideband/output schema implementados: brun_e_sideband_schema.contract.spec.ts valida request/result por tool (record_answer, lookup_methodology, get_user_context, end_session) y errores canónicos (brun_e.function_not_allowed, brun_e.invalid_arguments); brun_e_sideband_dispatcher.contract.spec.ts valida envelope canónico function_result para éxito/error y fallback controlado cuando result schema es inválido. |
Continuar con SP-23 (observabilidad mínima) |
| SP-23 | done | agent | 2026-03-20 | SP-07, SP-17 ✅ | Observabilidad mínima operativa: endpoint público GET /metrics (Prometheus text format) vía BrunEMetricsController; métricas base instrumentadas en start/complete/sideband (brune_start_session_total, brune_complete_session_total, brune_sideband_function_total, brune_sideband_errors_total); logs estructurados JSON habilitados por BRUNE_OBS_LOG_STRUCTURED con correlación (session_id, request_id, organization_id) en gateway/dispatcher; alertas mínimas por errores sideband cuando BRUNE_OBS_ALERTS_ENABLED=true. Validado con npm run test -- src/modules/brun-e (12 suites, 39 tests). |
Plan maestro SP completado (SP-01..SP-23) |
10.3 Check de sesion (resumen rapido)
Al final de cada sesion de trabajo, dejar este bloque actualizado:
ultimo_sp_cerrado:SP-01,SP-06,SP-11,SP-02,SP-07,SP-08,SP-09,SP-10,SP-03,SP-12,SP-13,SP-14,SP-04,SP-05,SP-15,SP-16,SP-17,SP-18,SP-19,SP-20,SP-21,SP-22,SP-23sp_en_curso: ningunosiguiente_sp_recomendado: ninguno (fase A+B completada); siguiente bloque recomendado: hardening pre-release (carga multi-pod, dashboard SLO, smoke en stage)riesgos_abiertos: (1) falta validacion E2E real contra OpenAI y DB con datos de carrera. (2) rate-limit actual es in-memory por pod; en multi-pod requiere store distribuido. (3) validar en dev/stage aplicacion de indice parcial en ambientes reales.decision_pendiente: ninguna para C-01..C-05nota_tecnica: hay errores TS pre-existentes en specs de auth/interview/role (no introducidos por brun-e)
11) Testing minimo de salida
Unit
- Start handler: ok, no elegible, sesion activa, fallo OpenAI.
- Complete handler: primer cierre, recierre idempotente.
- Dispatcher sideband: validacion schema y funcion no permitida.
- Handlers de 4 funciones.
Integracion
/start:200/401/403/409/429/503./complete: primera llamada + repetida idempotente.- Ownership enforcement en REST y sideband.
Contrato
- Request/result/error sideband canonicos.
- Output schema final valido o fallback controlado.
12) Checklist de ejecucion
- [ ] C-01..C-05 resueltos antes de build intensivo
- [ ] Contratos REST implementados segun diseno tecnico
- [ ] Reglas DRF de sesion activa y cierre respetadas
- [ ] Baseline de elegibilidad configurable por env
- [ ] Persistencia e idempotencia operativas
- [ ] Evento E-map exactamente-once por sesion
- [ ] Tests minimos verdes
- [ ] Observabilidad minima operativa