docs: add execution checklist to OPERATOR-PLAN
Ten phases (O.0–O.9), each ~one commit, in dependency order. Lets us tick boxes as work lands and surfaces what's blocking what.
This commit is contained in:
@@ -239,3 +239,145 @@ In rough priority order:
|
||||
real investor — design has a placeholder "Read-only" role for Jonas Berg)
|
||||
- White-label of the operator portal (partners get their own portal eventually;
|
||||
Dezky operator never gets white-labeled — it's our internal tool)
|
||||
|
||||
---
|
||||
|
||||
## Execution checklist
|
||||
|
||||
Tick boxes as work lands. Each phase is roughly one commit. Phases must be
|
||||
done in order — earlier ones unblock later ones.
|
||||
|
||||
### O.0 · Prep — service rename
|
||||
|
||||
- [ ] Rename `services/provisioning/` → `services/platform-api/`
|
||||
- [ ] Update `package.json` name → `@dezky/platform-api`
|
||||
- [ ] Update `docker-compose.yml`: container name, service key, network
|
||||
alias, volume names, env var `PROVISIONING_INTERNAL_URL` →
|
||||
`PLATFORM_API_INTERNAL_URL`
|
||||
- [ ] Update portal proxy routes to point at `http://platform-api:3001`
|
||||
- [ ] Verify customer portal `/api/me` still works end-to-end after rename
|
||||
|
||||
### O.1 · Authentik — operator OAuth client
|
||||
|
||||
- [ ] Create `dezky-operator` OAuth provider via Authentik API
|
||||
- [ ] Set redirect URIs to `https://operator.dezky.local/auth/oidc/{callback,logout}`
|
||||
- [ ] Confidential client; persist client_secret to `.env` as
|
||||
`OPERATOR_OIDC_CLIENT_SECRET`
|
||||
- [ ] Create application binding linking the provider to a
|
||||
`dezky-platform-admins`-only authorization flow (only group members can
|
||||
reach the consent screen)
|
||||
- [ ] Configure MFA-required policy on this provider
|
||||
- [ ] Verify via `curl` that the discovery doc resolves at
|
||||
`/application/o/dezky-operator/.well-known/openid-configuration`
|
||||
|
||||
### O.2 · platform-api — multi-audience + Partner CRUD
|
||||
|
||||
- [ ] `JwtAuthGuard`: accept audience list `['dezky-portal', 'dezky-operator']`
|
||||
- [ ] New decorator/guard `@RequiresOperatorAudience()` enforcing
|
||||
`aud === 'dezky-operator' && actor.platformAdmin`
|
||||
- [ ] `schemas/partner.schema.ts` — Partner model (slug, name, domain,
|
||||
status, marginPct, contactInfo, billingInfo)
|
||||
- [ ] `partners/` module: controller + service + DTOs (create / read /
|
||||
update / soft-delete)
|
||||
- [ ] Add `partnerId?: Types.ObjectId` (ref Partner, index) to Tenant schema
|
||||
- [ ] Aggregations: `Partner.customers` (count) and `Partner.mrr` (sum)
|
||||
computed at query time
|
||||
- [ ] Tenant lifecycle endpoints: `POST /tenants/:slug/suspend`,
|
||||
`POST /tenants/:slug/resume`, plan/seat-cap change via existing PATCH
|
||||
- [ ] All operator-only mutations gated by `@RequiresOperatorAudience()`
|
||||
- [ ] Smoke test: `curl` create-partner with a `dezky-operator` token works,
|
||||
same call with a `dezky-portal` token gets 403
|
||||
|
||||
### O.3 · Scaffold `apps/operator/`
|
||||
|
||||
- [ ] `apps/operator/package.json` (Nuxt 3, `nuxt-oidc-auth` beta.11, same
|
||||
deps as portal)
|
||||
- [ ] `nuxt.config.ts` with `oidc` block pointing at `dezky-operator`
|
||||
- [ ] Docker compose service `operator`, with Traefik labels for
|
||||
`operator.dezky.local`, `node_modules` volume, same `NODE_EXTRA_CA_CERTS`
|
||||
mount for mkcert
|
||||
- [ ] Network alias on Traefik: `operator.dezky.local`
|
||||
- [ ] User task: add `operator.dezky.local` to `/etc/hosts`
|
||||
- [ ] Session secrets in `.env`: `NUXT_OIDC_TOKEN_KEY` (base64-32),
|
||||
`NUXT_OIDC_SESSION_SECRET`, `NUXT_OIDC_AUTH_SESSION_SECRET` —
|
||||
**distinct from** the customer portal's secrets
|
||||
- [ ] Verify login: visit `https://operator.dezky.local`, bounce to Authentik,
|
||||
sign in as akadmin, land on a placeholder index page
|
||||
|
||||
### O.4 · Design system + app shell
|
||||
|
||||
- [ ] `assets/styles/tokens.css` — copy with `data-theme="dark"` as default
|
||||
- [ ] `assets/styles/base.css`
|
||||
- [ ] Components: `NodeMark.vue`, `UiIcon.vue` (copy from portal)
|
||||
- [ ] Shared primitives ported from the design: `Card`, `Button`, `Table`,
|
||||
`Badge`, `Mono`, `Eyebrow`, `StatusDot`, `Avatar`, `PageHeader`
|
||||
- [ ] `OpSidebar.vue` — collapsible, badges per nav item
|
||||
- [ ] `OpTopbar.vue` — env badge, ⌘K trigger, on-call pill, bell, avatar
|
||||
- [ ] `app.vue` shell wires sidebar + topbar + `<NuxtPage />`
|
||||
- [ ] Keyboard shortcut: ⌘[ collapses sidebar, ⌘K opens palette
|
||||
|
||||
### O.5 · Tenant management (real backend)
|
||||
|
||||
- [ ] `pages/tenants/index.vue` — list with status/plan/seats/MRR columns,
|
||||
filter by partner and status, search by slug/name
|
||||
- [ ] `pages/tenants/[slug].vue` — detail view with tabs
|
||||
- [ ] Tab: **Overview** — header card, key stats, partner link
|
||||
- [ ] Tab: **Users** — list users via `GET /users?tenantSlug=…`
|
||||
- [ ] Tab: **Resources** — provisioning status per integration
|
||||
(Authentik / Stalwart / OCIS), error messages, "Reconcile" button
|
||||
- [ ] Tab: **Billing** (mock fixtures)
|
||||
- [ ] Tab: **Audit** (mock fixtures)
|
||||
- [ ] Tab: **Support** (mock fixtures)
|
||||
- [ ] Tab: **Danger** — suspend, resume, change plan, soft-delete; real
|
||||
backend calls, confirmation modals
|
||||
|
||||
### O.6 · Partner management (real backend)
|
||||
|
||||
- [ ] `pages/partners/index.vue` — list with name/domain/status/customers/MRR
|
||||
- [ ] `pages/partners/[slug].vue` — detail panel with customers list,
|
||||
MRR breakdown, margin, contact info
|
||||
- [ ] "Create partner" modal — POST /partners
|
||||
- [ ] Attach / detach tenant to partner (PATCH on tenant.partnerId)
|
||||
|
||||
### O.7 · Visual-only screens (mock fixtures)
|
||||
|
||||
- [ ] `data/*.ts` — typed mock fixtures (tenants-extra, partners-extra,
|
||||
services, incident, flags, audit, team)
|
||||
- [ ] `pages/index.vue` — Overview dashboard
|
||||
- [ ] `pages/operator-team.vue` — real backend (Users where
|
||||
`platformAdmin === true`)
|
||||
- [ ] `pages/users.vue` — global users, real read
|
||||
- [ ] `pages/infrastructure.vue` — service health (mock for now;
|
||||
docker health check integration is a follow-up)
|
||||
- [ ] `pages/flags.vue` — feature flags (mock)
|
||||
- [ ] `pages/audit.vue` — global audit (mock)
|
||||
- [ ] `pages/support.vue` — placeholder
|
||||
- [ ] `pages/billing.vue` — placeholder
|
||||
- [ ] `pages/reports.vue` — placeholder
|
||||
- [ ] `pages/settings.vue` — placeholder
|
||||
|
||||
### O.8 · Interactions
|
||||
|
||||
- [ ] `CommandPalette.vue` — ⌘K opens, fuzzy search over tenants + partners
|
||||
+ flags + nav items + actions
|
||||
- [ ] `ImpersonationModal.vue` — visual stub with reason field, Demo-only
|
||||
badge, no-op confirm + toast
|
||||
- [ ] `ImpersonationBanner.vue` — top banner shown when impersonating
|
||||
- [ ] `IncidentModal.vue` — mock incident render
|
||||
- [ ] `TweaksPanel.vue` — theme (light/dark), density (comfy/compact),
|
||||
env (prod/staging/dev cosmetic switch)
|
||||
|
||||
### O.9 · Verification
|
||||
|
||||
- [ ] Sign in to `operator.dezky.local` as akadmin via the new OAuth client
|
||||
- [ ] Confirm JWT audience is `dezky-operator` (decode in DevTools, post
|
||||
response back)
|
||||
- [ ] Create a real Partner via the UI, see it in Mongo
|
||||
- [ ] Attach the `acme` tenant to that partner; verify count goes 0 → 1
|
||||
- [ ] Suspend a tenant from the Danger tab; confirm `status: 'suspended'`
|
||||
in Mongo
|
||||
- [ ] Sign in to `app.dezky.local` simultaneously in another browser
|
||||
profile, confirm the customer portal still works and that customer
|
||||
token's `aud` is `dezky-portal`
|
||||
- [ ] Tick all the relevant follow-up tasks in NEXT-STEPS.md as remaining
|
||||
work, file separate issues if anything was deferred
|
||||
|
||||
Reference in New Issue
Block a user