Design Spec · 2026-05-09 · Draft

Client Website Platform

A productized end-to-end pipeline for generating and maintaining client websites — a HOFMI trainee can run the build, a non-technical client can request changes from inside their own site, and Codesmith handles the work behind a 24-hour internal-review gate.

Authors Warden + Brynn Status Draft Spec v1.1 Updated post-Brynn review
2Sub-products, Two repos
24hStandard SLA
9Sub-WIs to spawn
daysMVP timeline
01 · Context

Context & problem

Why this exists, and the constraint window.

Brynn runs HOFMI ministry development + Transformate consultancy. There is an immediate batch of HOFMI ministry websites to (re)build, plus an external pipeline starting with a Witbank veterinarian. Today, building and maintaining client websites is bespoke work that consumes Brynn personally — every change request goes through him. This doesn't scale to the HOFMI cohort and burns ministry hours that should go elsewhere.

This spec defines a productized client-website platform that:

Hard timeline

MVP must be ready in days, not weeks (HOFMI ministry websites blocked on this). Each ministry site through the pipeline = ~2 days of human-in-loop work; the platform itself unblocks that throughput.

02 · Decomposition

Two sub-products, two repos

Generation and maintenance are different problems with different lifecycles. They share infrastructure but ship as distinct codebases.

Sub-productRepoRole
site-studio v2 brynnclaw/site-studio (existing) Generates new client sites end-to-end. Outputs OD-native HTML+DESIGN.md projects.
site-keeper brynnclaw/site-keeper (NEW) Auth'd client portal. Maintenance flow: comment → Codesmith → review → preview → approve → live.

Orchestrating documentation (this spec, the trainee playbook, runbooks) lives in brynnclaw/website-build-playbook.

03 · Principles

Three locked design principles

Load-bearing values — everything downstream must respect these.

Principle 3.1

Internal review gate — never raw Codesmith to client

Codesmith output never reaches the client without a human pass. Warden / Brynn / Matt / Gian review every preview before it goes to client_review. 24h SLA communicated to clients up-front; predictable timing > variable speed.

  • Trust posture — clients see polish, not work-in-progress
  • Bundling leverage — multiple comments on one page can be combined into one preview branch
  • Service-tier ladder — 24h is the floor; rush-tier (4h) and enterprise (instant) are the upsells
Principle 3.2

Preview branches are immutable from the client side

Comments-on-an-existing-preview = a new change_request row, NOT a free revision. The portal explicitly tells the client "approving the previous preview, opening this as a separate change." Visible in the billing summary too. This is the unit-economics gate — without it, scope mutates mid-execution and the agent eats unbilled compute.

Principle 3.3

Source of truth lives in git per client; the agent is stateless

Per-client GitHub repo (brynnclaw/site-<slug>) holds everything: HTML/CSS, brand kit, CLAUDE.md, AGENTS.md. Codesmith reads the repo + the change_request row. Nothing lives in agent working memory between requests. Portal displays "current state of the PR," not "current state of the agent." If Codesmith is replaced tomorrow by another runtime, every site keeps working without migration.

04 · Locks

Three locked architectural locks

Implementation invariants. Drift here = compounding pain later.

Lock 4.1

Preview deploy mechanism = production deploy mechanism

Same Cloudflare Pages project per client. main branch deploys to production. agent/<change-request-id> branches deploy to preview URLs. Env vars defined identically for preview and production environments from day 1, before client #1. Drift is structurally impossible.

Lock 4.2

Codesmith never invents environment variables

If a change requires a new secret, the state machine pauses with blocked_on_human_secret and emails Brynn. The agent does not create env vars itself; that's a human-only action.

Lock 4.3

OD's comments are mirrored to Supabase, not replaced

Open Design writes comments to its SQLite at /app/.od. A 30-second polling sync mirrors new/changed comments to Supabase Postgres. The 24h SLA absorbs the 30s lag trivially. OD stays unmodified (no fork to maintain) while Supabase remains the source of truth for the business workflow (Realtime, Edge Function triggers, billing meter, audit log).

Lock 4.4

Site-edit worker is stateless and distinct from Codesmith

Codesmith now operates as a persistent agent with its own workspace, memory, and identity (per project_codesmith_persistent_workspace.md, 2026-05-06). Codesmith is reserved for development work — building features, refactoring, multi-session coding tasks where continuity matters.

