@nylorun/runtime 0.22.0-beta → 0.24.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 (273) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/README.md +95 -30
  3. package/dist/adapters/execution/restate.d.ts +15 -3
  4. package/dist/adapters/execution/restate.js +78 -84
  5. package/dist/adapters/replication/pgoutput.d.ts +2 -0
  6. package/dist/adapters/replication/pgoutput.js +6 -3
  7. package/dist/adapters/sandbox/local.d.ts +9 -1
  8. package/dist/adapters/sandbox/local.js +16 -4
  9. package/dist/adapters/sandbox/virtual.js +4 -1
  10. package/dist/adapters/streams/s2.js +1 -13
  11. package/dist/api/a2a/endpoint.js +5 -1
  12. package/dist/api/a2a/routes.d.ts +2 -0
  13. package/dist/api/a2a/routes.js +2 -2
  14. package/dist/api/a2a/tasks.js +2 -1
  15. package/dist/api/ag-ui/endpoint.js +4 -2
  16. package/dist/api/ag-ui/history.js +4 -1
  17. package/dist/api/ag-ui/routes.d.ts +4 -1
  18. package/dist/api/ag-ui/routes.js +11 -10
  19. package/dist/api/ag-ui/translate.js +3 -2
  20. package/dist/api/components.d.ts +4953 -1204
  21. package/dist/api/components.js +25 -16
  22. package/dist/api/gate/keys.js +7 -1
  23. package/dist/api/gate/routes.d.ts +6 -0
  24. package/dist/api/gate/routes.js +39 -3
  25. package/dist/api/http/app.js +4 -4
  26. package/dist/api/http/define.d.ts +6 -0
  27. package/dist/api/http/define.js +13 -1
  28. package/dist/api/http/respond.js +1 -1
  29. package/dist/api/http/routes/artifacts.js +35 -8
  30. package/dist/api/http/routes/coverage.d.ts +9 -0
  31. package/dist/api/http/routes/coverage.js +34 -0
  32. package/dist/api/http/routes/environments.d.ts +11 -0
  33. package/dist/api/http/routes/environments.js +283 -0
  34. package/dist/api/http/routes/mcp.d.ts +1 -1
  35. package/dist/api/http/routes/mcp.js +2 -2
  36. package/dist/api/http/routes/me.js +1 -1
  37. package/dist/api/http/routes/reads.d.ts +45 -9
  38. package/dist/api/http/routes/reads.js +54 -5
  39. package/dist/api/http/routes/sessions.js +11 -6
  40. package/dist/api/http/routes/tenant.js +32 -10
  41. package/dist/api/openapi.js +31 -26
  42. package/dist/artifacts/disk.d.ts +59 -0
  43. package/dist/artifacts/disk.js +563 -0
  44. package/dist/artifacts/export.d.ts +1 -1
  45. package/dist/artifacts/export.js +2 -3
  46. package/dist/artifacts/folders.d.ts +6 -3
  47. package/dist/artifacts/folders.js +17 -12
  48. package/dist/artifacts/service.d.ts +2 -1
  49. package/dist/artifacts/service.js +6 -1
  50. package/dist/artifacts/workspace.d.ts +1 -1
  51. package/dist/artifacts/workspace.js +1 -1
  52. package/dist/core/flow-host.d.ts +3 -22
  53. package/dist/core/flow-host.js +11 -37
  54. package/dist/core/provider.d.ts +3 -2
  55. package/dist/core/provider.js +5 -37
  56. package/dist/core/sandbox-routes.d.ts +2 -21
  57. package/dist/core/sandbox-routes.js +10 -43
  58. package/dist/execution/types.d.ts +11 -9
  59. package/dist/execution/types.js +5 -7
  60. package/dist/gates/contract.d.ts +3 -24
  61. package/dist/gates/contract.js +1 -36
  62. package/dist/gates/egress-credentials.d.ts +9 -0
  63. package/dist/gates/egress-credentials.js +11 -4
  64. package/dist/gates/egress.d.ts +28 -1
  65. package/dist/gates/egress.js +115 -10
  66. package/dist/gates/fixture-model.d.ts +28 -0
  67. package/dist/gates/fixture-model.js +92 -0
  68. package/dist/gates/handler.js +4 -4
  69. package/dist/gates/http-tool.d.ts +6 -2
  70. package/dist/gates/http-tool.js +3 -5
  71. package/dist/gates/in-process.d.ts +5 -0
  72. package/dist/gates/in-process.js +12 -1
  73. package/dist/gates/journal.d.ts +30 -0
  74. package/dist/gates/journal.js +52 -0
  75. package/dist/gates/mcp-handler.d.ts +4 -0
  76. package/dist/gates/mcp-handler.js +5 -1
  77. package/dist/gates/meter.d.ts +10 -2
  78. package/dist/gates/meter.js +20 -5
  79. package/dist/gates/policy.d.ts +49 -0
  80. package/dist/gates/policy.js +134 -0
  81. package/dist/gates/tenant-vaults.d.ts +7 -7
  82. package/dist/gates/tenant-vaults.js +5 -5
  83. package/dist/gates/tool-calls.d.ts +10 -5
  84. package/dist/gates/tool-calls.js +28 -10
  85. package/dist/gates/tool-client.d.ts +8 -0
  86. package/dist/gates/tool-client.js +27 -11
  87. package/dist/gates/tool-contract.d.ts +10 -68
  88. package/dist/gates/tool-contract.js +1 -85
  89. package/dist/gates/tool-gate.d.ts +1 -1
  90. package/dist/harness/calls.d.ts +12 -5
  91. package/dist/harness/calls.js +21 -12
  92. package/dist/harness/executors.js +12 -9
  93. package/dist/harness/main.js +18 -5
  94. package/dist/harness/pod.d.ts +34 -0
  95. package/dist/harness/pod.js +41 -0
  96. package/dist/harness/service.d.ts +0 -1
  97. package/dist/harness/service.js +13 -5
  98. package/dist/harness/setup.d.ts +27 -0
  99. package/dist/harness/setup.js +150 -0
  100. package/dist/harness/ws-client.js +3 -2
  101. package/dist/harness/ws-port.d.ts +1 -5
  102. package/dist/harness/ws-port.js +1 -5
  103. package/dist/harness-api/in-process.js +3 -3
  104. package/dist/harness-api/record.d.ts +15 -1
  105. package/dist/harness-api/record.js +174 -44
  106. package/dist/harness-api/redact.d.ts +3 -0
  107. package/dist/harness-api/redact.js +45 -0
  108. package/dist/harness-api/server.d.ts +8 -0
  109. package/dist/harness-api/server.js +93 -13
  110. package/dist/harness-api/start.d.ts +10 -3
  111. package/dist/harness-api/start.js +40 -10
  112. package/dist/harness-api/tool-journal.d.ts +96 -0
  113. package/dist/harness-api/tool-journal.js +102 -0
  114. package/dist/harness-api/tool-status.d.ts +13 -0
  115. package/dist/harness-api/tool-status.js +26 -0
  116. package/dist/harness-api/workspace.d.ts +6 -3
  117. package/dist/harness-api/workspace.js +27 -11
  118. package/dist/harness-api/ws-server.d.ts +11 -3
  119. package/dist/harness-api/ws-server.js +94 -15
  120. package/dist/host/execution.d.ts +8 -6
  121. package/dist/host/gates.d.ts +2 -1
  122. package/dist/host/gates.js +10 -2
  123. package/dist/host/main.js +64 -21
  124. package/dist/host/operate.js +67 -5
  125. package/dist/host/stack-config.d.ts +70 -0
  126. package/dist/host/stack-config.js +67 -1
  127. package/dist/infra/execution.d.ts +18 -1
  128. package/dist/infra/execution.js +28 -4
  129. package/dist/infra/index.d.ts +1 -1
  130. package/dist/keys/client.js +4 -1
  131. package/dist/keys/keys.d.ts +5 -5
  132. package/dist/keys/keys.js +5 -7
  133. package/dist/management-openapi.json +999 -2211
  134. package/dist/mcp/connect.d.ts +25 -20
  135. package/dist/mcp/connect.js +29 -30
  136. package/dist/mcp/pool.d.ts +2 -2
  137. package/dist/mcp/pool.js +1 -1
  138. package/dist/mcp/preview.d.ts +3 -3
  139. package/dist/mcp/preview.js +10 -20
  140. package/dist/mcp/snapshot.d.ts +5 -11
  141. package/dist/mcp/snapshot.js +6 -7
  142. package/dist/model/pi-model.js +8 -0
  143. package/dist/openapi.json +6955 -2526
  144. package/dist/reads/types.d.ts +16 -3
  145. package/dist/record/append.d.ts +3 -1
  146. package/dist/record/append.js +1 -0
  147. package/dist/record/envelope.d.ts +5 -1
  148. package/dist/record/envelope.js +7 -3
  149. package/dist/record/environment.d.ts +25 -0
  150. package/dist/record/environment.js +44 -0
  151. package/dist/record/index.d.ts +1 -1
  152. package/dist/record/index.js +1 -1
  153. package/dist/retry.d.ts +21 -0
  154. package/dist/retry.js +34 -0
  155. package/dist/sandbox/disk-fs.d.ts +95 -0
  156. package/dist/sandbox/disk-fs.js +206 -0
  157. package/dist/sandbox/join.d.ts +17 -18
  158. package/dist/sandbox/join.js +46 -20
  159. package/dist/sandbox/manager.d.ts +16 -8
  160. package/dist/sandbox/manager.js +33 -17
  161. package/dist/sandbox/placement.d.ts +13 -12
  162. package/dist/sandbox/placement.js +29 -12
  163. package/dist/sandbox/pods/client.d.ts +6 -1
  164. package/dist/sandbox/pods/lifecycle.d.ts +28 -27
  165. package/dist/sandbox/pods/lifecycle.js +14 -26
  166. package/dist/sandbox/pods/reconcile.js +21 -15
  167. package/dist/sandbox/pods/spec.d.ts +15 -13
  168. package/dist/sandbox/pods/spec.js +16 -20
  169. package/dist/sandbox/records.d.ts +2 -2
  170. package/dist/sandbox/records.js +3 -3
  171. package/dist/sandbox/resolve.d.ts +35 -27
  172. package/dist/sandbox/resolve.js +66 -41
  173. package/dist/sandbox/session-sandbox.js +1 -1
  174. package/dist/sandbox/setup.d.ts +61 -0
  175. package/dist/sandbox/setup.js +123 -0
  176. package/dist/sandbox/share.d.ts +11 -21
  177. package/dist/sandbox/share.js +12 -41
  178. package/dist/sandbox/tenant-config.d.ts +12 -14
  179. package/dist/sandbox/tenant-config.js +12 -17
  180. package/dist/sandbox/types.d.ts +6 -0
  181. package/dist/store/postgres/control.js +8 -1
  182. package/dist/store/postgres/drizzle/0018_wake_outbox_retries.sql +3 -0
  183. package/dist/store/postgres/drizzle/0019_sandbox_signal_outbox.sql +18 -0
  184. package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
  185. package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
  186. package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
  187. package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
  188. package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
  189. package/dist/store/postgres/reads.js +117 -17
  190. package/dist/store/postgres/record-writer.d.ts +4 -4
  191. package/dist/store/postgres/record-writer.js +9 -9
  192. package/dist/store/postgres/record.d.ts +7 -0
  193. package/dist/store/postgres/record.js +23 -2
  194. package/dist/store/postgres/schema.d.ts +1213 -779
  195. package/dist/store/postgres/schema.js +136 -76
  196. package/dist/store/postgres/store.js +283 -117
  197. package/dist/store/types.d.ts +206 -88
  198. package/dist/streams/relay/core.js +1 -13
  199. package/dist/tenant/advance.d.ts +7 -6
  200. package/dist/tenant/advance.js +121 -49
  201. package/dist/tenant/artifact-tool.js +3 -4
  202. package/dist/tenant/commands.d.ts +15 -2
  203. package/dist/tenant/commands.js +94 -14
  204. package/dist/tenant/context.d.ts +67 -33
  205. package/dist/tenant/context.js +4 -15
  206. package/dist/tenant/control.d.ts +8 -1
  207. package/dist/tenant/control.js +2 -0
  208. package/dist/tenant/effects.d.ts +1 -1
  209. package/dist/tenant/effects.js +37 -20
  210. package/dist/tenant/environments.d.ts +167 -0
  211. package/dist/tenant/environments.js +823 -0
  212. package/dist/tenant/harness-tokens.d.ts +21 -0
  213. package/dist/tenant/harness-tokens.js +61 -0
  214. package/dist/tenant/host-token.d.ts +1 -1
  215. package/dist/tenant/host-token.js +2 -4
  216. package/dist/tenant/identity-file.d.ts +4 -4
  217. package/dist/tenant/identity-file.js +17 -8
  218. package/dist/tenant/issuers.js +3 -3
  219. package/dist/tenant/principals.js +1 -1
  220. package/dist/tenant/reset.js +3 -3
  221. package/dist/tenant/runtime.d.ts +6 -1
  222. package/dist/tenant/runtime.js +30 -4
  223. package/dist/tenant/scheduler.d.ts +7 -0
  224. package/dist/tenant/scheduler.js +38 -7
  225. package/dist/tenant/session-streams.d.ts +0 -1
  226. package/dist/tenant/session-streams.js +1 -13
  227. package/dist/tenant/session.d.ts +3 -3
  228. package/dist/tenant/session.js +0 -14
  229. package/dist/tenant/sessions.d.ts +13 -13
  230. package/dist/tenant/sessions.js +61 -138
  231. package/dist/tenant/signing-keys.d.ts +5 -1
  232. package/dist/tenant/status.js +3 -3
  233. package/dist/tenant/streams.js +14 -29
  234. package/dist/tenant/sweep.d.ts +39 -12
  235. package/dist/tenant/sweep.js +168 -63
  236. package/dist/tenant/transcript.d.ts +210 -20
  237. package/dist/tenant/transcript.js +62 -11
  238. package/dist/tenant/turn-timing.d.ts +43 -0
  239. package/dist/tenant/turn-timing.js +39 -0
  240. package/dist/tenant/types.d.ts +2 -0
  241. package/dist/traces/ids.d.ts +42 -0
  242. package/dist/traces/ids.js +85 -0
  243. package/dist/traces/key.d.ts +6 -0
  244. package/dist/traces/key.js +37 -0
  245. package/dist/traces/map.d.ts +113 -0
  246. package/dist/traces/map.js +317 -0
  247. package/dist/traces/options.d.ts +7 -0
  248. package/dist/traces/options.js +1 -0
  249. package/dist/traces/otlp.d.ts +42 -0
  250. package/dist/traces/otlp.js +84 -0
  251. package/dist/traces/projector.d.ts +58 -0
  252. package/dist/traces/projector.js +200 -0
  253. package/dist/traces/read.d.ts +23 -0
  254. package/dist/traces/read.js +16 -0
  255. package/dist/vault/coverage.d.ts +26 -0
  256. package/dist/vault/coverage.js +64 -0
  257. package/dist/vault/error.d.ts +11 -1
  258. package/dist/vault/error.js +5 -1
  259. package/dist/vault/service.d.ts +47 -48
  260. package/dist/vault/service.js +321 -382
  261. package/dist/vault/sources.d.ts +5 -16
  262. package/dist/vault/sources.js +6 -8
  263. package/dist/version.d.ts +1 -1
  264. package/dist/version.js +2 -2
  265. package/package.json +14 -13
  266. package/dist/api/http/routes/sandboxes.d.ts +0 -9
  267. package/dist/api/http/routes/sandboxes.js +0 -153
  268. package/dist/api/http/routes/vaults.d.ts +0 -10
  269. package/dist/api/http/routes/vaults.js +0 -139
  270. package/dist/record/sandbox.d.ts +0 -25
  271. package/dist/record/sandbox.js +0 -43
  272. package/dist/tenant/sandboxes.d.ts +0 -122
  273. package/dist/tenant/sandboxes.js +0 -562
@@ -69,14 +69,14 @@ export const sessions = nylorun.table("sessions", {
69
69
  ownerExpiresAt: timestamp({ withTimezone: true, mode: "date" }),
70
70
  /** The session owner (`ownerUserId`), not the lease column `owner`. */
71
71
  ownerUserId: field("->>'ownerUserId'"),
72
- /** The sandbox resource the session is attached to (`sandbox: { id }`). */
73
- sandboxId: field("->>'sandboxId'"),
72
+ /** The Environment the session names (`environment`, protocol 11). */
73
+ environmentId: field("->>'environmentId'"),
74
74
  }, (t) => [
75
75
  index("sessions_created").on(t.createdAt.desc().nullsLast(), t.id.desc().nullsFirst()),
76
76
  index("sessions_status").on(t.status, t.ownerExpiresAt),
77
77
  index("sessions_agent").on(t.agentId),
78
78
  index("sessions_owner_user").on(t.ownerUserId),
79
- index("sessions_sandbox").on(t.sandboxId),
79
+ index("sessions_environment").on(t.environmentId),
80
80
  ]);
81
81
  export const commands = documents("commands");
82
82
  export const effects = nylorun.table("effects", {
@@ -107,22 +107,92 @@ export const principals = nylorun.table("principals", {
107
107
  idempotencyKey: text(),
108
108
  createdAt: text().notNull(),
109
109
  });
110
+ /**
111
+ * An Environment (D55, protocol 11): its id, kind, labels, the recipe the app asked for and the
112
+ * recipe its compute runs (`applied`, copied from `recipe` when a stopped pod is asked to run),
113
+ * the owner guard, and when it was last used. Kind `pod` adds the pod's lifecycle columns.
114
+ * Sessions name it by id (the session body's `environmentId`). Not the `sandboxes` document
115
+ * table, which holds the virtual backend's compute records, keyed by workspace key.
116
+ */
117
+ export const environments = nylorun.table("environments", {
118
+ id: textC().primaryKey(),
119
+ kind: text({ enum: ["virtual", "pod", "none"] }).notNull(),
120
+ /** `{ key: value }`, filtered through `nylorun.doc(labels)`. */
121
+ labels: jsonText().notNull(),
122
+ /** What the app asked for (`EnvironmentRecipe`), resolved against the Tenant's limits. */
123
+ recipe: jsonText().notNull(),
124
+ /** The recipe the current pod or workspace runs. */
125
+ applied: jsonText().notNull(),
126
+ version: integer().notNull().default(1),
127
+ appliedVersion: integer().notNull().default(1),
128
+ /** The owner guard: only this owner's sessions may name the Environment. */
129
+ ownerUserId: text(),
130
+ /** A turn's end, a recipe change, a secret write, a stop or a reset (`lifecycle.deleteAfter`). */
131
+ lastUsedAt: timestamp({ withTimezone: true, mode: "string" }).notNull().defaultNow(),
132
+ createdAt: textC().notNull(),
133
+ updatedAt: textC().notNull(),
134
+ // Kind `pod` (F7.2, D34): null otherwise. `tenant/environments.ts` and the `Sandbox`
135
+ // object's reconcile (`sandbox/pods/reconcile.ts`) are the only writers.
136
+ /** The agent-sandbox Sandbox's name, `sbx-<16 base32>-g<volume_gen>` (`podName`). */
137
+ k8sName: textC(),
138
+ /** Bumped by every reset: a new Sandbox on a new volume. */
139
+ volumeGen: integer().notNull().default(0),
140
+ /** `running`, `suspended` or `deleted`. */
141
+ desired: text({ enum: ["running", "suspended", "deleted"] }),
142
+ /** `creating`, `running`, `suspended`, `lost`, `failed` or `deleting`. */
143
+ observed: text({ enum: ["creating", "running", "suspended", "lost", "failed", "deleting"] }),
144
+ /** The UID of the pod that last joined. */
145
+ podUid: text(),
146
+ /**
147
+ * Bumped by every join (`host.join`), relaunch, loss and reset: host and egress tokens carry
148
+ * it (`epc`), and one of an older epoch is refused (egress-gate reads it too).
149
+ */
150
+ hostEpoch: integer().notNull().default(0),
151
+ /** sha256 (hex) of the incarnation's join token; the token itself is never stored. */
152
+ joinTokenHash: text(),
153
+ /** Bumped by every change the Runtime asks of the pod: the sandboxes service's `opId`. */
154
+ rev: integer().notNull().default(0),
155
+ /** When a turn last ended on it, for the idle timer. */
156
+ lastActiveAt: timestamp({ withTimezone: true }),
157
+ /** When the current incarnation was asked to run, for the ready deadline. */
158
+ startedAt: timestamp({ withTimezone: true }),
159
+ /** Why it failed or was lost. */
160
+ reason: text(),
161
+ /** An earlier incarnation's Sandbox still to delete (after a reset or a loss). */
162
+ retiring: textC(),
163
+ /**
164
+ * The id the engine keeps on the volume (`/harness/volume-id`), recorded at the first join:
165
+ * a join with another one means the volume was replaced (its files are gone), so `lost`.
166
+ */
167
+ volumeId: text(),
168
+ /**
169
+ * The disk lease (R2d step 3): `{ kind, version?, startedAt, until }` while a snapshot, a
170
+ * restore or a seed copies the disk; turns are refused until it ends or `until` passes.
171
+ */
172
+ diskOp: jsonText(),
173
+ /** The disk version the next volume starts from: `{ artifactId, version }` (R2d step 3). */
174
+ diskSeed: jsonText(),
175
+ }, (t) => [check("environments_kind_check", sql `${t.kind} IN ('virtual', 'pod', 'none')`)]);
176
+ /**
177
+ * A vault (D56): the host model's (`id = 'host'`), or one Environment's, created and deleted with
178
+ * it (`environment_id`, unique, deleted with the Environment's row). No other vault exists.
179
+ */
110
180
  export const vaults = nylorun.table("vaults", {
111
181
  id: textC().primaryKey(),
112
182
  name: text().notNull(),
113
- ownerUserId: text().notNull(),
114
183
  /** JSON text, or null. */
115
184
  metadataJson: text(),
116
185
  createdAt: textC().notNull(),
117
- /**
118
- * `user`: one person's (owner `ownerUserId`); `installation`: the installation's own,
119
- * attachable to any session (owner `installation`); `host`: the model vault, never attached.
120
- */
121
- scope: text({ enum: ["user", "installation", "host"] }).notNull().default("user"),
186
+ /** The Environment the vault belongs to; null only for the host vault. */
187
+ environmentId: textC(),
122
188
  }, (t) => [
123
- index("vaults_owner").on(t.ownerUserId, t.createdAt, t.id),
124
- uniqueIndex("vaults_one_host").on(t.scope).where(sql `scope = 'host'`),
125
- check("vaults_scope_check", sql `scope IN ('user', 'installation', 'host')`),
189
+ uniqueIndex("vaults_environment").on(t.environmentId),
190
+ foreignKey({
191
+ name: "vaults_environment_id_fkey",
192
+ columns: [t.environmentId],
193
+ foreignColumns: [environments.id],
194
+ }).onDelete("cascade"),
195
+ check("vaults_owner_check", sql `(${t.id} = 'host') = (${t.environmentId} IS NULL)`),
126
196
  ]);
127
197
  /** A credential of a vault: its binding in the clear, its secret sealed. */
128
198
  export const vaultCredentials = nylorun.table("vault_credentials", {
@@ -146,6 +216,7 @@ export const vaultCredentials = nylorun.table("vault_credentials", {
146
216
  foreignColumns: [vaults.id],
147
217
  }).onDelete("cascade"),
148
218
  index("vault_credentials_vault").on(t.vaultId, t.createdAt, t.id),
219
+ uniqueIndex("vault_credentials_name").on(t.vaultId, t.name),
149
220
  ]);
150
221
  export const vaultAudit = nylorun.table("vault_audit", {
151
222
  ord: bigint({ mode: "number" }).primaryKey().generatedAlwaysAsIdentity(),
@@ -198,7 +269,9 @@ export const signingKeys = nylorun.table("signing_keys", {
198
269
  * The model usage ledger (P1.3): one row per model call a gate served, written after the call.
199
270
  * A second row for the same effect (a call the gate ran again after a gateway restart) is
200
271
  * flagged `duplicate`; both were billed. Budgets read their spend from it. Token counts are 0
201
- * when the provider sent none.
272
+ * when the provider sent none. `startedAt`, `endedAt` and `firstTokenMs` are the gate's own
273
+ * timing of the call (R7.1, migration 0022); null on rows written before, and `firstTokenMs`
274
+ * when the provider streamed nothing.
202
275
  */
203
276
  export const modelUsage = nylorun.table("model_usage", {
204
277
  id: textC().primaryKey(),
@@ -222,6 +295,12 @@ export const modelUsage = nylorun.table("model_usage", {
222
295
  /** True when an earlier row has the same `effectKey`: the provider billed the call twice. */
223
296
  duplicate: boolean().notNull(),
224
297
  createdAt: text().notNull(),
298
+ /** When the gate sent the call to the provider. */
299
+ startedAt: text(),
300
+ /** When the provider's answer was complete. */
301
+ endedAt: text(),
302
+ /** From `startedAt` to the provider's first stream event. */
303
+ firstTokenMs: integer(),
225
304
  }, (t) => [
226
305
  index("model_usage_session").on(t.sessionId, t.createdAt, t.id),
227
306
  index("model_usage_export").on(t.txid, t.id),
@@ -260,60 +339,6 @@ export const toolCrossings = nylorun.table("tool_crossings", {
260
339
  /** The gate's answer (`McpAnswer`), once the call ended; null while it runs or once lost. */
261
340
  answer: jsonText(),
262
341
  }, (t) => [index("tool_crossings_settled").on(t.settledAt)]);
263
- /**
264
- * A sandbox resource (blueprint D39, F7.1): its id, kind, the spec resolved against the
265
- * Tenant's limits when it was created, and the developer's labels. Sessions attach to it by id
266
- * (the session body's `sandboxId`). Not the `sandboxes` document table, which holds the virtual
267
- * backend's compute records, keyed by workspace key.
268
- */
269
- export const sandboxResources = nylorun.table("sandbox_resources", {
270
- id: textC().primaryKey(),
271
- kind: text({ enum: ["virtual", "pod"] }).notNull(),
272
- /** The resolved `SandboxManifest`. */
273
- spec: jsonText().notNull(),
274
- /** `{ key: value }`, filtered through `nylorun.doc(labels)`. */
275
- labels: jsonText().notNull(),
276
- createdAt: textC().notNull(),
277
- updatedAt: textC().notNull(),
278
- // Kind `pod` (F7.2, D34): null for a virtual sandbox. `tenant/sandboxes.ts` and the
279
- // `Sandbox` object's reconcile (`sandbox/pods/reconcile.ts`) are the only writers.
280
- /** The agent-sandbox Sandbox's name, `sbx-<16 base32>-g<volume_gen>` (`podName`). */
281
- k8sName: textC(),
282
- /** Bumped by every reset: a new Sandbox on a new volume. */
283
- volumeGen: integer().notNull().default(0),
284
- /** `running`, `suspended` or `deleted`. */
285
- desired: text({ enum: ["running", "suspended", "deleted"] }),
286
- /** `creating`, `running`, `suspended`, `expired`, `lost`, `failed` or `deleting`. */
287
- observed: text({
288
- enum: ["creating", "running", "suspended", "expired", "lost", "failed", "deleting"],
289
- }),
290
- /** The UID of the pod that last joined. */
291
- podUid: text(),
292
- /**
293
- * Bumped by every join (`host.join`), relaunch, loss and reset: host and egress tokens carry
294
- * it (`epc`), and one of an older epoch is refused (egress-gate reads it too).
295
- */
296
- hostEpoch: integer().notNull().default(0),
297
- /** sha256 (hex) of the incarnation's join token; the token itself is never stored. */
298
- joinTokenHash: text(),
299
- /** Bumped by every change the Runtime asks of the pod: the sandboxes service's `opId`. */
300
- rev: integer().notNull().default(0),
301
- /** When a turn last ended on it, for the idle timer. */
302
- lastActiveAt: timestamp({ withTimezone: true }),
303
- /** When its TTL passes (`createdAt + lifecycle.ttl`). */
304
- expiresAt: timestamp({ withTimezone: true }),
305
- /** When the current incarnation was asked to run, for the ready deadline. */
306
- startedAt: timestamp({ withTimezone: true }),
307
- /** Why it failed or was lost. */
308
- reason: text(),
309
- /** An earlier incarnation's Sandbox still to delete (after a reset or a loss). */
310
- retiring: textC(),
311
- /**
312
- * The id the engine keeps on the volume (`/harness/volume-id`), recorded at the first join:
313
- * a join with another one means the volume was replaced (its files are gone), so `lost`.
314
- */
315
- volumeId: text(),
316
- });
317
342
  /**
318
343
  * Artifacts (blueprint D35, F8.1 and F8.2): an id, a name and numbered versions. The bytes are in
319
344
  * the Object store under each version's `blobKey` (a folder's: its manifest); a blob counts only
@@ -331,8 +356,12 @@ export const artifacts = nylorun.table("artifacts", {
331
356
  labelsJson: text(),
332
357
  createdAt: textC().notNull(),
333
358
  updatedAt: text().notNull(),
359
+ /** The Environment whose disk versions this folder holds (R2d step 3); one per Environment. */
360
+ environmentId: textC(),
334
361
  }, (t) => [
335
362
  index("artifacts_session").on(t.sessionId, t.createdAt, t.id),
363
+ uniqueIndex("artifacts_environment_disk").on(t.environmentId).where(sql `environment_id IS NOT NULL`),
364
+ index("artifacts_creation_order").on(t.createdAt, t.id),
336
365
  check("artifacts_kind_check", sql `kind IN ('file', 'folder')`),
337
366
  ]);
338
367
  /** The versions of an artifact, immutable once written. */
@@ -344,7 +373,7 @@ export const artifactVersions = nylorun.table("artifact_versions", {
344
373
  size: bigint({ mode: "number" }).notNull(),
345
374
  sha256: text().notNull(),
346
375
  contentType: text().notNull(),
347
- source: text({ enum: ["upload", "engine", "export"] }).notNull(),
376
+ source: text({ enum: ["upload", "engine", "export", "snapshot"] }).notNull(),
348
377
  createdAt: textC().notNull(),
349
378
  }, (t) => [
350
379
  primaryKey({ name: "artifact_versions_pkey", columns: [t.artifactId, t.version] }),
@@ -406,7 +435,8 @@ export const definitionFileUses = nylorun.table("definition_file_uses", {
406
435
  * written in the transaction that commits what they announce and notified on
407
436
  * `nylorun_control` at commit (`control.ts`). A row outlives its notification so a process
408
437
  * whose listener was down catches up; the Tenant sweep deletes old ones. `created_at` is the
409
- * insert's own clock (`clock_timestamp()`), not its transaction's start.
438
+ * insert's own clock (`clock_timestamp()`), not its transaction's start. `sandbox_id` and
439
+ * `epoch` are a `host.revoked` signal's.
410
440
  */
411
441
  export const controlSignals = nylorun.table("control_signals", {
412
442
  id: bigint({ mode: "number" }).primaryKey().generatedAlwaysAsIdentity(),
@@ -417,16 +447,20 @@ export const controlSignals = nylorun.table("control_signals", {
417
447
  createdAt: timestamp({ withTimezone: true, mode: "string" })
418
448
  .notNull()
419
449
  .default(sql `clock_timestamp()`),
450
+ sandboxId: textC(),
451
+ epoch: integer(),
420
452
  }, (t) => [
421
453
  index("control_signals_created_at").on(t.createdAt),
422
- check("control_signals_kind_check", sql `${t.kind} IN ('session.cancel', 'sessions.reset')`),
454
+ check("control_signals_kind_check", sql `${t.kind} IN ('session.cancel', 'sessions.reset', 'host.revoked')`),
423
455
  ]);
424
456
  /**
425
457
  * The wake outbox (architecture §12.3): one row per wake a committed transaction asked for
426
458
  * (`Tx.wake`), written in that transaction, so a commit never loses its wake. The row is
427
459
  * deleted once Durable Session Execution has accepted the wake; until then the Tenant sweep
428
460
  * 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()`).
461
+ * one). `created_at` is the insert's own clock (`clock_timestamp()`). `attempts` counts the
462
+ * sweep's failed deliveries, `retry_at` is when it tries the row again, and a row
463
+ * `parked_at` is never tried again: it stays for an operator (`Tx.failOutbox`).
430
464
  */
431
465
  export const wakes = nylorun.table("wakes", {
432
466
  id: textC().primaryKey(),
@@ -436,20 +470,46 @@ export const wakes = nylorun.table("wakes", {
436
470
  createdAt: timestamp({ withTimezone: true, mode: "date" })
437
471
  .notNull()
438
472
  .default(sql `clock_timestamp()`),
473
+ attempts: integer().notNull().default(0),
474
+ retryAt: timestamp({ withTimezone: true, mode: "date" }),
475
+ parkedAt: timestamp({ withTimezone: true, mode: "date" }),
439
476
  }, (t) => [index("wakes_created_at").on(t.createdAt)]);
477
+ /**
478
+ * The sandbox signal outbox (F7.2): one row per pod sandbox reconcile or timer a committed
479
+ * transaction asked for (`Tx.signalSandbox`), kept and delivered as the wake outbox's rows are
480
+ * (`attempts`, `retry_at`, `parked_at`). A reconcile is sent with the idempotency key
481
+ * `signal:<id>`; a timer (`kind` `arm`) is its `timer` at `at` (ms since the epoch). Not in
482
+ * `wakes`, so a Runtime that delivers only wakes never reads one.
483
+ */
484
+ export const sandboxSignals = nylorun.table("sandbox_signals", {
485
+ id: textC().primaryKey(),
486
+ sandboxId: textC().notNull(),
487
+ kind: text().notNull(),
488
+ timer: text(),
489
+ at: bigint({ mode: "number" }),
490
+ createdAt: timestamp({ withTimezone: true, mode: "date" })
491
+ .notNull()
492
+ .default(sql `clock_timestamp()`),
493
+ attempts: integer().notNull().default(0),
494
+ retryAt: timestamp({ withTimezone: true, mode: "date" }),
495
+ parkedAt: timestamp({ withTimezone: true, mode: "date" }),
496
+ }, (t) => [
497
+ index("sandbox_signals_created_at").on(t.createdAt),
498
+ check("sandbox_signals_kind_check", sql `${t.kind} IN ('reconcile', 'arm')`),
499
+ ]);
440
500
  // ---------------------------------------------------------------------------
441
501
  // nylorun_streams: the record
442
502
  /**
443
- * Each sandbox's lifecycle stream (`nylorun.sandbox-event/1`), numbered from 0 per sandbox id.
444
- * Written only by the record module; not published to the stream relay.
503
+ * Each Environment's lifecycle stream (`nylorun.environment-event/1`), numbered from 0 per
504
+ * Environment id. Written only by the record module; not published to the stream relay.
445
505
  */
446
- export const sandboxEvents = nylorunStreams.table("sandbox_events", {
447
- sandboxId: textC().notNull(),
506
+ export const environmentEvents = nylorunStreams.table("environment_events", {
507
+ environmentId: textC().notNull(),
448
508
  seq: bigint({ mode: "number" }).notNull(),
449
509
  type: text().notNull(),
450
510
  body: jsonText().notNull(),
451
511
  committedAt: timestamp({ withTimezone: true, mode: "string" }).notNull().defaultNow(),
452
- }, (t) => [primaryKey({ name: "sandbox_events_pkey", columns: [t.sandboxId, t.seq] })]);
512
+ }, (t) => [primaryKey({ name: "environment_events_pkey", columns: [t.environmentId, t.seq] })]);
453
513
  /** Each session's log head: the next seq and the basin generation its events go to. */
454
514
  export const sessionLogHeads = nylorunStreams.table("session_log_heads", {
455
515
  sessionId: textC().primaryKey(),