api

package
v0.2.0-beta.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 3, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Index

Constants

View Source
const TenantHeader = "X-Tenant-ID"

TenantHeader is the canonical HTTP header carrying the tenant ID on read-side (query) requests. Ingest paths resolve tenant separately via gRPC metadata / OTLP resource attributes and do not go through this middleware.

Variables

View Source
var AuthFailureHook func(reason string)

AuthFailureHook is an optional callback invoked whenever API-key auth rejects a request. Set by main.go to increment the APIAuthFailuresTotal metric. Left as a package-level function pointer (rather than a DI parameter) to avoid circular imports between api and telemetry. Safe to leave nil.

Reasons: "missing_header", "bad_scheme", "bad_key".

View Source
var OtelContextStartTime = time.Now()

Functions

func APIKeyGate

func APIKeyGate(expectedKey, mcpPath string, next http.Handler) http.Handler

APIKeyGate wraps a handler so only requests matching IsProtectedPath require the key. Public paths flow through untouched, which is what keeps the UI bundle and health probes accessible without credentials.

func DBHealthMiddleware

func DBHealthMiddleware(h *DBHealth) func(http.Handler) http.Handler

DBHealthMiddleware returns 503 immediately when the DB poller reports unhealthy, for DB-dependent paths. Health/metrics/UI paths bypass the gate.

func IsProtectedPath

func IsProtectedPath(path, mcpPath string) bool

