Files
orca/internal/engine/registry.go
T
Jon Chery b25e074e1d feat(P04): audit log + persistence hardening
Implements Phase 4 of v0.1 Foundation:
- internal/store/migrations/0003_audit_log.sql: audit_log table with indexes
- internal/store/audit_repo.go: AuditRepo (Append + List)
- internal/store/audit_repo_test.go: 2 tests for audit persistence
- internal/engine/audit.go: Audit wrapper that persists to SQLite AND logs via slog
- internal/cli/audit.go: orca audit list command (text + JSON)
- Registry now records every join/leave/forget with actor/action/resource/result

Verified: audit entries persist across restarts, JSON output includes metadata,
node operations emit audit records. All tests pass with -race.

---ci---
project: orca
phase: 4
milestone: v0.1
status: execute
req_covered:
  - REQ-005
  - REQ-006
  - REQ-008
  - REQ-017
  - REQ-018
---/ci---
2026-06-03 12:48:31 +00:00

71 lines
1.9 KiB
Go

package engine
import (
"context"
"fmt"
"log/slog"
"git.cloudinit.dev/coreci/orca/internal/model"
"git.cloudinit.dev/coreci/orca/internal/store"
)
type NodeRegistry struct {
repo *store.NodeRepo
audit *Audit
log *slog.Logger
}
func NewNodeRegistry(repo *store.NodeRepo, audit *Audit, log *slog.Logger) *NodeRegistry {
if log == nil {
log = slog.Default()
}
return &NodeRegistry{repo: repo, audit: audit, log: log}
}
func (r *NodeRegistry) Join(ctx context.Context, n *model.Node) error {
if err := r.repo.Insert(ctx, n); err != nil {
r.audit.Record(ctx, "cli", "node.join", n.ID, "failure", err, map[string]any{
"name": n.Name,
"address": n.Address,
})
return fmt.Errorf("join node: %w", err)
}
r.audit.Record(ctx, "cli", "node.join", n.ID, "success", nil, map[string]any{
"name": n.Name,
"address": n.Address,
})
r.log.Info("node joined",
slog.String("node_id", n.ID),
slog.String("name", n.Name),
slog.String("address", n.Address))
return nil
}
func (r *NodeRegistry) Leave(ctx context.Context, id string) error {
if err := r.repo.UpdateState(ctx, id, model.NodeStateLeft); err != nil {
r.audit.Record(ctx, "cli", "node.leave", id, "failure", err, nil)
return fmt.Errorf("leave node: %w", err)
}
r.audit.Record(ctx, "cli", "node.leave", id, "success", nil, nil)
r.log.Info("node left", slog.String("node_id", id))
return nil
}
func (r *NodeRegistry) Forget(ctx context.Context, id string) error {
if err := r.repo.Delete(ctx, id); err != nil {
r.audit.Record(ctx, "cli", "node.forget", id, "failure", err, nil)
return fmt.Errorf("forget node: %w", err)
}
r.audit.Record(ctx, "cli", "node.forget", id, "success", nil, nil)
r.log.Info("node removed from registry", slog.String("node_id", id))
return nil
}
func (r *NodeRegistry) List(ctx context.Context) ([]*model.Node, error) {
return r.repo.List(ctx)
}
func (r *NodeRegistry) Get(ctx context.Context, id string) (*model.Node, error) {
return r.repo.Get(ctx, id)
}