@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.
Files changed (76) hide show
  1. package/dist/connect.d.ts +1 -1
  2. package/dist/{database-Cq_52uUo.d.ts → database-edkMkG02.d.ts} +1 -1
  3. package/dist/dialect.d.ts +10 -2
  4. package/dist/dialect.js +34 -1
  5. package/dist/grants.d.ts +32 -0
  6. package/dist/grants.js +27 -0
  7. package/dist/hyperdrive.d.ts +1 -1
  8. package/dist/index.d.ts +2 -2
  9. package/dist/log.d.ts +1 -1
  10. package/dist/migrate.js +6 -1
  11. package/dist/migrations/postgres/0000_baseline.sql +174 -46
  12. package/dist/migrations/postgres/0001_mcp_approvals.sql +24 -0
  13. package/dist/migrations/postgres/meta/0000_snapshot.json +963 -185
  14. package/dist/migrations/postgres/meta/0001_snapshot.json +1171 -30
  15. package/dist/migrations/postgres/meta/_journal.json +3 -24
  16. package/dist/migrations/sqlite/0000_baseline.sql +142 -35
  17. package/dist/migrations/sqlite/0001_mcp_approvals.sql +19 -0
  18. package/dist/migrations/sqlite/meta/0000_snapshot.json +878 -235
  19. package/dist/migrations/sqlite/meta/0001_snapshot.json +1062 -57
  20. package/dist/migrations/sqlite/meta/_journal.json +3 -24
  21. package/dist/portable.d.ts +2 -2
  22. package/dist/{schema-Wt-p8AOh.d.ts → schema-BqCB3XFm.d.ts} +1153 -83
  23. package/dist/schema-sqlite.d.ts +1171 -60
  24. package/dist/schema-sqlite.js +173 -11
  25. package/dist/schema-version.d.ts +9 -13
  26. package/dist/schema-version.js +38 -90
  27. package/dist/schema.d.ts +2 -2
  28. package/dist/schema.js +208 -15
  29. package/package.json +7 -2
  30. package/src/baseline/postgres.sql +46 -10
  31. package/src/baseline/sqlite.sql +15 -0
  32. package/src/baseline.ts +7 -3
  33. package/src/dialect.ts +25 -1
  34. package/src/grants.ts +51 -0
  35. package/src/migrate.ts +5 -0
  36. package/src/migrations/postgres/0000_baseline.sql +174 -46
  37. package/src/migrations/postgres/0001_mcp_approvals.sql +24 -0
  38. package/src/migrations/postgres/meta/0000_snapshot.json +963 -185
  39. package/src/migrations/postgres/meta/0001_snapshot.json +1171 -30
  40. package/src/migrations/postgres/meta/_journal.json +3 -24
  41. package/src/migrations/sqlite/0000_baseline.sql +142 -35
  42. package/src/migrations/sqlite/0001_mcp_approvals.sql +19 -0
  43. package/src/migrations/sqlite/meta/0000_snapshot.json +878 -235
  44. package/src/migrations/sqlite/meta/0001_snapshot.json +1062 -57
  45. package/src/migrations/sqlite/meta/_journal.json +3 -24
  46. package/src/schema-version.ts +26 -18
  47. package/src/schema.sqlite.ts +223 -8
  48. package/src/schema.ts +263 -11
  49. package/dist/migrations/postgres/0001_remove_syncs.sql +0 -13
  50. package/dist/migrations/postgres/0002_service_bindings.sql +0 -28
  51. package/dist/migrations/postgres/0003_exchanges.sql +0 -10
  52. package/dist/migrations/postgres/0004_live_indexes.sql +0 -3
  53. package/dist/migrations/postgres/meta/0002_snapshot.json +0 -1870
  54. package/dist/migrations/postgres/meta/0003_snapshot.json +0 -1917
  55. package/dist/migrations/postgres/meta/0004_snapshot.json +0 -2006
  56. package/dist/migrations/sqlite/0001_remove_syncs.sql +0 -13
  57. package/dist/migrations/sqlite/0002_service_bindings.sql +0 -23
  58. package/dist/migrations/sqlite/0003_exchanges.sql +0 -7
  59. package/dist/migrations/sqlite/0004_live_indexes.sql +0 -3
  60. package/dist/migrations/sqlite/meta/0002_snapshot.json +0 -1736
  61. package/dist/migrations/sqlite/meta/0003_snapshot.json +0 -1781
  62. package/dist/migrations/sqlite/meta/0004_snapshot.json +0 -1811
  63. package/src/migrations/postgres/0001_remove_syncs.sql +0 -13
  64. package/src/migrations/postgres/0002_service_bindings.sql +0 -28
  65. package/src/migrations/postgres/0003_exchanges.sql +0 -10
  66. package/src/migrations/postgres/0004_live_indexes.sql +0 -3
  67. package/src/migrations/postgres/meta/0002_snapshot.json +0 -1870
  68. package/src/migrations/postgres/meta/0003_snapshot.json +0 -1917
  69. package/src/migrations/postgres/meta/0004_snapshot.json +0 -2006
  70. package/src/migrations/sqlite/0001_remove_syncs.sql +0 -13
  71. package/src/migrations/sqlite/0002_service_bindings.sql +0 -23
  72. package/src/migrations/sqlite/0003_exchanges.sql +0 -7
  73. package/src/migrations/sqlite/0004_live_indexes.sql +0 -3
  74. package/src/migrations/sqlite/meta/0002_snapshot.json +0 -1736
  75. package/src/migrations/sqlite/meta/0003_snapshot.json +0 -1781
  76. package/src/migrations/sqlite/meta/0004_snapshot.json +0 -1811
