Files
oikos/internal/observability/record.go
dtoro c9975d60a5 phase 2 (part 2): sqlc queries, audit/event helpers, event NOTIFY trigger
- sqlc.yaml + internal/db/queries/*.sql: typed queries for entities,
  relationships, ontology, operations (signals, events, audit,
  idempotency, entity_status)
- internal/db/sqlcgen/: generated Go from sqlc (pgx/v5)
- internal/observability/record.go: Audit() and Event() helpers that
  write in the caller's transaction (SG10). actorLabel is interim text
  identity in detail JSON until OIDC resolution lands; actor_id column
  exists but is not yet populated
- migrations/008: post-commit pg_notify trigger on events table for
  SSE fan-out (SG8/SG10)
2026-07-07 08:49:59 +02:00

66 lines
1.8 KiB
Go

package observability
import (
"context"
"encoding/json"
"github.com/dtoro/oikos/internal/db/sqlcgen"
"github.com/google/uuid"
)
// Audit writes an audit_log entry. Pass a transaction-bound Queries so the
// entry commits or rolls back atomically with the state change it records.
//
// actorLabel is the interim textual actor identity ("operator:dev",
// "agent:mcp") recorded in detail; actor_id (a Person/Agent entity UUID)
// starts being populated when OIDC identity resolution lands.
func Audit(ctx context.Context, q *sqlcgen.Queries, actorType, actorLabel,
action string, entityID *uuid.UUID, method, path, correlationID string,
detail map[string]any) error {
if detail == nil {
detail = map[string]any{}
}
detail["actor"] = actorLabel
detailJSON, _ := json.Marshal(detail)
var corr *string
if correlationID != "" {
corr = &correlationID
}
return q.InsertAuditEntry(ctx, sqlcgen.InsertAuditEntryParams{
ActorType: actorType,
Action: action,
EntityID: entityID,
Method: &method,
Path: &path,
Detail: detailJSON,
CorrelationID: corr,
})
}
// Event emits a structured event in the caller's transaction (SG10). The
// post-commit NOTIFY trigger (migration 008) fans it out to SSE subscribers.
func Event(ctx context.Context, q *sqlcgen.Queries, eventType string,
entityID *uuid.UUID, severity, source string, correlationID string,
data map[string]any) error {
dataJSON, _ := json.Marshal(data)
if data == nil {
dataJSON = []byte("{}")
}
var corr *string
if correlationID != "" {
corr = &correlationID
}
_, err := q.InsertEvent(ctx, sqlcgen.InsertEventParams{
Type: eventType,
EntityID: entityID,
Severity: severity,
Source: source,
Data: dataJSON,
CorrelationID: corr,
})
return err
}