@nylorun/runtime 0.23.0-beta → 0.25.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 (301) hide show
  1. package/CHANGELOG.md +252 -0
  2. package/README.md +87 -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/card.d.ts +11 -1
  10. package/dist/api/a2a/card.js +26 -2
  11. package/dist/api/a2a/endpoint.js +9 -3
  12. package/dist/api/a2a/protocol.d.ts +25 -2
  13. package/dist/api/a2a/protocol.js +20 -3
  14. package/dist/api/a2a/routes.d.ts +6 -1
  15. package/dist/api/a2a/routes.js +67 -9
  16. package/dist/api/a2a/tasks.js +2 -1
  17. package/dist/api/ag-ui/endpoint.js +8 -3
  18. package/dist/api/ag-ui/history.d.ts +6 -1
  19. package/dist/api/ag-ui/history.js +39 -3
  20. package/dist/api/ag-ui/routes.d.ts +4 -1
  21. package/dist/api/ag-ui/routes.js +63 -20
  22. package/dist/api/ag-ui/translate.js +3 -2
  23. package/dist/api/components.d.ts +5327 -1091
  24. package/dist/api/components.js +40 -16
  25. package/dist/api/gate/keys.d.ts +2 -1
  26. package/dist/api/gate/keys.js +14 -1
  27. package/dist/api/gate/routes.d.ts +6 -0
  28. package/dist/api/gate/routes.js +52 -3
  29. package/dist/api/http/app.js +17 -6
  30. package/dist/api/http/body.d.ts +3 -3
  31. package/dist/api/http/body.js +7 -7
  32. package/dist/api/http/define.d.ts +6 -0
  33. package/dist/api/http/define.js +13 -1
  34. package/dist/api/http/respond.js +1 -1
  35. package/dist/api/http/routes/artifacts.js +35 -8
  36. package/dist/api/http/routes/coverage.d.ts +9 -0
  37. package/dist/api/http/routes/coverage.js +34 -0
  38. package/dist/api/http/routes/environment-manifests.d.ts +9 -0
  39. package/dist/api/http/routes/environment-manifests.js +118 -0
  40. package/dist/api/http/routes/environments.d.ts +11 -0
  41. package/dist/api/http/routes/environments.js +332 -0
  42. package/dist/api/http/routes/mcp.d.ts +1 -1
  43. package/dist/api/http/routes/mcp.js +2 -2
  44. package/dist/api/http/routes/me.js +1 -1
  45. package/dist/api/http/routes/reads.d.ts +94 -9
  46. package/dist/api/http/routes/reads.js +54 -5
  47. package/dist/api/http/routes/sessions.js +24 -9
  48. package/dist/api/http/routes/tenant.js +65 -15
  49. package/dist/api/inbound-files.d.ts +65 -0
  50. package/dist/api/inbound-files.js +115 -0
  51. package/dist/api/openapi.js +48 -28
  52. package/dist/artifacts/disk.d.ts +59 -0
  53. package/dist/artifacts/disk.js +569 -0
  54. package/dist/artifacts/export.d.ts +1 -1
  55. package/dist/artifacts/export.js +2 -3
  56. package/dist/artifacts/folders.d.ts +6 -3
  57. package/dist/artifacts/folders.js +17 -12
  58. package/dist/artifacts/media-types.d.ts +15 -0
  59. package/dist/artifacts/media-types.js +56 -4
  60. package/dist/artifacts/parts.d.ts +12 -3
  61. package/dist/artifacts/parts.js +15 -4
  62. package/dist/artifacts/service.d.ts +2 -1
  63. package/dist/artifacts/service.js +6 -1
  64. package/dist/artifacts/workspace.d.ts +5 -5
  65. package/dist/artifacts/workspace.js +7 -19
  66. package/dist/core/flow-host.d.ts +3 -4
  67. package/dist/core/flow-host.js +5 -2
  68. package/dist/core/provider.d.ts +3 -2
  69. package/dist/core/provider.js +5 -37
  70. package/dist/core/sandbox-routes.d.ts +2 -21
  71. package/dist/core/sandbox-routes.js +11 -44
  72. package/dist/core/turn-manifest.d.ts +3 -2
  73. package/dist/core/turn-manifest.js +29 -5
  74. package/dist/execution/types.d.ts +1 -1
  75. package/dist/gates/contract.d.ts +3 -24
  76. package/dist/gates/contract.js +1 -36
  77. package/dist/gates/egress-credentials.d.ts +9 -0
  78. package/dist/gates/egress-credentials.js +11 -4
  79. package/dist/gates/egress.d.ts +22 -1
  80. package/dist/gates/egress.js +116 -10
  81. package/dist/gates/fixture-model.d.ts +28 -0
  82. package/dist/gates/fixture-model.js +92 -0
  83. package/dist/gates/handler.js +4 -4
  84. package/dist/gates/http-tool.d.ts +9 -6
  85. package/dist/gates/http-tool.js +11 -17
  86. package/dist/gates/in-process.d.ts +5 -0
  87. package/dist/gates/in-process.js +12 -1
  88. package/dist/gates/journal.d.ts +30 -0
  89. package/dist/gates/journal.js +51 -0
  90. package/dist/gates/mcp-handler.d.ts +4 -0
  91. package/dist/gates/mcp-handler.js +7 -4
  92. package/dist/gates/meter.d.ts +10 -2
  93. package/dist/gates/meter.js +20 -5
  94. package/dist/gates/policy.d.ts +49 -0
  95. package/dist/gates/policy.js +132 -0
  96. package/dist/gates/tenant-vaults.d.ts +7 -7
  97. package/dist/gates/tenant-vaults.js +5 -5
  98. package/dist/gates/tool-calls.d.ts +10 -5
  99. package/dist/gates/tool-calls.js +28 -10
  100. package/dist/gates/tool-client.d.ts +8 -0
  101. package/dist/gates/tool-client.js +27 -11
  102. package/dist/gates/tool-contract.d.ts +11 -69
  103. package/dist/gates/tool-contract.js +1 -85
  104. package/dist/gates/tool-gate.d.ts +1 -1
  105. package/dist/harness/calls.d.ts +14 -7
  106. package/dist/harness/calls.js +30 -24
  107. package/dist/harness/executors.js +12 -9
  108. package/dist/harness/main.js +24 -5
  109. package/dist/harness/pod.d.ts +39 -0
  110. package/dist/harness/pod.js +46 -0
  111. package/dist/harness/service.d.ts +0 -1
  112. package/dist/harness/service.js +17 -9
  113. package/dist/harness/setup.d.ts +29 -0
  114. package/dist/harness/setup.js +174 -0
  115. package/dist/harness/ws-client.js +3 -2
  116. package/dist/harness/ws-port.d.ts +1 -5
  117. package/dist/harness/ws-port.js +1 -5
  118. package/dist/harness-api/in-process.js +3 -3
  119. package/dist/harness-api/record.d.ts +15 -1
  120. package/dist/harness-api/record.js +180 -50
  121. package/dist/harness-api/redact.d.ts +3 -0
  122. package/dist/harness-api/redact.js +45 -0
  123. package/dist/harness-api/server.d.ts +35 -4
  124. package/dist/harness-api/server.js +130 -17
  125. package/dist/harness-api/start.d.ts +10 -3
  126. package/dist/harness-api/start.js +40 -10
  127. package/dist/harness-api/tool-journal.d.ts +105 -0
  128. package/dist/harness-api/tool-journal.js +132 -0
  129. package/dist/harness-api/tool-status.d.ts +13 -0
  130. package/dist/harness-api/tool-status.js +26 -0
  131. package/dist/harness-api/workspace.d.ts +12 -7
  132. package/dist/harness-api/workspace.js +47 -25
  133. package/dist/harness-api/ws-server.d.ts +11 -3
  134. package/dist/harness-api/ws-server.js +94 -15
  135. package/dist/host/gates.d.ts +2 -1
  136. package/dist/host/gates.js +10 -2
  137. package/dist/host/main.js +54 -5
  138. package/dist/host/operate.js +67 -5
  139. package/dist/host/stack-config.d.ts +64 -0
  140. package/dist/host/stack-config.js +62 -2
  141. package/dist/keys/client.js +4 -1
  142. package/dist/keys/keys.d.ts +13 -6
  143. package/dist/keys/keys.js +9 -8
  144. package/dist/management-openapi.json +1264 -2171
  145. package/dist/mcp/connect.d.ts +26 -22
  146. package/dist/mcp/connect.js +30 -34
  147. package/dist/mcp/pool.d.ts +3 -6
  148. package/dist/mcp/pool.js +14 -28
  149. package/dist/mcp/preview.d.ts +3 -3
  150. package/dist/mcp/preview.js +10 -21
  151. package/dist/mcp/snapshot.d.ts +16 -24
  152. package/dist/mcp/snapshot.js +33 -45
  153. package/dist/model/capabilities.d.ts +26 -0
  154. package/dist/model/capabilities.js +44 -0
  155. package/dist/model/catalog.d.ts +34 -3
  156. package/dist/model/catalog.js +38 -2
  157. package/dist/model/models.d.ts +2 -0
  158. package/dist/model/models.js +1 -1
  159. package/dist/model/native-blocks.d.ts +35 -0
  160. package/dist/model/native-blocks.js +170 -0
  161. package/dist/model/pi-model.js +114 -94
  162. package/dist/openapi.json +12822 -3943
  163. package/dist/reads/types.d.ts +16 -3
  164. package/dist/record/append.d.ts +3 -1
  165. package/dist/record/append.js +1 -0
  166. package/dist/record/envelope.d.ts +5 -1
  167. package/dist/record/envelope.js +7 -3
  168. package/dist/record/environment.d.ts +25 -0
  169. package/dist/record/environment.js +44 -0
  170. package/dist/record/index.d.ts +1 -1
  171. package/dist/record/index.js +1 -1
  172. package/dist/sandbox/disk-fs.d.ts +95 -0
  173. package/dist/sandbox/disk-fs.js +206 -0
  174. package/dist/sandbox/join.d.ts +17 -18
  175. package/dist/sandbox/join.js +46 -15
  176. package/dist/sandbox/manager.d.ts +47 -21
  177. package/dist/sandbox/manager.js +162 -107
  178. package/dist/sandbox/placement.d.ts +14 -13
  179. package/dist/sandbox/placement.js +31 -14
  180. package/dist/sandbox/pods/client.d.ts +9 -2
  181. package/dist/sandbox/pods/lifecycle.d.ts +36 -27
  182. package/dist/sandbox/pods/lifecycle.js +29 -26
  183. package/dist/sandbox/pods/reconcile.js +18 -12
  184. package/dist/sandbox/pods/spec.d.ts +15 -13
  185. package/dist/sandbox/pods/spec.js +17 -20
  186. package/dist/sandbox/records.d.ts +2 -2
  187. package/dist/sandbox/records.js +3 -3
  188. package/dist/sandbox/resolve.d.ts +37 -27
  189. package/dist/sandbox/resolve.js +73 -41
  190. package/dist/sandbox/runtime-tools.d.ts +9 -0
  191. package/dist/sandbox/runtime-tools.js +16 -0
  192. package/dist/sandbox/session-sandbox.d.ts +4 -6
  193. package/dist/sandbox/session-sandbox.js +47 -94
  194. package/dist/sandbox/setup.d.ts +64 -0
  195. package/dist/sandbox/setup.js +160 -0
  196. package/dist/sandbox/share.d.ts +21 -26
  197. package/dist/sandbox/share.js +36 -63
  198. package/dist/sandbox/skills.d.ts +8 -6
  199. package/dist/sandbox/skills.js +24 -23
  200. package/dist/sandbox/tenant-config.d.ts +12 -14
  201. package/dist/sandbox/tenant-config.js +12 -17
  202. package/dist/sandbox/types.d.ts +6 -0
  203. package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
  204. package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
  205. package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
  206. package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
  207. package/dist/store/postgres/drizzle/0024_flat_manifest.sql +7 -0
  208. package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
  209. package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
  210. package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
  211. package/dist/store/postgres/drizzle/meta/_journal.json +56 -0
  212. package/dist/store/postgres/reads.js +123 -17
  213. package/dist/store/postgres/record-writer.d.ts +4 -4
  214. package/dist/store/postgres/record-writer.js +9 -9
  215. package/dist/store/postgres/record.d.ts +7 -0
  216. package/dist/store/postgres/record.js +23 -2
  217. package/dist/store/postgres/schema.d.ts +1126 -736
  218. package/dist/store/postgres/schema.js +136 -73
  219. package/dist/store/postgres/store.js +195 -67
  220. package/dist/store/types.d.ts +165 -50
  221. package/dist/tenant/advance.d.ts +7 -6
  222. package/dist/tenant/advance.js +118 -42
  223. package/dist/tenant/artifact-tool.d.ts +1 -1
  224. package/dist/tenant/artifact-tool.js +46 -26
  225. package/dist/tenant/commands.d.ts +15 -2
  226. package/dist/tenant/commands.js +115 -14
  227. package/dist/tenant/context.d.ts +56 -24
  228. package/dist/tenant/context.js +4 -15
  229. package/dist/tenant/effects.d.ts +1 -1
  230. package/dist/tenant/effects.js +44 -29
  231. package/dist/tenant/environment-manifests.d.ts +57 -0
  232. package/dist/tenant/environment-manifests.js +200 -0
  233. package/dist/tenant/environments.d.ts +174 -0
  234. package/dist/tenant/environments.js +1044 -0
  235. package/dist/tenant/harness-tokens.d.ts +21 -0
  236. package/dist/tenant/harness-tokens.js +61 -0
  237. package/dist/tenant/host-token.d.ts +1 -1
  238. package/dist/tenant/host-token.js +2 -4
  239. package/dist/tenant/identity-file.d.ts +4 -4
  240. package/dist/tenant/identity-file.js +17 -8
  241. package/dist/tenant/issuers.js +3 -3
  242. package/dist/tenant/model-setting.d.ts +14 -0
  243. package/dist/tenant/model-setting.js +51 -0
  244. package/dist/tenant/principals.js +1 -1
  245. package/dist/tenant/reset.js +3 -3
  246. package/dist/tenant/runtime.d.ts +6 -1
  247. package/dist/tenant/runtime.js +7 -1
  248. package/dist/tenant/session.d.ts +3 -3
  249. package/dist/tenant/session.js +0 -14
  250. package/dist/tenant/sessions.d.ts +18 -14
  251. package/dist/tenant/sessions.js +99 -143
  252. package/dist/tenant/signing-keys.d.ts +5 -1
  253. package/dist/tenant/skill-tool.d.ts +3 -3
  254. package/dist/tenant/skill-tool.js +6 -11
  255. package/dist/tenant/status.js +3 -3
  256. package/dist/tenant/sweep.js +13 -7
  257. package/dist/tenant/tool-results.js +1 -1
  258. package/dist/tenant/tool-search.d.ts +7 -11
  259. package/dist/tenant/tool-search.js +8 -5
  260. package/dist/tenant/transcript.d.ts +245 -36
  261. package/dist/tenant/transcript.js +64 -12
  262. package/dist/tenant/turn-timing.d.ts +43 -0
  263. package/dist/tenant/turn-timing.js +39 -0
  264. package/dist/tenant/types.d.ts +2 -0
  265. package/dist/traces/ids.d.ts +41 -0
  266. package/dist/traces/ids.js +81 -0
  267. package/dist/traces/key.d.ts +6 -0
  268. package/dist/traces/key.js +37 -0
  269. package/dist/traces/map.d.ts +113 -0
  270. package/dist/traces/map.js +289 -0
  271. package/dist/traces/options.d.ts +7 -0
  272. package/dist/traces/options.js +1 -0
  273. package/dist/traces/otlp.d.ts +42 -0
  274. package/dist/traces/otlp.js +84 -0
  275. package/dist/traces/projector.d.ts +58 -0
  276. package/dist/traces/projector.js +200 -0
  277. package/dist/traces/read.d.ts +23 -0
  278. package/dist/traces/read.js +16 -0
  279. package/dist/vault/coverage.d.ts +26 -0
  280. package/dist/vault/coverage.js +62 -0
  281. package/dist/vault/error.d.ts +11 -1
  282. package/dist/vault/error.js +5 -1
  283. package/dist/vault/host-model.d.ts +4 -1
  284. package/dist/vault/host-model.js +10 -2
  285. package/dist/vault/service.d.ts +92 -48
  286. package/dist/vault/service.js +462 -376
  287. package/dist/vault/sources.d.ts +5 -16
  288. package/dist/vault/sources.js +6 -8
  289. package/dist/version.d.ts +1 -1
  290. package/dist/version.js +2 -2
  291. package/package.json +13 -13
  292. package/dist/api/http/routes/sandboxes.d.ts +0 -9
  293. package/dist/api/http/routes/sandboxes.js +0 -153
  294. package/dist/api/http/routes/vaults.d.ts +0 -10
  295. package/dist/api/http/routes/vaults.js +0 -139
  296. package/dist/record/sandbox.d.ts +0 -25
  297. package/dist/record/sandbox.js +0 -43
  298. package/dist/sandbox/capability.d.ts +0 -3
  299. package/dist/sandbox/capability.js +0 -10
  300. package/dist/tenant/sandboxes.d.ts +0 -123
  301. package/dist/tenant/sandboxes.js +0 -566
