@nylorun/runtime 0.24.0-beta → 0.26.0-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 (171) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/README.md +18 -4
  3. package/dist/api/a2a/card.d.ts +11 -1
  4. package/dist/api/a2a/card.js +26 -2
  5. package/dist/api/a2a/endpoint.js +4 -2
  6. package/dist/api/a2a/protocol.d.ts +25 -2
  7. package/dist/api/a2a/protocol.js +20 -3
  8. package/dist/api/a2a/routes.d.ts +4 -1
  9. package/dist/api/a2a/routes.js +65 -7
  10. package/dist/api/ag-ui/endpoint.js +5 -2
  11. package/dist/api/ag-ui/history.d.ts +6 -1
  12. package/dist/api/ag-ui/history.js +35 -2
  13. package/dist/api/ag-ui/routes.js +53 -11
  14. package/dist/api/components.d.ts +1165 -678
  15. package/dist/api/components.js +16 -1
  16. package/dist/api/gate/keys.d.ts +2 -1
  17. package/dist/api/gate/keys.js +7 -0
  18. package/dist/api/gate/routes.js +13 -0
  19. package/dist/api/http/app.d.ts +2 -2
  20. package/dist/api/http/app.js +15 -4
  21. package/dist/api/http/body.d.ts +3 -3
  22. package/dist/api/http/body.js +7 -7
  23. package/dist/api/http/define.d.ts +1 -1
  24. package/dist/api/http/respond.js +1 -1
  25. package/dist/api/http/routes/environment-manifests.d.ts +9 -0
  26. package/dist/api/http/routes/environment-manifests.js +118 -0
  27. package/dist/api/http/routes/environments.js +61 -12
  28. package/dist/api/http/routes/reads.d.ts +52 -3
  29. package/dist/api/http/routes/sessions.js +14 -4
  30. package/dist/api/http/routes/tenant.js +35 -7
  31. package/dist/api/inbound-files.d.ts +65 -0
  32. package/dist/api/inbound-files.js +115 -0
  33. package/dist/api/openapi.js +18 -3
  34. package/dist/artifacts/disk.js +20 -14
  35. package/dist/artifacts/media-types.d.ts +15 -0
  36. package/dist/artifacts/media-types.js +56 -4
  37. package/dist/artifacts/parts.d.ts +12 -3
  38. package/dist/artifacts/parts.js +15 -4
  39. package/dist/artifacts/workspace.d.ts +4 -4
  40. package/dist/artifacts/workspace.js +6 -18
  41. package/dist/core/flow-host.d.ts +1 -1
  42. package/dist/core/flow-host.js +2 -2
  43. package/dist/core/sandbox-routes.js +2 -2
  44. package/dist/core/turn-manifest.d.ts +3 -2
  45. package/dist/core/turn-manifest.js +29 -5
  46. package/dist/gates/egress-credentials.js +1 -1
  47. package/dist/gates/egress.d.ts +13 -19
  48. package/dist/gates/egress.js +32 -29
  49. package/dist/gates/http-tool.d.ts +3 -4
  50. package/dist/gates/http-tool.js +23 -19
  51. package/dist/gates/journal.js +0 -1
  52. package/dist/gates/mcp-handler.js +2 -3
  53. package/dist/gates/policy.js +1 -3
  54. package/dist/gates/tool-contract.d.ts +1 -1
  55. package/dist/harness/calls.d.ts +2 -2
  56. package/dist/harness/calls.js +9 -12
  57. package/dist/harness/main.js +7 -1
  58. package/dist/harness/pod.d.ts +6 -1
  59. package/dist/harness/pod.js +5 -0
  60. package/dist/harness/service.js +5 -5
  61. package/dist/harness/setup.d.ts +2 -0
  62. package/dist/harness/setup.js +25 -1
  63. package/dist/harness-api/record.d.ts +1 -1
  64. package/dist/harness-api/record.js +13 -13
  65. package/dist/harness-api/server.d.ts +27 -4
  66. package/dist/harness-api/server.js +41 -8
  67. package/dist/harness-api/tool-journal.d.ts +17 -8
  68. package/dist/harness-api/tool-journal.js +41 -11
  69. package/dist/harness-api/workspace.d.ts +7 -5
  70. package/dist/harness-api/workspace.js +25 -19
  71. package/dist/host/gates.js +2 -2
  72. package/dist/host/stack-config.js +1 -1
  73. package/dist/keys/contract.d.ts +1 -1
  74. package/dist/keys/contract.js +1 -1
  75. package/dist/keys/keys.d.ts +9 -2
  76. package/dist/keys/keys.js +4 -1
  77. package/dist/management-openapi.json +366 -61
  78. package/dist/mcp/connect.d.ts +1 -2
  79. package/dist/mcp/connect.js +1 -4
  80. package/dist/mcp/pool.d.ts +1 -4
  81. package/dist/mcp/pool.js +13 -27
  82. package/dist/mcp/preview.d.ts +1 -1
  83. package/dist/mcp/preview.js +1 -2
  84. package/dist/mcp/snapshot.d.ts +11 -13
  85. package/dist/mcp/snapshot.js +27 -38
  86. package/dist/model/capabilities.d.ts +26 -0
  87. package/dist/model/capabilities.js +44 -0
  88. package/dist/model/catalog.d.ts +34 -3
  89. package/dist/model/catalog.js +38 -2
  90. package/dist/model/models.d.ts +2 -0
  91. package/dist/model/models.js +1 -1
  92. package/dist/model/native-blocks.d.ts +35 -0
  93. package/dist/model/native-blocks.js +170 -0
  94. package/dist/model/pi-model.js +106 -94
  95. package/dist/openapi.json +9554 -5076
  96. package/dist/record/environment.js +1 -1
  97. package/dist/sandbox/join.js +4 -0
  98. package/dist/sandbox/manager.d.ts +32 -14
  99. package/dist/sandbox/manager.js +132 -93
  100. package/dist/sandbox/placement.d.ts +1 -1
  101. package/dist/sandbox/placement.js +2 -2
  102. package/dist/sandbox/pods/client.d.ts +3 -1
  103. package/dist/sandbox/pods/lifecycle.d.ts +8 -0
  104. package/dist/sandbox/pods/lifecycle.js +15 -0
  105. package/dist/sandbox/pods/spec.js +2 -1
  106. package/dist/sandbox/resolve.d.ts +5 -3
  107. package/dist/sandbox/resolve.js +9 -2
  108. package/dist/sandbox/runtime-tools.d.ts +9 -0
  109. package/dist/sandbox/runtime-tools.js +16 -0
  110. package/dist/sandbox/session-sandbox.d.ts +4 -6
  111. package/dist/sandbox/session-sandbox.js +47 -94
  112. package/dist/sandbox/setup.d.ts +4 -1
  113. package/dist/sandbox/setup.js +46 -9
  114. package/dist/sandbox/share.d.ts +12 -7
  115. package/dist/sandbox/share.js +27 -25
  116. package/dist/sandbox/skills.d.ts +8 -6
  117. package/dist/sandbox/skills.js +24 -23
  118. package/dist/store/postgres/drizzle/0024_flat_manifest.sql +7 -0
  119. package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
  120. package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
  121. package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
  122. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  123. package/dist/store/postgres/migrate.js +1 -1
  124. package/dist/store/postgres/reads.js +8 -2
  125. package/dist/store/postgres/schema.d.ts +217 -0
  126. package/dist/store/postgres/schema.js +36 -2
  127. package/dist/store/postgres/store.js +79 -4
  128. package/dist/store/types.d.ts +68 -14
  129. package/dist/tenant/advance.js +3 -0
  130. package/dist/tenant/artifact-tool.d.ts +1 -1
  131. package/dist/tenant/artifact-tool.js +43 -22
  132. package/dist/tenant/commands.js +24 -4
  133. package/dist/tenant/context.d.ts +10 -4
  134. package/dist/tenant/effects.js +7 -9
  135. package/dist/tenant/environment-manifests.d.ts +57 -0
  136. package/dist/tenant/environment-manifests.js +200 -0
  137. package/dist/tenant/environments.d.ts +54 -47
  138. package/dist/tenant/environments.js +309 -85
  139. package/dist/tenant/ephemeral.d.ts +1 -1
  140. package/dist/tenant/host-token.js +1 -1
  141. package/dist/tenant/identity-file.d.ts +1 -1
  142. package/dist/tenant/identity-file.js +1 -1
  143. package/dist/tenant/model-setting.d.ts +14 -0
  144. package/dist/tenant/model-setting.js +51 -0
  145. package/dist/tenant/outbound.d.ts +6 -5
  146. package/dist/tenant/outbound.js +8 -6
  147. package/dist/tenant/sessions.d.ts +5 -1
  148. package/dist/tenant/sessions.js +45 -12
  149. package/dist/tenant/skill-tool.d.ts +3 -3
  150. package/dist/tenant/skill-tool.js +6 -11
  151. package/dist/tenant/sweep.js +1 -1
  152. package/dist/tenant/tool-results.js +1 -1
  153. package/dist/tenant/tool-search.d.ts +7 -11
  154. package/dist/tenant/tool-search.js +8 -5
  155. package/dist/tenant/transcript.d.ts +51 -32
  156. package/dist/tenant/transcript.js +2 -1
  157. package/dist/tenant/types.d.ts +2 -2
  158. package/dist/traces/ids.d.ts +0 -1
  159. package/dist/traces/ids.js +0 -4
  160. package/dist/traces/map.d.ts +2 -2
  161. package/dist/traces/map.js +1 -29
  162. package/dist/vault/coverage.js +11 -13
  163. package/dist/vault/host-model.d.ts +4 -1
  164. package/dist/vault/host-model.js +10 -2
  165. package/dist/vault/service.d.ts +49 -4
  166. package/dist/vault/service.js +153 -6
  167. package/dist/version.d.ts +1 -1
  168. package/dist/version.js +1 -1
  169. package/package.json +3 -3
  170. package/dist/sandbox/capability.d.ts +0 -3
  171. package/dist/sandbox/capability.js +0 -10
