Files
oikos/internal/domain/signal.go
dtoro aa2ca0ae6f phase 1: Go foundation — module, migrations, domain, seed ingest
Core deliverables:
- Go module github.com/dtoro/oikos (Go 1.26.3)
- cmd/oikos: single binary with role subcommands (migrate, seed, export)
- 6 SQL migrations: ontology meta-schema, entity instances (UUID+slug,
  blast_radius recursive function), operations (signals/checks/approvals),
  cognition (classifications/executions/feedback/patterns/skills), policy,
  observability (TimescaleDB hypertables + CAGGs + retention)
- Domain layer: entity, signal, execution, classification, pattern, skill,
  approval, check types + 11 sentinel errors + lifecycle state machines
- DB layer: pgx pool, SQL splitter (handles 94436 and -- comments), migration
  runner, seed ingest (ontology+inventory+policy) with content-hash dedup
- Config: env-based with defaults, secrets redaction
- Observability: slog JSON logger with debug mode
- Infrastructure: Makefile, docker-compose.yml, multi-stage Dockerfile
  (distroless, CGO_ENABLED=0)

Verified end-to-end against timescale/timescaledb:2.17.2-pg16:
- 6 migrations applied (65 SQL statements)
- Seeds ingested: 6 lifecycles, 59 entity types, 46 relationship types,
  111 entities, 144 relationships, 4 risk classes, 27 approval rules,
  9 autonomy settings
- Idempotent: second seed run is a no-op (content hash matches)

Bugs fixed during implementation:
- TimescaleDB CAGGs can't run in a transaction -> splitSQL() executes
  statements individually
- Semicolons in -- comments treated as separators -> comment handling
- YAML keys source/target didn't match code's source_type/target_type
- yaml.Marshal produced YAML for JSONB columns -> json.Marshal
2026-07-07 01:07:26 +02:00

66 lines
1.7 KiB
Go

package domain
import "time"
// Signal is an attention record — something the lab noticed that needs
// attention and possibly action. Dual entity: has an entities row + a
// signals table row for indexed querying.
type Signal struct {
EntityID UUID
Kind string
Severity string
TargetEntityID UUID
CheckID UUID
Evidence string
LikelyCause string
State string
OccurrenceCount int
FirstSeenAt time.Time
LastSeenAt time.Time
FlapCount int
HoldDownUntil *time.Time
MuteUntil *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
// Signal lifecycle states (see lifecycle_defs in seeds/ontology.yaml).
const (
SignalRaised = "raised"
SignalAcknowledged = "acknowledged"
SignalActing = "acting"
SignalMuted = "muted"
SignalResolved = "resolved"
SignalFailed = "failed"
)
// Signal severities.
const (
SeverityInfo = "info"
SeverityWarning = "warning"
SeverityCritical = "critical"
)
// ValidSignalTransitions defines legal state transitions.
var ValidSignalTransitions = map[string][]string{
SignalRaised: {SignalAcknowledged, SignalMuted, SignalResolved},
SignalAcknowledged: {SignalActing, SignalResolved, SignalMuted},
SignalActing: {SignalResolved, SignalRaised, SignalFailed},
SignalFailed: {SignalAcknowledged},
SignalMuted: {SignalRaised},
}
// CanTransition returns true if from→to is a legal signal state transition.
func (s *Signal) CanTransition(to string) bool {
allowed, ok := ValidSignalTransitions[s.State]
if !ok {
return false
}
for _, a := range allowed {
if a == to {
return true
}
}
return false
}