@coffre/db 0.9.0 → 0.10.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 (49) hide show
  1. package/dist/connect.d.ts +1 -1
  2. package/dist/{database-hd1hAll-.d.ts → database-BFnO2b2P.d.ts} +1 -1
  3. package/dist/dialect.d.ts +1 -1
  4. package/dist/grants.d.ts +2 -2
  5. package/dist/hyperdrive.d.ts +1 -1
  6. package/dist/index.d.ts +2 -2
  7. package/dist/log.d.ts +7 -1
  8. package/dist/log.js +11 -1
  9. package/dist/migrations/postgres/0012_sensitive_values.sql +18 -0
  10. package/dist/migrations/postgres/0013_contributor_role.sql +5 -0
  11. package/dist/migrations/postgres/0014_custom_roles.sql +4 -0
  12. package/dist/migrations/postgres/meta/0012_snapshot.json +3004 -0
  13. package/dist/migrations/postgres/meta/0013_snapshot.json +3004 -0
  14. package/dist/migrations/postgres/meta/0014_snapshot.json +3000 -0
  15. package/dist/migrations/postgres/meta/_journal.json +21 -0
  16. package/dist/migrations/sqlite/0012_sensitive_values.sql +15 -0
  17. package/dist/migrations/sqlite/0013_contributor_role.sql +28 -0
  18. package/dist/migrations/sqlite/0014_custom_roles.sql +27 -0
  19. package/dist/migrations/sqlite/meta/0012_snapshot.json +2726 -0
  20. package/dist/migrations/sqlite/meta/0013_snapshot.json +2726 -0
  21. package/dist/migrations/sqlite/meta/0014_snapshot.json +2722 -0
  22. package/dist/migrations/sqlite/meta/_journal.json +21 -0
  23. package/dist/portable.d.ts +2 -2
  24. package/dist/{schema-CCYKTG-E.d.ts → schema-NBVA9q-s.d.ts} +17 -0
  25. package/dist/schema-sqlite.d.ts +17 -0
  26. package/dist/schema-sqlite.js +3 -6
  27. package/dist/schema-version.d.ts +1 -1
  28. package/dist/schema-version.js +42 -0
  29. package/dist/schema.d.ts +1 -1
  30. package/dist/schema.js +3 -7
  31. package/package.json +2 -2
  32. package/src/grants.ts +3 -2
  33. package/src/log.ts +16 -0
  34. package/src/migrations/postgres/0012_sensitive_values.sql +18 -0
  35. package/src/migrations/postgres/0013_contributor_role.sql +5 -0
  36. package/src/migrations/postgres/0014_custom_roles.sql +4 -0
  37. package/src/migrations/postgres/meta/0012_snapshot.json +3004 -0
  38. package/src/migrations/postgres/meta/0013_snapshot.json +3004 -0
  39. package/src/migrations/postgres/meta/0014_snapshot.json +3000 -0
  40. package/src/migrations/postgres/meta/_journal.json +21 -0
  41. package/src/migrations/sqlite/0012_sensitive_values.sql +15 -0
  42. package/src/migrations/sqlite/0013_contributor_role.sql +28 -0
  43. package/src/migrations/sqlite/0014_custom_roles.sql +27 -0
  44. package/src/migrations/sqlite/meta/0012_snapshot.json +2726 -0
  45. package/src/migrations/sqlite/meta/0013_snapshot.json +2726 -0
  46. package/src/migrations/sqlite/meta/0014_snapshot.json +2722 -0
  47. package/src/migrations/sqlite/meta/_journal.json +21 -0
  48. package/src/schema.sqlite.ts +8 -9
  49. package/src/schema.ts +8 -10
@@ -85,6 +85,27 @@
85
85
  "when": 1792317461299,
86
86
  "tag": "0011_plain_values",
87
87
  "breakpoints": true
88
+ },
89
+ {
90
+ "idx": 12,
91
+ "version": "6",
92
+ "when": 1792340920098,
93
+ "tag": "0012_sensitive_values",
94
+ "breakpoints": true
95
+ },
96
+ {
97
+ "idx": 13,
98
+ "version": "6",
99
+ "when": 1792343374795,
100
+ "tag": "0013_contributor_role",
101
+ "breakpoints": true
102
+ },
103
+ {
104
+ "idx": 14,
105
+ "version": "6",
106
+ "when": 1792345777255,
107
+ "tag": "0014_custom_roles",
108
+ "breakpoints": true
88
109
  }
