---
name: audit_app_drift
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func AuditAppDrift(registryRoot string, windowsDeployRoot string) ([]AppDriftEntry, error)"
description: "Escanea todas las apps C++ del registry, extrae las versiones de modulos embebidas en sus artefactos compilados (exe Windows desplegado, modules_generated.cpp Windows/Linux) y las compara contra la version declarada en modules/*/module.md. Retorna una entrada por app con status ok|drift|no-build y la lista de modulos desactualizados."
tags: [audit, drift, cpp, modules, doctor, registry]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: error_go_core
imports:
  - fmt
  - os
  - path/filepath
  - regexp
  - sort
  - strings
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/audit_app_drift.go"
params:
  - name: registryRoot
    desc: "Ruta absoluta a la raiz del fn_registry (ej. $HOME/fn_registry). Se buscan modules/*/module.md, apps/*/app.md, projects/*/apps/*/app.md y cpp/build/{linux,windows}/apps/<name>/."
  - name: windowsDeployRoot
    desc: "Ruta absoluta donde se despliegan los .exe de Windows (ej. /mnt/c/Users/lucas/Desktop/apps). Si vacio, se omite la inspeccion del binario desplegado y solo se usan los artefactos bajo cpp/build."
output: "Slice de AppDriftEntry, uno por app C++ encontrada. Status: 'ok' (todas las versiones coinciden), 'drift' (al menos un modulo tiene version distinta), 'no-build' (no se encontro ningun artefacto compilado). Campo Stale lista los nombres de modulos con drift."
---

# audit_app_drift

Detecta apps C++ cuyos binarios/artefactos enlazan una version de modulo inferior a la que declara el registry.

## Cuando usarla

- Despues de bumpar la version de un modulo en `modules/*/module.md` — para saber que apps necesitan recompilarse.
- Como check en `fn doctor cpp-apps` para detectar drift antes de un deploy.
- En CI tras actualizar `fn_framework` o `fn_module_data_table` — lista apps con `status=drift`.

## Ejemplo

```go
entries, err := infra.AuditAppDrift("$HOME/fn_registry", "/mnt/c/Users/lucas/Desktop/apps")
if err != nil {
    log.Fatal(err)
}
for _, e := range entries {
    switch e.Status {
    case "drift":
        fmt.Printf("STALE  %s: modules %v (build: %s)\n", e.AppName, e.Stale, e.BuildKind)
    case "no-build":
        fmt.Printf("NOBLD  %s: no compiled artifact found\n", e.AppName)
    case "ok":
        fmt.Printf("OK     %s (%s)\n", e.AppName, e.BuildKind)
    }
}
```

Salida tipica:

```
STALE  app_gestion: modules [data_table] (build: linux-build)
OK     chart_demo (windows-deployed)
NOBLD  primitives_gallery: no compiled artifact found
```

## Prioridad de artefactos (por app)

1. `<windowsDeployRoot>/<name>/<name>.exe` — PE binario: busca patron `<module>\x00<version>\x00` directo en bytes.
2. `cpp/build/windows/apps/<name>/<name>_modules_generated.cpp` — parsea literales `{ "module", "version", ... }`.
3. `cpp/build/linux/apps/<name>/<name>_modules_generated.cpp` — mismo parser.

Si ninguno existe → `status = "no-build"`, `BuildKind = ""`.

## Gotchas

- **PE vs ELF string layout**: en ELF (Linux) el compilador puede separar las cadenas del struct en distintas regiones del `.rodata`, por lo que la busqueda directa en el binario no es fiable. Por eso se prefiere el `_modules_generated.cpp` (generado por `codegen_app_modules.py` al compilar). El binario PE Windows si tiene el patron contiguo.
- **Sin registry.db**: la funcion lee `module.md` directamente — no necesita la BD. Si el `fn index` no se ha ejecutado tras un bump de version, esta funcion igual detecta el drift porque lee la fuente de verdad (el `.md`).
- **Apps sin `uses_modules`**: si una app C++ no declara `uses_modules` ni tiene `_modules_generated.cpp`, aparece con `status=no-build` aunque compile correctamente. Normal para apps que no usan modulos opcionales.
- **Solo compara modulos del registry**: si el binario enlaza un modulo desconocido (que no tiene `module.md`), se ignora silenciosamente en el calculo de drift.
- **windowsDeployRoot vacio**: si el usuario no tiene montado `/mnt/c/...` (no WSL2), pasar `""` para evitar `os.Stat` lento en rutas inexistentes.
