//go:build integration // AIOE isolation and closed loop integration proofs, against a REAL Postgres. These prove at // runtime what migration 0043 promises: the dedicated missionmatch_aioe role can read outcomes and // write profiles but is DENIED on eeo, on candidates, and on every other tenant table; the app // role records outcomes under RLS; and one updater pass produces a versioned, audited profile the // scorer then loads. // // Env: MM_AIOE_DSN (optional; these tests skip without it, exactly like the compliance DSN). package integration import ( "context" "encoding/json" "fmt" "os" "testing" "time" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "github.com/missionworkforce/missionmatch/internal/aioe" "github.com/missionworkforce/missionmatch/internal/platform/database" ) func aioePool(t *testing.T) *pgxpool.Pool { t.Helper() dsn := os.Getenv("MM_AIOE_DSN") if dsn == "" { t.Skip("MM_AIOE_DSN not set, skipping aioe isolation tests") } pool, err := database.NewPool(context.Background(), dsn) if err != nil { t.Fatalf("aioe pool: %v", err) } t.Cleanup(pool.Close) if err := database.AssertRole(context.Background(), pool, "missionmatch_aioe"); err != nil { t.Fatalf("aioe role assert: %v", err) } return pool } // TestAioeRoleIsDeniedEverywhereButItsOwnTables is THE guardrail proof: the updater's credentials // are physically unable to read protected or personal data. Every denial must be a permission // error (42501), not an empty result. func TestAioeRoleIsDeniedEverywhereButItsOwnTables(t *testing.T) { pool := aioePool(t) ctx := context.Background() denied := []string{ "SELECT 1 FROM eeo.candidate_demographics LIMIT 1", "SELECT 1 FROM candidates LIMIT 1", "SELECT 1 FROM candidate_evaluations LIMIT 1", "SELECT 1 FROM requirements LIMIT 1", "SELECT 1 FROM users LIMIT 1", "SELECT 1 FROM auth_sessions LIMIT 1", "SELECT 1 FROM slate_members LIMIT 1", "SELECT 1 FROM audit_logs LIMIT 1", "SELECT 1 FROM organizations LIMIT 1", "INSERT INTO outcome_events (organization_id, module, subject_id, event_type, signal_value) VALUES (gen_random_uuid(), 'screen', gen_random_uuid(), 'screening_decision', 1)", } for _, q := range denied { _, err := pool.Exec(ctx, q) if pgCode(err) != "42501" { t.Fatalf("aioe role must be DENIED (42501) on %q, got %v", q, err) } } // And its own tables ARE readable (the grants are narrow, not broken). allowed := []string{ "SELECT count(*) FROM outcome_events", "SELECT count(*) FROM aioe_weight_profiles", "SELECT count(*) FROM aioe_weight_adjustments", "SELECT count(*) FROM aioe_runs", } for _, q := range allowed { if _, err := pool.Exec(ctx, q); err != nil { t.Fatalf("aioe role should run %q, got %v", q, err) } } } // TestAioeClosedLoop drives the whole loop against the real database: the app role records enough // positive outcomes with denormalized dimension scores, one updater pass produces an ACTIVE // versioned profile with audit rows, and the scorer's loader returns the new profile for that org. func TestAioeClosedLoop(t *testing.T) { pool := aioePool(t) ctx := context.Background() org := seedOrg(t) // Record 40 positive outcomes through the APP role under RLS, the capture path shape: high // skills score, low location score, so skills should rise and location fall. scores := map[string]string{ "clearance": "100", "skills": "95", "certifications": "50", "customer_experience": "50", "experience": "60", "location": "10", } meta, _ := json.Marshal(map[string]any{"dimension_scores": scores}) err := appDB.RunInTenantTx(ctxOrg(org), func(ctx context.Context, tx pgx.Tx) error { for i := 0; i < 40; i++ { if _, err := tx.Exec(ctx, ` INSERT INTO outcome_events (organization_id, module, subject_id, event_type, signal_value, reason_code, occurred_at, metadata) VALUES ($1, 'screen', gen_random_uuid(), 'screening_decision', 1, 'advance', now() - interval '1 day', $2)`, org, meta); err != nil { return err } } return nil }) if err != nil { t.Fatalf("record outcomes: %v", err) } updater := aioe.NewUpdater(pool, aioe.DefaultParams(), nil) report, err := updater.RunAll(ctx, time.Now(), "admin") if err != nil { t.Fatalf("updater: %v", err) } if report.ProfilesUpdated < 1 { t.Fatalf("expected at least one profile update, got %+v", report) } // The org now has exactly one ACTIVE profile with audit rows naming the window. var version, adjustments int if err := pool.QueryRow(ctx, ` SELECT profile_version FROM aioe_weight_profiles WHERE organization_id = $1 AND status = 'active'`, org).Scan(&version); err != nil { t.Fatalf("active profile: %v", err) } if version < 1 { t.Fatalf("active profile version = %d", version) } if err := pool.QueryRow(ctx, ` SELECT count(*) FROM aioe_weight_adjustments WHERE organization_id = $1 AND produced_version = $2`, org, version).Scan(&adjustments); err != nil { t.Fatalf("adjustments: %v", err) } if adjustments < 1 { t.Fatal("a produced profile must carry adjustment audit rows, nothing changes silently") } // The scorer's loader (app role, RLS scoped) picks the new profile up for this org. err = appDB.RunInTenantTx(ctxOrg(org), func(ctx context.Context, tx pgx.Tx) error { w, label, lerr := aioe.LoadActiveWeightsOnTx(ctx, tx) if lerr != nil { return lerr } if label != aioe.ProfileLabel(version) { return fmt.Errorf("loader returned %q, want %q", label, aioe.ProfileLabel(version)) } if err := w.Validate(); err != nil { return fmt.Errorf("loaded profile invalid: %w", err) } d := aioe.DefaultWeightsMap() if w.Skills.LessThanOrEqual(d["skills"]) { return fmt.Errorf("skills should have risen above the default, got %s", w.Skills) } return nil }) if err != nil { t.Fatal(err) } // Determinism: a second pass over the SAME day and window changes nothing further only if the // evidence is fully absorbed; what it must NEVER do is fail or produce an unaudited change. before := version if _, err := updater.RunAll(ctx, time.Now(), "admin"); err != nil { t.Fatalf("second pass: %v", err) } var after int if err := pool.QueryRow(ctx, ` SELECT profile_version FROM aioe_weight_profiles WHERE organization_id = $1 AND status = 'active'`, org).Scan(&after); err != nil { t.Fatal(err) } if after > before { var count int if err := pool.QueryRow(ctx, ` SELECT count(*) FROM aioe_weight_adjustments WHERE organization_id = $1 AND produced_version = $2`, org, after).Scan(&count); err != nil || count == 0 { t.Fatalf("version advanced to %d without audit rows (err %v)", after, err) } } }