Skip to Content

← All archived intake epics

Intake: tenant-management-app

breakdown.md

Breakdown: Tenant management app (Sustentus team + partners)

  • scope-slug: tenant-management-app · story: runs/tenant-management-app/01_scope/_source/story.md
  • initiative: Scale the bridge / objective: Establish product-market fit with vendor partners (2026-Q2, Objective 1)
  • personas: Admin (Sustentus team), Partner — plus Tenant admin as an email recipient only, from the 2026-08-27 extension
  • reworked: 2026-08-26 — identity architecture simplified with the author in-session: the membership-as-assignment design and the admin-role retirement are both unwound; the identity-role-rework stub is dropped and the batch re-cut to 7
  • extended: 2026-08-27 — David's notification story (runs/tenant-management-app/01_scope/_source/story-notifications.md) lifts the Q-12 deferral for the events the app itself causes; two stubs appended at 8–9, built on and after tenant-activity-dashboard. Nothing already cut is renumbered — only of m moves 7 → 9

What I understood

Sustentus 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 identity architecture (settled 2026-08-26, simplified same day)

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.

  • Sustentus staff are org-less users in the platform's one Clerk instance, marked by a user-level publicMetadata staff marker — no Sustentus org, so Sustentus never appears as a tenant of its own platform. Their only surface is this app.
  • Partner is the same shape: an org-less user in the same instance with a user-level partner marker. Not an org role, not a member of any tenant organization, no rows in the platform user collection.
  • Assignment is data, not membership: a 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.
  • The platform's identity model is untouched. The admin org role stays, no user migration, no creator-role change, no new claims, no webhook changes. sales stays a Mongo-only system role.
  • Partners act on tenants exclusively through this app's server (Clerk backend API + the Mongo record), authorized by marker + 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.

What I understood — the 2026-08-27 extension

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.

Where it sits

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.

Build order

  1. tenant-app-access — the standalone app with invitation-only sign-in; staff and partner markers; platform redirect — depends-on: none
  2. tenant-directory — searchable tenant directory + tenant detail view, scoped per audience — depends-on: tenant-app-access
  3. partner-assignment — Sustentus sets/re-assigns/clears a tenant's one partner via the tenant partnerId field — depends-on: tenant-directory
  4. tenant-lifecycle — create, suspend/reactivate, two-step delete with 30-day recovery — depends-on: tenant-directory, partner-assignment
  5. tenant-user-management — invite, remove, change role of a tenant's users, the existing admin role included — depends-on: tenant-directory
  6. tenant-plan-visibility — plan, seats and subscription status on each tenant; Sustentus can move a tenant between existing plans — depends-on: tenant-directory
  7. tenant-activity-dashboard — activity trail of every administrative action + the counts-and-activity landing dashboard — depends-on: tenant-lifecycle
  8. tenant-notification-centre — in-app notification centre telling staff and partners about the events the app causes, built on the trail — depends-on: tenant-activity-dashboard
  9. tenant-notification-email — the same events by email to tenant admins, the assigned partner and Sustentus staff — depends-on: tenant-notification-centre

Parallelizable

After 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.

