package infra import ( "database/sql" "fmt" "strings" _ "github.com/mattn/go-sqlite3" ) // ServiceSpecAudit reports drift between an app tagged `service` and the // `service:` frontmatter block populated by the indexer (issue 0105). type ServiceSpecAudit struct { AppID string `json:"app_id"` Name string `json:"name"` HasBlock bool `json:"has_block"` Runtime string `json:"runtime"` Port int `json:"port"` HealthPath string `json:"health_endpoint"` SystemdUnit string `json:"systemd_unit"` PCTargets []string `json:"pc_targets"` IsLocalOnly bool `json:"is_local_only"` RestartPolicy string `json:"restart_policy"` Issues []string `json:"issues"` OK bool `json:"ok"` } // AuditServicesSpec lists every app with tag `service` and reports whether its // `service:` frontmatter is complete enough for downstream monitoring // (services_monitor app, issue 0106). // // Rules: // - block must exist (otherwise IsLocalOnly/runtime are all defaults). // - runtime is required (one of: systemd-user, systemd-system, docker-compose, stdio, manual). // - pc_targets must declare >= 1 pc_id. // - if runtime starts with `systemd-`, systemd_unit is required. // - if runtime in {systemd-*, docker-compose} and port > 0, health_endpoint is recommended (warning, not failure). func AuditServicesSpec(registryRoot string) ([]ServiceSpecAudit, error) { dbPath := registryRoot + "/registry.db" db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&mode=ro") if err != nil { return nil, fmt.Errorf("audit_services_spec: open db: %w", err) } defer db.Close() rows, err := db.Query(` SELECT id, name, COALESCE(service_runtime,''), COALESCE(service_port,0), COALESCE(service_health_endpoint,''), COALESCE(service_systemd_unit,''), COALESCE(service_restart_policy,''), COALESCE(service_is_local_only,0) FROM apps WHERE tags LIKE '%service%' ORDER BY id `) if err != nil { return nil, fmt.Errorf("audit_services_spec: query: %w", err) } defer rows.Close() var out []ServiceSpecAudit for rows.Next() { var a ServiceSpecAudit var localOnly int if err := rows.Scan( &a.AppID, &a.Name, &a.Runtime, &a.Port, &a.HealthPath, &a.SystemdUnit, &a.RestartPolicy, &localOnly, ); err != nil { return nil, fmt.Errorf("audit_services_spec: scan: %w", err) } a.IsLocalOnly = localOnly != 0 a.HasBlock = a.Runtime != "" || a.SystemdUnit != "" || a.Port != 0 || a.HealthPath != "" // pc_targets from service_targets table. tRows, err := db.Query( "SELECT pc_id FROM service_targets WHERE app_id = ? ORDER BY pc_id", a.AppID, ) if err != nil { return nil, fmt.Errorf("audit_services_spec: service_targets query: %w", err) } for tRows.Next() { var pc string if err := tRows.Scan(&pc); err != nil { tRows.Close() return nil, err } a.PCTargets = append(a.PCTargets, pc) } tRows.Close() // Validate. if !a.HasBlock { a.Issues = append(a.Issues, "missing service: block in app.md") } if a.Runtime == "" { a.Issues = append(a.Issues, "missing service.runtime") } else if !validRuntimes[a.Runtime] { a.Issues = append(a.Issues, "invalid service.runtime: "+a.Runtime) } if len(a.PCTargets) == 0 { a.Issues = append(a.Issues, "missing service.pc_targets (>= 1 required)") } if strings.HasPrefix(a.Runtime, "systemd-") && a.SystemdUnit == "" { a.Issues = append(a.Issues, "runtime systemd-* requires service.systemd_unit") } if a.RestartPolicy != "" && !validRestart[a.RestartPolicy] { a.Issues = append(a.Issues, "invalid service.restart_policy: "+a.RestartPolicy) } a.OK = len(a.Issues) == 0 out = append(out, a) } return out, rows.Err() } var validRuntimes = map[string]bool{ "systemd-user": true, "systemd-system": true, "docker-compose": true, "stdio": true, "manual": true, } var validRestart = map[string]bool{ "always": true, "on-failure": true, "none": true, }