- Date: 2026-05-26
- Status: Accepted
- Affects:
apps/api/src/deploy.ts(new) — POST /api/v1/projects/:id/deployapps/api/src/index.ts— mount deployAppapps/web/src/lib/api-client.ts—deployProject(id)functionapps/web/src/components/deploy-button.tsx(new) — UI button + modalapps/web/src/app/app/workflow/page.tsx— embed DeployButton
- References: ADR 0020 (Layout Grid), ADR 0021 (30K MAU ceiling), ADR 0022 (multi-env)
Context
После ADR 0022 multi-env setup project content застревал в Postgres — нет automated механизма push'а composition state в connected repo. Юзер хочет workflow:
- Edit composition в ARNO UI (Test environment)
- Click Deploy → composition serialized → committed в connected repo
- CI repo (CF Pages git integration / GitHub Actions) auto-deploys → environment updated
Это Phase 4 ADR 0020 в минимальной форме (без version history, без conflict detection, без multi-device sync).
Decision
Single endpoint POST /api/v1/projects/:id/deploy который:
- Verifies project ownership (existing
requireOwnedProject) - Loads
connected_reporow (required — 400 если не connected) - Loads all
screen_compositionrows для project - Serialises каждый screen →
pages/{screenId}.layout.json - Atomic batch commit на
connected_repo.default_branchчерез ARNO GitHub App - Returns
{ commit: { sha, url, message }, branch, repo, screensDeployed }
Key design choices
1. Direct push на default_branch (no PR).
- Trunk-based dev pattern (ADR 0021 §"trunk-based development")
- Promotion test→prod = manual PR
staging → mainна GitHub - Future enhancement: optional
?openPR=truequery для PR-based deploy
2. Atomic single commit для всех screens.
- Tree built up-front, single
createCommitcall, singleupdateRef - Если любой blob upload падает —
createTreeне call'ится, commit не происходит - Юзер видит либо «all screens deployed» либо «failed, retry»
3. Empty repo / empty project safe.
- Empty screens → создаётся
pages/_arno_deploy.jsonmarker (traceability) - Идемпотентность: если tree identical с base —
noop: true, нет empty commit
4. Format: legacy instances JSONB shape.
- Не zones model (ADR 0020 D6) пока
- Зачем: совместимость с current backend API contract + localStorage migration ещё не финализирована
- TODO: migrate format когда zones model становится canonical (Phase 4 full implementation)
5. Single source repo per project (connected_repo schema).
- Один
connected_reporow на project. Можно использовать один repo с двумя branches для двух projects (наш случай:arno-designmain + staging для Arno-dev/Arno-test). - Branch determined by
connected_repo.default_branchfield (можно настраивать SQL update).
6. Auth через ARNO GitHub App installation token.
- Reuses Phase 13 sync infrastructure (
getInstallationToken) - Installation_id stored в
connected_repo.installationIdper project - 58-min token cache в KV (SYNC namespace)
7. Commit attribution.
- Commit author = ARNO GitHub App bot
- Commit message includes project name + timestamp + screen count
- TODO: co-author = real user (требует GitHub username из
user.login)
Rejected alternatives
| Alternative | Reason |
|---|---|
| Open PR вместо direct push | Adds friction для simple Deploy. PR pattern useful для production deploys (можно добавить opt-in). Trunk-based dev — direct push acceptable для staging. |
| One commit per screen | Pollutes git history. Atomic batch — cleaner diff. |
| Background job queue (Cloudflare Queues) | Overkill для 30K MAU ceiling (ADR 0021). Sync response достаточна для < 50 screens per project. |
| Frontend builds TSX → push | Coupling: frontend знает internal model. Backend authoritative — single endpoint. |
Use git-provider PR helpers вместо raw commit primitives | Init endpoint pattern (createBlob+Tree+Commit+updateRef) уже работает — reuse без extra abstraction. |
| Zones model serialization прямо сейчас | Frontend localStorage migration не финализирована. Backend всё ещё receives legacy instances. Format upgrade — отдельная задача. |
| Multi-file commit через GitHub Contents API individual PUTs | Не атомарно (between PUTs commit history fragmented). Tree-based — atomic. |
Consequences
Positive
- Один click → state в git → CI deploy. Closes "test → dev promotion" loop (ADR 0022).
- Atomic commit — clear git history, easy to revert (single commit revert).
- Reuses всю Phase 13 sync infra — no new auth / install flow.
- Format
pages/{screenId}.layout.jsonсоответствует ADR 0020 §D6 future state. - Idempotent — repeated deploy без изменений = noop.
Negative
- No conflict detection — если два юзера deploy одновременно, второй overrides первого (last-write-wins).
- No history viewer в ARNO UI — юзер должен идти в GitHub для diff/rollback.
- No rollback button — revert через GitHub UI (revert commit + PR merge).
- No preview / dry-run — нельзя посмотреть что commit'нится до Deploy.
Mitigations / Future work
- Conflict detection через If-Match eTag header (Phase 4 ADR 0020 spec) — добавить когда multi-user editing включится
- History viewer как separate ADR (показывать git log в UI)
- Rollback button = wrapper над GitHub revert API
- Preview = dry-run mode
?dryRun=truereturning diff без commit
Performance
- ~50 screens per project = 50 blob uploads + 1 tree + 1 commit + 1 ref update = ~53 GitHub API calls
- All blob uploads parallel (
Promise.all) — ~1-2 sec total at p50 - Worker CPU ms minimal (just JSON serialization)
- Fits CF Workers Bundled (50ms CPU limit) easily
Cost (per ADR 0021 30K MAU ceiling)
- GitHub API rate limit: 5K req/h per installation. 30K MAU × 1 deploy/day = ~1.25 req/s avg. Burst tolerable.
- CF Workers: negligible (Deploy is rare operation, не каждый request).
Implementation status
- Backend endpoint (
apps/api/src/deploy.ts) - Mounted в
apps/api/src/index.ts - Frontend API client (
deployProjectinapi-client.ts) - UI button + modal (
deploy-button.tsx+ CSS) - Embedded в workflow page
- E2E test (deferred until Playwright setup)
- Conflict detection (deferred Phase 4 full impl)
Re-evaluation triggers
- Multi-user editing включается → add conflict detection + locking
- Cost spike → add rate limiting per project (e.g. max 1 deploy/minute)
- PR workflow requested → add
?openPR=truequery option - Format mismatch (когда clients стартуют writeить zones format в Postgres) → migrate JSON shape
References
- ADR 0020 Phase 4 — GitHub serialization roadmap
- ADR 0021 — 30K MAU ceiling determines tier choices (no queues, sync response OK)
- ADR 0022 — multi-env setup defined trunk-based pattern
- Master spec §I.3.2 — connected_repo, GitHub App, Phase 13 sync
apps/api/src/init.ts— referenced pattern для commit pipeline
Changelog
- 2026-05-26 v1.0: Initial ADR. Backend + UI implementation done. Awaiting verify on test env.