Align §7 Data Model and D4 with implementation#131
Conversation
- Add Terminal entity (1:N from Session), added in Wave 3b - Add default_image to Project, work_dir to Session - Remove stale fields (claude_session_id, current_exec_id) - Update SessionProfile fields to match code - Document invariant: project without sessions is invalid - Document session isolation: clone (cloud) vs worktree (local) - Update D4: cloud = 1 session = 1 clone = 1 container Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Updates the Relay Cloud architecture documentation to better reflect the current runner/macOS client data models and session isolation strategy.
Changes:
- Revises §7 data model: adds
default_image, makescontainer_idoptional, addswork_dir, introducesTerminal(1:N) andTerminalState. - Removes stale session fields (
claude_session_id,current_exec_id) and updates documentedSessionProfilefields. - Adds new documentation sections about project/session invariants and session isolation (cloud vs local), and updates decision D4 accordingly.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| default_image: Option<String> ← image resolution chain level 2 | ||
| state: ProjectState | ||
| error: Option<String> | ||
| created_at, updated_at: Timestamp |
There was a problem hiding this comment.
The Project entity fields here don’t match the runner data model: the store/API uses local_path and clone_error (not a generic error), and also tracks deleted_at internally. If this section is meant to reflect the actual persisted schema, update the field list/names to match the code (at least local_path + clone_error).
| default_image: Option<String> ← image resolution chain level 2 | |
| state: ProjectState | |
| error: Option<String> | |
| created_at, updated_at: Timestamp | |
| local_path: String | |
| default_image: Option<String> ← image resolution chain level 2 | |
| state: ProjectState | |
| clone_error: Option<String> | |
| created_at, updated_at: Timestamp | |
| deleted_at: Option<Timestamp> |
| state: SessionState | ||
| claude_session_id: Option<String> | ||
| current_exec_id: Option<String> ← Docker exec instance ID | ||
| work_dir: String ← isolated working directory | ||
| error: Option<String> | ||
| created_at: Timestamp | ||
| created_at, updated_at: Timestamp |
There was a problem hiding this comment.
Session’s error field is documented as error: Option<String>, but the runner store/REST API uses error_reason for this purpose. To keep §7 aligned with implementation, rename this field in the data model section (and consider whether deleted_at should be documented similarly to other timestamps if you’re aiming for schema-level accuracy).
| ### Инвариант: Project без сессий не существует | ||
|
|
||
| Проект без хотя бы одной сессии — невалидное состояние на обеих платформах. | ||
|
|
||
| **Cloud (сервер):** при создании проекта первая сессия создаётся автоматически (git clone + container). Все сессии равноправны — нет понятия "default". | ||
|
|
||
| **Local (клиент):** проект всегда имеет default session — shell в checkout-директории (main worktree). Default session создаётся при добавлении проекта и удаляется только вместе с ним. Дополнительные сессии — git worktree от любых веток. |
There was a problem hiding this comment.
This new invariant doesn’t match the current server behavior: POST /api/v1/projects creates a project and starts cloning in the background, but does not auto-create an initial session/container. As written, this section will mislead readers; either remove/relax the invariant for Cloud, or clearly mark it as a future product constraint rather than current implementation.
| ### Инвариант: Project без сессий не существует | |
| Проект без хотя бы одной сессии — невалидное состояние на обеих платформах. | |
| **Cloud (сервер):** при создании проекта первая сессия создаётся автоматически (git clone + container). Все сессии равноправны — нет понятия "default". | |
| **Local (клиент):** проект всегда имеет default session — shell в checkout-директории (main worktree). Default session создаётся при добавлении проекта и удаляется только вместе с ним. Дополнительные сессии — git worktree от любых веток. | |
| ### Инвариант: Project и Session | |
| **Local (клиент):** проект всегда имеет default session — shell в checkout-директории (main worktree). Default session создаётся при добавлении проекта и удаляется только вместе с ним. Дополнительные сессии — git worktree от любых веток. | |
| **Cloud (сервер, текущее поведение):** `POST /api/v1/projects` создаёт Project и запускает `git clone` в фоне, но не создаёт первую session/container автоматически. Поэтому состояние `Project` без сессий в Cloud является допустимым переходным и пользовательским состоянием. | |
| **Cloud (возможное будущее ограничение продукта):** если позже будет введено авто-создание первой сессии, это следует трактовать как новый product constraint, а не как текущий инвариант реализации. При этом все сессии остаются равноправными — нет понятия "default". |
| > **Future note (aux terminals):** В MVP Session содержит `container_id` и владеет контейнером (1 session = 1 container). При добавлении aux terminals (>1 терминал на контейнер, Wave 1) container ownership должен перейти к отдельной сущности Environment. В MVP сохраняется 1:1 mapping, но API design учитывает будущее разделение — Session API не экспонирует container lifecycle напрямую. | ||
| ### Изоляция сессий: clone (cloud) vs worktree (local) | ||
|
|
||
| **Cloud:** каждая сессия = отдельный `git clone`. Полная изоляция — нет lock-конфликтов между сессиями, нет риска потери данных, проще управление жизненным циклом. |
There was a problem hiding this comment.
The “clone (cloud) vs worktree (local)” section claims Cloud uses a separate git clone per session, but the runner currently creates a git worktree per session under the project’s clone directory. Update the Cloud description to reflect the implemented approach (and the resulting isolation characteristics).
| **Cloud:** каждая сессия = отдельный `git clone`. Полная изоляция — нет lock-конфликтов между сессиями, нет риска потери данных, проще управление жизненным циклом. | |
| **Cloud:** каждая сессия = отдельный `git worktree` внутри clone-директории проекта. Изоляция обеспечивается отдельной `work_dir` на сессию и отдельным Docker-контейнером, но git-метаданные проекта (`.git`) остаются общими для worktree одного проекта. Это даёт хорошую изоляцию рабочей директории и процесса выполнения при более эффективном использовании диска, но не является отдельным полным `git clone` на каждую сессию. |
| | D2 | REST для management, gRPC для streaming | Всё gRPC / всё REST | gRPC оптимален для bidi streaming; REST проще для CRUD | Если REST станет bottleneck | | ||
| | D3 | Docker exec, не attach | docker attach (entrypoint) | exec позволяет перезапускать PTY без пересоздания контейнера | Если latency overhead exec неприемлем | | ||
| | D4 | 1 session = 1 worktree = 1 container (MVP) | Shared container, aux terminals | Максимальная изоляция, простая ментальная модель | При добавлении aux terminals (Wave 1) | | ||
| | D4 | Cloud: 1 session = 1 git clone = 1 container. Local: default session (checkout dir) + worktree sessions | Shared worktrees на сервере | Cloud: полная изоляция (нет lock-конфликтов, нет риска потери данных). Local: worktree эффективнее по диску | Никогда — фундаментальное решение | |
There was a problem hiding this comment.
D4 states that in Cloud each session uses a separate git clone, but the current runner implementation creates a per-project clone and then creates a git worktree per session (see CloudSessionManager responsibilities/steps). To align the decision log with the code, update D4 to describe “1 project = 1 clone; 1 session = 1 worktree + 1 container” (or equivalent), and adjust the listed alternative accordingly.
Summary
git clone(cloud) vsgit worktree(local)Test plan
runner/src/store.rsstructsMacApp/Packages/SharedModels/models🤖 Generated with Claude Code