@coffre/db 0.14.5 → 0.15.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/README.md +2 -1
- package/dist/change-records-_fKvahvs.js +55 -0
- package/dist/connect.d.ts +22 -10
- package/dist/connect.js +74 -12
- package/dist/database-CVteLhp1.d.ts +410 -0
- package/dist/{database-BadNeeN-.js → database-D55ENPX-.js} +46 -25
- package/dist/dialect.d.ts +4 -3
- package/dist/dialect.js +14 -5
- package/dist/grants.d.ts +4 -4
- package/dist/grants.js +4 -4
- package/dist/hyperdrive.d.ts +48 -23
- package/dist/hyperdrive.js +168 -91
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/log-DaD7fyUq.d.ts +722 -0
- package/dist/log.d.ts +2 -622
- package/dist/log.js +178 -13
- package/dist/migrate.d.ts +43 -3
- package/dist/migrate.js +149 -29
- package/dist/migrations/postgres/0022_log_015.sql +672 -0
- package/dist/migrations/postgres/0023_change_path.sql +153 -0
- package/dist/migrations/postgres/0024_use_015.sql +556 -0
- package/dist/migrations/postgres/0025_app_change.sql +221 -0
- package/dist/migrations/postgres/0026_issue_lock.sql +106 -0
- package/dist/migrations/postgres/0027_retention.sql +118 -0
- package/dist/migrations/postgres/0028_use_stalls.sql +594 -0
- package/dist/migrations/postgres/0029_use_objects.sql +887 -0
- package/dist/migrations/postgres/0030_unarchive_restores.sql +259 -0
- package/dist/migrations/postgres/0031_app_read.sql +65 -0
- package/dist/migrations/postgres/0032_secrets_live_index.sql +4 -0
- package/dist/migrations/postgres/0033_use_generations.sql +582 -0
- package/dist/migrations/postgres/0034_vault_credentials.sql +153 -0
- package/dist/migrations/postgres/0035_first_days.sql +3 -0
- package/dist/migrations/postgres/0036_vault_issues.sql +91 -0
- package/dist/migrations/postgres/0037_vault_bindings.sql +271 -0
- package/dist/migrations/postgres/0040_binding_origin.sql +159 -0
- package/dist/migrations/postgres/0041_cutover.sql +132 -0
- package/dist/migrations/postgres/0042_sign_in_index.sql +7 -0
- package/dist/migrations/postgres/0043_issue_revoke_index.sql +4 -0
- package/dist/migrations/postgres/0044_use_by_name.sql +705 -0
- package/dist/migrations/postgres/0045_decide_first.sql +878 -0
- package/dist/migrations/postgres/0046_way_in_uses.sql +1149 -0
- package/dist/migrations/postgres/0047_cutover_finish.sql +268 -0
- package/dist/migrations/postgres/0048_use_batch.sql +59 -0
- package/dist/migrations/postgres/0049_reads.sql +171 -0
- package/dist/migrations/postgres/meta/0022_snapshot.json +3831 -0
- package/dist/migrations/postgres/meta/0023_snapshot.json +3831 -0
- package/dist/migrations/postgres/meta/0024_snapshot.json +3831 -0
- package/dist/migrations/postgres/meta/0025_snapshot.json +3831 -0
- package/dist/migrations/postgres/meta/0026_snapshot.json +3831 -0
- package/dist/migrations/postgres/meta/0027_snapshot.json +3853 -0
- package/dist/migrations/postgres/meta/0028_snapshot.json +3853 -0
- package/dist/migrations/postgres/meta/0029_snapshot.json +3859 -0
- package/dist/migrations/postgres/meta/0030_snapshot.json +3859 -0
- package/dist/migrations/postgres/meta/0031_snapshot.json +3859 -0
- package/dist/migrations/postgres/meta/0032_snapshot.json +3875 -0
- package/dist/migrations/postgres/meta/0033_snapshot.json +3875 -0
- package/dist/migrations/postgres/meta/0034_snapshot.json +3875 -0
- package/dist/migrations/postgres/meta/0035_snapshot.json +3875 -0
- package/dist/migrations/postgres/meta/0036_snapshot.json +3875 -0
- package/dist/migrations/postgres/meta/0037_snapshot.json +3875 -0
- package/dist/migrations/postgres/meta/0038_snapshot.json +3881 -0
- package/dist/migrations/postgres/meta/0039_snapshot.json +3881 -0
- package/dist/migrations/postgres/meta/0040_snapshot.json +3931 -0
- package/dist/migrations/postgres/meta/0041_snapshot.json +3981 -0
- package/dist/migrations/postgres/meta/0042_snapshot.json +3981 -0
- package/dist/migrations/postgres/meta/0043_snapshot.json +3981 -0
- package/dist/migrations/postgres/meta/0044_snapshot.json +3991 -0
- package/dist/migrations/postgres/meta/0045_snapshot.json +3997 -0
- package/dist/migrations/postgres/meta/0046_snapshot.json +3997 -0
- package/dist/migrations/postgres/meta/0047_snapshot.json +4234 -0
- package/dist/migrations/postgres/meta/_journal.json +182 -0
- package/dist/migrations/sqlite/0018_log_015.sql +84 -0
- package/dist/migrations/sqlite/0019_retention.sql +2 -0
- package/dist/migrations/sqlite/0020_ended_references.sql +1 -0
- package/dist/migrations/sqlite/0021_secrets_live_index.sql +1 -0
- package/dist/migrations/sqlite/0022_binding_origin.sql +1 -0
- package/dist/migrations/sqlite/0023_sign_in_index.sql +2 -0
- package/dist/migrations/sqlite/0024_issue_revoke_index.sql +2 -0
- package/dist/migrations/sqlite/0025_way_in_uses.sql +42 -0
- package/dist/migrations/sqlite/0026_cutover_finish.sql +11 -0
- package/dist/migrations/sqlite/0027_reads.sql +38 -0
- package/dist/migrations/sqlite/meta/0018_snapshot.json +3529 -0
- package/dist/migrations/sqlite/meta/0019_snapshot.json +3538 -0
- package/dist/migrations/sqlite/meta/0020_snapshot.json +3545 -0
- package/dist/migrations/sqlite/meta/0021_snapshot.json +3553 -0
- package/dist/migrations/sqlite/meta/0022_snapshot.json +3560 -0
- package/dist/migrations/sqlite/meta/0023_snapshot.json +3579 -0
- package/dist/migrations/sqlite/meta/0024_snapshot.json +3598 -0
- package/dist/migrations/sqlite/meta/0025_snapshot.json +3609 -0
- package/dist/migrations/sqlite/meta/0026_snapshot.json +3616 -0
- package/dist/migrations/sqlite/meta/0027_snapshot.json +3834 -0
- package/dist/migrations/sqlite/meta/_journal.json +70 -0
- package/dist/platform-Y3gICK6n.d.ts +58 -0
- package/dist/portable.d.ts +2 -2
- package/dist/postgres-BI3xTnyw.js +1718 -0
- package/dist/postgres-platform-node.d.ts +14 -0
- package/dist/postgres-platform-node.js +21 -0
- package/dist/postgres-platform-workerd.d.ts +61 -0
- package/dist/postgres-platform-workerd.js +163 -0
- package/dist/{schema-BfQEBqFv.d.ts → schema-DgoPqqsI.d.ts} +1326 -7
- package/dist/schema-sqlite.d.ts +1306 -6
- package/dist/schema-sqlite.js +154 -18
- package/dist/schema-version.d.ts +1 -1
- package/dist/schema-version.js +254 -2
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js +226 -20
- package/package.json +13 -5
- package/src/active-subject.ts +9 -0
- package/src/change-records.ts +38 -0
- package/src/connect.ts +67 -13
- package/src/database.ts +67 -51
- package/src/dialect.ts +17 -8
- package/src/grants.ts +3 -3
- package/src/hyperdrive.ts +181 -104
- package/src/log.ts +196 -15
- package/src/migrate.ts +157 -30
- package/src/migrations/postgres/0022_log_015.sql +672 -0
- package/src/migrations/postgres/0023_change_path.sql +153 -0
- package/src/migrations/postgres/0024_use_015.sql +556 -0
- package/src/migrations/postgres/0025_app_change.sql +221 -0
- package/src/migrations/postgres/0026_issue_lock.sql +106 -0
- package/src/migrations/postgres/0027_retention.sql +118 -0
- package/src/migrations/postgres/0028_use_stalls.sql +594 -0
- package/src/migrations/postgres/0029_use_objects.sql +887 -0
- package/src/migrations/postgres/0030_unarchive_restores.sql +259 -0
- package/src/migrations/postgres/0031_app_read.sql +65 -0
- package/src/migrations/postgres/0032_secrets_live_index.sql +4 -0
- package/src/migrations/postgres/0033_use_generations.sql +582 -0
- package/src/migrations/postgres/0034_vault_credentials.sql +153 -0
- package/src/migrations/postgres/0035_first_days.sql +3 -0
- package/src/migrations/postgres/0036_vault_issues.sql +91 -0
- package/src/migrations/postgres/0037_vault_bindings.sql +271 -0
- package/src/migrations/postgres/0040_binding_origin.sql +159 -0
- package/src/migrations/postgres/0041_cutover.sql +132 -0
- package/src/migrations/postgres/0042_sign_in_index.sql +7 -0
- package/src/migrations/postgres/0043_issue_revoke_index.sql +4 -0
- package/src/migrations/postgres/0044_use_by_name.sql +705 -0
- package/src/migrations/postgres/0045_decide_first.sql +878 -0
- package/src/migrations/postgres/0046_way_in_uses.sql +1149 -0
- package/src/migrations/postgres/0047_cutover_finish.sql +268 -0
- package/src/migrations/postgres/0048_use_batch.sql +59 -0
- package/src/migrations/postgres/0049_reads.sql +171 -0
- package/src/migrations/postgres/meta/0022_snapshot.json +3831 -0
- package/src/migrations/postgres/meta/0023_snapshot.json +3831 -0
- package/src/migrations/postgres/meta/0024_snapshot.json +3831 -0
- package/src/migrations/postgres/meta/0025_snapshot.json +3831 -0
- package/src/migrations/postgres/meta/0026_snapshot.json +3831 -0
- package/src/migrations/postgres/meta/0027_snapshot.json +3853 -0
- package/src/migrations/postgres/meta/0028_snapshot.json +3853 -0
- package/src/migrations/postgres/meta/0029_snapshot.json +3859 -0
- package/src/migrations/postgres/meta/0030_snapshot.json +3859 -0
- package/src/migrations/postgres/meta/0031_snapshot.json +3859 -0
- package/src/migrations/postgres/meta/0032_snapshot.json +3875 -0
- package/src/migrations/postgres/meta/0033_snapshot.json +3875 -0
- package/src/migrations/postgres/meta/0034_snapshot.json +3875 -0
- package/src/migrations/postgres/meta/0035_snapshot.json +3875 -0
- package/src/migrations/postgres/meta/0036_snapshot.json +3875 -0
- package/src/migrations/postgres/meta/0037_snapshot.json +3875 -0
- package/src/migrations/postgres/meta/0038_snapshot.json +3881 -0
- package/src/migrations/postgres/meta/0039_snapshot.json +3881 -0
- package/src/migrations/postgres/meta/0040_snapshot.json +3931 -0
- package/src/migrations/postgres/meta/0041_snapshot.json +3981 -0
- package/src/migrations/postgres/meta/0042_snapshot.json +3981 -0
- package/src/migrations/postgres/meta/0043_snapshot.json +3981 -0
- package/src/migrations/postgres/meta/0044_snapshot.json +3991 -0
- package/src/migrations/postgres/meta/0045_snapshot.json +3997 -0
- package/src/migrations/postgres/meta/0046_snapshot.json +3997 -0
- package/src/migrations/postgres/meta/0047_snapshot.json +4234 -0
- package/src/migrations/postgres/meta/_journal.json +182 -0
- package/src/migrations/sqlite/0018_log_015.sql +84 -0
- package/src/migrations/sqlite/0019_retention.sql +2 -0
- package/src/migrations/sqlite/0020_ended_references.sql +1 -0
- package/src/migrations/sqlite/0021_secrets_live_index.sql +1 -0
- package/src/migrations/sqlite/0022_binding_origin.sql +1 -0
- package/src/migrations/sqlite/0023_sign_in_index.sql +2 -0
- package/src/migrations/sqlite/0024_issue_revoke_index.sql +2 -0
- package/src/migrations/sqlite/0025_way_in_uses.sql +42 -0
- package/src/migrations/sqlite/0026_cutover_finish.sql +11 -0
- package/src/migrations/sqlite/0027_reads.sql +38 -0
- package/src/migrations/sqlite/meta/0018_snapshot.json +3529 -0
- package/src/migrations/sqlite/meta/0019_snapshot.json +3538 -0
- package/src/migrations/sqlite/meta/0020_snapshot.json +3545 -0
- package/src/migrations/sqlite/meta/0021_snapshot.json +3553 -0
- package/src/migrations/sqlite/meta/0022_snapshot.json +3560 -0
- package/src/migrations/sqlite/meta/0023_snapshot.json +3579 -0
- package/src/migrations/sqlite/meta/0024_snapshot.json +3598 -0
- package/src/migrations/sqlite/meta/0025_snapshot.json +3609 -0
- package/src/migrations/sqlite/meta/0026_snapshot.json +3616 -0
- package/src/migrations/sqlite/meta/0027_snapshot.json +3834 -0
- package/src/migrations/sqlite/meta/_journal.json +70 -0
- package/src/postgres-js/README.md +183 -0
- package/src/postgres-js/UNLICENSE +24 -0
- package/src/postgres-js/bytes.ts +89 -0
- package/src/postgres-js/connection.ts +882 -0
- package/src/postgres-js/errors.ts +84 -0
- package/src/postgres-js/index.ts +516 -0
- package/src/postgres-js/platform.node.ts +29 -0
- package/src/postgres-js/platform.ts +60 -0
- package/src/postgres-js/platform.workerd.ts +225 -0
- package/src/postgres-js/query.ts +123 -0
- package/src/postgres-js/queue.ts +42 -0
- package/src/postgres-js/result.ts +46 -0
- package/src/postgres-js/types.ts +52 -0
- package/src/postgres.ts +375 -26
- package/src/schema-version.ts +2 -2
- package/src/schema.sqlite.ts +198 -7
- package/src/schema.ts +291 -6
- package/src/verify-runtime.ts +18 -29
- package/LICENSE +0 -21
- package/dist/database-wq1AVRAP.d.ts +0 -73
- package/dist/postgres-Bmar8y0q.js +0 -29
- package/dist/relations-BfR3JjT1.js +0 -38
- package/src/relations.ts +0 -40
package/src/schema.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
integer,
|
|
10
10
|
pgTable,
|
|
11
11
|
primaryKey,
|
|
12
|
+
smallint,
|
|
12
13
|
text,
|
|
13
14
|
timestamp,
|
|
14
15
|
unique,
|
|
@@ -17,7 +18,8 @@ import {
|
|
|
17
18
|
type AnyPgColumn,
|
|
18
19
|
} from 'drizzle-orm/pg-core';
|
|
19
20
|
|
|
20
|
-
import { ACTIVE_SUBJECT
|
|
21
|
+
import { ACTIVE_SUBJECT } from './active-subject.ts';
|
|
22
|
+
import { recorded } from './change-records.ts';
|
|
21
23
|
|
|
22
24
|
const bytea = customType<{ data: Buffer }>({
|
|
23
25
|
dataType: () => 'bytea',
|
|
@@ -74,8 +76,15 @@ export const environments = pgTable(
|
|
|
74
76
|
// A personal config's owner, `user:<email>`: a fork whose values only they
|
|
75
77
|
// read, the vault binding each to them. Set when it is made and never after.
|
|
76
78
|
owner: text(),
|
|
79
|
+
// Whether it is protected, on 0.15's environment object (0.14 keeps it in
|
|
80
|
+
// the policy's entries). Null until 0.15's cutover writes it.
|
|
81
|
+
protected: boolean(),
|
|
82
|
+
// 0.15's seal (0022_log_015): the vault's MAC over the object this row is part of, HMAC-SHA256 cut to 16
|
|
83
|
+
// bytes, the same seal its newest change in `log_changes` records. Null until the row is sealed.
|
|
84
|
+
seal: bytea(),
|
|
77
85
|
},
|
|
78
86
|
(table) => [
|
|
87
|
+
check('environments_seal_check', sql`${table.seal} IS NULL OR octet_length(${table.seal}) = 16`),
|
|
79
88
|
unique('environments_project_id_slug_key').on(table.projectId, table.slug),
|
|
80
89
|
unique('environments_project_scoped').on(table.id, table.projectId),
|
|
81
90
|
check('environments_slug_check', sql`${table.slug} ~ ${PLACE_SLUG}`),
|
|
@@ -118,8 +127,12 @@ export const secrets = pgTable(
|
|
|
118
127
|
archivedAt: timestamp('archived_at', { withTimezone: true }),
|
|
119
128
|
// Within its environment: the same key elsewhere has its own.
|
|
120
129
|
folder: text(),
|
|
130
|
+
// 0.15's seal (0022_log_015): the vault's MAC over the object this row is part of, HMAC-SHA256 cut to 16
|
|
131
|
+
// bytes, the same seal its newest change in `log_changes` records. Null until the row is sealed.
|
|
132
|
+
seal: bytea(),
|
|
121
133
|
},
|
|
122
134
|
(table) => [
|
|
135
|
+
check('secrets_seal_check', sql`${table.seal} IS NULL OR octet_length(${table.seal}) = 16`),
|
|
123
136
|
unique('secrets_project_id_environment_id_key_key').on(
|
|
124
137
|
table.projectId,
|
|
125
138
|
table.environmentId,
|
|
@@ -143,6 +156,8 @@ export const secrets = pgTable(
|
|
|
143
156
|
foreignColumns: [environments.id, environments.projectId],
|
|
144
157
|
}),
|
|
145
158
|
index('secrets_lookup_idx').on(table.projectId, table.environmentId, table.key),
|
|
159
|
+
// The live keys of an environment, which a read by environment alone (the places tree's counts, `liveKeysIn`) walks without the archived ones beside them; the one above leads with the project (T152, 0032).
|
|
160
|
+
index('secrets_live_idx').on(table.environmentId).where(sql`${table.archivedAt} IS NULL`),
|
|
146
161
|
],
|
|
147
162
|
);
|
|
148
163
|
|
|
@@ -257,8 +272,12 @@ export const vaultMembers = pgTable(
|
|
|
257
272
|
// The vault's MAC over the row and the member's grants, lapsed ones too:
|
|
258
273
|
// a grant added, edited or deleted outside the vault fails it.
|
|
259
274
|
mac: bytea().notNull(),
|
|
275
|
+
// 0.15's seal (0022_log_015): the vault's MAC over the object this row is part of, HMAC-SHA256 cut to 16
|
|
276
|
+
// bytes, the same seal its newest change in `log_changes` records. Null until the row is sealed.
|
|
277
|
+
seal: bytea(),
|
|
260
278
|
},
|
|
261
279
|
(table) => [
|
|
280
|
+
check('vault_members_seal_check', sql`${table.seal} IS NULL OR octet_length(${table.seal}) = 16`),
|
|
262
281
|
check('vault_members_mac_check', sql`octet_length(${table.mac}) = 32`),
|
|
263
282
|
check('vault_members_principal_check', sql`${table.principal} ~ '^(user|token):[^[:space:]:][^[:space:]]*$'`),
|
|
264
283
|
// A person is their email address, lowercased, as in sign-in.
|
|
@@ -378,6 +397,10 @@ export const auditLog = pgTable(
|
|
|
378
397
|
prevHash: bytea('prev_hash').notNull(),
|
|
379
398
|
mac: bytea().notNull(),
|
|
380
399
|
hash: bytea().notNull(),
|
|
400
|
+
// Set on every entry a writer under 0.15's log appends, and covered by no MAC: once the cutover is there, an
|
|
401
|
+
// allowed write without it is 0.14's code, which migration 0047's trigger refuses (docs/design/0.15-plan.md,
|
|
402
|
+
// "As built: PR 9b"). Gone with 0.14's appends in 0.16.
|
|
403
|
+
log015: boolean('log015'),
|
|
381
404
|
},
|
|
382
405
|
(table) => [
|
|
383
406
|
check('audit_log_author_check', sql`${table.author} IN ('app', 'vault')`),
|
|
@@ -485,6 +508,10 @@ export const secretReferences = pgTable(
|
|
|
485
508
|
createdSeq: bigint('created_seq', { mode: 'bigint' }).notNull(),
|
|
486
509
|
createdAt: createdAt(),
|
|
487
510
|
createdBy: text('created_by').notNull(),
|
|
511
|
+
// 0.15's end of the reference (0028): when its holder stopped reading its source, broken, replaced or cut, written
|
|
512
|
+
// with 0.14's `reference.end` in the vault's decision. The holder's object reads its newest row's source only while
|
|
513
|
+
// this is null. Null on rows 0.14 ended: 0.14's log holds those ends, which the cutover (plan PR 9) writes here.
|
|
514
|
+
endedAt: timestamp('ended_at', { withTimezone: true }),
|
|
488
515
|
},
|
|
489
516
|
(table) => [
|
|
490
517
|
unique('secret_references_created_seq_key').on(table.createdSeq),
|
|
@@ -598,14 +625,21 @@ export const serviceBindings = pgTable(
|
|
|
598
625
|
scope: text(),
|
|
599
626
|
// What a workload's binding says beyond its claims (`BindingPolicy`), canonical JSON: null on a binding `/api/auth/oidc` takes, never on another (docs/design/workload-access.md, section 11).
|
|
600
627
|
policy: text(),
|
|
628
|
+
// The instance it was made on, its public URL, which its object's seal covers (0040_binding_origin; PR 3b): null on one
|
|
629
|
+
// made before, which is named on the instance's public URL.
|
|
630
|
+
origin: text(),
|
|
601
631
|
label: text(),
|
|
602
632
|
createdAt: createdAt(),
|
|
603
633
|
createdBy: text('created_by').notNull(),
|
|
604
634
|
lastUsedAt: timestamp('last_used_at', { withTimezone: true }),
|
|
605
635
|
revokedAt: timestamp('revoked_at', { withTimezone: true }),
|
|
606
636
|
revokedBy: text('revoked_by'),
|
|
637
|
+
// 0.15's seal (0022_log_015): the vault's MAC over the object this row is part of, HMAC-SHA256 cut to 16
|
|
638
|
+
// bytes, the same seal its newest change in `log_changes` records. Null until the row is sealed.
|
|
639
|
+
seal: bytea(),
|
|
607
640
|
},
|
|
608
641
|
(table) => [
|
|
642
|
+
check('service_bindings_seal_check', sql`${table.seal} IS NULL OR octet_length(${table.seal}) = 16`),
|
|
609
643
|
check('service_bindings_principal_check', sql`${table.principal} LIKE 'token:%'`),
|
|
610
644
|
check('service_bindings_auth_mac_check', sql`octet_length(${table.authMac}) = 32`),
|
|
611
645
|
check('service_bindings_claims_check', sql`${table.claims}::jsonb IS NOT NULL`),
|
|
@@ -717,8 +751,12 @@ export const mcpConnections = pgTable(
|
|
|
717
751
|
lastUsedIp: text('last_used_ip'),
|
|
718
752
|
revokedAt: timestamp('revoked_at', { withTimezone: true }),
|
|
719
753
|
revokedBy: text('revoked_by'),
|
|
754
|
+
// 0.15's seal (T147): the vault's MAC over the object this row is part of, HMAC-SHA256 cut to 16 bytes, the same
|
|
755
|
+
// seal its newest change in `log_changes` records. Null until the row is sealed.
|
|
756
|
+
seal: bytea(),
|
|
720
757
|
},
|
|
721
758
|
(table) => [
|
|
759
|
+
check('mcp_connections_seal_check', sql`${table.seal} IS NULL OR octet_length(${table.seal}) = 16`),
|
|
722
760
|
check('mcp_connections_principal_check', sql`${table.principal} LIKE 'user:%'`),
|
|
723
761
|
check('mcp_connections_registration_check', sql`${table.registration} IN ('cimd', 'dcr')`),
|
|
724
762
|
check('mcp_connections_auth_mac_check', sql`octet_length(${table.authMac}) = 32`),
|
|
@@ -862,8 +900,12 @@ export const credentials = pgTable(
|
|
|
862
900
|
enrollmentId: uuid('enrollment_id'),
|
|
863
901
|
approvedAt: timestamp('approved_at', { withTimezone: true }),
|
|
864
902
|
approvedBy: text('approved_by'),
|
|
903
|
+
// 0.15's seal (0022_log_015): the vault's MAC over the object this row is part of, HMAC-SHA256 cut to 16
|
|
904
|
+
// bytes, the same seal its newest change in `log_changes` records. Null until the row is sealed.
|
|
905
|
+
seal: bytea(),
|
|
865
906
|
},
|
|
866
907
|
(table) => [
|
|
908
|
+
check('credentials_seal_check', sql`${table.seal} IS NULL OR octet_length(${table.seal}) = 16`),
|
|
867
909
|
unique('credentials_token_hash_key').on(table.tokenHash),
|
|
868
910
|
check('credentials_kind_check', sql`${table.kind} IN ('browser', 'cli', 'service', 'personal', 'handle', 'workload')`),
|
|
869
911
|
check('credentials_principal_check', sql`${table.principal} LIKE 'user:%' OR ${table.principal} LIKE 'token:%'`),
|
|
@@ -949,8 +991,251 @@ export const deviceAuthorizations = pgTable(
|
|
|
949
991
|
],
|
|
950
992
|
);
|
|
951
993
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
994
|
+
/**
|
|
995
|
+
* 0.15's configuration objects (0022_log_015): decision inputs no other row
|
|
996
|
+
* holds, one row each, sealed as every input is. `policy` holds the bulk
|
|
997
|
+
* cap's count and window, which a use reads in its own statement; `roles`
|
|
998
|
+
* the role tables. JSON, as the vault writes it. Written only through
|
|
999
|
+
* `change_input`.
|
|
1000
|
+
*/
|
|
1001
|
+
export const logConfig = pgTable(
|
|
1002
|
+
'log_config',
|
|
1003
|
+
{
|
|
1004
|
+
name: text().primaryKey(),
|
|
1005
|
+
value: text().notNull(),
|
|
1006
|
+
seal: bytea(),
|
|
1007
|
+
},
|
|
1008
|
+
(table) => [
|
|
1009
|
+
check('log_config_name_check', sql`${table.name} IN ('policy', 'roles')`),
|
|
1010
|
+
check('log_config_value_check', sql`${table.value}::jsonb IS NOT NULL`),
|
|
1011
|
+
check('log_config_seal_check', sql`${table.seal} IS NULL OR octet_length(${table.seal}) = 16`),
|
|
1012
|
+
],
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* 0.15's log (0022_log_015, docs/design/hot-path-one.md): two tables numbered
|
|
1017
|
+
* from one sequence, `log_number`, each number taken under the fence's lock
|
|
1018
|
+
* (a credential's issue: under the issue lock, which the barrier waits for;
|
|
1019
|
+
* 0026), so that the number alone orders uses against changes. Nothing reads or
|
|
1020
|
+
* writes them before 0.15's vault; the vault login reads them and writes
|
|
1021
|
+
* them only through the migration's functions.
|
|
1022
|
+
*
|
|
1023
|
+
* A change: any write to a decision input, or to configuration, kept for
|
|
1024
|
+
* good. `object` is the input it changes (`log_object(kind, id)`), `seal`
|
|
1025
|
+
* that object's seal after it, `payload` what it does. The MAC is the
|
|
1026
|
+
* vault's, 16 bytes, its key's tag then HMAC-SHA256 cut to 12 (D162), over everything but the number,
|
|
1027
|
+
* which the vault does not know when it asks. The vault login reads them,
|
|
1028
|
+
* and the app's reads the changes too, for the audit pages: a sign-in
|
|
1029
|
+
* through a trust binding, and the vault's token issues, revokes and
|
|
1030
|
+
* sign-outs, are recorded there alone (D147, T156, T164).
|
|
1031
|
+
*/
|
|
1032
|
+
export const logChanges = pgTable(
|
|
1033
|
+
'log_changes',
|
|
1034
|
+
{
|
|
1035
|
+
number: bigint({ mode: 'bigint' }).primaryKey(),
|
|
1036
|
+
// Milliseconds since the epoch, the vault's clock when it asked.
|
|
1037
|
+
at: bigint({ mode: 'number' }).notNull(),
|
|
1038
|
+
// A request id names one change: a retry finds its entry.
|
|
1039
|
+
requestId: text('request_id').notNull(),
|
|
1040
|
+
object: uuid().notNull(),
|
|
1041
|
+
actor: text().notNull(),
|
|
1042
|
+
payload: text().notNull(),
|
|
1043
|
+
seal: bytea(),
|
|
1044
|
+
mac: bytea().notNull(),
|
|
1045
|
+
},
|
|
1046
|
+
(table) => [
|
|
1047
|
+
unique('log_changes_request_id_key').on(table.requestId),
|
|
1048
|
+
check('log_changes_seal_check', sql`${table.seal} IS NULL OR octet_length(${table.seal}) = 16`),
|
|
1049
|
+
check('log_changes_mac_check', sql`octet_length(${table.mac}) = 16`),
|
|
1050
|
+
// Each input's newest change, its seal from the index alone (INCLUDE (seal), in the migration's SQL: Drizzle
|
|
1051
|
+
// has no INCLUDE): one probe per input.
|
|
1052
|
+
index('log_changes_object_idx').on(table.object, table.number),
|
|
1053
|
+
// The sign-ins through trust bindings, newest first by when the vault asked (`at`; its number comes later, after
|
|
1054
|
+
// any wait), every service's and one's: what the audit pages list where 0.14's `token.exchange` stood (T156). Only
|
|
1055
|
+
// a sign-in's payload holds a `signIn` object (change-records.ts, which the reads repeat).
|
|
1056
|
+
index('log_changes_sign_in_idx').on(table.at, table.number).where(recorded('postgres', table.payload, 'signIn')),
|
|
1057
|
+
index('log_changes_sign_in_actor_idx').on(table.actor, table.at, table.number).where(recorded('postgres', table.payload, 'signIn')),
|
|
1058
|
+
// The vault's token issues, revokes and sign-outs, ordered as the sign-ins, every member's and one's: what the audit
|
|
1059
|
+
// pages list where 0.14's `token.create`, `token.revoke` and `sign_out` stood, and count while they hide them (T164).
|
|
1060
|
+
// Only an issue's payload, by how it starts, holds an `issue` record, and only a revoke's these reasons
|
|
1061
|
+
// (change-records.ts). A binding's end revokes its credentials with the reason `unbound`, of which 0.14 logged
|
|
1062
|
+
// none: left out.
|
|
1063
|
+
index('log_changes_issue_revoke_idx').on(table.at, table.number).where(recorded('postgres', table.payload, 'issue', 'revoke', 'signOut')),
|
|
1064
|
+
index('log_changes_issue_revoke_actor_idx').on(table.actor, table.at, table.number).where(recorded('postgres', table.payload, 'issue', 'revoke', 'signOut')),
|
|
1065
|
+
],
|
|
1066
|
+
);
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* A use, the slim row (R53): no principal (the credential's), no
|
|
1070
|
+
* environment (the secret's, or `place`), no kind (the table says it).
|
|
1071
|
+
* Partitioned by the day of `at` (`make_day`), which retention drops whole;
|
|
1072
|
+
* fixed-width columns first, and eight of them, so the row pads to nothing. One index, the
|
|
1073
|
+
* replay's: a retried call repeats its `at` and request id, both under its
|
|
1074
|
+
* MAC, and lands in the same day.
|
|
1075
|
+
*/
|
|
1076
|
+
export const logUses = pgTable(
|
|
1077
|
+
'log_uses',
|
|
1078
|
+
{
|
|
1079
|
+
number: bigint({ mode: 'bigint' }).notNull(),
|
|
1080
|
+
at: bigint({ mode: 'number' }).notNull(),
|
|
1081
|
+
// Random, unique within its millisecond.
|
|
1082
|
+
requestId: bigint('request_id', { mode: 'bigint' }).notNull(),
|
|
1083
|
+
credential: uuid().notNull(),
|
|
1084
|
+
secret: uuid().notNull(),
|
|
1085
|
+
// What the use was for, as @coffre/vault codes it (a use, a reveal…), plus 256 when it may keep a sensitive value:
|
|
1086
|
+
// one column, so that the row has eight and its null bitmap fits the header's padding (8 B a use, measured).
|
|
1087
|
+
purpose: smallint().notNull(),
|
|
1088
|
+
mac: bytea().notNull(),
|
|
1089
|
+
// The environment it was asked at, when not the secret's own (a fork, a reference).
|
|
1090
|
+
place: uuid(),
|
|
1091
|
+
},
|
|
1092
|
+
(table) => [
|
|
1093
|
+
check('log_uses_mac_check', sql`octet_length(${table.mac}) = 16`),
|
|
1094
|
+
uniqueIndex('log_uses_request_idx').on(table.at, table.requestId),
|
|
1095
|
+
],
|
|
1096
|
+
);
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* 0.15's reads as the audit pages list them (T166; formal/Reads.tla): a
|
|
1100
|
+
* projection of each use a signed pair seals, written by the vault's
|
|
1101
|
+
* projector apart from the checkpointer, from the sealed objects at the
|
|
1102
|
+
* use's number: its account, its key's place, the fork it was read in,
|
|
1103
|
+
* through a reference the source and its place, the version read; under the
|
|
1104
|
+
* vault's MAC over all of it, the number included. Partitioned by the day of
|
|
1105
|
+
* `at`, as the uses are (`make_reads_day`), and dropped with them.
|
|
1106
|
+
*/
|
|
1107
|
+
export const logReads = pgTable(
|
|
1108
|
+
'log_reads',
|
|
1109
|
+
{
|
|
1110
|
+
number: bigint({ mode: 'bigint' }).notNull(),
|
|
1111
|
+
at: bigint({ mode: 'number' }).notNull(),
|
|
1112
|
+
credential: uuid().notNull(),
|
|
1113
|
+
purpose: smallint().notNull(),
|
|
1114
|
+
principal: text().notNull(),
|
|
1115
|
+
secret: uuid().notNull(),
|
|
1116
|
+
environment: uuid().notNull(),
|
|
1117
|
+
project: uuid().notNull(),
|
|
1118
|
+
place: uuid(),
|
|
1119
|
+
source: uuid(),
|
|
1120
|
+
sourceEnvironment: uuid('source_environment'),
|
|
1121
|
+
sourceProject: uuid('source_project'),
|
|
1122
|
+
version: uuid(),
|
|
1123
|
+
mac: bytea().notNull(),
|
|
1124
|
+
},
|
|
1125
|
+
(table) => [
|
|
1126
|
+
check('log_reads_mac_check', sql`octet_length(${table.mac}) = 16`),
|
|
1127
|
+
uniqueIndex('log_reads_number_idx').on(table.at, table.number),
|
|
1128
|
+
index('log_reads_principal_idx').on(table.principal, table.at, table.number),
|
|
1129
|
+
],
|
|
1130
|
+
);
|
|
1131
|
+
|
|
1132
|
+
/** The days each member's reads are in (`log_reads`), so that a page of theirs reads those days alone: the projector writes them with the reads. */
|
|
1133
|
+
export const logReadDays = pgTable(
|
|
1134
|
+
'log_read_days',
|
|
1135
|
+
{ principal: text().notNull(), day: integer().notNull() },
|
|
1136
|
+
(table) => [primaryKey({ name: 'log_read_days_pkey', columns: [table.principal, table.day] })],
|
|
1137
|
+
);
|
|
1138
|
+
|
|
1139
|
+
/** How far the projector has gone: the number every use at or below it is projected, and when. */
|
|
1140
|
+
export const logReadsCursor = pgTable(
|
|
1141
|
+
'log_reads_cursor',
|
|
1142
|
+
{
|
|
1143
|
+
onlyRow: boolean('only_row').primaryKey().default(true),
|
|
1144
|
+
through: bigint({ mode: 'bigint' }).notNull(),
|
|
1145
|
+
// The signed pair at or below `through`, whose floor of `at` the next read of uses takes.
|
|
1146
|
+
pair: bigint({ mode: 'bigint' }).notNull(),
|
|
1147
|
+
at: bigint({ mode: 'number' }).notNull(),
|
|
1148
|
+
// The vault's MAC over the three (formal/Reads.tla, CursorMac): a cursor moved ahead would have the pages list reads as
|
|
1149
|
+
// whole that were never projected. Null at the start, before any read is projected.
|
|
1150
|
+
mac: bytea(),
|
|
1151
|
+
},
|
|
1152
|
+
(table) => [
|
|
1153
|
+
check('log_reads_cursor_only_row_check', sql`${table.onlyRow}`),
|
|
1154
|
+
check('log_reads_cursor_mac_check', sql`${table.mac} IS NULL OR octet_length(${table.mac}) = 16`),
|
|
1155
|
+
],
|
|
1156
|
+
);
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* The bulk cap (R28, R52): each principal's last logged use of each key, as
|
|
1160
|
+
* the start of that use's refresh period, written only by a use that holds
|
|
1161
|
+
* the principal's lock (exclusive to enter a key, shared to move it on:
|
|
1162
|
+
* 0028), at most once per refresh per key. A row older than the window, a
|
|
1163
|
+
* refresh, the deadline and a minute counts for nothing; `prune_bulk`
|
|
1164
|
+
* forgets it an hour after that.
|
|
1165
|
+
*/
|
|
1166
|
+
export const logBulk = pgTable(
|
|
1167
|
+
'log_bulk',
|
|
1168
|
+
{
|
|
1169
|
+
principal: text().notNull(),
|
|
1170
|
+
secret: uuid().notNull(),
|
|
1171
|
+
last: bigint({ mode: 'number' }).notNull(),
|
|
1172
|
+
},
|
|
1173
|
+
(table) => [primaryKey({ name: 'log_bulk_pkey', columns: [table.principal, table.secret] })],
|
|
1174
|
+
);
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* The fence's one row: the lowest `at` a use may still be written with,
|
|
1178
|
+
* raised by each checkpoint's barrier (`close_one`), never lowered. Made
|
|
1179
|
+
* by the migration; without it every use is refused.
|
|
1180
|
+
*/
|
|
1181
|
+
export const logFence = pgTable(
|
|
1182
|
+
'log_fence',
|
|
1183
|
+
{
|
|
1184
|
+
onlyRow: boolean('only_row').primaryKey().default(true),
|
|
1185
|
+
atFloor: bigint('at_floor', { mode: 'number' }).notNull(),
|
|
1186
|
+
},
|
|
1187
|
+
(table) => [check('log_fence_only_row_check', sql`${table.onlyRow}`)],
|
|
1188
|
+
);
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* A signed checkpoint: the pair of tree heads (W212), changes and uses, at
|
|
1192
|
+
* one `upto`, every number at or below which is final. One row, so that no
|
|
1193
|
+
* state holds one head without the other. `at_floor` is the floor its
|
|
1194
|
+
* barrier set: the next window's uses have an `at` at or above it.
|
|
1195
|
+
*/
|
|
1196
|
+
export const logCheckpoints = pgTable(
|
|
1197
|
+
'log_checkpoints',
|
|
1198
|
+
{
|
|
1199
|
+
upto: bigint({ mode: 'bigint' }).primaryKey(),
|
|
1200
|
+
at: bigint({ mode: 'number' }).notNull(),
|
|
1201
|
+
atFloor: bigint('at_floor', { mode: 'number' }).notNull(),
|
|
1202
|
+
changesSize: bigint('changes_size', { mode: 'bigint' }).notNull(),
|
|
1203
|
+
changesRoot: bytea('changes_root').notNull(),
|
|
1204
|
+
// The signed note (C2SP tlog-checkpoint), as witnesses get it.
|
|
1205
|
+
changesNote: text('changes_note').notNull(),
|
|
1206
|
+
usesSize: bigint('uses_size', { mode: 'bigint' }).notNull(),
|
|
1207
|
+
usesRoot: bytea('uses_root').notNull(),
|
|
1208
|
+
usesNote: text('uses_note').notNull(),
|
|
1209
|
+
},
|
|
1210
|
+
(table) => [
|
|
1211
|
+
check('log_checkpoints_changes_root_check', sql`octet_length(${table.changesRoot}) = 32`),
|
|
1212
|
+
check('log_checkpoints_uses_root_check', sql`octet_length(${table.usesRoot}) = 32`),
|
|
1213
|
+
// A day's retention cut, the first checkpoint whose floor is past the day's end (migration 0027).
|
|
1214
|
+
index('log_checkpoints_floor_idx').on(table.atFloor, table.upto),
|
|
1215
|
+
],
|
|
1216
|
+
);
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* The two trees' tiles (C2SP tlog-tiles: height 8, up to 256 hashes of one
|
|
1220
|
+
* level). Partitioned by tree; the uses tree's leaf tiles (level 0) by
|
|
1221
|
+
* `leaf`, the first leaf a tile covers (`make_tiles`), so that retention
|
|
1222
|
+
* drops a table rather than deleting a day's rows (W212); its levels above,
|
|
1223
|
+
* which span far past their first leaf, in one table (W217).
|
|
1224
|
+
*/
|
|
1225
|
+
export const logTiles = pgTable(
|
|
1226
|
+
'log_tiles',
|
|
1227
|
+
{
|
|
1228
|
+
tree: text().notNull(),
|
|
1229
|
+
level: smallint().notNull(),
|
|
1230
|
+
// idx · 256^(level + 1): a tile's place, in its tree's leaves.
|
|
1231
|
+
leaf: bigint({ mode: 'bigint' }).notNull(),
|
|
1232
|
+
hashes: bytea().notNull(),
|
|
1233
|
+
},
|
|
1234
|
+
(table) => [
|
|
1235
|
+
primaryKey({ name: 'log_tiles_pkey', columns: [table.tree, table.level, table.leaf] }),
|
|
1236
|
+
check('log_tiles_tree_check', sql`${table.tree} IN ('changes', 'uses')`),
|
|
1237
|
+
check('log_tiles_level_check', sql`${table.level} BETWEEN 0 AND 7`),
|
|
1238
|
+
check('log_tiles_leaf_check', sql`${table.leaf} >= 0 AND ${table.leaf} % (256::numeric ^ (${table.level} + 1)) = 0`),
|
|
1239
|
+
check('log_tiles_hashes_check', sql`octet_length(${table.hashes}) BETWEEN 32 AND 8192 AND octet_length(${table.hashes}) % 32 = 0`),
|
|
1240
|
+
],
|
|
1241
|
+
);
|
package/src/verify-runtime.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import { postgresConnection } from './postgres.ts';
|
|
2
|
+
import { postgresClient } from './postgres.ts';
|
|
5
3
|
|
|
6
4
|
function requiredEnvironment(name: string): string {
|
|
7
5
|
const value = process.env[name]?.trim();
|
|
@@ -10,19 +8,15 @@ function requiredEnvironment(name: string): string {
|
|
|
10
8
|
}
|
|
11
9
|
|
|
12
10
|
const expectedRole = requiredEnvironment('COFFRE_RUNTIME_ROLE');
|
|
13
|
-
const
|
|
14
|
-
application_name: 'coffre-runtime-verification',
|
|
15
|
-
...postgresConnection(requiredEnvironment('DATABASE_URL')),
|
|
16
|
-
max: 1,
|
|
17
|
-
});
|
|
11
|
+
const client = postgresClient(requiredEnvironment('DATABASE_URL'), { application_name: 'coffre-runtime-verification', max: 1 });
|
|
18
12
|
|
|
19
13
|
try {
|
|
20
|
-
const identity = await
|
|
14
|
+
const identity = await client.unsafe<{
|
|
21
15
|
current_user: string;
|
|
22
16
|
memberships: string[];
|
|
23
17
|
safe_membership: boolean;
|
|
24
18
|
unsafe_attributes: boolean;
|
|
25
|
-
}>(
|
|
19
|
+
}[]>(
|
|
26
20
|
`SELECT current_user,
|
|
27
21
|
(
|
|
28
22
|
role.rolsuper OR role.rolcreatedb OR role.rolcreaterole
|
|
@@ -47,14 +41,14 @@ try {
|
|
|
47
41
|
FROM pg_roles role
|
|
48
42
|
WHERE role.rolname = current_user`,
|
|
49
43
|
);
|
|
50
|
-
assert.deepEqual(identity
|
|
44
|
+
assert.deepEqual([...identity], [{
|
|
51
45
|
current_user: expectedRole,
|
|
52
46
|
memberships: ['coffre_app'],
|
|
53
47
|
safe_membership: true,
|
|
54
48
|
unsafe_attributes: false,
|
|
55
49
|
}]);
|
|
56
50
|
|
|
57
|
-
const ownership = await
|
|
51
|
+
const ownership = await client.unsafe<{ object_count: number }[]>(
|
|
58
52
|
`SELECT count(*)::int AS object_count
|
|
59
53
|
FROM pg_shdepend dependency
|
|
60
54
|
JOIN pg_roles role ON role.oid = dependency.refobjid
|
|
@@ -62,9 +56,9 @@ try {
|
|
|
62
56
|
AND dependency.deptype = 'o'
|
|
63
57
|
AND role.rolname = current_user`,
|
|
64
58
|
);
|
|
65
|
-
assert.equal(ownership
|
|
59
|
+
assert.equal(ownership[0]?.object_count, 0, 'runtime role owns database objects');
|
|
66
60
|
|
|
67
|
-
const privileges = await
|
|
61
|
+
const privileges = await client.unsafe<{
|
|
68
62
|
audit_delete: boolean;
|
|
69
63
|
audit_truncate: boolean;
|
|
70
64
|
audit_update: boolean;
|
|
@@ -74,7 +68,7 @@ try {
|
|
|
74
68
|
head_hash_update: boolean;
|
|
75
69
|
migrations_select: boolean;
|
|
76
70
|
schema_create: boolean;
|
|
77
|
-
}>(
|
|
71
|
+
}[]>(
|
|
78
72
|
`SELECT has_database_privilege(current_user, current_database(), 'CREATE') AS database_create,
|
|
79
73
|
has_database_privilege(current_user, current_database(), 'TEMPORARY') AS database_temporary,
|
|
80
74
|
has_schema_privilege(current_user, 'public', 'CREATE') AS schema_create,
|
|
@@ -91,7 +85,7 @@ try {
|
|
|
91
85
|
current_user, 'drizzle.__drizzle_migrations', 'SELECT'
|
|
92
86
|
) AS migrations_select`,
|
|
93
87
|
);
|
|
94
|
-
assert.deepEqual(privileges
|
|
88
|
+
assert.deepEqual([...privileges], [{
|
|
95
89
|
audit_delete: false,
|
|
96
90
|
audit_truncate: false,
|
|
97
91
|
audit_update: false,
|
|
@@ -103,20 +97,15 @@ try {
|
|
|
103
97
|
schema_create: false,
|
|
104
98
|
}]);
|
|
105
99
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
assert.equal(head.rowCount, 1, 'runtime head update did not affect the singleton');
|
|
114
|
-
} finally {
|
|
115
|
-
await client.query('ROLLBACK');
|
|
116
|
-
client.release();
|
|
117
|
-
}
|
|
100
|
+
// What every append does to the log's head, rolled back.
|
|
101
|
+
const rolledBack = new Error('rolled back');
|
|
102
|
+
await assert.rejects(client.begin(async (tx) => {
|
|
103
|
+
const head = await tx.unsafe(`UPDATE audit_chain_head SET next_seq = next_seq, head_hash = head_hash WHERE only_row`);
|
|
104
|
+
assert.equal(head.count, 1, 'runtime head update did not affect the singleton');
|
|
105
|
+
throw rolledBack;
|
|
106
|
+
}), (error) => error === rolledBack);
|
|
118
107
|
|
|
119
108
|
console.log(`runtime database role ${expectedRole} passed privilege verification`);
|
|
120
109
|
} finally {
|
|
121
|
-
await
|
|
110
|
+
await client.end();
|
|
122
111
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Erwin Kuhn
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { g as schema_d_exports } from "./schema-BfQEBqFv.js";
|
|
2
|
-
import { Query, Table } from "drizzle-orm";
|
|
3
|
-
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
4
|
-
import pg from "pg";
|
|
5
|
-
//#region src/prepared.d.ts
|
|
6
|
-
/** The part of a select builder `prepared` reads, once. */
|
|
7
|
-
type Select = PromiseLike<unknown> & {
|
|
8
|
-
toSQL(): Query;
|
|
9
|
-
prepare(...name: never[]): unknown;
|
|
10
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* A select built once per process, or once per isolate on Workers, and run
|
|
13
|
-
* with new values each time. Drizzle rebuilds a query's SQL from its
|
|
14
|
-
* builder at every run, and that was most of a short query's CPU; this
|
|
15
|
-
* builds it from `build` the first time a database of its dialect asks,
|
|
16
|
-
* then hands the SQL and its columns to that call's own session
|
|
17
|
-
* (`session.prepareQuery`), which binds `values` to the placeholders
|
|
18
|
-
* (`sql.placeholder`) and maps the rows as the builder would.
|
|
19
|
-
*
|
|
20
|
-
* `build` must not depend on anything but the database's dialect, and the
|
|
21
|
-
* values go to the driver as they are, not through a column's encoding:
|
|
22
|
-
* strings, numbers, bigints and bytes.
|
|
23
|
-
*
|
|
24
|
-
* const member = prepared((db) => db.select().from(tablesOf(db).vaultMembers).where(eq(…, sql.placeholder('principal'))));
|
|
25
|
-
* const [row] = await member(db, { principal });
|
|
26
|
-
*/
|
|
27
|
-
declare function prepared<Q extends Select>(build: (db: Queryable) => Q): (db: Queryable, values?: Record<string, unknown>) => Promise<Awaited<Q>>;
|
|
28
|
-
//#endregion
|
|
29
|
-
//#region src/database.d.ts
|
|
30
|
-
/**
|
|
31
|
-
* A database, typed as Postgres whichever it is; see portable.ts for why
|
|
32
|
-
* that holds. connect.ts opens one of each from a URL.
|
|
33
|
-
*/
|
|
34
|
-
type Database = NodePgDatabase<typeof schema_d_exports>;
|
|
35
|
-
type Transaction = Parameters<Parameters<Database['transaction']>[0]>[0];
|
|
36
|
-
/** Anything a read can run on: the database, or an open transaction. */
|
|
37
|
-
type Queryable = Database | Transaction;
|
|
38
|
-
/**
|
|
39
|
-
* What Drizzle needs from a connection pool. A `pg.Pool` fits, and so does the
|
|
40
|
-
* per-invocation Hyperdrive pool, whose class name ends in `Pool` for the
|
|
41
|
-
* same reason: that is how Drizzle knows to check out a client for a
|
|
42
|
-
* transaction rather than run BEGIN on a shared connection.
|
|
43
|
-
*/
|
|
44
|
-
type PoolLike = pg.Pool | {
|
|
45
|
-
query: pg.Pool['query'];
|
|
46
|
-
connect: () => Promise<Pick<pg.PoolClient, 'query'> & {
|
|
47
|
-
release: () => void;
|
|
48
|
-
}>;
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* A query made on the database from inside one of its own transactions,
|
|
52
|
-
* rather than through the transaction. With one connection per call, as on
|
|
53
|
-
* Workers, it would wait for the transaction, which waits for it: a call
|
|
54
|
-
* Cloudflare cancels as hung, and nothing says why. So it is refused at
|
|
55
|
-
* once, with this, wherever the database runs.
|
|
56
|
-
*/
|
|
57
|
-
declare class QueryOutsideTransaction extends Error {
|
|
58
|
-
constructor();
|
|
59
|
-
}
|
|
60
|
-
declare function createDatabase(pool: PoolLike): Database;
|
|
61
|
-
/**
|
|
62
|
-
* The tables of the database's own dialect: the Postgres ones on Postgres,
|
|
63
|
-
* the SQLite ones on SQLite. Every query builds on these.
|
|
64
|
-
*/
|
|
65
|
-
declare function tablesOf(db: Queryable): typeof schema_d_exports;
|
|
66
|
-
/**
|
|
67
|
-
* The database's own twin of `table`, which may be another dialect's: the
|
|
68
|
-
* server names tables by importing schema.ts, and the database decides
|
|
69
|
-
* which one that means.
|
|
70
|
-
*/
|
|
71
|
-
declare function own<T extends Table>(db: Queryable, table: T): T;
|
|
72
|
-
//#endregion
|
|
73
|
-
export { Transaction as a, tablesOf as c, Queryable as i, prepared as l, PoolLike as n, createDatabase as o, QueryOutsideTransaction as r, own as s, Database as t };
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { checkServerIdentity } from "node:tls";
|
|
2
|
-
import { parseIntoClientConfig } from "pg-connection-string";
|
|
3
|
-
//#region src/postgres.ts
|
|
4
|
-
/** Keep libpq's system-root shortcut from being read as a certificate filename by pg. */
|
|
5
|
-
function postgresConnection(url) {
|
|
6
|
-
let parsed;
|
|
7
|
-
try {
|
|
8
|
-
parsed = new URL(url);
|
|
9
|
-
} catch {
|
|
10
|
-
throw new Error("invalid Postgres URL");
|
|
11
|
-
}
|
|
12
|
-
const parameters = parsed.searchParams;
|
|
13
|
-
if (parameters.getAll("sslrootcert").at(-1) !== "system") return { connectionString: url };
|
|
14
|
-
const mode = parameters.getAll("sslmode").at(-1);
|
|
15
|
-
if (mode !== void 0 && mode !== "verify-full") throw new Error("sslrootcert=system requires sslmode=verify-full");
|
|
16
|
-
parameters.delete("sslrootcert");
|
|
17
|
-
parameters.set("sslmode", "verify-full");
|
|
18
|
-
const config = parseIntoClientConfig(parsed.toString());
|
|
19
|
-
const host = config.host;
|
|
20
|
-
if (!host) throw new Error("sslrootcert=system requires a Postgres hostname");
|
|
21
|
-
config.ssl = {
|
|
22
|
-
...config.ssl,
|
|
23
|
-
rejectUnauthorized: true,
|
|
24
|
-
checkServerIdentity: (_hostname, certificate) => checkServerIdentity(host, certificate)
|
|
25
|
-
};
|
|
26
|
-
return config;
|
|
27
|
-
}
|
|
28
|
-
//#endregion
|
|
29
|
-
export { postgresConnection as t };
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { relations, sql } from "drizzle-orm";
|
|
2
|
-
//#region src/relations.ts
|
|
3
|
-
/**
|
|
4
|
-
* What both schemas share beyond their columns: the relations and the
|
|
5
|
-
* one generated column.
|
|
6
|
-
*
|
|
7
|
-
* Relations are for Drizzle's relational queries (`db.query.secrets.findMany({ with })`), which read a row and what hangs off it in one
|
|
8
|
-
* statement on every dialect. They add no constraints; the foreign keys in
|
|
9
|
-
* each schema do that. Each schema calls this with its own tables, so a
|
|
10
|
-
* relational query on SQLite joins SQLite tables.
|
|
11
|
-
*/
|
|
12
|
-
function relationsOf(t) {
|
|
13
|
-
return {
|
|
14
|
-
environmentsRelations: relations(t.environments, ({ one }) => ({ project: one(t.projects, {
|
|
15
|
-
fields: [t.environments.projectId],
|
|
16
|
-
references: [t.projects.id]
|
|
17
|
-
}) })),
|
|
18
|
-
secretsRelations: relations(t.secrets, ({ one }) => ({
|
|
19
|
-
project: one(t.projects, {
|
|
20
|
-
fields: [t.secrets.projectId],
|
|
21
|
-
references: [t.projects.id]
|
|
22
|
-
}),
|
|
23
|
-
environment: one(t.environments, {
|
|
24
|
-
fields: [t.secrets.environmentId],
|
|
25
|
-
references: [t.environments.id]
|
|
26
|
-
})
|
|
27
|
-
}))
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* `identities.active_subject`: the subject while the account is bound, null
|
|
32
|
-
* once revoked. A unique index on (provider, issuer_hash, active_subject)
|
|
33
|
-
* binds an account to one person at a time. Plain column names, so the
|
|
34
|
-
* expression reads the same on both databases.
|
|
35
|
-
*/
|
|
36
|
-
const ACTIVE_SUBJECT = sql`CASE WHEN revoked_at IS NULL THEN subject END`;
|
|
37
|
-
//#endregion
|
|
38
|
-
export { relationsOf as n, ACTIVE_SUBJECT as t };
|