IsProtectedPath reports whether a request path requires API-key authentication. Protected: /api/*, /v1/* (OTLP HTTP), and the MCP path. Unprotected: /live, /ready, /health*, /metrics* (Prometheus), /ws* (WebSocket), and the UI static bundle ("/" + assets).

func LoadTenantKeys

func LoadTenantKeys(path string) (map[string]string, error)

LoadTenantKeys parses a tenant-keys file where each non-empty, non-comment line is a `key=tenant` pair. The returned map is keyed by bearer token (value=tenant ID) so an authenticated request can be scoped to the key's bound tenant regardless of any client-asserted X-Tenant-ID header.

File format (YAML/INI-friendly, also accepts plain `key=tenant`):

# one mapping per line, comments start with '#'
5f4dcc3b5aa765d61d8327deb882cf99=acme
c20ad4d76fe97759aa27a0c99bff6710=beta

Whitespace around tokens is ignored; duplicate keys return an error so misconfiguration fails loud at startup.

func MetricsMiddleware

func MetricsMiddleware(metrics *telemetry.Metrics, next http.Handler) http.Handler

MetricsMiddleware records OtelContext_http_requests_total and OtelContext_http_request_duration_seconds for every HTTP request.

func RecoverMiddleware

func RecoverMiddleware(metrics *telemetry.Metrics, next http.Handler) http.Handler

RecoverMiddleware catches panics from downstream handlers/middleware, logs the stack trace, increments the panics-recovered metric, and responds with a generic 500. It must be installed as the OUTERMOST middleware (after MetricsMiddleware is wrapped around the stack) so panics anywhere below are caught. http.ErrAbortHandler is re-panicked to preserve net/http's sentinel-abort contract.

func RequireAPIKey

func RequireAPIKey(expectedKey string, next http.Handler) http.Handler

RequireAPIKey returns middleware that requires an `Authorization: Bearer <key>` header matching the configured API key. When expectedKey is empty the middleware is a pass-through (auth disabled) — the caller is expected to log a warning at startup in that case.

The comparison is constant-time via subtle.ConstantTimeCompare to avoid timing side channels. On mismatch or missing header a 401 is returned with a JSON body `{"error":"unauthorized"}`.

func TenantMiddleware

func TenantMiddleware(cfg *config.Config) func(http.Handler) http.Handler

TenantMiddleware extracts the tenant ID from the X-Tenant-ID header (falling back to cfg.DefaultTenant — or "default" when cfg is nil or empty) and stashes it on the request context via storage.WithTenantContext so repository reads can scope their WHERE clause with storage.TenantFromContext.

The middleware is path-aware: only requests whose path begins with "/api/" are tenant-scoped. OTLP write endpoints ("/v1/..."), health probes ("/live", "/ready"), Prometheus scrape ("/metrics/..."), MCP, WebSocket and UI assets pass through untouched — these either resolve tenant separately (OTLP) or are tenant-agnostic/privileged.

Types

type DBHealth

type DBHealth struct {
	// contains filtered or unexported fields
}

DBHealth periodically pings the database and exposes the result via an atomic boolean. The HTTP middleware below short-circuits /api/* traffic with a 503 when the flag is false, preventing goroutine pile-up on pool acquisition when the DB is unreachable.

To avoid false-positive 503 windows under load — e.g. SQLite with MaxOpen=1 where a busy write transiently blocks the 2s health ping — the poller flips to unhealthy only after failureThreshold consecutive failed pings. A single successful ping clears the counter and restores healthy immediately.

func NewDBHealth

func NewDBHealth(db DBPinger, driver string, metrics *telemetry.Metrics) *DBHealth

NewDBHealth constructs a health poller. Default poll interval is 5s; ping timeout is 2s per attempt; failureThreshold defaults to 3 consecutive failed pings before the gate flips. Start() must be called to begin polling.

func (*DBHealth) Healthy

func (h *DBHealth) Healthy() bool

Healthy reports the most recent ping result.

func (*DBHealth) SetFailureThreshold

func (h *DBHealth) SetFailureThreshold(n int)

SetFailureThreshold overrides the number of consecutive failed pings before the middleware flips to 503. n <= 0 normalises to 1 (legacy behaviour: any single failure trips the gate).

func (*DBHealth) Start

func (h *DBHealth) Start(ctx context.Context)

Start launches the background poller.

func (*DBHealth) Stop

func (h *DBHealth) Stop()

Stop signals the poller to exit and waits briefly for it to finish.

type DBPinger

type DBPinger interface {
	PingContext(ctx context.Context) error
}

DBPinger is the minimum DB interface DBHealth needs. *sql.DB satisfies it; tests can pass a stub.

type GraphEdge

type GraphEdge struct {
	Source       string  `json:"source"`
	Target       string  `json:"target"`
	CallCount    int64   `json:"call_count"`
	AvgLatencyMs float64 `json:"avg_latency_ms"`
	ErrorRate    float64 `json:"error_rate"`
	Status       string  `json:"status"`
}

GraphEdge represents a call relationship between two services.

type GraphNode

type GraphNode struct {
	ID          string      `json:"id"`
	Type        string      `json:"type"`
	HealthScore float64     `json:"health_score"`
	Status      string      `json:"status"`
	Metrics     NodeMetrics `json:"metrics"`
	Alerts      []string    `json:"alerts"`
}

GraphNode represents a service in the system graph.

type NodeMetrics

type NodeMetrics struct {
	RequestRateRPS float64 `json:"request_rate_rps"`
	ErrorRate      float64 `json:"error_rate"`
	AvgLatencyMs   float64 `json:"avg_latency_ms"`
	P99LatencyMs   float64 `json:"p99_latency_ms"`
	SpanCount1H    int64   `json:"span_count_1h"`
}

NodeMetrics holds per-service observability metrics.

type RateLimiter

type RateLimiter struct {
	// contains filtered or unexported fields
}

RateLimiter is a per-IP token bucket rate limiter middleware.

func NewRateLimiter

func NewRateLimiter(rps float64) *RateLimiter

NewRateLimiter creates a RateLimiter with the given requests-per-second limit.

func (*RateLimiter) Middleware

func (rl *RateLimiter) Middleware(next http.Handler) http.Handler

Middleware returns an http.Handler that enforces rate limiting.

func (*RateLimiter) MiddlewareExcept

func (rl *RateLimiter) MiddlewareExcept(skip func(path string) bool) func(http.Handler) http.Handler

MiddlewareExcept returns a handler chain that applies the rate limit only when skip(path) returns false. Intended to exempt OTLP ingestion paths from the per-IP API limiter.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server handles HTTP API requests.

func NewServer

func NewServer(repo *storage.Repository, hub *realtime.Hub, eventHub *realtime.EventHub, metrics *telemetry.Metrics) *Server

NewServer creates a new API server.

func (*Server) BroadcastLog

func (s *Server) BroadcastLog(l storage.Log)

BroadcastLog sends a log entry to the buffered WebSocket hub.

func (*Server) RegisterRoutes

func (s *Server) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers API endpoints on the provided mux.

func (*Server) SetDLQSaturationProbe

func (s *Server) SetDLQSaturationProbe(fn func() float64)

SetDLQSaturationProbe registers a callback returning DLQ disk fullness as a fraction in [0.0, 1.0]. Used by /ready to flip to 503 when DLQ is at risk of FIFO-evicting unflushed batches. Pass nil to disable the check.

func (*Server) SetGraph

func (s *Server) SetGraph(g *graph.Graph)

SetGraph wires the in-memory service graph into the API server.

func (*Server) SetGraphRAG

func (s *Server) SetGraphRAG(g *graphrag.GraphRAG)

SetGraphRAG wires the GraphRAG instance for advanced queries.

func (*Server) SetPipelineSaturationProbe

func (s *Server) SetPipelineSaturationProbe(fn func() float64)

SetPipelineSaturationProbe registers a callback returning ingest pipeline queue fullness as a fraction in [0.0, 1.0]. Used by /ready to flip to 503 when the pipeline is at hard capacity (already returning 429/RESOURCE_EXHAUSTED to clients). Pass nil to disable the check.

func (*Server) SetVectorIndex

func (s *Server) SetVectorIndex(idx *vectordb.Index)

SetVectorIndex wires the TF-IDF vector index for semantic log search.

type SystemGraphResponse

type SystemGraphResponse struct {
	Timestamp time.Time     `json:"timestamp"`
	System    SystemSummary `json:"system"`
	Nodes     []GraphNode   `json:"nodes"`
	Edges     []GraphEdge   `json:"edges"`
}

SystemGraphResponse is the full AI-consumable system graph.

type SystemSummary

type SystemSummary struct {
	TotalServices      int     `json:"total_services"`
	Healthy            int     `json:"healthy"`
	Degraded           int     `json:"degraded"`
	Critical           int     `json:"critical"`
	OverallHealthScore float64 `json:"overall_health_score"`
	TotalErrorRate     float64 `json:"total_error_rate"`
	AvgLatencyMs       float64 `json:"avg_latency_ms"`
	UptimeSeconds      float64 `json:"uptime_seconds"`
}

SystemSummary is the top-level system health summary.

type TenantKeyAuth

type TenantKeyAuth struct {
	// contains filtered or unexported fields
}

TenantKeyAuth holds a key→tenant map loaded from APITenantKeysFile and exposes middleware that both authenticates AND pins the tenant onto the request context. When enabled it OVERRIDES any X-Tenant-ID header — callers cannot cross tenants by swapping headers.

func NewTenantKeyAuth

func NewTenantKeyAuth(entries map[string]string) *TenantKeyAuth

NewTenantKeyAuth constructs a TenantKeyAuth from a pre-loaded map. nil or empty disables the layer; callers should fall back to the shared API_KEY path in that case.

func (*TenantKeyAuth) Enabled

func (a *TenantKeyAuth) Enabled() bool

Enabled reports whether per-tenant keys are configured.

func (*TenantKeyAuth) Lookup

func (a *TenantKeyAuth) Lookup(key string) (string, bool)

Lookup returns the tenant bound to the given bearer key, using a constant-time compare against every entry so mismatches don't leak via timing.

func (*TenantKeyAuth) Middleware

func (a *TenantKeyAuth) Middleware(mcpPath string, next http.Handler) http.Handler

Middleware returns an http.Handler wrapper that requires an `Authorization: Bearer <key>` header present in the tenant-keys map. On success the matched tenant is pinned onto the request context via storage.WithTenantContext, overriding any client-supplied X-Tenant-ID. On mismatch (including missing header) it returns 401.

Public paths (IsProtectedPath == false) pass through unchanged so UI assets and health probes remain reachable without credentials.

Directories

Path Synopsis
Package views provides explicit JSON view models for the HTTP API.
Package views provides explicit JSON view models for the HTTP API.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL