@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.
Files changed (38) hide show
  1. package/dist/connect.d.ts +1 -1
  2. package/dist/{database-DBtn3jqp.d.ts → database-Dh4W2pAj.d.ts} +1 -1
  3. package/dist/dialect.d.ts +1 -1
  4. package/dist/grants.d.ts +12 -5
  5. package/dist/grants.js +13 -8
  6. package/dist/hyperdrive.d.ts +9 -2
  7. package/dist/hyperdrive.js +16 -2
  8. package/dist/index.d.ts +2 -2
  9. package/dist/log.d.ts +1 -1
  10. package/dist/migrations/postgres/0015_workload_access.sql +43 -0
  11. package/dist/migrations/postgres/0016_spent_tokens.sql +12 -0
  12. package/dist/migrations/postgres/meta/0015_snapshot.json +3256 -0
  13. package/dist/migrations/postgres/meta/0016_snapshot.json +3256 -0
  14. package/dist/migrations/postgres/meta/_journal.json +14 -0
  15. package/dist/migrations/sqlite/0015_workload_access.sql +70 -0
  16. package/dist/migrations/sqlite/meta/0015_snapshot.json +2959 -0
  17. package/dist/migrations/sqlite/meta/_journal.json +7 -0
  18. package/dist/portable.d.ts +2 -2
  19. package/dist/{schema-NBVA9q-s.d.ts → schema-DG2t57Kf.d.ts} +327 -2
  20. package/dist/schema-sqlite.d.ts +346 -1
  21. package/dist/schema-sqlite.js +53 -5
  22. package/dist/schema-version.d.ts +1 -1
  23. package/dist/schema-version.js +21 -0
  24. package/dist/schema.d.ts +2 -2
  25. package/dist/schema.js +54 -5
  26. package/package.json +2 -2
  27. package/src/grants.ts +18 -9
  28. package/src/hyperdrive.ts +16 -2
  29. package/src/migrations/postgres/0015_workload_access.sql +43 -0
  30. package/src/migrations/postgres/0016_spent_tokens.sql +12 -0
  31. package/src/migrations/postgres/meta/0015_snapshot.json +3256 -0
  32. package/src/migrations/postgres/meta/0016_snapshot.json +3256 -0
  33. package/src/migrations/postgres/meta/_journal.json +14 -0
  34. package/src/migrations/sqlite/0015_workload_access.sql +70 -0
  35. package/src/migrations/sqlite/meta/0015_snapshot.json +2959 -0
  36. package/src/migrations/sqlite/meta/_journal.json +7 -0
  37. package/src/schema.sqlite.ts +73 -2
  38. package/src/schema.ts +73 -2
@@ -1,5 +1,5 @@
1
1
  declare namespace schema_sqlite_d_exports {
2
- export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, dismissedKeys, environments, environmentsRelations, identities, mcpApprovals, mcpConnections, oauthClients, projects, secretReferences, secretVersions, secrets, secretsRelations, serviceBindings, vaultGrants, vaultMembers };
2
+ export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, dismissedKeys, enrollments, environments, environmentsRelations, identities, mcpApprovals, mcpConnections, oauthClients, projects, secretReferences, secretVersions, secrets, secretsRelations, serviceBindings, vaultGrants, vaultMembers };
3
3
  }
4
4
  export declare const projects: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
5
5
  name: "projects";
@@ -2400,6 +2400,25 @@ export declare const serviceBindings: import("drizzle-orm/sqlite-core").SQLiteTa
2400
2400
  }, {}, {
2401
2401
  length: undefined;
2402
2402
  }>;
2403
+ policy: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2404
+ name: "policy";
2405
+ tableName: "service_bindings";
2406
+ dataType: "string";
2407
+ columnType: "SQLiteText";
2408
+ data: string;
2409
+ driverParam: string;
2410
+ notNull: false;
2411
+ hasDefault: false;
2412
+ isPrimaryKey: false;
2413
+ isAutoincrement: false;
2414
+ hasRuntimeDefault: false;
2415
+ enumValues: [string, ...string[]];
2416
+ baseColumn: never;
2417
+ identity: undefined;
2418
+ generated: undefined;
2419
+ }, {}, {
2420
+ length: undefined;
2421
+ }>;
2403
2422
  label: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2404
2423
  name: "label";
2405
2424
  tableName: "service_bindings";
@@ -3294,6 +3313,203 @@ export declare const mcpApprovals: import("drizzle-orm/sqlite-core").SQLiteTable
3294
3313
  };
3295
3314
  dialect: "sqlite";