@@ -169,6 +169,34 @@
169
169
  "when": 1791730977999,
170
170
  "tag": "0023_artifact_pages",
171
171
  "breakpoints": true
172
+ },
173
+ {
174
+ "idx": 24,
175
+ "version": "7",
176
+ "when": 1791986741535,
177
+ "tag": "0024_flat_manifest",
178
+ "breakpoints": true
179
+ },
180
+ {
181
+ "idx": 25,
182
+ "version": "7",
183
+ "when": 1791993934444,
184
+ "tag": "0025_environment_manifests",
185
+ "breakpoints": true
186
+ },
187
+ {
188
+ "idx": 26,
189
+ "version": "7",
190
+ "when": 1791995791323,
191
+ "tag": "0026_environment_forks",
192
+ "breakpoints": true
193
+ },
194
+ {
195
+ "idx": 27,
196
+ "version": "7",
197
+ "when": 1791997067694,
198
+ "tag": "0027_environment_image_digest",
199
+ "breakpoints": true
172
200
  }
173
201
  ]
174
202
  }
@@ -96,7 +96,7 @@ export async function assertCurrentLayout(q) {
96
96
  if (row?.versioned)
97
97
  throw openError("database-layout-old", "The database was created by a pre-release build of one Tenant per database, whose " +
98
98
  "migrations this release replaces. It is never changed: point the Runtime at a new " +
99
- "database (locally, `nylorun reset` and then `nylorun start`).");
99
+ "database (locally, `nylorun local reset` and then `nylorun start`).");
100
100
  }
