Adds audit_knowledge_graph (MCP tool) and GET /api/v1/audit/drift (endpoint) backed by a shared internal/audit package. One pass surfaces the structural gaps an operator otherwise finds by accident: orphan check entities, checks targeting deprecated/destroyed entities, probes stuck down/unknown, unmonitored declared types, and live edges pointing at destroyed targets. Each finding carries a suggested remediation runbook. Read-only and safe to run unattended. Ships the knowledge-graph-audit skill (SKILL.md + seeded runbook) that interprets the report and routes findings to the lifecycle runbooks.
68 lines
1.5 KiB
Go
68 lines
1.5 KiB
Go
package audit
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/dtoro/oikos/internal/db"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
// createTestDB provisions a throwaway migrated database, same convention as
|
|
// internal/scheduler/coverage_test.go.
|
|
func createTestDB(t *testing.T, baseURL string) *db.Pool {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
|
|
admin, err := pgx.Connect(ctx, baseURL)
|
|
if err != nil {
|
|
t.Fatalf("connect admin: %v", err)
|
|
}
|
|
dbName := fmt.Sprintf("oikos_aud_%08x", rand.Int63())
|
|
if _, err := admin.Exec(ctx, "CREATE DATABASE "+dbName); err != nil {
|
|
admin.Close(ctx)
|
|
t.Fatalf("create test db: %v", err)
|
|
}
|
|
admin.Close(ctx)
|
|
|
|
at := strings.LastIndex(baseURL, "/")
|
|
testURL := baseURL[:at+1] + dbName
|
|
if q := strings.Index(baseURL[at:], "?"); q >= 0 {
|
|
testURL += baseURL[at+q:]
|
|
}
|
|
|
|
pool, err := db.New(ctx, testURL)
|
|
if err != nil {
|
|
t.Fatalf("connect test db: %v", err)
|
|
}
|
|
if err := pool.Migrate(ctx); err != nil {
|
|
t.Fatalf("migrate: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
pool.Close()
|
|
if admin, err := pgx.Connect(ctx, baseURL); err == nil {
|
|
admin.Exec(ctx, "DROP DATABASE IF EXISTS "+dbName+" WITH (FORCE)")
|
|
admin.Close(ctx)
|
|
}
|
|
})
|
|
return pool
|
|
}
|
|
|
|
func getenvOrDefault(key, def string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
func mustExec(t *testing.T, pool *db.Pool, ctx context.Context, q string, args ...any) {
|
|
t.Helper()
|
|
if _, err := pool.Exec(ctx, q, args...); err != nil {
|
|
t.Fatalf("exec %s: %v", q, err)
|
|
}
|
|
}
|