3296
3315
  }>;
3316
+ /**
3317
+ * Enrollment codes (docs/design/workload-access.md, section 4.4): what CI
3318
+ * mints for a platform that signs no identity, and the app exchanges for a
3319
+ * workload's session at `/api/auth/workload`. Never a credential itself, and
3320
+ * only ever added: the log says which code a promote superseded. Its scope,
3321
+ * fixed at minting, is the binding's within what the minter held; the MAC
3322
+ * covers everything but its hint and creation time (auth-rows.ts in the server).
3323
+ */
3324
+ export declare const enrollments: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
3325
+ name: "enrollments";
3326
+ schema: undefined;
3327
+ columns: {
3328
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3329
+ name: "id";
3330
+ tableName: "enrollments";
3331
+ dataType: "string";
3332
+ columnType: "SQLiteText";
3333
+ data: string;
3334
+ driverParam: string;
3335
+ notNull: true;
3336
+ hasDefault: false;
3337
+ isPrimaryKey: true;
3338
+ isAutoincrement: false;
3339
+ hasRuntimeDefault: false;
3340
+ enumValues: [string, ...string[]];
3341
+ baseColumn: never;
3342
+ identity: undefined;
3343
+ generated: undefined;
3344
+ }, {}, {
3345
+ length: undefined;
3346
+ }>;
3347
+ authMac: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3348
+ name: string;
3349
+ tableName: "enrollments";
3350
+ dataType: "buffer";
3351
+ columnType: "SQLiteBlobBuffer";
3352
+ data: Buffer<ArrayBufferLike>;
3353
+ driverParam: Buffer<ArrayBufferLike>;
3354
+ notNull: true;
3355
+ hasDefault: false;
3356
+ isPrimaryKey: false;
3357
+ isAutoincrement: false;
3358
+ hasRuntimeDefault: false;
3359
+ enumValues: undefined;
3360
+ baseColumn: never;
3361
+ identity: undefined;
3362
+ generated: undefined;
3363
+ }, {}, {}>;
3364
+ bindingId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3365
+ name: "binding_id";
3366
+ tableName: "enrollments";
3367
+ dataType: "string";
3368
+ columnType: "SQLiteText";
3369
+ data: string;
3370
+ driverParam: string;
3371
+ notNull: true;
3372
+ hasDefault: false;
3373
+ isPrimaryKey: false;
3374
+ isAutoincrement: false;
3375
+ hasRuntimeDefault: false;
3376
+ enumValues: [string, ...string[]];
3377
+ baseColumn: never;
3378
+ identity: undefined;
3379
+ generated: undefined;
3380
+ }, {}, {
3381
+ length: number | undefined;
3382
+ }>;
3383
+ codeHash: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3384
+ name: string;
3385
+ tableName: "enrollments";
3386
+ dataType: "buffer";
3387
+ columnType: "SQLiteBlobBuffer";
3388
+ data: Buffer<ArrayBufferLike>;
3389
+ driverParam: Buffer<ArrayBufferLike>;
3390
+ notNull: true;
3391
+ hasDefault: false;
3392
+ isPrimaryKey: false;
3393
+ isAutoincrement: false;
3394
+ hasRuntimeDefault: false;
3395
+ enumValues: undefined;
3396
+ baseColumn: never;
3397
+ identity: undefined;
3398
+ generated: undefined;
3399
+ }, {}, {}>;
3400
+ codeHint: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3401
+ name: "code_hint";
3402
+ tableName: "enrollments";
3403
+ dataType: "string";
3404
+ columnType: "SQLiteText";
3405
+ data: string;
3406
+ driverParam: string;
3407
+ notNull: true;
3408
+ hasDefault: false;
3409
+ isPrimaryKey: false;
3410
+ isAutoincrement: false;
3411
+ hasRuntimeDefault: false;
3412
+ enumValues: [string, ...string[]];
3413
+ baseColumn: never;
3414
+ identity: undefined;
3415
+ generated: undefined;
3416
+ }, {}, {
3417
+ length: number | undefined;
3418
+ }>;
3419
+ scope: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3420
+ name: "scope";
3421
+ tableName: "enrollments";
3422
+ dataType: "string";
3423
+ columnType: "SQLiteText";
3424
+ data: string;
3425
+ driverParam: string;
3426
+ notNull: true;
3427
+ hasDefault: false;
3428
+ isPrimaryKey: false;
3429
+ isAutoincrement: false;
3430
+ hasRuntimeDefault: false;
3431
+ enumValues: [string, ...string[]];
3432
+ baseColumn: never;
3433
+ identity: undefined;
3434
+ generated: undefined;
3435
+ }, {}, {
3436
+ length: undefined;
3437
+ }>;
3438
+ createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3439
+ name: string;
3440
+ tableName: "enrollments";
3441
+ dataType: "date";
3442
+ columnType: "SQLiteTimestamp";
3443
+ data: Date;
3444
+ driverParam: number;
3445
+ notNull: true;
3446
+ hasDefault: true;
3447
+ isPrimaryKey: false;
3448
+ isAutoincrement: false;
3449
+ hasRuntimeDefault: false;
3450
+ enumValues: undefined;
3451
+ baseColumn: never;
3452
+ identity: undefined;
3453
+ generated: undefined;
3454
+ }, {}, {}>;
3455
+ createdBy: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3456
+ name: "created_by";
3457
+ tableName: "enrollments";
3458
+ dataType: "string";
3459
+ columnType: "SQLiteText";
3460
+ data: string;
3461
+ driverParam: string;
3462
+ notNull: true;
3463
+ hasDefault: false;
3464
+ isPrimaryKey: false;
3465
+ isAutoincrement: false;
3466
+ hasRuntimeDefault: false;
3467
+ enumValues: [string, ...string[]];
3468
+ baseColumn: never;
3469
+ identity: undefined;
3470
+ generated: undefined;
3471
+ }, {}, {
3472
+ length: number | undefined;
3473
+ }>;
3474
+ createdWith: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3475
+ name: "created_with";
3476
+ tableName: "enrollments";
3477
+ dataType: "string";
3478
+ columnType: "SQLiteText";
3479
+ data: string;
3480
+ driverParam: string;
3481
+ notNull: false;
3482
+ hasDefault: false;
3483
+ isPrimaryKey: false;
3484
+ isAutoincrement: false;
3485
+ hasRuntimeDefault: false;
3486
+ enumValues: [string, ...string[]];
3487
+ baseColumn: never;
3488
+ identity: undefined;
3489
+ generated: undefined;
3490
+ }, {}, {
3491
+ length: number | undefined;
3492
+ }>;
3493
+ expiresAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3494
+ name: string;
3495
+ tableName: "enrollments";
3496
+ dataType: "date";
3497
+ columnType: "SQLiteTimestamp";
3498
+ data: Date;
3499
+ driverParam: number;
3500
+ notNull: true;
3501
+ hasDefault: false;
3502
+ isPrimaryKey: false;
3503
+ isAutoincrement: false;
3504
+ hasRuntimeDefault: false;
3505
+ enumValues: undefined;
3506
+ baseColumn: never;
3507
+ identity: undefined;
3508
+ generated: undefined;
3509
+ }, {}, {}>;
3510
+ };
3511
+ dialect: "sqlite";
3512
+ }>;
3297
3513
  export declare const credentials: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
