# Программное подключение к сборщику демо

Версия HTTP API: 0.4.0 (FastAPI + Pydantic + Uvicorn). Машиночитаемая спецификация: `openapi.json`
(на работающем сервисе — GET `/v1/openapi.json`). Этот документ:
GET `/v1/api.md`. Архив клиента: GET `/integration.zip`.

Интерактивная документация: `/docs` (Swagger), `/redoc` (описание схем).
OpenAPI генерируется из тех же моделей, которые проверяют настоящие запросы
и ответы сервиса. В Swagger нажми Try it out у GET примера Калинова;
для закрытых операций введи выданный API-ключ в Authorize. POST /v1/jobs
создаёт настоящую задачу с расходом лимитов — для первого теста используй GET.

## Адрес и ключ — что это

`SERVICE_URL` — адрес сервиса Ларри. `SERVICE_API_KEY` — выданный ключ доступа
к этому сервису. Это настройки твоего HTTP-клиента, а не поля настройки аватара
и не параметры JSON сборки. Старые имена `FACTORY_URL`, `FACTORY_TOKEN` и
`--factory` поддерживаются как совместимые варианты. Если заданы оба имени,
новое имеет приоритет. `.env` автоматически не загружается: задай окружение
своей программы или передай адрес через `--service-url`.

При новой сборке тело запроса содержит только `source_url`. Ключ передаётся
в заголовке `Authorization: Bearer …`; ChatGPT-авторизация остаётся у сборщика.
Пример Калинова доступен без ключа, новая сборка и скачивание артефактов — с ключом.

Внешний адрес API: `https://demo-factory.nglain.com`.
Страница получателя: `https://demo-factory.nglain.com/yura`.
Swagger: `https://demo-factory.nglain.com/docs`.
`http://127.0.0.1:8795` — отдельный SSH-туннель Ларри, для подключения Юры он не нужен.
Ключ передаётся отдельно, он не включён в страницу, спецификацию и комплект.

## 1. Прочитать уже собранный пример

```sh
curl --fail-with-body "$SERVICE_URL/v1/examples/kalinov/result"
# или: python3 client.py --service-url "$SERVICE_URL" example
```

HTTP 200 возвращает BuildResult непосредственно, без обёртки `result`.
Это тот же `kalinov-result.json`, что лежит в комплекте. Новая задача не создаётся,
модель не вызывается. У текущего Калинова `build_state=needs_review`,
`auto_deploy=false`: пакет можно скачивать и проверять, клиентское демо ещё не принято.
Ссылки внутри `artifacts` требуют ключ даже при публичном чтении примера.

## 2. Создать новую сборку

```sh
curl --fail-with-body -X POST "$SERVICE_URL/v1/jobs" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: customer-001-build-001' \
  --data '{"source_url":"https://kalinovrodnik.ru/"}'
```

HTTP 202 — Job. Сохрани `job_id` и ключ повтора до следующего запроса.
`Idempotency-Key`: 1–128 символов. Одинаковые ключ и точное тело вернут прежнюю
задачу, в том числе уже завершённую. Иной URL с прежним ключом — HTTP 409.
Для сознательной повторной сборки используй новый ключ. Ключ повтора общий
для сервиса, поэтому добавляй свой уникальный префикс.

URL: публичный HTTP(S), порты 80/443, без логина, пароля, пробелов или фрагмента.
В теле никаких других полей. JSON-объект до 65536 байт, с Content-Length;
передача chunked не поддерживается. Публичность адресов также проверяется worker.
Лимит — 20 новых задач за скользящие последние 24 часа на весь сервис.
Новые задачи используют лимиты Codex/ChatGPT Ларри.

## 3. Читать статус, затем результат

```sh
curl --fail-with-body "$SERVICE_URL/v1/jobs/НОМЕР_ЗАДАЧИ" \
  -H "Authorization: Bearer $SERVICE_API_KEY"
curl --fail-with-body "$SERVICE_URL/v1/jobs/НОМЕР_ЗАДАЧИ/result" \
  -H "Authorization: Bearer $SERVICE_API_KEY"
```

`job_id` — 32 строчных hex-символа. Статус содержит `state`, `stage`, `attempt`,
`reasons`, `auto_deploy`, `profile_version`, `created_at`, `updated_at`.
Даты — Unix UTC seconds. `stage` — пояснение этапа, может быть null и не является enum.

| state | Что делать программе |
|---|---|
| queued, running | Опрос через 3–10 секунд. Сохраняй номер задачи. |
| failed | Прочитай reasons. Не считай отсутствие результата успехом. |
| needs_review | Получи result, скачай для проверки; автоматический запуск запрещён. |
| succeeded | Сверь result и auto_deploy; состояние предусмотрено контрактом, текущий worker его сам не выставляет. |

Время работы worker ограничено 1800 секундами; ожидание в очереди может увеличить
общую длительность. Тайм-аут клиента не отменяет задачу. После обрыва сети
повторяй POST с прежним ключом или GET по сохранённому job_id. Не создавай новую
задачу при каждом тайм-ауте. GET result до готовности возвращает 409.
GET `/v1/jobs` возвращает `{"jobs":[…]}` — последние 30 задач, без пагинации.

## 4. Анализировать BuildResult

Проверяй `schema_version`, `job_id`, `source_url`, `runtime_contract.id/version`
и поля, перечисленные в `contract.json` → `result.required`.

- `build_state` и `auto_deploy` — допуск к дальнейшему запуску. `needs_review`
  нельзя автоматически превращать в готовое клиентское демо.
- `scope` — что реально вошло в сборку: маршруты, режим снимка, пропуски.
- `checks` — словарь проверок: `status`, `scope`, `evidence`. Успешная проверка
  упаковки/структуры знаний не подтверждает качество диалога. `not_run` — не проверено.
