# Partner demo — 5-minute script

Prove EdgeCMS’s wedge: **edit → publish snapshot → channel promote → rollback** on the same Worker, with Storyblok-class click-to-edit on a real demo route.

Audience: design-partner eng + marketer. No rebuild, no CDN purge dance.

## Staging dry-run (operator, ~90s)

Live staging already has tenant **`partner`** + seeded baseline. Prefer this before a partner call:

```bash
# From edgecms-saas tip
bun run demo:partner:dry-run:staging
# or:
bun scripts/partner-demo-dry-run.ts \
  --base-url https://edgecms-api-staging.cheongnanfeng.workers.dev \
  --website-url https://edgecms-website-staging.cheongnanfeng.workers.dev \
  --tenant partner \
  --email smoke.admin@example.com \
  --password 'SmokePassw0rd!'
# After website deploy with short aliases:
#   … --check-aliases
```

Expect every beat `PASS` (ensure → visual-edit → draft fidelity → promote → zh-CN provenance → locale-readiness → synergy → abac-deny → rollback). Auth requires an `Origin` header matching the API host (the script sets it).

| Surface | URL |
| --- | --- |
| **1. Configure** | https://edgecms.skyvis.ai/demo/ |
| **2. Waiting room** | https://edgecms.skyvis.ai/demo/waiting-room/ |
| **3. Protected site** | https://edgecms.skyvis.ai/demo/site/ |
| Legacy studio URL | `…/api/demo/studio` → 302 to `/demo/` |
| Admin sign-in | https://edgecms-api-staging.cheongnanfeng.workers.dev/sign-in |
| Releases UI | https://edgecms-api-staging.cheongnanfeng.workers.dev/tenants/partner/releases |
| Published page | …/api/tenants/partner/api/demo/pages/home?locale=en |
| Locale provenance | …/demo/pages/home?locale=zh-CN |
| Docs (5 min) | https://edgecms.skyvis.ai/docs/partner-demo-5min/ |
| Docs (promote) | https://edgecms.skyvis.ai/docs/atomic-edge-promote/ |
| Short aliases | `/docs/partner-demo/` → 5 min; `/docs/promote/` → Atomic Edge Promote |

**Product flow (same-site UI on edgecms.skyvis.ai):** Configure brand/locale/channel → join waiting room (`POST …/waiting-room/join`) → gate opens after `admitAt` → admit (`POST …/waiting-room/admit`) → protected site (`/demo/site/`) loads JSON content and edits/promotes in page — **no iframe studio**.

**Protection:** marketing pages never show admin passwords. Waiting ticket is HMAC-scoped `waiting` only; admit upgrades to `visual-edit` + `release` (~15 min) on tenant `partner` / `pages/home`. Content/promote/rollback require the admitted capability.

Inventory: store `docs/partner-staging.md`. Do not quote public SLO numbers; no certification claims.

## Prep (once, ~60s — local)

```bash
cd edgecms-saas
bun install
bun run --filter edgecms-api db:migrate:local
# first-time local DB only — use the prepare-local-dev SUPER_ADMIN_EMAILS default
# so Admin dashboard tenant fleet does not 403 (role=admin ≠ allowlisted super-admin)
bun run db:seed:smoke -- --email smoke.admin@example.com --password 'SmokePassw0rd!' \
  --tenant-slug partner --tenant-name 'Partner Demo'
bun run dev:api    # :8787
bun run dev:admin  # :5173  (prefer explicit; `bun run dev` also runs website :8790)
```

1. Sign in to Admin (`http://localhost:5173`) → open tenant **partner** → **Releases**.
2. Click **Ensure demo content** (idempotent: creates `pages` / `home` + `zh-CN → zh → en` cascade; backfills thin `zh` body when missing).
3. Label a release `baseline`, leave **Promote to published** checked → **Publish snapshot**.

Sanity:

```bash
curl -i "http://localhost:8787/api/tenants/<tenant>/api/demo/pages/home?locale=en"
# expect 200, X-EdgeCMS-Perspective: published, X-EdgeCMS-Release-Id present

curl -i "http://localhost:8787/api/tenants/<tenant>/api/demo/pages/home?locale=zh-CN"
# expect X-Content-Locale-Resolved + X-Content-Locale-Provenance (title→en, body→zh)
```

## Script (≈5 minutes)

