// Package health defines a small Checker abstraction and a steward-managed // Set that aggregates every registered Checker into a single readiness probe. // // A Checker is anything with a name and a Check(ctx) method. Concrete checks // live next to the subsystem they probe (DBCheck here, future probes such as // IndexCheck or QueueCheck land in their own files). Each check is registered // as a steward asset; Set multi-injects them via the `inject:""` tag so adding // a probe is a registration-only operation — no edits to Set or Run. // // The handler that exposes /readyz lives in the HTTP layer (Phase 5) and // consumes (*Set).Run directly. package health import ( "context" "time" "sourcecraft.dev/bigbes/lethe/internal/platform/database" ) // perCheckTimeout bounds the time any single Checker.Check call is allowed to // take. The HTTP /readyz handler typically has its own request budget; the // per-check timeout here is the inner bound so a hung check cannot starve the // rest of the set. const perCheckTimeout = 2 * time.Second // Checker is the contract every health probe satisfies. Implementations may // be steward services with their own dependencies (see DBCheck). type Checker interface { Name() string Check(ctx context.Context) error } // Set is the steward-managed aggregator. It multi-injects every registered // Checker via the `inject:""` tag. Adding a new check is a matter of // registering an asset that implements Checker — no edits to Set are needed. type Set struct { Checks []Checker `inject:""` } // Run invokes every registered Checker sequentially, applying a per-check // timeout via context.WithTimeout. Returns a map keyed by Checker.Name() with // the error each check produced (nil on success), and an aggregate allOK // flag that is true iff every check succeeded. // // Empty Checks slice → empty map and allOK=true. This is intentional: an // empty set means no subsystem has declared a readiness signal yet, not that // the system is unhealthy. func (s *Set) Run(ctx context.Context) (map[string]error, bool) { results := make(map[string]error, len(s.Checks)) allOK := true for _, c := range s.Checks { ctx2, cancel := context.WithTimeout(ctx, perCheckTimeout) err := c.Check(ctx2) cancel() results[c.Name()] = err if err != nil { allOK = false } } return results, allOK } // DBCheck verifies the SQLite database is reachable by pinging the // underlying *sql.DB. It is a steward service: register it as an asset and // it will be picked up by Set's multi-injection. type DBCheck struct { DB *database.Database `inject:""` } // Name returns the stable identifier surfaced in the readiness response. func (c *DBCheck) Name() string { return "database" } // Check pings the database. The 2s timeout is supplied by Set.Run. func (c *DBCheck) Check(ctx context.Context) error { return c.DB.DB.PingContext(ctx) }