Saltar a contenido

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:

Regla de precedencia:

  1. DRF define comportamiento de negocio y alcance funcional.
  2. Diseno tecnico define contratos, arquitectura y reglas operativas.
  3. 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_KEY en entorno servidor.
  • BRUNE_PROMPT_ID y BRUNE_PROMPT_VERSION unicos para dev|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 (403 o 429).
  • 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_answer
  • lookup_methodology
  • get_user_context
  • end_session
  • arguments validado 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_events y final_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=true
  • BRUNE_TRANSCRIPT_STORE_ENABLED=true
  • BRUNE_TRANSCRIPT_RETENTION_DAYS=30
  • BRUNE_FINAL_REPORT_RETENTION_DAYS=180
  • BRUNE_PII_PURGE_CRON=0 3 * * *
  • BRUNE_PII_ACCESS_ROLES=admin,coach_support
  • BRUNE_PII_DELETE_ON_REQUEST_ENABLED=true

5.2 Observabilidad MVP (I-04)

Decision vigente I-04 (opcion 1, salida rapida):

  • metricas minimas via /metrics para 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=true
  • BRUNE_METRICS_PATH=/metrics
  • BRUNE_OBS_LOG_STRUCTURED=true
  • BRUNE_OBS_ALERTS_ENABLED=true
  • BRUNE_OBS_TRACING_ENABLED=false

5.3 Stack sideband WS v1 (I-05)

Decision vigente I-05 (opcion 1, salida rapida):

  • sideband en NestJS WebSocketGateway in-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=true
  • BRUNE_WS_MAX_CONNECTIONS_PER_POD=500
  • BRUNE_WS_MAX_MESSAGES_PER_MINUTE=120
  • BRUNE_WS_MAX_MESSAGE_SIZE_KB=32
  • BRUNE_WS_HEARTBEAT_SECONDS=15
  • BRUNE_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:00Z a 23: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:

  1. sesion activa (409),
  2. elegibilidad estructural (403),
  3. cooldown (429),
  4. maximo diario UTC (403).

Variables de configuracion:

  • BRUNE_ELIGIBILITY_DAY_TZ=UTC
  • BRUNE_COOLDOWN_MINUTES=10
  • BRUNE_MAX_SESSIONS_PER_DAY=3
  • BRUNE_SESSION_MAX_MINUTES=8
  • BRUNE_ELIGIBILITY_SCOPE=user_org
  • BRUNE_ELIGIBILITY_REQUIRE_PLAN=false
  • BRUNE_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)

  1. Bloque 7.1 (P0 obligatorio).
  2. Bloque 7.2 (Fase A funcional).
  3. Bloque 7.3 (persistencia y cierre).
  4. Bloque 7.4 (sideband Fase B).
  5. 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:

  1. Revisar tabla 10.2 y tomar el primer SP en todo cuyas dependencias esten en done.
  2. Marcar ese SP como in_progress con fecha.
  3. Al cerrar el trabajo, actualizar estado, evidencia y siguiente.
  4. Si queda bloqueado, marcar blocked y completar bloqueado_por.

Estados permitidos:

  • todo
  • in_progress
  • blocked
  • done

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-23
  • sp_en_curso: ninguno
  • siguiente_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-05
  • nota_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

13) Referencias