---
description: "Window 2: Backend scaffold + domain modules (Phase 1 + Phase 3 — start AFTER Window 1 completes)"
---

# Window 2: Backend Scaffold + Domain Modules

> **Prerequisites:** Window 1 must be complete (product identity, pnpm-workspace, .npmrc exist).
> **Scope:** `backend/` directory ONLY. Do NOT modify `client/`, `mobile/`, root config files, or `shared/`.
> **Reference:** `docs/ECOSYSTEM_ADAPTATION_ROADMAP.md` Sections 4 (Phase 1, Phase 3).
> **Scaffold reference repos:** `../learning_ai_notes/backend/`, `../learning_ai_trails/backend/`, `../learning_ai_flowmonk/backend/`

## Context

EffoRise is an identity-based habit tracker. Product ID: `efforise`, backend port: 4020. The backend follows the standard ByteLyst product-backend pattern: Fastify 5 + TypeScript ESM + `@bytelyst/*` shared packages + Zod validation.

All `@bytelyst/*` packages come from the Gitea npm registry (configured in root `.npmrc`). Use `@bytelyst/datastore` with `DB_PROVIDER=memory` for local dev (no Cosmos needed).

---

## Phase 1: Backend Scaffold

### Step 1: Create `backend/package.json`

Copy structure from `../learning_ai_notes/backend/package.json`. Key deps:

**dependencies:**
- `@bytelyst/fastify-core`, `@bytelyst/config`, `@bytelyst/errors`, `@bytelyst/datastore`
- `@bytelyst/cosmos`, `@bytelyst/auth`, `@bytelyst/fastify-auth`, `@bytelyst/backend-config`
- `@bytelyst/backend-telemetry`, `@bytelyst/backend-flags`, `@bytelyst/field-encrypt`
- `@bytelyst/logger`, `fastify`, `zod` (^3.24.2), `jose`

**devDependencies:**
- `typescript` (^5.7.3), `tsx`, `vitest`, `eslint` (^9.0.0), `@bytelyst/testing`

**scripts:**
- `"dev": "tsx watch src/server.ts"`
- `"build": "tsc"`
- `"typecheck": "tsc --noEmit"`
- `"test": "vitest run"`
- `"start": "node dist/server.js"`

### Step 2: Create `backend/tsconfig.json`

Extend `../../tsconfig.base.json` if available, otherwise use ecosystem standard:
```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["src/**/*.test.ts", "dist"]
}
```

### Step 3: Create `backend/src/lib/` Files

Create these standard lib files (copy patterns from `../learning_ai_notes/backend/src/lib/`):

1. **`product-config.ts`** — Read `../../shared/product.json`, export `productConfig`, `PRODUCT_ID`
2. **`config.ts`** — Zod env schema (PORT=4020, HOST, NODE_ENV, DB_PROVIDER, JWT_SECRET, CORS_ORIGIN, COSMOS_ENDPOINT, COSMOS_KEY, COSMOS_DATABASE)
3. **`auth.ts`** — Re-export JWT extraction from `@bytelyst/auth` or `@bytelyst/fastify-auth`
4. **`request-context.ts`** — `getUserId(req)`, `getRequestProductId(req)` helpers
5. **`errors.ts`** — Re-export from `@bytelyst/errors`
6. **`datastore.ts`** — `@bytelyst/datastore` provider init (Cosmos or memory via `DB_PROVIDER`)
7. **`cosmos-init.ts`** — Register all Cosmos containers on startup (effo_identities, effo_efforts, effo_habits, effo_streaks, effo_insights)
8. **`field-encrypt.ts`** — Field encryption singleton from `@bytelyst/field-encrypt`
9. **`telemetry.ts`** — Telemetry event buffer stub from `@bytelyst/backend-telemetry`
10. **`feature-flags.ts`** — Feature flag registry stub from `@bytelyst/backend-flags`

### Step 4: Create `backend/src/server.ts`

Fastify entrypoint using `createServiceApp()` from `@bytelyst/fastify-core`:
- Register CORS, auth, health endpoint
- Register cosmos-init on startup
- Register route modules (added in Phase 3)
- `startService()` on port 4020

### Step 5: Create `backend/src/diagnostics.test.ts`

Standard diagnostic health-check test (copy from any ecosystem backend).

### Step 6: Create Config Files

- `backend/.env.example` — All env vars with defaults
- `backend/.gitignore` — `dist/`, `node_modules/`, `.env`
- `backend/eslint.config.js` — ESLint 9 flat config (copy from `../learning_ai_notes/backend/`)
- `backend/vitest.config.ts` — Vitest config
- `backend/Dockerfile` — Multi-stage Fastify build (copy from `../learning_ai_notes/backend/Dockerfile`)

### Step 7: Add Vite Proxy

Add to `vite.config.ts` (in the root, this is the ONE root file you may touch):
```ts
server: {
  proxy: {
    '/api': 'http://localhost:4020'
  }
}
```

