Files
fn_registry/functions/infra/vault_doctor.go
T
egutierrez e3c8979e8d chore: auto-commit (95 archivos)
- cmd/fn/doctor.go
- cmd/fn/main.go
- cpp/apps/primitives_gallery/playground/tables/CMakeLists.txt
- cpp/apps/primitives_gallery/playground/tables/data_table.cpp
- cpp/apps/primitives_gallery/playground/tables/data_table_logic.cpp
- cpp/apps/primitives_gallery/playground/tables/data_table_logic.h
- cpp/apps/primitives_gallery/playground/tables/self_test.cpp
- cpp/apps/primitives_gallery/playground/tables/tql.cpp
- cpp/apps/primitives_gallery/playground/tables/viz.cpp
- cpp/apps/primitives_gallery/playground/tables/viz.h
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:50:34 +02:00

231 lines
6.5 KiB
Go

package infra
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
// VaultDoctorEntry holds the health report for a single vault.
type VaultDoctorEntry struct {
VaultName string `json:"vault_name"`
VaultPath string `json:"vault_path"`
ProjectID string `json:"project_id"`
Issues []string `json:"issues"` // human-readable issues; empty = healthy
IndexedFiles int `json:"indexed_files"` // 0 if no vault_index.db
LastIndexedAt int64 `json:"last_indexed_at"` // unix seconds; 0 if N/A
DiskFiles int `json:"disk_files"` // count via WalkDir (no hashing)
Status string `json:"status"` // "ok" | "warning" | "error"
}
// VaultDoctor audits every vault declared in projects/*/vaults/vault.yaml under
// repoRoot. For each vault it performs a series of checks (disk presence, layout,
// index existence, staleness, drift) and returns a slice of VaultDoctorEntry.
//
// The function is read-only: it never writes to disk or any database.
// Returns an error only if VaultManifestRead fails (manifest parse error).
func VaultDoctor(repoRoot string) ([]VaultDoctorEntry, error) {
entries, err := VaultManifestRead(repoRoot)
if err != nil {
return nil, fmt.Errorf("vault_doctor: read manifests: %w", err)
}
results := make([]VaultDoctorEntry, 0, len(entries))
for _, e := range entries {
result := auditVault(e)
results = append(results, result)
}
return results, nil
}
func auditVault(e VaultManifestEntry) VaultDoctorEntry {
entry := VaultDoctorEntry{
VaultName: e.Name,
VaultPath: e.Path,
ProjectID: e.ProjectID,
}
// Resolve symlinks for disk checks
realPath, err := filepath.EvalSymlinks(e.Path)
if err != nil || realPath == "" {
realPath = e.Path
}
// CHECK 1: directory_missing
info, statErr := os.Stat(realPath)
if statErr != nil || !info.IsDir() {
entry.Issues = append(entry.Issues, "directory_missing")
entry.Status = "error"
return entry
}
// COUNT disk files (cheap walk — no hashing, no mime detection)
diskCount := countDiskFiles(realPath)
entry.DiskFiles = diskCount
// CHECK 2: layout_missing / non_standard_layout
hasData := dirExists(filepath.Join(realPath, "data"))
hasKnowledge := dirExists(filepath.Join(realPath, "knowledge"))
if !hasData && !hasKnowledge {
// Check if it looks like a non-standard but intentional layout
if hasNonStandardLayout(realPath) {
entry.Issues = append(entry.Issues, "non_standard_layout")
} else {
entry.Issues = append(entry.Issues, "layout_missing")
}
}
// CHECK 3: index_missing
indexPath := filepath.Join(realPath, "vault_index.db")
_, indexStatErr := os.Stat(indexPath)
if indexStatErr != nil {
entry.Issues = append(entry.Issues, "index_missing")
entry.setWarningStatus()
entry.setFinalStatus()
return entry
}
// Open vault index (read-only) for checks 4 and 5
vdb, openErr := VaultIndexOpen(realPath)
if openErr != nil {
entry.Issues = append(entry.Issues, fmt.Sprintf("index_open_error: %v", openErr))
entry.setWarningStatus()
return entry
}
defer vdb.Close()
// Query indexed file count and max indexed_at
var indexedCount int
var maxIndexedAt int64
row := vdb.QueryRow(`SELECT COUNT(*), COALESCE(MAX(indexed_at), 0) FROM files`)
if scanErr := row.Scan(&indexedCount, &maxIndexedAt); scanErr != nil {
entry.Issues = append(entry.Issues, fmt.Sprintf("index_query_error: %v", scanErr))
} else {
entry.IndexedFiles = indexedCount
entry.LastIndexedAt = maxIndexedAt
}
// CHECK 4: index_stale — any file on disk newer than MAX(indexed_at)
if maxIndexedAt > 0 {
maxTime := time.Unix(maxIndexedAt, 0)
if isIndexStale(realPath, maxTime) {
entry.Issues = append(entry.Issues, "index_stale")
}
}
// CHECK 5: index_drift — disk file count != indexed count
if indexedCount != diskCount {
entry.Issues = append(entry.Issues, fmt.Sprintf("index_drift: disk=%d indexed=%d", diskCount, indexedCount))
}
// CHECK 6: empty_vault
if diskCount == 0 {
entry.Issues = append(entry.Issues, "empty_vault")
}
entry.setFinalStatus()
return entry
}
// setWarningStatus sets status to warning if not already error.
func (e *VaultDoctorEntry) setWarningStatus() {
if e.Status != "error" {
e.Status = "warning"
}
}
// setFinalStatus derives the final Status from Issues.
func (e *VaultDoctorEntry) setFinalStatus() {
if e.Status == "error" {
return
}
if len(e.Issues) == 0 {
e.Status = "ok"
} else {
e.Status = "warning"
}
}
// countDiskFiles walks realPath and counts regular files, excluding:
// vault_index.db*, .git/, hidden files/dirs at any depth.
func countDiskFiles(realPath string) int {
count := 0
_ = filepath.WalkDir(realPath, func(path string, d os.DirEntry, err error) error {
if err != nil {
return nil
}
name := d.Name()
// Skip hidden entries
if strings.HasPrefix(name, ".") {
if d.IsDir() {
return filepath.SkipDir
}
return nil
}
// Skip .git
if d.IsDir() && name == ".git" {
return filepath.SkipDir
}
// Skip vault_index.db files
if !d.IsDir() && (name == "vault_index.db" || name == "vault_index.db-shm" || name == "vault_index.db-wal") {
return nil
}
if !d.IsDir() {
count++
}
return nil
})
return count
}
// isIndexStale returns true if any regular file under realPath has an mtime
// strictly after maxTime (excluding vault_index.db* and hidden files).
func isIndexStale(realPath string, maxTime time.Time) bool {
stale := false
_ = filepath.WalkDir(realPath, func(path string, d os.DirEntry, err error) error {
if err != nil || stale {
return nil
}
name := d.Name()
if strings.HasPrefix(name, ".") {
if d.IsDir() {
return filepath.SkipDir
}
return nil
}
if d.IsDir() && name == ".git" {
return filepath.SkipDir
}
if !d.IsDir() {
if name == "vault_index.db" || name == "vault_index.db-shm" || name == "vault_index.db-wal" {
return nil
}
fi, statErr := d.Info()
if statErr == nil && fi.ModTime().After(maxTime) {
stale = true
}
}
return nil
})
return stale
}
// hasNonStandardLayout returns true when a vault directory contains
// subdirectories that are clearly intentional but not data/knowledge.
// Heuristic: any subdir at the vault root that is not data/knowledge.
func hasNonStandardLayout(realPath string) bool {
entries, err := os.ReadDir(realPath)
if err != nil {
return false
}
standardDirs := map[string]bool{"data": true, "knowledge": true, ".git": true}
for _, e := range entries {
if e.IsDir() && !standardDirs[e.Name()] && !strings.HasPrefix(e.Name(), ".") {
return true
}
}
return false
}