# Phase 19 — AuthBlocks User Management in the CMS Status: proposed (rev. 2 — scope expanded by Daniel 2026-06-19). Author: product-designer. Date: 2026-06-19. Implementer: TBD (separate delegation). Wire the AuthBlocks user-administration surface (create users, manage existing accounts, manage registration invites, manage role permissions) into the `DeepDrftManager` CMS so an admin can run account management from inside the same authenticated CMS they already use — **and** stand up the public-facing **self-service registration** form on the `DeepDrftPublic` site so an invited user can redeem a registration code and create their own account. Daniel's framing: *"this is already part of the AuthBlocks library so we just need to wire it up properly."* **That framing is correct for the CMS surface — and the wiring there is further along than it implies.** Almost the entire CMS-side integration already landed as a side-effect of the prior AuthBlocks startup separation (`PLAN_authblocks_trackmanager.md`, landed 2026-05-25) and the login/logout integration; what remains there is a thin **navigation + verification + polish** slice, not an integration project. **Scope expansion (Daniel, 2026-06-19).** The original (rev. 1) spec deferred public self-service registration entirely and treated "create user" as a single CMS path. Daniel reversed both: he wants **all three account-creation paths** wired, with each placed on its correct host. Two of the three are CMS-side (and ride the already-done CMS wiring above); the third is **public-facing** and requires a genuine cold-start AuthBlocks integration on `DeepDrftPublic`, which today has **no AuthBlocks reference at all**. The public-registration work is therefore a **distinct track** with its own host, routing, and layout considerations — not part of the CMS nav slice. The spec below separates *what is already done* from *the genuine remaining work*, and separates the **CMS track** (waves 19.1–19.3, the original slice) from the new **public-site track** (wave 19.4). --- ## 0. The three account-creation paths (verified against AuthBlocks source) Daniel asked for the registration model to be double-checked against `C:\Development\AuthBlocks`. Verified — his three-path understanding is **correct and complete**. The model: | # | Path | Component(s) | Route | Host | Backed by | Email? | |---|------|--------------|-------|------|-----------|--------| | 1 | **Admin provisions a user directly** (bypasses email/code loop) | `SuperRegister.razor` | `/account/superregister` | **CMS** | `POST api/auth/admin-register` (UserAdmin-gated) — **working** | No | | 2 | **Public self-service** — invited user redeems a code and self-registers | `Register.razor` | `/account/register` | **PUBLIC SITE** | `POST api/auth/register` (unauthenticated) — **working** | No (consumes code) | | 3 | **Admin provisions a registration token + triggers the invite email** | `NewRegistration.razor` → `NewRegistrationForm.razor` | `/useradmin/registrations/new` | **CMS** | `POST api/pendingregistration/create` (UserAdmin-gated) — **working, sends email server-side** | **Yes — real, not stubbed** | **Path 3's email is real.** This is the headline correction to rev. 1, which worried the token-provisioning path might be stubbed like Reset Password. It is not. `PendingRegistrationRoutes.Create` (`AuthBlocksLib/Routes/PendingRegistrationRoutes.cs:62`) generates a token, persists the pending registration, builds the invite link (`{ReturnHost}?UserEmail=&RegistrationToken=`), renders `RegistrationEmailTemplate.Create(...)`, and **sends it via `IGeneralEmailSender.SendEmailAsync`** — a Mailtrap-backed `MailtrapEmailSender` registered in `AuthBlocksExtensions` (line 109) and configured in **DeepDrftAPI** from `environment/authblocks.json` (`AuthBlocks:Email:Host` / `:Token`, `Program.cs:106–109`; `ApplicationName="DeepDrft"`, `SupportEmail` from config). On email-send failure the route **rolls back** the pending-registration row and returns 500. So the full invite→email→redeem loop is functional end-to-end across paths 2 and 3: an admin provisions (path 3) → the prospective user receives an email with a code + link → they land on the public `/account/register` form (path 2) with email + token pre-filled from the query string → they set a password and the account is created. **The one genuinely stubbed surface is Reset Password** — `Users.razor:55` (`// todo integrate with email for secure reset`) has an empty handler and **no backing API endpoint exists** (`AuthRoutes` maps login/register/admin-register/refresh/logout/me/roles — no reset route). That is the subject of the separate `authblocks-password-reset-brief.md`; it must **not** be filed as a DeepDrft bug. **Two distinct admin "create" verbs — both stay, they are not duplicates.** `SuperRegister` (path 1) creates a *live account immediately* with a password the admin sets. The registration-token form (path 3) creates a *pending invite* — no account yet — and lets the user set their own password via email. They serve different needs (provision-now vs. invite-by-email); both belong in the CMS nav. (Note the older rev. 1 "canonical create-user" question conflated `SuperRegister` with `NewUser` at `/useradmin/users/new` — that `NewUser` `ModelView` create form still exists as a third bare admin create path, but it is **not** one of Daniel's three; treat it as redundant with `SuperRegister` and do not surface it in nav. See OQ2.) --- ## 1. What AuthBlocks ships, and how it is packaged Read from local source at `C:\Development\AuthBlocks` (not public docs). The key question the brief raised — *is the user-admin surface consumable or host-bound?* — resolves cleanly: **it is consumable.** ### The user-admin surface is a published RCL, despite the "Web" name `AuthBlocksWeb` is an `Microsoft.NET.Sdk.Razor` project (not `Sdk.Web`) with **no `Program.cs`** — it is a Razor Class Library, not a runnable host. `pack.ps1` packs it as **`Cerebellum.AuthBlocks.Web`** and pushes it to nuget.org alongside the other four packages. So the user-admin Razor components are distributed as a normal RCL and are consumed by reference, exactly like any MudBlazor-based component package. **No extraction fork is needed** — the architectural risk the brief flagged ("if the pages are host-bound and need extracting into an RCL") does not materialize. The pages are already in the RCL. ### What's in the package (the consumable surface) Components under `AuthBlocksWeb/Components/`: - **Account pages** (`Pages/Account/`): - `Login`, `Logout`, `AccessDenied`. - `Register.razor` → `/account/register` (**path 2** — public self-service via invite code; `@rendermode InteractiveServer`; reads `UserEmail` + `RegistrationToken` from the query string and pre-fills, so a deep link from the invite email lands ready to submit; calls `AuthStateProvider.RegisterAsync` → `POST api/auth/register`; **no role gate** — it is meant to be reachable by an unauthenticated visitor). - `SuperRegister.razor` → `/account/superregister` (**path 1** — admin creates a live account immediately, with a role multiselect; gated `[HierarchicalRoleAuthorize(UserAdmin)]`; calls `IAuthApiClient.AdminRegisterAsync` → `POST api/auth/admin-register`). - **User admin pages** (`Pages/UserAdmin/`), each `@page`-routed and gated `[HierarchicalRoleAuthorize(SystemRoleConstants.UserAdmin)]`: - `Users/Users.razor` → `/useradmin/users` — searchable user grid; per-row Reset Password (**stubbed — `// todo integrate with email`, no backing endpoint**), Deactivate/Reactivate, edit modal. - `Users/NewUser.razor` → `/useradmin/users/new` — bare create-user form (redundant with `SuperRegister`; not one of Daniel's three paths — do not surface in nav). - `Registrations/Registrations.razor` → `/useradmin/registrations` — pending-invite grid (email, consumed?, dates), with `NewRegistration.razor` → `/useradmin/registrations/new` (**path 3** — `NewRegistrationForm` posts to `PendingRegistrationClient.CreatePendingRegistration` → `POST api/pendingregistration/create`, which mints the token **and sends the invite email**) and the edit-registration modal. - `Permissions/Permissions.razor` → `/useradmin/permissions` — user↔role assignment. - **Menu fragments** (`Components/Layout/`): `AccountNavMenu`, `UserAdminMenu` (a `MudNavGroup` with the three user-admin `MudNavLink`s, itself wrapped in a `HierarchicalRoleAuthorizeView` so it only renders for `UserAdmin`+). - **Shared** (`Components/Shared/`): `LogoutButton`, `StatusMessage`. - **DI entry point** (`Startup.cs`): `ConfigureAuthServices(IServiceCollection, string apiBaseUrl)` registers the cascading auth state, the JWT client stack, **and every user-admin client + ViewModel** (`UsersClient`/`UsersViewModel`, `RoleClient`, `UserRolesClient`/`PermissionsViewModel`, `PendingRegistrationClient`/`RegistrationsViewModel`), all pointed at `apiBaseUrl`. The pages lean on `Cerebellum.BlazorBlocks.Web` for their grid scaffolding (`ModelView`, `ModelPageViewModel`, `ConfirmCancelModal`) and MudBlazor for chrome — both already present in the CMS. ### The API side is already hosted The clients those ViewModels use call the AuthBlocks **API** surface, which `DeepDrftAPI` already mounts via `app.MapAuthBlocks()` (`Program.cs:184`): `api/auth/*` (incl. `admin-register`, gated `UserAdmin`; and `roles`), `api/users/*`, `api/roles/*`, `api/user-roles/*`, `api/pending-registration/*`. `AddAuthBlocks` + `UseAuthBlocksStartupAsync` (migrate + seed) are wired, and the Auth DB + secrets live in `DeepDrftAPI/environment/`. This all landed with the startup separation. --- ## 2. What is ALREADY wired in DeepDrftManager (do not redo) Verified against the current `DeepDrftManager` source. These are the integration steps a naive plan would propose — and they are **already done**: 1. **Package reference.** `DeepDrftManager.csproj:11` references `Cerebellum.AuthBlocks.Web` (10.3.33), which transitively brings `AuthBlocksWeb.Client`, `AuthBlocksLib`, `AuthBlocksModels`. 2. **Service wiring.** `Program.cs:35` calls `AuthBlocksWeb.Startup.ConfigureAuthServices(builder.Services, contentApiUrl)` — so the user-admin clients and ViewModels are **already in the container**, already pointed at DeepDrftAPI. 3. **Page discovery.** `Routes.razor:2` sets `AdditionalAssemblies="new[] { typeof(AuthBlocksWeb._Imports).Assembly }"` and `Program.cs:131` mirrors it for endpoint mapping. **The Blazor router already discovers every AuthBlocksWeb page**, including `/useradmin/users`, `/useradmin/registrations`, `/useradmin/permissions`, `/useradmin/users/new`, `/account/superregister`. They are route-reachable *today* by typing the URL. 4. **Default layout.** `Routes.razor:6` sets `DefaultLayout="typeof(Layout.CmsLayout)"`. Since the AuthBlocks pages declare no `@layout`, they **already render inside CmsLayout chrome.** 5. **Role gating already satisfied.** The pages gate on `SystemRoleConstants.UserAdmin`. The DeepDrft admin is seeded in role **`Admin`**, and `SystemRole` (id 1, `Admin`) is the **parent** of `UserAdmin` (id 2) — `Admin.InheritsFrom`/hierarchical authorize means **the existing admin already passes the `UserAdmin` gate** with no role change, no new seed, no DB edit. 6. **Auth-state + redirect plumbing.** `AuthorizeRouteView` with `RedirectToLogin` / `RedirectToAccessDenied` (`Routes.razor`) already protects the surface coherently. **Net:** an authenticated DeepDrft admin can, right now, navigate to `/useradmin/users` and the page should render and call DeepDrftAPI. The reason it *feels* unbuilt is that **nothing in the CMS UI links to these pages** — `CmsLayout` has no nav drawer at all (just an app bar with a Home button), so the surface is invisible and unverified. This is the crux: the CMS-side work is not *integration*, it is *exposure + verification + fit-and-finish*. --- ## 2b. What is NOT wired on DeepDrftPublic (the public-registration track — genuine cold start) The public self-service registration form (path 2) lives in the **same RCL** (`Cerebellum.AuthBlocks.Web`) as the CMS pages. But unlike the CMS, **DeepDrftPublic has no AuthBlocks footprint at all** — verified: - **No package reference.** Neither `DeepDrftPublic.csproj` nor `DeepDrftPublic.Client.csproj` references `Cerebellum.AuthBlocks.Web` (or any AuthBlocks package). - **No service wiring.** `DeepDrftPublic/Program.cs` never calls `ConfigureAuthServices` — the `AuthStateProvider` / JWT client stack that `Register.razor` depends on is absent from the container. - **No page discovery.** No `AdditionalAssemblies` entry for the AuthBlocks RCL, so the router cannot reach `/account/register` even if the package were referenced. So path 2 is a **from-cold integration on the public site**, not a "flip it on" task. The render-mode substrate, at least, is compatible: DeepDrftPublic is already a Blazor Web App with **both** `AddInteractiveServerComponents` + `AddInteractiveWebAssemblyComponents` and the matching render modes (`Program.cs:33–34, 147–148`), so `Register.razor`'s `@rendermode InteractiveServer` is satisfiable without a render-mode change. The public-track integration steps (mirror of §2 items 1–4, but on the public host): 1. **Package reference** — add `Cerebellum.AuthBlocks.Web` to `DeepDrftPublic.csproj` (the host owns the page discovery + DI; the client assembly need not reference it unless a client-rendered surface is wanted — `Register` is `InteractiveServer`, so server-host wiring suffices). 2. **Service wiring** — call `AuthBlocksWeb.Startup.ConfigureAuthServices(builder.Services, contentApiUrl)` in `DeepDrftPublic/Program.cs`, pointed at the same DeepDrftAPI base URL the public site already uses for `api/track/*` (it resolves from `environment/api.json` `Api:ContentApiUrl`). This registers the `AuthStateProvider` and JWT client stack `Register.razor` needs. **Open scope question (OQ6):** this also pulls in the *entire* AuthBlocks client surface (all user-admin clients/VMs) and the cascading auth state — heavier than the public site needs. Acceptable for v1 (it is inert without the gated pages mounted), but worth a conscious "is the public site now auth-aware?" decision (it gains a logged-in concept it did not have). 3. **Page discovery** — add the AuthBlocks `_Imports` assembly to `AdditionalAssemblies` on the public site's router (and mirror for endpoint mapping) so `/account/register` is route-reachable. **This also exposes `/account/login`, `/account/superregister`, and the `/useradmin/*` pages on the public host.** The `/useradmin/*` and `/account/superregister` pages self-gate to `UserAdmin` (a public visitor fails the gate → RedirectToLogin/AccessDenied), so they are not a data-exposure risk, but surfacing admin routes on the public origin at all is a posture choice. **Recommendation:** if the framework supports it cleanly, register only the `Register` page (or scope discovery), or accept the gated routes as present-but-unreachable-in-practice. Flag as OQ7. 4. **Layout** — `Register.razor` declares no `@layout`, so it inherits the public site's `DefaultLayout` (the full public chrome: player bar, nav, footer). Decide whether self-registration should render in the full public layout or a lean auth layout (mirroring the CMS's `CmsHomeLayout` splash idiom). For v1, the public layout is acceptable; a lean layout is polish. Flag as OQ8. 5. **CORS** — DeepDrftAPI's `ContentApiPolicy` must allow the **public site origin** for the `api/auth/*` calls `Register` makes. The public origin is **already** an allowed origin for `api/track/*` (same proxy hop), and the policy is origin-scoped not path-scoped, so this should already be satisfied — but it is a **verification item** (mirror of the CMS's G2 CORS check), not an assumption. 6. **Entry point / link** — once the form is reachable, decide whether/where the public site *links* to it. The invite email's deep link lands directly on `/account/register?UserEmail=&RegistrationToken=`, so the form works without any public nav link (the email is the entry point). A visible "Register" link in the public nav is **optional** and arguably unwanted (registration is invite-only — a public "Register" link invites confusion/abuse since there is no self-serve code issuance). **Recommendation: no public nav link; the email deep link is the sole entry point.** Flag as OQ9. This is why the public track is its **own wave (19.4)**, parallel to but independent of the CMS nav work. --- ## 3. The genuine remaining work ### G1 — Navigation: there is no way to reach the surface from the UI *(the real gap)* `CmsLayout.razor` is an app bar + a single Home `MudIconButton` — **no `MudDrawer`, no nav menu.** The catalogue, releases, upload, and now user-admin surfaces are all reachable only by typed URL or in-page buttons. Mounting `UserAdminMenu` requires a navigation container to mount it *into*. Three shapes, meaningfully different (diverge-before-converge): - **G1-a — Minimal: app-bar overflow menu.** Add a `MudMenu` (or a few `MudIconButton`s) to the existing app bar with links to the three user-admin routes (+ SuperRegister). Smallest change; keeps CmsLayout's current spare aesthetic. *Cost:* doesn't scale — the CMS already has catalogue/releases/upload that arguably belong in a real nav too, and an overflow menu gets crowded. - **G1-b — Recommended: a real `MudDrawer` nav in CmsLayout.** Add a left drawer (toggle in the app bar) holding the existing primary destinations (Catalogue, Releases, Upload) **and** the shipped `UserAdminMenu` fragment (which self-gates to `UserAdmin`+). This is the idiomatic Blazor/MudBlazor CMS shape, it reuses AuthBlocks' own menu component verbatim, and it gives the CMS the navigation spine it's currently missing. *Cost:* slightly larger CmsLayout change; a small visual-design pass on the drawer. - **G1-c — Maximal: dedicated "Administration" section.** A drawer *plus* a distinct admin sub-area (its own landing page summarizing user counts / pending registrations, mirroring the catalogue dashboard idiom). *Cost:* net-new surface (an admin dashboard) beyond what AuthBlocks ships; scope creep for v1. **DECIDED: G1-b (Daniel, 2026-06-19).** A real `MudDrawer` nav in `CmsLayout` mounting `UserAdminMenu` + the existing CMS destinations. It solves the actual gap (no nav) with the least bespoke code, reuses the shipped `UserAdminMenu`, and is the natural home for the CMS's other destinations too. G1-c's admin dashboard remains deferred (good later idea, not a v1 gate); G1-a is the rejected stopgap. > **Borrowed precedent:** this is the standard MudBlazor admin-template layout (persistent left > `MudDrawer` + `MudNavMenu`/`MudNavGroup`), which `UserAdminMenu` is already authored against — it > *is* a `MudNavGroup`. We are adopting the pattern the component was built for, not inventing one. ### G2 — Verification pass (the surface is wired but unproven end-to-end) Because nothing exercised these pages in the CMS, treat first-light as verification, not assumption. Confirm against a running DeepDrftAPI + Auth DB: - `/useradmin/users` lists users (the `UsersClient` → `api/users/*` round-trip works cross-origin / cross-host, with the bearer token the CMS already holds). - `/account/superregister` (**path 1**) creates a live account immediately — `admin-register` is `UserAdmin`-gated server-side and the admin's token must carry the role claim end-to-end. - `/useradmin/registrations/new` (**path 3**) provisions a token **and sends the invite email** — verify the email actually arrives (Mailtrap), the link/code in it are correct, and the rollback fires if the send fails. This is the surface most likely to surface a *config* gap (`AuthBlocks:Email:Host`/`:Token` must be real, not placeholder, in DeepDrftAPI's `environment/authblocks.json`). - `/useradmin/registrations` lists invites; `/useradmin/permissions` reads + assigns roles. - **CORS / token presentation:** the prior plan widened DeepDrftAPI CORS for the Manager origin for login; confirm the *same* allowance covers `api/users/*` / `api/pendingregistration/*` etc. (it should — same origin, same policy). - **Two admin create verbs both stay** — `SuperRegister` (path 1, provision-now) and the registration-token form (path 3, invite-by-email). The bare `NewUser` (`/useradmin/users/new`) is redundant with `SuperRegister` and is **not** surfaced in nav (OQ2). Both nav-surfaced paths are verified. This pass is where any *latent* break surfaces (a client config typo, a missing role claim in the CMS-issued token, a package-version mismatch). It is real work even though no code may change if it all passes. ### G3 — Theming / fit-and-finish The AuthBlocks pages are MudBlazor-default-styled and were authored against AuthBlocks' own theme, not the DeepDrft CMS palette (`DeepDrftPalettes.Cms`, mounted in CmsLayout with `IsDarkMode="false"`). Expect minor visual seams: the AuthBlocks `ThemeColorDemo`/MudBlazor defaults vs. the CMS's DM-Sans / charleston palette. Scope for v1: **accept MudBlazor-default styling inside the CMS palette** (the `MudThemeProvider` in CmsLayout already themes Mud components, so the pages inherit the CMS palette for free) and only fix outright legibility/contrast breaks. A deeper bespoke restyle of the AuthBlocks grids is explicitly **out of v1** — flag as deferred polish. ### G4 — Package version alignment *(housekeeping, flag don't gate)* DeepDrftManager references `Cerebellum.AuthBlocks.Web` **10.3.33**; AuthBlocks source is at **10.3.35**. Minor lag. Bumping to 10.3.35 is low-risk and gets the latest user-admin fixes, but is **not required** for this phase to function. Note it; let Daniel decide whether to bump in this pass or separately. --- ## 4. Scope boundaries **In for v1 (two tracks):** *CMS track (waves 19.1–19.3):* - G1-b: a `MudDrawer` nav in `CmsLayout` mounting `UserAdminMenu` (+ the existing CMS destinations). - All three CMS-side account paths surfaced in nav: path 1 (`SuperRegister`, provision-now) and path 3 (`/useradmin/registrations/new`, invite-by-email), plus the users/permissions grids. - G2: end-to-end verification of list/create/deactivate users, registrations (incl. the **real invite email** send), permissions. - G3: accept-the-palette theming; fix only legibility breaks. *Public-site track (wave 19.4) — the reversed deferral:* - **Path 2 — public self-service registration** (`/account/register`) wired on **DeepDrftPublic**: package reference + `ConfigureAuthServices` + page discovery + layout + CORS verification (§2b). The invite email's deep link is the entry point. **Deferred (note, don't build):** - **Admin dashboard (G1-c)** — a user-admin landing summarizing counts / pending invites. Good later; not a v1 gate. - **Reset Password** — the AuthBlocks `Users` page stubs it (`// todo integrate with email`; **no backing endpoint exists** in `AuthRoutes`). It is an *upstream AuthBlocks* gap, not a DeepDrft wiring task. Daniel is handling it as a **separate AuthBlocks-repo effort** with another team — see the standalone `product-notes/authblocks-password-reset-brief.md`. **Do not implement password reset inside DeepDrftHome.** - **Bespoke restyle** of the AuthBlocks grids to the editorial DeepDrft aesthetic (CMS or public). - A lean public auth layout for `/account/register` (full public chrome is acceptable for v1 — OQ8). - A visible public-nav "Register" link (registration is invite-only; the email deep link suffices — OQ9). - **G4 version bump** — housekeeping, Daniel's call on timing. **Explicitly not needed (the brief's worried-about fork):** - Extracting AuthBlocks pages into a new RCL. They already ship in `Cerebellum.AuthBlocks.Web`. - New DI/service wiring, new routing, new role seeding, new Auth connection string. All present. --- ## 5. Phased breakdown (for clean dispatch) **Two tracks.** The CMS track (19.1–19.3) is the original exposure+verify+polish slice. The public-site track (19.4) is a parallel, independent cold-start integration on a different host. They share only the DeepDrftAPI auth surface (already mounted) and can proceed concurrently. ### CMS track - **19.1 — CmsLayout navigation (cold-start, the only CMS code wave).** Add a `MudDrawer` + toggle to `CmsLayout.razor`; mount the shipped `UserAdminMenu` fragment (self-gates to `UserAdmin`+) and the existing CMS destinations (Catalogue `/catalogue`, Releases `/releases`, Upload `/tracks/upload`). Surface **both** admin account paths: path 1 (`SuperRegister`, `/account/superregister`) and path 3 (`/useradmin/registrations/new`, reachable via the `UserAdminMenu` Registrations link → its New button). Do **not** surface the redundant bare `NewUser` (OQ2). Scope: `CmsLayout.razor` (+ a small `.razor.css` if the drawer needs sizing). **No service, API, data, or AuthBlocks-source change.** - Acceptance: an authenticated `Admin` sees a nav drawer; the User Administration group appears and links to Users / Registrations / Permissions; a "Create user" affordance reaches `SuperRegister`; a non-`UserAdmin` user does not see the group; existing CMS destinations are reachable from the drawer. - **19.2 — End-to-end verification (after 19.1; may surface follow-ups).** Exercise G2 against a running DeepDrftAPI. Confirm list/create/deactivate users, **invite-email send (path 3)**, permission round-trips, and cross-host token + CORS. File any latent break as a follow-up (likely a one-line config fix — esp. the Mailtrap creds — or an upstream AuthBlocks issue). **Mostly test, not code.** - **19.3 — Theming legibility sweep (after 19.1, parallel-ok with 19.2).** Walk each user-admin page in the CMS palette; fix only contrast/legibility breaks. Defer bespoke restyle. ### Public-site track - **19.4 — Public self-service registration on DeepDrftPublic (cold-start, parallel to 19.1).** Wire path 2 per §2b: add the `Cerebellum.AuthBlocks.Web` package reference to `DeepDrftPublic`, call `ConfigureAuthServices` in `Program.cs` pointed at the existing DeepDrftAPI base URL, add page discovery so `/account/register` is reachable, settle the layout (OQ8) and route-exposure posture (OQ7), and verify CORS for the public origin. **This is real host-integration code on the public site** (unlike the CMS, where wiring pre-exists) — scope: `DeepDrftPublic.csproj`, `DeepDrftPublic/Program.cs`, the public router, possibly a lean layout. No AuthBlocks-source change. - Acceptance: an invited user clicking the deep link in their registration email lands on `/account/register` with email + token pre-filled, sets a username/password, and the account is created (the row in `pending_registration` is consumed); the form renders coherently in the chosen public layout; an unauthenticated visitor can reach the form (it is not role-gated). The full path-3→path-2 loop (admin provisions in CMS → email arrives → user redeems on public site) works end-to-end. **Dependency shape:** `19.1 → {19.2, 19.3}` (CMS track); `19.4` is **independent** and parallel to the CMS track (it touches a different host; its only dependency, the DeepDrftAPI `api/auth/register` endpoint, is already live). The full invite→redeem acceptance test for 19.4 benefits from 19.1+19.2 being able to *generate* a real invite, but 19.4 can be built and unit-verified against a token minted directly via the API. Recommended kick-off: 19.1 and 19.4 in parallel. --- ## 6. Open questions for Daniel **Resolved (Daniel, 2026-06-19):** 1. **Nav shape (G1) — DECIDED G1-b.** Real `MudDrawer` nav mounting `UserAdminMenu` + existing CMS destinations. Locked. 2. **Admin create paths — DECIDED: surface path 1 (`SuperRegister`) + path 3 (registration-token form); do NOT surface the bare `NewUser`.** Both of Daniel's two admin paths stay (they are not duplicates — provision-now vs. invite-by-email); the rev-1 "which single canonical create path" question dissolves because there are legitimately two. `NewUser` is redundant with `SuperRegister` and is hidden from nav. 5. **Reset Password — DECIDED: non-functional in v1, handled separately.** Confirmed an upstream AuthBlocks gap (stub + no endpoint), not a DeepDrft bug. Daniel is running it as a separate AuthBlocks-repo effort with another team (see `authblocks-password-reset-brief.md`). The 19.2 verification pass must not file it. **Still open:** 3. **Admin dashboard (G1-c) — defer or include?** **Recommend defer.** Net-new surface beyond what AuthBlocks ships; v1 should expose the working pages, not build a new one. 4. **Package bump (G4) — now or separate?** Bump `Cerebellum.AuthBlocks.Web` 10.3.33 → 10.3.35 in this pass, or leave it? **Recommend leave it** unless 19.2 surfaces a fix that needs it. Note: if 19.4 adds the package to DeepDrftPublic, pin **both** hosts to the same version to avoid a split-version RCL. 6. **Public-site auth footprint (19.4).** Wiring `ConfigureAuthServices` into DeepDrftPublic pulls in the *entire* AuthBlocks client surface + cascading auth state — the public site gains a "logged-in" concept it does not have today. Acceptable for v1 (inert without gated pages mounted), but it is a real posture shift. **Recommend accept it** (it is the supported wiring path; scoping it down is bespoke work for no v1 benefit) — confirm Daniel is comfortable the public site becomes nominally auth-aware. 7. **Public route exposure (19.4).** Adding the AuthBlocks RCL to the public router's `AdditionalAssemblies` exposes not just `/account/register` but also `/account/login`, `/account/superregister`, and the `/useradmin/*` routes on the **public origin** (all self-gating to `UserAdmin`, so not a data leak — but admin routes visible on the public host). **Recommend:** accept them as present-but-gated for v1 (simplest); narrow discovery to just `Register` later if the exposure bothers us. Flag if Daniel wants the narrow path now. 8. **Public-registration layout (19.4).** Render `/account/register` in the **full public chrome** (player bar/nav/footer) or a **lean auth layout** (mirroring the CMS `CmsHomeLayout` splash)? **Recommend full public chrome for v1**, lean layout as polish. 9. **Public "Register" nav link (19.4).** Add a visible Register link to the public nav, or rely solely on the invite email's deep link? **Recommend deep-link-only** — registration is invite-only; a public "Register" link with no self-serve code issuance invites confusion/abuse. Items 6–9 shape the public-site track (19.4). 3, 4 are CMS scope/timing calls. None block 19.1.