b25e074e1d
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---
71 lines
1.9 KiB
Go
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)
|
|
}
|