### Step 8: Verify Phase 1

```bash
cd backend && pnpm install && pnpm run typecheck && pnpm run build && pnpm test
```

Commit: `feat(backend): Phase 1 backend scaffold with Fastify 5 + @bytelyst/* packages`

---

## Phase 3: Domain Modules

### Module Pattern

Each module lives in `backend/src/modules/<name>/` with three files:
- `types.ts` — Zod schemas + TypeScript types, Cosmos doc shape
- `repository.ts` — CRUD operations via `@bytelyst/datastore` `getCollection()`
- `routes.ts` — Fastify route handlers with Zod validation
- `routes.test.ts` — Vitest tests using `buildTestApp()` pattern

Every Cosmos document MUST include `productId: "efforise"`.

### Module 1: `identities`

Container: `effo_identities`, partition: `/userId`

| Field | Type | Description |
|-------|------|-------------|
| id | string | UUID |
| userId | string | Owner |
| productId | "efforise" | Required |
| name | string | "Writer", "Athlete", etc. |
| parentId | string? | For tree structure |
| description | string | What this identity means |
| color | string | Accent color |
| icon | string | Emoji or icon key |
| order | number | Sort order |
| createdAt | string | ISO timestamp |
| updatedAt | string | ISO timestamp |

Routes: GET /api/identities, POST /api/identities, GET /api/identities/:id, PATCH /api/identities/:id, DELETE /api/identities/:id, PATCH /api/identities/:id/reorder

### Module 2: `efforts`

Container: `effo_efforts`, partition: `/userId`

| Field | Type | Description |
|-------|------|-------------|
| id | string | UUID |
| userId | string | Owner |
| productId | "efforise" | Required |
| identityId | string | Which identity this effort supports |
| habitId | string? | Optional link to habit |
| type | "micro" \| "medium" \| "deep" | Effort depth |
| description | string | What was done |
| durationMinutes | number? | Optional duration |
| loggedAt | string | When the effort happened |
| createdAt | string | ISO timestamp |

Routes: GET /api/efforts, POST /api/efforts, GET /api/efforts/:id, PATCH /api/efforts/:id, DELETE /api/efforts/:id, GET /api/efforts/stats (aggregation)

### Module 3: `habits`

Container: `effo_habits`, partition: `/userId`

| Field | Type | Description |
|-------|------|-------------|
| id | string | UUID |
| userId | string | Owner |
| productId | "efforise" | Required |
| identityId | string | Which identity this habit supports |
| name | string | "Write 500 words", "Run 3 miles" |
| frequency | "daily" \| "weekly" \| "custom" | How often |
| customDays | number[]? | If frequency=custom, which days (0-6) |
| targetCount | number | Times per period |
| reminderTime | string? | HH:MM for push notification |
| isActive | boolean | Can be paused |
| createdAt | string | ISO timestamp |
| updatedAt | string | ISO timestamp |

Routes: GET /api/habits, POST /api/habits, GET /api/habits/:id, PATCH /api/habits/:id, DELETE /api/habits/:id

### Module 4: `streaks`

Container: `effo_streaks`, partition: `/userId`

| Field | Type | Description |
|-------|------|-------------|
| id | string | UUID |
| userId | string | Owner |
| productId | "efforise" | Required |
| habitId | string | Which habit |
| currentStreak | number | Current consecutive days |
| longestStreak | number | All-time record |
| lastLogDate | string | YYYY-MM-DD |
| totalCompletions | number | All-time count |
| updatedAt | string | ISO timestamp |

Routes: GET /api/streaks, GET /api/streaks/:habitId, POST /api/streaks/:habitId/log (increment)

### Module 5: `insights`

Container: `effo_insights`, partition: `/userId`

| Field | Type | Description |
|-------|------|-------------|
| id | string | UUID |
| userId | string | Owner |
| productId | "efforise" | Required |
| type | "weekly_summary" \| "coaching" \| "milestone" | Insight type |
| title | string | Display title |
| body | string | Insight content (markdown) |
| relatedIdentityIds | string[] | Referenced identities |
| generatedAt | string | ISO timestamp |
| dismissedAt | string? | If user dismissed |

Routes: GET /api/insights, GET /api/insights/:id, POST /api/insights/:id/dismiss

### Step: Register All Modules

Update `server.ts` to register all 5 route modules. Update `cosmos-init.ts` to register all 5 containers.

### Step: Create `backend/src/test-helpers.ts`

Standard `buildTestApp()` helper for Vitest (copy pattern from `../learning_ai_notes/backend/src/test-helpers.ts`).

### Verify Phase 3

```bash
cd backend && pnpm run typecheck && pnpm test && pnpm run build
```

Commit: `feat(backend): Phase 3 domain modules — identities, efforts, habits, streaks, insights`

---

## Done Signal

When complete, tell the user: **"Window 2 complete. Backend with 5 domain modules is ready. Window 3 can now build web screens against these APIs. Window 5 (mobile) can also start."**
