@nylorun/runtime 0.20.0-beta → 0.21.1-beta

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 (109) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +19 -12
  3. package/dist/adapters/execution/restate.js +26 -1
  4. package/dist/api/components.d.ts +155 -36
  5. package/dist/api/components.js +3 -3
  6. package/dist/api/http/app.js +2 -0
  7. package/dist/api/http/routes/mcp.d.ts +9 -0
  8. package/dist/api/http/routes/mcp.js +32 -0
  9. package/dist/api/http/routes/vaults.d.ts +1 -9
  10. package/dist/api/http/routes/vaults.js +3 -103
  11. package/dist/api/openapi.js +6 -3
  12. package/dist/artifacts/media-types.d.ts +2 -0
  13. package/dist/artifacts/media-types.js +5 -0
  14. package/dist/core/flow-host.d.ts +2 -3
  15. package/dist/core/flow-host.js +10 -7
  16. package/dist/execution/types.d.ts +13 -5
  17. package/dist/execution/types.js +13 -5
  18. package/dist/gates/http-tool.d.ts +7 -0
  19. package/dist/gates/http-tool.js +34 -3
  20. package/dist/gates/mcp-handler.d.ts +4 -1
  21. package/dist/gates/mcp-handler.js +64 -8
  22. package/dist/gates/tenant-vaults.d.ts +9 -15
  23. package/dist/gates/tenant-vaults.js +9 -17
  24. package/dist/gates/tool-client.js +36 -17
  25. package/dist/gates/tool-contract.d.ts +19 -3
  26. package/dist/gates/tool-contract.js +9 -3
  27. package/dist/gates/tool-gate.d.ts +2 -2
  28. package/dist/harness/calls.d.ts +2 -0
  29. package/dist/harness/calls.js +13 -3
  30. package/dist/harness-api/record.d.ts +7 -6
  31. package/dist/harness-api/record.js +99 -17
  32. package/dist/harness-api/server.js +35 -27
  33. package/dist/harness-api/start.js +2 -1
  34. package/dist/host/config-for.d.ts +0 -2
  35. package/dist/host/config-for.js +0 -1
  36. package/dist/host/create-host.d.ts +1 -1
  37. package/dist/host/create-host.js +2 -1
  38. package/dist/host/gates.js +0 -1
  39. package/dist/host/http.d.ts +0 -1
  40. package/dist/host/http.js +0 -6
  41. package/dist/host/main.js +13 -3
  42. package/dist/host/stack-config.d.ts +7 -8
  43. package/dist/host/stack-config.js +50 -92
  44. package/dist/index.d.ts +0 -1
  45. package/dist/keys/client.js +1 -9
  46. package/dist/keys/keys.d.ts +11 -13
  47. package/dist/keys/keys.js +5 -7
  48. package/dist/management-openapi.json +315 -345
  49. package/dist/mcp/connect.d.ts +159 -13
  50. package/dist/mcp/connect.js +538 -55
  51. package/dist/mcp/pool.d.ts +11 -2
  52. package/dist/mcp/pool.js +74 -27
  53. package/dist/mcp/preview.d.ts +35 -0
  54. package/dist/mcp/preview.js +215 -0
  55. package/dist/mcp/snapshot.d.ts +79 -5
  56. package/dist/mcp/snapshot.js +145 -27
  57. package/dist/model/catalog.d.ts +14 -0
  58. package/dist/model/catalog.js +14 -1
  59. package/dist/model/pi-model.js +36 -2
  60. package/dist/openapi.json +362 -123
  61. package/dist/redact.d.ts +19 -1
  62. package/dist/redact.js +42 -13
  63. package/dist/sandbox/session-sandbox.d.ts +7 -0
  64. package/dist/sandbox/session-sandbox.js +89 -28
  65. package/dist/store/postgres/drizzle/0016_mcp_oauth_removed.sql +5 -0
  66. package/dist/store/postgres/drizzle/0017_wake_outbox.sql +9 -0
  67. package/dist/store/postgres/drizzle/meta/_journal.json +14 -0
  68. package/dist/store/postgres/schema.d.ts +105 -261
  69. package/dist/store/postgres/schema.js +18 -36
  70. package/dist/store/postgres/store.js +54 -21
  71. package/dist/store/postgres/tenant.d.ts +14 -0
  72. package/dist/store/postgres/tenant.js +22 -2
  73. package/dist/store/types.d.ts +48 -13
  74. package/dist/tenant/advance.js +1 -4
  75. package/dist/tenant/artifact-tool.d.ts +9 -1
  76. package/dist/tenant/artifact-tool.js +113 -9
  77. package/dist/tenant/commands.js +5 -5
  78. package/dist/tenant/context.d.ts +15 -13
  79. package/dist/tenant/effects.d.ts +2 -2
  80. package/dist/tenant/effects.js +20 -5
  81. package/dist/tenant/ephemeral.d.ts +0 -6
  82. package/dist/tenant/ephemeral.js +0 -1
  83. package/dist/tenant/outbound.d.ts +14 -5
  84. package/dist/tenant/outbound.js +21 -9
  85. package/dist/tenant/runtime.js +11 -15
  86. package/dist/tenant/sessions.js +20 -3
  87. package/dist/tenant/store-pg.js +9 -0
  88. package/dist/tenant/sweep.d.ts +14 -0
  89. package/dist/tenant/sweep.js +69 -10
  90. package/dist/tenant/tool-results.d.ts +57 -0
  91. package/dist/tenant/tool-results.js +298 -0
  92. package/dist/tenant/tool-search.d.ts +43 -0
  93. package/dist/tenant/tool-search.js +100 -0
  94. package/dist/tenant/transcript.d.ts +108 -0
  95. package/dist/tenant/transcript.js +13 -0
  96. package/dist/tenant/types.d.ts +0 -14
  97. package/dist/vault/crypto.d.ts +0 -7
  98. package/dist/vault/crypto.js +0 -16
  99. package/dist/vault/headers.d.ts +17 -0
  100. package/dist/vault/headers.js +61 -0
  101. package/dist/vault/service.d.ts +11 -68
  102. package/dist/vault/service.js +83 -438
  103. package/dist/vault/sources.d.ts +20 -48
  104. package/dist/vault/sources.js +25 -189
  105. package/dist/version.d.ts +1 -1
  106. package/dist/version.js +1 -1
  107. package/package.json +3 -3
  108. package/dist/vault/oauth.d.ts +0 -55
  109. package/dist/vault/oauth.js +0 -186
