diff --git a/includes/CorpusProvision.php b/includes/CorpusProvision.php index 8f6b1a2..41baf19 100644 --- a/includes/CorpusProvision.php +++ b/includes/CorpusProvision.php @@ -145,14 +145,16 @@ final class CorpusProvision private static function createOwnerUser(PDO $db, int $clientId, string $email, string $displayName): int { - $stmt = $db->prepare('SELECT id, client_id FROM client_users WHERE email = ? LIMIT 1'); - $stmt->execute([$email]); - $existing = $stmt->fetch(PDO::FETCH_ASSOC); - if ($existing && (int)$existing['client_id'] === $clientId) { - return (int)$existing['id']; - } - if ($existing) { - throw new RuntimeException("Email {$email} already belongs to another workspace."); + // client_users.email has a UNIQUE constraint, so an SSO-provisioned workspace + // uses a synthetic internal email to avoid conflicting with any real CaveauAI + // account that may share the same address. The real email lives on clients.contact_email. + $ssoEmail = self::ssoInternalEmail($db, $clientId); + + $stmt = $db->prepare('SELECT id FROM client_users WHERE email = ? AND client_id = ? LIMIT 1'); + $stmt->execute([$ssoEmail, $clientId]); + $existingId = (int)($stmt->fetchColumn() ?: 0); + if ($existingId > 0) { + return $existingId; } $usernameBase = preg_replace('/[^a-z0-9._-]+/', '', strtolower(strstr($email, '@', true) ?: 'owner')); @@ -167,13 +169,20 @@ final class CorpusProvision $stmt->execute([ $clientId, $username, - $email, + $ssoEmail, $displayName !== '' ? $displayName : null, password_hash(bin2hex(random_bytes(16)), PASSWORD_DEFAULT), ]); return (int)$db->lastInsertId(); } + private static function ssoInternalEmail(PDO $db, int $clientId): string + { + // Derive a stable internal-only email from the client_id so it is unique + // even when the same real address already exists for another workspace. + return 'dbn-sso-' . $clientId . '@dbn.tools.internal'; + } + private static function ensureDefaultCorpus(PDO $db, int $clientId, string $email): int { $stmt = $db->prepare(