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, sessionID *uuid.UUID, 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, SessionID: sessionID, }) } // 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 }