| Min | You do | Partner sees |
| --- | --- | --- |
| 0:00 | Open **Published page** from Releases. | Live page from the channel release (immutable KV snapshot). |
| 0:30 | Open **Visual edit host** (same tenant). Click a field (title / hero heading), change copy, blur. | Overlay highlight → status `Saved … → D1 (draft)`. Host loads **draft** perspective with a preview token. |
| 1:30 | Refresh **Published page** (no new snapshot yet). | **Old** copy still live — DB is truth for draft; channel still points at baseline. |
| 2:00 | Releases → label `partner-edit-1` → **Publish snapshot** (promote on). | New release id; channel pointer flips (&lt;~2s). |
| 2:30 | Refresh **Published page**. | New copy live. Note `X-EdgeCMS-Release-Id` changed. |
| 3:00 | Optional: `?locale=zh-CN` on the demo page. | Cascade headers: fallible `title`→`en`, `body`→`zh`; locale-exact `legal`→`zh-CN` (no silent fallback). |
| 3:10 | Optional: `GET …/admin/releases/<id>/locale-readiness` after snapshot. | `ready: true` (or `LOCALE_EXACT_NOT_READY` gaps block promote). |
| 3:15 | **ABAC deny beat** (same sitting): `POST …/admin/releases/demo/abac-deny` (default `locale=fr`). Or open `GET …/admin/releases/demo/synergy` for the composed checklist. | `403 Attribute policy denied`; audit chain / SIEM sink shows `abac.denied` next to `entry.visual_edited` / `release.promoted`. |
| 3:30 | On the **published** channel card → **Rollback**. | Pointer returns to `baseline`; previous snapshot still in KV; audit `release.rolled_back`. |
| 4:00 | Refresh published page. | Baseline copy restored — no rebuild. |
| 4:30 | Close on the line: *“Same Worker, draft and live; promote is a pointer, not a pipeline — and SecOps sees the loop.”* | |

## URLs (tenant-scoped)

```
Admin Releases:  /tenants/<tenant>/releases
Visual host:     /api/tenants/<tenant>/api/demo/host/home?locale=en
Published page:  /api/tenants/<tenant>/api/demo/pages/home?locale=en
Draft overlays:  …/pages/home?visualEdit=1&perspective=draft&previewToken=<token>
                 (host mints token; prefer opening via Visual edit host)
```

## Failure checklist

| Symptom | Fix |
| --- | --- |
| 404 on demo page | **Ensure demo content**, then publish a snapshot. |
| Visual host save fails / CSRF | Stay signed in; host fetches `/api/csrf` and sends `x-csrf-token`. |
| Edits don’t show on published page after save | Expected until **Publish snapshot** + promote. Draft host should show edits immediately. |
| Rollback disabled | Need a `previousReleaseId` — publish at least two promoted releases. |
| Releases UI empty / wrong tenant | Admin calls must use `/admin/releases` (tenant-prefixed), not bare `/api/admin/releases`. |

## Composed synergy path (VE × locale-exact × release × env×release × locale × ABAC)

One partner sitting that multiplies the wedge bets:

```bash
# Checklist + URLs (auth required) — includes locale-exact-readiness beat
curl -s -b <session> "http://localhost:8787/api/tenants/<tenant>/api/admin/releases/demo/synergy"

# After snapshot: locale-exact readiness (promote gate preview)
curl -s -b <session> "http://localhost:8787/api/tenants/<tenant>/api/admin/releases/<releaseId>/locale-readiness"

# ABAC deny → abac.denied on the tenant audit chain (and AUDIT_SIEM_* sink if set)
curl -i -b <session> -X POST "http://localhost:8787/api/tenants/<tenant>/api/admin/releases/demo/abac-deny" \
  -H 'content-type: application/json' -d '{}'
```

Lifecycle audit actions SecOps should see on the chain: `entry.visual_edited`, `release.created`, `release.promoted`, `environment.release_pushed` (when a linked-env push is release-sourced / promoteChannel), `release.rolled_back` (category `content`). Optional: set `ENABLE_RELEASE_SEMANTIC=true` → promote also indexes + emits `release.semantic_indexed`; search via `GET …/admin/releases/:releaseId/semantic-search?q=…` (spike, not GA). Environments carry a channel release across instances — they are not a second publish UX ([atomic-edge-promote.md](./atomic-edge-promote.md#environments--channel-releases)).

The visual-edit host forwards `x-edgecms-locale` + `x-edgecms-channel=published` on PATCH so custom roles with locale conditions can deny real edits; built-in unrestricted roles stay RBAC-only unless you use the **abac-deny** probe.

## Out of scope for this script

Packaged Datadog/Splunk connectors (hookable webhook/NDJSON sink already exists — see [operations-security.md](./operations-security.md)), GA semantic search UX, release history pagination redesign, full invite→SIEM SecOps sitting (use [security-walkthrough-10min.md](./security-walkthrough-10min.md)).

Deep dive: [atomic-edge-promote.md](./atomic-edge-promote.md).
