# Architecture (Restructured)

## 1\. Goals of the Restructure

*   **One app, one deploy.** A single Next.js project instead of 8 workspace packages.
    
*   **No tRPC.** Use native Next.js primitives: **Server Components** for reads and **Server Actions** (plus a few Route Handlers) for writes. Type-safety comes from plain TypeScript function signatures + **Zod** + **Prisma**, not a transport layer.
    
*   **Keep clean layering.** UI → Action → Service → Repository → Prisma. Business rules never leak into components; SQL never leaks out of repositories.
    
*   **Extensible by default.** The data model (see `ER_DIAGRAM.eraser`) is additive-only, polymorphic where it helps, and JSON-extensible so future features don't require breaking migrations.
    

* * *

## 2\. Technology Stack

| Concern | Choice |
| --- | --- |
| Framework | **Next.js (App Router)** — RSC + Server Actions |
| Language | TypeScript (strict) |
| Database | **PostgreSQL** |
| ORM | **Prisma** |
| Validation | **Zod** (shared input/output schemas) |
| Auth | Session-based (httpOnly cookie + `sessions` table) |
| Authorization | Table-driven **RBAC** (`roles` / `permissions` / `role_permissions`) |
| UI | Tailwind CSS + shadcn/ui |
| Client data (interactive) | TanStack Query (only where client interactivity needs it) |
| Files | Local disk now → S3-compatible later (storage abstraction) |
| Background work | In-process job runner (notifications, deadline sweeps) |
| Deploy | Node (`next start`) under PM2, Caddy reverse proxy, Docker Postgres |

**Removed vs old SDD:** tRPC, `packages/trpc`, `packages/database`, `packages/notifications`, `packages/ui`, `packages/shared`, `packages/config`, `packages/utils`, `packages/ai`, `turbo.json`, `pnpm-workspace.yaml`.

* * *

## 3\. High-Level Layering

```mermaid
flowchart TD
    subgraph Client
      RSC[Server Components<br/>read data]
      CC[Client Components<br/>forms, boards, editors]
    end

    subgraph NextServer[Next.js Server]
      SA[Server Actions<br/>'use server']
      RH[Route Handlers<br/>/api/* files, webhooks, health]
      SVC[Service Layer<br/>business rules, RBAC, tx, activity, notifications]
      REPO[Repository Layer<br/>Prisma queries only]
    end

    DB[(PostgreSQL)]

    RSC -->|call service read| SVC
    CC -->|invoke| SA
    CC -->|fetch| RH
    SA --> SVC
    RH --> SVC
    SVC --> REPO
    REPO --> DB
```

**Rules**

*   Components (RSC or client) **never** touch Prisma directly.
    
*   Server Actions/Route Handlers are thin: authenticate → validate (Zod) → call a service → return.
    
*   Services own business logic, permission checks, transactions, activity logging, and notification triggers. Services may call multiple repositories.
    
*   Repositories are the **only** place Prisma is used. No business rules inside them.
    

* * *

## 4\. Request Flows

### 4.1 Mutation (write)

```mermaid
sequenceDiagram
    participant U as Client Component
    participant A as Server Action
    participant S as Service
    participant R as Repository
    participant DB as PostgreSQL

    U->>A: submit form (typed args)
    A->>A: getCurrentUser() + Zod.parse(input)
    A->>S: service.doThing(user, dto)
    S->>S: can(user, permission, resource)?
    S->>R: repo.write(...) inside prisma.$transaction
    R->>DB: INSERT/UPDATE
    S->>R: activityRepo.record(...)
    S-->>A: result
    A-->>U: revalidatePath() / typed result
```

### 4.2 Read (RSC)

```mermaid
sequenceDiagram
    participant P as Server Component (page.tsx)
    participant S as Service (read)
    participant R as Repository
    participant DB as PostgreSQL

    P->>S: service.getProjectDashboard(user, projectId)
    S->>S: assert membership / RBAC scope
    S->>R: repo.findDashboardData(...)
    R->>DB: SELECT ...
    R-->>S: rows
    S-->>P: view model
    P-->>P: render
```

* * *

## 5\. Folder Structure (single Next.js app)

