# 0024b — Security loader: security/ YAML files + shell/security/ loader > Parte b del issue [0024-centralized-security-groups.md](0024-centralized-security-groups.md). > Requiere 0024a (pkg/security/ tipos). ## Objetivo Crear la carpeta `security/` en la raíz del proyecto con los YAML de grupos de usuarios, grupos de agentes y permisos. Crear el loader impuro `shell/security/loader.go` que los lee y devuelve un `security.SecurityPolicy`. ## Contexto - `pkg/security/` ya existe (0024a). Este sub-issue añade la capa de persistencia (YAML) y el loader. - Los YAML de `security/` se commitean al repositorio — son configuración de acceso, no secrets. - El código se mergea con feature flag = false (loader creado pero no usado todavía). ## Arquitectura ``` security/ NEW — en raíz del proyecto user-groups.yaml NEW agent-groups.yaml NEW permissions.yaml NEW shell/security/ NEW loader.go NEW loader_test.go NEW ``` ### Patron pure core / impure shell - `security/*.yaml` — datos de configuración (no código) - `shell/security/loader.go` — **impuro**: lee filesystem, parsea YAML, construye `security.SecurityPolicy` ## Tareas ### Fase 1: YAML files - [ ] **1.1** Crear `security/user-groups.yaml`: ```yaml # Grupos de usuarios del sistema # Members: lista de Matrix user IDs, o "*" para todos los usuarios groups: admins: members: [] # rellenar con los administradores reales everyone: members: ["*"] ``` - [ ] **1.2** Crear `security/agent-groups.yaml`: ```yaml # Grupos de agentes del sistema # Agents: lista de agent IDs (del campo agent.id en config.yaml), o "*" para todos groups: assistants: agents: - assistant-bot - asistente-2 all: agents: ["*"] ``` - [ ] **1.3** Crear `security/permissions.yaml`: ```yaml # Políticas de permisos: para cada grupo de agentes, qué acciones tiene cada grupo de usuarios # Actions: "*" = todo, "ask" = chat libre, "command:" = comandos, "tool:" = tools policies: - agent_group: all permissions: - user_group: admins actions: ["*"] - user_group: everyone actions: ["ask"] ``` ### Fase 2: Shell loader - [ ] **2.1** Crear `shell/security/loader.go` con función `Load(dir string) (security.SecurityPolicy, error)`: - Lee `/user-groups.yaml` → `[]security.UserGroup` - Lee `/agent-groups.yaml` → `[]security.AgentGroup` - Lee `/permissions.yaml` → `[]security.AgentPolicy` - Si el directorio no existe o está vacío: devuelve `security.SecurityPolicy{}` sin error (backward compat) - Si un archivo no existe individualmente: ese campo queda vacío (no es error) - Si el YAML es inválido: devuelve error con mensaje claro indicando qué archivo falló - [ ] **2.2** Definir structs YAML intermedios (solo para parseo) distintos de los tipos puros de `pkg/security/`. Convertir tras parsear. Esto mantiene `pkg/security/` independiente de `gopkg.in/yaml.v3`. ### Fase 3: Tests del loader - [ ] **3.1** Test: directorio inexistente → policy vacía, sin error - [ ] **3.2** Test: directorio vacío (sin YAML) → policy vacía, sin error - [ ] **3.3** Test: los 3 YAML válidos → policy con todos los campos - [ ] **3.4** Test: solo `user-groups.yaml` presente → user groups poblados, resto vacío - [ ] **3.5** Test: YAML malformado → error con nombre de archivo en el mensaje - [ ] **3.6** Test: `user_group: "*"` y `agent: ["*"]` parseados correctamente como strings literales ### Fase 4: Cleanup - [ ] **4.1** `go build -tags goolm ./...` compila - [ ] **4.2** `go test -tags goolm ./shell/security/...` pasa - [ ] **4.3** `go test -tags goolm ./...` pasa completo ## Ejemplo de uso ```go // En el launcher (todavía no wired — eso es 0024c) policy, err := shellsecurity.Load("security/") if err != nil { log.Fatal("error loading security policy", err) } // policy.UserGroups, policy.AgentGroups, policy.Policies disponibles acl := security.ResolveACL("assistant-bot", policy) ``` ## Decisiones de diseño - **Structs YAML separados de los tipos puros**: `pkg/security/` no importa `gopkg.in/yaml.v3`. El loader usa tipos intermedios locales y convierte. Mantiene el core verdaderamente puro. - **Directorio no existente = policy vacía**: no fuerza a crear los YAML si no se necesitan (ej: agentes puramente públicos). Backward compat con configuraciones existentes. - **3 archivos separados**: cada uno puede editarse independientemente. Los grupos son más estables que los permisos. ## Prerequisitos - 0024a completado (`pkg/security/` con tipos y `ResolveACL`) ## Riesgos - **Typos en user IDs de YAML**: si un Matrix ID tiene un typo, el usuario no tendrá acceso. No hay validación de formato de ID en este issue — es aceptable para MVP.