Міграція Golden
Новий project-golden, project-application-api, Gateway та Electron вводяться поруч із чинними stack-golden і Golden Electron. Production перемикається частинами, без big bang.
Незмінні правила
- Legacy collections, queues та endpoints не змінюємо до переведення відповідної логіки.
- Нові consumers використовують окремі versioned queues та commands.
- Read-only sync можна виконувати паралельно для порівняння з legacy.
- Partner-write для конкретної TU та операції виконує лише legacy або нова система, але не обидві.
- Workers мають idempotency, lock і явний owner.
- Rollback перемикає routing назад; canonical дані не видаляються.
- Джерело істини для зв’язку TU↔користувач — колекція TU.
details.ladiesу користувача пишемо для сумісності, але не читаємо і не віддаємо на фронт з нових ендпоінтів. Деталі — «Зв’язок TU ↔ користувач».
Одиниця міграції
Мігруємо не весь stack-golden одним релізом і не окремий endpoint без його залежностей. Одиниця міграції — завершена capability: frontend route, canonical data, partner operation, background writer, logs і post-effects.
Для capability, яку вже переключили:
- Frontend звертається лише до
project-application-api. - Canonical collection має одного owner-а запису для конкретних полів та операцій.
- Старий публічний route вимкнений або тимчасово проксіює команду у V2; він не виконує власний write.
- Ще не перенесений legacy-код читає canonical collection через один compatibility repository.
- Старі довільні Mongoose models не направляємо на нову collection: назви полів, шифрування та семантика query несумісні.
- Після parity, pilot і rollback period legacy collection та bridge цієї capability видаляються.
Постійного двостороннього sync між legacy і canonical collections не робимо. Тимчасова проєкція допустима лише як короткий rollback-bridge з метрикою lag і явною датою видалення.
Співіснування V2 і legacy
flowchart LR Front["Frontend"] -->|"перенесені routes"| V2["project-application-api"] Front -->|"решта routes"| Stack["stack / stack-golden"] V2 --> Project["project-golden"] Project --> Partner["Golden partner API"] V2 --> Canonical[("project_admins / project_tus")] Project --> Canonical Stack --> Compat["LegacyGolden compatibility repositories"] Compat --> Canonical V2 -->|"лише неперенесені post-effects"| LegacyEffects["versioned legacy commands"] LegacyEffects --> Stack
Compatibility repository є anti-corruption layer: він робить конкретні canonical query, розшифровує secret на межі та повертає стару форму тільки legacy consumer-у. Його не імпортуємо хаотично по файлах — старі сервіси отримують вузькі методи на кшталт findActiveAdmins(), findTUCredentials() і findTUsByPartnerIds().
Допускається кілька writer-ів однієї collection лише для різних, явно зафіксованих полів. Наприклад, поки TU Card живе у Stack, Stack володіє зв’язком KM, а V2 refresh — partner profile/status. Два сервіси не можуть одночасно змінювати одне assignment або partner password.
Середовища
| Контур | Хто працює | Дані й доступ |
|---|---|---|
| Legacy production | Чинні Stack, Golden service та Electron | Поточні endpoints, queues і collections |
| V2 test | Нові project-application-api, Gateway, Golden service та Electron | Test endpoints, окремі queues/collections, test operators і TU |
| V2 shadow | Нові server services без live-write | Читають production-compatible дані й порівнюють результат із legacy |
| V2 pilot | Allowlist операторів/TU | Live-write дозволений лише для явно переведеного scope |
| V2 production | Нові services | Legacy лишається rollback-контуром до завершення міграції |
Test Electron має окремі appId, product name, user data directory, API origin, socket namespace та update channel. Його можна встановити поруч із production Electron.
Етапи
| № | Етап | Результат | Умова переходу далі |
|---|---|---|---|
| 01 | Packages і tooling | @it-monkeys/contracts, @it-monkeys/backend-common та project-platform | Packages збираються через link, pack і ручний release |
| 02 | Каркаси | project-application-api, Gateway і project-golden розгорнуті без production routing | Healthcheck, auth, RMQ і observability працюють |
| 03 | Admins і TU read model | Новий Golden читає partner API та пише canonical Admin/TU | Повторний sync і backfill не створюють дублікати |
| 04 | Shadow comparison | Legacy і V2 результати порівнюються автоматично | Узгоджені counts, status, assignments і profile fields |
| 05 | Test Electron | Нова програма працює на V2 test із тестовими TU | Login, startup, reconnect, sync та workspace проходять сценарії |
| 06 | Production pilot | Кілька операторів/TU переведені через allowlist | Немає подвійних writes, втрати actions/tasks і деградації partner API |
| 07 | Вертикальна міграція | Фічі переходять по одній із власним flag і rollback | Кожна фіча пройшла parity та pilot |
| 08 | Розширення | Allowlist поступово збільшується | Метрики й support стабільні на кожному кроці |
| 09 | Вимкнення legacy | Старі workers/routes зупинені, repository архівується пізніше | Повний scope працює на V2 і минув rollback period |
Фактичний стан на 2026-08-27
| Частина | Уже є | Що блокує production cutover |
|---|---|---|
| Packages | contracts, backend-common, canonical schemas/repositories | Release packages і точні production versions |
| Deployables | project-application-api, project-golden, healthcheck, Stack JWT, internal HTTP | Production config, secrets, network policy та telemetry |
| Admins | migration, list/resolve/create/update, Golden credentials validation | Legacy admin compatibility repository, TU count, routing flag |
| TU catalog | migration, Golden list adapter, compare/refresh, canonical update | Read endpoints, shadow/dry-run, legacy TU compatibility reads |
| TU password | Golden partner mutation і encrypted canonical password | Legacy Electron/workspace повинні читати canonical credentials |
| Assignments | тімлід/operator batch flow, stale-state check, user/TU update | Tasks, Electron effects, canonical logs і production parity |
| KM | service method синхронізації зі Stack TU Card | Визначити Stack command; refresh не має знімати KM |
| Logs | спільна tu_logs через TuLogRepository: рольові події призначень і lifecycle refresh | — |
Поточні migration endpoints є apply-командами, а не безпечним shadow: повторний запуск переносить значення зі старих collections і може перезаписати нові canonical зміни. Перед production вони мають отримати dry-run/compare, deployment guard і бути вимкнені одразу після cutover.
Перший production vertical slice
frontend Admin/TU routes
→ project-application-api
→ internal HTTP
→ project-golden
→ Golden partner API
project-application-api ↔ canonical project_admins / project_tus
stack-golden → compatibility repositories → ті самі canonical collectionsНа цьому тижні реалістичний production scope:
| Черга | Capability | Результат | Чи можна закрити повністю |
|---|---|---|---|
| 0 | Dark deploy двох сервісів | Health, auth, internal network, secrets та індекси перевірені без frontend traffic | ✅ |
| 1 | Admins | Frontend CRUD працює через V2; project_admins — єдине джерело; legacy лише читає через adapter | ✅ |
| 2 | TU catalog/read | Backfill, read API та shadow comparison працюють на project_tus; legacy Electron читає canonical TU | ✅ після compatibility reads |
| 3 | TU password | V2 є єдиним writer partner/canonical password; legacy workspace читає розшифровані canonical credentials | ✅ після credentials adapter |
| 4 | TU refresh write | Старий LadySyncService вимкнений; V2 володіє profile/status/admin | ✅ лише після detach post-effects |
| 5 | Manual assignments | До cutover legacy flow пише assignments у canonical TU через compatibility repository; після cutover усі TU/user/log/task/Electron effects переходять у V2 | ❌ V2 не вмикати до parity |
| 6 | Electron workspace, statistics, favorites, sender | Залишаються у stack-golden і старому Electron | окремі наступні vertical slices |
Отже, самі сервіси можна безпечно залити цього тижня. Frontend routing треба вмикати окремими flags: admins, tu-read, tu-password, tu-refresh, tu-assignments. Один загальний goldenV2=true для всього сервісу не використовуємо.
Міграція Admins
project_admins стає єдиним source of truth. Старий golden_admins не можна просто замінити імпортом schema: legacy очікує id_api, відкритий password, isBlocked і підтримує довільні query, а canonical document має login, passwordEncrypted і status.
У stack-golden є 13 файлів із прямим import AdminModel, плюс залежності від legacy IAdmin. Вони покривають CRUD, sync/statistics, Electron/API operations і тести. Для завершеного cutover:
- Додати один
LegacyGoldenAdminRepositoryповерхProjectAdminRepository. - Реалізувати лише фактично потрібні typed methods; не переносити legacy endpoint із довільним Mongo query.
- Замінити прямі imports
AdminModelу runtime-коді на repository. - У коротке maintenance-вікно зупинити legacy Admin writes, виконати backfill зі збереженням
_idі звірити count/login/status/hash credentials. - Переключити frontend Admin routes на V2; старий Admin CRUD вимкнути або зробити proxy без власного write.
- Вимкнути Admin migration endpoint і залишити
golden_adminsтільки як rollback snapshot на обмежений строк.
Compatibility spike вже підтвердив роботу @it-monkeys/contracts і @it-monkeys/backend-common з legacy Mongoose 6.2.2. Не можна неявно оновлювати Mongoose у stack-golden: підняття lock-версії вже спричиняло сторонні type errors.
Міграція TU
TU складніша за Admin: GoldenLadyModel читають і змінюють Electron, favorites, metrics, statistics, workspace, sync та assignment flows. Тому підміна model на project_tus небезпечна.
Робимо один LegacyGoldenTURepository з вузькими projections:
- знайти TU/credentials за partner ID;
- знайти canonical admin і partner admin ID;
- отримати TU за списком partner ID або Stack Card ID;
- оновити лише ті legacy-owned поля, власник яких зафіксований у migration matrix.
Порядок cutover:
- Додати
POST /v2/tus/findі, за потреби frontend,POST /v2/tus/findByIdз role scope. - Додати dry-run TU migration та shadow compare без canonical write.
- Перевести legacy runtime reads, необхідні Electron/workspace, на
LegacyGoldenTURepository. - Зупинити старий TU sync, виконати фінальний backfill зі збереженням
_id, звірити profile/status/admin/assignments/password presence. - Увімкнути V2 read і password окремими flags.
- Увімкнути V2 refresh write лише після того, як видалення/деактивація виконує всі operator detach effects.
- Manual assignment routes залишити legacy до готовності tasks, Electron
SET_LADY/DROP_LADYта canonical logs; потім перемкнути всією capability за один cutover.
Поки manual assignments належать legacy, вони вже повинні змінювати project_tus.assignments через compatibility repository, а не продовжувати запис у golden_ladies. Інакше V2 read одразу стане застарілим.
KM не входить у системний detach: його canonical зв’язок оновлюється тільки під час зміни TU Card у Stack.
Борг після вимкнення legacy TU sync
2026-08-28 зі stack-golden видалені LadySyncService, CheckGoldenLadiesWorker і UpdateDataGoldenLadiesWorker: їхню роботу виконує кластерний TU refresh у project-application-api. Разом із ними зникли два post-effects, які новий refresh поки не повторює.
| № | Що зникло | Де було | Коли повернути |
|---|---|---|---|
| 1 | Автоматичний initFavorites() після синхронізації TU | LadySyncService.processLadyUpdates | Під час міграції favorites і вигрузки статистики: крок має отримати FavoriteService |
| 2 | Запис lifecycle-подій TU (created, deleted, activated, deactivated) | LadySyncService | Закрито: refresh пише їх у спільну tu_logs, яку читають і legacy statistics |
Ручний роут GET /initFavorites у stack-golden лишився, тож до міграції favorites ініціалізацію можна запускати вручну.
Favorites: стан
2026-09-03 з’явились canonical collections ProjectFavorite і ProjectGlobalFavorite: schema, indexes і repositories у backend-common, типи у contracts, FavoritesModule і POST /migrations/favorites/apply у project-application-api. Writer поки лишається legacy: golden_favorites, golden_global_mans і golden_man_profiles живуть без dual-write, а canonical наповнюється тільки міграцією.
Порядок далі: імпорт статистики → ProjectFavoriteService як єдиний writer платної активності → FavoriteService і GlobalFavoriteService з API → переключення Electron snapshot → вимкнення legacy favorites. Нотатки favorites (note) і language у canonical не входять і чекають NotesService та окремого рішення.
Логи TU для всіх проєктів живуть в одній колекції tu_logs (TuLogRepository у backend-common): платформа пише туди рольові події призначень і lifecycle-події refresh, legacy-сервіси читають їх через свої адаптери. Старі log_ladies, log_lady_events і golden_lady_event_logs більше не пишуться.
Борг: online оператора у /v2/team/find
POST /v2/team/find віддає структуру команди без isOnline і sleepMode. Frontend поки бере online зі старого джерела, тому сторінка тимчасово живе на двох запитах.
Причина: online зберігається в масиві в пам’яті процесу stack-golden (src/socket/users/onlineUsers.ts), який наповнюють socket-з’єднання. Ні Redis, ні Mongo його не містять, тому project-application-api як окремий stateless-кластер прочитати його не може.
Тимчасовий RMQ-запит у stack-golden свідомо не робимо: це завело б у новий сервіс залежність, яку одразу довелось би знімати.
Повернути разом із OperatorOnlineService: коли Electron Gateway почне писати OperatorOnlineSession, /v2/team дочитує сесії й додає два поля у ProjectTeamGroupDto.operators[]. Контракт при цьому не ламається — поля з’являються, а не змінюють форму.
Борг: post-effects Electron у v2
Закрито: усі post-effects v2 ідуть через Electron Gateway, роль якого поки виконує stack-golden (internal/electron-gateway). Платформа шле лише факти, а команди програмі оператора будує Gateway:
| Операція v2 | Route Gateway | Що робить Gateway |
|---|---|---|
| Зняття TU з користувача: команда зі Stack, refresh, зміна призначень | detached-tus | DROP_LADY, logout mailer-сокета, deleteBadInvites знятого тімліда |
| Призначення TU оператору у зміні | assigned-tus | SET_LADY з профілем і паролем TU; зайняту анкету ставить у чергу на зміну |
POST /v2/tus/updateProjectData з findNew | tu-project-data | UPDATE_SENDER_TYPE операторам усіх змін TU |
| Блокування або переведення оператора | kick | перезапуск програми; шле сам Stack |
Недоступність Gateway не валить операцію: зміни вже записані, помилка лише логується. З появою окремого Electron Gateway зміниться лише адреса ELECTRON_GATEWAY_URL, за межами платформи.
Live-стан теж читається через Gateway: online-operators віддає операторів із підключеною програмою (статистика, isOnline). Без Gateway — нікого, запит статистики не падає. Legacy stack-* загортає відповідь у { success, data } без code; InternalHttpClient платформи приймає обидва формати.
Борг: reset пароля TU для Electron
POST /golden/mail-extension/resetLadyPassword міняє пароль однієї TU і повертає новий пароль оператору: Electron ним логіниться на партнерський сайт. PATCH /v2/tus/password міняє паролі пачкою і навмисно нічого не повертає, тому прямою заміною не є.
Переносимо разом з Electron API; до того часу роут залишається у stack-golden.
Інвентар залежностей від golden_ladies
Розклад перед підміною GoldenLadyRepository на canonical. stack-golden звертається до колекції у 20 файлах: ~50 читань через репозиторій і 19 повз нього.
| Модуль | Звернень | Рішення | Примітка |
|---|---|---|---|
GoldenLadyRepository | 3 | місце підміни | вже anti-corruption layer: фільтр-DSL і join level/historyLevel зі Stack |
LadyService — логіка роутів | ~12 | видаляється | разом із роутами LadyController |
LadyService — set/drop тімліда й оператора | 4 записи | адаптер | не помирають з роутами: їх кличуть системні detach-флоу |
LadyService.autoResetLadyPassword | 1 запис | адаптер | мігрує разом з mail-extension |
workspace | 8 | вузький метод credentials | усі вісім — це пароль TU для partner API |
ElectronApi.checkLadiesOnline | 1 | лишається | старий Electron досі викликає |
ElectronApi — решта | 4 | адаптер | до переходу на новий Electron |
MailExtension.setNAFStopTime | 1 запис | перевірити актуальність поля | старий відголосок розширення |
MailExtension — решта, extension | 4 | адаптер | |
favorites | 10 | адаптер | мігрує разом зі статистикою |
StatisticsV2, statistics, new-year | 12 | адаптер | |
Metric | 7 | адаптер | |
OnlineTime, OnlineAnalysis | 5 | адаптер | |
Dashboard, ErrorReport, ai-notes, operator, supervisor, socket/electron | 8 | адаптер |
Живого тут майже все, тому адаптер однаково має вміти повний legacy-документ. Чистка зменшує кількість викликів, але не обсяг адаптера.
Стан підміни
GoldenLadyRepository читає project_tus і віддає стару форму IGoldenLady. Колекції golden_ladies більше не торкається жоден сервіс — модель лишилась тільки як джерело типу.
Що зійшлося при звірці 1003 legacy TU з canonical:
_idзбігаються один-в-один: міграція зберегла ідентифікатори, тому зв’язки заladyIdуgolden_lady_event_logsта інших колекціях працюють без змін;- паролі: 1003 з 1003 розшифровуються з
credentials.passwordEncryptedі збігаються з legacy plaintext; name,stackId,clientManager,findNew,stopNafTime— нуль розбіжностей;supervisors/operators: у legacy завжди 3 позиції, у canonical завжди зміни1,2,3— мапінг позиційний за номером зміни;ageіphotoрозходяться в 53 і 66 TU: canonical свіжіший, бо legacy-профіль оновлював уже мертвийupdateDataLadies;isDeletedрозходиться у 249 TU: canonical правий, у legacy баг.
lastUpdateActive і lastUpdateDeleted віддаються порожніми — у canonical їх немає і ніхто їх не читає. level та historyLevel як і раніше приходять зі Stack по RMQ.
Системні detach-флоу
OperatorService.dropAllLadiesFromOperators і SupervisorService.dropAllLadiesFromSupervisor знімають TU при блокуванні або видаленні користувача. Вони викликають ті самі dropGoldenLadyFrom*, що й роути призначень, тому не зникають разом із LadyController і потребують окремої відповідності у v2.
Зв’язок TU ↔ користувач
Зв’язок «яка TU за ким закріплена» зберігається двічі: на боці TU (assignments.shifts[] у canonical, operators/supervisors у legacy) і на боці користувача (users.assignments[].details.ladies). Обидві сторони пишуться окремими запитами, між ними немає транзакції, а в Golden ще й через RMQ.
Правило. Джерело істини — сторона TU. Нові й оновлені логіки читають призначення з колекції TU; details.ladies лишається тільки як запис для сумісності зі старим кодом і на фронт з нових ендпоінтів не потрапляє. Запис прибираємо останнім кроком, коли жодне читання його не використовує.
Чому сторона TU
- Призначення — атрибут TU: номер зміни (1–3) є властивістю анкети, у користувача такого поняття немає. Тому зі сторони користувача неможливо відповісти «на якій зміні», а зі сторони TU — можна. Все, що вміє сторона користувача, сторона TU вміє теж; зворотне хибно.
- Кардинальність асиметрична: у TU рівно три слоти, у оператора в середньому 12 TU і до 67.
- Запис стає одним оновленням одного документа замість двох незалежних.
TU_SCOPEуproject-application-apiуже побудований наassignments.shifts.*.
Стан у базі
Одна логіка реалізована чотирма несумісними способами:
| Проєкт | Сторона TU | Сторона користувача |
|---|---|---|
| golden | operators[3], supervisors[3] — строго 3/3 у всіх 1003 | details.ladies: number[] — id_api, не mongo id |
| udate | operators[3], supervisors[3] + currentOperator | details.ladies: string[] |
| prime | supervisor[] довжиною 2 у 332 і 3 у 201; поля для операторів немає | details.ladies: string[] |
| chathouse | дубль полів operator/operators і supervisor/supervisors; пишеться однина, operator порожній у 675 з 676 | details.ladies: string[] |
stack-commons описує IChathouseOperatorFamily.ladies і chathouse_ladies.operators, яких у базі фактично немає — типи тут не відповідають даним.
Окремо стоїть stack_ladies.assignedForEmailUserIds (97 TU) — ще одне посилання TU→користувачі, у п’ятій формі.
Звірка сторін на production-дампі
| Зв’язок | Пар | Розходжень |
|---|---|---|
| golden operator | 865 | 0 |
| golden тімлід | 898 | 0 |
| prime тімлід | 242 | 0 |
| chathouse тімлід | 1247 | 0 |
| client manager, усі 4 проєкти | 1545 | 0 |
| udate operator | 807 / 830 | 23 висячі на боці TU |
| udate тімлід | 804 / 807 | 3 висячі |
Масової розсинхронізації немає. Усі 26 розходжень — у udate: 12 припадають на assignment із isDeleted: true (працівника зняли, з користувача прибрали, з TU ні), решта — живі assignment без TU у масиві. Три випадки з тімлідом — на одному користувачі.
Важливіше за розходження те, що 1489 зв’язків (prime 242, chathouse 1247) існують лише на боці користувача: у цих проєктах TU не знає свого оператора взагалі. Тому для них це не дублювання, а єдине джерело, і зняття потребує спершу міграції даних у shifts.
Інвентар читань
stack-golden — переведено на сторону TU. Вісім точок, усі ізольовані, бо сервіс обслуговує один проєкт:
| Місце | Було |
|---|---|
supervisor/SupervisorService.getLadyApiIdsOnFamilySupervisor | RMQ FamilySupervisorFindOne → .ladies; три споживачі: GoldenFavoritesService, global-man.service ×2 |
supervisor/SupervisorService.dropAllLadiesFromSupervisor | getLadies({ api_ids: supervisorFamily.ladies }) |
operator/OperatorService.dropAllLadiesFromOperators | RMQ по оператору → getLadies({ api_ids }) |
favorites/GoldenFavoritesService.getEmailFavoritesByOperatorForSupervisor | RMQ → .ladies → $in |
extension/GoldenExtensionService.getLadyMessageStatus | гілки тімліда/оператора → .ladies |
extension/GoldenExtensionService.getLadyMailStatus | те саме, копія |
StatisticsV2/StatisticsServiceV2 ×3 | familyOperator.ladies → importantLadyIds |
debug/debug.service | .ladies.length для класифікації WS/Electron/Extension |
OnlineTime/OnlineTimeService до цього списку не належить: operator.ladies там — поле власного документа OnlineTimeModel, а не користувача.
stack — не чіпаємо. Близько 60 звернень у 14 файлах, і жодне з них не є golden-only: скрізь або switch (family) зі спільним входом, або clientManager[family].ladies, або familyOperator.ladies, де проєкт — параметр. Часткова заміна для одного проєкту означала б розгалуження в двадцяти місцях і два джерела істини в одній функції.
| Категорія | Де |
|---|---|
записи (лишаються з TODO) | UserRepository 1482/1507/3483/3507, FamilyOperatorController, FamilySupervisorController, StackClientManagerService |
| зворотній пошук «хто працює з цією TU» | UserRepository 1287, 1818, 2384, 2968, 3460 |
| тікети | UserRepository 4647–5037, ticket-tu.service, ticket-assignee.service, ticket.service |
| лічильники для UI | StackHrService, StackSupervisorService2, StackUserService, StackTopManagerService |
| списки TU користувача для UI | StackOperatorService, stack/components/ladies/LadyService |
| client manager | StackClientManagerService |
Точка для наступних проєктів. Усі перелічені читання отримують .ladies не з бази напряму, а з шести місць у UserRepository: _reconstructLegacySupervisor, _reconstructLegacyOperator, _reconstructFamilySupervisorFromAssignment, _reconstructFamilyOperatorFromAssignment, _reconstructLegacyClientManager і агрегація findFamilyUsers. У двох із них поле протікає неявно, через ...details. Коли сторона TU з’явиться в усіх проєктах, правити треба буде саме ці шість місць, а не шість десятків споживачів.
Індекси
Читання за призначенням спирається на project_tu_operator_assignments, project_tu_supervisor_assignments і project_tu_client_manager_assignments. Вони описані в projectTUSchema, але project-application-api працює з autoIndex: false, тому на середовищі їх може не бути — у production-дампі станом на 01.09.2026 project_tus мала лише _id. Синхронізація виконується окремою командою:
npm run db:indexes:sync
Її треба прогнати на кожному середовищі перед тим, як читання за assignments.shifts.* піде під навантаженням. stackId індексу поки немає — додати, коли зʼявиться гарячий пошук за ним.
Латентна пастка
findSupervisorsByLadyFamilyIds, findClientManagersByLadyFamilyIds і findOperatorFamiliesByLadyId фільтрують assignments.role та assignments.details.ladies без $elemMatch — умови можуть збігтися на різних елементах масиву. Зараз не стріляє (жоден користувач не має перетину TU між assignment різних ролей), спрацює, щойно хтось стане одночасно CM і оператором в одному проєкті. Індексу на assignments.details.ladies немає, тому ці запити ще й скануються повністю по 16 332 користувачах.
Shadow comparison
Поточний POST /v2/tus/refresh є writer і не використовується як shadow. Потрібна окрема read-only команда, яка отримує partner snapshot, викликає TUSyncService.compareSnapshot, повертає diff і нічого не змінює у Mongo, users, logs або partner API.
Для кожного shadow run зберігаємо або рахуємо:
- кількість записів legacy/V2;
- пропущені та зайві external IDs;
- відмінності status і assignments;
- hash нормалізованих важливих полів;
- час і кількість partner requests;
- помилки та повторні спроби.
Shadow не виконує password change, assignment, sender, task close, media upload, online ping або іншу partner mutation.
Production cutover checklist
- Зафіксувати package versions; не використовувати локальні links.
- Перевірити однаковий cipher key у
project-application-api,project-goldenі legacy compatibility layer. - Запустити
db:indexes:syncяк окремий deployment job після переглядуdiffIndexes; application instances не синхронізують indexes на startup. - Закрити internal Project API від зовнішньої мережі.
- Обмежити або вимкнути apply-migration endpoints; роль director не повинна мати постійного доступу до повторного production backfill.
- Поставити feature flags у стан off, розгорнути обидва сервіси й перевірити health/partner test calls.
- Виконати dry-run, maintenance freeze, Admin backfill, потім TU backfill.
- Перевірити counts, IDs, status, admin relations, assignment relations і можливість розшифрувати вибіркові credentials без їх логування.
- Вмикати flags у порядку
admins → tu-read → tu-password → tu-refresh;tu-assignmentsокремо. - Для кожного flag мати owner, метрики, allowlist і одну дію rollback без видалення canonical data.
Перемикання Electron
Stack після login повертає, який workspace доступний оператору: legacy, v2-test або v2. TU, передана у V2 pilot, не повинна одночасно видаватися для роботи legacy Electron.
Gateway додатково тримає Redis lease на project + tuId. Lease захищає V2 від двох нових sessions, але головний захист від legacy/V2 конфлікту — routing і allowlist у Stack.
Перед розширенням pilot перевіряємо:
- Login, update і reconnect.
- Одна активна session на TU.
- Startup data та локальна БД.
- Chat/mail send із partner confirmation.
- Tasks, actions, online і sender sync з record-level ACK.
- Відновлення після падіння Electron, Gateway, API та проєктного сервісу.
Перемикання server-фічі
Кожна фіча має окремий flag із scope project, user або tuId:
legacy read/write
→ V2 shadow read
→ V2 read + legacy write
→ V2 read/write для pilot
→ V2 read/write для проєктуНе всі фічі потребують проміжного V2 read + legacy write. Його використовуємо лише там, де V2 може коректно прочитати legacy результат.
Rollback
- Заборонити нові V2 sessions або requests для pilot scope.
- Дочекатися ACK активних sync batches і зупинити V2 writers/workers.
- Повернути Stack routing та frontend feature flag на legacy.
- Перепідключити оператора до legacy Electron.
- Зберегти V2 collections і telemetry для аналізу; не робити rollback через видалення даних.
Після rollback новий writer не запускається повторно, доки не перевірено незавершені tasks, sender operations і partner mutations.