@@ -5,36 +5,15 @@
5
5
  {
6
6
  "idx": 0,
7
7
  "version": "7",
8
- "when": 1790930587507,
8
+ "when": 1791237720902,
9
9
  "tag": "0000_baseline",
10
10
  "breakpoints": true
11
11
  },
12
12
  {
13
13
  "idx": 1,
14
14
  "version": "7",
15
- "when": 1791062292678,
16
- "tag": "0001_remove_syncs",
17
- "breakpoints": true
18
- },
19
- {
20
- "idx": 2,
21
- "version": "7",
22
- "when": 1791069304538,
23
- "tag": "0002_service_bindings",
24
- "breakpoints": true
25
- },
26
- {
27
- "idx": 3,
28
- "version": "7",
29
- "when": 1791072336889,
30
- "tag": "0003_exchanges",
31
- "breakpoints": true
32
- },
33
- {
34
- "idx": 4,
35
- "version": "7",
36
- "when": 1791084354462,
37
- "tag": "0004_live_indexes",
15
+ "when": 1791242674488,
16
+ "tag": "0001_mcp_approvals",
38
17
  "breakpoints": true
39
18
  }
40
19
  ]
@@ -38,7 +38,7 @@ CREATE TABLE `audit_log` (
38
38
  FOREIGN KEY (`secret_version_id`) REFERENCES `secret_versions`(`id`) ON UPDATE no action ON DELETE restrict,
39
39
  FOREIGN KEY (`related_seq`) REFERENCES `audit_log`(`seq`) ON UPDATE no action ON DELETE restrict,
40
40
  CONSTRAINT "audit_log_author_check" CHECK("audit_log"."author" IN ('app', 'vault')),
41
- CONSTRAINT "audit_log_actor_check" CHECK("audit_log"."actor" GLOB 'user:?*' OR "audit_log"."actor" GLOB 'token:?*' OR "audit_log"."actor" GLOB 'sync:?*' OR "audit_log"."actor" GLOB 'system:?*'),
41
+ CONSTRAINT "audit_log_actor_check" CHECK("audit_log"."actor" GLOB 'user:?*' OR "audit_log"."actor" GLOB 'token:?*' OR "audit_log"."actor" GLOB 'system:?*'),
42
42
  CONSTRAINT "audit_log_decision_check" CHECK("audit_log"."decision" IN ('allow', 'deny')),
43
43
  CONSTRAINT "audit_log_metadata_check" CHECK(json_valid("audit_log"."metadata")),
44
44
  CONSTRAINT "audit_log_prev_hash_check" CHECK(octet_length("audit_log"."prev_hash") = 32),
@@ -54,6 +54,18 @@ CREATE INDEX `audit_log_operation_idx` ON `audit_log` (`operation_id`,`seq`);-->
54
54
  CREATE INDEX `audit_log_action_idx` ON `audit_log` (`author`,`action`,`seq`);--> statement-breakpoint
55
55
  CREATE INDEX `audit_log_releases_idx` ON `audit_log` (`author`,`actor`,`action`,`decision`,`occurred_at`);--> statement-breakpoint
56
56
  CREATE INDEX `audit_log_subject_idx` ON `audit_log` (`author`,`subject_principal`,`seq`);--> statement-breakpoint
57
+ CREATE INDEX `audit_log_unbind_idx` ON `audit_log` (json_extract("metadata", '$.bindingId')) WHERE "audit_log"."author" = 'app' AND "audit_log"."action" = 'token.unbind' AND "audit_log"."decision" = 'allow';--> statement-breakpoint
58
+ CREATE INDEX `audit_log_exchange_idx` ON `audit_log` (json_extract("metadata", '$.credentialId')) WHERE "audit_log"."author" = 'app' AND "audit_log"."action" = 'token.exchange' AND "audit_log"."decision" = 'allow';--> statement-breakpoint
59
+ CREATE INDEX `audit_log_reference_end_idx` ON `audit_log` (`related_seq`) WHERE "audit_log"."author" = 'vault' AND "audit_log"."action" = 'reference.end' AND "audit_log"."decision" = 'allow';--> statement-breakpoint
60
+ CREATE INDEX `audit_log_also_project_idx` ON `audit_log` (json_extract("metadata", '$.also.projectId'),`seq`) WHERE json_extract("audit_log"."metadata", '$.also') IS NOT NULL;--> statement-breakpoint
61
+ CREATE INDEX `audit_log_also_environment_idx` ON `audit_log` (json_extract("metadata", '$.also.environmentId'),`seq`) WHERE json_extract("audit_log"."metadata", '$.also') IS NOT NULL;--> statement-breakpoint
62
+ CREATE INDEX `audit_log_also_secret_idx` ON `audit_log` (json_extract("metadata", '$.also.secretId'),`seq`) WHERE json_extract("audit_log"."metadata", '$.also') IS NOT NULL;--> statement-breakpoint
63
+ CREATE TABLE `consumed_tokens` (
64
+ `hash` blob PRIMARY KEY NOT NULL,
65
+ `consumed_at` integer DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)) NOT NULL,
66
+ CONSTRAINT "consumed_tokens_hash_check" CHECK(octet_length("consumed_tokens"."hash") = 32)
67
+ );
68
+ --> statement-breakpoint
57
69
  CREATE TABLE `credentials` (
58
70
  `id` text PRIMARY KEY NOT NULL,
59
71
  `kind` text NOT NULL,
@@ -81,6 +93,8 @@ CREATE TABLE `credentials` (
81
93
  );
82
94
  --> statement-breakpoint
83
95
  CREATE INDEX `credentials_principal_idx` ON `credentials` (`principal`,`generation`);--> statement-breakpoint
96
+ CREATE INDEX `credentials_live_idx` ON `credentials` (`principal`,`expires_at`) WHERE "credentials"."revoked_at" IS NULL;--> statement-breakpoint
97
+ CREATE INDEX `credentials_issued_by_idx` ON `credentials` (`principal`,`created_by`,`created_at`);--> statement-breakpoint
84
98
  CREATE UNIQUE INDEX `credentials_token_hash_key` ON `credentials` (`token_hash`);--> statement-breakpoint
85
99
  CREATE TABLE `device_authorizations` (
86
100
  `id` text PRIMARY KEY NOT NULL,
@@ -108,6 +122,18 @@ CREATE TABLE `device_authorizations` (
108
122
  --> statement-breakpoint
109
123
  CREATE UNIQUE INDEX `device_authorizations_device_code_hash_key` ON `device_authorizations` (`device_code_hash`);--> statement-breakpoint
110
124
  CREATE UNIQUE INDEX `device_authorizations_user_code_key` ON `device_authorizations` (`user_code`);--> statement-breakpoint
125
+ CREATE TABLE `dismissed_keys` (
126
+ `environment_id` text NOT NULL,
127
+ `key` text NOT NULL,
128
+ `dismissed_at` integer DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)) NOT NULL,
129
+ `dismissed_by` text NOT NULL,
130
+ `restored_at` integer,
131
+ `restored_by` text,
132
+ PRIMARY KEY(`environment_id`, `key`),
133
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE restrict,
134
+ CONSTRAINT "dismissed_keys_key_check" CHECK(length("dismissed_keys"."key") BETWEEN 1 AND 128 AND "dismissed_keys"."key" GLOB '[A-Za-z_]*' AND "dismissed_keys"."key" NOT GLOB '*[^A-Za-z0-9_]*')
135
+ );
136
+ --> statement-breakpoint
111
137
  CREATE TABLE `environments` (
112
138
  `id` text PRIMARY KEY NOT NULL,
113
139
  `project_id` text NOT NULL,
@@ -116,7 +142,7 @@ CREATE TABLE `environments` (
116
142
  `created_at` integer DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)) NOT NULL,
117
143
  `archived_at` integer,
118
144
  FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE restrict,
119
- CONSTRAINT "environments_slug_check" CHECK(length("environments"."slug") BETWEEN 1 AND 63 AND "environments"."slug" GLOB '[a-z0-9]*' AND "environments"."slug" NOT GLOB '*[^a-z0-9-]*')
145
+ CONSTRAINT "environments_slug_check" CHECK((length("environments"."slug") BETWEEN 1 AND 63 AND "environments"."slug" GLOB '[a-z0-9]*' AND "environments"."slug" NOT GLOB '*[^a-z0-9-]*') OR (instr("environments"."slug", '~') BETWEEN 2 AND 64 AND substr("environments"."slug", 1, instr("environments"."slug", '~') - 1) GLOB '[a-z0-9]*' AND substr("environments"."slug", 1, instr("environments"."slug", '~') - 1) NOT GLOB '*[^a-z0-9-]*' AND length(substr("environments"."slug", instr("environments"."slug", '~') + 1)) BETWEEN 1 AND 40 AND substr("environments"."slug", instr("environments"."slug", '~') + 1) NOT GLOB '*[^a-z0-9-]*'))
120
146
  );
