# Local Deployment Plan

## Domains

Для локальной разработки целевая схема:

```text
127.0.0.1 auth.local.nodedc
127.0.0.1 launcher.local.nodedc
127.0.0.1 task.local.nodedc
127.0.0.1 agents.local.nodedc
127.0.0.1 tender.local.nodedc
127.0.0.1 onec.local.nodedc
127.0.0.1 dm.local.nodedc
```

На macOS изменение `/etc/hosts` требует admin password. Если агент не может выполнить `sudo`, эти строки нужно добавить вручную.

## Current local apps

Launcher сейчас запускается как Vite app + local BFF из:

```text
/Users/dcconstructions/Downloads/mnt/data/nodedc_launcher
```

В dev режиме `npm run dev` запускает BFF на `:5173`; Vite подключен как middleware. Чистый Vite режим сохранен как `npm run dev:vite`.

Task Manager сейчас доступен как Plane self-host runtime:

```text
http://localhost:8090
```

Runtime:

```text
/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/plane-app
```

Source fork:

```text
/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/plane-src
```

## AI Workspace Topology

Локальная доступность UI не равна честному AI Workspace e2e. Перед интерпретацией результата запускайте:

```bash
cd /Users/dcconstructions/Downloads/mnt/NODEDC/platform
sh infra/scripts/check-local-test-system.sh
```

Скрипт ничего не прокидывает, не деплоит и не меняет `.env`. Он проверяет env/compose/contracts/smoke и классифицирует текущий режим:

- `contract-only`: можно доверять только контрактным smoke-тестам без runtime e2e.
- `local-ops-vertical`: локальный Ops Gateway и локальный Tasker проверяются вертикально, без live Codex worker.
- `local-ui-only`: локальный UI доступен, но AI write path не доказан.
- `local-remote-worker-relay`: product apps локальные, local Assistant включил action relay, а `ai-hub.nodedc.ru` используется только как транспорт к удаленному Codex worker.
- `hybrid-prod-bridge`: часть контура локальная, но action relay не настроен или topology смешана. Такой результат нельзя считать local e2e.
- `true-local-e2e`: Engine, AI Workspace Assistant, Hub, Ops Gateway, Tasker и worker находятся в одной локальной топологии.
- `tunnel-local-e2e`: то же самое через явно выбранный tunnel/Tailscale-профиль.

Не надо пытаться неявно “дотянуть” Mac-local окружение до Synology. Если нужен Tailscale, это отдельный явный профиль: Hub public URL, Gateway public URL и Engine/Tasker downstream должны быть заданы так, чтобы именно выполняющий Codex worker мог их достичь. До этого UI-диалоги через удаленного агента считаются `hybrid-prod-bridge`, а не доказательством локальной записи.

Канон локального тестирования описан в `docs/LOCAL_TESTING_SYSTEM.md`.

Перед controlled Synology apply запускайте общий predeploy gate:

```bash
infra/scripts/check-ai-workspace-release-gates.sh
```

Он не деплоит, не перезапускает контейнеры и не меняет `.env`. Скрипт только выполняет meaningful проверки до runtime e2e: topology classification, Platform contract smoke, Ops Gateway static/build/smoke, Engine static check и diff hygiene.

Адресная модель описана в `docs/AI_WORKSPACE_CONFIG_CONTRACT.md`. При изменении env examples, compose или AI Workspace URL запускайте:

```bash
infra/scripts/check-ai-workspace-config-contract.sh
```

## Target local flow

Первый local infra milestone:

```text
auth.local.nodedc      -> Authentik
launcher.local.nodedc  -> Launcher web/backend
task.local.nodedc      -> Plane proxy/runtime
```

Все внешние запросы идут через reverse proxy.

На этом этапе reverse proxy реализуется через Caddy, а Authentik запускается за ним без прямой публикации host ports.

Для текущей локальной машины proxy image задан как `nodedc/plane-proxy:ru`: это уже существующий Caddy-based image из Plane-стенда. На чистой машине можно заменить `PLATFORM_PROXY_IMAGE` на `caddy:2-alpine`.

Текущие приложения подключаются как внешние upstream:

```text
launcher.local.nodedc -> host.docker.internal:5173
task.local.nodedc     -> host.docker.internal:8090
```

Это сохраняет Launcher и Plane в их текущих репозиториях и runtime-папках.

## Environment

Базовые переменные должны жить в:

```text
platform/infra/.env
```

Пример значений хранится в:

```text
platform/infra/.env.example
```

Для генерации локальных secrets:

```bash
cd /Users/dcconstructions/Downloads/mnt/NODEDC/platform
./infra/scripts/init-dev-env.sh
```

Скрипт создает `platform/infra/.env`, выставляет права `600` и не перезаписывает существующий файл.

## Start commands

```bash
cd /Users/dcconstructions/Downloads/mnt/NODEDC/platform
docker compose --env-file infra/.env -f infra/docker-compose.dev.yml up -d
```

## Authentik bootstrap

After Authentik is healthy, bootstrap local NODE.DC identity objects:

```bash
NODEDC_BOOTSTRAP_ADMIN_EMAIL=dcctouch@gmail.com infra/scripts/bootstrap-authentik-dev.sh
```

This creates:

- `nodedc:superadmin`;
- `nodedc:launcher:admin`;
- `nodedc:launcher:user`;
- `nodedc:taskmanager:admin`;
- `nodedc:taskmanager:user`;
- `NODE.DC Launcher` application with OAuth2/OIDC provider;
- `NODE.DC Task Manager` application with OAuth2/OIDC provider;
- group bindings for application access.

OIDC providers must have a signing key. Without it Authentik returns an empty JWKS (`{}`), and Launcher callback fails token validation.

The script fills missing `LAUNCHER_OIDC_CLIENT_SECRET` and `PLANE_OIDC_CLIENT_SECRET` values in ignored `infra/.env`. Secrets are not committed.

Проверка:

```bash
docker compose --env-file infra/.env -f infra/docker-compose.dev.yml ps
curl -I -H 'Host: auth.local.nodedc' http://127.0.0.1/
curl -I -H 'Host: launcher.local.nodedc' http://127.0.0.1/
curl -I -H 'Host: task.local.nodedc' http://127.0.0.1/
```

Текущая локальная проверка:

```text
auth.local.nodedc -> 302 Authentik authentication flow
launcher.local.nodedc -> 200 Launcher/Vite HTML
task.local.nodedc -> 200 Plane HTML
```

Эти проверки выполнены через `curl` с `Host` header. Для проверки в браузере нужны записи в `/etc/hosts`.

## Authentik version note

Официальный compose Authentik для текущей ветки 2026.2 использует PostgreSQL, `server` и `worker`. Redis, указанный в раннем ТЗ как ожидаемый сервис, в актуальном официальном compose не используется. Если позже будет выбран старый pinned Authentik или отдельная HA-схема, Redis надо вернуть отдельной задачей.

## Implementation order

1. Согласовать локальные domain/port bindings.
2. Добавить reverse proxy config.
3. Поднять Authentik server, worker и Postgres.
4. Прокинуть Authentik за proxy с корректными headers.
5. Подключить текущий Launcher как внешний сервис или через compose service.
6. Подключить текущий Plane runtime как внешний service target.
7. После стабилизации собрать воспроизводимый compose.

## Notes

Текущий Plane runtime не переносится на нулевом этапе. Любое изменение `plane-app/plane.env`, volumes или startup scripts делается только после backup и отдельной задачи.
