@govcore/schema 0.1.0 → 0.2.1
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 +2041 -5
- package/dist/index.js +231 -10
- package/dist/index.js.map +1 -0
- package/dist/migrate.d.ts +4 -3
- package/dist/migrate.js +50 -58
- package/dist/migrate.js.map +1 -0
- package/package.json +28 -10
- package/CHANGELOG.md +0 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/migrate.d.ts.map +0 -1
- package/dist/schema.d.ts +0 -2024
- package/dist/schema.d.ts.map +0 -1
- package/dist/schema.js +0 -231
- package/src/index.ts +0 -21
- package/src/migrate.ts +0 -85
- package/src/schema.ts +0 -311
package/dist/schema.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.js
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
// @govcore/schema — platform table definitions (identity, tenancy, auth, audit).
|
|
2
|
-
//
|
|
3
|
-
// Edge-safe: imports only `drizzle-orm/pg-core`, no DB client. The app imports
|
|
4
|
-
// these for type-safe queries and to declare foreign keys *to* platform tables.
|
|
5
|
-
// The govcore-migrate runner (./migrate) is a separate, non-edge entrypoint.
|
|
6
|
-
//
|
|
7
|
-
// All platform tables live in a dedicated `govcore` Postgres schema (design
|
|
8
|
-
// §13.4) so they never collide with an app's `public` tables and ownership is
|
|
9
|
-
// obvious. The authored DDL lives in ../migrations and must stay in sync.
|
|
10
|
-
import { boolean, integer, jsonb, pgSchema, text, timestamp, unique, uniqueIndex, uuid, } from 'drizzle-orm/pg-core';
|
|
11
|
-
/** The Postgres schema every GovCore platform table belongs to. */
|
|
12
|
-
export const govcore = pgSchema('govcore');
|
|
13
|
-
/** Content/federation visibility. Used by the federation package (later phase). */
|
|
14
|
-
export const visibility = govcore.enum('visibility', ['org', 'connections', 'instance']);
|
|
15
|
-
/** Generic workflow status shared by federation connections and links. */
|
|
16
|
-
export const federationStatus = govcore.enum('federation_status', ['pending', 'active', 'rejected']);
|
|
17
|
-
// ── Tenancy root ────────────────────────────────────────────────────────────
|
|
18
|
-
export const organizations = govcore.table('organizations', {
|
|
19
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
20
|
-
name: text('name').notNull(),
|
|
21
|
-
slug: text('slug').notNull(),
|
|
22
|
-
/** App-extensible bag for org settings GovCore itself doesn't model. */
|
|
23
|
-
metadata: jsonb('metadata').notNull().default({}),
|
|
24
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
25
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
26
|
-
}, (t) => [uniqueIndex('organizations_slug_unique').on(t.slug)]);
|
|
27
|
-
/**
|
|
28
|
-
* The tenancy column contract (design §5): every tenant-scoped table — core's
|
|
29
|
-
* and the app's own domain tables — carries `organization_id` referencing
|
|
30
|
-
* `organizations`. RLS policies (see ../migrations/0001) key off this column.
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* export const permits = pgTable('permits', { ...orgScoped(organizations), title: text('title') })
|
|
34
|
-
*/
|
|
35
|
-
export const orgScoped = (orgs) => ({
|
|
36
|
-
organizationId: uuid('organization_id')
|
|
37
|
-
.notNull()
|
|
38
|
-
.references(() => orgs.id, { onDelete: 'cascade' }),
|
|
39
|
-
});
|
|
40
|
-
// ── Identity ────────────────────────────────────────────────────────────────
|
|
41
|
-
export const users = govcore.table('users', {
|
|
42
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
43
|
-
organizationId: uuid('organization_id')
|
|
44
|
-
.notNull()
|
|
45
|
-
.references(() => organizations.id, { onDelete: 'cascade' }),
|
|
46
|
-
/** Last-selected active org, honored first by active-membership resolution. */
|
|
47
|
-
lastActiveOrganizationId: uuid('last_active_organization_id').references(() => organizations.id, { onDelete: 'set null' }),
|
|
48
|
-
name: text('name'),
|
|
49
|
-
email: text('email').notNull(),
|
|
50
|
-
emailVerified: timestamp('email_verified'),
|
|
51
|
-
image: text('image'),
|
|
52
|
-
passwordHash: text('password_hash'),
|
|
53
|
-
/**
|
|
54
|
-
* App-defined role (see @govcore/rbac `createRbac`) — stored as `text`, not
|
|
55
|
-
* a fixed enum, so GovCore ships no role vocabulary of its own. Denormalized
|
|
56
|
-
* cache of the active membership's role; nullable so the Auth.js adapter can
|
|
57
|
-
* create a user before a membership is assigned.
|
|
58
|
-
*/
|
|
59
|
-
role: text('role'),
|
|
60
|
-
/** Platform-level role (e.g. instance/platform admin), separate from per-org role. */
|
|
61
|
-
instanceRole: text('instance_role'),
|
|
62
|
-
isActive: boolean('is_active').notNull().default(true),
|
|
63
|
-
// Account-lockout + password-expiry state; the policy itself lives app-side.
|
|
64
|
-
failedLoginAttempts: integer('failed_login_attempts').notNull().default(0),
|
|
65
|
-
lockoutUntil: timestamp('lockout_until'),
|
|
66
|
-
lastPasswordChangedAt: timestamp('last_password_changed_at').notNull().defaultNow(),
|
|
67
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
68
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
69
|
-
},
|
|
70
|
-
// One identity per email across all orgs — unambiguous auth/SSO lookups.
|
|
71
|
-
(t) => [uniqueIndex('users_email_unique').on(t.email)]);
|
|
72
|
-
// Auth.js (drizzle-adapter) tables. Not org-scoped; read during auth flows
|
|
73
|
-
// before a tenant context exists, so they are intentionally outside RLS.
|
|
74
|
-
export const accounts = govcore.table('accounts', {
|
|
75
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
76
|
-
userId: uuid('user_id')
|
|
77
|
-
.notNull()
|
|
78
|
-
.references(() => users.id, { onDelete: 'cascade' }),
|
|
79
|
-
type: text('type').notNull(),
|
|
80
|
-
provider: text('provider').notNull(),
|
|
81
|
-
providerAccountId: text('provider_account_id').notNull(),
|
|
82
|
-
refreshToken: text('refresh_token'),
|
|
83
|
-
accessToken: text('access_token'),
|
|
84
|
-
expiresAt: timestamp('expires_at'),
|
|
85
|
-
tokenType: text('token_type'),
|
|
86
|
-
scope: text('scope'),
|
|
87
|
-
idToken: text('id_token'),
|
|
88
|
-
sessionState: text('session_state'),
|
|
89
|
-
});
|
|
90
|
-
export const sessions = govcore.table('sessions', {
|
|
91
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
92
|
-
sessionToken: text('session_token').notNull().unique(),
|
|
93
|
-
userId: uuid('user_id')
|
|
94
|
-
.notNull()
|
|
95
|
-
.references(() => users.id, { onDelete: 'cascade' }),
|
|
96
|
-
expires: timestamp('expires').notNull(),
|
|
97
|
-
});
|
|
98
|
-
export const verificationTokens = govcore.table('verification_tokens', {
|
|
99
|
-
identifier: text('identifier').notNull(),
|
|
100
|
-
token: text('token').notNull(),
|
|
101
|
-
expires: timestamp('expires').notNull(),
|
|
102
|
-
});
|
|
103
|
-
// ── Membership model (the heart of tenancy) ─────────────────────────────────
|
|
104
|
-
export const userOrganizationMemberships = govcore.table('user_organization_memberships', {
|
|
105
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
106
|
-
userId: uuid('user_id')
|
|
107
|
-
.notNull()
|
|
108
|
-
.references(() => users.id, { onDelete: 'cascade' }),
|
|
109
|
-
organizationId: uuid('organization_id')
|
|
110
|
-
.notNull()
|
|
111
|
-
.references(() => organizations.id, { onDelete: 'cascade' }),
|
|
112
|
-
/** App-defined role for this membership (text, not an enum — see `users.role`). */
|
|
113
|
-
role: text('role').notNull(),
|
|
114
|
-
/** Revocation soft-deactivates so the historical row survives for audit. */
|
|
115
|
-
isActive: boolean('is_active').notNull().default(true),
|
|
116
|
-
isPrimary: boolean('is_primary').notNull().default(false),
|
|
117
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
118
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
119
|
-
}, (t) => [uniqueIndex('user_org_membership_unique').on(t.userId, t.organizationId)]);
|
|
120
|
-
// ── Audit (append-only; immutability + RLS in ../migrations/0001) ───────────
|
|
121
|
-
export const auditLog = govcore.table('audit_log', {
|
|
122
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
123
|
-
organizationId: uuid('organization_id'), // historical UUID — no FK on purpose
|
|
124
|
-
userId: uuid('user_id'), // historical UUID — no FK on purpose
|
|
125
|
-
action: text('action').notNull(),
|
|
126
|
-
entityType: text('entity_type').notNull(),
|
|
127
|
-
entityId: uuid('entity_id'),
|
|
128
|
-
before: jsonb('before'),
|
|
129
|
-
after: jsonb('after'),
|
|
130
|
-
metadata: jsonb('metadata'),
|
|
131
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
132
|
-
});
|
|
133
|
-
// ── Federation (cross-organization connections and content links) ──────────
|
|
134
|
-
/** Explicit bilateral connection between two organizations. */
|
|
135
|
-
export const orgConnections = govcore.table('org_connections', {
|
|
136
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
137
|
-
fromOrgId: uuid('from_org_id')
|
|
138
|
-
.notNull()
|
|
139
|
-
.references(() => organizations.id, { onDelete: 'cascade' }),
|
|
140
|
-
toOrgId: uuid('to_org_id')
|
|
141
|
-
.notNull()
|
|
142
|
-
.references(() => organizations.id, { onDelete: 'cascade' }),
|
|
143
|
-
status: federationStatus('status').notNull().default('pending'),
|
|
144
|
-
createdBy: uuid('created_by').references(() => users.id),
|
|
145
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
146
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
147
|
-
}, (t) => [unique('unique_org_connection').on(t.fromOrgId, t.toOrgId)]);
|
|
148
|
-
/** Approved relationship between content items across orgs. No FK on entity ids
|
|
149
|
-
* (they cross org boundaries); link semantics are app-defined (`link_type` text). */
|
|
150
|
-
export const crossOrgLinks = govcore.table('cross_org_links', {
|
|
151
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
152
|
-
sourceOrgId: uuid('source_org_id')
|
|
153
|
-
.notNull()
|
|
154
|
-
.references(() => organizations.id, { onDelete: 'cascade' }),
|
|
155
|
-
sourceEntityType: text('source_entity_type').notNull(),
|
|
156
|
-
sourceEntityId: uuid('source_entity_id').notNull(),
|
|
157
|
-
targetOrgId: uuid('target_org_id')
|
|
158
|
-
.notNull()
|
|
159
|
-
.references(() => organizations.id, { onDelete: 'cascade' }),
|
|
160
|
-
targetEntityType: text('target_entity_type').notNull(),
|
|
161
|
-
targetEntityId: uuid('target_entity_id').notNull(),
|
|
162
|
-
linkType: text('link_type').notNull(),
|
|
163
|
-
status: federationStatus('status').notNull().default('pending'),
|
|
164
|
-
rejectionReason: text('rejection_reason'),
|
|
165
|
-
flaggedForReview: boolean('flagged_for_review').notNull().default(false),
|
|
166
|
-
flagReason: text('flag_reason'),
|
|
167
|
-
createdBy: uuid('created_by').references(() => users.id),
|
|
168
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
169
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
170
|
-
});
|
|
171
|
-
// ── Support access (break-glass + act-as) ──────────────────────────────────
|
|
172
|
-
// Instance-operator constructs that deliberately cross the tenant boundary, so
|
|
173
|
-
// they are NOT under the org-GUC RLS (the actor is an instance admin operating
|
|
174
|
-
// across orgs). Authorization is enforced in @govcore/support (design §6.6).
|
|
175
|
-
export const breakGlassSessions = govcore.table('break_glass_sessions', {
|
|
176
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
177
|
-
instanceAdminId: uuid('instance_admin_id')
|
|
178
|
-
.notNull()
|
|
179
|
-
.references(() => users.id),
|
|
180
|
-
targetOrgId: uuid('target_org_id')
|
|
181
|
-
.notNull()
|
|
182
|
-
.references(() => organizations.id),
|
|
183
|
-
reason: text('reason').notNull(),
|
|
184
|
-
grantedAt: timestamp('granted_at').notNull().defaultNow(),
|
|
185
|
-
expiresAt: timestamp('expires_at').notNull(),
|
|
186
|
-
requiresApproval: boolean('requires_approval').notNull().default(false),
|
|
187
|
-
approvedAt: timestamp('approved_at'),
|
|
188
|
-
approvedBy: uuid('approved_by').references(() => users.id),
|
|
189
|
-
revokedAt: timestamp('revoked_at'),
|
|
190
|
-
revokedBy: uuid('revoked_by').references(() => users.id),
|
|
191
|
-
});
|
|
192
|
-
export const actAsSessions = govcore.table('act_as_sessions', {
|
|
193
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
194
|
-
breakGlassSessionId: uuid('break_glass_session_id')
|
|
195
|
-
.notNull()
|
|
196
|
-
.references(() => breakGlassSessions.id),
|
|
197
|
-
instanceAdminId: uuid('instance_admin_id')
|
|
198
|
-
.notNull()
|
|
199
|
-
.references(() => users.id),
|
|
200
|
-
targetOrgId: uuid('target_org_id')
|
|
201
|
-
.notNull()
|
|
202
|
-
.references(() => organizations.id),
|
|
203
|
-
startedAt: timestamp('started_at').notNull().defaultNow(),
|
|
204
|
-
expiresAt: timestamp('expires_at').notNull(),
|
|
205
|
-
endedAt: timestamp('ended_at'),
|
|
206
|
-
endReason: text('end_reason'),
|
|
207
|
-
});
|
|
208
|
-
export const ACT_AS_DEFAULT_TTL_MINUTES = 30;
|
|
209
|
-
export const ACT_AS_END_REASONS = [
|
|
210
|
-
'admin_ended',
|
|
211
|
-
'expired',
|
|
212
|
-
'parent_revoked',
|
|
213
|
-
'parent_expired',
|
|
214
|
-
];
|
|
215
|
-
// ── Instance / platform configuration (singletons; not org-scoped) ─────────
|
|
216
|
-
export const instanceSettings = govcore.table('instance_settings', {
|
|
217
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
218
|
-
disabledModules: jsonb('disabled_modules').$type().notNull().default({}),
|
|
219
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
220
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
221
|
-
});
|
|
222
|
-
export const platformConfig = govcore.table('platform_config', {
|
|
223
|
-
id: text('id').primaryKey().default('singleton'),
|
|
224
|
-
instanceName: text('instance_name').notNull().default('GovCore'),
|
|
225
|
-
defaultTheme: text('default_theme').notNull().default('base'),
|
|
226
|
-
allowLocalAuth: boolean('allow_local_auth').notNull().default(true),
|
|
227
|
-
/** Stamped onto new orgs at provisioning time. Null means no tier. */
|
|
228
|
-
defaultSupportTier: text('default_support_tier'),
|
|
229
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
230
|
-
updatedBy: uuid('updated_by').references(() => users.id, { onDelete: 'set null' }),
|
|
231
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// @govcore/schema — platform table definitions + schema version.
|
|
2
|
-
// Edge-safe entrypoint (no DB client). The migrate runner is ./migrate.
|
|
3
|
-
|
|
4
|
-
import type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core'
|
|
5
|
-
|
|
6
|
-
export * from './schema'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A Drizzle Postgres database **or** transaction handle. Functions in the
|
|
10
|
-
* tenancy/audit packages accept this so callers inject their own client (the
|
|
11
|
-
* app's `postgres-js` db, or a `tx` inside a transaction — which extends it).
|
|
12
|
-
*/
|
|
13
|
-
export type GovcoreDb = PgDatabase<PgQueryResultHKT, any, any>
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Bumped whenever the platform schema changes. Written to instance settings on
|
|
17
|
-
* boot for observability ("this instance runs platform schema vN", design §5),
|
|
18
|
-
* and stamped into backup archives (design §13.5). Not load-bearing for
|
|
19
|
-
* correctness — the migrations are — but useful for diagnostics.
|
|
20
|
-
*/
|
|
21
|
-
export const CORE_SCHEMA_VERSION = '0.0.0'
|
package/src/migrate.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
// @govcore/schema/migrate — the govcore-migrate runner.
|
|
2
|
-
//
|
|
3
|
-
// NOT edge-safe (uses node:fs + a postgres client). Applies the authored
|
|
4
|
-
// platform migrations in ../migrations in lexical order, each in its own
|
|
5
|
-
// transaction, tracked in `govcore.__govcore_migrations` — kept separate from
|
|
6
|
-
// the app's own Drizzle journal so the two migration streams never overlap
|
|
7
|
-
// (design §5).
|
|
8
|
-
//
|
|
9
|
-
// Runs as the OWNER / DDL role (design §13.2): set GOVCORE_MIGRATE_DATABASE_URL
|
|
10
|
-
// to the owning role's connection string; falls back to DATABASE_URL. The app
|
|
11
|
-
// runtime connects as a separate NON-OWNER role, which RLS binds.
|
|
12
|
-
|
|
13
|
-
import { readdirSync, readFileSync } from 'node:fs'
|
|
14
|
-
import { dirname, join } from 'node:path'
|
|
15
|
-
import { fileURLToPath } from 'node:url'
|
|
16
|
-
import postgres from 'postgres'
|
|
17
|
-
|
|
18
|
-
const MIGRATIONS_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'migrations')
|
|
19
|
-
|
|
20
|
-
export interface MigrateOptions {
|
|
21
|
-
/** Connection string for the owner/DDL role. Defaults to env. */
|
|
22
|
-
connectionString?: string
|
|
23
|
-
/** Optional logger; defaults to console.log. */
|
|
24
|
-
log?: (message: string) => void
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Apply all pending platform migrations. Idempotent — already-applied files are skipped. */
|
|
28
|
-
export async function migrate(options: MigrateOptions = {}): Promise<{ applied: string[] }> {
|
|
29
|
-
const connectionString =
|
|
30
|
-
options.connectionString ??
|
|
31
|
-
process.env.GOVCORE_MIGRATE_DATABASE_URL ??
|
|
32
|
-
process.env.DATABASE_URL
|
|
33
|
-
if (!connectionString) {
|
|
34
|
-
throw new Error(
|
|
35
|
-
'govcore-migrate: set GOVCORE_MIGRATE_DATABASE_URL (owner role) or DATABASE_URL',
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
const log = options.log ?? ((m: string) => console.log(m))
|
|
39
|
-
const sql = postgres(connectionString, { max: 1 })
|
|
40
|
-
const applied: string[] = []
|
|
41
|
-
try {
|
|
42
|
-
await sql.unsafe('CREATE SCHEMA IF NOT EXISTS govcore')
|
|
43
|
-
await sql.unsafe(
|
|
44
|
-
`CREATE TABLE IF NOT EXISTS govcore.__govcore_migrations (
|
|
45
|
-
name text PRIMARY KEY,
|
|
46
|
-
applied_at timestamptz NOT NULL DEFAULT now()
|
|
47
|
-
)`,
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
const done = new Set(
|
|
51
|
-
(await sql`SELECT name FROM govcore.__govcore_migrations`).map((r) => r.name as string),
|
|
52
|
-
)
|
|
53
|
-
const files = readdirSync(MIGRATIONS_DIR)
|
|
54
|
-
.filter((f) => f.endsWith('.sql'))
|
|
55
|
-
.sort()
|
|
56
|
-
|
|
57
|
-
for (const file of files) {
|
|
58
|
-
if (done.has(file)) continue
|
|
59
|
-
const ddl = readFileSync(join(MIGRATIONS_DIR, file), 'utf8')
|
|
60
|
-
await sql.begin(async (tx) => {
|
|
61
|
-
await tx.unsafe(ddl)
|
|
62
|
-
await tx`INSERT INTO govcore.__govcore_migrations (name) VALUES (${file})`
|
|
63
|
-
})
|
|
64
|
-
applied.push(file)
|
|
65
|
-
log(`govcore-migrate: applied ${file}`)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
log(
|
|
69
|
-
applied.length
|
|
70
|
-
? `govcore-migrate: applied ${applied.length} migration(s)`
|
|
71
|
-
: 'govcore-migrate: already up to date',
|
|
72
|
-
)
|
|
73
|
-
return { applied }
|
|
74
|
-
} finally {
|
|
75
|
-
await sql.end()
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Run when invoked as the `govcore-migrate` bin.
|
|
80
|
-
if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
|
|
81
|
-
migrate().catch((err) => {
|
|
82
|
-
console.error(err)
|
|
83
|
-
process.exit(1)
|
|
84
|
-
})
|
|
85
|
-
}
|
package/src/schema.ts
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
// @govcore/schema — platform table definitions (identity, tenancy, auth, audit).
|
|
2
|
-
//
|
|
3
|
-
// Edge-safe: imports only `drizzle-orm/pg-core`, no DB client. The app imports
|
|
4
|
-
// these for type-safe queries and to declare foreign keys *to* platform tables.
|
|
5
|
-
// The govcore-migrate runner (./migrate) is a separate, non-edge entrypoint.
|
|
6
|
-
//
|
|
7
|
-
// All platform tables live in a dedicated `govcore` Postgres schema (design
|
|
8
|
-
// §13.4) so they never collide with an app's `public` tables and ownership is
|
|
9
|
-
// obvious. The authored DDL lives in ../migrations and must stay in sync.
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
type AnyPgColumn,
|
|
13
|
-
boolean,
|
|
14
|
-
integer,
|
|
15
|
-
jsonb,
|
|
16
|
-
pgSchema,
|
|
17
|
-
text,
|
|
18
|
-
timestamp,
|
|
19
|
-
unique,
|
|
20
|
-
uniqueIndex,
|
|
21
|
-
uuid,
|
|
22
|
-
} from 'drizzle-orm/pg-core'
|
|
23
|
-
|
|
24
|
-
/** The Postgres schema every GovCore platform table belongs to. */
|
|
25
|
-
export const govcore = pgSchema('govcore')
|
|
26
|
-
|
|
27
|
-
/** Content/federation visibility. Used by the federation package (later phase). */
|
|
28
|
-
export const visibility = govcore.enum('visibility', ['org', 'connections', 'instance'])
|
|
29
|
-
|
|
30
|
-
/** Generic workflow status shared by federation connections and links. */
|
|
31
|
-
export const federationStatus = govcore.enum('federation_status', ['pending', 'active', 'rejected'])
|
|
32
|
-
|
|
33
|
-
// ── Tenancy root ────────────────────────────────────────────────────────────
|
|
34
|
-
|
|
35
|
-
export const organizations = govcore.table(
|
|
36
|
-
'organizations',
|
|
37
|
-
{
|
|
38
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
39
|
-
name: text('name').notNull(),
|
|
40
|
-
slug: text('slug').notNull(),
|
|
41
|
-
/** App-extensible bag for org settings GovCore itself doesn't model. */
|
|
42
|
-
metadata: jsonb('metadata').notNull().default({}),
|
|
43
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
44
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
45
|
-
},
|
|
46
|
-
(t) => [uniqueIndex('organizations_slug_unique').on(t.slug)],
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* The tenancy column contract (design §5): every tenant-scoped table — core's
|
|
51
|
-
* and the app's own domain tables — carries `organization_id` referencing
|
|
52
|
-
* `organizations`. RLS policies (see ../migrations/0001) key off this column.
|
|
53
|
-
*
|
|
54
|
-
* @example
|
|
55
|
-
* export const permits = pgTable('permits', { ...orgScoped(organizations), title: text('title') })
|
|
56
|
-
*/
|
|
57
|
-
export const orgScoped = (orgs: typeof organizations) => ({
|
|
58
|
-
organizationId: uuid('organization_id')
|
|
59
|
-
.notNull()
|
|
60
|
-
.references((): AnyPgColumn => orgs.id, { onDelete: 'cascade' }),
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
// ── Identity ────────────────────────────────────────────────────────────────
|
|
64
|
-
|
|
65
|
-
export const users = govcore.table(
|
|
66
|
-
'users',
|
|
67
|
-
{
|
|
68
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
69
|
-
organizationId: uuid('organization_id')
|
|
70
|
-
.notNull()
|
|
71
|
-
.references(() => organizations.id, { onDelete: 'cascade' }),
|
|
72
|
-
/** Last-selected active org, honored first by active-membership resolution. */
|
|
73
|
-
lastActiveOrganizationId: uuid('last_active_organization_id').references(
|
|
74
|
-
(): AnyPgColumn => organizations.id,
|
|
75
|
-
{ onDelete: 'set null' },
|
|
76
|
-
),
|
|
77
|
-
name: text('name'),
|
|
78
|
-
email: text('email').notNull(),
|
|
79
|
-
emailVerified: timestamp('email_verified'),
|
|
80
|
-
image: text('image'),
|
|
81
|
-
passwordHash: text('password_hash'),
|
|
82
|
-
/**
|
|
83
|
-
* App-defined role (see @govcore/rbac `createRbac`) — stored as `text`, not
|
|
84
|
-
* a fixed enum, so GovCore ships no role vocabulary of its own. Denormalized
|
|
85
|
-
* cache of the active membership's role; nullable so the Auth.js adapter can
|
|
86
|
-
* create a user before a membership is assigned.
|
|
87
|
-
*/
|
|
88
|
-
role: text('role'),
|
|
89
|
-
/** Platform-level role (e.g. instance/platform admin), separate from per-org role. */
|
|
90
|
-
instanceRole: text('instance_role'),
|
|
91
|
-
isActive: boolean('is_active').notNull().default(true),
|
|
92
|
-
// Account-lockout + password-expiry state; the policy itself lives app-side.
|
|
93
|
-
failedLoginAttempts: integer('failed_login_attempts').notNull().default(0),
|
|
94
|
-
lockoutUntil: timestamp('lockout_until'),
|
|
95
|
-
lastPasswordChangedAt: timestamp('last_password_changed_at').notNull().defaultNow(),
|
|
96
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
97
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
98
|
-
},
|
|
99
|
-
// One identity per email across all orgs — unambiguous auth/SSO lookups.
|
|
100
|
-
(t) => [uniqueIndex('users_email_unique').on(t.email)],
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
// Auth.js (drizzle-adapter) tables. Not org-scoped; read during auth flows
|
|
104
|
-
// before a tenant context exists, so they are intentionally outside RLS.
|
|
105
|
-
|
|
106
|
-
export const accounts = govcore.table('accounts', {
|
|
107
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
108
|
-
userId: uuid('user_id')
|
|
109
|
-
.notNull()
|
|
110
|
-
.references(() => users.id, { onDelete: 'cascade' }),
|
|
111
|
-
type: text('type').notNull(),
|
|
112
|
-
provider: text('provider').notNull(),
|
|
113
|
-
providerAccountId: text('provider_account_id').notNull(),
|
|
114
|
-
refreshToken: text('refresh_token'),
|
|
115
|
-
accessToken: text('access_token'),
|
|
116
|
-
expiresAt: timestamp('expires_at'),
|
|
117
|
-
tokenType: text('token_type'),
|
|
118
|
-
scope: text('scope'),
|
|
119
|
-
idToken: text('id_token'),
|
|
120
|
-
sessionState: text('session_state'),
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
export const sessions = govcore.table('sessions', {
|
|
124
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
125
|
-
sessionToken: text('session_token').notNull().unique(),
|
|
126
|
-
userId: uuid('user_id')
|
|
127
|
-
.notNull()
|
|
128
|
-
.references(() => users.id, { onDelete: 'cascade' }),
|
|
129
|
-
expires: timestamp('expires').notNull(),
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
export const verificationTokens = govcore.table('verification_tokens', {
|
|
133
|
-
identifier: text('identifier').notNull(),
|
|
134
|
-
token: text('token').notNull(),
|
|
135
|
-
expires: timestamp('expires').notNull(),
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
// ── Membership model (the heart of tenancy) ─────────────────────────────────
|
|
139
|
-
|
|
140
|
-
export const userOrganizationMemberships = govcore.table(
|
|
141
|
-
'user_organization_memberships',
|
|
142
|
-
{
|
|
143
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
144
|
-
userId: uuid('user_id')
|
|
145
|
-
.notNull()
|
|
146
|
-
.references(() => users.id, { onDelete: 'cascade' }),
|
|
147
|
-
organizationId: uuid('organization_id')
|
|
148
|
-
.notNull()
|
|
149
|
-
.references(() => organizations.id, { onDelete: 'cascade' }),
|
|
150
|
-
/** App-defined role for this membership (text, not an enum — see `users.role`). */
|
|
151
|
-
role: text('role').notNull(),
|
|
152
|
-
/** Revocation soft-deactivates so the historical row survives for audit. */
|
|
153
|
-
isActive: boolean('is_active').notNull().default(true),
|
|
154
|
-
isPrimary: boolean('is_primary').notNull().default(false),
|
|
155
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
156
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
157
|
-
},
|
|
158
|
-
(t) => [uniqueIndex('user_org_membership_unique').on(t.userId, t.organizationId)],
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
// ── Audit (append-only; immutability + RLS in ../migrations/0001) ───────────
|
|
162
|
-
|
|
163
|
-
export const auditLog = govcore.table('audit_log', {
|
|
164
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
165
|
-
organizationId: uuid('organization_id'), // historical UUID — no FK on purpose
|
|
166
|
-
userId: uuid('user_id'), // historical UUID — no FK on purpose
|
|
167
|
-
action: text('action').notNull(),
|
|
168
|
-
entityType: text('entity_type').notNull(),
|
|
169
|
-
entityId: uuid('entity_id'),
|
|
170
|
-
before: jsonb('before'),
|
|
171
|
-
after: jsonb('after'),
|
|
172
|
-
metadata: jsonb('metadata'),
|
|
173
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
// ── Federation (cross-organization connections and content links) ──────────
|
|
177
|
-
|
|
178
|
-
/** Explicit bilateral connection between two organizations. */
|
|
179
|
-
export const orgConnections = govcore.table(
|
|
180
|
-
'org_connections',
|
|
181
|
-
{
|
|
182
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
183
|
-
fromOrgId: uuid('from_org_id')
|
|
184
|
-
.notNull()
|
|
185
|
-
.references(() => organizations.id, { onDelete: 'cascade' }),
|
|
186
|
-
toOrgId: uuid('to_org_id')
|
|
187
|
-
.notNull()
|
|
188
|
-
.references(() => organizations.id, { onDelete: 'cascade' }),
|
|
189
|
-
status: federationStatus('status').notNull().default('pending'),
|
|
190
|
-
createdBy: uuid('created_by').references(() => users.id),
|
|
191
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
192
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
193
|
-
},
|
|
194
|
-
(t) => [unique('unique_org_connection').on(t.fromOrgId, t.toOrgId)],
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
/** Approved relationship between content items across orgs. No FK on entity ids
|
|
198
|
-
* (they cross org boundaries); link semantics are app-defined (`link_type` text). */
|
|
199
|
-
export const crossOrgLinks = govcore.table('cross_org_links', {
|
|
200
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
201
|
-
sourceOrgId: uuid('source_org_id')
|
|
202
|
-
.notNull()
|
|
203
|
-
.references(() => organizations.id, { onDelete: 'cascade' }),
|
|
204
|
-
sourceEntityType: text('source_entity_type').notNull(),
|
|
205
|
-
sourceEntityId: uuid('source_entity_id').notNull(),
|
|
206
|
-
targetOrgId: uuid('target_org_id')
|
|
207
|
-
.notNull()
|
|
208
|
-
.references(() => organizations.id, { onDelete: 'cascade' }),
|
|
209
|
-
targetEntityType: text('target_entity_type').notNull(),
|
|
210
|
-
targetEntityId: uuid('target_entity_id').notNull(),
|
|
211
|
-
linkType: text('link_type').notNull(),
|
|
212
|
-
status: federationStatus('status').notNull().default('pending'),
|
|
213
|
-
rejectionReason: text('rejection_reason'),
|
|
214
|
-
flaggedForReview: boolean('flagged_for_review').notNull().default(false),
|
|
215
|
-
flagReason: text('flag_reason'),
|
|
216
|
-
createdBy: uuid('created_by').references(() => users.id),
|
|
217
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
218
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
// ── Support access (break-glass + act-as) ──────────────────────────────────
|
|
222
|
-
// Instance-operator constructs that deliberately cross the tenant boundary, so
|
|
223
|
-
// they are NOT under the org-GUC RLS (the actor is an instance admin operating
|
|
224
|
-
// across orgs). Authorization is enforced in @govcore/support (design §6.6).
|
|
225
|
-
|
|
226
|
-
export const breakGlassSessions = govcore.table('break_glass_sessions', {
|
|
227
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
228
|
-
instanceAdminId: uuid('instance_admin_id')
|
|
229
|
-
.notNull()
|
|
230
|
-
.references(() => users.id),
|
|
231
|
-
targetOrgId: uuid('target_org_id')
|
|
232
|
-
.notNull()
|
|
233
|
-
.references(() => organizations.id),
|
|
234
|
-
reason: text('reason').notNull(),
|
|
235
|
-
grantedAt: timestamp('granted_at').notNull().defaultNow(),
|
|
236
|
-
expiresAt: timestamp('expires_at').notNull(),
|
|
237
|
-
requiresApproval: boolean('requires_approval').notNull().default(false),
|
|
238
|
-
approvedAt: timestamp('approved_at'),
|
|
239
|
-
approvedBy: uuid('approved_by').references(() => users.id),
|
|
240
|
-
revokedAt: timestamp('revoked_at'),
|
|
241
|
-
revokedBy: uuid('revoked_by').references(() => users.id),
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
export const actAsSessions = govcore.table('act_as_sessions', {
|
|
245
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
246
|
-
breakGlassSessionId: uuid('break_glass_session_id')
|
|
247
|
-
.notNull()
|
|
248
|
-
.references(() => breakGlassSessions.id),
|
|
249
|
-
instanceAdminId: uuid('instance_admin_id')
|
|
250
|
-
.notNull()
|
|
251
|
-
.references(() => users.id),
|
|
252
|
-
targetOrgId: uuid('target_org_id')
|
|
253
|
-
.notNull()
|
|
254
|
-
.references(() => organizations.id),
|
|
255
|
-
startedAt: timestamp('started_at').notNull().defaultNow(),
|
|
256
|
-
expiresAt: timestamp('expires_at').notNull(),
|
|
257
|
-
endedAt: timestamp('ended_at'),
|
|
258
|
-
endReason: text('end_reason'),
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
export const ACT_AS_DEFAULT_TTL_MINUTES = 30
|
|
262
|
-
export const ACT_AS_END_REASONS = [
|
|
263
|
-
'admin_ended',
|
|
264
|
-
'expired',
|
|
265
|
-
'parent_revoked',
|
|
266
|
-
'parent_expired',
|
|
267
|
-
] as const
|
|
268
|
-
export type ActAsEndReason = (typeof ACT_AS_END_REASONS)[number]
|
|
269
|
-
|
|
270
|
-
// ── Instance / platform configuration (singletons; not org-scoped) ─────────
|
|
271
|
-
|
|
272
|
-
export const instanceSettings = govcore.table('instance_settings', {
|
|
273
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
274
|
-
disabledModules: jsonb('disabled_modules').$type<Record<string, boolean>>().notNull().default({}),
|
|
275
|
-
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
276
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
export const platformConfig = govcore.table('platform_config', {
|
|
280
|
-
id: text('id').primaryKey().default('singleton'),
|
|
281
|
-
instanceName: text('instance_name').notNull().default('GovCore'),
|
|
282
|
-
defaultTheme: text('default_theme').notNull().default('base'),
|
|
283
|
-
allowLocalAuth: boolean('allow_local_auth').notNull().default(true),
|
|
284
|
-
/** Stamped onto new orgs at provisioning time. Null means no tier. */
|
|
285
|
-
defaultSupportTier: text('default_support_tier'),
|
|
286
|
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
287
|
-
updatedBy: uuid('updated_by').references(() => users.id, { onDelete: 'set null' }),
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
// ── Inferred types ──────────────────────────────────────────────────────────
|
|
291
|
-
|
|
292
|
-
export type Organization = typeof organizations.$inferSelect
|
|
293
|
-
export type NewOrganization = typeof organizations.$inferInsert
|
|
294
|
-
export type User = typeof users.$inferSelect
|
|
295
|
-
export type NewUser = typeof users.$inferInsert
|
|
296
|
-
export type UserOrganizationMembership = typeof userOrganizationMemberships.$inferSelect
|
|
297
|
-
export type NewUserOrganizationMembership = typeof userOrganizationMemberships.$inferInsert
|
|
298
|
-
export type AuditEntry = typeof auditLog.$inferSelect
|
|
299
|
-
export type NewAuditEntry = typeof auditLog.$inferInsert
|
|
300
|
-
export type OrgConnection = typeof orgConnections.$inferSelect
|
|
301
|
-
export type NewOrgConnection = typeof orgConnections.$inferInsert
|
|
302
|
-
export type CrossOrgLink = typeof crossOrgLinks.$inferSelect
|
|
303
|
-
export type NewCrossOrgLink = typeof crossOrgLinks.$inferInsert
|
|
304
|
-
export type BreakGlassSession = typeof breakGlassSessions.$inferSelect
|
|
305
|
-
export type NewBreakGlassSession = typeof breakGlassSessions.$inferInsert
|
|
306
|
-
export type ActAsSession = typeof actAsSessions.$inferSelect
|
|
307
|
-
export type NewActAsSession = typeof actAsSessions.$inferInsert
|
|
308
|
-
export type InstanceSettings = typeof instanceSettings.$inferSelect
|
|
309
|
-
export type NewInstanceSettings = typeof instanceSettings.$inferInsert
|
|
310
|
-
export type PlatformConfig = typeof platformConfig.$inferSelect
|
|
311
|
-
export type NewPlatformConfig = typeof platformConfig.$inferInsert
|