121
147
  --> statement-breakpoint
122
148
  CREATE UNIQUE INDEX `environments_project_id_slug_key` ON `environments` (`project_id`,`slug`);--> statement-breakpoint
@@ -145,16 +171,86 @@ CREATE TABLE `identities` (
145
171
  CREATE UNIQUE INDEX `identities_active_subject` ON `identities` (`provider`,`issuer_hash`,`active_subject`);--> statement-breakpoint
146
172
  CREATE INDEX `identities_principal_idx` ON `identities` (`principal`,`generation`);--> statement-breakpoint
147
173
  CREATE UNIQUE INDEX `identities_member_generation_key` ON `identities` (`id`,`principal`,`generation`);--> statement-breakpoint
174
+ CREATE TABLE `mcp_connections` (
175
+ `id` text PRIMARY KEY NOT NULL,
176
+ `auth_mac` blob NOT NULL,
177
+ `principal` text NOT NULL,
178
+ `generation` integer NOT NULL,
179
+ `client_id` text NOT NULL,
180
+ `client_name` text NOT NULL,
181
+ `client_host` text,
182
+ `registration` text NOT NULL,
183
+ `scopes` text NOT NULL,
184
+ `redirect_uri` text NOT NULL,
185
+ `code_hash` blob,
186
+ `code_challenge` text,
187
+ `code_expires_at` integer,
188
+ `refresh_hash` blob,
189
+ `refresh_previous_hash` blob,
190
+ `expires_at` integer NOT NULL,
191
+ `created_at` integer DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)) NOT NULL,
192
+ `last_used_at` integer,
193
+ `last_used_ip` text,
194
+ `revoked_at` integer,
195
+ `revoked_by` text,
196
+ FOREIGN KEY (`principal`) REFERENCES `vault_members`(`principal`) ON UPDATE no action ON DELETE restrict,
197
+ CONSTRAINT "mcp_connections_principal_check" CHECK("mcp_connections"."principal" LIKE 'user:%'),
198
+ CONSTRAINT "mcp_connections_registration_check" CHECK("mcp_connections"."registration" IN ('cimd', 'dcr')),
199
+ CONSTRAINT "mcp_connections_auth_mac_check" CHECK(octet_length("mcp_connections"."auth_mac") = 32),
200
+ CONSTRAINT "mcp_connections_code_hash_check" CHECK("mcp_connections"."code_hash" IS NULL OR octet_length("mcp_connections"."code_hash") = 32),
201
+ CONSTRAINT "mcp_connections_refresh_hash_check" CHECK("mcp_connections"."refresh_hash" IS NULL OR octet_length("mcp_connections"."refresh_hash") = 32)
202
+ );
203
+ --> statement-breakpoint
204
+ CREATE INDEX `mcp_connections_refresh_previous_idx` ON `mcp_connections` (`refresh_previous_hash`);--> statement-breakpoint
205
+ CREATE INDEX `mcp_connections_live_idx` ON `mcp_connections` (`principal`,`expires_at`) WHERE "mcp_connections"."revoked_at" IS NULL;--> statement-breakpoint
206
+ CREATE UNIQUE INDEX `mcp_connections_code_hash_key` ON `mcp_connections` (`code_hash`);--> statement-breakpoint
207
+ CREATE UNIQUE INDEX `mcp_connections_refresh_hash_key` ON `mcp_connections` (`refresh_hash`);--> statement-breakpoint
208
+ CREATE TABLE `oauth_clients` (
209
+ `id` text PRIMARY KEY NOT NULL,
210
+ `auth_mac` blob NOT NULL,
211
+ `name` text NOT NULL,
212
+ `redirect_uris` text NOT NULL,
213
+ `created_at` integer DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)) NOT NULL,
214
+ `created_ip` text,
215
+ `revoked_at` integer,
216
+ CONSTRAINT "oauth_clients_auth_mac_check" CHECK(octet_length("oauth_clients"."auth_mac") = 32),
217
+ CONSTRAINT "oauth_clients_redirect_uris_check" CHECK(json_valid("oauth_clients"."redirect_uris"))
218
+ );
219
+ --> statement-breakpoint
148
220
  CREATE TABLE `projects` (
149
221
  `id` text PRIMARY KEY NOT NULL,
150
222
  `slug` text NOT NULL,
151
223
  `name` text NOT NULL,
152
224
  `created_at` integer DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)) NOT NULL,