89
110
  ]
90
111
  }
@@ -1,5 +1,5 @@
1
- import { m as schema_d_exports } from "./schema-CCYKTG-E.js";
2
- import { t as Database } from "./database-hd1hAll-.js";
1
+ import { m as schema_d_exports } from "./schema-NBVA9q-s.js";
2
+ import { t as Database } from "./database-BFnO2b2P.js";
3
3
  import { t as schema_sqlite_d_exports } from "./schema-sqlite.js";
4
4
  import { LibSQLDatabase } from "drizzle-orm/libsql";
5
5
  //#region src/portable.d.ts
@@ -731,6 +731,23 @@ declare const secretVersions: import("drizzle-orm/pg-core").PgTableWithColumns<{
731
731
  identity: undefined;
732
732
  generated: undefined;
733
733
  }, {}, {}>;
734
+ sensitive: import("drizzle-orm/pg-core").PgColumn<{
735
+ name: "sensitive";
736
+ tableName: "secret_versions";
737
+ dataType: "boolean";
738
+ columnType: "PgBoolean";
739
+ data: boolean;
740
+ driverParam: boolean;
741
+ notNull: true;
742
+ hasDefault: true;
743
+ isPrimaryKey: false;
744
+ isAutoincrement: false;
745
+ hasRuntimeDefault: false;
746
+ enumValues: undefined;
747
+ baseColumn: never;
748
+ identity: undefined;
749
+ generated: undefined;
750
+ }, {}, {}>;
734
751
  };
735
752
  dialect: "pg";
736
753
  }>;
@@ -775,6 +775,23 @@ export declare const secretVersions: import("drizzle-orm/sqlite-core").SQLiteTab
775
775
  }, {}, {
776
776
  length: undefined;
777
777
  }>;
778
+ sensitive: import("drizzle-orm/sqlite-core").SQLiteColumn<{
779
+ name: string;
780
+ tableName: "secret_versions";
781
+ dataType: "boolean";
782
+ columnType: "SQLiteBoolean";
783
+ data: boolean;
784
+ driverParam: number;
785
+ notNull: true;
786
+ hasDefault: true;
787
+ isPrimaryKey: false;
788
+ isAutoincrement: false;
789
+ hasRuntimeDefault: false;
790
+ enumValues: undefined;
791
+ baseColumn: never;
792
+ identity: undefined;
793
+ generated: undefined;
794
+ }, {}, {}>;
778
795
  };
779
796
  dialect: "sqlite";
780
797
  }>;
@@ -1,7 +1,6 @@
1
1
  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
- import { ROLE_NAMES, assignableToEnvironment } from "@coffre/core/access";
5
4
  import { blob, check, customType, foreignKey, index, integer, primaryKey, sqliteTable, text, unique, uniqueIndex } from "drizzle-orm/sqlite-core";
6
5
  //#region src/schema.sqlite.ts
