# 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:<name>" = comandos, "tool:<name>" = 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 `<dir>/user-groups.yaml` → `[]security.UserGroup`
  - Lee `<dir>/agent-groups.yaml` → `[]security.AgentGroup`
  - Lee `<dir>/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.