153
225
  `archived_at` integer,
154
- CONSTRAINT "projects_slug_check" CHECK(length("projects"."slug") BETWEEN 1 AND 63 AND "projects"."slug" GLOB '[a-z0-9]*' AND "projects"."slug" NOT GLOB '*[^a-z0-9-]*')
226
+ `folder` text,
227
+ CONSTRAINT "projects_slug_check" CHECK((length("projects"."slug") BETWEEN 1 AND 63 AND "projects"."slug" GLOB '[a-z0-9]*' AND "projects"."slug" NOT GLOB '*[^a-z0-9-]*') OR (instr("projects"."slug", '~') BETWEEN 2 AND 64 AND substr("projects"."slug", 1, instr("projects"."slug", '~') - 1) GLOB '[a-z0-9]*' AND substr("projects"."slug", 1, instr("projects"."slug", '~') - 1) NOT GLOB '*[^a-z0-9-]*' AND length(substr("projects"."slug", instr("projects"."slug", '~') + 1)) BETWEEN 1 AND 40 AND substr("projects"."slug", instr("projects"."slug", '~') + 1) NOT GLOB '*[^a-z0-9-]*')),
228
+ CONSTRAINT "projects_folder_check" CHECK("projects"."folder" IS NULL OR (length("projects"."folder") BETWEEN 1 AND 64 AND "projects"."folder" NOT GLOB '*/*' AND "projects"."folder" = trim("projects"."folder")))
155
229
  );
