Aller au contenu

Signals API

Chaque worker, handler d’ingest et scanner côté hub écrit ses findings pertinents pour le Trust Score dans une seule table : signal_streams. Trust Score, Anomaly Correlation, Compliance Pareto et tous les dashboards y lisent. Plus de tables silo par feature.

Conventions du wire-format

ChampForme
sourcelowercase_snake_casecert_scan, dns_check, …
subject_typelowercase_snake_caseagent, endpoint, domain, dependency, …
signal_keydotted lowercase — cert.expiry_days, dns.dmarc_present
signal_valueJSONB ; convention : {"value": <native>, …metadata}
severityNULL ou l’une de `info
observed_atTIMESTAMPTZ ; colonne de partition sur l’hypertable
ttl_secondsNULL = sticky

Les CHECK-constraints Postgres valident chaque regex pour qu’une faute de frappe remonte immédiatement en 23514 ; signals.Emitter.validate() fait le même contrôle en Go pour que l’erreur atterrisse au call site.

Interface Go

import "github.com/gotrust/monsys/hub/api/signals"
emitter := signals.NewSQLEmitter(pool)
err := emitter.Emit(ctx, tenantID, signals.Signal{
Source: "my_check",
SubjectType: "agent",
SubjectID: agentID.String(),
Key: "my.finding",
Value: map[string]any{"value": 42, "extra": "context"},
Severity: signals.SeverityHigh,
ObservedAt: time.Now().UTC(),
})

EmitBatch utilise pgx.CopyFrom ; all-or-nothing : un seul signal invalide rejette tout le batch (voir le rationale dans signals/emitter.go).

Signaux émis par l’agent

L’agent peut émettre des signaux via un payload dédié agent_signals (Phase 1.4–1.6). Le hub valide :

  • source ∈ allowlist (backup_check, clock_check, endpoint_posture)
  • subject_type ∈ allowlist (agent)
  • subject_id écrasé par le agent_id de l’appelant quand subject_type=agent

Un agent compromis ne peut jamais réclamer des findings pour un autre agent de son tenant.

Source → catégorie Trust Score

Mapping dans hub/api/trust_score.SourceToCategory. Ajouter une nouvelle source = une entrée de plus ; sans cette entrée la source compte zéro point au Trust Score.

RLS

signal_streams a ENABLE ROW LEVEL SECURITY avec USING tenant_id = current_setting('app.current_tenant', true)::UUID. Défense en profondeur. Toute requête côté hub DOIT quand même inclure un WHERE tenant_id = $1 explicite — set_config(..., true) ne survit pas au connection-recycling de pgxpool. Voir docs/internal/decisions/2026-Q2-connected-dashboards.md D-0003.

La compression n’est pas activée sur signal_streams (Timescale 2.18+ refuse la combinaison RLS+columnstore). Rétention via drop_chunks, 180j par défaut.