- `limitations` — обязательные ограничения для отчёта и ручной приёмки.
- `summary` — необязательное описание компании и помощника, а не единый применяемый конфиг.
- `artifacts` — клиентские ZIP; `download_url`, `bytes`, `sha256`, `kind`.
  `manifest_sha256` относится к manifest.json клиентского пакета.

У текущего Калинова 13 URL, неполный каталог, 90 отсутствующих маршрутов,
визуальное сходство и разговор/голос требуют проверки. Упаковка не гарантирует
точную полную копию любого произвольного сайта.

## 5. Скачать и подготовить файлы

```sh
python3 importer.py --service-url "$SERVICE_URL" \
  --job-id 1a28451ed2a146028b1031a52329d50d --output ./received-kalinov
# Для нового сайта:
python3 importer.py --service-url "$SERVICE_URL" \
  --url 'https://сайт-клиента.example/' \
  --idempotency-key customer-002-build-001 --output ./received-client
```

Без `--deploy` сервисы не запускаются. Прогресс идёт в stderr, итоговый JSON —
в stdout; код выхода 0 подтверждает подготовку файлов, не приёмку демо.
`job-id.txt` и `result.json` сохраняются в output. Папка должна быть новой;
для продолжения после ошибки используй сохранённый job-id и другую output.

Из своего кода: скачай `artifacts[].download_url` с Bearer, разрешая URL
относительно SERVICE_URL, сохраняя тот же origin. Перенаправления скачивания
не поддерживаются. Сверь bytes и SHA-256 ZIP до распаковки; исключи выход пути
за корень, символические ссылки и размер более 3 ГБ. Затем проверь SHA-256
manifest.json и каждый путь/hash в его files.

Для `initavatar-content-kb-v1` получи GET `/v1/platform`: совместимая общая
платформа скачивается отдельно; её ZIP также проверяется по bytes/SHA.
Импортёр принимает schema_version=0.1 и runtime_contract версии 0.1.0;
для content-kb-v1 совместима платформа initavatar-runtime-archive-v1 версии 0.1.0.
После проверки он запускает install_bundle.py из проверенного
клиентского пакета, подставляя фиксированный runtime. Для других контрактов
нужен соответствующий обработчик; неизвестный контракт нельзя угадывать.
Клиентский пакет и общая платформа Калинова занимают примерно 104 и 42 МБ.

## 6. Развёртывание — сторона Юры

В подготовленном demo/ есть site/, agent/, runtime/, compose.yml и RUN.md.
Юра задаёт свои домены/origin, URL виджета, свободные порты, модель, голос и
собственный OPENAI_API_KEY разговорного InitAvatar. Настройка клиента SERVICE_*
к этим значениям не относится. Разговор, голос, действия в браузере и сходство
сайта проверяются после запуска.

POST `/v1/jobs/{job_id}/deployments` только сохраняет отчёт получателя:

```json
{
  "deployment_id": "customer-001-run-001",
  "package_id": "из BuildResult",
  "manifest_sha256": "из BuildResult",
  "state": "deployed",
  "demo_url": "https://demo.customer.example/",
  "checks": {}
}
```

HTTP 200: `{"stored":true,"evidence_origin":"recipient_reported"}`.
`package_id` и manifest должны совпасть с результатом. Для `verified` обязательны
checks `site`, `knowledge`, `model`, `voice`, `browser`; у всех переданных checks
status должен быть `passed`. Отчёт не меняет build_state и не является независимой
приёмкой сервера. Повтор с тем же deployment_id и идентичным телом принят;
изменение прежнего отчёта — 409. Для следующего отчёта используй новый deployment_id.

## Ошибки и повторы

Ошибки описанных ручек: JSON `{"error":"код"}`. Проверяй HTTP status прежде,
чем читать ответ как Job/BuildResult. Не сохраняй Authorization в логах.

| HTTP | Примеры error | Действие |
|---|---|---|
| 401 | unauthorized | Проверь выданный ключ; не запускай новую сборку. |
| 404 | not_found, example_not_available, integration_not_available, outside_example_scope | Проверь URI/id; архив или пример могут отсутствовать. |
| 409 | idempotency_conflict | Тот же ключ использован с иным URL; исправь запрос. |
| 409 | result_not_available | Прочитай статус; для receipt это также неизвестная задача. |
| 409 | receipt_conflict | Прежний deployment_id получил другое тело. |
| 422 | invalid_source_url, non_public_source, only_source_url_allowed | Исправь URL/поля. |
| 422 | idempotency_key_required, invalid_body_size, invalid_json, body_must_be_object, unsupported_transfer_encoding | Исправь заголовок/JSON/передачу тела. |
| 422 | daily_job_limit | Лимит последних 24 часов. Не повторяй часто; готовые задачи читать можно. |
| 422 | package_mismatch, invalid_deployment_id, unknown_receipt_fields, invalid_deployment_state, invalid_checks, invalid_receipt, verification_requires_passed_checks | Исправь отчёт получателя. |

Обрыв сети/502/503 от ingress: повтор с прежним ключом и постепенной задержкой.
Не повторяй автоматически ошибки 401/404/422. Неизвестную ошибку или несовместимую
версию останови для разбора. Здесь нет cancellation, webhook, SSE, готовой ссылки
на развёрнутое демо или OAuth-передачи ChatGPT получателю.

Дополнительные URI перечислены в OpenAPI: healthz, каталог примеров, импортёр,
пакет подключения, UI и выбранный readonly-preview. GET /healthz проверяет API,
но не доказывает готовность worker, ассистента или внешнего HTTPS.
