# Этапы разработки

## Общий принцип

Не делаем сразу тяжелую доменную платформу.

Идем вертикальными поставками:
1. сначала транспорт и создание задачи
2. потом source-side представление
3. потом синхронизация
4. потом уведомления и полировка

## Freeze point на 2026-04-19

Текущий вертикальный срез временно заморожен.

На момент freeze point уже есть:
- отправка запроса из source project в target project того же `workspace`
- отсутствие требования прямого membership в target project для отправки
- source-side список `Открытые / Завершенные`
- source-side карточка на shell `Предложений`
- source-side редактирование открытого запроса по `заголовку` и `описанию`
- зеркалирование статуса, комментариев, вложений и activity из целевого контура
- source-side действия `Принять`, `Отклонить`, `Ответ во внешний контур`
- индикатор непрочитанных изменений
- карточка `Маршрутизация` в целевом формате `3 x 3`

Дальше по roadmap пока не идем, пока не приняты продуктовые решения по внутреннему жизненному циклу принятого запроса.

## Этап 0. Термины и навигация

### Цель

Подготовить интерфейс и словарь сущностей без ломки backend-логики.

### Что входит

- `Рабочие элементы` -> `Внутренний контур`
- новая вкладка `Внешние контуры`
- подготовка i18n-ключей и текстов
- фиксация терминов в продуктовой документации

### Результат

В проекте уже есть новая структура навигации, но бизнес-логика еще не внедрена.

## Этап 1. Маршрутизация запроса в целевой проект

### Цель

Сделать рабочий сценарий отправки задачи из source project в target project.

### Что входит

- новая create-form во `Внешних контурах`
- выбор целевого проекта
- выбор исполнителя из целевого проекта
- orchestration endpoint
- использование intake bridge как backend-моста
- немедленный перевод в default-state целевого проекта
- создание target issue
- запись source/target metadata

### Что не входит

- полноценная синхронизация файлов
- полноценная зеркальная история
- сложные уведомления

### Критерий приемки

- пользователь отправляет внешний запрос
- в целевом проекте появляется обычная задача
- задача не зависает в triage
- source-side список уже видит факт отправки

### Статус

Реализовано.

Что работает фактически:
- `POST /external-contours/` создает target issue в целевом проекте
- target issue сразу попадает в обычный workflow целевого проекта
- source-side `GET /external-contours/` возвращает отправленные запросы с metadata источника и цели
- target contour теперь выбирается по policy `same workspace + intake enabled`, а не только из joined projects
- прямое membership в target project для отправки не требуется

## Этап 2. Source-side список и статусная пришлепка

### Цель

Дать инициатору читаемый контроль за отправленными запросами.

### Что входит

- список `Открытые`
- список `Завершенные`
- текущий статус задачи как пришлепка
- отображение целевого проекта
- отображение исполнителя
- отображение даты обновления
- индикатор новых изменений

### Критерий приемки

- в проекте-источнике виден список отправленных запросов
- у каждой записи отображается актуальный статус целевой задачи

### Статус

Реализовано частично в рамках текущего вертикального среза.

Что уже работает:
- source-side список `Открытые / Завершенные`
- status pill по фактическому state целевой задачи
- отображение целевого проекта
- отображение исполнителей целевого контура
- отображение фактической даты последнего изменения
- индикатор новых изменений в source-side списке на базе unread уведомлений
- открытие source-side detail экрана

Что еще остается на следующие этапы:
- полноценная зеркальная activity/history
- уведомления

Дополнительно реализовано:
- source-side detail показывает блок маршрутизации
- в карточке видны источник, цель, отправитель, дата отправки и связанная целевая задача

## Этап 3. Source-side детальный экран и зеркалирование изменений

### Цель

Сделать так, чтобы отправитель видел ход исполнения не только по одной плашке статуса.

### Что входит

- детальный экран внешнего контура
- блок `Исходный запрос`
- блок `Текущий статус`
- mirrored activity stream
- mirrored comments
- mirrored files
- mirrored description updates

### Важное правило

Исходный запрос не должен теряться.

Поэтому target-изменения лучше показывать отдельным блоком истории, а не просто затирать исходное описание.

### Критерий приемки

- если в target issue добавили файл, инициатор видит его в source-side карточке
- если в target issue поменяли статус, это видно в source-side карточке
- если в target issue написали комментарий, это видно в source-side карточке

### Статус

Реализовано частично.

Что уже работает:
- source-side detail использует отдельный экран `Внешних контуров`
- в карточке отображается блок маршрутизации с ключевой source-target связью
- у отправителя открытый внешний запрос редактируется прямо из source-side карточки по полям `заголовок` и `описание`, даже без membership в target project
- текущий статус берется из фактического state целевой задачи
- если у инициатора нет membership в target project, карточка переключается в source-side readonly режим без прямого открытия чужого проекта
- блок `Маршрутизация` перестроен в фиксированный формат `3 x 3`
- в `Маршрутизацию` перенесены `Назначенный` и `Срок выполнения`
- в блоке `Свойства` убраны дубли `Внешний контур`, `Назначенный` и `Срок выполнения`
- название исходного внутреннего контура в карточке маршрутизации берется из живого проекта, а не из застывшего metadata snapshot
- для закрытого внешнего запроса доступны source-side действия `Принять` и `Отклонить`
- `Принять` фиксирует решение источника в bridge metadata и помечает запрос как принятый во внутренний контур
- `Отклонить` требует комментарий причины, возвращает target issue в default-state целевого проекта и переносит source-side карточку обратно в список `Открытые`
- source-side readonly карточка зеркалит актуальные комментарии, вложения и activity целевой задачи
- вложения доступны через proxy download endpoint без прямого membership в target project
- detail карточка source-only пользователя обновляется polling-ом и подтягивает новые комментарии без ручной перезагрузки
- инициатор может отправить комментарий обратно во внешний контур прямо из source-side карточки

