vault backup: 2026-04-29 19:56:12
This commit is contained in:
parent
351cd2383c
commit
360e4e543c
3 changed files with 263 additions and 71 deletions
|
|
@ -9,8 +9,6 @@ tags:
|
|||
|
||||
# Next Session Start Prompt
|
||||
|
||||
> Copy the block below and paste as the first message in the new Claude Code session.
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
|
|
@ -20,93 +18,59 @@ tags:
|
|||
|
||||
Платформа — AI SaaS для генерации accessibility-материалов (CC, AD, SDH) из видео.
|
||||
Рабочая директория: /Users/ai_leed/Documents/Projects/Oliver/video-accessibility
|
||||
Текущая ветка: feat/pm-productivity (от main)
|
||||
Последний коммит: 1bf0fb9
|
||||
Текущая ветка: feat/pm-productivity → мержим в main, новая ветка: feat/pr7-security-and-briefs
|
||||
Последний коммит: 08fcb4d
|
||||
|
||||
## Полный план
|
||||
## Документы для чтения (В ЭТОМ ПОРЯДКЕ)
|
||||
|
||||
Читай план: /Users/ai_leed/.claude/plans/snoopy-watching-fountain.md
|
||||
(копия в Obsidian: [[Full-Implementation-Plan]])
|
||||
1. Хендовер (история всего сделанного):
|
||||
/Users/ai_leed/Library/Mobile Documents/iCloud~md~obsidian/Documents/VadymSamoilenko/01 Projects/video-accessibility/Multi-Tenant Audit — Handover.md
|
||||
|
||||
Хендовер с историей что сделано и что осталось:
|
||||
/Users/ai_leed/Library/Mobile Documents/iCloud~md~obsidian/Documents/VadymSamoilenko/01 Projects/video-accessibility/Multi-Tenant Audit — Handover.md
|
||||
2. План на эту сессию (PR-7):
|
||||
/Users/ai_leed/Library/Mobile Documents/iCloud~md~obsidian/Documents/VadymSamoilenko/01 Projects/video-accessibility/PR-7-Plan.md
|
||||
|
||||
## Что уже сделано (не трогать)
|
||||
|
||||
### PR-1 (merged): Multi-tenancy foundations
|
||||
- org-изоляция во всех эндпоинтах (assert_job_in_user_org)
|
||||
- WebSocket membership check
|
||||
- organization_id в моделях
|
||||
- миграция 2026_05_add_organization_id.py
|
||||
### PR-2 (commit c7a6f13): PM/Production dashboards, workflow blockers
|
||||
### PR-3+4+5 (commit 1bf0fb9): PM tools, deadline, clone, glossary, VTT diff, CSRF
|
||||
### PR-6 (commit 08fcb4d): WS real-time updates, per-cue AD playback, beforeunload guard,
|
||||
team auto-assignment, broadcast_to_job/broadcast_to_user bugfix
|
||||
|
||||
### PR-2 (pushed feat/workflow-blockers, commit c7a6f13):
|
||||
- PM dashboard + Production dashboard
|
||||
- role-based login redirect (linguist/reviewer → /qc/queue)
|
||||
- sidebar badges (live QC count)
|
||||
- two-stage QC enforcement (_assert_can_approve)
|
||||
- email notifications enabled
|
||||
- PM в complete_job, Final Review, Audit Log
|
||||
## Задачи этой сессии (PR-7)
|
||||
|
||||
### PR-3+4+5 (pushed feat/pm-productivity, commit 1bf0fb9):
|
||||
- W-5: project defaults autofill в NewJob
|
||||
- PM-9: quick filter presets в JobsList
|
||||
- PM-10: PM в canManageJobs / New Job button / Final Review action
|
||||
- PM-12: server pagination (size:50, page state, pagination controls)
|
||||
- PM-1: deadline field в Job model + JobsList column + overdue red + NewJob date picker
|
||||
- PM-2: POST /jobs/{id}/clone + useCloneJob hook + Clone button
|
||||
- R-4: reject categories (pill-select) в QCDetail reject modal
|
||||
- R-2: reviewed_cues/total_cues + progress bar + 80% approve gate + /mark-cue-reviewed
|
||||
- L-9: Cmd+S saves full VTT, Escape closes modals
|
||||
- T-1: JOB_STATUS_LABELS / getJobStatusLabel unified in jobStatusMessages.ts
|
||||
- T-14: settings.upload_max_video_bytes + settings.upload_signed_url_ttl_hours
|
||||
КРИТИЧНО — делать первыми:
|
||||
|
||||
## Что осталось (приоритет сверху вниз)
|
||||
1. MT-11: Cross-org assignment (language_qc.py:182 assign_linguist + :307 assign_reviewer)
|
||||
→ добавить проверку job_org == linguist_org перед сохранением
|
||||
|
||||
### PR-3 (продолжение)
|
||||
- PM-7: Bulk assign linguist
|
||||
- Бэкенд: нет endpoint. Нужен POST /jobs/{id}/languages/bulk-assign с {linguist_id, reviewer_id, language_codes[]}
|
||||
- Фронт: кнопка "Assign defaults to all languages" в QCDetail (берёт PM defaults из проекта) + select-all checkbox
|
||||
- Файлы: backend/app/services/language_qc.py, routes_language_qc.py, frontend/src/routes/admin/QCDetail.tsx
|
||||
2. MT-12: Legacy PM bypass (routes_clients.py:487-488 _assert_client_access)
|
||||
→ удалить строку `if user.role in (CLIENT, PROJECT_MANAGER): return`
|
||||
|
||||
- PM-15: Read-only share link для CLIENT
|
||||
- Новый файл backend/app/api/v1/routes_share.py
|
||||
- POST /share/projects/{id} → создаёт ShareToken (TTL 7d, read-only) в MongoDB
|
||||
- GET /share/{token} → публичная страница (без auth) с video player + download links
|
||||
- Новый фронт компонент frontend/src/routes/share/ShareView.tsx
|
||||
- Зарегистрировать роут в App.tsx (публичный, не обёрнут в PrivateRoute)
|
||||
3. MT-13: Glossary без org-чека (routes_glossaries.py:47-56)
|
||||
→ вынести _assert_client_access в core/dependencies.py, добавить в glossary handlers
|
||||
|
||||
### PR-4 (остаток)
|
||||
- L-1/L-6: Glossary inline в VttEditor + CPS warning
|
||||
- Файл: frontend/src/components/VttEditor.tsx
|
||||
- Глоссарий: подтянуть через apiClient.getGlossaries(clientId), подсветить preferred terms в cue text
|
||||
- CPS: warn если characters_per_second > 20 рядом с cue
|
||||
4. MT-15 (partial): Заменить get_accessible_project_ids на get_membership_context
|
||||
только в routes_jobs.py list_jobs + get_job, и routes_clients.py
|
||||
|
||||
- L-3: Optimistic locking (concurrent edits → 409)
|
||||
- Backend: routes_jobs.py PATCH /jobs/{id}/vtt добавить If-Match check по updated_at
|
||||
- Frontend: передавать заголовок If-Match: {updated_at}, показывать toast при 409
|
||||
ПОТОМ:
|
||||
|
||||
- L-7: Diff AI baseline vs current
|
||||
- Backend: при первом сохранении VTT после AI проставить тег "ai_baseline" на VTT version
|
||||
- Frontend: кнопка "Compare to AI" в QCDetail, side-by-side diff view
|
||||
5. W-13: Поле Job.failure + structured failure UI в JobDetail.tsx
|
||||
|
||||
### PR-5 (остаток)
|
||||
- T-6: Native <track> для captions в VideoWithCaptions.tsx
|
||||
- Убрать кастомный div overlay (строки ~215-221)
|
||||
- Заменить на <track kind="captions" src={blobUrl}> с Blob URL из VTT контента
|
||||
6. W-14: GET /admin/production/failures + bulk-retry endpoint + /dashboard/production/failures
|
||||
|
||||
- T-7: AD audio sync с video в VideoWithCaptions.tsx
|
||||
- <audio> для AD уже есть (~строки 272-286) но без синхронизации
|
||||
- Привязать play/pause/seek через ref на <video> и <audio>
|
||||
7. W-12: Job Brief workflow — новая коллекция job_briefs + routes_briefs.py + BriefsList.tsx + NewJob интеграция
|
||||
|
||||
- T-11: CSRF
|
||||
- Backend: double-submit cookie pattern в main.py
|
||||
- Frontend: api.ts interceptor добавляет X-CSRF-Token из cookie
|
||||
## Deploy-блокеры (напомни мне про них перед пушем)
|
||||
|
||||
## Инструкции
|
||||
- Запустить 5 миграций: 2026-04-27-000000 через 2026-04-28-000003
|
||||
- Проверить db.memberships.countDocuments() > 0
|
||||
- Проверить organization_id на старых jobs
|
||||
|
||||
1. Перед началом прочти хендовер в Obsidian (ссылка выше)
|
||||
2. Работаем на ветке feat/pm-productivity
|
||||
3. После каждого блока работы: коммит → пуш → обновить Obsidian хендовер
|
||||
4. Использовать скиллы: fastapi-expert для backend, react-expert для frontend, security-reviewer для CSRF
|
||||
5. После завершения всего — создать финальный хендовер с инструкцией по деплою
|
||||
## Команды
|
||||
|
||||
cd /Users/ai_leed/Documents/Projects/Oliver/video-accessibility
|
||||
git checkout -b feat/pr7-security-and-briefs
|
||||
cd backend && ruff check .
|
||||
cd frontend && npm run type-check && npm run lint
|
||||
```
|
||||
|
|
|
|||
225
01 Projects/video-accessibility/PR-7-Plan.md
Normal file
225
01 Projects/video-accessibility/PR-7-Plan.md
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
---
|
||||
title: PR-7 — Security Fixes + Failure UI + Job Brief
|
||||
date: 2026-04-29
|
||||
tags:
|
||||
- video-accessibility
|
||||
- plan
|
||||
- in-progress
|
||||
status: planned
|
||||
---
|
||||
|
||||
# PR-7 — Следующая сессия
|
||||
|
||||
> [!warning] Приоритет: Security-фиксы ПЕРЕД любыми новыми фичами
|
||||
> MT-11, MT-12, MT-13 — реальные дыры в multi-tenancy. Без них нельзя деплоить.
|
||||
|
||||
---
|
||||
|
||||
## Блок 1 — Security (MT-11 / MT-12 / MT-13)
|
||||
|
||||
### MT-11 — Cross-org assignment
|
||||
|
||||
> [!danger] Критично
|
||||
> **Файл:** `backend/app/services/language_qc.py` — функции `assign_linguist` (строка 182) и `assign_reviewer` (строка 307)
|
||||
> **Проблема:** PM org A назначает linguist из org B → linguist видит чужой job в очереди.
|
||||
|
||||
**Фикс** — после `linguist_doc = await db.users.find_one(...)` добавить:
|
||||
|
||||
```python
|
||||
job_org = job_doc.get("organization_id")
|
||||
linguist_org = linguist_doc.get("organization_id")
|
||||
if job_org and linguist_org and job_org != linguist_org:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Assignee is not in the same organization as this job"
|
||||
)
|
||||
```
|
||||
|
||||
То же самое в `assign_reviewer`.
|
||||
|
||||
---
|
||||
|
||||
### MT-12 — PM/CLIENT legacy bypass
|
||||
|
||||
> [!danger] Критично
|
||||
> **Файл:** `backend/app/api/v1/routes_clients.py` — функция `_assert_client_access` (строка 476)
|
||||
> **Проблема:** строка `if user.role in (CLIENT, PROJECT_MANAGER): return` пускает ЛЮБОГО PM к ЛЮБОМУ client.
|
||||
|
||||
**Текущий код (конец функции):**
|
||||
```python
|
||||
mem = await db.memberships.find_one({"user_id": str(user.id), "organization_id": client_id})
|
||||
if mem:
|
||||
return
|
||||
# Legacy fallback for pre-migration users
|
||||
if user.role == UserRole.PROJECT_MANAGER and client_id in (user.pm_client_ids or []):
|
||||
return
|
||||
if user.role in (UserRole.CLIENT, UserRole.PROJECT_MANAGER): # УДАЛИТЬ ЭТУ СТРОКУ
|
||||
return
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
```
|
||||
|
||||
**Фикс:** удалить последнее `if user.role in (...)` перед `raise`. Строку с pm_client_ids оставить.
|
||||
|
||||
---
|
||||
|
||||
### MT-13 — Glossary без org-чека
|
||||
|
||||
> [!danger] Критично
|
||||
> **Файл:** `backend/app/api/v1/routes_glossaries.py:47-56`
|
||||
> **Проблема:** `GET /clients/{client_id}/glossaries` не вызывает `_assert_client_access` — любой аутентифицированный пользователь читает глоссарий чужой org.
|
||||
|
||||
**Фикс:**
|
||||
1. Вынести `_assert_client_access` из `routes_clients.py` в `core/dependencies.py` как shared helper
|
||||
2. Импортировать в `routes_glossaries.py`, добавить вызов в начало каждого handler'а с `client_id` path param
|
||||
|
||||
---
|
||||
|
||||
## Блок 2 — MT-15: Частичная консолидация authz
|
||||
|
||||
> [!bug] Два параллельных authz-стека
|
||||
> `core/authz.py` (новый, `MembershipContext`) используется только в `routes_organizations.py`.
|
||||
> `core/dependencies.py` (legacy, `get_accessible_project_ids`) — во всех остальных роутах.
|
||||
|
||||
**Стратегия — не ломать всё сразу:**
|
||||
1. В `routes_jobs.py` — в `list_jobs` и `get_job` заменить `get_accessible_project_ids` на `get_membership_context` + `accessible_org_ids()`
|
||||
2. В `routes_clients.py` — заменить `_assert_client_access` на dependency `require_org_role(OrgRole.VIEWER)`
|
||||
3. `get_accessible_project_ids` помечаем deprecated, не удаляем — остальные роуты переводить в следующих PR
|
||||
|
||||
---
|
||||
|
||||
## Блок 3 — W-13: Failure Recovery UI
|
||||
|
||||
> [!tip] Backend
|
||||
> **Новое поле `Job.failure`** в `backend/app/models/job.py`:
|
||||
> ```python
|
||||
> class JobFailure(BaseModel):
|
||||
> step: str # "ai_processing" | "translation" | "tts" | "render"
|
||||
> reason_code: str # "GEMINI_503" | "TTS_QUOTA" | "GCS_TIMEOUT" ...
|
||||
> reason_text: str # human-readable
|
||||
> failed_at: datetime
|
||||
> retry_count: int = 0
|
||||
> ```
|
||||
>
|
||||
> В Celery tasks (`ingest_and_ai.py`, `translate_and_synthesize.py`) при поимке исключения записывать в `job.failure`.
|
||||
|
||||
> [!tip] Frontend
|
||||
> **Файл:** `frontend/src/routes/jobs/JobDetail.tsx:670-684`
|
||||
>
|
||||
> Вместо одной кнопки "Retry TTS":
|
||||
> - Step name: AI Processing / Translation / TTS / Render
|
||||
> - Reason code + human text
|
||||
> - Failed at (datetime)
|
||||
> - Retry count (предупреждение если >3)
|
||||
> - Кнопки: **Retry** / **Escalate**
|
||||
|
||||
---
|
||||
|
||||
## Блок 4 — W-14: Bulk Failures Dashboard
|
||||
|
||||
> [!tip] Backend
|
||||
> - `GET /admin/production/failures` — все jobs в `*_failed` статусах, с `failure` полем
|
||||
> - `POST /admin/production/bulk-retry` — `{job_ids: []}`, перезапускает воркер
|
||||
|
||||
> [!tip] Frontend
|
||||
> - Новый роут `/dashboard/production/failures`
|
||||
> - Table: title / status / error step / reason / failed_at
|
||||
> - Multi-select + "Retry selected"
|
||||
> - Группировка по `reason_code` (accordion) — "10 jobs: TTS API 503"
|
||||
> - Sidebar badge у Production: кол-во failed jobs
|
||||
|
||||
---
|
||||
|
||||
## Блок 5 — W-12: Job Brief Workflow *(самый большой)*
|
||||
|
||||
> [!note] Концепция
|
||||
> PM создаёт brief (без файла) → Production видит в inbox → берёт в работу → загружает файл → brief конвертируется в обычный Job
|
||||
|
||||
**Новая коллекция `job_briefs`:**
|
||||
```python
|
||||
class JobBrief(BaseModel):
|
||||
id: str
|
||||
organization_id: str
|
||||
project_id: str
|
||||
created_by: str # PM user_id
|
||||
title: str
|
||||
languages: list[str]
|
||||
outputs: RequestedOutputs
|
||||
deadline: Optional[datetime]
|
||||
team: dict # {linguist_id?, reviewer_id?}
|
||||
brand_context: Optional[str]
|
||||
status: Literal["open", "claimed", "converted"]
|
||||
claimed_by: Optional[str] # Production user_id
|
||||
claimed_at: Optional[datetime]
|
||||
job_id: Optional[str] # заполняется после convert
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
```
|
||||
|
||||
**Backend endpoints — новый `routes_briefs.py`:**
|
||||
| Method | Path | Кто | Что |
|
||||
|--------|------|-----|-----|
|
||||
| POST | /briefs | PM/ADMIN | создать brief |
|
||||
| GET | /briefs | Production/PM/ADMIN | список (Production видит open, PM — свои) |
|
||||
| GET | /briefs/{id} | - | детали |
|
||||
| POST | /briefs/{id}/claim | Production | взять в работу (status → claimed) |
|
||||
| POST | /briefs/{id}/unclaim | Production | отказаться (status → open) |
|
||||
| POST | /briefs/{id}/convert | Production | загрузить файл → создать Job (brief.job_id, status → converted) |
|
||||
|
||||
**Job model:** добавить `brief_id: Optional[str]`
|
||||
|
||||
**Frontend:**
|
||||
- `BriefsList.tsx` — Production inbox (open briefs), кнопка "Claim"
|
||||
- `BriefDetail.tsx` — детали + upload form (берём NewJob.tsx как основу)
|
||||
- `NewJob.tsx` — `?brief_id=xxx` pre-populate поля
|
||||
- PM Dashboard — секция "My Briefs" (3 колонки: open / claimed / converted)
|
||||
- Sidebar badge Production — кол-во unclaimed briefs
|
||||
|
||||
---
|
||||
|
||||
## Deploy Checklist
|
||||
|
||||
> [!warning] Обязательно ПЕРЕД первым деплоем
|
||||
|
||||
```bash
|
||||
# Внутри backend-контейнера:
|
||||
python -m app.migrations.runner
|
||||
|
||||
# Должны выполниться (по порядку):
|
||||
# 2026-04-27-000000 add_project_manager_role
|
||||
# 2026-04-28-000000 create_memberships_collection
|
||||
# 2026-04-28-000001 backfill_memberships
|
||||
# 2026-04-28-000002 create_invitations_collection
|
||||
# 2026-04-28-000003 backfill_job_organization_id
|
||||
```
|
||||
|
||||
После миграции проверить:
|
||||
1. `db.memberships.countDocuments()` > 0
|
||||
2. Каждый linguist/reviewer/production имеет >= 1 membership запись
|
||||
3. Выборка 5-10 старых jobs — все имеют `organization_id`
|
||||
|
||||
---
|
||||
|
||||
## Что НЕ делаем в этой сессии
|
||||
|
||||
- **MT-14** (GCS org-prefix в путях) — требует lazy migration существующих файлов в GCS, отдельный PR
|
||||
- **MT-16** (JWT с org_ids) — требует force re-login всех пользователей, нужно явное ОК от стейкхолдеров
|
||||
- **MT-17** (Membership без team_id) — низкий приоритет
|
||||
- **PR-2/3** (per-file multi-upload settings, server-side progress) — UX, не безопасность
|
||||
- **PR-7/8** (queue depth widget, skip step) — следующий Production-PR
|
||||
|
||||
---
|
||||
|
||||
## Порядок работы
|
||||
|
||||
```
|
||||
[ ] 1. Блок 1: MT-11 → MT-12 → MT-13 (3 небольших security-фикса, ~1ч)
|
||||
[ ] 2. Блок 2: MT-15 partial (jobs + clients, ~1ч)
|
||||
[ ] 3. ruff check + type-check + commit
|
||||
[ ] 4. Блок 3: W-13 failure field + UI (~2ч)
|
||||
[ ] 5. Блок 4: W-14 failures dashboard (~2ч)
|
||||
[ ] 6. ruff check + type-check + commit
|
||||
[ ] 7. Блок 5: W-12 Job Brief (~4ч, самый долгий)
|
||||
[ ] 8. Финальный ruff + lint + commit + push
|
||||
[ ] 9. Обновить хендовер
|
||||
```
|
||||
|
||||
|
|
@ -482,3 +482,6 @@ tags: [daily]
|
|||
- 19:29 | `aimpress`
|
||||
- **Asked:** Set up Forgejo, Plane, and Maybe Finance on HomeLab with Docker and add them to home.ai-impress.com.
|
||||
- **Done:** Restarted Plane container and identified need for OpenAI API key configuration in Maybe Finance setup.
|
||||
- 19:55 | `video-accessibility`
|
||||
- **Asked:** Developer requested to continue video-accessibility project work on fe branch with plan and handover review.
|
||||
- **Done:** Retrieved project context and Obsidian CLI documentation to prepare for task implementation.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue