# TrainVault — Agent Operating Rules

> This file is loaded on EVERY turn. Keep it under 200 lines. Never paste spec detail here.
> Copy this file to `.cursorrules` as well (identical content) if using Cursor.

## 1. What this repo is
Internal video access-management portal. Admin uploads/orders training videos and grants
access; learners (Regional Manager / Set Manager) watch them in-browser. Videos must not be
downloadable. Access can auto-expire.

## 2. Context discipline (token budget is a hard constraint)
- **Never** scan the repo broadly. No `grep -r` across `src/`, no "read all files to understand".
- Read ONLY the files listed in the active phase prompt's `SCOPE` block, plus `docs/SPEC.md`
  §index when you need a contract name.
- Load at most ONE `docs/*.md` shard per task. Shards: `SPEC.md`, `VIDEO.md`, `EMAIL.md`,
  `SECURITY.md`, `UI.md`.
- If you believe you need a file outside `SCOPE`, STOP and ask. Do not read it speculatively.
- Types are the shared language. Refer to `@trainvault/contracts` type names
  (e.g. `VideoDTO`, `IssueStreamSessionResponse`) instead of re-describing shapes in prose.
- Before any change touching >3 files, output a PLAN first (file list + 1 line each) and wait
  for `go`. No code in the plan.
- End every completed phase by appending 5–15 lines to `docs/DECISIONS.md`. That file is how the
  next session gets context without re-reading code. Append only; never rewrite history.

## 3. Stack — fixed, do not substitute
| Layer | Choice |
|---|---|
| Monorepo | pnpm workspaces (`apps/web`, `apps/api`, `apps/worker`, `packages/contracts`) |
| Frontend | Vite + React 19 + TypeScript + Tailwind v4 (the existing Figma Make prototype) |
| Player | `hls.js` (MSE). Native HLS only as Safari fallback. |
| Backend | NestJS 11 + TypeScript |
| DB | PostgreSQL 16 + Prisma |
| Queue | BullMQ + Redis |
| Transcode | ffmpeg (system binary) in `apps/worker` |
| Storage | S3-compatible (MinIO in dev). Private bucket, no public ACL, ever. |
| Auth | HttpOnly cookie session (opaque token in Redis + DB). **No JWT in localStorage.** |
| Email | Nodemailer SMTP adapter + provider adapters behind one interface |
| Validation | `zod` schemas in `packages/contracts`, shared by both ends |

## 4. Non-negotiable invariants
1. **No raw video URL ever reaches the browser.** Only short-TTL, session-scoped playlist /
   segment / key URLs. Originals live in a private bucket path the API never proxies.
2. **Every access decision is server-side.** The client hiding a button is not access control.
   Effective access = `userGrant ∪ roleGrant`, minus expiry, evaluated on every request.
3. **Expiry is checked at request time, not login time.** An expired user with a live session is
   rejected on the next API call.
4. **Every stream key request is logged** (user, video, session, IP, UA, timestamp).
5. **Destructive ops are soft-deletes.** No `DELETE FROM` on videos, users, or events.
6. No secrets in code or in committed `.env`. `.env.example` only.
7. All list endpoints paginate. Default 25, max 100.
8. Timestamps are `timestamptz`, stored UTC, rendered in `Asia/Kolkata` by default.

## 5. Code conventions
- TS `strict: true`. No `any`. No non-null `!` except immediately after a guard.
- NestJS: one feature = one module dir with `*.controller.ts`, `*.service.ts`, `*.repository.ts`,
  `dto/`. Controllers hold zero business logic.
- Errors: throw typed domain errors; a global filter maps them to
  `{ error: { code, message, details? } }`. Never leak stack traces or SQL.
- React: no data fetching in components — use TanStack Query hooks in `src/api/`.
- Reuse the prototype's design tokens from `guidelines/Guidelines.md`. Do not invent colours,
  radii, or a component library. Radius is 6px everywhere; primary is `#2563EB`.
- File length ceiling 300 lines. Split before exceeding.
- Tests: Vitest. Every auth, access-resolution, expiry, and stream-token function needs unit
  tests. UI does not need tests.

## 6. Hierarchy of authority (highest wins)
1. This file
2. The active phase prompt in `docs/prompts/`
3. `docs/SPEC.md` and its shards
4. Existing code in the repo
5. Your own preferences — lowest. If you want to deviate, ask; don't refactor silently.

## 7. Definition of done for any phase
- [ ] `pnpm -r typecheck` clean
- [ ] `pnpm -r lint` clean
- [ ] Tests for the phase pass
- [ ] `.env.example` updated if new config was added
- [ ] `docs/DECISIONS.md` appended
- [ ] A 5-line summary: what changed, what's stubbed, what the next phase needs

## 8. Things you must never do
- Never add a package not named above without asking.
- Never expose a `download` attribute, a direct `<video src>` to storage, or a presigned URL
  with TTL > 120s.
- Never write a migration that drops a column containing data.
- Never claim the video is "impossible to download" in code comments or UI copy — see
  `docs/VIDEO.md` §0 for the honest threat model.
- Never mock or stub something and report it as complete. Say it's stubbed.
