# Сон и пробуждение Galaxy-приложения

Galaxy-приложение засыпает по простою вместе с галактикой-носителем, и на время сна его контейнер остановлен. Эта страница описывает, что именно останавливается, какие поля показывают настоящее состояние, чем приложение поднять и сколько это занимает.

[Что останавливается](#что-останавливается-во-сне) · [Поля состояния](#поля-status-и-reachability-отвечают-на-разные-вопросы) · [Чем поднять](#чем-поднять-спящее-приложение) · [Логи](#логи-спящего-приложения) · [Окно по расписанию](#окно-пробуждения-по-расписанию)

## Что останавливается во сне

Приложение живёт в контейнере на общем хосте — галактике. Спать могут обе части, и это разные состояния.

| Что спит | Как это выглядит | Что нужно, чтобы поднять |
|---|---|---|
| Контейнер приложения, галактика работает | Приложение не отвечает, соседние приложения той же галактики работают | Запуск контейнера — секунды |
| Галактика вместе со всеми контейнерами | Не отвечает ни одно приложение этой галактики | Загрузка машины галактики, затем запуск контейнера — минуты |

Постоянный том `/data` сон переживает. Пробуждение запускает **тот же** контейнер, а не создаёт новый, поэтому строки, которые приложение записало в свой поток вывода до сна, доступны и после пробуждения. Полностью новый контейнер появляется только при повторной [загрузке кода](./deploy/deploy.md) — вот она журнал предыдущего контейнера не сохраняет.

## Поля status и reachability отвечают на разные вопросы

Поле `status` в ответе [`GET /v1/infra/servers/:id`](./servers/get.md) — это состояние учётной записи приложения на платформе. Пробуждение переводит запись в `running` в конце, когда контейнер уже запущен, поэтому во время подъёма галактики запись остаётся в `sleeping` — на холодной машине это занимает минуты. Обратное расхождение тоже возможно: запись может числиться в `running`, пока контейнер уже не работает.

Поэтому рядом идёт блок `reachability` — он отвечает на вопрос «может ли приложение ответить прямо сейчас». Блок приходит только у приложения, `kind` равен `GALAXY_APP`, у остальных типов серверов там `null`.

```json
{
  "success": true,
  "data": {
    "id": "e22bb297-8a0a-4597-9b42-47d059cd1090",
    "kind": "GALAXY_APP",
    "status": "sleeping",
    "reachability": {
      "effectiveStatus": "running",
      "hostStatus": "running",
      "hostTunnel": "CONNECTED",
      "container": "running",
      "forwarder": "active",
      "publicEntry": "live",
      "probe": "ok",
      "probedAt": "2026-08-12T13:24:58.117Z"
    }
  }
}
```

Показаны поля, относящиеся к сну. Полный ответ — [`GET /v1/infra/servers/:id`](./servers/get.md).

| Поле | Описание |
|------|---------|
| `effectiveStatus` | Сводный ответ о доступности — `running`, `sleeping`, `waking`, `unreachable` или `unknown`. Как он выводится, показано в таблице ниже |
| `hostStatus` | Состояние галактики-носителя в том же наборе значений, что и `status` сервера |
| `hostTunnel` | Соединение галактики с платформой — `CONNECTED`, `DISCONNECTED` или `NONE` |
| `container` | Контейнер приложения — `running`, `stopped` или `unknown`. Значение `unknown` означает, что платформа не смогла посмотреть, а не что контейнер остановлен |
| `forwarder` | Маршрутизация к контейнеру на хосте — `active`, `inactive` или `unknown`. Во сне останавливается вместе с контейнером |
| `publicEntry` | Публичный адрес приложения — `live` (у шлюза есть живой туннель на поддомен приложения), `no-tunnel` (туннеля нет, снаружи адрес не обслуживается) или `unknown` (спросить шлюз не удалось). Значение `unknown` вердикт никогда не ухудшает |
| `probe` | Чем закончился живой опрос хоста — `ok`, `host-down` (галактика спит или недоступна, опрос не отправлялся), `host-busy` (хост занят другой операцией), `failed` или `not-attempted` |
| `probedAt` | Время опроса по стандарту ISO 8601. Значение `null` означает, что опроса не было |

Как складывается `effectiveStatus`:

| Состояние | `effectiveStatus` |
|---|---|
| Галактика загружается | `waking` |
| Галактика спит | `sleeping` |
| Галактика работает, контейнер остановлен | `sleeping` |
| Галактика работает, контейнер работает, маршрутизация активна, у публичного адреса есть туннель | `running` |
| Галактика работает, контейнер работает, маршрутизация активна, туннеля у публичного адреса нет | `unreachable` |
| Галактика работает, контейнер работает, маршрутизация остановлена | `unreachable` |
| Галактика в ошибке либо потеряла соединение с платформой | `unreachable` |
| Посмотреть не удалось | `unknown` |

**Публичный вход проверяется отдельно.** Контейнер и маршрутизация измеряются на самой галактике, а последний участок пути до `https://app-XXXX.vibecode.bitrix24.tech` — это туннель приложения на шлюзе, и на галактике его не видно: запущенный юнит маршрутизации означает, что его запустили, а не что он дозвонился до шлюза. Поэтому приложение с живым контейнером и активной маршрутизацией, но без туннеля, отвечает `unreachable`, а не `running` — снаружи такой адрес не обслуживается. Если состояние шлюза узнать не удалось, `publicEntry` равен `unknown` и вердикт остаётся прежним: «не смогли посмотреть» — это не «ничего не работает».

**Важно:** живой опрос хоста платформа делает только когда галактика работает и соединена с платформой. Спящую галактику чтение состояния не будит, поэтому у спящей галактики `container` и `forwarder` всегда `unknown`, а `probe` равен `host-down`. Результат опроса платформа держит несколько секунд и отдаёт из памяти: опрос занимает канал команд, общий для всех приложений галактики, поэтому частый опрос состояния его не занимает.

## Чем поднять спящее приложение

| Способ | Что делает |
|---|---|
| [`POST /v1/infra/servers/:id/wake`](./lifecycle/wake.md) | Поднимает приложение через хост. При необходимости запускает галактику, затем стартует контейнер |
| [`POST /v1/infra/servers/:id/start`](./lifecycle/start.md) | То же самое. Отдельного смысла у двух вызовов для приложения нет |
| [Загрузка кода](./deploy/deploy.md) | Поднимает галактику сама и оставляет приложение работающим |
| Обращение к адресу приложения | Если пробуждение разрешено, запрос через шлюз поднимает приложение и до готовности отвечает кодом `BH_SERVER_WAKING` с заголовком `Retry-After` — подробности в [Авторизации в приложении на Black Hole](./app-runtime.md) |
| [Доставка события портала](./event-subscriptions.md) | Платформа поднимает приложение перед доставкой |

Ответ на вызов пробуждения приходит сразу, а не по готовности: у приложения нет своей облачной машины, и параметр `?wait=true` ожидания здесь не даёт. Готовность проверяйте опросом [`GET /v1/infra/servers/:id`](./servers/get.md) по полю `reachability.effectiveStatus` — значение `waking` означает, что галактика ещё загружается.

Сколько ждать: подъём холодной галактики платформа ждёт до 15 минут и столько же готова ждать от вызывающего. Точное значение бюджета приходит в ответе на чтение логов спящего приложения, поле `recovery.coldBootBudgetSeconds`. Приложение на уже работающей галактике стартует за секунды.

Запрет пробуждения на хосте блокирует оба вызова, и `/start` его не снимает. Коды отказов — в таблице жизненного цикла на странице [Galaxy-приложение](./galaxy.md).

При обращении к адресу приложения без туннеля подтверждённый запрет пробуждения обрабатывается отдельно. Только в режиме `PUBLIC` шлюз возвращает `409 BH_APP_UNAVAILABLE` без `Retry-After`: браузер показывает «Приложение временно недоступно» и кнопку «Обновить», машинный клиент получает JSON. Причина запрета, баланс и бренд платформы не раскрываются, пробуждение и автоматический опрос не запускаются. Уже открытая страница ожидания сменится этим экраном при следующем опросе. После снятия запрета повторите открытие вручную. В приватном режиме для опознанного пользователя сохраняется `402 BH_WAKE_BLOCKED`.

## Логи спящего приложения

Чтение [`GET /v1/infra/servers/:id/logs`](./deploy/logs.md) не будит ни приложение, ни галактику. Пока галактика спит или недоступна, ответ приходит со статусом `200`, пустым массивом `data.logs` и двумя диагностическими полями.

```json
{
  "success": true,
  "data": {
    "logs": [],
    "lines": [],
    "hint": "Galaxy host is asleep or unreachable, so the container log cannot be read right now. …",
    "recovery": {
      "reason": "The galaxy host carrying this container is not RUNNING + CONNECTED. …",
      "recoveryAction": "POST /v1/infra/servers/e22bb297-8a0a-4597-9b42-47d059cd1090/wake",
      "logsPreserved": true,
      "coldBootBudgetSeconds": 900,
      "poll": "GET /v1/infra/servers/e22bb297-8a0a-4597-9b42-47d059cd1090 (read `reachability`)",
      "wakeSchedule": { "available": true, "code": null }
    }
  }
}
```

| Поле | Описание |
|------|---------|
| `hint` | Та же подсказка обычным текстом, одной строкой. Текст зависит от того, может ли вызывающий позвать пробуждение: когда может — называет вызов пробуждения; когда нет — адреса пробуждения не называет вовсе, зато называет ровно то условие, которое сработало именно у него. Второй путь подъёма подсказка называет только тогда, когда он этому вызывающему доступен, то есть вместе с полем `recovery.deliveryWakesHost`, а не всегда |
| `recovery.reason` | Почему журнал сейчас не читается |
| `recovery.recoveryAction` | Вызов ПРОБУЖДЕНИЯ. **Поле условное — его может не быть.** Отсутствует всякий раз, когда вызывающий пробуждение позвать не может. Перечень таких состояний открыт и растёт, поэтому ветвитесь по НАЛИЧИЮ поля, а не по списку. Сегодня в него входят режим «только чтение», ключ Коворка/Кода, выключенный пилот галактик, ключ обслуживания агента, владелец с идущим удалением аккаунта и доступ к приложению по связи, когда сервером владеет другой ключ. Сюда же входит запрет пробуждения на самом ХОСТЕ галактики: он отбивает пробуждение любому вызывающему, включая полноправного владельца, поэтому в этом состоянии поля тоже нет. Проверяйте наличие ключа перед обращением: в этом состоянии адреса пробуждения нет ни в одном поле ответа — ни здесь, ни в `hint`. **Важно:** обратное не гарантия. НАЛИЧИЕ поля означает, что пробуждение не отбито ни по вызывающему, ни запретом на хосте, — но не что вызов состоится в любом случае: остаются отказы по СОСТОЯНИЮ пары, которые ответ логов не предсказывает (`422`, когда приложение не в том статусе или хост не загрузился, `404`, когда галактика осиротела). Обрабатывайте такой отказ, а не считайте наличие поля гарантией |
| `recovery.logsPreserved` | Значение `true` означает, что строки, записанные до сна, доступны после пробуждения |
| `recovery.coldBootBudgetSeconds` | Бюджет подъёма холодной галактики в секундах |
| `recovery.poll` | Чем проверять готовность вместо повторного чтения логов. **Поле условное — его может не быть.** Отсутствует, когда `GET /v1/infra/servers/{id}` отказывает самому вызывающему: у ключа обслуживания агента этого маршрута нет в перечне, а у владельца с идущим удалением аккаунта он живёт в замороженном контуре, в отличие от чтения журнала. Нет поля — просто перечитайте журнал позже, подсказка так и говорит |
| `recovery.deliveryWakesHost` | Второй путь подъёма. **Поле условное вдвойне: приходит, только когда пробуждение вызывающему недоступно И деплой ему при этом доступен.** `action` — вызов деплоя, `cost` — его цена словами. Деплой на спящее галакси-приложение поднимает хост сам, но это ВЫКЛАДКА КОДА и расход баланса, а не пробуждение — ради одного лишь чтения журнала его звать не надо. **Важно:** отсутствие поля означает, что и этот путь закрыт: послабление режима «только чтение» снимает у деплоя гейт РЕЖИМА и только его, а ключу Коворка/Кода, выключенному пилоту и ключу обслуживания агента деплой отказывают те же двери, что и пробуждению. Поле отсутствует и при запрете пробуждения на хосте — деплой спящий хост с таким запретом НЕ поднимает, а отвечает `502` |
| `recovery.wakeSchedule` | Доступно ли окно пробуждения по расписанию **этому вызывающему**: и пригодность самого приложения, и право вызывающего на запись — одним вердиктом. Поле `available` — ответ, поле `code` при отказе несёт тот же код, которым ответит создание окна. Когда `recoveryAction` отсутствует, поле приходит `available: false` с кодом той двери, которая отбила пробуждение: создание окна — та же запись и отбивают её те же двери. Исключение — дверь окна, которая проверяется раньше этой: тогда `code` называет дверь окна, и `hint` называет ту же дверь, а не говорит, что окно отбивает то же условие, что и пробуждение. **Важно:** у окна есть и ДВЕ собственные двери, которых у пробуждения нет, поэтому `code` может назвать отказ, которого в списке дверей пробуждения не найти, а `available: false` может прийти и при наличии `recoveryAction`. Первая проверяется раньше всех остальных: маршрут окна отсутствует в перечне маршрутов ключа внешнего участника, тогда как пробуждение, деплой и чтение журнала в этом перечне есть, — `EXTERNAL_COLLABORATOR_KEY_OUT_OF_SCOPE`. Вторая проверяется позже дверей режима, заморозки, Коворка и пилота, но РАНЬШЕ проверки владения сервером: маршруту окна нужен скоуп `vibe:infra`, которого чтение логов и пробуждение не требуют, поэтому до этого отказа доходит и обычный ключ без скоупа — `INFRA_SCOPE_REQUIRED`, и лечится он выдачей скоупа, а не сменой режима. Владение проверяется после скоупа, поэтому вызывающему, пришедшему по связи приложения и не несущему скоуп, придёт `INFRA_SCOPE_REQUIRED`, а не `NOT_FOUND` |

Порядок действий: поднять приложение вызовом из `recovery.recoveryAction`, дождаться `reachability.effectiveStatus` равного `running`, повторить чтение логов.

**Если поля `recoveryAction` в ответе нет** — пробуждение откажут. Причина бывает двух родов, и `hint` называет ту, что сработала: либо дверь самого вызывающего (перечень ниже), либо запрет пробуждения на ХОСТЕ галактики — тогда ключ ни при чём, отказ придёт любому, и снимать запрет идёт владелец аккаунта. Вызов пробуждения в этом состоянии отвечает `403 SERVER_WAKE_BLOCKED`, а пока у аккаунта просрочен доступ — `402` с кодом подписочной или тарифной стены. Окно по расписанию в этом состоянии создать можно, но хост оно не поднимет: запрет действует и на пробуждение по расписанию. Адреса пробуждения нет и в прозе: в этом состоянии ответ намеренно не называет ни `POST …/wake`, ни создание окна по расписанию — окно отбивает та же дверь. `hint` вместо адреса называет ровно то условие, которое сработало у него самого, а не список через «или», — и называет ПЕРВОЕ по порядку, в каком двери отбивают на самом деле. Порядок такой: у ключа обслуживания агента пробуждения нет в перечне маршрутов; при идущем удалении аккаунта пробуждение закрыто целиком, и смена ключа не поможет — заморожен владелец, а не ключ (доставка при этом остаётся открытой, см. ниже про второй путь); режим ключа переключается по [Режиму доступа](/docs/keys-auth/access-mode); ключу Коворка/Кода управляющий контур закрыт целиком; выключенный пилот галактик снимает администратор аккаунта; и последняя — доступ к приложению по связи, когда сервером владеет другой ключ: пробуждение проверяет владение строже, чем чтение журнала, поэтому такому вызывающему оно отвечает `404`, и помогает только вызов ключом-владельцем сервера.

**У части вызывающих выход всё же есть, и это не пробуждение.** Ключу в режиме «только чтение» разрешён деплой на свой сервер, а деплой на спящее галакси-приложение **поднимает хост сам**. Тот же путь открыт ещё двум вызывающим, которым отказано в пробуждении: владельцу с идущим удалением аккаунта (заморозка стоит на плагине `/v1/infra`, а доставка живёт в другом) и тому, кого пустили по связи приложения (владение доставка проверяет мягче, чем пробуждение). Закрыт этот путь ровно трём: ключу Коворка/Кода, ключу обслуживания агента и вызывающему на аккаунте с выключенным пилотом галактик. Когда этот путь открыт, платформа называет его машинно — полем `recovery.deliveryWakesHost` (`action` — вызов, `cost` — цена словами), и цена тут не формальность: это выкладка кода и расход баланса Вайбкод, а не пробуждение. Звать его ради одного лишь чтения журнала не надо.

**Открыт он не всем, и поле это показывает.** Послабление для режима «только чтение» снимает у деплоя гейт РЕЖИМА и только его. Ключу Коворка/Кода управляющий контур закрыт целиком, при выключенном пилоте галактик отказывают любые не-GET по этому семейству, а у ключа обслуживания агента деплоя нет в его перечне маршрутов — всем троим `POST …/deploy` ответит отказом ровно так же, как и пробуждение. Поэтому **проверяйте наличие `deliveryWakesHost`, а не предполагайте его**: нет поля — открытого пути нет, и надо менять условие из `hint`. **Важно:** обратное тоже не гарантировано, ровно как у `recoveryAction`: наличие поля означает, что деплой не отбит ни по identity и режиму ключа, ни запретом на хосте. Оно не отвечает за состояние счёта НА МОМЕНТ ВЫЗОВА: заморозка баланса закрывает всё семейство `/v1/infra` целиком (`402 ACCOUNT_FROZEN`), и если она наступит после этого чтения, названный `action` получит именно её — тем более что деплой тратит баланс по построению. Пока условие не изменилось, повторять чтение логов в цикле смысла нет: ответ не изменится.

## Окно пробуждения по расписанию

Окно пробуждения поднимает приложение по расписанию, без вызова руками. Доступно оно не каждому приложению, и проверять это следует по ответу платформы, а не по общему правилу: поле `recovery.wakeSchedule` в ответе на чтение логов спящего приложения даёт готовый ответ для конкретного приложения, а при отказе — его код.

Ответ этот — и про приложение, и про вызывающего сразу. Создание окна остаётся записью, поэтому ключу в режиме «только чтение», ключу Коворка/Кода и порталу с выключенным пилотом галактик оно откажет; в этих состояниях поле приходит `available: false` с кодом отбившей двери. Пробуждение — хороший первый признак: нет `recovery.recoveryAction` — окна этому вызывающему тоже не создать. Но признак этот не полный, потому что у окна есть две двери, которых у пробуждения нет: маршрут отсутствует в перечне ключа внешнего участника (`EXTERNAL_COLLABORATOR_KEY_OUT_OF_SCOPE`) и требует скоуп `vibe:infra`, которого пробуждение не требует (`INFRA_SCOPE_REQUIRED`). Любая из двух откажет в окне и тому, кто приложение разбудить может, — поэтому читайте само поле `recovery.wakeSchedule`, а не выводите его из `recoveryAction`.

Приложение в галактике отказ `ALWAYS_ON_CONFLICT` не получает: тариф оно не выбирает, а наследует от галактики, и режимом «Всегда онлайн» платформа его не считает. Это касается и агентов, и ботов: своего таймера простоя у них нет, но окно пробуждения им доступно на общих условиях. Остальные отказы окна при этом действуют — например, `WAKE_SCHEDULE_GALAXY_DISABLED`, когда на платформе выключена возможность для приложений галактики.

Список окон, создание, изменение и удаление — [Пробуждение по расписанию](./wake-schedules.md).

## Смотрите также

- [Galaxy-приложение](./galaxy.md)
- [Разбудить сервер](./lifecycle/wake.md)
- [Логи сервера](./deploy/logs.md)
- [Получить сервер](./servers/get.md)
- [Пробуждение по расписанию](./wake-schedules.md)
- [Авторизация в приложении на Black Hole](./app-runtime.md)