3298
3514
  name: "credentials";
3299
3515
  schema: undefined;
@@ -3626,6 +3842,135 @@ export declare const credentials: import("drizzle-orm/sqlite-core").SQLiteTableW
3626
3842
  }, {}, {
3627
3843
  length: number | undefined;
3628
3844
  }>;
3845
+ jkt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3846
+ name: "jkt";
3847
+ tableName: "credentials";
3848
+ dataType: "string";
3849
+ columnType: "SQLiteText";
3850
+ data: string;
3851
+ driverParam: string;
3852
+ notNull: false;
3853
+ hasDefault: false;
3854
+ isPrimaryKey: false;
3855
+ isAutoincrement: false;
3856
+ hasRuntimeDefault: false;
3857
+ enumValues: [string, ...string[]];
3858
+ baseColumn: never;
3859
+ identity: undefined;
3860
+ generated: undefined;
3861
+ }, {}, {
3862
+ length: undefined;
3863
+ }>;
3864
+ notAfter: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3865
+ name: string;
3866
+ tableName: "credentials";
3867
+ dataType: "date";
3868
+ columnType: "SQLiteTimestamp";
3869
+ data: Date;
3870
+ driverParam: number;
3871
+ notNull: false;
3872
+ hasDefault: false;
3873
+ isPrimaryKey: false;
3874
+ isAutoincrement: false;
3875
+ hasRuntimeDefault: false;
3876
+ enumValues: undefined;
3877
+ baseColumn: never;
3878
+ identity: undefined;
3879
+ generated: undefined;
3880
+ }, {}, {}>;
3881
+ ancestors: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3882
+ name: "ancestors";
3883
+ tableName: "credentials";
3884
+ dataType: "string";
3885
+ columnType: "SQLiteText";
3886
+ data: string;
3887
+ driverParam: string;
3888
+ notNull: false;
3889
+ hasDefault: false;
3890
+ isPrimaryKey: false;
3891
+ isAutoincrement: false;
3892
+ hasRuntimeDefault: false;
3893
+ enumValues: [string, ...string[]];
3894
+ baseColumn: never;
3895
+ identity: undefined;
3896
+ generated: undefined;
3897
+ }, {}, {
3898
+ length: undefined;
3899
+ }>;
3900
+ constraints: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3901
+ name: "constraints";
3902
+ tableName: "credentials";
3903
+ dataType: "string";
3904
+ columnType: "SQLiteText";
3905
+ data: string;
3906
+ driverParam: string;
3907
+ notNull: false;
3908
+ hasDefault: false;
3909
+ isPrimaryKey: false;
3910
+ isAutoincrement: false;
3911
+ hasRuntimeDefault: false;
3912
+ enumValues: [string, ...string[]];
3913
+ baseColumn: never;
3914
+ identity: undefined;
3915
+ generated: undefined;
3916
+ }, {}, {
3917
+ length: undefined;
3918
+ }>;
3919
+ enrollmentId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3920
+ name: "enrollment_id";
3921
+ tableName: "credentials";
3922
+ dataType: "string";
3923
+ columnType: "SQLiteText";
3924
+ data: string;
3925
+ driverParam: string;
3926
+ notNull: false;
3927
+ hasDefault: false;
3928
+ isPrimaryKey: false;
3929
+ isAutoincrement: false;
3930
+ hasRuntimeDefault: false;
3931
+ enumValues: [string, ...string[]];
3932
+ baseColumn: never;
3933
+ identity: undefined;
3934
+ generated: undefined;
3935
+ }, {}, {
3936
+ length: number | undefined;
3937
+ }>;
3938
+ approvedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3939
+ name: string;
3940
+ tableName: "credentials";
3941
+ dataType: "date";
3942
+ columnType: "SQLiteTimestamp";
3943
+ data: Date;
3944
+ driverParam: number;
3945
+ notNull: false;
3946
+ hasDefault: false;
3947
+ isPrimaryKey: false;
3948
+ isAutoincrement: false;
3949
+ hasRuntimeDefault: false;
3950
+ enumValues: undefined;
3951
+ baseColumn: never;
3952
+ identity: undefined;
3953
+ generated: undefined;
3954
+ }, {}, {}>;
3955
+ approvedBy: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3956
+ name: "approved_by";
3957
+ tableName: "credentials";
3958
+ dataType: "string";
3959
+ columnType: "SQLiteText";
3960
+ data: string;
3961
+ driverParam: string;
3962
+ notNull: false;
3963
+ hasDefault: false;
3964
+ isPrimaryKey: false;
3965
+ isAutoincrement: false;
3966
+ hasRuntimeDefault: false;
3967
+ enumValues: [string, ...string[]];
3968
+ baseColumn: never;
3969
+ identity: undefined;
3970
+ generated: undefined;
3971
+ }, {}, {
3972
+ length: number | undefined;
3973
+ }>;
3629
3974
  };
