---
phase: 01-foundation
plan: 04
type: execute
wave: 2
depends_on: ["01-01"]
files_modified:
  - packages/portal/package.json
  - packages/portal/tsconfig.json
  - packages/portal/tailwind.config.ts
  - packages/portal/app/layout.tsx
  - packages/portal/app/page.tsx
  - packages/portal/app/(auth)/login/page.tsx
  - packages/portal/app/dashboard/layout.tsx
  - packages/portal/app/dashboard/page.tsx
  - packages/portal/app/tenants/page.tsx
  - packages/portal/app/tenants/[id]/page.tsx
  - packages/portal/app/tenants/new/page.tsx
  - packages/portal/app/agents/page.tsx
  - packages/portal/app/agents/[id]/page.tsx
  - packages/portal/app/agents/new/page.tsx
  - packages/portal/app/api/auth/[...nextauth]/route.ts
  - packages/portal/lib/auth.ts
  - packages/portal/lib/api.ts
  - packages/portal/lib/queries.ts
  - packages/portal/components/tenant-form.tsx
  - packages/portal/components/agent-designer.tsx
  - packages/portal/components/nav.tsx
  - packages/portal/middleware.ts
  - packages/shared/api/__init__.py
  - packages/shared/api/portal.py
  - tests/integration/test_portal_tenants.py
  - tests/integration/test_portal_agents.py
autonomous: true
requirements:
  - PRTA-01
  - PRTA-02

user_setup:
  - service: none
    why: "Portal uses email/password auth against local DB — no external OAuth provider needed in Phase 1"

must_haves:
  truths:
    - "Operator can log in to the portal with email and password"
    - "Operator can create a new tenant with name and slug"
    - "Operator can view, edit, and delete existing tenants"
    - "Operator can create an AI employee via the Agent Designer with name, role, persona, system prompt, tool assignments, and escalation rules"
    - "Operator can view, edit, and delete existing agents"
    - "Agent Designer is a prominent, dedicated module — not buried in settings"
  artifacts:
    - path: "packages/portal/app/(auth)/login/page.tsx"
      provides: "Login page with email/password form"
    - path: "packages/portal/app/tenants/page.tsx"
      provides: "Tenant list page with create/edit/delete"
    - path: "packages/portal/app/agents/new/page.tsx"
      provides: "Agent Designer form — the primary way operators define AI employees"
    - path: "packages/portal/components/agent-designer.tsx"
      provides: "Agent Designer form component with all fields"
    - path: "packages/portal/lib/auth.ts"
      provides: "Auth.js v5 configuration with Credentials provider"
      exports: ["auth", "signIn", "signOut", "handlers"]
    - path: "packages/shared/api/portal.py"
      provides: "FastAPI endpoints for tenant CRUD and agent CRUD"
      exports: ["portal_router"]
  key_links:
    - from: "packages/portal/lib/api.ts"
      to: "packages/shared/api/portal.py"
      via: "TanStack Query hooks calling FastAPI CRUD endpoints"
      pattern: "fetch.*api.*(tenants|agents)"
    - from: "packages/portal/lib/auth.ts"
      to: "packages/shared/api/portal.py"
      via: "Credentials provider validates against /auth/verify endpoint"
      pattern: "authorize.*fetch.*auth/verify"
    - from: "packages/portal/middleware.ts"
      to: "packages/portal/lib/auth.ts"
      via: "Auth middleware protects dashboard routes"
      pattern: "auth.*middleware"
---

<objective>
Build the Next.js admin portal with Auth.js v5 authentication, tenant CRUD, and the Agent Designer module, backed by FastAPI CRUD endpoints. The Agent Designer is the primary interface for operators to define their AI employees.

Purpose: Give operators a real admin interface to create tenants and configure AI employees. Per user decision, the portal starts in Phase 1 with Auth.js v5 — no hardcoded credentials or throwaway auth.

Output: Working portal at localhost:3000 with login, tenant management (create/list/view/edit/delete), and Agent Designer (name, role, persona, system prompt, tool assignments, escalation rules). Backed by FastAPI API endpoints with integration tests.
</objective>

<execution_context>
@/home/adelorenzo/.claude/get-shit-done/workflows/execute-plan.md
@/home/adelorenzo/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-foundation/01-CONTEXT.md
@.planning/phases/01-foundation/01-RESEARCH.md
@.planning/phases/01-foundation/01-01-SUMMARY.md