@@ -1,4 +1,4 @@
1
- import type { ModelCallExportPage, ModelCallsPage, SandboxPage, SessionManifestView, SessionPage, SessionListItem, SessionUsageTotals } from "@nylorun/core/contracts";
1
+ import type { ArtifactKind, ArtifactPage, ModelCallExportPage, ModelCallsPage, EnvironmentPage, ToolCallsPage, SessionManifestView, SessionPage, SessionListItem, SessionUsageTotals, LiveEvent } from "@nylorun/core/contracts";
2
2
  export type ReadAccess = {
3
3
  owner?: string;
4
4
  agents?: readonly string[];
@@ -6,20 +6,33 @@ export type ReadAccess = {
6
6
  export type SessionFilters = {
7
7
  agentId?: string;
8
8
  status?: SessionListItem["status"];
9
- sandboxId?: string;
9
+ environmentId?: string;
10
10
  ownerUserId?: string;
11
11
  };
12
12
  export type PageOptions = {
13
13
  limit: number;
14
14
  cursor?: string;
15
15
  };
16
+ export type ArtifactFilters = {
17
+ sessionId?: string;
18
+ kind?: ArtifactKind;
19
+ labels: Record<string, string>;
20
+ };
16
21
  /** Public projections only. No execution-state methods or mutable transactions. */
17
22
  export interface ReadStore {
23
+ artifacts(filters: ArtifactFilters, page: PageOptions, access: ReadAccess): Promise<ArtifactPage>;
18
24
  sessions(filters: SessionFilters, page: PageOptions, access: ReadAccess): Promise<SessionPage>;
19
25
  manifest(id: string, access: ReadAccess): Promise<SessionManifestView>;
20
26
  usage(id: string, turnId: string | undefined, access: ReadAccess): Promise<SessionUsageTotals>;
21
27
  modelCalls(id: string, turnId: string | undefined, page: PageOptions, access: ReadAccess): Promise<ModelCallsPage>;
22
28
  exportModel(after: string | undefined, limit: number): Promise<ModelCallExportPage>;
23
- sandboxes(labels: Record<string, string>, page: PageOptions, access: ReadAccess, grants?: readonly string[]): Promise<SandboxPage>;
29
+ /** A session's tool calls (R5.2), from the effect journal, in the order they were first sent. */
30
+ toolCalls(id: string, turnId: string | undefined, page: PageOptions, access: ReadAccess): Promise<ToolCallsPage>;
31
+ /**
32
+ * One turn's events (R7.5), in log order: the session's rows whose `turnId` is `turnId`. Empty
33
+ * for a turn the session never had.
34
+ */
35
+ turnEvents(id: string, turnId: string, access: ReadAccess): Promise<LiveEvent[]>;
36
+ environments(labels: Record<string, string>, page: PageOptions, access: ReadAccess, grants?: readonly string[]): Promise<EnvironmentPage>;
24
37
  close(): Promise<void>;
25
38
  }
@@ -10,7 +10,7 @@
10
10
  * transaction (`store/postgres/record-writer.ts`): the database driver stays behind
11
11
  * `store/postgres/` (session-store.md §3), and this module decides what is written.
12
12
  */
13
- import type { EventPayload, EventType, LiveEvent, SessionEventOf } from "@nylorun/core/contracts";
13
+ import type { EventPayload, EventType, EventWriter, LiveEvent, SessionEventOf } from "@nylorun/core/contracts";
14
14
  /** The record's two statements, in the caller's transaction. */
15
15
  export interface RecordWriter {
16
16
  /**
@@ -40,6 +40,8 @@ export interface AppendInput<T extends EventType> {
40
40
  time: Date;
41
41
  type: T;
42
42
  payload: EventPayload<T>;
43
+ /** Who wrote it (R4.9). */
44
+ writer?: EventWriter;
43
45
  }
44
46
  /**
45
47
  * Appends one event through `writer`. An event the catalog does not describe throws
@@ -14,6 +14,7 @@ export async function appendEvent(writer, input) {
14
14
  time: input.time,
15
15
  type: input.type,
16
16
  payload: input.payload,
17
+ ...(input.writer ? { writer: input.writer } : {}),
17
18
  });
18
19
  await writer.insert({ sessionId: input.sessionId, seq, generation, type: input.type, body: event });
19
20
  return { event, generation };
@@ -1,10 +1,14 @@
1
- import { type EventPayload, type EventType, type SessionEventOf } from "@nylorun/core/contracts";
1
+ import { type EventPayload, type EventType, type EventWriter, type SessionEventOf } from "@nylorun/core/contracts";
2
+ /** Core's own writes: what an event says when its writer says nothing (R4.9). */
3
+ export declare const CORE: EventWriter;
2
4
  /** A write of an event the catalog does not describe: a bug in the writer. */
3
5
  export declare class InvalidEventError extends Error {
4
6
  readonly eventType: string;
5
7
  constructor(eventType: string, detail: string);
6
8
  }
7
9
  export interface EventInput<T extends EventType> {
10
+ /** Who wrote it (R4.9); absent, core, observed. */
11
+ writer?: EventWriter;
8
12
  tenantId: string;
9
13
  sessionId: string;
10
14
  turnId: string | null;
@@ -7,6 +7,8 @@ import { randomUUID } from "node:crypto";
7
7
  import { z } from "zod";
8
8
  import { EVENT_CATALOG, EVENT_SCHEMA, EVENT_SCHEMAS, isEventType, } from "@nylorun/core/contracts";
9
9
  import { encodeCursor } from "./cursor.js";
10
+ /** Core's own writes: what an event says when its writer says nothing (R4.9). */
11
+ export const CORE = { source: { kind: "core", id: "runtime" } };
10
12
  /** A write of an event the catalog does not describe: a bug in the writer. */
11
13
  export class InvalidEventError extends Error {
12
14
  eventType;
@@ -22,12 +24,13 @@ export function buildEvent(input) {
22
24
  if (!isEventType(type))
23
25
  throw new InvalidEventError(type, "unknown type");
24
26
  const entry = EVENT_CATALOG[type];
27
+ const writer = input.writer ?? CORE;
25
28
  const event = JSON.parse(JSON.stringify({
26
29
  schema: EVENT_SCHEMA,
27
30
  eventId: randomUUID(),
28
31
  tenantId: input.tenantId,
29
32
  sessionId: input.sessionId,
30
- runId: null,
33
+ runId: writer.runId ?? null,
31
34
  turnId: input.turnId,
32
35
  incarnation: 0,
33
36
  epoch: input.epoch,
@@ -35,8 +38,9 @@ export function buildEvent(input) {
35
38
  cursor: encodeCursor(input.sessionId, input.seq),
36
39
  time: input.time.toISOString(),
37
40
  schemaVersion: entry.version,
38
- source: { kind: entry.source, id: "runtime" },
39
- evidence: "observed",
41
+ source: writer.source,
42
+ evidence: writer.evidence ?? "observed",
43
+ ...(writer.principal ? { principal: writer.principal } : {}),
40
44
  visibility: entry.visibility ?? "public",
41
45
  retention: "full",
42
46
  type,
@@ -0,0 +1,25 @@
1
+ import { type EnvironmentEvent, type EnvironmentEventPayload, type EnvironmentEventType } from "@nylorun/core/contracts";
2
+ /** The Environment stream's two statements, in the caller's transaction. */
3
+ export interface EnvironmentRecordWriter {
4
+ /** The seq the Environment's next event takes: one past its last, or 0. */
5
+ nextSeq(environmentId: string): Promise<number>;
6
+ insert(row: {
7
+ environmentId: string;
8
+ seq: number;
9
+ type: string;
10
+ body: EnvironmentEvent;
11
+ }): Promise<void>;
12
+ }
13
+ export interface EnvironmentAppendInput<T extends EnvironmentEventType> {
14
+ tenantId: string;
15
+ environmentId: string;
16
+ time: Date;
17
+ type: T;
18
+ payload: EnvironmentEventPayload<T>;
19
+ }
20
+ /** Builds the event and checks it against the Environment event catalog. */
21
+ export declare function buildEnvironmentEvent<T extends EnvironmentEventType>(input: EnvironmentAppendInput<T> & {
22
+ seq: number;
23
+ }): EnvironmentEvent;
24
+ /** Appends one event to the Environment's stream through `writer`. */
25
+ export declare function appendEnvironmentEvent<T extends EnvironmentEventType>(writer: EnvironmentRecordWriter, input: EnvironmentAppendInput<T>): Promise<EnvironmentEvent>;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A environment's lifecycle stream (blueprint D39, F7.1): `environment.created`, `environment.attached`,
3
+ * `environment.detached` and `environment.deleted` (and the rest of `ENVIRONMENT_EVENT_TYPES`, egress-gate's
4
+ * `environment.egress` for every connection among them, D67), on the `nylorun.environment-event/1` envelope, numbered
5
+ * from 0 per environment id in `nylorun_streams.environment_events`. The record module is its only
6
+ * writer, as for session events; the store runs the two statements through
7
+ * `EnvironmentRecordWriter` in the caller's transaction, under the environment row's lock (or, for a
8
+ * environment being created, its new row).
9
+ *
10
+ * The stream lives in the record only: it is not published to the stream relay, so no S2
11
+ * stream follows it yet. Clients read it with `GET /v1/environmentes/{id}/events`.
12
+ */
13
+ import { randomUUID } from "node:crypto";
14
+ import { z } from "zod";
15
+ import { ENVIRONMENT_EVENT_PAYLOADS, ENVIRONMENT_EVENT_SCHEMA, EnvironmentEventSchema, } from "@nylorun/core/contracts";
16
+ import { InvalidEventError } from "./envelope.js";
17
+ /** Builds the event and checks it against the Environment event catalog. */
18
+ export function buildEnvironmentEvent(input) {
19
+ const payload = ENVIRONMENT_EVENT_PAYLOADS[input.type];
20
+ const checkedPayload = payload.safeParse(input.payload);
21
+ if (!checkedPayload.success)
22
+ throw new InvalidEventError(input.type, z.prettifyError(checkedPayload.error));
23
+ const event = JSON.parse(JSON.stringify({
24
+ schema: ENVIRONMENT_EVENT_SCHEMA,
25
+ eventId: randomUUID(),
26
+ tenantId: input.tenantId,
27
+ environmentId: input.environmentId,
28
+ seq: input.seq,
29
+ time: input.time.toISOString(),
30
+ type: input.type,
31
+ payload: input.payload,
32
+ }));
33
+ const checked = EnvironmentEventSchema.safeParse(event);
34
+ if (!checked.success)
35
+ throw new InvalidEventError(input.type, z.prettifyError(checked.error));
36
+ return event;
37
+ }
38
+ /** Appends one event to the Environment's stream through `writer`. */
39
+ export async function appendEnvironmentEvent(writer, input) {
40
+ const seq = await writer.nextSeq(input.environmentId);
41
+ const event = buildEnvironmentEvent({ ...input, seq });
42
+ await writer.insert({ environmentId: input.environmentId, seq, type: input.type, body: event });
43
+ return event;
44
+ }
@@ -9,4 +9,4 @@
9
9
  export { buildEvent, InvalidEventError, type EventInput } from "./envelope.js";
10
10
  export { decodeCursor, encodeCursor } from "./cursor.js";
11
11
  export { appendEvent, type AppendInput, type RecordWriter } from "./append.js";
12
- export { appendSandboxEvent, buildSandboxEvent, type SandboxAppendInput, type SandboxRecordWriter, } from "./sandbox.js";
12
+ export { appendEnvironmentEvent, buildEnvironmentEvent, type EnvironmentAppendInput, type EnvironmentRecordWriter, } from "./environment.js";
@@ -9,4 +9,4 @@
9
9
  export { buildEvent, InvalidEventError } from "./envelope.js";
10
10
  export { decodeCursor, encodeCursor } from "./cursor.js";
11
11
  export { appendEvent } from "./append.js";
12
- export { appendSandboxEvent, buildSandboxEvent, } from "./sandbox.js";
12
+ export { appendEnvironmentEvent, buildEnvironmentEvent, } from "./environment.js";
@@ -0,0 +1,95 @@
1
+ /** One entry of a disk listing, its path relative to the volume. */
2
+ export interface DiskEntry {
3
+ readonly path: string;
4
+ readonly type: "file" | "dir" | "link";
5
+ readonly size: number;
6
+ readonly mode: number;
7
+ readonly mtimeMs: number;
8
+ readonly target?: string;
9
+ }
10
+ export interface DiskListing {
11
+ readonly entries: readonly DiskEntry[];
12
+ /** More than `maxEntries` entries: `entries` holds `maxEntries + 1` of them. */
13
+ readonly truncated: boolean;
14
+ }
15
+ /** One entry to write: a file's bytes (or a range to append), a directory or a link. */
16
+ export interface DiskPut {
17
+ readonly path: string;
18
+ readonly type: "file" | "dir" | "link";
19
+ readonly mode?: number;
20
+ readonly mtimeMs?: number;
21
+ /** A file's bytes, or the next range of them with `append`. */
22
+ readonly bytes?: Uint8Array;
23
+ /** Appends `bytes` to what the file holds (a large file moves in ranges). */
24
+ readonly append?: boolean;
25
+ /** A link's target. */
26
+ readonly target?: string;
27
+ }
28
+ /** The disk's roots: `workspace`, and in a pod `harness`, as absolute directories. */
29
+ export type DiskRoots = Readonly<Partial<Record<"workspace" | "harness", string>>>;
30
+ /** The engine's own files under `harness/`, which a disk version never holds. */
31
+ export declare const ENGINE_FILES: ReadonlySet<string>;
32
+ /** The absolute path of `path` (relative to the volume), refused when it leaves its root. */
33
+ export declare function diskPath(roots: DiskRoots, path: string): string;
34
+ /** The disk's entries, in path order, at most `maxEntries + 1`. */
35
+ export declare function listDisk(roots: DiskRoots, maxEntries: number): Promise<DiskListing>;
36
+ /** `length` bytes of file `path` from `offset`, and whether that reached its end; undefined when gone. */
37
+ export declare function readDisk(roots: DiskRoots, path: string, offset: number, length: number): Promise<{
38
+ bytes: Uint8Array;
39
+ eof: boolean;
40
+ } | undefined>;
41
+ /** Empties every root, keeping the engine's own files. */
42
+ export declare function clearDisk(roots: DiskRoots): Promise<void>;
43
+ /** Writes `puts` in order: directories, files (or their next range) and links. */
44
+ export declare function putDisk(roots: DiskRoots, puts: readonly DiskPut[]): Promise<void>;
45
+ /** One disk operation, as core asks it of the workspace that holds the disk. */
46
+ export type DiskOp = {
47
+ readonly list: {
48
+ readonly maxEntries: number;
49
+ };
50
+ } | {
51
+ readonly read: {
52
+ readonly path: string;
53
+ readonly offset: number;
54
+ readonly length: number;
55
+ };
56
+ } | {
57
+ readonly clear: true;
58
+ } | {
59
+ readonly put: readonly DiskPut[];
60
+ };
61
+ /** Its answer. */
62
+ export type DiskAnswer = ({
63
+ readonly kind: "listed";
64
+ } & DiskListing) | {
65
+ readonly kind: "read";
66
+ readonly bytes: Uint8Array;
67
+ readonly eof: boolean;
68
+ } | {
69
+ readonly kind: "missing";
70
+ } | {
71
+ readonly kind: "done";
72
+ };
73
+ export declare function runDiskOp(roots: DiskRoots, op: DiskOp): Promise<DiskAnswer>;
74
+ /** A disk operation as it crosses the Harness API: bytes as base64. */
75
+ export type WireDiskOp = {
76
+ readonly list: {
77
+ readonly maxEntries: number;
78
+ };
79
+ } | {
80
+ readonly read: {
81
+ readonly path: string;
82
+ readonly offset: number;
83
+ readonly length: number;
84
+ };
85
+ } | {
86
+ readonly clear: true;
87
+ } | {
88
+ readonly put: readonly (Omit<DiskPut, "bytes"> & {
89
+ readonly base64?: string;
90
+ })[];
91
+ };
92
+ export declare function toWireOp(op: DiskOp): WireDiskOp;
93
+ export declare function fromWireOp(op: WireDiskOp): DiskOp;
94
+ export declare function toWireAnswer(answer: DiskAnswer): Record<string, unknown>;
95
+ export declare function fromWireAnswer(answer: Record<string, unknown>): DiskAnswer;
@@ -0,0 +1,206 @@
1
+ /**
2
+ * An Environment's disk as files (R2d step 3): what a version holds and how it is put back. A
3
+ * disk is one or two roots, named by the first segment of every path: `workspace/…` (the
4
+ * sandbox's `/workspace`) and, in a pod, `harness/…` (the engine's `/harness`, where a vendor
5
+ * harness keeps its state). Both backends use this over their own directories: the pod's volume
6
+ * (`adapters/sandbox/local.ts`) and the virtual workspace's host directory
7
+ * (`adapters/sandbox/virtual.ts`).
8
+ *
9
+ * A listing has regular files (size, mode, modification time), symbolic links (their target as
10
+ * stored) and empty directories; sockets, FIFOs and devices are skipped, and hard links are listed
11
+ * as separate files. The engine's own files are never listed or cleared (`ENGINE_FILES`): a
12
+ * restored volume keeps its own identity (`volume-id`), trust bundle and setup marker.
13
+ *
14
+ * Writes go to a temporary name and are renamed into place; a link replaces what is at its path.
15
+ * Times are whole milliseconds, rounded: a time written back with `utimes` can read back a hair
16
+ * under the millisecond it was, which rounding keeps and flooring would not.
17
+ */
18
+ import { randomUUID } from "node:crypto";
19
+ import { lstat, mkdir, open, readdir, readlink, rename, rm, symlink, utimes, chmod, appendFile, writeFile } from "node:fs/promises";
20
+ import { posix } from "node:path";
21
+ /** The engine's own files under `harness/`, which a disk version never holds. */
22
+ export const ENGINE_FILES = new Set([
23
+ "volume-id",
24
+ "egress-ca-bundle.pem",
25
+ "setup.json",
26
+ "sandboxes",
27
+ ]);
28
+ /** The absolute path of `path` (relative to the volume), refused when it leaves its root. */
29
+ export function diskPath(roots, path) {
30
+ const [root, ...rest] = path.split("/");
31
+ const base = roots[root];
32
+ if (!base)
33
+ throw new Error(`${path} is not under a root of this disk`);
34
+ if (rest.some((segment) => segment === "" || segment === "." || segment === ".."))
35
+ throw new Error(`${path} is not a plain relative path`);
36
+ if (root === "harness" && ENGINE_FILES.has(rest[0] ?? ""))
37
+ throw new Error(`${path} is the engine's own`);
38
+ return rest.length === 0 ? base : posix.join(base, ...rest);
39
+ }
40
+ const missing = (error) => error?.code === "ENOENT" || error?.code === "ENOTDIR";
41
+ /** The disk's entries, in path order, at most `maxEntries + 1`. */
42
+ export async function listDisk(roots, maxEntries) {
43
+ const entries = [];
44
+ const full = () => entries.length > maxEntries;
45
+ const walk = async (absolute, relative, top) => {
46
+ let names;
47
+ try {
48
+ names = (await readdir(absolute)).sort();
49
+ }
50
+ catch (error) {
51
+ if (missing(error))
52
+ return;
53
+ throw error;
54
+ }
55
+ if (names.length === 0 && !top) {
56
+ const info = await lstat(absolute);
57
+ entries.push({ path: relative, type: "dir", size: 0, mode: info.mode & 0o7777, mtimeMs: Math.round(info.mtimeMs) });
58
+ return;
59
+ }
60
+ for (const name of names) {
61
+ if (full())
62
+ return;
63
+ if (relative === "harness" && ENGINE_FILES.has(name))
64
+ continue;
65
+ const path = `${relative}/${name}`;
66
+ const child = posix.join(absolute, name);
67
+ let info;
68
+ try {
69
+ info = await lstat(child);
70
+ }
71
+ catch (error) {
72
+ if (missing(error))
73
+ continue;
74
+ throw error;
75
+ }
76
+ const mode = info.mode & 0o7777;
77
+ const mtimeMs = Math.round(info.mtimeMs);
78
+ if (info.isSymbolicLink())
79
+ entries.push({ path, type: "link", size: 0, mode, mtimeMs, target: await readlink(child) });
80
+ else if (info.isDirectory())
81
+ await walk(child, path, false);
82
+ else if (info.isFile())
83
+ entries.push({ path, type: "file", size: info.size, mode, mtimeMs });
84
+ }
85
+ };
86
+ for (const [name, base] of Object.entries(roots))
87
+ if (base && !full())
88
+ await walk(base, name, true);
89
+ entries.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
90
+ return { entries: entries.slice(0, maxEntries + 1), truncated: full() };
91
+ }
92
+ /** `length` bytes of file `path` from `offset`, and whether that reached its end; undefined when gone. */
93
+ export async function readDisk(roots, path, offset, length) {
94
+ let handle;
95
+ try {
96
+ handle = await open(diskPath(roots, path), "r");
97
+ }
98
+ catch (error) {
99
+ if (missing(error))
100
+ return undefined;
101
+ throw error;
102
+ }
103
+ try {
104
+ const size = (await handle.stat()).size;
105
+ const want = Math.max(0, Math.min(length, size - offset));
106
+ const buffer = Buffer.alloc(want);
107
+ let read = 0;
108
+ while (read < want) {
109
+ const { bytesRead } = await handle.read(buffer, read, want - read, offset + read);
110
+ if (bytesRead === 0)
111
+ break;
112
+ read += bytesRead;
113
+ }
114
+ return { bytes: new Uint8Array(buffer.subarray(0, read)), eof: offset + read >= size };
115
+ }
116
+ finally {
117
+ await handle.close();
118
+ }
119
+ }
120
+ /** Empties every root, keeping the engine's own files. */
121
+ export async function clearDisk(roots) {
122
+ for (const [name, base] of Object.entries(roots)) {
123
+ if (!base)
124
+ continue;
125
+ await mkdir(base, { recursive: true });
126
+ for (const child of await readdir(base)) {
127
+ if (name === "harness" && ENGINE_FILES.has(child))
128
+ continue;
129
+ await rm(posix.join(base, child), { recursive: true, force: true });
130
+ }
131
+ }
132
+ }
133
+ /** Writes `puts` in order: directories, files (or their next range) and links. */
134
+ export async function putDisk(roots, puts) {
135
+ for (const put of puts) {
136
+ const target = diskPath(roots, put.path);
137
+ if (put.type === "dir") {
138
+ await mkdir(target, { recursive: true });
139
+ if (put.mode !== undefined)
140
+ await chmod(target, put.mode);
141
+ continue;
142
+ }
143
+ await mkdir(posix.dirname(target), { recursive: true });
144
+ if (put.type === "link") {
145
+ await rm(target, { force: true, recursive: true });
146
+ await symlink(put.target ?? "", target);
147
+ continue;
148
+ }
149
+ const bytes = put.bytes ?? new Uint8Array();
150
+ if (put.append)
151
+ await appendFile(target, bytes);
152
+ else {
153
+ const next = `${target}.${randomUUID()}.part`;
154
+ await writeFile(next, bytes);
155
+ await rm(target, { force: true, recursive: true });
156
+ await rename(next, target);
157
+ }
158
+ if (put.mode !== undefined)
159
+ await chmod(target, put.mode);
160
+ if (put.mtimeMs !== undefined)
161
+ await utimes(target, new Date(put.mtimeMs), new Date(put.mtimeMs));
162
+ }
163
+ }
164
+ export async function runDiskOp(roots, op) {
165
+ if ("list" in op)
166
+ return { kind: "listed", ...(await listDisk(roots, op.list.maxEntries)) };
167
+ if ("read" in op) {
168
+ const read = await readDisk(roots, op.read.path, op.read.offset, op.read.length);
169
+ return read === undefined ? { kind: "missing" } : { kind: "read", ...read };
170
+ }
171
+ if ("clear" in op)
172
+ await clearDisk(roots);
173
+ else
174
+ await putDisk(roots, op.put);
175
+ return { kind: "done" };
176
+ }
177
+ export function toWireOp(op) {
178
+ if (!("put" in op))
179
+ return op;
180
+ return {
181
+ put: op.put.map(({ bytes, ...rest }) => ({
182
+ ...rest,
183
+ ...(bytes === undefined ? {} : { base64: Buffer.from(bytes).toString("base64") }),
184
+ })),
185
+ };
186
+ }
187
+ export function fromWireOp(op) {
188
+ if (!("put" in op))
189
+ return op;
190
+ return {
191
+ put: op.put.map(({ base64, ...rest }) => ({
192
+ ...rest,
193
+ ...(base64 === undefined ? {} : { bytes: new Uint8Array(Buffer.from(base64, "base64")) }),
194
+ })),
195
+ };
196
+ }
197
+ export function toWireAnswer(answer) {
198
+ return answer.kind === "read"
199
+ ? { kind: "read", base64: Buffer.from(answer.bytes).toString("base64"), eof: answer.eof }
200
+ : { ...answer };
201
+ }
202
+ export function fromWireAnswer(answer) {
203
+ if (answer.kind === "read")
204
+ return { kind: "read", bytes: new Uint8Array(Buffer.from(String(answer.base64), "base64")), eof: answer.eof === true };
205
+ return answer;
206
+ }
@@ -1,23 +1,13 @@
1
+ import type { HostJoinAnswer as PublishedJoinAnswer, HostJoinRequest } from "@nylorun/core/harness-api";
1
2
  import type { TenantContext } from "../tenant/context.js";
3
+ import { type SetupFile, type SetupPlan, type SetupReport } from "./setup.js";
2
4
  import { type HostClaims } from "../tenant/host-token.js";
3
- export interface HostJoinRequest {
4
- readonly sandboxId: string;
5
- readonly podUid: string;
6
- readonly joinToken: string;
7
- /** The id the engine keeps on the volume (`/harness/volume-id`). */
8
- readonly volumeId?: string;
9
- }
10
- /** What `join` and `renew` answer. */
11
- export interface HostJoinAnswer {
12
- readonly hostToken: string;
13
- readonly egressToken: string;
14
- readonly sandboxId: string;
15
- readonly epoch: number;
16
- /** ISO time the tokens expire. */
17
- readonly expiresAt: string;
18
- /** The egress CA certificate (PEM) the sandbox trusts (R2c). */
19
- readonly caCertificate: string;
20
- }
5
+ export type { HostJoinRequest } from "@nylorun/core/harness-api";
6
+ /** What `join` and `renew` answer: the published answer, with our engine's setup plan typed. */
7
+ export type HostJoinAnswer = Omit<PublishedJoinAnswer, "setup"> & {
8
+ /** What to run and copy on a new disk before taking turns (`sandbox/setup.ts`); join only. */
9
+ readonly setup?: SetupPlan;
10
+ };
21
11
  /** A refused join, renewal or host connection. */
22
12
  export declare class HostAuthError extends Error {
23
13
  readonly status: 400 | 401 | 503;
@@ -34,6 +24,15 @@ export interface HostAuthority {
34
24
  renew(hostToken: string): Promise<HostJoinAnswer>;
35
25
  /** The claims of a current host's token, for a connection; refuses a stale one. */
36
26
  verify(hostToken: string): Promise<HostClaims>;
27
+ /** Whether to run the setup again, with the plan to run (`GET host/setup`). */
28
+ setup(hostToken: string): Promise<{
29
+ retry: boolean;
30
+ setup?: SetupPlan;
31
+ }>;
32
+ /** Records the setup's outcome (`POST host/setup`). */
33
+ reportSetup(hostToken: string, report: SetupReport): Promise<void>;
34
+ /** Input file `index` of the host's recipe, or one file of it (`GET host/files/{index}[/{path}]`). */
35
+ setupFile(hostToken: string, index: number, path: string | undefined): Promise<SetupFile | undefined>;
37
36
  }
38
37
  export declare function sha256Hex(value: string): string;
39
38
  export declare function hostAuthority(ctx: TenantContext): HostAuthority;