@@ -1048,14 +1048,14 @@ export declare const vaultCredentials: import("drizzle-orm/pg-core").PgTableWith
1048
1048
  tableName: "vault_credentials";
1049
1049
  dataType: "string";
1050
1050
  columnType: "PgText";
1051
- data: "bearer" | "model" | "oauth";
1051
+ data: "bearer" | "headers" | "model";
1052
1052
  driverParam: string;
1053
1053
  notNull: true;
1054
1054
  hasDefault: false;
1055
1055
  isPrimaryKey: false;
1056
1056
  isAutoincrement: false;
1057
1057
  hasRuntimeDefault: false;
1058
- enumValues: ["bearer", "oauth", "model"];
1058
+ enumValues: ["bearer", "headers", "model"];
1059
1059
  baseColumn: never;
1060
1060
  identity: undefined;
1061
1061
  generated: undefined;
@@ -1442,264 +1442,6 @@ export declare const vaultIdempotency: import("drizzle-orm/pg-core").PgTableWith
1442
1442
  };
1443
1443
  dialect: 'pg';
1444
1444
  }>;
1445
- /**
1446
- * An MCP OAuth connect between its start and its callback (F9 C2): found by the SHA-256 of its
1447
- * `state`, used once, for ten minutes. The PKCE verifier and any client secret are sealed under
1448
- * the vault key (`sealBytes`), which only the keys module holds.
1449
- */
1450
- export declare const oauthPending: import("drizzle-orm/pg-core").PgTableWithColumns<{
1451
- name: "oauth_pending";
1452
- schema: "nylorun";
1453
- columns: {
1454
- stateHash: import("drizzle-orm/pg-core").PgColumn<{
1455
- name: "stateHash";
1456
- tableName: "oauth_pending";
1457
- dataType: "string";
1458
- columnType: "PgText";
1459
- data: string;
1460
- driverParam: string;
1461
- notNull: true;
1462
- hasDefault: false;
1463
- isPrimaryKey: true;
1464
- isAutoincrement: false;
1465
- hasRuntimeDefault: false;
1466
- enumValues: [string, ...string[]];
1467
- baseColumn: never;
1468
- identity: undefined;
1469
- generated: undefined;
1470
- }, {}, {}>;
1471
- vaultId: import("drizzle-orm/pg-core").PgColumn<{
1472
- name: "vaultId";
1473
- tableName: "oauth_pending";
1474
- dataType: "string";
1475
- columnType: "PgText";
1476
- data: string;
1477
- driverParam: string;
1478
- notNull: true;
1479
- hasDefault: false;
1480
- isPrimaryKey: false;
1481
- isAutoincrement: false;
1482
- hasRuntimeDefault: false;
1483
- enumValues: [string, ...string[]];
1484
- baseColumn: never;
1485
- identity: undefined;
1486
- generated: undefined;
1487
- }, {}, {}>;
1488
- server: import("drizzle-orm/pg-core").PgColumn<{
1489
- name: "server";
1490
- tableName: "oauth_pending";
1491
- dataType: "string";
1492
- columnType: "PgText";
1493
- data: string;
1494
- driverParam: string;
1495
- notNull: true;
1496
- hasDefault: false;
1497
- isPrimaryKey: false;
1498
- isAutoincrement: false;
1499
- hasRuntimeDefault: false;
1500
- enumValues: [string, ...string[]];
1501
- baseColumn: never;
1502
- identity: undefined;
1503
- generated: undefined;
1504
- }, {}, {}>;
1505
- url: import("drizzle-orm/pg-core").PgColumn<{
1506
- name: "url";
1507
- tableName: "oauth_pending";
1508
- dataType: "string";
1509
- columnType: "PgText";
1510
- data: string;
1511
- driverParam: string;
1512
- notNull: true;
1513
- hasDefault: false;
1514
- isPrimaryKey: false;
1515
- isAutoincrement: false;
1516
- hasRuntimeDefault: false;
1517
- enumValues: [string, ...string[]];
1518
- baseColumn: never;
1519
- identity: undefined;
1520
- generated: undefined;
1521
- }, {}, {}>;
1522
- tokenEndpoint: import("drizzle-orm/pg-core").PgColumn<{
1523
- name: "tokenEndpoint";
1524
- tableName: "oauth_pending";
1525
- dataType: "string";
1526
- columnType: "PgText";
1527
- data: string;
1528
- driverParam: string;
1529
- notNull: true;
1530
- hasDefault: false;
1531
- isPrimaryKey: false;
1532
- isAutoincrement: false;
1533
- hasRuntimeDefault: false;
1534
- enumValues: [string, ...string[]];
1535
- baseColumn: never;
1536
- identity: undefined;
1537
- generated: undefined;
1538
- }, {}, {}>;
1539
- clientId: import("drizzle-orm/pg-core").PgColumn<{
1540
- name: "clientId";
1541
- tableName: "oauth_pending";
1542
- dataType: "string";
1543
- columnType: "PgText";
1544
- data: string;
1545
- driverParam: string;
1546
- notNull: true;
1547
- hasDefault: false;
1548
- isPrimaryKey: false;
1549
- isAutoincrement: false;
1550
- hasRuntimeDefault: false;
1551
- enumValues: [string, ...string[]];
1552
- baseColumn: never;
1553
- identity: undefined;
1554
- generated: undefined;
1555
- }, {}, {}>;
1556
- tokenEndpointAuth: import("drizzle-orm/pg-core").PgColumn<{
1557
- name: "tokenEndpointAuth";
1558
- tableName: "oauth_pending";
1559
- dataType: "string";
1560
- columnType: "PgText";
1561
- data: "client_secret_basic" | "client_secret_post" | "none";
1562
- driverParam: string;
1563
- notNull: true;
1564
- hasDefault: false;
1565
- isPrimaryKey: false;
1566
- isAutoincrement: false;
1567
- hasRuntimeDefault: false;
1568
- enumValues: ["none", "client_secret_basic", "client_secret_post"];
1569
- baseColumn: never;
1570
- identity: undefined;
1571
- generated: undefined;
1572
- }, {}, {}>;
1573
- resource: import("drizzle-orm/pg-core").PgColumn<{
1574
- name: "resource";
1575
- tableName: "oauth_pending";
1576
- dataType: "string";
1577
- columnType: "PgText";
1578
- data: string;
1579
- driverParam: string;
1580
- notNull: false;
1581
- hasDefault: false;
1582
- isPrimaryKey: false;
1583
- isAutoincrement: false;
1584
- hasRuntimeDefault: false;
1585
- enumValues: [string, ...string[]];
1586
- baseColumn: never;
1587
- identity: undefined;
1588
- generated: undefined;
1589
- }, {}, {}>;
1590
- kekId: import("drizzle-orm/pg-core").PgColumn<{
1591
- name: "kekId";
1592
- tableName: "oauth_pending";
1593
- dataType: "string";
1594
- columnType: "PgText";
1595
- data: string;
1596
- driverParam: string;
1597
- notNull: true;
1598
- hasDefault: false;
1599
- isPrimaryKey: false;
1600
- isAutoincrement: false;
1601
- hasRuntimeDefault: false;
1602
- enumValues: [string, ...string[]];
1603
- baseColumn: never;
1604
- identity: undefined;
1605
- generated: undefined;
1606
- }, {}, {}>;
1607
- clientSecret: import("drizzle-orm/pg-core").PgColumn<{
1608
- name: "clientSecret";
1609
- tableName: "oauth_pending";
1610
- dataType: "custom";
1611
- columnType: "PgCustomColumn";
1612
- data: Uint8Array<ArrayBufferLike>;
1613
- driverParam: Buffer<ArrayBufferLike>;
1614
- notNull: false;
1615
- hasDefault: false;
1616
- isPrimaryKey: false;
1617
- isAutoincrement: false;
1618
- hasRuntimeDefault: false;
1619
- enumValues: undefined;
1620
- baseColumn: never;
1621
- identity: undefined;
1622
- generated: undefined;
1623
- }, {}, {
1624
- pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1625
- }>;
1626
- codeVerifier: import("drizzle-orm/pg-core").PgColumn<{
1627
- name: "codeVerifier";
1628
- tableName: "oauth_pending";
1629
- dataType: "custom";
1630
- columnType: "PgCustomColumn";
1631
- data: Uint8Array<ArrayBufferLike>;
1632
- driverParam: Buffer<ArrayBufferLike>;
1633
- notNull: true;
1634
- hasDefault: false;
1635
- isPrimaryKey: false;
1636
- isAutoincrement: false;
1637
- hasRuntimeDefault: false;
1638
- enumValues: undefined;
1639
- baseColumn: never;
1640
- identity: undefined;
1641
- generated: undefined;
1642
- }, {}, {
1643
- pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1644
- }>;
1645
- redirectUri: import("drizzle-orm/pg-core").PgColumn<{
1646
- name: "redirectUri";
1647
- tableName: "oauth_pending";
1648
- dataType: "string";
1649
- columnType: "PgText";
1650
- data: string;
1651
- driverParam: string;
1652
- notNull: true;
1653
- hasDefault: false;
1654
- isPrimaryKey: false;
1655
- isAutoincrement: false;
1656
- hasRuntimeDefault: false;
1657
- enumValues: [string, ...string[]];
1658
- baseColumn: never;
1659
- identity: undefined;
1660
- generated: undefined;
1661
- }, {}, {}>;
1662
- expiresAt: import("drizzle-orm/pg-core").PgColumn<{
1663
- name: "expiresAt";
1664
- tableName: "oauth_pending";
1665
- dataType: "custom";
1666
- columnType: "PgCustomColumn";
1667
- data: string;
1668
- driverParam: string;
1669
- notNull: true;
1670
- hasDefault: false;
1671
- isPrimaryKey: false;
1672
- isAutoincrement: false;
1673
- hasRuntimeDefault: false;
1674
- enumValues: undefined;
1675
- baseColumn: never;
1676
- identity: undefined;
1677
- generated: undefined;
1678
- }, {}, {
1679
- pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1680
- }>;
1681
- createdAt: import("drizzle-orm/pg-core").PgColumn<{
1682
- name: "createdAt";
1683
- tableName: "oauth_pending";
1684
- dataType: "custom";
1685
- columnType: "PgCustomColumn";
1686
- data: string;
1687
- driverParam: string;
1688
- notNull: true;
1689
- hasDefault: false;
1690
- isPrimaryKey: false;
1691
- isAutoincrement: false;
1692
- hasRuntimeDefault: false;
1693
- enumValues: undefined;
1694
- baseColumn: never;
1695
- identity: undefined;
1696
- generated: undefined;
1697
- }, {}, {
1698
- pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1699
- }>;
1700
- };
1701
- dialect: 'pg';
1702
- }>;
1703
1445
  export declare const tenantSettings: import("drizzle-orm/pg-core").PgTableWithColumns<{
1704
1446
  name: "tenant_settings";
1705
1447
  schema: "nylorun";
@@ -3598,6 +3340,109 @@ export declare const controlSignals: import("drizzle-orm/pg-core").PgTableWithCo
3598
3340
  };
