diff --git a/AGENTS.md b/AGENTS.md index f499c50..8fcd71a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -112,3 +112,43 @@ Before writing any code that uses a library API: Key versions to double-check: Prisma client API, Express 4 vs 5 routing, Zod v3 schema methods. + + +# Codex workflow notes + +These notes adapt the local `CLAUDE.md` guidance for Codex. + +## Tooling + +- Prefer `rg` / `rg --files` for search and file discovery. +- Prefer `sed`, `nl`, and other focused shell reads for file inspection. +- Use `multi_tool_use.parallel` when independent reads or searches can run at the same time. +- Use `apply_patch` for manual file edits. +- Do not rely on Claude-only `lean-ctx` tools unless they are explicitly available in the current session. + +## UI — Modal/Drawer close must refresh parent page + +Whenever you add or modify a modal/drawer (Ant Design `Modal`, `Drawer`, or any custom +overlay), both `onCancel` and `onClose` handlers MUST trigger a refresh of the data on +the page from which the modal/drawer was opened. Closing via the × button, Esc key, +backdrop click, or a "Cancel" footer button must all call the parent's data-loading +function (`load()`, `fetchX()`, `loadX(page)`, etc.). + +Rationale: the modal may have side effects (nested actions, auto-save, cascading +updates) that change server state even when the user "cancels". Forcing a refresh +keeps the page consistent with the server without requiring manual F5. + +Pattern: + +```tsx +// BAD + setOpen(false)} ... /> + +// GOOD + { setOpen(false); void load(); }} ... /> +``` + +Applies equally to custom footer "Отмена" buttons inside a Modal/Drawer form. +If the load function is defined inside a `useEffect` closure, extract it as a +top-level `useCallback` so it can be invoked from close handlers. + diff --git a/backend/src/prisma/migrations/20260426000000_ttbus0_event_outbox/migration.sql b/backend/src/prisma/migrations/20260426000000_ttbus0_event_outbox/migration.sql new file mode 100644 index 0000000..24a2f69 --- /dev/null +++ b/backend/src/prisma/migrations/20260426000000_ttbus0_event_outbox/migration.sql @@ -0,0 +1,28 @@ +-- TTBUS-0.1: transactional outbox and consumer deduplication tables. + +CREATE TABLE "event_outbox" ( + "id" TEXT NOT NULL, + "topic" TEXT NOT NULL, + "message_id" TEXT NOT NULL, + "type" TEXT NOT NULL, + "envelope" JSONB NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "sent_at" TIMESTAMP(3), + "attempts" INTEGER NOT NULL DEFAULT 0, + "last_error" TEXT, + + CONSTRAINT "event_outbox_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "processed_messages" ( + "consumer_group" TEXT NOT NULL, + "message_id" TEXT NOT NULL, + "processed_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "processed_messages_pkey" PRIMARY KEY ("consumer_group", "message_id") +); + +CREATE UNIQUE INDEX "event_outbox_message_id_key" ON "event_outbox"("message_id"); +CREATE INDEX "event_outbox_sent_at_created_at_idx" ON "event_outbox"("sent_at", "created_at"); +CREATE INDEX "event_outbox_message_id_idx" ON "event_outbox"("message_id"); +CREATE INDEX "processed_messages_processed_at_idx" ON "processed_messages"("processed_at"); diff --git a/backend/src/prisma/schema.prisma b/backend/src/prisma/schema.prisma index 3ba3580..a063fa2 100644 --- a/backend/src/prisma/schema.prisma +++ b/backend/src/prisma/schema.prisma @@ -1467,3 +1467,31 @@ model UserGroupSystemRole { @@index([groupId]) @@map("user_group_system_roles") } + +// ===== EVENT BUS / TRANSACTIONAL OUTBOX ===== + +model EventOutbox { + id String @id @default(uuid()) + topic String + messageId String @unique @default(uuid()) @map("message_id") + type String + envelope Json + createdAt DateTime @default(now()) @map("created_at") + sentAt DateTime? @map("sent_at") + attempts Int @default(0) + lastError String? @map("last_error") + + @@index([sentAt, createdAt]) + @@index([messageId]) + @@map("event_outbox") +} + +model ProcessedMessage { + consumerGroup String @map("consumer_group") + messageId String @map("message_id") + processedAt DateTime @default(now()) @map("processed_at") + + @@id([consumerGroup, messageId]) + @@index([processedAt]) + @@map("processed_messages") +} diff --git a/backend/src/shared/eventbus/envelope.ts b/backend/src/shared/eventbus/envelope.ts new file mode 100644 index 0000000..cf332a7 --- /dev/null +++ b/backend/src/shared/eventbus/envelope.ts @@ -0,0 +1,34 @@ +import { z } from 'zod'; + +export const EVENT_TOPICS = { + issues: 'tt.issues', + comments: 'tt.comments', + releases: 'tt.releases', + workflow: 'tt.workflow', + notificationsDlq: 'tt.notifications.dlq', +} as const; + +export type EventTopic = (typeof EVENT_TOPICS)[keyof typeof EVENT_TOPICS]; + +export const eventActorSchema = z.object({ + userId: z.string().uuid().nullable(), + ip: z.string().optional(), + userAgent: z.string().optional(), +}); + +export type EventActor = z.infer; + +export const eventEnvelopeSchema = z.object({ + v: z.literal(1), + messageId: z.string().uuid(), + type: z.string().min(1), + occurredAt: z.string().datetime(), + actor: eventActorSchema, + payload: z.record(z.unknown()), + meta: z.object({ + tenantId: z.string().nullable(), + correlationId: z.string().optional(), + }), +}); + +export type EventEnvelope = z.infer; diff --git a/backend/src/shared/outbox/outbox.service.ts b/backend/src/shared/outbox/outbox.service.ts new file mode 100644 index 0000000..341f668 --- /dev/null +++ b/backend/src/shared/outbox/outbox.service.ts @@ -0,0 +1,91 @@ +import { randomUUID } from 'node:crypto'; +import type { Prisma } from '@prisma/client'; + +import { type EventActor, type EventEnvelope, eventEnvelopeSchema } from '../eventbus/envelope.js'; + +export type PublishInTxOptions = { + correlationId?: string; + occurredAt?: Date; +}; + +const FORBIDDEN_PAYLOAD_KEYS = new Set([ + 'password', + 'passwordHash', + 'password_hash', + 'token', + 'accessToken', + 'access_token', + 'refreshToken', + 'refresh_token', + 'apiKey', + 'api_key', + 'secret', +]); + +const FORBIDDEN_PAYLOAD_KEYS_NORMALIZED = new Set( + [...FORBIDDEN_PAYLOAD_KEYS].map((key) => key.replace(/[_-]/g, '').toLowerCase()), +); + +function toJsonPayload(payload: Record): Record { + const serialized = JSON.stringify(payload); + if (serialized === undefined) { + throw new Error('Event payload must be JSON-serializable'); + } + return JSON.parse(serialized) as Record; +} + +function assertNoSecretKeys(value: unknown, path = 'payload') { + if (Array.isArray(value)) { + value.forEach((item, index) => assertNoSecretKeys(item, `${path}[${index}]`)); + return; + } + + if (typeof value !== 'object' || value === null) { + return; + } + + for (const [key, nestedValue] of Object.entries(value)) { + const normalizedKey = key.replace(/[_-]/g, '').toLowerCase(); + if (FORBIDDEN_PAYLOAD_KEYS_NORMALIZED.has(normalizedKey)) { + throw new Error(`Event payload must not contain secret field "${path}.${key}"`); + } + assertNoSecretKeys(nestedValue, `${path}.${key}`); + } +} + +export async function publishInTx( + tx: Prisma.TransactionClient, + topic: string, + type: string, + payload: Record, + actor: EventActor, + options: PublishInTxOptions = {}, +): Promise { + assertNoSecretKeys(payload); + const jsonPayload = toJsonPayload(payload); + + const messageId = randomUUID(); + const envelope: EventEnvelope = eventEnvelopeSchema.parse({ + v: 1, + messageId, + type, + occurredAt: (options.occurredAt ?? new Date()).toISOString(), + actor, + payload: jsonPayload, + meta: { + tenantId: null, + ...(options.correlationId && { correlationId: options.correlationId }), + }, + }); + + await tx.eventOutbox.create({ + data: { + topic, + messageId, + type, + envelope: envelope as unknown as Prisma.InputJsonObject, + }, + }); + + return messageId; +} diff --git a/backend/src/shared/outbox/processed-messages.service.ts b/backend/src/shared/outbox/processed-messages.service.ts new file mode 100644 index 0000000..75d9689 --- /dev/null +++ b/backend/src/shared/outbox/processed-messages.service.ts @@ -0,0 +1,28 @@ +import type { Prisma } from '@prisma/client'; + +import { prisma } from '../../prisma/client.js'; + +type PrismaLike = Pick | Prisma.TransactionClient; + +export async function markProcessedOnce( + consumerGroup: string, + messageId: string, + client: PrismaLike = prisma, +): Promise { + try { + await client.processedMessage.create({ + data: { consumerGroup, messageId }, + }); + return true; + } catch (err) { + if ( + typeof err === 'object' && + err !== null && + 'code' in err && + (err as { code?: string }).code === 'P2002' + ) { + return false; + } + throw err; + } +} diff --git a/backend/tests/outbox.service.test.ts b/backend/tests/outbox.service.test.ts new file mode 100644 index 0000000..94f7696 --- /dev/null +++ b/backend/tests/outbox.service.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, it } from 'vitest'; + +import { prisma } from '../src/prisma/client.js'; +import { EVENT_TOPICS, eventEnvelopeSchema } from '../src/shared/eventbus/envelope.js'; +import { publishInTx } from '../src/shared/outbox/outbox.service.js'; +import { markProcessedOnce } from '../src/shared/outbox/processed-messages.service.js'; + +describe('transactional outbox', () => { + it('writes a valid envelope inside the caller transaction', async () => { + const messageId = await prisma.$transaction((tx) => + publishInTx( + tx, + EVENT_TOPICS.issues, + 'ISSUE_CREATED', + { issueId: 'issue-1', projectId: 'project-1' }, + { userId: null }, + { occurredAt: new Date('2026-04-26T00:00:00.000Z'), correlationId: 'corr-1' }, + ), + ); + + const row = await prisma.eventOutbox.findUniqueOrThrow({ where: { messageId } }); + expect(row.topic).toBe(EVENT_TOPICS.issues); + expect(row.type).toBe('ISSUE_CREATED'); + expect(row.sentAt).toBeNull(); + expect(row.attempts).toBe(0); + + const envelope = eventEnvelopeSchema.parse(row.envelope); + expect(envelope).toMatchObject({ + v: 1, + messageId, + type: 'ISSUE_CREATED', + occurredAt: '2026-04-26T00:00:00.000Z', + actor: { userId: null }, + payload: { issueId: 'issue-1', projectId: 'project-1' }, + meta: { tenantId: null, correlationId: 'corr-1' }, + }); + }); + + it('rolls back the outbox row when the caller transaction fails', async () => { + await expect( + prisma.$transaction(async (tx) => { + await publishInTx( + tx, + EVENT_TOPICS.issues, + 'ISSUE_UPDATED', + { issueId: 'rollback-issue' }, + { userId: null }, + ); + throw new Error('force rollback'); + }), + ).rejects.toThrow('force rollback'); + + await expect( + prisma.eventOutbox.findFirstOrThrow({ + where: { + type: 'ISSUE_UPDATED', + envelope: { path: ['payload', 'issueId'], equals: 'rollback-issue' }, + }, + }), + ).rejects.toThrow(); + }); + + it('rejects payloads with secret-like fields before writing', async () => { + await expect( + prisma.$transaction((tx) => + publishInTx( + tx, + EVENT_TOPICS.issues, + 'ISSUE_UPDATED', + { issueId: 'issue-1', actorSnapshot: { password_hash: 'secret' } }, + { userId: null }, + ), + ), + ).rejects.toThrow('payload.actorSnapshot.password_hash'); + }); +}); + +describe('processed message deduplication', () => { + it('marks the first message and skips duplicates for the same consumer group', async () => { + const messageId = '8d6763ae-ded0-4cb7-9cc5-8bb6cbbd5f87'; + + await expect(markProcessedOnce('notifications-service', messageId)).resolves.toBe(true); + await expect(markProcessedOnce('notifications-service', messageId)).resolves.toBe(false); + await expect(markProcessedOnce('webhooks-service', messageId)).resolves.toBe(true); + }); +}); diff --git a/backend/tests/setup.ts b/backend/tests/setup.ts index 0a355e1..c04a08e 100644 --- a/backend/tests/setup.ts +++ b/backend/tests/setup.ts @@ -60,6 +60,8 @@ beforeAll(async () => { }); // Clean test database (respect foreign keys) + await prisma.processedMessage.deleteMany(); + await prisma.eventOutbox.deleteMany(); await prisma.auditLog.deleteMany(); await prisma.timeLog.deleteMany(); await prisma.comment.deleteMany(); diff --git a/docs/admin-vs-jira-comparison.html b/docs/admin-vs-jira-comparison.html new file mode 100644 index 0000000..f4e5cc0 --- /dev/null +++ b/docs/admin-vs-jira-comparison.html @@ -0,0 +1,1487 @@ + + + + +Сравнение TaskTime и коробочной JIRA — админка и функциональность + + + +
+ +
+

TaskTime MVP vs коробочная Jira — сравнительный анализ

+

Чего уже хватает для запуска, а чего нет. Два пласта: администрирование и бизнес-функциональность. Jira рассматривается без Marketplace-плагинов.

+
+
Дата отчёта24 апреля 2026 (v2 — добавлен пласт функциональности)
+
Ветка источникаfeat/release-card-enhancements
+
База Jira для сравненияJira Software DC 9.x (vanilla)
+
Автор анализаauto-scan модулей backend + frontend
+
Скоупадминка + функциональность end-user
+
План реализацииdocs/tz/MVP2JIRA/
+
+
+ +
+ Версия 4 (2026-04-24): v2 добавила функциональную часть, v3 — анализ Стэка & Enterprise на 5 000 users, v4 — отдельную вкладку «Yandex Tracker» с референсным сравнением (SaaS + Enterprise on-prem, РФ-альтернатива Jira). +
+ + + + +
+ +
+

