Синхронизация и сборка
This page has not been translated into English yet. The Russian original is below.
Статус: реализовано (backend развёрнут на*.reeel.dev; клиент — Dart-пакетreeel_platform_client, интеграция в редактор —PlatformSyncService). Полный контракт API — источник истины — вplatform-docs/reeel-editor-api.md. Ниже — как этим пользуется редактор и как устроены потоки.
Роли
| Роль | Кто это | Ответственность |
|---|---|---|
| Editor | Редактор REEEL (десктоп) |
Вход по email/паролю, привязка проекта, синхронизация снапшота, постановка сборки. Использует
reeel_platform_client
.
|
| Platform API |
https://api.reeel.dev
|
Единственная точка входа: auth, организации/проекты, ревизии снапшотов, очередь сборок, выдача контента. |
| Storage |
https://storage.reeel.dev
|
Объектное хранилище blob'ов. Доступ только по presigned-URL из ответов API (авторизация в самом URL). |
| Build Worker | Отдельный исполнитель (пока не реализован) |
Берёт задачу + снимок ревизии, собирает конфигурацию (
config_builder
/ болванки), заливает артефакт и статус.
|
Клиент платформы (
reeel_platform_client
) fs-агностичен и рассчитан на переиспользование будущим worker'ом (тот тоже читает ревизии и blob'ы).
Аутентификация
Email/пароль → JWT. OTP
нет
(не путать с runtime-клиентом
reeel_api_client
, у которого свой контракт
reeel.dev/reeel/api
).
-
POST /auth/signup→202(пользовательPENDING_VERIFICATION); на почту — ссылка/auth/verify-email?token=…(TTL 24 ч, одноразовая).POST /auth/verify-email. -
POST /auth/login→{ access_token, refresh_token, token_type, expires_in }. -
access
— JWT RS256, TTL
15 мин
; шлётся как
Authorization: Bearer <jwt>. - refresh — opaque, TTL 30 дней , ротируется на каждом обмене. ⚠️ Повторное предъявление ротированного refresh = компрометация → аннулируется вся семья токенов. Клиент хранит только последнюю пару и не ретраит старый refresh.
-
POST /auth/refresh— обновление пары;POST /auth/logout— отзыв предъявленного refresh;GET /auth/me— текущий пользователь. -
OIDC device flow
(браузерный вход) поддержан в пакете (
DeviceFlowApi, публичный клиентreeel-cloud-cli, PKCE, эндпоинты из discovery); в UI редактора сейчас выведен только вход по паролю. - Чего в API нет : resend письма, forgot/reset и смены пароля.
Роли (
owner > admin > developer > viewer
) привязаны к
организации
и действуют на все её проекты (project-scoped ролей нет).
Ресурсная модель
organization (org_…)
└── project (prj_…) slug, region, tier, defaultDomain=<slug>.apps.reeel.dev
├── snapshot revision (snp_…) контент проекта, content-addressed
└── build task (rbt_…) сборка ревизии под конфигурацию
Проект в редакторе (папка с
project.json
) привязывается к удалённому
prj_…
; привязка (
orgId
+
projectId
+ последняя ревизия) хранится локально в
.reeel/state.json
.
Поток 1 — синхронизация проекта (Editor → Platform)
Трёхфазная, content-addressed по SHA-256:
negotiate → presigned PUT → commit
. Оркестрируется
SnapshotSync.push(projectId, files)
; в редакторе —
PlatformSyncService.sync()
.
Editor api.reeel.dev storage.reeel.dev
│ 1. sha256+size каждого файла │
│ 2. POST …/snapshots:negotiate ─────►│ │
│◄──── { revisionId, missing[], upload[] (presigned PUT) } │
│ 3. PUT сырые байты (для каждого missing) ─────────────────►│ (без Authorization)
│◄──────────────────────────────────────────── 200/204 ──────┤
│ 4. POST …/snapshots/{revisionId}:commit ─►│ │
│◄──── { status: COMMITTED, contentHash, revision, … } │
-
Что синхронизируется:
всё содержимое проекта —
project.json,reeel/,data/, произвольные ассеты,.well-known/— кроме любого пути, где сегмент начинается с.(.reeel/,.git/,.claude/,.env, …). Правило зашито вPlatformSyncService.isSyncablePath(исключение — точный сегмент.well-known). ⚠️ При выключенном content-gating всё это публично читаемо — не кладите секреты в синкаемые файлы. -
Идемпотентность по контенту:
повторный
commitидентичного контента возвращает ту же ревизию без создания новой; неизменные файлы не перезаливаются (пустойmissing). -
Ошибки:
403 forbidden_path(недопустимый путь в negotiate),409 incomplete_snapshot(залиты не все blob'ы — клиент ре-negotiate'ит и повторяет commit один раз),422 manifest_invalid(невалидныйproject.json, сerrors[]). -
Dry-run:
перед push редактор прогоняет локальную валидацию манифеста (
ProjectService.validate()); на сервере естьPOST …/snapshots:validate(всегда200). -
Pull / история:
GET …/snapshots/current,…/snapshots/{revisionId},GET …/snapshots?cursor=…&limit=…(курсорная пагинация), blob —GET …/blobs/{sha256}(302).
Поток 2 — постановка сборки в очередь (Editor → Platform)
Editor api.reeel.dev
│ POST /api/v1/reeel/builds │
│ { projectId, revisionId, configurationName } │ (+ опц. Idempotency-Key)
│ ──────────────────────────────────────────────►│ 201 { taskId: rbt_…, status: queued }
│ GET /api/v1/reeel/builds/{taskId} (poll 2–5с)─►│ queued → running → succeeded/failed
│◄──── { status, artifactRef | errorCode }───────┤
-
configurationName— имя конфигурации изproject.json(выбранной в редакторе). Сервер не валидирует его против манифеста при постановке — ошибка всплывёт при сборке. -
Опрос:
BuildsApi.poll(в редакторе —PlatformSyncService.build()); backoff на429. Есть SSE-стрим логов (Accept: text/event-stream,BuildsApi.streamLogs). -
Идемпотентность:
опциональный
Idempotency-Key(тот же ключ+тело = та же задача). -
Списка сборок нет — трекинг по
taskId. Не путать с legacy/api/v1/builds.
Поток 3 — исполнение сборки (Worker → Platform) — планируется
Worker (ещё не реализован) заберёт задачу, скачает ревизию (
snapshots/{id}
+
blobs/{sha256}
), соберёт конфигурацию через существующий пайплайн (
config_builder
,
make build
) или
быстрый путь через болванки
(
TemplateFlavorResolver
→ перепаковка
flutter_assets
без перекомпиляции; см. template
builds.md) и зальёт артефакт + финальный статус. Тот же
reeel_platform_client
предназначен и для worker'а.
Runtime-контент и gating (вне этого майлстоуна)
Собранное приложение тянет контент через
GET /content/{projectId}/{path}
(302 на presigned GET). ⚠️ У новых проектов
content-gating включён по умолчанию
— runtime требует content-grant (
enroll
install → обмен на grant, TTL 15 мин). Пакет реализует этот lifecycle (
ContentApi
), но редактор его не использует; включение grant-flow в собранные приложения — отдельная задача.
Обработка ошибок
Формат — RFC 7807 ProblemDetail. Машинное поле
code
есть
только
у бизнес-ошибок
/api/v1/**
и
/content/**
; у
/auth/**
и входных
401/403/429
его нет — там ветвление по HTTP-статусу. Клиент маппит всё это в
PlatformApiException { statusCode, code?, detail, errors[], warnings[], missing[] }
и игнорирует неизвестные поля ответов. Полный индекс кодов — в
platform-docs
.
Поверхность API (что использует редактор)
| Операция | Метод / путь |
Клиент (
reeel_platform_client
)
|
|---|---|---|
| Вход / сессия |
POST /auth/login
,
/auth/refresh
,
GET /auth/me
|
AuthApi
|
| Список организаций / проектов |
GET /api/v1/organizations
,
…/{orgId}/projects
|
OrganizationsApi
,
ProjectsApi
|
| Создать проект |
POST /api/v1/organizations/{orgId}/projects
|
ProjectsApi.create
|
| Negotiate снапшота |
POST …/snapshots:negotiate
|
SnapshotsApi
/
SnapshotSync
|
| Upload blob'а |
PUT <presigned>
(storage)
|
AuthedHttp.putToStorage
|
| Commit ревизии |
POST …/snapshots/{revisionId}:commit
|
SnapshotSync.push
|
| Текущая ревизия / история |
GET …/snapshots/current
,
…/snapshots?cursor=
|
SnapshotsApi
|
| Поставить сборку |
POST /api/v1/reeel/builds
|
BuildsApi.enqueue
|
| Статус / логи сборки |
GET /api/v1/reeel/builds/{taskId}
(poll / SSE)
|
BuildsApi.poll
/
streamLogs
|
Реализация — статус
Готово
-
Dart-клиент
reeel_platform_clientна все группы эндпоинтов (auth + device flow, организации, проекты, участники, снапшоты +SnapshotSync, сборки, content). -
Транспорт: инъекция Bearer, single-flight refresh на
401, presigned PUT, разбор ProblemDetail. -
Интеграция в редактор:
PlatformSyncService(вход email/пароль, привязка проекта в.reeel/state.json,sync(),build()), персистентные токены, конфигplatform_api_url. - UI: форма входа, кнопки Sync/Build и меню аккаунта в тулбаре, диалог привязки.
Планируется
-
Build worker (протокол claim/artifact; переиспользует
reeel_platform_client). - Runtime content-grant flow в собранных приложениях (gating включён по умолчанию).
- Device-flow вход в UI редактора (в пакете реализован).
- Push-уведомления о статусе сборки вместо поллинга.