```plaintext
agency-os/
├── prisma/
│   ├── schema.prisma            # single schema (implements ER_DIAGRAM.eraser)
│   ├── migrations/
│   └── seed.ts                  # roles, permissions, demo users/project
├── public/
├── src/
│   ├── app/                     # App Router (routes only)
│   │   ├── (auth)/login/
│   │   ├── (main)/              # authenticated shell (sidebar + header)
│   │   │   ├── layout.tsx
│   │   │   ├── page.tsx         # role-aware dashboard
│   │   │   ├── projects/
│   │   │   │   ├── page.tsx
│   │   │   │   ├── new/
│   │   │   │   └── [projectId]/
│   │   │   │       ├── (dashboard)/
│   │   │   │       ├── planning/
│   │   │   │       ├── development/   # sprints, backlog, kanban, deps
│   │   │   │       ├── knowledge/
│   │   │   │       ├── ai-skills/
│   │   │   │       ├── activity/
│   │   │   │       └── settings/
│   │   │   │       └── whiteboard/

      
│   │   │   ├── ai-library/
│   │   │   ├── notifications/
│   │   │   └── settings/
│   │   ├── share/[token]/       # public, read-only client progress
│   │   ├── api/                 # Route Handlers only where actions don't fit
│   │   │   ├── files/[...]/route.ts   # upload/download streaming
│   │   │   └── health/route.ts
│   │   └── layout.tsx
│   │
│   ├── features/                # DOMAIN MODULES (the heart of the app)
│   │   ├── auth/
│   │   │   ├── actions.ts       # 'use server' entry points
│   │   │   ├── service.ts       # business logic
│   │   │   ├── repository.ts    # Prisma access
│   │   │   ├── schema.ts        # Zod DTOs
│   │   │   └── components/
│   │   ├── projects/
│   │   ├── planning/            # documents, references, whiteboards, decisions
│   │   ├── development/         # sprints, main tasks, sub tasks, deps, checklists
│   │   ├── knowledge/           # shares `documents` with planning (workspace flag)
│   │   ├── ai-skills/
│   │   ├── contractors/
│   │   ├── notifications/
│   │   ├── activity/
│   │   ├── search/
│   │   └── files/
│   │
│   ├── server/
│   │   ├── db.ts                # Prisma client singleton
│   │   ├── auth/
│   │   │   ├── session.ts       # create/verify/destroy session cookie
│   │   │   ├── context.ts       # getCurrentUser() for RSC + actions
│   │   │   └── rbac.ts          # can(user, permission, resource)
│   │   └── jobs/                # background workers
│   │       ├── runner.ts
│   │       ├── deadline-sweep.ts
│   │       └── notification-dispatch.ts
│   │
│   ├── components/ui/           # shadcn components
│   ├── lib/                     # utils, constants, enums mirror
│   ├── hooks/                   # client hooks (TanStack Query wrappers)
│   ├── types/
│   └── middleware.ts            # protects (main) routes, redirects to /login
│
├── docker-compose.yml          # PostgreSQL 16
├── .env
├── next.config.ts
└── package.json
```

**Feature module contract** — every folder under `features/` may expose: `actions.ts` (server actions) · `service.ts` (logic) · `repository.ts` (Prisma) · `schema.ts` (Zod) · `components/` (UI). Adding a new domain = add a new folder. Nothing else in the app needs to change.

* * *

## 6\. Data Access

*   **Prisma singleton** in `src/server/db.ts` (guards against dev hot-reload leaks).
    
*   **Repositories** wrap Prisma per aggregate root (Project, Sprint, Task, Document, AISkill, Notification, ActivityLog, …). One repository per aggregate.
    
*   **Transactions** (`prisma.$transaction`) live in **services**, for multi-step operations: project creation (+ default docs + activity), contractor assignment (+ membership + activity + notification), task completion (+ status + notification), share-link generation, sprint creation.
    
*   **Soft delete** via `deleted_at` on major entities; projects also carry `archived_at`
    
    *   `ARCHIVED` status. Audit tables (`activity_logs`, `decision_logs`) are append-only.
        

* * *

## 7\. Validation & Type Safety (without tRPC)

*   Each feature has `schema.ts` with **Zod** DTOs. Server Actions `parse()` input at the boundary; the parsed type flows into services.
    
*   Service and repository signatures are plain typed TS functions — call-site type checking replaces tRPC's wire types.
    
*   Prisma generates DB types; services map DB rows → view models returned to RSC.
    