Что остается:
- зеркалирование inline-файлов из комментариев и описания, а не только issue attachments
- realtime вместо polling
- отдельная сущность или шаг для переноса принятого результата во `Внутренний контур`

## Этап 4. Уведомления

### Цель

Не заставлять инициатора вручную проверять изменения.

### Что входит

- in-app notification на:
  - принятие
  - перевод статуса
  - добавление файла
  - новый комментарий
  - завершение
  - отмену

### Критерий приемки

- инициатор получает уведомления по ключевым событиям жизненного цикла внешнего запроса

### Статус

Реализовано частично.

Что уже работает:
- создаются in-app уведомления по изменениям целевой задачи внешнего контура
- покрыты события:
  - смена статуса
  - новый комментарий
  - изменение описания
  - новое вложение
- уведомление привязано к source project, а не требует membership в target project
- notification payload несет `external contour request id` и `target issue id`
- список уведомлений помечает такие записи как `is_external_contour = true`
- notification preview может открыть source-side карточку внешнего контура, а не обычный issue preview
- открытие source-side карточки помечает связанные unread уведомления как прочитанные и снимает индикатор новых изменений в списке

Что остается:
- in-app уведомления на явные source-side решения `Принять / Отклонить`
- отдельный индикатор новых изменений в списке `Открытые / Завершенные`
- push/realtime канал вместо обычного цикла обновления UI

## Этап 5. Полировка и правила эксплуатации

### Что входит

- edge cases
- повторная отправка
- отмена
- защита от удаления target issue
- аудит прав
- тест-кейсы
- регламент эксплуатации

## Технические решения, которые желательно держать с самого начала

### 1. Не ломать штатный intake

Он остается отдельным продуктовым модулем.

### 2. Явно хранить source/target связь

Даже если первая версия идет через `IntakeIssue.extra`, связь не должна быть неявной.

### 3. Использовать фактический статус target issue

В source-side плашке лучше показывать не abstract intake status, а реальный текущий статус исполнения.

### 4. Не пытаться делать полную двустороннюю редакцию сразу

На старте безопаснее сделать:
- создание из источника
- исполнение в цели
- синхронизацию результата обратно в источник

## Открытые вопросы

### 1. Что именно делает `Принять`

Нужно зафиксировать конечную бизнес-логику:
- `Принять` только ставит source-side решение `accepted`
- `Принять` создает отдельную сущность во `Внутреннем контуре`
- `Принять` переводит карточку в отдельный внутренний статус без создания дубликата

Это главный блокирующий вопрос перед следующим этапом.

### 2. Где дальше живет принятый запрос

Нужно решить:
- карточка остается во `Внешних контурах` как историческая запись
- карточка уходит во `Внутренний контур`
- карточка одновременно видна в обоих местах, но с разной ролью

### 3. Нужна ли отдельная сущность source-side

Нужно принять решение:
- достаточно ли текущей source-side проекции поверх bridge metadata
- или уже пора вводить отдельную таблицу/модель для внешних контуров

### 4. Файлы

Нужно решить:
- достаточно ли proxy-доступа к файлам целевой задачи
- или файлы надо физически копировать в source-side representation
- нужно ли зеркалировать inline-файлы из описания и комментариев

### 5. Комментарии

Нужно решить:
- source-side reply остается облегченной обратной связью
- или нужен полноценный двусторонний поток комментариев как единый discussion-thread

### 6. Уровень realtime

Нужно решить:
- хватает ли polling для PoC
- или следующий этап уже должен включать push/realtime события

### 7. Счетчики и вкладки

Нужно решить:
- нужен ли отдельный unread-counter по вкладкам `Открытые / Завершенные`
- нужен ли отдельный сегмент для запросов, принятых во `Внутренний контур`

### 8. Право редактирования после отправки

Нужно решить:
- отправитель редактирует только `заголовок` и `описание`
- или после отправки он может менять еще `срок`, `назначенного`, `приоритет`, `метки`

### 9. Доступ к target issue

Нужно решить:
- должен ли инициатор иметь прямую ссылку на target issue
- или source-side карточка должна оставаться единственной точкой просмотра для пользователей без membership

Текущее решение:
- при отсутствии membership в target project прямой переход в target issue скрывается
- карточка остается доступной из source project

## Рекомендуемый порядок фактической разработки

1. Этап 0
2. Этап 1
3. Этап 2
4. Этап 3
5. Этап 4
6. Этап 5

Это даст быстрый полезный результат и не загонит проект в ранний тяжелый refactor.
