@coffre/db 0.3.0 → 0.4.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.
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
@@ -33,7 +33,7 @@ CREATE TABLE "audit_log" (
33
33
  "mac" "bytea" NOT NULL,
34
34
  "hash" "bytea" NOT NULL,
35
35
  CONSTRAINT "audit_log_author_check" CHECK ("audit_log"."author" IN ('app', 'vault')),
36
- CONSTRAINT "audit_log_actor_check" CHECK ("audit_log"."actor" ~ '^(user|token|sync|system):.+$'),
36
+ CONSTRAINT "audit_log_actor_check" CHECK ("audit_log"."actor" ~ '^(user|token|system):.+$'),
37
37
  CONSTRAINT "audit_log_decision_check" CHECK ("audit_log"."decision" IN ('allow', 'deny')),
38
38
  CONSTRAINT "audit_log_metadata_check" CHECK ("audit_log"."metadata"::jsonb IS NOT NULL),
39
39
  CONSTRAINT "audit_log_prev_hash_check" CHECK (octet_length("audit_log"."prev_hash") = 32),
@@ -41,6 +41,12 @@ CREATE TABLE "audit_log" (
41
41
  CONSTRAINT "audit_log_hash_check" CHECK (octet_length("audit_log"."hash") = 32)
42
42
  );
43
43
  --> statement-breakpoint
44
+ CREATE TABLE "consumed_tokens" (
45
+ "hash" "bytea" PRIMARY KEY NOT NULL,
46
+ "consumed_at" timestamp with time zone DEFAULT now() NOT NULL,
47
+ CONSTRAINT "consumed_tokens_hash_check" CHECK (octet_length("consumed_tokens"."hash") = 32)
48
+ );
49
+ --> statement-breakpoint
44
50
  CREATE TABLE "credentials" (
45
51
  "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
46
52
  "kind" text NOT NULL,
@@ -91,6 +97,17 @@ CREATE TABLE "device_authorizations" (
91
97
  )))
92
98
  );
93
99
  --> statement-breakpoint
