# 2026-02-07 — Customer ID schema + "hashed id" confusion ## Current reality ### User table Prisma schema: ```prisma model User { id String @id @default(cuid()) email String @unique username String @unique passwordHash String firstName String? lastName String? displayName String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } ``` * When a user registers via the API (`POST /api/auth/register`), Prisma assigns a **CUID** (looks "hashed" in Prisma Studio, e.g. `cmk07b7n4000q...`). * That value is **not derived from the JWT token**. * During testing you manually created a user with an ID like `u000` (and other prefixed IDs), which is why you're seeing mixed formats. ### ContainerInstance.customerId (what broke) You observed `ContainerInstance.customerId` being **polluted**: * sometimes `u000` (manual) * sometimes `u-dev-001` (hardcoded during testing/dev) * sometimes a Prisma CUID (new registrations) Root cause (confirmed): * Frontend / provisioning calls were hardcoded to use `u-dev-001` as `customerId`. * New registrations correctly used Prisma's default `User.id` (CUID), but container creation wasn't consistently using it. ## Short-term decision (now) ✅ **Use `User.id` as the authoritative customer identifier** everywhere. That means: * `customerId` in `ContainerInstance` should always be set to `req.user.id`. * Stop inventing or hardcoding IDs (`u-dev-001`, etc.) in the frontend. * Keep `username` / `displayName` for human-friendly display and troubleshooting. This keeps the system consistent immediately and avoids adding schema complexity right now. ## Why this is OK Pros: * Guaranteed unique * Harder to guess (good for security) * Already produced by Prisma automatically * No more "what format is customerId?" problems Cons: * Not pretty/human-friendly in logs (CUIDs) Mitigation: * When displaying in UI or logs, show a composite: * `displayName (username)` and optionally the first 6–8 chars of the id * Example: `Jester (jester) • cmk07b7n4` ## Long-term (optional) improvement If you later want a human-readable / sequential ID: Add a second field, e.g.: * `publicCustomerId` (`u001`, `u002`, etc.) generated by a counter table or dedicated allocator Then: * Keep `id` as the internal primary key (CUID) * Use `publicCustomerId` only for: * display * support tickets * invoices / Stripe customer metadata * external references But: don't block on this now. ## Cleanup / migration note If you want to clean existing polluted rows later: * Update all `ContainerInstance.customerId = 'u-dev-001'` to the correct `User.id` (based on ownership mapping / audit trail / known associations). * If no reliable mapping exists, leave historic test rows as-is and only enforce the rule going forward. ## Implementation rule (what to enforce) * Provisioning endpoint should **ignore any client-supplied customerId**. * Always derive from auth context: ```js const customerId = req.user.id; ``` * If you need admin tooling to create for others, that should be a separate privileged endpoint that explicitly sets ownership (and logs it).