eb8dbf66a1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
110 lines
7.1 KiB
Markdown
110 lines
7.1 KiB
Markdown
---
|
|
name: audit_projects_coverage
|
|
kind: function
|
|
lang: go
|
|
domain: infra
|
|
version: "1.0.0"
|
|
purity: impure
|
|
signature: "func AuditProjectsCoverage(registryRoot string) ([]ProjectCoverage, error)"
|
|
description: "Audita la cobertura de los projects del registry frente a sus sub-repos Gitea: comprueba si cada project tiene .git local, remote origin y repo_url declarado, y cuantos de sus hijos (apps + analyses) estan clonados en disco versus solo conocidos por la BD. Motor del subcomando fn doctor projects. Solo lee registry.db + filesystem + git local, nunca la red ni la API de Gitea. Incluye FindOrphanProjectRefs, el check inverso: detecta apps o analyses que declaran un project_id sin fila en la tabla projects (project paraguas huerfano, riesgo de perdida al sincronizar)."
|
|
tags: [projects, gitea, subrepo, audit, infra, fn-doctor, doctor]
|
|
uses_functions: []
|
|
uses_types: []
|
|
returns: []
|
|
returns_optional: false
|
|
error_type: "error_go_core"
|
|
imports: ["database/sql", "os/exec", "path/filepath", "strings", "text/tabwriter", "github.com/mattn/go-sqlite3"]
|
|
tested: true
|
|
tests: ["healthy project con un hijo sin clonar marca children_missing", "project sin repo_url ni remote marca no_gitea_repo", "project sin directorio en disco marca dir_not_found", "error si registry.db no existe", "repo con origin devuelve true", "repo sin origin devuelve false", "sin issues lo deja claro", "con issues cuenta los afectados", "app con project_id huerfano lo detecta y agrupa ordenado", "app con project_id valido no aparece", "sin huerfanos devuelve slice vacio sin error", "sin huerfanos lo deja claro", "con huerfanos lista ids y cuenta"]
|
|
test_file_path: "functions/infra/audit_projects_coverage_test.go"
|
|
file_path: "functions/infra/audit_projects_coverage.go"
|
|
params:
|
|
- name: registryRoot
|
|
desc: "Raiz del repositorio (el directorio que contiene registry.db). Los dir_path relativos de projects, apps y analysis se resuelven contra esta raiz."
|
|
output: "Slice de ProjectCoverage, una entrada por fila de la tabla projects, con flags de git/remote/repo_url, conteos de hijos clonados vs declarados, y la lista de issues detectados. La funcion de formato FormatProjectsCoverage produce una tabla de texto humano."
|
|
---
|
|
|
|
## Ejemplo
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"fn-registry/functions/infra"
|
|
)
|
|
|
|
func main() {
|
|
rows, err := infra.AuditProjectsCoverage("/home/enmanuel/fn_registry")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Print(infra.FormatProjectsCoverage(rows))
|
|
}
|
|
```
|
|
|
|
Salida típica:
|
|
|
|
```
|
|
PROJECT GIT REMOTE REPO_URL CHILDREN ISSUES
|
|
fleet_monitoring ✓ ✓ ✓ 2/2 -
|
|
fn_monitoring ✓ ✓ ✓ 3/3 -
|
|
message_bus ✓ ✓ ✓ 3/4 children_missing
|
|
web_scraping ✗ ✗ ✗ 0/3 no_gitea_repo; children_missing
|
|
|
|
1/4 projects con problemas de cobertura.
|
|
```
|
|
|
|
## Check inverso: FindOrphanProjectRefs
|
|
|
|
Mientras `AuditProjectsCoverage` parte de la tabla `projects` y mira hacia abajo (¿están sus hijos clonados?), `FindOrphanProjectRefs` recorre el grafo en sentido contrario: parte de las apps y analyses y mira hacia arriba (¿existe el project paraguas que declaran?). Detecta el drift inverso, apps o analyses cuyo `project_id` no tiene ninguna fila en la tabla `projects`. Es un project huérfano: existe en otro PC y nunca se sincronizó a este, o nunca se creó aquí. Es un riesgo de pérdida silenciosa, porque el enlace del hijo apunta a un project que este registro no conoce.
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"fn-registry/functions/infra"
|
|
)
|
|
|
|
func main() {
|
|
orphans, err := infra.FindOrphanProjectRefs("/home/enmanuel/fn_registry")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Print(infra.FormatOrphanProjectRefs(orphans))
|
|
}
|
|
```
|
|
|
|
La firma es `func FindOrphanProjectRefs(registryRoot string) ([]OrphanProjectRef, error)`. Cada `OrphanProjectRef` agrupa, por `ProjectID` huérfano, los ids de las apps (`Apps`) y analyses (`Analyses`) que lo referencian, ambas listas ordenadas alfabéticamente y el slice resultante ordenado por `ProjectID`. Cuando todos los hijos apuntan a un project conocido devuelve un slice vacío (no nil) sin error. `FormatOrphanProjectRefs` produce una tabla de texto humano con el `project_id`, cuántas apps y analyses lo referencian y sus ids; si no hay huérfanos imprime una sola línea dejándolo claro.
|
|
|
|
Caso real detectado en este registro: apps con `project_id` ∈ {`element_agents`, `imagegen`, `osint_graph`} sin fila correspondiente en `projects`.
|
|
|
|
Salida típica con huérfanos:
|
|
|
|
```
|
|
PROJECT_ID APPS ANALYSES REFERENCED_BY
|
|
element_agents 1 0 shell_agent
|
|
imagegen 1 0 imagegen_ui
|
|
osint_graph 2 1 graph_explorer, scraper, gliner_glirel_tuning
|
|
|
|
3 project_id huérfanos (referenciados por hijos pero sin fila en projects).
|
|
```
|
|
|
|
## Cuando usarla
|
|
|
|
Úsala antes de un `/full-git-pull` masivo o tras clonar el registry en un PC nuevo para saber qué projects están realmente respaldados por su sub-repo Gitea y cuántos de sus hijos (apps y analyses) quedarían sin clonar. También como motor del futuro subcomando `fn doctor projects`: el caller la enchufa desde `cmd/fn/doctor.go` igual que `AuditUsesFunctions` o `AuditServicesSpec`, formatea con `FormatProjectsCoverage` para texto humano y serializa el slice directamente para `--json`.
|
|
|
|
## Gotchas
|
|
|
|
- Es **impura**: lee `registry.db` (abierto en modo read-only `?mode=ro`), recorre el filesystem y ejecuta `git -C <dir> remote get-url origin`. No toca la red ni la API de Gitea, así que no necesita token y es rápida.
|
|
- `HasRemote` solo se evalúa cuando el project tiene `.git` local; si no hay `.git`, queda `false` sin intentar el comando git.
|
|
- `gitHasRemoteOrigin` devuelve `false` ante cualquier error (no hay remote `origin`, no es un repo, git no instalado). No distingue "sin origin" de "git ausente"; si necesitas esa distinción, comprueba `git` por separado.
|
|
- El issue `no_gitea_repo` se emite solo cuando faltan **ambos** indicadores (`!HasRemote && !RepoURLDeclared`). Un project con `repo_url` declarado pero sin clonar (`dir_not_found`) NO se marca `no_gitea_repo` — el repo existe en Gitea, simplemente no está en este disco.
|
|
- `ChildrenMissing` cuenta los hijos (apps + analyses con ese `project_id`) cuya carpeta no tiene `.git` en disco: son los que se perderían o habría que reclonar. Cero hijos en la BD produce `0/0` y no genera issue.
|
|
- Si `projects.dir_path` está vacío, se deriva `projects/<id>`. Los `dir_path` ya absolutos se respetan tal cual.
|
|
- Devuelve error únicamente si `registry.db` no puede abrirse o consultarse. Los projects cuyo directorio no existe SÍ aparecen en el resultado, marcados con `dir_not_found`, para que el caller los muestre en vez de descartarlos en silencio.
|
|
- `FindOrphanProjectRefs` también es **impura**: lee `registry.db` en modo read-only (`?mode=ro`), pero no toca el filesystem ni git, solo cruza las tablas `projects`, `apps` y `analysis`. Ignora los hijos con `project_id` vacío (no son huérfanos, simplemente no pertenecen a ningún project). Devuelve un slice vacío no-nil cuando no hay huérfanos, así que el caller puede distinguir "sin huérfanos" (slice vacío, error nil) de "fallo al leer la BD" (error no nil).
|