Ключевое саммари — административная и функциональная части

+

Сколько закрыто от коробочной Jira, где дыры, где TaskTime уже впереди. Цифры получены обходом backend/src/modules, frontend/src/pages, prisma/schema.prisma и docs/user-manual/features/.

+ +
+
Есть
+
Частично
+
Нет
+
Лучше, чем в Jira
+
P0 блокер запуска
+
P1 критично
+
P2 важно
+
P3 nice-to-have
+
+ +
Администрирование
+
+
Покрыто от Jira
~55%
+
P0 гэпов
4
+
P1 гэпов
6
+
P2 гэпов
4
+
Сильнее Jira
5
+
+ +
Бизнес-функциональность
+
+
Покрыто от Jira
~60%
+
P0 гэпов
1
+
P1 гэпов
7
+
P2 гэпов
6
+
Сильнее Jira
7
+
+ +

+ Короткий вердикт: TaskTime ближе к Jira по сквозным абстракциям (Workflow / Screens / Field Schemas) и опережает её по релизному контуру (Release Workflows + Checkpoints + Burndown), AI/MCP, гибкому Role Scheme, бесшовной интеграции с GitLab и TTQL-поиску. Но на уровне end-user фич не хватает двух столпов: вложений и уведомлений/коммуникаций (watchers, @mentions, email, in-app bell). Плюс agile-отчётности (Velocity, CFD, Sprint Report). +

+
+ +
+

Где TaskTime уже сильнее коробочной Jira

+

Уникальные конкурентные преимущества — их нельзя терять при реализации гэпов.

+
+
+
Админка
+
+
    +
  • Role Scheme с 33 permission-флагами — плотнее, чем стандартная Permission Scheme Jira.
  • +
  • Field Schemas с 4-уровневым scope — GLOBAL / PROJECT / ISSUE_TYPE / PROJECT+ISSUE_TYPE из коробки.
  • +
  • Release Workflows + Statuses + Checkpoint Templates — отдельный lifecycle релиза с конструктором.
  • +
  • Custom Field type REFERENCE — собственный справочник (в Jira — только Marketplace).
  • +
  • UAT-тесты в админке — встроенный онбординг-чеклист для ролей, нет у Jira.
  • +
  • Monitoring Page — системные / endpoint / page метрики, auto-refresh 30s.
  • +
  • MCP-сервер как first-class — в Jira DC вообще не существует.
  • +
+
+
+
+
Функциональность
+
+
    +
  • Release Checkpoints — детерминированные release-gates с preview, risk scoring (LOW / MEDIUM / HIGH / CRITICAL), матрицей issue × checkpoint, аудитом. В Jira DC такого нет.
  • +
  • Release Burndown — actual + ideal, daily snapshots, retention. Jira DC — только sprint burndown.
  • +
  • TTQL с release-/checkpoint-функциямиviolatedCheckpoints(), releasePlannedDate(), currentUser(). Семантически богаче, чем JQL.
  • +
  • AI-декомпозиция Epic → Subtasks + оценка часов через Claude, сессии логируются с токенами и стоимостью.
  • +
  • GitLab webhooks из коробки — auto-transition по MR open/merge, без Marketplace-плагина.
  • +
  • Bulk-operations с friendly-пикерами и CSV/XLSX-экспортом — в Jira DC экспорт через отдельный шаг.
  • +
  • Teams (Business + Flow) + Operations + UAT Tests — орг-абстракции, которых в Jira нет.
  • +
+
+
+
+
+ +
+

Блокеры и критичные гэпы

+

Сгруппировано по уровню, раздельно для админки и функциональности. Ссылки на ТЗ (TTxxx-N) ведут в docs/tz/MVP2JIRA/.

+ +
+ + +
+
Админка — 4 P0 / 6 P1 / 4 P2
+ +
+

P0 Блокеры продакшна — 4 ТЗ

+
    +
  • Event BusTTBUS-0 (54ч). Kafka (KRaft) + transactional outbox. Prerequisite для Notifications и Webhooks.
  • +
  • Notifications (Email + in-app bell)TTNOTIF-1 (110ч). SMTP, Notification Scheme per-project, watchers, mention-parser.
  • +
  • ResolutionsTTRES-1 (33ч). 5 системных + TTQL.
  • +
  • Issue SecurityTTSEC-3 (52ч). PUBLIC / TEAM / PRIVATE + extras + permission.
  • +
  • Backup/Restore в приложении — закрывается DevOps (pg_dump, snapshots, Git).
  • +
+
+ +
+

P1 Критично для прома — 6 ТЗ

+
    +
  • SSO / OIDCTTSSO-1 (102ч).
  • +
  • Password Policy + Audit Log UI + BannerTTSEC-4 (63ч).
  • +
  • Priorities CRUD + Status reconciliationTTCORE-1 (63ч).
  • +
  • Components + fix/affectsVersionTTPROJ-1 (48ч).
  • +
  • Screens (контекст полей)TTSCR-1 (24ч).
  • +
  • Attachments (full feature)TTATTACH-1 (84ч).
  • +
+
+ +
+

P2 Важно, но не блокирует — 4 ТЗ

+
    +
  • 2FA / TOTPTTMFA-1 (68ч).
  • +
  • Webhooks (system + project)TTINTEG-1 (70ч).
  • +
  • Time Tracking parser + auto-stopTTCFG-1 (23ч).
  • +
  • Look & Feel (брендинг)TTBRAND-1 (32ч).
  • +
+
+
+ + +
+
Функциональность — 1 P0 / 7 P1 / 6 P2
+ +
+

P0 Блокеры прод-использования

+

На самом уровне задач, без чего нельзя эксплуатировать трекер.

+
    +
  • Attachments на задачах и комментариях — в модели нет Attachment, нет multer, нет upload-endpoint'а. Закрывается тем же ТЗ TTATTACH-1. Без вложений в реальном bug tracking невозможны скриншоты, логи, артефакты.
  • +
+
+ +
+

P1 Критично для полноценной работы команды

+
    +
  • Watchers + @mentions в комментариях — требует TTNOTIF-1. Без этого пользователи не узнают об изменениях.
  • +
  • In-app bell (история уведомлений) — через ту же TTNOTIF-1.
  • +
  • Velocity chart (средняя скорость команды за N спринтов). Требует Story Points или опоры на estimatedHours. Новое ТЗ
  • +
  • Sprint Report (commitment vs completed, added / removed scope) — базовая agile-отчётность. Новое ТЗ
  • +
  • Cumulative Flow Diagram — wip-поток по статусам во времени. Новое ТЗ
  • +
  • Persistent Activity stream per issue — сейчас audit_logs есть, но нет UI «история задачи на вкладке». Новое ТЗ
  • +
  • CSV/XLSX import задач — критично для миграции из Jira / Excel. Новое ТЗ
  • +
+
+ +
+

P2 Важно, но обходится

+
    +
  • Kanban swimlanes (по assignee / epic / priority).
  • +
  • WIP-лимиты на колонках доски с жёстким enforcement.
  • +
  • Roadmap / Timeline view (аналог Advanced Roadmaps в base Jira DC тоже нет, но ожидается).
  • +
  • Personal Dashboards с виджетами — сейчас единый read-only дашборд проекта.
  • +
  • Story Points как first-class — сейчас только estimatedHours.
  • +
  • Reactions / pinned / edit-history на комментариях.
  • +
+
+ +
+

P3 Nice-to-have / отложено

+
    +
  • Gantt chart / dependency graph.
  • +
  • Mobile app / offline mode.
  • +
  • Visual query builder (JQL builder) — у нас есть help-страница /search/help.
  • +
  • Shared Dashboards admin-управление.
  • +
  • Comment reactions.
  • +
  • Time in Status / Control Chart / Cycle Time report.
  • +
+
+
+ +
+
+ +
+

Итоговая оценка реализации гэпов

+

Сводная матрица по всем областям. Колонка «Эффект на MVP-ready» показывает, насколько закрытие гэпа приближает к production для корпоративного заказчика.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
СлойОбластьПокрытиеКрит-тьТрудозатратыЭффект на MVP-ready
АдминSMTP + Notification Scheme + in-app bell0%P0110ч + 54ч infraРазблокирует коммуникационный pillar: watchers, mentions, подписки, рассылки. Без него команда работает вслепую.
АдминResolutions0%P033чЗакрывает сценарий «Won't Do / Duplicate / Can't Reproduce». Нужен для корректных DONE-переходов и отчётности.
АдминIssue Security0%P052чCompliance + корп-контур. Без Issue Security невозможно скрыть HR/Security-задачи от остальной команды проекта.
Функц.Attachments (full feature)0%P084чБазовая функция трекера. Пока её нет — любая операционная работа (bug reports со скриншотами, логи) невозможна.
АдминSSO / OIDC0%P1102чТребование корп-контура. Без SSO невозможно автопровижн и массовый онбординг.
АдминPriorities CRUD + Status reconciliation~40%P163чУбирает хард-энум и двойное моделирование Issue.status / workflowStatusId.
АдминPassword Policy + Audit UI + Banner~30%P163чAudit таблица уже есть, но нет UI; policy захардкожен — нужна админ-страница.
АдминComponents + fix/affectsVersion0%P148чДля крупных проектов без компонент организовать работу практически нельзя.
АдминScreens (контекст полей)~40%P124чField Schema есть, но нет showOnCreate/Edit/View — добавляется 3 флагами.
Функц.Watchers + @mentions0%P1входит в TTNOTIF-1Разблокируется вместе с Notifications pillar.
Функц.Velocity / CFD / Sprint Report0%P1~80–120ч суммарноТрио классической agile-отчётности. Без них Scrum-команда лишена ретроспективных метрик.
Функц.CSV/XLSX import issues0%P1~30–40чОбязательно для миграции с Jira / Excel. Bulk export уже есть — import несимметричен.
Функц.Activity stream per issue (UI)~50%P1~20–30чДанные в audit_logs есть, нужна только агрегированная вкладка на странице задачи.
Админ2FA / TOTP0%P268чОжидается в корп-контуре, но паритет с Jira (там тоже нет из коробки).
АдминWebhooks admin UI~30%P270чЕсть notifier для checkpoints; нужен универсальный UI.
Функц.Kanban swimlanes + WIP0%P2~40–60чУвеличивает потолок сложности команд 20+ человек.
Функц.Story Points (first-class)(через hours)P2~20чЕсть estimatedHours; Points — отдельная культурная единица, добавляется полем / CF.
Функц.Personal Dashboards с виджетами0%P2~80чСейчас дашборд один на проект, read-only.
Функц.Roadmap / Timeline0%P2~100чВ vanilla Jira DC тоже нет — покрывается по запросу PM-команд.
+ +
+ Оценка сверху: закрытие всех P0 (≈ 333ч — админка + attachments) + всех критических P1 (≈ 430ч) = ~760 часов (≈ 4–5 команд-месяцев). + Это приведёт трекер к уровню, в котором его можно заменять Jira для любой среднего размера ИТ-организации (50–300 пользователей) без ощутимых потерь и с явным выигрышем по релизному контуру, AI, интеграциям и кастомизации полей. +
+
+ +
+

Рекомендованный план дозакрытия до MVP-ready

+

Минимально необходимый набор для закрытия P0 и наиболее болезненных P1. Объём — ориентировочные спринты (2 недели). Полный план: mvp2jira_plan.md.

+ +
+ 14 отдельных ТЗ в docs/tz/MVP2JIRA/. + Scenarios: single-team ~14 спринтов / two-teams ~9 спринтов. +
+ + + + + + + + + + + + + + + + + +
#ИнициативаКрит-тьОбъёмЧто входит
1Event Bus + Email + in-app notifications (pillar)P03 спринтаKafka outbox (TTBUS-0) + SMTP / Notification Scheme (TTNOTIF-1). Разблокирует watchers, mentions, webhook-notifier.
2ResolutionsP00.5 спринтаTTRES-1: модель + 5 системных + TTQL-фильтр.
3Issue SecurityP01 спринтTTSEC-3: PUBLIC / TEAM / PRIVATE + permission ISSUES_CHANGE_VISIBILITY.
4Attachments (full feature)P01.5 спринтаTTATTACH-1: модель + pluggable storage + thumbnails + inherit permissions + undo-delete.
5SSO / OIDCP12 спринтаTTSSO-1: несколько IdP, JIT provisioning, маппинг групп.
6Password Policy + Audit Log UI + BannerP11 спринтTTSEC-4: админ-UI + deep-diff drawer + баннер.
7Priorities CRUD + Status reconciliationP11 спринтTTCORE-1: enum → таблица + dual-field API на переход.
8Components + VersionsP11 спринтTTPROJ-1: M:M Components + fix / affectsVersion.
9Screens (контекст полей)P10.5 спринтаTTSCR-1: 3 флага на FieldSchemaItem + 7 virtual system fields.
10Velocity + Sprint Report + CFDP12 спринтаAgile-отчётность: три графика на основе sprint-scope snapshots и status-transition log.
11CSV/XLSX import задачP10.5 спринтаСимметрия к существующему bulk-export. Mapping: summary, type, status (по имени), assignee (email), custom fields.
12Activity stream per issue (UI)P10.5 спринтаНовая вкладка на странице задачи, агрегация из audit_logs. Данные уже индексированы.
+ +

+ Итог: ≈ 14 спринтов full-time на команду, чтобы закрыть все P0 + критические P1 обоих пластов. Если срезать до только P0, получается ≈ 6 спринтов (3 месяца). +

+
+ +
+ + + +
+ + + +
+

1. Система — общие настройки, безопасность, почта

+

Соответствует блокам «System», «Security» и «Mail» в Jira Administration.

+ + + + + + + + + + + + + + + + + + + + +
Раздел JiraАналог в TaskTimeСтатусКритичностьПримечание
General Configuration (base URL, app title)частично AdminSystemPageЧастичноP2Из настроек — только сессия (TTL) и JWT exp (read-only). Нет base URL, заголовка, tz, контактов админа.
Outgoing Mail (SMTP)НетP0Нет nodemailer, SMTP-клиента и модуля почты. Любые уведомления невозможны.
Incoming Mail HandlersНетP2Создание задачи из письма — отсутствует.
Session & JWTAdminSystemPageЕстьСкользящая сессия 5–10080 мин. JWT_EXPIRES_IN — ENV, только чтение.
Password Policyвалидация в коде (CVE-11)ЧастичноP1Правила хард-кодом, без админ-UI. Нет policy: срок, история, сложность, блокировка.
Captcha on loginНетP2Есть rate-limit, но CAPTCHA после N fails — нет.
2FA / MFAНетP2В коробочной Jira тоже нет — паритет, но корпоративно ожидается.
SSO / LDAP / SAML / OIDCНетP1Проверено: в backend нет упоминаний ldap / saml / oauth / oidc / sso.
Регистрация пользователей (toggle)AdminUsersPage → Registration switchЕстьПереключатель на странице пользователей.
Announcement BannerНетP1Нужен для maintenance-окна и релиз-анонсов.
Look and Feel (лого, цвета)НетP2Только Ant Design дефолт.
InternationalizationRU hard-codedНетP3Исключено из списка ТЗ — UI остаётся RU-only.
Attachments (функционал вложений)(нет функционала)НетP1В модели нет Attachment. Полноценная фича: TTATTACH-1 (доп. детали на вкладке «Функциональность»).
Backup / RestoreOut-of-scopeP3Закрывается через DevOps: pg_dump cron на хосте, снэпшоты дисков.
Services (scheduled jobs)НетP2Кроны работают, но управлять ими из UI нельзя.
LicenseНетP3Актуально только для коммерческой self-hosted модели.
+
+ +
+

2. Пользователи, группы и доступы

+

Соответствует «User Management» в Jira.

+ + + + + + + + + + + + +
Раздел JiraАналог в TaskTimeСтатусКритичностьПримечание
UsersAdminUsersPageЕстьCRUD, выдача temp-пароля, deactivate с проверкой зависимостей, mustChangePassword.
GroupsAdminGroupsPage + AdminGroupDetailPageЕстьГруппы + вложенность ролей проекта (ProjectGroupRole) через TTSEC-2.
Global PermissionsSystemRoleType (5 ролей)ЧастичноP1Роли SUPER_ADMIN / ADMIN / RELEASE_MANAGER / USER / AUDITOR, но нет отдельной матрицы глобальных разрешений.
Project Roles (глобально)AdminRolesPageЕстьCRUD глобальных ролей.
Role Schemes (per project)AdminRoleSchemesPage + PermissionMatrixDrawerЛучшеМатрица 33 permission-флагов на роль — плотнее чем стандартная Permission Scheme Jira.
Permission Scheme (независимая)НетP1Jira отделяет Permission Scheme от Role Scheme. TaskTime объединил — осознанное упрощение, но может мешать при требованиях «разные проекты — разные права для одной и той же роли».
User directories (LDAP / Crowd)НетP1Пользователи только внутренние.
Issue Security SchemeНетP0Нельзя скрыть отдельные задачи от части участников проекта.
+
+ +
+

3. Проекты

+ + + + + + + + + + +
Раздел JiraАналог в TaskTimeСтатусКритичностьПримечание
Projects (CRUD, lead, key)AdminProjectsPageЕстьУправление проектами и настройками.
Project CategoriesAdminCategoriesPageЕстьКатегоризация проектов.
Components (подсистемы внутри проекта)НетP1Нет моделей Component. Для крупных проектов — ограничение.
Versions / Fix VersionRelease + ReleaseItemЧастичноP2Есть концепция Releases с checkpoints, но нет полей fixVersion / affectsVersion на задаче в чистом виде.
Project TemplatesНетP3Нельзя клонировать проект с настройками как шаблон.
Default AssigneeНетP3В Jira — project lead / component lead / unassigned.
+
+ +
+

4. Таксономия задач — типы, приоритеты, статусы, резолюции

+ + + + + + + + + + + +
Раздел JiraАналог в TaskTimeСтатусКритичностьПримечание
Issue Types (CRUD, иконки)IssueTypeConfig + AdminIssueTypeConfigsPageЕстьНастраиваемый тип с иконкой / цветом.
Issue Type SchemesAdminIssueTypeSchemesPageЕстьПривязка набора типов к проекту (IssueTypeSchemeProject).
Priorities (CRUD)enum IssuePriorityНетP1Жёсткий enum CRITICAL / HIGH / MEDIUM / LOW. Нет админ-UI, нельзя добавить «Blocker / Trivial» или сменить иконки.
Statuses (глобальные)WorkflowStatus + AdminWorkflowStatusesPageЧастичноP1Есть таблица, но одновременно используется enum IssueStatus в Issue.status — двойное моделирование.
ResolutionsНетP0Нет ни модели, ни поля на задаче. Сценарий «закрыто как Won't Do / Duplicate» невозможен.
LabelsCustomFieldType.LABELЕстьЧерез кастом-поле типа LABEL.
Issue Link TypesAdminLinkTypesPageЕстьCRUD, inbound / outbound имена, флаг system.
+
+ +
+

5. Workflow и Screens

+ + + + + + + + + + +
Раздел JiraАналог в TaskTimeСтатусКритичностьПримечание
Workflows (steps + transitions)AdminWorkflowEditorPage + workflow-engineЕстьПолный конструктор с condition / validator / post-function. Системные workflow защищены.
Workflow SchemesAdminWorkflowSchemesPage + editorЕстьПривязка workflow → issue type → проект.
Transition ScreensAdminTransitionScreensPage + editorЕстьЭкраны, показываемые при выполнении перехода.
View / Edit / Create ScreensНетP1Поля на просмотр / редактирование / создание управляются Field Schema. TTSCR-1 закроет.
Screen Schemes / Issue Type Screen SchemesНетP2В Jira — связка создание / просмотр / правка → экран.
Release Workflows (уникальная фича)AdminReleaseWorkflowsPage + editor, AdminReleaseStatusesPage, checkpointsЛучшеДоп. ценность vs Jira — отдельный workflow для lifecycle релиза с checkpoint-типами и шаблонами.
+
+ +
+

6. Поля и схемы полей

+ + + + + + + + +
Раздел JiraАналог в TaskTimeСтатусКритичностьПримечание
Custom Fields (CRUD, типы)AdminCustomFieldsPageЕсть13 типов: TEXT / TEXTAREA / NUMBER / DECIMAL / DATE / DATETIME / URL / CHECKBOX / SELECT / MULTI_SELECT / USER / LABEL / REFERENCE.
Field ConfigurationsFieldSchema + AdminFieldSchemasPage + detailЕстьItems = поля, isRequired, showOnKanban. Draft / Active статус.
Field Configuration SchemesFieldSchemaBinding (GLOBAL / PROJECT / ISSUE_TYPE / PROJECT_ISSUE_TYPE)ЛучшеБогаче Jira: 4-уровневая область применения.
Default / System fields в UIвстроены в IssueЧастичноP3Системные поля (summary, description, assignee, priority) не редактируются через Field Schema.
+
+ +
+

7. Уведомления, события, почта

+ + + + + + + + + + +
Раздел JiraАналог в TaskTimeСтатусКритичностьПримечание
Events (системный реестр)НетP2Нет реестра событий «issue created / assigned / commented».
Event ListenersНетP2
Notification SchemesНетP0Нельзя определить «на событие X шлём email роли Y / пользователю Z».
In-app notificationsНетP1Нет колокольчика и истории уведомлений пользователя.
Outgoing Mail (SMTP)НетP0Корневая блокировка всего pillar'а уведомлений.
Mail TemplatesНетP2
+
+ +
+

8. Интеграции

+ + + + + + + + +
Раздел JiraАналог в TaskTimeСтатусКритичностьПримечание
DVCS (GitLab / GitHub / Bitbucket)integrations/gitlabЛучшеGitLab webhooks + pipeline-service из коробки. В Jira DC — через Marketplace.
Webhooks (админ-реестр)частично (есть webhook-notifier для checkpoints)ЧастичноP2Нет универсального UI «добавить свой webhook на событие».
Application Links / OAuth between appsНетP3
AI / MCPmcp/http-transport.ts + ai / ai-sessionsЛучшеMCP-сервер — уникальная фича vs Jira.
+
+ +
+

9. Аудит, мониторинг, обслуживание

+ + + + + + + + + + + +
Раздел JiraАналог в TaskTimeСтатусКритичностьПримечание
Audit Log (UI)audit_logs в БД; AdminCheckpointAuditPageЧастичноP1Таблица и индексы есть. Нет общего админ-UI просмотра / фильтра (TTSEC-4).
Monitoring / PerformanceAdminMonitoringPage + AdminMonitoringTabЕстьSystem / endpoint / page метрики, auto-refresh 30s, clear.
Logging & ProfilingНетP2Только через docker-логи.
Reindex (Lucene)P3Не требуется — поиск на PostgreSQL + TTQL compiler.
Integrity CheckerНетP2
Backup / RestoreOut-of-scopeP3DevOps (pg_dump + snapshots).
UAT-тестыuat-tests.data.tsЛучшеСвоя админ-фича, нет у Jira.
+
+ +
+ + + +
+ + + +
+ Про эту вкладку: здесь сравнивается то, что видит рядовой пользователь — задачи, доски, спринты, релизы, отчёты, поиск, комментарии. Админская конфигурация — на соседней вкладке. + База сравнения — Jira Software DC 9.x без Marketplace-плагинов. Это важно: многие «привычные» по Jira фичи (Advanced Roadmaps, Tempo, Structure, Portfolio) — это плагины, в коробочной Jira их нет. +
+ +
+

F1. Задачи и иерархия

+

Модель Issue, иерархия parent / child, типы, приоритеты, статусы, пользовательские поля.

+ + + + + + + + + + + + + + + + + + + + + + +
Фича JiraАналог в TaskTimeСтатусКритичностьПримечание
Создание / редактирование задачиIssueDetailPage + CreateIssueModalЕстьCRUD, inline-edit полей, permission checks через Role Scheme.
Типы задач (Epic / Story / Task / Bug / Subtask)IssueTypeConfig + IssueTypeSchemeЕстьПолностью настраиваемо. Эпики — просто тип в конфиге.
Parent / Subtasks (иерархия)Issue.parentId self-relationЕстьМногоуровневая иерархия (Epic → Story → Subtask). Без лимита глубины.
Issue Links (depends, blocks, relates)IssueLink + AdminLinkTypesPageЕстьCRUD типов линков, inbound / outbound тексты. Паритет с Jira.
Prioritiesenum CRITICAL / HIGH / MEDIUM / LOWЧастичноP1Enum хардкоднут. Иконок / цветов не настроить (см. TTCORE-1).
Statuses на задачеIssue.status (enum) + workflowStatusIdЧастичноP1Двойное моделирование — надо свести к одному источнику (TTCORE-1).
Resolution на задачеНетP0Нет поля / модели. Сценарий «закрыто как Duplicate» невозможен. TTRES-1.
Custom Fields per-issue13 типов, включая REFERENCEЛучшеREFERENCE (справочник) — в Jira DC нет, только через Marketplace.
Story Pointsесть estimatedHoursЧастичноP2Оценка в часах, не в story points. Для Scrum-команд, работающих в points, — ограничение.
Acceptance CriteriaIssue.acceptanceCriteriaЕстьОтдельное поле — удобнее Jira (там через description).
Due date / Start dateIssue.dueDateЧастичноP3Есть dueDate, нет startDate.
Attachments на задачеНетP0Ключевая дыра. Нет модели Attachment, нет upload. Нельзя приложить скриншот / лог. TTATTACH-1.
Bulk operations (change status / assignee / sprint)BulkStatusWizardModal, bulk-ops APIЛучшеFriendly-пикеры (см. TTBULK-1), CSV / XLSX экспорт из коробки.
Issue history (field changes)audit_logs в БДЧастичноP1Данные пишутся, но нет вкладки «История» на странице задачи. Нужен UI.
Flagged / impedimentsНетP3Можно эмулировать через custom field.
VotingНетP3Мало используется, не критично.
Cloning issueНетP3В Jira клонирование с переносом полей / субтасков.
Move Issue (между проектами)MoveIssueModalЕстьГотово, с валидацией совместимости схем.
+
+ +
+

F2. Доски — Kanban

+

Доска проекта для визуализации потока задач. BoardPage + KanbanBoard.

+ + + + + + + + + + + + + +
Фича Jira Agile BoardАналог в TaskTimeСтатусКритичностьПримечание
Kanban-доскаBoardPage + KanbanBoardЕстьDnD между колонками, фильтры по assignee / epic / type, real-time обновления.
Колонки = статусы workflowWorkflowStatus → columnsЕстьКонфигурируется через workflow editor.
Swimlanes (по assignee / epic / priority)НетP2Упрощает навигацию для 20+ участников. Сейчас только фильтры.
WIP-лимиты с enforcementНетP2Можно показывать, можно блокировать дроп. В Jira — soft warning.
Card cover images / attachments previewНетP3Blocked by Attachments (TTATTACH-1).
Quick-create с клавишиНетP3Сейчас через модалку.
Card customization (показ Story Points, due date, labels)частично (через showOnKanban в FieldSchema)ЧастичноP2Есть флаг, но настройка через админку, не самим пользователем.
Epic-панель / epic-link фильтрфильтр по epic-родителюЕстьЧерез обычный фильтр по parent.
Scrum-доска (sprint view)SprintIssuesDrawer + sprint-scoped filterЧастичноP2Нет отдельной strict sprint-board как в Jira; показываем через drawer и фильтр.
+
+ +
+

F3. Scrum — спринты и бэклог

+

Модели Sprint, SprintIssue. Страницы SprintsPage, GlobalSprintsPage, SprintPlanningDrawer.

