- 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)
66 lines
1.8 KiB
Go
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
|
|
}
|