7
6
  /**
@@ -125,7 +124,8 @@ const secretVersions = sqliteTable("secret_versions", {
125
124
  recipe: text(),
126
125
  pairId: text("pair_id"),
127
126
  pairHalf: text("pair_half"),
128
- plaintext: text()
127
+ plaintext: text(),
128
+ sensitive: flag("sensitive").notNull().default(false)
129
129
  }, (table) => [
130
130
  unique("secret_versions_secret_id_version_key").on(table.secretId, table.version),
131
131
  check("secret_versions_version_check", sql`${table.version} > 0`),
@@ -157,8 +157,6 @@ const dismissedKeys = sqliteTable("dismissed_keys", {
157
157
  foreignColumns: [environments.id]
158
158
  }).onDelete("restrict")
159
159
  ]);
160
- const ROLES = sql.raw(ROLE_NAMES.map((role) => `'${role}'`).join(", "));
161
- const ENVIRONMENT_ROLES = sql.raw(ROLE_NAMES.filter(assignableToEnvironment).map((role) => `'${role}'`).join(", "));
162
160
  const vaultMembers = sqliteTable("vault_members", {
163
161
  principal: text().primaryKey(),
164
162
  status: text().notNull(),
@@ -195,8 +193,7 @@ const vaultGrants = sqliteTable("vault_grants", {
195
193
  }, (table) => [
196
194
  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)`),
197
195
  check("vault_grants_environment_slug_check", sql`${table.environmentSlug} IS NULL OR (${isSlug(table.environmentSlug, 63)})`),
198
- check("vault_grants_role_check", sql`${table.role} IN (${ROLES})`),
199
- check("vault_grants_environment_role_check", sql`(${table.environmentId} IS NULL AND ${table.environmentSlug} IS NULL) OR ${table.role} IN (${ENVIRONMENT_ROLES})`),
196
+ check("vault_grants_role_check", sql`length(${table.role}) BETWEEN 2 AND 32 AND ${table.role} GLOB '[a-z]*' AND ${table.role} NOT GLOB '*[^a-z0-9-]*'`),
200
197
  unique("vault_grants_on_project").on(table.principal, table.projectId),
201
198
  unique("vault_grants_on_environment").on(table.principal, table.environmentId),
202
199
  unique("vault_grants_on_environment_slug").on(table.principal, table.environmentSlug),
@@ -1,4 +1,4 @@
1
- import { i as Queryable } from "./database-hd1hAll-.js";
1
+ import { i as Queryable } from "./database-BFnO2b2P.js";
2
2
  import { Engine } from "./dialect.js";
3
3
  //#region src/schema-version.d.ts
4
4
  /**
@@ -92,6 +92,27 @@ const KNOWN_MIGRATIONS = {
92
92
  "when": 1792317460399,
93
93
  "tag": "0011_plain_values",
94
94
  "breakpoints": true
95
+ },
96
+ {
97
+ "idx": 12,
98
+ "version": "7",
99
+ "when": 1792340919452,
100
+ "tag": "0012_sensitive_values",
101
+ "breakpoints": true
102
+ },
103
+ {
104
+ "idx": 13,
105
+ "version": "7",
106
+ "when": 1792343374226,
107
+ "tag": "0013_contributor_role",
108
+ "breakpoints": true
109
+ },
110
+ {
111
+ "idx": 14,
112
+ "version": "7",
113
+ "when": 1792345776262,
114
+ "tag": "0014_custom_roles",
115
+ "breakpoints": true
95
116
  }
96
117
  ].map((entry) => entry.tag),
97
118
  sqlite: [
@@ -178,6 +199,27 @@ const KNOWN_MIGRATIONS = {
178
199
  "when": 1792317461299,
179
200
  "tag": "0011_plain_values",
180
201
  "breakpoints": true
202
+ },
203
+ {
204
+ "idx": 12,
205
+ "version": "6",
206
+ "when": 1792340920098,
207
+ "tag": "0012_sensitive_values",
208
+ "breakpoints": true
209
+ },
210
+ {
211
+ "idx": 13,
212
+ "version": "6",
213
+ "when": 1792343374795,
214
+ "tag": "0013_contributor_role",
215
+ "breakpoints": true
216
+ },
217
+ {
218
+ "idx": 14,
219
+ "version": "6",
220
+ "when": 1792345777255,
221
+ "tag": "0014_custom_roles",
222
+ "breakpoints": true
181
223
  }
182
224
  ].map((entry) => entry.tag)
183
225
  };
package/dist/schema.d.ts CHANGED
@@ -1,2 +1,2 @@
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-CCYKTG-E.js";
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-NBVA9q-s.js";
2
2
  export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, dismissedKeys, environments, environmentsRelations, identities, mcpApprovals, mcpConnections, oauthClients, projects, secretReferences, secretVersions, secrets, secretsRelations, serviceBindings, vaultGrants, vaultMembers };
package/dist/schema.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { n as relationsOf, t as ACTIVE_SUBJECT } from "./relations-BfR3JjT1.js";
3
3
  import { sql } from "drizzle-orm";
4
- import { ROLE_NAMES, assignableToEnvironment } from "@coffre/core/access";
5
4
  import { bigint, boolean, check, customType, foreignKey, index, integer, pgTable, primaryKey, text, timestamp, unique, uniqueIndex, uuid } from "drizzle-orm/pg-core";
6
5
  //#region src/schema.ts
7
6
  var schema_exports = /* @__PURE__ */ __exportAll({
@@ -130,7 +129,8 @@ const secretVersions = pgTable("secret_versions", {
130
129
  recipe: text(),
131
130
  pairId: uuid("pair_id"),
132
131
  pairHalf: text("pair_half"),
133
- plaintext: text()
132
+ plaintext: text(),
133
+ sensitive: boolean().notNull().default(false)
134
134
  }, (table) => [
135
135
  unique("secret_versions_secret_id_version_key").on(table.secretId, table.version),
136
136
  check("secret_versions_version_check", sql`${table.version} > 0`),
@@ -169,9 +169,6 @@ const dismissedKeys = pgTable("dismissed_keys", {
169
169
  foreignColumns: [environments.id]
170
170
  }).onDelete("restrict")
171
171
  ]);
172
- /** The role catalogue, as SQL: every role, and those an environment may be granted. */
173
- const ROLES = sql.raw(ROLE_NAMES.map((role) => `'${role}'`).join(", "));
174
- const ENVIRONMENT_ROLES = sql.raw(ROLE_NAMES.filter(assignableToEnvironment).map((role) => `'${role}'`).join(", "));
175
172
  /**
176
173
  * The vault's member directory: everyone it has admitted, written by the
177
174
  * vault alone, read by both. A principal with no row is no member.
@@ -220,8 +217,7 @@ const vaultGrants = pgTable("vault_grants", {
220
217
  }, (table) => [
221
218
  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)`),
222
219
  check("vault_grants_environment_slug_check", sql`${table.environmentSlug} IS NULL OR ${table.environmentSlug} ~ '^[a-z0-9][a-z0-9-]{0,62}$'`),
223
- check("vault_grants_role_check", sql`${table.role} IN (${ROLES})`),
224
- check("vault_grants_environment_role_check", sql`(${table.environmentId} IS NULL AND ${table.environmentSlug} IS NULL) OR ${table.role} IN (${ENVIRONMENT_ROLES})`),
220
+ check("vault_grants_role_check", sql`${table.role} ~ '^[a-z][a-z0-9-]{1,31}$'`),
225
221
  unique("vault_grants_on_project").on(table.principal, table.projectId),
226
222
  unique("vault_grants_on_environment").on(table.principal, table.environmentId),
227
223
  unique("vault_grants_on_environment_slug").on(table.principal, table.environmentSlug),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffre/db",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "coffre's database: the schema, its migrations and the connections, on Postgres or SQLite.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -79,7 +79,7 @@
79
79
  "drizzle-orm": "0.45.2",
80
80
  "pg": "8.16.3",
81
81
  "pg-connection-string": "2.14.0",
82
- "@coffre/core": "0.9.0"
82
+ "@coffre/core": "0.10.0"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@types/node": "26.1.1",
package/src/grants.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  type Conversion,
10
10
  type ConversionInput,
11
11
  type InstanceRole,
12
+ type BuiltinRole,
12
13
  type Role,
13
14
  type Scope,
14
15
  } from '@coffre/core/access';
@@ -129,10 +130,10 @@ export function everyProjectConversion(
129
130
  role: person ? role : 'member',
130
131
  everyProject: live
131
132
  .filter((grant) => grant.projectId === null && grant.environmentId === null)
132
- .map((grant) => ({ environmentSlug: grant.environmentSlug, role: grant.role as Role, expiresAt: grant.expiresAt })),
133
+ .map((grant) => ({ environmentSlug: grant.environmentSlug, role: grant.role as BuiltinRole, expiresAt: grant.expiresAt })),
133
134
  grants: live
134
135
  .filter((grant) => grant.projectId !== null)
135
- .map((grant) => ({ projectId: grant.projectId, environmentId: grant.environmentId, role: grant.role as Role, expiresAt: grant.expiresAt })),
136
+ .map((grant) => ({ projectId: grant.projectId, environmentId: grant.environmentId, role: grant.role as BuiltinRole, expiresAt: grant.expiresAt })),
136
137
  projects,
137
138
  });
138
139
  }
package/src/log.ts CHANGED
@@ -283,6 +283,22 @@ export async function newestVaultEntries(db: Queryable, action: string, limit: n
283
283
  return rows as StoredEntry[];
284
284
  }
285
285
 
286
+ /**
287
+ * The number of the newest entry in the vault's name, allowed, of one
288
+ * `action`, or null for none: whether one was appended since, and nothing
289
+ * of what it says, so its MAC is not checked. One indexed read.
290
+ */
291
+ export async function newestVaultSeq(db: Queryable, action: string): Promise<bigint | null> {
292
+ const { auditLog } = tablesOf(db);
293
+ const [row] = await db
294
+ .select({ seq: auditLog.seq })
295
+ .from(auditLog)
296
+ .where(and(eq(auditLog.author, 'vault'), eq(auditLog.action, action), eq(auditLog.decision, 'allow')))
297
+ .orderBy(desc(auditLog.seq))
298
+ .limit(1);
299
+ return (row?.seq as bigint | undefined) ?? null;
300
+ }
301
+
286
302
  /** How many entries in its name the vault reads back, a page at a time, for its own newest of a kind (`newestAuthentic`). */
287
303
  export const LOOK_BACK = { page: 32, pages: 32 };
288
304
 
@@ -0,0 +1,18 @@
1
+ -- Sensitive values (0.10): a version never handed back to a person, which the vault's wrap of its data key binds. Defaults to false, so the release before writes on as it did.
2
+ ALTER TABLE "secret_versions" ADD COLUMN "sensitive" boolean DEFAULT false NOT NULL;--> statement-breakpoint
3
+ -- A version is still only ever erased: its sensitivity with the rest of it.
4
+ CREATE OR REPLACE FUNCTION secret_versions_erase_only() RETURNS trigger LANGUAGE plpgsql AS $$
5
+ BEGIN
6
+ IF octet_length(NEW.ciphertext) <> 0 OR octet_length(NEW.wrapped_dek) <> 0 OR NEW.plaintext IS NOT NULL
7
+ OR (NEW.id, NEW.secret_id, NEW.version, NEW.envelope_version, NEW.iv, NEW.auth_tag,
8
+ NEW.kek_provider, NEW.kek_id, NEW.kek_version, NEW.created_at, NEW.created_by,
9
+ NEW.recipe, NEW.pair_id, NEW.pair_half, NEW.sensitive)
10
+ IS DISTINCT FROM
11
+ (OLD.id, OLD.secret_id, OLD.version, OLD.envelope_version, OLD.iv, OLD.auth_tag,
12
+ OLD.kek_provider, OLD.kek_id, OLD.kek_version, OLD.created_at, OLD.created_by,
13
+ OLD.recipe, OLD.pair_id, OLD.pair_half, OLD.sensitive) THEN
14
+ RAISE EXCEPTION 'a secret version is only ever erased' USING ERRCODE = 'insufficient_privilege';
15
+ END IF;
16
+ RETURN NEW;
17
+ END
18
+ $$;
@@ -0,0 +1,5 @@
1
+ -- The Contributor role (0.10): use and write, never read. The role checks widen to it, which the release before never grants.
2
+ ALTER TABLE "vault_grants" DROP CONSTRAINT "vault_grants_role_check";--> statement-breakpoint
3
+ ALTER TABLE "vault_grants" DROP CONSTRAINT "vault_grants_environment_role_check";--> statement-breakpoint
4
+ ALTER TABLE "vault_grants" ADD CONSTRAINT "vault_grants_role_check" CHECK ("vault_grants"."role" IN ('viewer', 'contributor', 'developer', 'maintainer', 'access-manager', 'auditor', 'owner'));--> statement-breakpoint
5
+ ALTER TABLE "vault_grants" ADD 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', 'contributor', 'developer', 'auditor'));
@@ -0,0 +1,4 @@
1
+ -- Roles an Owner makes (0.10): a grant names a role by its id, which the vault's policy defines; which of them an environment may be granted, the vault decides. The checks widen: the release before grants only the roles it ships.
2
+ ALTER TABLE "vault_grants" DROP CONSTRAINT "vault_grants_environment_role_check";--> statement-breakpoint
3
+ ALTER TABLE "vault_grants" DROP CONSTRAINT "vault_grants_role_check";--> statement-breakpoint
4
+ ALTER TABLE "vault_grants" ADD CONSTRAINT "vault_grants_role_check" CHECK ("vault_grants"."role" ~ '^[a-z][a-z0-9-]{1,31}$');