@nylorun/runtime 0.23.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 (252) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/README.md +79 -28
  3. package/dist/adapters/execution/restate.js +1 -1
  4. package/dist/adapters/replication/pgoutput.d.ts +2 -0
  5. package/dist/adapters/replication/pgoutput.js +6 -3
  6. package/dist/adapters/sandbox/local.d.ts +9 -1
  7. package/dist/adapters/sandbox/local.js +16 -4
  8. package/dist/adapters/sandbox/virtual.js +4 -1
  9. package/dist/api/a2a/endpoint.js +5 -1
  10. package/dist/api/a2a/routes.d.ts +2 -0
  11. package/dist/api/a2a/routes.js +2 -2
  12. package/dist/api/a2a/tasks.js +2 -1
  13. package/dist/api/ag-ui/endpoint.js +4 -2
  14. package/dist/api/ag-ui/history.js +4 -1
  15. package/dist/api/ag-ui/routes.d.ts +4 -1
  16. package/dist/api/ag-ui/routes.js +11 -10
  17. package/dist/api/ag-ui/translate.js +3 -2
  18. package/dist/api/components.d.ts +4953 -1204
  19. package/dist/api/components.js +25 -16
  20. package/dist/api/gate/keys.js +7 -1
  21. package/dist/api/gate/routes.d.ts +6 -0
  22. package/dist/api/gate/routes.js +39 -3
  23. package/dist/api/http/app.js +4 -4
  24. package/dist/api/http/define.d.ts +6 -0
  25. package/dist/api/http/define.js +13 -1
  26. package/dist/api/http/respond.js +1 -1
  27. package/dist/api/http/routes/artifacts.js +35 -8
  28. package/dist/api/http/routes/coverage.d.ts +9 -0
  29. package/dist/api/http/routes/coverage.js +34 -0
  30. package/dist/api/http/routes/environments.d.ts +11 -0
  31. package/dist/api/http/routes/environments.js +283 -0
  32. package/dist/api/http/routes/mcp.d.ts +1 -1
  33. package/dist/api/http/routes/mcp.js +2 -2
  34. package/dist/api/http/routes/me.js +1 -1
  35. package/dist/api/http/routes/reads.d.ts +45 -9
  36. package/dist/api/http/routes/reads.js +54 -5
  37. package/dist/api/http/routes/sessions.js +11 -6
  38. package/dist/api/http/routes/tenant.js +32 -10
  39. package/dist/api/openapi.js +31 -26
  40. package/dist/artifacts/disk.d.ts +59 -0
  41. package/dist/artifacts/disk.js +563 -0
  42. package/dist/artifacts/export.d.ts +1 -1
  43. package/dist/artifacts/export.js +2 -3
  44. package/dist/artifacts/folders.d.ts +6 -3
  45. package/dist/artifacts/folders.js +17 -12
  46. package/dist/artifacts/service.d.ts +2 -1
  47. package/dist/artifacts/service.js +6 -1
  48. package/dist/artifacts/workspace.d.ts +1 -1
  49. package/dist/artifacts/workspace.js +1 -1
  50. package/dist/core/flow-host.d.ts +2 -3
  51. package/dist/core/flow-host.js +3 -0
  52. package/dist/core/provider.d.ts +3 -2
  53. package/dist/core/provider.js +5 -37
  54. package/dist/core/sandbox-routes.d.ts +2 -21
  55. package/dist/core/sandbox-routes.js +10 -43
  56. package/dist/execution/types.d.ts +1 -1
  57. package/dist/gates/contract.d.ts +3 -24
  58. package/dist/gates/contract.js +1 -36
  59. package/dist/gates/egress-credentials.d.ts +9 -0
  60. package/dist/gates/egress-credentials.js +11 -4
  61. package/dist/gates/egress.d.ts +28 -1
  62. package/dist/gates/egress.js +115 -10
  63. package/dist/gates/fixture-model.d.ts +28 -0
  64. package/dist/gates/fixture-model.js +92 -0
  65. package/dist/gates/handler.js +4 -4
  66. package/dist/gates/http-tool.d.ts +6 -2
  67. package/dist/gates/http-tool.js +3 -5
  68. package/dist/gates/in-process.d.ts +5 -0
  69. package/dist/gates/in-process.js +12 -1
  70. package/dist/gates/journal.d.ts +30 -0
  71. package/dist/gates/journal.js +52 -0
  72. package/dist/gates/mcp-handler.d.ts +4 -0
  73. package/dist/gates/mcp-handler.js +5 -1
  74. package/dist/gates/meter.d.ts +10 -2
  75. package/dist/gates/meter.js +20 -5
  76. package/dist/gates/policy.d.ts +49 -0
  77. package/dist/gates/policy.js +134 -0
  78. package/dist/gates/tenant-vaults.d.ts +7 -7
  79. package/dist/gates/tenant-vaults.js +5 -5
  80. package/dist/gates/tool-calls.d.ts +10 -5
  81. package/dist/gates/tool-calls.js +28 -10
  82. package/dist/gates/tool-client.d.ts +8 -0
  83. package/dist/gates/tool-client.js +27 -11
  84. package/dist/gates/tool-contract.d.ts +10 -68
  85. package/dist/gates/tool-contract.js +1 -85
  86. package/dist/gates/tool-gate.d.ts +1 -1
  87. package/dist/harness/calls.d.ts +12 -5
  88. package/dist/harness/calls.js +21 -12
  89. package/dist/harness/executors.js +12 -9
  90. package/dist/harness/main.js +18 -5
  91. package/dist/harness/pod.d.ts +34 -0
  92. package/dist/harness/pod.js +41 -0
  93. package/dist/harness/service.d.ts +0 -1
  94. package/dist/harness/service.js +13 -5
  95. package/dist/harness/setup.d.ts +27 -0
  96. package/dist/harness/setup.js +150 -0
  97. package/dist/harness/ws-client.js +3 -2
  98. package/dist/harness/ws-port.d.ts +1 -5
  99. package/dist/harness/ws-port.js +1 -5
  100. package/dist/harness-api/in-process.js +3 -3
  101. package/dist/harness-api/record.d.ts +15 -1
  102. package/dist/harness-api/record.js +174 -44
  103. package/dist/harness-api/redact.d.ts +3 -0
  104. package/dist/harness-api/redact.js +45 -0
  105. package/dist/harness-api/server.d.ts +8 -0
  106. package/dist/harness-api/server.js +93 -13
  107. package/dist/harness-api/start.d.ts +10 -3
  108. package/dist/harness-api/start.js +40 -10
  109. package/dist/harness-api/tool-journal.d.ts +96 -0
  110. package/dist/harness-api/tool-journal.js +102 -0
  111. package/dist/harness-api/tool-status.d.ts +13 -0
  112. package/dist/harness-api/tool-status.js +26 -0
  113. package/dist/harness-api/workspace.d.ts +6 -3
  114. package/dist/harness-api/workspace.js +27 -11
  115. package/dist/harness-api/ws-server.d.ts +11 -3
  116. package/dist/harness-api/ws-server.js +94 -15
  117. package/dist/host/gates.d.ts +2 -1
  118. package/dist/host/gates.js +10 -2
  119. package/dist/host/main.js +54 -5
  120. package/dist/host/operate.js +67 -5
  121. package/dist/host/stack-config.d.ts +64 -0
  122. package/dist/host/stack-config.js +61 -1
  123. package/dist/keys/client.js +4 -1
  124. package/dist/keys/keys.d.ts +5 -5
  125. package/dist/keys/keys.js +5 -7
  126. package/dist/management-openapi.json +999 -2211
  127. package/dist/mcp/connect.d.ts +25 -20
  128. package/dist/mcp/connect.js +29 -30
  129. package/dist/mcp/pool.d.ts +2 -2
  130. package/dist/mcp/pool.js +1 -1
  131. package/dist/mcp/preview.d.ts +3 -3
  132. package/dist/mcp/preview.js +10 -20
  133. package/dist/mcp/snapshot.d.ts +5 -11
  134. package/dist/mcp/snapshot.js +6 -7
  135. package/dist/model/pi-model.js +8 -0
  136. package/dist/openapi.json +6955 -2526
  137. package/dist/reads/types.d.ts +16 -3
  138. package/dist/record/append.d.ts +3 -1
  139. package/dist/record/append.js +1 -0
  140. package/dist/record/envelope.d.ts +5 -1
  141. package/dist/record/envelope.js +7 -3
  142. package/dist/record/environment.d.ts +25 -0
  143. package/dist/record/environment.js +44 -0
  144. package/dist/record/index.d.ts +1 -1
  145. package/dist/record/index.js +1 -1
  146. package/dist/sandbox/disk-fs.d.ts +95 -0
  147. package/dist/sandbox/disk-fs.js +206 -0
  148. package/dist/sandbox/join.d.ts +17 -18
  149. package/dist/sandbox/join.js +42 -15
  150. package/dist/sandbox/manager.d.ts +16 -8
  151. package/dist/sandbox/manager.js +33 -17
  152. package/dist/sandbox/placement.d.ts +13 -12
  153. package/dist/sandbox/placement.js +29 -12
  154. package/dist/sandbox/pods/client.d.ts +6 -1
  155. package/dist/sandbox/pods/lifecycle.d.ts +28 -27
  156. package/dist/sandbox/pods/lifecycle.js +14 -26
  157. package/dist/sandbox/pods/reconcile.js +18 -12
  158. package/dist/sandbox/pods/spec.d.ts +15 -13
  159. package/dist/sandbox/pods/spec.js +16 -20
  160. package/dist/sandbox/records.d.ts +2 -2
  161. package/dist/sandbox/records.js +3 -3
  162. package/dist/sandbox/resolve.d.ts +35 -27
  163. package/dist/sandbox/resolve.js +66 -41
  164. package/dist/sandbox/session-sandbox.js +1 -1
  165. package/dist/sandbox/setup.d.ts +61 -0
  166. package/dist/sandbox/setup.js +123 -0
  167. package/dist/sandbox/share.d.ts +11 -21
  168. package/dist/sandbox/share.js +12 -41
  169. package/dist/sandbox/tenant-config.d.ts +12 -14
  170. package/dist/sandbox/tenant-config.js +12 -17
  171. package/dist/sandbox/types.d.ts +6 -0
  172. package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
  173. package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
  174. package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
  175. package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
  176. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  177. package/dist/store/postgres/reads.js +117 -17
  178. package/dist/store/postgres/record-writer.d.ts +4 -4
  179. package/dist/store/postgres/record-writer.js +9 -9
  180. package/dist/store/postgres/record.d.ts +7 -0
  181. package/dist/store/postgres/record.js +23 -2
  182. package/dist/store/postgres/schema.d.ts +934 -761
  183. package/dist/store/postgres/schema.js +102 -73
  184. package/dist/store/postgres/store.js +119 -66
  185. package/dist/store/types.d.ts +109 -48
  186. package/dist/tenant/advance.d.ts +7 -6
  187. package/dist/tenant/advance.js +115 -42
  188. package/dist/tenant/artifact-tool.js +3 -4
  189. package/dist/tenant/commands.d.ts +15 -2
  190. package/dist/tenant/commands.js +93 -12
  191. package/dist/tenant/context.d.ts +48 -22
  192. package/dist/tenant/context.js +4 -15
  193. package/dist/tenant/effects.d.ts +1 -1
  194. package/dist/tenant/effects.js +37 -20
  195. package/dist/tenant/environments.d.ts +167 -0
  196. package/dist/tenant/environments.js +823 -0
  197. package/dist/tenant/harness-tokens.d.ts +21 -0
  198. package/dist/tenant/harness-tokens.js +61 -0
  199. package/dist/tenant/host-token.d.ts +1 -1
  200. package/dist/tenant/host-token.js +2 -4
  201. package/dist/tenant/identity-file.d.ts +4 -4
  202. package/dist/tenant/identity-file.js +17 -8
  203. package/dist/tenant/issuers.js +3 -3
  204. package/dist/tenant/principals.js +1 -1
  205. package/dist/tenant/reset.js +3 -3
  206. package/dist/tenant/runtime.d.ts +6 -1
  207. package/dist/tenant/runtime.js +7 -1
  208. package/dist/tenant/session.d.ts +3 -3
  209. package/dist/tenant/session.js +0 -14
  210. package/dist/tenant/sessions.d.ts +13 -13
  211. package/dist/tenant/sessions.js +61 -138
  212. package/dist/tenant/signing-keys.d.ts +5 -1
  213. package/dist/tenant/status.js +3 -3
  214. package/dist/tenant/sweep.js +12 -6
  215. package/dist/tenant/transcript.d.ts +210 -20
  216. package/dist/tenant/transcript.js +62 -11
  217. package/dist/tenant/turn-timing.d.ts +43 -0
  218. package/dist/tenant/turn-timing.js +39 -0
  219. package/dist/tenant/types.d.ts +2 -0
  220. package/dist/traces/ids.d.ts +42 -0
  221. package/dist/traces/ids.js +85 -0
  222. package/dist/traces/key.d.ts +6 -0
  223. package/dist/traces/key.js +37 -0
  224. package/dist/traces/map.d.ts +113 -0
  225. package/dist/traces/map.js +317 -0
  226. package/dist/traces/options.d.ts +7 -0
  227. package/dist/traces/options.js +1 -0
  228. package/dist/traces/otlp.d.ts +42 -0
  229. package/dist/traces/otlp.js +84 -0
  230. package/dist/traces/projector.d.ts +58 -0
  231. package/dist/traces/projector.js +200 -0
  232. package/dist/traces/read.d.ts +23 -0
  233. package/dist/traces/read.js +16 -0
  234. package/dist/vault/coverage.d.ts +26 -0
  235. package/dist/vault/coverage.js +64 -0
  236. package/dist/vault/error.d.ts +11 -1
  237. package/dist/vault/error.js +5 -1
  238. package/dist/vault/service.d.ts +47 -48
  239. package/dist/vault/service.js +321 -382
  240. package/dist/vault/sources.d.ts +5 -16
  241. package/dist/vault/sources.js +6 -8
  242. package/dist/version.d.ts +1 -1
  243. package/dist/version.js +2 -2
  244. package/package.json +13 -13
  245. package/dist/api/http/routes/sandboxes.d.ts +0 -9
  246. package/dist/api/http/routes/sandboxes.js +0 -153
  247. package/dist/api/http/routes/vaults.d.ts +0 -10
  248. package/dist/api/http/routes/vaults.js +0 -139
  249. package/dist/record/sandbox.d.ts +0 -25
  250. package/dist/record/sandbox.js +0 -43
  251. package/dist/tenant/sandboxes.d.ts +0 -123
  252. package/dist/tenant/sandboxes.js +0 -566