*   Shared enums (statuses, priorities, roles) live in `src/lib/enums.ts` and mirror the Prisma enums, so UI and server agree without a transport contract.
    

* * *

## 8\. Authentication

*   **Session-based.** On login: verify credentials → create `sessions` row → set `httpOnly`, `secure`, `sameSite=lax` cookie with the session token.
    
*   `middleware.ts` guards `(main)/**` and `share` is public (token-scoped).
    
*   `getCurrentUser()` (in `server/auth/context.ts`) resolves the session for RSC and Server Actions. Unauthenticated → redirect `/login`.
    
*   First login sets `users.is_first_login = true` → contractor onboarding wizard.
    

* * *

## 9\. Authorization (RBAC, future-proof)

Table-driven so new roles (e.g. **Project Manager**) require **zero schema changes** — just new rows:

*   `roles` (scope = `SYSTEM` | `PROJECT`, e.g. `developer`, `contractor`, `project_manager`)
    
*   `permissions` (`project.create`, `task.update`, `credential.read`, …)
    
*   `role_permissions` (join)
    
*   `users.role_id` → global role; `project_members.role_id` → per-project role
    

`can(user, permission, project?)` resolves system role + project membership role, unions their permissions, and enforces project scoping (contractors only see assigned projects).

* * *

## 10\. Cross-Cutting Concerns

| Concern | Where | Notes |
| --- | --- | --- |
| Activity logging | Service layer → `activity_logs` | Append-only; polymorphic `entity_type`/`entity_id`; string `action` so new events need no migration |
| Notifications | `features/notifications` + `server/jobs` | `notifications` (logical) → `notification_logs` (per-channel delivery). Providers: Email, WhatsApp, In-App. `notification_preferences` per user/type/channel |
| Files | `features/files` + `/api/files` | Storage abstraction (`local` now, `s3` later). Polymorphic `attachments` (`attachable_type`/`attachable_id`) |
| Search | `features/search` | Title-only (Postgres `ILIKE`) for Phase 1; `pg_trgm`/`tsvector` later without schema change |
| Documents | `features/planning` + `features/knowledge` | One `documents` table with `workspace` (PLANNING/KNOWLEDGE) + `type`; versioned via `document_versions` |

* * *

## 11\. Background Jobs

A lightweight in-process runner (`server/jobs/runner.ts`) started with the server:

*   **notification-dispatch** — drains `PENDING` notifications, calls providers, writes `notification_logs`, marks `SENT`/`FAILED`.
    
*   **deadline-sweep** — periodic scan for approaching/overdue task deadlines → enqueue notifications.
    

Can be swapped for a real queue (BullMQ/Redis) later without touching feature code.

* * *

## 12\. Error Handling

*   Actions catch service errors and return a typed `{ ok: false, error }` shape (or throw for `error.tsx` boundaries on RSC reads).
    
*   Domain errors are typed (`NotFoundError`, `ForbiddenError`, `ValidationError`).
    
*   Client surfaces failures via toasts; forms show field-level Zod errors.
    

* * *

## 13\. Deployment (self-hosted)

```plaintext
git pull → npm ci → prisma migrate deploy → next build → pm2 reload
```

*   **PostgreSQL** via `docker-compose.yml` (bound to `127.0.0.1:5432`).
    
*   **Caddy** terminates HTTPS and reverse-proxies to `localhost:3000`.
    
*   **PM2** runs `next start` with auto-restart + env injection.
    
*   Env validated at boot with Zod (`src/lib/env.ts`).
    

* * *

## 14\. Extensibility Principles (won't break later)

1.  **Additive migrations only** — never rename/drop columns in place; add + backfill + deprecate.
    
2.  **Enums grow, never shrink** — Postgres `ALTER TYPE ... ADD VALUE` is non-breaking.
    
3.  `metadata json` on major entities absorbs new attributes without migrations.
    
4.  **Polymorphic links** (`attachments`, `comments`, `task_dependencies`, `task_labels`, `activity_logs`) let new entity types plug in with no schema change (integrity enforced in services).
    
5.  **Table-driven RBAC** — new roles/permissions are data, not code.
    
6.  **Unified documents + versioning** — new doc types are enum values, not new tables.
    
7.  **Storage & notification abstractions** — swap providers without touching callers.
    
8.  **Multi-tenancy path** — add an optional `organization_id` FK later; nullable + backfill = non-breaking.