156
230
  --> statement-breakpoint
157
231
  CREATE UNIQUE INDEX `projects_slug_key` ON `projects` (`slug`);--> statement-breakpoint
232
+ CREATE TABLE `secret_references` (
233
+ `id` text PRIMARY KEY NOT NULL,
234
+ `project_id` text NOT NULL,
235
+ `environment_id` text NOT NULL,
236
+ `secret_id` text NOT NULL,
237
+ `source_project_id` text NOT NULL,
238
+ `source_environment_id` text NOT NULL,
239
+ `source_secret_id` text NOT NULL,
240
+ `created_seq` integer NOT NULL,
241
+ `created_at` integer DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)) NOT NULL,
242
+ `created_by` text NOT NULL,
243
+ FOREIGN KEY (`secret_id`) REFERENCES `secrets`(`id`) ON UPDATE no action ON DELETE restrict,
244
+ FOREIGN KEY (`source_secret_id`) REFERENCES `secrets`(`id`) ON UPDATE no action ON DELETE restrict,
245
+ FOREIGN KEY (`created_seq`) REFERENCES `audit_log`(`seq`) ON UPDATE no action ON DELETE restrict
246
+ );
247
+ --> statement-breakpoint
248
+ CREATE INDEX `secret_references_holder_idx` ON `secret_references` (`secret_id`,`created_seq`);--> statement-breakpoint
249
+ CREATE INDEX `secret_references_source_idx` ON `secret_references` (`source_secret_id`);--> statement-breakpoint
250
+ CREATE INDEX `secret_references_project_idx` ON `secret_references` (`project_id`);--> statement-breakpoint
251
+ CREATE INDEX `secret_references_source_project_idx` ON `secret_references` (`source_project_id`);--> statement-breakpoint
252
+ CREATE INDEX `secret_references_created_by_idx` ON `secret_references` (`created_by`);--> statement-breakpoint
253
+ CREATE UNIQUE INDEX `secret_references_created_seq_key` ON `secret_references` (`created_seq`);--> statement-breakpoint
158
254
  CREATE TABLE `secret_versions` (
159
255
  `id` text PRIMARY KEY NOT NULL,
160
256
  `secret_id` text NOT NULL,
@@ -187,52 +283,45 @@ CREATE TABLE `secrets` (
187
283
  `current_version_id` text,
188
284
  `current_version` integer DEFAULT 0 NOT NULL,
189
285
  `archived_at` integer,
286
+ `folder` text,
190
287
  FOREIGN KEY (`current_version_id`) REFERENCES `secret_versions`(`id`) ON UPDATE no action ON DELETE no action,
191
288
  FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE restrict,
192
289
  FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE restrict,
193
290
  FOREIGN KEY (`environment_id`,`project_id`) REFERENCES `environments`(`id`,`project_id`) ON UPDATE no action ON DELETE no action,
194
- CONSTRAINT "secrets_key_check" CHECK(length("secrets"."key") BETWEEN 1 AND 128 AND "secrets"."key" GLOB '[A-Za-z_]*' AND "secrets"."key" NOT GLOB '*[^A-Za-z0-9_]*')
291
+ CONSTRAINT "secrets_key_check" CHECK(length("secrets"."key") BETWEEN 1 AND 128 AND "secrets"."key" GLOB '[A-Za-z_]*' AND "secrets"."key" NOT GLOB '*[^A-Za-z0-9_]*'),
292
+ CONSTRAINT "secrets_folder_check" CHECK("secrets"."folder" IS NULL OR (length("secrets"."folder") BETWEEN 1 AND 64 AND "secrets"."folder" NOT GLOB '*/*' AND "secrets"."folder" = trim("secrets"."folder")))
195
293
  );
196
294
  --> statement-breakpoint
197
295
  CREATE INDEX `secrets_lookup_idx` ON `secrets` (`project_id`,`environment_id`,`key`);--> statement-breakpoint
198
296
  CREATE UNIQUE INDEX `secrets_project_id_environment_id_key_key` ON `secrets` (`project_id`,`environment_id`,`key`);--> statement-breakpoint
199
- CREATE TABLE `sync_keys` (
200
- `sync_id` text NOT NULL,
201
- `key` text NOT NULL,
202
- `secret_version_id` text,
203
- `pushed_at` integer DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)) NOT NULL,
204
- `removed_at` integer,
205
- PRIMARY KEY(`sync_id`, `key`),
206
- FOREIGN KEY (`sync_id`) REFERENCES `syncs`(`id`) ON UPDATE no action ON DELETE restrict,
207
- FOREIGN KEY (`secret_version_id`) REFERENCES `secret_versions`(`id`) ON UPDATE no action ON DELETE restrict
208
- );
209
- --> statement-breakpoint
210
- CREATE TABLE `syncs` (
297
+ CREATE TABLE `service_bindings` (
211
298
  `id` text PRIMARY KEY NOT NULL,
212
- `project_id` text NOT NULL,
213
- `environment_id` text NOT NULL,
214
- `provider` text NOT NULL,
215
- `config` text NOT NULL,
216
- `credential_secret_id` text NOT NULL,
299
+ `auth_mac` blob NOT NULL,
300
+ `principal` text NOT NULL,
301
+ `generation` integer NOT NULL,
302
+ `profile` text NOT NULL,
303
+ `issuer` text NOT NULL,
304
+ `jwks_uri` text NOT NULL,
305
+ `claims` text NOT NULL,
306
+ `label` text,
217
307
  `created_at` integer DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)) NOT NULL,