For per-client website maintenance, the platform spawns a separate, stateless ephemeral agent — working name site-edit-worker (final name TBD). Sonnet 4.6, fresh process per change_request, no /home/ workspace, no MEMORY.md, no conversation history retention. Reads only the change_request row, the per-client GitHub repo, and the element screenshot — nothing else.

The bridge runtime routes by task_type:

task_typeWorkerLifecycle
codesmith_sessionCodesmith (persistent identity, /home/codesmith)Sessions resume across days
site_editsite-edit-worker (ephemeral, stateless)Single-shot per change_request

This separation prevents cross-client information leakage, keeps Codesmith's persistent workspace clean of high-volume routine work, and cleanly enforces Principle 3.3 (agent stateless between requests) for the maintenance flow.

05 · Generation

Sub-product A · site-studio v2

Extends the existing 13-stage pipeline. Biggest shift: Stage 7 retargets output from Astro to OD-native HTML+DESIGN.md projects that Open Design can open and edit directly.

Stage list with phasing

StagePurposePhase
0 — Discovery + briefClient interview, niche identificationMVP
0.5 — Niche researchPain mining, competitor grading, persona genPhase 2
1 — Brand kitGenerate / import DESIGN.md for the clientMVP (manual)
2 — Content acquisitionHarvest existing client content (FB page, old site, photos)Phase 2
3 — Copy extract + rewriteExistingMVP
4 — Image gen (KIE)ExistingMVP
5 — Animation extractionExistingMVP
6 — SEO + structured dataExistingMVP
6.5 — AOESitemap + schema for agent crawlersPhase 3
7 — Project assembly (OD-native)HTML files + DESIGN.md to per-client repo. Was Astro; now OD-native.MVP
8 — OD composition + impeccable auditTrainee opens OD with project pre-loaded; refines interactively. Runs impeccable audit against output before progressing — ban violations block Stage 9.MVP
9 — CF Pages deployCloudflare Pages project per client; custom domain boundMVP
10 — Portal provisioningCreate Supabase row for client; send first-login emailMVP

Trainee entry — Option D (hybrid)

Per-client repo structure

brynnclaw/site-<slug>/
├── README.md          — client-facing handoff doc
├── CLAUDE.md          — agent operating context for Codesmith
├── AGENTS.md          — change-request handling rules for Codesmith
├── DESIGN.md          — brand kit (OD-readable)
├── pages/
│   ├── index.html
│   └── ...
├── assets/            — images, fonts (OD generates / fetches)
├── partials/          — header.html, footer.html (shared, included)
└── deploy/
    └── cloudflare-pages.yaml

main branch = live. agent/<change-request-id> branches = previews. CF Pages auto-deploys both.

06 · Maintenance

Sub-product B · site-keeper portal

Next.js 16 + Self-hosted Supabase. Single public surface (CF tunnel), everything else Tailscale-internal.

Topology · A → C migration path

MVP — topology A
Single OD instance, multi-project

One OD container on HOFMI-TEAM-1 holding all client projects. Auth shell reverse-proxies authenticated user to OD, scoped to their project via path/cookie. Cheap, fast to ship, fits the HOFMI cohort.

Steady state — topology C
Cold-spin per-client containers

Orchestrator spawns ephemeral OD container on client login. Cold-start screen tells client: "Spinning up a full sandbox of your site so your edits never touch live..." — turns the 10-30s wait into a trust-building moment. No data migration (projects are git-backed).

Skipping topology B (always-on per-client) — pays for idle.

Hosts

ComponentHostNotes
site-keeper Next.js shellHOFMI-TEAM-1 (CF tunnel)Only public surface
Self-hosted Supabase (full stack)HOFMI-TEAM-1~3-4GB RAM; sovereignty pattern
Open Design instance(s)HOFMI-TEAM-1 (Tailscale only)Topology A: one container; C: ephemeral
Codesmith bridgeHOFMI-TEAM-1Reuse current bridge runtime
Overflow capacityhofmi-app-1 (100.105.87.117)Currently underutilized; available when load increases

Auth · self-hosted Supabase

GoTrue magic-link primary; OAuth optional. Per-tenant claim in JWT (org_id) used by Postgres RLS to enforce tenant isolation. One-app multi-tenant — clients = organizations in one site-keeper deployment, not subdomain-per-client.

