You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
taskbot/docs/plan-multicomunidades.md

380 lines
18 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Plan de habilitación multicomunidades con control de acceso
Propósito
- Permitir que el bot opere en varias comunidades/grupos sin perder control.
- Introducir un mecanismo de whitelist (allowed_groups) y un flujo de aprobación administrativa.
- Mantener las mismas funcionalidades en todos los grupos aprobados.
Resumen de estrategia
- Instancia única multicomunidad con “gating” por grupo permitido.
- Descubrimiento seguro: registrar grupos desconocidos como pending y no operar hasta aprobación.
- Comandos/admin y notificaciones para aprobar/bloquear grupos.
Estructura del plan por etapas
Cada etapa indica objetivos y archivos a tocar. La implementación es incremental y desplegable paso a paso.
Etapa 0 — Preparación y criterios
Objetivos
- Alinear alcance y criterios de aceptación.
- Definir nuevas variables de entorno y defaults.
Criterios de aceptación
- Variables definidas:
- ADMIN_USERS: lista de user_ids normalizados con permisos de administración.
- ALLOWED_GROUPS: lista inicial (semilla) de group_ids a marcar como allowed.
- AUTO_APPROVE_PATTERNS (opcional): patrones de nombre de grupo confiables (con cautela).
- Comportamiento por defecto para grupos desconocidos: registrar como pending y no operar.
- Decidir si el bot responde (o guarda silencio) en grupos pending/blocked.
Archivos a tocar
- Documentación (docs/operations.md, docs/architecture.md) — actualización en Etapa 6.
Etapa 1 — Esquema y servicio AllowedGroups (sin gating todavía)
Objetivos
- Crear tabla allowed_groups con estados: pending | allowed | blocked.
- Implementar servicio con caché en memoria y API:
- upsertPending(groupId, label?, discoveredBy?) → pending
- isAllowed(groupId) → boolean
- setStatus(groupId, status, label?) → boolean
- listByStatus(status) → {group_id, label}[]
- seedFromEnv(ALLOWED_GROUPS)
- Métricas básicas por estado (gauges).
Cambios de datos
- Tabla allowed_groups:
- group_id TEXT PRIMARY KEY
- label TEXT NULL
- status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','allowed','blocked'))
- discovered_at TEXT NOT NULL DEFAULT now
- updated_at TEXT NOT NULL DEFAULT now
- discovered_by TEXT NULL
Archivos a tocar
- src/db/migrations/index.ts: añadir una nueva migración (v9) para crear allowed_groups e índices.
- src/services/allowed-groups.ts (nuevo): implementación del servicio con caché y métodos arriba.
- src/services/metrics.ts: gauges allowed_groups_total{status} (opcional en esta etapa).
- tests/unit/services/allowed-groups.test.ts (nuevo).
Etapa 2 — Descubrimiento seguro de grupos (registrar, no operar)
Objetivos
- Al recibir tráfico de un group_id desconocido:
- Insertar/actualizar en allowed_groups con status=pending y label (si disponible).
- No ejecutar lógica de negocio (comandos, sync, recordatorios).
- Métricas y logs del descubrimiento.
Archivos a tocar
- src/server.ts:
- handleMessageUpsert: antes de cualquier operación de grupo, consultar AllowedGroups.
- Si no existe: upsertPending y retornar sin procesar comandos.
- Añadir métricas: unknown_groups_discovered_total.
- src/services/contacts.ts:
- Helper opcional para obtener/actualizar el nombre del grupo si el webhook lo trae (si no, dejar label=null).
- src/services/metrics.ts: contador unknown_groups_discovered_total.
- tests/unit/server/unknown-group-discovery.test.ts (nuevo).
Etapa 3 — Gating mínimo en superficies críticas
Objetivos
- Bloquear procesamiento de comandos si el group_id no está allowed.
- Asegurar que sincronización de grupos/miembros solo opere sobre grupos allowed.
- Responder con mensaje breve o permanecer en silencio para pending/blocked (configurable).
Archivos a tocar
- src/services/command.ts:
- Al inicio de processTareaCommand cuando se esté en grupo: consultar AllowedGroups.isAllowed().
- Si no allowed: devolver mensaje breve o [] según política (config).
- Sustituir usos de GroupSyncService.isGroupActive por AllowedGroups.isAllowed para el gating de creación de tareas con group_id.
- src/services/group-sync.ts:
- Filtrar y operar únicamente sobre grupos allowed al sincronizar (scheduler + reconcile).
- Donde se cachean grupos activos, cruzar con allowed_groups o cambiar la fuente de verdad a AllowedGroups si procede.
- src/server.ts:
- En handleMessageUpsert, antes de encolar/ejecutar comandos, exigir allowed en caso de que sea un grupo.
- src/services/metrics.ts:
- commands_blocked_total
- sync_skipped_group_total (si se decide contabilizar grupos saltados por no allowed)
- tests/unit/services/command.gating.test.ts (nuevo).
- tests/unit/services/group-sync.gating.test.ts (nuevo).
Etapa 4 — Flujo de aprobación administrativa y notificaciones
Objetivos
- Comandos admin:
- /admin habilitar-aquí
- /admin deshabilitar-aquí
- /admin pendientes
- /admin allow-group <group_id>
- /admin block-group <group_id>
- Validación de permisos con ADMIN_USERS (IDs normalizados).
- Notificación automática a ADMIN_USERS al descubrir un grupo pending (con nombre si se conoce).
Archivos a tocar
- src/server.ts:
- Router para comandos /admin → nuevo servicio AdminService.
- Validación de ADMIN_USERS en validateEnv (no obligatorio pero recomendable).
- src/services/admin.ts (nuevo):
- Implementa los subcomandos y policy de permisos.
- src/services/allowed-groups.ts:
- Exponer setStatus, listByStatus, maybe get(groupId).
- src/services/response-queue.ts o src/services/webhook-manager.ts:
- Utilizar ResponseQueue para notificar a ADMIN_USERS por DM cuando se descubra un grupo pending.
- tests/unit/services/command.admin-approval.test.ts (nuevo).
- tests/unit/services/admin.test.ts (nuevo).
Etapa 5 — Integración con servicios dependientes (recordatorios, tareas, rate limit)
Objetivos
- Recordatorios: si incluyen contexto de grupo, operar solo en grupos allowed; si no, DM sin dependencia del gating de grupo.
- Tareas: validar group_id permitido al crear desde un grupo (ya resuelto en Etapa 3).
- Rate limit (opcional): aislar por grupo si se desea (clave compuesta groupId+userId).
Archivos a tocar
- src/services/reminders.ts:
- Al componer secciones por grupo (y “sin responsable”), filtrar por AllowedGroups.isAllowed(gid) si aplica.
- src/tasks/service.ts:
- Mantener la lógica de setear group_id=null si no existe/permitido, delegando la decisión al llamador (CommandService).
- src/services/rate-limit.ts (opcional):
- Añadir variantes de clave por grupo (ej. `${userId}:${groupId || 'dm'}`) si se activa por env.
- tests/unit/services/reminders.gating.test.ts (nuevo).
- tests/unit/tasks/service.gating.test.ts (nuevo).
Etapa 6 — Observabilidad y operaciones
Objetivos
- Métricas agregadas y documentación operativa.
- Logs claros en cambios de estado y descubrimientos.
Archivos a tocar
- src/services/metrics.ts:
- allowed_groups_total{status}, unknown_groups_discovered_total, commands_blocked_total, admin_actions_total{action}.
- src/server.ts:
- Confirmar exposición de métricas en /metrics.
- docs/operations.md:
- Documentar ADMIN_USERS, ALLOWED_GROUPS, AUTO_APPROVE_PATTERNS, comportamiento de pending/blocked, y comandos admin.
- docs/architecture.md:
- Añadir sección “Control de acceso por grupos (allowed_groups)”.
Etapa 7 — Hardening, pruebas de regresión y rollout
Objetivos
- Pruebas E2E con múltiples grupos en estados allowed/pending/blocked.
- Verificación de que no hay rutas sin gating (comandos, schedulers, envíos).
- Plan de despliegue gradual:
1) Activar registro (Etapas 12) sin gating duro.
2) Activar gating (Etapa 3) con política de silencio o mensaje breve.
3) Habilitar comandos admin (Etapa 4).
4) Ajustar recordatorios/tareas (Etapa 5).
5) Cerrar documentación y métricas (Etapa 6).
Archivos a tocar
- tests/** (nuevos casos integrados multigrupo).
- scripts de CI (si aplican) para asegurar la ejecución de nuevas suites.
Mapa rápido de archivos por etapa (resumen)
- Etapa 1:
- src/db/migrations/index.ts (nueva migración v9)
- src/services/allowed-groups.ts (nuevo)
- src/services/metrics.ts (gauges)
- Etapa 2:
- src/server.ts
- src/services/contacts.ts (opcional)
- src/services/metrics.ts (counter)
- Etapa 3:
- src/services/command.ts
- src/services/group-sync.ts
- src/server.ts
- src/services/metrics.ts
- Etapa 4:
- src/server.ts
- src/services/admin.ts (nuevo)
- src/services/allowed-groups.ts (extensiones)
- src/services/response-queue.ts (notificaciones a admins)
- Etapa 5:
- src/services/reminders.ts
- src/tasks/service.ts (validación indirecta ya cubierta por llamador)
- src/services/rate-limit.ts (opcional)
- Etapa 6:
- src/services/metrics.ts
- docs/operations.md
- docs/architecture.md
- Etapa 7:
- tests/** integración/regresión
- CI (si aplica)
Variables de entorno previstas (añadidos)
- ADMIN_USERS: lista separada por comas de IDs de usuario administradores (normalizados).
- ALLOWED_GROUPS: lista separada por comas de group_ids iniciales a permitir.
- AUTO_APPROVE_PATTERNS (opcional): patrones para autoaprobar por nombre (con cautela).
- MAX_MEMBERS_SNAPSHOT_AGE_MS: ya usada; garantizar coherencia en gating si se cruza con membresía.
- METRICS_ENABLED, NODE_ENV, TZ: respetadas.
Riesgos y mitigaciones
- Fuga de operaciones en grupos no permitidos: tests específicos (Etapas 3 y 7).
- Identificación de nombres de grupo: si no se dispone de “subject” en eventos, almacenar solo group_id; completar label más tarde desde comando admin o tras un fetch.
- Sobrecarga de DB por descubrimiento: caché en AllowedGroups + operaciones idempotentes; escribir solo si no existe o cambió estado/label.
Criterios de “hecho” globales
- El bot solo opera en grupos con status=allowed.
- Flujo de aprobación funciona desde grupo (/admin habilitar-aquí) y por DM (/admin allow-group <id>).
- Métricas y logs permiten auditar descubrimientos y cambios de estado.
- Documentación de operación actualizada.
Notas de implementación
- Mantener compatibilidad hacia atrás: en creación de tareas, si el group_id no está allowed, persistir sin group_id (null).
- No bloquear DMs por diseño (gating aplica solo a grupos).
- Reutilizar ResponseQueue para todas las notificaciones (incluyendo admins).
Plan detallado etapa por etapa (con impacto en tests y archivos a tocar)
Convenciones transversales para no romper la suite:
- Nueva variable GROUP_GATING_MODE = 'off' | 'discover' | 'enforce'. Valor por defecto: 'off' (especialmente en tests). Solo se activará en tests específicos.
- Helper de tests tests/helpers/db.ts:
- makeMemDb(), injectAllServices(db), seedAllowed(db, groupIds[]), resetServices().
- Opcional tests/setup.ts (o equivalente): fija GROUP_GATING_MODE='off' por defecto para toda la suite.
- AllowedGroups con dbInstance inyectable, caché con clearCache/resetForTests y métodos idempotentes.
Etapa 0 — Preparación y criterios (tests-first)
Objetivo
- Asegurar un “harness” de tests estable y predecible para DB y servicios estáticos.
Cambios de código
- Ninguno funcional. Solo infraestructura de tests (helpers y, si procede, setup global).
Archivos a tocar (código)
- tests/helpers/db.ts (nuevo)
- tests/setup.ts (opcional, si existe configuración global de tests)
Tests nuevos a añadir
- tests/unit/db/migrations.smoke.test.ts: comprueba que initializeDatabase en :memory: no lanza y crea tablas base (sin allowed_groups aún).
Tests existentes a actualizar
- Ninguno obligatorio. Opcional: migrar suites que crean DB manualmente a usar tests/helpers/db.ts.
Etapa 1 — Esquema y servicio AllowedGroups (sin gating todavía)
Objetivo
- Crear tabla allowed_groups y servicio con caché. No bloquear nada aún.
Cambios de código
- src/db/migrations/index.ts: añadir migración v9_allowed_groups (CREATE TABLE IF NOT EXISTS ...).
- src/services/allowed-groups.ts (nuevo): métodos upsertPending, isAllowed, setStatus, listByStatus, seedFromEnv, clearCache/resetForTests.
- src/services/metrics.ts: gauges allowed_groups_total{status} (best-effort).
Tests nuevos a añadir
- tests/unit/services/allowed-groups.test.ts
- tests/unit/db/migrations.allowed-groups.test.ts
Tests existentes a actualizar
- Ninguno. La migración se ejecuta en initializeDatabase de suites que ya lo llaman.
Etapa 2 — Descubrimiento seguro de grupos (registrar, no operar)
Objetivo
- Registrar grupos desconocidos como pending en modo 'discover', sin cambiar comportamiento por defecto de la suite.
Cambios de código
- src/server.ts:
- handleMessageUpsert: si GROUP_GATING_MODE='discover' y es group_id desconocido → upsertPending y return temprano sin procesar comandos/sync.
- Métrica unknown_groups_discovered_total (src/services/metrics.ts).
- src/services/contacts.ts (opcional): obtener label/subject si viene en el webhook.
Tests nuevos a añadir
- tests/unit/server/unknown-group-discovery.test.ts
Tests existentes a actualizar
- tests/unit/server.test.ts: asegurar que:
- Setea GROUP_GATING_MODE='off' en beforeEach, o
- Si prueba mensajes de grupo, usar seedAllowed(db, ['<group_id>']).
- Resto de suites: sin cambios esperados.
Etapa 3 — Gating mínimo en superficies críticas
Objetivo
- Bloquear comandos y sync en grupos no allowed cuando GROUP_GATING_MODE='enforce'.
Cambios de código
- src/services/command.ts:
- Al inicio, si message viene de grupo y GROUP_GATING_MODE='enforce', llamar AllowedGroups.isAllowed(groupId). Si no allowed: política por defecto “silencio” o respuesta breve configurable.
- src/services/group-sync.ts:
- Filtrar operaciones a solo grupos AllowedGroups.isAllowed.
- src/server.ts:
- Antes de encolar/ejecutar comandos, si es grupo y 'enforce', exigir allowed.
- src/services/metrics.ts:
- commands_blocked_total
- sync_skipped_group_total (opcional)
Tests nuevos a añadir
- tests/unit/services/command.gating.test.ts
- tests/unit/services/group-sync.gating.test.ts
Tests existentes a actualizar
- tests/unit/services/command.claim-unassign.test.ts: sembrar grupo allowed en beforeAll/beforeEach o fijar GROUP_GATING_MODE='off'.
- tests/unit/services/command.reminders-config.test.ts: idem si el test ejecuta comandos en contexto de grupo.
- tests/unit/services/command.date-parsing.test.ts: normalmente DM; no tocar salvo que simule grupo.
- tests/unit/services/group-sync.members.test.ts: sembrar grupo allowed para '123@g.us'.
- tests/unit/services/group-sync.scheduler.test.ts: sembrar grupos allowed en el scheduler o setear mode='off'.
- tests/unit/tasks/claim-unassign.test.ts: si hay contexto de grupo, seedAllowed; si es DM, sin cambios.
- tests/unit/services/cleanup-inactive.test.ts, tests/unit/services/metrics-health.test.ts, tests/unit/services/response-queue*.test.ts, tests/unit/services/reminders.test.ts: no deberían requerir cambios; si alguno falla por simulación de grupo, aplicar seedAllowed o mode='off'.
Etapa 4 — Flujo de aprobación administrativa y notificaciones
Objetivo
- Permitir a ADMIN_USERS aprobar/bloquear grupos y consultar pendientes; notificar a admins al descubrir pending.
Cambios de código
- src/services/admin.ts (nuevo): /admin habilitar-aquí, deshabilitar-aquí, pendientes, allow-group <id>, block-group <id>.
- src/server.ts: router para /admin y validación de ADMIN_USERS; usar ResponseQueue para notificaciones.
- src/services/allowed-groups.ts: asegurar setStatus, listByStatus, get(groupId).
- src/services/response-queue.ts (o webhook-manager): enviar DM a ADMIN_USERS en descubrimiento pending (best-effort y detrás de flag para no romper tests existentes).
Tests nuevos a añadir
- tests/unit/services/admin.test.ts
- tests/unit/services/command.admin-approval.test.ts
Tests existentes a actualizar
- tests/unit/server.test.ts: si parsea /admin, agregar casos explícitos o mantener aislado con mode='off' para suites no relacionadas.
- No tocar otras suites.
Etapa 5 — Integración con recordatorios y tareas
Objetivo
- Recordatorios y operaciones por grupo respetan AllowedGroups cuando hay contexto de grupo. DMs no se ven afectados.
Cambios de código
- src/services/reminders.ts: filtrar por AllowedGroups.isAllowed(gid) cuando el recordatorio/consulta use contexto de grupo.
- src/tasks/service.ts: sin cambios funcionales (seguir permitiendo group_id=null).
- src/services/rate-limit.ts (opcional): clave compuesta por grupo.
Tests nuevos a añadir
- tests/unit/services/reminders.gating.test.ts
- tests/unit/tasks/service.gating.test.ts
Tests existentes a actualizar
- tests/unit/services/reminders.test.ts: si usa contexto de grupo, seedAllowed o mode='off' para mantener expectativas.
- Resto: sin cambios.
Etapa 6 — Observabilidad y operaciones
Objetivo
- Métricas agregadas y documentación.
Cambios de código
- src/services/metrics.ts: allowed_groups_total{status}, unknown_groups_discovered_total, commands_blocked_total, admin_actions_total{action}.
- docs/operations.md: documentar variables y comportamiento.
- docs/architecture.md: sección de control de acceso por grupos.
- Confirmar /metrics en src/server.ts.
Tests nuevos a añadir
- Opcional: tests/unit/services/metrics-health.test.ts puede ampliar cobertura para nuevos contadores (no estricta; tolerar ausencia si METRICS_ENABLED='false').
Tests existentes a actualizar
- tests/unit/services/metrics-health.test.ts: si asume conjunto exacto de métricas, flexibilizar aserciones o aislar nuevas métricas tras METRICS_ENABLED.
Etapa 7 — Hardening, regresión y rollout
Objetivo
- Validar que no hay rutas sin gating y que la activación progresiva no rompe nada.
Cambios de código
- Ninguno obligatorio más allá de ajustes menores.
Tests nuevos a añadir
- tests/integration/multi-groups.e2e.test.ts (si procede).
- Casos combinados con varios estados: allowed/pending/blocked + discover/enforce.
Tests existentes a actualizar
- Ninguno; mantener GROUP_GATING_MODE='off' por defecto y activar explícitamente en tests que cubren gating.
Checklist por etapa para mantener la suite en verde
- Antes de tocar código de gating: añadir tests nuevos primero (TDD light).
- Default en tests: GROUP_GATING_MODE='off'.
- Donde haya contexto de grupo en tests existentes: seedAllowed(memdb, ['<gid>']) en el setup del archivo.
- Limpiar cachés entre tests: AllowedGroups.resetForTests() en afterEach/afterAll cuando el servicio se use.
- initializeDatabase(memdb) siempre presente en suites con DB en memoria para aplicar migraciones (incluida v9).