101
101
  /**
102
102
  * The database's schema version: the number of migrations its journal records, or undefined
@@ -61,10 +61,11 @@ function publicToolCall(body) {
61
61
  effectId: request.effectId,
62
62
  turnId: request.turnId,
63
63
  callId: typeof request.context?.callId === "string" ? request.context.callId : null,
64
- capabilityId: request.capabilityId ?? null,
65
64
  toolName: request.toolName ?? null,
66
65
  kind: body.toolKind ?? null,
67
66
  server: body.server ?? null,
67
+ skill: body.skill ?? null,
68
+ plugin: body.plugin ?? null,
68
69
  status,
69
70
  // A row written before R5 counted no sends: it was sent once.
70
71
  attempts: body.attempts ?? 1,
@@ -332,7 +333,11 @@ export function createPostgresReadStore(source, tenantId) {
332
333
  id: row.id,
333
334
  kind: row.kind,
334
335
  labels: row.labels,
335
- recipe: row.recipe,
336
+ manifest: row.recipe,
337
+ ...(row.manifestName === null ? {} : { manifestName: row.manifestName }),
338
+ ...(row.manifestVersion === null ? {} : { manifestVersion: row.manifestVersion }),
339
+ ...(row.manifestHash === null ? {} : { manifestHash: row.manifestHash }),
340
+ ...(row.forkedFrom ? { forkedFrom: row.forkedFrom } : {}),
336
341
  version: row.version,
337
342
  appliedVersion: row.appliedVersion,
338
343
  ownerUserId: row.ownerUserId,
@@ -353,6 +358,7 @@ export function createPostgresReadStore(source, tenantId) {
353
358
  volumeGeneration: row.volumeGen,
354
359
  hostEpoch: row.hostEpoch,
355
360
  ...(row.reason ? { reason: row.reason } : {}),
361
+ ...(row.imageDigest ? { imageDigest: row.imageDigest } : {}),
356
362
  },
357
363
  }
358
364
  : {}),
@@ -1307,6 +1307,25 @@ export declare const environments: import("drizzle-orm/pg-core").PgTableWithColu
1307
1307
  identity: undefined;
1308
1308
  generated: undefined;
1309
1309
  }, {}, {}>;
1310
+ imageDigest: import("drizzle-orm/pg-core").PgColumn<{
1311
+ name: "imageDigest";
1312
+ tableName: "environments";
1313
+ dataType: "custom";
1314
+ columnType: "PgCustomColumn";
1315
+ data: string;
1316
+ driverParam: string;
1317
+ notNull: false;
1318
+ hasDefault: false;
1319
+ isPrimaryKey: false;
1320
+ isAutoincrement: false;
1321
+ hasRuntimeDefault: false;
1322
+ enumValues: undefined;
1323
+ baseColumn: never;
1324
+ identity: undefined;
1325
+ generated: undefined;
1326
+ }, {}, {
1327
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1328
+ }>;
1310
1329
  diskOp: import("drizzle-orm/pg-core").PgColumn<{
1311
1330
  name: "diskOp";
1312
1331
  tableName: "environments";
@@ -1345,6 +1364,204 @@ export declare const environments: import("drizzle-orm/pg-core").PgTableWithColu
1345
1364
  }, {}, {
1346
1365
  pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1347
1366
  }>;
1367
+ manifestName: import("drizzle-orm/pg-core").PgColumn<{
1368
+ name: "manifestName";
1369
+ tableName: "environments";
1370
+ dataType: "custom";
1371
+ columnType: "PgCustomColumn";
1372
+ data: string;
1373
+ driverParam: string;
1374
+ notNull: false;
1375
+ hasDefault: false;
1376
+ isPrimaryKey: false;
1377
+ isAutoincrement: false;
1378
+ hasRuntimeDefault: false;
1379
+ enumValues: undefined;
1380
+ baseColumn: never;
1381
+ identity: undefined;
1382
+ generated: undefined;
1383
+ }, {}, {
1384
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1385
+ }>;
1386
+ manifestVersion: import("drizzle-orm/pg-core").PgColumn<{
1387
+ name: "manifestVersion";
1388
+ tableName: "environments";
1389
+ dataType: "number";
1390
+ columnType: "PgInteger";
1391
+ data: number;
1392
+ driverParam: string | number;
1393
+ notNull: false;
1394
+ hasDefault: false;
1395
+ isPrimaryKey: false;
1396
+ isAutoincrement: false;
1397
+ hasRuntimeDefault: false;
1398
+ enumValues: undefined;
1399
+ baseColumn: never;
1400
+ identity: undefined;
1401
+ generated: undefined;
1402
+ }, {}, {}>;
1403
+ manifestHash: import("drizzle-orm/pg-core").PgColumn<{
1404
+ name: "manifestHash";
1405
+ tableName: "environments";
1406
+ dataType: "custom";
1407
+ columnType: "PgCustomColumn";
1408
+ data: string;
1409
+ driverParam: string;
1410
+ notNull: false;
1411
+ hasDefault: false;
1412
+ isPrimaryKey: false;
1413
+ isAutoincrement: false;
1414
+ hasRuntimeDefault: false;
1415
+ enumValues: undefined;
1416
+ baseColumn: never;
1417
+ identity: undefined;
1418
+ generated: undefined;
1419
+ }, {}, {
1420
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1421
+ }>;
1422
+ forkedFrom: import("drizzle-orm/pg-core").PgColumn<{
1423
+ name: "forkedFrom";
1424
+ tableName: "environments";
1425
+ dataType: "custom";
1426
+ columnType: "PgCustomColumn";
1427
+ data: unknown;
1428
+ driverParam: unknown;
1429
+ notNull: false;
1430
+ hasDefault: false;
1431
+ isPrimaryKey: false;
1432
+ isAutoincrement: false;
1433
+ hasRuntimeDefault: false;
1434
+ enumValues: undefined;
1435
+ baseColumn: never;
1436
+ identity: undefined;
1437
+ generated: undefined;
1438
+ }, {}, {
1439
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1440
+ }>;
1441
+ };
1442
+ dialect: 'pg';
1443
+ }>;
1444
+ /**
1445
+ * An environment manifest's versions (D63): a named, versioned definition of a kind of
1446
+ * Environment, saved like an agent. A version is written once and never changed; a save whose
1447
+ * body hashes the same as the latest writes nothing.
1448
+ */
1449
+ export declare const environmentManifests: import("drizzle-orm/pg-core").PgTableWithColumns<{
1450
+ name: "environment_manifests";
1451
+ schema: "nylorun";
1452
+ columns: {
1453
+ name: import("drizzle-orm/pg-core").PgColumn<{
1454
+ name: "name";
1455
+ tableName: "environment_manifests";
1456
+ dataType: "custom";
1457
+ columnType: "PgCustomColumn";
1458
+ data: string;
1459
+ driverParam: string;
1460
+ notNull: true;
1461
+ hasDefault: false;
1462
+ isPrimaryKey: false;
1463
+ isAutoincrement: false;
1464
+ hasRuntimeDefault: false;
1465
+ enumValues: undefined;
1466
+ baseColumn: never;
1467
+ identity: undefined;
1468
+ generated: undefined;
1469
+ }, {}, {
1470
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1471
+ }>;
1472
+ version: import("drizzle-orm/pg-core").PgColumn<{
1473
+ name: "version";
1474
+ tableName: "environment_manifests";
1475
+ dataType: "number";
1476
+ columnType: "PgInteger";
1477
+ data: number;
1478
+ driverParam: string | number;
1479
+ notNull: true;
1480
+ hasDefault: false;
1481
+ isPrimaryKey: false;
1482
+ isAutoincrement: false;
1483
+ hasRuntimeDefault: false;
1484
+ enumValues: undefined;
1485
+ baseColumn: never;
1486
+ identity: undefined;
1487
+ generated: undefined;
1488
+ }, {}, {}>;
1489
+ body: import("drizzle-orm/pg-core").PgColumn<{
1490
+ name: "body";
1491
+ tableName: "environment_manifests";
1492
+ dataType: "custom";
1493
+ columnType: "PgCustomColumn";
1494
+ data: unknown;
1495
+ driverParam: unknown;
1496
+ notNull: true;
1497
+ hasDefault: false;
1498
+ isPrimaryKey: false;
1499
+ isAutoincrement: false;
1500
+ hasRuntimeDefault: false;
1501
+ enumValues: undefined;
1502
+ baseColumn: never;
1503
+ identity: undefined;
1504
+ generated: undefined;
1505
+ }, {}, {
1506
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1507
+ }>;
1508
+ hash: import("drizzle-orm/pg-core").PgColumn<{
1509
+ name: "hash";
1510
+ tableName: "environment_manifests";
1511
+ dataType: "custom";
1512
+ columnType: "PgCustomColumn";
1513
+ data: string;
1514
+ driverParam: string;
1515
+ notNull: true;
1516
+ hasDefault: false;
1517
+ isPrimaryKey: false;
1518
+ isAutoincrement: false;
1519
+ hasRuntimeDefault: false;
1520
+ enumValues: undefined;
1521
+ baseColumn: never;
1522
+ identity: undefined;
1523
+ generated: undefined;
1524
+ }, {}, {
1525
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1526
+ }>;
1527
+ savedBy: import("drizzle-orm/pg-core").PgColumn<{
1528
+ name: "savedBy";
1529
+ tableName: "environment_manifests";
1530
+ dataType: "custom";
1531
+ columnType: "PgCustomColumn";
1532
+ data: unknown;
1533
+ driverParam: unknown;
1534
+ notNull: true;
1535
+ hasDefault: false;
1536
+ isPrimaryKey: false;
1537
+ isAutoincrement: false;
1538
+ hasRuntimeDefault: false;
1539
+ enumValues: undefined;
1540
+ baseColumn: never;
1541
+ identity: undefined;
1542
+ generated: undefined;
1543
+ }, {}, {
1544
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1545
+ }>;
1546
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
1547
+ name: "createdAt";
1548
+ tableName: "environment_manifests";
1549
+ dataType: "custom";
1550
+ columnType: "PgCustomColumn";
1551
+ data: string;
1552
+ driverParam: string;
1553
+ notNull: true;
1554
+ hasDefault: false;
1555
+ isPrimaryKey: false;
1556
+ isAutoincrement: false;
1557
+ hasRuntimeDefault: false;
1558
+ enumValues: undefined;
1559
+ baseColumn: never;
1560
+ identity: undefined;
1561
+ generated: undefined;
1562
+ }, {}, {
1563
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
1564
+ }>;
1348
1565
  };