User roles

Edit primitive — element-level via OD adoption

Client clicks any element on their site preview, leaves a comment. OD's edit-mode bridge handles element selection + position + selector + screenshot. We adopted OD's engine; we did not build it.

MVP routing rule: ALL changes route to agent + review. Self-serve direct-edit lane (text/image/link via manualEditPatch) reserved for Phase 2 once the system has earned trust.

Quality control on every edit: the site-edit-worker invokes the impeccable skill against the diff before transitioning to internal_review. New ban violations introduced by an edit must be self-fixed or flagged on escalation. Reviewer sees the impeccable report alongside the preview.

07 · Lifecycle

Change-request state machine

The full end-to-end flow including the 2-min scoping pass and blocked-on-topup gate that protect unit economics.

submitted (client adds comment in OD; OD writes to SQLite) ↓ (sync poller, ~30s) queued (mirrored to Supabase change_requests table) ↓ (Edge Function trigger) scoping (estimator agent runs ~2-min scoping pass; produces time + cost estimate) ↓ scoped (estimate ready) ├─→ if estimate WITHIN client's retainer hours OR credit balance: │ proceed to → processing ├─→ if estimate EXCEEDS available: │ → blocked_on_topup (email client: "needs N hours / $X to proceed; top up to continue") │ when client tops up: → processing │ when client cancels: → cancelled (no charge) └─→ if estimate flags new env-var requirement: → blocked_on_human_secret (email Brynn; pause until secret provided) processing (Codesmith spawned with brief + project ctx; branches client repo; applies patches) ↓ internal_review (Codesmith done; preview deployed to internal-only URL; review queue UI surfaces it) ├─→ approve & send → client_review ├─→ adjust + re-run → processing └─→ reject (Codesmith couldn't deliver) → escalated_to_manual client_review (preview email sent: link + diff + time estimate + cost line item) ├─→ approve (also accepts cost) → approved ├─→ comment back ("not quite right") → NEW change_request, original goes to → done_replaced ├─→ no response 3 days → email reminder (stale_d3) ├─→ no response 7 days → final reminder (stale_d7) └─→ no response 14 days → auto-archive (silence ≠ approval) → stale_archived approved ↓ (auto-merge agent/<id> → main; CF Pages redeploys; bill the client per cost line) live (deployed, billed)

Off-happy-path states

08 · Billing

Billing & pricing model

Two billing modes per client. Estimate shown before approval — the email itself is the cost gate.

Mode 1
Retainer

Client pays a monthly retainer for N hours of platform time. Each change_request deducts its actual time. Hours reset monthly. Email at client_review: "this change took 1.5 hours; deducted from your 10-hour monthly retainer; 6.2 hours remaining this month."

Mode 2
Credit

Client purchases credits up-front (e.g., $50 ≈ 2 hours). Each change_request deducts cost. Credits don't expire. Email at client_review: "this change cost $35; deducted from your $50 credit balance; $15 remaining."

Currency

Estimator (the 2-minute scoping pass)

Produces estimated time (minutes), estimated cost (in client currency), and risk flags ("looks like a structural change," "needs new env var," etc.).

MVP estimator — heuristic table by patch kind

Patch kindEstimated time
Text edit (<200 chars, not H1)5 min
H1 / hero text edit15 min
Link edit5 min
Image swap (alt + src)10 min
Style edit (color, spacing)30 min
Outer HTML / structural60 min
New section90 min
Multi-element / "redesign this page"180+ min, flagged for manual quote

Phase 2: Sonnet pre-classifies the request based on comment text + element + project history.

Pricing tier ladder · Phase 3 wiring

Schema fields exist in MVP; logic wired in Phase 3:

TierSLAReview depthPricing
starter24hFull reviewPay-per-credit
pro24hFull reviewMonthly retainer
rush4hLighter reviewPremium retainer
enterpriseInstantDedicated agent, no reviewTop-tier retainer
09 · Schema

Data model · Supabase Postgres + RLS

Implementer-facing. Skim unless you're writing the migration.

