# Gateway release controller

`release-controller`는 Gateway API·frontend·orchestrator와 분리된 PM2
프로세스입니다. 관리자 GUI가 `GatewayReleaseOperation`을 만들면 controller가
선택 commit을 고정하고 다음 순서로 전환합니다.

1. commit 전용 worktree를 준비하고 frozen lockfile로 의존성을 설치합니다.
2. `release-manifest.json`의 protocol, component와 실제 migration head를
   확인합니다.
3. Gateway API와 frontend를 빌드하고 gateway migration을 적용합니다.
4. 기존 `sammo:gateway-api`, `sammo:gateway-frontend`,
   `sammo:gateway-orchestrator`를 중지하고 새 worktree에서 시작합니다.
5. 두 HTTP endpoint와 세 PM2 process가 모두 준비된 경우에만 현재·이전
   릴리스 상태를 게시합니다. 실패하면 이전 세 프로세스를 복구합니다.

Controller는 PM2 자식으로 실행되지만 자신의 `args=daemon`과 PM2 identity를
Gateway process 환경에 전달하지 않습니다. 이 값이 frontend 정의를 덮으면 Vite가
의도한 preview port 대신 기본 개발 port로 실행될 수 있으므로, process `online`
여부뿐 아니라 Gateway API와 frontend HTTP readiness를 모두 확인합니다.

## 환경 변수

- `GATEWAY_DATABASE_URL`: Gateway PostgreSQL URL입니다. 필수입니다.
- `REDIS_URL`: Gateway API와 orchestrator가 사용할 Redis URL입니다. 필수입니다.
- `GATEWAY_DB_SCHEMA`: Gateway schema이며 기본값은 `public`입니다.
- `RELEASE_CONTROLLER_WORKSPACE_ROOT`: Git checkout입니다.
- `RELEASE_CONTROLLER_WORKTREE_ROOT`: commit worktree 상위 경로입니다.
- `GATEWAY_API_PORT`, `GATEWAY_FRONTEND_PORT`, `GATEWAY_BASE_PATH`: readiness와
  frontend build 계약입니다.
- `RELEASE_CONTROLLER_POLL_MS`, `RELEASE_CONTROLLER_READINESS_TIMEOUT_MS`: queue
  poll과 준비 제한 시간입니다.
- `RELEASE_CONTROLLER_POSTGRES_POOL_MAX`: controller 자체 Gateway DB pool 상한이며
  기본값은 2입니다. Gateway API/orchestrator는 각각
  `GATEWAY_API_POSTGRES_POOL_MAX`(기본 4),
  `GATEWAY_ORCHESTRATOR_POSTGRES_POOL_MAX`(기본 2)를 사용합니다.
- `TURBO_CACHE_DIR`: 선택 사항인 공유 local cache 경로입니다. 없으면 원래
  `RELEASE_CONTROLLER_WORKSPACE_ROOT/.turbo/release-cache`를 사용합니다. 상대 경로는
  원래 workspace 기준으로 해석합니다.
- `RELEASE_TURBO_CONCURRENCY`: Turbo worker 수입니다. 기본값 1은 실행 중인 game/Gateway
  process와 4 GiB runtime을 공유하는 production cold build의 OOM을 피합니다. 더 큰
  격리 build host에서만 측정 후 2 이상으로 올립니다.

비밀값은 Git에서 제외된 환경 파일 또는 process 환경으로 전달해 주세요.
`VITE_*`에는 공개 URL만 넣어 주세요.

Self-upgrade는 controller의 script와 cwd만 선택 commit worktree로 바꿉니다.
`RELEASE_CONTROLLER_WORKSPACE_ROOT`는 원래 Git checkout을 유지해야 합니다. 이를
controller artifact worktree로 바꾸면 아직 게시된 release state가 없는 최초
DEPLOY의 rollback이 frontend build가 없는 controller worktree를 이전 Gateway로
오인할 수 있습니다.

## 설치와 실행

먼저 controller가 읽을 Gateway schema를 migration하고 의존 package를 함께
빌드합니다.

```sh
pnpm install --frozen-lockfile
pnpm exec turbo run build --filter=@sammo-ts/release-controller --concurrency=1 --ui=stream
pnpm --filter @sammo-ts/infra prisma:migrate:deploy:gateway
pnpm --filter @sammo-ts/release-controller start
```

운영에서는 마지막 명령 대신 `sammo:release-controller`라는 PM2 process로
`app/release-controller/dist/index.js daemon`을 실행해 주세요. 상태와 queue
한 건 처리는 다음 CLI로 확인할 수 있습니다.

```sh
pnpm --filter @sammo-ts/release-controller status
pnpm --filter @sammo-ts/release-controller run-once
```

