Prototype To Client Promotion
Prototype-to-Client Tenant Promotion Procedural Runbook
Source Issue: #578
Status:uningested(Point-in-time write-up pending/docs distillalignment)
Target Canonical Spec:docs/runbooks/PROTOTYPE_TO_CLIENT_PROMOTION.md
1. Purpose, Architecture & Paradigm Shift
In SiteSwarm, client acquisition transitions from outbound prospecting directly into engineering through the Pre-Awareness Prototype Lifecycle (codified under Epic #575 and PROTOTYPE_WORKFLOW_AND_PROGRESSIVE_ESCALATION.md). Rather than presenting static mockups or pitch decks, prospective business owners experience a sub-second, interactive edge prototype built from verified business facts at apps/proto-<slug>/.
When a prospect executes a service agreement and pays their onboarding deposit, we do not throw away the prototype, nor do we re-scaffold a greenfield application from scratch. Instead, this runbook defines the procedural, auditable promotion workflow to elevate a signed prospect prototype into a first-class production client tenant workspace at apps/<client-prefix>-<site-slug>/.
flowchart TD subgraph PrePromotion["1. Prototype Origin & Discovery Intake"] ProtoApp["Phase 1-3 Prototype\napps/proto-<slug>"] --> IntakeCheck["Post-Discovery Intake Checklist\nCLIENT_INTAKE_CHECKLIST.md §2.1 & §5.3"] IntakeCheck --> RatifyVibe["Ratify Builder Liberties &\nFinalize VIBE.md Contract"] end
subgraph GitPromotion["2. Git & Workspace Synchronization"] RatifyVibe --> GitMv["In-Place Renaming (git mv)\nPreserve Commit Pedigree & Blame"] GitMv --> SyncManifests["Workspace Synchronization\npackage.json, root pnpm-workspace.yaml,\nswarm.config.ts & wrangler.jsonc"] end
subgraph CloudflareInfra["3. Cloudflare Tenant Infrastructure"] SyncManifests --> ProvD1["Dedicated D1 SQLite DB\nMigrate schema.sql & seed.sql"] ProvD1 --> ProvR2["Dedicated R2 Media Bucket\nIngest CDN Hotlinks -> Client R2"] ProvR2 --> WireIngress["Platforms Custom Hostname Ingress\nDual Worker Route Binding"] end
subgraph ProductionReady["4. Zero-Lockout & Quality Verification"] WireIngress --> ExportCheck["Zero-Lockout Portability Verification\n(swarm export-client / wrangler d1 export)"] ExportCheck --> MachineGate["Tier 1 & Tier 2 Quality Gates\npnpm run check & pnpm run lint:arch"] MachineGate --> LiveTenant["Live Production Client Tenant\napps/<client-prefix>-<site-slug>"] endWhy a Procedural Runbook Over Black-Box Automation
Tenant promotion involves high-stakes infrastructure boundaries: renaming Git directories, binding production Cloudflare D1 databases, provisioning dedicated R2 storage, migrating external CDN assets, and wiring live DNS hostnames without causing downtime or email (MX) disruption. Rather than relying on a rigid, over-constrained compiled CLI, this runbook provides an auditable, step-by-step procedure paired with an autonomous agent guidance recipe that engineers and agent workflows can execute deterministically.
2. Pre-Promotion Verification & Lineage Checklist
Before executing tenant promotion, the engineer or agent must verify the following prerequisites:
- Commercial Gate: Master Services Agreement (MSA) signed, deposit received, and tenant billing schedule locked.
- Discovery Synthesis Completed: Post-discovery intake checklist (
CLIENT_INTAKE_CHECKLIST.md) completed, including Know-Your-Customer (KYC) identity records, authorized technical DNS contact, and domain inventory. - Prototype Working Tree Clean: Prototype directory
apps/proto-<slug>/exists onmain(or a dedicated feature branch) with a clean git working tree (git statusshows 0 uncommitted changes). - Tenant Naming Agreement: Target client prefix and site slug confirmed per monorepo convention:
apps/<client-prefix>-<site-slug>(e.g.,apps/clk-construction,apps/rcc-stjohn).
3. Step-by-Step Tenant Promotion Procedure
Step 1: In-Place Git Directory Renaming (Preserving Authorship)
[!CAUTION] Zero Sibling Copying & History Preservation: Never use
cp -ror manual directory copying to duplicate the prototype into a new client folder. Copying breaksgit blame, severs commit history, obscures architectural decisions made during prototyping, and causes file duplication. Promotion must always occur viagit mv.
Execute the in-place Git move from the monorepo root:
# 1. Ensure you are on the target promotion branchgit checkout -b feature/tenant-promote-<client-prefix>-<site-slug>
# 2. Rename the directory using git mvgit mv apps/proto-<slug> apps/<client-prefix>-<site-slug>
# 3. Stage the directory renamegit commit -m "chore(tenants): promote proto-<slug> to <client-prefix>-<site-slug> [lineage: #578]"Git automatically tracks this operation as a file rename (preserving 100% of the commit log, line-by-line blame, and original author attribution).
Step 2: Workspace Manifest & Configuration Synchronization
After moving the directory, synchronize the monorepo package graph, root tooling, and app manifests.
2.1 Update App package.json
Edit apps/<client-prefix>-<site-slug>/package.json to reflect the new client package identity:
{ "name": "@siteswarm/app-<client-prefix>-<site-slug>", "description": "Production tenant frontend for <Client Brand Name>", "private": true, "type": "module", "scripts": { "dev": "astro dev", "build": "astro build", "preview": "wrangler dev", "check": "astro check" }}2.2 Verify Root Workspace Registration
The root pnpm-workspace.yaml includes 'apps/*'. Verify that pnpm recognizes the promoted package:
# Re-link the workspace graphpnpm install2.3 Register Root Convenience Scripts in package.json
Add development and build shortcut scripts to the root package.json under "scripts":
{ "scripts": { "dev:<client-prefix>-<site-slug>": "pnpm --filter=@siteswarm/app-<client-prefix>-<site-slug> run dev", "build:<client-prefix>-<site-slug>": "pnpm --filter=@siteswarm/app-<client-prefix>-<site-slug> run build" }}2.4 Synchronize App Governance Manifest (swarm.config.ts)
Create or update apps/<client-prefix>-<site-slug>/swarm.config.ts using @siteswarm/governance:
import { defineAppConfig } from "@siteswarm/governance";
export default defineAppConfig({ appId: "<client-prefix>-<site-slug>", name: "<Client Official Brand / DBA Name>", brand: { primaryColor: "#<HEX>", accentColor: "#<HEX>", fontFamilyHeading: "'<Display Font>', serif", fontFamilyBody: "'<Body Font>', sans-serif", logoAssetPath: "public/favicon.svg", }, deployment: { productionDomain: "www.<client-domain>.com", stagingDomain: "<client-prefix>-<site-slug>-staging.<platform-domain>", cloudPlatform: "cloudflare", }, capabilities: { health: { type: "horizontal", version: "1.0.0", targets: ["src/pages/api/health.ts"], options: { endpoint: "/api/health", }, }, "dynamic-seo": { type: "horizontal", version: "1.0.0", targets: ["src/layouts/BaseLayout.astro"], options: { schemaType: "LocalBusiness", enableBreadcrumbs: true, }, }, critique: { type: "horizontal", version: "0.1.0", targets: ["src/layouts/BaseLayout.astro"], options: { enabledEnvironments: ["development", "preview", "staging"], requireAuth: false, }, }, },});2.5 Update Cloudflare Worker Configuration (wrangler.jsonc)
Update apps/<client-prefix>-<site-slug>/wrangler.jsonc:
- Set the top-level Worker name:
"name": "<client-prefix>-<site-slug>" - Configure
env.stagingandenv.productionblocks - Set
"APP_ID": "<client-prefix>-<site-slug>"in vars - Disable session KV: ensure Astro adapter declares
session: falseinastro.config.mjs
Step 3: Dedicated Cloudflare Infrastructure Provisioning
SiteSwarm adheres strictly to tenant isolation. Each client tenant receives dedicated Cloudflare D1 SQLite database(s) and dedicated R2 media bucket(s). Multi-tenant shared tables are strictly forbidden for client business content.
3.1 Provision Dedicated Cloudflare D1 Databases
Provision separate SQLite databases for production and staging:
# Provision production D1 databasewrangler d1 create <client-prefix>-<site-slug>-prod-d1
# Provision staging D1 databasewrangler d1 create <client-prefix>-<site-slug>-staging-d1Record the output database_id UUIDs returned by Wrangler.
3.2 Migrate Prototype Schema & Seed Data
If the prototype reached Phase 3 escalation and generated a relational schema (schema.sql) and verified seed data (seed.sql):
# 1. Apply schema to staging and productionwrangler d1 execute <client-prefix>-<site-slug>-staging-d1 --remote --file=./schema.sqlwrangler d1 execute <client-prefix>-<site-slug>-prod-d1 --remote --file=./schema.sql
# 2. Seed initial verified business records into staging and productionwrangler d1 execute <client-prefix>-<site-slug>-staging-d1 --remote --file=./seed.sqlwrangler d1 execute <client-prefix>-<site-slug>-prod-d1 --remote --file=./seed.sql3.3 Provision Dedicated Cloudflare R2 Media Buckets
Provision isolated object storage buckets for client media uploads:
# Provision production R2 bucketwrangler r2 bucket create <client-prefix>-<site-slug>-media-prod
# Provision staging R2 bucketwrangler r2 bucket create <client-prefix>-<site-slug>-media-staging3.4 Bind Infrastructure in wrangler.jsonc
In the client app (or Worker B CMS if utilizing EmDash split architecture per ADR-0003), bind the provisioned databases and buckets:
{ "d1_databases": [ { "binding": "DB", "database_name": "<client-prefix>-<site-slug>-prod-d1", "database_id": "<GENERATED_PROD_D1_UUID>" } ], "r2_buckets": [ { "binding": "MEDIA", "bucket_name": "<client-prefix>-<site-slug>-media-prod" } ], "env": { "staging": { "d1_databases": [ { "binding": "DB", "database_name": "<client-prefix>-<site-slug>-staging-d1", "database_id": "<GENERATED_STAGING_D1_UUID>" } ], "r2_buckets": [ { "binding": "MEDIA", "bucket_name": "<client-prefix>-<site-slug>-media-staging" } ] } }}Step 4: Remote Asset Migration Checklist & R2 Ingestion
In the prototype phase, media assets may have been hotlinked from external CDNs (Unsplash, prospect web presences, Yelp, or social media) with aspect-ratio SVG fallbacks per PROTO_BASELINE_REQUIREMENTS.md. In production client tenants, third-party hotlinking is strictly prohibited. All external media must be audited, ingested into the tenant’s dedicated R2 bucket, and served from edge storage.
4.1 Execute External Asset Audit
Audit all external image and media URLs across the tenant application:
grep -rnE 'https?://[^"\x27\s>]+\.(jpg|jpeg|png|webp|svg|avif|gif)' apps/<client-prefix>-<site-slug>/src/Also check prospect.json and component props for remote image links.
4.2 Ingest Assets into Client R2 Storage
For each external asset:
- Download the asset locally:
Terminal window curl -sSL "<remote_cdn_url>" -o /tmp/asset-name.webp - Upload the asset directly to the client’s dedicated R2 bucket:
Terminal window wrangler r2 object put <client-prefix>-<site-slug>-media-prod/uploads/<asset-name>.webp --file=/tmp/asset-name.webpwrangler r2 object put <client-prefix>-<site-slug>-media-staging/uploads/<asset-name>.webp --file=/tmp/asset-name.webp - Update the markup or CMS seed record to reference the tenant-hosted media path (e.g.,
/media/uploads/<asset-name>.webpor client media CDN domain).
4.3 Verify Zero External Hotlinks
Re-run the audit to ensure zero external media URLs remain in production code:
# Must return 0 lines matching external unmanaged CDNsgrep -rnE 'https?://(images\.unsplash\.com|cdn\.[a-z0-9]+\.com)' apps/<client-prefix>-<site-slug>/src/Step 5: Cloudflare for Platforms Custom Domain Ingress Connection
Client domain onboarding follows CLOUDFLARE_FOR_PLATFORMS_AND_DOMAIN_INGRESS.md to route traffic to the client’s Worker without requiring domain transfer or nameserver delegation.
flowchart LR ClientDNS["Client DNS Registrar\n(GoDaddy, Route 53, Namecheap)\nCNAME www -> <client>-ingress.<platform-domain>"] --> PlatformZone["SiteSwarm Platform Zone\nCustom Hostname: www.<client-domain>.com\nAuto TLS & DCV Validation"] PlatformZone --> DualRoute{"Dual Worker Route Binding"} DualRoute -- "Grey Cloud / Non-CF" --> CustomRoute["Route: www.<client-domain>.com/*"] DualRoute -- "Orange Cloud / O2O" --> IngressRoute["Route: <client>-ingress.<platform-domain>/*"] CustomRoute --> ClientWorker["Worker A: apps/<client-prefix>-<site-slug>"] IngressRoute --> ClientWorker5.1 Create Dedicated CNAME Target on Platform Zone
On the SiteSwarm platform Cloudflare zone:
- Create a proxied (Orange Cloud)
CNAMErecord:<client-prefix>-<site-slug>-ingress.<platform-domain>$\to$fallback.<platform-domain>
5.2 Register Custom Hostname with Zero-Downtime Pre-Validation
- In SiteSwarm’s Cloudflare Zone under Custom Hostnames, register
www.<client-domain>.com. - Cloudflare outputs a Domain Control Validation (DCV)
TXTrecord (e.g.,_acme-challenge.www.<client-domain>.com). - Have the client add the DCV
TXTrecord at their existing registrar while their old website remains live. - Verify that SSL Status transitions to Active before making any traffic changes.
5.3 Configure Dual Worker Route Binding in wrangler.jsonc
To ensure total immunity against client proxy toggles (Grey Cloud DNS-Only vs. Orange-to-Orange O2O), bind both routes in apps/<client-prefix>-<site-slug>/wrangler.jsonc:
{ "env": { "staging": { "routes": [ { "pattern": "<client-prefix>-<site-slug>-staging.<platform-domain>/*", "zone_name": "<platform-domain>" } ] }, "production": { "routes": [ { "pattern": "www.<client-domain>.com/*", "custom_domain": true }, { "pattern": "<client-prefix>-<site-slug>-ingress.<platform-domain>/*", "zone_name": "<platform-domain>" } ] } }}5.4 Execute Production DNS Cutover
- Have the client point their
wwwrecord as aCNAMEpointing to<client-prefix>-<site-slug>-ingress.<platform-domain>. - For the zone apex (
@/clientbusiness.com):- If using a registrar supporting
ALIAS/ANAMEor CNAME flattening (Cloudflare, Route 53, Porkbun), flatten@to<client-prefix>-<site-slug>-ingress.<platform-domain>. - If on GoDaddy / Namecheap, configure registrar root redirect:
https://clientbusiness.com$\to$https://www.clientbusiness.com.
- If using a registrar supporting
- Verify that the client’s
MX,SPF,DKIM, andDMARCrecords remain untouched at their authoritative DNS registrar.
4. Zero-Lockout Data Portability & Clean Export Guarantee (#207)
SiteSwarm’s core contract is that clients retain 100% ownership of their data. Promoted client tenants must never be locked into proprietary schemas or inseparable multi-tenant databases.
Verification of Data Portability
Upon promotion, verify that the tenant can be cleanly exported under CLIENT_OFFBOARDING.md:
- Atomic D1 SQLite Export:
Verify that a clean, single-tenant SQLite export can be produced without manual data slicing:
Terminal window wrangler d1 export <client-prefix>-<site-slug>-prod-d1 --remote --output=/tmp/<client>-export.sqlhead -n 20 /tmp/<client>-export.sql - Dedicated R2 Media Archive:
Verify that the media bucket can be synced or exported directly:
Terminal window wrangler r2 object list <client-prefix>-<site-slug>-media-prod - Execution Time SLA:
Data export must execute in under 5 minutes with zero schema refactoring. When
pnpm swarm export-client <client-slug>(codified under Issue #204 & #207) is executed, the promoted tenant produces a self-contained archive bundle with SQLite database and all media assets.
5. Quality Verification & Quality Gate Runbook
Before deploying the promoted client tenant to production or closing the promotion milestone, execute the full quality verification suite:
1. Monorepo Architectural Linting (Tier 1 Machine Gate)
# Verify monorepo governance and app typespnpm run checkpnpm run lint:archMust pass with 0 errors.
2. Documentation Taxonomy & Link Linter (Tier 2 Runtime Proof)
npx tsx .agents/skills/docs/scripts/docs.ts auditMust pass with exit code 0 and verify zero broken relative links.
3. Anti-Leakage & Privacy Audit
Ensure no internal platform terminology or prototype lineage artifacts leak into public visitor markup:
grep -riE "SiteSwarm|EmDash|Worker B|apps/proto-|prospect\.json" apps/<client-prefix>-<site-slug>/src/Must return 0 matches in public visitor templates.
4. Mobile Core Web Vitals Audit
Deploy to preview/staging Worker and audit via Lighthouse:
- Performance: $\ge$ 98 (target 100/100)
- Cumulative Layout Shift (CLS): 0.000
- Time to First Byte (TTFB): < 150ms
6. Anti-Patterns & Operational Guardrails
| Anti-Pattern | Severity | Impact | Corrective Mandate |
|---|---|---|---|
Directory Copying (cp -r) |
Critical | Destroys git commit lineage and author attribution. | Always use git mv apps/proto-<slug> apps/<client-prefix>-<site-slug>. |
| Multi-Tenant Database Sharing | Critical | Violates Zero-Lockout Data Portability; exposes cross-client data risks. | Provision dedicated D1 database and R2 bucket per tenant. |
| Unmanaged CDN Hotlinks | High | Subject to external CDN rate limits, 404s, CORS failures, and link rot. | Ingest all media into tenant R2 bucket during Step 4. |
| Single-Route Binding | High | Site breaks if client flips Orange Cloud / Grey Cloud toggle in their DNS. | Always bind dual routes (www.<domain>/* + <client>-ingress.<platform-domain>/*). |
| Nameserver Hijacking | High | Breaks client’s existing corporate email (MX records) during onboarding. |
Use Cloudflare for Platforms Custom Hostnames; never migrate client nameservers. |
| Orphaned Prototype Preview Workers | Medium | Unused Cloudflare Workers accumulating on platform account. | Decommission prototype worker script (wrangler delete proto-<slug>) once production tenant is active. |
🔍 Founder Trimming & Distillation Notes
Notes for distillation into canonical docs/runbooks/PROTOTYPE_TO_CLIENT_PROMOTION.md:
- When the
/prototype promoteautomation recipe (#444) is implemented, the step-by-step CLI commands can be paired with interactive prompts while retaining manual shell fallbacks. - The dual-route binding example is synchronized with
docs/architecture/CLOUDFLARE_FOR_PLATFORMS_AND_DOMAIN_INGRESS.mdand requires no divergent configuration. - Asset ingestion can integrate with the automated media ingestion pipeline (#599).