-- Tenancy
CREATE TABLE clients (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  slug text UNIQUE NOT NULL,
  name text NOT NULL,
  domain text,
  currency text NOT NULL CHECK (currency IN ('ZAR','USD')),
  billing_mode text NOT NULL CHECK (billing_mode IN ('retainer','credit')),
  retainer_hours_per_month numeric,
  retainer_used_minutes_this_period int DEFAULT 0,
  retainer_period_resets_at timestamptz,
  credit_balance_minor int DEFAULT 0,
  subscription_tier text DEFAULT 'starter',
  github_repo text NOT NULL,
  od_project_id text,
  created_at timestamptz DEFAULT now()
);

CREATE TABLE users (
  id uuid PRIMARY KEY,
  email text UNIQUE NOT NULL,
  client_id uuid REFERENCES clients(id),
  role text NOT NULL CHECK (role IN ('client','reviewer','operator','admin')),
  created_at timestamptz DEFAULT now()
);

CREATE TYPE change_request_status AS ENUM (
  'submitted','queued','scoping','scoped','blocked_on_topup','blocked_on_human_secret',
  'processing','internal_review','client_review',
  'approved','live','done_replaced',
  'cancelled','failed','escalated_to_manual','stale_archived'
);

CREATE TABLE change_requests (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  client_id uuid NOT NULL REFERENCES clients(id),
  page_path text NOT NULL,
  od_comment_id text,
  od_element_selector text,
  od_element_screenshot_url text,
  client_note text NOT NULL,
  status change_request_status NOT NULL DEFAULT 'submitted',
  estimated_minutes int,
  estimated_cost_minor int,
  actual_minutes int,
  actual_cost_minor int,
  billing_source text,
  estimate_shown_to_client_at timestamptz,
  estimate_approved_by_client_at timestamptz,
  reviewer_user_id uuid REFERENCES users(id),
  reviewer_decision text,
  reviewer_decision_at timestamptz,
  agent_branch text,
  preview_url text,
  pr_url text,
  parent_change_request_id uuid REFERENCES change_requests(id),
  created_at timestamptz DEFAULT now(),
  updated_at timestamptz DEFAULT now()
);
CREATE INDEX ON change_requests (client_id, status, created_at DESC);
CREATE INDEX ON change_requests (status, updated_at DESC);

CREATE TABLE billing_meter (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  client_id uuid NOT NULL REFERENCES clients(id),
  change_request_id uuid REFERENCES change_requests(id),
  minutes int NOT NULL,
  cost_minor int NOT NULL,
  currency text NOT NULL,
  source text NOT NULL,
  occurred_at timestamptz DEFAULT now()
);

CREATE TABLE audit_log (
  id bigserial PRIMARY KEY,
  actor_user_id uuid REFERENCES users(id),
  actor_role text,
  action text NOT NULL,
  target_table text,
  target_id uuid,
  payload jsonb,
  occurred_at timestamptz DEFAULT now()
);

RLS shape: clients see only their own rows via app.current_client_id JWT claim; reviewers see internal_review across clients; operators & admins see all. Production policies will be more granular.

10 · Email

Email cadence

Exactly 4 client-facing triggers + 2 conditional ones. Every email is a tax on attention.

TriggerWhenContent
client_review Preview ready + estimate approved Preview URL · before/after screenshots · time estimate (hours if ≥30 min, else minutes) · cost line · billing source · Approve / Comment buttons
live After client approves; merged + deployed "Your update is live" + link to live URL
stale_d3 client_review unactioned 3 days from email send Gentle reminder + same content as client_review
stale_d7 client_review unactioned 7 days from email send Final reminder + "we'll auto-archive at day 14"
blocked_on_topup Conditional — balance too low "Needs N more hours / $X to proceed"
blocked_on_human_secret Conditional — Brynn-only "Codesmith needs new env var: <name>"

NOT emailed: submitted, queued, scoping, processing, internal_review, rejected_by_internal, failed. The in-portal Realtime indicator handles those.

11 · Open Design

Open Design integration

Three integration points. OD is the editor, deploy mechanism, and (eventually) the per-client container.

  1. OD instance(s) — MVP topology A: one container, multi-project, each client = an OD project keyed by clients.od_project_id. Topology C: ephemeral per-session containers spawned via Coolify. Project files mounted from per-client GitHub repo (clone on container start).
  2. Comment storage sync — OD writes to SQLite via /api/comments. A 30-second polling sync (Node.js) reads new/changed comments → upserts to change_requests in Supabase. The 30s lag is invisible against the 24h SLA.
  3. Source-patches application — Codesmith uses OD's applyManualEditPatch(source, patch) for trivial patches. For structural changes Codesmith does its own editing (branching off the per-client repo) and commits.

