--- name: audit_services_spec kind: function lang: go domain: infra version: "1.0.0" purity: impure signature: "func AuditServicesSpec(registryRoot string) ([]ServiceSpecAudit, error)" description: "Audita apps con tag 'service': reporta drift entre el bloque service: del app.md y los datos requeridos por el monitor (port, health_endpoint, systemd_unit, pc_targets). Lee registry.db read-only via sql.Open. Issue 0105." tags: [audit, services, doctor, registry, sqlite, issue-0105] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: error_go_core imports: - database/sql - github.com/mattn/go-sqlite3 tested: false file_path: functions/infra/audit_services_spec.go params: - name: registryRoot desc: "Ruta absoluta a la raiz del fn_registry (donde vive registry.db)." output: "Slice de ServiceSpecAudit (uno por app con tag service). OK=false si Issues no esta vacio." --- # audit_services_spec Reporta apps con tag `service` cuya `service:` block esta incompleta. ## Cuando usarla - Subcomando `fn doctor services-spec` (este es su unico consumer hoy). - Antes de desplegar `services_monitor` (issue 0106) — si esta funcion devuelve `OK=false` para alguna app, el monitor no puede reconciliar estado. - En CI/cron para detectar regresiones cuando alguien crea app `tag: service` sin bloque. ## Ejemplo ```go audits, err := infra.AuditServicesSpec("$HOME/fn_registry") for _, a := range audits { if !a.OK { fmt.Println(a.AppID, "issues:", a.Issues) } } ``` ## Reglas que valida - bloque presente (alguno de runtime/systemd_unit/port/health_endpoint != default). - `runtime` declarado y en allowlist (`systemd-user`, `systemd-system`, `docker-compose`, `stdio`, `manual`). - `pc_targets` con al menos 1 pc_id (cruzado contra tabla `service_targets`). - `runtime` empieza con `systemd-` ⇒ `systemd_unit` obligatorio. - `restart_policy` (si declarada) en `always`, `on-failure`, `none`. ## Gotchas - Lee `registry.db` en modo `?mode=ro`; si la base no existe o esta locked retorna error. - `service:` bloque parcial pasa el check `HasBlock=true` pero falla validaciones especificas — ver `Issues[]` para detalles. - No valida que el `port` este libre o el `systemd_unit` exista en disco; eso lo hace `services_status_go_infra` (runtime check).