1349
1566
  dialect: 'pg';
1350
1567
  }>;
@@ -165,14 +165,48 @@ export const environments = nylorun.table("environments", {
165
165
  * a join with another one means the volume was replaced (its files are gone), so `lost`.
166
166
  */
167
167
  volumeId: text(),
168
+ /** The workload image by digest, from the first ready pod on (Q14): later pods run it. */
169
+ imageDigest: textC(),
168
170
  /**
169
171
  * The disk lease (R2d step 3): `{ kind, version?, startedAt, until }` while a snapshot, a
170
172
  * restore or a seed copies the disk; turns are refused until it ends or `until` passes.
171
173
  */
172
174
  diskOp: jsonText(),
173
- /** The disk version the next volume starts from: `{ artifactId, version }` (R2d step 3). */
175
+ /**
176
+ * The disk version the next volume starts from: `{ artifactId, version }` (R2d step 3). A
177
+ * fork onto other setup adds `setup: true` (Q17): once the version is written (`written`),
178
+ * the seed stays until the engine reports the setup done, and turns wait for both.
179
+ */
174
180
  diskSeed: jsonText(),
175
- }, (t) => [check("environments_kind_check", sql `${t.kind} IN ('virtual', 'pod', 'none')`)]);
181
+ /**
182
+ * The environment manifest it was made from and its version (D63): null for an Environment
183
+ * made from an inline manifest. Never changed after creation.
184
+ */
185
+ manifestName: textC(),
186
+ manifestVersion: integer(),
187
+ /** The hash of what it runs (`recipe`), from creation on. */
188
+ manifestHash: textC(),
189
+ /** The Environment it was forked from: `{ environment, disk }` (Q15). Never changed. */
190
+ forkedFrom: jsonText(),
191
+ }, (t) => [
192
+ check("environments_kind_check", sql `${t.kind} IN ('virtual', 'pod', 'none')`),
193
+ index("environments_manifest").on(t.manifestName, t.manifestVersion),
194
+ ]);
195
+ /**
196
+ * An environment manifest's versions (D63): a named, versioned definition of a kind of
197
+ * Environment, saved like an agent. A version is written once and never changed; a save whose
198
+ * body hashes the same as the latest writes nothing.
199
+ */
200
+ export const environmentManifests = nylorun.table("environment_manifests", {
201
+ name: textC().notNull(),
202
+ version: integer().notNull(),
203
+ /** The manifest (`EnvironmentManifest`) as saved. */
204
+ body: jsonText().notNull(),
205
+ hash: textC().notNull(),
206
+ /** `{ actor }`: the subject the save acted for, else `application`. */
207
+ savedBy: jsonText().notNull(),
208
+ createdAt: textC().notNull(),
209
+ }, (t) => [primaryKey({ name: "environment_manifests_pkey", columns: [t.name, t.version] })]);
176
210
  /**
177
211
  * A vault (D56): the host model's (`id = 'host'`), or one Environment's, created and deleted with
178
212
  * it (`environment_id`, unique, deleted with the Environment's row). No other vault exists.
@@ -50,7 +50,7 @@
50
50
  */