@@ -1,12 +1,15 @@
1
1
  import { z } from "zod";
2
2
  /** Drizzle read projections on a separate, read-only Tenant pool. */
3
- import { and, asc, desc, eq, gt, inArray, isNull, lt, ne, or, sql, } from "drizzle-orm";
3
+ import { and, asc, desc, eq, exists, gt, inArray, isNull, lt, ne, or, sql, } from "drizzle-orm";
4
+ import { isArtifactId } from "@nylorun/core/compatibility";
4
5
  import { readCursor } from "../../reads/cursor.js";
5
6
  import { fail } from "../../tenant/http.js";
7
+ import { artifactView } from "../../artifacts/service.js";
6
8
  import { sandboxWorkspaceKey } from "../../sandbox/records.js";
7
9
  import { database, driverError } from "./db.js";
8
10
  import { createPostgresReadClient } from "./connect.js";
9
- import { modelUsage, sandboxResources, sandboxes, sessions, sessionEvents, } from "./schema.js";
11
+ import { toolCallDuration, toolCallStatus } from "../../harness-api/tool-status.js";
12
+ import { effects, artifacts, modelUsage, environments, sandboxes, sessions, sessionEvents, } from "./schema.js";
10
13
  const sessionFields = {
11
14
  id: sessions.id,
12
15
  agentId: sessions.agentId,
@@ -14,7 +17,7 @@ const sessionFields = {
14
17
  status: sessions.status,
15
18
  activeTurnId: sql `${sessions.body}->>'activeTurnId'`,
16
19
  lastTurnId: sql `${sessions.body}->>'lastTurnId'`,
17
- sandboxId: sessions.sandboxId,
20
+ environmentId: sessions.environmentId,
18
21
  createdAt: sessions.createdAt,
19
22
  };
20
23
  function accessWhere(access) {
@@ -23,6 +26,7 @@ function accessWhere(access) {
23
26
  function publicCall(row) {
24
27
  return {
25
28
  id: row.id,
29
+ effectId: row.effectKey,
26
30
  sessionId: row.sessionId,
27
31
  turnId: row.turnId,
28
32
  agentId: row.agentId,
@@ -44,6 +48,33 @@ function publicCall(row) {
44
48
  },
45
49
  };
46
50
  }
51
+ function publicToolCall(body) {
52
+ const { request } = body;
53
+ const status = toolCallStatus(body);
54
+ const value = body.outcome?.value;
55
+ const error = value?.kind === "failed"
56
+ ? { code: String(value.code ?? "tool.failed"), message: String(value.message ?? "") }
57
+ : status === "uncertain"
58
+ ? { code: "uncertain", message: body.error ?? "" }
59
+ : null;
60
+ return {
61
+ effectId: request.effectId,
62
+ turnId: request.turnId,
63
+ callId: typeof request.context?.callId === "string" ? request.context.callId : null,
64
+ capabilityId: request.capabilityId ?? null,
65
+ toolName: request.toolName ?? null,
66
+ kind: body.toolKind ?? null,
67
+ server: body.server ?? null,
68
+ status,
69
+ // A row written before R5 counted no sends: it was sent once.
70
+ attempts: body.attempts ?? 1,
71
+ input: request.input ?? null,
72
+ error,
73
+ startedAt: body.startedAt ?? null,
74
+ settledAt: body.settledAt ?? null,
75
+ durationMs: toolCallDuration(body) ?? null,
76
+ };
77
+ }
47
78
  function keyOf(key, length, timestamp = false, nullable = false) {
48
79
  if (key &&
49
80
  (key.length !== length ||
@@ -87,6 +118,39 @@ export function createPostgresReadStore(source, tenantId) {
87
118
  }
88
119
  return {
89
120
  close: () => pool.end({ timeout: 2 }),
121
+ artifacts: (filters, page, access) => read(async (tx) => {
122
+ if (filters.sessionId !== undefined)
123
+ await requireSession(tx, filters.sessionId, access);
124
+ const cursor = readCursor(tenantId, "artifacts", {
125
+ sessionId: filters.sessionId ?? null,
126
+ kind: filters.kind ?? null,
127
+ labels: Object.entries(filters.labels).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
128
+ });
129
+ const key = keyOf(cursor.decode(page.cursor), 2, true);
130
+ if (key && !isArtifactId(key[1]))
131
+ fail(400, "Invalid artifact cursor key", { code: "cursor_invalid" });
132
+ const rows = await tx
133
+ .select()
134
+ .from(artifacts)
135
+ .where(and(access.owner === undefined && access.agents === undefined
136
+ ? undefined
137
+ : exists(tx.select({ id: sessions.id })
138
+ .from(sessions)
139
+ .where(and(eq(sessions.id, artifacts.sessionId), accessWhere(access)))), filters.sessionId === undefined
140
+ ? undefined
141
+ : eq(artifacts.sessionId, filters.sessionId), filters.kind === undefined ? undefined : eq(artifacts.kind, filters.kind),
142
+ // Labels are a flat string map serialized by JSON.stringify. Match its exact
143
+ // key/value pair: jsonb rejects NUL/unpaired surrogate escapes labels may hold.
144
+ ...Object.entries(filters.labels).map(([k, v]) => sql `strpos(${artifacts.labelsJson}, ${JSON.stringify(k) + ":" + JSON.stringify(v)}) > 0`), key ? sql `(${artifacts.createdAt}, ${artifacts.id}) < (${key[0]}, ${key[1]})` : undefined))
145
+ .orderBy(desc(artifacts.createdAt), desc(artifacts.id))
146
+ .limit(page.limit + 1);
147
+ const items = rows.slice(0, page.limit);
148
+ const last = items.at(-1);
149
+ return {
150
+ artifacts: items.map((row) => artifactView(row)),
151
+ nextCursor: rows.length > page.limit && last ? cursor.encode([last.createdAt, last.id]) : null,
152
+ };
153
+ }),
90
154
  sessions: (filters, page, access) => read(async (tx) => {
91
155
  const cursor = readCursor(tenantId, "sessions", filters);
92
156
  const key = keyOf(cursor.decode(page.cursor), 2, true, true);
@@ -98,9 +162,9 @@ export function createPostgresReadStore(source, tenantId) {
98
162
  const rows = await tx
99
163
  .select(fields)
100
164
  .from(sessions)
101
- .where(and(accessWhere(access), position, filters.agentId === undefined ? undefined : eq(sessions.agentId, filters.agentId), filters.status === undefined ? undefined : eq(sessions.status, filters.status), filters.sandboxId === undefined
165
+ .where(and(accessWhere(access), position, filters.agentId === undefined ? undefined : eq(sessions.agentId, filters.agentId), filters.status === undefined ? undefined : eq(sessions.status, filters.status), filters.environmentId === undefined
102
166
  ? undefined
103
- : eq(sessions.sandboxId, filters.sandboxId), filters.ownerUserId === undefined
167
+ : eq(sessions.environmentId, filters.environmentId), filters.ownerUserId === undefined
104
168
  ? undefined
105
169
  : eq(sessions.ownerUserId, filters.ownerUserId)))
106
170
  .orderBy(sql `${sessions.createdAt} desc nulls last`, desc(sessions.id))
@@ -182,6 +246,37 @@ export function createPostgresReadStore(source, tenantId) {
182
246
  asOf: new Date().toISOString(),
183
247
  };
184
248
  }),
249
+ toolCalls: (id, turnId, page, access) => read(async (tx) => {
250
+ await requireSession(tx, id, access);
251
+ const cursor = readCursor(tenantId, "tool-calls", { sessionId: id, turnId });
252
+ const key = keyOf(cursor.decode(page.cursor), 2);
253
+ // In the order of their first send; a row written before R5 has no `startedAt`.
254
+ const startedAt = sql `coalesce(${effects.body}->>'startedAt', '')`;
255
+ const rows = await tx
256
+ .select({ id: effects.id, body: effects.body, startedAt })
257
+ .from(effects)
258
+ .where(and(eq(effects.sessionId, id), eq(effects.kind, "tool"), turnId === undefined ? undefined : eq(effects.turnId, turnId), key ? sql `(${startedAt}, ${effects.id}) > (${key[0]}, ${key[1]})` : undefined))
259
+ .orderBy(asc(startedAt), asc(effects.id))
260
+ .limit(page.limit + 1);
261
+ const items = rows.slice(0, page.limit);
262
+ const last = items.at(-1);
263
+ return {
264
+ calls: items.map((row) => publicToolCall(row.body)),
265
+ nextCursor: rows.length > page.limit && last ? cursor.encode([last.startedAt, last.id]) : null,
266
+ asOf: new Date().toISOString(),
267
+ };
268
+ }),
269
+ turnEvents: (id, turnId, access) => read(async (tx) => {
270
+ await requireSession(tx, id, access);
271
+ // The record has no turn column: one session's rows, on the turn id in the event (no
272
+ // index yet, R8).
273
+ const rows = await tx
274
+ .select({ body: sessionEvents.body })
275
+ .from(sessionEvents)
276
+ .where(and(eq(sessionEvents.sessionId, id), sql `${sessionEvents.body}->>'turnId' = ${turnId}`))
277
+ .orderBy(asc(sessionEvents.seq));
278
+ return rows.map((row) => row.body);
279
+ }),
185
280
  exportModel: (after, limit) => read(async (tx) => {
186
281
  const cursor = readCursor(tenantId, "model-export", {});
187
282
  const key = keyOf(cursor.decode(after), 2);
@@ -205,19 +300,19 @@ export function createPostgresReadStore(source, tenantId) {
205
300
  asOf: new Date().toISOString(),
206
301
  };
207
302
  }),
208
- sandboxes: (labels, page, access, grants) => read(async (tx) => {
209
- const cursor = readCursor(tenantId, "sandboxes", labels);
303
+ environments: (labels, page, access, grants) => read(async (tx) => {
304
+ const cursor = readCursor(tenantId, "environments", labels);
210
305
  const key = keyOf(cursor.decode(page.cursor), 1);
211
306
  const allowed = grants === undefined
212
307
  ? undefined
213
308
  : or(...grants.map((grant) => grant.endsWith("/*")
214
- ? sql `starts_with(${sandboxResources.id}, ${grant.slice(0, -1)})`
215
- : eq(sandboxResources.id, grant)));
309
+ ? sql `starts_with(${environments.id}, ${grant.slice(0, -1)})`
310
+ : eq(environments.id, grant)));
216
311
  const rows = await tx
217
312
  .select()
218
- .from(sandboxResources)
219
- .where(and(grants?.length === 0 ? sql `false` : allowed, key ? gt(sandboxResources.id, key[0]) : undefined, or(isNull(sandboxResources.desired), ne(sandboxResources.desired, "deleted")), ...Object.entries(labels).map(([k, v]) => sql `${sandboxResources.labels}::jsonb->>${k} = ${v}`)))
220
- .orderBy(asc(sandboxResources.id))
313
+ .from(environments)
314
+ .where(and(grants?.length === 0 ? sql `false` : allowed, key ? gt(environments.id, key[0]) : undefined, or(isNull(environments.desired), ne(environments.desired, "deleted")), ...Object.entries(labels).map(([k, v]) => sql `${environments.labels}::jsonb->>${k} = ${v}`)))
315
+ .orderBy(asc(environments.id))
221
316
  .limit(page.limit + 1);
222
317
  const items = [];
223
318
  for (const row of rows.slice(0, page.limit)) {
@@ -226,7 +321,7 @@ export function createPostgresReadStore(source, tenantId) {
226
321
  items: sql `coalesce(json_agg(json_build_object('id', ${sessions.id}, 'activeTurnId', ${sessions.body}->>'activeTurnId') order by ${sessions.id}), '[]'::json)`,
227
322
  })
228
323
  .from(sessions)
229
- .where(and(eq(sessions.sandboxId, row.id), accessWhere(access)));
324
+ .where(and(eq(sessions.environmentId, row.id), accessWhere(access)));
230
325
  const [compute] = await tx
231
326
  .select({ body: sandboxes.body })
232
327
  .from(sandboxes)
@@ -237,14 +332,19 @@ export function createPostgresReadStore(source, tenantId) {
237
332
  id: row.id,
238
333
  kind: row.kind,
239
334
  labels: row.labels,
240
- spec: row.spec,
335
+ recipe: row.recipe,
336
+ version: row.version,
337
+ appliedVersion: row.appliedVersion,
338
+ ownerUserId: row.ownerUserId,
241
339
  state: isPod
242
340
  ? row.observed === "running"
243
341
  ? "running"
244
342
  : row.observed === "creating"
245
343
  ? "creating"
246
344
  : "stopped"
247
- : (compute?.body?.state ?? "ready"),
345
+ : row.kind === "none"
346
+ ? "ready"
347
+ : (compute?.body?.state ?? "ready"),
248
348
  ...(isPod
249
349
  ? {
250
350
  pod: {
@@ -252,11 +352,11 @@ export function createPostgresReadStore(source, tenantId) {
252
352
  observed: row.observed,
253
353
  volumeGeneration: row.volumeGen,
254
354
  hostEpoch: row.hostEpoch,
255
- ...(row.expiresAt ? { expiresAt: new Date(row.expiresAt).toISOString() } : {}),
256
355
  ...(row.reason ? { reason: row.reason } : {}),
257
356
  },
258
357
  }
259
358
  : {}),
359
+ lastUsedAt: new Date(row.lastUsedAt).toISOString(),
260
360
  sessions: related.items,
261
361
  createdAt: row.createdAt,
262
362
  updatedAt: row.updatedAt,
@@ -264,7 +364,7 @@ export function createPostgresReadStore(source, tenantId) {
264
364
  }
265
365
  const last = items.at(-1);
266
366
  return {
267
- sandboxes: items,
367
+ environments: items,
268
368
  nextCursor: rows.length > page.limit && last ? cursor.encode([last.id]) : null,
269
369
  };
270
370
  }),
@@ -1,9 +1,9 @@
1
- import type { RecordWriter, SandboxRecordWriter } from "../../record/index.js";
1
+ import type { RecordWriter, EnvironmentRecordWriter } from "../../record/index.js";
2
2
  import type { Transaction } from "./db.js";
3
3
  /**
4
- * The sandbox stream writer of transaction `db` (`record/sandbox.ts`): the only inserts into
5
- * `sandbox_events`. The caller holds the sandbox row's lock, which orders its events.
4
+ * The Environment stream writer of transaction `db` (`record/environment.ts`): the only inserts
5
+ * into `environment_events`. The caller holds the Environment row's lock, which orders its events.
6
6
  */
7
- export declare function postgresSandboxRecordWriter(db: Transaction): SandboxRecordWriter;
7
+ export declare function postgresEnvironmentRecordWriter(db: Transaction): EnvironmentRecordWriter;
8
8
  /** The record writer of transaction `db`. */
9
9
  export declare function postgresRecordWriter(db: Transaction): RecordWriter;
@@ -5,22 +5,22 @@
5
5
  * run in the store's transaction under the caller's session row lock.
6
6
  */
7
7
  import { eq, sql } from "drizzle-orm";
8
- import { sandboxEvents, sessionEvents, sessionLogHeads, tenant } from "./schema.js";
8
+ import { environmentEvents, sessionEvents, sessionLogHeads, tenant } from "./schema.js";
9
9
  /**
10
- * The sandbox stream writer of transaction `db` (`record/sandbox.ts`): the only inserts into
11
- * `sandbox_events`. The caller holds the sandbox row's lock, which orders its events.
10
+ * The Environment stream writer of transaction `db` (`record/environment.ts`): the only inserts
11
+ * into `environment_events`. The caller holds the Environment row's lock, which orders its events.
12
12
  */
13
- export function postgresSandboxRecordWriter(db) {
13
+ export function postgresEnvironmentRecordWriter(db) {
14
14
  return {
15
- async nextSeq(sandboxId) {
15
+ async nextSeq(environmentId) {
16
16
  const [row] = await db
17
- .select({ next: sql `coalesce(max(${sandboxEvents.seq}) + 1, 0)`.mapWith(Number) })
18
- .from(sandboxEvents)
19
- .where(eq(sandboxEvents.sandboxId, sandboxId));
17
+ .select({ next: sql `coalesce(max(${environmentEvents.seq}) + 1, 0)`.mapWith(Number) })
18
+ .from(environmentEvents)
19
+ .where(eq(environmentEvents.environmentId, environmentId));
20
20
  return row?.next ?? 0;
21
21
  },
22
22
  async insert(row) {
23
- await db.insert(sandboxEvents).values(row);
23
+ await db.insert(environmentEvents).values(row);
24
24
  },
25
25
  };
26
26
  }
@@ -1,5 +1,12 @@
1
+ import type { LiveEvent } from "@nylorun/core/contracts";
1
2
  import type { Sql } from "postgres";
2
3
  import type { RecordReader } from "../../streams/relay/types.js";
3
4
  export declare function createPostgresRecordReader(sql: Sql, options: {
4
5
  tenantId: string;
5
6
  }): RecordReader;
7
+ /**
8
+ * The trace projector's read of a turn's start (R7.2): the `command.message` that started turn
9
+ * `turnId` of `sessionId`, or undefined when the log has none. One session's rows, filtered on
10
+ * the turn id in the event (the record has no turn column; no index yet, R8).
11
+ */
12
+ export declare function createPostgresTurnMessages(sql: Sql): (sessionId: string, turnId: string) => Promise<LiveEvent | undefined>;
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Reads the record back for the stream relay (Durable Streams §7.2–7.3): a session's rows to
3
3
  * refill a gap in S2, every log head to reconcile after a fresh slot, and the Tenant's current
4
- * basin generation to tell obsolete rows apart.
4
+ * basin generation to tell obsolete rows apart. The trace projector reads a turn's start
5
+ * (`createPostgresTurnMessages`).
5
6
  *
6
7
  * The database holds one Tenant, so the record has no Tenant column: the reader is made for
7
8
  * that Tenant (`tenantId`), fills `RecordRow.tenantId` and `LogHead.tenantId` with it, and
8
9
  * reads nothing for any other id.
9
10
  */
10
- import { and, asc, eq, gt, gte, lt } from "drizzle-orm";
11
+ import { and, asc, eq, gt, gte, lt, sql as sql_ } from "drizzle-orm";
11
12
  import { database, driverError } from "./db.js";
12
13
  import { sessionEvents, sessionLogHeads, tenant } from "./schema.js";
13
14
  export function createPostgresRecordReader(sql, options) {
@@ -58,3 +59,23 @@ export function createPostgresRecordReader(sql, options) {
58
59
  },
59
60
  };
60
61
  }
62
+ /**
63
+ * The trace projector's read of a turn's start (R7.2): the `command.message` that started turn
64
+ * `turnId` of `sessionId`, or undefined when the log has none. One session's rows, filtered on
65
+ * the turn id in the event (the record has no turn column; no index yet, R8).
66
+ */
67
+ export function createPostgresTurnMessages(sql) {
68
+ const db = database(sql);
69
+ return async (sessionId, turnId) => {
70
+ const [row] = await db
71
+ .select({ body: sessionEvents.body })
72
+ .from(sessionEvents)
73
+ .where(and(eq(sessionEvents.sessionId, sessionId), eq(sessionEvents.type, "command.message"), sql_ `${sessionEvents.body}->>'turnId' = ${turnId}`))
74
+ .orderBy(asc(sessionEvents.seq))
75
+ .limit(1)
76
+ .catch((error) => {
77
+ throw driverError(error);
78
+ });
79
+ return row?.body;
80
+ };
81
+ }