@usine/executor 0.3.0 → 0.4.1-next.240.g158c84b

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 (185) hide show
  1. package/LICENSE +661 -21
  2. package/README.md +116 -19
  3. package/dist/child-command.d.ts +1 -1
  4. package/dist/child-command.d.ts.map +1 -1
  5. package/dist/child-command.js +6 -4
  6. package/dist/child-command.js.map +1 -1
  7. package/dist/child-environment.d.ts +19 -0
  8. package/dist/child-environment.d.ts.map +1 -0
  9. package/dist/child-environment.js +18 -0
  10. package/dist/child-environment.js.map +1 -0
  11. package/dist/claude-login-lock.d.ts +3 -2
  12. package/dist/claude-login-lock.d.ts.map +1 -1
  13. package/dist/claude-login-lock.js +10 -4
  14. package/dist/claude-login-lock.js.map +1 -1
  15. package/dist/cloudflare/client.d.ts +50 -0
  16. package/dist/cloudflare/client.d.ts.map +1 -0
  17. package/dist/cloudflare/client.js +108 -0
  18. package/dist/cloudflare/client.js.map +1 -0
  19. package/dist/cloudflare/cloudflare.d.ts +17 -0
  20. package/dist/cloudflare/cloudflare.d.ts.map +1 -0
  21. package/dist/cloudflare/cloudflare.js +704 -0
  22. package/dist/cloudflare/cloudflare.js.map +1 -0
  23. package/dist/cloudflare/model.d.ts +42 -0
  24. package/dist/cloudflare/model.d.ts.map +1 -0
  25. package/dist/cloudflare/model.js +2 -0
  26. package/dist/cloudflare/model.js.map +1 -0
  27. package/dist/cloudflare/shell/environment.d.ts +12 -0
  28. package/dist/cloudflare/shell/environment.d.ts.map +1 -0
  29. package/dist/cloudflare/shell/environment.js +12 -0
  30. package/dist/cloudflare/shell/environment.js.map +1 -0
  31. package/dist/cloudflare/shell/shell.d.ts +2 -0
  32. package/dist/cloudflare/shell/shell.d.ts.map +1 -0
  33. package/dist/cloudflare/shell/shell.js +155 -0
  34. package/dist/cloudflare/shell/shell.js.map +1 -0
  35. package/dist/durable-session.d.ts +21 -3
  36. package/dist/durable-session.d.ts.map +1 -1
  37. package/dist/durable-session.js +69 -10
  38. package/dist/durable-session.js.map +1 -1
  39. package/dist/exe-dev/commands.d.ts +1 -1
  40. package/dist/exe-dev/commands.d.ts.map +1 -1
  41. package/dist/exe-dev/exe-dev.d.ts +1 -1
  42. package/dist/exe-dev/exe-dev.d.ts.map +1 -1
  43. package/dist/exe-dev/exe-dev.js +9 -1
  44. package/dist/exe-dev/exe-dev.js.map +1 -1
  45. package/dist/exe-dev/preflight.d.ts +2 -2
  46. package/dist/exe-dev/preflight.d.ts.map +1 -1
  47. package/dist/exe-dev/preflight.js +5 -4
  48. package/dist/exe-dev/preflight.js.map +1 -1
  49. package/dist/executor.d.ts +164 -15
  50. package/dist/executor.d.ts.map +1 -1
  51. package/dist/executor.js +100 -3
  52. package/dist/executor.js.map +1 -1
  53. package/dist/guard.d.ts.map +1 -1
  54. package/dist/guard.js +1 -0
  55. package/dist/guard.js.map +1 -1
  56. package/dist/harness-preflight.d.ts +9 -8
  57. package/dist/harness-preflight.d.ts.map +1 -1
  58. package/dist/harness-preflight.js +15 -10
  59. package/dist/harness-preflight.js.map +1 -1
  60. package/dist/local/local.d.ts +1 -1
  61. package/dist/local/local.d.ts.map +1 -1
  62. package/dist/local/local.js +76 -37
  63. package/dist/local/local.js.map +1 -1
  64. package/dist/memory-preflight.d.ts +3 -1
  65. package/dist/memory-preflight.d.ts.map +1 -1
  66. package/dist/memory-preflight.js +1 -1
  67. package/dist/memory-preflight.js.map +1 -1
  68. package/dist/pre-push-hook.d.ts +1 -1
  69. package/dist/pre-push-hook.js +1 -1
  70. package/dist/prepared-attempt.d.ts +8 -0
  71. package/dist/prepared-attempt.d.ts.map +1 -1
  72. package/dist/prepared-attempt.js.map +1 -1
  73. package/dist/provision.d.ts +64 -13
  74. package/dist/provision.d.ts.map +1 -1
  75. package/dist/provision.js +99 -42
  76. package/dist/provision.js.map +1 -1
  77. package/dist/remote/commands.d.ts +1 -1
  78. package/dist/remote/commands.d.ts.map +1 -1
  79. package/dist/remote/commands.js +1 -1
  80. package/dist/remote/commands.js.map +1 -1
  81. package/dist/remote/container.d.ts +90 -0
  82. package/dist/remote/container.d.ts.map +1 -0
  83. package/dist/remote/container.js +480 -0
  84. package/dist/remote/container.js.map +1 -0
  85. package/dist/remote/liveness.d.ts +1 -1
  86. package/dist/remote/liveness.d.ts.map +1 -1
  87. package/dist/remote/liveness.js +6 -4
  88. package/dist/remote/liveness.js.map +1 -1
  89. package/dist/remote/machine.d.ts +52 -6
  90. package/dist/remote/machine.d.ts.map +1 -1
  91. package/dist/remote/remote.d.ts +11 -7
  92. package/dist/remote/remote.d.ts.map +1 -1
  93. package/dist/remote/remote.js +340 -92
  94. package/dist/remote/remote.js.map +1 -1
  95. package/dist/remote/sweep.d.ts +3 -1
  96. package/dist/remote/sweep.d.ts.map +1 -1
  97. package/dist/remote/sweep.js +2 -1
  98. package/dist/remote/sweep.js.map +1 -1
  99. package/dist/salvage.d.ts +18 -10
  100. package/dist/salvage.d.ts.map +1 -1
  101. package/dist/salvage.js +123 -29
  102. package/dist/salvage.js.map +1 -1
  103. package/dist/ssh/api-keys.d.ts +36 -0
  104. package/dist/ssh/api-keys.d.ts.map +1 -0
  105. package/dist/ssh/api-keys.js +72 -0
  106. package/dist/ssh/api-keys.js.map +1 -0
  107. package/dist/ssh/codex-homes.d.ts +21 -0
  108. package/dist/ssh/codex-homes.d.ts.map +1 -0
  109. package/dist/ssh/codex-homes.js +78 -0
  110. package/dist/ssh/codex-homes.js.map +1 -0
  111. package/dist/ssh/container.d.ts +32 -0
  112. package/dist/ssh/container.d.ts.map +1 -0
  113. package/dist/ssh/container.js +282 -0
  114. package/dist/ssh/container.js.map +1 -0
  115. package/dist/ssh/credentials.d.ts +3 -0
  116. package/dist/ssh/credentials.d.ts.map +1 -1
  117. package/dist/ssh/credentials.js +3 -0
  118. package/dist/ssh/credentials.js.map +1 -1
  119. package/dist/ssh/gateway/admin.d.ts +1 -1
  120. package/dist/ssh/gateway/admin.d.ts.map +1 -1
  121. package/dist/ssh/gateway/admin.js +2 -2
  122. package/dist/ssh/gateway/admin.js.map +1 -1
  123. package/dist/ssh/gateway/attachments.d.ts +15 -6
  124. package/dist/ssh/gateway/attachments.d.ts.map +1 -1
  125. package/dist/ssh/gateway/attachments.js +39 -25
  126. package/dist/ssh/gateway/attachments.js.map +1 -1
  127. package/dist/ssh/gateway/environment.d.ts +21 -2
  128. package/dist/ssh/gateway/environment.d.ts.map +1 -1
  129. package/dist/ssh/gateway/environment.js +76 -6
  130. package/dist/ssh/gateway/environment.js.map +1 -1
  131. package/dist/ssh/gateway/forward.d.ts +1 -1
  132. package/dist/ssh/gateway/forward.d.ts.map +1 -1
  133. package/dist/ssh/gateway/forward.js +1 -1
  134. package/dist/ssh/gateway/forward.js.map +1 -1
  135. package/dist/ssh/gateway/preflight.d.ts +51 -12
  136. package/dist/ssh/gateway/preflight.d.ts.map +1 -1
  137. package/dist/ssh/gateway/preflight.js +155 -69
  138. package/dist/ssh/gateway/preflight.js.map +1 -1
  139. package/dist/ssh/limits.d.ts +60 -0
  140. package/dist/ssh/limits.d.ts.map +1 -0
  141. package/dist/ssh/limits.js +84 -0
  142. package/dist/ssh/limits.js.map +1 -0
  143. package/dist/ssh/model.d.ts +97 -9
  144. package/dist/ssh/model.d.ts.map +1 -1
  145. package/dist/ssh/preflight.d.ts +26 -7
  146. package/dist/ssh/preflight.d.ts.map +1 -1
  147. package/dist/ssh/preflight.js +50 -10
  148. package/dist/ssh/preflight.js.map +1 -1
  149. package/dist/ssh/provisioner/boat.d.ts +33 -0
  150. package/dist/ssh/provisioner/boat.d.ts.map +1 -0
  151. package/dist/ssh/provisioner/boat.js +247 -0
  152. package/dist/ssh/provisioner/boat.js.map +1 -0
  153. package/dist/ssh/provisioner/hetzner.d.ts +32 -0
  154. package/dist/ssh/provisioner/hetzner.d.ts.map +1 -0
  155. package/dist/ssh/provisioner/hetzner.js +337 -0
  156. package/dist/ssh/provisioner/hetzner.js.map +1 -0
  157. package/dist/ssh/provisioner/host-key.d.ts +11 -0
  158. package/dist/ssh/provisioner/host-key.d.ts.map +1 -0
  159. package/dist/ssh/provisioner/host-key.js +57 -0
  160. package/dist/ssh/provisioner/host-key.js.map +1 -0
  161. package/dist/ssh/provisioner/http.d.ts +45 -0
  162. package/dist/ssh/provisioner/http.d.ts.map +1 -0
  163. package/dist/ssh/provisioner/http.js +72 -0
  164. package/dist/ssh/provisioner/http.js.map +1 -0
  165. package/dist/ssh/provisioner/machines.d.ts +48 -0
  166. package/dist/ssh/provisioner/machines.d.ts.map +1 -0
  167. package/dist/ssh/provisioner/machines.js +686 -0
  168. package/dist/ssh/provisioner/machines.js.map +1 -0
  169. package/dist/ssh/provisioner/model.d.ts +92 -0
  170. package/dist/ssh/provisioner/model.d.ts.map +1 -0
  171. package/dist/ssh/provisioner/model.js +37 -0
  172. package/dist/ssh/provisioner/model.js.map +1 -0
  173. package/dist/ssh/ssh.d.ts +14 -11
  174. package/dist/ssh/ssh.d.ts.map +1 -1
  175. package/dist/ssh/ssh.js +94 -27
  176. package/dist/ssh/ssh.js.map +1 -1
  177. package/dist/ssh/worker-records.d.ts +18 -0
  178. package/dist/ssh/worker-records.d.ts.map +1 -0
  179. package/dist/ssh/worker-records.js +100 -0
  180. package/dist/ssh/worker-records.js.map +1 -0
  181. package/dist/ssh/workers.d.ts +34 -7
  182. package/dist/ssh/workers.d.ts.map +1 -1
  183. package/dist/ssh/workers.js +92 -18
  184. package/dist/ssh/workers.js.map +1 -1
  185. package/package.json +19 -11
@@ -1,6 +1,7 @@
1
1
  import type { SshCommand } from "@usine/agents/child-host";
2
- import type { GatewayAdminPort, GatewayCredentialKind, GatewayDomain, GatewayLlmProvider, SshDestination, SshIdentityFile, SshPort, WorkerName, WorkerRoot } from "@usine/core/worker";
3
- import type { Effect, Option, Redacted, Ref } from "effect";
2
+ import type { InstanceId } from "@usine/core/instance";
3
+ import type { BoatSize, ContainerCpus, ContainerImage, ContainerMemoryMib, ContainerPids, GatewayAdminPort, GatewayCredentialKind, GatewayDomain, GatewayLlmProvider, HetznerImage, HetznerLabels, HetznerLocation, HetznerResourceName, HetznerServerType, MachineProvider, SshDestination, SshIdentityFile, SshPort, SshUser, WorkerName, WorkerRoot } from "@usine/core/worker";
4
+ import type { Duration, Effect, Option, Redacted, Ref } from "effect";
4
5
  import type { HarnessProbe } from "../harness-preflight.ts";
5
6
  import type { RemoteMachine } from "../remote/machine.ts";
6
7
  import type { GatewayAdmin } from "./gateway/admin.ts";
@@ -18,45 +19,132 @@ export interface SshWorkerGateway {
18
19
  /** The LLM providers the Gateway has credentials for. A harness whose provider isn't listed uses its own login through the forward proxy. */
19
20
  readonly llmProviders: ReadonlyArray<GatewayLlmProvider>;
20
21
  }