<interfaces>
<!-- From Plan 01 — SQLAlchemy models the API endpoints operate on -->

From packages/shared/models/tenant.py:
```python
class Tenant(Base):
    id: Mapped[uuid.UUID]
    name: Mapped[str]  # unique
    slug: Mapped[str]  # unique
    settings: Mapped[dict]  # JSON
    created_at: Mapped[datetime]
    updated_at: Mapped[datetime]

class Agent(Base):
    id: Mapped[uuid.UUID]
    tenant_id: Mapped[uuid.UUID]  # FK -> Tenant
    name: Mapped[str]
    role: Mapped[str]
    persona: Mapped[str | None]
    system_prompt: Mapped[str | None]
    model_preference: Mapped[str]  # "quality" | "fast"
    tool_assignments: Mapped[list]  # JSON
    escalation_rules: Mapped[list]  # JSON
    is_active: Mapped[bool]
    created_at: Mapped[datetime]
    updated_at: Mapped[datetime]
```

From packages/shared/models/auth.py:
```python
class PortalUser(Base):
    id: Mapped[uuid.UUID]
    email: Mapped[str]  # unique
    hashed_password: Mapped[str]
    name: Mapped[str]
    is_admin: Mapped[bool]
    created_at: Mapped[datetime]
    updated_at: Mapped[datetime]
```

