@coffre/db 0.14.4 → 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 -596
- package/dist/log.js +224 -10
- package/dist/migrate.d.ts +43 -3
- package/dist/migrate.js +149 -29
- package/dist/migrations/postgres/0021_prune_vault_releases.sql +19 -0
- 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/0021_snapshot.json +3334 -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 +189 -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-z8GcN_Xu.d.ts → schema-DgoPqqsI.d.ts} +1329 -9
- package/dist/schema-sqlite.d.ts +1308 -8
- package/dist/schema-sqlite.js +156 -20
- package/dist/schema-version.d.ts +1 -1
- package/dist/schema-version.js +261 -2
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js +229 -22
- 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 +244 -11
- package/src/migrate.ts +157 -30
- package/src/migrations/postgres/0021_prune_vault_releases.sql +19 -0
- 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/0021_snapshot.json +3334 -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 +189 -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 +200 -9
- package/src/schema.ts +294 -8
- package/src/verify-runtime.ts +18 -29
- package/LICENSE +0 -21
- package/dist/database-CFhTnnzM.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.
|
|
@@ -330,8 +349,9 @@ export const vaultGrants = pgTable(
|
|
|
330
349
|
* read them from the entries before (an entry that names none, which only
|
|
331
350
|
* a forged one is, counts as a key of its own, `entry:<seq>`). No login
|
|
332
351
|
* writes it: the vault and the backup read it. A row a window old counts
|
|
333
|
-
* for nothing
|
|
334
|
-
*
|
|
352
|
+
* for nothing: the vault's checkpoint forgets those a day and an hour old,
|
|
353
|
+
* longer than any window (`prune_vault_releases`), so a count reads about a
|
|
354
|
+
* day of keys.
|
|
335
355
|
*/
|
|
336
356
|
export const vaultReleases = pgTable(
|
|
337
357
|
'vault_releases',
|
|
@@ -377,6 +397,10 @@ export const auditLog = pgTable(
|
|
|
377
397
|
prevHash: bytea('prev_hash').notNull(),
|
|
378
398
|
mac: bytea().notNull(),
|
|
379
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'),
|
|
380
404
|
},
|
|
381
405
|
(table) => [
|
|
382
406
|
check('audit_log_author_check', sql`${table.author} IN ('app', 'vault')`),
|
|
@@ -484,6 +508,10 @@ export const secretReferences = pgTable(
|
|
|
484
508
|
createdSeq: bigint('created_seq', { mode: 'bigint' }).notNull(),
|
|
485
509
|
createdAt: createdAt(),
|
|
486
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 }),
|
|
487
515
|
},
|
|
488
516
|
(table) => [
|
|
489
517
|
unique('secret_references_created_seq_key').on(table.createdSeq),
|
|
@@ -597,14 +625,21 @@ export const serviceBindings = pgTable(
|
|
|
597
625
|
scope: text(),
|
|
598
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).
|
|
599
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(),
|
|
600
631
|
label: text(),
|
|
601
632
|
createdAt: createdAt(),
|
|
602
633
|
createdBy: text('created_by').notNull(),
|
|
603
634
|
lastUsedAt: timestamp('last_used_at', { withTimezone: true }),
|
|
604
635
|
revokedAt: timestamp('revoked_at', { withTimezone: true }),
|
|
605
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(),
|
|
606
640
|
},
|
|
607
641
|
(table) => [
|
|
642
|
+
check('service_bindings_seal_check', sql`${table.seal} IS NULL OR octet_length(${table.seal}) = 16`),
|
|
608
643
|
check('service_bindings_principal_check', sql`${table.principal} LIKE 'token:%'`),
|
|
609
644
|
check('service_bindings_auth_mac_check', sql`octet_length(${table.authMac}) = 32`),
|
|
610
645
|
check('service_bindings_claims_check', sql`${table.claims}::jsonb IS NOT NULL`),
|
|
@@ -716,8 +751,12 @@ export const mcpConnections = pgTable(
|
|
|
716
751
|
lastUsedIp: text('last_used_ip'),
|
|
717
752
|
revokedAt: timestamp('revoked_at', { withTimezone: true }),
|
|
718
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(),
|
|
719
757
|
},
|
|
720
758
|
(table) => [
|
|
759
|
+
check('mcp_connections_seal_check', sql`${table.seal} IS NULL OR octet_length(${table.seal}) = 16`),
|
|
721
760
|
check('mcp_connections_principal_check', sql`${table.principal} LIKE 'user:%'`),
|
|
722
761
|
check('mcp_connections_registration_check', sql`${table.registration} IN ('cimd', 'dcr')`),
|
|
723
762
|
check('mcp_connections_auth_mac_check', sql`octet_length(${table.authMac}) = 32`),
|
|
@@ -861,8 +900,12 @@ export const credentials = pgTable(
|
|
|
861
900
|
enrollmentId: uuid('enrollment_id'),
|
|
862
901
|
approvedAt: timestamp('approved_at', { withTimezone: true }),
|
|
863
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(),
|
|
864
906
|
},
|
|
865
907
|
(table) => [
|
|
908
|
+
check('credentials_seal_check', sql`${table.seal} IS NULL OR octet_length(${table.seal}) = 16`),
|
|
866
909
|
unique('credentials_token_hash_key').on(table.tokenHash),
|
|
867
910
|
check('credentials_kind_check', sql`${table.kind} IN ('browser', 'cli', 'service', 'personal', 'handle', 'workload')`),
|
|
868
911
|
check('credentials_principal_check', sql`${table.principal} LIKE 'user:%' OR ${table.principal} LIKE 'token:%'`),
|
|
@@ -948,8 +991,251 @@ export const deviceAuthorizations = pgTable(
|
|
|
948
991
|
],
|
|
949
992
|
);
|
|
950
993
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
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-z8GcN_Xu.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 };
|