Documentation
¶
Overview ¶
Package api provides Chronos's HTTP REST API. It is a thin transport layer over the persistence ports: parse the request, call a port, render the response. Per the cognitive-stack vision Chronos surfaces signals; it does not interpret or render prose. The wire shape is just the structured signal — Title/Summary/Suggestion are not part of this API.
Index ¶
- Constants
- func Chain(h http.Handler, mws ...Middleware) http.Handler
- func MarshalIndent(v any) ([]byte, error)
- type ConfigValidateRequest
- type ConfigValidateResponse
- type DetectorReport
- type EvidenceDTO
- type ExplanationDTO
- type FeatureSampleDTO
- type FederationExportResponse
- type FederationPatternStats
- type IngestBatchRequest
- type IngestBatchResponse
- type IngestRequest
- type Middleware
- type SSEBroadcaster
- type Server
- type SignalDTO
- type TimeWindowDTO
Constants ¶
const FederationExportVersion = "v1"
FederationExportVersion is the wire-format generation. Bump on shape changes so downstream pull-mirror clients can reject incompatible payloads explicitly.
const MaxIngestBatchSize = 1000
MaxIngestBatchSize caps a single /v1/ingest/batch payload. Beyond this, callers must chunk — the limit guards memory in the handler path and gives operators a predictable upper bound on transaction duration.
Variables ¶
This section is empty.
Functions ¶
func Chain ¶
func Chain(h http.Handler, mws ...Middleware) http.Handler
Chain composes middlewares into a single handler. The first middleware in the slice is the outermost — i.e. it sees the request first and the response last.
func MarshalIndent ¶ added in v0.5.0
MarshalIndent is exported only so the cmd-line helper that pulls federation snapshots can render JSON with the same shape the wire uses. Kept here so the wire and the CLI never drift.
Types ¶
type ConfigValidateRequest ¶ added in v0.5.0
ConfigValidateRequest is the wire shape for POST /v1/config/validate. Callers pass the env vars they intend to flip; the handler reports what each detector would do under that config WITHOUT mutating the running server's behaviour.
type ConfigValidateResponse ¶ added in v0.5.0
type ConfigValidateResponse struct {
Detectors []DetectorReport `json:"detectors"`
}
ConfigValidateResponse lists one report per detector, sorted by name for stable diffing across runs.
type DetectorReport ¶ added in v0.5.0
type DetectorReport struct {
Name string `json:"name"`
Enabled bool `json:"enabled"`
Reason string `json:"reason,omitempty"`
Thresholds map[string]any `json:"thresholds,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
DetectorReport carries the dry-run verdict for one detector.
func BuildConfigReportForExport ¶ added in v0.5.0
func BuildConfigReportForExport(cfg *config.Config) []DetectorReport
BuildConfigReportForExport is the exported entry point for callers outside the HTTP layer (e.g. the MCP describe_detector tool) that want the same per-detector verdict the /v1/config/validate endpoint returns. Returns the list directly rather than the wrapping struct so MCP/CLI surfaces can render it however they like.
type EvidenceDTO ¶
type EvidenceDTO struct {
Series uuid.UUID `json:"series"`
Time time.Time `json:"time"`
Kind string `json:"kind"`
Score float64 `json:"score"`
Metrics map[string]float64 `json:"metrics,omitempty"`
}
EvidenceDTO is one piece of supporting evidence.
type ExplanationDTO ¶ added in v0.5.0
type ExplanationDTO struct {
FeatureEvolution []FeatureSampleDTO `json:"feature_evolution,omitempty"`
ComparablePeers int `json:"comparable_peers,omitempty"`
BaselineWindowDays int `json:"baseline_window_days,omitempty"`
ThresholdUsed float64 `json:"threshold_used,omitempty"`
DetectorVersion string `json:"detector_version,omitempty"`
}
ExplanationDTO is the wire shape of an Explanation value object. All fields are optional; a Signal without an Explanation omits this object entirely (pointer in SignalDTO).
type FeatureSampleDTO ¶ added in v0.5.0
FeatureSampleDTO is one observation in the feature evolution series.
type FederationExportResponse ¶ added in v0.5.0
type FederationExportResponse struct {
GeneratedAt time.Time `json:"generated_at"`
Source string `json:"source"` // "chronos"
Version string `json:"version"` // schema version of this payload
Patterns []FederationPatternStats `json:"patterns"`
TotalSignals int `json:"total_signals"`
}
FederationExportResponse is the wire shape for GET /v1/federation/export. Carries aggregated, anonymized pattern statistics: counts per pattern type, average / min / max strength and confidence, mean sample size, plus the generation timestamp.
Nothing in the payload identifies a tenant. There are no scope_ids, no series ids, no signal ids, and no per-row evidence. That is the load-bearing safety property of the federation hook — the export is community-grade statistical insight, not raw data.
type FederationPatternStats ¶ added in v0.5.0
type FederationPatternStats struct {
Pattern string `json:"pattern"`
Count int `json:"count"`
AvgStrength float64 `json:"avg_strength"`
MinStrength float64 `json:"min_strength"`
MaxStrength float64 `json:"max_strength"`
AvgConfidence float64 `json:"avg_confidence"`
MinConfidence float64 `json:"min_confidence"`
MaxConfidence float64 `json:"max_confidence"`
AvgSampleSize float64 `json:"avg_sample_size,omitempty"` // mean of metrics["n"] when populated
TentativeCount int `json:"tentative_count,omitempty"`
EstablishedCount int `json:"established_count,omitempty"`
StrongCount int `json:"strong_count,omitempty"`
}
FederationPatternStats summarises one pattern type's signal population.
type IngestBatchRequest ¶ added in v0.5.0
type IngestBatchRequest struct {
Observations []IngestRequest `json:"observations"`
// DeferDetection is accepted for API forward-compatibility. Chronos
// already separates ingest from detection (the compute pipeline
// runs detection at job time, not on ingest), so the flag is
// currently a no-op — it is echoed in the response so callers can
// rely on the field shape without branching.
DeferDetection bool `json:"defer_detection,omitempty"`
}
IngestBatchRequest is the wire shape for POST /v1/ingest/batch. It accepts an array of observations in one HTTP call so integrators backfilling history don't pay one round-trip per datapoint.
All observations land in a single repository call; on any one observation failing validation the entire batch is rejected (all-or-nothing semantics — partial writes would be hard to roll back and harder to reason about).
type IngestBatchResponse ¶ added in v0.5.0
type IngestBatchResponse struct {
Accepted int `json:"accepted"`
DeferDetection bool `json:"defer_detection"`
}
IngestBatchResponse confirms how many observations were persisted and whether detection was deferred.
type IngestRequest ¶
type IngestRequest struct {
ID uuid.UUID `json:"id,omitempty"`
EntityID uuid.UUID `json:"entity_id"`
ScopeID uuid.UUID `json:"scope_id"`
Timestamp time.Time `json:"timestamp"`
Features []float64 `json:"features"`
Labels []string `json:"labels,omitempty"`
Meta map[string]string `json:"meta,omitempty"`
// Adapter labels the source for retention policy / count queries. If
// empty, signals fall back to "http" so streaming clients are
// distinguishable from pull adapters in the Count column.
Adapter string `json:"adapter,omitempty"`
}
IngestRequest is the wire shape for POST /v1/ingest. It mirrors the vision's TimeSeriesPoint with multi-feature support.
type Middleware ¶
Middleware wraps an http.Handler with cross-cutting behaviour. It is the standard "decorator" signature used by net/http composition.
func BearerAuth ¶
func BearerAuth(token string) Middleware
BearerAuth requires every request to carry an "Authorization: Bearer <token>" header matching the configured token. If token is empty, authentication is disabled and the middleware is a no-op — that matches the documented "no auth out of the box" default. The /health route is always public so liveness probes never need credentials.
Token comparison uses subtle.ConstantTimeCompare to avoid leaking the prefix length via timing.
func Logging ¶
func Logging(logger *slog.Logger, metrics *observability.Metrics) Middleware
Logging emits a structured slog event for every HTTP request once the response has been written, and (when metrics is non-nil) records the observation for /metrics scraping. Fields on the slog event: method, path, status, duration, remote.
func Recover ¶
func Recover(logger *slog.Logger) Middleware
Recover converts panics in downstream handlers into 500 responses and logs the stack trace. Without it, a single buggy handler can crash the whole HTTP server because net/http re-throws the panic.
type SSEBroadcaster ¶
type SSEBroadcaster interface {
// Subscribe registers a stream client. scopes is a server-side
// allowlist: nil delivers every scope (operator-grade reads),
// non-nil restricts delivery to the listed scope ids. The handler
// constructs the allowlist from ?scope_id= (single-element) or
// ?scope_in= (multi-element) so clients can't widen their own
// filter post-handshake.
Subscribe(scopes []uuid.UUID, pattern string) (uuid.UUID, <-chan domain.Signal)
Unsubscribe(uuid.UUID)
}
SSEBroadcaster is the structural contract the api layer requires of any streaming source. internal/notify.SSE satisfies it without either package importing the other (notify -> api would otherwise cycle through webhook.go's use of ToSignalDTO).
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server holds dependencies for HTTP handlers.
func NewServer ¶
func NewServer(states ports.EntityStateRepository, signals ports.SignalRepository, metrics *observability.Metrics, logger *slog.Logger) *Server
NewServer wires an HTTP server. Pass slog.Default() for the logger if the caller has no preference. The states repository is required to support the streaming /v1/ingest endpoint; pass nil to disable it (the route still mounts but returns 501). metrics may be nil, in which case /metrics returns an empty document and observations in handlers are no-ops.
func (*Server) RegisterRoutes ¶
RegisterRoutes wires API routes onto a ServeMux. Routes are stable wire contracts; bump /v1 to introduce incompatible changes.
func (*Server) WithSSE ¶
func (s *Server) WithSSE(sse SSEBroadcaster) *Server
WithSSE attaches an SSE broadcaster so the /v1/signals/stream route can register subscribers. Returns the server for chaining. Without this attachment, /v1/signals/stream responds 501 Not Implemented.
type SignalDTO ¶
type SignalDTO struct {
ID uuid.UUID `json:"id"`
ScopeID uuid.UUID `json:"scope_id"`
Series uuid.UUID `json:"series"`
Pattern string `json:"pattern"`
DetectedAt time.Time `json:"detected_at"`
Window TimeWindowDTO `json:"window"`
Strength float64 `json:"strength"`
Confidence float64 `json:"confidence"`
Metrics map[string]float64 `json:"metrics,omitempty"`
Evidence []EvidenceDTO `json:"evidence,omitempty"`
// Explanation carries detector-side context that lets downstream
// consumers narrate WHY the signal fired without re-deriving the
// data. Omitted when the detector did not surface one.
Explanation *ExplanationDTO `json:"explanation,omitempty"`
// ConfidenceClass is the qualitative grade (tentative /
// established / strong) the detector assigned based on sample
// size vs MIN_POINTS. Empty when the detector did not classify.
ConfidenceClass string `json:"confidence_class,omitempty"`
}
SignalDTO is the wire shape for signals returned by the HTTP API. It is decoupled from domain.Signal so internal refactors do not break clients. There is no Title/Summary/Suggestion: per the cognitive- stack vision, Chronos emits signals — not prose. Downstream consumers (Nous) interpret the structured fields.
func ToSignalDTO ¶
ToSignalDTO renders a domain.Signal into its wire form.