Out of scope (whole scope)

  • Partner self-serve signup, certification tracks, and tier management.
  • Partner commissions, margins, or revenue-share reporting.
  • Invoicing, payment collection, or refunds through this app.
  • Designing or editing the plan and price catalogue (editions, seat prices, currencies).
  • Expert-marketplace management, lead management, or any in-tenant operational work.
  • Platform-health and uptime reporting beyond the tenant counts on the dashboard.
  • Access for tenants' own users.
  • Notifications on retirement-by-trial-lapse and on a tenant's payment failing — David asked for both; each needs the business to be tracking trial expiry and payment state before anything can fire [Q-15]. When it is, they are two more event types on stubs 8–9's delivery, not a rebuild.
  • Recipient preferences, muting, opt-out, unsubscribe management, and digests [Q-16].
  • A before-and-after change record showing old and new values — the trail is the audit [Q-13].
  • Any notification channel beyond the in-app centre and email — no push, no chat, no webhooks.
  • A partner-firm entity — the person is the partner in v1.
  • Any partner access inside tenant platform surfaces — partner tenant-editing arrives later by copying admin surfaces into this app, not by putting partners in tenant orgs.
  • Staff access inside tenant platform surfaces, including any cross-tenant impersonation.
  • The admin-flag identity rework (retiring the 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.md

Stub: Partner assignment on tenants

  • feature-slug: partner-assignment
  • scope: tenant-management-app
  • personas: Admin (Sustentus team), Partner
  • initiative: Scale the bridge / objective: Establish product-market fit with vendor partners (2026-Q2, Objective 1)
  • depends-on: tenant-directory
  • sequence: 3 of 9

Problem

Partner 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.

Proposed change

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.

Acceptance criteria (rough)

  • A Sustentus person can set, change, or clear the partner on any tenant from its detail view.
  • A tenant can never hold two partners at once — the single field makes double assignment structurally impossible.
  • A newly assigned partner sees the tenant in their directory on their next look; an unassigned partner no longer does.
  • Partners cannot change any assignment, including their own, from this app.
  • Nothing in the tenant-facing platform can see or change the assignment.

Out of scope (this feature)

  • Automatic assignment on tenant creation — that lands with tenant creation itself (tenant-lifecycle).
  • Notifying a partner of an assignment change [Q-12].
  • A partner-firm entity — the assigned person is the partner in v1 (settled 2026-08-26); firm-level assignment is a later round.

Notes for Define

  • One partner at most per tenant, assignment is Sustentus-only, re-assignable at any time [Q-3].
  • The field lives on the Tenant model (packages/services/src/db/models/tenant.ts) — Define owns the field shape, its index (the partner-scoped directory query filters on it), and the migration; no existing tenant needs a value, absence means unassigned.
  • The write should be one shared service function so tenant-lifecycle's auto-assignment path (partner-created tenant → that partner) uses the same code [Q-3, Q-4].
  • Since partners hold no Clerk memberships, this field is the single source of a partner's scope — nothing else to keep in sync, and nothing outside the app (tenant admins included) can touch it.

_done/tenant-activity-dashboard.md

Stub: Activity trail and landing dashboard

  • feature-slug: tenant-activity-dashboard
  • scope: tenant-management-app
  • personas: Admin (Sustentus team), Partner
  • initiative: Scale the bridge / objective: Establish product-market fit with vendor partners (2026-Q2, Objective 1)
  • depends-on: tenant-lifecycle
  • sequence: 7 of 9

Problem

Tenants 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.

Proposed change

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.

Acceptance criteria (rough)

  • Every administrative action in the app lands in the trail with actor, action, tenant, and time.
  • A Sustentus person sees the trail across all tenants; a partner sees only their assigned tenants' entries.
  • The landing dashboard shows total and active tenant counts and the recent-activity feed, scoped per audience.
  • Nobody — Sustentus included — can edit or delete a trail entry.

Out of scope (this feature)

  • A platform-health or uptime figure — waits until it has a real definition [Q-11].
  • Notifications or alerts on any recorded event — this stub builds the record, not the telling. Deferred at Q-12; the deferral was lifted by the 2026-08-27 amendment and the telling is tenant-notification-centre (8) and tenant-notification-email (9), both built on this trail [Q-13].
  • Recording actions taken outside this app — no exceptions needed: partner assignment can only change here (settled 2026-08-26), so every event the trail owes Sustentus originates in the app.

Notes for Define

  • The trail is the record in place of notifications [Q-10, Q-12]; dashboard content is settled as counts + activity only [Q-11].
  • Counts and feed are scoped exactly like the directory [Q-3] — a partner's scope being the tenants whose partnerId is theirs.

_done/tenant-app-access.md

Stub: Standalone tenant app with invitation-only access

  • feature-slug: tenant-app-access
  • scope: tenant-management-app
  • personas: Admin (Sustentus team), Partner
  • initiative: Scale the bridge / objective: Establish product-market fit with vendor partners (2026-Q2, Objective 1)
  • depends-on: none
  • sequence: 1 of 9

Problem

Sustentus 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.

Proposed change

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.

Acceptance criteria (rough)

  • The app is reachable at its own address with its own sign-in against the platform Clerk instance, and no tenant-facing user can get past sign-in into any content.
  • A Sustentus operator can invite a named Sustentus person, who can then sign in and is recognised as Sustentus staff — without belonging to any Clerk organization.
  • A Sustentus operator can invite one named person for a partner firm, who can then sign in and is recognised as that partner — also without belonging to any Clerk organization.
  • Someone who was never invited cannot get in — there is no self-registration path.
  • An invitation can be revoked, and the person can no longer sign in.
  • A staff member or partner who lands on the tenant-facing platform is redirected here, never stranded in a no-org dead end; an org-less user with neither marker gets today's behaviour.
  • The platform's roles, claims, and webhook sync are untouched.

Out of scope (this feature)

  • Any tenant data on screen — directory, detail, lifecycle and plans come in later stubs.
  • Partner tiers, certification, or more than one person per partner firm.
  • Notifications of any kind.
  • Any change to platform org roles, the admin role included.

Notes for Define

  • Access is invitation-only for both audiences, no blanket team access and no self-serve partner signup [Q-1, Q-2].
  • The app stands alone with its own sign-in surface, but the identity behind it is the one platform Clerk instance (settled 2026-08-26) — a second app on the same instance needs Clerk multi-domain (satellite) configuration; Define owns verifying that setup for a separate deployment project.
  • Staff and partner recognition = user-level 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.
  • It reads and administers the platform's existing single system of record for identity and subscriptions — no copy of its own (scope assumption). Staff and partners hold no Clerk memberships and need no rows in the platform user collection; the app authorizes on markers alone (plus, from partner-assignment onward, the tenant's partnerId).
  • touches: a new app in the monorepo plus a new deployment project (the story asks for a separate one); a small apps/web touch for the marked-org-less redirect; Clerk dashboard config (satellite domain, no role changes).

