АРХ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: актуализация roadmap NDC platform
This commit is contained in:
parent
78deab1a23
commit
ca9fd34e91
|
|
@ -20,6 +20,7 @@ WORKSPACE_SLUG = "nodedc"
|
|||
PROJECT_IDENTIFIER = "NDCPLATFORM"
|
||||
PROJECT_NAME = "NDC platform"
|
||||
CODEX_EMAIL = "codex@nodedc.local"
|
||||
PLATFORM_OWNER_EMAIL = "dcctouch@gmail.com"
|
||||
|
||||
STATE_TEMPLATES = [
|
||||
{"group": "backlog", "name": "В обсуждении", "color": "#60646C", "default": True},
|
||||
|
|
@ -595,6 +596,29 @@ server/control-plane-store.mjs переведен на atomic write: запис
|
|||
Ограничение: email/password/profile update уже работает как dev-flow, но production UX для reset-password/invite-email и подтверждения смены email еще нужно вынести в отдельный полноценный этап.
|
||||
""",
|
||||
),
|
||||
text_block(
|
||||
"launcher",
|
||||
"Актуализация 2026-05-09",
|
||||
"""
|
||||
За последние итерации Launcher фактически стал текущим control-plane для корпоративного контура NODE.DC. Через него уже проверяются: клиенты, участники, группы, инвайты, сервисный каталог, матрица доступов, Operational Core workspace binding, user profile, client avatar, fullscreen admin panels и service-specific role modal для Operational Core.
|
||||
|
||||
Зафиксирован продуктовый принцип: enterprise-контур управляется через Launcher. Для таких пользователей и workspace Launcher является source of truth по приглашениям, сервисному доступу и базовым ролям; downstream-приложения получают техническую проекцию и не должны становиться вторым независимым источником прав.
|
||||
|
||||
Отдельно вынесена новая большая карточка "Публичный контур пользователей". Она закрывает сценарий обычных внешних пользователей, которые приходят не через клиентскую компанию, а через запрос приглашения и дальше используют отдельные сервисы как standalone-продукты.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"launcher-actual-20260509",
|
||||
"Чекер актуализации 2026-05-09",
|
||||
[
|
||||
{"text": "Зафиксировать Launcher как source of truth для enterprise-доступов.", "checked": True},
|
||||
{"text": "Зафиксировать Operational Core role modal как текущую модель детальных назначений.", "checked": True},
|
||||
{"text": "Отделить public/self-service контур в самостоятельную карточку.", "checked": True},
|
||||
"Заменить JSON-backed store на production persistence.",
|
||||
"Описать recovery/MFA/email-change UX без раскрытия Authentik UI.",
|
||||
"Вынести billing/limits в отдельный RFC после стабилизации public-контура.",
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -867,6 +891,560 @@ Plane должен оставаться самостоятельным прод
|
|||
"Запретить прямые записи Launcher в Plane DB.",
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"plane",
|
||||
"Актуализация 2026-05-09",
|
||||
"""
|
||||
Текущая модель Operational Core разделена на два режима. Для workspace, которыми управляет Launcher, участники/инвайты/права должны быть скрыты или переведены в readonly внутри Task Manager, чтобы не получить конфликтующий source of truth. Для standalone/public workspace штатные механики Task Manager остаются включенными: пользователь может создавать workspace, приглашать участников и управлять проектами внутри продукта.
|
||||
|
||||
Практически проверено: корпоративные назначения из Launcher доходят до Operational Core, public-пользователь может создать workspace после выдачи доступа, а прямой доступ к сервису проходит через NODE.DC SSO. Safari-only падение workspace зафиксировано как отдельный deferred debug, потому что Chrome/Chromium flow работает и проблема не должна блокировать платформенную обвязку.
|
||||
|
||||
Открытая развилка: нужно формально добавить managedBy=launcher/managedBy=tasker или эквивалентный флаг в mapping workspace, чтобы интерфейс Task Manager понимал, когда скрывать собственное управление пользователями, а когда оставлять автономный SaaS-режим.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"plane-actual-20260509",
|
||||
"Чекер актуализации 2026-05-09",
|
||||
[
|
||||
{"text": "Сохранить автономность Task Manager как standalone-продукта.", "checked": True},
|
||||
{"text": "Зафиксировать managedBy=launcher для enterprise workspace.", "checked": True},
|
||||
{"text": "Зафиксировать managedBy=tasker для standalone/public workspace.", "checked": True},
|
||||
"Скрыть или readonly-заблокировать Task Manager users/invites для managedBy=launcher.",
|
||||
"Оставить Task Manager users/invites включенными для managedBy=tasker.",
|
||||
"Очистить оставшиеся demo users/seed data без удаления живых связей.",
|
||||
"Оформить Safari-only workspace crash как отдельный deferred debug.",
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
"slug": "design-guide-canon",
|
||||
"name": "NDC дизайн-гайд: единый UI-код платформы",
|
||||
"priority": "medium",
|
||||
"state_group": "cancelled",
|
||||
"assignees": [CODEX_EMAIL],
|
||||
"description_html": html(
|
||||
"Карточка сохранена как отложенная архитектурная ссылка: реальный дизайн-канон NODE.DC живет в HDESIGN-CODE.md, а не в теле канбан-карточки.",
|
||||
"Цель актуализации: убрать конфликт источников истины и зафиксировать, что дальнейшие UI-работы должны сверяться с документом дизайн-канона и существующими launcher/tasker компонентами.",
|
||||
"Статус: deferred. Карточка не является активной задачей разработки до решения выделять дизайн-систему в отдельный пакет.",
|
||||
),
|
||||
"blocks": [
|
||||
text_block(
|
||||
"design-guide",
|
||||
"Текущая архитектура",
|
||||
"""
|
||||
Единый визуальный канон NODE.DC зафиксирован в репозитории Operational Core в документе HDESIGN-CODE.md. Он описывает glass/card shell, top-bar, admin overlay, auth screens, spacing, typography, motion, token usage и правила интеграции в Launcher/Task Manager.
|
||||
|
||||
Карточка NDCPLATFORM-7 больше не должна конкурировать с этим документом и не должна хранить длинный HTML-дизайн-гайд. Канбан-карточка остается навигационной отметкой: дизайн-канон существует, но вынос в отдельную design-system работу пока отложен.
|
||||
""",
|
||||
),
|
||||
text_block(
|
||||
"design-guide",
|
||||
"Этап 1. Зафиксировать источник дизайн-канона",
|
||||
"""
|
||||
Статус: выполнено.
|
||||
|
||||
HDESIGN-CODE.md является source of truth для текущего UI-кода NODE.DC. При разработке Launcher/admin overlay, auth screens и Task Manager NODE.DC-слоев сверяемся с ним, а не с историческим HTML-телом карточки.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"design-guide1",
|
||||
"Чекер этапа 1. Источник дизайн-канона",
|
||||
[
|
||||
{"text": "Признать HDESIGN-CODE.md реальным дизайн-каноном.", "checked": True},
|
||||
{"text": "Оставить NDCPLATFORM-7 в deferred/cancelled, без активной разработки.", "checked": True},
|
||||
{"text": "Не использовать старое HTML-тело карточки как актуальный UI-спек.", "checked": True},
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"design-guide",
|
||||
"Реализация этапа 1",
|
||||
"""
|
||||
Карточка приведена к короткой structured layout записи. Исторический смысл сохранен, но актуальный дизайн-канон вынесен в документ репозитория, чтобы UI-решения не расходились между карточкой, кодом и документацией.
|
||||
""",
|
||||
),
|
||||
text_block(
|
||||
"design-guide",
|
||||
"Этап 2. Future design-system extraction",
|
||||
"""
|
||||
Статус: backlog/deferred.
|
||||
|
||||
Если появится необходимость переиспользовать NODE.DC UI между несколькими приложениями как библиотеку, эту работу нужно открывать отдельным этапом внутри той же крупной карточки: tokens, primitives, surfaces, modals, cards, auth shell и cross-app visual QA.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"design-guide2",
|
||||
"Чекер этапа 2. Future design-system extraction",
|
||||
[
|
||||
"Принять решение, нужен ли отдельный design-system package.",
|
||||
"Выделить переиспользуемые primitives без ломки Launcher/Tasker UI.",
|
||||
"Описать visual QA для Auth/Launcher/Tasker экранов.",
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
"slug": "tasker-provisioning-workspace-onboarding",
|
||||
"name": "Tasker provisioning и workspace onboarding",
|
||||
"priority": "high",
|
||||
"state_group": "started",
|
||||
"assignees": [CODEX_EMAIL],
|
||||
"description_html": html(
|
||||
"Архитектурный узел между Launcher control plane и Operational Core: пользователь должен попадать в Tasker через NODE.DC SSO без redirect-loop, 500-страниц и ручных Plane-инвайтов.",
|
||||
"Цель: разделить доступ к приложению, workspace onboarding и доменные роли Tasker. Launcher выдает сервисный доступ и enterprise-назначения, Tasker сохраняет собственные workspace/project/task модели и standalone-режим.",
|
||||
"Критерий приемки: пользователь с доступом к Operational Core получает предсказуемый исход: назначенный workspace/project, разрешенный self-service workspace или ожидание назначения; для managedBy=launcher нет второго источника прав внутри Tasker.",
|
||||
),
|
||||
"blocks": [
|
||||
text_block(
|
||||
"tasker-provisioning",
|
||||
"Текущая архитектура",
|
||||
"""
|
||||
Launcher уже является рабочим control plane для клиентов, пользователей, групп, инвайтов, service grants, Operational Core workspace binding и project-level назначений. Authentik получает техническую group projection из Launcher и остается внутренним IdP.
|
||||
|
||||
Operational Core подключен как Plane fork: OIDC/handoff, ExternalIdentityLink, live access middleware, workspace policy hook и internal adapter endpoints живут в Tasker, а Launcher вызывает их через защищенный server-to-server API. Прямых записей Launcher в Plane DB нет.
|
||||
|
||||
Фактически реализованы: auto-create/link локального Tasker user по verified OIDC/handoff claims, workspace membership bridge, project membership bridge, policy check на создание workspace, NODE.DC create-workspace UX и очистка stale assignees после снятия пользователей из workspace/project.
|
||||
|
||||
Открытая архитектурная граница: нужно формально закрепить managedBy=launcher/managedBy=tasker для workspace, чтобы enterprise-workspace управлялись из Launcher, а standalone/public workspace сохраняли штатные Tasker users/invites/admin mechanics.
|
||||
""",
|
||||
),
|
||||
text_block(
|
||||
"tasker-provisioning",
|
||||
"Этап 1. OIDC fail-safe и local user/link provisioning",
|
||||
"""
|
||||
Статус: реализовано.
|
||||
|
||||
Этап закрывает патологию первого входа: Authentik подтверждает пользователя, но Tasker не знает локального User/ExternalIdentityLink. Сейчас Tasker умеет idempotently создать или связать локального пользователя по verified OIDC/handoff claims и не уводит пользователя в бесконечный login loop.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"tasker-provisioning1",
|
||||
"Чекер этапа 1. OIDC fail-safe и local user/link provisioning",
|
||||
[
|
||||
{"text": "Развести ошибки: нет platform access, нет local user/link, нет workspace membership.", "checked": True},
|
||||
{"text": "Добавить auto-link existing Plane user по email отдельным env-флагом.", "checked": True},
|
||||
{"text": "Добавить auto-create Tasker user по verified OIDC claims отдельным env-флагом.", "checked": True},
|
||||
{"text": "Создавать/обновлять ExternalIdentityLink provider=authentik + subject.", "checked": True},
|
||||
{"text": "Синхронизировать display name/email/avatar без пересоздания старого Plane user.", "checked": True},
|
||||
{"text": "Сохранить standalone Plane auth/API механизмы вне NODE.DC env-профиля.", "checked": True},
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"tasker-provisioning",
|
||||
"Реализация этапа 1",
|
||||
"""
|
||||
Tasker OIDC слой живет в plane-src/apps/api/plane/authentication/views/app/oidc.py. Он проверяет state/nonce/JWKS/audience/issuer, принимает groups, резолвит или создает локального пользователя и обновляет ExternalIdentityLink.
|
||||
|
||||
Launcher service handoff ведет в /auth/nodedc/handoff/ и потребляется через защищенный /api/internal/handoff/consume. Handoff передает normalized user, avatarUrl и groups без раскрытия пароля или Authentik service token.
|
||||
|
||||
Профильная синхронизация идет из claims и Launcher avatar URL. Существующий Plane owner dcctouch@gmail.com не пересоздается: связь держится через ExternalIdentityLink и старый Plane user сохраняет workspace/project/task связи.
|
||||
""",
|
||||
),
|
||||
text_block(
|
||||
"tasker-provisioning",
|
||||
"Этап 2. Launcher -> Tasker workspace adapter",
|
||||
"""
|
||||
Статус: реализовано.
|
||||
|
||||
Этап добавляет управляемый bridge для enterprise workspace: Launcher может назначить или снять пользователя в Operational Core workspace через internal Tasker API, а Tasker idempotently меняет WorkspaceMember без Plane email-invite.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"tasker-provisioning2",
|
||||
"Чекер этапа 2. Launcher -> Tasker workspace adapter",
|
||||
[
|
||||
{"text": "Добавить Tasker internal workspace catalog endpoint.", "checked": True},
|
||||
{"text": "Добавить internal endpoint ensure workspace membership.", "checked": True},
|
||||
{"text": "Добавить internal endpoint remove workspace membership.", "checked": True},
|
||||
{"text": "Защитить endpoints внутренним token/secret.", "checked": True},
|
||||
{"text": "Добавить Launcher admin routes для workspace membership.", "checked": True},
|
||||
{"text": "Сохранять workspace membership projection в Launcher storage.", "checked": True},
|
||||
{"text": "Не использовать Plane email-invite как основной enterprise flow.", "checked": True},
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"tasker-provisioning",
|
||||
"Реализация этапа 2",
|
||||
"""
|
||||
Tasker adapter endpoints лежат в plane-src/apps/api/plane/authentication/views/nodedc_workspace_adapter.py и подключены в plane-src/apps/api/plane/urls.py. Они резолвят workspace/user по slug/id/email/subject, создают или деактивируют WorkspaceMember и возвращают normalized membership summary.
|
||||
|
||||
Launcher BFF вызывает adapter из server/dev-server.mjs, проверяет права администратора на client/user, пишет projection через server/control-plane-store.mjs и обновляет UI через admin overlay.
|
||||
""",
|
||||
),
|
||||
text_block(
|
||||
"tasker-provisioning",
|
||||
"Этап 3. Workspace onboarding policy",
|
||||
"""
|
||||
Статус: частично реализовано.
|
||||
|
||||
Сервисный доступ к Operational Core и право создавать workspace разделены. Launcher уже возвращает workspacePolicy через internal access check, а Tasker спрашивает policy перед create-workspace flow. Полная трехрежимная модель client/user/group policy еще не оформлена как production data model.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"tasker-provisioning3",
|
||||
"Чекер этапа 3. Workspace onboarding policy",
|
||||
[
|
||||
{"text": "Добавить Launcher setting taskManager.workspaceCreationPolicy.", "checked": True},
|
||||
{"text": "Возвращать workspacePolicy из /api/internal/access/check.", "checked": True},
|
||||
{"text": "Добавить Tasker backend workspace policy resolver.", "checked": True},
|
||||
{"text": "Сохранить self-service create-workspace flow в NODE.DC дизайне.", "checked": True},
|
||||
"Оформить admin_managed ожидание назначения без create workspace.",
|
||||
"Добавить production policy model на уровне client/service/user/group.",
|
||||
"Связать policy с managedBy=launcher/managedBy=tasker.",
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"tasker-provisioning",
|
||||
"Реализация этапа 3",
|
||||
"""
|
||||
Launcher хранит базовую настройку workspace creation policy в settings.taskManager. Tasker использует plane.authentication.nodedc_workspace_policy, чтобы получить решение из Launcher access-check endpoint и разрешить или запретить создание workspace.
|
||||
|
||||
Create workspace UI в Tasker приведен к NODE.DC auth-card layout без удаления штатной Plane формы: вариант nodedc-auth сохраняет standalone совместимость и не ломает остальные вызовы.
|
||||
""",
|
||||
),
|
||||
text_block(
|
||||
"tasker-provisioning",
|
||||
"Этап 4. Project-level доступы Operational Core из Launcher",
|
||||
"""
|
||||
Статус: реализовано локально, ожидает ручной acceptance.
|
||||
|
||||
Этап закрывает следующий слой после workspace binding: Launcher может назначать роль пользователя внутри конкретного проекта Tasker. Это остается adapter-вызовом через internal API, а не прямой записью в Plane DB.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"tasker-provisioning4",
|
||||
"Чекер этапа 4. Project-level access bridge",
|
||||
[
|
||||
{"text": "Расширить Tasker workspace catalog проектами внутри workspace.", "checked": True},
|
||||
{"text": "Добавить internal endpoint ensure project membership.", "checked": True},
|
||||
{"text": "Добавить internal endpoint remove project membership.", "checked": True},
|
||||
{"text": "Сохранять project membership projection в Launcher storage.", "checked": True},
|
||||
{"text": "Показать workspaces и projects в Operational Core modal.", "checked": True},
|
||||
{"text": "При project role гарантировать минимальный workspace membership.", "checked": True},
|
||||
"Провести ручной acceptance: назначение роли проекта, refresh, вход пользователем в Tasker.",
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"tasker-provisioning",
|
||||
"Реализация этапа 4",
|
||||
"""
|
||||
Tasker: добавлены endpoints /api/internal/nodedc/project-memberships/ensure/ и /api/internal/nodedc/project-memberships/remove/. Они защищены тем же internal token, резолвят workspace/project/user и idempotently создают или обновляют ProjectMember.
|
||||
|
||||
Launcher: добавлены admin routes для project memberships, control-plane projection taskManagerProjectMemberships и UI в Operational Core modal. Роль проекта меняется кликом: прочерк, гость, участник, админ.
|
||||
|
||||
Проверки этапа ранее проходили: npm run build в Launcher, node --check server/dev-server.mjs server/control-plane-store.mjs и python compile для Tasker adapter/routes.
|
||||
""",
|
||||
),
|
||||
text_block(
|
||||
"tasker-provisioning",
|
||||
"Этап 5. Stale assignees cleanup после снятия пользователей",
|
||||
"""
|
||||
Статус: реализовано в рабочем дереве, ожидает финальную проверку и коммит после подтверждения.
|
||||
|
||||
После удаления, блокировки или снятия пользователя из workspace/project Tasker не должен продолжать показывать его исполнителем в карточках и группировках. Последняя локальная правка удаляет IssueAssignee на membership remove и фильтрует assignee arrays только по активным workspace/project membership.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"tasker-provisioning5",
|
||||
"Чекер этапа 5. Stale assignees cleanup",
|
||||
[
|
||||
{"text": "Удалять IssueAssignee при снятии workspace membership.", "checked": True},
|
||||
{"text": "Удалять IssueAssignee при снятии project membership.", "checked": True},
|
||||
{"text": "Покрыть admin/license/member remove paths.", "checked": True},
|
||||
{"text": "Фильтровать backend assignee_ids по active workspace/project membership.", "checked": True},
|
||||
{"text": "Добавить frontend guard в internal Kanban card.", "checked": True},
|
||||
{"text": "Проверить текущую БД на реально stale IssueAssignee.", "checked": True},
|
||||
"Прогнать целевой regression по issue list/kanban после restart backend/web.",
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"tasker-provisioning",
|
||||
"Реализация этапа 5",
|
||||
"""
|
||||
Затронуты Tasker файлы: plane-src/apps/api/plane/authentication/views/nodedc_workspace_adapter.py, workspace/project/member/license views, issue list/detail endpoints, common grouper utilities и internal-contour-card.tsx.
|
||||
|
||||
Проверка БД 2026-05-09: запрос по активным IssueAssignee без active WorkspaceMember/ProjectMember вернул 0 записей. Значит текущий runtime не содержит реально stale assignee links после последней очистки.
|
||||
|
||||
Рабочее дерево Task Manager остается dirty: этот этап еще не закоммичен и требует финальной проверки перед переводом карточного пункта в полностью закрытое состояние.
|
||||
""",
|
||||
),
|
||||
text_block(
|
||||
"tasker-provisioning",
|
||||
"Этап 6. Source-of-truth split managedBy",
|
||||
"""
|
||||
Статус: следующий критический этап.
|
||||
|
||||
Нужно формально закрепить источник управления для workspace. managedBy=launcher означает enterprise workspace: пользователи, инвайты и базовые роли идут из Launcher, а Tasker не должен давать конфликтующее управление. managedBy=tasker означает standalone/public workspace: штатные Tasker механизмы пользователей, инвайтов и ролей остаются включенными.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"tasker-provisioning6",
|
||||
"Чекер этапа 6. Source-of-truth split managedBy",
|
||||
[
|
||||
"Добавить managedBy в Launcher Tasker workspace binding.",
|
||||
"Возвращать managedBy/workspacePolicy из Launcher internal access-check.",
|
||||
"Передавать managedBy в Tasker adapter responses или workspace policy resolver.",
|
||||
"Скрыть или readonly-заблокировать Tasker users/invites для managedBy=launcher.",
|
||||
"Оставить Tasker users/invites включенными для managedBy=tasker.",
|
||||
"Проверить enterprise client admin и public self-service user flows отдельно.",
|
||||
"Зафиксировать правила в NDCPLATFORM-4 и NDCPLATFORM-10 после реализации.",
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
"slug": "safari-workspace-crash-debug",
|
||||
"name": "Safari: workspace crash и storage/OIDC debug",
|
||||
"priority": "high",
|
||||
"state_group": "cancelled",
|
||||
"assignees": [CODEX_EMAIL],
|
||||
"description_html": html(
|
||||
"Отложенный debug Safari-only падения workspace в Task Manager. Проблема не блокирует текущую платформенную архитектуру, потому что Chrome/Chromium flow работает.",
|
||||
"Цель актуализации: сохранить симптом и границы диагностики, но не смешивать browser-specific bug с Launcher/Auth/Tasker source-of-truth работами.",
|
||||
"Статус: deferred. Возвращаться после закрытия критичного managedBy/source-of-truth этапа или при воспроизводимом Safari regression report.",
|
||||
),
|
||||
"blocks": [
|
||||
text_block(
|
||||
"safari-crash",
|
||||
"Текущая архитектура",
|
||||
"""
|
||||
Safari-only падение workspace относится к browser/runtime compatibility слою Task Manager. Оно не меняет целевую архитектуру: Authentik остается внутренним IdP, Launcher — source of truth для enterprise-доступов, Task Manager — standalone-capable Operational Core module.
|
||||
|
||||
Пока Chrome/Chromium flow работает, этот debug не должен блокировать платформенную работу по access matrix, workspace onboarding и managedBy split. Карточка остается отложенной, чтобы не смешивать runtime browser bug с архитектурными задачами.
|
||||
""",
|
||||
),
|
||||
text_block(
|
||||
"safari-crash",
|
||||
"Этап 1. Сохранить симптом и границы debug",
|
||||
"""
|
||||
Статус: зафиксировано как deferred.
|
||||
|
||||
Нужно вернуться к карточке только при наличии воспроизводимого сценария: Safari version, URL, user, workspace slug, console trace, network trace, storage/cookie state и отличие от Chrome на том же аккаунте.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"safari1",
|
||||
"Чекер этапа 1. Deferred symptom",
|
||||
[
|
||||
{"text": "Не считать Safari-only падение блокером Launcher/Auth/Tasker архитектуры.", "checked": True},
|
||||
{"text": "Сохранить карточку как отдельный debug bucket.", "checked": True},
|
||||
"Снять Safari console trace.",
|
||||
"Снять Safari network trace.",
|
||||
"Сравнить localStorage/sessionStorage/cookies Safari vs Chrome.",
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"safari-crash",
|
||||
"Этап 2. Future Safari diagnostics",
|
||||
"""
|
||||
Статус: deferred.
|
||||
|
||||
Будущий debug должен идти от воспроизводимого crash-path: auth/session sync, workspace bootstrap API, storage hydration, frontend route boundary, browser-specific cookie policy или WebKit-only JS/runtime issue.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"safari2",
|
||||
"Чекер этапа 2. Safari diagnostics",
|
||||
[
|
||||
"Воспроизвести падение на актуальном Safari.",
|
||||
"Отделить auth/session issue от frontend runtime crash.",
|
||||
"Проверить WebKit cookie/storage policy на task.local.nodedc.",
|
||||
"Сформулировать минимальный фикс без изменения общей архитектуры.",
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
"slug": "public-user-entry-and-service-access",
|
||||
"name": "Публичный контур пользователей",
|
||||
"priority": "high",
|
||||
"state_group": "backlog",
|
||||
"assignees": [PLATFORM_OWNER_EMAIL, CODEX_EMAIL],
|
||||
"description_html": html(
|
||||
"Отдельный public/open-access контур для внешних пользователей, которые приходят не как участники клиентской компании, а как самостоятельные пользователи сервисов NODE.DC.",
|
||||
"Цель: не плодить новый админский интерфейс, а расширить существующий Launcher control plane. Enterprise-компании остаются company-scoped, public users попадают в отдельный Public Access Pool и получают сервисные доступы вручную через root-admin flow.",
|
||||
"Критерий приемки: пользователь может нажать Запросить доступ в Auth/Login, заявка появляется в Launcher в разделе Инвайты как вкладка Заявки, root admin вручную апрувит/отклоняет, генерирует ссылку, а после входа пользователь видит только витрину и свои разрешенные сервисы.",
|
||||
),
|
||||
"blocks": [
|
||||
text_block(
|
||||
"public-users",
|
||||
"Текущая архитектура",
|
||||
"""
|
||||
Уже есть единый Authentik login, branded под NODE.DC, и Launcher как точка входа. Launcher умеет показывать витрину приложений, хранить клиентов, пользователей, инвайты, группы, матрицу доступов и привязку Operational Core workspace к клиенту.
|
||||
|
||||
Enterprise-сценарий частично реализован: root admin создает компанию, добавляет туда client admin, выдает доступы, а client admin дальше управляет пользователями, группами, инвайтами и назначениями в рамках своей компании через Launcher. В Operational Core такие пользователи не должны видеть управление users/invites/workspace creation для launcher-managed workspace; они работают только в среде, которую им задал админ.
|
||||
|
||||
Public/open-access контур должен быть отдельной группой пользователей, не привязанной к клиентской компании. Рабочее название: Public Access Pool / Свободный доступ. Это не новый enterprise-client и не хаотичный список отдельных компаний, а отдельный cohort в Launcher, видимый root admin. Public user не получает Launcher admin overlay, пока ему явно не выдали роль; он видит витрину и сервисы.
|
||||
|
||||
Глобальная identity должна быть одна на email/Auth subject. Если пользователь сначала попал в Public Access Pool, а потом его надо перевести в enterprise-компанию, правильная операция — не удаление и повторная регистрация, а административный перевод: создать company membership, выбрать роль/группы, при необходимости убрать public-cohort marker и пересчитать доступы.
|
||||
|
||||
Также появился standalone-сценарий Operational Core: public-пользователь после выдачи доступа к сервису может создать собственный workspace и управлять им через штатный Task Manager, но только для workspace managedBy=tasker. Создание NODE.DC identity и первичная выдача сервисного доступа остаются в Launcher.
|
||||
|
||||
Еще не реализованы: кнопка Запросить доступ в login flow, модель access requests с обязательными полями, вкладка Заявки рядом с текущими Инвайтами, Public Access Pool в Launcher admin, перевод public user в компанию, правила приглашения других людей в public Tasker workspace без почтовой инфраструктуры.
|
||||
""",
|
||||
),
|
||||
text_block(
|
||||
"public-users",
|
||||
"Этап 1. Разделение enterprise и Public Access Pool",
|
||||
"""
|
||||
Статус: backlog.
|
||||
|
||||
Нужно формально разделить два режима: enterprise/direct-contract company scope и public/open-access pool. Enterprise-контур остается текущей реализацией: root admin создает компанию, назначает client admin, client admin управляет только своей компанией через Launcher.
|
||||
|
||||
Public Access Pool — отдельный cohort для внешних самостоятельных пользователей. Он должен быть доступен root admin в существующем Launcher admin UI как отдельный режим/область рядом с компаниями, но не должен засорять список компаний сотнями псевдо-клиентов.
|
||||
|
||||
Главное правило безопасности: у каждого Operational Core workspace должен быть ровно один источник управления правами. managedBy=launcher означает управление из Launcher и readonly/hidden member management в Tasker; managedBy=tasker означает штатное workspace management внутри Operational Core, но identity/service entitlement все равно выдаются через Launcher.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"public-users1",
|
||||
"Чекер этапа 1. Разделение enterprise и Public Access Pool",
|
||||
[
|
||||
"Зафиксировать типы контуров: enterprise/direct-contract и public/open-access.",
|
||||
"Добавить модель Public Access Pool или эквивалентный user cohort без создания отдельной компании на каждого пользователя.",
|
||||
"Сделать global user уникальным по email/Auth subject независимо от public или enterprise происхождения.",
|
||||
"Показать Public Access Pool root admin в существующем Launcher admin selector/режиме.",
|
||||
"Скрыть Public Access Pool от client admin компаний.",
|
||||
"Описать managedBy=launcher для корпоративных workspace.",
|
||||
"Описать managedBy=tasker для публичных standalone workspace.",
|
||||
"Развести видимость админки Launcher для root admin, client admin и public user.",
|
||||
"Описать, как public-пользователь видит витрину сервисов без администрирования Launcher.",
|
||||
"Зафиксировать запрет прямого создания Auth/Launcher users из Tasker без Launcher approval.",
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"public-users",
|
||||
"Этап 2. Запрос приглашения из окна входа",
|
||||
"""
|
||||
Статус: backlog.
|
||||
|
||||
В branded login нужно добавить безопасный сценарий запроса доступа. Это не регистрация с паролем и не обход Authentik: пользователь оставляет заявку, а учетная запись и invite создаются только после ручного решения root admin.
|
||||
|
||||
Заявка должна попадать в Launcher admin, а не теряться в почте. Почтовое уведомление можно добавить позже как транспорт, но source of truth для обработки заявок должен быть внутри Launcher.
|
||||
|
||||
UI-решение: не добавлять новое большое окно. Расширить существующий раздел Инвайты вкладками Заявки и Сгенерированные инвайты. Для root admin вкладка Заявки показывает public/open-access заявки. Для client admin текущие company-scoped инвайты остаются без доступа к global public queue.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"public-users2",
|
||||
"Чекер этапа 2. Запрос приглашения из окна входа",
|
||||
[
|
||||
"Добавить вторичную кнопку Запросить доступ под кнопкой Войти.",
|
||||
"Добавить форму заявки с обязательными полями: email, имя, фамилия, отчество, телефон, компания.",
|
||||
"Оставить интересующий сервис/задачу и комментарий опциональными полями.",
|
||||
"Блокировать отправку, если любое обязательное поле пустое.",
|
||||
"Дублировать required validation server-side в Launcher public endpoint.",
|
||||
"Не запрашивать пароль на этапе заявки.",
|
||||
"Сохранять заявку server-side в Launcher storage/backend как accessRequest.",
|
||||
"Расширить текущий раздел Инвайты вкладками Заявки и Сгенерированные инвайты.",
|
||||
"Добавить статусы заявки: новая, в работе, принята, отклонена, архив.",
|
||||
"Добавить действие Принять: создать invite в Public Access Pool для указанного email.",
|
||||
"Добавить действие Отклонить без создания пользователя/Auth identity.",
|
||||
"Пока нет почты, показывать сгенерированную ссылку для ручной передачи админом.",
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"public-users",
|
||||
"Этап 3. Public user access flow",
|
||||
"""
|
||||
Статус: backlog.
|
||||
|
||||
После апрува пользователь регистрируется по invite link, попадает в Launcher и видит витрину сервисов. Администрирования Launcher у него нет. Он находится в Public Access Pool, но не получает доступ к Operational Core или другим сервисам автоматически.
|
||||
|
||||
Доступы к сервисам на первом этапе выдает root admin вручную через матрицу доступов. Когда public user получает Operational Core, он может создать собственный workspace в Tasker. Такой workspace должен быть managedBy=tasker: Tasker управляет проектами и workspace membership, Launcher управляет только identity, service entitlement и глобальной блокировкой пользователя.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"public-users3",
|
||||
"Чекер этапа 3. Public user access flow",
|
||||
[
|
||||
"Проверить, что public user не видит администрирование Launcher.",
|
||||
"Проверить, что public user после invite попадает в Public Access Pool.",
|
||||
"Не выдавать Operational Core автоматически только по факту принятого invite.",
|
||||
"Показывать все сервисы витрины с состояниями доступен/нет доступа.",
|
||||
"Раздавать сервисный доступ public user через матрицу доступов Launcher.",
|
||||
"Открывать Operational Core без повторной авторизации после доступа.",
|
||||
"Разрешить создание workspace внутри Tasker для managedBy=tasker.",
|
||||
"Оставить настройки workspace/project внутри Tasker включенными для managedBy=tasker.",
|
||||
"Скрывать настройки участников/инвайтов Tasker только для managedBy=launcher.",
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"public-users",
|
||||
"Этап 4. Перевод Public user в enterprise-компанию",
|
||||
"""
|
||||
Статус: backlog.
|
||||
|
||||
Нужна штатная операция root admin: перевести пользователя из Public Access Pool в конкретную компанию. Это безопаснее, чем удалять пользователя и заставлять его регистрироваться заново по enterprise-инвайту, потому что Authentik identity, session history, audit trail, accepted invites и будущие billing/usage links остаются консистентными.
|
||||
|
||||
Целевая модель: Launcher хранит global user отдельно от company memberships. Public Access Pool — cohort/source/status, а не единственный контейнер identity. При переводе root admin выбирает компанию, роль, группы и политику доступа. Система создает или обновляет ClientMembership, может архивировать public pool membership/request, пересчитывает Authentik groups и service grants. Если enterprise admin позже выписывает invite на email уже существующего public user, accept flow должен merge by email в тот же global user, а не создавать дубль.
|
||||
|
||||
Нужна также мягкая операция Убрать из Public Pool. Она не должна по умолчанию удалять global user/Auth identity: она архивирует public cohort и отзывает public-only grants. Полное удаление пользователя — отдельное destructive действие с явным cleanup-планом.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"public-users4",
|
||||
"Чекер этапа 4. Перевод Public user в компанию",
|
||||
[
|
||||
"Добавить root-admin действие Перевести в компанию для пользователя из Public Access Pool.",
|
||||
"Выбирать target company, company role и группы при переводе.",
|
||||
"Создавать или обновлять ClientMembership без создания второго global user.",
|
||||
"Принимать enterprise invite на email существующего public user через merge by email.",
|
||||
"Архивировать или отключать public cohort marker после успешного перевода по выбору admin.",
|
||||
"Пересчитывать Authentik groups/service grants после перевода.",
|
||||
"Логировать перевод в audit с source pool, target company, actor и ролью.",
|
||||
"Добавить действие Убрать из Public Pool без удаления global Auth identity по умолчанию.",
|
||||
"Оставить полное удаление user отдельным destructive flow с подтверждением и cleanup.",
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"public-users",
|
||||
"Этап 5. Public workspace collaboration и invite boundary",
|
||||
"""
|
||||
Статус: backlog.
|
||||
|
||||
Самый опасный участок — как public-пользователи приглашают других людей в свой Operational Core workspace без готовой почтовой инфраструктуры. Нельзя позволять Tasker напрямую создавать Authentik/Launcher users: это ломает единый identity perimeter.
|
||||
|
||||
Правило для NODE.DC public mode: если приглашенный email уже принадлежит активному NODE.DC user с доступом к Operational Core, Tasker может добавить его в managedBy=tasker workspace по точному email/ID без глобального поиска людей. Если пользователя нет или у него нет entitlement на Operational Core, Tasker должен создать request в Launcher с контекстом workspace/requestedBy/targetEmail/role. Root admin вручную апрувит, генерирует invite link и после принятия доступ доводится до нужного workspace через Tasker adapter.
|
||||
|
||||
Для managedBy=launcher workspace любые Tasker-side invites/users остаются скрытыми или readonly. Приглашения в enterprise-контуре идут через Launcher company admin/root admin, как в текущей реализации.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"public-users5",
|
||||
"Чекер этапа 5. Public workspace collaboration",
|
||||
[
|
||||
"Запретить прямое создание Authentik/Launcher user из Tasker invite flow.",
|
||||
"Разрешить добавление существующего NODE.DC user в public managedBy=tasker workspace по точному email/ID.",
|
||||
"Проверять, что target user имеет Operational Core entitlement перед прямым добавлением.",
|
||||
"Если target user отсутствует или не имеет entitlement, создавать Launcher accessRequest с Tasker workspace context.",
|
||||
"Показывать такие заявки root admin во вкладке Заявки с источником Operational Core.",
|
||||
"После approval создавать invite link и связывать accepted user с исходным Tasker workspace.",
|
||||
"Оставить enterprise managedBy=launcher workspace без Tasker-side invites.",
|
||||
"Отложить автоматическую email-доставку до отдельного mailer/email этапа.",
|
||||
],
|
||||
),
|
||||
text_block(
|
||||
"public-users",
|
||||
"Этап 6. Billing-ready модель",
|
||||
"""
|
||||
Статус: backlog.
|
||||
|
||||
Биллинг пока не реализуется, но модель публичного контура должна не закрыть путь к оплатам. Для enterprise-клиентов остаются прямые договоры и ручные даты договора/оплаты. Для public/self-service пользователей позже появятся подписки, тарифы, лимиты и модульные entitlements.
|
||||
""",
|
||||
),
|
||||
checker(
|
||||
"public-users6",
|
||||
"Чекер этапа 6. Billing-ready модель",
|
||||
[
|
||||
"Описать entitlement по каждому сервису отдельно.",
|
||||
"Описать статус подписки public user: trial, active, expired, blocked.",
|
||||
"Заложить лимиты Operational Core: workspace count, members count, storage.",
|
||||
"Заложить лимиты Voice Tasker: minutes, requests, model tier.",
|
||||
"Не внедрять платежный провайдер до отдельного billing RFC.",
|
||||
"Не смешивать прямые договоры enterprise-клиентов и public subscriptions.",
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -1073,6 +1651,7 @@ def ensure_issue(workspace, project, codex_user, spec):
|
|||
issue.target_date = None
|
||||
issue.external_source = SOURCE
|
||||
issue.external_id = spec["slug"]
|
||||
issue.created_by = issue.created_by or codex_user
|
||||
issue.updated_by = codex_user
|
||||
issue.save(disable_auto_set_user=True)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue