tenant-management-appbreakdown.mdof m moves 7 → 9Sustentus needs a brand-new, standalone tenant-management app — its own product surface with its own sign-in, never part of the tenant-facing platform. Two audiences use it: invited Sustentus staff, who administer every tenant, and invited ("verified") partners, who administer only the tenants assigned to them — with a reduced power set (view, user management, plan visibility; never suspend, delete, or plan changes). It works directly against the platform's single system of record for tenants, plans, pricing and users rather than keeping its own copy. The first release is bare minimum: sign in, find a tenant, see its detail, run its lifecycle, manage its users, see and change its plan, and read an honest activity trail behind a counts-plus-activity dashboard.
The first rework put partners inside tenant orgs (membership-as-assignment) and retired the
admin role for a metadata flag. The author unwound both the same day: partners never enter
tenant orgs, everything they do goes through this app's server, and the platform's role model is
left exactly as it is.
publicMetadata staff marker — no Sustentus org, so Sustentus never appears as a
tenant of its own platform. Their only surface is this app.user collection.partnerId field on the Tenant document (the Mongo
system of record) holding the assigned partner's Clerk user id. At most one partner per tenant
is the field's cardinality; only this app writes it, so nothing outside the app can silently
change an assignment.admin org role stays, no user migration,
no creator-role change, no new claims, no webhook changes. sales stays a Mongo-only system
role.partnerId. When partners later need to edit more of a
tenant, the relevant admin surfaces are copied into this app and scoped the same way — no
in-tenant access required, and no platform identity change.The batch's original settlement said the activity trail was the record and nobody would be told about anything (Q-12). David has now named the events he wants people told about — a tenant created, a plan changed, a tenant retired, a tenant's payment broken — asked for a notification centre in the app, asked that email reach the tenant's own admins and the assigned partner, and asked that changes be audited. The audit he asks for is the trail stub 7 already builds, so no new audit work follows from it [Q-13]. Two of his four events — retirement-by-trial-lapse and broken payment — can't fire until the business tracks trial expiry and payment state, so this round covers only the events the app itself causes [Q-15]. Recipients are all three audiences from the start, each with its own event set, and nobody configures what they get [Q-14, Q-16].
That cuts cleanly in two: being told inside the app, and being told outside it. The centre comes first because it establishes which events are notifiable and who is in scope for each; email is a second delivery of the same events to an audience that will never open the app.
Beside the service journey rather than on it — this is the operations surface for the tenant provisioning and onboarding step (a partner provisions the vendor; Sustentus oversees all tenants). Feature-role-matrix entity: vendors/tenants; personas Admin and the partner-programme Partner actor.
partnerId field — depends-on: tenant-directoryadmin role included — depends-on: tenant-directoryAfter tenant-directory (2), stubs 3, 5 and 6 are independent of one another; 4 needs 3 (auto-assignment on partner-created tenants); 7 comes after them all so it records the actions the other stubs introduce. 8 and 9 are a strict chain on 7 — the trail is the event record the centre reads, and email delivers the same events the centre establishes as notifiable, to a further audience. The linear order above is foundation-first, then impact.
admin role for an orthogonal privilege flag) —
dropped from this batch 2026-08-26; if wanted on its own merits it is its own future scope._done/partner-assignment.mdPartner scoping is the heart of the app — "partners administer tenants assigned/referred to them" — but nothing yet records which tenant belongs to which partner, so a partner's view has nothing to scope by.
The assignment is a field, not a membership (settled 2026-08-26): a partnerId on the Tenant document holding the assigned partner's Clerk user id. Sustentus staff assign by setting it, re-assign by replacing it, and remove it by clearing it — all from the tenant detail view. At most one partner per tenant is the field's cardinality, and a tenant may have none. The assignment is visible on the tenant detail view, and a partner's directory and detail scope follows the field immediately — gaining it makes the tenant appear for that partner, losing it makes it disappear. Only this app writes the field, so an assignment can never change behind Sustentus's back.
_done/tenant-activity-dashboard.mdTenants are being created, suspended, deleted and re-planned with no record of who did what, when — and the app opens onto a placeholder instead of a picture of the estate.
Every administrative action taken in the app — lifecycle, assignment, user management, plan movement — is recorded as who did what, to which tenant, when. The record is visible as an activity trail: Sustentus staff see it across all tenants, a partner sees only the trail of their assigned tenants. The placeholder home becomes the landing dashboard: total tenants, active tenants, and the recent-activity feed reading from the trail — scoped the same way.
partnerId is theirs._done/tenant-app-access.mdSustentus staff and partners have no surface of their own for overseeing tenants — tenant administration is trapped inside the tenant-facing platform, and partners have nowhere to work at all, which blocks the partner-led onboarding motion Objective 1 depends on. Neither actor even has an identity home yet: nothing in the platform's Clerk instance distinguishes a Sustentus staff member or a partner from any other user.
Stand up the tenant-management app as its own product surface, separate from the main platform, signing in against the same Clerk instance the platform uses — no second user pool. Both audiences are org-less users distinguished by user-level publicMetadata markers: a staff marker for invited Sustentus people (full administration over all tenants) and a partner marker for the one named person invited per partner firm. Neither belongs to any Clerk organization, so Sustentus never appears as a tenant of its own platform and partners never enter tenant orgs. Access is by invitation only: a Sustentus person invites named staff and named partners; nobody self-registers; a signed-in user carrying neither marker gets nothing. The platform app learns one small thing: an org-less session carrying either marker is redirected here instead of today's no-org dead end. Signed-in users land on a placeholder home that states their audience (Sustentus or partner) until the dashboard arrives.
admin role included.publicMetadata markers; who may set them (bootstrap: manually in the Clerk dashboard; thereafter: the invite flow in this app) is Define's to pin down.user collection; the app authorizes on markers alone (plus, from partner-assignment onward, the tenant's partnerId)._done/tenant-directory.mdOnce inside the app there is nothing to look at: nobody can find a tenant or see its state, so no administration can happen.
A searchable tenant directory and a tenant detail view, both scoped by audience. Sustentus staff see every tenant; a partner sees only the tenants assigned to them. The directory lists each tenant with its name and status and is searchable by name; the detail view shows the tenant's status, its plan and subscription status, and its users — read-only in this stub, with the actions arriving in later stubs.
partnerId equals their Clerk user id (settled 2026-08-26) — a plain Mongo filter on the Tenant collection; partner-assignment owns the field and its writes, and until it lands every partner's directory is legitimately empty._done/tenant-lifecycle.mdThe directory shows tenants but nobody can make one, pause one, or end one — the provisioning motion (a partner provisions their client's tenant; Sustentus oversees the estate) has no home.
Tenant lifecycle actions, split by audience. Both audiences can create a tenant; a tenant a partner creates is automatically assigned to that partner. Sustentus staff can suspend and reactivate a tenant, and delete one in two steps: a deleted tenant is deactivated and recoverable for 30 days, then permanently removed — permanent removal is Sustentus-only and requires a typed confirmation. Partners can never suspend, delete, or recover a tenant.
partnerId to the creating partner, through partner-assignment's shared write (settled 2026-08-26).admin role, exactly as tenants get their admin today. Define owns whether creating a tenant requires naming that first user or can leave the org empty._done/tenant-notification-centre.mdThe app records what happened but tells nobody. A partner learns their tenant moved plan only by going to look; a Sustentus person watching a growing estate has to scan a feed to notice a tenant was created or suspended. The trail is a place you go, not a thing that reaches you.
A notification centre inside the app. The administrative events the app itself causes — a tenant created, its plan changed, its users' roles changed, and its lifecycle moving (suspended, reactivated, deleted, restored, permanently removed) — reach the people who should know, in a centre they can open from anywhere in the app with an unread count on it.
Who is told follows the scope each audience already has: a Sustentus person is told about every tenant; a partner is told only about the tenants assigned to them, and never about an event they have no power over. Somebody is not notified of their own action. Each notification names what happened, which tenant it happened to, who did it, and when, and takes the reader to that tenant. A person can mark one read, or mark all read; nothing is ever deleted, because the underlying record is the audit trail.
The events are the trail's events — one record, two views. This feature adds being told; it does not add a second log [Q-13].
partnerId is theirs. Re-assignment changes what a partner may see going forward; the acceptance criterion above settles that it also hides prior events._done/tenant-notification-email.mdThe people most affected by a tenant event are the ones who never see the centre. A tenant's own admins do not sign into this app and never will — yet their plan changing or their workspace being suspended is something they must be told about. A partner may go days without opening the app while a tenant of theirs is provisioned or moved.
The same events that raise a notification also send email, to three audiences, each with its own event set and its own wording [Q-14]:
Nobody is emailed about their own action, and a tenant with no partner assigned simply has no partner recipient. Every recipient in an audience gets that audience's full set — there is nothing to configure [Q-16].
Email that cannot be delivered must be visible as a failure rather than disappearing: a send that fails is recorded against the event, so somebody can see that a tenant admin was never actually told.
_done/tenant-plan-visibility.mdThe commercial state of a tenant — which plan, how many seats, whether the subscription is healthy — is invisible in the app, so the team can't answer the most basic account question and can't move a tenant that has outgrown its plan.
Each tenant's detail view carries its commercial state read from the existing subscription record: plan name, seat counts, and subscription status. Sustentus staff can move a tenant between the plans that already exist. Partners see the same plan, seats and status on their assigned tenants but can change nothing, and never see money amounts, invoices, or payment history.
_done/tenant-user-management.mdA tenant's users can be seen but not touched — yet user management is the one administrative power partners were explicitly granted, and the commonest reason either audience opens a tenant at all.
From a tenant's detail view, both audiences — Sustentus staff on any tenant, a partner on their assigned tenants — can manage that tenant's users: invite a new user, remove a user, and change a user's role. The assignable roles are the existing in-tenant Clerk roles — admin, customer, expert, vendor, csm, sdm — so granting or revoking tenant administration is just assigning or unassigning the admin role, exactly as the platform already understands it. Changes take effect in the tenant's own platform experience, since the app administers the same single record of users the platform runs on.
admin role can be granted and revoked like any other, and takes effect in the tenant's platform session.admin-role user through this app.sales — it stays a Mongo-only system role, never assigned here.