Daemon은 시작 시와 이후 24시간마다 commit worktree를 자동 정리합니다. 현재·이전
Gateway release와 활성 PM2 process가 사용하는 경로는 항상 보호하고, 나머지는
마지막 사용 후 24시간과 최신 2개 cache를 보장한 뒤 제거합니다. 변경이 있거나 Git
제거가 실패한 worktree는 raw directory 삭제로 우회하지 않고 다음 주기까지
보존합니다. Profile worktree는 Gateway orchestrator가 같은 정책으로 별도
관리합니다.

## Controller self-upgrade

이 명령은 현재 daemon과 별개의 CLI process에서 실행됩니다. 대상 worktree를
빌드하고 gateway migration을 적용한 뒤 `sammo:release-controller`만 새
worktree로 전환합니다. 새 daemon 시작에 실패하면 이전 definition을
복구합니다.

```sh
pnpm --filter @sammo-ts/release-controller build
pnpm --filter @sammo-ts/release-controller self-upgrade BRANCH main
# 또는
pnpm --filter @sammo-ts/release-controller self-upgrade COMMIT <full-sha>
```

Database migration은 일반적으로 되돌리지 않습니다. 이전 애플리케이션으로
rollback하려면 새 schema와의 하위 호환성을 릴리스 전에 확인해 주세요.

## 멈춘 빌드 복구

운영 container나 PM2 process를 먼저 종료하지 마세요. 관리자 화면의
`Gateway 릴리스` 또는 profile `버전 업데이트` 작업 이력에서 로그의 마지막 단계와
작업 상태를 확인합니다.

1. `RUNNING`이고 마지막 단계가 `claim`, `resolve`, `workspace`, `build` 중 하나이면
   `빌드 중단`을 누릅니다.
2. 작업이 `CANCELLED`가 되고 로그에 빌드 종료가 기록될 때까지 기다립니다. Controller와
   orchestrator는 해당 process group에 SIGTERM을 보내고 제한 시간 뒤 SIGKILL로
   정리하며, 기존 active Gateway/profile runtime과 profile DB는 유지합니다.
3. 같은 행의 `재시도`를 누르면 최초 작업이 고정한 commit으로 새 작업을 등록합니다.
   branch의 최신 commit을 새로 선택하려면 새 배포 작업을 등록합니다.

마지막 단계가 `migration`, `switch`, `readiness`이면 중단 요청을 거부합니다. 이 구간에서
container restart, PM2 delete 또는 DB row 직접 변경으로 lease를 무효화하지 말고 작업 로그와
controller/orchestrator 상태를 조사합니다. Profile 상태가 `PAUSED`이면 runtime 장애가 아니라
turn gate가 닫힌 상태이므로 배포 완료 후 서버 관리 화면에서 `턴 재개`를 사용합니다.

호스트에서는 stack wrapper로 container와 로그를 읽기 전용 확인합니다. 운영 stack의 가까운
README에 정의된 경로에서 다음 순서로 확인하며, `down --volumes`나 `RESET`은 빌드 복구에
사용하지 않습니다.

```sh
./scripts/stack.sh ps
./scripts/stack.sh logs runtime
```

`release-manifest.json`의 `controllerProtocol`이 올라간 릴리스는 controller를
먼저 self-upgrade해야 합니다. Protocol 2는 `GatewayReleaseLog` 진행 로그 저장을
요구합니다. 구형 controller로 새 Gateway만 배포하면 관리자 화면과 controller의
기능이 어긋날 수 있으므로, 일반 배포의 manifest protocol 검사를 우회하지
마세요. Self-upgrade CLI만 다음 protocol을 허용하며 schema head와 component는
동일하게 검증합니다.

Gateway 릴리스가 terminal인데 후속 profile 작업이 `QUEUED`, `attempts=0`에서
두 poll 주기 이상 움직이지 않으면 build process를 종료할 문제가 아니라 profile
orchestrator poll 자체를 조사합니다. Gateway 전환 직후 reconcile과 worktree cleanup이
겹쳐도 PM2 Node client session은 직렬화되며, `connect/list` callback이 5초 안에 오지
않으면 해당 scheduled task를 실패시켜 다음 poll로 자동 복구합니다. 이 timeout 전후에
PM2 mutation을 수동 반복하지 말고 active Gateway release row가 없는지, orchestrator
started 로그와 profile operation attempts가 그대로인지 먼저 확인합니다. 제한 복구가
필요하면 현재 PM2 definition의 `sammo:gateway-orchestrator`만 재시작하고 Gateway
API/frontend, release-controller, profile daemon과 container는 유지합니다.
