diff --git a/01 Projects/video-accessibility/Multi-Tenant Audit — Handover.md b/01 Projects/video-accessibility/Multi-Tenant Audit — Handover.md new file mode 100644 index 0000000..c05be1f --- /dev/null +++ b/01 Projects/video-accessibility/Multi-Tenant Audit — Handover.md @@ -0,0 +1,147 @@ +--- +title: Video Accessibility — Multi-Tenant Audit Handover +date: 2026-04-29 +tags: + - video-accessibility + - security + - saas + - handover +status: in-progress +--- + +# Multi-Tenant Audit — Handover + +> [!success] PR-1 MERGED (branch: `feat/multi-tenant-isolation`) +> Committed `4949873` · Pushed to Bitbucket. Run the migration script before deploying to prod. + +--- + +## Контекст + +Платформа — AI SaaS для генерации accessibility-материалов (CC, AD, SDH) из видео. Клиент Oliver Internal, сервер optical-web-1. Аудит выявил **100+ дефектов** в изоляции по тенантам и UX для всех ролей (PM, Linguist, Reviewer, Production). + +Полный план: [[snoopy-watching-fountain]] (plan file в `.claude/plans/`) + +--- + +## Роли в системе + +| Роль | Зона ответственности | +|------|---------------------| +| **ADMIN** | Платформенный оператор, видит всё | +| **PROJECT_MANAGER** | Создаёт проекты, назначает команду, финальный ревью, доставка клиенту | +| **PRODUCTION** | Загружает видео, настраивает jobs, мониторит AI-пайплайн, retry falls | +| **LINGUIST** | Первая стадия QC: правит VTT, глоссарий | +| **REVIEWER** | Вторая стадия QC: апрув/реджект с категориями | +| **CLIENT** | Только read-only через share-link (не имеет навигации в основном UI) | + +--- + +## Что сделано — PR-1 «Multi-tenancy foundations» + +### Ключевые изменения + +| Файл | Что изменилось | +|------|---------------| +| `core/dependencies.py` | `get_user_org_ids()` + `assert_job_in_user_org()` — ADMIN→None, staff→orgs или `[]` (больше нет `None` для LINGUIST/REVIEWER/PRODUCTION без тимы) | +| `models/job.py` | Поле `organization_id: Optional[str]` | +| `models/review_note.py` | Поле `organization_id: Optional[str]` | +| `models/vtt_version.py` | Поле `organization_id: Optional[str]` | +| `models/audit_log.py` | Поле `organization_id: Optional[str]` | +| `routes_jobs.py` | `GET /jobs/{id}` — org-check для ВСЕХ ролей; bulk-delete/approve/return-to-qc скипают чужие jobs; `POST /jobs` принимает `client_id`, ставит `organization_id`; **удалён `time.sleep(1)`** | +| `routes_review_notes.py` | `assert_job_in_user_org` в каждом хендлере; PM добавлен в allowedRoles | +| `routes_vtt_versions.py` | `_assert_job_access` хелпер в каждом хендлере; PM добавлен | +| `routes_websockets.py` | `/ws/jobs/{job_id}` проверяет org перед accept | +| `services/audit_logger.py` | `log_action` и `log_job_action` принимают `organization_id` | +| `migrations/2026_05_add_organization_id.py` | Бэкфилл + индексы | +| `tests/unit/test_cross_tenant_isolation.py` | 10 unit-тестов | + +### Закрытые MT-issues +`MT-1` `MT-2` `MT-3` `MT-4` `MT-5` (частично) `MT-6` `MT-7` `MT-8`(ws) `W-8`(sleep) + +--- + +## Что осталось по плану + +### 🔴 PR-2 «Workflow blockers» — СЛЕДУЮЩИЙ (в работе) + +> [!warning] Нельзя деплоить другие улучшения до PR-2: broken UX блокирует ежедневную работу + +| ID | Описание | Файл(ы) | +|----|----------|---------| +| W-1 | PM-дашборд — нет ветки `project_manager` | `Dashboard.tsx` | +| W-2 | Linguist/Reviewer не редиректят на свою страницу при логине | `App.tsx`, auth routes | +| W-3 | Sidebar badges с количеством pending задач | `Sidebar.tsx` | +| W-4 | Назначение команды при создании job (сейчас только после AI) | `NewJob.tsx`, `QCDetail.tsx` | +| W-5 | Project defaults не подставляются при выборе existing project | `NewJob.tsx:309-311` | +| W-6 | Two-stage QC order не enforced (linguist→reviewer) | `language_qc.py:965-981` | +| W-7 | Email уведомления жёстко выключены | `tasks/notify.py:111` | +| W-9 | PM не может complete job | `routes_jobs.py` | +| W-10 | Final Review `/admin/final` недоступен PM из sidebar | `Sidebar.tsx` | +| W-11 | Production dashboard отсутствует | `Dashboard.tsx` | + +### 🟡 PR-3 «PM productivity» + +- Deadline column + overdue colors в JobsList +- Clone project/job +- Bulk assign linguist +- Server pagination (сейчас size:10000!) +- "Needs my attention" filter presets +- Project share read-only link для CLIENT + +### 🟡 PR-4 «Linguist & Reviewer productivity» + +- Glossary inline в VttEditor +- Diff AI baseline vs current edit +- Optimistic locking (concurrent edits → 409) +- Reviewer reviewed-cues tracking + gate approve +- Reject reason categories (не free-text) +- Hotkeys (Cmd+Enter save, `]`/`[` prev/next cue) + +### 🟢 PR-5 «Polish & tech debt» + +- Единый `lib/jobStatusMessages.ts` +- Native `` для captions (сейчас кастомный div) +- AD audio sync с video +- CSRF защита +- Unified upload size limit (сейчас 3 разных значения в коде) + +--- + +## Архитектурные решения + +> [!tip] Принцип проверки org-изоляции +> `assert_job_in_user_org` возвращает **404 (не 403)** — чтобы не раскрывать факт существования чужого job. Три уровня fallback: `organization_id` → `project.client_id` → legacy `client_id==user.id`. + +> [!note] GCS пути (MT-14 — ещё не сделано) +> Файлы в GCS хранятся без org-префикса (`{job_id}/source.mp4`). Signed URLs не проверяют org. Это остаётся открытой уязвимостью — нужен lazy-migration путей и валидация в `get_signed_url`. + +> [!warning] MT-15 — два параллельных authz-стека +> `core/authz.py` (новый, частично используется в `routes_organizations.py`) vs legacy `dependencies.py`. Нужна консолидация, но это большая миграция. Пока добавлены функции в `dependencies.py`. + +--- + +## Команды + +```bash +# Запуск локально +./scripts/run-local.sh + +# Запустить миграцию (внутри backend-контейнера) +python -m migrations.2026_05_add_organization_id + +# Тесты (внутри контейнера) +cd backend && poetry run pytest tests/unit/test_cross_tenant_isolation.py -v + +# Lint +cd backend && ruff check . +``` + +--- + +## Открытые риски перед деплоем + +1. **Запустить миграцию** `2026_05_add_organization_id.py` — без неё все существующие jobs имеют `organization_id=None` → все staff (linguist/reviewer/production) с memberships не увидят старые jobs +2. **Проверить что у существующих staff есть memberships/teams** — иначе они увидят 0 jobs и заблокируются +3. **MT-12** (`_assert_client_access` legacy bypass для PM) — ещё не закрыт +4. **MT-16** (JWT без `org_ids`) — каждый запрос ходит в Mongo для memberships; 60s cache есть, но stale-window открыт