_done/tenant-directory.md

Stub: Tenant directory and detail view

  • feature-slug: tenant-directory
  • scope: tenant-management-app
  • personas: Admin (Sustentus team), Partner
  • initiative: Scale the bridge / objective: Establish product-market fit with vendor partners (2026-Q2, Objective 1)
  • depends-on: tenant-app-access
  • sequence: 2 of 9

Problem

Once inside the app there is nothing to look at: nobody can find a tenant or see its state, so no administration can happen.

Proposed change

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.

Acceptance criteria (rough)

  • A Sustentus person sees all tenants in the directory and can search by name.
  • A partner sees only their assigned tenants — and no trace that others exist.
  • Opening a tenant shows its status, plan and subscription status, and its user list.
  • A partner cannot open a tenant that is not assigned to them, even by address.

Out of scope (this feature)

  • Creating, suspending, deleting or editing anything — read-only.
  • Changing plans or managing users.
  • Money amounts, invoices, or payment history — never shown to partners [Q-9].

Notes for Define

  • Partner scope = assigned tenants only [Q-3]; partner sees plan name, seats and subscription status but no amounts [Q-9].
  • A partner's assigned set is the tenants whose 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.
  • Staff scope = every tenant; staff identity is the org-less staff marker, so "sees all" must not depend on holding any membership.
  • Tenant data is read live from the existing system of record for tenants, plans and users (scope assumption).
  • The directory + detail set is half of the settled "bare minimum" definition [Q-6].

_done/tenant-lifecycle.md

Stub: Tenant lifecycle — create, suspend, delete, recover

  • feature-slug: tenant-lifecycle
  • scope: tenant-management-app
  • personas: Admin (Sustentus team), Partner
  • initiative: Scale the bridge / objective: Establish product-market fit with vendor partners (2026-Q2, Objective 1)
  • depends-on: tenant-directory, partner-assignment
  • sequence: 4 of 9

Problem

The 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.

Proposed change

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.

Acceptance criteria (rough)

  • A Sustentus person can create a tenant; a partner can create a tenant and it is automatically assigned to them.
  • A Sustentus person can suspend and reactivate a tenant, and its status shows accordingly everywhere.
  • Deleting a tenant deactivates it and marks it recoverable; within 30 days a Sustentus person can restore it intact.
  • Permanent removal requires typing a confirmation, is Sustentus-only, and cannot be undone.
  • A partner sees none of the suspend/delete/recover controls on their assigned tenants.