100
+ CREATE TABLE "dismissed_keys" (
101
+ "environment_id" uuid NOT NULL,
102
+ "key" text NOT NULL,
103
+ "dismissed_at" timestamp with time zone DEFAULT now() NOT NULL,
104
+ "dismissed_by" text NOT NULL,
105
+ "restored_at" timestamp with time zone,
106
+ "restored_by" text,
107
+ CONSTRAINT "dismissed_keys_pkey" PRIMARY KEY("environment_id","key"),
108
+ CONSTRAINT "dismissed_keys_key_check" CHECK ("dismissed_keys"."key" ~ '^[A-Za-z_][A-Za-z0-9_]{0,127}$')
109
+ );
110
+ --> statement-breakpoint
94
111
  CREATE TABLE "environments" (
95
112
  "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
96
113
  "project_id" uuid NOT NULL,
@@ -100,7 +117,7 @@ CREATE TABLE "environments" (
100
117
  "archived_at" timestamp with time zone,
101
118
  CONSTRAINT "environments_project_id_slug_key" UNIQUE("project_id","slug"),
102
119
  CONSTRAINT "environments_project_scoped" UNIQUE("id","project_id"),
103
- CONSTRAINT "environments_slug_check" CHECK ("environments"."slug" ~ '^[a-z0-9][a-z0-9-]{0,62}$')
120
+ CONSTRAINT "environments_slug_check" CHECK ("environments"."slug" ~ '^[a-z0-9][a-z0-9-]{0,62}(~[a-z0-9-]{1,40})?$')
104
121
  );
105
122
  --> statement-breakpoint
106
123
  CREATE TABLE "identities" (
@@ -124,14 +141,73 @@ CREATE TABLE "identities" (
124
141
  CONSTRAINT "identities_auth_mac_check" CHECK (octet_length("identities"."auth_mac") = 32)
125
142
  );
126
143
  --> statement-breakpoint
144
+ CREATE TABLE "mcp_connections" (
145
+ "id" uuid PRIMARY KEY NOT NULL,
146
+ "auth_mac" "bytea" NOT NULL,
147
+ "principal" text NOT NULL,
148
+ "generation" integer NOT NULL,
149
+ "client_id" text NOT NULL,
150
+ "client_name" text NOT NULL,
151
+ "client_host" text,
152
+ "registration" text NOT NULL,
153
+ "scopes" text NOT NULL,
154
+ "redirect_uri" text NOT NULL,
155
+ "code_hash" "bytea",
156
+ "code_challenge" text,
157
+ "code_expires_at" timestamp with time zone,
158
+ "refresh_hash" "bytea",
159
+ "refresh_previous_hash" "bytea",
160
+ "expires_at" timestamp with time zone NOT NULL,
161
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
162
+ "last_used_at" timestamp with time zone,
163
+ "last_used_ip" text,
164
+ "revoked_at" timestamp with time zone,
165
+ "revoked_by" text,
166
+ CONSTRAINT "mcp_connections_code_hash_key" UNIQUE("code_hash"),
167
+ CONSTRAINT "mcp_connections_refresh_hash_key" UNIQUE("refresh_hash"),
168
+ CONSTRAINT "mcp_connections_principal_check" CHECK ("mcp_connections"."principal" LIKE 'user:%'),
169
+ CONSTRAINT "mcp_connections_registration_check" CHECK ("mcp_connections"."registration" IN ('cimd', 'dcr')),
170
+ CONSTRAINT "mcp_connections_auth_mac_check" CHECK (octet_length("mcp_connections"."auth_mac") = 32),
171
+ CONSTRAINT "mcp_connections_code_hash_check" CHECK ("mcp_connections"."code_hash" IS NULL OR octet_length("mcp_connections"."code_hash") = 32),
172
+ CONSTRAINT "mcp_connections_refresh_hash_check" CHECK ("mcp_connections"."refresh_hash" IS NULL OR octet_length("mcp_connections"."refresh_hash") = 32)
173
+ );
174
+ --> statement-breakpoint
175
+ CREATE TABLE "oauth_clients" (
176
+ "id" uuid PRIMARY KEY NOT NULL,
177
+ "auth_mac" "bytea" NOT NULL,
178
+ "name" text NOT NULL,
179
+ "redirect_uris" text NOT NULL,
180
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
181
+ "created_ip" text,
182
+ "revoked_at" timestamp with time zone,
183
+ CONSTRAINT "oauth_clients_auth_mac_check" CHECK (octet_length("oauth_clients"."auth_mac") = 32),
184
+ CONSTRAINT "oauth_clients_redirect_uris_check" CHECK ("oauth_clients"."redirect_uris"::jsonb IS NOT NULL)
185
+ );
186
+ --> statement-breakpoint
127
187
  CREATE TABLE "projects" (
128
188
  "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
129
189
  "slug" text NOT NULL,
130
190
  "name" text NOT NULL,
131
191
  "created_at" timestamp with time zone DEFAULT now() NOT NULL,
132
192
  "archived_at" timestamp with time zone,
193
+ "folder" text,
133
194
  CONSTRAINT "projects_slug_key" UNIQUE("slug"),
134
- CONSTRAINT "projects_slug_check" CHECK ("projects"."slug" ~ '^[a-z0-9][a-z0-9-]{0,62}$')
195
+ CONSTRAINT "projects_slug_check" CHECK ("projects"."slug" ~ '^[a-z0-9][a-z0-9-]{0,62}(~[a-z0-9-]{1,40})?$'),
196
+ CONSTRAINT "projects_folder_check" CHECK ("projects"."folder" IS NULL OR (char_length("projects"."folder") BETWEEN 1 AND 64 AND "projects"."folder" !~ '[/[:cntrl:]]' AND "projects"."folder" = btrim("projects"."folder")))
197
+ );
198
+ --> statement-breakpoint
199
+ CREATE TABLE "secret_references" (
200
+ "id" uuid PRIMARY KEY NOT NULL,
201
+ "project_id" uuid NOT NULL,
202
+ "environment_id" uuid NOT NULL,
203
+ "secret_id" uuid NOT NULL,
204
+ "source_project_id" uuid NOT NULL,
205
+ "source_environment_id" uuid NOT NULL,
206
+ "source_secret_id" uuid NOT NULL,
207
+ "created_seq" bigint NOT NULL,
208
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
209
+ "created_by" text NOT NULL,
210
+ CONSTRAINT "secret_references_created_seq_key" UNIQUE("created_seq")
135
211
  );
136
212
  --> statement-breakpoint
137
213
  CREATE TABLE "secret_versions" (
@@ -164,51 +240,48 @@ CREATE TABLE "secrets" (
164
240
  "current_version_id" uuid,
165
241
  "current_version" integer DEFAULT 0 NOT NULL,
166
242
  "archived_at" timestamp with time zone,
243
+ "folder" text,
167
244
  CONSTRAINT "secrets_project_id_environment_id_key_key" UNIQUE("project_id","environment_id","key"),
168
- CONSTRAINT "secrets_key_check" CHECK ("secrets"."key" ~ '^[A-Za-z_][A-Za-z0-9_]{0,127}$')
245
+ CONSTRAINT "secrets_key_check" CHECK ("secrets"."key" ~ '^[A-Za-z_][A-Za-z0-9_]{0,127}$'),
246
+ CONSTRAINT "secrets_folder_check" CHECK ("secrets"."folder" IS NULL OR (char_length("secrets"."folder") BETWEEN 1 AND 64 AND "secrets"."folder" !~ '[/[:cntrl:]]' AND "secrets"."folder" = btrim("secrets"."folder")))
169
247
  );
170
248
  --> statement-breakpoint
171
- CREATE TABLE "sync_keys" (
172
- "sync_id" uuid NOT NULL,
173
- "key" text NOT NULL,
174
- "secret_version_id" uuid,
175
- "pushed_at" timestamp with time zone DEFAULT now() NOT NULL,
176
- "removed_at" timestamp with time zone,
177
- CONSTRAINT "sync_keys_pkey" PRIMARY KEY("sync_id","key")
178
- );
179
- --> statement-breakpoint
180
- CREATE TABLE "syncs" (
181
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
182
- "project_id" uuid NOT NULL,
183
- "environment_id" uuid NOT NULL,
184
- "provider" text NOT NULL,
185
- "config" text NOT NULL,
186
- "credential_secret_id" uuid NOT NULL,
249
+ CREATE TABLE "service_bindings" (
250
+ "id" uuid PRIMARY KEY NOT NULL,
251
+ "auth_mac" "bytea" NOT NULL,
252
+ "principal" text NOT NULL,
253
+ "generation" integer NOT NULL,
254
+ "profile" text NOT NULL,
255
+ "issuer" text NOT NULL,
256
+ "jwks_uri" text NOT NULL,
257
+ "claims" text NOT NULL,
258
+ "label" text,
187
259
  "created_at" timestamp with time zone DEFAULT now() NOT NULL,
188
260
  "created_by" text NOT NULL,
189
- "paused_at" timestamp with time zone,
190
- "archived_at" timestamp with time zone,
191
- "lease_until" timestamp with time zone,
192
- "last_run_at" timestamp with time zone,
193
- "last_status" text,
194
- "last_error" text,
195
- CONSTRAINT "syncs_config_check" CHECK ("syncs"."config"::jsonb IS NOT NULL),
196
- CONSTRAINT "syncs_last_status_check" CHECK ("syncs"."last_status" IS NULL OR "syncs"."last_status" IN ('ok', 'partial', 'failed'))
261
+ "last_used_at" timestamp with time zone,
262
+ "revoked_at" timestamp with time zone,
263
+ "revoked_by" text,
264
+ CONSTRAINT "service_bindings_principal_check" CHECK ("service_bindings"."principal" LIKE 'token:%'),
265
+ CONSTRAINT "service_bindings_auth_mac_check" CHECK (octet_length("service_bindings"."auth_mac") = 32),
266
+ CONSTRAINT "service_bindings_claims_check" CHECK ("service_bindings"."claims"::jsonb IS NOT NULL)
197
267
  );
198
268
  --> statement-breakpoint
199
269
  CREATE TABLE "vault_grants" (
200
270
  "principal" text NOT NULL,
201
271
  "project_id" uuid,
202
272
  "environment_id" uuid,
273
+ "environment_slug" text,
203
274
  "role" text NOT NULL,
204
275
  "expires_at" bigint,
205
276
  "granted_at" bigint NOT NULL,
206
277
  "granted_by" text NOT NULL,
207
278
  CONSTRAINT "vault_grants_on_project" UNIQUE("principal","project_id"),
208
279
  CONSTRAINT "vault_grants_on_environment" UNIQUE("principal","environment_id"),
209
- CONSTRAINT "vault_grants_one_place" CHECK (("vault_grants"."project_id" IS NULL) <> ("vault_grants"."environment_id" IS NULL)),
280
+ CONSTRAINT "vault_grants_on_environment_slug" UNIQUE("principal","environment_slug"),
281
+ 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)),
282
+ CONSTRAINT "vault_grants_environment_slug_check" CHECK ("vault_grants"."environment_slug" IS NULL OR "vault_grants"."environment_slug" ~ '^[a-z0-9][a-z0-9-]{0,62}$'),
210
283
  CONSTRAINT "vault_grants_role_check" CHECK ("vault_grants"."role" IN ('viewer', 'developer', 'maintainer', 'access-manager', 'auditor', 'owner')),
211
- CONSTRAINT "vault_grants_environment_role_check" CHECK ("vault_grants"."environment_id" IS NULL OR "vault_grants"."role" IN ('viewer', 'developer', 'auditor'))
284
+ 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'))
212
285
  );
213
286
  --> statement-breakpoint
214
287
  CREATE TABLE "vault_members" (
@@ -223,7 +296,7 @@ CREATE TABLE "vault_members" (
223
296
  "access_seq" bigint NOT NULL,
224
297
  "mac" "bytea" NOT NULL,
225
298
  CONSTRAINT "vault_members_mac_check" CHECK (octet_length("vault_members"."mac") = 32),
226
- CONSTRAINT "vault_members_principal_check" CHECK ("vault_members"."principal" ~ '^(user|token|sync):[^[:space:]:][^[:space:]]*$'),
299
+ CONSTRAINT "vault_members_principal_check" CHECK ("vault_members"."principal" ~ '^(user|token):[^[:space:]:][^[:space:]]*$'),
227
300
  CONSTRAINT "vault_members_user_lowercase" CHECK ("vault_members"."principal" NOT LIKE 'user:%' OR "vault_members"."principal" = lower("vault_members"."principal")),
228
301
  CONSTRAINT "vault_members_status_check" CHECK ("vault_members"."status" IN ('active', 'removed')),
229
302
  CONSTRAINT "vault_members_owner_check" CHECK (NOT "vault_members"."owner" OR ("vault_members"."status" = 'active' AND "vault_members"."principal" LIKE 'user:%')),
@@ -238,17 +311,19 @@ ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_related_seq_fkey" FOREIGN KEY
238
311
  ALTER TABLE "credentials" ADD CONSTRAINT "credentials_principal_fkey" FOREIGN KEY ("principal") REFERENCES "public"."vault_members"("principal") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
239
312
  ALTER TABLE "credentials" ADD CONSTRAINT "credentials_identity_id_fkey" FOREIGN KEY ("identity_id","principal","generation") REFERENCES "public"."identities"("id","principal","generation") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
240
313
  ALTER TABLE "device_authorizations" ADD CONSTRAINT "device_authorizations_principal_fkey" FOREIGN KEY ("principal") REFERENCES "public"."vault_members"("principal") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
314
+ ALTER TABLE "dismissed_keys" ADD CONSTRAINT "dismissed_keys_environment_id_fkey" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
241
315
  ALTER TABLE "environments" ADD CONSTRAINT "environments_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
242
316
  ALTER TABLE "identities" ADD CONSTRAINT "identities_principal_fkey" FOREIGN KEY ("principal") REFERENCES "public"."vault_members"("principal") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
317
+ ALTER TABLE "mcp_connections" ADD CONSTRAINT "mcp_connections_principal_fkey" FOREIGN KEY ("principal") REFERENCES "public"."vault_members"("principal") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
318
+ ALTER TABLE "secret_references" ADD CONSTRAINT "secret_references_secret_id_fkey" FOREIGN KEY ("secret_id") REFERENCES "public"."secrets"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
319
+ ALTER TABLE "secret_references" ADD CONSTRAINT "secret_references_source_secret_id_fkey" FOREIGN KEY ("source_secret_id") REFERENCES "public"."secrets"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
320
+ ALTER TABLE "secret_references" ADD CONSTRAINT "secret_references_created_seq_fkey" FOREIGN KEY ("created_seq") REFERENCES "public"."audit_log"("seq") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
243
321
  ALTER TABLE "secret_versions" ADD CONSTRAINT "secret_versions_secret_id_fkey" FOREIGN KEY ("secret_id") REFERENCES "public"."secrets"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
244
322
  ALTER TABLE "secrets" ADD CONSTRAINT "secrets_current_version_id_secret_versions_id_fk" FOREIGN KEY ("current_version_id") REFERENCES "public"."secret_versions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
245
323
  ALTER TABLE "secrets" ADD CONSTRAINT "secrets_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
246
324
  ALTER TABLE "secrets" ADD CONSTRAINT "secrets_environment_id_fkey" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
247
325
  ALTER TABLE "secrets" ADD CONSTRAINT "secrets_environment_in_project" FOREIGN KEY ("environment_id","project_id") REFERENCES "public"."environments"("id","project_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
248
- ALTER TABLE "sync_keys" ADD CONSTRAINT "sync_keys_sync_id_fkey" FOREIGN KEY ("sync_id") REFERENCES "public"."syncs"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
249
- ALTER TABLE "sync_keys" ADD CONSTRAINT "sync_keys_secret_version_id_fkey" FOREIGN KEY ("secret_version_id") REFERENCES "public"."secret_versions"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
250
- ALTER TABLE "syncs" ADD CONSTRAINT "syncs_environment_in_project" FOREIGN KEY ("environment_id","project_id") REFERENCES "public"."environments"("id","project_id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
251
- ALTER TABLE "syncs" ADD CONSTRAINT "syncs_credential_secret_id_fkey" FOREIGN KEY ("credential_secret_id") REFERENCES "public"."secrets"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
326
+ ALTER TABLE "service_bindings" ADD CONSTRAINT "service_bindings_principal_fkey" FOREIGN KEY ("principal") REFERENCES "public"."vault_members"("principal") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
252
327
  ALTER TABLE "vault_grants" ADD CONSTRAINT "vault_grants_principal_fkey" FOREIGN KEY ("principal") REFERENCES "public"."vault_members"("principal") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
253
328
  ALTER TABLE "vault_grants" ADD CONSTRAINT "vault_grants_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
254
329
  ALTER TABLE "vault_grants" ADD CONSTRAINT "vault_grants_environment_id_fkey" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
@@ -260,12 +335,29 @@ CREATE INDEX "audit_log_operation_idx" ON "audit_log" USING btree ("operation_id
260
335
  CREATE INDEX "audit_log_action_idx" ON "audit_log" USING btree ("author","action","seq");--> statement-breakpoint
261
336
  CREATE INDEX "audit_log_releases_idx" ON "audit_log" USING btree ("author","actor","action","decision","occurred_at");--> statement-breakpoint
262
337
  CREATE INDEX "audit_log_subject_idx" ON "audit_log" USING btree ("author","subject_principal","seq");--> statement-breakpoint
338
+ CREATE INDEX "audit_log_unbind_idx" ON "audit_log" USING btree ((("metadata")::jsonb ->> 'bindingId')) WHERE "audit_log"."author" = 'app' AND "audit_log"."action" = 'token.unbind' AND "audit_log"."decision" = 'allow';--> statement-breakpoint
339
+ CREATE INDEX "audit_log_exchange_idx" ON "audit_log" USING btree ((("metadata")::jsonb ->> 'credentialId')) WHERE "audit_log"."author" = 'app' AND "audit_log"."action" = 'token.exchange' AND "audit_log"."decision" = 'allow';--> statement-breakpoint
340
+ CREATE INDEX "audit_log_reference_end_idx" ON "audit_log" USING btree ("related_seq") WHERE "audit_log"."author" = 'vault' AND "audit_log"."action" = 'reference.end' AND "audit_log"."decision" = 'allow';--> statement-breakpoint
341
+ CREATE INDEX "audit_log_also_project_idx" ON "audit_log" USING btree (((("metadata")::jsonb -> 'also') ->> 'projectId'),"seq") WHERE (("audit_log"."metadata")::jsonb -> 'also') IS NOT NULL;--> statement-breakpoint
342
+ CREATE INDEX "audit_log_also_environment_idx" ON "audit_log" USING btree (((("metadata")::jsonb -> 'also') ->> 'environmentId'),"seq") WHERE (("audit_log"."metadata")::jsonb -> 'also') IS NOT NULL;--> statement-breakpoint
343
+ CREATE INDEX "audit_log_also_secret_idx" ON "audit_log" USING btree (((("metadata")::jsonb -> 'also') ->> 'secretId'),"seq") WHERE (("audit_log"."metadata")::jsonb -> 'also') IS NOT NULL;--> statement-breakpoint
263
344
  CREATE INDEX "credentials_principal_idx" ON "credentials" USING btree ("principal","generation");--> statement-breakpoint
345
+ CREATE INDEX "credentials_live_idx" ON "credentials" USING btree ("principal","expires_at") WHERE "credentials"."revoked_at" IS NULL;--> statement-breakpoint
346
+ CREATE INDEX "credentials_issued_by_idx" ON "credentials" USING btree ("principal","created_by","created_at");--> statement-breakpoint
264
347
  CREATE UNIQUE INDEX "identities_active_subject" ON "identities" USING btree ("provider","issuer_hash","active_subject");--> statement-breakpoint
265
348
  CREATE INDEX "identities_principal_idx" ON "identities" USING btree ("principal","generation");--> statement-breakpoint
349
+ CREATE INDEX "mcp_connections_refresh_previous_idx" ON "mcp_connections" USING btree ("refresh_previous_hash");--> statement-breakpoint
350
+ CREATE INDEX "mcp_connections_live_idx" ON "mcp_connections" USING btree ("principal","expires_at") WHERE "mcp_connections"."revoked_at" IS NULL;--> statement-breakpoint
351
+ CREATE INDEX "secret_references_holder_idx" ON "secret_references" USING btree ("secret_id","created_seq");--> statement-breakpoint
352
+ CREATE INDEX "secret_references_source_idx" ON "secret_references" USING btree ("source_secret_id");--> statement-breakpoint
353
+ CREATE INDEX "secret_references_project_idx" ON "secret_references" USING btree ("project_id");--> statement-breakpoint
354
+ CREATE INDEX "secret_references_source_project_idx" ON "secret_references" USING btree ("source_project_id");--> statement-breakpoint
355
+ CREATE INDEX "secret_references_created_by_idx" ON "secret_references" USING btree ("created_by");--> statement-breakpoint
266
356
  CREATE INDEX "secret_versions_secret_idx" ON "secret_versions" USING btree ("secret_id","version" DESC NULLS LAST);--> statement-breakpoint
267
357
  CREATE INDEX "secrets_lookup_idx" ON "secrets" USING btree ("project_id","environment_id","key");--> statement-breakpoint
268
- CREATE INDEX "syncs_environment_idx" ON "syncs" USING btree ("environment_id");--> statement-breakpoint
358
+ CREATE INDEX "service_bindings_principal_idx" ON "service_bindings" USING btree ("principal","issuer");--> statement-breakpoint
359
+ CREATE INDEX "service_bindings_live_idx" ON "service_bindings" USING btree ("principal","issuer","generation","created_at","id") WHERE "service_bindings"."revoked_at" IS NULL;--> statement-breakpoint
360
+ CREATE UNIQUE INDEX "vault_grants_on_every_project" ON "vault_grants" USING btree ("principal") WHERE "vault_grants"."project_id" IS NULL AND "vault_grants"."environment_id" IS NULL AND "vault_grants"."environment_slug" IS NULL;--> statement-breakpoint
269
361
 
270
362
  -- The chain starts at sequence 0 from 32 zero bytes.
271
363
  INSERT INTO audit_chain_head (only_row, next_seq, head_hash)
@@ -290,13 +382,35 @@ CREATE TRIGGER audit_log_no_truncate BEFORE TRUNCATE ON audit_log
290
382
  FOR EACH STATEMENT EXECUTE FUNCTION audit_log_append_only();
291
383
  --> statement-breakpoint
292
384
 
385
+ -- A secret version is never rewritten in place, only erased: a deleted
386
+ -- project or environment stays as a tombstone the log's entries name, and
387
+ -- its versions with it, their ciphertext and wrapped data key emptied. That
388
+ -- is the one change a version takes, for every login.
389
+ CREATE FUNCTION secret_versions_erase_only() RETURNS trigger LANGUAGE plpgsql AS $$
390
+ BEGIN
391
+ IF octet_length(NEW.ciphertext) <> 0 OR octet_length(NEW.wrapped_dek) <> 0
392
+ OR (NEW.id, NEW.secret_id, NEW.version, NEW.envelope_version, NEW.iv, NEW.auth_tag,
393
+ NEW.kek_provider, NEW.kek_id, NEW.kek_version, NEW.created_at, NEW.created_by)
394
+ IS DISTINCT FROM
395
+ (OLD.id, OLD.secret_id, OLD.version, OLD.envelope_version, OLD.iv, OLD.auth_tag,
396
+ OLD.kek_provider, OLD.kek_id, OLD.kek_version, OLD.created_at, OLD.created_by) THEN
397
+ RAISE EXCEPTION 'a secret version is only ever erased' USING ERRCODE = 'insufficient_privilege';
398
+ END IF;
399
+ RETURN NEW;
400
+ END
401
+ $$;
402
+ --> statement-breakpoint
403
+ CREATE TRIGGER secret_versions_erase_only BEFORE UPDATE ON secret_versions
404
+ FOR EACH ROW EXECUTE FUNCTION secret_versions_erase_only();
405
+ --> statement-breakpoint
406
+
293
407
  -- Two logins, provisioned outside coffre (their passwords are never ours),
294
408
  -- each with only the rights of its group:
295
409
  --
296
410
  -- coffre_runtime the server, through coffre_app: read and insert,
297
411
  -- UPDATE on named columns only, and never DELETE. A
298
- -- revoked session, a used device code and a removed
299
- -- sync key are rows that say so, not gaps.
412
+ -- revoked session, a used device code and a spent
413
+ -- token are rows that say so, not gaps.
300
414
  -- coffre_vault_runtime the vault, through coffre_vault: its members and
301
415
  -- grants, and reading what it decides on.
302
416
  --
@@ -378,6 +492,10 @@ GRANT USAGE ON SCHEMA drizzle TO coffre_app, coffre_vault;
378
492
  GRANT SELECT ON drizzle.__drizzle_migrations TO coffre_app, coffre_vault;
379
493
  --> statement-breakpoint
380
494
 
495
+ -- The app reads and adds; it never deletes. A binding, a registered client
496
+ -- and a connection change only in what their MAC allows to change in place;
497
+ -- a spent token and a reference never change at all, and the vault's log
498
+ -- says which references have ended.
381
499
  GRANT SELECT, INSERT ON
382
500
  projects,
383
501
  environments,
@@ -387,8 +505,12 @@ GRANT SELECT, INSERT ON
387
505
  identities,
388
506
  credentials,
389
507
  device_authorizations,
390
- syncs,
391
- sync_keys
508
+ service_bindings,
509
+ consumed_tokens,
510
+ secret_references,
511
+ dismissed_keys,
512
+ oauth_clients,
513
+ mcp_connections
392
514
  TO coffre_app;
393
515
  --> statement-breakpoint
394
516
 
@@ -399,11 +521,11 @@ GRANT SELECT ON
399
521
  TO coffre_app;
400
522
  --> statement-breakpoint
401
523
 
402
- GRANT UPDATE (slug, name, archived_at) ON projects TO coffre_app;
524
+ GRANT UPDATE (slug, name, archived_at, folder) ON projects TO coffre_app;
403
525
  --> statement-breakpoint
404
526
  GRANT UPDATE (slug, name, archived_at) ON environments TO coffre_app;
405
527
  --> statement-breakpoint
406
- GRANT UPDATE (key, current_version_id, current_version, updated_at, archived_at) ON secrets TO coffre_app;
528
+ GRANT UPDATE (key, current_version_id, current_version, updated_at, archived_at, folder) ON secrets TO coffre_app;
407
529
  --> statement-breakpoint
408
530
  GRANT UPDATE (next_seq, head_hash) ON audit_chain_head TO coffre_app;
409
531
  --> statement-breakpoint
@@ -414,11 +536,14 @@ GRANT UPDATE (last_used_at, last_used_ip, revoked_at, revoked_by, auth_mac) ON c
414
536
  GRANT UPDATE (decided_at, decision, generation, principal, consumed_at, auth_mac)
415
537
  ON device_authorizations TO coffre_app;
416
538
  --> statement-breakpoint
417
- GRANT UPDATE (config, credential_secret_id, paused_at, archived_at, lease_until,
418
- last_run_at, last_status, last_error)
419
- ON syncs TO coffre_app;
539
+ GRANT UPDATE (label, last_used_at, revoked_at, revoked_by, auth_mac) ON service_bindings TO coffre_app;
420
540
  --> statement-breakpoint
421
- GRANT UPDATE (secret_version_id, pushed_at, removed_at) ON sync_keys TO coffre_app;
541
+ GRANT UPDATE (dismissed_at, dismissed_by, restored_at, restored_by) ON dismissed_keys TO coffre_app;
542
+ --> statement-breakpoint
543
+ GRANT UPDATE (revoked_at, auth_mac) ON oauth_clients TO coffre_app;
544
+ --> statement-breakpoint
545
+ GRANT UPDATE (code_challenge, code_expires_at, refresh_hash, refresh_previous_hash, last_used_at, last_used_ip, revoked_at, revoked_by, auth_mac)
546
+ ON mcp_connections TO coffre_app;
422
547
  --> statement-breakpoint
423
548
 
424
549
  -- The vault reads what it decides on and the log it chains to, and writes
@@ -447,6 +572,9 @@ REVOKE TRUNCATE ON ALL TABLES IN SCHEMA public FROM coffre_vault;
447
572
  --> statement-breakpoint
448
573
  REVOKE UPDATE ON audit_log, secret_versions FROM coffre_app, coffre_vault;
449
574
  --> statement-breakpoint
575
+ -- What erasing a version empties, and nothing else: the trigger above holds every login to it.
576
+ GRANT UPDATE (ciphertext, wrapped_dek) ON secret_versions TO coffre_app;
577
+ --> statement-breakpoint
450
578
  REVOKE CREATE ON SCHEMA public FROM PUBLIC, coffre_app, coffre_runtime, coffre_vault, coffre_vault_runtime;
451
579
  --> statement-breakpoint
452
580
 
@@ -0,0 +1,24 @@
1
+ CREATE TABLE "mcp_approvals" (
2
+ "id" uuid PRIMARY KEY NOT NULL,
3
+ "connection_id" uuid NOT NULL,
4
+ "tool" text NOT NULL,
5
+ "arguments" text NOT NULL,
6
+ "digest" "bytea" NOT NULL,
7
+ "status" text DEFAULT 'pending' NOT NULL,
8
+ "outcome" text,
9
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
10
+ "expires_at" timestamp with time zone NOT NULL,
11
+ "decided_at" timestamp with time zone,
12
+ "reported_at" timestamp with time zone,
13
+ CONSTRAINT "mcp_approvals_status_check" CHECK ("mcp_approvals"."status" IN ('pending', 'approved', 'denied', 'cancelled', 'failed')),
14
+ CONSTRAINT "mcp_approvals_digest_check" CHECK (octet_length("mcp_approvals"."digest") = 32),
15
+ CONSTRAINT "mcp_approvals_arguments_check" CHECK ("mcp_approvals"."arguments"::jsonb IS NOT NULL)
16
+ );
17
+ --> statement-breakpoint
18
+ ALTER TABLE "mcp_approvals" ADD CONSTRAINT "mcp_approvals_connection_fkey" FOREIGN KEY ("connection_id") REFERENCES "public"."mcp_connections"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
19
+ CREATE INDEX "mcp_approvals_open_idx" ON "mcp_approvals" USING btree ("connection_id","digest") WHERE "mcp_approvals"."reported_at" IS NULL;--> statement-breakpoint
20
+ -- The app asks for approvals and records what became of them: the tool,
21
+ -- its arguments, their digest, the connection and the expiry are what the
22
+ -- person is shown, and never change. Nothing is deleted.
23
+ GRANT SELECT, INSERT ON mcp_approvals TO coffre_app;--> statement-breakpoint
24
+ GRANT UPDATE (status, outcome, decided_at, reported_at) ON mcp_approvals TO coffre_app;