e3c8979e8d
- 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>
231 lines
6.5 KiB
Go
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
|
|
}
|