51
51
  import { AsyncLocalStorage } from "node:async_hooks";
52
52
  import { randomUUID } from "node:crypto";
53
- import { and, count, eq, getTableColumns, gte, gt, inArray, isNotNull, isNull, lt, lte, ne, or, sql, } from "drizzle-orm";
53
+ import { and, count, desc, eq, getTableColumns, gte, gt, inArray, isNotNull, isNull, lt, lte, ne, or, sql, } from "drizzle-orm";
54
54
  import { appendEnvironmentEvent, appendEvent } from "../../record/index.js";
55
55
  import { OwnershipLostError, PrincipalRoleConflict } from "../ownership.js";
56
56
  import { followControlSignals, pruneSignals, writeSignal } from "./control.js";
@@ -58,7 +58,7 @@ import { database, driverError } from "./db.js";
58
58
  import { expectedSchemaVersion, readSchemaVersion } from "./migrate.js";
59
59
  import { createPostgresRecordReader } from "./record.js";
60
60
  import { postgresEnvironmentRecordWriter, postgresRecordWriter } from "./record-writer.js";
61
- import { artifacts, artifactVersions, artifactContent, definitionFiles, definitionFileUses, commands, definitions, effects, links, modelBudgets, modelUsage, principals, sandboxes, environmentEvents, environments, sessionEvents, sessionLogHeads, sessions, toJson, toolCrossings, signingKeys, tenant, tenantSettings, vaultAudit, vaultCredentials, vaultIdempotency, vaults, sandboxSignals, wakes, } from "./schema.js";
61
+ import { artifacts, artifactVersions, artifactContent, definitionFiles, definitionFileUses, commands, definitions, effects, links, modelBudgets, modelUsage, principals, sandboxes, environmentEvents, environments, environmentManifests, sessionEvents, sessionLogHeads, sessions, toJson, toolCrossings, signingKeys, tenant, tenantSettings, vaultAudit, vaultCredentials, vaultIdempotency, vaults, sandboxSignals, wakes, } from "./schema.js";
62
62
  export function createPostgresSessionStore(options) {
63
63
  return new PostgresSessionStore(options);
64
64
  }
