6.4 KiB
Implementation Plan: Global Policy Search
Feature: 001-global-policy-search
Branch: 001-global-policy-search
Version: 1.0.0
Created: 2025-12-05
Technical Context
| Aspect | Decision | Notes |
|---|---|---|
| Database ORM | Drizzle ORM | Existing in project |
| Search Method | PostgreSQL ilike |
Case-insensitive substring search |
| Auth Provider | Azure AD (NextAuth) | Extract tenantId from tid claim |
| UI Components | Shadcn UI | Input, Table, Card |
| Data Fetching | Server Actions | Constitution: Server-First |
| API Security | X-API-SECRET header | For n8n ingestion |
Constitution Check
| Principle | Compliant | Implementation |
|---|---|---|
| Server-First | ✅ | Server Actions for search, API route for ingestion |
| TypeScript Strict | ✅ | Full types for schema, validators, actions |
| Drizzle ORM | ✅ | policySettings table with proper indexes |
| Shadcn UI | ✅ | Input, Table components for search UI |
| Azure AD Multi-Tenancy | ✅ | tenantId from session, all queries filtered |
Gate Evaluation
Gate 1: Scope Check
- Feature spec exists:
specs/001-global-policy-search/spec.md - User stories defined: 3 stories (Search, Tenant Isolation, Ingestion API)
- Requirements numbered: F-001 through F-009
- Result: PASS
Gate 2: Constitution Compliance
- No client-side data fetching for primary flows
- All types explicitly defined (no
any) - Database via Drizzle ORM only
- UI components from Shadcn library
- Tenant isolation enforced
- Result: PASS
Gate 3: Security Review
- Authentication required for search (getUserAuth)
- Tenant isolation in all queries (tenantId filter)
- API secret for ingestion endpoint (X-API-SECRET)
- Input validation with Zod schemas
- Result: PASS
Phase 0: Research (Complete)
See: specs/001-global-policy-search/research.md
Resolved:
- Drizzle
ilikefor case-insensitive search - Server Actions pattern for search
- API_SECRET header authentication
- Azure AD tenantId extraction from session
- Upsert with
onConflictDoUpdate
Phase 1: Design (Complete)
Artifacts Generated
| Artifact | Path | Status |
|---|---|---|
| Data Model | specs/001-global-policy-search/data-model.md |
✅ |
| API Contract | specs/001-global-policy-search/contracts/policy-settings-api.yaml |
✅ |
| Server Actions Contract | specs/001-global-policy-search/contracts/server-actions.md |
✅ |
| Quickstart Guide | specs/001-global-policy-search/quickstart.md |
✅ |
Phase 2: Implementation Tasks
Task 1: Database Schema
File: lib/db/schema/policySettings.ts
Priority: P1
Estimate: 30 min
- Create
policySettingspgTable - Add indexes for tenant filtering and search
- Export types:
PolicySetting,NewPolicySetting - Run
npm run db:push
Task 2: Zod Validators
File: lib/validators/policySettings.ts
Priority: P1
Estimate: 15 min
policySettingSchemafor single settingbulkPolicySettingsSchemafor API input- Export input types
Task 3: Extend NextAuth Session
File: lib/auth/utils.ts
Priority: P1
Estimate: 20 min
- Add
jwtcallback to extracttidclaim - Add
sessioncallback to includetenantId - Update TypeScript types for extended session
Task 4: Server Actions
File: lib/actions/policySettings.ts
Priority: P1
Estimate: 45 min
searchPolicySettings(searchTerm)with ilike querygetPolicySettingById(id)with tenant checkgetRecentPolicySettings(limit)sorted by lastSyncedAt- Full tenant isolation in all queries
Task 5: Ingestion API Route
File: app/api/policy-settings/route.ts
Priority: P2
Estimate: 30 min
- POST handler with X-API-SECRET validation
- Bulk upsert with
onConflictDoUpdate - DELETE handler for tenant cleanup
- Proper error responses (400, 401, 500)
Task 6: Environment Variable
File: .env (local), lib/env.mjs
Priority: P2
Estimate: 10 min
- Add
POLICY_API_SECRETto .env - Add optional validation in env.mjs
Task 7: Search Page UI
File: app/(app)/search/page.tsx
Priority: P1
Estimate: 45 min
- Search input with debounce
- Results table with columns
- Loading and empty states
- Error handling
Task 8: Search Components
Files: components/search/*.tsx
Priority: P1
Estimate: 30 min
SearchInput.tsx- Input with search iconResultsTable.tsx- Table with policy dataEmptyState.tsx- No results message
Task 9: Navigation Update
File: config/nav.ts
Priority: P1
Estimate: 5 min
- Add search link to sidebar
- Icon: Search or MagnifyingGlass
Dependencies (Implementation Order)
Task 1 (Schema)
↓
Task 2 (Validators)
↓
Task 3 (Auth) ──────┬──→ Task 4 (Server Actions)
│
└──→ Task 5 (API Route)
↓
Task 6 (Env Var)
Task 4 (Server Actions)
↓
Task 7 (Search Page) ←── Task 8 (Components)
↓
Task 9 (Navigation)
Acceptance Criteria
From spec requirements:
- F-001: Full-text search across settings (ilike on name/value)
- F-002: Display policy name, type, setting name/value
- F-003: Debounced instant search (300ms)
- F-004: User only sees own tenant's data
- F-005: No tenant impersonation possible
- F-006: API_SECRET header required for ingestion
- F-007: Bulk upsert endpoint operational
- F-008: Zod validation on all API inputs
- F-009: Upsert logic prevents duplicates
Test Plan
Manual Testing
-
Search Flow
- Login with Azure AD
- Navigate to /search
- Enter search term
- Verify results from own tenant only
-
Ingestion API
- POST without secret → 401
- POST with wrong secret → 401
- POST with correct secret → 200
- Verify data appears in search
-
Tenant Isolation
- Ingest data for tenant A
- Login as tenant B user
- Search should return empty
Environment Variables Required
| Variable | Purpose | Required In |
|---|---|---|
POLICY_API_SECRET |
n8n ingestion auth | Production, Development |
Rollback Plan
If issues arise:
- Revert branch to previous commit
- Run
npm run db:pushto restore schema - No data migration needed (new table)