From packages/shared/db.py:
```python
async def get_session() -> AsyncGenerator[AsyncSession, None]: ...
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: FastAPI portal API endpoints (tenant CRUD, agent CRUD, auth verify)</name>
  <files>
    packages/shared/api/__init__.py,
    packages/shared/api/portal.py,
    tests/integration/test_portal_tenants.py,
    tests/integration/test_portal_agents.py
  </files>
  <action>
    1. Create `packages/shared/api/portal.py` with a FastAPI `APIRouter` (prefix="/api/portal"):

       **Auth endpoint:**
       - `POST /auth/verify`: Accepts `{ email: str, password: str }`, validates against PortalUser table using bcrypt, returns `{ id, email, name, is_admin }` or 401. Used by Auth.js Credentials provider.
       - `POST /auth/register`: Accepts `{ email, password, name }`, creates PortalUser with bcrypt-hashed password. Returns 201 with user info. (Needed for initial setup — consider restricting to admin-only in production.)

       **Tenant endpoints (PRTA-01):**
       - `GET /tenants`: List all tenants (paginated, 20 per page). No RLS — platform admin sees all tenants.
       - `POST /tenants`: Create tenant. Accepts `{ name: str, slug: str, settings: dict? }`. Validates name length 2-100, slug format (lowercase, hyphens, 2-50 chars). Returns 201 with tenant object.
       - `GET /tenants/{id}`: Get tenant by ID. Returns 404 if not found.
       - `PUT /tenants/{id}`: Update tenant. Accepts partial updates. Returns updated tenant.
       - `DELETE /tenants/{id}`: Delete tenant. Returns 204. Cascade deletes agents and channel_connections.

       **Agent endpoints (PRTA-02):**
       - `GET /tenants/{tenant_id}/agents`: List agents for a tenant.
       - `POST /tenants/{tenant_id}/agents`: Create agent. Accepts `{ name, role, persona?, system_prompt?, model_preference?, tool_assignments?, escalation_rules? }`. Name required, min 1 char. Role required, min 1 char. Returns 201.
       - `GET /tenants/{tenant_id}/agents/{id}`: Get agent by ID.
       - `PUT /tenants/{tenant_id}/agents/{id}`: Update agent. Accepts partial updates.
       - `DELETE /tenants/{tenant_id}/agents/{id}`: Delete agent. Returns 204.

       Use Pydantic v2 request/response schemas (TenantCreate, TenantResponse, AgentCreate, AgentResponse, etc.). Use SQLAlchemy 2.0 `select()` style — never 1.x `session.query()`.

    2. Create `tests/integration/test_portal_tenants.py` (PRTA-01):
       - Test create tenant with valid data returns 201
       - Test create tenant with duplicate slug returns 409
       - Test list tenants returns created tenants
       - Test get tenant by ID returns correct tenant
       - Test update tenant name
       - Test delete tenant returns 204 and tenant is gone
       - Test create tenant with invalid slug (uppercase, too short) returns 422
       - Use `httpx.AsyncClient` with the FastAPI app

    3. Create `tests/integration/test_portal_agents.py` (PRTA-02):
       - Test create agent with all fields returns 201
       - Test create agent with minimal fields (name + role only) returns 201 with defaults
       - Test list agents for a tenant returns only that tenant's agents
       - Test get agent by ID
       - Test update agent persona and system prompt
       - Test delete agent
       - Test Agent Designer fields are all stored and retrievable: name, role, persona, system_prompt, model_preference, tool_assignments (JSON array), escalation_rules (JSON array)
       - Use `httpx.AsyncClient`
  </action>
  <verify>
    <automated>cd /home/adelorenzo/repos/konstruct && pytest tests/integration/test_portal_tenants.py tests/integration/test_portal_agents.py -x -q</automated>
  </verify>
  <done>
    - Tenant CRUD endpoints all functional with proper validation and error responses
    - Agent CRUD endpoints support all Agent Designer fields
    - Auth verify endpoint validates email/password against PortalUser table
    - Integration tests prove all CRUD operations work correctly
    - Pydantic schemas enforce input validation
  </done>
</task>

<task type="auto">
  <name>Task 2: Next.js portal with Auth.js v5, tenant management, and Agent Designer</name>
  <files>
    packages/portal/package.json,
    packages/portal/tsconfig.json,
    packages/portal/tailwind.config.ts,
    packages/portal/app/layout.tsx,
    packages/portal/app/page.tsx,
    packages/portal/app/(auth)/login/page.tsx,
    packages/portal/app/dashboard/layout.tsx,
    packages/portal/app/dashboard/page.tsx,
    packages/portal/app/tenants/page.tsx,
    packages/portal/app/tenants/[id]/page.tsx,
    packages/portal/app/tenants/new/page.tsx,
    packages/portal/app/agents/page.tsx,
    packages/portal/app/agents/[id]/page.tsx,
    packages/portal/app/agents/new/page.tsx,
    packages/portal/app/api/auth/[...nextauth]/route.ts,
    packages/portal/lib/auth.ts,
    packages/portal/lib/api.ts,
    packages/portal/lib/queries.ts,
    packages/portal/components/tenant-form.tsx,
    packages/portal/components/agent-designer.tsx,
    packages/portal/components/nav.tsx,
    packages/portal/middleware.ts
  </files>
  <action>
    1. Initialize Next.js 16 project in `packages/portal/`:
       - `npx create-next-app@latest . --typescript --tailwind --eslint --app`
       - Install: `@tanstack/react-query react-hook-form zod next-auth@5 @hookform/resolvers`
       - Initialize shadcn/ui: `npx shadcn@latest init` then add components: button, input, textarea, card, table, form, label, select, dialog, toast, navigation-menu, separator, badge

    2. Create `packages/portal/lib/auth.ts`:
       - Auth.js v5 with Credentials provider per research Pattern 7
       - Credentials provider calls `POST ${API_URL}/api/portal/auth/verify` with email + password
       - JWT session strategy (stateless, no DB session table needed for Phase 1)
       - Custom pages: signIn -> "/login"
       - Export `{ handlers, auth, signIn, signOut }`

    3. Create `packages/portal/app/api/auth/[...nextauth]/route.ts`:
       - Re-export `handlers.GET` and `handlers.POST` from lib/auth.ts

    4. Create `packages/portal/middleware.ts`:
       - Protect all routes except `/login` and `/api/auth/*`
       - Redirect unauthenticated users to `/login`

    5. Create `packages/portal/app/(auth)/login/page.tsx`:
       - Email + password login form using shadcn/ui Input, Button, Card
       - Form validation with React Hook Form + Zod (email format, password min 8 chars)
       - Error display for invalid credentials
       - On success, redirect to /dashboard

    6. Create `packages/portal/lib/api.ts`:
       - API client configured with base URL from env (NEXT_PUBLIC_API_URL)
       - Typed fetch wrapper with error handling

    7. Create `packages/portal/lib/queries.ts`:
       - TanStack Query hooks: `useTenants()`, `useTenant(id)`, `useCreateTenant()`, `useUpdateTenant()`, `useDeleteTenant()`
       - TanStack Query hooks: `useAgents(tenantId)`, `useAgent(tenantId, id)`, `useCreateAgent()`, `useUpdateAgent()`, `useDeleteAgent()`
       - Proper invalidation on mutations

    8. Create `packages/portal/components/nav.tsx`:
       - Sidebar navigation with links: Dashboard, Tenants, Employees (label it "Employees" not "Agents" — per the AI employee branding)
       - Active state highlighting
       - Logout button calling signOut

    9. Create `packages/portal/app/dashboard/layout.tsx`:
       - Layout with sidebar nav + main content area
       - TanStack QueryClientProvider wrapping children

    10. Create `packages/portal/app/dashboard/page.tsx`:
       - Simple dashboard landing page with tenant count and agent count stats

    11. Create tenant management pages:
       - `app/tenants/page.tsx`: Table listing all tenants with name, slug, created date. "New Tenant" button. Row click navigates to detail.
       - `app/tenants/new/page.tsx`: Tenant creation form (name, slug). Slug auto-generated from name (lowercase, hyphenated).
       - `app/tenants/[id]/page.tsx`: Tenant detail with edit form and delete button. Shows agents for this tenant.

    12. Create `packages/portal/components/tenant-form.tsx`:
       - Reusable form for create/edit tenant. React Hook Form + Zod validation.

    13. Create Agent Designer pages — PER USER DECISION this is a PROMINENT, DEDICATED module:
       - `app/agents/page.tsx`: Card grid of all agents across tenants. Each card shows agent name, role, tenant name, active status. "New Employee" button.
       - `app/agents/new/page.tsx`: Full Agent Designer form. Grouped into sections:
         - **Identity:** Name (text), Role (text) — e.g., "Customer Support Lead"
         - **Personality:** Persona (textarea — personality description), System Prompt (textarea — raw system prompt override)
         - **Configuration:** Model Preference (select: "quality" / "fast"), Tenant (select dropdown)
         - **Capabilities:** Tool Assignments (JSON editor or tag-style input — list of tool names)
         - **Escalation:** Escalation Rules (JSON editor or structured form — condition + action pairs)
         - **Status:** Active toggle
       - `app/agents/[id]/page.tsx`: Edit existing agent with same form, pre-populated. Delete button.

    14. Create `packages/portal/components/agent-designer.tsx`:
       - The Agent Designer form component. React Hook Form + Zod validation.
       - Zod schema: name (min 1), role (min 1), persona (optional), system_prompt (optional), model_preference (enum: quality|fast), tool_assignments (string array), escalation_rules (array of {condition: string, action: string}), is_active (boolean).
       - Use the "employee" language in labels and placeholders: "Employee Name", "Job Title" (for role), "Job Description" (for persona), "Statement of Work" (for system_prompt) — per user's specific vision that the Agent Designer is about defining an employee.
       - shadcn/ui components: Card for section grouping, Textarea for persona/system_prompt, Input for name/role, Select for model_preference, Badge for tool tags.

    15. Create `packages/portal/app/layout.tsx`:
       - Root layout with Tailwind, font, metadata (title: "Konstruct Portal")

    16. `packages/portal/app/page.tsx`:
       - Redirect to /dashboard if authenticated, /login if not

    17. Update `docker-compose.yml` to add portal service on port 3000 with env vars.
  </action>
  <verify>
    <automated>cd /home/adelorenzo/repos/konstruct/packages/portal && npm run build</automated>
  </verify>
  <done>
    - Portal builds successfully with Next.js 16
    - Login page authenticates against FastAPI /auth/verify via Auth.js v5 Credentials provider
    - Protected routes redirect to /login when unauthenticated
    - Tenant CRUD: list, create, view, edit, delete all functional
    - Agent Designer: all fields (name, role, persona, system prompt, model preference, tool assignments, escalation rules) saveable and loadable
    - Agent Designer uses employee-centric language (Employee Name, Job Title, Job Description, Statement of Work)
    - Agent Designer is a prominent top-level module, not buried in settings
    - shadcn/ui styling with Tailwind CSS
  </done>
</task>

</tasks>

<verification>
- `pytest tests/integration/test_portal_tenants.py tests/integration/test_portal_agents.py -x` proves API CRUD works
- `cd packages/portal && npm run build` compiles without errors
- Portal pages render tenant list, tenant create/edit, agent designer
- Auth.js v5 login flow works with email/password
</verification>

<success_criteria>
- Operator can log in, create tenants, and configure AI employees through the portal
- Agent Designer prominently accessible with all required fields
- All API CRUD operations validated by integration tests
- Portal builds cleanly with Next.js 16
</success_criteria>

<output>
After completion, create `.planning/phases/01-foundation/01-04-SUMMARY.md`
</output>
