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 --> SiteLifecycleStageBinding 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 ofchoco-004. Hasdeploy_target,github_repo, requires GitHub OAuth.PLATFORM_TEMPLATE— backed by a choco-owned template, served from the Cloudflare edge tier (R2 + Worker) atwelcome.choco.tools/{slug}. Nodeploy_target, nogithub_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 + PLAYGROUND → USER_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
- Site Provisioning Saga State Machine debugging a site stuck mid-provision
- Tutorial Workflow Runs adding a new tutorial scenario to the gallery