A new tenant went from zero to a live managed subdomain without a manual infrastructure step
Sixteen checks passed. Tenant “Real Success Academy” (slug rsa) was created through the platform API, its managed
subdomain rsa.beta.skillpixel.vn was provisioned by the
GitHub Actions workflow
dispatched from the backend, and the host served HTTPS at the end of the run. No findings were filed.
Three items were not exercisable on beta and are listed under
Not verified on beta — chiefly custom-domain
activation itself, which is deliberately not supported on beta.
What was verified
One row per ticket. The custom-domain tickets are covered by tests only on beta — see Not verified on beta.
| Ticket | Scope | Result | Evidence |
|---|---|---|---|
| SP-617 | Domain ownership verification via our own TXT challenge (PR #316) | TESTS ONLY | Section 5 |
| SP-618 | Firebase authorized domains and the 75/90 cap (PR #317) | PASS | Check 11 |
| SP-619 | Cloudflare custom hostnames and canonical-host rules (PR #318) | PARTIAL | Check 16 |
| SP-477 | Custom-domain activation behind a readiness checklist (PR #319) | TESTS ONLY | Section 5 |
| SP-633 | Managed-subdomain provisioning workflow (PR #329) | PASS | Section 3 |
| SP-634 | Dispatch and status from platform-admin as a GitHub App (PR #332) | PASS | Checks 5–6 |
| SP-636 | Managed-subdomain zone enforcement (PR #342) | PASS | Checks 1, 3 |
Checks
Executed in order against the beta backend, which was running the head of main (bebb0a83). Tenant id cfd2ab30-5ca9-480d-9f41-74bdeef568f5, domain id 6e292fcc-8c7b-48f7-9787-de41947b64c3.
| # | Step | Call | Result | Evidence |
|---|---|---|---|---|
| 1 | Managed zone is fixed per environment | GET /platform/domains/managed-zone |
PASS | 200, {"zone":"beta.skillpixel.vn","label_pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"} |
| 2 | Create the tenant | POST /platform/tenants |
PASS | 201, “Real Success Academy”, slug rsa |
| 3 | A managed subdomain outside the zone is rejected | POST .../domains with rsa.skillpixel.vn |
PASS | 422 “Managed subdomains must be a single label under beta.skillpixel.vn, e.g. acme.beta.skillpixel.vn” |
| 4 | Add the in-zone managed subdomain | POST .../domains with rsa.beta.skillpixel.vn |
PASS | 201, primary |
| 5 | Dispatch provisioning as the GitHub App | POST .../domains/{id}/provision {action: add} |
PASS | 202, run_name “Provision rsa.beta.skillpixel.vn (add, beta)” |
| 6 | Status endpoint finds the run by its run name | GET .../provisioning-status |
PASS | no run yet → in_progress → completed/success, with the run URL |
| 7 | Workflow run completes with every step green | GitHub Actions run 33108126792 |
PASS | success in about 2 minutes — see Section 3 |
| 8 | Activate the domain | PATCH .../domains/{id} ACTIVE |
PASS | 200, status ACTIVE, verified_at 2026-08-27T19:23:55Z, no activation_warning |
| 9 | Add the tenant admin | POST .../memberships |
PASS | 201, skillpixeldeveloper@gmail.com with TENANT_ADMIN |
| 10 | Activate the tenant (gate passes with a domain and an admin present) | PATCH /platform/tenants/{id} ACTIVE |
PASS | 200, status ACTIVE |
| 11 | Firebase authorized domain was added by the workflow | Identity Toolkit config, project skill-pixel-beta |
PASS | rsa.beta.skillpixel.vn present in the authorized-domain list |
| 12 | DigitalOcean app domain reaches phase ACTIVE | DO app lms-beta-frontend domain list |
PASS | ALIAS domain phase ACTIVE, certificate issued |
| 13 | Cloudflare record is a DNS-only CNAME to the DO app | dig rsa.beta.skillpixel.vn |
PASS | CNAME lms-beta-frontend-8n8v8.ondigitalocean.app |
| 14 | TLS certificate matches the host | Certificate presented on the TLS handshake | PASS | CN rsa.beta.skillpixel.vn, Google Trust Services WE1, issued via DigitalOcean |
| 15 | The host serves the app over HTTPS | GET https://rsa.beta.skillpixel.vn |
PASS | HTTP 200 |
| 16 | Bootstrap resolves the tenant by host and reports the canonical URL | GET /bootstrap with X-Tenant-Host |
PASS | slug rsa, canonical_url https://rsa.beta.skillpixel.vn, canonical_redirect false |
The provisioning run
One workflow_dispatch of provision-tenant-domain.yml, dispatched by the backend with a one-hour GitHub App installation token.
Run 33108126792 — success in about two minutes
- Validate inputs. Hostname shape and environment accepted.
- Install tooling.
doctlandyqavailable. - Resolve the app. DigitalOcean app
lms-beta-frontendlocated for the beta environment. - Upsert the Cloudflare record. DNS-only CNAME to the app's
ondigitalocean.apphost (runbook §5). - Add the DigitalOcean domain. ALIAS domain appended to the app spec (runbook §4).
- Wait for the domain and certificate. Polled until the domain reached phase ACTIVE with a certificate.
- Add the Firebase authorized domain. Identity Toolkit config updated for
skill-pixel-beta(runbook §7). - Summary. Step summary printed with the resulting hostname and app.
The workflow is idempotent: re-running add for an existing hostname is a no-op on each of the three systems.
The platform-admin domain row and the activation click stay manual by design.
Not verified on beta
Three items could not be exercised on this environment. They are covered by unit and end-to-end tests and are not counted as failures.
Custom-domain activation with real Cloudflare and Firebase
Custom domains are intentionally not supported on beta. The split-host beta setup cannot serve a customer-owned domain end to end, so the Cloudflare environment is left unset there and activation is blocked with a clear 503 instead of a silent skip. TXT ownership verification still works on beta, since it only needs a DNS lookup.
Covered by unit tests for the TXT challenge, the Firebase client and cap, the Cloudflare custom-hostname client and the canonical-host rules, plus the SP-477 end-to-end test over the readiness checklist. First real exercise happens on production once the production prerequisites on the roadmap are in place.
The remove workflow path
Only action: add was run. The remove path (delete the Cloudflare record, the DigitalOcean domain and the Firebase
authorized domain) is best-effort by design — each step tolerates an already-absent resource — and was not dispatched here.
Production
Nothing in Phase 4 is in a production release yet; production is still on v0.0.39. The workflow's prod environment path,
the skillpixel.vn managed zone and the production GitHub App secrets have not been exercised.
Test residue and cleanup
State left on beta, recorded so a later reader does not mistake it for real data.
The rsa tenant and its subdomain were left live
- Tenant. “Real Success Academy” (
rsa,cfd2ab30-5ca9-480d-9f41-74bdeef568f5) ACTIVE, withskillpixeldeveloper@gmail.comas tenant admin. Empty catalogue. - Domain.
rsa.beta.skillpixel.vnACTIVE and verified, primary. Cloudflare CNAME, DigitalOcean ALIAS domain and Firebase authorized domain all left in place — this is the second live beta tenant host and is useful as a standing smoke target. - Removal. To tear it down, dispatch the same workflow with
action: removefrom platform-admin, then delete the domain row and the tenant.
Method
Checks were API-driven: curl against the beta Cloud Run API with a Firebase ID token minted for the beta platform-admin account.
DNS was checked with dig, the certificate was read from the TLS handshake, and the GitHub Actions run was inspected through the REST API.
No browser session and no screenshots — every observable in this report is an HTTP response, a DNS answer or a certificate.
Verified by Claude (Fable 5) on 2026-08-27.