218
308
  `created_by` text NOT NULL,
219
- `paused_at` integer,
220
- `archived_at` integer,
221
- `lease_until` integer,
222
- `last_run_at` integer,
223
- `last_status` text,
224
- `last_error` text,
225
- FOREIGN KEY (`environment_id`,`project_id`) REFERENCES `environments`(`id`,`project_id`) ON UPDATE no action ON DELETE restrict,
226
- FOREIGN KEY (`credential_secret_id`) REFERENCES `secrets`(`id`) ON UPDATE no action ON DELETE restrict,
227
- CONSTRAINT "syncs_config_check" CHECK(json_valid("syncs"."config")),
228
- CONSTRAINT "syncs_last_status_check" CHECK("syncs"."last_status" IS NULL OR "syncs"."last_status" IN ('ok', 'partial', 'failed'))
309
+ `last_used_at` integer,
310
+ `revoked_at` integer,
311
+ `revoked_by` text,
312
+ FOREIGN KEY (`principal`) REFERENCES `vault_members`(`principal`) ON UPDATE no action ON DELETE restrict,
313
+ CONSTRAINT "service_bindings_principal_check" CHECK("service_bindings"."principal" LIKE 'token:%'),
314
+ CONSTRAINT "service_bindings_auth_mac_check" CHECK(octet_length("service_bindings"."auth_mac") = 32),
315
+ CONSTRAINT "service_bindings_claims_check" CHECK(json_valid("service_bindings"."claims"))
229
316
  );
