CRUMB a card from devarno-cloud

Site Hosting Modes & Lifecycle Stages

choco intermediate 5 min read

ELI5

Each site row carries two labels. One says where it’s hosted (the user’s GitHub or the platform’s edge), the other says how mature it is (just a playground, adopted, or live). The two labels look like they overlap, but they describe different facts — one is who owns the source, the other is where in its life is it.

Technical Deep Dive

Defined as proto enums in proto/events/onboarding/v1/lifecycle.proto:125-144 and as columns on the sites table per ADR-001 (architecture/decisions/ADR-001-platform-hosted-welcome.md:29-33).

Two Orthogonal Discriminators

classDiagram
class Site {
site_id
hosting_mode "USER_GIT or PLATFORM_TEMPLATE"
lifecycle_stage "PLAYGROUND, ADOPTED, LIVE"
deploy_status "last build outcome"
}
class SiteHostingMode {
<<enum>>
USER_GIT
PLATFORM_TEMPLATE
}
class SiteLifecycleStage {
<<enum>>
PLAYGROUND
ADOPTED
LIVE
}
Site --> SiteHostingMode
Site --> SiteLifecycleStage

Binding Invariant

Per lifecycle.proto:136-138:

hosting_mode = PLATFORM_TEMPLATE ⇒ stage ∈ {PLAYGROUND}
hosting_mode = USER_GIT ⇒ stage ∈ {ADOPTED, LIVE}

Enforced both at the DB layer via a CHECK constraint and at the app layer (ADR-001-A:33). ADOPTED is the transient state immediately after adoption while the first user_git deploy settles; LIVE is the long-run state.

Hosting Mode Semantics

  • USER_GIT — backed by a user-owned GitHub repo, deployed through the saga of choco-004. Has deploy_target, github_repo, requires GitHub OAuth.
  • PLATFORM_TEMPLATE — backed by a choco-owned template, served from the Cloudflare edge tier (R2 + Worker) at welcome.choco.tools/{slug}. No deploy_target, no github_repo, no GitHub link required.

Why Two Columns, Not One

ADR-001 alternative C considered flattening to a single enum and rejected it: each branch in the codebase (provisioning router, serving, edit surface, analytics) needs both facts independently. Encoding them in one enum forces every fork to map enum → ownership implicitly, which drifts.

Adoption

PLATFORM_TEMPLATE + PLAYGROUNDUSER_GIT + ADOPTED is one atomic UPDATE under FOR UPDATE lock (ADR-001:35). The accompanying saga reuses provisioning_sagas (see choco-004) but is distinguished by a new saga_kind column.

Deploy Status Is Separate

deploy_status tracks the most recent build outcome; lifecycle_stage is an independent positional fact (lifecycle.proto:131-132). A LIVE site can have a failed last deploy without changing stage.

Key Terms

  • playground — terminal-until-adopted; the platform owns the content.
  • adopted — transient, post-adoption; first user_git deploy still settling.
  • welcome site — the canonical PLATFORM_TEMPLATE+PLAYGROUND instance, served at welcome.choco.tools/{slug}.

Q&A

Q: Can a site be USER_GIT + PLAYGROUND? A: No — the invariant excludes it. PLAYGROUND is reserved for platform_template; user_git sites enter as ADOPTED (post-adoption) or are seeded as LIVE.

Q: Why does the saga short-circuit for PLATFORM_TEMPLATE? A: SiteProvisionRequested.hosting_mode (lifecycle.proto:171) is read by the gateway; PLATFORM_TEMPLATE skips the user_git saga entirely and emits WelcomeSiteProvisioned (lifecycle.proto:222) as the terminal signal.

Q: Where does the lifecycle invariant live in code? A: CHECK constraint in the sites migration plus an app-layer guard; ADR-001:33 names both layers as the enforcement points.

Examples

A user signs up without a GitHub link. The gateway inserts a site with hosting_mode=PLATFORM_TEMPLATE, lifecycle_stage=PLAYGROUND, github_repo=NULL. They edit pages. They later adopt: one UPDATE with FOR UPDATE rewrites the row to hosting_mode=USER_GIT, lifecycle_stage=ADOPTED, github_repo='owner/name', and WelcomeSiteAdopted (lifecycle.proto:236) fires.

neighbors on the map