@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
@@ -10,8 +10,8 @@
10
10
  * 1. **One transaction per `tx` call, READ COMMITTED or stronger.** Nothing a
11
11
  * transaction wrote is visible to others before it commits, and nothing is
12
12
  * kept when `fn` throws: document writes, event sequences, record rows,
13
- * and `afterCommit` callbacks are all discarded, and `tx` rejects with the
14
- * error `fn` threw.
13
+ * and outbox requests are all discarded, and `tx` rejects with the error `fn`
14
+ * threw.
15
15
  * 2. **Session-scoped writes lock the session row first.** `lockSession` takes
16
16
  * a row lock (`SELECT … FOR UPDATE`) held until the transaction ends. Effect
17
17
  * intent and outcome, checkpoint settlement and event
@@ -25,18 +25,22 @@
25
25
  * generation, `streams/basin.ts`), and the cursor is
26
26
  * `base64url("<sessionId>:<seq>")` (see `record/cursor.ts`).
27
27
  * 4. **No external I/O inside `fn`.** No model, tool, MCP, sandbox, Restate or
28
- * S2 call, and no `fetch`, runs inside a transaction. A wake is written in the
29
- * transaction (`Tx.wake`, the wake outbox) and delivered after commit, and events
30
- * are delivered to commit listeners after commit (seam rule 1 and 2).
28
+ * S2 call, and no `fetch`, runs inside a transaction. What a transaction asks of
29
+ * Durable Session Execution (a wake, a pod sandbox's reconcile or timer) is written
30
+ * to the outbox in the transaction (`Tx.wake`, `Tx.signalSandbox`) and delivered
31
+ * after commit; what it tells the other processes goes on the control bus
32
+ * (`Tx.signal`); and events are delivered to commit listeners after commit (seam
33
+ * rule 1 and 2).
31
34
  * 5. **No nested transactions.** Calling `store.tx` from inside `fn` rejects.
32
35
  * A `Tx` must not be used after its `tx` call settles.
33
36
  * 6. **Post-commit order.** After a commit, the store first calls every commit
34
- * listener once with the transaction's events (in allocation order), then runs the `afterCommit` callbacks in registration
35
- * order and awaits them. Listener and callback failures are reported to the
36
- * store's error hook; they never reject `tx`, because the commit stands.
37
- * Recovery from a lost post-commit step is the Tenant sweep's job: a wake's
38
- * delivery is a callback too, and its outbox row is what the sweep delivers
39
- * again (`Tx.wake`).
37
+ * listener once with the transaction's events (in allocation order), then hands
38
+ * the transaction's outbox requests to the delivery (`deliverTo`), in the order
39
+ * they were written, without the caller waiting. Listener and delivery failures
40
+ * are reported to the store's error hook; they never reject `tx`, because the
41
+ * commit stands. A request whose delivery fails, or that a crash cuts off, stays
42
+ * in the outbox, which the Tenant sweep delivers again. Nothing else runs after
43
+ * commit: a step that is not written down is lost to a crash.
40
44
  * 7. **Documents are values.** `get` and queries return fresh copies; mutating
41
45
  * them changes nothing until `put`.
42
46
  * 8. **Ownership columns are store-managed.** `owner`, `epoch` and
@@ -58,9 +62,8 @@
58
62
  */
59
63
  import type { KeyRole } from "@nylorun/core/compatibility";
60
64
  import type { RecordReader } from "../streams/relay/types.js";
61
- import type { Wake } from "../execution/types.js";
62
- import type { EventPayload, EventType, LiveEvent, SandboxEvent, SandboxEventPayload, SandboxEventType, SandboxKind, SandboxPodDesired, SandboxPodObserved, SessionEventOf } from "@nylorun/core/contracts";
63
- import type { SandboxManifest } from "@nylorun/core/define";
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";
64
67
  import type { HostEffect } from "@nylorun/harness/run";
65
68
  import type { ArtifactRow, ArtifactVersionRow, DefinitionFileRow, ModelBudgetRow, ModelUsageRow, ModelUsageWrite, PrincipalRow, ToolCrossingRow, SigningKeyRow, VaultAuditRow, VaultCredentialRow, VaultIdempotencyRow, VaultRow } from "./postgres/schema.js";
66
69
  /** Tables holding JSON documents keyed by `id`. */
@@ -69,7 +72,9 @@ export declare const DOC_TABLES: readonly DocTable[];
69
72
  /** Session lifecycle (architecture §10.2). */
70
73
  export type SessionStatus = "idle" | "runnable" | "running" | "waiting" | "paused" | "uncertain" | "completed" | "failed" | "cancelled";
71
74
  /** Effect states (§10.3), plus the flow host's `queued` and `cancelled`. */
72
- export type EffectStatus = "pending" | "invoking" | "completed" | "uncertain" | "queued" | "cancelled";
75
+ export type EffectStatus = "pending" | "invoking"
76
+ /** A tool call the Tool Gate held for a person's approval (R5 step 2): nothing was sent. */
77
+ | "held" | "completed" | "uncertain" | "queued" | "cancelled";
73
78
  export type EffectKind = HostEffect["kind"];
74
79
  /**
75
80
  * Ownership of a session by a Worker (§10.6). Read with the session; written
@@ -122,28 +127,51 @@ export interface DefinitionDoc {
122
127
  export interface SandboxDoc {
123
128
  key: string;
124
129
  }
125
- /** A sandbox resource (`sandbox_resources`): what `PUT /v1/sandboxes/{id}` created. */
126
- export interface SandboxResource {
130
+ /** An Environment (`environments`, D55): what `PUT /v1/environments/{id}` created. */
131
+ export interface Environment {
127
132
  id: string;
128
- kind: SandboxKind;
129
- /** The spec resolved against the Tenant's limits when the sandbox was created. */
130
- spec: SandboxManifest;
133
+ kind: EnvironmentKind;
131
134
  labels: Record<string, string>;
135
+ /** What the app asked for, resolved against the Tenant's limits. */
136
+ recipe: EnvironmentRecipe;
137
+ /** The recipe the current pod or workspace runs (copied from `recipe` at the next wake). */
138
+ applied: EnvironmentRecipe;
139
+ version: number;
140
+ appliedVersion: number;
141
+ /** The owner guard: only this owner's sessions may name it. */
142
+ ownerUserId?: string;
143
+ /** ISO time it was last used (`lifecycle.deleteAfter` counts from it). */
144
+ lastUsedAt: string;
132
145
  createdAt: string;
133
146
  updatedAt: string;
134
- /** Kind `pod`: the pod's lifecycle (F7.2). Absent for a virtual sandbox. */
147
+ /** Kind `pod`: the pod's lifecycle (F7.2). Absent otherwise. */
135
148
  pod?: SandboxPodState;
149
+ /** The disk lease while a snapshot, restore or seed copies the disk (R2d step 3). */
150
+ diskOp?: DiskOperation;
151
+ /** The disk version the next volume starts from (R2d step 3). */
152
+ diskSeed?: {
153
+ artifactId: string;
154
+ version: number;
155
+ };
156
+ }
157
+ /** A disk lease (R2d step 3): what holds the disk, since when, and until when unless renewed. */
158
+ export interface DiskOperation {
159
+ kind: "snapshot" | "restore" | "seed";
160
+ version?: number;
161
+ /** ISO times. */
162
+ startedAt: string;
163
+ until: string;
136
164
  }
137
165
  /**
138
- * A pod sandbox's lifecycle columns (`sandbox_resources`, F7.2, D34): what the Runtime wants
166
+ * A pod Environment's lifecycle columns (`environments`, F7.2, D34): what the Runtime wants
139
167
  * (`desired`), what it last saw (`observed`), and the fences of its incarnations.
140
168
  */
141
169
  export interface SandboxPodState {
142
170
  /** The agent-sandbox Sandbox's name (`podName`), of generation `volumeGen`. */
143
171
  k8sName: string;
144
172
  volumeGen: number;
145
- desired: SandboxPodDesired;
146
- observed: SandboxPodObserved;
173
+ desired: PodDesired;
174
+ observed: PodObserved;
147
175
  /** The UID of the pod that last joined. */
148
176
  podUid?: string;
149
177
  /** Bumped by every join, relaunch, loss and reset; host and egress tokens carry it. */
@@ -154,7 +182,6 @@ export interface SandboxPodState {
154
182
  rev: number;
155
183
  /** ISO times. */
156
184
  lastActiveAt?: string;
157
- expiresAt?: string;
158
185
  startedAt?: string;
159
186
  reason?: string;
160
187
  /** An earlier incarnation's Sandbox still to delete (after a reset or a loss). */
@@ -229,7 +256,7 @@ export interface StoreCounts {
229
256
  sandboxes: number;
230
257
  definitions: number;
231
258
  }
232
- export type ResetScope = "sessions" | "sandboxes" | "all";
259
+ export type ResetScope = "sessions" | "environments" | "all";
233
260
  /** Result of `takeOwnership`. */
234
261
  export type TakeOwnership = {
235
262
  status: "owned";
@@ -272,6 +299,14 @@ export interface SessionStore {
272
299
  * throw; what it throws goes to `onError`, as do listener failures.
273
300
  */
274
301
  followSignals(onSignal: (signal: ControlSignal) => void, options?: FollowSignalsOptions): Promise<SignalFollower>;
302
+ /**
303
+ * Sets where committed outbox requests go (`Tx.wake`, `Tx.signalSandbox`): after each
304
+ * commit the store hands them to `deliver` without waiting for it, so a slow execution
305
+ * never holds up the caller. Without one, they wait in the outbox for the Tenant sweep.
306
+ */
307
+ deliverTo(deliver: OutboxDelivery | undefined): void;
308
+ /** Resolves once every delivery started so far has settled (`close` waits for them too). */
309
+ delivered(): Promise<void>;
275
310
  close(): Promise<void>;
276
311
  }
277
312
  /** A wake as the outbox delivers it: its `dedupeKey` is the request's idempotency key. */
@@ -279,18 +314,38 @@ export type OutboxWake = Wake & {
279
314
  dedupeKey: string;
280
315
  };
281
316
  /**
282
- * Hands an outbox request to Durable Session Execution (`Tx.wake`). Resolving means the
283
- * execution accepted it; `false` means it was not delivered and stays in the outbox, as a
284
- * rejection does, without an error.
317
+ * What a committed transaction asked of Durable Session Execution, as the outbox delivers it:
318
+ * a wake (`Tx.wake`), or a pod sandbox's reconcile or timer (`Tx.signalSandbox`; a reconcile
319
+ * carries its idempotency key, `signal:<row id>`).
285
320
  */
286
- export type WakeDelivery = (sessionId: string, wake: OutboxWake) => boolean | void | Promise<boolean | void>;
287
- /** A wake outbox request not yet delivered (`Tx.pendingWakes`). */
288
- export interface PendingWake {
289
- id: string;
321
+ export type OutboxRequest = {
322
+ kind: "wake";
290
323
  sessionId: string;
291
324
  wake: OutboxWake;
325
+ } | {
326
+ kind: "sandbox";
327
+ sandboxId: string;
328
+ signal: SandboxSignal;
329
+ };
330
+ /**
331
+ * Hands an outbox request to Durable Session Execution (`SessionStore.deliverTo`). Resolving
332
+ * means the execution accepted it; `false` means it was not delivered and stays in the
333
+ * outbox, due at the sweep's next pass, as after a rejection, which also goes to the store's
334
+ * error hook.
335
+ */
336
+ export type OutboxDelivery = (request: OutboxRequest) => boolean | void | Promise<boolean | void>;
337
+ /** An outbox row: which table (`kind`) and its id. */
338
+ export type OutboxRef = {
339
+ kind: OutboxRequest["kind"];
340
+ id: string;
341
+ };
342
+ /** An outbox request not yet delivered (`Tx.pendingOutbox`). */
343
+ export interface PendingRequest extends OutboxRef {
344
+ request: OutboxRequest;
292
345
  /** ISO time it was written. */
293
346
  createdAt: string;
347
+ /** The sweep's failed deliveries of it so far (`failOutbox`). */
348
+ attempts: number;
294
349
  }
295
350
  /**
296
351
  * A signal on the control bus (D21, D48): one process tells the others with the Tenant open.
@@ -307,6 +362,15 @@ export type ControlSignal = {
307
362
  /** The Tenant's sessions were reset and it moved to basin generation `generation`. */
308
363
  type: "sessions.reset";
309
364
  generation: number;
365
+ } | {
366
+ /**
367
+ * Pod sandbox `sandboxId`'s host epoch moved to `epoch` (a join, a stop, a reset, a
368
+ * deletion, a loss): every process closes its connections hosting it at an older epoch
369
+ * (`HarnessApiServer.revokeHost`).
370
+ */
371
+ type: "host.revoked";
372
+ sandboxId: string;
373
+ epoch: number;
310
374
  };
311
375
  export interface FollowSignalsOptions {
312
376
  /** How often recent signals are read back. Default 5 s. */
@@ -327,7 +391,7 @@ export interface SessionStoreOptions {
327
391
  tenantId: string;
328
392
  /** Clock for event `time`. Defaults to `() => new Date()`. */
329
393
  now?: () => Date;
330
- /** Receives listener and `afterCommit` failures, which never reject `tx`. */
394
+ /** Receives listener and outbox delivery failures, which never reject `tx`. */
331
395
  onError?: (error: unknown) => void;
332
396
  }
333
397
  /** The transaction handle. Valid only inside its `tx` callback. */
@@ -365,29 +429,46 @@ export interface Tx {
365
429
  * commit listeners. Rejects when the session does not exist. The returned
366
430
  * cursor is final.
367
431
  */
368
- event<T extends EventType>(sessionId: string, turnId: string | null, type: T, payload: EventPayload<T>): Promise<SessionEventOf<T>>;
369
- /**
370
- * Runs `fn` after a successful commit (never on rollback): a sandbox signal or a host
371
- * revocation. Nothing writes it down, so a crash after commit loses it. A wake goes
372
- * through `wake`, which does.
373
- */
374
- afterCommit(fn: () => void | Promise<void>): void;
432
+ event<T extends EventType>(sessionId: string, turnId: string | null, type: T, payload: EventPayload<T>,
433
+ /** Who wrote it (R4.9): absent, core, observed. */
434
+ writer?: EventWriter): Promise<SessionEventOf<T>>;
375
435
  /**
376
436
  * Asks for an advance of `sessionId` once this transaction commits. The request is a row
377
437
  * of the wake outbox written in this transaction, so it commits, or rolls back, with what
378
- * caused it: a commit never loses its wake. After commit, in `afterCommit` order, the
379
- * store calls `deliver` (`DurableExecution.wake`, through the Tenant) and deletes the row
380
- * once it resolves to anything but `false`. A request `deliver` declines (`false`) or
381
- * rejects, or one a crash cut off, stays in the outbox until the Tenant sweep delivers it
382
- * (`pendingWakes`, `deleteWakes`). `deliver` gets the request's idempotency key as
383
- * `dedupeKey`: the wake's own, or `wake:<row id>` without one, so a request delivered
384
- * twice still causes one advance.
385
- */
386
- wake(sessionId: string, wake: Wake, deliver: WakeDelivery): Promise<void>;
387
- /** Outbox requests written before `before`, oldest first. */
388
- pendingWakes(before: Date, limit: number): Promise<PendingWake[]>;
438
+ * caused it: a commit never loses its wake. After commit the store hands it to the
439
+ * delivery (`SessionStore.deliverTo`, `DurableExecution.wake` through the Tenant) without
440
+ * waiting, and deletes the row once that resolves to anything but `false`. A request the
441
+ * delivery declines or rejects is due at the sweep's next pass; one a crash cut off, or
442
+ * still being sent, is due once it is older than the sweep's grace (`pendingOutbox`). The
443
+ * delivery gets the request's idempotency key as `dedupeKey`: the wake's own, or
444
+ * `wake:<row id>` without one, so a request delivered twice still causes one advance.
445
+ */
446
+ wake(sessionId: string, wake: Wake): Promise<void>;
447
+ /**
448
+ * Asks for a pod sandbox's reconcile, or sets one of its timers, once this transaction
449
+ * commits (`DurableExecution.sandbox`): a row of the sandbox signal outbox, delivered as a
450
+ * wake's is. A reconcile is delivered with the key `signal:<row id>`, so one delivered
451
+ * twice runs once.
452
+ */
453
+ signalSandbox(sandboxId: string, signal: SandboxSignal): Promise<void>;
454
+ /**
455
+ * Outbox requests that are due, by the database's clock: written more than `graceMs` ago,
456
+ * or whose delivery failed and whose retry time has come (`failOutbox`), and not parked.
457
+ * Oldest first, a retried one by its retry time, so a request that keeps failing never
458
+ * holds the head of the queue.
459
+ */
460
+ pendingOutbox(graceMs: number, limit: number): Promise<PendingRequest[]>;
389
461
  /** Deletes delivered outbox requests. */
390
- deleteWakes(ids: readonly string[]): Promise<void>;
462
+ deleteOutbox(rows: readonly OutboxRef[]): Promise<void>;
463
+ /**
464
+ * Records a failed delivery of outbox request `row`: one more attempt, due again `retryInMs`
465
+ * from now (the database's clock). With `park` it is never due again: it stays in the
466
+ * outbox, with when it was parked, for an operator to send again or delete.
467
+ */
468
+ failOutbox(row: OutboxRef, retry: {
469
+ retryInMs: number;
470
+ park: boolean;
471
+ }): Promise<void>;
391
472
  /**
392
473
  * Locks the session and takes ownership when it has no owner or the owner's
393
474
  * lease ended at or before `now`; then increments `epoch` and sets the lease
@@ -416,9 +497,10 @@ export interface Tx {
416
497
  sessionsWithStatus<T extends SessionDoc = SessionDoc>(statuses: readonly (SessionStatus | (string & {}))[]): Promise<StoredSession<T>[]>;
417
498
  /**
418
499
  * Sessions `running` or `runnable` with no owner or an owner whose lease
419
- * ended at or before `now`, oldest lease first. The sweep re-wakes these.
500
+ * ended at or before `now`, by id, the first `limit` after `after` (a page:
501
+ * pass the last id of the previous one). The sweep re-wakes these.
420
502
  */
421
- orphanedSessions<T extends SessionDoc = SessionDoc>(now: Date, limit: number): Promise<StoredSession<T>[]>;
503
+ orphanedSessions<T extends SessionDoc = SessionDoc>(now: Date, limit: number, after?: string): Promise<StoredSession<T>[]>;
422
504
  /** Sessions, optionally of one agent and one owner (`ownerUserId`), by id. */
423
505
  listSessions<T extends SessionDoc = SessionDoc>(filter?: {
424
506
  agentId?: string;
@@ -426,37 +508,54 @@ export interface Tx {
426
508
  }): Promise<StoredSession<T>[]>;
427
509
  listDefinitions<T extends DefinitionDoc = DefinitionDoc>(): Promise<T[]>;
428
510
  listSandboxes<T extends SandboxDoc = SandboxDoc>(): Promise<T[]>;
429
- /** The sandbox resource `id`; with `lock`, its row is locked until the transaction ends. */
430
- sandboxResource(id: string, options?: {
511
+ /** Environment `id`; with `lock`, its row is locked until the transaction ends. */
512
+ environment(id: string, options?: {
431
513
  lock?: boolean;
432
- }): Promise<SandboxResource | undefined>;
433
- /**
434
- * Creates the sandbox resource unless one with its id exists, while the Tenant holds fewer
435
- * than `limit`: `exists` and `limit` write nothing. Serialized with every other create.
436
- */
437
- createSandboxResource(row: SandboxResource, limit: number): Promise<"created" | "exists" | "limit">;
438
- updateSandboxLabels(id: string, labels: Record<string, string>, updatedAt: string): Promise<void>;
439
- /** Replaces a sandbox's spec (a pod's new `lifecycle.ttl`; the caller holds the row's lock). */
440
- updateSandboxSpec(id: string, spec: SandboxManifest, updatedAt: string): Promise<void>;
441
- /** Changes a pod sandbox's lifecycle columns (the caller holds the row's lock). */
442
- updateSandboxPod(id: string, patch: SandboxPodPatch, updatedAt: string): Promise<void>;
443
- deleteSandboxResource(id: string): Promise<void>;
444
- /** Sandbox resources by id, those with every label in `labels` when it is given. */
445
- listSandboxResources(filter?: {
514
+ }): Promise<Environment | undefined>;
515
+ /**
516
+ * Creates the Environment unless one with its id exists, while the Tenant holds fewer than
517
+ * `limit`: `exists` and `limit` write nothing. Serialized with every other create.
518
+ */
519
+ createEnvironment(row: Environment, limit: number): Promise<"created" | "exists" | "limit">;
520
+ /**
521
+ * Changes an Environment's columns other than its pod's (the caller holds the row's lock):
522
+ * labels, recipe and version, the applied recipe and its version, the owner guard (`null`
523
+ * clears it), and when it was last used.
524
+ */
525
+ updateEnvironment(id: string, patch: {
446
526
  labels?: Record<string, string>;
447
- }): Promise<SandboxResource[]>;
448
- /** Sessions attached to sandbox resource `sandboxId` (`Session.sandboxId`), by id. */
449
- sessionsOnSandbox<T extends SessionDoc = SessionDoc>(sandboxId: string): Promise<StoredSession<T>[]>;
527
+ recipe?: EnvironmentRecipe;
528
+ version?: number;
529
+ applied?: EnvironmentRecipe;
530
+ appliedVersion?: number;
531
+ ownerUserId?: string | null;
532
+ lastUsedAt?: string;
533
+ diskOp?: DiskOperation | null;
534
+ diskSeed?: {
535
+ artifactId: string;
536
+ version: number;
537
+ } | null;
538
+ }, updatedAt: string): Promise<void>;
539
+ /** Changes a pod Environment's lifecycle columns (the caller holds the row's lock). */
540
+ updateEnvironmentPod(id: string, patch: SandboxPodPatch, updatedAt: string): Promise<void>;
541
+ /** Deletes the Environment; its vault and credentials go with it (foreign key). */
542
+ deleteEnvironment(id: string): Promise<void>;
543
+ /** Environments by id, those with every label in `labels` when it is given. */
544
+ listEnvironments(filter?: {
545
+ labels?: Record<string, string>;
546
+ }): Promise<Environment[]>;
547
+ /** Sessions that name Environment `environmentId` (`Session.environmentId`), by id. */
548
+ sessionsOnEnvironment<T extends SessionDoc = SessionDoc>(environmentId: string): Promise<StoredSession<T>[]>;
450
549
  /**
451
- * Appends an event to the sandbox's lifecycle stream through the record module. The caller
452
- * holds the sandbox row's lock (or created the row in this transaction).
550
+ * Appends an event to the Environment's lifecycle stream through the record module. The
551
+ * caller holds the Environment row's lock (or created the row in this transaction).
453
552
  */
454
- sandboxEvent<T extends SandboxEventType>(sandboxId: string, type: T, payload: SandboxEventPayload<T>): Promise<SandboxEvent>;
455
- /** The sandbox's lifecycle stream, from `fromSeq` on, at most `limit` events. */
456
- sandboxEvents(sandboxId: string, options?: {
553
+ 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?: {
457
556
  fromSeq?: number;
458
557
  limit?: number;
459
- }): Promise<SandboxEvent[]>;
558
+ }): Promise<EnvironmentEvent[]>;
460
559
  /** `invoking` effects of one session; takeover turns them `uncertain`. */
461
560
  invokingEffects<T extends EffectDoc = EffectDoc>(sessionId: string): Promise<T[]>;
462
561
  effectsForSession<T extends EffectDoc = EffectDoc>(sessionId: string, filter?: SessionEffectFilter): Promise<T[]>;
@@ -492,20 +591,20 @@ export interface Tx {
492
591
  putPrincipal(id: string, tokenHash: string, createdAt: string, role?: KeyRole): Promise<PrincipalRow>;
493
592
  /** Deletes principal `id`; false when there was none. */
494
593
  deletePrincipal(id: string): Promise<boolean>;
495
- /** Rejects on a duplicate id, or a second `host` vault. */
594
+ /** Rejects on a duplicate id, or a second vault for one Environment. */
496
595
  insertVault(row: VaultRow): Promise<void>;
497
596
  getVault(id: string): Promise<VaultRow | undefined>;
498
- /** User vaults of one owner, ordered by `createdAt`, then id. */
499
- vaultsByOwner(ownerUserId: string): Promise<VaultRow[]>;
500
- /** Installation vaults (any session may attach them), ordered by `createdAt`, then id. */
501
- installationVaults(): Promise<VaultRow[]>;
597
+ /** The vault of Environment `environmentId` (D56). */
598
+ vaultOfEnvironment(environmentId: string): Promise<VaultRow | undefined>;
502
599
  updateVaultMetadata(id: string, metadataJson: string | null): Promise<void>;
503
600
  /** Deletes the vault and its credentials. */
504
601
  deleteVault(id: string): Promise<void>;
505
602
  /** Rejects on a duplicate id or an unknown vault. */
506
603
  insertCredential(row: VaultCredentialRow): Promise<void>;
507
- /** Looks a credential up by id alone (attachment checks); callers compare `vaultId`. */
604
+ /** Looks a credential up by id alone; callers compare `vaultId`. */
508
605
  getCredential(id: string): Promise<VaultCredentialRow | undefined>;
606
+ /** The credential named `name` in vault `vaultId` (names are unique per vault). */
607
+ credentialByName(vaultId: string, name: string): Promise<VaultCredentialRow | undefined>;
509
608
  /** Credentials of one vault, ordered by `createdAt`, then id. */
510
609
  credentialsForVault(vaultId: string, filter?: {
511
610
  type?: VaultCredentialRow["type"];
@@ -543,6 +642,11 @@ export interface Tx {
543
642
  countSigningKeys(): Promise<number>;
544
643
  /** Appends a row, setting `duplicate` when one with the same `effectKey` exists; returns it. */
545
644
  recordModelUsage(row: ModelUsageWrite): Promise<ModelUsageRow>;
645
+ /**
646
+ * The first row the gate recorded for the model call `effectKey` (R7.1), never a `duplicate`;
647
+ * undefined when no gate metered it.
648
+ */
649
+ modelUsageOf(effectKey: string): Promise<ModelUsageRow | undefined>;
546
650
  modelUsageTotals(query: ModelUsageQuery): Promise<ModelUsageTotals>;
547
651
  /** Every budget, ordered by scope, then scope id. */
548
652
  listModelBudgets(): Promise<ModelBudgetRow[]>;
@@ -551,6 +655,11 @@ export interface Tx {
551
655
  toolCrossing(key: string): Promise<ToolCrossingRow | undefined>;
552
656
  /** Inserts the running call's row; false when a row with `key` already exists. */
553
657
  startToolCrossing(row: Pick<ToolCrossingRow, "key" | "hash" | "startedAt">): Promise<boolean>;
658
+ /**
659
+ * Starts a lost call's row again (R5.3): `startedAt` moves on, on a row with no answer and
660
+ * `hash`. False when the row is gone, settled, or another request's.
661
+ */
662
+ restartToolCrossing(row: Pick<ToolCrossingRow, "key" | "hash" | "startedAt">): Promise<boolean>;
554
663
  /** Records the call's answer. */
555
664
  settleToolCrossing(key: string, answer: unknown, settledAt: string): Promise<void>;
556
665
  /** Deletes rows that settled before `before`; returns how many. */
@@ -559,6 +668,10 @@ export interface Tx {
559
668
  artifact(id: string, options?: {
560
669
  lock?: boolean;
561
670
  }): Promise<ArtifactRow | undefined>;
671
+ /** The folder artifact that holds Environment `environmentId`'s disk versions (R2d step 3). */
672
+ diskArtifact(environmentId: string, options?: {
673
+ lock?: boolean;
674
+ }): Promise<ArtifactRow | undefined>;
562
675
  /** Artifacts by creation time, then id: those of one session when `sessionId` is given. */
563
676
  listArtifacts(filter?: {
564
677
  sessionId?: string;
@@ -575,6 +688,11 @@ export interface Tx {
575
688
  artifactVersions(artifactId: string): Promise<ArtifactVersionRow[]>;
576
689
  /** Deletes the artifact and its versions; returns the versions' blob keys (empty when none). */
577
690
  deleteArtifact(id: string): Promise<string[]>;
691
+ /**
692
+ * Deletes one version of a locked artifact (its content rows go with it) and returns its blob
693
+ * key; the latest version becomes the newest one left. The artifact itself stays, even empty.
694
+ */
695
+ deleteArtifactVersion(artifactId: string, version: number): Promise<string | undefined>;
578
696
  /**
579
697
  * Serializes the Tenant's artifact writes: held until the transaction ends. Take it before
580
698
  * reading the total a new version must fit under.
@@ -620,9 +738,9 @@ export interface Tx {
620
738
  * Tenant's record rows and log heads. The Tenant moves to the next basin
621
739
  * generation and the current one is retired, so session ids it frees start again in an
622
740
  * empty basin;
623
- * - `sandboxes`: sandbox records, sandbox resources and their lifecycle streams;
741
+ * - `environments`: sandbox records, Environments with their vaults, and their lifecycle
742
+ * streams;
624
743
  * - `all`: both, plus definitions (and which definition files they use),
625
- * user vaults with their credentials,
626
744
  * the model usage ledger, the model budgets and Tenant-wide artifacts. The host vault, principals, signing keys,
627
745
  * settings, audit and vault idempotency rows stay.
628
746
  */
@@ -22,6 +22,7 @@
22
22
  *
23
23
  * The relay never writes the record and never deletes from S2.
24
24
  */
25
+ import { sleep } from "../../retry.js";
25
26
  import { basinOf } from "../basin.js";
26
27
  import { sessionStream } from "../types.js";
27
28
  const RETRY_MIN_MS = 50;
@@ -351,16 +352,3 @@ export function createStreamRelay(options) {
351
352
  };
352
353
  }
353
354
  const messageOf = (error) => (error instanceof Error ? error.message : String(error));
354
- function sleep(ms, signal) {
355
- return new Promise((resolve) => {
356
- if (signal.aborted)
357
- return resolve();
358
- const done = () => {
359
- clearTimeout(timer);
360
- signal.removeEventListener("abort", done);
361
- resolve();
362
- };
363
- const timer = setTimeout(done, ms);
364
- signal.addEventListener("abort", done, { once: true });
365
- });
366
- }
@@ -1,6 +1,6 @@
1
1
  import type { AdvanceResult } from "../execution/types.js";
2
2
  import type { Tx } from "../store/types.js";
3
- import type { Session, TenantContext } from "./context.js";
3
+ import { type Session, type TenantContext } from "./context.js";
4
4
  /**
5
5
  * Advance session `id` if it has a runnable checkpoint (`WorkerHandlers.advance`). Returns
6
6
  * `busy` when another advance holds a live lease, `done` otherwise.
@@ -9,12 +9,13 @@ export declare function advance(ctx: TenantContext, id: string, signal: AbortSig
9
9
  /**
10
10
  * Takeover (§10.5 step 2, §11.4), inside the transaction that took ownership: the dead
11
11
  * owner's `invoking` effects become `uncertain` and are never invoked again, except model
12
- * calls when the gate recovers them (P1.2) and remote MCP and HTTP tool calls when the Tool
13
- * Gate does (F4.1 G3): those stay `invoking`, and the replay re-sends them. When one belongs
14
- * to the active turn, the session becomes `uncertain` too, with an `effect.uncertain` event.
15
- * Mutates and writes `s`. Returns the effect ids it marked.
12
+ * calls when the gate recovers them (P1.2) and every tool call (R5.3): those stay `invoking`,
13
+ * and the replay decides. It re-sends a model call, and an MCP or HTTP tool call with the same
14
+ * key; a sandbox or core-served tool call fails to the model as `tool.interrupted`
15
+ * (`harness-api/tool-journal.ts`). When an effect marked `uncertain` belongs to the active
16
+ * turn, the session becomes `uncertain` too, with an `effect.uncertain` event. Mutates and
17
+ * writes `s`. Returns the effect ids it marked.
16
18
  */
17
19
  export declare function takeOver(t: Tx, s: Session, options?: {
18
20
  recoversModelCalls?: boolean;
19
- recoversToolCalls?: boolean;
20
21
  }): Promise<string[]>;