Files
ITWorx-Pulse-Public/internal/agentstore/contract.go
T
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

105 lines
4.1 KiB
Go

// Package agentstore is the transport boundary between pulse-agent and pulse-api.
//
// The agent runs with the narrow read-only host access it needs and never exposes a
// network endpoint; the API never reaches out to the host. Both processes already share
// PostgreSQL on the internal-only Compose network (see deploy/compose.yaml and ADR-0010),
// so the agent writes bounded, normalized snapshots into the database and the API reads
// the most recent one per capability. This keeps the privilege separation required by
// SYSTEM_ARCHITECTURE section "pulse-agent" without adding an inbound port to the agent.
//
// Freshness is deliberately the reader's problem, not the writer's: a snapshot carries
// the time the agent observed it, and the reader decides whether that is still usable.
// A capability with no snapshot, or one older than its freshness window, resolves to
// Unknown and never to Healthy (ADR-0008).
package agentstore
import (
"context"
"encoding/json"
"errors"
"time"
)
// Capability identifies one bounded telemetry surface an agent can report. The set is
// closed: a reader must never accept a capability it does not recognize, because an
// unknown capability cannot be normalized or bounded.
type Capability string
const (
CapabilityHost Capability = "host"
CapabilityProcesses Capability = "processes"
CapabilityContainers Capability = "containers"
CapabilityArray Capability = "array"
CapabilityDisks Capability = "disks"
CapabilityPools Capability = "pools"
CapabilityShares Capability = "shares"
)
// Capabilities lists every capability the platform recognizes, in a stable order.
func Capabilities() []Capability {
return []Capability{
CapabilityHost, CapabilityProcesses, CapabilityContainers,
CapabilityArray, CapabilityDisks, CapabilityPools, CapabilityShares,
}
}
// Valid reports whether the capability is one this platform recognizes.
func (c Capability) Valid() bool {
for _, known := range Capabilities() {
if c == known {
return true
}
}
return false
}
// MaxPayloadBytes bounds a single snapshot. The largest realistic payload is the process
// inventory at the documented scale target; this leaves generous headroom while keeping a
// misbehaving or compromised agent from filling the database.
const MaxPayloadBytes = 2 << 20
// ErrNoSnapshot is returned by Reader.Latest when the capability has never been reported.
// It is an expected condition on a fresh install, not a failure: callers translate it into
// an Unknown status with an explicit reason.
var ErrNoSnapshot = errors.New("no agent snapshot recorded")
// Snapshot is one bounded observation of a single capability.
type Snapshot struct {
// AgentID identifies the reporting agent.
AgentID string
// Capability is the telemetry surface this payload describes.
Capability Capability
// ObservedAt is when the agent read the underlying source, in UTC.
ObservedAt time.Time
// ReceivedAt is when the store accepted the snapshot, in UTC. It is set by the
// writer implementation, never by the agent, so a skewed agent clock cannot make
// stale data look fresh.
ReceivedAt time.Time
// Payload is the domain RawSnapshot for this capability, JSON encoded.
Payload json.RawMessage
}
// Age reports how long ago the agent observed this snapshot.
func (s Snapshot) Age(now time.Time) time.Duration { return now.Sub(s.ObservedAt) }
// Writer is the narrow interface pulse-agent depends on. The agent must not be able to
// read other agents' data or mutate anything else.
type Writer interface {
// Put records the newest snapshot for one capability, replacing any previous one.
// Implementations reject an unknown capability, an oversized payload, a zero or
// future ObservedAt, and payloads that are not valid JSON objects.
Put(ctx context.Context, snapshot Snapshot) error
}
// Reader is the narrow interface pulse-api depends on.
type Reader interface {
// Latest returns the most recent snapshot for the capability, or ErrNoSnapshot.
Latest(ctx context.Context, capability Capability) (Snapshot, error)
}
// Store is the combined boundary. Only the migration-owning implementation satisfies it.
type Store interface {
Writer
Reader
}