Documentation
¶
Overview ¶
Package storage exposes a request-scoped DB query counter so HTTP middleware can observe the number of database round-trips a single panel request fires. Closes audit P-02: the audit suspected N+1 patterns in `clients_flow.go` / `agent_flow.go` but had no way to confirm without SQL tracing. This file plus instrumentedExecutor.go in each backend provides that confirmation surface.
Index ¶
- Constants
- Variables
- func DBQueryCount(ctx context.Context) int64
- func DecodeKeysetCursor(encoded string) (time.Time, string, error)
- func EncodeKeysetCursor(createdAt time.Time, id string) string
- func IncrementDBQuery(ctx context.Context)
- func NormalizeCursorLimit(limit int) int
- func WithDBQueryCounter(ctx context.Context) context.Context
- type AgentCertPins
- type AgentCertificateRecoveryGrantRecord
- type AgentCertificateRecoveryGrantStore
- type AgentConfigTargetRecord
- type AgentFallbackStateRecord
- type AgentFallbackStateStore
- type AgentRecord
- type AgentRevocationRecord
- type AgentRevocationStore
- type AgentUpdateStrategyRecord
- type AuditEventRecord
- type AuditStore
- type CPSecretRecord
- type CPSecretStore
- type CertificateAuthorityRecord
- type CertificateAuthorityStore
- type ClientAssignmentRecord
- type ClientDeploymentRecord
- type ClientIPAggregateRecord
- type ClientIPHistoryRecord
- type ClientRecord
- type ClientStoredeprecated
- type ClientUsageRecord
- type ConfigApplyBatchRecord
- type ConfigApplyBatchStore
- type ConfigApplyBatchTargetRecord
- type ConsumedTotpRecord
- type ConsumedTotpStore
- type DCHealthPointRecord
- type DiscoveredClientRecord
- type EnrollmentStore
- type EnrollmentTokenRecord
- type FleetGroupIntegrationRecord
- type FleetGroupRecord
- type FleetStore
- type InstanceRecord
- type IntegrationProviderRecord
- type IntegrationStore
- type JobRecord
- type JobStore
- type JobTargetRecord
- type ListAuditEventsCursorParams
- type ListJobsCursorParams
- type LoginLockoutRecord
- type LoginLockoutStore
- type MetricSnapshotRecord
- type MetricStore
- type MigrationStore
- type PanelSettingsRecord
- type PanelSettingsStore
- type ReassignCounts
- type RetentionSettings
- type RetentionSettingsRecord
- type RetentionSettingsStore
- type ServerLoadHourlyRecord
- type ServerLoadPointRecord
- type SessionRecord
- type SessionStore
- type Store
- type TelemetryDiagnosticsCurrentRecord
- type TelemetryRuntimeCurrentRecord
- type TelemetryRuntimeDCRecord
- type TelemetryRuntimeEventRecord
- type TelemetryRuntimeUpstreamRecord
- type TelemetrySecurityInventoryCurrentRecord
- type TelemetryStore
- type TimeseriesStore
- type TxFn
- type UpdateConfigStore
- type UserAppearanceRecord
- type UserAppearanceStore
- type UserFleetGroupScopeRecord
- type UserFleetGroupScopeStore
- type UserRecord
- type UserStore
Constants ¶
const ( ConfigScopeGroup = "group" ConfigScopeAgent = "agent" )
Config target scope kinds.
const ( ConfigApplyBatchModeAllAtOnce = "all_at_once" ConfigApplyBatchModeRolling = "rolling" )
Config-apply batch rollout modes.
const ( ConfigApplyBatchStatusRunning = "running" ConfigApplyBatchStatusSucceeded = "succeeded" ConfigApplyBatchStatusFailed = "failed" ConfigApplyBatchStatusHalted = "halted" )
Config-apply batch lifecycle statuses.
const ( ConfigApplyTargetStatusPending = "pending" ConfigApplyTargetStatusRunning = "running" ConfigApplyTargetStatusSucceeded = "succeeded" ConfigApplyTargetStatusFailed = "failed" ConfigApplyTargetStatusSkipped = "skipped" )
Config-apply batch per-target delivery statuses.
const ( DefaultCursorPageSize = 100 MaxCursorPageSize = 500 )
Cursor pagination defaults (S25 T1). Callers omit Limit (== 0) to get DefaultCursorPageSize; values above MaxCursorPageSize are clamped down so an operator cannot ask for an unbounded page.
const DefaultListLimit = 1000
DefaultListLimit caps unbounded list queries so a long-lived control plane can never stream millions of rows back to the caller (P-7). Queries that already accept an explicit limit override this value; queries that have no natural ceiling (per-client IP history, raw timeseries) apply this value silently as a safety floor. 1000 is large enough that the dashboard page- sized views (typically <= 500 rows) are unaffected, while bounding the worst case at a few hundred kilobytes of result data.
Variables ¶
var ( // ErrNotFound reports a missing persisted record. ErrNotFound = errors.New("storage record not found") // ErrConflict reports a uniqueness or state-transition conflict during persistence. ErrConflict = errors.New("storage conflict") // ErrNestedTransact is returned when a TxFn calls Transact on the tx // argument. Transactions are not reentrant — see P2-ARCH-01. ErrNestedTransact = errors.New("storage: nested Transact call not allowed") )
Functions ¶
func DBQueryCount ¶
DBQueryCount returns the number of queries seen since WithDBQueryCounter was called on ctx. Returns 0 when no counter is attached.
func DecodeKeysetCursor ¶
DecodeKeysetCursor parses an opaque cursor produced by EncodeKeysetCursor. An empty input returns the zero cursor (first page). Malformed input returns an error so the HTTP layer can respond 400 — silently treating garbage as "first page" would let a stale-but-valid-looking cursor produce wrong results without the client noticing.
func EncodeKeysetCursor ¶
EncodeKeysetCursor returns the base64-url JSON encoding of the (createdAt, id) keyset position. Empty id == sentinel "first page" — callers should pass "" for both arguments to mean that.
func IncrementDBQuery ¶
IncrementDBQuery is called by instrumented dbExecutor wrappers on every query (Exec/Query/QueryRow). When ctx carries no counter (operations outside a tracked HTTP request — startup, batch writer, background jobs), this is a cheap no-op.
func NormalizeCursorLimit ¶
NormalizeCursorLimit applies the DefaultCursorPageSize/MaxCursorPageSize envelope. Exposed so storage backends and HTTP handlers stay consistent.
func WithDBQueryCounter ¶
WithDBQueryCounter installs a fresh atomic counter onto ctx. Call once at the start of every operation whose query count you want to measure (typically the HTTP middleware on each inbound request). Returns a derived ctx that downstream storage calls can read.
Types ¶
type AgentCertPins ¶
type AgentCertPins struct {
Serial string
SPKI []byte
PrevSerial string
PrevSPKI []byte
OverlapUntil *time.Time
}
AgentRecord stores one enrolled host agent snapshot. AgentCertPins is the set of agent credentials the panel accepts at a given moment: the current one, and — while a rotation overlap window is open — the previous one. OverlapUntil is nil when no rotation is in flight.
The pair is deliberately small and time-bounded: it exists so an interrupted renewal cannot strand a node on a certificate the panel has already forgotten (R11), not to soften the fail-closed pinning.
type AgentCertificateRecoveryGrantRecord ¶
type AgentCertificateRecoveryGrantRecord struct {
AgentID string
IssuedBy string
IssuedAt time.Time
ExpiresAt time.Time
UsedAt *time.Time
RevokedAt *time.Time
}
AgentCertificateRecoveryGrantRecord stores one administrator-approved certificate recovery window.
type AgentCertificateRecoveryGrantStore ¶
type AgentCertificateRecoveryGrantStore interface {
PutAgentCertificateRecoveryGrant(ctx context.Context, grant AgentCertificateRecoveryGrantRecord) error
ListAgentCertificateRecoveryGrants(ctx context.Context) ([]AgentCertificateRecoveryGrantRecord, error)
GetAgentCertificateRecoveryGrant(ctx context.Context, agentID string) (AgentCertificateRecoveryGrantRecord, error)
UseAgentCertificateRecoveryGrant(ctx context.Context, agentID string, usedAt time.Time) (AgentCertificateRecoveryGrantRecord, error)
RevokeAgentCertificateRecoveryGrant(ctx context.Context, agentID string, revokedAt time.Time) (AgentCertificateRecoveryGrantRecord, error)
}
AgentCertificateRecoveryGrantStore persists administrator-approved certificate recovery windows.
type AgentConfigTargetRecord ¶
type AgentConfigTargetRecord struct {
ScopeType string
ScopeID string
SectionsJSON string
CreatedAt time.Time
UpdatedAt time.Time
}
AgentConfigTargetRecord is the operator's desired Telemt config for one scope. ScopeType is ConfigScopeGroup (ScopeID = fleet group id) or ConfigScopeAgent (ScopeID = agent id). SectionsJSON is a sparse JSON object of editable config sections (general/timeouts/censorship/upstreams/dc_overrides).
type AgentFallbackStateRecord ¶
AgentFallbackStateRecord persists when an agent first entered ME→Direct fallback. Cleared when MERuntimeReady returns to true. EnteredAt is the stable origin used to compute fallback duration for severity bucketing even across control-plane restarts.
type AgentFallbackStateStore ¶
type AgentFallbackStateStore interface {
PutAgentFallbackState(ctx context.Context, rec AgentFallbackStateRecord) error
DeleteAgentFallbackState(ctx context.Context, agentID string) error
GetAgentFallbackState(ctx context.Context, agentID string) (AgentFallbackStateRecord, error)
// TODO(cursor): add ListAgentFallbackStateCursor next sprint. This is the
// boot-time restore path; deferred from S25 T1 because the bound is
// "agents in fallback simultaneously" which is small in practice.
ListAgentFallbackState(ctx context.Context) ([]AgentFallbackStateRecord, error)
}
AgentFallbackStateStore persists per-agent ME→Direct fallback windows. A row exists exactly while the agent is currently in fallback: it is inserted when MERuntimeReady first flips to false and deleted when it returns to true. The persisted EnteredAt is the source of truth for fallback-duration severity classification across panel restarts. See AgentFallbackStateRecord in models.go.
type AgentRecord ¶
type AgentRecord struct {
ID string
NodeName string
FleetGroupID string
Version string
ReadOnly bool
LastSeenAt time.Time
CertIssuedAt *time.Time
CertExpiresAt *time.Time
// CertSerial pins the latest issued certificate's serial number so a
// previously-issued cert (e.g. a not-yet-expired old one harvested
// from a backup or rotation log) cannot impersonate the agent
// (Q4.U-S-04). Hex-encoded big-endian serial.
CertSerial string
// CertSPKISHA256 is the SHA-256 hash of the agent's serving cert SPKI,
// set on first successful enroll (S-02). Empty bytes mean "not yet
// pinned"; subsequent dials must verify the served cert hashes to this
// value via storage.UpdateAgentCertPin.
CertSPKISHA256 []byte
// BootstrapState is the enrollment lifecycle state ("pending",
// "expired", "active"; empty for legacy inbound rows). Written by the
// bootstrap token/enroll paths; surfaced so a half-added node restores
// and renders as pending rather than offline (R9a).
BootstrapState string
// TransportMode is the persisted dial mode ("inbound"/"outbound").
TransportMode string
// DialAddress is the panel-dials-agent target for outbound rows.
DialAddress string
}
type AgentRevocationRecord ¶
AgentRevocationRecord tracks one deregistered agent whose mTLS client certificate may still be cryptographically valid. The record survives control-plane restart so a revoked agent cannot silently reconnect. CertExpiresAt is the cert validity cut-off; once the cert has expired the row is eligible for pruning because the cert can no longer authenticate regardless of the revocation list.
type AgentRevocationStore ¶
type AgentRevocationStore interface {
PutAgentRevocation(ctx context.Context, revocation AgentRevocationRecord) error
ListAgentRevocations(ctx context.Context) ([]AgentRevocationRecord, error)
DeleteExpiredAgentRevocations(ctx context.Context, before time.Time) (int64, error)
}
AgentRevocationStore persists deregistered-agent IDs so the revocation set survives control-plane restart. See AgentRevocationRecord in models.go.
type AgentUpdateStrategyRecord ¶
type AgentUpdateStrategyRecord struct {
AgentID string
Mode string
RestartSpec string
BinaryPath string
AssetFlavor string
CreatedAt time.Time
UpdatedAt time.Time
}
AgentUpdateStrategyRecord is the persisted Telemt update strategy for one agent (Telemt Update v1, Task 8): how `telemt.update` jobs should be applied on that agent. Mode is one of "binary" | "docker" | "none". RestartSpec / BinaryPath carry the resolved telemtrestart.Spec fields; AssetFlavor distinguishes release-asset variants (e.g. "" | "v3").
type AuditEventRecord ¶
type AuditEventRecord struct {
ID string
ActorID string
Action string
TargetID string
CreatedAt time.Time
Details map[string]any
PrevHash string
EventHash string
}
AuditEventRecord stores one immutable control-plane audit event.
PrevHash and EventHash form the tamper-evident chain (migration 0038). PrevHash is the EventHash of the previous row in (created_at, id) ascending order; EventHash is sha256(prev_hash || canonical(record)) computed by the producer side. Both fields are empty on rows written before the migration and on the synthetic genesis position. The verify-audit-chain subcommand walks the chain and recomputes; any admin who silently rewrites a row will fail the verifier.
type AuditStore ¶
type AuditStore interface {
AppendAuditEvent(ctx context.Context, event AuditEventRecord) error
// AppendAuditEventsBulk inserts a batch of audit events in one
// transaction (P6-6.1b, finding #10). Rows are inserted in slice
// order; hash-chain fields (PrevHash/EventHash) are computed by the
// producer BEFORE buffering, so bulk insertion does not affect chain
// integrity. IDs are unique — no conflict clause.
AppendAuditEventsBulk(ctx context.Context, events []AuditEventRecord) error
// ListAuditEvents returns the most recent audit events in ascending
// chronological order. limit caps the number of rows returned; values
// <= 0 fall back to a hard maximum of 1024.
ListAuditEvents(ctx context.Context, limit int) ([]AuditEventRecord, error)
// ListAuditEventsCursor returns one page in (created_at DESC, id DESC)
// order — newest first — for keyset pagination. The returned next cursor
// is non-empty iff a further page may exist. Limit follows
// DefaultCursorPageSize / MaxCursorPageSize. Note this differs from
// ListAuditEvents which returns ASCENDING for legacy timeline replay;
// the cursor variant goes newest-first because that's the operator's
// audit-page reading order.
ListAuditEventsCursor(ctx context.Context, params ListAuditEventsCursorParams) ([]AuditEventRecord, ListAuditEventsCursorParams, error)
// PruneAuditEvents deletes audit_events rows with created_at strictly
// before the cutoff and returns the number of deleted rows. Used by the
// retention worker (P2-REL-04 / finding M-R2) to keep audit_events from
// growing unbounded.
PruneAuditEvents(ctx context.Context, before time.Time) (int64, error)
// LatestAuditChainHash returns the event_hash of the most recently
// persisted audit row, or "" when the table is empty (no chain yet).
// Producers read this once before each batch flush so each new row's
// PrevHash is the previous row's EventHash. Migration 0038 added the
// underlying columns.
LatestAuditChainHash(ctx context.Context) (string, error)
}
AuditStore persists immutable operator and security events.
type CPSecretRecord ¶
CPSecretRecord is one row of the cp_secrets key/value table. Value is raw byte material (e.g. the CSRF HMAC seed) stored verbatim. Used only by the offline migrate tooling to enumerate every secret for a table-complete sqlite→postgres copy; the runtime path reads/writes a single key at a time via Get/PutCPSecret.
type CPSecretStore ¶
type CPSecretStore interface {
GetCPSecret(ctx context.Context, key string) ([]byte, error)
PutCPSecret(ctx context.Context, key string, value []byte) error
// ListCPSecrets returns every cp_secrets row. cp_secrets has no
// natural listing method on the runtime path (callers always know
// the key); this exists for the offline migrate tooling so the
// table-complete copy can enumerate and re-Put every secret
// verbatim. Values are raw bytes — never re-encoded.
ListCPSecrets(ctx context.Context) ([]CPSecretRecord, error)
}
CPSecretStore persists small opaque per-cluster secrets like the CSRF HMAC seed (Q2.U-S-24). Values are bytes so callers can store raw key material without an extra encoding hop.
type CertificateAuthorityRecord ¶
CertificateAuthorityRecord stores the persisted control-plane root CA material.
type CertificateAuthorityStore ¶
type CertificateAuthorityStore interface {
PutCertificateAuthority(ctx context.Context, authority CertificateAuthorityRecord) error
GetCertificateAuthority(ctx context.Context) (CertificateAuthorityRecord, error)
}
CertificateAuthorityStore persists the control-plane root CA required for agent mTLS continuity.
type ClientAssignmentRecord ¶
type ClientAssignmentRecord struct {
ID string
ClientID string
TargetType string
FleetGroupID string
AgentID string
CreatedAt time.Time
}
ClientAssignmentRecord stores one desired rollout target for a managed client.
type ClientDeploymentRecord ¶
type ClientDeploymentRecord struct {
ClientID string
AgentID string
DesiredOperation string
Status string
LastError string
ConnectionLinks []string
LinkDiagnostic string
LastAppliedAt *time.Time
UpdatedAt time.Time
LastResetEpochSecs uint64
}
ClientDeploymentRecord stores the current rollout state for one client on one agent. ConnectionLinks holds every Telemt-reported link for this user (one per tls_domain × host combination). Stored as a JSON array on disk. LastResetEpochSecs is the unix-seconds value Telemt returned the last time the panel completed a client.reset_quota job here — zero when the panel has never reset this pair. Used for drift detection against Telemt's currently-reported quota_last_reset_unix. LinkDiagnostic is an operator-facing warning persisted alongside an otherwise-successful apply (IN-M2). Empty means "no issue". Non-empty when the node reported no connection links on a non-delete success, so ConnectionLinks may be stale.
type ClientIPAggregateRecord ¶
ClientIPAggregateRecord captures the per-IP aggregate across nodes: the earliest first-seen and the latest last-seen for a single IP address, regardless of which agent reported each end of the window. Used by the dashboard's IP-history endpoint so the heavy aggregation runs in SQL instead of in CP memory.
type ClientIPHistoryRecord ¶
type ClientIPHistoryRecord struct {
AgentID string
ClientID string
IPAddress string
FirstSeen time.Time
LastSeen time.Time
}
ClientIPHistoryRecord stores one unique IP seen for a client on an agent.
type ClientRecord ¶
type ClientRecord struct {
ID string
Name string
SecretCiphertext string
UserADTag string
Enabled bool
MaxTCPConns int
MaxUniqueIPs int
DataQuotaBytes int64
ExpirationRFC3339 string
SubscriptionToken string // opaque /sub/<token> handle; "" means not yet generated
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
ClientRecord stores one centrally managed Telemt client definition.
type ClientStore
deprecated
type ClientStore interface {
PutClient(ctx context.Context, client ClientRecord) error
ListClients(ctx context.Context) ([]ClientRecord, error)
PutClientAssignment(ctx context.Context, assignment ClientAssignmentRecord) error
ListClientAssignments(ctx context.Context, clientID string) ([]ClientAssignmentRecord, error)
PutClientDeployment(ctx context.Context, deployment ClientDeploymentRecord) error
ListClientDeployments(ctx context.Context, clientID string) ([]ClientDeploymentRecord, error)
// Per-(client, agent) usage counters. Persisted so the in-memory
// server.clientUsage map can rehydrate across restarts without losing
// accumulated traffic totals.
//
// UpsertClientUsage is the single-row variant. The live agent-flow
// telemetry hot path has since moved to the clients_repository-backed
// domain service (clientsSvc.UpsertUsage / UpsertUsageBulk — see
// server/agent_flow.go persistClientUsageRecords), not this Store-level
// method. UpsertClientUsage itself is still a genuine production caller
// via storage.MigrationStore (migrate-schema CLI, copy_extra.go
// copyTierOneEntities) plus the storagetest contract suite — do not
// remove.
UpsertClientUsage(ctx context.Context, record ClientUsageRecord) error
ListClientUsage(ctx context.Context) ([]ClientUsageRecord, error)
DeleteClientUsageByClient(ctx context.Context, clientID string) error
}
ClientStore persists centrally managed Telemt clients, rollout assignments, and per-node deployment state.
Deprecated: Wave 4.2 replaced direct Store access with clients.Repository + UnitOfWork. No longer embedded in the Store aggregate (AC#10). Retained as a standalone interface for MigrationStore (migrate-schema CLI) and storagetest Transact contract tests.
type ClientUsageRecord ¶
type ClientUsageRecord struct {
ClientID string
AgentID string
TrafficUsedBytes uint64
UniqueIPsUsed int
ActiveTCPConns int
ActiveUniqueIPs int
QuotaUsedBytes uint64
QuotaLastResetUnix uint64
AgentBootID string
LastTotalBytes uint64
ObservedAt time.Time
}
ClientUsageRecord stores the lifetime traffic + live-gauge counters for one (client, agent) pair. Persisted so the usage mirror rehydrates across panel restarts without losing accumulated totals. AgentBootID + LastTotalBytes are the P4 cumulative-counter watermark: the reporting agent process epoch and the last cumulative total seen for it; TrafficUsedBytes is the panel-accumulated absolute.
type ConfigApplyBatchRecord ¶
type ConfigApplyBatchRecord struct {
ID string
FleetGroupID string
Mode string
WaveSize int
ExpectedRevision string
Status string
CreatedAt time.Time
UpdatedAt time.Time
}
ConfigApplyBatchRecord tracks one group-wide config-apply rollout: the operator triggers a config push to every agent in FleetGroupID, and the batch coordinates delivery across one or more waves (see ConfigApplyBatchTargetRecord.WaveIndex) depending on Mode. ExpectedRevision pins the agent_config_targets revision this batch is rolling out, so a concurrent edit to the group's desired config cannot silently change what an in-flight batch delivers.
type ConfigApplyBatchStore ¶
type ConfigApplyBatchStore interface {
// CreateConfigApplyBatch inserts a batch row and its full target set
// atomically: either every row lands or none does. Implementations
// MUST run this inside a single transaction.
CreateConfigApplyBatch(ctx context.Context, b ConfigApplyBatchRecord, targets []ConfigApplyBatchTargetRecord) error
// GetConfigApplyBatch returns the batch plus every target row, ordered
// by wave_index then agent_id. Returns ErrNotFound when no batch with
// the given id exists.
GetConfigApplyBatch(ctx context.Context, id string) (ConfigApplyBatchRecord, []ConfigApplyBatchTargetRecord, error)
// ListRunningConfigApplyBatches returns every batch whose status is
// ConfigApplyBatchStatusRunning. Used by the coordinator to resume
// in-flight rollouts after a restart.
ListRunningConfigApplyBatches(ctx context.Context) ([]ConfigApplyBatchRecord, error)
// ActiveConfigApplyBatchForGroup returns the running batch for a fleet
// group, if any. The bool is false (with a zero-value record) when the
// group has no batch in ConfigApplyBatchStatusRunning.
ActiveConfigApplyBatchForGroup(ctx context.Context, fleetGroupID string) (ConfigApplyBatchRecord, bool, error)
// UpdateConfigApplyBatchStatus transitions a batch's status and bumps
// updated_at. Returns ErrNotFound when no batch with the given id exists.
UpdateConfigApplyBatchStatus(ctx context.Context, id, status string, now time.Time) error
// SetConfigApplyBatchTargetJob records the job enqueued for one target
// (wave enqueue) and updates its status in the same write.
SetConfigApplyBatchTargetJob(ctx context.Context, batchID, agentID, jobID, status string) error
// UpdateConfigApplyBatchTargetStatus updates one target's delivery
// status and message without touching its job id. message is typically
// the agent's own failure text (empty for succeeded/skipped targets) and
// is persisted so it survives eviction of the underlying job from the
// in-memory jobs store, keeping the batch-status view resumable.
UpdateConfigApplyBatchTargetStatus(ctx context.Context, batchID, agentID, status, message string) error
// PruneConfigApplyBatches deletes batches in a terminal status
// (succeeded/failed/halted) whose updated_at predates the cutoff.
// Targets are removed via ON DELETE CASCADE. Returns the number of
// batches deleted.
PruneConfigApplyBatches(ctx context.Context, before time.Time) (int64, error)
}
ConfigApplyBatchStore persists group-wide config-apply rollout batches and their per-agent target rows (see ConfigApplyBatchRecord / ConfigApplyBatchTargetRecord).
type ConfigApplyBatchTargetRecord ¶
type ConfigApplyBatchTargetRecord struct {
BatchID string
AgentID string
WaveIndex int
JobID string
Status string
Message string
}
ConfigApplyBatchTargetRecord is one agent's delivery record within a ConfigApplyBatchRecord. JobID is "" until the target's wave is enqueued (SetConfigApplyBatchTargetJob populates it alongside the job-queued status transition). Message carries the terminal outcome's human-readable reason (e.g. the agent's own failure text) and is persisted so it survives eviction of the underlying job from the in-memory jobs store — see UpdateConfigApplyBatchTargetStatus. Empty for non-terminal targets and for successful ones.
type ConsumedTotpRecord ¶
ConsumedTotpRecord stores one already-consumed TOTP code for replay prevention (Q2.U-S-17). The persistence layer keeps the code only long enough to bridge the verifier acceptance window (90s) so a CP restart cannot let an in-flight code be re-used.
type ConsumedTotpStore ¶
type ConsumedTotpStore interface {
UpsertConsumedTotp(ctx context.Context, record ConsumedTotpRecord) error
ListConsumedTotp(ctx context.Context) ([]ConsumedTotpRecord, error)
DeleteExpiredConsumedTotp(ctx context.Context, before time.Time) error
}
ConsumedTotpStore persists already-consumed TOTP codes for replay prevention across restarts (Q2.U-S-17). Implementations are expected to GC rows older than the verifier acceptance window via DeleteExpiredConsumedTotp; the auth service runs that GC alongside session cleanup.
type DCHealthPointRecord ¶
type DCHealthPointRecord struct {
AgentID string
CapturedAt time.Time
DC int
CoveragePctAvg float64
CoveragePctMin float64
RTTMsAvg float64
RTTMsMax float64
AliveWritersMin int
RequiredWriters int
LoadMax int
SampleCount int
}
DCHealthPointRecord stores one aggregated DC health snapshot.
type DiscoveredClientRecord ¶
type DiscoveredClientRecord struct {
ID string
AgentID string
ClientName string
Secret string
Status string
TotalOctets uint64
CurrentConnections int
ActiveUniqueIPs int
ConnectionLinks []string
MaxTCPConns int
MaxUniqueIPs int
DataQuotaBytes int64
Expiration string
DiscoveredAt time.Time
UpdatedAt time.Time
}
DiscoveredClientRecord stores one Telemt user found on an agent that is not managed by the panel.
type EnrollmentStore ¶
type EnrollmentStore interface {
PutEnrollmentToken(ctx context.Context, token EnrollmentTokenRecord) error
ListEnrollmentTokens(ctx context.Context) ([]EnrollmentTokenRecord, error)
GetEnrollmentToken(ctx context.Context, value string) (EnrollmentTokenRecord, error)
ConsumeEnrollmentToken(ctx context.Context, value string, consumedAt time.Time) (EnrollmentTokenRecord, error)
RevokeEnrollmentToken(ctx context.Context, value string, revokedAt time.Time) (EnrollmentTokenRecord, error)
// PruneEnrollmentTokens deletes tokens that can never be consumed
// again: consumed or revoked before the cutoff, or expired before
// the cutoff while never consumed. Live tokens are never touched.
// Returns the number of deleted rows (C4).
PruneEnrollmentTokens(ctx context.Context, before time.Time) (int64, error)
}
EnrollmentStore persists one-time agent enrollment tokens.
type EnrollmentTokenRecord ¶
type EnrollmentTokenRecord struct {
Value string
FleetGroupID string
IssuedAt time.Time
ExpiresAt time.Time
ConsumedAt *time.Time
RevokedAt *time.Time
}
EnrollmentTokenRecord stores one enrollment token and its consumption state.
type FleetGroupIntegrationRecord ¶
type FleetGroupIntegrationRecord struct {
ID string
FleetGroupID string
Kind string
ProviderID *string
Config []byte
Enabled bool
CreatedAt time.Time
UpdatedAt time.Time
}
FleetGroupIntegrationRecord attaches one integration install to a fleet group. At most one row per (fleet_group_id, kind). ProviderID is nullable: some integrations embed their entire config inline and do not reference a shared provider.
type FleetGroupRecord ¶
type FleetGroupRecord struct {
ID string
Name string
Label string
Description string
CreatedAt time.Time
UpdatedAt time.Time
}
FleetGroupRecord stores one fleet group in the global control-plane namespace.
ID is a UUID assigned at creation and never changes. Name is an immutable human-readable slug (unique, used in URLs / CLI / logs). Label is a free-form display name the operator can edit. Description is free text — rendered on the detail page.
type FleetStore ¶
type FleetStore interface {
// PutFleetGroup upserts a fleet group by id. Used by migration/copy
// helpers and tests. HTTP-layer CRUD calls the explicit
// Create/Update/Delete methods below, which enforce uniqueness and
// bump updated_at.
PutFleetGroup(ctx context.Context, group FleetGroupRecord) error
CreateFleetGroup(ctx context.Context, group FleetGroupRecord) error
UpdateFleetGroup(ctx context.Context, group FleetGroupRecord) error
GetFleetGroup(ctx context.Context, id string) (FleetGroupRecord, error)
GetFleetGroupByName(ctx context.Context, name string) (FleetGroupRecord, error)
ListFleetGroups(ctx context.Context) ([]FleetGroupRecord, error)
// DeleteFleetGroup removes the row. Callers are responsible for
// reassigning or detaching dependents first (agents, enrollment
// tokens, client_assignments); the DB enforces FK integrity so a
// non-reassigned delete fails with a constraint error.
DeleteFleetGroup(ctx context.Context, id string) error
// ReassignFleetGroupMembers moves every FK reference to `fromID`
// (agents.fleet_group_id, enrollment_tokens.fleet_group_id,
// client_assignments.fleet_group_id) to `toID` in one transaction.
// Returns the number of rows touched per table for audit logging.
ReassignFleetGroupMembers(ctx context.Context, fromID, toID string) (ReassignCounts, error)
// CountFleetGroupMembers returns how many rows in each dependent
// table reference `fleetGroupID`. Powers the deletion-preview HTTP
// endpoint so the operator sees the blast radius before confirming.
CountFleetGroupMembers(ctx context.Context, fleetGroupID string) (ReassignCounts, error)
PutAgent(ctx context.Context, agent AgentRecord) error
// PutAgentsBulk upserts a batch of agents in a single transaction. Semantics
// match PutAgent per row (UPSERT on id); when the same ID appears twice the
// last occurrence wins. A nil/empty slice is a no-op that returns nil. Used
// by the control-plane batch writer (P3-PERF-01a) to avoid N individual
// INSERTs per flush. See also storage/postgres and storage/sqlite
// implementations which chunk large batches.
PutAgentsBulk(ctx context.Context, agents []AgentRecord) error
// TODO(cursor): add ListAgentsCursor next sprint. ListAgents is currently
// only called at boot and from the operator agents-page; the boot path is
// safe because the table is small, but the operator path can grow without
// bound on large fleets. S25 deferred this to keep T1 scope to the two
// worst offenders (jobs + audit) — see commit feat(server): cursor
// pagination for ListJobs and ListAuditEvents.
ListAgents(ctx context.Context) ([]AgentRecord, error)
// EarliestAgentCertExpiry returns the minimum cert_expires_at across
// all enrolled agents, or nil when no agent has one recorded
// (P6-6.3f, finding #14 — replaces the metrics poller's full
// ListAgents scan with a single-scalar MIN query).
EarliestAgentCertExpiry(ctx context.Context) (*time.Time, error)
DeleteAgent(ctx context.Context, agentID string) error
UpdateAgentNodeName(ctx context.Context, agentID string, nodeName string) error
// UpdateAgentFleetGroup reassigns the agent to a different fleet
// group. Empty fleetGroupID detaches the agent (NULL on the column).
// Returns ErrNotFound when the agent doesn't exist; FK-constraint
// errors surface verbatim so the HTTP layer can map them to 4xx.
UpdateAgentFleetGroup(ctx context.Context, agentID, fleetGroupID string) error
// UpdateAgentCertSerial pins the latest-issued client cert serial
// for the agent so the gRPC connect path can reject any cert that
// does not match (Q4.U-S-04).
UpdateAgentCertSerial(ctx context.Context, agentID string, serial string) error
// GetAgentCertSerial returns the pinned serial; "" means unpinned
// (legacy agent whose cert pre-dates the schema migration).
GetAgentCertSerial(ctx context.Context, agentID string) (string, error)
// UpdateAgentCertPin persists the SPKI SHA-256 hash for an agent. Set
// after first successful enroll; subsequent dials verify against this
// value. Empty pin means "not yet pinned" (S-02). Returns ErrNotFound
// if no agent with the given ID exists, matching the convention of
// every other Update* method on this interface.
UpdateAgentCertPin(ctx context.Context, agentID string, pin []byte) error
// GetAgentCertPin returns the SPKI pin previously stored via
// UpdateAgentCertPin. Returns ErrNotFound if no agent with the given
// ID exists; returns empty bytes (no error) if the agent exists but
// is not yet pinned.
GetAgentCertPin(ctx context.Context, agentID string) ([]byte, error)
// RotateAgentCert records a freshly issued agent credential (serial +
// SPKI pin) and keeps the PREVIOUS one valid until overlapUntil.
//
// Issuance used to overwrite the pin outright, but the agent only learns
// about its new certificate when the renewal response reaches it. An
// interrupted exchange therefore left the panel expecting the new
// credential while the agent still held the old one — and the fail-closed
// verifier refused it. An inbound agent could ask for a new signature and
// self-heal; a listen-mode node could not, and stayed stranded until an
// operator issued a recovery grant (R11 / R-1).
//
// The overlap is bounded in time and in size: at most two credentials are
// accepted at once, and the first connection presenting the new one closes
// the window (CloseAgentCertOverlap). Outside that pair the verifier is
// fail-closed exactly as before.
//
// presentedSerial is the serial of the certificate the agent authenticated
// with on the connection that carried this renewal ("" when no presented
// credential exists — enrollment, recovery). When an overlap window is
// already OPEN and the presenter is the PREVIOUS credential, the agent has
// just proven it never took delivery of the current one: shifting
// prev := current would evict the only certificate the agent holds from
// the accepted set (two consecutive lost renewal responses then strand a
// listen-mode node — R11-1). In that case only the current credential
// moves; prev and its overlap deadline stay untouched, so the credential
// the agent demonstrably holds stays accepted until the original window
// closes.
RotateAgentCert(ctx context.Context, agentID string, serial string, spki []byte, overlapUntil time.Time, presentedSerial string) error
// GetAgentCertPins returns the credentials the panel accepts for the agent:
// the current one, plus the previous one while the overlap window is open.
GetAgentCertPins(ctx context.Context, agentID string) (AgentCertPins, error)
// CloseAgentCertOverlap drops the previous credential. Called the first
// time an agent connects presenting the CURRENT one — proof that it has
// taken delivery of the new certificate.
CloseAgentCertOverlap(ctx context.Context, agentID string) error
// UpdateAgentTransportMode changes the agent's transport_mode and
// dial_address. dialAddress is empty when switching to inbound mode.
// Returns ErrNotFound when the agent doesn't exist.
UpdateAgentTransportMode(ctx context.Context, agentID, transportMode, dialAddress string) error
PutInstance(ctx context.Context, instance InstanceRecord) error
// PutInstancesBulk upserts a batch of Telemt instances in a single
// transaction. Same semantics as PutInstance per row; empty slice is a
// no-op. See P3-PERF-01a.
PutInstancesBulk(ctx context.Context, instances []InstanceRecord) error
// TODO(cursor): add ListInstancesCursor next sprint. Same rationale as
// ListAgents above — deferred from S25 T1 to bound the change footprint.
ListInstances(ctx context.Context) ([]InstanceRecord, error)
DeleteInstancesByAgent(ctx context.Context, agentID string) error
// GetAgentConfigTarget returns the desired config for one scope, or
// storage.ErrNotFound when none is set.
GetAgentConfigTarget(ctx context.Context, scopeType, scopeID string) (AgentConfigTargetRecord, error)
// ListAgentConfigTargets returns all config targets (group + agent scopes).
ListAgentConfigTargets(ctx context.Context) ([]AgentConfigTargetRecord, error)
// UpsertAgentConfigTarget creates or replaces the config target for a scope.
UpsertAgentConfigTarget(ctx context.Context, rec AgentConfigTargetRecord) error
// DeleteAgentConfigTarget removes the config target for a scope; returns rows deleted.
DeleteAgentConfigTarget(ctx context.Context, scopeType, scopeID string) (int64, error)
// GetAgentUpdateStrategy returns the persisted Telemt update strategy for
// one agent, or storage.ErrNotFound when none is set.
GetAgentUpdateStrategy(ctx context.Context, agentID string) (AgentUpdateStrategyRecord, error)
// UpsertAgentUpdateStrategy creates or replaces the update strategy for an
// agent. CreatedAt is preserved across replaces of an existing row.
UpsertAgentUpdateStrategy(ctx context.Context, rec AgentUpdateStrategyRecord) error
// DeleteAgentUpdateStrategy removes the update strategy for an agent.
// Idempotent: deleting an absent row is not an error.
DeleteAgentUpdateStrategy(ctx context.Context, agentID string) error
}
FleetStore persists fleet topology and discovered Telemt runtime state.
type InstanceRecord ¶
type InstanceRecord struct {
ID string
AgentID string
Name string
Version string
ConfigFingerprint string
Connections int
ReadOnly bool
UpdatedAt time.Time
}
InstanceRecord stores one Telemt runtime observed through an agent.
type IntegrationProviderRecord ¶
type IntegrationProviderRecord struct {
ID string
Kind string
Label string
Config []byte
CreatedAt time.Time
UpdatedAt time.Time
}
IntegrationProviderRecord stores credentials for an external integration backend (e.g. a Cloudflare account). A single provider can back FleetGroupIntegrationRecord rows across many groups. Config is opaque JSON — the shape is owned by the integration implementation and validated at install time.
type IntegrationStore ¶
type IntegrationStore interface {
CreateIntegrationProvider(ctx context.Context, provider IntegrationProviderRecord) error
UpdateIntegrationProvider(ctx context.Context, provider IntegrationProviderRecord) error
GetIntegrationProvider(ctx context.Context, id string) (IntegrationProviderRecord, error)
ListIntegrationProviders(ctx context.Context) ([]IntegrationProviderRecord, error)
ListIntegrationProvidersByKind(ctx context.Context, kind string) ([]IntegrationProviderRecord, error)
DeleteIntegrationProvider(ctx context.Context, id string) error
CreateFleetGroupIntegration(ctx context.Context, integration FleetGroupIntegrationRecord) error
UpdateFleetGroupIntegration(ctx context.Context, integration FleetGroupIntegrationRecord) error
GetFleetGroupIntegration(ctx context.Context, id string) (FleetGroupIntegrationRecord, error)
ListFleetGroupIntegrations(ctx context.Context, fleetGroupID string) ([]FleetGroupIntegrationRecord, error)
DeleteFleetGroupIntegration(ctx context.Context, id string) error
}
IntegrationStore persists the integration-scaffolding entities: shared provider credentials and per-fleet-group integration installs. The store itself is kind-agnostic — config is an opaque JSON blob. Validation, reconciliation, and kind-specific semantics live in the fleet/integrations registry.
type JobRecord ¶
type JobRecord struct {
ID string
Action string
ActorID string
Status string
CreatedAt time.Time
TTL time.Duration
IdempotencyKey string
PayloadJSON string
}
JobRecord stores one orchestration job.
type JobStore ¶
type JobStore interface {
PutJob(ctx context.Context, job JobRecord) error
// GetJob returns a single job row by primary key, or ErrNotFound.
// P8.1 (audit #24): backs jobs.Service.GetWithContext — terminal jobs
// evicted from the in-memory maps are read back from here until the
// retention worker deletes them (PruneTerminalJobs).
GetJob(ctx context.Context, id string) (JobRecord, error)
// ListJobs returns every job. S25 T1: capped defensively at
// DefaultListLimit by the SQL backends so a long-lived store cannot
// stream millions of rows even if a caller forgets to paginate. New
// callers should use ListJobsCursor (keyset pagination) instead.
ListJobs(ctx context.Context) ([]JobRecord, error)
// ListJobsCursor returns one page of jobs in (created_at DESC, id DESC)
// order. The returned next cursor is non-empty iff a further page may
// exist; callers thread it back through params.AfterCreatedAt /
// params.AfterID. Limit follows DefaultCursorPageSize / MaxCursorPageSize.
ListJobsCursor(ctx context.Context, params ListJobsCursorParams) ([]JobRecord, ListJobsCursorParams, error)
PutJobTarget(ctx context.Context, target JobTargetRecord) error
ListJobTargets(ctx context.Context, jobID string) ([]JobTargetRecord, error)
// ListAllJobTargets returns every job_targets row in one round-trip.
// Used by Service.restore() to avoid the per-job N+1 SELECT pattern.
// Empty result is fine — callers must not assume the slice is non-nil.
ListAllJobTargets(ctx context.Context) ([]JobTargetRecord, error)
// PruneTerminalJobs deletes jobs in succeeded/failed/expired status
// whose created_at predates the cutoff (Q2.U-P-02). Returns the
// number of rows deleted; the bound also cascades to job_targets via
// ON DELETE CASCADE in the schema.
PruneTerminalJobs(ctx context.Context, before time.Time) (int64, error)
}
JobStore persists orchestration jobs and per-target result state.
type JobTargetRecord ¶
type JobTargetRecord struct {
JobID string
AgentID string
Status string
ResultText string
ResultJSON string
UpdatedAt time.Time
}
JobTargetRecord stores delivery and result state for one job target.
type ListAuditEventsCursorParams ¶
ListAuditEventsCursorParams selects a single page of audit events in (created_at DESC, id DESC) order. Same cursor + limit semantics as ListJobsCursorParams.
type ListJobsCursorParams ¶
ListJobsCursorParams selects a single page of jobs in (created_at DESC, id DESC) order. AfterCreatedAt + AfterID is the keyset cursor — leave both zero for the first page. Limit <= 0 falls back to DefaultCursorPageSize and values above MaxCursorPageSize are clamped.
type LoginLockoutRecord ¶
type LoginLockoutRecord struct {
Username string
Failures int
LockedAt *time.Time
UpdatedAt time.Time
}
LoginLockoutRecord stores the persistent login-failure state for one account (S7). Failures accumulates until the lockout threshold is reached; at that point LockedAt is set to the wall-clock time the lockout began. A nil LockedAt means "not currently locked". Username is the raw account name as submitted to /auth/login so the auth service can still match it after a restart — the service normalises to lower-case before lookup.
type LoginLockoutStore ¶
type LoginLockoutStore interface {
UpsertLoginLockout(ctx context.Context, record LoginLockoutRecord) error
GetLoginLockout(ctx context.Context, username string) (LoginLockoutRecord, error)
DeleteLoginLockout(ctx context.Context, username string) error
ListLoginLockouts(ctx context.Context) ([]LoginLockoutRecord, error)
DeleteExpiredLoginLockouts(ctx context.Context, before time.Time) (int64, error)
}
LoginLockoutStore persists per-account login-failure state so a control-plane restart or fail-over cannot reset the lockout counter (S7). See LoginLockoutRecord.
type MetricSnapshotRecord ¶
type MetricSnapshotRecord struct {
ID string
AgentID string
InstanceID string
CapturedAt time.Time
Values map[string]uint64
}
MetricSnapshotRecord stores one aggregated metric capture.
type MetricStore ¶
type MetricStore interface {
AppendMetricSnapshot(ctx context.Context, snapshot MetricSnapshotRecord) error
// AppendMetricSnapshotsBulk inserts a batch of metric snapshots in a
// single transaction. Empty slice is a no-op. See P3-PERF-01a.
AppendMetricSnapshotsBulk(ctx context.Context, snapshots []MetricSnapshotRecord) error
// TODO(cursor): add ListMetricSnapshotsCursor next sprint. The current
// implementations cap the result at 512 rows internally so this is not
// catastrophically unbounded, but operators viewing more than 512 most-
// recent snapshots silently lose history. Deferred from S25 T1.
ListMetricSnapshots(ctx context.Context) ([]MetricSnapshotRecord, error)
// PruneMetricSnapshots deletes metric_snapshots rows with captured_at
// strictly before the cutoff and returns the number of deleted rows.
// Used by the retention worker (P2-REL-05).
PruneMetricSnapshots(ctx context.Context, before time.Time) (int64, error)
}
MetricStore persists aggregated control-plane metric snapshots.
type MigrationStore ¶
type MigrationStore interface {
Store
ClientStore
}
MigrationStore is the full storage surface required by the migrate-schema CLI subcommand and storagetest Transact contract tests. It composes Store with the legacy row-level client interface so migration code can iterate every table without needing to assemble per-domain Repositories. discovered_clients is copied as raw rows (no typed method), so no discovered-client interface is embedded here.
Production code MUST NOT accept or return MigrationStore — it exists only for offline migration tooling and low-level storage contract tests. Both SQLite and Postgres concrete stores satisfy this interface.
type PanelSettingsRecord ¶
type PanelSettingsRecord struct {
HTTPPublicURL string
GRPCPublicEndpoint string
// PasswordMinLength is the operator-configured minimum password length.
// Zero is sentinel for "not configured" — callers should treat it as
// the compiled-in default (auth.DefaultPasswordMinLength).
PasswordMinLength int32
UpdatedAt time.Time
}
PanelSettingsRecord stores operator-managed public access settings for the panel.
type PanelSettingsStore ¶
type PanelSettingsStore interface {
PutPanelSettings(ctx context.Context, settings PanelSettingsRecord) error
GetPanelSettings(ctx context.Context) (PanelSettingsRecord, error)
}
PanelSettingsStore persists operator-managed panel network and TLS settings.
type ReassignCounts ¶
ReassignCounts summarises how many FK references to a fleet group exist (or were moved, depending on the method). Used by the deletion-preview endpoint and the reassignment audit entry.
type RetentionSettings ¶
type RetentionSettings = RetentionSettingsRecord
RetentionSettings is the storage-layer alias used across the Store interface. Callers in the control-plane server wrap it with their own typed RetentionSettings struct; at the storage boundary this alias keeps the interface decoupled from server internals while reusing the same field layout (see RetentionSettingsRecord).
type RetentionSettingsRecord ¶
type RetentionSettingsRecord struct {
TSRawSeconds int `json:"ts_raw_seconds"`
TSHourlySeconds int `json:"ts_hourly_seconds"`
TSDCSeconds int `json:"ts_dc_seconds"`
IPHistorySeconds int `json:"ip_history_seconds"`
EventSeconds int `json:"event_history_seconds"`
AuditEventSeconds int `json:"audit_event_seconds"`
MetricSnapshotSeconds int `json:"metric_snapshot_seconds"`
// JobsSeconds bounds how long terminal jobs (succeeded/failed/
// expired) live in the jobs table before the rollup loop deletes
// them via PruneTerminalJobs (Q2.U-P-02). Zero disables job
// pruning so existing dev fixtures keep their full history.
JobsSeconds int `json:"jobs_seconds"`
// WebhookOutboxSeconds bounds how long terminal webhook_outbox rows
// (delivered or dead) are kept for operator audit before the rollup
// loop prunes them via webhooks.Storage.PruneOutbox (C4).
WebhookOutboxSeconds int `json:"webhook_outbox_seconds"`
// EnrollmentTokenSeconds bounds how long dead enrollment tokens
// (consumed, revoked, or expired-unconsumed) are kept for operator
// forensics before the rollup loop prunes them via
// PruneEnrollmentTokens (C4).
EnrollmentTokenSeconds int `json:"enrollment_token_seconds"`
// ConfigApplyBatchSeconds bounds how long terminal group config-apply
// batches (succeeded/failed/halted) and their targets live before the
// rollup loop deletes them via PruneConfigApplyBatches (Phase A / A5).
// Zero disables the prune, matching JobsSeconds's zero-disables
// convention, so existing dev fixtures keep their full history.
ConfigApplyBatchSeconds int `json:"config_apply_batch_seconds"`
}
RetentionSettingsRecord stores operator-managed timeseries/event retention windows. Persisted as an opaque JSON blob in panel_settings.retention_json so adding new retention knobs never needs another migration.
type RetentionSettingsStore ¶
type RetentionSettingsStore interface {
GetRetentionSettings(ctx context.Context) (RetentionSettings, error)
PutRetentionSettings(ctx context.Context, settings RetentionSettings) error
}
RetentionSettingsStore persists operator-managed retention windows for timeseries data, runtime events, and client IP history. Returns ErrNotFound when no row has been written yet so the caller can fall back to its own defaults.
type ServerLoadHourlyRecord ¶
type ServerLoadHourlyRecord struct {
AgentID string
BucketHour time.Time
CPUPctAvg float64
CPUPctMax float64
MemPctAvg float64
MemPctMax float64
ConnectionsAvg float64
ConnectionsMax int
ActiveUsersAvg float64
ActiveUsersMax int
DCCoverageMin float64
DCCoverageAvg float64
SampleCount int
}
ServerLoadHourlyRecord stores one hourly rollup of server load metrics.
type ServerLoadPointRecord ¶
type ServerLoadPointRecord struct {
AgentID string
CapturedAt time.Time
CPUPctAvg float64
CPUPctMax float64
MemPctAvg float64
MemPctMax float64
DiskPctAvg float64
DiskPctMax float64
Load1M float64
Load5M float64
Load15M float64
ConnectionsAvg int
ConnectionsMax int
ConnectionsMEAvg int
ConnectionsDirectAvg int
ActiveUsersAvg int
ActiveUsersMax int
ConnectionsTotal uint64
ConnectionsBadTotal uint64
HandshakeTimeoutsTotal uint64
DCCoverageMinPct float64
DCCoverageAvgPct float64
HealthyUpstreams int
TotalUpstreams int
NetBytesSent uint64
NetBytesRecv uint64
SampleCount int
}
ServerLoadPointRecord stores one aggregated runtime snapshot for timeseries.
type SessionRecord ¶
type SessionRecord struct {
ID string
UserID string
CreatedAt time.Time
// LastSeenAt is the persisted sliding-refresh timestamp (Q2.U-S-12).
// Updated by SessionStore.TouchSession at most every
// sessionTouchThrottle so the idle-timeout survives a restart
// without thrashing the store on every authenticated request.
LastSeenAt time.Time
}
SessionRecord stores one authenticated user session.
type SessionStore ¶
type SessionStore interface {
PutSession(ctx context.Context, session SessionRecord) error
GetSession(ctx context.Context, sessionID string) (SessionRecord, error)
DeleteSession(ctx context.Context, sessionID string) error
ListSessions(ctx context.Context) ([]SessionRecord, error)
DeleteExpiredSessions(ctx context.Context, before time.Time) error
// TouchSession persists a refreshed LastSeenAt so the sliding idle
// timeout survives a control-plane restart (Q2.U-S-12). Implementations
// must update only the last_seen_at column to avoid contention on
// the rest of the row.
TouchSession(ctx context.Context, sessionID string, lastSeenAt time.Time) error
}
SessionStore persists authenticated user sessions.
type Store ¶
type Store interface {
UserStore
UserFleetGroupScopeStore
UserAppearanceStore
SessionStore
CPSecretStore
ConsumedTotpStore
LoginLockoutStore
AgentRevocationStore
AgentFallbackStateStore
FleetStore
JobStore
AuditStore
MetricStore
TelemetryStore
EnrollmentStore
AgentCertificateRecoveryGrantStore
PanelSettingsStore
RetentionSettingsStore
UpdateConfigStore
CertificateAuthorityStore
TimeseriesStore
IntegrationStore
ConfigApplyBatchStore
// Transact runs fn inside a single database transaction. The tx
// argument is a Store implementation bound to the transaction:
// all mutations performed through it either commit as a unit or
// roll back together.
//
// Contract:
// - On fn returning nil, the transaction commits.
// - On fn returning a non-nil error, the transaction rolls back
// and the error is returned to the caller.
// - On panic inside fn, the transaction rolls back and the panic
// is re-raised.
// - Context cancellation during fn aborts the transaction.
// - PostgreSQL: serialization failures (SQLSTATE 40001) are
// retried up to 3 times automatically. Default isolation is
// read-committed.
// - SQLite: uses BEGIN IMMEDIATE so the writer lock is acquired
// up front. No retry loop (single-writer semantics).
// - TxFn MUST NOT call tx.Transact; nested calls return
// ErrNestedTransact immediately.
Transact(ctx context.Context, fn TxFn) error
// Ping verifies that the database connection is alive.
Ping(ctx context.Context) error
Close() error
}
Store aggregates the persistence capabilities required by the control-plane.
type TelemetryDiagnosticsCurrentRecord ¶
type TelemetryDiagnosticsCurrentRecord struct {
AgentID string
ObservedAt time.Time
State string
StateReason string
SystemInfoJSON string
EffectiveLimitsJSON string
SecurityPostureJSON string
MinimalAllJSON string
MEPoolJSON string
DcsJSON string
}
TelemetryDiagnosticsCurrentRecord stores the latest slower diagnostics payloads for one node.
type TelemetryRuntimeCurrentRecord ¶
type TelemetryRuntimeCurrentRecord struct {
AgentID string
ObservedAt time.Time
RuntimeJSON string
}
TelemetryRuntimeCurrentRecord stores one node's latest Telemt runtime summary as a canonical JSON blob (P3-3.1, audit #3). RuntimeJSON is the whole json.Marshal(server.AgentRuntime); the storage layer treats it as an opaque string and must return it byte-for-byte. ObservedAt is stored in a separate column ONLY for ORDER BY in List and for diagnostics; on read the server overwrites the runtime's updated_at from this column (the single source of truth for the clock — see P3-3.2).
type TelemetryRuntimeDCRecord ¶
type TelemetryRuntimeDCRecord struct {
AgentID string
DC int
ObservedAt time.Time
AvailableEndpoints int
AvailablePct float64
RequiredWriters int
AliveWriters int
CoveragePct float64
RTTMs float64
Load float64
}
TelemetryRuntimeDCRecord stores one node's latest DC health row.
type TelemetryRuntimeEventRecord ¶
type TelemetryRuntimeEventRecord struct {
AgentID string
Sequence int64
ObservedAt time.Time
Timestamp time.Time
EventType string
Context string
Severity string
}
TelemetryRuntimeEventRecord stores one recent runtime event observed for a node.
type TelemetryRuntimeUpstreamRecord ¶
type TelemetryRuntimeUpstreamRecord struct {
AgentID string
UpstreamID int
ObservedAt time.Time
RouteKind string
Address string
Healthy bool
Fails int
EffectiveLatencyMs float64
}
TelemetryRuntimeUpstreamRecord stores one node's latest upstream health row.
type TelemetrySecurityInventoryCurrentRecord ¶
type TelemetrySecurityInventoryCurrentRecord struct {
AgentID string
ObservedAt time.Time
State string
StateReason string
Enabled bool
EntriesTotal int
EntriesJSON string
}
TelemetrySecurityInventoryCurrentRecord stores the latest security inventory payload for one node.
type TelemetryStore ¶
type TelemetryStore interface {
PutTelemetryRuntimeCurrent(ctx context.Context, record TelemetryRuntimeCurrentRecord) error
GetTelemetryRuntimeCurrent(ctx context.Context, agentID string) (TelemetryRuntimeCurrentRecord, error)
ListTelemetryRuntimeCurrent(ctx context.Context) ([]TelemetryRuntimeCurrentRecord, error)
ReplaceTelemetryRuntimeDCs(ctx context.Context, agentID string, records []TelemetryRuntimeDCRecord) error
ListTelemetryRuntimeDCs(ctx context.Context, agentID string) ([]TelemetryRuntimeDCRecord, error)
// ListAllTelemetryRuntimeDCs returns DC rows for every agent in one
// query. Cold-start rehydration groups the result by agent_id in
// memory instead of issuing one query per agent (A2).
ListAllTelemetryRuntimeDCs(ctx context.Context) ([]TelemetryRuntimeDCRecord, error)
ReplaceTelemetryRuntimeUpstreams(ctx context.Context, agentID string, records []TelemetryRuntimeUpstreamRecord) error
ListTelemetryRuntimeUpstreams(ctx context.Context, agentID string) ([]TelemetryRuntimeUpstreamRecord, error)
// ListAllTelemetryRuntimeUpstreams returns upstream rows for every
// agent in one query (A2 cold-start rehydration).
ListAllTelemetryRuntimeUpstreams(ctx context.Context) ([]TelemetryRuntimeUpstreamRecord, error)
AppendTelemetryRuntimeEvents(ctx context.Context, agentID string, records []TelemetryRuntimeEventRecord) error
ListTelemetryRuntimeEvents(ctx context.Context, agentID string, limit int) ([]TelemetryRuntimeEventRecord, error)
// ListAllTelemetryRuntimeEventsPerAgent returns the most recent
// perAgentLimit events PER agent (a windowed query, NOT a global
// limit) for every agent in one round-trip (A2 cold-start
// rehydration). When perAgentLimit <= 0 all events are returned.
ListAllTelemetryRuntimeEventsPerAgent(ctx context.Context, perAgentLimit int) ([]TelemetryRuntimeEventRecord, error)
PruneTelemetryRuntimeEvents(ctx context.Context, olderThan time.Time) (int64, error)
PutTelemetryDiagnosticsCurrent(ctx context.Context, record TelemetryDiagnosticsCurrentRecord) error
GetTelemetryDiagnosticsCurrent(ctx context.Context, agentID string) (TelemetryDiagnosticsCurrentRecord, error)
PutTelemetrySecurityInventoryCurrent(ctx context.Context, record TelemetrySecurityInventoryCurrentRecord) error
GetTelemetrySecurityInventoryCurrent(ctx context.Context, agentID string) (TelemetrySecurityInventoryCurrentRecord, error)
// PutTelemetryRuntimeCurrentBulk upserts a batch of runtime JSON blobs
// in a single transaction (P6-6.1a, finding #10). Per-row semantics match
// PutTelemetryRuntimeCurrent; duplicate AgentIDs inside one batch
// collapse to last-wins on BOTH backends (Postgres cannot upsert the
// same conflict key twice in one statement, so implementations dedup
// before inserting).
PutTelemetryRuntimeCurrentBulk(ctx context.Context, records []TelemetryRuntimeCurrentRecord) error
// ReplaceTelemetryRuntimeDCsBulk applies the ReplaceTelemetryRuntimeDCs
// semantics for many agents in ONE transaction: every agent key present
// in byAgent has its DC rows deleted and re-inserted from the mapped
// slice (an empty slice clears the agent's rows). Agents absent from the
// map are untouched.
ReplaceTelemetryRuntimeDCsBulk(ctx context.Context, byAgent map[string][]TelemetryRuntimeDCRecord) error
// ReplaceTelemetryRuntimeUpstreamsBulk: see ReplaceTelemetryRuntimeDCsBulk.
ReplaceTelemetryRuntimeUpstreamsBulk(ctx context.Context, byAgent map[string][]TelemetryRuntimeUpstreamRecord) error
// AppendTelemetryRuntimeEventsBulk inserts/upserts runtime events for
// MANY agents in one transaction. Records carry their own AgentID.
// Conflict target (agent_id, sequence) matches the single-agent
// AppendTelemetryRuntimeEvents; duplicates within one batch collapse
// to last-wins.
AppendTelemetryRuntimeEventsBulk(ctx context.Context, records []TelemetryRuntimeEventRecord) error
// PutTelemetryDiagnosticsCurrentBulk upserts a batch of diagnostics
// snapshots in one transaction. Duplicate AgentIDs collapse last-wins.
PutTelemetryDiagnosticsCurrentBulk(ctx context.Context, records []TelemetryDiagnosticsCurrentRecord) error
// PutTelemetrySecurityInventoryCurrentBulk upserts a batch of security
// inventory snapshots in one transaction. Duplicate AgentIDs collapse
// last-wins.
PutTelemetrySecurityInventoryCurrentBulk(ctx context.Context, records []TelemetrySecurityInventoryCurrentRecord) error
}
TelemetryStore persists current Telemt telemetry projections and recent runtime events.
type TimeseriesStore ¶
type TimeseriesStore interface {
AppendServerLoadPoint(ctx context.Context, record ServerLoadPointRecord) error
// AppendServerLoadPointsBulk inserts a batch of server-load points in a
// single transaction. Same ON-CONFLICT DO NOTHING semantics as the
// single-row variant. Empty slice is a no-op. See P3-PERF-01a.
AppendServerLoadPointsBulk(ctx context.Context, records []ServerLoadPointRecord) error
ListServerLoadPoints(ctx context.Context, agentID string, from time.Time, to time.Time) ([]ServerLoadPointRecord, error)
PruneServerLoadPoints(ctx context.Context, olderThan time.Time) (int64, error)
AppendDCHealthPoint(ctx context.Context, record DCHealthPointRecord) error
// AppendDCHealthPointsBulk inserts a batch of DC-health points in a
// single transaction. Same ON-CONFLICT DO NOTHING semantics. Empty
// slice is a no-op. See P3-PERF-01a.
AppendDCHealthPointsBulk(ctx context.Context, records []DCHealthPointRecord) error
ListDCHealthPoints(ctx context.Context, agentID string, from time.Time, to time.Time) ([]DCHealthPointRecord, error)
PruneDCHealthPoints(ctx context.Context, olderThan time.Time) (int64, error)
UpsertClientIPHistory(ctx context.Context, record ClientIPHistoryRecord) error
// UpsertClientIPHistoryBulk upserts a batch of client-ip history rows in
// a single transaction. Same semantics as the single-row UPSERT
// (last_seen is updated on conflict). Empty slice is a no-op. See
// P3-PERF-01a.
UpsertClientIPHistoryBulk(ctx context.Context, records []ClientIPHistoryRecord) error
ListClientIPHistory(ctx context.Context, clientID string, from time.Time, to time.Time) ([]ClientIPHistoryRecord, error)
// AggregateClientIPHistory folds the per-(agent, ip) rows into one
// per IP using SQL GROUP BY: MIN(first_seen) and MAX(last_seen).
// Sorted last_seen DESC. Pushes the work into the database so the
// CP no longer holds the full raw set in memory just to collapse
// duplicates.
AggregateClientIPHistory(ctx context.Context, clientID string, from time.Time, to time.Time, limit int) ([]ClientIPAggregateRecord, error)
CountUniqueClientIPs(ctx context.Context, clientID string) (int, error)
// CountUniqueClientIPsForClients returns the unique-IP count for
// each client ID in a single SQL round-trip (Q2.U-P-03). The
// returned map only carries entries for client IDs with at least
// one history row; missing entries should be treated as zero.
// Empty input is a no-op that returns an empty map.
CountUniqueClientIPsForClients(ctx context.Context, clientIDs []string) (map[string]int, error)
// ListServerLoadPointsForAgents fetches the raw load points for a
// batch of agents in a single SQL round-trip (Q2.U-P-01). The
// per-agent slices stay sorted by captured_at ascending to match
// the single-agent shape. Missing agents return no key.
ListServerLoadPointsForAgents(ctx context.Context, agentIDs []string, from time.Time, to time.Time) (map[string][]ServerLoadPointRecord, error)
PruneClientIPHistory(ctx context.Context, olderThan time.Time) (int64, error)
RollupServerLoadHourly(ctx context.Context, bucketHour time.Time) error
ListServerLoadHourly(ctx context.Context, agentID string, from time.Time, to time.Time) ([]ServerLoadHourlyRecord, error)
PruneServerLoadHourly(ctx context.Context, olderThan time.Time) (int64, error)
}
TimeseriesStore persists historical metric points for server load, DC health, and client IPs.
type TxFn ¶
TxFn is the callback invoked by Store.Transact. The tx argument implements the full Store interface so that existing methods compose without duplication — see P2-ARCH-01.
NOTE: TxFn MUST NOT call tx.Transact recursively. Nested Transact calls on the same connection would deadlock (SQLite) or escalate isolation requirements unpredictably (PostgreSQL). Both backends detect the nested call and return ErrNestedTransact.
type UpdateConfigStore ¶
type UpdateConfigStore interface {
PutUpdateSettings(ctx context.Context, settings json.RawMessage) error
GetUpdateSettings(ctx context.Context) (json.RawMessage, error)
PutUpdateState(ctx context.Context, state json.RawMessage) error
GetUpdateState(ctx context.Context) (json.RawMessage, error)
// PutPanelSelfUpdate and GetPanelSelfUpdate persist the panel
// self-update phase (updates.SelfUpdateState) under its own
// update_config key, independent of the settings/state keys above.
PutPanelSelfUpdate(ctx context.Context, raw json.RawMessage) error
GetPanelSelfUpdate(ctx context.Context) (json.RawMessage, error)
// PutPendingAgentUpdates and GetPendingAgentUpdates persist the
// agent-ID -> requested-version map behind the reconcile-on-reconnect
// of operator-requested agent self-updates, under its own
// update_config key.
PutPendingAgentUpdates(ctx context.Context, raw json.RawMessage) error
GetPendingAgentUpdates(ctx context.Context) (json.RawMessage, error)
// PutPendingTelemtUpdates and GetPendingTelemtUpdates mirror
// PutPendingAgentUpdates/GetPendingAgentUpdates for telemt.update jobs
// (Task 11), under their own update_config key.
PutPendingTelemtUpdates(ctx context.Context, raw json.RawMessage) error
GetPendingTelemtUpdates(ctx context.Context) (json.RawMessage, error)
PutGeoIPSettings(ctx context.Context, settings json.RawMessage) error
GetGeoIPSettings(ctx context.Context) (json.RawMessage, error)
PutGeoIPState(ctx context.Context, state json.RawMessage) error
GetGeoIPState(ctx context.Context) (json.RawMessage, error)
}
UpdateConfigStore persists update settings and cached version state as opaque JSON blobs.
type UserAppearanceRecord ¶
type UserAppearanceRecord struct {
UserID string
Theme string
Density string
HelpMode string
UpdatedAt time.Time
}
UserAppearanceRecord stores one user's persisted appearance preferences.
type UserAppearanceStore ¶
type UserAppearanceStore interface {
PutUserAppearance(ctx context.Context, appearance UserAppearanceRecord) error
GetUserAppearance(ctx context.Context, userID string) (UserAppearanceRecord, error)
ListUserAppearances(ctx context.Context) ([]UserAppearanceRecord, error)
}
UserAppearanceStore persists per-user appearance preferences.
type UserFleetGroupScopeRecord ¶
type UserFleetGroupScopeRecord struct {
UserID string
FleetGroupID string
GrantedBy string
GrantedAt time.Time
}
UserFleetGroupScopeRecord is one (user, fleet_group) scope grant with its full provenance. The runtime ListUserFleetGroupScopes returns only the fleet-group ids; this richer record exists for the offline migrate tooling so granted_by / granted_at survive the copy (audit fidelity).
type UserFleetGroupScopeStore ¶
type UserFleetGroupScopeStore interface {
// ListUserFleetGroupScopes returns every fleet-group id the given
// user is scoped to. An empty slice means "global" — the user sees
// every fleet group.
ListUserFleetGroupScopes(ctx context.Context, userID string) ([]string, error)
// SetUserFleetGroupScopes replaces the user's scope set with the
// supplied list. An empty input clears the scope (back to global).
// Caller is expected to pre-validate that every fleet group id
// exists; the store relies on FK integrity for the runtime guarantee.
SetUserFleetGroupScopes(ctx context.Context, userID string, fleetGroupIDs []string, grantedBy string, grantedAt time.Time) error
// ListAllUserFleetGroupScopes returns every scope grant across all
// users with full provenance (granted_by / granted_at). Exists for
// the offline migrate tooling so the copy preserves audit fields the
// per-user ListUserFleetGroupScopes drops. No runtime caller.
ListAllUserFleetGroupScopes(ctx context.Context) ([]UserFleetGroupScopeRecord, error)
}
UserFleetGroupScopeStore persists the per-operator fleet-group scope mapping introduced by R-S-14. An empty list for a user means "global" (legacy single-tenant behaviour); admins are global regardless of stored rows.
type UserRecord ¶
type UserRecord struct {
ID string
Username string
PasswordHash string
Role string
TotpEnabled bool
TotpSecret string
CreatedAt time.Time
}
UserRecord stores one local control-plane account.
type UserStore ¶
type UserStore interface {
PutUser(ctx context.Context, user UserRecord) error
DeleteUser(ctx context.Context, userID string) error
GetUserByID(ctx context.Context, userID string) (UserRecord, error)
GetUserByUsername(ctx context.Context, username string) (UserRecord, error)
ListUsers(ctx context.Context) ([]UserRecord, error)
}
UserStore persists local control-plane user records.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package migrateguard refuses to apply migrations that would silently destroy production data unless the operator has explicitly opted in.
|
Package migrateguard refuses to apply migrations that would silently destroy production data unless the operator has explicitly opted in. |
|
Package postgres bulk insert helpers (P3-PERF-01a).
|
Package postgres bulk insert helpers (P3-PERF-01a). |
|
Package sqlite bulk insert helpers (P3-PERF-01a).
|
Package sqlite bulk insert helpers (P3-PERF-01a). |
|
Package sqlshared holds the pieces of the SQLite and PostgreSQL stores that are genuinely dialect-independent.
|
Package sqlshared holds the pieces of the SQLite and PostgreSQL stores that are genuinely dialect-independent. |
|
internal/controlplane/storage/uow/uow.go
|
internal/controlplane/storage/uow/uow.go |