Out of scope (this feature)

  • Plan selection at creation beyond the existing default — plan movement is tenant-plan-visibility's job [Q-8].
  • Notifying anyone of lifecycle events [Q-12].

Notes for Define

  • Lifecycle set and the audience split are settled [Q-4, Q-5, Q-6]; the 30-day two-step deletion and typed confirmation are settled [Q-7].
  • Auto-assignment on partner creation rests on partner-assignment [Q-3] — concretely, creating the tenant also sets its partnerId to the creating partner, through partner-assignment's shared write (settled 2026-08-26).
  • Creation happens server-side in this app, not through Clerk's org-creation flow, and the platform's role model is untouched — the tenant's first user is invited with the existing 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.md

Stub: Notification centre

  • feature-slug: tenant-notification-centre
  • scope: tenant-management-app
  • personas: Admin (Sustentus team), Partner
  • initiative: Scale the bridge / objective: Establish product-market fit with vendor partners (2026-Q2, Objective 1)
  • depends-on: tenant-activity-dashboard
  • sequence: 8 of 9

Problem

The 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.

Proposed change

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].

Acceptance criteria (rough)

  • Creating a tenant, changing its plan, changing a user's role, and every lifecycle move raises a notification to the people in scope for that tenant.
  • A Sustentus person is notified about every tenant; a partner only about tenants assigned to them.
  • Nobody is notified of an action they performed themselves.
  • The centre shows an unread count, and a person can mark one or all read; read state is per person.
  • Opening a notification takes the reader to the tenant it concerns.
  • A notification and its trail entry describe the same event — no event appears in one and not the other, and marking a notification read changes nothing in the trail.
  • A partner never sees a notification for a tenant that is not theirs, including one raised before the tenant was assigned to them.

Out of scope (this feature)

  • Email — that is tenant-notification-email's job, and this feature does not send any.
  • Notification on retirement-by-trial-lapse or on a payment failing [Q-15].
  • Recipient preferences, muting, or opt-out — a fixed event set per audience [Q-16].
  • A before-and-after change record showing old and new values [Q-13].
  • Notifying tenants' own admins — they never sign into this app; email reaches them in the next stub [Q-14].

Notes for Define

  • The audit David asked for is the trail tenant-activity-dashboard already built [Q-13] — build notifications on that record rather than beside it, so the two can never disagree. Define owns whether a notification is a projection of a trail entry or a sibling record keyed to it, but "one event, two views" is the settled property.
  • Scoping is the directory's scoping [Q-3] — a partner's scope is the tenants whose partnerId is theirs. Re-assignment changes what a partner may see going forward; the acceptance criterion above settles that it also hides prior events.
  • Read state is per person, so it cannot live on the shared event record.
  • Event set is settled at the app-caused events only [Q-15]; the two David named that are missing are deferred, not forgotten, and land on this same delivery when subscription state exists.

_done/tenant-notification-email.md

Stub: Email notification to tenant admins, partners and staff

  • feature-slug: tenant-notification-email
  • scope: tenant-management-app
  • personas: Admin (Sustentus team), Partner, Tenant admin (recipient only)
  • initiative: Scale the bridge / objective: Establish product-market fit with vendor partners (2026-Q2, Objective 1)
  • depends-on: tenant-notification-centre
  • sequence: 9 of 9

Problem

The 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.

Proposed change

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]:

  • A tenant's own admins — told about what happened to their workspace: it was created, its plan changed, it was suspended, reactivated, or deleted. Written for someone who does not know this app exists and cannot act on it here; it tells them what changed and who to talk to.
  • The assigned partner — told about the tenants assigned to them, in the language of someone administering on the tenant's behalf, with a way back into the app.
  • Sustentus staff — told about the estate, so the people who run it are not dependent on opening the app.

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.

Acceptance criteria (rough)

  • Each notifiable event sends email to the tenant's own admins, the assigned partner, and Sustentus staff, per that audience's event set.
  • A tenant's admins receive mail written for someone outside this app — no links into it, no internal vocabulary.
  • The assigned partner and Sustentus staff receive mail that links back to the tenant in the app.
  • The person who performed the action is not emailed about it.
  • A tenant with no partner assigned sends no partner mail and raises no error.
  • A failed send is recorded against the event and visible to Sustentus staff — it never fails silently.
  • Email and the in-app centre agree: any event that notifies also emails the audiences configured for it.