@@ -255,6 +255,10 @@ function environmentOf(row) {
255
255
  updatedAt: row.updatedAt,
256
256
  ...(row.diskOp ? { diskOp: row.diskOp } : {}),
257
257
  ...(row.diskSeed ? { diskSeed: row.diskSeed } : {}),
258
+ ...(row.manifestName !== null ? { manifestName: row.manifestName } : {}),
259
+ ...(row.manifestVersion !== null ? { manifestVersion: row.manifestVersion } : {}),
260
+ ...(row.manifestHash !== null ? { manifestHash: row.manifestHash } : {}),
261
+ ...(row.forkedFrom ? { forkedFrom: row.forkedFrom } : {}),
258
262
  ...(row.k8sName !== null && row.desired !== null && row.observed !== null
259
263
  ? {
260
264
  pod: {
@@ -271,11 +275,22 @@ function environmentOf(row) {
271
275
  ...(row.reason !== null ? { reason: row.reason } : {}),
272
276
  ...(row.retiring !== null ? { retiring: row.retiring } : {}),
273
277
  ...(row.volumeId !== null ? { volumeId: row.volumeId } : {}),
278
+ ...(row.imageDigest !== null ? { imageDigest: row.imageDigest } : {}),
274
279
  },
275
280
  }
276
281
  : {}),
277
282
  };
278
283
  }
284
+ function environmentManifestOf(row) {
285
+ return {
286
+ name: row.name,
287
+ version: row.version,
288
+ body: row.body,
289
+ hash: row.hash,
290
+ savedBy: row.savedBy,
291
+ createdAt: row.createdAt,
292
+ };
293
+ }
279
294
  /** A pod patch as column values: ISO times become dates, `null` clears. */
280
295
  function podColumns(patch) {
281
296
  const date = (value) => value === undefined ? undefined : value === null ? null : new Date(value);
@@ -293,6 +308,7 @@ function podColumns(patch) {
293
308
  reason: patch.reason,
294
309
  retiring: patch.retiring,
295
310
  volumeId: patch.volumeId,
311
+ imageDigest: patch.imageDigest,
296
312
  };
297
313
  return Object.fromEntries(Object.entries(columns).filter(([, value]) => value !== undefined));
298
314
  }
