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

220 lines
10 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).