@govcore/schema 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +74 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/migrations/0003_organization_lifecycle.sql +17 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -63,6 +63,74 @@ declare const organizations: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
63
63
|
identity: undefined;
|
|
64
64
|
generated: undefined;
|
|
65
65
|
}, {}, {}>;
|
|
66
|
+
status: drizzle_orm_pg_core.PgColumn<{
|
|
67
|
+
name: "status";
|
|
68
|
+
tableName: "organizations";
|
|
69
|
+
dataType: "string";
|
|
70
|
+
columnType: "PgText";
|
|
71
|
+
data: string;
|
|
72
|
+
driverParam: string;
|
|
73
|
+
notNull: true;
|
|
74
|
+
hasDefault: true;
|
|
75
|
+
isPrimaryKey: false;
|
|
76
|
+
isAutoincrement: false;
|
|
77
|
+
hasRuntimeDefault: false;
|
|
78
|
+
enumValues: [string, ...string[]];
|
|
79
|
+
baseColumn: never;
|
|
80
|
+
identity: undefined;
|
|
81
|
+
generated: undefined;
|
|
82
|
+
}, {}, {}>;
|
|
83
|
+
statusReason: drizzle_orm_pg_core.PgColumn<{
|
|
84
|
+
name: "status_reason";
|
|
85
|
+
tableName: "organizations";
|
|
86
|
+
dataType: "string";
|
|
87
|
+
columnType: "PgText";
|
|
88
|
+
data: string;
|
|
89
|
+
driverParam: string;
|
|
90
|
+
notNull: false;
|
|
91
|
+
hasDefault: false;
|
|
92
|
+
isPrimaryKey: false;
|
|
93
|
+
isAutoincrement: false;
|
|
94
|
+
hasRuntimeDefault: false;
|
|
95
|
+
enumValues: [string, ...string[]];
|
|
96
|
+
baseColumn: never;
|
|
97
|
+
identity: undefined;
|
|
98
|
+
generated: undefined;
|
|
99
|
+
}, {}, {}>;
|
|
100
|
+
statusChangedAt: drizzle_orm_pg_core.PgColumn<{
|
|
101
|
+
name: "status_changed_at";
|
|
102
|
+
tableName: "organizations";
|
|
103
|
+
dataType: "date";
|
|
104
|
+
columnType: "PgTimestamp";
|
|
105
|
+
data: Date;
|
|
106
|
+
driverParam: string;
|
|
107
|
+
notNull: false;
|
|
108
|
+
hasDefault: false;
|
|
109
|
+
isPrimaryKey: false;
|
|
110
|
+
isAutoincrement: false;
|
|
111
|
+
hasRuntimeDefault: false;
|
|
112
|
+
enumValues: undefined;
|
|
113
|
+
baseColumn: never;
|
|
114
|
+
identity: undefined;
|
|
115
|
+
generated: undefined;
|
|
116
|
+
}, {}, {}>;
|
|
117
|
+
statusChangedBy: drizzle_orm_pg_core.PgColumn<{
|
|
118
|
+
name: "status_changed_by";
|
|
119
|
+
tableName: "organizations";
|
|
120
|
+
dataType: "string";
|
|
121
|
+
columnType: "PgUUID";
|
|
122
|
+
data: string;
|
|
123
|
+
driverParam: string;
|
|
124
|
+
notNull: false;
|
|
125
|
+
hasDefault: false;
|
|
126
|
+
isPrimaryKey: false;
|
|
127
|
+
isAutoincrement: false;
|
|
128
|
+
hasRuntimeDefault: false;
|
|
129
|
+
enumValues: undefined;
|
|
130
|
+
baseColumn: never;
|
|
131
|
+
identity: undefined;
|
|
132
|
+
generated: undefined;
|
|
133
|
+
}, {}, {}>;
|
|
66
134
|
metadata: drizzle_orm_pg_core.PgColumn<{
|
|
67
135
|
name: "metadata";
|
|
68
136
|
tableName: "organizations";
|
|
@@ -117,6 +185,11 @@ declare const organizations: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
117
185
|
};
|
|
118
186
|
dialect: "pg";
|
|
119
187
|
}>;
|
|
188
|
+
/** Organization lifecycle states. Only `active` permits tenant access. */
|
|
189
|
+
declare const ORGANIZATION_STATUSES: readonly ["active", "suspended", "archived"];
|
|
190
|
+
type OrganizationStatus = (typeof ORGANIZATION_STATUSES)[number];
|
|
191
|
+
/** Pure: may an org in this status resolve sessions and run tenant transactions? */
|
|
192
|
+
declare function isOrganizationActive(status: string | null | undefined): boolean;
|
|
120
193
|
/**
|
|
121
194
|
* The tenancy column contract (design §5): every tenant-scoped table — core's
|
|
122
195
|
* and the app's own domain tables — carries `organization_id` referencing
|
|
@@ -2049,4 +2122,4 @@ declare function isUniqueViolation(err: unknown): boolean;
|
|
|
2049
2122
|
*/
|
|
2050
2123
|
declare const CORE_SCHEMA_VERSION = "0.0.0";
|
|
2051
2124
|
|
|
2052
|
-
export { ACT_AS_DEFAULT_TTL_MINUTES, ACT_AS_END_REASONS, type ActAsEndReason, type ActAsSession, type AuditEntry, type BreakGlassSession, CORE_SCHEMA_VERSION, type CrossOrgLink, type GovcoreDb, type InstanceSettings, type NewActAsSession, type NewAuditEntry, type NewBreakGlassSession, type NewCrossOrgLink, type NewInstanceSettings, type NewOrgConnection, type NewOrganization, type NewPlatformConfig, type NewUser, type NewUserOrganizationMembership, type OrgConnection, type Organization, type PlatformConfig, type User, type UserOrganizationMembership, accounts, actAsSessions, auditLog, breakGlassSessions, crossOrgLinks, federationStatus, govcore, instanceSettings, isUniqueViolation, orgConnections, orgScoped, organizations, platformConfig, sessions, userOrganizationMemberships, users, verificationTokens, visibility };
|
|
2125
|
+
export { ACT_AS_DEFAULT_TTL_MINUTES, ACT_AS_END_REASONS, type ActAsEndReason, type ActAsSession, type AuditEntry, type BreakGlassSession, CORE_SCHEMA_VERSION, type CrossOrgLink, type GovcoreDb, type InstanceSettings, type NewActAsSession, type NewAuditEntry, type NewBreakGlassSession, type NewCrossOrgLink, type NewInstanceSettings, type NewOrgConnection, type NewOrganization, type NewPlatformConfig, type NewUser, type NewUserOrganizationMembership, ORGANIZATION_STATUSES, type OrgConnection, type Organization, type OrganizationStatus, type PlatformConfig, type User, type UserOrganizationMembership, accounts, actAsSessions, auditLog, breakGlassSessions, crossOrgLinks, federationStatus, govcore, instanceSettings, isOrganizationActive, isUniqueViolation, orgConnections, orgScoped, organizations, platformConfig, sessions, userOrganizationMemberships, users, verificationTokens, visibility };
|
package/dist/index.js
CHANGED
|
@@ -19,13 +19,32 @@ var organizations = govcore.table(
|
|
|
19
19
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
20
20
|
name: text("name").notNull(),
|
|
21
21
|
slug: text("slug").notNull(),
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Lifecycle state — see ORGANIZATION_STATUSES. `active` is the only state in
|
|
24
|
+
* which a tenant transaction runs or a session resolves; `suspended` and
|
|
25
|
+
* `archived` (soft-delete) are blocked at the createAuth + tenantAction gates.
|
|
26
|
+
*/
|
|
27
|
+
status: text("status").notNull().default("active"),
|
|
28
|
+
/** Why the org was suspended/archived — surfaced to operators; cleared on reinstate. */
|
|
29
|
+
statusReason: text("status_reason"),
|
|
30
|
+
statusChangedAt: timestamp("status_changed_at"),
|
|
31
|
+
/** Historical UUID of the operator who last changed status — no FK on purpose. */
|
|
32
|
+
statusChangedBy: uuid("status_changed_by"),
|
|
33
|
+
/**
|
|
34
|
+
* App-extensible bag for org settings GovCore itself doesn't model. NOT for
|
|
35
|
+
* platform-modeled concerns (lifecycle now lives in `status`, not here); keys
|
|
36
|
+
* are the consuming app's own namespace.
|
|
37
|
+
*/
|
|
23
38
|
metadata: jsonb("metadata").notNull().default({}),
|
|
24
39
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
25
40
|
updatedAt: timestamp("updated_at").notNull().defaultNow()
|
|
26
41
|
},
|
|
27
42
|
(t) => [uniqueIndex("organizations_slug_unique").on(t.slug)]
|
|
28
43
|
);
|
|
44
|
+
var ORGANIZATION_STATUSES = ["active", "suspended", "archived"];
|
|
45
|
+
function isOrganizationActive(status) {
|
|
46
|
+
return status === "active";
|
|
47
|
+
}
|
|
29
48
|
var orgScoped = (orgs) => ({
|
|
30
49
|
organizationId: uuid("organization_id").notNull().references(() => orgs.id, { onDelete: "cascade" })
|
|
31
50
|
});
|
|
@@ -209,6 +228,7 @@ export {
|
|
|
209
228
|
ACT_AS_DEFAULT_TTL_MINUTES,
|
|
210
229
|
ACT_AS_END_REASONS,
|
|
211
230
|
CORE_SCHEMA_VERSION,
|
|
231
|
+
ORGANIZATION_STATUSES,
|
|
212
232
|
accounts,
|
|
213
233
|
actAsSessions,
|
|
214
234
|
auditLog,
|
|
@@ -217,6 +237,7 @@ export {
|
|
|
217
237
|
federationStatus,
|
|
218
238
|
govcore,
|
|
219
239
|
instanceSettings,
|
|
240
|
+
isOrganizationActive,
|
|
220
241
|
isUniqueViolation,
|
|
221
242
|
orgConnections,
|
|
222
243
|
orgScoped,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/schema.ts","../src/index.ts"],"sourcesContent":["// @govcore/schema — platform table definitions (identity, tenancy, auth, audit).\n//\n// Edge-safe: imports only `drizzle-orm/pg-core`, no DB client. The app imports\n// these for type-safe queries and to declare foreign keys *to* platform tables.\n// The govcore-migrate runner (./migrate) is a separate, non-edge entrypoint.\n//\n// All platform tables live in a dedicated `govcore` Postgres schema (design\n// §13.4) so they never collide with an app's `public` tables and ownership is\n// obvious. The authored DDL lives in ../migrations and must stay in sync.\n\nimport {\n type AnyPgColumn,\n boolean,\n integer,\n jsonb,\n pgSchema,\n text,\n timestamp,\n unique,\n uniqueIndex,\n uuid,\n} from 'drizzle-orm/pg-core'\n\n/** The Postgres schema every GovCore platform table belongs to. */\nexport const govcore = pgSchema('govcore')\n\n/** Content/federation visibility. Used by the federation package (later phase). */\nexport const visibility = govcore.enum('visibility', ['org', 'connections', 'instance'])\n\n/** Generic workflow status shared by federation connections and links. */\nexport const federationStatus = govcore.enum('federation_status', ['pending', 'active', 'rejected'])\n\n// ── Tenancy root ────────────────────────────────────────────────────────────\n\nexport const organizations = govcore.table(\n 'organizations',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n name: text('name').notNull(),\n slug: text('slug').notNull(),\n /** App-extensible bag for org settings GovCore itself doesn't model. */\n metadata: jsonb('metadata').notNull().default({}),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n },\n (t) => [uniqueIndex('organizations_slug_unique').on(t.slug)],\n)\n\n/**\n * The tenancy column contract (design §5): every tenant-scoped table — core's\n * and the app's own domain tables — carries `organization_id` referencing\n * `organizations`. RLS policies (see ../migrations/0001) key off this column.\n *\n * @example\n * export const permits = pgTable('permits', { ...orgScoped(organizations), title: text('title') })\n */\nexport const orgScoped = (orgs: typeof organizations) => ({\n organizationId: uuid('organization_id')\n .notNull()\n .references((): AnyPgColumn => orgs.id, { onDelete: 'cascade' }),\n})\n\n// ── Identity ────────────────────────────────────────────────────────────────\n\nexport const users = govcore.table(\n 'users',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n organizationId: uuid('organization_id')\n .notNull()\n .references(() => organizations.id, { onDelete: 'cascade' }),\n /** Last-selected active org, honored first by active-membership resolution. */\n lastActiveOrganizationId: uuid('last_active_organization_id').references(\n (): AnyPgColumn => organizations.id,\n { onDelete: 'set null' },\n ),\n name: text('name'),\n email: text('email').notNull(),\n emailVerified: timestamp('email_verified'),\n image: text('image'),\n passwordHash: text('password_hash'),\n /**\n * App-defined role (see @govcore/rbac `createRbac`) — stored as `text`, not\n * a fixed enum, so GovCore ships no role vocabulary of its own. Denormalized\n * cache of the active membership's role; nullable so the Auth.js adapter can\n * create a user before a membership is assigned.\n */\n role: text('role'),\n /** Platform-level role (e.g. instance/platform admin), separate from per-org role. */\n instanceRole: text('instance_role'),\n isActive: boolean('is_active').notNull().default(true),\n // Account-lockout + password-expiry state; the policy itself lives app-side.\n failedLoginAttempts: integer('failed_login_attempts').notNull().default(0),\n lockoutUntil: timestamp('lockout_until'),\n lastPasswordChangedAt: timestamp('last_password_changed_at').notNull().defaultNow(),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n },\n // One identity per email across all orgs — unambiguous auth/SSO lookups.\n (t) => [uniqueIndex('users_email_unique').on(t.email)],\n)\n\n// Auth.js (drizzle-adapter) tables. Not org-scoped; read during auth flows\n// before a tenant context exists, so they are intentionally outside RLS.\n\nexport const accounts = govcore.table('accounts', {\n id: uuid('id').primaryKey().defaultRandom(),\n userId: uuid('user_id')\n .notNull()\n .references(() => users.id, { onDelete: 'cascade' }),\n type: text('type').notNull(),\n provider: text('provider').notNull(),\n providerAccountId: text('provider_account_id').notNull(),\n refreshToken: text('refresh_token'),\n accessToken: text('access_token'),\n expiresAt: timestamp('expires_at'),\n tokenType: text('token_type'),\n scope: text('scope'),\n idToken: text('id_token'),\n sessionState: text('session_state'),\n})\n\nexport const sessions = govcore.table('sessions', {\n id: uuid('id').primaryKey().defaultRandom(),\n sessionToken: text('session_token').notNull().unique(),\n userId: uuid('user_id')\n .notNull()\n .references(() => users.id, { onDelete: 'cascade' }),\n expires: timestamp('expires').notNull(),\n})\n\nexport const verificationTokens = govcore.table('verification_tokens', {\n identifier: text('identifier').notNull(),\n token: text('token').notNull(),\n expires: timestamp('expires').notNull(),\n})\n\n// ── Membership model (the heart of tenancy) ─────────────────────────────────\n\nexport const userOrganizationMemberships = govcore.table(\n 'user_organization_memberships',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n userId: uuid('user_id')\n .notNull()\n .references(() => users.id, { onDelete: 'cascade' }),\n organizationId: uuid('organization_id')\n .notNull()\n .references(() => organizations.id, { onDelete: 'cascade' }),\n /** App-defined role for this membership (text, not an enum — see `users.role`). */\n role: text('role').notNull(),\n /** Revocation soft-deactivates so the historical row survives for audit. */\n isActive: boolean('is_active').notNull().default(true),\n isPrimary: boolean('is_primary').notNull().default(false),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n },\n (t) => [uniqueIndex('user_org_membership_unique').on(t.userId, t.organizationId)],\n)\n\n// ── Audit (append-only; immutability + RLS in ../migrations/0001) ───────────\n\nexport const auditLog = govcore.table('audit_log', {\n id: uuid('id').primaryKey().defaultRandom(),\n organizationId: uuid('organization_id'), // historical UUID — no FK on purpose\n userId: uuid('user_id'), // historical UUID — no FK on purpose\n action: text('action').notNull(),\n entityType: text('entity_type').notNull(),\n entityId: uuid('entity_id'),\n before: jsonb('before'),\n after: jsonb('after'),\n metadata: jsonb('metadata'),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n})\n\n// ── Federation (cross-organization connections and content links) ──────────\n\n/** Explicit bilateral connection between two organizations. */\nexport const orgConnections = govcore.table(\n 'org_connections',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n fromOrgId: uuid('from_org_id')\n .notNull()\n .references(() => organizations.id, { onDelete: 'cascade' }),\n toOrgId: uuid('to_org_id')\n .notNull()\n .references(() => organizations.id, { onDelete: 'cascade' }),\n status: federationStatus('status').notNull().default('pending'),\n createdBy: uuid('created_by').references(() => users.id),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n },\n (t) => [unique('unique_org_connection').on(t.fromOrgId, t.toOrgId)],\n)\n\n/** Approved relationship between content items across orgs. No FK on entity ids\n * (they cross org boundaries); link semantics are app-defined (`link_type` text). */\nexport const crossOrgLinks = govcore.table('cross_org_links', {\n id: uuid('id').primaryKey().defaultRandom(),\n sourceOrgId: uuid('source_org_id')\n .notNull()\n .references(() => organizations.id, { onDelete: 'cascade' }),\n sourceEntityType: text('source_entity_type').notNull(),\n sourceEntityId: uuid('source_entity_id').notNull(),\n targetOrgId: uuid('target_org_id')\n .notNull()\n .references(() => organizations.id, { onDelete: 'cascade' }),\n targetEntityType: text('target_entity_type').notNull(),\n targetEntityId: uuid('target_entity_id').notNull(),\n linkType: text('link_type').notNull(),\n status: federationStatus('status').notNull().default('pending'),\n rejectionReason: text('rejection_reason'),\n flaggedForReview: boolean('flagged_for_review').notNull().default(false),\n flagReason: text('flag_reason'),\n createdBy: uuid('created_by').references(() => users.id),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n})\n\n// ── Support access (break-glass + act-as) ──────────────────────────────────\n// Instance-operator constructs that deliberately cross the tenant boundary, so\n// they are NOT under the org-GUC RLS (the actor is an instance admin operating\n// across orgs). Authorization is enforced in @govcore/support (design §6.6).\n\nexport const breakGlassSessions = govcore.table('break_glass_sessions', {\n id: uuid('id').primaryKey().defaultRandom(),\n instanceAdminId: uuid('instance_admin_id')\n .notNull()\n .references(() => users.id),\n targetOrgId: uuid('target_org_id')\n .notNull()\n .references(() => organizations.id),\n reason: text('reason').notNull(),\n grantedAt: timestamp('granted_at').notNull().defaultNow(),\n expiresAt: timestamp('expires_at').notNull(),\n requiresApproval: boolean('requires_approval').notNull().default(false),\n approvedAt: timestamp('approved_at'),\n approvedBy: uuid('approved_by').references(() => users.id),\n revokedAt: timestamp('revoked_at'),\n revokedBy: uuid('revoked_by').references(() => users.id),\n})\n\nexport const actAsSessions = govcore.table('act_as_sessions', {\n id: uuid('id').primaryKey().defaultRandom(),\n breakGlassSessionId: uuid('break_glass_session_id')\n .notNull()\n .references(() => breakGlassSessions.id),\n instanceAdminId: uuid('instance_admin_id')\n .notNull()\n .references(() => users.id),\n targetOrgId: uuid('target_org_id')\n .notNull()\n .references(() => organizations.id),\n startedAt: timestamp('started_at').notNull().defaultNow(),\n expiresAt: timestamp('expires_at').notNull(),\n endedAt: timestamp('ended_at'),\n endReason: text('end_reason'),\n})\n\nexport const ACT_AS_DEFAULT_TTL_MINUTES = 30\nexport const ACT_AS_END_REASONS = [\n 'admin_ended',\n 'expired',\n 'parent_revoked',\n 'parent_expired',\n] as const\nexport type ActAsEndReason = (typeof ACT_AS_END_REASONS)[number]\n\n// ── Instance / platform configuration (singletons; not org-scoped) ─────────\n\nexport const instanceSettings = govcore.table('instance_settings', {\n id: uuid('id').primaryKey().defaultRandom(),\n disabledModules: jsonb('disabled_modules').$type<Record<string, boolean>>().notNull().default({}),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n})\n\nexport const platformConfig = govcore.table('platform_config', {\n id: text('id').primaryKey().default('singleton'),\n instanceName: text('instance_name').notNull().default('GovCore'),\n defaultTheme: text('default_theme').notNull().default('base'),\n allowLocalAuth: boolean('allow_local_auth').notNull().default(true),\n /** Stamped onto new orgs at provisioning time. Null means no tier. */\n defaultSupportTier: text('default_support_tier'),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n updatedBy: uuid('updated_by').references(() => users.id, { onDelete: 'set null' }),\n})\n\n// ── Inferred types ──────────────────────────────────────────────────────────\n\nexport type Organization = typeof organizations.$inferSelect\nexport type NewOrganization = typeof organizations.$inferInsert\nexport type User = typeof users.$inferSelect\nexport type NewUser = typeof users.$inferInsert\nexport type UserOrganizationMembership = typeof userOrganizationMemberships.$inferSelect\nexport type NewUserOrganizationMembership = typeof userOrganizationMemberships.$inferInsert\nexport type AuditEntry = typeof auditLog.$inferSelect\nexport type NewAuditEntry = typeof auditLog.$inferInsert\nexport type OrgConnection = typeof orgConnections.$inferSelect\nexport type NewOrgConnection = typeof orgConnections.$inferInsert\nexport type CrossOrgLink = typeof crossOrgLinks.$inferSelect\nexport type NewCrossOrgLink = typeof crossOrgLinks.$inferInsert\nexport type BreakGlassSession = typeof breakGlassSessions.$inferSelect\nexport type NewBreakGlassSession = typeof breakGlassSessions.$inferInsert\nexport type ActAsSession = typeof actAsSessions.$inferSelect\nexport type NewActAsSession = typeof actAsSessions.$inferInsert\nexport type InstanceSettings = typeof instanceSettings.$inferSelect\nexport type NewInstanceSettings = typeof instanceSettings.$inferInsert\nexport type PlatformConfig = typeof platformConfig.$inferSelect\nexport type NewPlatformConfig = typeof platformConfig.$inferInsert\n","// @govcore/schema — platform table definitions + schema version.\n// Edge-safe entrypoint (no DB client). The migrate runner is ./migrate.\n\nimport type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core'\n\nexport * from './schema'\n\n/**\n * A Drizzle Postgres database **or** transaction handle. Functions in the\n * tenancy/audit packages accept this so callers inject their own client (the\n * app's `postgres-js` db, or a `tx` inside a transaction — which extends it).\n */\nexport type GovcoreDb = PgDatabase<PgQueryResultHKT, any, any>\n\n/**\n * True when `err` is (or wraps) a Postgres unique-constraint violation\n * (SQLSTATE 23505). Pure and edge-safe — inspects `code`, touches no DB client.\n * Lets operator flows turn a duplicate slug/email into a typed result instead of\n * a 500. Walks the `cause` chain because Drizzle wraps the driver error in a\n * `DrizzleQueryError` and puts the `postgres-js`/`pg` error (which carries the\n * `code`) on `.cause` — so the top-level error's own `code` is undefined.\n */\nexport function isUniqueViolation(err: unknown): boolean {\n for (let e: unknown = err, depth = 0; e && typeof e === 'object' && depth < 5; depth++) {\n if ((e as { code?: string }).code === '23505') return true\n e = (e as { cause?: unknown }).cause\n }\n return false\n}\n\n/**\n * Bumped whenever the platform schema changes. Written to instance settings on\n * boot for observability (\"this instance runs platform schema vN\", design §5),\n * and stamped into backup archives (design §13.5). Not load-bearing for\n * correctness — the migrations are — but useful for diagnostics.\n */\nexport const CORE_SCHEMA_VERSION = '0.0.0'\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/schema.ts","../src/index.ts"],"sourcesContent":["// @govcore/schema — platform table definitions (identity, tenancy, auth, audit).\n//\n// Edge-safe: imports only `drizzle-orm/pg-core`, no DB client. The app imports\n// these for type-safe queries and to declare foreign keys *to* platform tables.\n// The govcore-migrate runner (./migrate) is a separate, non-edge entrypoint.\n//\n// All platform tables live in a dedicated `govcore` Postgres schema (design\n// §13.4) so they never collide with an app's `public` tables and ownership is\n// obvious. The authored DDL lives in ../migrations and must stay in sync.\n\nimport {\n type AnyPgColumn,\n boolean,\n integer,\n jsonb,\n pgSchema,\n text,\n timestamp,\n unique,\n uniqueIndex,\n uuid,\n} from 'drizzle-orm/pg-core'\n\n/** The Postgres schema every GovCore platform table belongs to. */\nexport const govcore = pgSchema('govcore')\n\n/** Content/federation visibility. Used by the federation package (later phase). */\nexport const visibility = govcore.enum('visibility', ['org', 'connections', 'instance'])\n\n/** Generic workflow status shared by federation connections and links. */\nexport const federationStatus = govcore.enum('federation_status', ['pending', 'active', 'rejected'])\n\n// ── Tenancy root ────────────────────────────────────────────────────────────\n\nexport const organizations = govcore.table(\n 'organizations',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n name: text('name').notNull(),\n slug: text('slug').notNull(),\n /**\n * Lifecycle state — see ORGANIZATION_STATUSES. `active` is the only state in\n * which a tenant transaction runs or a session resolves; `suspended` and\n * `archived` (soft-delete) are blocked at the createAuth + tenantAction gates.\n */\n status: text('status').notNull().default('active'),\n /** Why the org was suspended/archived — surfaced to operators; cleared on reinstate. */\n statusReason: text('status_reason'),\n statusChangedAt: timestamp('status_changed_at'),\n /** Historical UUID of the operator who last changed status — no FK on purpose. */\n statusChangedBy: uuid('status_changed_by'),\n /**\n * App-extensible bag for org settings GovCore itself doesn't model. NOT for\n * platform-modeled concerns (lifecycle now lives in `status`, not here); keys\n * are the consuming app's own namespace.\n */\n metadata: jsonb('metadata').notNull().default({}),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n },\n (t) => [uniqueIndex('organizations_slug_unique').on(t.slug)],\n)\n\n/** Organization lifecycle states. Only `active` permits tenant access. */\nexport const ORGANIZATION_STATUSES = ['active', 'suspended', 'archived'] as const\nexport type OrganizationStatus = (typeof ORGANIZATION_STATUSES)[number]\n\n/** Pure: may an org in this status resolve sessions and run tenant transactions? */\nexport function isOrganizationActive(status: string | null | undefined): boolean {\n return status === 'active'\n}\n\n/**\n * The tenancy column contract (design §5): every tenant-scoped table — core's\n * and the app's own domain tables — carries `organization_id` referencing\n * `organizations`. RLS policies (see ../migrations/0001) key off this column.\n *\n * @example\n * export const permits = pgTable('permits', { ...orgScoped(organizations), title: text('title') })\n */\nexport const orgScoped = (orgs: typeof organizations) => ({\n organizationId: uuid('organization_id')\n .notNull()\n .references((): AnyPgColumn => orgs.id, { onDelete: 'cascade' }),\n})\n\n// ── Identity ────────────────────────────────────────────────────────────────\n\nexport const users = govcore.table(\n 'users',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n organizationId: uuid('organization_id')\n .notNull()\n .references(() => organizations.id, { onDelete: 'cascade' }),\n /** Last-selected active org, honored first by active-membership resolution. */\n lastActiveOrganizationId: uuid('last_active_organization_id').references(\n (): AnyPgColumn => organizations.id,\n { onDelete: 'set null' },\n ),\n name: text('name'),\n email: text('email').notNull(),\n emailVerified: timestamp('email_verified'),\n image: text('image'),\n passwordHash: text('password_hash'),\n /**\n * App-defined role (see @govcore/rbac `createRbac`) — stored as `text`, not\n * a fixed enum, so GovCore ships no role vocabulary of its own. Denormalized\n * cache of the active membership's role; nullable so the Auth.js adapter can\n * create a user before a membership is assigned.\n */\n role: text('role'),\n /** Platform-level role (e.g. instance/platform admin), separate from per-org role. */\n instanceRole: text('instance_role'),\n isActive: boolean('is_active').notNull().default(true),\n // Account-lockout + password-expiry state; the policy itself lives app-side.\n failedLoginAttempts: integer('failed_login_attempts').notNull().default(0),\n lockoutUntil: timestamp('lockout_until'),\n lastPasswordChangedAt: timestamp('last_password_changed_at').notNull().defaultNow(),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n },\n // One identity per email across all orgs — unambiguous auth/SSO lookups.\n (t) => [uniqueIndex('users_email_unique').on(t.email)],\n)\n\n// Auth.js (drizzle-adapter) tables. Not org-scoped; read during auth flows\n// before a tenant context exists, so they are intentionally outside RLS.\n\nexport const accounts = govcore.table('accounts', {\n id: uuid('id').primaryKey().defaultRandom(),\n userId: uuid('user_id')\n .notNull()\n .references(() => users.id, { onDelete: 'cascade' }),\n type: text('type').notNull(),\n provider: text('provider').notNull(),\n providerAccountId: text('provider_account_id').notNull(),\n refreshToken: text('refresh_token'),\n accessToken: text('access_token'),\n expiresAt: timestamp('expires_at'),\n tokenType: text('token_type'),\n scope: text('scope'),\n idToken: text('id_token'),\n sessionState: text('session_state'),\n})\n\nexport const sessions = govcore.table('sessions', {\n id: uuid('id').primaryKey().defaultRandom(),\n sessionToken: text('session_token').notNull().unique(),\n userId: uuid('user_id')\n .notNull()\n .references(() => users.id, { onDelete: 'cascade' }),\n expires: timestamp('expires').notNull(),\n})\n\nexport const verificationTokens = govcore.table('verification_tokens', {\n identifier: text('identifier').notNull(),\n token: text('token').notNull(),\n expires: timestamp('expires').notNull(),\n})\n\n// ── Membership model (the heart of tenancy) ─────────────────────────────────\n\nexport const userOrganizationMemberships = govcore.table(\n 'user_organization_memberships',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n userId: uuid('user_id')\n .notNull()\n .references(() => users.id, { onDelete: 'cascade' }),\n organizationId: uuid('organization_id')\n .notNull()\n .references(() => organizations.id, { onDelete: 'cascade' }),\n /** App-defined role for this membership (text, not an enum — see `users.role`). */\n role: text('role').notNull(),\n /** Revocation soft-deactivates so the historical row survives for audit. */\n isActive: boolean('is_active').notNull().default(true),\n isPrimary: boolean('is_primary').notNull().default(false),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n },\n (t) => [uniqueIndex('user_org_membership_unique').on(t.userId, t.organizationId)],\n)\n\n// ── Audit (append-only; immutability + RLS in ../migrations/0001) ───────────\n\nexport const auditLog = govcore.table('audit_log', {\n id: uuid('id').primaryKey().defaultRandom(),\n organizationId: uuid('organization_id'), // historical UUID — no FK on purpose\n userId: uuid('user_id'), // historical UUID — no FK on purpose\n action: text('action').notNull(),\n entityType: text('entity_type').notNull(),\n entityId: uuid('entity_id'),\n before: jsonb('before'),\n after: jsonb('after'),\n metadata: jsonb('metadata'),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n})\n\n// ── Federation (cross-organization connections and content links) ──────────\n\n/** Explicit bilateral connection between two organizations. */\nexport const orgConnections = govcore.table(\n 'org_connections',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n fromOrgId: uuid('from_org_id')\n .notNull()\n .references(() => organizations.id, { onDelete: 'cascade' }),\n toOrgId: uuid('to_org_id')\n .notNull()\n .references(() => organizations.id, { onDelete: 'cascade' }),\n status: federationStatus('status').notNull().default('pending'),\n createdBy: uuid('created_by').references(() => users.id),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n },\n (t) => [unique('unique_org_connection').on(t.fromOrgId, t.toOrgId)],\n)\n\n/** Approved relationship between content items across orgs. No FK on entity ids\n * (they cross org boundaries); link semantics are app-defined (`link_type` text). */\nexport const crossOrgLinks = govcore.table('cross_org_links', {\n id: uuid('id').primaryKey().defaultRandom(),\n sourceOrgId: uuid('source_org_id')\n .notNull()\n .references(() => organizations.id, { onDelete: 'cascade' }),\n sourceEntityType: text('source_entity_type').notNull(),\n sourceEntityId: uuid('source_entity_id').notNull(),\n targetOrgId: uuid('target_org_id')\n .notNull()\n .references(() => organizations.id, { onDelete: 'cascade' }),\n targetEntityType: text('target_entity_type').notNull(),\n targetEntityId: uuid('target_entity_id').notNull(),\n linkType: text('link_type').notNull(),\n status: federationStatus('status').notNull().default('pending'),\n rejectionReason: text('rejection_reason'),\n flaggedForReview: boolean('flagged_for_review').notNull().default(false),\n flagReason: text('flag_reason'),\n createdBy: uuid('created_by').references(() => users.id),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n})\n\n// ── Support access (break-glass + act-as) ──────────────────────────────────\n// Instance-operator constructs that deliberately cross the tenant boundary, so\n// they are NOT under the org-GUC RLS (the actor is an instance admin operating\n// across orgs). Authorization is enforced in @govcore/support (design §6.6).\n\nexport const breakGlassSessions = govcore.table('break_glass_sessions', {\n id: uuid('id').primaryKey().defaultRandom(),\n instanceAdminId: uuid('instance_admin_id')\n .notNull()\n .references(() => users.id),\n targetOrgId: uuid('target_org_id')\n .notNull()\n .references(() => organizations.id),\n reason: text('reason').notNull(),\n grantedAt: timestamp('granted_at').notNull().defaultNow(),\n expiresAt: timestamp('expires_at').notNull(),\n requiresApproval: boolean('requires_approval').notNull().default(false),\n approvedAt: timestamp('approved_at'),\n approvedBy: uuid('approved_by').references(() => users.id),\n revokedAt: timestamp('revoked_at'),\n revokedBy: uuid('revoked_by').references(() => users.id),\n})\n\nexport const actAsSessions = govcore.table('act_as_sessions', {\n id: uuid('id').primaryKey().defaultRandom(),\n breakGlassSessionId: uuid('break_glass_session_id')\n .notNull()\n .references(() => breakGlassSessions.id),\n instanceAdminId: uuid('instance_admin_id')\n .notNull()\n .references(() => users.id),\n targetOrgId: uuid('target_org_id')\n .notNull()\n .references(() => organizations.id),\n startedAt: timestamp('started_at').notNull().defaultNow(),\n expiresAt: timestamp('expires_at').notNull(),\n endedAt: timestamp('ended_at'),\n endReason: text('end_reason'),\n})\n\nexport const ACT_AS_DEFAULT_TTL_MINUTES = 30\nexport const ACT_AS_END_REASONS = [\n 'admin_ended',\n 'expired',\n 'parent_revoked',\n 'parent_expired',\n] as const\nexport type ActAsEndReason = (typeof ACT_AS_END_REASONS)[number]\n\n// ── Instance / platform configuration (singletons; not org-scoped) ─────────\n\nexport const instanceSettings = govcore.table('instance_settings', {\n id: uuid('id').primaryKey().defaultRandom(),\n disabledModules: jsonb('disabled_modules').$type<Record<string, boolean>>().notNull().default({}),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n})\n\nexport const platformConfig = govcore.table('platform_config', {\n id: text('id').primaryKey().default('singleton'),\n instanceName: text('instance_name').notNull().default('GovCore'),\n defaultTheme: text('default_theme').notNull().default('base'),\n allowLocalAuth: boolean('allow_local_auth').notNull().default(true),\n /** Stamped onto new orgs at provisioning time. Null means no tier. */\n defaultSupportTier: text('default_support_tier'),\n updatedAt: timestamp('updated_at').notNull().defaultNow(),\n updatedBy: uuid('updated_by').references(() => users.id, { onDelete: 'set null' }),\n})\n\n// ── Inferred types ──────────────────────────────────────────────────────────\n\nexport type Organization = typeof organizations.$inferSelect\nexport type NewOrganization = typeof organizations.$inferInsert\nexport type User = typeof users.$inferSelect\nexport type NewUser = typeof users.$inferInsert\nexport type UserOrganizationMembership = typeof userOrganizationMemberships.$inferSelect\nexport type NewUserOrganizationMembership = typeof userOrganizationMemberships.$inferInsert\nexport type AuditEntry = typeof auditLog.$inferSelect\nexport type NewAuditEntry = typeof auditLog.$inferInsert\nexport type OrgConnection = typeof orgConnections.$inferSelect\nexport type NewOrgConnection = typeof orgConnections.$inferInsert\nexport type CrossOrgLink = typeof crossOrgLinks.$inferSelect\nexport type NewCrossOrgLink = typeof crossOrgLinks.$inferInsert\nexport type BreakGlassSession = typeof breakGlassSessions.$inferSelect\nexport type NewBreakGlassSession = typeof breakGlassSessions.$inferInsert\nexport type ActAsSession = typeof actAsSessions.$inferSelect\nexport type NewActAsSession = typeof actAsSessions.$inferInsert\nexport type InstanceSettings = typeof instanceSettings.$inferSelect\nexport type NewInstanceSettings = typeof instanceSettings.$inferInsert\nexport type PlatformConfig = typeof platformConfig.$inferSelect\nexport type NewPlatformConfig = typeof platformConfig.$inferInsert\n","// @govcore/schema — platform table definitions + schema version.\n// Edge-safe entrypoint (no DB client). The migrate runner is ./migrate.\n\nimport type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core'\n\nexport * from './schema'\n\n/**\n * A Drizzle Postgres database **or** transaction handle. Functions in the\n * tenancy/audit packages accept this so callers inject their own client (the\n * app's `postgres-js` db, or a `tx` inside a transaction — which extends it).\n */\nexport type GovcoreDb = PgDatabase<PgQueryResultHKT, any, any>\n\n/**\n * True when `err` is (or wraps) a Postgres unique-constraint violation\n * (SQLSTATE 23505). Pure and edge-safe — inspects `code`, touches no DB client.\n * Lets operator flows turn a duplicate slug/email into a typed result instead of\n * a 500. Walks the `cause` chain because Drizzle wraps the driver error in a\n * `DrizzleQueryError` and puts the `postgres-js`/`pg` error (which carries the\n * `code`) on `.cause` — so the top-level error's own `code` is undefined.\n */\nexport function isUniqueViolation(err: unknown): boolean {\n for (let e: unknown = err, depth = 0; e && typeof e === 'object' && depth < 5; depth++) {\n if ((e as { code?: string }).code === '23505') return true\n e = (e as { cause?: unknown }).cause\n }\n return false\n}\n\n/**\n * Bumped whenever the platform schema changes. Written to instance settings on\n * boot for observability (\"this instance runs platform schema vN\", design §5),\n * and stamped into backup archives (design §13.5). Not load-bearing for\n * correctness — the migrations are — but useful for diagnostics.\n */\nexport const CORE_SCHEMA_VERSION = '0.0.0'\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- 0003_organization_lifecycle — org status (active | suspended | archived).
|
|
2
|
+
--
|
|
3
|
+
-- Lifecycle was previously unmodeled; the only escape hatch was
|
|
4
|
+
-- organizations.metadata, which nothing enforced. Suspension/archival now live
|
|
5
|
+
-- in first-class columns so the createAuth and tenantAction gates can bind to
|
|
6
|
+
-- them (design: an org must be `active` to resolve a session or run a tenant
|
|
7
|
+
-- transaction). `organizations` is not under RLS, so the runtime pool reads
|
|
8
|
+
-- these columns without a tenant GUC.
|
|
9
|
+
|
|
10
|
+
ALTER TABLE govcore.organizations
|
|
11
|
+
ADD COLUMN IF NOT EXISTS status text NOT NULL DEFAULT 'active',
|
|
12
|
+
ADD COLUMN IF NOT EXISTS status_reason text,
|
|
13
|
+
ADD COLUMN IF NOT EXISTS status_changed_at timestamptz,
|
|
14
|
+
ADD COLUMN IF NOT EXISTS status_changed_by uuid;
|
|
15
|
+
|
|
16
|
+
-- Fast lookup / partial scans of non-active tenants for the operator console.
|
|
17
|
+
CREATE INDEX IF NOT EXISTS organizations_status_idx ON govcore.organizations (status);
|