OD version pinning

Pin docker image to a specific SHA (docker.io/vanjayak/open-design@sha256:...), not :latest. Per FLEETSEC doctrine: update only after release-notes review + 1-2 week soak. Sentinel watches the OD release feed.

11.5 · Design quality references

Skills + repos the platform consumes (not platform code — inputs).

ReferenceTypeWhere it's used
impeccable (pbakaus skill) Skill Stage 8 OD composition audit (generation) + every site-edit-worker invocation (maintenance). 7 absolute bans + design discipline.
robinstickel/awesome-design-principles (745★) Repo Linked from each per-client CLAUDE.md. Trainees skim relevant principles during Stage 0 brief. site-edit-worker references it for project context. The "design principles GitHub repo Brynn was looking for."
bergside/awesome-design-skills (67 SKILL.md+DESIGN.md files) Repo Library of pre-built design-system SKILL.md files compatible with OD's skills protocol. Mineable for industry-pack design systems in site-studio v2 (Stage 1 brand-kit).
VoltAgent/awesome-design-md Repo 69+ public brand DESIGN.md files (Stripe / Linear / Vercel / Apple). Reference content for Stage 1 brand-kit.
frontend-design (Anthropic skill) Skill Reserved for bespoke track premium client work, not the playbook track.
shadcn/ui Components For the site-keeper portal's own UI (operator/reviewer/client surfaces), not for the generated client sites.

These references live in their upstream repos and are linked from per-client CLAUDE.md files so the site-edit-worker can pull principles into project context on every invocation — without retaining state itself.

12 · Agents

Agent integration

The bridge runtime routes change_request work to a stateless ephemeral agent (site-edit-worker), distinct from the persistent Codesmith dev-work lane. See §4.4 for the lock.

12.1 · Site-edit-worker (the maintenance agent)

Inputs (read-only)

Outputs

