@coffre/db 0.10.2 → 0.11.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/dist/connect.d.ts +1 -1
- package/dist/{database-DBtn3jqp.d.ts → database-Dh4W2pAj.d.ts} +1 -1
- package/dist/dialect.d.ts +1 -1
- package/dist/grants.d.ts +12 -5
- package/dist/grants.js +13 -8
- package/dist/hyperdrive.d.ts +9 -2
- package/dist/hyperdrive.js +16 -2
- package/dist/index.d.ts +2 -2
- package/dist/log.d.ts +1 -1
- package/dist/migrations/postgres/0015_workload_access.sql +43 -0
- package/dist/migrations/postgres/0016_spent_tokens.sql +12 -0
- package/dist/migrations/postgres/meta/0015_snapshot.json +3256 -0
- package/dist/migrations/postgres/meta/0016_snapshot.json +3256 -0
- package/dist/migrations/postgres/meta/_journal.json +14 -0
- package/dist/migrations/sqlite/0015_workload_access.sql +70 -0
- package/dist/migrations/sqlite/meta/0015_snapshot.json +2959 -0
- package/dist/migrations/sqlite/meta/_journal.json +7 -0
- package/dist/portable.d.ts +2 -2
- package/dist/{schema-NBVA9q-s.d.ts → schema-DG2t57Kf.d.ts} +327 -2
- package/dist/schema-sqlite.d.ts +346 -1
- package/dist/schema-sqlite.js +53 -5
- package/dist/schema-version.d.ts +1 -1
- package/dist/schema-version.js +21 -0
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js +54 -5
- package/package.json +2 -2
- package/src/grants.ts +18 -9
- package/src/hyperdrive.ts +16 -2
- package/src/migrations/postgres/0015_workload_access.sql +43 -0
- package/src/migrations/postgres/0016_spent_tokens.sql +12 -0
- package/src/migrations/postgres/meta/0015_snapshot.json +3256 -0
- package/src/migrations/postgres/meta/0016_snapshot.json +3256 -0
- package/src/migrations/postgres/meta/_journal.json +14 -0
- package/src/migrations/sqlite/0015_workload_access.sql +70 -0
- package/src/migrations/sqlite/meta/0015_snapshot.json +2959 -0
- package/src/migrations/sqlite/meta/_journal.json +7 -0
- package/src/schema.sqlite.ts +73 -2
- package/src/schema.ts +73 -2
package/src/schema.sqlite.ts
CHANGED
|
@@ -385,6 +385,14 @@ export const auditLog = sqliteTable(
|
|
|
385
385
|
index('audit_log_unbind_idx')
|
|
386
386
|
.on(sql`json_extract(${table.metadata}, '$.bindingId')`)
|
|
387
387
|
.where(sql`${table.author} = 'app' AND ${table.action} = 'token.unbind' AND ${table.decision} = 'allow'`),
|
|
388
|
+
// Whether a target is frozen: its newest allowed freeze or lift (docs/design/workload-access.md, section 8.2).
|
|
389
|
+
index('audit_log_freeze_idx')
|
|
390
|
+
.on(sql`json_extract(${table.metadata}, '$.target')`, table.seq)
|
|
391
|
+
.where(sql`${table.author} = 'app' AND ${table.action} IN ('workload.freeze', 'workload.unfreeze') AND ${table.decision} = 'allow'`),
|
|
392
|
+
// An enrollment code's tombstone: the app's successful `enrollment.supersede` naming it.
|
|
393
|
+
index('audit_log_supersede_idx')
|
|
394
|
+
.on(sql`json_extract(${table.metadata}, '$.enrollmentId')`)
|
|
395
|
+
.where(sql`${table.author} = 'app' AND ${table.action} = 'enrollment.supersede' AND ${table.decision} = 'allow'`),
|
|
388
396
|
// The run a credential was issued for: its exchange's entry, by the credential's ID.
|
|
389
397
|
index('audit_log_exchange_idx')
|
|
390
398
|
.on(sql`json_extract(${table.metadata}, '$.credentialId')`)
|
|
@@ -514,6 +522,8 @@ export const serviceBindings = sqliteTable(
|
|
|
514
522
|
claims: text().notNull(),
|
|
515
523
|
// What the credentials it issues reach (`CredentialScope`), JSON; null on a binding of 0.4 the app has not sealed yet.
|
|
516
524
|
scope: text(),
|
|
525
|
+
// 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).
|
|
526
|
+
policy: text(),
|
|
517
527
|
label: text(),
|
|
518
528
|
createdAt: createdAt(),
|
|
519
529
|
createdBy: text('created_by').notNull(),
|
|
@@ -661,6 +671,46 @@ export const mcpApprovals = sqliteTable(
|
|
|
661
671
|
],
|
|
662
672
|
);
|
|
663
673
|
|
|
674
|
+
/**
|
|
675
|
+
* Enrollment codes (docs/design/workload-access.md, section 4.4): what CI
|
|
676
|
+
* mints for a platform that signs no identity, and the app exchanges for a
|
|
677
|
+
* workload's session at `/api/auth/workload`. Never a credential itself, and
|
|
678
|
+
* only ever added: the log says which code a promote superseded. Its scope,
|
|
679
|
+
* fixed at minting, is the binding's within what the minter held; the MAC
|
|
680
|
+
* covers everything but its hint and creation time (auth-rows.ts in the server).
|
|
681
|
+
*/
|
|
682
|
+
export const enrollments = sqliteTable(
|
|
683
|
+
'enrollments',
|
|
684
|
+
{
|
|
685
|
+
id: text().primaryKey(),
|
|
686
|
+
authMac: bytes('auth_mac').notNull(),
|
|
687
|
+
bindingId: text('binding_id').notNull(),
|
|
688
|
+
// The SHA-256 of the code, `coffre_enr_…`, and enough of it to recognize.
|
|
689
|
+
codeHash: bytes('code_hash').notNull(),
|
|
690
|
+
codeHint: text('code_hint').notNull(),
|
|
691
|
+
// `CredentialScope`, JSON: what its sessions reach, R32's sensitive per place.
|
|
692
|
+
scope: text().notNull(),
|
|
693
|
+
createdAt: createdAt(),
|
|
694
|
+
// Who minted it, and with which of their credentials: a CI run's session.
|
|
695
|
+
createdBy: text('created_by').notNull(),
|
|
696
|
+
createdWith: text('created_with'),
|
|
697
|
+
expiresAt: time('expires_at').notNull(),
|
|
698
|
+
},
|
|
699
|
+
(table) => [
|
|
700
|
+
unique('enrollments_code_hash_key').on(table.codeHash),
|
|
701
|
+
check('enrollments_auth_mac_check', sql`octet_length(${table.authMac}) = 32`),
|
|
702
|
+
check('enrollments_code_hash_check', sql`octet_length(${table.codeHash}) = 32`),
|
|
703
|
+
check('enrollments_scope_check', sql`json_valid(${table.scope})`),
|
|
704
|
+
foreignKey({
|
|
705
|
+
name: 'enrollments_binding_id_fkey',
|
|
706
|
+
columns: [table.bindingId],
|
|
707
|
+
foreignColumns: [serviceBindings.id],
|
|
708
|
+
}).onDelete('restrict'),
|
|
709
|
+
// A binding's codes, newest first, for its page.
|
|
710
|
+
index('enrollments_binding_idx').on(table.bindingId, table.createdAt),
|
|
711
|
+
],
|
|
712
|
+
);
|
|
713
|
+
|
|
664
714
|
export const credentials = sqliteTable(
|
|
665
715
|
'credentials',
|
|
666
716
|
{
|
|
@@ -685,12 +735,24 @@ export const credentials = sqliteTable(
|
|
|
685
735
|
lastUsedIp: text('last_used_ip'),
|
|
686
736
|
revokedAt: time('revoked_at'),
|
|
687
737
|
revokedBy: text('revoked_by'),
|
|
738
|
+
// A workload's session (docs/design/workload-access.md, section 11), null on every other credential:
|
|
739
|
+
// the thumbprint of the key its proofs are signed by, and its hard end.
|
|
740
|
+
jkt: text(),
|
|
741
|
+
notAfter: time('not_after'),
|
|
742
|
+
// A `narrow` child's: its ancestors' IDs, root first, JSON, and its limits (`Constraints`), canonical JSON.
|
|
743
|
+
ancestors: text(),
|
|
744
|
+
constraints: text(),
|
|
745
|
+
// The enrollment code a session was made from, and, where its binding says `approve`, who approved it.
|
|
746
|
+
enrollmentId: text('enrollment_id'),
|
|
747
|
+
approvedAt: time('approved_at'),
|
|
748
|
+
approvedBy: text('approved_by'),
|
|
688
749
|
},
|
|
689
750
|
(table) => [
|
|
690
751
|
unique('credentials_token_hash_key').on(table.tokenHash),
|
|
691
|
-
check('credentials_kind_check', sql`${table.kind} IN ('browser', 'cli', 'service', 'personal', 'handle')`),
|
|
752
|
+
check('credentials_kind_check', sql`${table.kind} IN ('browser', 'cli', 'service', 'personal', 'handle', 'workload')`),
|
|
692
753
|
check('credentials_principal_check', sql`${table.principal} LIKE 'user:%' OR ${table.principal} LIKE 'token:%'`),
|
|
693
|
-
check('credentials_kind_matches_principal', sql`(${table.kind}
|
|
754
|
+
check('credentials_kind_matches_principal', sql`(${table.kind} IN ('service', 'workload')) = (${table.principal} LIKE 'token:%')`),
|
|
755
|
+
check('credentials_workload_jkt_check', sql`${table.kind} <> 'workload' OR ${table.jkt} IS NOT NULL`),
|
|
694
756
|
check('credentials_token_hash_check', sql`octet_length(${table.tokenHash}) = 32`),
|
|
695
757
|
check('credentials_auth_mac_check', sql`octet_length(${table.authMac}) = 32`),
|
|
696
758
|
foreignKey({
|
|
@@ -710,6 +772,15 @@ export const credentials = sqliteTable(
|
|
|
710
772
|
index('credentials_live_idx').on(table.principal, table.expiresAt).where(sql`${table.revokedAt} IS NULL`),
|
|
711
773
|
// What a trust binding issued lately, for its rate.
|
|
712
774
|
index('credentials_issued_by_idx').on(table.principal, table.createdBy, table.createdAt),
|
|
775
|
+
foreignKey({
|
|
776
|
+
name: 'credentials_enrollment_id_fkey',
|
|
777
|
+
columns: [table.enrollmentId],
|
|
778
|
+
foreignColumns: [enrollments.id],
|
|
779
|
+
}).onDelete('restrict'),
|
|
780
|
+
// A code's sessions made by the exchange, ended ones too: its capacity and its tripwire's last hour, never a child.
|
|
781
|
+
index('credentials_enrollment_roots_idx')
|
|
782
|
+
.on(table.enrollmentId, table.createdAt)
|
|
783
|
+
.where(sql`${table.kind} = 'workload' AND ${table.ancestors} IS NULL`),
|
|
713
784
|
],
|
|
714
785
|
);
|
|
715
786
|
|
package/src/schema.ts
CHANGED
|
@@ -402,6 +402,14 @@ export const auditLog = pgTable(
|
|
|
402
402
|
index('audit_log_unbind_idx')
|
|
403
403
|
.on(sql`((${table.metadata})::jsonb ->> 'bindingId')`)
|
|
404
404
|
.where(sql`${table.author} = 'app' AND ${table.action} = 'token.unbind' AND ${table.decision} = 'allow'`),
|
|
405
|
+
// Whether a target is frozen: its newest allowed freeze or lift (docs/design/workload-access.md, section 8.2).
|
|
406
|
+
index('audit_log_freeze_idx')
|
|
407
|
+
.on(sql`((${table.metadata})::jsonb ->> 'target')`, table.seq.desc())
|
|
408
|
+
.where(sql`${table.author} = 'app' AND ${table.action} IN ('workload.freeze', 'workload.unfreeze') AND ${table.decision} = 'allow'`),
|
|
409
|
+
// An enrollment code's tombstone: the app's successful `enrollment.supersede` naming it.
|
|
410
|
+
index('audit_log_supersede_idx')
|
|
411
|
+
.on(sql`((${table.metadata})::jsonb ->> 'enrollmentId')`)
|
|
412
|
+
.where(sql`${table.author} = 'app' AND ${table.action} = 'enrollment.supersede' AND ${table.decision} = 'allow'`),
|
|
405
413
|
// The run a credential was issued for: its exchange's entry, by the credential's ID.
|
|
406
414
|
index('audit_log_exchange_idx')
|
|
407
415
|
.on(sql`((${table.metadata})::jsonb ->> 'credentialId')`)
|
|
@@ -563,6 +571,8 @@ export const serviceBindings = pgTable(
|
|
|
563
571
|
claims: text().notNull(),
|
|
564
572
|
// What the credentials it issues reach (`CredentialScope`), JSON; null on a binding of 0.4 the app has not sealed yet.
|
|
565
573
|
scope: text(),
|
|
574
|
+
// 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).
|
|
575
|
+
policy: text(),
|
|
566
576
|
label: text(),
|
|
567
577
|
createdAt: createdAt(),
|
|
568
578
|
createdBy: text('created_by').notNull(),
|
|
@@ -726,6 +736,46 @@ export const mcpApprovals = pgTable(
|
|
|
726
736
|
],
|
|
727
737
|
);
|
|
728
738
|
|
|
739
|
+
/**
|
|
740
|
+
* Enrollment codes (docs/design/workload-access.md, section 4.4): what CI
|
|
741
|
+
* mints for a platform that signs no identity, and the app exchanges for a
|
|
742
|
+
* workload's session at `/api/auth/workload`. Never a credential itself, and
|
|
743
|
+
* only ever added: the log says which code a promote superseded. Its scope,
|
|
744
|
+
* fixed at minting, is the binding's within what the minter held; the MAC
|
|
745
|
+
* covers everything but its hint and creation time (auth-rows.ts in the server).
|
|
746
|
+
*/
|
|
747
|
+
export const enrollments = pgTable(
|
|
748
|
+
'enrollments',
|
|
749
|
+
{
|
|
750
|
+
id: uuid().primaryKey(),
|
|
751
|
+
authMac: bytea('auth_mac').notNull(),
|
|
752
|
+
bindingId: uuid('binding_id').notNull(),
|
|
753
|
+
// The SHA-256 of the code, `coffre_enr_…`, and enough of it to recognize.
|
|
754
|
+
codeHash: bytea('code_hash').notNull(),
|
|
755
|
+
codeHint: text('code_hint').notNull(),
|
|
756
|
+
// `CredentialScope`, JSON: what its sessions reach, R32's sensitive per place.
|
|
757
|
+
scope: text().notNull(),
|
|
758
|
+
createdAt: createdAt(),
|
|
759
|
+
// Who minted it, and with which of their credentials: a CI run's session.
|
|
760
|
+
createdBy: text('created_by').notNull(),
|
|
761
|
+
createdWith: uuid('created_with'),
|
|
762
|
+
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
|
763
|
+
},
|
|
764
|
+
(table) => [
|
|
765
|
+
unique('enrollments_code_hash_key').on(table.codeHash),
|
|
766
|
+
check('enrollments_auth_mac_check', sql`octet_length(${table.authMac}) = 32`),
|
|
767
|
+
check('enrollments_code_hash_check', sql`octet_length(${table.codeHash}) = 32`),
|
|
768
|
+
check('enrollments_scope_check', sql`${table.scope}::jsonb IS NOT NULL`),
|
|
769
|
+
foreignKey({
|
|
770
|
+
name: 'enrollments_binding_id_fkey',
|
|
771
|
+
columns: [table.bindingId],
|
|
772
|
+
foreignColumns: [serviceBindings.id],
|
|
773
|
+
}).onDelete('restrict'),
|
|
774
|
+
// A binding's codes, newest first, for its page.
|
|
775
|
+
index('enrollments_binding_idx').on(table.bindingId, table.createdAt.desc()),
|
|
776
|
+
],
|
|
777
|
+
);
|
|
778
|
+
|
|
729
779
|
/**
|
|
730
780
|
* Bearer credentials coffre issues itself: browser sessions, CLI sessions and
|
|
731
781
|
* service tokens.
|
|
@@ -758,12 +808,24 @@ export const credentials = pgTable(
|
|
|
758
808
|
lastUsedIp: text('last_used_ip'),
|
|
759
809
|
revokedAt: timestamp('revoked_at', { withTimezone: true }),
|
|
760
810
|
revokedBy: text('revoked_by'),
|
|
811
|
+
// A workload's session (docs/design/workload-access.md, section 11), null on every other credential:
|
|
812
|
+
// the thumbprint of the key its proofs are signed by, and its hard end.
|
|
813
|
+
jkt: text(),
|
|
814
|
+
notAfter: timestamp('not_after', { withTimezone: true }),
|
|
815
|
+
// A `narrow` child's: its ancestors' IDs, root first, JSON, and its limits (`Constraints`), canonical JSON.
|
|
816
|
+
ancestors: text(),
|
|
817
|
+
constraints: text(),
|
|
818
|
+
// The enrollment code a session was made from, and, where its binding says `approve`, who approved it.
|
|
819
|
+
enrollmentId: uuid('enrollment_id'),
|
|
820
|
+
approvedAt: timestamp('approved_at', { withTimezone: true }),
|
|
821
|
+
approvedBy: text('approved_by'),
|
|
761
822
|
},
|
|
762
823
|
(table) => [
|
|
763
824
|
unique('credentials_token_hash_key').on(table.tokenHash),
|
|
764
|
-
check('credentials_kind_check', sql`${table.kind} IN ('browser', 'cli', 'service', 'personal', 'handle')`),
|
|
825
|
+
check('credentials_kind_check', sql`${table.kind} IN ('browser', 'cli', 'service', 'personal', 'handle', 'workload')`),
|
|
765
826
|
check('credentials_principal_check', sql`${table.principal} LIKE 'user:%' OR ${table.principal} LIKE 'token:%'`),
|
|
766
|
-
check('credentials_kind_matches_principal', sql`(${table.kind}
|
|
827
|
+
check('credentials_kind_matches_principal', sql`(${table.kind} IN ('service', 'workload')) = (${table.principal} LIKE 'token:%')`),
|
|
828
|
+
check('credentials_workload_jkt_check', sql`${table.kind} <> 'workload' OR ${table.jkt} IS NOT NULL`),
|
|
767
829
|
check('credentials_token_hash_check', sql`octet_length(${table.tokenHash}) = 32`),
|
|
768
830
|
check('credentials_auth_mac_check', sql`octet_length(${table.authMac}) = 32`),
|
|
769
831
|
foreignKey({
|
|
@@ -783,6 +845,15 @@ export const credentials = pgTable(
|
|
|
783
845
|
index('credentials_live_idx').on(table.principal, table.expiresAt).where(sql`${table.revokedAt} IS NULL`),
|
|
784
846
|
// What a trust binding issued lately, for its rate.
|
|
785
847
|
index('credentials_issued_by_idx').on(table.principal, table.createdBy, table.createdAt),
|
|
848
|
+
foreignKey({
|
|
849
|
+
name: 'credentials_enrollment_id_fkey',
|
|
850
|
+
columns: [table.enrollmentId],
|
|
851
|
+
foreignColumns: [enrollments.id],
|
|
852
|
+
}).onDelete('restrict'),
|
|
853
|
+
// A code's sessions made by the exchange, ended ones too: its capacity and its tripwire's last hour, never a child.
|
|
854
|
+
index('credentials_enrollment_roots_idx')
|
|
855
|
+
.on(table.enrollmentId, table.createdAt)
|
|
856
|
+
.where(sql`${table.kind} = 'workload' AND ${table.ancestors} IS NULL`),
|
|
786
857
|
],
|
|
787
858
|
);
|
|
788
859
|
|