@@ -638,12 +654,16 @@ class PostgresTx {
638
654
  const [counted] = await this.db.select({ n: count() }).from(environments);
639
655
  if ((counted?.n ?? 0) >= limit)
640
656
  return "limit";
641
- const { pod, ownerUserId, diskOp, diskSeed, ...environment } = row;
657
+ const { pod, ownerUserId, diskOp, diskSeed, manifestName, manifestVersion, manifestHash, forkedFrom, ...environment } = row;
642
658
  await this.db.insert(environments).values({
643
659
  ...environment,
644
660
  ownerUserId: ownerUserId ?? null,
645
661
  diskOp: diskOp ?? null,
646
662
  diskSeed: diskSeed ?? null,
663
+ manifestName: manifestName ?? null,
664
+ manifestVersion: manifestVersion ?? null,
665
+ manifestHash: manifestHash ?? null,
666
+ forkedFrom: forkedFrom ?? null,
647
667
  ...(pod ? podColumns(pod) : {}),
648
668
  });
649
669
  return "created";
@@ -680,6 +700,61 @@ class PostgresTx {
680
700
  .orderBy(environments.id);
681
701
  return rows.map(environmentOf);
682
702
  }
703
+ // --- Environment manifests (D63) -----------------------------------------------
704
+ async lockEnvironmentManifest(name) {
705
+ this.check();
706
+ await this.db.execute(sql `SELECT pg_advisory_xact_lock(hashtext(${`nylorun.environment_manifests:${name}`}))`);
707
+ }
708
+ async environmentManifest(name, version) {
709
+ this.check();
710
+ const [row] = await this.db
711
+ .select()
712
+ .from(environmentManifests)
713
+ .where(version === undefined
714
+ ? eq(environmentManifests.name, name)
715
+ : and(eq(environmentManifests.name, name), eq(environmentManifests.version, version)))
716
+ .orderBy(desc(environmentManifests.version))
717
+ .limit(1);
718
+ return row && environmentManifestOf(row);
719
+ }
720
+ async insertEnvironmentManifest(row) {
721
+ this.check();
722
+ await this.db.insert(environmentManifests).values(row);
723
+ }
724
+ async listEnvironmentManifests() {
725
+ this.check();
726
+ const rows = await this.db
727
+ .selectDistinctOn([environmentManifests.name])
728
+ .from(environmentManifests)
729
+ .orderBy(environmentManifests.name, desc(environmentManifests.version));
730
+ return rows.map(environmentManifestOf);
731
+ }
732
+ async environmentManifestVersions(name) {
733
+ this.check();
734
+ const rows = await this.db
735
+ .select()
736
+ .from(environmentManifests)
737
+ .where(eq(environmentManifests.name, name))
738
+ .orderBy(desc(environmentManifests.version));
739
+ return rows.map(environmentManifestOf);
740
+ }
741
+ async deleteEnvironmentManifest(name) {
742
+ this.check();
743
+ const deleted = await this.db
744
+ .delete(environmentManifests)
745
+ .where(eq(environmentManifests.name, name))
746
+ .returning({ version: environmentManifests.version });
747
+ return deleted.length;
748
+ }
749
+ async environmentsOfManifest(name) {
750
+ this.check();
751
+ const rows = await this.db
752
+ .select({ version: environments.manifestVersion, n: count() })
753
+ .from(environments)
754
+ .where(eq(environments.manifestName, name))
755
+ .groupBy(environments.manifestVersion);
756
+ return new Map(rows.filter((row) => row.version !== null).map((row) => [row.version, row.n]));
757
+ }
683
758
  async sessionsOnEnvironment(environmentId) {
684
759
  this.check();
685
760
  const rows = await this.sessions()
@@ -702,7 +777,7 @@ class PostgresTx {
702
777
  const rows = await this.db
703
778
  .select({ body: environmentEvents.body })
704
779
  .from(environmentEvents)
705
- .where(and(eq(environmentEvents.environmentId, environmentId), options.fromSeq === undefined ? undefined : gte(environmentEvents.seq, options.fromSeq)))
780
+ .where(and(eq(environmentEvents.environmentId, environmentId), options.fromSeq === undefined ? undefined : gte(environmentEvents.seq, options.fromSeq), options.types === undefined ? undefined : inArray(environmentEvents.type, [...options.types]), options.since === undefined ? undefined : gte(environmentEvents.committedAt, options.since), options.until === undefined ? undefined : lt(environmentEvents.committedAt, options.until)))
706
781
  .orderBy(environmentEvents.seq)
707
782
  .limit(options.limit ?? 1000);
708
783
  return rows.map((row) => row.body);
@@ -63,7 +63,7 @@
63
63
  import type { KeyRole } from "@nylorun/core/compatibility";
64
64
  import type { RecordReader } from "../streams/relay/types.js";
65
65
  import type { SandboxSignal, Wake } from "../execution/types.js";
66
- import type { EventPayload, EventType, EventWriter, LiveEvent, EnvironmentEvent, EnvironmentEventPayload, EnvironmentEventType, EnvironmentKind, EnvironmentRecipe, PodDesired, PodObserved, SessionEventOf } from "@nylorun/core/contracts";
66
+ import type { EventPayload, EventType, EventWriter, LiveEvent, EnvironmentEvent, EnvironmentEventPayload, EnvironmentEventType, EnvironmentKind, EnvironmentManifest, EnvironmentRecipe, PodDesired, PodObserved, SessionEventOf } from "@nylorun/core/contracts";
67
67
  import type { HostEffect } from "@nylorun/harness/run";
68
68
  import type { ArtifactRow, ArtifactVersionRow, DefinitionFileRow, ModelBudgetRow, ModelUsageRow, ModelUsageWrite, PrincipalRow, ToolCrossingRow, SigningKeyRow, VaultAuditRow, VaultCredentialRow, VaultIdempotencyRow, VaultRow } from "./postgres/schema.js";
69
69
  /** Tables holding JSON documents keyed by `id`. */
@@ -148,12 +148,42 @@ export interface Environment {
148
148
  pod?: SandboxPodState;
149
149
  /** The disk lease while a snapshot, restore or seed copies the disk (R2d step 3). */
150
150
  diskOp?: DiskOperation;
151
- /** The disk version the next volume starts from (R2d step 3). */
152
- diskSeed?: {
153
- artifactId: string;
154
- version: number;
151
+ /**
152
+ * The disk version the next volume starts from (R2d step 3). `setup` (a fork onto other setup,
153
+ * Q17): the setup runs once the version is written (`written`), and the seed stays until it is done.
154
+ */
155
+ diskSeed?: DiskSeed;
156
+ /** The environment manifest it was made from and its version (D63); absent for an inline one. */
157
+ manifestName?: string;
158
+ manifestVersion?: number;
159
+ /** The hash of what it runs (`recipe`), recorded at creation (protocol 14). */
160
+ manifestHash?: string;
161
+ /** The Environment it was forked from and the disk version it started from (Q15). */
162
+ forkedFrom?: {
163
+ environment: string;
164
+ disk: number | null;
155
165
  };
156
166
  }
167
+ /** A pending seed of an Environment's disk (`disk_seed`). */
168
+ export interface DiskSeed {
169
+ artifactId: string;
170
+ version: number;
171
+ /** Run the setup on the seeded disk (a fork onto other setup, Q17). */
172
+ setup?: true;
173
+ /** The version is written; with `setup`, the seed waits for the setup to be done. */
174
+ written?: true;
175
+ }
176
+ /** One version of an environment manifest (`environment_manifests`, D63). */
177
+ export interface EnvironmentManifestRow {
178
+ name: string;
179
+ version: number;
180
+ body: EnvironmentManifest;
181
+ hash: string;
182
+ savedBy: {
183
+ actor: string;
184
+ };
185
+ createdAt: string;
186
+ }
157
187
  /** A disk lease (R2d step 3): what holds the disk, since when, and until when unless renewed. */
158
188
  export interface DiskOperation {
159
189
  kind: "snapshot" | "restore" | "seed";
@@ -188,6 +218,11 @@ export interface SandboxPodState {
188
218
  retiring?: string;
189
219
  /** The id the engine keeps on the volume, from the first join. */
190
220
  volumeId?: string;
221
+ /**
222
+ * The workload image by digest (`repo@sha256:…`), recorded the first time a pod of the
223
+ * Environment is ready (Q14): every later pod, after a stop, a relaunch or a reset, runs it.
224
+ */
225
+ imageDigest?: string;
191
226
  }
192
227
  /** A change to a pod's columns: `null` clears an optional one. */
193
228
  export type SandboxPodPatch = {
@@ -531,10 +566,7 @@ export interface Tx {
531
566
  ownerUserId?: string | null;
532
567
  lastUsedAt?: string;
533
568
  diskOp?: DiskOperation | null;
534
- diskSeed?: {
535
- artifactId: string;
536
- version: number;
537
- } | null;
569
+ diskSeed?: DiskSeed | null;
538
570
  }, updatedAt: string): Promise<void>;
539
571
  /** Changes a pod Environment's lifecycle columns (the caller holds the row's lock). */
540
572
  updateEnvironmentPod(id: string, patch: SandboxPodPatch, updatedAt: string): Promise<void>;
@@ -551,11 +583,25 @@ export interface Tx {
551
583
  * caller holds the Environment row's lock (or created the row in this transaction).
552
584
  */
553
585
  environmentEvent<T extends EnvironmentEventType>(environmentId: string, type: T, payload: EnvironmentEventPayload<T>): Promise<EnvironmentEvent>;
554
- /** The Environment's lifecycle stream, from `fromSeq` on, at most `limit` events. */
555
- environmentEvents(environmentId: string, options?: {
556
- fromSeq?: number;
557
- limit?: number;
558
- }): Promise<EnvironmentEvent[]>;
586
+ /**
587
+ * The Environment's stream, from `fromSeq` on, at most `limit` events: only `types` when given,
588
+ * and only events recorded at or after `since` and before `until` (ISO times) when given.
589
+ */
590
+ environmentEvents(environmentId: string, options?: EnvironmentEventsQuery): Promise<EnvironmentEvent[]>;
591
+ /** Holds manifest `name`'s save lock until the transaction ends, so saves of one name are serial. */
592
+ lockEnvironmentManifest(name: string): Promise<void>;
593
+ /** Version `version` of manifest `name`, or its latest when `version` is omitted. */
594
+ environmentManifest(name: string, version?: number): Promise<EnvironmentManifestRow | undefined>;
595
+ /** Writes a new version (the caller holds the name's lock and chose the next number). */
596
+ insertEnvironmentManifest(row: EnvironmentManifestRow): Promise<void>;
597
+ /** Each manifest's latest version, by name. */
598
+ listEnvironmentManifests(): Promise<EnvironmentManifestRow[]>;
599
+ /** Every version of manifest `name`, newest first. */
600
+ environmentManifestVersions(name: string): Promise<EnvironmentManifestRow[]>;
601
+ /** Deletes every version of manifest `name`; how many there were. */
602
+ deleteEnvironmentManifest(name: string): Promise<number>;
603
+ /** How many Environments were made from each version of manifest `name` (deleting ones too). */
604
+ environmentsOfManifest(name: string): Promise<Map<number, number>>;
559
605
  /** `invoking` effects of one session; takeover turns them `uncertain`. */
560
606
  invokingEffects<T extends EffectDoc = EffectDoc>(sessionId: string): Promise<T[]>;
561
607
  effectsForSession<T extends EffectDoc = EffectDoc>(sessionId: string, filter?: SessionEffectFilter): Promise<T[]>;
@@ -746,3 +792,11 @@ export interface Tx {
746
792
  */
747
793
  reset(scope: ResetScope): Promise<void>;
748
794
  }
795
+ /** Which of an Environment's events to read (`Tx.environmentEvents`). */
796
+ export interface EnvironmentEventsQuery {
797
+ readonly fromSeq?: number;
798
+ readonly limit?: number;
799
+ readonly types?: readonly string[];
800
+ readonly since?: string;
801
+ readonly until?: string;
802
+ }
@@ -313,6 +313,9 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
313
313
  // Aborted before a harness took it: settled as an abort of the engine would be.
314
314
  if (end.kind === "aborted")
315
315
  throw signal.reason;
316
+ // The harness's own tools take names of this turn's tools (Harness API 3): the turn fails.
317
+ if (end.kind === "refused")
318
+ throw new Error(end.message);
316
319
  // Checked before the abort: whatever core's signal says, the run's effects are unaccounted for.
317
320
  if (end.kind === "released" && end.reason === "connection.lost")
318
321
  throw new RunLost();
@@ -4,7 +4,7 @@ import type { HostEffect } from "@nylorun/harness/run";
4
4
  import { type TenantContext } from "./context.js";
5
5
  export { isReadArtifactCall, isSaveArtifactCall } from "../harness/calls.js";
6
6
  export declare function callSaveArtifact(ctx: TenantContext, request: HostEffect, signal: AbortSignal): Promise<SandboxToolOutcome>;
7
- /** What `read_artifact` returns: a page of a text artifact, or an image as a file. */
7
+ /** What `read_artifact` returns: a page of a text artifact, or a file the model reads. */
8
8
  interface ReadOutcome {
9
9
  readonly kind: "completed";
10
10
  readonly output: Record<string, unknown>;