+ + + + + + + + + + + + + +
Фича Jira ScrumАналог в TaskTimeСтатусКритичностьПримечание
CRUD спринта (plan / start / close)SprintsPage + SprintPlanningDrawerЕстьCreate / start / close, drag задач в sprint.
Cross-project Global SprintsGlobalSprintsPageЛучшеВ Jira DC cross-project boards — отдельная фича «Board by filter». У нас нативно.
Backlog view (ранжирование)drag-and-drop, orderIndexЕстьПорядок сохраняется через orderIndex.
Sprint GoalНетP3Отдельное поле для цели спринта.
Burndown chartчастично (release-level есть)ЧастичноP2Release-burndown есть, sprint-burndown на базовом уровне.
Velocity chartНетP1Ключевая agile-метрика (avg delivered за N спринтов).
Sprint Report (commit vs completed)НетP1added / removed scope, что не сделано, что перенесено.
Sprint capacity / team commitmentНетP2Капасити команды на спринт по часам / поинтам.
Retrospective / Sprint Review артефактыНетP3В Jira тоже слабо — делается плагинами.
+
+ +
+

F4. Релизы и чекпоинты — уникальная зона

+

Самая сильная часть TaskTime. Модели Release, ReleaseItem, Checkpoint, CheckpointRun. Страницы ReleasesPage, GlobalReleasesPage.

+ + + + + + + + + + + + + + +
Фича Jira ReleaseАналог в TaskTimeСтатусКритичностьПримечание
Версии (CRUD)Release + ReleaseItemЕстьПлюс статусы, даты, ответственные.
fixVersion / affectsVersion на задачечастично через releaseId (single)ЧастичноP1fixVersion ≡ releaseId (single), affectsVersion — M:M в TTPROJ-1.
Release Hub (обзор версии)GlobalReleasesPage + Release CardЕстьСводная карточка с метриками, чекпоинтами, задачами.
Release Notes generationНетP2Автосборка заметок по задачам в версии.
Release Workflow (lifecycle релиза)AdminReleaseWorkflowsPageЛучшеНет в Jira. Конструктор с статусами, переходами.
Release Checkpoints (quality gates)Checkpoint / CheckpointRun / CheckpointCriterionЛучшеНет в Jira. 5 типов критериев + TTQL + combined; preview; risk scoring LOW / MEDIUM / HIGH / CRITICAL; матрица issue × checkpoint.
Release BurndownReleaseBurndown + daily snapshotsЛучшеВ Jira DC только sprint burndown. Actual + ideal + retention + manual backfill.
Risk scoring релизаrules-engine по чекпоинтам + violationsЛучшеДетерминированный расчёт risk-level по violated checkpoints.
Checkpoint AuditAdminCheckpointAuditPageЛучшеЛог всех запусков / переполнений, exportable.
Webhook-notifier на release-eventsrelease-webhook-notifier.serviceЕстьТочечный notifier (не общий webhooks-реестр).
+
+ + + +
+

F6. Отчёты и дашборды

+

Слабое место относительно Jira. Сейчас только 2 отчёта + единый read-only Dashboard.

+ + + + + + + + + + + + + + + + + +
Фича Jira ReportsАналог в TaskTimeСтатусКритичностьПримечание
Задачи по статусамAdmin → Отчёты → Задачи по статусамЕстьФильтр по проекту / спринту / периоду.
Задачи по исполнителямAdmin → Отчёты → Задачи по исполнителямЕстьВыявление перекоса нагрузки.
Velocity ChartНетP1Ключевой agile-отчёт.
Sprint Report (commit vs done)НетP1Scope-changes, что перенесено / отменено.
Cumulative Flow DiagramНетP1Накопительный поток задач по статусам.
Burndown / Burnup (sprint)частичноЧастичноP2Release-burndown — да; sprint-level — базово.
Burndown (release)ReleaseBurndownЛучшеВ Jira DC отсутствует.
Time Tracking reportчастично (My Time / TimePage)ЧастичноP2Сводки по пользователю / дню есть, нет кросс-команды / кросс-проекта.
Control Chart / Cycle Time / Time in StatusНетP2Метрики lean / kanban-команд.
Personal Dashboards с виджетами (drag-n-drop)НетP2Сейчас один общий DashboardPage read-only.
Shared DashboardsНетP3В Jira — публикация дашборда ролям / группам.
Gadgets / widgets marketplaceНетP3Концепция плагинных гаджетов.
Release-level risk / violations matrixAdminCheckpointAuditPage + violationsЛучшеНовый тип отчётности, в Jira нет.
+
+ +
+

F7. Учёт времени

+

Интегрированный таймер и ручной лог часов. Модель TimeLog, страница TimePage.

+ + + + + + + + + + + + +
Фича Jira Time TrackingАналог в TaskTimeСтатусКритичностьПримечание
Original / Remaining estimateIssue.estimatedHoursЧастичноP2Один оценочный слот. В Jira — original + remaining.
Work logs (manual entries)TimeLog CRUDЕстьРаботает через TimePage.
Live timer (start / stop)таймер, 1 active per userЛучшеВ Jira DC — нет, плагин Tempo.
Auto-stop таймераНетP2TTCFG-1: auto-stop через N часов.
Парсер формата (1w 2d 3h)НетP2TTCFG-1: Jira-style парсер.
My Time / daily aggregationTimePageЕстьПерсональная сводка дня.
Timesheet (team-wide таблица)НетP2Кросс-команда недельная / месячная.
Billing rates / costsНетP3Из коробки в Jira тоже нет.
+
+ +
+

F8. Коллаборация — комментарии, watchers, mentions, вложения

+

Крупная дыра. Без уведомлений и вложений трекер остаётся CRM-style, а не Jira-style.

+ + + + + + + + + + + + + + + +
Фича JiraАналог в TaskTimeСтатусКритичностьПримечание
Комментарии (CRUD)Comment CRUD + permissionsЕстьAdd / edit own / delete, role-based permissions.
@mentions в комментарияхНетP1Парсер-лексер + нотификация упомянутому. Закрывается TTNOTIF-1.
Watchers на задачеНетP1Нет модели Watcher. Закрывается TTNOTIF-1.
Notifications (in-app bell)НетP1Нет колокольчика и истории. Закрывается TTNOTIF-1.
Email notificationsНетP0Нет SMTP. Закрывается TTNOTIF-1.
Attachments (upload)НетP0Нет модели, нет storage, нет multer. TTATTACH-1.
Thumbnails / preview вложенийНетP1Зависит от TTATTACH-1.
Comment reactionsНетP3В Jira есть эмоджи-реакции (Cloud).
Edit-history / pinned commentsНетP2Улучшение UX.
Rich-text editor (Markdown / WYSIWYG)базовыйЧастичноP3Базовый редактор без таблиц / code blocks на уровне Jira.
Activity stream per issue (UI-вкладка)audit_logs (данные) / нет UIЧастичноP1Данные пишутся, нужна отдельная вкладка на странице задачи.
+
+ +
+

F9. Интеграции, AI, уникальные фичи

+

Зона преимущества TaskTime. MCP, AI-декомпозиция, GitLab из коробки, Teams / Operations / UAT.

+ + + + + + + + + + + + + + + + +
ФичаАналог в TaskTimeСтатусКритичностьПримечание
GitLab (webhook → auto-transition)integrations/gitlab + webhooks + pipeline-serviceЛучшеMR open / merge → статус задачи. В Jira DC — только через Marketplace.
GitHub / Bitbucket DVCSНетP3Можно добавить по запросу.
CI Pipeline DashboardPipelineDashboardPageЛучшеВизуализация пайплайнов — нет у Jira нативно.
AI — декомпозиция Epic → Subtasksai/ai.service + Anthropic providerЛучшеНет у Jira DC. Claude-based.
AI — оценка часов задачиai moduleЛучшеС логированием sessions / tokens / стоимости.
MCP-сервер (Model Context Protocol)mcp/http-transport + toolsЛучшеУникально. Позволяет подключить задачи к Claude / агентам.
Teams abstractionBusinessTeamsPage, FlowTeamsPageЛучшеБизнес-команды и flow-команды отдельно от User Groups. В Jira нет.
Operations (meta-проект)OperationsPage + OperationDetailPageЛучшеOps-задачи отдельно от продуктовых.
UAT Tests (роль-specific чеклисты)UatTestsPageЛучшеВстроенный онбординг-чеклист.
Slack / Teams notificationsНетP2Требует SMTP → webhook → channel. В Jira — плагины.
Email → create issueНетP3Incoming Mail Handlers.
Mobile appНетP3Jira имеет — у нас пока нет PWA.
+
+ +
+ + + +
+ + + +
+ Целевой сценарий: on-premise Astra Linux SE 1.7+ / Red OS 7.3+, корпоративный контур РФ, + 5 000 зарегистрированных пользователей, из них ~1 500 DAU (30%) и ~500–800 concurrent в пиках. + Для такой нагрузки single-node compose из deploy/docker-compose.production.ymlне годится. + Текущие целевые NFR в docs/architecture/overview.md заявляют 2 500+ concurrent sessions; для 5 000 + users этого мало, нужна горизонтальная стратегия. +
+ +
+

E1. Стэк — как есть

+

Выжимка из backend/package.json, frontend/package.json, docker-compose.yml, deploy/docker-compose.production.yml, docs/architecture/overview.md.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
СлойТехнологияВерсияEnterprise-profileКомментарий
RuntimeNode.js20 LTSПодходитLTS до 2026-04. Поддержка clustering через node:cluster / PM2 / K8s.
ЯзыкTypeScript5.7ПодходитСтрогая типизация — плюс для enterprise аудита.
HTTP-фреймворкExpress4.21ПриемлемоЗрелый, но не самый быстрый. Для 5k users ок, в hot-path при тестировании проверить.
ORMPrisma6.5ПриемлемоБез connection pool из коробки — нужен PgBouncer. Query planner иногда генерит неэффективный SQL — мониторить.
ВалидацияZod3.24ПодходитИспользуется как на backend, так и на frontend — паритет схем.
БД (основная)PostgreSQL16 (alpine)Single-nodeНет streaming replication, нет read-replicas, нет pooler. Для 5k users — критический гэп.
Кэш / сессииRedis7 (alpine)Single-nodeAOF включён, но нет Sentinel/Cluster. Используется для rate-limiting и кэша.
Event BusНетKafka в плане TTBUS-0 (54ч). Критически важен для notifications/webhooks pillar'а.
AuthJWT (access + refresh) + bcryptjsПриемлемоRefresh-tokens в БД — OK. Нет SSO (TTSSO-1, 102ч), нет MFA (TTMFA-1, 68ч).
Метрикиprom-client + Prometheus + Grafana15.1Готовоdeploy/prometheus, deploy/grafana уже в репо. Дашборды готовы.
Логированиеstdout → docker logsНет агрегацииНет централизованного логирования (Loki / ELK / Graylog). Для 5k users обязательно.
ТрейсингНетНет OpenTelemetry / Jaeger / Tempo. Для SLO-debugging обязательно.
Cron / schedulernode-cron4.2In-processРаботает внутри backend-инстанса. При horizontal scaling — race: нужен leader election или внешний scheduler.
Webhooks outnative fetchCustomCheckpoint-notifier есть, общий реестр — TTINTEG-1 (70ч).
Reverse proxy / TLSNginxSingleRate limiting 5–30 r/s, security headers. Single instance — SPOF.
Frontend runtimeReact + Vite18.3 / 6.xПодходитSPA, Ant Design 5, Zustand 5, Recharts 3.
UI-состояниеZustand5.0ПодходитЛёгкий, без излишней оркестрации.
Drag & drop@hello-pangea/dnd18.xПодходитFork react-beautiful-dnd, поддержан.
CI / CDGitHub ActionsПриемлемо5 workflows (ci, build, deploy-staging, deploy-production, update-docs). Нет canary / blue-green.
КонтейнеризацияDocker ComposeSingle-hostНет K8s / Swarm. Horizontal scaling через compose не делается — нужен orchestrator.
AIAnthropic SDK 0.39ПодходитСессии логируются (tokens/стоимость), feature-flag FEATURES_AI.
MCP@modelcontextprotocol/sdk 1.29ПодходитОтдельный сервис в compose, HTTP transport на 3002.
Pipeline-serviceОтдельный Node/Express + свой PostgresStandaloneПравильная изоляция CI-данных. Тоже single-node.
Secrets.env / docker envНет vaultДля корпоративного контура нужен Vault / Conjur / Kube Secrets + sealed.
+
+ +
+

E2. Текущая prod-постура (как деплоится сейчас)

+

Что реально видно в deploy/docker-compose.production.yml + deploy/nginx/nginx.conf.template.

+ +
+
+
Что есть
+
    +
  • Единый Docker Compose на один хост: web (nginx+static), backend, postgres, redis, mcp, pipeline-service, pipeline-postgres.
  • +
  • Healthcheck'и на все сервисы, restart: unless-stopped.
  • +
  • Rate-limit в nginx (5 r/s auth, 30 r/s api), client_max_body_size 10m.
  • +
  • Скрипты backup-postgres.sh, restore-postgres.sh, rollback.sh в deploy/scripts/.
  • +
  • Prometheus + Grafana (есть конфиги в deploy/).
  • +
  • Feature-flags через ENV (FEATURES_*).
  • +
+
+
+
Что отсутствует на уровне инфры
+
    +
  • Нет репликации Postgres (одна instance → SPOF + только вертикальное масштабирование).
  • +
  • Нет PgBouncer — Prisma-пул упрётся в max_connections постгри при >5 backend replica.
  • +
  • Нет Redis HA (ни Sentinel, ни Cluster).
  • +
  • Нет K8s / orchestrator — масштабировать backend можно только добавив реплик в compose и балансировщик перед ним.
  • +
  • Нет centralized logging (нет Loki / ELK / Vector).
  • +
  • Нет tracing (нет OpenTelemetry).
  • +
  • Нет WAF / IPS перед nginx.
  • +
  • Нет CDN для фронтенд-статики (для 5k — не критично, но снижает latency региональным пользователям).
  • +
  • Нет blue-green / canary deploy; deploy-скрипт останавливает контейнеры.
  • +
  • Нет secrets vault; всё через .env-файлы на хосте.
  • +
+
+
+
+ +
+

E3. Оценка нагрузки для 5 000 пользователей

+

Грубый расчёт — для sizing'а. Реальные цифры нужно валидировать load-тестом с Playwright + k6 на staging.

+ +
+
Зарегистрировано
5 000
+
DAU (≈30%)
~1 500
+
Concurrent (peak)
500–800
+
RPS sustained
50–100
+
RPS peak
200–400
+
Issues в БД (2–3 года)
500k–1.5M
+
+ + + + + + + + + + + + + + + + + + + +
РесурсОценка для 5k usersСейчасДействие
Backend инстансы3–5 реплик × 1–2 vCPU / 1 GB1 репликаДобавить replicas, поставить nginx upstream или K8s Service. Удостовериться в stateless (сейчас node-cron in-process — требует leader election).
Postgres4–8 vCPU / 16–32 GB RAM / NVMe; shared_buffers 25%, effective_cache_size 75%; streaming replication + 1 read-replicasingle alpine без тюнингаТюнинг + primary-standby + PgBouncer (transaction pooling) между backend и postgres.
Connection pool~60–100 соединений PgBouncer; Prisma 10–20 на backend × 5 = 50–100Prisma прямо в PostgresP0. Без PgBouncer любая replica-атака упрётся в max_connections (default 100).
RedisSentinel (1 primary + 2 replicas + 3 sentinels) или Cluster (3 shard × 2 nodes)single nodeДля rate-limiting и кэша. Для session-store (если будет) — обязательно HA.
Kafka (будущее)3 broker × KRaft, 3 × 4 vCPU / 16 GB / SSDотсутствуетTTBUS-0 (54ч). Для notifications + webhooks. 3 ноды — минимум для quorum.
Frontend (статика)nginx + gzip + brotli + long-cache; опционально CDNесть nginxVite-бандл ~1.5–2 MB gzipped. Для РФ-контура CDN не обязателен.
Attachments storageS3-совместимый (MinIO для on-prem) или NFS; ~100 GB / год при средних 5 MB/задачане реализованоTTATTACH-1 предусматривает pluggable storage (local / s3).
Logs~30–100 GB / месяц при 100 RPS; retention 30–90 днейdocker-logsVector / Loki или ELK + S3 archive.
Бэкапы Postgrespg_dump / pg_basebackup ежедневно + WAL-archive; S3 / NFS; RPO ≤ 15 мин, RTO ≤ 1честь cron-скриптНужна регулярная drill-проверка restore.
+
+ +
+

E4. Матрица Enterprise-готовности

+

