- Date: 2026-05-25 (v1.0) · 2026-05-29 (v2.0 — supersedes v1.0)
- Status: Accepted (v2.0). Supersedes the v1.0 env-branch model below.
- Affects:
- Branching workflow + worktree convention (
Arno/Rules.mdbranch-strategy section) - CF Pages project
arno(per-branch builds) + preview-proxy Worker - Workers:
arno-prod(prod),arno-dev(dev),arno-test-<topic>(ephemeral per session) - DNS in zone
arnomake.com:test-*.arnomake.com,dev.arnomake.com - Neon branches:
production,dev, per-session preview branches apps/api/wrangler*.toml,apps/api/src/index.ts(CORS),.github/workflows/*- GitHub branch protection on
main
- Branching workflow + worktree convention (
- References: ADR 0021 (30K MAU ceiling), ADR 0029 (auto-migrate solo)
Changelog
- 2026-06-02 v2.1: Phase C shipped — dev env pair landed (
dev.arnomake.com↔arno-api-dev, Neon branchdev, Liveblocks projectarno-dev). Naming reconciled with reality (arno-api-dev, parallel to existingarno-api/arno-api-test; v2.0 saidarno-devbut prod was never renamed toarno-prod— Phase D never shipped). Introduced pair-atomic deploy invariant: every workflow (ci.yml,release.yml,deploy-test.yml) deploys backend → healthcheck → web in that order, so a failed backend never leaves a new web pointing at an old backend. Single web artefact serves all envs (host-based API switch inapps/web/src/lib/api-client.ts:HOST_API+packages/url-import-extension/arno-bridge.js). - 2026-05-29 v2.0: Pivot from environment-branching (v1.0) to trunk-based development + ephemeral preview environments. Drivers: branch drift + migration collisions observed under v1.0; opaque
*.pages.devpreview URLs; requirement for multiple parallel sessions each with an isolated, on-brand, speaking preview URL. Environments become deploy slots ofmain's artifacts, not long-lived branches. - 2026-05-25 v1.0: Initial 2-env (prod + test-on-
feat/**). Superseded.
Context
v1.0 mapped environments to long-lived branches (test = CF Pages preview of feat/**). In practice this produced exactly the failure modes trunk-based development is designed to avoid:
- Drift.
feat/layout-gridran 100+ commits behindmain;deploy-test.ymlneeded a "warn if stale" step as a band-aid. Long-lived branches diverge. - Migration collisions. Parallel branches both claimed the next sequential migration number → journal divergence (HANDOFF: "миграции применяются вручную т.к. журнал divergent").
- Rebuilt, not promoted. Prod was a different build than what was validated → "passed on test, broke on prod" risk.
- Opaque preview URLs. CF appended hash suffixes (
feat-layout-grid-sl4j.arno-1hu.pages.dev); the bare alias served stale builds.
New requirement: the owner runs multiple parallel sessions (each effectively a concurrent contributor). Each needs its own isolated preview, simultaneously, on *.arnomake.com, with a speaking name.
This is the canonical big-org pattern: Trunk-Based Development + Continuous Delivery ("build once, promote the artifact") + ephemeral preview/review environments (cf. Google/Meta monorepo trunk, Vercel/Netlify/CF preview deployments, Netflix Spinnaker artifact promotion).
Decision
1. Branching — trunk-based
main= the only long-lived branch and source of truth. Protected.feat/<topic>— short-lived, one per session/feature.<topic>is a short kebab slug; it also names the preview URL (test-<topic>.arnomake.com).- No long-lived
dev/test/releasebranches. Environments are deploy slots (below), not branches. - Squash-merge PRs into
main→ linear history, one commit per feature (clean with many small session commits, trivial revert).
2. Session → branch lifecycle (Rule)
On a new session that recognizes "Arno project" and begins real work:
- Create
feat/<topic>from freshorigin/main+ worktreeArno-<topic>/; push spins up the ephemeral test env.
Teardown triggers (reliable signals — "chat closed" is not observable):
- Branch merged to
main→ auto-delete branch + worktree + ephemeral env (worker, Neon branch, DNS) via GH Actions on branchdelete. - Session ended with zero commits → delete (no waste from pure Q&A/design sessions).
- Stale (no activity N days) → periodic cleanup cron.
Pure discussion sessions create nothing (or auto-clean if empty).
3. Environments (deploy slots, not branches)
| tier | frontend (user-facing) | backend worker | backend URL | Neon | Liveblocks |
|---|---|---|---|---|---|
| test (per session) | test-<topic>.arnomake.com | arno-test-<topic> (ephemeral) | arno-test-<topic>.vadimpianof.workers.dev | per-session branch (from production) | arno-test |
dev (main RC) | dev.arnomake.com | arno-dev | arno-dev.vadimpianof.workers.dev | dev (from main) | arno-test |
| prod | arnomake.com | arno-prod | api.arnomake.com | production | arno |
Naming note (intentional, documented to avoid confusion vs the common dev<staging<prod convention): here dev = the integrated main / release-candidate (more stable) and test-* = active WIP per session (churny). Backend workers follow arno-<tier>[-<topic>].
4. Frontend previews — speaking, parallel, on-brand, free SSL
- Pattern
test-<topic>.arnomake.comis a single-level subdomain → covered by free Universal SSL (*.arnomake.com). Thetest-prefix (not a<topic>.test.nesting) is deliberate: it avoids a 2-level wildcard that would require Advanced Certificate Manager ($10/mo). - Routing: a preview-proxy Worker on
test-*.arnomake.comresolves the branch's latest CF Pages deployment (via CF API, cached) and proxies it — which also fixes the stale-alias problem. DNS fortest-*managed per-branch by CI (or a scoped wildcard), without shadowingapi/dev/apex (they have explicit records). - CF Pages auto-builds every branch, so frontend previews cost ~0 config; only the proxy Worker is added (free on existing Workers Paid).
5. Backend symmetry — ephemeral env per session
Backend mirrors the 3 tiers. The asymmetry vs frontend: CF Pages builds branches automatically, but each backend env must be deployed + secret-injected + given a Neon branch + torn down by CI. Accepted as the cost of true per-session isolation (eliminates shared-DB coupling between parallel sessions). Backend URLs are internal (workers.dev) — only frontends need .arnomake.com.
6. Promotion (gated, build-once)
- Merge to
main→ auto-deploy to dev (dev.arnomake.com, RC). - Prod = manual
workflow_dispatch"release" promoting the same artifact/SHA validated on dev (CF Pages promote +wrangler versions deploy), behind an environment-approval gate. Never auto on merge. Aligns with existing rules "backend deploy only from main" + "push to main only on explicit directive".
7. Migrations
- Per-session Neon branch removes runtime collisions (each branch migrates its own DB).
- Rebase-before-merge (branch-up-to-date rule) linearizes migration order at the single integration point (
main). - Optional extra: timestamp/ULID migration IDs to minimize journal merge-conflicts — pending verification that Drizzle's sequential journal supports it; adopt only if it doesn't fight the tooling.
8. Guardrails — branch protection on main
PR required (no direct push) · status checks green (typecheck/test/e2e) · branch up-to-date with main required (kills drift) · squash/linear history · no force-push, no deletion · prod deploy only via the gated release workflow.
Rejected alternatives
| Alternative | Why rejected |
|---|---|
| Environment branches (GitFlow / v1.0) | Drift + migration collisions (observed); rebuilt-not-promoted; scales badly with parallel contributors |
<topic>.test.arnomake.com (nested) | 2-level wildcard not covered by Universal SSL → needs ACM $10/mo. Flattened to test-<topic>. prefix → free |
Single preview slot (preview.arnomake.com, overwritten) | Breaks the core requirement: multiple parallel sessions need simultaneous previews |
| Shared non-prod backend + DB for all previews | Couples parallel sessions on schema/data; chose per-session isolation instead |
Keep prod worker arno-api | Owner chose full naming symmetry → arno-prod (one-time careful migration, §Implementation) |
| Per-PR ephemeral Neon + per-branch backend custom domains | Backend is internal; workers.dev URLs suffice — avoids per-branch DNS/route churn |
Consequences
Positive: no long-lived-branch drift; clean parallel on-brand speaking previews at $0 extra SSL; true per-session isolation; artifact promotion (no prod surprises); scales to more sessions / a team; gated prod.
Negative / cost: the largest work item is CI orchestration of ephemeral environments (create: deploy worker + inject secrets + Neon branch + DNS; teardown on branch delete). Per-env secret injection. Preview-proxy Worker is a dependency in the preview path. Prod worker rename is a one-time prod-touching migration.
Mitigation: ephemeral orchestration written once, then automatic; if it proves too heavy, fall back to a shared non-prod backend for FE-only sessions (re-eval trigger).
Implementation (phased)
Phase A — config + CI (no prod impact)
-
apps/api/src/index.tsCORS allowlist +=https://test-*.arnomake.com(regex),https://dev.arnomake.com - Rework
deployworkflows: pushfeat/*→ build + deployarno-test-<topic>+ frontend branch build + Neon branch; mergemain→ deployarno-dev+dev.arnomake.com - GH Actions on branch
delete→ teardown ephemeral env - Branch protection on
main
Phase B — preview routing
- preview-proxy Worker on
test-*.arnomake.com(resolve latest deployment per branch) - DNS strategy for
test-*(per-branch records via CI, or scoped wildcard) — verify no shadowing ofapi/dev/apex
Phase C — dev env (✅ shipped 2026-06-02 v2.1)
-
arno-api-devworker (apps/api/wrangler.dev.toml) — name aligned witharno-api/arno-api-test, not v2.0'sarno-dev -
ci.ymldev pair deploy block (migrate dev-Neon → backend → healthcheck → web) -
release.ymlreordered for pair-atomicity (backend → healthcheck → web) - Host-based API switch in
api-client.ts+arno-bridge.js(one web artefact, three pairs) - Owner UI: Neon branch
dev+ Liveblocks projectarno-dev+ OAuth AppArno-dev+ KV namespacesAUTH-DEV/SYNC-DEV+ GH secretsDEV_DATABASE_URL/CF_WORKERS_SUBDOMAIN→ wrangler secrets (see HANDOFF "Dev env one-time checklist")
Phase D — prod rename (prod-touching, separate careful step)
- Create
arno-prod(copy code + secrets + KV bindings) → verify onarno-prod.workers.dev - Move custom domain
api.arnomake.comarno-api→arno-prod(brief cutover, low traffic) - Verify
api.arnomake.com/health→ delete oldarno-api. Reversible (re-attach domain / redeploy).
Phase E — promotion + gate
-
releaseworkflow (workflow_dispatch, environment approval) promoting validatedmainartifact to prod
Phase F — migrations
- Verify Drizzle timestamp/ULID ordering; adopt if clean. Otherwise rely on per-branch DB + rebase.
Re-evaluation triggers
- Parallel sessions / preview volume high → review CF + Neon free-tier usage
- Ephemeral orchestration too heavy to maintain → shared non-prod backend for FE-only sessions
- Team > 1 → formalize onboarding + CODEOWNERS
- Secret rotation > monthly OR team > 2 → adopt a secrets manager (Doppler)
v1.0 (superseded — historical)
The original 2-env design (prod + test-on-feat/** preview, shared arno-api-test, Neon feat-layout-grid branch) is preserved in git history at the pre-v2.0 revision of this file. Key still-valid rationale carried forward: Neon branches (not separate projects) for cheap isolated DBs; secrets via wrangler secret put + GHA secrets (no Doppler at this scale); same Sentry project with env tag. Operational detail lives in docs/runbooks/environments.md.