230
317
  --> statement-breakpoint
231
- CREATE INDEX `syncs_environment_idx` ON `syncs` (`environment_id`);--> statement-breakpoint
318
+ CREATE INDEX `service_bindings_principal_idx` ON `service_bindings` (`principal`,`issuer`);--> statement-breakpoint
319
+ CREATE INDEX `service_bindings_live_idx` ON `service_bindings` (`principal`,`issuer`,`generation`,`created_at`,`id`) WHERE "service_bindings"."revoked_at" IS NULL;--> statement-breakpoint
232
320
  CREATE TABLE `vault_grants` (
233
321
  `principal` text NOT NULL,
234
322
  `project_id` text,
235
323
  `environment_id` text,
324
+ `environment_slug` text,
236
325
  `role` text NOT NULL,
237
326
  `expires_at` integer,
238
327
  `granted_at` integer NOT NULL,
@@ -240,13 +329,16 @@ CREATE TABLE `vault_grants` (
240
329
  FOREIGN KEY (`principal`) REFERENCES `vault_members`(`principal`) ON UPDATE no action ON DELETE restrict,
241
330
  FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE restrict,
242
331
  FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE restrict,
243
- CONSTRAINT "vault_grants_one_place" CHECK(("vault_grants"."project_id" IS NULL) <> ("vault_grants"."environment_id" IS NULL)),
332
+ CONSTRAINT "vault_grants_one_place" CHECK((("vault_grants"."project_id" IS NULL) <> ("vault_grants"."environment_id" IS NULL) AND "vault_grants"."environment_slug" IS NULL) OR ("vault_grants"."project_id" IS NULL AND "vault_grants"."environment_id" IS NULL)),
333
+ CONSTRAINT "vault_grants_environment_slug_check" CHECK("vault_grants"."environment_slug" IS NULL OR (length("vault_grants"."environment_slug") BETWEEN 1 AND 63 AND "vault_grants"."environment_slug" GLOB '[a-z0-9]*' AND "vault_grants"."environment_slug" NOT GLOB '*[^a-z0-9-]*')),
244
334
  CONSTRAINT "vault_grants_role_check" CHECK("vault_grants"."role" IN ('viewer', 'developer', 'maintainer', 'access-manager', 'auditor', 'owner')),
245
- CONSTRAINT "vault_grants_environment_role_check" CHECK("vault_grants"."environment_id" IS NULL OR "vault_grants"."role" IN ('viewer', 'developer', 'auditor'))
335
+ CONSTRAINT "vault_grants_environment_role_check" CHECK(("vault_grants"."environment_id" IS NULL AND "vault_grants"."environment_slug" IS NULL) OR "vault_grants"."role" IN ('viewer', 'developer', 'auditor'))
246
336
  );
247
337
  --> statement-breakpoint
338
+ CREATE UNIQUE INDEX `vault_grants_on_every_project` ON `vault_grants` (`principal`) WHERE "vault_grants"."project_id" IS NULL AND "vault_grants"."environment_id" IS NULL AND "vault_grants"."environment_slug" IS NULL;--> statement-breakpoint
248
339
  CREATE UNIQUE INDEX `vault_grants_on_project` ON `vault_grants` (`principal`,`project_id`);--> statement-breakpoint
249
340
  CREATE UNIQUE INDEX `vault_grants_on_environment` ON `vault_grants` (`principal`,`environment_id`);--> statement-breakpoint
341
+ CREATE UNIQUE INDEX `vault_grants_on_environment_slug` ON `vault_grants` (`principal`,`environment_slug`);--> statement-breakpoint
250
342
  CREATE TABLE `vault_members` (
251
343
  `principal` text PRIMARY KEY NOT NULL,
252
344
  `status` text NOT NULL,
@@ -259,7 +351,7 @@ CREATE TABLE `vault_members` (
259
351
  `access_seq` integer NOT NULL,
260
352
  `mac` blob NOT NULL,
261
353
  CONSTRAINT "vault_members_mac_check" CHECK(octet_length("vault_members"."mac") = 32),
262
- CONSTRAINT "vault_members_principal_check" CHECK(("vault_members"."principal" GLOB 'user:?*' OR "vault_members"."principal" GLOB 'token:?*' OR "vault_members"."principal" GLOB 'sync:?*')
354
+ CONSTRAINT "vault_members_principal_check" CHECK(("vault_members"."principal" GLOB 'user:?*' OR "vault_members"."principal" GLOB 'token:?*')
263
355
  AND substr("vault_members"."principal", instr("vault_members"."principal", ':') + 1, 1) <> ':'
264
356
  AND instr("vault_members"."principal", ' ') = 0 AND instr("vault_members"."principal", char(9)) = 0
265
357
  AND instr("vault_members"."principal", char(10)) = 0 AND instr("vault_members"."principal", char(13)) = 0),
@@ -291,3 +383,18 @@ CREATE TRIGGER `audit_log_no_delete` BEFORE DELETE ON `audit_log`
291
383
  BEGIN
292
384
  SELECT RAISE(ABORT, 'audit_log is append-only');
293
385
  END;
386
+ --> statement-breakpoint
387
+
388
+ -- A secret version is never rewritten in place, only erased: a deleted
389
+ -- project or environment stays as a tombstone the log's entries name, and
390
+ -- its versions with it, their ciphertext and wrapped data key emptied. That
391
+ -- is the one change a version takes.
392
+ CREATE TRIGGER `secret_versions_erase_only` BEFORE UPDATE ON `secret_versions`
393
+ WHEN length(NEW.`ciphertext`) <> 0 OR length(NEW.`wrapped_dek`) <> 0
394
+ OR NEW.`id` IS NOT OLD.`id` OR NEW.`secret_id` IS NOT OLD.`secret_id` OR NEW.`version` IS NOT OLD.`version`
395
+ OR NEW.`envelope_version` IS NOT OLD.`envelope_version` OR NEW.`iv` IS NOT OLD.`iv` OR NEW.`auth_tag` IS NOT OLD.`auth_tag`
396
+ OR NEW.`kek_provider` IS NOT OLD.`kek_provider` OR NEW.`kek_id` IS NOT OLD.`kek_id` OR NEW.`kek_version` IS NOT OLD.`kek_version`
397
+ OR NEW.`created_at` IS NOT OLD.`created_at` OR NEW.`created_by` IS NOT OLD.`created_by`
398
+ BEGIN
399
+ SELECT RAISE(ABORT, 'a secret version is only ever erased');
400
+ END;
@@ -0,0 +1,19 @@
1
+ CREATE TABLE `mcp_approvals` (
2
+ `id` text PRIMARY KEY NOT NULL,
3
+ `connection_id` text NOT NULL,
4
+ `tool` text NOT NULL,
5
+ `arguments` text NOT NULL,
6
+ `digest` blob NOT NULL,
7
+ `status` text DEFAULT 'pending' NOT NULL,
8
+ `outcome` text,
9
+ `created_at` integer DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)) NOT NULL,
10
+ `expires_at` integer NOT NULL,
11
+ `decided_at` integer,
12
+ `reported_at` integer,
13
+ FOREIGN KEY (`connection_id`) REFERENCES `mcp_connections`(`id`) ON UPDATE no action ON DELETE restrict,
14
+ CONSTRAINT "mcp_approvals_status_check" CHECK("mcp_approvals"."status" IN ('pending', 'approved', 'denied', 'cancelled', 'failed')),
15
+ CONSTRAINT "mcp_approvals_digest_check" CHECK(octet_length("mcp_approvals"."digest") = 32),
16
+ CONSTRAINT "mcp_approvals_arguments_check" CHECK(json_valid("mcp_approvals"."arguments"))
17
+ );
18
+ --> statement-breakpoint
19
+ CREATE INDEX `mcp_approvals_open_idx` ON `mcp_approvals` (`connection_id`,`digest`) WHERE "mcp_approvals"."reported_at" IS NULL;