feat: establish core identity foundation
CI / verify (pull_request) Successful in 25s
CI / infrastructure-smoke (pull_request) Successful in 12s
CI / migration-smoke (pull_request) Successful in 22s

Adds Core-owned identity contracts, a Core-owned Better Auth database schema
(ekklesia_core_user/session/account/verification), a first-class Core-owned
migration source that coexists with module-owned migrations under the exact
existing migration system, and an explicit Better Auth construction boundary.
No authentication endpoint or sign-in UI is exposed yet (ADR 0010).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
ekklesia
2026-07-21 16:39:15 -05:00
co-authored by Claude Sonnet 5
parent df0f20d8af
commit 679a5d225c
43 changed files with 4259 additions and 64 deletions
+91
View File
@@ -0,0 +1,91 @@
import { getTableName } from "drizzle-orm";
import { getTableConfig } from "drizzle-orm/pg-core";
import { describe, expect, it } from "vitest";
import {
ekklesiaCoreAccount,
ekklesiaCoreSession,
ekklesiaCoreUser,
ekklesiaCoreVerification,
identityAdapterSchema,
} from "../src/identity/schema.js";
describe("Core identity Drizzle schema", () => {
it("uses explicit, unambiguous ekklesia_core_* physical table names", () => {
expect(getTableName(ekklesiaCoreUser)).toBe("ekklesia_core_user");
expect(getTableName(ekklesiaCoreSession)).toBe("ekklesia_core_session");
expect(getTableName(ekklesiaCoreAccount)).toBe("ekklesia_core_account");
expect(getTableName(ekklesiaCoreVerification)).toBe("ekklesia_core_verification");
});
it("explicitly maps every Better Auth logical model to its physical table object", () => {
expect(identityAdapterSchema).toEqual({
account: ekklesiaCoreAccount,
session: ekklesiaCoreSession,
user: ekklesiaCoreUser,
verification: ekklesiaCoreVerification,
});
});
it("gives the user table a unique email and a default-false verification flag", () => {
const config = getTableConfig(ekklesiaCoreUser);
const columns = Object.fromEntries(config.columns.map((column) => [column.name, column]));
expect(columns.id?.primary).toBe(true);
expect(columns.email?.isUnique).toBe(true);
expect(columns.email?.notNull).toBe(true);
expect(columns.email_verified?.hasDefault).toBe(true);
expect(columns.email_verified?.notNull).toBe(true);
});
it("scopes session and account rows to a user with cascading delete", () => {
for (const table of [ekklesiaCoreSession, ekklesiaCoreAccount]) {
const config = getTableConfig(table);
expect(config.foreignKeys).toHaveLength(1);
const foreignKey = config.foreignKeys[0];
if (!foreignKey) {
throw new Error("Expected a foreign key.");
}
const reference = foreignKey.reference();
expect(reference.columns.map((column) => column.name)).toEqual(["user_id"]);
expect(reference.foreignColumns.map((column) => column.name)).toEqual(["id"]);
expect(getTableName(reference.foreignTable)).toBe("ekklesia_core_user");
expect(foreignKey.onDelete).toBe("cascade");
}
});
it("indexes session.user_id, account.user_id, and verification.identifier for lookup", () => {
expect(
getTableConfig(ekklesiaCoreSession).indexes.map((index) =>
index.config.columns.map((column) => ("name" in column ? column.name : undefined)),
),
).toEqual([["user_id"]]);
expect(
getTableConfig(ekklesiaCoreAccount).indexes.map((index) =>
index.config.columns.map((column) => ("name" in column ? column.name : undefined)),
),
).toEqual([["user_id"]]);
expect(
getTableConfig(ekklesiaCoreVerification).indexes.map((index) =>
index.config.columns.map((column) => ("name" in column ? column.name : undefined)),
),
).toEqual([["identifier"]]);
});
it("requires a unique session token", () => {
const config = getTableConfig(ekklesiaCoreSession);
const token = config.columns.find((column) => column.name === "token");
expect(token?.isUnique).toBe(true);
expect(token?.notNull).toBe(true);
});
it("keeps every table in the default public PostgreSQL schema", () => {
for (const table of [
ekklesiaCoreUser,
ekklesiaCoreSession,
ekklesiaCoreAccount,
ekklesiaCoreVerification,
]) {
expect(getTableConfig(table).schema).toBeUndefined();
}
});
});