// 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)
}