tenantpilot/specs/001-global-policy-search/plan.md
2025-12-05 22:06:22 +01:00

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 ilike for 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 policySettings pgTable
  • 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

  • policySettingSchema for single setting
  • bulkPolicySettingsSchema for API input
  • Export input types

Task 3: Extend NextAuth Session

File: lib/auth/utils.ts Priority: P1 Estimate: 20 min

  • Add jwt callback to extract tid claim
  • Add session callback to include tenantId
  • Update TypeScript types for extended session

Task 4: Server Actions

File: lib/actions/policySettings.ts Priority: P1 Estimate: 45 min

  • searchPolicySettings(searchTerm) with ilike query
  • getPolicySettingById(id) with tenant check
  • getRecentPolicySettings(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_SECRET to .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 icon
  • ResultsTable.tsx - Table with policy data
  • EmptyState.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

  1. Search Flow

    • Login with Azure AD
    • Navigate to /search
    • Enter search term
    • Verify results from own tenant only
  2. Ingestion API

    • POST without secret → 401
    • POST with wrong secret → 401
    • POST with correct secret → 200
    • Verify data appears in search
  3. 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:

  1. Revert branch to previous commit
  2. Run npm run db:push to restore schema
  3. No data migration needed (new table)