3599
3341
  dialect: 'pg';
3600
3342
  }>;
3343
+ /**
3344
+ * The wake outbox (architecture §12.3): one row per wake a committed transaction asked for
3345
+ * (`Tx.wake`), written in that transaction, so a commit never loses its wake. The row is
3346
+ * deleted once Durable Session Execution has accepted the wake; until then the Tenant sweep
3347
+ * delivers it again, with the same idempotency key (`dedupe_key`, or `wake:<id>` without
3348
+ * one). `created_at` is the insert's own clock (`clock_timestamp()`).
3349
+ */
3350
+ export declare const wakes: import("drizzle-orm/pg-core").PgTableWithColumns<{
3351
+ name: "wakes";
3352
+ schema: "nylorun";
3353
+ columns: {
3354
+ id: import("drizzle-orm/pg-core").PgColumn<{
3355
+ name: "id";
3356
+ tableName: "wakes";
3357
+ dataType: "custom";
3358
+ columnType: "PgCustomColumn";
3359
+ data: string;
3360
+ driverParam: string;
3361
+ notNull: true;
3362
+ hasDefault: false;
3363
+ isPrimaryKey: true;
3364
+ isAutoincrement: false;
3365
+ hasRuntimeDefault: false;
3366
+ enumValues: undefined;
3367
+ baseColumn: never;
3368
+ identity: undefined;
3369
+ generated: undefined;
3370
+ }, {}, {
3371
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
3372
+ }>;
3373
+ sessionId: import("drizzle-orm/pg-core").PgColumn<{
3374
+ name: "sessionId";
3375
+ tableName: "wakes";
3376
+ dataType: "custom";
3377
+ columnType: "PgCustomColumn";
3378
+ data: string;
3379
+ driverParam: string;
3380
+ notNull: true;
3381
+ hasDefault: false;
3382
+ isPrimaryKey: false;
3383
+ isAutoincrement: false;
3384
+ hasRuntimeDefault: false;
3385
+ enumValues: undefined;
3386
+ baseColumn: never;
3387
+ identity: undefined;
3388
+ generated: undefined;
3389
+ }, {}, {
3390
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
3391
+ }>;
3392
+ reason: import("drizzle-orm/pg-core").PgColumn<{
3393
+ name: "reason";
3394
+ tableName: "wakes";
3395
+ dataType: "string";
3396
+ columnType: "PgText";
3397
+ data: string;
3398
+ driverParam: string;
3399
+ notNull: true;
3400
+ hasDefault: false;
3401
+ isPrimaryKey: false;
3402
+ isAutoincrement: false;
3403
+ hasRuntimeDefault: false;
3404
+ enumValues: [string, ...string[]];
3405
+ baseColumn: never;
3406
+ identity: undefined;
3407
+ generated: undefined;
3408
+ }, {}, {}>;
3409
+ dedupeKey: import("drizzle-orm/pg-core").PgColumn<{
3410
+ name: "dedupeKey";
3411
+ tableName: "wakes";
3412
+ dataType: "string";
3413
+ columnType: "PgText";
3414
+ data: string;
3415
+ driverParam: string;
3416
+ notNull: false;
3417
+ hasDefault: false;
3418
+ isPrimaryKey: false;
3419
+ isAutoincrement: false;
3420
+ hasRuntimeDefault: false;
3421
+ enumValues: [string, ...string[]];
3422
+ baseColumn: never;
3423
+ identity: undefined;
3424
+ generated: undefined;
3425
+ }, {}, {}>;
3426
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
3427
+ name: "createdAt";
3428
+ tableName: "wakes";
3429
+ dataType: "date";
3430
+ columnType: "PgTimestamp";
3431
+ data: Date;
3432
+ driverParam: string;
3433
+ notNull: true;
3434
+ hasDefault: true;
3435
+ isPrimaryKey: false;
3436
+ isAutoincrement: false;
3437
+ hasRuntimeDefault: false;
3438
+ enumValues: undefined;
3439
+ baseColumn: never;
3440
+ identity: undefined;
3441
+ generated: undefined;
3442
+ }, {}, {}>;
3443
+ };
3444
+ dialect: 'pg';
3445
+ }>;
3601
3446
  /**
3602
3447
  * Each sandbox's lifecycle stream (`nylorun.sandbox-event/1`), numbered from 0 per sandbox id.
3603
3448
  * Written only by the record module; not published to the stream relay.
@@ -3924,7 +3769,6 @@ export type VaultRow = typeof vaults.$inferSelect;
3924
3769
  export type VaultCredentialRow = typeof vaultCredentials.$inferSelect;
3925
3770
  export type VaultAuditRow = Omit<typeof vaultAudit.$inferSelect, "ord">;
3926
3771
  export type VaultIdempotencyRow = typeof vaultIdempotency.$inferSelect;
3927
- export type OAuthPendingRow = typeof oauthPending.$inferSelect;
3928
3772
  export type SigningKeyRow = typeof signingKeys.$inferSelect;
3929
3773
  export type ModelUsageRow = typeof modelUsage.$inferSelect;
3930
3774
  export type ModelBudgetRow = typeof modelBudgets.$inferSelect;
@@ -129,8 +129,8 @@ export const vaultCredentials = nylorun.table("vault_credentials", {
129
129
  id: textC().primaryKey(),
130
130
  vaultId: text().notNull(),
131
131
  name: text().notNull(),
132
- type: text({ enum: ["bearer", "oauth", "model"] }).notNull(),
133
- /** JSON text of the non-secret binding (url, or provider and model). */
132
+ type: text({ enum: ["bearer", "headers", "model"] }).notNull(),
133
+ /** JSON text of the non-secret binding: url (and via, identity, header names), or provider and model. */
134
134
  bindingJson: text().notNull(),