Что критично для 5 000 users и корпоративного контура. Сгруппировано по слоям.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
КатегорияТребованиеСтатусКрит-тьТрудозатратыКомментарий
RuntimeHorizontal scaling backend (stateless, 3–5 replicas)НетP0~40–60чУбрать in-process state (node-cron), вывести jobs в отдельный worker с leader-election (Redis Redlock или Postgres advisory lock).
RuntimeOrchestrator (K8s / Swarm / Nomad)НетP1~80–120чДля 5k users compose на одном хосте — SPOF. Минимально — K3s / managed K8s.
RuntimeGraceful shutdown + connection drainingЧастичноP1~16чДля zero-downtime deploy обязательно.
БДPgBouncer (transaction pooling)НетP0~16чБез этого больше 3–4 backend replicas не запустить.
БДPostgres HA (Patroni / streaming replication + automated failover)НетP0~80чМинимум primary + 1 sync standby + automatic failover (Patroni + etcd / consul).
БДRead-replica для reporting / отчётовНетP2~24чРазвязать тяжёлые TTQL-запросы и dashboard-агрегации от транзакционного primary.
БДDB tuning (shared_buffers, WAL, autovacuum)НетP1~16чСейчас только postgres:16-alpine без конфига.
БДPartitioning больших таблиц (audit_logs, time_logs)НетP2~40чПри 5k users audit_logs быстро превысит 10M записей. Partitioning по дате обязателен.
CacheRedis HA (Sentinel / Cluster)НетP0~40чRedis — сейчас в critical path для rate-limit. Его падение = DoS.
CacheRedis-based distributed locks (для cron и idempotency)НетP1~16чНужен для работы node-cron в кластере backend.
MessagingKafka (KRaft) для событий + outbox patternНетP054ч (TTBUS-0)Разблокирует Notifications (TTNOTIF-1) и Webhooks (TTINTEG-1). Уже в плане MVP2JIRA.
ObservabilityPrometheus + GrafanaЕстьКонфиги в deploy/prometheus и deploy/grafana. prom-client уже встроен.
ObservabilityCentralized logging (Loki + Promtail или ELK / Vector)НетP1~40чДля 5k users docker-logs не поисковый. Loki + Grafana — дешевле всего.
ObservabilityDistributed tracing (OpenTelemetry + Jaeger / Tempo)НетP1~40чДля debugging cross-service latency (backend ↔ pipeline-service ↔ postgres).
ObservabilitySLO / error-budget / alertingЧастичноP1~24чДашборды в Grafana — да; алерты (Alertmanager / Slack / email) — не настроены продакшн-grade.
ObservabilityAudit Log UI + экспортЧастичноP1~63ч (TTSEC-4)Данные есть, UI — нет. Обязательно для ФЗ-152 compliance и корп-безопасности.
SecuritySSO / OIDCНетP0102ч (TTSSO-1)Корп-контур без SSO невозможен. Keycloak / Blitz Identity / Azure AD.
SecurityMFA / TOTP (минимум для SUPER_ADMIN)НетP168ч (TTMFA-1)Для админов — обязательно.
SecuritySecrets vault (HashiCorp Vault / Conjur / Kube Secrets + sealed)НетP1~40чСейчас .env-файлы на хосте — не соответствует корп-стандартам.
SecurityWAF / IPS перед nginxНетP2~16чModSecurity / CrowdSec / облачный WAF.
SecurityImage scanning (Trivy / Grype в CI)НетP2~8чВ pipeline сейчас нет vulnerability scan Docker-образов.
SecurityTLS 1.2+ / mTLS между сервисамиЧастичноP2~16чTLS снаружи — да (nginx); между backend↔postgres / backend↔redis внутри сети — обычно plaintext.
SecurityIssue Security (visibility levels)НетP052ч (TTSEC-3)Корп-контур: HR / инфобез задачи не должны быть видны всем.
SecurityPassword policy + lockoutЧастичноP1входит в TTSEC-4Правила хардкодом, без admin-UI.
DRAutomated Postgres backups + off-siteЧастичноP1~16чСкрипт есть; нужна cron-регулярность + off-site copy + restore drill.
DRRPO / RTO документация + drillsНетP1~8чРегламент + квартальные учения восстановления.
DRGeo-redundancy (multi-DC / multi-region)НетP3по отдельному плануЕсть docs/architecture/geo-redundancy-plan.md — план для второго DC.
CI/CDBlue-green / canary / rolling deployНетP1~40чСейчас deploy-скрипт гасит контейнеры — downtime пара минут. Для 5k users непозволительно.
CI/CDDB migration safety (expand/contract, review-gate)ЧастичноP1~16чPrisma migrate deploy — OK. Но нет проверок обратной совместимости и auto-lock.
CI/CDLoad-testing в CI (k6 / Artillery)НетP2~40чНужен baseline-тест перед релизами.
AppFull-text search через pg_trgm / tsvector или ElasticsearchЧастичноP2~60чСейчас TTQL без FTS по description/comments. При 1M+ задач — обязательно.
AppPagination всех API-листингов с cursorЧастичноP2~30чПроверить, что все /api/*/list используют keyset-пагинацию.
AppIdempotency-keys для bulk и webhooksЧастичноP2~24чBulk-ops уже есть частично; webhooks in/out — обязательно.
AppRate limiting per-user (не только per-IP в nginx)ЧастичноP2~16чRedis-based token bucket per userId.
AppFile upload pipeline (virus scan, size limits, rate-limit)НетP1входит в TTATTACH-1После закрытия attachments.
+
+ +
+

E5. Целевая архитектура для 5 000 users (HA)

+

Минимальная HA-постура. Все слои без SPOF, каждый компонент — 3 ноды или primary+replica(+арбитр).

+ +
+                          ┌────────────────────────┐
+                          │  Corporate IdP (OIDC)   │
+                          │  Keycloak / Blitz / AD  │
+                          └───────────┬────────────┘
+                                      │ OIDC
+                                      ▼
+┌─────────────┐    ┌──────────────────────────────────────────────────┐
+│  Пользова-   │    │      LB / ingress (keepalived + nginx × 2)       │
+│  тели (5k)   │───▶│      TLS term + WAF (ModSecurity / CrowdSec)     │
+└─────────────┘    └────┬──────────────────────────┬──────────────────┘
+                        │                          │
+         ┌──────────────┴────┐            ┌────────┴──────────┐
+         ▼                   ▼            ▼                   ▼
+   ┌──────────┐     ┌──────────┐    ┌──────────┐     ┌──────────────┐
+   │ backend  │ ... │ backend  │    │  MCP     │     │ notifications│
+   │ (3–5)    │     │ (3–5)    │    │ (2)      │     │ service (2)  │
+   └────┬─────┘     └────┬─────┘    └────┬─────┘     └──────┬───────┘
+        │                │                │                 │
+        └────────┬───────┴───────┬────────┘                 │
+                 ▼               ▼                          ▼
+          ┌──────────────┐  ┌──────────────┐       ┌────────────────┐
+          │  PgBouncer    │  │  Redis        │       │  Kafka (KRaft)  │
+          │  (2, ha)      │  │  Sentinel     │       │  3 broker       │
+          └──────┬───────┘  │  3 primary    │       │  (TTBUS-0)      │
+                 ▼           │  + 3 replicas │       └────────────────┘
+     ┌──────────────────┐   └──────────────┘
+     │ PostgreSQL HA    │
+     │ Patroni + etcd   │
+     │ primary + 2 repl │◀── WAL archive → S3 / MinIO ─▶ off-site
+     └──────────────────┘                                       │
+                                                                ▼
+                                         Backups / Point-in-time recovery
+                                         RPO ≤ 15 мин, RTO ≤ 1ч
+
+ Observability:   Prometheus × 2  ─┐
+                  Grafana          │  Alertmanager → Slack / email / PagerDuty
+                  Loki + Promtail  │  (всем backend/mcp/pipeline — side-car логи)
+                  Tempo + OTel    ─┘
+
+ Attachments:     MinIO (S3-совместимое) — 3 ноды, erasure coding
+ Secrets:         HashiCorp Vault — 3 ноды с Raft storage
+ CI/CD:           GitHub Actions → registry → ArgoCD / deploy.sh → K8s (K3s)
+
+ +

+ Диаграмма отражает минимум для 5 000 users. Для 10 000+ — добавляется read-replica Postgres под отчёты, Kafka-кластер увеличивается до 5 broker'ов, backend вырастает до 6–8 реплик. +

+
+ +
+

E6. План дозакрытия до Enterprise-ready (5 000 users)

+

Только инфраструктурный трек. Продуктовые гэпы (TTNOTIF-1, TTSEC-3 и т.д.) — см. вкладки «Саммари» / «Админка». Часть инициатив пересекается.

+ + + + + + + + + + + + + + + + + + + + + +
#ИнициативаКрит-тьОбъёмЧто входит
1PgBouncer + Postgres tuningP00.5 спринтаTransaction pooling, настройка shared_buffers / WAL / autovacuum, установка max_connections ≈ 200.
2Postgres HA (Patroni / streaming replication + failover)P02 спринтаPrimary + sync standby + automatic failover через Patroni + etcd. WAL-archive на S3 / MinIO.
3Redis Sentinel (3+3)P01 спринтДля rate-limiting, locks и будущего session-store.
4Stateless backend + leader-election для jobsP01 спринтВынести node-cron в отдельный worker. Redis Redlock / Postgres advisory lock.
5Orchestrator (K3s / managed K8s)P12 спринтаДеплой через Helm charts. nginx ingress + Horizontal Pod Autoscaler.
6Event Bus (Kafka KRaft)P054ч (TTBUS-0)Prereq для notifications (TTNOTIF-1) и webhooks (TTINTEG-1). 3-node кластер.
7Centralized logging (Loki + Promtail)P11 спринтSide-car агенты на каждый сервис, хранение 30–90 дней, архив в S3.
8Distributed tracing (OpenTelemetry + Tempo)P11 спринтИнструментирование Express-middleware + Prisma.
9SSO / OIDCP0102ч (TTSSO-1)Интеграция с корп-IdP (Keycloak / Blitz / AD).
10Secrets vault (HashiCorp Vault)P11 спринтУбрать .env с хостов. Vault Agent для инжекта.
11Blue-green / rolling deployP10.5 спринтаGraceful shutdown + connection draining + K8s rolling update.
12Automated backup + restore drillP10.5 спринтаКвартальный drill с документированием RPO/RTO.
13Alertmanager + runbooksP10.5 спринтаПравила для SLI (p95 latency, 5xx rate, DB pool saturation) + playbook'и на on-call.
14Load-testing baseline (k6)P21 спринтСценарии: 500 concurrent + 5k ramp-up, регресс-тест на main.
15Partitioning audit_logs / time_logsP20.5 спринтаПо месяцам, retention 2–3 года hot, остальное — archive.
16Read-replica Postgres под отчётыP20.5 спринтаРазгрузить primary от тяжёлых TTQL-агрегаций.
+ +
+ Итог по infrastructure-треку: ≈ 10–12 спринтов (5–6 месяцев infra-команды) для полной HA + observability + security-постуры на 5 000 users. + Из них P0-минимум (pgbouncer + pg ha + redis ha + stateless + kafka + sso + issue security) ≈ 6 спринтов — без этого трекер на 5k users падает на первом же инциденте. + Стэк сам по себе (Node 20 / TS / Postgres 16 / Redis 7 / React 18) — зрелый и enterprise-grade; основная работа — эксплуатационная обвязка, а не замена технологий. +
+ +
+ Отличие этого плана от плана «MVP-ready»: там 760ч закрывают функциональные дыры (attachments, notifications, resolutions, security, velocity и т.д.). + Здесь — операционные. Оба трека можно вести параллельно; шесть пунктов (Event Bus TTBUS-0, SSO TTSSO-1, Issue Security TTSEC-3, Audit UI TTSEC-4, MFA TTMFA-1, Attachments TTATTACH-1) пересекаются и зачтутся в обоих. +
+
+ +
+ + + +
+ + + +
+ Почему это сравнение важно: Yandex Tracker — прямой конкурент TaskTime в российском корп-сегменте. + С 2022 года доступен в двух вариантах: Yandex 360 SaaS (yandex.cloud) и Yandex Tracker Enterprise (on-premise), + имеет сертификацию ФСТЭК и аттестации для КИИ. Для fintech-заказчика, выбирающего замену Jira, это реалистичная альтернатива TaskTime. + Данные о Yandex Tracker — по публичной документации yandex.cloud/docs/tracker на 2026-04. +
+ +
+

Y1. Контекст и позиционирование

+

Что такое Yandex Tracker в 2026 году, в каких конфигурациях поставляется, где пересекается с TaskTime и Jira.

+ +
+
+
Yandex Tracker — ключевые факты
+
    +
  • Модель: Yandex 360 SaaS (облако) + Enterprise on-prem (K8s-based).
  • +
  • Парадигма: «очереди» (queues) вместо проектов — немного иная ментальная модель, но похоже на Jira Project.
  • +
  • Типы задач, workflow, статусы, резолюции, приоритеты — всё есть, настраивается.
  • +
  • Language-first: UI и документация изначально на русском. RU-техподдержка.
  • +
  • Макросы + автодействия (триггеры) — аналог Jira Automation Rules.
  • +
  • Мобильные приложения iOS/Android.
  • +
  • Интеграции: GitLab, Bitbucket, Telegram-бот, Yandex 360 (Почта, Диск, Мессенджер), Yandex Forms (Service Desk).
  • +
  • Сертификация: ФСТЭК на Enterprise-поставку, поддержка КИИ-контура, ФЗ-152.
  • +
  • AI: интеграция с YandexGPT (генерация резюме, подсказки).
  • +
+
+
+
TaskTime — как позиционируется против
+
    +
  • Тот же рынок: РФ-fintech, on-prem Astra / Red OS, ФЗ-152.
  • +
  • Сильнее: Release Checkpoints + Release Burndown + Release Workflows (у Yandex Tracker нет структурированных релиз-gate).
  • +
  • Сильнее: AI-декомпозиция через Claude + MCP-сервер (Yandex Tracker имеет YandexGPT-подсказки, но не MCP / не decomposition-loop).
  • +
  • Сильнее: TTQL с release-/checkpoint-функциями, REFERENCE custom field, 33-permission Role Scheme.
  • +
  • Слабее: нет attachments, notifications pillar, мобильного приложения, Service Desk / SLA, автодействий-UI, дашбордов с виджетами.
  • +
  • Паритет: workflow, issue types, custom fields, issue links, bulk ops, TTQL vs Yandex Query Language.
  • +
+
+
+
+ +
+

Y2. Админка — сравнение

+

Сопоставление административных возможностей Yandex Tracker, TaskTime и Jira DC 9.x.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ФункцияYandex TrackerTaskTimeJira DC 9.xКомментарий
Users / GroupsЕстьЕстьЕстьПаритет по базовому user management.
Queues / ProjectsQueuesProjectsProjectsРазная терминология, одинаковая семантика.
Permissions per queue / projectЕсть33 флагаPermission SchemeTaskTime имеет более гранулярную матрицу; у Yandex Tracker — наборы ролей + доступы по типу.
SSO / SAML / OIDCЕстьНет (TTSSO-1)НетYandex Tracker поддерживает SAML и Yandex ID; в TaskTime запланировано (102ч).
LDAP / AD syncЕсть (Enterprise)НетНетВ Yandex Tracker Enterprise — штатно.
Issue Types (CRUD)ЕстьЕстьЕстьВсе три.
Statuses / WorkflowsЕстьЕстьЕстьКонструктор статусов и переходов — у всех.
Автодействия / triggersЕсть (макросы + автодействия)Частично (post-functions)Workflow post-functionsYandex Tracker ближе всего к Jira Cloud Automation — UI-конструктор триггеров. В TaskTime только backend post-functions.
Priorities (CRUD)ЕстьEnumЕстьTaskTime — хардкод enum, см. TTCORE-1.
ResolutionsЕстьНет (TTRES-1)ЕстьКритический гэп TaskTime.
Components / ВерсииЕстьRelease-вместо-versionЕстьTTPROJ-1 закроет.
Custom FieldsЕсть (~12 типов)13 типов + REFERENCEЕстьREFERENCE — собственный справочник — есть только у TaskTime.
Field Schemas / ConfigurationsЕсть (per queue)4-level scopeЕстьTaskTime — GLOBAL / PROJECT / ISSUE_TYPE / PROJECT_ISSUE_TYPE, богаче.
Issue Security / visibilityЕсть (access per queue / per issue)Нет (TTSEC-3)ЕстьYandex Tracker умеет скрывать отдельные задачи — TaskTime пока нет.
Notification SchemesЕстьНет (TTNOTIF-1)ЕстьКрупнейший админ-гэп TaskTime.
SMTPЕстьНетЕстьВ SaaS — готово, в Enterprise — в админке.
Audit Log UIЕстьЕсть данные, нет UIЕстьTTSEC-4 закроет.
Announcement BannerЕстьНетНетУ Yandex Tracker — «системные сообщения».
Look & Feel (брендинг)Есть (Enterprise)Нет (TTBRAND-1)НетYandex Tracker Enterprise — логотип, цвета, email-подпись.
Release Workflows (lifecycle релиза)НетЕстьНетТолько у TaskTime.
Release Checkpoints (quality gates)НетЕстьНетТолько у TaskTime.
UAT-тесты в админкеНетЕстьНетТолько у TaskTime.
+
+ +
+

Y3. Функциональность — сравнение

+

Сопоставление бизнес-возможностей, которые видит рядовой пользователь.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ФункцияYandex TrackerTaskTimeJira DC 9.xКомментарий
Создание / редактирование задачиЕстьЕстьЕстьПаритет.
Иерархия задач (Epic / Story / Subtask)Есть (эпики, подзадачи)parentId self-relationЕстьПаритет.
Attachments на задачах / комментарияхЕсть (лимит 50 МБ/файл)Нет (TTATTACH-1)ЕстьP0-гэп TaskTime. Интеграция с Yandex Disk у YT.
Комментарии + @mentionsЕсть + notificationsЕсть CRUD, нет mentionsЕстьЗакрывается TTNOTIF-1.
Watchers / подпискиЕсть (follow/unfollow)НетЕстьЗакрывается TTNOTIF-1.
In-app bell / email notificationsЕстьНетЕстьЗакрывается TTNOTIF-1 (110ч).
Issue Links (blocks, relates, duplicates)ЕстьЕстьЕстьПаритет.
Bulk operationsЕстьCSV/XLSX exportLimitedTaskTime bulk-ops с friendly-пикерами (TTBULK-1) лучше, но нет import.
CSV / XLSX importЕстьНет (план P1)CSV onlyYandex Tracker поддерживает миграцию из Jira и Excel.
Kanban-доскаЕсть + swimlanes + WIPЕсть, без swimlanes / WIPЕстьYT полнее.
Scrum-sprintsЕстьЕстьЕстьПаритет по базе.
Velocity / Sprint Report / CFDЕстьНет (план P1)ЕстьКрупный гэп agile-отчётности.
Roadmap / Timeline / GanttЕсть (гантограммы)НетНет в vanillaYandex Tracker имеет штатный timeline. Для PM-команд — важно.
Personal Dashboards с виджетамиЕстьRead-onlyЕстьYT — drag-drop конструктор.
Поиск / язык запросовYQL (Yandex Query Language)TTQLJQLФункционально близко; TTQL имеет release-/checkpoint-функции, которых нет в YQL.
Saved FiltersЕсть (private/public)PRIVATE/SHARED/PUBLICЕстьПаритет.
Full-text search (description, comments)Есть (ClickHouse под капотом)Phase 2LuceneПока TaskTime слабее.
Time Tracking (manual + live timer)ЕстьTimer встроенныйManual onlyTaskTime с live-таймером — сильнее vanilla Jira; YT тоже имеет timer.
Macros / автодействия (UI)Есть (мощно)НетНет в DC (есть в Cloud)YT-макросы — их сильная сторона.
Service Desk / SLA modeЕсть (через Yandex Forms)НетJira SM — отдельный продуктДля customer-facing очередей важно.
Mobile appiOS + AndroidНет (даже PWA)ЕстьДля мобильных ролей (менеджмент, ops) — существенный фактор.
GitLab / Bitbucket / GitHubЕстьGitLab webhooksPluginПаритет по GitLab.
AI / GPTYandexGPT-подсказкиClaude + MCP + decompositionНетTaskTime глубже: декомпозиция Epic → Subtasks, оценка часов, MCP для агентов.
Release Management (планы, burndown)Есть базовыйReleases + Checkpoints + BurndownRelease HubTaskTime — сильнее всех.
Release WorkflowsНетЕстьНетУникальная фича TaskTime.
Release Checkpoints с TTQLНетЕстьНетУникальная фича TaskTime.
+
+ +
+

Y4. Enterprise-постура (on-prem, 5000 users, РФ)

+

Сравнение зрелости инфраструктуры и эксплуатации для on-prem enterprise-сценария.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
АспектYandex Tracker EnterpriseTaskTime (сейчас)TaskTime (после infra-плана)Комментарий
Поставка on-premK8s + Helm chartsDocker Compose single-hostK3s + HelmYandex Tracker из коробки в K8s; TaskTime нужен orchestrator — см. E6 п.5.
Horizontal scalingAuto-scaling podsSingle replica3–5 replicas + HPAЦелевая архитектура E5.
Postgres HAManaged по умолчаниюSingle nodePatroni + standbyE4: 80ч на внедрение.
Redis HASentinel / ClusterSingle nodeSentinel 3+3E4: 40ч.
Event busKafka / внутренние очередиНетKafka KRaft 3-node (TTBUS-0)54ч.
Logging / tracingЦентрализованное из коробкиdocker-logsLoki + Tempo + OTelE4: 80ч суммарно.
Metrics / алертыВстроенные дашбордыPrometheus + GrafanaЕсть + AlertmanagerПаритет по метрикам.
SSO / SAML / OIDCЕсть + LDAP syncНетTTSSO-1 (OIDC)102ч.
Secrets managementSealed secrets / Vault.env filesHashiCorp VaultE4: 40ч.
Backup / PITRManagedСкрипты pg_dumpWAL-archive + drillsE4: 16ч.
Blue-green / zero-downtime deployRolling updateDowntime deployK8s rollingE4: 40ч.
Сертификация ФСТЭК / КИИЕстьНетТребует процессаКлючевое преимущество YT для гос-fin сектора. TaskTime-сертификация — отдельный проект за рамками ТЗ.
ФЗ-152 complianceЕстьAudit + RBAC есть+ Issue Security + Audit UIНужно закрыть TTSEC-3 + TTSEC-4.
SLA поддержки от вендора24/7 Yandex SupportЗависит от командыЗависит от командыПоставка в виде on-prem обычно сопровождается SLA-контрактом; для TaskTime — нужно формировать.
Гибкость доработкиТолько через API / макросыПолный open-source контрольПолный контрольКлючевое преимущество TaskTime: исходники, TS-типизация, возможность глубоких кастомизаций.
+
+ +
+

Y5. Итоговый вердикт

+

Где TaskTime впереди, где догоняет, и что это значит для позиционирования.

+ +
+
+
Где TaskTime впереди Yandex Tracker
+
+
    +
  • Release Workflows + Checkpoints + Burndown — детерминированные релиз-gate с risk scoring. У Yandex Tracker релиз-контур минимальный.
  • +
  • AI-integration уровня decomposition — Claude-based декомпозиция Epic → Subtasks, оценка часов, логирование токенов. YT имеет YandexGPT-подсказки, но не decomposition-loop.
  • +
  • MCP-сервер — первоклассный протокол для подключения AI-агентов. У YT нет аналога.
  • +
  • Role Scheme с 33 permission-флагами — гранулярнее, чем YT role-based модель.
  • +
  • Custom Field type REFERENCE — собственный справочник на уровне админки.
  • +
  • Field Schema 4-level scope (GLOBAL / PROJECT / ISSUE_TYPE / PROJECT_ISSUE_TYPE).
  • +
  • Open-source контроль — исходники под контролем заказчика, возможность любых доработок без зависимости от вендора.
  • +
  • GitLab + Pipeline-service из коробки — визуализация CI-пайплайнов, которой у YT нет.
  • +
+
+
+
+
Где TaskTime догоняет
+
+
    +
  • Attachments — критически; у YT штатно + интеграция с Yandex Disk (TTATTACH-1, 84ч).
  • +
  • Notifications pillar — email, in-app bell, watchers, @mentions (TTNOTIF-1 + TTBUS-0, 164ч).
  • +
  • Mobile app — iOS/Android у YT; в TaskTime даже PWA нет.
  • +
  • Agile-отчётность — Velocity, CFD, Sprint Report (план P1).
  • +
  • Roadmap / Gantt — timeline-диаграмма у YT штатно.
  • +
  • Personal Dashboards с виджетами — drag-drop конструктор у YT.
  • +
  • SSO / SAML / LDAP sync (TTSSO-1, 102ч).
  • +
  • Automation / автодействия UI — макросы у YT, у нас только backend post-functions.
  • +
  • Service Desk / SLA-режим — не приоритет MVP, но отсутствует.
  • +
  • ФСТЭК / КИИ сертификация — у YT есть, у TaskTime — требует отдельной процедуры.
  • +
  • On-prem K8s-поставка — у YT штатно, у нас — в плане E6.
  • +
+
+
+
+ +
+ Стратегический вывод: Yandex Tracker — более зрелый на момент 2026-04 в части коммодити-фич (notifications, attachments, mobile, Gantt, macros, SSO, SLA-поддержка, сертификация). + TaskTime — стратегически уникален в 4 зонах: релизный контур с checkpoints, AI/MCP, гибкая кастомизация полей/ролей, open-source контроль. +

+ Реалистичное позиционирование: TaskTime выигрывает у Yandex Tracker там, где заказчику важен релизный контроль с gate'ами, глубокая AI-интеграция и полный контроль кода (например, для внутренних команд банка, где готовы содержать devops-ресурс). + Yandex Tracker выигрывает там, где нужен «взял и работает» — готовые мобильные приложения, notifications, dashboards и формальная сертификация для закупки в гос- и крупном корп-секторе. +
+ +
+ Что закрытие плана MVP2JIRA даст в терминах YT-паритета: + после выполнения 14 ТЗ из docs/tz/MVP2JIRA/ (~760ч) TaskTime закрывает ~70% гэпов относительно Yandex Tracker (notifications, attachments, SSO, security, resolutions, priorities, components, audit UI, screens, bulk-import). + Остаются: mobile app, Gantt/Timeline, Personal Dashboards с виджетами, automation UI, Service Desk, ФСТЭК-сертификация — это следующая фаза после MVP-ready. +
+
+ +
+ + +
+ Сгенерировано 2026-04-24 · docs/admin-vs-jira-comparison.html · v2 (добавлен пласт функциональности)
+ Источники: backend/src/modules, frontend/src/pages, backend/src/prisma/schema.prisma, docs/user-manual/features/, docs/tz/MVP2JIRA/ +
+ +
+ + + + + diff --git a/docs/tz/MVP2JIRA/INDEX.md b/docs/tz/MVP2JIRA/INDEX.md new file mode 100644 index 0000000..10c5098 --- /dev/null +++ b/docs/tz/MVP2JIRA/INDEX.md @@ -0,0 +1,108 @@ +# MVP2JIRA — Индекс ТЗ + +**Дата:** 2026-04-23 +**План реализации:** [mvp2jira_plan.md](./mvp2jira_plan.md) +**Источник:** [admin-vs-jira-comparison.html](../../admin-vs-jira-comparison.html) + +--- + +## Все ТЗ (14 штук) + +| Ключ | Название | Фаза | Оценка | Статус | +|------|----------|------|--------|--------| +| [TTBUS-0](./TTBUS-0.md) | Event Bus (Kafka + Transactional Outbox) | **P0-prereq** | 54ч (1.5сп) | OPEN | +| [TTNOTIF-1](./TTNOTIF-1.md) | Notifications Service (Email + In-app Bell) | **P0** | 110ч (2сп) | OPEN | +| [TTRES-1](./TTRES-1.md) | Resolutions (результат закрытия задачи) | **P0** | 33ч (0.5сп) | OPEN | +| [TTSEC-3](./TTSEC-3.md) | Issue Security (уровни видимости задач) | **P0** | 52ч (1сп) | OPEN | +| [TTSSO-1](./TTSSO-1.md) | SSO / OIDC | **P1** | 102ч (2сп) | OPEN | +| [TTSEC-4](./TTSEC-4.md) | Password Policy + Audit Log UI + Banner | **P1** | 63ч (1сп) | OPEN | +| [TTCORE-1](./TTCORE-1.md) | Priorities CRUD + IssueStatus reconciliation | **P1** | 63ч (1сп) | OPEN | +| [TTPROJ-1](./TTPROJ-1.md) | Components + fix/affects Versions | **P1** | 48ч (1сп) | OPEN | +| [TTSCR-1](./TTSCR-1.md) | Screens (контекст видимости полей) | **P1** | 24ч (0.5сп) | OPEN | +| [TTATTACH-1](./TTATTACH-1.md) | Функционал вложений (Issue + Comment) | **P1** | 84ч (1.5сп) | OPEN | +| [TTMFA-1](./TTMFA-1.md) | 2FA / TOTP | **P2** | 68ч (1сп) | OPEN | +| [TTINTEG-1](./TTINTEG-1.md) | Webhooks (system + project) | **P2** | 70ч (1сп) | OPEN | +| [TTCFG-1](./TTCFG-1.md) | Time Tracking (parser + auto-stop) | **P2** | 23ч (0.5сп) | OPEN | +| [TTBRAND-1](./TTBRAND-1.md) | Look & Feel (брендинг) | **P2** | 32ч (0.5сп) | OPEN | + +**Итого:** ~826 часов ≈ **14.5 спринтов** (одна команда full-time). + +--- + +## По фазам + +### P0 — Блокеры запуска (~249ч / 5сп) +1. **TTBUS-0** — фундамент event-bus. Блокирует TTNOTIF-1 и TTINTEG-1. +2. **TTNOTIF-1** — уведомления. Критично, без них трекер не полноценен. +3. **TTRES-1** — resolutions. Закрытие задач с причиной. +4. **TTSEC-3** — issue security. Скрытие отдельных задач. + +### P1 — Критично для корп-использования (~384ч / 7сп) +5. **TTSSO-1** — SSO/OIDC. +6. **TTSEC-4** — password policy, audit UI, banner. +7. **TTCORE-1** — приоритеты + согласование статусов. +8. **TTPROJ-1** — components + versions. +9. **TTSCR-1** — screens (контекст полей). +10. **TTATTACH-1** — функционал вложений (в JIRA-отчёте было в P2, поднято до P1). + +### P2 — Важно, но не блокирует (~193ч / 3сп) +11. **TTMFA-1** — 2FA. +12. **TTINTEG-1** — webhooks. +13. **TTCFG-1** — time tracking parser. +14. **TTBRAND-1** — брендинг. + +--- + +## Осознанно НЕ реализовано (P3 / out-of-scope) + +- **Backup/Restore на уровне приложения** — закрывается через DevOps (pg_dump cron, снэпшоты, git). +- **Интернационализация (i18n)** — UI остаётся RU-only. +- **Independent Permission Scheme** — текущая Role Scheme с 33 флагами достаточна. +- **Incoming mail handlers** — сценарий нечастотный, нет запроса от клиентов. +- **Services cron UI** — BullMQ dashboard покрывает need. +- **DB Integrity Checker** — применимо только после backup/restore, который тоже вынесен. +- **Application Links** — не нужно для self-hosted MVP. +- **License management** — специфично для коммерческой self-hosted модели. + +Эти пункты могут появиться как отдельные roadmap-инициативы, если потребуется (см. `mvp2jira_plan.md` секция 10). + +--- + +## Roadmap после MVP2JIRA + +Инфраструктура, подготовленная в этом плане, открывает дешёвые доработки: + +- **TTAUTO-1** — Automations (асинхронный аналог ScriptRunner). Уже готова event-bus (TTBUS-0), добавить consumer + DSL для правил. +- **TTINTEG-2** — Public Events API (pull + WebSocket stream). +- **TTI18N-1** — i18n при появлении international клиентов. +- **TTMAIL-2** — Incoming Mail Handlers. +- **TTAV-1** — Virus scanning для attachments (ClamAV через Kafka). +- **TTMFA-2** — WebAuthn/passkey в дополнение к TOTP. +- **TTSSO-2** — SAML2 и LDAP если пойдут запросы. + +--- + +## Дерево зависимостей (сокращённо) + +``` +TTBUS-0 ─┬─► TTNOTIF-1 ─► TTINTEG-1 + │ ▲ + │ │ (уведомления TTSEC-3 violations) + │ │ +TTSEC-3 ─┼────────┘ + │ +TTRES-1 (independent, желает TTCORE-1) +TTCORE-1 (independent) +TTSSO-1 (independent) +TTSEC-4 ─► TTMFA-1 +TTPROJ-1 (independent) +TTSCR-1 (independent, желает TTPROJ-1) +TTATTACH-1 ─► TTBRAND-1 +TTCFG-1 (independent) +``` + +--- + +## История изменений этого индекса + +- **2026-04-23** — Создан. 14 ТЗ, ~826ч, все решения утверждены. diff --git a/docs/tz/MVP2JIRA/TTATTACH-1.md b/docs/tz/MVP2JIRA/TTATTACH-1.md new file mode 100644 index 0000000..c5a500b --- /dev/null +++ b/docs/tz/MVP2JIRA/TTATTACH-1.md @@ -0,0 +1,428 @@ +# ТЗ: TTATTACH-1 — Функционал вложений (Issue + Comment) + +**Дата:** 2026-04-23 +**Тип:** EPIC | **Приоритет:** P1 | **Статус:** OPEN +**Проект:** TaskTime MVP (TTMP) +**Исполнитель:** TBD +**Автор ТЗ:** Claude Code (auto-generated) + +--- + +## 1. Постановка задачи + +В текущей системе **нет функционала вложений**. Невозможно прикрепить скриншот к задаче, спецификацию к комментарию, мокап к release-note. Это критический пробел для любого серьёзного tracker'а. + +Задача реализует: +- Полиморфные attachments к **Issue** и **Comment**. +- **Pluggable storage** — local FS и S3 (выбор через ENV). +- **MIME whitelist** (images/docs/text/archives) + admin-настройка. +- **Max file size** 25 MB default, в админ-настройке. +- **Thumbnails** для images (server-side через `sharp`) + lightbox preview. +- **Inherit permissions** — доступ к attachment = доступ к parent entity (через TTSEC-3). +- **Hard-delete** + 10-сек undo-banner на фронте. +- **Multipart upload** (без TUS — файлы до 25MB). +- **Virus scan — нет в MVP** (только MIME whitelist). + +### Пользовательские сценарии + +**Dev прикладывает скриншот:** +1. На карточке issue жмёт «Прикрепить файл» → drag-n-drop. +2. Upload показывает прогресс-бар, после завершения — thumbnail. +3. Клик по thumbnail → lightbox с zoom. + +**QA прикладывает PDF-спецификацию:** +1. На комментарии кнопка «+Файл». +2. PDF загружается, в комментарии появляется иконка-файл + имя + размер + кнопка «Скачать». + +**Админ настраивает storage:** +1. `/admin/attachments/settings` → max size 50 MB, добавляет MIME `video/mp4`. +2. ENV `ATTACHMENT_STORAGE=s3`, `S3_BUCKET=tt-uploads`. + +**PRIVATE задача:** +1. На PRIVATE issue (TTSEC-3) attachment виден только тем, у кого есть доступ к issue. + +--- + +## 2. Текущее состояние + +- **Модель Attachment отсутствует.** +- **Multer / upload endpoint отсутствует** для задач (есть только CSV-экспорт). +- **Storage abstraction отсутствует.** +- **Thumbnail pipeline отсутствует.** +- Comments — есть, markdown-рендеринг в BEM-стиле (react-markdown). + +--- + +## 3. Зависимости + +### Модули backend (новые) +- [ ] `modules/attachments/attachments.service.ts` — upload, download, delete, list. +- [ ] `modules/attachments/attachments.router.ts` — endpoints. +- [ ] `shared/storage/` — абстракция `StorageProvider`, реализации `LocalStorage` и `S3Storage`. +- [ ] `shared/thumbnails/` — sharp-wrapper для image resize. + +### Модули backend (изменённые) +- [ ] `modules/issues/issues.service.ts` — добавить relation `attachments`. +- [ ] `modules/comments/comments.service.ts` — аналогично. +- [ ] `shared/middleware/visibility.ts` (TTSEC-3) — extend для attachments. + +### Frontend +- [ ] `components/attachments/AttachmentUploader.tsx` — drag-n-drop + progress. +- [ ] `components/attachments/AttachmentList.tsx` — превью + download. +- [ ] `components/attachments/ImageLightbox.tsx` — overlay для images. +- [ ] `pages/admin/AdminAttachmentsSettingsPage.tsx` — MIME whitelist + max size. + +### Модели данных (Prisma) +- [ ] `Attachment` — полиморфная. +- [ ] `SystemSetting` ключи: `attachments.max_size_bytes`, `attachments.mime_whitelist`, `attachments.enabled`. + +### Внешние зависимости +- [ ] `multer` — multipart parsing. +- [ ] `sharp` — thumbnails (image resize). +- [ ] `@aws-sdk/client-s3` — S3 (опционально, lazy-loaded если storage=s3). +- [ ] `mime-types` — validation. + +### Блокеры +- Нет. TTSEC-3 желателен (иначе attachments не наследуют visibility), но не строго блокер. + +--- + +## 4. Риски + +| # | Риск | Вероятность | Влияние | Митигация | +|---|------|-------------|---------|-----------| +| 1 | Upload без virus-scan → вредонос попадает в storage | Средняя | Infection | MIME whitelist + Content-Disposition:attachment при download + отсутствие inline-рендеринга неdoc/image файлов; в roadmap — ClamAV через Kafka (следующее ТЗ) | +| 2 | Пользователь скачивает PRIVATE attachment через direct URL без сессии | Высокая | Leak | Signed URLs с JWT-токеном (TTL 60 сек), генерятся при каждом запросе; S3 — presigned URL | +| 3 | Thumbnail-генерация блокирует upload (sharp slow на больших images) | Средняя | Slow uploads | Async thumbnail: запись attachment в БД сразу, генерация в BullMQ background-job | +| 4 | S3-bucket misconfigured (public read) → leak | Высокая | Leak | Bucket-policy требование «private», health-check при старте сервиса | +| 5 | Big file blocks HTTP keep-alive | Средняя | Performance | Nginx/proxy-level `client_max_body_size=30M` (25 MB + overhead); backend `express.json()` не применяется к `/attachments/upload` | +| 6 | Hard-delete — undo-окно не успевает, юзер случайно удалил важное | Средняя | Data loss | Undo-banner 10 сек + подтверждение для файлов > 10MB; audit-log восстановления (хотя файл уже unlink) | +| 7 | Local storage переполняется | Средняя | Crash | Cleanup cron для soft-deleted attachments (но у нас hard-delete); метрика дискового пространства + alert | + +--- + +## 5. Особенности реализации + +### 5.1 Модели + +```prisma +enum AttachmentEntityType { + ISSUE + COMMENT +} + +model Attachment { + id String @id @default(uuid()) + entityType AttachmentEntityType @map("entity_type") + entityId String @map("entity_id") + filename String // original filename + storageKey String @map("storage_key") // path в storage + mimeType String @map("mime_type") + sizeBytes Int @map("size_bytes") + checksumSha256 String @map("checksum_sha256") + uploadedById String @map("uploaded_by_id") + thumbnailKey String? @map("thumbnail_key") // для images, null для остальных + createdAt DateTime @default(now()) + + uploadedBy User @relation(fields: [uploadedById], references: [id]) + + @@index([entityType, entityId]) + @@index([uploadedById]) + @@map("attachments") +} +``` + +### 5.2 Storage abstraction + +```typescript +// shared/storage/storage.interface.ts +export interface StorageProvider { + put(key: string, data: Buffer | Readable, mime: string): Promise; + get(key: string): Promise; + delete(key: string): Promise; + getSignedUrl(key: string, ttlSeconds: number): Promise; + exists(key: string): Promise; +} + +// shared/storage/local.ts +export class LocalStorage implements StorageProvider { + constructor(private basePath: string) {} + // использует fs/promises + crypto.createHmac для signed URLs +} + +// shared/storage/s3.ts +export class S3Storage implements StorageProvider { + constructor(private bucket: string, private client: S3Client) {} + // использует @aws-sdk/s3-request-presigner +} + +// shared/storage/index.ts +export function createStorage(): StorageProvider { + const type = process.env.ATTACHMENT_STORAGE ?? 'local'; + if (type === 's3') { + return new S3Storage( + process.env.S3_BUCKET!, + new S3Client({ region: process.env.AWS_REGION, ... }), + ); + } + return new LocalStorage(process.env.ATTACHMENT_LOCAL_PATH ?? '/var/attachments'); +} +``` + +### 5.3 Upload endpoint + +```typescript +// POST /api/:entityType/:entityId/attachments (entityType: issues|comments) +// Content-Type: multipart/form-data, field "file" +router.post( + '/:entityType/:entityId/attachments', + requireAuth, + multer({ limits: { fileSize: 25 * 1024 * 1024 } }).single('file'), + async (req, res) => { + const { entityType, entityId } = req.params; + const file = req.file; + + // 1. Validate MIME + const settings = await getAttachmentSettings(); + if (!settings.mimeWhitelist.includes(file.mimetype)) { + return res.status(400).json({ error: 'MIME_NOT_ALLOWED' }); + } + + // 2. Check parent entity exists + user has access + await assertCanAccessEntity(req.userId, entityType, entityId); + + // 3. Compute checksum + const checksum = crypto.createHash('sha256').update(file.buffer).digest('hex'); + + // 4. Store + const storageKey = `${entityType}/${entityId}/${Date.now()}-${randomSuffix()}-${sanitize(file.originalname)}`; + await storage.put(storageKey, file.buffer, file.mimetype); + + // 5. Create DB record + const att = await prisma.attachment.create({ + data: { + entityType: entityType.toUpperCase(), + entityId, + filename: file.originalname, + storageKey, + mimeType: file.mimetype, + sizeBytes: file.size, + checksumSha256: checksum, + uploadedById: req.userId, + }, + }); + + // 6. Async: generate thumbnail для images + if (file.mimetype.startsWith('image/')) { + await queue.add('generate-thumbnail', { attachmentId: att.id }); + } + + return res.status(201).json(att); + } +); +``` + +### 5.4 Download с signed URLs + +```typescript +// GET /api/attachments/:id/download +// 1. Find attachment +// 2. Check access (parent entity visibility) +// 3. Если local — stream из файла +// 4. Если S3 — redirect на presigned URL (TTL 60 сек) +// 5. Set Content-Disposition: attachment; filename=... +// 6. Audit-log скачивания (для sensitive MIME) +``` + +### 5.5 Thumbnail generator (BullMQ) + +```typescript +// shared/thumbnails/generator.ts +export async function generateThumbnail(attachmentId: string): Promise { + const att = await prisma.attachment.findUnique({ where: { id: attachmentId } }); + if (!att) return; + + const original = await storage.get(att.storageKey); + const resized = await sharp(await streamToBuffer(original)) + .resize(320, 320, { fit: 'inside', withoutEnlargement: true }) + .jpeg({ quality: 80 }) + .toBuffer(); + + const thumbKey = `thumbs/${att.id}.jpg`; + await storage.put(thumbKey, resized, 'image/jpeg'); + + await prisma.attachment.update({ + where: { id: att.id }, + data: { thumbnailKey: thumbKey }, + }); +} +``` + +### 5.6 Default MIME whitelist + +```json +{ + "mimeWhitelist": [ + "image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml", + "application/pdf", + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.ms-excel", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-powerpoint", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "text/plain", + "text/csv", + "text/markdown", + "application/zip", + "application/x-tar", + "application/gzip", + "application/json" + ], + "maxSizeBytes": 26214400, + "enabled": true +} +``` + +### 5.7 Delete + undo + +- `DELETE /api/attachments/:id` — hard-delete (удаляет файл со storage + запись из БД). +- Audit-log событие `attachment_deleted` с `{ filename, sizeBytes }` — для расследований. +- Фронт после delete показывает toast «Файл удалён» + «Отменить» на 10 сек. +- Если «Отменить» нажата **в течение окна** — из локального state удаляется `attachment`, но запрос **не уходит** (на фронте debounce-реализация отложенного запроса). + +```typescript +// Frontend: +const handleDelete = () => { + setOptimisticallyDeleted(attachmentId); + const timer = setTimeout(() => { + api.delete(`/attachments/${attachmentId}`); // только через 10 сек + }, 10_000); + showToast({ message: 'Файл удалён', action: { label: 'Отменить', onClick: () => { + clearTimeout(timer); + setOptimisticallyDeleted(null); + } } }); +}; +``` + +### 5.8 Frontend — AttachmentUploader + +- Drag-n-drop zone + button «Выбрать файлы». +- Многофайловый upload параллельно. +- Прогресс-бар per-file + общий. +- Ошибки (413, 400 MIME_NOT_ALLOWED) — показываем на тосте. + +### 5.9 AttachmentList + +Для каждого attachment: +- **Images:** thumbnail 120×120 + click → lightbox с original. +- **Non-images:** иконка (PDF/doc/generic) + filename + size + кнопки Download/Delete. +- Group by upload date (today / yesterday / earlier). + +### 5.10 Admin settings UI + +`/admin/attachments/settings`: +- Toggle «Enabled». +- InputNumber max size (MB). +- TagsInput для MIME whitelist. +- Info-блок: текущий storage (local/s3), disk usage (если local). + +--- + +## 6. Требования к реализации + +### Функциональные +- [ ] FR-1: Модель Attachment + API upload/download/delete/list. +- [ ] FR-2: Storage pluggable (local/s3) через ENV. +- [ ] FR-3: MIME whitelist + max size настраивается в админке. +- [ ] FR-4: Thumbnails для images генерятся async. +- [ ] FR-5: Permissions наследуются от parent entity (через TTSEC-3 JOIN). +- [ ] FR-6: Signed URL TTL 60 сек для local; presigned для S3. +- [ ] FR-7: Hard-delete + 10-сек undo-banner на фронте. +- [ ] FR-8: Multipart up to 25 MB. +- [ ] FR-9: Attachment прикрепляется к Issue и Comment. +- [ ] FR-10: Drag-n-drop + progress в UI. +- [ ] FR-11: Lightbox для images. + +### Нефункциональные +- [ ] NFR-1: Upload 10 MB файла < 5 сек на localhost. +- [ ] NFR-2: Thumbnail generation async — не блокирует upload. +- [ ] NFR-3: Download для S3 → presigned URL, backend не стримит сам. +- [ ] NFR-4: Disk-usage для local: alert при > 80% заполнения volume. + +### Безопасность +- [ ] SEC-1: MIME whitelist — single source of truth, не обходится через extension. +- [ ] SEC-2: Content-Disposition: attachment (не inline) для всех, кроме images/pdf (где разрешаем inline). +- [ ] SEC-3: Signed URLs с JWT-подписью и expiry. +- [ ] SEC-4: S3-bucket проверяется на приватность при старте. +- [ ] SEC-5: Audit-log для upload, delete, download (sensitive). +- [ ] SEC-6: Filename sanitization (убираем `..`, control chars). + +### Тестирование +- [ ] Unit: storage abstraction (обе реализации). +- [ ] Unit: MIME-validation, size limits, filename-sanitization. +- [ ] Integration: upload PDF to issue → download works → visibility через TTSEC-3. +- [ ] Integration: PRIVATE issue — не-admin не скачивает attachment (403). +- [ ] E2E: drag-n-drop → thumbnail → lightbox. +- [ ] Покрытие ≥ 70%. + +--- + +## 7. Критерии приёмки + +- [ ] AC-1: Upload PNG к issue → появляется thumbnail через ~2 сек. +- [ ] AC-2: Upload PDF к comment → иконка файла + download. +- [ ] AC-3: Upload MP4 (не в whitelist) → 400 MIME_NOT_ALLOWED. +- [ ] AC-4: Upload 30MB → 413. +- [ ] AC-5: S3-storage работает через ENV-switch (integration-тест на MinIO). +- [ ] AC-6: Delete + undo в течение 10 сек сохраняет файл. +- [ ] AC-7: Delete после 10 сек реально удаляет (storage + DB). +- [ ] AC-8: PRIVATE issue — 3й пользователь получает 403 на /attachments/:id/download. +- [ ] AC-9: Admin settings настраиваются, применяются без перезапуска. +- [ ] AC-10: Tests + security-review зелёные. + +--- + +## 8. Оценка трудоёмкости + +| Этап | Часы | +|------|------| +| Storage abstraction (local + s3) | 12 | +| Prisma model + migration | 2 | +| Upload endpoint (multer + validation + sanitization) | 6 | +| Download endpoint + signed URLs | 5 | +| Delete + audit-logging | 3 | +| Thumbnail pipeline (sharp + BullMQ) | 6 | +| Visibility integration (TTSEC-3) | 3 | +| Admin settings UI | 4 | +| Frontend: AttachmentUploader (drag-n-drop + progress) | 8 | +| Frontend: AttachmentList + Lightbox | 8 | +| Integration с IssueDetailPage + Comment | 4 | +| Tests (unit + integration + security + MinIO) | 14 | +| Docs | 3 | +| Code review + AI review | 6 | +| **Итого** | **84** (~1.5 спринта) | + +--- + +## 9. Связанные задачи + +- **Зависит от:** нет (TTSEC-3 желателен для полноценной visibility-фильтрации). +- **Блокирует:** **TTBRAND-1** (reuse storage-абстракции для логотипа). +- **Связано:** TTNOTIF-1 (attachments в email — опциональная фаза). + +--- + +## 10. Иерархия задач + +``` +TTATTACH-1 (EPIC) — Функционал вложений + ├─ TTATTACH-1.1 — Storage abstraction (local + s3) + ├─ TTATTACH-1.2 — Prisma model + migration + ├─ TTATTACH-1.3 — Upload/download/delete endpoints + security + ├─ TTATTACH-1.4 — Thumbnail BullMQ pipeline + ├─ TTATTACH-1.5 — Visibility integration + ├─ TTATTACH-1.6 — Admin settings UI + ├─ TTATTACH-1.7 — Frontend uploader + list + lightbox + └─ TTATTACH-1.8 — Tests + docs +``` diff --git a/docs/tz/MVP2JIRA/TTBRAND-1.md b/docs/tz/MVP2JIRA/TTBRAND-1.md new file mode 100644 index 0000000..e08e66b --- /dev/null +++ b/docs/tz/MVP2JIRA/TTBRAND-1.md @@ -0,0 +1,268 @@ +# ТЗ: TTBRAND-1 — Look & Feel (брендинг) + +**Дата:** 2026-04-23 +**Тип:** TASK | **Приоритет:** P2 | **Статус:** OPEN +**Проект:** TaskTime MVP (TTMP) +**Исполнитель:** TBD +**Автор ТЗ:** Claude Code (auto-generated) + +--- + +## 1. Постановка задачи + +Возможность кастомизации брендинга для корпоративного заказчика: логотип, favicon, заголовок приложения, primary-color, текст на логин-странице, текст футера, логотип в email-шаблонах. + +**Storage переиспользует TTATTACH-1** — логотип/favicon/email-logo хранятся через `StorageProvider` (local/s3). + +**Темы (light/dark)** уже реализованы — в этом ТЗ **не трогаем**. + +### Пользовательские сценарии + +**Админ кастомизирует продукт:** +1. `/admin/branding` → upload логотипа (PNG 400×80). +2. Primary color → `#0066cc`. +3. App title → «CorpTasks». +4. Footer text → «© 2026 ОАО Ромашка. Только для внутреннего использования». +5. Save → весь UI обновляется, email-шаблоны тоже. + +--- + +## 2. Текущее состояние + +- Логотип и favicon — встроены в frontend bundle, кастомизация невозможна без rebuild'а. +- Primary color — hard-coded через AntDesign default theme. +- App title — `TaskTime MVP` в index.html. +- Email-шаблоны (после TTNOTIF-1) — без логотипа. + +--- + +## 3. Зависимости + +### Модули backend +- [ ] `modules/admin/branding.router.ts` — GET/PATCH настроек. +- [ ] `modules/admin/branding.service.ts` — lookup + upload delegation. + +### Frontend +- [ ] `pages/admin/AdminBrandingPage.tsx` — форма настроек + upload. +- [ ] `components/layout/Logo.tsx` — читает из settings. +- [ ] `App.tsx` — в useEffect обновить `document.title` + favicon + CSS-vars для primary color. +- [ ] `pages/LoginPage.tsx` — блок с loginPageText. + +### Модели данных (Prisma) +- [ ] `SystemSetting` ключи: `branding.*`. + +### Внешние зависимости +- Нет новых. Переиспользует storage из TTATTACH-1. + +### Блокеры +- **TTATTACH-1 обязателен** (storage abstraction + upload API). + +--- + +## 4. Риски + +| # | Риск | Вероятность | Влияние | Митигация | +|---|------|-------------|---------|-----------| +| 1 | Огромный логотип (5MB PNG) замедляет загрузку страницы | Средняя | UX | Validation на upload: max 500KB для brand-assets; server-side resize через sharp (аналог thumbnail) | +| 2 | Primary color = ярко-жёлтый → нечитабельно | Низкая | Brand-fail | Preview в настройках + contrast-checker (WCAG AA) с warning | +| 3 | Старый логотип закэширован у пользователей | Средняя | UX | Storage-key в URL меняется на каждый upload (timestamp в filename); cache-busting автоматический | +| 4 | SVG логотип содержит XSS (embedded script) | Средняя | Compromise | SVG-sanitizer при upload (удаляет `