3630
3975
  dialect: "sqlite";
3631
3976
  }>;
@@ -278,6 +278,8 @@ const auditLog = sqliteTable("audit_log", {
278
278
  index("audit_log_releases_idx").on(table.author, table.actor, table.action, table.decision, table.occurredAt),
279
279
  index("audit_log_subject_idx").on(table.author, table.subjectPrincipal, table.seq),
280
280
  index("audit_log_unbind_idx").on(sql`json_extract(${table.metadata}, '$.bindingId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.unbind' AND ${table.decision} = 'allow'`),
281
+ index("audit_log_freeze_idx").on(sql`json_extract(${table.metadata}, '$.target')`, table.seq).where(sql`${table.author} = 'app' AND ${table.action} IN ('workload.freeze', 'workload.unfreeze') AND ${table.decision} = 'allow'`),
282
+ index("audit_log_supersede_idx").on(sql`json_extract(${table.metadata}, '$.enrollmentId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'enrollment.supersede' AND ${table.decision} = 'allow'`),
281
283
  index("audit_log_exchange_idx").on(sql`json_extract(${table.metadata}, '$.credentialId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.exchange' AND ${table.decision} = 'allow'`),
282
284
  index("audit_log_token_create_idx").on(sql`json_extract(${table.metadata}, '$.targetCredentialId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.create' AND ${table.decision} = 'allow'`),
283
285
  index("audit_log_reference_end_idx").on(table.relatedSeq).where(sql`${table.author} = 'vault' AND ${table.action} = 'reference.end' AND ${table.decision} = 'allow'`),
@@ -367,6 +369,7 @@ const serviceBindings = sqliteTable("service_bindings", {
367
369
  jwks: text(),
368
370
  claims: text().notNull(),
369
371
  scope: text(),
372
+ policy: text(),
370
373
  label: text(),
371
374
  createdAt: createdAt(),
372
375
  createdBy: text("created_by").notNull(),
@@ -475,6 +478,37 @@ const mcpApprovals = sqliteTable("mcp_approvals", {
475
478
  }).onDelete("restrict"),
476
479
  index("mcp_approvals_open_idx").on(table.connectionId, table.digest).where(sql`${table.reportedAt} IS NULL`)
477
480
  ]);
481
+ /**
482
+ * Enrollment codes (docs/design/workload-access.md, section 4.4): what CI
483
+ * mints for a platform that signs no identity, and the app exchanges for a
484
+ * workload's session at `/api/auth/workload`. Never a credential itself, and
485
+ * only ever added: the log says which code a promote superseded. Its scope,
486
+ * fixed at minting, is the binding's within what the minter held; the MAC
487
+ * covers everything but its hint and creation time (auth-rows.ts in the server).
488
+ */
489
+ const enrollments = sqliteTable("enrollments", {
490
+ id: text().primaryKey(),
491
+ authMac: bytes("auth_mac").notNull(),
492
+ bindingId: text("binding_id").notNull(),
493
+ codeHash: bytes("code_hash").notNull(),
494
+ codeHint: text("code_hint").notNull(),
495
+ scope: text().notNull(),
496
+ createdAt: createdAt(),
497
+ createdBy: text("created_by").notNull(),
498
+ createdWith: text("created_with"),
499
+ expiresAt: time("expires_at").notNull()
500
+ }, (table) => [
501
+ unique("enrollments_code_hash_key").on(table.codeHash),
502
+ check("enrollments_auth_mac_check", sql`octet_length(${table.authMac}) = 32`),
503
+ check("enrollments_code_hash_check", sql`octet_length(${table.codeHash}) = 32`),
504
+ check("enrollments_scope_check", sql`json_valid(${table.scope})`),
505
+ foreignKey({
506
+ name: "enrollments_binding_id_fkey",
507
+ columns: [table.bindingId],
508
+ foreignColumns: [serviceBindings.id]
509
+ }).onDelete("restrict"),
510
+ index("enrollments_binding_idx").on(table.bindingId, table.createdAt)
511
+ ]);
478
512
  const credentials = sqliteTable("credentials", {
479
513
  id: text().primaryKey(),
480
514
  kind: text().notNull(),
@@ -493,12 +527,20 @@ const credentials = sqliteTable("credentials", {
493
527
  lastUsedAt: time("last_used_at"),
494
528
  lastUsedIp: text("last_used_ip"),
495
529
  revokedAt: time("revoked_at"),
496
- revokedBy: text("revoked_by")
530
+ revokedBy: text("revoked_by"),
531
+ jkt: text(),
532
+ notAfter: time("not_after"),
533
+ ancestors: text(),
534
+ constraints: text(),
535
+ enrollmentId: text("enrollment_id"),
536
+ approvedAt: time("approved_at"),
537
+ approvedBy: text("approved_by")
497
538
  }, (table) => [
498
539
  unique("credentials_token_hash_key").on(table.tokenHash),
499
- check("credentials_kind_check", sql`${table.kind} IN ('browser', 'cli', 'service', 'personal', 'handle')`),
540
+ check("credentials_kind_check", sql`${table.kind} IN ('browser', 'cli', 'service', 'personal', 'handle', 'workload')`),
500
541
  check("credentials_principal_check", sql`${table.principal} LIKE 'user:%' OR ${table.principal} LIKE 'token:%'`),
501
- check("credentials_kind_matches_principal", sql`(${table.kind} = 'service') = (${table.principal} LIKE 'token:%')`),
542
+ check("credentials_kind_matches_principal", sql`(${table.kind} IN ('service', 'workload')) = (${table.principal} LIKE 'token:%')`),
543
+ check("credentials_workload_jkt_check", sql`${table.kind} <> 'workload' OR ${table.jkt} IS NOT NULL`),
502
544
  check("credentials_token_hash_check", sql`octet_length(${table.tokenHash}) = 32`),
503
545
  check("credentials_auth_mac_check", sql`octet_length(${table.authMac}) = 32`),
504
546
  foreignKey({
@@ -521,7 +563,13 @@ const credentials = sqliteTable("credentials", {
521
563
  }).onDelete("restrict"),
522
564
  index("credentials_principal_idx").on(table.principal, table.generation),
523
565
  index("credentials_live_idx").on(table.principal, table.expiresAt).where(sql`${table.revokedAt} IS NULL`),
524
- index("credentials_issued_by_idx").on(table.principal, table.createdBy, table.createdAt)
566
+ index("credentials_issued_by_idx").on(table.principal, table.createdBy, table.createdAt),
567
+ foreignKey({
568
+ name: "credentials_enrollment_id_fkey",
569
+ columns: [table.enrollmentId],
570
+ foreignColumns: [enrollments.id]
571
+ }).onDelete("restrict"),
572
+ index("credentials_enrollment_roots_idx").on(table.enrollmentId, table.createdAt).where(sql`${table.kind} = 'workload' AND ${table.ancestors} IS NULL`)
525
573
  ]);
526
574
  const deviceAuthorizations = sqliteTable("device_authorizations", {
527
575
  id: text().primaryKey(),
@@ -560,4 +608,4 @@ const { environmentsRelations, secretsRelations } = relationsOf(asPostgres({
560
608
  secretVersions
561
609
  }));
562
610
  //#endregion
563
- export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, dismissedKeys, environments, environmentsRelations, identities, mcpApprovals, mcpConnections, oauthClients, projects, secretReferences, secretVersions, secrets, secretsRelations, serviceBindings, vaultGrants, vaultMembers };
611
+ export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, dismissedKeys, enrollments, environments, environmentsRelations, identities, mcpApprovals, mcpConnections, oauthClients, projects, secretReferences, secretVersions, secrets, secretsRelations, serviceBindings, vaultGrants, vaultMembers };
@@ -1,4 +1,4 @@
1
- import { i as Queryable } from "./database-DBtn3jqp.js";
1
+ import { i as Queryable } from "./database-Dh4W2pAj.js";
2
2
  import { Engine } from "./dialect.js";
3
3
  //#region src/schema-version.d.ts
4
4
  /**
@@ -113,6 +113,20 @@ const KNOWN_MIGRATIONS = {
113
113
  "when": 1792345776262,
114
114
  "tag": "0014_custom_roles",
115
115
  "breakpoints": true
116
+ },
117
+ {
118
+ "idx": 15,
119
+ "version": "7",
120
+ "when": 1792803757287,
121
+ "tag": "0015_workload_access",
122
+ "breakpoints": true
123
+ },
124
+ {
125
+ "idx": 16,
126
+ "version": "7",
127
+ "when": 1792814038307,
128
+ "tag": "0016_spent_tokens",
129
+ "breakpoints": true
116
130
  }
117
131
  ].map((entry) => entry.tag),
118
132
  sqlite: [
@@ -220,6 +234,13 @@ const KNOWN_MIGRATIONS = {
220
234
  "when": 1792345777255,
221
235
  "tag": "0014_custom_roles",
222
236
  "breakpoints": true
237
+ },
238
+ {
239
+ "idx": 15,
240
+ "version": "6",
241
+ "when": 1792803757926,
242
+ "tag": "0015_workload_access",
243
+ "breakpoints": true
223
244
  }
224
245
  ].map((entry) => entry.tag)
225
246
  };
package/dist/schema.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as secrets, a as deviceAuthorizations, b as vaultGrants, c as environmentsRelations, d as mcpConnections, f as oauthClients, g as secretVersions, h as secretReferences, i as credentials, l as identities, n as auditLog, o as dismissedKeys, p as projects, r as consumedTokens, s as environments, t as auditChainHead, u as mcpApprovals, v as secretsRelations, x as vaultMembers, y as serviceBindings } from "./schema-NBVA9q-s.js";
2
- export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, dismissedKeys, environments, environmentsRelations, identities, mcpApprovals, mcpConnections, oauthClients, projects, secretReferences, secretVersions, secrets, secretsRelations, serviceBindings, vaultGrants, vaultMembers };
1
+ import { S as vaultMembers, _ as secretVersions, a as deviceAuthorizations, b as serviceBindings, c as environments, d as mcpApprovals, f as mcpConnections, g as secretReferences, i as credentials, l as environmentsRelations, m as projects, n as auditLog, o as dismissedKeys, p as oauthClients, r as consumedTokens, s as enrollments, t as auditChainHead, u as identities, v as secrets, x as vaultGrants, y as secretsRelations } from "./schema-DG2t57Kf.js";
2
+ export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, dismissedKeys, enrollments, environments, environmentsRelations, identities, mcpApprovals, mcpConnections, oauthClients, projects, secretReferences, secretVersions, secrets, secretsRelations, serviceBindings, vaultGrants, vaultMembers };
package/dist/schema.js CHANGED
@@ -10,6 +10,7 @@ var schema_exports = /* @__PURE__ */ __exportAll({
10
10
  credentials: () => credentials,
11
11
  deviceAuthorizations: () => deviceAuthorizations,
12
12
  dismissedKeys: () => dismissedKeys,
13
+ enrollments: () => enrollments,
13
14
  environments: () => environments,
14
15
  environmentsRelations: () => environmentsRelations,
15
16
  identities: () => identities,
@@ -302,6 +303,8 @@ const auditLog = pgTable("audit_log", {
302
303
  index("audit_log_releases_idx").on(table.author, table.actor, table.action, table.decision, table.occurredAt),
303
304
  index("audit_log_subject_idx").on(table.author, table.subjectPrincipal, table.seq),
304
305
  index("audit_log_unbind_idx").on(sql`((${table.metadata})::jsonb ->> 'bindingId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.unbind' AND ${table.decision} = 'allow'`),
306
+ index("audit_log_freeze_idx").on(sql`((${table.metadata})::jsonb ->> 'target')`, table.seq.desc()).where(sql`${table.author} = 'app' AND ${table.action} IN ('workload.freeze', 'workload.unfreeze') AND ${table.decision} = 'allow'`),
307
+ index("audit_log_supersede_idx").on(sql`((${table.metadata})::jsonb ->> 'enrollmentId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'enrollment.supersede' AND ${table.decision} = 'allow'`),
305
308
  index("audit_log_exchange_idx").on(sql`((${table.metadata})::jsonb ->> 'credentialId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.exchange' AND ${table.decision} = 'allow'`),
306
309
  index("audit_log_token_create_idx").on(sql`((${table.metadata})::jsonb ->> 'targetCredentialId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.create' AND ${table.decision} = 'allow'`),
307
310
  index("audit_log_reference_end_idx").on(table.relatedSeq).where(sql`${table.author} = 'vault' AND ${table.action} = 'reference.end' AND ${table.decision} = 'allow'`),
@@ -414,6 +417,7 @@ const serviceBindings = pgTable("service_bindings", {
414
417
  jwks: text(),
415
418
  claims: text().notNull(),
416
419
  scope: text(),
420
+ policy: text(),
417
421
  label: text(),
418
422
  createdAt: createdAt(),
419
423
  createdBy: text("created_by").notNull(),
@@ -533,6 +537,37 @@ const mcpApprovals = pgTable("mcp_approvals", {
533
537
  index("mcp_approvals_open_idx").on(table.connectionId, table.digest).where(sql`${table.reportedAt} IS NULL`)
534
538
  ]);
535
539
  /**
540
+ * Enrollment codes (docs/design/workload-access.md, section 4.4): what CI
541
+ * mints for a platform that signs no identity, and the app exchanges for a
542
+ * workload's session at `/api/auth/workload`. Never a credential itself, and
543
+ * only ever added: the log says which code a promote superseded. Its scope,
544
+ * fixed at minting, is the binding's within what the minter held; the MAC
545
+ * covers everything but its hint and creation time (auth-rows.ts in the server).
546
+ */
547
+ const enrollments = pgTable("enrollments", {
548
+ id: uuid().primaryKey(),
549
+ authMac: bytea("auth_mac").notNull(),
550
+ bindingId: uuid("binding_id").notNull(),
551
+ codeHash: bytea("code_hash").notNull(),
552
+ codeHint: text("code_hint").notNull(),
553
+ scope: text().notNull(),
554
+ createdAt: createdAt(),
555
+ createdBy: text("created_by").notNull(),
556
+ createdWith: uuid("created_with"),
557
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull()
558
+ }, (table) => [
559
+ unique("enrollments_code_hash_key").on(table.codeHash),
560
+ check("enrollments_auth_mac_check", sql`octet_length(${table.authMac}) = 32`),
561
+ check("enrollments_code_hash_check", sql`octet_length(${table.codeHash}) = 32`),
562
+ check("enrollments_scope_check", sql`${table.scope}::jsonb IS NOT NULL`),
563
+ foreignKey({
564
+ name: "enrollments_binding_id_fkey",
565
+ columns: [table.bindingId],
566
+ foreignColumns: [serviceBindings.id]
567
+ }).onDelete("restrict"),
568
+ index("enrollments_binding_idx").on(table.bindingId, table.createdAt.desc())
569
+ ]);
570
+ /**
536
571
  * Bearer credentials coffre issues itself: browser sessions, CLI sessions and
537
572
  * service tokens.
538
573
  *
@@ -558,12 +593,20 @@ const credentials = pgTable("credentials", {
558
593
  lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
559
594
  lastUsedIp: text("last_used_ip"),
560
595
  revokedAt: timestamp("revoked_at", { withTimezone: true }),
561
- revokedBy: text("revoked_by")
596
+ revokedBy: text("revoked_by"),
597
+ jkt: text(),
598
+ notAfter: timestamp("not_after", { withTimezone: true }),
599
+ ancestors: text(),
600
+ constraints: text(),
601
+ enrollmentId: uuid("enrollment_id"),
602
+ approvedAt: timestamp("approved_at", { withTimezone: true }),
603
+ approvedBy: text("approved_by")
562
604
  }, (table) => [
563
605
  unique("credentials_token_hash_key").on(table.tokenHash),
564
- check("credentials_kind_check", sql`${table.kind} IN ('browser', 'cli', 'service', 'personal', 'handle')`),
606
+ check("credentials_kind_check", sql`${table.kind} IN ('browser', 'cli', 'service', 'personal', 'handle', 'workload')`),
565
607
  check("credentials_principal_check", sql`${table.principal} LIKE 'user:%' OR ${table.principal} LIKE 'token:%'`),
566
- check("credentials_kind_matches_principal", sql`(${table.kind} = 'service') = (${table.principal} LIKE 'token:%')`),
608
+ check("credentials_kind_matches_principal", sql`(${table.kind} IN ('service', 'workload')) = (${table.principal} LIKE 'token:%')`),
609
+ check("credentials_workload_jkt_check", sql`${table.kind} <> 'workload' OR ${table.jkt} IS NOT NULL`),
567
610
  check("credentials_token_hash_check", sql`octet_length(${table.tokenHash}) = 32`),
568
611
  check("credentials_auth_mac_check", sql`octet_length(${table.authMac}) = 32`),
569
612
  foreignKey({
@@ -586,7 +629,13 @@ const credentials = pgTable("credentials", {
586
629
  }).onDelete("restrict"),
587
630
  index("credentials_principal_idx").on(table.principal, table.generation),
588
631
  index("credentials_live_idx").on(table.principal, table.expiresAt).where(sql`${table.revokedAt} IS NULL`),
589
- index("credentials_issued_by_idx").on(table.principal, table.createdBy, table.createdAt)
632
+ index("credentials_issued_by_idx").on(table.principal, table.createdBy, table.createdAt),
633
+ foreignKey({
634
+ name: "credentials_enrollment_id_fkey",
635
+ columns: [table.enrollmentId],
636
+ foreignColumns: [enrollments.id]
637
+ }).onDelete("restrict"),
638
+ index("credentials_enrollment_roots_idx").on(table.enrollmentId, table.createdAt).where(sql`${table.kind} = 'workload' AND ${table.ancestors} IS NULL`)
590
639
  ]);
591
640
  /**
592
641
  * A CLI asking to be signed in from a browser (RFC 8628, device flow).
@@ -632,4 +681,4 @@ const { environmentsRelations, secretsRelations } = relationsOf({
632
681
  secretVersions
633
682
  });
634
683
  //#endregion
635
- export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, dismissedKeys, environments, environmentsRelations, identities, mcpApprovals, mcpConnections, oauthClients, projects, secretReferences, secretVersions, secrets, secretsRelations, serviceBindings, schema_exports as t, vaultGrants, vaultMembers };
684
+ export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, dismissedKeys, enrollments, environments, environmentsRelations, identities, mcpApprovals, mcpConnections, oauthClients, projects, secretReferences, secretVersions, secrets, secretsRelations, serviceBindings, schema_exports as t, vaultGrants, vaultMembers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffre/db",
3
- "version": "0.10.2",
3
+ "version": "0.11.0",
4
4
  "description": "coffre's database: the schema, its migrations and the connections, on Postgres or SQLite.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -79,7 +79,7 @@
79
79
  "drizzle-orm": "0.45.2",
80
80
  "pg": "8.16.3",
81
81
  "pg-connection-string": "2.14.0",
82
- "@coffre/core": "0.10.2"
82
+ "@coffre/core": "0.11.0"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@types/node": "26.1.1",