22
+ /**
23
+ * The container of each Attempt on an SSH Worker whose machine has Docker. Each limit that is
24
+ * `None` is derived from the machine at preflight: its total less a reserve for the machine's own
25
+ * processes, divided by the Attempts that run on it at once.
26
+ */
27
+ export interface SshWorkerContainer {
28
+ /** The image to run. If `None`, `ATTEMPT_IMAGE`. */
29
+ readonly image: Option.Option<ContainerImage>;
30
+ readonly cpus: Option.Option<ContainerCpus>;
31
+ readonly memoryMib: Option.Option<ContainerMemoryMib>;
32
+ /** The maximum number of processes and threads. */
33
+ readonly pids: Option.Option<ContainerPids>;
34
+ }
35
+ /** The LLM provider API keys that each Attempt on a provisioned Worker gets in its environment. */
36
+ export interface ProviderApiKeys {
37
+ /** Claude Code's `ANTHROPIC_API_KEY`. */
38
+ readonly anthropic: Option.Option<Redacted.Redacted<string>>;
39
+ /** Codex's `OPENAI_API_KEY`. */
40
+ readonly openai: Option.Option<Redacted.Redacted<string>>;
41
+ }
42
+ /** Creates each Attempt's machine as a Hetzner Cloud server from a snapshot. */
43
+ export interface HetznerProvisioner {
44
+ readonly provider: "hetzner";
45
+ /** The Hetzner Cloud API token, with read and write access to the project. */
46
+ readonly token: Redacted.Redacted<string>;
47
+ readonly serverType: HetznerServerType;
48
+ readonly location: HetznerLocation;
49
+ /** A snapshot's ID or description, or a system image's name. */
50
+ readonly image: HetznerImage;
51
+ /** The private network to attach each server to. Behind a Gateway, each server gets no public address. */
52
+ readonly network: Option.Option<HetznerResourceName>;
53
+ /** The project's firewall to apply to each server. */
54
+ readonly firewall: Option.Option<HetznerResourceName>;
55
+ /** The project's SSH key that each server authorizes. */
56
+ readonly sshKey: HetznerResourceName;
57
+ readonly sshUser: SshUser;
58
+ readonly labels: HetznerLabels;
59
+ readonly apiKeys: ProviderApiKeys;
60
+ }
61
+ /** Creates each Attempt's machine as a Boat sandbox. */
62
+ export interface BoatProvisioner {
63
+ readonly provider: "boat";
64
+ /** The Boat API key. */
65
+ readonly token: Redacted.Redacted<string>;
66
+ readonly size: BoatSize;
67
+ readonly apiKeys: ProviderApiKeys;
68
+ }
69
+ export type SshProvisioner = HetznerProvisioner | BoatProvisioner;
70
+ /** How the executor finds a Worker's machines: one long-lived machine, or one per Attempt. */
71
+ export type SshWorkerMachine = {
72
+ readonly _tag: "Fixed";
73
+ /** The `ssh` destination: `user@host` or a Host alias. */
74
+ readonly destination: SshDestination;
75
+ } | {
76
+ readonly _tag: "Provisioned";
77
+ readonly provisioner: SshProvisioner;
78
+ };
21
79
  export interface SshWorkerAddress {
22
80
  /**
23
81
  * The Worker's name in `usine.yaml`; its state is in `workers/<name>` under `root`. A request
24
82
  * for a Worker without an address fails with a `machine` capability error.
25
83
  */
26
84
  readonly name: WorkerName;
27
- /** The `ssh` destination: `user@host` or a Host alias. */
28
- readonly destination: SshDestination;
85
+ readonly machine: SshWorkerMachine;
29
86
  /** The private key for authentication. If `None`, the SSH agent and configuration are used. */
30
87
  readonly identityFile: Option.Option<SshIdentityFile>;
31
- /** The SSH server port. If `None`, the SSH configuration's port is used. */
88
+ /** The SSH server port. If `None`, the SSH configuration's port, or a provisioned machine's, is used. */
32
89
  readonly port: Option.Option<SshPort>;
33
90
  /** The machine directory that contains `workers/<name>`. */
34
91
  readonly root: WorkerRoot;
35
92
  /** The Gateway in front of the machine. If `None`, each Attempt's token is written on the machine (ADR 0026). */
36
93
  readonly gateway: Option.Option<SshWorkerGateway>;
94
+ /** Each Attempt's container. Used only if the machine has Docker. */
95
+ readonly container: SshWorkerContainer;
37
96
  }
38
97
  export interface SshExecutorOptions {
39
98
  readonly workers: ReadonlyArray<SshWorkerAddress>;
99
+ /**
100
+ * The identity of this usine instance on the Workers' machines, which the instances that share
101
+ * a GitHub App share too. It labels each Attempt container, and tags each provisioned machine.
102
+ */
103
+ readonly instance: string;
104
+ /**
105
+ * Tells apart the instances that share a GitHub App. It labels each Attempt container too, so
106
+ * that a later process removes only this instance's containers.
107
+ */
108
+ readonly instanceId: InstanceId;
40
109
  /** The local SSH command and the options placed before each Worker's port and identity file. Defaults to `ssh` in batch mode with keepalives. */
41
110
  readonly ssh?: SshCommand;
42
111
  /** The timeout for the parent task's cancellation, then separately for the Attempt to finish. Defaults to 10 seconds. */
43
112
  readonly interruptGrace?: Parameters<typeof Effect.timeoutOption>[1];
44
113
  /** Probes a brain's binary and login on a Worker's machine. Defaults to the `@usine/agents/preflight` checks over SSH, which send no prompt. Behind a Gateway, the Gateway checks also apply. */
45
114
  readonly harnessReadiness?: (machine: SshMachine) => HarnessProbe;
115
+ /**
116
+ * The local directory for the host keys of provisioned machines, one file per machine, which
117
+ * the executor creates. Defaults to a temporary directory removed with the executor.
118
+ */
119
+ readonly knownHostsDirectory?: string;
120
+ /**
121
+ * The local directory where the executor records how to reach each long-lived Worker's machine
122
+ * when it adds the Worker, which `sshWorkerRecordsFor` reads. Defaults to a temporary
123
+ * directory removed with the executor.
124
+ */
125
+ readonly workerRecordsDirectory?: string;
126
+ /** The provider API endpoints, which tests replace. Defaults to the providers' own. */
127
+ readonly providerEndpoints?: Partial<Record<MachineProvider, string>>;
128
+ /** The longest wait for a created machine to answer over SSH. Defaults to 10 minutes. */
129
+ readonly machineReadyTimeout?: Duration.Input;
130
+ /** The pause between checks of a machine being created. Defaults to 3 seconds. */
131
+ readonly machinePollInterval?: Duration.Input;
46
132
  }
47
133
  export interface MachineGateway {
48
134
  readonly settings: SshWorkerGateway;
49
135
  readonly forward: GatewayForward;
50
136
  readonly admin: GatewayAdmin;
51
137
  /**
52
- * The Anthropic credential kind the Gateway uses for Claude Code, from the last preflight.
53
- * `None` before the first preflight, if `llmProviders` doesn't include `anthropic`, or if the
54
- * Gateway is too old to report it.
138
+ * The credential kind the Gateway uses for each provider, from the last preflight: Anthropic
139
+ * for Claude Code, OpenAI for Codex. `None` before the first preflight, for a provider that
140
+ * `llmProviders` doesn't list, or for Anthropic if the Gateway is too old to report it.
55
141
  */
56
- readonly anthropicCredential: Ref.Ref<Option.Option<GatewayCredentialKind>>;
142
+ readonly credentials: Ref.Ref<GatewayCredentials>;
57
143
  /** Whether a preflight already warned that the Gateway is too old to report its Anthropic credential kind. */
58
144
  readonly warnedOfUnreportedCredential: Ref.Ref<boolean>;
59
145
  }
