@coffre/db 0.3.0 → 0.4.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/connect.d.ts +1 -1
- package/dist/{database-Cq_52uUo.d.ts → database-edkMkG02.d.ts} +1 -1
- package/dist/dialect.d.ts +10 -2
- package/dist/dialect.js +34 -1
- package/dist/grants.d.ts +32 -0
- package/dist/grants.js +27 -0
- package/dist/hyperdrive.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/log.d.ts +1 -1
- package/dist/migrate.js +6 -1
- package/dist/migrations/postgres/0000_baseline.sql +174 -46
- package/dist/migrations/postgres/0001_mcp_approvals.sql +24 -0
- package/dist/migrations/postgres/meta/0000_snapshot.json +963 -185
- package/dist/migrations/postgres/meta/0001_snapshot.json +1171 -30
- package/dist/migrations/postgres/meta/_journal.json +3 -24
- package/dist/migrations/sqlite/0000_baseline.sql +142 -35
- package/dist/migrations/sqlite/0001_mcp_approvals.sql +19 -0
- package/dist/migrations/sqlite/meta/0000_snapshot.json +878 -235
- package/dist/migrations/sqlite/meta/0001_snapshot.json +1062 -57
- package/dist/migrations/sqlite/meta/_journal.json +3 -24
- package/dist/portable.d.ts +2 -2
- package/dist/{schema-Wt-p8AOh.d.ts → schema-BqCB3XFm.d.ts} +1153 -83
- package/dist/schema-sqlite.d.ts +1171 -60
- package/dist/schema-sqlite.js +173 -11
- package/dist/schema-version.d.ts +9 -13
- package/dist/schema-version.js +38 -90
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js +208 -15
- package/package.json +7 -2
- package/src/baseline/postgres.sql +46 -10
- package/src/baseline/sqlite.sql +15 -0
- package/src/baseline.ts +7 -3
- package/src/dialect.ts +25 -1
- package/src/grants.ts +51 -0
- package/src/migrate.ts +5 -0
- package/src/migrations/postgres/0000_baseline.sql +174 -46
- package/src/migrations/postgres/0001_mcp_approvals.sql +24 -0
- package/src/migrations/postgres/meta/0000_snapshot.json +963 -185
- package/src/migrations/postgres/meta/0001_snapshot.json +1171 -30
- package/src/migrations/postgres/meta/_journal.json +3 -24
- package/src/migrations/sqlite/0000_baseline.sql +142 -35
- package/src/migrations/sqlite/0001_mcp_approvals.sql +19 -0
- package/src/migrations/sqlite/meta/0000_snapshot.json +878 -235
- package/src/migrations/sqlite/meta/0001_snapshot.json +1062 -57
- package/src/migrations/sqlite/meta/_journal.json +3 -24
- package/src/schema-version.ts +26 -18
- package/src/schema.sqlite.ts +223 -8
- package/src/schema.ts +263 -11
- package/dist/migrations/postgres/0001_remove_syncs.sql +0 -13
- package/dist/migrations/postgres/0002_service_bindings.sql +0 -28
- package/dist/migrations/postgres/0003_exchanges.sql +0 -10
- package/dist/migrations/postgres/0004_live_indexes.sql +0 -3
- package/dist/migrations/postgres/meta/0002_snapshot.json +0 -1870
- package/dist/migrations/postgres/meta/0003_snapshot.json +0 -1917
- package/dist/migrations/postgres/meta/0004_snapshot.json +0 -2006
- package/dist/migrations/sqlite/0001_remove_syncs.sql +0 -13
- package/dist/migrations/sqlite/0002_service_bindings.sql +0 -23
- package/dist/migrations/sqlite/0003_exchanges.sql +0 -7
- package/dist/migrations/sqlite/0004_live_indexes.sql +0 -3
- package/dist/migrations/sqlite/meta/0002_snapshot.json +0 -1736
- package/dist/migrations/sqlite/meta/0003_snapshot.json +0 -1781
- package/dist/migrations/sqlite/meta/0004_snapshot.json +0 -1811
- package/src/migrations/postgres/0001_remove_syncs.sql +0 -13
- package/src/migrations/postgres/0002_service_bindings.sql +0 -28
- package/src/migrations/postgres/0003_exchanges.sql +0 -10
- package/src/migrations/postgres/0004_live_indexes.sql +0 -3
- package/src/migrations/postgres/meta/0002_snapshot.json +0 -1870
- package/src/migrations/postgres/meta/0003_snapshot.json +0 -1917
- package/src/migrations/postgres/meta/0004_snapshot.json +0 -2006
- package/src/migrations/sqlite/0001_remove_syncs.sql +0 -13
- package/src/migrations/sqlite/0002_service_bindings.sql +0 -23
- package/src/migrations/sqlite/0003_exchanges.sql +0 -7
- package/src/migrations/sqlite/0004_live_indexes.sql +0 -3
- package/src/migrations/sqlite/meta/0002_snapshot.json +0 -1736
- package/src/migrations/sqlite/meta/0003_snapshot.json +0 -1781
- package/src/migrations/sqlite/meta/0004_snapshot.json +0 -1811
package/dist/schema-sqlite.js
CHANGED
|
@@ -2,7 +2,7 @@ import { n as relationsOf, t as ACTIVE_SUBJECT } from "./relations-BfR3JjT1.js";
|
|
|
2
2
|
import { asPostgres } from "./portable.js";
|
|
3
3
|
import { sql } from "drizzle-orm";
|
|
4
4
|
import { ROLE_NAMES, assignableToEnvironment } from "@coffre/core/access";
|
|
5
|
-
import { blob, check, customType, foreignKey, index, integer, sqliteTable, text, unique, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
5
|
+
import { blob, check, customType, foreignKey, index, integer, primaryKey, sqliteTable, text, unique, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
6
6
|
//#region src/schema.sqlite.ts
|
|
7
7
|
/**
|
|
8
8
|
* The schema in schema.ts, for SQLite (through libsql). Same tables,
|
|
@@ -28,13 +28,26 @@ const createdAt = () => time("created_at").notNull().default(now);
|
|
|
28
28
|
const flag = (name) => integer(name, { mode: "boolean" });
|
|
29
29
|
/** `^[a-z0-9][a-z0-9-]{0,max-1}$`, in GLOB. */
|
|
30
30
|
const isSlug = (column, max) => sql`length(${column}) BETWEEN 1 AND ${sql.raw(String(max))} AND ${column} GLOB '[a-z0-9]*' AND ${column} NOT GLOB '*[^a-z0-9-]*'`;
|
|
31
|
+
/** A slug, or a tombstone's, `market~deleted-2026-10-05`: schema.ts's `PLACE_SLUG`, in GLOB. */
|
|
32
|
+
const isPlaceSlug = (column) => {
|
|
33
|
+
const base = sql`substr(${column}, 1, instr(${column}, '~') - 1)`;
|
|
34
|
+
const suffix = sql`substr(${column}, instr(${column}, '~') + 1)`;
|
|
35
|
+
return sql`(${isSlug(column, 63)}) OR (instr(${column}, '~') BETWEEN 2 AND 64 AND ${base} GLOB '[a-z0-9]*' AND ${base} NOT GLOB '*[^a-z0-9-]*' AND length(${suffix}) BETWEEN 1 AND 40 AND ${suffix} NOT GLOB '*[^a-z0-9-]*')`;
|
|
36
|
+
};
|
|
37
|
+
/** A folder's name; the app also refuses control characters, which GLOB has no class for. */
|
|
38
|
+
const folderCheck = (name, column) => check(name, sql`${column} IS NULL OR (length(${column}) BETWEEN 1 AND 64 AND ${column} NOT GLOB '*/*' AND ${column} = trim(${column}))`);
|
|
31
39
|
const projects = sqliteTable("projects", {
|
|
32
40
|
id: text().primaryKey(),
|
|
33
41
|
slug: text().notNull(),
|
|
34
42
|
name: text().notNull(),
|
|
35
43
|
createdAt: createdAt(),
|
|
36
|
-
archivedAt: time("archived_at")
|
|
37
|
-
|
|
44
|
+
archivedAt: time("archived_at"),
|
|
45
|
+
folder: text()
|
|
46
|
+
}, (table) => [
|
|
47
|
+
unique("projects_slug_key").on(table.slug),
|
|
48
|
+
check("projects_slug_check", isPlaceSlug(table.slug)),
|
|
49
|
+
folderCheck("projects_folder_check", table.folder)
|
|
50
|
+
]);
|
|
38
51
|
const environments = sqliteTable("environments", {
|
|
39
52
|
id: text().primaryKey(),
|
|
40
53
|
projectId: text("project_id").notNull(),
|
|
@@ -45,7 +58,7 @@ const environments = sqliteTable("environments", {
|
|
|
45
58
|
}, (table) => [
|
|
46
59
|
unique("environments_project_id_slug_key").on(table.projectId, table.slug),
|
|
47
60
|
unique("environments_project_scoped").on(table.id, table.projectId),
|
|
48
|
-
check("environments_slug_check",
|
|
61
|
+
check("environments_slug_check", isPlaceSlug(table.slug)),
|
|
49
62
|
foreignKey({
|
|
50
63
|
name: "environments_project_id_fkey",
|
|
51
64
|
columns: [table.projectId],
|
|
@@ -61,10 +74,12 @@ const secrets = sqliteTable("secrets", {
|
|
|
61
74
|
updatedAt: time("updated_at").notNull().default(now),
|
|
62
75
|
currentVersionId: text("current_version_id").references(() => secretVersions.id, { onDelete: "no action" }),
|
|
63
76
|
currentVersion: integer("current_version").notNull().default(0),
|
|
64
|
-
archivedAt: time("archived_at")
|
|
77
|
+
archivedAt: time("archived_at"),
|
|
78
|
+
folder: text()
|
|
65
79
|
}, (table) => [
|
|
66
80
|
unique("secrets_project_id_environment_id_key_key").on(table.projectId, table.environmentId, table.key),
|
|
67
81
|
check("secrets_key_check", sql`length(${table.key}) BETWEEN 1 AND 128 AND ${table.key} GLOB '[A-Za-z_]*' AND ${table.key} NOT GLOB '*[^A-Za-z0-9_]*'`),
|
|
82
|
+
folderCheck("secrets_folder_check", table.folder),
|
|
68
83
|
foreignKey({
|
|
69
84
|
name: "secrets_project_id_fkey",
|
|
70
85
|
columns: [table.projectId],
|
|
@@ -108,6 +123,25 @@ const secretVersions = sqliteTable("secret_versions", {
|
|
|
108
123
|
}).onDelete("restrict"),
|
|
109
124
|
index("secret_versions_secret_idx").on(table.secretId, table.version)
|
|
110
125
|
]);
|
|
126
|
+
const dismissedKeys = sqliteTable("dismissed_keys", {
|
|
127
|
+
environmentId: text("environment_id").notNull(),
|
|
128
|
+
key: text().notNull(),
|
|
129
|
+
dismissedAt: time("dismissed_at").notNull().default(now),
|
|
130
|
+
dismissedBy: text("dismissed_by").notNull(),
|
|
131
|
+
restoredAt: time("restored_at"),
|
|
132
|
+
restoredBy: text("restored_by")
|
|
133
|
+
}, (table) => [
|
|
134
|
+
primaryKey({
|
|
135
|
+
name: "dismissed_keys_pkey",
|
|
136
|
+
columns: [table.environmentId, table.key]
|
|
137
|
+
}),
|
|
138
|
+
check("dismissed_keys_key_check", sql`length(${table.key}) BETWEEN 1 AND 128 AND ${table.key} GLOB '[A-Za-z_]*' AND ${table.key} NOT GLOB '*[^A-Za-z0-9_]*'`),
|
|
139
|
+
foreignKey({
|
|
140
|
+
name: "dismissed_keys_environment_id_fkey",
|
|
141
|
+
columns: [table.environmentId],
|
|
142
|
+
foreignColumns: [environments.id]
|
|
143
|
+
}).onDelete("restrict")
|
|
144
|
+
]);
|
|
111
145
|
const ROLES = sql.raw(ROLE_NAMES.map((role) => `'${role}'`).join(", "));
|
|
112
146
|
const ENVIRONMENT_ROLES = sql.raw(ROLE_NAMES.filter(assignableToEnvironment).map((role) => `'${role}'`).join(", "));
|
|
113
147
|
const vaultMembers = sqliteTable("vault_members", {
|
|
@@ -123,7 +157,7 @@ const vaultMembers = sqliteTable("vault_members", {
|
|
|
123
157
|
mac: bytes("mac").notNull()
|
|
124
158
|
}, (table) => [
|
|
125
159
|
check("vault_members_mac_check", sql`octet_length(${table.mac}) = 32`),
|
|
126
|
-
check("vault_members_principal_check", sql`(${table.principal} GLOB 'user:?*' OR ${table.principal} GLOB 'token:?*'
|
|
160
|
+
check("vault_members_principal_check", sql`(${table.principal} GLOB 'user:?*' OR ${table.principal} GLOB 'token:?*')
|
|
127
161
|
AND substr(${table.principal}, instr(${table.principal}, ':') + 1, 1) <> ':'
|
|
128
162
|
AND instr(${table.principal}, ' ') = 0 AND instr(${table.principal}, char(9)) = 0
|
|
129
163
|
AND instr(${table.principal}, char(10)) = 0 AND instr(${table.principal}, char(13)) = 0`),
|
|
@@ -136,16 +170,20 @@ const vaultGrants = sqliteTable("vault_grants", {
|
|
|
136
170
|
principal: text().notNull(),
|
|
137
171
|
projectId: text("project_id"),
|
|
138
172
|
environmentId: text("environment_id"),
|
|
173
|
+
environmentSlug: text("environment_slug"),
|
|
139
174
|
role: text().notNull(),
|
|
140
175
|
expiresAt: integer("expires_at", { mode: "number" }),
|
|
141
176
|
grantedAt: integer("granted_at", { mode: "number" }).notNull(),
|
|
142
177
|
grantedBy: text("granted_by").notNull()
|
|
143
178
|
}, (table) => [
|
|
144
|
-
check("vault_grants_one_place", sql`(${table.projectId} IS NULL) <> (${table.environmentId} IS NULL)`),
|
|
179
|
+
check("vault_grants_one_place", sql`((${table.projectId} IS NULL) <> (${table.environmentId} IS NULL) AND ${table.environmentSlug} IS NULL) OR (${table.projectId} IS NULL AND ${table.environmentId} IS NULL)`),
|
|
180
|
+
check("vault_grants_environment_slug_check", sql`${table.environmentSlug} IS NULL OR (${isSlug(table.environmentSlug, 63)})`),
|
|
145
181
|
check("vault_grants_role_check", sql`${table.role} IN (${ROLES})`),
|
|
146
|
-
check("vault_grants_environment_role_check", sql
|
|
182
|
+
check("vault_grants_environment_role_check", sql`(${table.environmentId} IS NULL AND ${table.environmentSlug} IS NULL) OR ${table.role} IN (${ENVIRONMENT_ROLES})`),
|
|
147
183
|
unique("vault_grants_on_project").on(table.principal, table.projectId),
|
|
148
184
|
unique("vault_grants_on_environment").on(table.principal, table.environmentId),
|
|
185
|
+
unique("vault_grants_on_environment_slug").on(table.principal, table.environmentSlug),
|
|
186
|
+
uniqueIndex("vault_grants_on_every_project").on(table.principal).where(sql`${table.projectId} IS NULL AND ${table.environmentId} IS NULL AND ${table.environmentSlug} IS NULL`),
|
|
149
187
|
foreignKey({
|
|
150
188
|
name: "vault_grants_principal_fkey",
|
|
151
189
|
columns: [table.principal],
|
|
@@ -186,7 +224,7 @@ const auditLog = sqliteTable("audit_log", {
|
|
|
186
224
|
hash: bytes("hash").notNull()
|
|
187
225
|
}, (table) => [
|
|
188
226
|
check("audit_log_author_check", sql`${table.author} IN ('app', 'vault')`),
|
|
189
|
-
check("audit_log_actor_check", sql`${table.actor} GLOB 'user:?*' OR ${table.actor} GLOB 'token:?*' OR ${table.actor} GLOB '
|
|
227
|
+
check("audit_log_actor_check", sql`${table.actor} GLOB 'user:?*' OR ${table.actor} GLOB 'token:?*' OR ${table.actor} GLOB 'system:?*'`),
|
|
190
228
|
check("audit_log_decision_check", sql`${table.decision} IN ('allow', 'deny')`),
|
|
191
229
|
check("audit_log_metadata_check", sql`json_valid(${table.metadata})`),
|
|
192
230
|
check("audit_log_prev_hash_check", sql`octet_length(${table.prevHash}) = 32`),
|
|
@@ -226,7 +264,45 @@ const auditLog = sqliteTable("audit_log", {
|
|
|
226
264
|
index("audit_log_releases_idx").on(table.author, table.actor, table.action, table.decision, table.occurredAt),
|
|
227
265
|
index("audit_log_subject_idx").on(table.author, table.subjectPrincipal, table.seq),
|
|
228
266
|
index("audit_log_unbind_idx").on(sql`json_extract(${table.metadata}, '$.bindingId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.unbind' AND ${table.decision} = 'allow'`),
|
|
229
|
-
index("audit_log_exchange_idx").on(sql`json_extract(${table.metadata}, '$.credentialId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.exchange' AND ${table.decision} = 'allow'`)
|
|
267
|
+
index("audit_log_exchange_idx").on(sql`json_extract(${table.metadata}, '$.credentialId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.exchange' AND ${table.decision} = 'allow'`),
|
|
268
|
+
index("audit_log_reference_end_idx").on(table.relatedSeq).where(sql`${table.author} = 'vault' AND ${table.action} = 'reference.end' AND ${table.decision} = 'allow'`),
|
|
269
|
+
index("audit_log_also_project_idx").on(sql`json_extract(${table.metadata}, '$.also.projectId')`, table.seq).where(sql`json_extract(${table.metadata}, '$.also') IS NOT NULL`),
|
|
270
|
+
index("audit_log_also_environment_idx").on(sql`json_extract(${table.metadata}, '$.also.environmentId')`, table.seq).where(sql`json_extract(${table.metadata}, '$.also') IS NOT NULL`),
|
|
271
|
+
index("audit_log_also_secret_idx").on(sql`json_extract(${table.metadata}, '$.also.secretId')`, table.seq).where(sql`json_extract(${table.metadata}, '$.also') IS NOT NULL`)
|
|
272
|
+
]);
|
|
273
|
+
const secretReferences = sqliteTable("secret_references", {
|
|
274
|
+
id: text().primaryKey(),
|
|
275
|
+
projectId: text("project_id").notNull(),
|
|
276
|
+
environmentId: text("environment_id").notNull(),
|
|
277
|
+
secretId: text("secret_id").notNull(),
|
|
278
|
+
sourceProjectId: text("source_project_id").notNull(),
|
|
279
|
+
sourceEnvironmentId: text("source_environment_id").notNull(),
|
|
280
|
+
sourceSecretId: text("source_secret_id").notNull(),
|
|
281
|
+
createdSeq: int64("created_seq").notNull(),
|
|
282
|
+
createdAt: createdAt(),
|
|
283
|
+
createdBy: text("created_by").notNull()
|
|
284
|
+
}, (table) => [
|
|
285
|
+
unique("secret_references_created_seq_key").on(table.createdSeq),
|
|
286
|
+
foreignKey({
|
|
287
|
+
name: "secret_references_secret_id_fkey",
|
|
288
|
+
columns: [table.secretId],
|
|
289
|
+
foreignColumns: [secrets.id]
|
|
290
|
+
}).onDelete("restrict"),
|
|
291
|
+
foreignKey({
|
|
292
|
+
name: "secret_references_source_secret_id_fkey",
|
|
293
|
+
columns: [table.sourceSecretId],
|
|
294
|
+
foreignColumns: [secrets.id]
|
|
295
|
+
}).onDelete("restrict"),
|
|
296
|
+
foreignKey({
|
|
297
|
+
name: "secret_references_created_seq_fkey",
|
|
298
|
+
columns: [table.createdSeq],
|
|
299
|
+
foreignColumns: [auditLog.seq]
|
|
300
|
+
}).onDelete("restrict"),
|
|
301
|
+
index("secret_references_holder_idx").on(table.secretId, table.createdSeq),
|
|
302
|
+
index("secret_references_source_idx").on(table.sourceSecretId),
|
|
303
|
+
index("secret_references_project_idx").on(table.projectId),
|
|
304
|
+
index("secret_references_source_project_idx").on(table.sourceProjectId),
|
|
305
|
+
index("secret_references_created_by_idx").on(table.createdBy)
|
|
230
306
|
]);
|
|
231
307
|
const auditChainHead = sqliteTable("audit_chain_head", {
|
|
232
308
|
onlyRow: flag("only_row").primaryKey().default(true),
|
|
@@ -296,6 +372,92 @@ const consumedTokens = sqliteTable("consumed_tokens", {
|
|
|
296
372
|
hash: bytes("hash").primaryKey(),
|
|
297
373
|
consumedAt: time("consumed_at").notNull().default(now)
|
|
298
374
|
}, (table) => [check("consumed_tokens_hash_check", sql`octet_length(${table.hash}) = 32`)]);
|
|
375
|
+
/**
|
|
376
|
+
* MCP clients that registered themselves (RFC 7591), the fallback to Client
|
|
377
|
+
* ID Metadata Documents, which need no row. The ID is the client's
|
|
378
|
+
* `client_id`. Its redirects are under its MAC: a database writer cannot add
|
|
379
|
+
* one of their own to a client people have approved.
|
|
380
|
+
*/
|
|
381
|
+
const oauthClients = sqliteTable("oauth_clients", {
|
|
382
|
+
id: text().primaryKey(),
|
|
383
|
+
authMac: bytes("auth_mac").notNull(),
|
|
384
|
+
name: text().notNull(),
|
|
385
|
+
redirectUris: text("redirect_uris").notNull(),
|
|
386
|
+
createdAt: createdAt(),
|
|
387
|
+
createdIp: text("created_ip"),
|
|
388
|
+
revokedAt: time("revoked_at")
|
|
389
|
+
}, (table) => [check("oauth_clients_auth_mac_check", sql`octet_length(${table.authMac}) = 32`), check("oauth_clients_redirect_uris_check", sql`json_valid(${table.redirectUris})`)]);
|
|
390
|
+
/**
|
|
391
|
+
* One MCP client a person connected: one approved authorization, its
|
|
392
|
+
* scopes, its code until the client redeems it, and the refresh token it
|
|
393
|
+
* holds since. Access tokens have no row: each names its connection under a
|
|
394
|
+
* MAC, and every request checks this row. Revoking it ends them all.
|
|
395
|
+
*/
|
|
396
|
+
const mcpConnections = sqliteTable("mcp_connections", {
|
|
397
|
+
id: text().primaryKey(),
|
|
398
|
+
authMac: bytes("auth_mac").notNull(),
|
|
399
|
+
principal: text().notNull(),
|
|
400
|
+
generation: integer().notNull(),
|
|
401
|
+
clientId: text("client_id").notNull(),
|
|
402
|
+
clientName: text("client_name").notNull(),
|
|
403
|
+
clientHost: text("client_host"),
|
|
404
|
+
registration: text().notNull(),
|
|
405
|
+
scopes: text().notNull(),
|
|
406
|
+
redirectUri: text("redirect_uri").notNull(),
|
|
407
|
+
codeHash: bytes("code_hash"),
|
|
408
|
+
codeChallenge: text("code_challenge"),
|
|
409
|
+
codeExpiresAt: time("code_expires_at"),
|
|
410
|
+
refreshHash: bytes("refresh_hash"),
|
|
411
|
+
refreshPreviousHash: bytes("refresh_previous_hash"),
|
|
412
|
+
expiresAt: time("expires_at").notNull(),
|
|
413
|
+
createdAt: createdAt(),
|
|
414
|
+
lastUsedAt: time("last_used_at"),
|
|
415
|
+
lastUsedIp: text("last_used_ip"),
|
|
416
|
+
revokedAt: time("revoked_at"),
|
|
417
|
+
revokedBy: text("revoked_by")
|
|
418
|
+
}, (table) => [
|
|
419
|
+
check("mcp_connections_principal_check", sql`${table.principal} LIKE 'user:%'`),
|
|
420
|
+
check("mcp_connections_registration_check", sql`${table.registration} IN ('cimd', 'dcr')`),
|
|
421
|
+
check("mcp_connections_auth_mac_check", sql`octet_length(${table.authMac}) = 32`),
|
|
422
|
+
check("mcp_connections_code_hash_check", sql`${table.codeHash} IS NULL OR octet_length(${table.codeHash}) = 32`),
|
|
423
|
+
check("mcp_connections_refresh_hash_check", sql`${table.refreshHash} IS NULL OR octet_length(${table.refreshHash}) = 32`),
|
|
424
|
+
foreignKey({
|
|
425
|
+
name: "mcp_connections_principal_fkey",
|
|
426
|
+
columns: [table.principal],
|
|
427
|
+
foreignColumns: [vaultMembers.principal]
|
|
428
|
+
}).onDelete("restrict"),
|
|
429
|
+
unique("mcp_connections_code_hash_key").on(table.codeHash),
|
|
430
|
+
unique("mcp_connections_refresh_hash_key").on(table.refreshHash),
|
|
431
|
+
index("mcp_connections_refresh_previous_idx").on(table.refreshPreviousHash),
|
|
432
|
+
index("mcp_connections_live_idx").on(table.principal, table.expiresAt).where(sql`${table.revokedAt} IS NULL`)
|
|
433
|
+
]);
|
|
434
|
+
/**
|
|
435
|
+
* A change an MCP client asked for, waiting for its person on coffre's own
|
|
436
|
+
* page: see schema.ts.
|
|
437
|
+
*/
|
|
438
|
+
const mcpApprovals = sqliteTable("mcp_approvals", {
|
|
439
|
+
id: text().primaryKey(),
|
|
440
|
+
connectionId: text("connection_id").notNull(),
|
|
441
|
+
tool: text().notNull(),
|
|
442
|
+
arguments: text().notNull(),
|
|
443
|
+
digest: bytes("digest").notNull(),
|
|
444
|
+
status: text().notNull().default("pending"),
|
|
445
|
+
outcome: text(),
|
|
446
|
+
createdAt: createdAt(),
|
|
447
|
+
expiresAt: time("expires_at").notNull(),
|
|
448
|
+
decidedAt: time("decided_at"),
|
|
449
|
+
reportedAt: time("reported_at")
|
|
450
|
+
}, (table) => [
|
|
451
|
+
check("mcp_approvals_status_check", sql`${table.status} IN ('pending', 'approved', 'denied', 'cancelled', 'failed')`),
|
|
452
|
+
check("mcp_approvals_digest_check", sql`octet_length(${table.digest}) = 32`),
|
|
453
|
+
check("mcp_approvals_arguments_check", sql`json_valid(${table.arguments})`),
|
|
454
|
+
foreignKey({
|
|
455
|
+
name: "mcp_approvals_connection_fkey",
|
|
456
|
+
columns: [table.connectionId],
|
|
457
|
+
foreignColumns: [mcpConnections.id]
|
|
458
|
+
}).onDelete("restrict"),
|
|
459
|
+
index("mcp_approvals_open_idx").on(table.connectionId, table.digest).where(sql`${table.reportedAt} IS NULL`)
|
|
460
|
+
]);
|
|
299
461
|
const credentials = sqliteTable("credentials", {
|
|
300
462
|
id: text().primaryKey(),
|
|
301
463
|
kind: text().notNull(),
|
|
@@ -379,4 +541,4 @@ const { environmentsRelations, secretsRelations } = relationsOf(asPostgres({
|
|
|
379
541
|
secretVersions
|
|
380
542
|
}));
|
|
381
543
|
//#endregion
|
|
382
|
-
export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, environments, environmentsRelations, identities, projects, secretVersions, secrets, secretsRelations, serviceBindings, vaultGrants, vaultMembers };
|
|
544
|
+
export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, dismissedKeys, environments, environmentsRelations, identities, mcpApprovals, mcpConnections, oauthClients, projects, secretReferences, secretVersions, secrets, secretsRelations, serviceBindings, vaultGrants, vaultMembers };
|
package/dist/schema-version.d.ts
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
|
-
import { i as Queryable } from "./database-
|
|
1
|
+
import { i as Queryable } from "./database-edkMkG02.js";
|
|
2
2
|
import { Engine } from "./dialect.js";
|
|
3
3
|
//#region src/schema-version.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Minimum migration prefix required by this application image, per engine:
|
|
6
|
-
* Advance this only when the runtime needs the new schema. The sync-removal
|
|
7
|
-
* migration only drops unused tables: new code works with either prefix,
|
|
8
|
-
* so Workers Builds can deploy before an owner runs the migration.
|
|
9
|
-
* Applied migrations remain immutable (see baseline.ts).
|
|
10
|
-
*/
|
|
11
|
-
export declare const REQUIRED_MIGRATIONS: Record<Engine, number>;
|
|
12
|
-
export declare function requiredMigrations(db: Queryable): number;
|
|
13
4
|
/**
|
|
14
5
|
* Every migration this version ships, oldest first, per engine: read from
|
|
15
6
|
* the journals at build time, so a Worker, which has no files, knows them
|
|
16
|
-
* too.
|
|
17
|
-
* `REQUIRED_MIGRATIONS` lets it run on.
|
|
7
|
+
* too.
|
|
18
8
|
*/
|
|
19
9
|
export declare const KNOWN_MIGRATIONS: Record<Engine, readonly string[]>;
|
|
20
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Whether the database has applied every migration this version ships:
|
|
12
|
+
* below that, the app serves nothing but its readiness, which says so
|
|
13
|
+
* (`coffre migrate` runs before each deploy). A database never loses a
|
|
14
|
+
* migration, so once true it stays true.
|
|
15
|
+
*/
|
|
16
|
+
export declare function migrated(db: Queryable): Promise<boolean>;
|
|
21
17
|
//#endregion
|
package/dist/schema-version.js
CHANGED
|
@@ -1,109 +1,57 @@
|
|
|
1
|
-
import { engineOf } from "./dialect.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { engineOf, migrationLedger } from "./dialect.js";
|
|
2
|
+
import { count, sql } from "drizzle-orm";
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region src/schema-version.ts
|
|
5
|
+
/**
|
|
6
|
+
* Every migration this version ships, oldest first, per engine: read from
|
|
7
|
+
* the journals at build time, so a Worker, which has no files, knows them
|
|
8
|
+
* too.
|
|
9
|
+
*/
|
|
10
|
+
const KNOWN_MIGRATIONS = {
|
|
11
|
+
postgres: [{
|
|
5
12
|
"idx": 0,
|
|
6
13
|
"version": "7",
|
|
7
|
-
"when":
|
|
14
|
+
"when": 1791237720902,
|
|
8
15
|
"tag": "0000_baseline",
|
|
9
16
|
"breakpoints": true
|
|
10
|
-
},
|
|
11
|
-
{
|
|
17
|
+
}, {
|
|
12
18
|
"idx": 1,
|
|
13
19
|
"version": "7",
|
|
14
|
-
"when":
|
|
15
|
-
"tag": "
|
|
16
|
-
"breakpoints": true
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"idx": 2,
|
|
20
|
-
"version": "7",
|
|
21
|
-
"when": 1791069304538,
|
|
22
|
-
"tag": "0002_service_bindings",
|
|
20
|
+
"when": 1791242674488,
|
|
21
|
+
"tag": "0001_mcp_approvals",
|
|
23
22
|
"breakpoints": true
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"idx": 3,
|
|
27
|
-
"version": "7",
|
|
28
|
-
"when": 1791072336889,
|
|
29
|
-
"tag": "0003_exchanges",
|
|
30
|
-
"breakpoints": true
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"idx": 4,
|
|
34
|
-
"version": "7",
|
|
35
|
-
"when": 1791084354462,
|
|
36
|
-
"tag": "0004_live_indexes",
|
|
37
|
-
"breakpoints": true
|
|
38
|
-
}
|
|
39
|
-
];
|
|
40
|
-
//#endregion
|
|
41
|
-
//#region src/migrations/sqlite/meta/_journal.json
|
|
42
|
-
var entries = [
|
|
43
|
-
{
|
|
23
|
+
}].map((entry) => entry.tag),
|
|
24
|
+
sqlite: [{
|
|
44
25
|
"idx": 0,
|
|
45
26
|
"version": "6",
|
|
46
|
-
"when":
|
|
27
|
+
"when": 1791237724735,
|
|
47
28
|
"tag": "0000_baseline",
|
|
48
29
|
"breakpoints": true
|
|
49
|
-
},
|
|
50
|
-
{
|
|
30
|
+
}, {
|
|
51
31
|
"idx": 1,
|
|
52
32
|
"version": "6",
|
|
53
|
-
"when":
|
|
54
|
-
"tag": "
|
|
55
|
-
"breakpoints": true
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"idx": 2,
|
|
59
|
-
"version": "6",
|
|
60
|
-
"when": 1791069305749,
|
|
61
|
-
"tag": "0002_service_bindings",
|
|
33
|
+
"when": 1791242675074,
|
|
34
|
+
"tag": "0001_mcp_approvals",
|
|
62
35
|
"breakpoints": true
|
|
63
|
-
}
|
|
64
|
-
{
|
|
65
|
-
"idx": 3,
|
|
66
|
-
"version": "6",
|
|
67
|
-
"when": 1791072337548,
|
|
68
|
-
"tag": "0003_exchanges",
|
|
69
|
-
"breakpoints": true
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
"idx": 4,
|
|
73
|
-
"version": "6",
|
|
74
|
-
"when": 1791084355588,
|
|
75
|
-
"tag": "0004_live_indexes",
|
|
76
|
-
"breakpoints": true
|
|
77
|
-
}
|
|
78
|
-
];
|
|
79
|
-
//#endregion
|
|
80
|
-
//#region src/schema-version.ts
|
|
81
|
-
/**
|
|
82
|
-
* Minimum migration prefix required by this application image, per engine:
|
|
83
|
-
* Advance this only when the runtime needs the new schema. The sync-removal
|
|
84
|
-
* migration only drops unused tables: new code works with either prefix,
|
|
85
|
-
* so Workers Builds can deploy before an owner runs the migration.
|
|
86
|
-
* Applied migrations remain immutable (see baseline.ts).
|
|
87
|
-
*/
|
|
88
|
-
const REQUIRED_MIGRATIONS = {
|
|
89
|
-
postgres: 1,
|
|
90
|
-
sqlite: 1
|
|
36
|
+
}].map((entry) => entry.tag)
|
|
91
37
|
};
|
|
92
|
-
function requiredMigrations(db) {
|
|
93
|
-
return REQUIRED_MIGRATIONS[engineOf(db)];
|
|
94
|
-
}
|
|
95
38
|
/**
|
|
96
|
-
*
|
|
97
|
-
* the
|
|
98
|
-
*
|
|
99
|
-
*
|
|
39
|
+
* Whether the database has applied every migration this version ships:
|
|
40
|
+
* below that, the app serves nothing but its readiness, which says so
|
|
41
|
+
* (`coffre migrate` runs before each deploy). A database never loses a
|
|
42
|
+
* migration, so once true it stays true.
|
|
100
43
|
*/
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
44
|
+
async function migrated(db) {
|
|
45
|
+
if (!await hasLedger(db)) return false;
|
|
46
|
+
const [ledger] = await db.select({ n: count() }).from(migrationLedger(db));
|
|
47
|
+
return (ledger?.n ?? 0) >= KNOWN_MIGRATIONS[engineOf(db)].length;
|
|
48
|
+
}
|
|
49
|
+
async function hasLedger(db) {
|
|
50
|
+
if (engineOf(db) === "postgres") {
|
|
51
|
+
const { rows } = await db.execute(sql`SELECT to_regclass('drizzle.__drizzle_migrations') IS NOT NULL AS present`);
|
|
52
|
+
return rows[0]?.present === true;
|
|
53
|
+
}
|
|
54
|
+
return (await db.all(sql`SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = '__drizzle_migrations'`)).length > 0;
|
|
107
55
|
}
|
|
108
56
|
//#endregion
|
|
109
|
-
export { KNOWN_MIGRATIONS,
|
|
57
|
+
export { KNOWN_MIGRATIONS, migrated };
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as deviceAuthorizations, c as
|
|
2
|
-
export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, environments, environmentsRelations, identities, projects, secretVersions, secrets, secretsRelations, serviceBindings, vaultGrants, vaultMembers };
|
|
1
|
+
import { _ as secrets, a as deviceAuthorizations, b as vaultGrants, c as environmentsRelations, d as mcpConnections, f as oauthClients, g as secretVersions, h as secretReferences, i as credentials, l as identities, n as auditLog, o as dismissedKeys, p as projects, r as consumedTokens, s as environments, t as auditChainHead, u as mcpApprovals, v as secretsRelations, x as vaultMembers, y as serviceBindings } from "./schema-BqCB3XFm.js";
|
|
2
|
+
export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, dismissedKeys, environments, environmentsRelations, identities, mcpApprovals, mcpConnections, oauthClients, projects, secretReferences, secretVersions, secrets, secretsRelations, serviceBindings, vaultGrants, vaultMembers };
|