Out of scope (this feature)

  • Recipient preferences, muting, per-event opt-out, unsubscribe management, and daily or weekly digests [Q-16].
  • Notification on retirement-by-trial-lapse or on a payment failing [Q-15].
  • Any channel beyond email and the in-app centre — no push, no chat, no webhooks into a tenant's or partner's own systems.
  • Marketing or lifecycle campaigns to tenant admins — these are transactional notices about their workspace, nothing more.
  • Giving tenants' own admins any access to this app; they remain recipients only.

Notes for Define

  • The three-audience recipient model is settled [Q-14]; the per-audience event matrix is the main thing Define pins down, and the sensible default is that tenant admins get their own workspace's events, partners get their assigned tenants' events, and staff get everything.
  • Tenant-admin email is this app's first outbound surface to people outside it — the original scope kept the app non-tenant-facing, and this does not change that: they receive mail, they gain no access [Q-14].
  • Sending must not be able to block or fail an administrative action — a plan change that succeeded must stay succeeded even if its mail does not go out. That is what makes the recorded failure above necessary rather than nice to have.
  • Whether "Sustentus staff" means every marked staff user or a named subset is Define's to settle; Q-1 established that everyone invited holds full administration, which argues for all of them.

_done/tenant-plan-visibility.md

Stub: Plan and subscription visibility, plan movement

  • feature-slug: tenant-plan-visibility
  • scope: tenant-management-app
  • personas: Admin (Sustentus team), Partner
  • initiative: Scale the bridge / objective: Establish product-market fit with vendor partners (2026-Q2, Objective 1)
  • depends-on: tenant-directory
  • sequence: 6 of 9

Problem

The 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.

Proposed change

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.

Acceptance criteria (rough)

  • A tenant's detail view shows its plan name, seat counts, and subscription status, live from the system of record.
  • A Sustentus person can move a tenant to a different existing plan, and the new plan shows immediately.
  • A partner sees plan, seats and status on assigned tenants, has no change control, and sees no amounts, invoices, or payment history anywhere.

Out of scope (this feature)

  • Creating, editing, or pricing plans — the catalogue is a later round [Q-8].
  • Collecting payment, issuing invoices, or refunds.
  • Currencies management (seen in the reference screenshot; deliberately not in v1).

Notes for Define

  • v1 is read-plus-move-between-existing-plans; catalogue management is explicitly deferred [Q-8].
  • Partner money visibility is bounded: plan/seats/status yes, amounts never [Q-9].

_done/tenant-user-management.md

Stub: Tenant user management

  • feature-slug: tenant-user-management
  • scope: tenant-management-app
  • personas: Admin (Sustentus team), Partner
  • initiative: Scale the bridge / objective: Establish product-market fit with vendor partners (2026-Q2, Objective 1)
  • depends-on: tenant-directory
  • sequence: 5 of 9

Problem

A 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.

Proposed change

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.

Acceptance criteria (rough)

  • A Sustentus person can invite, remove, and change the role of a user on any tenant.
  • A partner can do the same on an assigned tenant, and cannot on any other.
  • A change made here is what the tenant's own platform sees — one record, no copy.
  • The admin role can be granted and revoked like any other, and takes effect in the tenant's platform session.
  • A tenant can never lose its last admin-role user through this app.

Out of scope (this feature)

  • Per-user permission exceptions or role-template editing — this is membership and role, nothing finer.
  • Managing the app's own users (Sustentus staff and partners) — that is tenant-app-access.
  • sales — it stays a Mongo-only system role, never assigned here.

Notes for Define

  • User management is in both audiences' power set [Q-5, Q-6]; partner scope is assigned tenants only [Q-3].
  • One system of record for users — the app administers it, never mirrors it (scope assumption). Role changes made here flow to Mongo through the existing Clerk webhook sync, untouched.
  • No new roles, no flags: the platform's role model is used as-is (settled 2026-08-26). The platform's own in-tenant user management remains the second door for these same operations; the last-admin invariant here is scoped to this app's actions.