146
+ /** The credential kind the Gateway holds for each provider; see `MachineGateway.credentials`. */
147
+ export type GatewayCredentials = Readonly<Record<GatewayLlmProvider, Option.Option<GatewayCredentialKind>>>;
60
148
  export interface SshMachine extends RemoteMachine {
61
149
  /** The Worker's address from `usine.yaml`; preflight checks its root. */
62
150
  readonly address: SshWorkerAddress;
@@ -1 +1 @@
1
- {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/ssh/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,OAAO,EACP,UAAU,EACV,UAAU,EACX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,uFAAuF;AACvF,MAAM,WAAW,gBAAgB;IAC/B,gJAAgJ;IAChJ,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,qHAAqH;IACrH,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;IACrC,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,kCAAkC;IAClC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/C,6IAA6I;IAC7I,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;IACrC,+FAA+F;IAC/F,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACtD,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACtC,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,iHAAiH;IACjH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAClD,iJAAiJ;IACjJ,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC;IAC1B,yHAAyH;IACzH,QAAQ,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,iMAAiM;IACjM,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,YAAY,CAAC;CACnE;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC;IAC5E,8GAA8G;IAC9G,QAAQ,CAAC,4BAA4B,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,UAAW,SAAQ,aAAa;IAC/C,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;CACjD"}
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/ssh/model.ts"],"names":[],"mappings":"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"}
@@ -1,16 +1,35 @@
1
+ import type { RemoteShell } from "@usine/agents/child-host";
1
2
  import type { ClaudeLoginLock } from "@usine/agents/runtime";
2
3
  import type { BrainSpec } from "@usine/core/agent/brain";
3
4
  import type { WorkerName } from "@usine/core/worker";
4
- import { Effect } from "effect";
5
- import { type ExecutorError, WorkerCapabilityError } from "../executor.ts";
5
+ import { Effect, Option } from "effect";
6
+ import { type ExecutorError, type PreflightReport, WorkerCapabilityError } from "../executor.ts";
6
7
  import { type HarnessProbe } from "../harness-preflight.ts";
7
8
  import type { RemoteCommands } from "../remote/commands.ts";
9
+ import type { SshContainers } from "./container.ts";
8
10
  import type { SshMachine } from "./model.ts";
11
+ import type { SshWorkerEntry } from "./workers.ts";
9
12
  /**
10
- * Verifies an SSH Worker without changing its machine: SSH connects, the root is a writable
11
- * directory, Git runs, memory is enough for the concurrent Attempts, the Gateway (if any)
12
- * responds and is reachable, and each brain is ready. Claude brains are checked under the
13
- * Worker's lock in `logins`.
13
+ * Verifies an SSH Worker's machine: SSH connects, the root is a writable directory, Git runs,
14
+ * memory is enough for the concurrent Attempts, the Gateway (if any) responds and is reachable,
15
+ * Docker and the Attempt image work if the machine has Docker, and each brain is ready. Pulls
16
+ * the Attempt image and changes nothing else. With Docker, it derives the container limits that
17
+ * `usine.yaml` leaves unset for `maxConcurrentAttempts` containers, which the report gives, and
18
+ * brains are probed in a container from the image; without, as the SSH user, which the report
19
+ * gives as degraded. Claude brains are checked under the Worker's lock in `logins`.
14
20
  */
15
- export declare const sshPreflightFor: (commands: RemoteCommands, machineFor: (worker: WorkerName) => Effect.Effect<SshMachine, ExecutorError>, harnessProbe: (machine: SshMachine) => HarnessProbe, logins: ClaudeLoginLock["Service"]) => (worker: string & import("effect/Brand").Brand<"WorkerName">, brains: readonly BrainSpec[], maxConcurrentAttempts: number) => Effect.Effect<undefined, import("../executor.ts").CredentialUnavailableError | import("../executor.ts").ExecutorOperationError | WorkerCapabilityError, never>;
21
+ export declare const sshMachinePreflightFor: (commands: RemoteCommands, harnessProbe: (machine: SshMachine, shell: RemoteShell) => HarnessProbe, logins: ClaudeLoginLock["Service"], containers: SshContainers) => (machine: SshMachine, brains: readonly BrainSpec[], maxConcurrentAttempts: number) => Effect.Effect<{
22
+ degraded: Option.Option<string>;
23
+ deferred: Option.Option<string>;
24
+ containerLimits: Option.Option<import("@usine/core/worker").ContainerLimits>;
25
+ }, import("../executor.ts").CredentialUnavailableError | import("../executor.ts").ExecutorOperationError | WorkerCapabilityError, never>;
26
+ /**
27
+ * Verifies an SSH Worker: a long-lived Worker's machine with `verifyMachine`, or a provisioned
28
+ * Worker with `verifyProvisioned`, which has no machine before an Attempt.
29
+ */
30
+ export declare const sshPreflightFor: (workerFor: (worker: WorkerName) => Effect.Effect<SshWorkerEntry, ExecutorError>, verifyMachine: ReturnType<typeof sshMachinePreflightFor>, verifyProvisioned: (entry: SshWorkerEntry, brains: ReadonlyArray<BrainSpec>, maxConcurrentAttempts: number) => Effect.Effect<PreflightReport, ExecutorError>) => (worker: string & import("effect/Brand").Brand<"WorkerName">, brains: readonly BrainSpec[], maxConcurrentAttempts: number) => Effect.Effect<PreflightReport | {
31
+ degraded: Option.Option<string>;
32
+ deferred: Option.Option<string>;
33
+ containerLimits: Option.Option<import("@usine/core/worker").ContainerLimits>;
34
+ }, import("../executor.ts").CredentialUnavailableError | import("../executor.ts").ExecutorOperationError | WorkerCapabilityError, never>;
16
35
  //# sourceMappingURL=preflight.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/ssh/preflight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,EACL,KAAK,aAAa,EAElB,qBAAqB,EAEtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,YAAY,EAA0B,MAAM,yBAAyB,CAAC;AAEpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAY7C;;;;;GAKG;AACH,eAAO,MAAM,eAAe,aAChB,cAAc,cACZ,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,gBAC9D,CAAC,OAAO,EAAE,UAAU,KAAK,YAAY,UAC3C,eAAe,CAAC,SAAS,CAAC,iSA0EX,CAAC"}
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/ssh/preflight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAgB,MAAM,EAAS,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EACL,KAAK,aAAa,EAElB,KAAK,eAAe,EACpB,qBAAqB,EAEtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,YAAY,EAA0B,MAAM,yBAAyB,CAAC;AAEpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAIpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAYnD;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,aACvB,cAAc,gBACV,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,KAAK,YAAY,UAC/D,eAAe,CAAC,SAAS,CAAC,cACtB,aAAa;;;;wIAqHvB,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,eAAe,cACf,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,aAAa,CAAC,iBAChE,UAAU,CAAC,OAAO,sBAAsB,CAAC,qBACrC,CACjB,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,EAChC,qBAAqB,EAAE,MAAM,KAC1B,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,aAAa,CAAC;;;;wIAa3B,CAAC"}
@@ -1,19 +1,24 @@
1
- import { Effect, Option } from "effect";
1
+ import { Array as Arr, Effect, Match, Option } from "effect";
2
2
  import { logPreflightFailure, WorkerCapabilityError, } from "../executor.js";
3
3
  import { verifyHarnessReadiness } from "../harness-preflight.js";
4
4
  import { verifyLinuxMemory } from "../memory-preflight.js";
5
+ import { gatewayExportedEnvironment } from "./gateway/environment.js";
5
6
  import { verifyGateway } from "./gateway/preflight.js";
7
+ import { limitFields } from "./limits.js";
6
8
  /** A script that succeeds if `$1` is a directory the SSH user can write to. */
7
9
  const WRITABLE_DIRECTORY_SCRIPT = 'test -d "$1" && test -w "$1"';
8
10
  const capabilityError = (worker, capability, message, cause) => new WorkerCapabilityError({ worker, capability, message, cause });
9
11
  /**
10
- * Verifies an SSH Worker without changing its machine: SSH connects, the root is a writable
11
- * directory, Git runs, memory is enough for the concurrent Attempts, the Gateway (if any)
12
- * responds and is reachable, and each brain is ready. Claude brains are checked under the
13
- * Worker's lock in `logins`.
12
+ * Verifies an SSH Worker's machine: SSH connects, the root is a writable directory, Git runs,
13
+ * memory is enough for the concurrent Attempts, the Gateway (if any) responds and is reachable,
14
+ * Docker and the Attempt image work if the machine has Docker, and each brain is ready. Pulls
15
+ * the Attempt image and changes nothing else. With Docker, it derives the container limits that
16
+ * `usine.yaml` leaves unset for `maxConcurrentAttempts` containers, which the report gives, and
17
+ * brains are probed in a container from the image; without, as the SSH user, which the report
18
+ * gives as degraded. Claude brains are checked under the Worker's lock in `logins`.
14
19
  */
15
- export const sshPreflightFor = (commands, machineFor, harnessProbe, logins) => Effect.fn("SshExecutor.preflight")(function* (worker, brains, maxConcurrentAttempts) {
16
- const machine = yield* machineFor(worker);
20
+ export const sshMachinePreflightFor = (commands, harnessProbe, logins, containers) => Effect.fn("SshExecutor.verifyMachine")(function* (machine, brains, maxConcurrentAttempts) {
21
+ const worker = machine.address.name;
17
22
  const host = machine.shell.destination;
18
23
  const root = machine.address.root;
19
24
  yield* Effect.annotateCurrentSpan({ worker, host, maxConcurrentAttempts });
@@ -35,13 +40,48 @@ export const sshPreflightFor = (commands, machineFor, harnessProbe, logins) => E
35
40
  yield* verifyLinuxMemory(worker, maxConcurrentAttempts, commands.run("preflight", machine.shell, "cat", ["/proc/meminfo"]));
36
41
  if (Option.isSome(machine.gateway))
37
42
  yield* verifyGateway(commands, worker, machine, machine.gateway.value);
38
- yield* verifyHarnessReadiness(worker, brains, harnessProbe(machine), logins);
39
- yield* Effect.logInfo("SSH Worker preflight verified", {
43
+ const containerCheck = yield* containers.verify(machine, maxConcurrentAttempts);
44
+ const harnesses = Arr.dedupe(brains.map(({ harness }) => harness));
45
+ const probe = (shell) => verifyHarnessReadiness(worker, machine.provisioned, brains, harnessProbe(machine, shell), logins);
46
+ yield* containerCheck._tag === "Container" && harnesses.length > 0
47
+ ? Effect.scoped(containers
48
+ .preflightShell(machine, containerCheck.settings, harnesses, Option.match(machine.gateway, {
49
+ onNone: () => ({}),
50
+ onSome: ({ settings }) => gatewayExportedEnvironment(settings, `${machine.workerDirectory}/gh`),
51
+ }))
52
+ .pipe(Effect.flatMap(probe)))
53
+ : probe(machine.shell);
54
+ const fields = {
40
55
  worker,
41
56
  host,
42
57
  root,
43
58
  gitVersion,
44
- harnesses: [...new Set(brains.map(({ harness }) => harness))].join(","),
59
+ isolation: containerCheck._tag,
60
+ harnesses: harnesses.join(","),
61
+ };
62
+ return yield* Match.valueTags(containerCheck, {
63
+ Container: ({ limits }) => Effect.as(Effect.logInfo("SSH Worker preflight verified", { ...fields, ...limitFields(limits) }), {
64
+ degraded: Option.none(),
65
+ deferred: Option.none(),
66
+ containerLimits: Option.some(limits.limits),
67
+ }),
68
+ SharedUser: ({ degraded }) => Effect.as(Effect.logInfo("SSH Worker preflight verified", fields), {
69
+ degraded: Option.some(degraded),
70
+ deferred: Option.none(),
71
+ containerLimits: Option.none(),
72
+ }),
73
+ });
74
+ });
75
+ /**
76
+ * Verifies an SSH Worker: a long-lived Worker's machine with `verifyMachine`, or a provisioned
77
+ * Worker with `verifyProvisioned`, which has no machine before an Attempt.
78
+ */
79
+ export const sshPreflightFor = (workerFor, verifyMachine, verifyProvisioned) => Effect.fn("SshExecutor.preflight")(function* (worker, brains, maxConcurrentAttempts) {
80
+ yield* Effect.annotateCurrentSpan({ worker, maxConcurrentAttempts });
81
+ const entry = yield* workerFor(worker);
82
+ return yield* Option.match(entry.machine, {
83
+ onNone: () => verifyProvisioned(entry, brains, maxConcurrentAttempts),
84
+ onSome: (machine) => verifyMachine(machine, brains, maxConcurrentAttempts),
45
85
  });
46
86
  }, logPreflightFailure);
47
87
  //# sourceMappingURL=preflight.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/ssh/preflight.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAEL,mBAAmB,EACnB,qBAAqB,GAEtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAqB,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD,+EAA+E;AAC/E,MAAM,yBAAyB,GAAG,8BAA8B,CAAC;AAEjE,MAAM,eAAe,GAAG,CACtB,MAAkB,EAClB,UAA4B,EAC5B,OAAe,EACf,KAAc,EACS,EAAE,CAAC,IAAI,qBAAqB,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;AAE9F;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,QAAwB,EACxB,UAA4E,EAC5E,YAAmD,EACnD,MAAkC,EAClC,EAAE,CACF,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,QAAQ,CAAC,EAC1C,MAAkB,EAClB,MAAgC,EAChC,qBAA6B;IAE7B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;IAClC,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,EAAE,CAAC,CAAC;IAC3E,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,QAAQ;SAC9B,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;SAC/C,IAAI,CACH,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,eAAe,CACb,MAAM,EACN,SAAS,EACT,gCAAgC,MAAM,QAAQ,IAAI,EAAE,EACpD,KAAK,CACN,CACF,CACF,CAAC;IACJ,IAAI,SAAS,CAAC,QAAQ,KAAK,CAAC;QAC1B,OAAO,KAAK,CAAC,CAAC,eAAe,CAC3B,MAAM,EACN,SAAS,EACT,gCAAgC,MAAM,QAAQ,IAAI,EAAE,EACpD,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,wBAAwB,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAC3F,CAAC;IACJ,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,QAAQ;SAChC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;SACxF,IAAI,CACH,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,iBAAiB,IAAI,OAAO,IAAI,EAAE,EAAE,KAAK,CAAC,CAC9E,CACF,CAAC;IACJ,IAAI,WAAW,CAAC,QAAQ,KAAK,CAAC;QAC5B,OAAO,KAAK,CAAC,CAAC,eAAe,CAC3B,MAAM,EACN,SAAS,EACT,YAAY,IAAI,mBAAmB,MAAM,mCAAmC,IAAI,EAAE,EAClF,IAAI,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,aAAa,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC,CACpF,CAAC;IACJ,MAAM,UAAU,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ;SAChC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,WAAW,CAAC,CAAC;SACrD,IAAI,CACH,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,WAAW,MAAM,iBAAiB,EAAE,KAAK,CAAC,CAChF,CACF,CAAC,CAAC,IAAI,EAAE,CAAC;IACZ,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,cAAc,CAAC;QACxC,OAAO,KAAK,CAAC,CAAC,eAAe,CAC3B,MAAM,EACN,WAAW,EACX,WAAW,MAAM,wCAAwC,EACzD,IAAI,KAAK,CAAC,UAAU,CAAC,CACtB,CAAC;IACJ,KAAK,CAAC,CAAC,iBAAiB,CACtB,MAAM,EACN,qBAAqB,EACrB,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,eAAe,CAAC,CAAC,CACnE,CAAC;IAEF,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QAChC,KAAK,CAAC,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACzE,KAAK,CAAC,CAAC,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7E,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,+BAA+B,EAAE;QACrD,MAAM;QACN,IAAI;QACJ,IAAI;QACJ,UAAU;QACV,SAAS,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;KACxE,CAAC,CAAC;AACL,CAAC,EAAE,mBAAmB,CAAC,CAAC"}
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/ssh/preflight.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAEL,mBAAmB,EAEnB,qBAAqB,GAEtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAqB,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1C,+EAA+E;AAC/E,MAAM,yBAAyB,GAAG,8BAA8B,CAAC;AAEjE,MAAM,eAAe,GAAG,CACtB,MAAkB,EAClB,UAA4B,EAC5B,OAAe,EACf,KAAc,EACS,EAAE,CAAC,IAAI,qBAAqB,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;AAE9F;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,QAAwB,EACxB,YAAuE,EACvE,MAAkC,EAClC,UAAyB,EACzB,EAAE,CACF,MAAM,CAAC,EAAE,CAAC,2BAA2B,CAAC,CAAC,QAAQ,CAAC,EAC9C,OAAmB,EACnB,MAAgC,EAChC,qBAA6B;IAE7B,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;IACpC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;IAClC,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,EAAE,CAAC,CAAC;IAC3E,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,QAAQ;SAC9B,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;SAC/C,IAAI,CACH,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,eAAe,CACb,MAAM,EACN,SAAS,EACT,gCAAgC,MAAM,QAAQ,IAAI,EAAE,EACpD,KAAK,CACN,CACF,CACF,CAAC;IACJ,IAAI,SAAS,CAAC,QAAQ,KAAK,CAAC;QAC1B,OAAO,KAAK,CAAC,CAAC,eAAe,CAC3B,MAAM,EACN,SAAS,EACT,gCAAgC,MAAM,QAAQ,IAAI,EAAE,EACpD,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,wBAAwB,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAC3F,CAAC;IACJ,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,QAAQ;SAChC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;SACxF,IAAI,CACH,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,iBAAiB,IAAI,OAAO,IAAI,EAAE,EAAE,KAAK,CAAC,CAC9E,CACF,CAAC;IACJ,IAAI,WAAW,CAAC,QAAQ,KAAK,CAAC;QAC5B,OAAO,KAAK,CAAC,CAAC,eAAe,CAC3B,MAAM,EACN,SAAS,EACT,YAAY,IAAI,mBAAmB,MAAM,mCAAmC,IAAI,EAAE,EAClF,IAAI,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,aAAa,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC,CACpF,CAAC;IACJ,MAAM,UAAU,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ;SAChC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,WAAW,CAAC,CAAC;SACrD,IAAI,CACH,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,WAAW,MAAM,iBAAiB,EAAE,KAAK,CAAC,CAChF,CACF,CAAC,CAAC,IAAI,EAAE,CAAC;IACZ,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,cAAc,CAAC;QACxC,OAAO,KAAK,CAAC,CAAC,eAAe,CAC3B,MAAM,EACN,WAAW,EACX,WAAW,MAAM,wCAAwC,EACzD,IAAI,KAAK,CAAC,UAAU,CAAC,CACtB,CAAC;IACJ,KAAK,CAAC,CAAC,iBAAiB,CACtB,MAAM,EACN,qBAAqB,EACrB,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,eAAe,CAAC,CAAC,CACnE,CAAC;IAEF,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QAChC,KAAK,CAAC,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACzE,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC;IAChF,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,CAAC,KAAkB,EAAE,EAAE,CACnC,sBAAsB,CACpB,MAAM,EACN,OAAO,CAAC,WAAW,EACnB,MAAM,EACN,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,EAC5B,MAAM,CACP,CAAC;IACJ,KAAK,CAAC,CAAC,cAAc,CAAC,IAAI,KAAK,WAAW,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;QAChE,CAAC,CAAC,MAAM,CAAC,MAAM,CACX,UAAU;aACP,cAAc,CACb,OAAO,EACP,cAAc,CAAC,QAAQ,EACvB,SAAS,EACT,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE;YAC5B,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;YAClB,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CACvB,0BAA0B,CAAC,QAAQ,EAAE,GAAG,OAAO,CAAC,eAAe,KAAK,CAAC;SACxE,CAAC,CACH;aACA,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAC/B;QACH,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACzB,MAAM,MAAM,GAAG;QACb,MAAM;QACN,IAAI;QACJ,IAAI;QACJ,UAAU;QACV,SAAS,EAAE,cAAc,CAAC,IAAI;QAC9B,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC;KAC/B,CAAC;IACF,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,cAAc,EAAE;QAC5C,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CACxB,MAAM,CAAC,EAAE,CACP,MAAM,CAAC,OAAO,CAAC,+BAA+B,EAAE,EAAE,GAAG,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,EACtF;YACE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE;YACvB,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE;YACvB,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;SAClB,CAC5B;QACH,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAC3B,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,+BAA+B,EAAE,MAAM,CAAC,EAAE;YACjE,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;YAC/B,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE;YACvB,eAAe,EAAE,MAAM,CAAC,IAAI,EAAE;SACL,CAAC;KAC/B,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEL;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,SAA+E,EAC/E,aAAwD,EACxD,iBAIkD,EAClD,EAAE,CACF,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,QAAQ,CAAC,EAC1C,MAAkB,EAClB,MAAgC,EAChC,qBAA6B;IAE7B,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC,CAAC;IACrE,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACvC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE;QACxC,MAAM,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,qBAAqB,CAAC;QACrE,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,qBAAqB,CAAC;KAC3E,CAAC,CAAC;AACL,CAAC,EAAE,mBAAmB,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { type WorkerName } from "@usine/core/worker";
2
+ import { Effect, Option } from "effect";
3
+ import { MachineUnavailableError, WorkerCapabilityError } from "../../executor.ts";
4
+ import type { BoatProvisioner } from "../model.ts";
5
+ import { type ListedMachine, type MachineTags, ProviderApiError } from "./model.ts";
6
+ /** Boat's public API. */
7
+ export declare const BOAT_API = "https://boat.dev/api/v1";
8
+ /**
9
+ * Returns the Boat driver for `worker`'s sandboxes (#550). Each sandbox is created with none of
10
+ * the account's secrets (`noEnv`), no snapshots, which Boat can store outside the EU, no command
11
+ * monitoring, and no automatic stop, and the executor deletes it. Its name carries the instance,
12
+ * Worker, Run, creating process, and Attempt, because Boat has no labels. `publicKey` is the
13
+ * OpenSSH public key each sandbox authorizes.
14
+ */
15
+ export declare const boatDriverFor: (worker: WorkerName, settings: BoatProvisioner, publicKey: string, base?: string) => Effect.Effect<{
16
+ provider: "boat";
17
+ verify: (_worker: string & import("effect/Brand").Brand<"WorkerName">, maxConcurrentAttempts: number) => Effect.Effect<{
18
+ warnings: string[];
19
+ }, WorkerCapabilityError, never>;
20
+ create: (_name: string, tags: MachineTags) => Effect.Effect<{
21
+ id: string & import("effect/Brand").Brand<"MachineId">;
22
+ hostKey: Option.Option<never>;
23
+ }, MachineUnavailableError, never>;
24
+ endpoint: (id: string & import("effect/Brand").Brand<"MachineId">, interval: Effect.Effect<void, never, never>) => Effect.Effect<{
25
+ destination: string & import("effect/Brand").Brand<"SshDestination">;
26
+ port: Option.Option<number & import("effect/Brand").Brand<"SshPort">>;
27
+ hostKey: Option.Option<string>;
28
+ }, MachineUnavailableError, never>;
29
+ tag: (id: string & import("effect/Brand").Brand<"MachineId">, tags: MachineTags) => Effect.Effect<void, ProviderApiError, never>;
30
+ remove: (id: string & import("effect/Brand").Brand<"MachineId">) => Effect.Effect<void, ProviderApiError, never>;
31
+ list: (instance: string, listed: string & import("effect/Brand").Brand<"WorkerName">) => Effect.Effect<ListedMachine[], ProviderApiError, never>;
32
+ }, never, import("effect/http/HttpClient").HttpClient>;
33
+ //# sourceMappingURL=boat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boat.d.ts","sourceRoot":"","sources":["../../../src/ssh/provisioner/boat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACzF,OAAO,EAAY,MAAM,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAC1D,OAAO,EACL,uBAAuB,EAEvB,qBAAqB,EACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,WAAW,EAChB,gBAAgB,EAEjB,MAAM,YAAY,CAAC;AAEpB,yBAAyB;AACzB,eAAO,MAAM,QAAQ,4BAA4B,CAAC;AA+FlD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,WAChB,UAAU,YACR,eAAe,aACd,MAAM,SACX,MAAM;;;;;;;;;;;;;;;;;sDAsPV,CAAC"}
@@ -0,0 +1,247 @@
1
+ import { MachineId, SshDestination, SshPort } from "@usine/core/worker";
2
+ import { DateTime, Effect, Option, Schema } from "effect";
3
+ import { MachineUnavailableError, WorkerCapabilityError, } from "../../executor.js";
4
+ import { failureText, providerApiFor } from "./http.js";
5
+ import { attemptTag, ProviderApiError, tagValue, } from "./model.js";
6
+ /** Boat's public API. */
7
+ export const BOAT_API = "https://boat.dev/api/v1";
8
+ /** The prefix of the name of every sandbox that usine creates. */
9
+ const NAME_PREFIX = "usine";
10
+ const Sandbox = Schema.Struct({
11
+ id: Schema.String,
12
+ name: Schema.String,
13
+ state: Schema.String,
14
+ error: Schema.optionalKey(Schema.NullOr(Schema.String)),
15
+ createdAt: Schema.optionalKey(Schema.NullOr(Schema.String)),
16
+ });
17
+ const OneSandbox = Schema.Struct({ sandbox: Sandbox });
18
+ const SandboxList = Schema.Struct({
19
+ sandboxes: Schema.Array(Sandbox),
20
+ pageInfo: Schema.optionalKey(Schema.Struct({ nextCursor: Schema.NullOr(Schema.String), hasMore: Schema.Boolean })),
21
+ });
22
+ const SshKey = Schema.Struct({
23
+ machineIp: Schema.optionalKey(Schema.NullOr(Schema.String)),
24
+ sshUser: Schema.optionalKey(Schema.String),
25
+ sshEndpoint: Schema.optionalKey(Schema.NullOr(Schema.String)),
26
+ hostKey: Schema.optionalKey(Schema.String),
27
+ });
28
+ const Limits = Schema.Struct({
29
+ canStart: Schema.optionalKey(Schema.Boolean),
30
+ activeSandboxes: Schema.optionalKey(Schema.Int),
31
+ maxActiveSandboxes: Schema.optionalKey(Schema.NullOr(Schema.Int)),
32
+ starts: Schema.optionalKey(Schema.Struct({
33
+ unlimited: Schema.optionalKey(Schema.Boolean),
34
+ day: Schema.optionalKey(Schema.Struct({ remaining: Schema.NullOr(Schema.Int) })),
35
+ })),
36
+ machineTypes: Schema.optionalKey(Schema.Array(Schema.Struct({
37
+ type: Schema.String,
38
+ available: Schema.Boolean,
39
+ reason: Schema.optionalKey(Schema.NullOr(Schema.String)),
40
+ }))),
41
+ });
42
+ /** The states in which a sandbox accepts SSH. */
43
+ const RUNNING = new Set(["ready", "idle", "running"]);
44
+ /** The states from which a new sandbox never runs. */
45
+ const ENDED = new Set(["error", "cancelled", "archiving", "archived"]);
46
+ /** Returns the name that carries a sandbox's tags; at most 120 characters, Boat's limit. */
47
+ const nameOf = (tags) => [
48
+ NAME_PREFIX,
49
+ tagValue(tags.instance, 24),
50
+ tagValue(tags.worker, 24),
51
+ tagValue(tags.run, 32),
52
+ tagValue(tags.process, 12),
53
+ Option.match(tags.attempt, { onNone: () => "-", onSome: attemptTag }),
54
+ ].join(" ");
55
+ /** Returns the reason a creation failure is retried, by Boat's documented statuses. */
56
+ const creationReason = (failure) => {
57
+ if (failure._tag === "ProviderUnanswered")
58
+ return "unreachable";
59
+ if (failure.status === 503)
60
+ return "capacity";
61
+ if (failure.status === 402 || failure.status === 429)
62
+ return "quota";
63
+ if (failure.status === 401 || failure.status === 403 || failure.status === 400)
64
+ return "refused";
65
+ return "provider";
66
+ };
67
+ /** Returns where to connect: Boat's `sshEndpoint` forward if set, else the machine's address. */
68
+ const destinationOf = (answer) => {
69
+ const forwarded = answer.sshEndpoint ?? undefined;
70
+ if (forwarded !== undefined) {
71
+ const separator = forwarded.lastIndexOf(":");
72
+ const port = Number(forwarded.slice(separator + 1));
73
+ if (separator > 0 && Number.isInteger(port) && port > 0 && port < 65_536)
74
+ return Option.some({
75
+ host: forwarded.slice(0, separator),
76
+ port: Option.some(SshPort.make(port)),
77
+ });
78
+ }
79
+ return Option.map(Option.fromNullishOr(answer.machineIp), (host) => ({
80
+ host,
81
+ port: Option.none(),
82
+ }));
83
+ };
84
+ /**
85
+ * Returns the Boat driver for `worker`'s sandboxes (#550). Each sandbox is created with none of
86
+ * the account's secrets (`noEnv`), no snapshots, which Boat can store outside the EU, no command
87
+ * monitoring, and no automatic stop, and the executor deletes it. Its name carries the instance,
88
+ * Worker, Run, creating process, and Attempt, because Boat has no labels. `publicKey` is the
89
+ * OpenSSH public key each sandbox authorizes.
90
+ */
91
+ export const boatDriverFor = (worker, settings, publicKey, base = BOAT_API) => Effect.gen(function* () {
92
+ const api = yield* providerApiFor("boat", base, settings.token);
93
+ const apiError = (operation) => (failure) => new ProviderApiError({
94
+ provider: "boat",
95
+ operation,
96
+ status: failure._tag === "ProviderRefused" ? Option.some(failure.status) : Option.none(),
97
+ message: `Boat couldn't ${operation}: ${failureText(failure)}`,
98
+ });
99
+ const providerGap = (message, cause) => new WorkerCapabilityError({ worker, capability: "provider", message, cause });
100
+ const unavailable = (reason, message) => new MachineUnavailableError({ worker, provider: "boat", reason, message });
101
+ const verify = Effect.fn("BoatDriver.verify")(function* (_worker, maxConcurrentAttempts) {
102
+ yield* Effect.annotateCurrentSpan({ worker, maxConcurrentAttempts });
103
+ yield* api.send("GET", "/me");
104
+ const limits = yield* api
105
+ .send("GET", "/limits")
106
+ .pipe(Effect.flatMap(api.decode(Limits, "read the limits")));
107
+ const machineType = limits.machineTypes?.find(({ type }) => type === settings.size);
108
+ if (machineType !== undefined && !machineType.available)
109
+ return yield* providerGap(`Boat doesn't offer machine size ${settings.size} to the account of worker "${worker}"${machineType.reason === undefined || machineType.reason === null
110
+ ? ""
111
+ : `: ${machineType.reason}`}`, new Error(`size ${settings.size} unavailable`));
112
+ const maximum = limits.maxActiveSandboxes ?? undefined;
113
+ const warnings = [
114
+ ...(limits.canStart === false
115
+ ? [`Boat reports that the account of worker "${worker}" can't start sandboxes now.`]
116
+ : []),
117
+ ...(maximum !== undefined && maximum < maxConcurrentAttempts
118
+ ? [
119
+ `Boat allows ${String(maximum)} active sandboxes, fewer than max_concurrent_attempts (${String(maxConcurrentAttempts)}).`,
120
+ ]
121
+ : []),
122
+ ];
123
+ yield* Effect.logInfo("Boat provisioner verified", {
124
+ worker,
125
+ size: settings.size,
126
+ canStart: limits.canStart ?? "not reported",
127
+ activeSandboxes: limits.activeSandboxes ?? "not reported",
128
+ maxActiveSandboxes: maximum ?? "not reported",
129
+ startsLeftToday: limits.starts?.unlimited === true
130
+ ? "unlimited"
131
+ : (limits.starts?.day?.remaining ?? "not reported"),
132
+ });
133
+ return { warnings };
134
+ }, (effect) => effect.pipe(Effect.catchTags({
135
+ ProviderUnanswered: (failure) => Effect.fail(providerGap(`Can't reach the Boat API for worker "${worker}": ${failure.detail}`, failure)),
136
+ ProviderRefused: (failure) => Effect.fail(providerGap(failure.status === 401
137
+ ? `Boat refused the API key of worker "${worker}". Create one with boat api-key create, and set it in provisioner.token_env.`
138
+ : `Boat refused a preflight request of worker "${worker}": ${failureText(failure)}`, failure)),
139
+ })));
140
+ const create = Effect.fn("BoatDriver.create")(function* (_name, tags) {
141
+ yield* Effect.annotateCurrentSpan({ worker, run: tags.run });
142
+ const response = yield* api.send("POST", "/sandboxes", {
143
+ body: {
144
+ type: settings.size,
145
+ name: nameOf(tags),
146
+ ttlSeconds: null,
147
+ noEnv: true,
148
+ snapshots: false,
149
+ monitoring: false,
150
+ failFast: true,
151
+ },
152
+ });
153
+ const created = yield* api.decode(OneSandbox, "create a sandbox")(response);
154
+ return { id: MachineId.make(created.sandbox.id), hostKey: Option.none() };
155
+ }, (effect) => effect.pipe(Effect.catchTags({
156
+ ProviderUnanswered: (failure) => Effect.fail(unavailable("unreachable", `Can't reach the Boat API to create a sandbox: ${failure.detail}`)),
157
+ ProviderRefused: (failure) => Effect.fail(unavailable(creationReason(failure), `Boat didn't create a ${settings.size} sandbox: ${failureText(failure)}`)),
158
+ })));
159
+ const endpoint = Effect.fn("BoatDriver.endpoint")(function* (id, interval) {
160
+ yield* Effect.annotateCurrentSpan({ worker, machine: id });
161
+ while (true) {
162
+ const { sandbox } = yield* api
163
+ .send("GET", `/sandboxes/${id}`)
164
+ .pipe(Effect.flatMap(api.decode(OneSandbox, "read the sandbox")));
165
+ if (RUNNING.has(sandbox.state))
166
+ break;
167
+ if (ENDED.has(sandbox.state))
168
+ return yield* unavailable("capacity", `The new Boat sandbox ended in state ${sandbox.state}${sandbox.error === undefined || sandbox.error === null ? "" : `: ${sandbox.error}`}`);
169
+ yield* Effect.logDebug("Waiting for the Boat sandbox to be ready", {
170
+ worker,
171
+ machine: id,
172
+ state: sandbox.state,
173
+ });
174
+ yield* interval;
175
+ }
176
+ const answer = yield* api
177
+ .send("POST", `/sandboxes/${id}/sshkey`, { body: { key: publicKey } })
178
+ .pipe(Effect.flatMap(api.decode(SshKey, "add the SSH key")));
179
+ const target = destinationOf(answer);
180
+ if (Option.isNone(target))
181
+ return yield* unavailable("provider", "Boat reported no SSH address for the new sandbox");
182
+ const user = answer.sshUser ?? "user";
183
+ return {
184
+ destination: SshDestination.make(`${user}@${target.value.host}`),
185
+ port: target.value.port,
186
+ hostKey: Option.fromUndefinedOr(answer.hostKey),
187
+ };
188
+ }, (effect) => effect.pipe(Effect.catchTags({
189
+ ProviderUnanswered: (failure) => Effect.fail(unavailable("unreachable", `Can't reach the Boat API to read a new sandbox: ${failure.detail}`)),
190
+ ProviderRefused: (failure) => Effect.fail(unavailable(failure.status === 404 ? "capacity" : creationReason(failure), failure.status === 404
191
+ ? "Boat removed the new sandbox before it was ready"
192
+ : `Boat couldn't ready the new sandbox: ${failureText(failure)}`)),
193
+ })));
194
+ const tag = Effect.fn("BoatDriver.tag")(function* (id, tags) {
195
+ yield* Effect.annotateCurrentSpan({ worker, machine: id });
196
+ yield* api
197
+ .send("PATCH", `/sandboxes/${id}`, { body: { name: nameOf(tags) } })
198
+ .pipe(Effect.mapError(apiError("rename the sandbox")));
199
+ });
200
+ const remove = Effect.fn("BoatDriver.remove")(function* (id) {
201
+ yield* Effect.annotateCurrentSpan({ worker, machine: id });
202
+ yield* api
203
+ .send("DELETE", `/sandboxes/${id}`, { headers: { "X-Ascii-Confirm-Delete": id } })
204
+ .pipe(Effect.asVoid, Effect.catchIf((failure) => failure._tag === "ProviderRefused" && failure.status === 404, () => Effect.void), Effect.mapError(apiError("delete the sandbox")));
205
+ });
206
+ const list = Effect.fn("BoatDriver.list")(function* (instance, listed) {
207
+ yield* Effect.annotateCurrentSpan({ worker: listed });
208
+ const prefix = `${NAME_PREFIX} ${tagValue(instance, 24)} ${tagValue(listed, 24)} `;
209
+ const machines = [];
210
+ let cursor = null;
211
+ do {
212
+ const query = new Map([["limit", "200"]]);
213
+ if (cursor !== null)
214
+ query.set("cursor", cursor);
215
+ const page = yield* api
216
+ .send("GET", "/sandboxes", { query: Object.fromEntries(query) })
217
+ .pipe(Effect.flatMap(api.decode(SandboxList, "list sandboxes")), Effect.mapError(apiError("list the sandboxes")));
218
+ for (const sandbox of page.sandboxes) {
219
+ if (!sandbox.name.startsWith(prefix))
220
+ continue;
221
+ const [, , workerTag = "", run = "", process = "", attempt = "-"] = sandbox.name.split(" ");
222
+ machines.push({
223
+ id: MachineId.make(sandbox.id),
224
+ tags: {
225
+ worker: workerTag,
226
+ run,
227
+ process,
228
+ attempt: attempt === "-" ? Option.none() : Option.some(attempt),
229
+ },
230
+ createdAt: Option.getOrElse(Option.flatMap(Option.fromNullishOr(sandbox.createdAt), (at) => DateTime.make(at)), () => DateTime.makeUnsafe(0)),
231
+ });
232
+ }
233
+ cursor = page.pageInfo?.hasMore === true ? page.pageInfo.nextCursor : null;
234
+ } while (cursor !== null);
235
+ return machines;
236
+ });
237
+ return {
238
+ provider: "boat",
239
+ verify,
240
+ create,
241
+ endpoint,
242
+ tag,
243
+ remove,
244
+ list,
245
+ };
246
+ });
247
+ //# sourceMappingURL=boat.js.map