135
135
  expiresAt: text(),
136
136
  createdAt: textC().notNull(),
@@ -165,40 +165,6 @@ export const vaultIdempotency = nylorun.table("vault_idempotency", {
165
165
  /** JSON text of the original response. */
166
166
  response: text().notNull(),
167
167
  });
168
- /**
169
- * An MCP OAuth connect between its start and its callback (F9 C2): found by the SHA-256 of its
170
- * `state`, used once, for ten minutes. The PKCE verifier and any client secret are sealed under
171
- * the vault key (`sealBytes`), which only the keys module holds.
172
- */
173
- export const oauthPending = nylorun.table("oauth_pending", {
174
- stateHash: text().primaryKey(),
175
- vaultId: text().notNull(),
176
- /** The MCP server's name in the agent, the credential's name. */
177
- server: text().notNull(),
178
- /** The MCP server's URL (normalized): the credential's binding. */
179
- url: text().notNull(),
180
- tokenEndpoint: text().notNull(),
181
- clientId: text().notNull(),
182
- /** How the client authenticates at the token endpoint: `none`, `client_secret_basic` or `client_secret_post`. */
183
- tokenEndpointAuth: text({ enum: ["none", "client_secret_basic", "client_secret_post"] }).notNull(),
184
- /** The RFC 8707 resource indicator sent with the authorization request, if any. */
185
- resource: text(),
186
- kekId: text().notNull(),
187
- /** Sealed; null for a public client. */
188
- clientSecret: bytes(),
189
- /** Sealed. */
190
- codeVerifier: bytes().notNull(),
191
- redirectUri: text().notNull(),
192
- expiresAt: textC().notNull(),
193
- createdAt: textC().notNull(),
194
- }, (t) => [
195
- foreignKey({
196
- name: "oauth_pending_vault_id_fkey",
197
- columns: [t.vaultId],
198
- foreignColumns: [vaults.id],
199
- }).onDelete("cascade"),
200
- index("oauth_pending_expires").on(t.expiresAt),
201
- ]);
202
168
  export const tenantSettings = nylorun.table("tenant_settings", {
203
169
  key: text().primaryKey(),
204
170
  value: text().notNull(),
@@ -455,6 +421,22 @@ export const controlSignals = nylorun.table("control_signals", {
455
421
  index("control_signals_created_at").on(t.createdAt),
456
422
  check("control_signals_kind_check", sql `${t.kind} IN ('session.cancel', 'sessions.reset')`),
457
423
  ]);
424
+ /**
425
+ * The wake outbox (architecture §12.3): one row per wake a committed transaction asked for
426
+ * (`Tx.wake`), written in that transaction, so a commit never loses its wake. The row is
427
+ * deleted once Durable Session Execution has accepted the wake; until then the Tenant sweep
428
+ * delivers it again, with the same idempotency key (`dedupe_key`, or `wake:<id>` without
429
+ * one). `created_at` is the insert's own clock (`clock_timestamp()`).
430
+ */
431
+ export const wakes = nylorun.table("wakes", {
432
+ id: textC().primaryKey(),
433
+ sessionId: textC().notNull(),
434
+ reason: text().notNull(),
435
+ dedupeKey: text(),
436
+ createdAt: timestamp({ withTimezone: true, mode: "date" })
437
+ .notNull()
438
+ .default(sql `clock_timestamp()`),
439
+ }, (t) => [index("wakes_created_at").on(t.createdAt)]);
458
440
  // ---------------------------------------------------------------------------
459
441
  // nylorun_streams: the record
460
442
  /**
@@ -49,6 +49,7 @@
49
49
  * `timestamptz`.
50
50
  */
51
51
  import { AsyncLocalStorage } from "node:async_hooks";
52
+ import { randomUUID } from "node:crypto";
52
53
  import { and, count, eq, getTableColumns, gte, inArray, isNotNull, isNull, lt, lte, ne, or, sql, } from "drizzle-orm";
53
54
  import { appendEvent, appendSandboxEvent } from "../../record/index.js";
54
55
  import { OwnershipLostError, PrincipalRoleConflict } from "../ownership.js";
@@ -57,7 +58,7 @@ import { database, driverError } from "./db.js";
57
58
  import { expectedSchemaVersion, readSchemaVersion } from "./migrate.js";
58
59
  import { createPostgresRecordReader } from "./record.js";
59
60
  import { postgresRecordWriter, postgresSandboxRecordWriter } from "./record-writer.js";
60
- import { artifacts, artifactVersions, artifactContent, definitionFiles, definitionFileUses, commands, definitions, effects, links, modelBudgets, modelUsage, principals, sandboxes, sandboxEvents, sandboxResources, sessionEvents, sessionLogHeads, sessions, toJson, toolCrossings, signingKeys, tenant, tenantSettings, vaultAudit, vaultCredentials, vaultIdempotency, vaults, oauthPending, } from "./schema.js";
61
+ import { artifacts, artifactVersions, artifactContent, definitionFiles, definitionFileUses, commands, definitions, effects, links, modelBudgets, modelUsage, principals, sandboxes, sandboxEvents, sandboxResources, sessionEvents, sessionLogHeads, sessions, toJson, toolCrossings, signingKeys, tenant, tenantSettings, vaultAudit, vaultCredentials, vaultIdempotency, vaults, wakes, } from "./schema.js";
61
62
  export function createPostgresSessionStore(options) {
62
63
  return new PostgresSessionStore(options);
63
64
  }
@@ -110,7 +111,7 @@ class PostgresSessionStore {
110
111
  let t;
111
112
  // Wrapped so `begin` does not treat an array result as queries to await.
112
113
  const run = this.db.transaction(async (db) => {
113
- t = new PostgresTx(db, this.tenantId, this.now);
114
+ t = new PostgresTx(db, this.tenantId, this.now, this.deleteWake);
114
115
  try {
115
116
  return { value: await this.active.run(this, () => fn(t)) };
116
117
  }
@@ -153,6 +154,10 @@ class PostgresSessionStore {
153
154
  }
154
155
  return result.value;
155
156
  }
157
+ /** Deletes a delivered outbox request, outside any transaction (`Tx.wake`). */
158
+ deleteWake = async (id) => {
159
+ await this.db.delete(wakes).where(eq(wakes.id, id));
160
+ };
156
161
  onCommit(listener) {
157
162
  this.listeners.add(listener);
158
163
  return () => this.listeners.delete(listener);
@@ -256,21 +261,27 @@ const SIGNING_KEY_STAMP = {
256
261
  };
257
262
  /** The audit columns a row has (`ord` only orders them). */
258
263
  const { ord: _ord, ...AUDIT } = getTableColumns(vaultAudit);
259
- const SESSION_TABLES = [sessions, commands, effects, links];
264
+ const SESSION_TABLES = [sessions, commands, effects, links, wakes];
265
+ /** An outbox row as it is delivered: the wake's own dedupe key, or one made from the row id. */
266
+ function outboxWake(id, reason, dedupeKey) {
267
+ return { reason, dedupeKey: dedupeKey ?? `wake:${id}` };
268
+ }
260
269
  /** The inserted row's `column` (`ON CONFLICT … DO UPDATE`). */
261
270
  const excluded = (column) => sql.raw(`excluded.${column}`);
262
271
  class PostgresTx {
263
272
  db;
264
273
  tenantId;
265
274
  now;
275
+ deleteWake;
266
276
  closed = false;
267
277
  events = [];
268
278
  generations = [];
269
279
  callbacks = [];
270
- constructor(db, tenantId, now) {
280
+ constructor(db, tenantId, now, deleteWake) {
271
281
  this.db = db;
272
282
  this.tenantId = tenantId;
273
283
  this.now = now;
284
+ this.deleteWake = deleteWake;
274
285
  }
275
286
  check() {
276
287
  if (this.closed)
@@ -345,6 +356,45 @@ class PostgresTx {
345
356
  this.check();
346
357
  this.callbacks.push(fn);
347
358
  }
359
+ // --- wake outbox ---------------------------------------------------------
360
+ async wake(sessionId, wake, deliver) {
361
+ this.check();
362
+ const id = randomUUID();
363
+ await this.db.insert(wakes).values({
364
+ id,
365
+ sessionId,
366
+ reason: wake.reason,
367
+ dedupeKey: wake.dedupeKey ?? null,
368
+ });
369
+ const delivered = outboxWake(id, wake.reason, wake.dedupeKey ?? null);
370
+ // A rejection goes to the store's error hook (as every callback's), and the row stays.
371
+ this.callbacks.push(async () => {
372
+ if ((await deliver(sessionId, delivered)) === false)
373
+ return;
374
+ await this.deleteWake(id);
375
+ });
376
+ }
377
+ async pendingWakes(before, limit) {
378
+ this.check();
379
+ const rows = await this.db
380
+ .select()
381
+ .from(wakes)
382
+ .where(lt(wakes.createdAt, before))
383
+ .orderBy(wakes.createdAt, wakes.id)
384
+ .limit(limit);
385
+ return rows.map((row) => ({
386
+ id: row.id,
387
+ sessionId: row.sessionId,
388
+ wake: outboxWake(row.id, row.reason, row.dedupeKey),
389
+ createdAt: row.createdAt.toISOString(),
390
+ }));
391
+ }
392
+ async deleteWakes(ids) {
393
+ this.check();
394
+ if (ids.length === 0)
395
+ return;
396
+ await this.db.delete(wakes).where(inArray(wakes.id, [...ids]));
397
+ }
348
398
  // --- ownership -----------------------------------------------------------
349
399
  async takeOwnership(sessionId, claim) {
350
400
  this.check();
@@ -765,23 +815,6 @@ class PostgresTx {
765
815
  this.check();
766
816
  await this.db.insert(vaultIdempotency).values(row);
767
817
  }
768
- async insertOAuthPending(row) {
769
- this.check();
770
- await this.db.insert(oauthPending).values(row);
771
- }
772
- async takeOAuthPending(stateHash) {
773
- this.check();
774
- const [row] = await this.db
775
- .delete(oauthPending)
776
- .where(eq(oauthPending.stateHash, stateHash))
777
- .returning();
778
- return row;
779
- }
780
- async deleteExpiredOAuthPending(now) {
781
- this.check();
782
- const result = await this.db.delete(oauthPending).where(lt(oauthPending.expiresAt, now));
783
- return result.count;
784
- }
785
818
  // --- signing keys --------------------------------------------------------
786
819
  async lockSigningKeys() {
787
820
  this.check();
@@ -11,6 +11,8 @@
11
11
  * tables of a pre-release build of it. It is never touched: this release starts fresh.
12
12
  * 2. Apply the missing migrations (`migrate.ts`). A database with migrations this Runtime
13
13
  * does not know is `schema-too-new`; a migration Postgres rejects is `migration-failed`.
14
+ * `0016_mcp_oauth_removed` deletes the vault's MCP OAuth credentials (protocol 10), each
15
+ * with an audit row; the open returns them (`removedCredentials`) for a boot warning.
14
16
  * 3. When `nylorun.tenant` is empty, create the Tenant: its row (id, name, created time) and
15
17
  * its first principals. A database whose row is gone but that holds a Tenant's data
16
18
  * (principals, sessions, agents, vaults, keys) is `envelope-invalid`, never given a new
@@ -74,6 +76,18 @@ export interface OpenedTenantDatabase {
74
76
  from: number;
75
77
  to: number;
76
78
  };
79
+ /**
80
+ * The MCP OAuth credentials this call's migration `0016_mcp_oauth_removed` deleted (protocol
81
+ * 10), from the audit rows it wrote: the Host names each in a boot warning. Empty otherwise.
82
+ */
83
+ removedCredentials: RemovedCredential[];
84
+ }
85
+ /** A vault credential a migration deleted. */
86
+ export interface RemovedCredential {
87
+ vaultId: string;
88
+ credentialId: string;
89
+ /** The URL it was bound to. */
90
+ url: string | null;
77
91
  }
78
92
  /**
79
93
  * Migrates the database, creates its Tenant when it has none, and opens its Session Store.
@@ -4,8 +4,10 @@ import { TenantEnvelopeSchema, } from "@nylorun/core/contracts";
4
4
  import { openError, TenantOpenError } from "../../tenant/cause.js";
5
5
  import { database, driverError } from "./db.js";
6
6
  import { applyMigrations, assertCurrentLayout, isStatementError, lockMigrations, shippedMigrations, } from "./migrate.js";
7
- import { definitions, principals, sessions, signingKeys, TENANT_SCHEMA, tenant, vaults, } from "./schema.js";
7
+ import { definitions, principals, sessions, signingKeys, TENANT_SCHEMA, tenant, vaultAudit, vaults, } from "./schema.js";
8
8
  import { createPostgresSessionStore } from "./store.js";
9
+ /** The migration that deletes the vault's `oauth` credentials, each with an audit row. */
10
+ const OAUTH_REMOVED_MIGRATION = "0016_mcp_oauth_removed";
9
11
  /**
10
12
  * Migrates the database, creates its Tenant when it has none, and opens its Session Store.
11
13
  * Throws a `TenantOpenError` with the cause when the Tenant cannot be opened (see above).
@@ -22,7 +24,8 @@ export async function openTenantDatabase(options) {
22
24
  await assertCurrentLayout(tx);
23
25
  const migrated = await applyMigrations(tx, migrations);
24
26
  const created = await ensureTenant(tx, options.create, migrated, now);
25
- return { created, migrated };
27
+ const removedCredentials = await removedBy(tx, migrations, migrated);
28
+ return { created, migrated, removedCredentials };
26
29
  });
27
30
  }
28
31
  catch (thrown) {
@@ -82,6 +85,23 @@ export async function readTenantEnvelope(q) {
82
85
  throw driverError(error);
83
86
  }
84
87
  }
88
+ /**
89
+ * The credentials `0016_mcp_oauth_removed` deleted, when this transaction applied it: its
90
+ * audit rows (actor `migration`). Empty when it ran before, or is not among `migrations`.
91
+ */
92
+ async function removedBy(tx, migrations, migrated) {
93
+ const index = migrations.findIndex((migration) => migration.tag === OAUTH_REMOVED_MIGRATION);
94
+ if (index < migrated.from || index >= migrated.to)
95
+ return [];
96
+ const rows = await tx
97
+ .select({ vaultId: vaultAudit.vaultId, credentialId: vaultAudit.credentialId, url: vaultAudit.target })
98
+ .from(vaultAudit)
99
+ .where(and(eq(vaultAudit.actor, "migration"), eq(vaultAudit.action, "delete")))
100
+ .orderBy(vaultAudit.ord);
101
+ return rows.flatMap((row) => row.vaultId && row.credentialId
102
+ ? [{ vaultId: row.vaultId, credentialId: row.credentialId, url: row.url }]
103
+ : []);
104
+ }
85
105
  /**
86
106
  * Creates the Tenant when there is none, and adds the principals it is missing. Records the
87
107
  * schema version on the Tenant row when this transaction migrated.