Required workflow steps

  1. Read CLAUDE.md + AGENTS.md from the client's repo (project context — voice, brand, gotchas)
  2. Check out main, branch agent/<change-request-id>
  3. Apply the requested edit (using OD's applyManualEditPatch for trivial patches, or direct file edits for structural changes)
  4. Run impeccable audit on the diff — auto-fix catchable violations; flag the rest with impeccable.violations on the change_request row
  5. Commit, push, open PR
  6. Update change_request row, transition status to internal_review

12.2 · Codesmith (the development agent — separate lane)

Codesmith is not used for client-website-platform change_request work. Codesmith retains its existing role: development work on Brynn's platforms (arkon, arkon-os, arkonhelm, etc.) where multi-session continuity, named identity, and persistent workspace are valuable. Per project_codesmith_persistent_workspace.md (2026-05-06): Codesmith has /home/codesmith/, MEMORY.md, synced identity.

Why not unify: cross-tenant leakage risk if one persistent agent handles all clients; Codesmith's workspace would balloon with high-volume routine edits; Principle 3.3 (agent stateless between requests) is cleanly enforced when the maintenance agent literally cannot retain state.

12.3 · Bridge router

Minor extension to dispatch by task_type:

// pseudocode
async function dispatch(taskType: string, payload: any) {
  switch (taskType) {
    case 'codesmith_session':
      return spawnCodesmith(payload); // existing path
    case 'site_edit':
      return spawnSiteEditWorker(payload); // NEW — ephemeral, stateless
    default:
      throw new Error(`unknown task_type: ${taskType}`);
  }
}

spawnSiteEditWorker mints a fresh Claude Agent SDK worker, no /home/ directory, no MEMORY.md, no identity sync. The system prompt is built from §12.1 inputs only; nothing carried over from prior invocations.

12.4 · Self-wake + monitoring

13 · MVP scope

MVP scope · cut to ship in days

What's in, what's deferred. Push back here if anything needs to flip.

In MVP 12 items

  1. site-studio v2 retargeted to OD-native output (Stage 7 swap)
  2. Per-client GitHub repo provisioning (manual create-repo script)
  3. site-keeper Next.js shell + Supabase Auth + reverse-proxy to OD (topology A)
  4. Supabase data model with RLS policies
  5. SQLite → Supabase comment sync (30s polling)
  6. Edge Function trigger to Codesmith on scoped → processing
  7. Heuristic estimator (patch-kind table)
  8. Internal review queue UI in site-keeper
  9. Email pipeline (Resend or Supabase SMTP) with the 4 triggers
  10. Preview deploy via OD's CF Pages integration
  11. Approval → auto-merge + deploy
  12. Matt + Gian provisioned as reviewer + ~30 min training

Phase 2 — next 2-4 weeks 7 items

Phase 3 — post-MVP soak 5 items

14 · Non-goals

Out of scope

15 · Open

Open implementation questions

Defer to writing-plans.

  1. Estimator heuristic table — exact minute values per patch kind (validate against actuals after first 10 change_requests)
  2. Cold-start orchestration — Coolify vs plain compose profiles vs k8s for Topology C
  3. Custom-domain CF Pages binding — automation script vs manual at MVP
  4. Codesmith branch cleanup — auto-delete merged? archive after N days?
  5. Stale change_requests beyond 30 days — purge / archive / retain?
  6. Cross-client analytics for Brynn — top-down view of MRR, retention, volume
  7. HOFMI sub-domain pattern — eu.hofmi.org / na.hofmi.org or per-domain?
  8. Auth.js fallback if Supabase Auth has issues — abstraction layer or accept lock-in?
16 · Risk

Risks & mitigations

RiskMitigation
OD 0.6.0 docker image lands later than expected MVP can ship with 0.5.0; CF Pages deploy stage stubbed manually for first 2-3 sites until 0.6.0
Codesmith time estimates systematically wrong Heuristic for MVP; instrument actual times; tune table after 10 change_requests; Sonnet-estimator in Phase 2
Clients abuse the "comment on preview = new request" gate Make billing line visible BEFORE approval, in the email, in the portal. Document policy in onboarding email. First 5 clients agree explicitly.
Bridge-Warden goes down during peak review Matt + Gian as backup reviewers; 24h SLA absorbs most outages; tightened alert thresholds catch drift faster
Site-edit-worker leaks information across clients Stateless guarantee enforced by spawning fresh worker per request, no /home/ workspace, no memory persistence; bridge router dispatches site_edit separately from Codesmith's codesmith_session; audit log records every read against change_requests to detect anomalies
impeccable skill version drift introduces false ban violations Pin impeccable skill version in each per-client repo's CLAUDE.md; review impeccable releases monthly; Stage 8 trainee audit catches deploy-blockers before client ever sees a violation report
Supabase self-host corruption / data loss Daily Postgres pg_dump → encrypted R2 (extends warden_backups pattern); test restore monthly
OD release introduces a breaking change to comments contract Pin to specific SHA; review release notes 1-2 weeks before bumping; staging container test
Client unhappy after a change goes live One-click git revert rollback in portal; preserves previous live state in <60s
Costs exceed $50/client/mo target Monitor unit economics monthly; first sign of drift = investigate before scaling client count
17 · Helm WI plan

Helm work-item plan

After spec approval, file the umbrella WI under transformate tenant and decompose into 9 implementation sub-WIs in writing-plans phase.

Umbrella WI

Title: Build client-website-platform (site-studio v2 + site-keeper portal)
Status: proposal → approved (after Brynn reads spec)
Tenant: transformate
Description: reference this spec path · MVP-in-days for HOFMI batch · two repos to create + extend · dependencies: self-hosted Supabase, OD 0.6.0+, CF Pages access, Resend/SMTP

Sub-WIs to spawn

  1. site-studio v2 OD-native retarget (Stage 7)
  2. Self-hosted Supabase deployment + schema migration
  3. site-keeper Next.js + Auth + reverse-proxy
  4. SQLite → Supabase comment sync worker
  5. Agent integration — bridge router (task_type dispatch) + ephemeral site-edit-worker (Sonnet 4.6, stateless) + impeccable invocation in workflow
  6. Internal review queue UI
  7. Email pipeline + 4 triggers
  8. CF Pages provisioning per client
  9. Operational runbook + Matt/Gian training
18 · Approval

Approval