02 / 05RBAC.spec

Access control designed around real clinic teams.

Six organization roles, scoped at the server boundary. Enforced through Appwrite Functions, not hidden in the UI.

This is the working specification for how access is granted, scoped, and revoked inside a MediFlow organization. It mirrors the model in the codebase rather than the marketing one.

01
Section one

The permission matrix.

FullScopedNone
no.Capability
Owner
Admin
Clinician
Staff
Billing
Readonly
01Manage organization & billing
02Invite & manage seats
03Manage feature flags
04Create / update patients
05Read patient PHI
06Schedule appointments
07Use operations copilot
08View audit foundations
02
Section two

Six roles, scoped to the work.

  1. 01Owner
    • PHIfull
    • Billingfull
    • Settingsfull
    • Membersfull

    Manages organization, subscription, seats, users, sensitive settings, and ownership transfer.

  2. 02Admin
    • PHIfull
    • Billingread
    • Settingsfull
    • Membersnon-owner

    Manages workspace users, records, and operations without owner-level authority over other owners.

  3. 03Clinician
    • PHIfull
    • Billingnone
    • Settingsnone
    • Membersnone

    Owns clinical work: records, vitals, documents, appointments, and enabled AI workflows.

  4. 04Staff
    • PHIscoped
    • Billingnone
    • Settingsnone
    • Membersnone

    Supports day-to-day operations: patient viewing, scheduling, limited data entry.

  5. 05Billing
    • PHIread
    • Billingfull
    • Settingsnone
    • Membersnone

    Accesses billing information and payment workflows without broad clinical write access.

  6. 06Readonly
    • PHIread
    • Billingnone
    • Settingsnone
    • Membersnone

    Supervisory, audit, handover, or expired-subscription access. No mutations.

03
Section three

Rules the system will not break.

  1. 01The last owner cannot be removed or demoted.
  2. 02Admins cannot manage owner memberships.
  3. 03Billing states can block non-billing mutations.
  4. 04Expired or suspended workspaces become read-only for non-billing writes.
  5. 05Appwrite Team memberships are the source of truth for membership and role.
  6. 06Seat limits block invites when the plan limit is reached.
04
Section four

Server-authoritative, not UI-hidden.

Permission decisions belong at the server boundary. Client-side hide/show is an experience improvement; it is never the source of truth for PHI, billing, membership, or administrative operations.

functions/patient/update.tsServer-authoritative permission check
import { session, assertRole, requireOrgScope } from "@/lib/auth";
import { audit } from "@/lib/audit";

export async function handler({ req, log }) {
  const { user, team } = await session(req);

  await assertRole(team, ["owner", "admin", "clinician"]);
  await requireOrgScope(team, req.body.patientId);

  await audit.write({
    actor: user,
    action: "patient.update",
    class: "phi.write",
    targetId: req.body.patientId,
  });

  // PHI mutation runs only after role + scope + audit succeed.
  return updatePatient(req.body);
}
05
Section five

Billing state can block writes.

Pending or past-due states block non-billing mutations. Expired and suspended states move the workspace into read-only behavior while owner and billing recovery paths stay open.

audit.event.jsonBilling-state decision recorded in the audit log
{
  "event": "audit.decision",
  "ts": "2026-05-24T10:14:02Z",
  "actor": { "type": "user", "role": "clinician" },
  "org": { "id": "org_8f3", "plan": "growth" },
  "billingState": "past_due",
  "decision": "block",
  "class": "phi.write",
  "rule": "billing.state.gate",
  "metadata": { "targetType": "patient", "phi": false }
}
06
Section six

Evidence trail.