[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"docs-source-storage":3,"docs-tabs-source-storage":6},{"content":4,"lastmod":5},"# Хранилище исходного кода\n\nХранилище исходного кода автоматически сохраняет снимки кода вашего приложения при каждом деплое. Если над приложением начинает работать другой человек или новая AI-сессия, последнюю версию можно скачать и продолжить с того же места — код не потеряется.\n\n## Зачем это нужно\n\nКогда вы дорабатываете приложение, важно не потерять рабочую версию кода. Хранилище делает это автоматически: при каждом успешном деплое платформа сохраняет снимок исходников. Если позже над приложением начнёт работать другой человек или новая AI-сессия — он скачает последнюю версию и продолжит с того же места, без ручной пересылки архивов.\n\nСнимок привязан к приложению, а не к конкретному разработчику: код остаётся у приложения, даже если меняется команда.\n\n## Чем это отличается от Git и GitHub\n\nЭто не система контроля версий и не замена Git. Хранилище решает более узкую задачу — страховка и передача проекта.\n\n| | Хранилище исходного кода | Git \u002F GitHub |\n|---|---|---|\n| Что хранит | целые снимки архива кода | историю изменений построчно |\n| Когда сохраняет | автоматически при каждом деплое | вручную, по команде разработчика |\n| Нужны ли навыки Git | нет | да |\n| Ветки, слияния, сравнение версий | нет | да |\n\nНужна командная разработка с ветками и историей — используйте Git. Нужно просто не терять рабочий код и уметь вернуться к прошлой версии — достаточно хранилища.\n\n## Как это работает\n\n1. Вы дорабатываете приложение и запускаете деплой.\n2. Платформа сама сохраняет снимок исходников — очередную версию: `v1`, `v2`, `v3`, …\n3. В любой момент смотрите список версий и скачиваете нужную.\n4. Перед публикацией приложения платформа проверяет, что свежий снимок есть.\n\n```\nправка кода  →  деплой  →  снимок vN сохранён автоматически\n                                      ↓\n        список версий  →  скачать любую  →  продолжить работу\n```\n\n## Основные понятия\n\n- **Снимок (снапшот)** — заархивированная копия исходного кода приложения на момент сохранения.\n- **Версия `vN`** — порядковый номер снимка (`v1`, `v2`, …). Самый свежий считается текущим.\n- **Тег** — метка `manual` или `published`: помечает версию как важную, чтобы её не удалила автоматическая очистка.\n- **Дедупликация** — если код не изменился, новый снимок не создаётся, возвращается уже существующая версия.\n\n## Доступ и эндпоинты\n\n**Базовый URL:** `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1`  \n**Авторизация:** заголовок `X-Api-Key`. Управлять снапшотами могут ключ авторизации приложения (`vibe_app_*`), личный ключ автора приложения (`vibe_api_*`) или администратор портала.  \n**Допустимые форматы архива:** `application\u002Fgzip`, `application\u002Fx-tar`, `application\u002Fzip`, `application\u002Foctet-stream`.  \n**Лимит тела:** 200 МБ на один запрос.\n\n**Эндпоинты:**\n\n| Метод | Путь | Действие |\n|-------|------|----------|\n| `POST` | `\u002Fv1\u002Fapps\u002F:id\u002Fsources` | Сохранить снапшот |\n| `GET` | `\u002Fv1\u002Fapps\u002F:id\u002Fsources` | Список версий |\n| `GET` | `\u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId\u002Fdownload` | Подписанная ссылка на скачивание |\n| `PATCH` | `\u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId` | Обновить теги \u002F комментарий |\n| `POST` | `\u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId\u002Ftag` | Добавить или снять тег |\n| `DELETE` | `\u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId` | Удалить версию |\n| `POST` | `\u002Fv1\u002Fapps\u002F:id\u002Fsources\u002Fcleanup` | Массовая очистка старых версий |\n\n## Сохранение исходников\n\nС версии 2026-05-23 платформа **автоматически** сохраняет байты исходников в хранилище при каждом успешном развёртывании. Это касается:\n\n- `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy { source: { content: \u003Cbase64> } }` — встроенные байты сохраняются как новая версия\n- `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy { source: { url: \u003Csigned URL из хранилища> } }` — существующая версия привязывается к идентификатору развёртывания\n- `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy { source: { versionId: 'vN' } }` — то же\n\n**Исключение**: развёртывание с внешним URL (не из хранилища Вайбкод) возвращает **409 SNAPSHOT_REQUIRED**. Чтобы обойти — либо сначала загрузите архив через `POST \u002Fv1\u002Fapps\u002F:id\u002Fsources` и разверните через `{source: {versionId: 'vN'}}`, либо передайте заголовок `X-Skip-Source-Snapshot: \u003Creason>` для явного отказа.\n\n### Ответ деплоя: блок `source`\n\nОтвет `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy` содержит блок `data.source` — результат автосохранения. По нему AI-агент или клиент понимает, попал ли код в депо:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"status\": \"running\",\n    \"appUrl\": \"https:\u002F\u002Fapp-b7c1e2a4.vibecode.bitrix24.tech\",\n    \"source\": {\n      \"autoSaved\": true,\n      \"savedVersionId\": \"v4\",\n      \"sha256\": \"a3f5d8b2c1e9f4...\",\n      \"linkedDeployId\": \"deploy:2026-05-21T10:15:30.000Z\",\n      \"skippedReason\": null\n    }\n  }\n}\n```\n\n| Поле | Тип | Описание |\n|------|-----|----------|\n| `source.autoSaved` | boolean | `true` — снимок сохранён; `false` — пропущен (причина в `skippedReason`) |\n| `source.savedVersionId` | string \\| отсутствует | Идентификатор созданной версии (`vN`) при `autoSaved: true` |\n| `source.sha256` | string \\| отсутствует | SHA-256 сохранённого архива |\n| `source.linkedDeployId` | string \\| отсутствует | Идентификатор развёртывания, к которому привязан снимок |\n| `source.skippedReason` | string \\| null | `null` при успехе; иначе причина пропуска (см. ниже) |\n\nДеплой завершается успешно (`200`) независимо от блока `source` — автосохранение работает по принципу «лучшее усилие» и никогда не валит развёртывание. Значения `skippedReason`:\n\n| `skippedReason` | Когда |\n|-----------------|-------|\n| `feature-disabled-platform` | Депонирование выключено на уровне платформы |\n| `feature-disabled-portal` | Владелец портала отключил депонирование для своего портала |\n| `external-url-or-toggles-off` | Источник — внешний URL (не из хранилища Вайбкод) |\n| `save-failed` | Временная ошибка хранилища — повторите деплой или сохраните вручную через `POST \u002Fv1\u002Fapps\u002F:id\u002Fsources` |\n| `\u003Cзначение заголовка>` | Был передан `X-Skip-Source-Snapshot: \u003Creason>` — снимок пропущен намеренно |\n\nЧтобы убедиться, что код задепонирован, проверьте `data.source.autoSaved === true` и сохраните `savedVersionId` для последующего отката или передачи.\n\n### Когда снапшот не создаётся\n\nЕсли сохранение исходников отключено на портале или источник — внешний URL (не хранилище Вайбкод), снапшот не создаётся, а развёртывание завершается штатно. Чтобы сохранить версию явно, загрузите архив через `POST \u002Fv1\u002Fapps\u002F:id\u002Fsources`.\n\n### Когда `POST \u002Fsources` всё ещё нужен явно\n\nТолько три сценария:\n\n1. **Пометить версию** — поставить тег `manual` или `published`, чтобы версия хранилась бессрочно.\n2. **Сохранить без развёртывания** — зафиксировать промежуточный результат для передачи другому разработчику.\n3. **Подготовка к откату** — снимок исходного состояния перед рискованным изменением.\n\n## Когда вызывать\n\nТиповой сценарий для AI-агента (с 2026-05-23 явный вызов `POST \u002Fsources` перед деплоем не требуется — платформа сохраняет автоматически):\n\n1. Изменил код → `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy` — исходники сохраняются автоматически.\n2. `POST \u002Fv1\u002Fapps\u002F:id\u002Fpublish` — публикует приложение в каталоге Битрикс24. Тег `published` добавляется к снапшоту автоматически.\n3. Повторил цикл при следующем изменении.\n\nСценарий передачи проекта новому разработчику или новой AI-сессии:\n\n1. `GET \u002Fv1\u002Fapps\u002F:id\u002Fsources` — список доступных версий.\n2. `GET \u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId\u002Fdownload` — подписанная ссылка на архив.\n3. Скачать архив, распаковать и продолжить работу.\n\nДля MCP-клиентов: инструмент `save_sources` упаковывает файловое дерево в tar.gz на стороне клиента и отправляет одним запросом. Инструмент `load_sources` загружает последнюю версию и распаковывает обратно в дерево файлов. Прямой вызов HTTP-эндпоинта доступен для клиентов, которые самостоятельно упаковывают архив.\n\n## Гарантия наличия снапшота перед публикацией\n\n`POST \u002Fv1\u002Fapps\u002F:id\u002Fpublish` проверяет наличие снапшота не старше 10 минут. Если снапшота нет или он устарел — возвращается `409 SNAPSHOT_REQUIRED` с подсказкой, какой вызов нужно сделать перед повторной публикацией.\n\nПроверка работает только если в портале включено сохранение исходников (по умолчанию включено, владелец портала может отключить — см. раздел «Отключение для портала»).\n\nПример отказа:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"SNAPSHOT_REQUIRED\",\n    \"message\": \"Deploy requires a recent source snapshot. Call POST \u002Fv1\u002Fapps\u002F:id\u002Fsources first.\",\n    \"hint\": {\n      \"requiredAction\": \"POST \u002Fv1\u002Fapps\u002F:id\u002Fsources\",\n      \"toolName\": \"save_sources\",\n      \"freshnessWindowMinutes\": 10,\n      \"lastSnapshot\": {\n        \"versionId\": \"v3\",\n        \"timestamp\": \"2026-05-21T09:42:11.000Z\",\n        \"ageMinutes\": 23\n      }\n    }\n  }\n}\n```\n\nПоле `hint.lastSnapshot` равно `null`, если для приложения ещё нет ни одного снапшота.\n\nПараметры для повторной публикации той же версии (не самой свежей):\n\n- `sourceVersionId` в теле — формат `v\u003CN>`, например `\"sourceVersionId\": \"v3\"`.\n- Заголовок `x-source-version: v3` — альтернатива телу.\n\n## Срок жизни версий (политика хранения)\n\nПосле сохранения версия проходит через автоматическую очистку по графику «дед-отец-сын» (Grandfather-Father-Son):\n\n1. **Последние 5 версий** хранятся всегда, независимо от тегов.\n2. **Дневная сетка за 14 дней** — одна версия на каждый календарный день (UTC).\n3. **Недельная сетка за 4 недели** — одна версия на каждые 7 дней.\n4. **Теги `published` и `manual`** — хранятся бессрочно, никогда не удаляются автоматически.\n\nДля пользовательской очистки на запрос — `POST \u002Fv1\u002Fapps\u002F:id\u002Fsources\u002Fcleanup`. Версии с тегами `published` и `manual` исключаются и из неё.\n\n## Сохранение снапшота\n\n`POST \u002Fv1\u002Fapps\u002F:id\u002Fsources`\n\nПринимает сырые байты архива исходного кода в теле запроса. Формат определяется заголовком `Content-Type`. Метаданные (теги, комментарий, имя файла) передаются через дополнительные заголовки.\n\n> **Только бинарная загрузка (контракт v2).** Эндпоинт принимает **сырые байты архива** в теле запроса (`--data-binary`). `multipart\u002Fform-data` **больше не поддерживается** — такой запрос возвращает `400 INVALID_CONTENT_TYPE`. Ранние версии клиентов, которые слали `multipart\u002Fform-data` с полями формы (`-F \"tag=manual\"`), нужно обновить: тело — это сами байты архива, а **теги, комментарий и имя файла передаются заголовками `X-Tags` \u002F `X-Note` \u002F `X-Filename`** (не полями формы и не query-параметрами). MCP-клиенты используют инструмент `save_sources`, который сам упаковывает и отправляет архив в правильном формате.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|----------|-----|----------|\n| `id` (path) | UUID | Идентификатор приложения. Получить — [`GET \u002Fv1\u002Fapps`](.\u002Fapps.md). |\n\n### Заголовки запроса\n\n| Заголовок | Обязательный | Описание |\n|-----------|--------------|----------|\n| `Content-Type` | да | Формат архива. Допустимые значения: `application\u002Fgzip`, `application\u002Fx-tar`, `application\u002Fzip`, `application\u002Foctet-stream`. Любое другое значение → `400 INVALID_CONTENT_TYPE`. |\n| `X-Filename` | нет | Произвольное имя файла для отображения (например, `app-v1.tar.gz`). Если не указан — имя выводится из `Content-Type` (например, `source.tar.gz` для `application\u002Fgzip`). |\n| `X-Tags` | нет | Теги через запятую. Распознаются `manual` и `published` — они защищают версию от автоматической очистки. |\n| `X-Note` | нет | Произвольный комментарий, который сохраняется в записи о версии. |\n| `X-AI-Session-Id` | нет | Идентификатор AI-сессии. Группирует снапшоты в манифесте по сессии. |\n\n### Тело запроса\n\nСырые байты архива, не `multipart\u002Fform-data`. Максимальный размер — 200 МБ.\n\n### Ответ при успешном сохранении\n\n`HTTP 201`:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"versionId\": \"v3\",\n    \"id\": \"b7c1e2a4-9f5d-4c3a-8e21-0a1b2c3d4e5f\",\n    \"filename\": \"2026-05-21T10-15-30-000Z-v3.tar.gz\",\n    \"contentType\": \"application\u002Fgzip\",\n    \"sha256\": \"a3f5d8b2c1e9f4...\",\n    \"size\": 184320,\n    \"timestamp\": \"2026-05-21T10:15:30.000Z\",\n    \"tags\": [],\n    \"note\": null,\n    \"deduplicated\": false\n  }\n}\n```\n\n### Поля ответа\n\n| Поле | Тип | Описание |\n|------|-----|----------|\n| `data.versionId` | string | Идентификатор версии вида `v\u003CN>`. |\n| `data.id` | string | Внутренний идентификатор записи снапшота (UUID). |\n| `data.filename` | string | Имя файла архива в хранилище. |\n| `data.contentType` | string | Тип архива: `application\u002Fgzip`, `application\u002Fzip`, `application\u002Fx-tar` или `application\u002Foctet-stream`. |\n| `data.sha256` | string | SHA-256 содержимого архива. Используется для дедупликации. |\n| `data.size` | number | Размер архива в байтах. |\n| `data.timestamp` | string | Время сохранения (ISO 8601, UTC). |\n| `data.tags` | string[] | Активные теги версии: `manual`, `published`. |\n| `data.note` | string \\| null | Комментарий из заголовка `X-Note` или `null`. |\n| `data.deduplicated` | boolean | `true`, если архив с тем же содержимым уже был сохранён — возвращена существующая версия. |\n\n### Идемпотентность\n\nПовторное сохранение архива с тем же содержимым даёт тот же `sha256` и не создаёт новую версию. Эндпоинт возвращает `HTTP 201` с `deduplicated: true` и `versionId` уже существующей версии. Если при повторном сохранении переданы новые теги или комментарий — они добавляются к существующей версии (теги объединяются, комментарий перезаписывается).\n\nВ обоих случаях обновляется метка `App.lastSourceSavedAt` — проверка свежести перед `POST \u002Fv1\u002Fapps\u002F:id\u002Fpublish` принимает дедуплицированное сохранение наравне с настоящим.\n\n### Ответ при отключённом сохранении\n\n`HTTP 200` — снапшот не создан:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"skipped\": true,\n    \"reason\": \"DISABLED_GLOBALLY\"\n  }\n}\n```\n\n`reason` — `\"DISABLED_GLOBALLY\"` (платформа не включила функцию) или `\"DISABLED_FOR_PORTAL\"` (владелец портала отключил для своего портала). При любом из вариантов `POST \u002Fv1\u002Fapps\u002F:id\u002Fpublish` не проверяет наличие снапшота.\n\n### Примеры\n\n#### curl — tar.gz\n\n```bash\ncurl -X POST https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F\u003CAPP_ID>\u002Fsources \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Content-Type: application\u002Fgzip\" \\\n  -H \"X-Note: Добавил OAuth-флоу\" \\\n  -H \"X-Tags: manual\" \\\n  --data-binary @app-sources.tar.gz\n```\n\n#### curl — zip\n\n```bash\ncurl -X POST https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F\u003CAPP_ID>\u002Fsources \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Content-Type: application\u002Fzip\" \\\n  --data-binary @app-sources.zip\n```\n\n#### JavaScript\n\n```javascript\nconst archive = await fs.readFile('app-sources.tar.gz')\n\nconst res = await fetch(\n  `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F${appId}\u002Fsources`,\n  {\n    method: 'POST',\n    headers: {\n      'X-Api-Key': process.env.VIBE_APP_KEY,\n      'Content-Type': 'application\u002Fgzip',\n      'X-Note': 'Добавил OAuth-флоу',\n    },\n    body: archive,\n  },\n)\nconst json = await res.json()\nconsole.log(json.data.versionId, 'deduplicated:', json.data.deduplicated)\n```\n\n### Коды ошибок\n\n| HTTP | Код | Когда возвращается |\n|------|-----|---------------------|\n| 400 | `INVALID_CONTENT_TYPE` | `Content-Type` не входит в список допустимых форматов архива. Формат `multipart\u002Fform-data` не принимается — отправляйте сырые байты архива. |\n| 400 | `INVALID_BLOB` | Тело запроса не является бинарным буфером (например, передан текст). |\n| 400 | `INVALID_FILENAME` | Заголовок `X-Filename` содержит символы вне набора `a-zA-Z0-9._-`. |\n| 400 | `INVALID_TAGS` | Тег из заголовка `X-Tags` содержит недопустимые символы (разрешены `a-zA-Z0-9_-`). |\n| 403 | `NOT_AUTHORIZED` | Только автор приложения, OAuth-ключ приложения или администратор портала могут управлять снапшотами. |\n| 404 | `APP_NOT_FOUND` | Приложение не существует, удалено или принадлежит другому порталу. |\n| 413 | — | Размер архива превышает лимит в 200 МБ. |\n| 500 | `SOURCE_STORAGE_ERROR` | Внутренняя ошибка хранилища. |\n\n## Обновление метаданных версии\n\n`PATCH \u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId`\n\nОбновляет теги и\u002Fили комментарий существующей версии без повторной загрузки архива. Удобно, если нужно добавить тег или скорректировать комментарий задним числом.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|----------|-----|----------|\n| `id` (path) | UUID | Идентификатор приложения. |\n| `versionId` (path) | string | Идентификатор версии вида `v\u003CN>`. |\n\n### Поля тела\n\n| Поле | Тип | Обязательное | Описание |\n|------|-----|--------------|----------|\n| `tags` | string[] | нет | Новый полный список тегов. **Заменяет** существующие теги целиком. Если поле отсутствует — теги не изменяются. |\n| `note` | string \\| null | нет | Комментарий. Строка — перезаписывает текущий. `null` — очищает комментарий. Если поле отсутствует — комментарий не изменяется. |\n\nМожно передать только `tags`, только `note` или оба поля одновременно.\n\n### Ответ\n\n`HTTP 200`:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"versionId\": \"v3\",\n    \"tags\": [\"manual\"],\n    \"note\": \"Финальная версия перед релизом\"\n  }\n}\n```\n\n### Примеры\n\n#### curl — добавить тег `manual`\n\n```bash\ncurl -X PATCH https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F\u003CAPP_ID>\u002Fsources\u002Fv3 \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{ \"tags\": [\"manual\"] }'\n```\n\n#### curl — очистить комментарий\n\n```bash\ncurl -X PATCH https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F\u003CAPP_ID>\u002Fsources\u002Fv3 \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{ \"note\": null }'\n```\n\n#### JavaScript\n\n```javascript\nawait fetch(\n  `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F${appId}\u002Fsources\u002Fv3`,\n  {\n    method: 'PATCH',\n    headers: {\n      'X-Api-Key': process.env.VIBE_APP_KEY,\n      'Content-Type': 'application\u002Fjson',\n    },\n    body: JSON.stringify({ tags: ['manual'], note: 'Финальная версия' }),\n  },\n)\n```\n\n### Коды ошибок\n\n| HTTP | Код | Когда возвращается |\n|------|-----|---------------------|\n| 400 | `INVALID_METADATA` | Тело не прошло валидацию (некорректные теги или типы полей). |\n| 400 | `INVALID_VERSION_ID` | Формат `versionId` не соответствует `v\u003Cцелое неотрицательное число>`. |\n| 403 | `NOT_AUTHORIZED` | Только автор приложения, OAuth-ключ приложения или администратор портала могут управлять снапшотами. |\n| 404 | `APP_NOT_FOUND` | Приложение не существует, удалено или принадлежит другому порталу. |\n| 404 | `VERSION_NOT_FOUND` | Версия с таким `versionId` не существует или была удалена. |\n\n## Список версий\n\n`GET \u002Fv1\u002Fapps\u002F:id\u002Fsources`\n\nВозвращает все актуальные (не удалённые) версии в порядке убывания времени сохранения — самая свежая первой.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|----------|-----|----------|\n| `id` (path) | UUID | Идентификатор приложения. |\n\n### Ответ\n\n`HTTP 200`:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"totalVersions\": 3,\n    \"currentVersionId\": \"v3\",\n    \"totalSizeBytes\": 552960,\n    \"versions\": [\n      {\n        \"versionId\": \"v3\",\n        \"filename\": \"2026-05-21T10-15-30-000Z-v3.tar.gz\",\n        \"contentType\": \"application\u002Fgzip\",\n        \"timestamp\": \"2026-05-21T10:15:30.000Z\",\n        \"size\": 184320,\n        \"sha256\": \"a3f5d8b2c1e9f4...\",\n        \"tags\": [],\n        \"savedBy\": {\n          \"userId\": \"8f1a2b3c-...\",\n          \"session\": \"claude-session-2026-05-21\"\n        },\n        \"linkedDeployId\": null,\n        \"deployStatus\": null,\n        \"note\": \"Добавил OAuth-флоу\"\n      },\n      {\n        \"versionId\": \"v2\",\n        \"filename\": \"2026-05-20T18-02-11-000Z-v2-published.tar.gz\",\n        \"contentType\": \"application\u002Fgzip\",\n        \"timestamp\": \"2026-05-20T18:02:11.000Z\",\n        \"size\": 184320,\n        \"sha256\": \"9e7c4b2a1f8d3...\",\n        \"tags\": [\"published\"],\n        \"savedBy\": {\n          \"userId\": \"8f1a2b3c-...\",\n          \"session\": null\n        },\n        \"linkedDeployId\": \"publish:2026-05-20T18:05:42.000Z\",\n        \"deployStatus\": \"success\",\n        \"note\": null\n      },\n      {\n        \"versionId\": \"v1\",\n        \"filename\": \"2026-05-20T09-44-50-000Z-v1.tar.gz\",\n        \"contentType\": \"application\u002Fgzip\",\n        \"timestamp\": \"2026-05-20T09:44:50.000Z\",\n        \"size\": 184320,\n        \"sha256\": \"5a8d3e2f1c4b9...\",\n        \"tags\": [],\n        \"savedBy\": { \"userId\": \"8f1a2b3c-...\", \"session\": null },\n        \"linkedDeployId\": null,\n        \"deployStatus\": null,\n        \"note\": null\n      }\n    ]\n  }\n}\n```\n\n### Поля ответа\n\n| Поле | Тип | Описание |\n|------|-----|----------|\n| `data.totalVersions` | number | Общее количество актуальных версий. |\n| `data.currentVersionId` | string \\| null | Идентификатор самой свежей версии (`v\u003CN>`). `null`, если версий нет. |\n| `data.totalSizeBytes` | number | Суммарный размер всех архивов в байтах. |\n| `data.versions[].versionId` | string | Идентификатор версии вида `v\u003CN>`. |\n| `data.versions[].filename` | string | Имя файла в хранилище. Содержит суффикс `-published` или `-manual`, если есть соответствующий тег. |\n| `data.versions[].contentType` | string \\| null | Тип архива (`application\u002Fgzip`, `application\u002Fzip` и т.д.). |\n| `data.versions[].timestamp` | string | Время сохранения (ISO 8601, UTC). |\n| `data.versions[].size` | number | Размер архива в байтах. |\n| `data.versions[].sha256` | string | SHA-256 содержимого архива. Используется для дедупликации. |\n| `data.versions[].tags` | string[] | Активные теги: `manual`, `published`. |\n| `data.versions[].savedBy.userId` | string \\| null | Идентификатор пользователя Вайбкод. |\n| `data.versions[].savedBy.session` | string \\| null | Идентификатор AI-сессии (из заголовка `X-AI-Session-Id`). |\n| `data.versions[].linkedDeployId` | string \\| null | Идентификатор публикации (заполняется после `POST \u002Fv1\u002Fapps\u002F:id\u002Fpublish`). |\n| `data.versions[].deployStatus` | string \\| null | Статус публикации: `success` или `failed`. |\n| `data.versions[].note` | string \\| null | Комментарий из поля `X-Note` при сохранении или из PATCH. |\n\n### Связанные серверные версии (`linkedServerSources`)\n\nМассив `versions` перечисляет только версии, привязанные к приложению. Версии, сохранённые под сервером (через `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsources` или авто-сохранение при деплое под личным ключом), в него не входят — они возвращаются отдельным **аддитивным** полем `data.linkedServerSources`, сгруппированным по серверу:\n\n```json\n{\n  \"data\": {\n    \"totalVersions\": 0,\n    \"versions\": [],\n    \"linkedServerSources\": [\n      {\n        \"serverContext\": {\n          \"serverId\": \"8de64f8d-...\",\n          \"serverName\": \"srv-prod\",\n          \"serverDisplayName\": \"Prod\",\n          \"linkedApp\": null\n        },\n        \"totalVersions\": 2,\n        \"totalSizeBytes\": 20,\n        \"versions\": [ \u002F* та же форма, что и элементы versions[]; нумерация v\u003CN> — на сервер *\u002F ]\n      }\n    ],\n    \"linkedServerSourcesTruncated\": false,\n    \"linkedServerHint\": {\n      \"message\": \"This app has source versions stored under a server (server-keyed storage). List and download them via the server endpoint.\",\n      \"listEndpoint\": \"GET \u002Fv1\u002Finfra\u002Fservers\u002F:serverId\u002Fsources\",\n      \"downloadEndpoint\": \"GET \u002Fv1\u002Finfra\u002Fservers\u002F:serverId\u002Fsources\u002F:versionId\u002Fdownload\",\n      \"docs\": \"https:\u002F\u002Fvibecode.bitrix24.tech\u002Fdocs-content\u002Fsource-storage.md\"\n    }\n  }\n}\n```\n\n| Поле | Тип | Описание |\n|------|-----|----------|\n| `data.linkedServerSources[]` | array | Группы серверных версий (по одной на сервер). Пусто, если связанных серверных версий нет. |\n| `data.linkedServerSources[].serverContext` | object | Сервер: `serverId`, `serverName`, `serverDisplayName`, `linkedApp` (`null` для серверов на личном ключе). |\n| `data.linkedServerSources[].totalVersions` | number | Точное число версий на сервере (не ограничено размером списка ниже). |\n| `data.linkedServerSources[].totalSizeBytes` | number | Суммарный размер версий этого сервера. |\n| `data.linkedServerSources[].versions[]` | array | Версии сервера в том же формате, что `versions[]`. Нумерация `v\u003CN>` — своя на каждый сервер. |\n| `data.linkedServerSourcesTruncated` | boolean | `true`, если при очень большой истории список версий был усечён (полный список — на server-эндпоинте). |\n| `data.linkedServerHint` | object \\| null | Присутствует, когда `linkedServerSources` не пуст. Указывает на авторитетные эндпоинты списка и скачивания серверных версий. |\n\nПоле заполняется, когда вызывающий — автор приложения (личный `vibe_api_*` ключ) или администратор портала. При вызове **ключом OAuth-приложения** секция пуста (машинный ключ не перечисляет личные серверы автора). При запросе с `?sha256=` секция не вычисляется (проба на существование остаётся лёгкой). Скачивание и полный per-server список — через `GET \u002Fv1\u002Finfra\u002Fservers\u002F:serverId\u002Fsources` (см. `serverContext.serverId`); `versions` \u002F `totalVersions` \u002F `currentVersionId` этого ответа остаются привязанными к приложению и не меняются.\n\n### Примеры\n\n#### curl\n\n```bash\ncurl -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F\u003CAPP_ID>\u002Fsources\n```\n\n#### JavaScript\n\n```javascript\nconst res = await fetch(\n  `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F${appId}\u002Fsources`,\n  { headers: { 'X-Api-Key': process.env.VIBE_APP_KEY } },\n)\nconst { data } = await res.json()\nconsole.log(`Версий: ${data.totalVersions}, последняя: ${data.currentVersionId}`)\n```\n\n### Коды ошибок\n\n| HTTP | Код | Когда возвращается |\n|------|-----|---------------------|\n| 403 | `NOT_AUTHORIZED` | Только автор приложения, OAuth-ключ приложения или администратор портала могут управлять снапшотами. |\n| 404 | `APP_NOT_FOUND` | Приложение не существует, удалено или принадлежит другому порталу. |\n\n## Скачивание архива\n\n`GET \u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId\u002Fdownload`\n\nВозвращает подписанную ссылку на архив версии. Ссылка действует 30 минут.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|----------|-----|----------|\n| `id` (path) | UUID | Идентификатор приложения. |\n| `versionId` (path) | string | Идентификатор версии вида `v\u003CN>`. |\n\n### Ответ\n\n`HTTP 200`:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"url\": \"https:\u002F\u002F\u003Cstorage-endpoint>\u002F...\",\n    \"expiresAt\": \"2026-05-21T10:45:30.000Z\"\n  }\n}\n```\n\n### Примеры\n\n#### curl\n\n```bash\n# Получить ссылку\ncurl -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F\u003CAPP_ID>\u002Fsources\u002Fv3\u002Fdownload\n\n# Скачать архив по полученной ссылке (без дополнительных заголовков)\ncurl -o source-v3.tar.gz \"\u003Curl из ответа выше>\"\n```\n\n#### JavaScript\n\n```javascript\nconst { data } = await fetch(\n  `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F${appId}\u002Fsources\u002Fv3\u002Fdownload`,\n  { headers: { 'X-Api-Key': process.env.VIBE_APP_KEY } },\n).then((r) => r.json())\n\nconst archive = await fetch(data.url)\nconst buffer = await archive.arrayBuffer()\n\u002F\u002F Дальше — распаковка архива (tar.gz: tar library; zip: JSZip или аналог)\n```\n\n### Коды ошибок\n\n| HTTP | Код | Когда возвращается |\n|------|-----|---------------------|\n| 400 | `INVALID_VERSION_ID` | Формат `versionId` не соответствует `v\u003Cцелое неотрицательное число>`. |\n| 403 | `NOT_AUTHORIZED` | Только автор приложения, OAuth-ключ приложения или администратор портала могут управлять снапшотами. |\n| 404 | `APP_NOT_FOUND` | Приложение не существует, удалено или принадлежит другому порталу. |\n| 404 | `VERSION_NOT_FOUND` | Версия с таким `versionId` не существует или была удалена. |\n| 502 | `SOURCE_DOWNLOAD_URL_FAILED` | Хранилище временно недоступно — повторите запрос. |\n\n## Постановка и снятие тега\n\n`POST \u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId\u002Ftag`\n\nРаспознаются два тега, оба защищают версию от автоматической очистки:\n\n- `manual` — версия закреплена оператором вручную.\n- `published` — версия зафиксирована как опубликованная (этот тег также проставляется автоматически после `POST \u002Fv1\u002Fapps\u002F:id\u002Fpublish`).\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|----------|-----|----------|\n| `id` (path) | UUID | Идентификатор приложения. |\n| `versionId` (path) | string | Идентификатор версии вида `v\u003CN>`. |\n\n### Поля тела\n\n| Поле | Тип | Обязательное | Описание |\n|------|-----|--------------|----------|\n| `tag` | string | да | `manual` или `published`. |\n| `action` | string | да | `add` — добавить тег, `remove` — снять. |\n\n### Ответ\n\n`HTTP 200`:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"versionId\": \"v3\",\n    \"tags\": [\"manual\"]\n  }\n}\n```\n\n### Примеры\n\n#### curl\n\n```bash\ncurl -X POST https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F\u003CAPP_ID>\u002Fsources\u002Fv3\u002Ftag \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{ \"tag\": \"manual\", \"action\": \"add\" }'\n```\n\n#### JavaScript\n\n```javascript\nawait fetch(\n  `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F${appId}\u002Fsources\u002Fv3\u002Ftag`,\n  {\n    method: 'POST',\n    headers: {\n      'X-Api-Key': process.env.VIBE_APP_KEY,\n      'Content-Type': 'application\u002Fjson',\n    },\n    body: JSON.stringify({ tag: 'manual', action: 'add' }),\n  },\n)\n```\n\n### Коды ошибок\n\n| HTTP | Код | Когда возвращается |\n|------|-----|---------------------|\n| 400 | `INVALID_TAG` | Тег не входит в набор `manual`, `published`. |\n| 400 | `INVALID_ACTION` | Действие не равно `add` или `remove`. |\n| 400 | `INVALID_VERSION_ID` | Формат `versionId` не соответствует `v\u003Cцелое неотрицательное число>`. |\n| 403 | `NOT_AUTHORIZED` | Только автор приложения, OAuth-ключ приложения или администратор портала могут управлять снапшотами. |\n| 404 | `APP_NOT_FOUND` | Приложение не существует, удалено или принадлежит другому порталу. |\n| 404 | `VERSION_NOT_FOUND` | Версия с таким `versionId` не существует или была удалена. |\n\n## Удаление версии\n\n`DELETE \u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId`\n\nПомечает версию как удалённую. Восстановить версию через API нельзя. Версия с тегом `published` или `manual` защищена от удаления — сначала снимите тег через `PATCH \u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId` с телом `{\"tags\": []}` (или с сохранением других тегов). Ответ включает `hint.preservedTags` — теги без защитных меток, которые стоит сохранить при снятии защиты.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|----------|-----|----------|\n| `id` (path) | UUID | Идентификатор приложения. |\n| `versionId` (path) | string | Идентификатор версии вида `v\u003CN>`. |\n\n### Ответ\n\n`HTTP 200`:\n\n```json\n{\n  \"success\": true,\n  \"data\": { \"versionId\": \"v3\" }\n}\n```\n\n### Примеры\n\n#### curl\n\n```bash\ncurl -X DELETE https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F\u003CAPP_ID>\u002Fsources\u002Fv3 \\\n  -H \"X-Api-Key: YOUR_APP_KEY\"\n```\n\n#### JavaScript\n\n```javascript\nawait fetch(\n  `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F${appId}\u002Fsources\u002Fv3`,\n  {\n    method: 'DELETE',\n    headers: { 'X-Api-Key': process.env.VIBE_APP_KEY },\n  },\n)\n```\n\n### Коды ошибок\n\n| HTTP | Код | Когда возвращается |\n|------|-----|---------------------|\n| 400 | `INVALID_VERSION_ID` | Формат `versionId` не соответствует `v\u003Cцелое неотрицательное число>`. |\n| 403 | `NOT_AUTHORIZED` | Только автор приложения, OAuth-ключ приложения или администратор портала могут управлять снапшотами. |\n| 404 | `APP_NOT_FOUND` | Приложение не существует, удалено или принадлежит другому порталу. |\n| 404 | `VERSION_NOT_FOUND` | Версия с таким `versionId` не существует или уже удалена. |\n| 409 | `PROTECTED_BY_TAG` | Версия отмечена тегом `published` или `manual`. Сначала снимите тег через `PATCH \u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId`. |\n\n## Массовая очистка\n\n`POST \u002Fv1\u002Fapps\u002F:id\u002Fsources\u002Fcleanup`\n\nУдаляет старые версии, оставляя `keepLatest` самых свежих (по умолчанию — 5). Версии с тегами `manual` и `published` исключаются из очистки независимо от `keepLatest`.\n\n### Параметры пути\n\n| Параметр | Тип | Описание |\n|----------|-----|----------|\n| `id` (path) | UUID | Идентификатор приложения. |\n\n### Поля тела\n\n| Поле | Тип | Обязательное | По умолчанию | Описание |\n|------|-----|--------------|--------------|----------|\n| `keepLatest` | number | нет | `5` | Сколько последних версий оставить. Целое неотрицательное число. `0` оставит только версии с тегами `manual` и `published`. |\n\nТело можно опустить — применяется значение по умолчанию.\n\n### Ответ\n\n`HTTP 200`:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"deletedVersions\": [\"v2\", \"v1\"]\n  }\n}\n```\n\n### Примеры\n\n#### curl\n\n```bash\ncurl -X POST https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F\u003CAPP_ID>\u002Fsources\u002Fcleanup \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{ \"keepLatest\": 3 }'\n```\n\n#### JavaScript\n\n```javascript\nconst res = await fetch(\n  `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fapps\u002F${appId}\u002Fsources\u002Fcleanup`,\n  {\n    method: 'POST',\n    headers: {\n      'X-Api-Key': process.env.VIBE_APP_KEY,\n      'Content-Type': 'application\u002Fjson',\n    },\n    body: JSON.stringify({ keepLatest: 3 }),\n  },\n)\nconst { data } = await res.json()\nconsole.log('Удалено версий:', data.deletedVersions.length)\n```\n\n### Коды ошибок\n\n| HTTP | Код | Когда возвращается |\n|------|-----|---------------------|\n| 400 | `INVALID_KEEP_LATEST` | Значение `keepLatest` не является целым неотрицательным числом. |\n| 403 | `NOT_AUTHORIZED` | Только автор приложения, OAuth-ключ приложения или администратор портала могут управлять снапшотами. |\n| 404 | `APP_NOT_FOUND` | Приложение не существует, удалено или принадлежит другому порталу. |\n\n## Деплой из снапшота\n\n`POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy` принимает поле `source.versionId` — альтернатива `source.url` и `source.content`. Позволяет развернуть на сервере ровно ту версию, которая была сохранена через `save_sources`, без отдельной загрузки файла.\n\n```json\n{\n  \"source\": {\n    \"versionId\": \"v3\"\n  },\n  \"start\": \"node server.js\"\n}\n```\n\nСервер разрешает `versionId` в подписанную ссылку на архив и выполняет деплой. После успешного или неуспешного деплоя поле `linkedDeployId` у снапшота обновляется автоматически.\n\n**Ограничение:** серверу должен соответствовать ключ авторизации приложения (`vibe_app_*`). Личные и менеджмент-ключи без привязанного `appId` не могут разрешить снапшот — вернётся `400 SOURCE_VERSION_REQUIRES_APP`.\n\n## Поведение перед публикацией\n\nОписано выше в разделе «Гарантия наличия снапшота перед публикацией». Справочник кодов ошибок `POST \u002Fv1\u002Fapps\u002F:id\u002Fpublish`, специфичных для проверки снапшота:\n\n| HTTP | Код | Когда возвращается |\n|------|-----|---------------------|\n| 409 | `SNAPSHOT_REQUIRED` | Снапшота нет или он старше 10 минут (только при включённом сохранении). В ответе — поле `hint` с описанием действия. |\n\n## Проверка состояния портала\n\n`GET \u002Fv1\u002Fme` возвращает блок `data.capabilities.apps.sourceStorage`:\n\nПри включённом сохранении:\n\n```json\n{\n  \"capabilities\": {\n    \"apps\": {\n      \"sourceStorage\": {\n        \"enabled\": true,\n        \"requiredBeforeDeploy\": false,\n        \"automaticOnDeploy\": true,\n        \"freshnessWindowMinutes\": 10,\n        \"limits\": {\n          \"maxBlobBytes\": 209715200\n        }\n      }\n    }\n  }\n}\n```\n\nПри отключённом сохранении:\n\n```json\n{\n  \"capabilities\": {\n    \"apps\": {\n      \"sourceStorage\": {\n        \"enabled\": false,\n        \"requiredBeforeDeploy\": false,\n        \"automaticOnDeploy\": false,\n        \"disabledBy\": \"platform\",\n        \"readOperations\": \"available\",\n        \"reactivation\": {\n          \"scope\": \"platform\",\n          \"permission\": \"platform admin\"\n        }\n      }\n    }\n  }\n}\n```\n\nПоле `disabledBy` — `\"platform\"` (функция не включена на уровне платформы) или `\"portal\"` (владелец портала отключил). Поле `readOperations: \"available\"` означает, что список версий и скачивание остаются доступными даже при отключённом сохранении. Блок `reactivation` указывает, на каком уровне (платформа или портал) включается функция и какие права для этого нужны.\n\n## Отключение для портала\n\nВладелец портала может отключить сохранение исходников через раздел администрирования (`PATCH \u002Fapi\u002Fadmin\u002Fsource-storage` с телом `{ \"sourceStorageEnabled\": false }`, требует сессии администратора). При отключении:\n\n- `POST \u002Fv1\u002Fapps\u002F:id\u002Fsources` возвращает `200 { skipped: true, reason: \"DISABLED_FOR_PORTAL\" }`.\n- `POST \u002Fv1\u002Fapps\u002F:id\u002Fpublish` не проверяет наличие снапшота.\n- `GET \u002Fv1\u002Fapps\u002F:id\u002Fsources` продолжает возвращать ранее сохранённые версии.\n- `GET \u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId\u002Fdownload` остаётся доступным.\n\nИстория сохранений переживает повторное включение — ранее созданные версии остаются доступными.\n\n## Поведение для AI-моделей\n\nMCP-инструмент `save_sources` обёртывает `POST \u002Fv1\u002Fapps\u002F:id\u002Fsources`. Инструмент `load_sources` скачивает и распаковывает последний снапшот в дерево файлов.\n\nС 2026-05-23 явный вызов `save_sources` перед развёртыванием не требуется — платформа сохраняет байты автоматически. `save_sources` остаётся нужным только для трёх явных сценариев (раздел «Сохранение исходников» выше).\n\nКаналы, через которые модель узнаёт о сохранении исходников:\n\n- Описание MCP-инструментов `save_sources` и `load_sources` — видно при первом обращении.\n- Подсказка в ответе `409 SNAPSHOT_REQUIRED` (развёртывание с внешним URL) — предлагает загрузить через `POST \u002Fsources` или передать `X-Skip-Source-Snapshot`.\n- Подсказка в ответе `409 SNAPSHOT_REQUIRED` (публикация без свежего снапшота) — направляет в цикл `publish → save_sources → publish` (актуально, если источник — внешний URL и автосохранение было пропущено).\n- Блок `capabilities.apps.sourceStorage` в ответе `GET \u002Fv1\u002Fme` — программная проверка состояния.\n\n## Реестр исходников\n\nСтраница **«Исходники приложений»** доступна в личном кабинете через левое меню (иконка пакета). Она показывает сводный список всех задепонированных снапшоты для удобной передачи кода новому разработчику или возобновления AI-сессии.\n\n**Кто что видит:**\n- **Участник портала (MEMBER)** — только свои снапшоты (те, что он загружал).\n- **Администратор портала (ADMIN)** — все снапшоты портала, включая снапшоты других участников. Может скачать любой снапшот.\n\n**Как скачать:**\n1. Откройте «Исходники приложений» в меню.\n2. Найдите нужную строку (сервер или приложение).\n3. Нажмите кнопку «Скачать» — браузер загрузит архив последней версии.\n\nДля legacy-приложений (строки с типом «Приложение») также доступен переход в «Хранилище» для просмотра истории версий.\n\n**Примечание:** страница работает независимо от состояния платформенного toggle депонирования — исторические снапшоты доступны даже если новые сохранения временно отключены.\n\n## Смотрите также\n\n- [Ключи и авторизация](.\u002Fkeys-auth.md)\n- [Коды ошибок](.\u002Ferrors.md)\n- [MCP для AI](.\u002Fmcp.md)\n- [Деплой приложения](.\u002Finfra\u002Fdeploy.md)\n","2026-07-07",{}]