withruntime 0.33.1 → 0.33.3

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.
package/dist/ci-cli.d.ts CHANGED
@@ -5,6 +5,6 @@ type Out = {
5
5
  write: (text: string) => void;
6
6
  error: (text: string) => void;
7
7
  };
8
- export declare const CI_HELP = "runtime ci <command>\n\n CI/CD checks each push and pull request of a connected GitHub repository in\n sandboxes, as the steps its config file names. A fork's pull request waits\n until a writer re-runs it on GitHub. In a private beta: outside it, each\n command says how to ask to join.\n\n import [dir] [--write] [--force] This folder's GitHub Actions workflows and CircleCI\n config as a runtime.yml: printed, or written with\n --write; what isn't carried over is noted at its top.\n Needs no key and sends nothing\n connect <owner/name> [--branch main] [--config runtime.yml] [--installation <id>]\n [--trust <app-slug>[bot]]... [--name <name>]\n Connect a repository; prints its id. Takes a full key.\n --trust runs that App's pushes as its own from the start\n repos [ls] [--all] Connected repositories, with the Apps each trusts;\n --all: disconnected ones too\n repos trust <repoId | owner/name> <app-slug>[bot]...\n Run that App's pushes as the repository's own code,\n keeping the Apps it trusts already\n repos untrust <repoId | owner/name> <app-slug>[bot]...\n Stop trusting those Apps, keeping the rest\n disconnect <repoId | owner/name> Stop checking it; its checks still going are cancelled\n ls [--repository <repoId>] [--branch <name>] [--commit <sha>] [--pr <number>]\n [--state <state>]... [--limit 20] Checks, newest first, with their jobs by state\n get <checkId> One check and its jobs in plan order\n cancel <checkId> Cancel a queued or running check: its jobs end,\n its machines are let go, GitHub is told\n logs <jobId> A job's output, its last 256 KiB, and its sandboxes\n tests <repoId> [--quarantined] Its test files: time, recent outcomes, quarantine\n flakes [--repository <repoId>] [--all] Flaky tests: each quarantined (or still gating), its\n owner and 48-hour deadline; --all: ended ones too\n flake <flakeId> One flaky test: its proof, owner, and recent runs\n flake prove <flakeId> Its 100 proof runs again at main's next commit\n flake hand <flakeId> <login | org/team> Hand it to someone else\n flake delete <flakeId> --reason <why> Say why its test is deleted; it ends once main\n no longer holds the file\n\n A failed job's sandbox may be kept paused: `runtime sandboxes wake <id>`, then\n `runtime sandboxes shell <id>` to look around where it failed.\n\nExamples\n runtime ci import --write\n runtime ci connect acme/web --config .runtime/ci.yml\n runtime ci repos trust acme/web acme-bot[bot]\n runtime ci ls --commit $(git rev-parse HEAD)\n runtime ci logs <jobId>\n";
8
+ export declare const CI_HELP = "runtime ci <command>\n\n CI/CD checks each push and pull request of a connected GitHub repository in\n sandboxes, as the steps its config file names. A fork's pull request runs\n nothing; a writer can open it again from a branch of the repository. In a\n private beta: outside it, each command says how to ask to join.\n\n import [dir] [--write] [--force] This folder's GitHub Actions workflows and CircleCI\n config as a runtime.yml: printed, or written with\n --write; what isn't carried over is noted at its top.\n Needs no key and sends nothing\n connect <owner/name> [--branch main] [--config runtime.yml] [--installation <id>]\n [--trust <app-slug>[bot]]... [--name <name>]\n Connect a repository; prints its id. Takes a full key.\n --trust runs that App's pushes as its own from the start\n repos [ls] [--all] Connected repositories, with the Apps each trusts;\n --all: disconnected ones too\n repos trust <repoId | owner/name> <app-slug>[bot]...\n Run that App's pushes as the repository's own code,\n keeping the Apps it trusts already\n repos untrust <repoId | owner/name> <app-slug>[bot]...\n Stop trusting those Apps, keeping the rest\n repos undo <repoId | owner/name> on|off When main's check fails, find the first commit that\n fails it and open a pull request reverting it, which\n lands once its check passes. Off until turned on;\n takes an owner's or admin's full key\n disconnect <repoId | owner/name> Stop checking it; its checks still going are cancelled\n checks [--repository <repoId>] [--branch <name>] [--commit <sha>] [--pr <number>]\n [--state <state>]... [--limit 20] Checks, newest first, with their jobs by state (or ls)\n check <checkId> One check and its jobs in plan order (or get)\n cancel <checkId> Cancel a queued or running check: its jobs end,\n its machines are let go, GitHub is told\n logs <jobId> A job's output, its last 256 KiB, and its sandboxes\n tests <repoId> [--quarantined] Its test files: time, recent outcomes, quarantine\n flakes [--repository <repoId>] [--all] Flaky tests: each quarantined (or still gating), its\n owner and 48-hour deadline; --all: ended ones too\n flake <flakeId> One flaky test: its proof, owner, and recent runs\n flake prove <flakeId> Its 100 proof runs again at main's next commit\n flake hand <flakeId> <login | org/team> Hand it to someone else\n flake delete <flakeId> --reason <why> Say why its test is deleted; it ends once main\n no longer holds the file\n releases [--repository <repoId>] [--commit <sha>] [--limit 50]\n A release's steps as they were taken, oldest\n first: migrations, deploys, servers, waits\n\n A failed job's sandbox may be kept paused: `runtime sandboxes wake <id>`, then\n `runtime sandboxes shell <id>` to look around where it failed. A key reads the\n checks of repositories its own agent connected; an account-wide key reads and\n cancels every check of the account (switch one at withruntime.com/account/keys).\n\nExamples\n runtime ci import --write\n runtime ci connect acme/web --config .runtime/ci.yml\n runtime ci repos trust acme/web acme-bot[bot]\n runtime ci repos undo acme/web on\n runtime ci checks --commit $(git rev-parse HEAD)\n runtime ci cancel <checkId>\n runtime ci logs <jobId>\n";
9
9
  export declare function ciCommand(argv: string[], out: Out, client: () => Promise<Runtime>, kit: CliKit): Promise<number>;
10
10
  export {};
package/dist/ci-cli.js CHANGED
@@ -6,9 +6,9 @@ import { ci, } from "./products/ci.js";
6
6
  export const CI_HELP = `runtime ci <command>
7
7
 
8
8
  CI/CD checks each push and pull request of a connected GitHub repository in
9
- sandboxes, as the steps its config file names. A fork's pull request waits
10
- until a writer re-runs it on GitHub. In a private beta: outside it, each
11
- command says how to ask to join.
9
+ sandboxes, as the steps its config file names. A fork's pull request runs
10
+ nothing; a writer can open it again from a branch of the repository. In a
11
+ private beta: outside it, each command says how to ask to join.
12
12
 
13
13
  import [dir] [--write] [--force] This folder's GitHub Actions workflows and CircleCI
14
14
  config as a runtime.yml: printed, or written with
@@ -25,10 +25,14 @@ export const CI_HELP = `runtime ci <command>
25
25
  keeping the Apps it trusts already
26
26
  repos untrust <repoId | owner/name> <app-slug>[bot]...
27
27
  Stop trusting those Apps, keeping the rest
28
+ repos undo <repoId | owner/name> on|off When main's check fails, find the first commit that
29
+ fails it and open a pull request reverting it, which
30
+ lands once its check passes. Off until turned on;
31
+ takes an owner's or admin's full key
28
32
  disconnect <repoId | owner/name> Stop checking it; its checks still going are cancelled
29
- ls [--repository <repoId>] [--branch <name>] [--commit <sha>] [--pr <number>]
30
- [--state <state>]... [--limit 20] Checks, newest first, with their jobs by state
31
- get <checkId> One check and its jobs in plan order
33
+ checks [--repository <repoId>] [--branch <name>] [--commit <sha>] [--pr <number>]
34
+ [--state <state>]... [--limit 20] Checks, newest first, with their jobs by state (or ls)
35
+ check <checkId> One check and its jobs in plan order (or get)
32
36
  cancel <checkId> Cancel a queued or running check: its jobs end,
33
37
  its machines are let go, GitHub is told
34
38
  logs <jobId> A job's output, its last 256 KiB, and its sandboxes
@@ -40,15 +44,22 @@ export const CI_HELP = `runtime ci <command>
40
44
  flake hand <flakeId> <login | org/team> Hand it to someone else
41
45
  flake delete <flakeId> --reason <why> Say why its test is deleted; it ends once main
42
46
  no longer holds the file
47
+ releases [--repository <repoId>] [--commit <sha>] [--limit 50]
48
+ A release's steps as they were taken, oldest
49
+ first: migrations, deploys, servers, waits
43
50
 
44
51
  A failed job's sandbox may be kept paused: \`runtime sandboxes wake <id>\`, then
45
- \`runtime sandboxes shell <id>\` to look around where it failed.
52
+ \`runtime sandboxes shell <id>\` to look around where it failed. A key reads the
53
+ checks of repositories its own agent connected; an account-wide key reads and
54
+ cancels every check of the account (switch one at withruntime.com/account/keys).
46
55
 
47
56
  Examples
48
57
  runtime ci import --write
49
58
  runtime ci connect acme/web --config .runtime/ci.yml
50
59
  runtime ci repos trust acme/web acme-bot[bot]
51
- runtime ci ls --commit $(git rev-parse HEAD)
60
+ runtime ci repos undo acme/web on
61
+ runtime ci checks --commit $(git rev-parse HEAD)
62
+ runtime ci cancel <checkId>
52
63
  runtime ci logs <jobId>
53
64
  `;
54
65
  const when = (ms) => (ms === null ? "-" : new Date(ms).toISOString());
@@ -181,6 +192,18 @@ export async function ciCommand(argv, out, client, kit) {
181
192
  print(`${repo.repository} trusts ${trusted(repo)}.`, repo);
182
193
  return 0;
183
194
  }
195
+ if (action === "undo") {
196
+ const id = need(more[0], "the repository's id or owner/name");
197
+ const say = need(more[1], "on or off");
198
+ if (say !== "on" && say !== "off")
199
+ throw usage(`${say} is neither on nor off: ${me} ci repos undo ${id} on`);
200
+ const found = await api.repositories.find(id);
201
+ const repo = await api.repositories.setUndo(found?.id ?? id, say === "on");
202
+ print(repo.undoCulprits
203
+ ? `${repo.repository}: when ${repo.productionBranch}'s check fails, its culprit is found and a pull request reverting it opens.`
204
+ : `${repo.repository}: a failing ${repo.productionBranch} is reported, never undone.`, repo);
205
+ return 0;
206
+ }
184
207
  if (action === "connect")
185
208
  return ciCommand(["connect", ...more], out, client, kit);
186
209
  if (action === "disconnect" || action === "rm")
@@ -192,6 +215,7 @@ export async function ciCommand(argv, out, client, kit) {
192
215
  "connect",
193
216
  "trust",
194
217
  "untrust",
218
+ "undo",
195
219
  "disconnect",
196
220
  ]);
197
221
  const args = parse(listing, ["all"], []);
@@ -201,13 +225,14 @@ export async function ciCommand(argv, out, client, kit) {
201
225
  })).toArray(Infinity);
202
226
  print(all.length
203
227
  ? table([
204
- ["ID", "REPOSITORY", "BRANCH", "CONFIG", "TRUSTED APPS", "STATE"],
228
+ ["ID", "REPOSITORY", "BRANCH", "CONFIG", "TRUSTED APPS", "UNDO", "STATE"],
205
229
  ...all.map((r) => [
206
230
  r.id,
207
231
  r.repository,
208
232
  r.productionBranch,
209
233
  r.configPath,
210
234
  trusted(r),
235
+ r.undoCulprits ? "on" : "off",
211
236
  r.state,
212
237
  ]),
213
238
  ])
@@ -217,7 +242,8 @@ export async function ciCommand(argv, out, client, kit) {
217
242
  case "disconnect":
218
243
  return disconnect(need(rest[0], "the repository's id or owner/name"));
219
244
  case "ls":
220
- case "list": {
245
+ case "list":
246
+ case "checks": {
221
247
  const args = parse(rest, [], ["repository", "branch", "commit", "pr", "state", "limit"]);
222
248
  const commit = flag(args, "commit");
223
249
  if (commit !== undefined && !/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(commit))
@@ -254,7 +280,8 @@ export async function ciCommand(argv, out, client, kit) {
254
280
  : "No checks.", all);
255
281
  return 0;
256
282
  }
257
- case "get": {
283
+ case "get":
284
+ case "check": {
258
285
  const check = await api.checks.get(need(rest[0], "a check's id"));
259
286
  const job = (j) => [
260
287
  j.id,
@@ -275,6 +302,11 @@ export async function ciCommand(argv, out, client, kit) {
275
302
  }
276
303
  case "cancel": {
277
304
  const check = await api.checks.cancel(need(rest[0], "a check's id (runtime ci ls)"));
305
+ // A key that may cancel the check but not read it is told its state alone.
306
+ if (!("commit" in check)) {
307
+ print(`${check.id}: ${check.state}`, check);
308
+ return 0;
309
+ }
278
310
  print(`${check.repository} ${check.commit.slice(0, 12)} ${where(check)}: ${check.state}${check.reason ? ` (${check.reason})` : ""}`, check);
279
311
  return 0;
280
312
  }
@@ -351,6 +383,30 @@ export async function ciCommand(argv, out, client, kit) {
351
383
  ].join("\n"), f);
352
384
  return 0;
353
385
  }
386
+ case "releases": {
387
+ const args = parse(rest, [], ["repository", "commit", "limit"]);
388
+ const repositoryId = flag(args, "repository");
389
+ const commit = flag(args, "commit");
390
+ const limit = flag(args, "limit");
391
+ const steps = (await api.releases.list({
392
+ limit: limit ? whole(limit, "--limit") : 50,
393
+ ...(repositoryId ? { repositoryId } : {}),
394
+ ...(commit ? { commit } : {}),
395
+ })).data.reverse();
396
+ print(steps.length
397
+ ? table([
398
+ ["AT", "RELEASE", "BY", "SERVER", "STEP"],
399
+ ...steps.map((s) => [
400
+ when(s.at),
401
+ s.commit.slice(0, 9),
402
+ s.source,
403
+ s.host ?? "-",
404
+ s.line,
405
+ ]),
406
+ ])
407
+ : "No release steps.", steps);
408
+ return 0;
409
+ }
354
410
  case "tests": {
355
411
  const args = parse(rest, ["quarantined"], []);
356
412
  const repositoryId = need(args.positional[0], "a repository's id (runtime ci repos)");
@@ -379,6 +435,8 @@ export async function ciCommand(argv, out, client, kit) {
379
435
  "connect",
380
436
  "repos",
381
437
  "disconnect",
438
+ "checks",
439
+ "check",
382
440
  "ls",
383
441
  "get",
384
442
  "cancel",
@@ -386,6 +444,7 @@ export async function ciCommand(argv, out, client, kit) {
386
444
  "tests",
387
445
  "flakes",
388
446
  "flake",
447
+ "releases",
389
448
  ]);
390
449
  }
391
450
  /** A review's verdict in words: each finding with its place and fix, then
package/dist/index.d.ts CHANGED
@@ -82,4 +82,4 @@ export type { Agent, AgentDefinition, AgentEvent, AgentMemoryEntry, AgentMemoryF
82
82
  export { AGENT_TEMPLATES, AGENT_USES } from "./products/agents.js";
83
83
  export type { AgentsDeployChange, AgentsDeployOptions, AgentsDeployResult, AgentsFile, AgentsFileAgent, AgentsFileTrigger, AgentsSource, } from "./agents-deploy.js";
84
84
  export type { Container, ContainerState, CreateContainer, UpdateContainer, } from "./products/containers.js";
85
- export type { CiFlake, CiFlakeDetail, CiRepository, CiTestResult, ConnectCiRepository, ListCiFlakes, ListCiRepositories, UpdateCiFlake, } from "./products/ci.js";
85
+ export type { CiFlake, CiFlakeDetail, CiReleaseStep, CiRepository, CiTestResult, ConnectCiRepository, ListCiFlakes, ListCiReleaseSteps, ListCiRepositories, UpdateCiFlake, } from "./products/ci.js";
package/dist/keys.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFile } from "node:child_process";
2
- import { generateKeyPair, privateDecrypt } from "node:crypto";
2
+ import { privateDecrypt } from "node:crypto";
3
3
  import { hostname } from "node:os";
4
4
  import { promisify } from "node:util";
5
5
  import { connectionOrigins } from "./credentials.js";
@@ -7,6 +7,7 @@ import { RuntimeError } from "./errors.js";
7
7
  import { named } from "./cli-name.js";
8
8
  import { sandboxIdentityHeaders } from "./identity.js";
9
9
  import { describeRoute, envFetch } from "./proxy.js";
10
+ import { sealing } from "./sealing.js";
10
11
  const KEY = /^rtcloud_([a-f0-9-]{36})_[A-Za-z0-9_-]{43}$/;
11
12
  /** How often to ask whether the owner has answered. The website answers
12
13
  * "pending" to anything faster than every 4.5 seconds. */
@@ -79,16 +80,19 @@ export async function createKey(request, env, options = {}) {
79
80
  async function post(path, body, key) {
80
81
  const address = `${origins.auth}/api/connect/${path}`;
81
82
  let response;
83
+ // The sandbox's identity first: its own lookup has its own deadline, and
84
+ // the call's 20 s start when the call does.
85
+ const identity = await sandboxIdentityHeaders();
82
86
  try {
83
87
  response = await fetcher(address, {
84
88
  method: "POST",
85
89
  redirect: "error",
86
- signal: AbortSignal.timeout(20_000),
87
90
  headers: {
88
91
  "content-type": "application/json",
89
92
  ...(key ? { authorization: `Bearer ${key}` } : {}),
90
- ...(await sandboxIdentityHeaders()),
93
+ ...identity,
91
94
  },
95
+ signal: AbortSignal.timeout(20_000),
92
96
  body: JSON.stringify(body),
93
97
  });
94
98
  }
@@ -101,11 +105,7 @@ export async function createKey(request, env, options = {}) {
101
105
  throw failure(response.status === 429 ? "rate_limited" : "key_request_failed", typeof result.error === "string" ? result.error : "Runtime could not take this request.", response.status);
102
106
  return result;
103
107
  }
104
- const pair = await promisify(generateKeyPair)("rsa", {
105
- modulusLength: 2048,
106
- publicKeyEncoding: { type: "spki", format: "pem" },
107
- privateKeyEncoding: { type: "pkcs8", format: "pem" },
108
- });
108
+ const pair = await sealing.pair();
109
109
  const started = await post("start", {
110
110
  purpose: "key",
111
111
  agentName: name,
package/dist/login.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFile } from "node:child_process";
2
- import { generateKeyPair, privateDecrypt } from "node:crypto";
2
+ import { privateDecrypt } from "node:crypto";
3
3
  import { hostname } from "node:os";
4
4
  import { promisify } from "node:util";
5
5
  import { me, named } from "./cli-name.js";
@@ -7,6 +7,7 @@ import { Runtime } from "./client.js";
7
7
  import { checkBrowserOrigins, connectionOrigins, connectionStore } from "./credentials.js";
8
8
  import { sandboxIdentityHeaders } from "./identity.js";
9
9
  import { describeRoute, envFetch } from "./proxy.js";
10
+ import { sealing } from "./sealing.js";
10
11
  class ConnectionError extends Error {
11
12
  status;
12
13
  constructor(status, message) {
@@ -73,16 +74,19 @@ function connection(env, options) {
73
74
  async function request(path, body, key) {
74
75
  let response;
75
76
  const address = `${origins.auth}/api/connect/${path}`;
77
+ // The sandbox's identity first: its own lookup has its own deadline, and
78
+ // the call's 20 s start when the call does.
79
+ const identity = await sandboxIdentityHeaders();
76
80
  try {
77
81
  response = await fetcher(address, {
78
82
  method: "POST",
79
83
  redirect: "error",
80
- signal: AbortSignal.timeout(20_000),
81
84
  headers: {
82
85
  "content-type": "application/json",
83
86
  ...(key ? { authorization: `Bearer ${key}` } : {}),
84
- ...(await sandboxIdentityHeaders()),
87
+ ...identity,
85
88
  },
89
+ signal: AbortSignal.timeout(20_000),
86
90
  body: JSON.stringify(body ?? {}),
87
91
  });
88
92
  }
@@ -259,11 +263,7 @@ export async function beginDeviceLogin(env, agentName, options = {}) {
259
263
  let pending = await store.readPending();
260
264
  const resumed = pending !== null;
261
265
  if (!pending) {
262
- const pair = await promisify(generateKeyPair)("rsa", {
263
- modulusLength: 2048,
264
- publicKeyEncoding: { type: "spki", format: "pem" },
265
- privateKeyEncoding: { type: "pkcs8", format: "pem" },
266
- });
266
+ const pair = await sealing.pair();
267
267
  // The computer's own name goes to the approval page beside the code, so an
268
268
  // owner can tell this request from a link somebody else started.
269
269
  const started = await request("start", {
@@ -27,6 +27,9 @@ export type CiRepository = {
27
27
  /** The GitHub Apps (`<app-slug>[bot]`, lowercase, sorted) whose pushes run
28
28
  * as the code of someone who can write. */
29
29
  trustedApps: string[];
30
+ /** Whether a red production branch's culprit is undone by a pull request
31
+ * CI/CD opens: off until `setUndo` turns it on. */
32
+ undoCulprits: boolean;
30
33
  state: "connected" | "disconnected";
31
34
  };
32
35
  export type ConnectCiRepository = {
@@ -62,7 +65,7 @@ export type CiCheck = {
62
65
  /** The GitHub login that pushed, opened or re-ran it. */
63
66
  actor: string | null;
64
67
  /** The repository's writers' code. A fork's pull request is not, and runs
65
- * nothing until a writer re-runs it on GitHub. */
68
+ * nothing; a writer can open it again from a branch of the repository. */
66
69
  trusted: boolean;
67
70
  /** Queued for a writer's approval: nothing of it runs. */
68
71
  waiting: boolean;
@@ -165,6 +168,12 @@ export type CiReview = {
165
168
  export type CiCheckDetail = CiCheck & {
166
169
  jobs: CiJob[];
167
170
  };
171
+ /** A cancel's answer to a key that may cancel the check but not read it:
172
+ * its id and state, nothing of its jobs or logs. */
173
+ export type CiCancelled = {
174
+ id: string;
175
+ state: CiCheckState;
176
+ };
168
177
  /** A job with the tail of its output: at most 256 KiB, the oldest dropped first. */
169
178
  export type CiJobDetail = CiJob & {
170
179
  log: string;
@@ -288,6 +297,30 @@ export type UpdateCiFlake = {
288
297
  owner?: string;
289
298
  deleteReason?: string;
290
299
  };
300
+ /** One step a release took, as it took it: a migration's copy test or
301
+ * apply, a deploy ref moved, a server's canary or release, the website's
302
+ * deploy or promote, or a wait with why. */
303
+ export type CiReleaseStep = {
304
+ id: number;
305
+ repositoryId: string;
306
+ /** The commit this step of the release is of. */
307
+ commit: string;
308
+ /** Who took it: `mover` (the release on CI/CD's pass) or the deploy
309
+ * target that walks it (`hostd`, `controller`, `settings`, `follower`). */
310
+ source: string;
311
+ /** The server it was on, when it was on one. */
312
+ host: string | null;
313
+ line: string;
314
+ /** Unix milliseconds. */
315
+ at: number;
316
+ };
317
+ export type ListCiReleaseSteps = {
318
+ repositoryId?: string;
319
+ /** Only this commit's release. */
320
+ commit?: string;
321
+ limit?: number;
322
+ cursor?: string;
323
+ };
291
324
  export declare function ci(t: Transport): {
292
325
  repositories: {
293
326
  /** Connect a GitHub repository for checks. Only a full key connects
@@ -302,6 +335,12 @@ export declare function ci(t: Transport): {
302
335
  trust: (id: string, apps: string[], options?: RequestOptions) => Promise<CiRepository>;
303
336
  /** Takes Apps off those it trusts, keeping the rest. */
304
337
  untrust: (id: string, apps: string[], options?: RequestOptions) => Promise<CiRepository>;
338
+ /** Turn on (or off) the undo of a red production branch's culprit:
339
+ * when main's check fails, CI/CD finds the first commit that fails it
340
+ * and opens a pull request reverting it, which lands once its own
341
+ * check passes. Off until set; only an owner's or admin's full key
342
+ * sets it. */
343
+ setUndo: (id: string, on: boolean, options?: RequestOptions) => Promise<CiRepository>;
305
344
  /** Disconnect: its checks still going are cancelled; what ended stays. */
306
345
  disconnect: (id: string, options?: RequestOptions) => Promise<CiRepository>;
307
346
  };
@@ -311,8 +350,9 @@ export declare function ci(t: Transport): {
311
350
  /** A check with its jobs in plan order. */
312
351
  get: (id: string, options?: RequestOptions) => Promise<CiCheckDetail>;
313
352
  /** Cancel a queued or running check: its jobs end and its machines
314
- * are let go. A check that has ended is answered as it is. */
315
- cancel: (id: string, options?: RequestOptions) => Promise<CiCheckDetail>;
353
+ * are let go. A check that has ended is answered as it is; to a key
354
+ * that may cancel it but not read it, with its id and state alone. */
355
+ cancel: (id: string, options?: RequestOptions) => Promise<CiCheckDetail | CiCancelled>;
316
356
  };
317
357
  jobs: {
318
358
  /** A job with the tail of its log and the sandboxes it ran in. */
@@ -322,6 +362,11 @@ export declare function ci(t: Transport): {
322
362
  /** A repository's test files by name, with their quarantine. */
323
363
  list: (repositoryId: string, query?: ListCiTests, options?: RequestOptions) => Promise<Page<CiTest>>;
324
364
  };
365
+ releases: {
366
+ /** The steps releases took, newest first, of one repository or one
367
+ * commit: only repositories whose releases CI/CD runs have any. */
368
+ list: (query?: ListCiReleaseSteps, options?: RequestOptions) => Promise<Page<CiReleaseStep>>;
369
+ };
325
370
  flakes: {
326
371
  /** Flaky tests, newest first; `state: ["quarantined", "gating"]`
327
372
  * lists the open ones. */
@@ -61,6 +61,17 @@ export function ci(t) {
61
61
  const drop = new Set(apps.map((a) => a.toLowerCase()));
62
62
  return setTrustedApps(repo.id, repo.trustedApps.filter((a) => !drop.has(a)), options);
63
63
  },
64
+ /** Turn on (or off) the undo of a red production branch's culprit:
65
+ * when main's check fails, CI/CD finds the first commit that fails it
66
+ * and opens a pull request reverting it, which lands once its own
67
+ * check passes. Off until set; only an owner's or admin's full key
68
+ * sets it. */
69
+ setUndo: (id, on, options) => t.json({
70
+ method: "POST",
71
+ path: `${at("/v1/ci/repositories", id)}:update`,
72
+ body: { undoCulprits: on },
73
+ ...options,
74
+ }),
64
75
  /** Disconnect: its checks still going are cancelled; what ended stays. */
65
76
  disconnect: (id, options) => t.json({
66
77
  method: "DELETE",
@@ -74,7 +85,8 @@ export function ci(t) {
74
85
  /** A check with its jobs in plan order. */
75
86
  get: (id, options) => t.json({ method: "GET", path: at("/v1/ci/checks", id), ...options }),
76
87
  /** Cancel a queued or running check: its jobs end and its machines
77
- * are let go. A check that has ended is answered as it is. */
88
+ * are let go. A check that has ended is answered as it is; to a key
89
+ * that may cancel it but not read it, with its id and state alone. */
78
90
  cancel: (id, options) => t.json({
79
91
  method: "POST",
80
92
  path: `${at("/v1/ci/checks", id)}:cancel`,
@@ -89,6 +101,11 @@ export function ci(t) {
89
101
  /** A repository's test files by name, with their quarantine. */
90
102
  list: (repositoryId, query = {}, options) => paged("/v1/ci/tests", { ...query, repositoryId }, options),
91
103
  },
104
+ releases: {
105
+ /** The steps releases took, newest first, of one repository or one
106
+ * commit: only repositories whose releases CI/CD runs have any. */
107
+ list: (query = {}, options) => paged("/v1/ci/releases", query, options),
108
+ },
92
109
  flakes: {
93
110
  /** Flaky tests, newest first; `state: ["quarantined", "gating"]`
94
111
  * lists the open ones. */
@@ -0,0 +1,6 @@
1
+ export declare const sealing: {
2
+ pair: () => Promise<{
3
+ publicKey: string;
4
+ privateKey: string;
5
+ }>;
6
+ };
@@ -0,0 +1,14 @@
1
+ import { generateKeyPair } from "node:crypto";
2
+ import { promisify } from "node:util";
3
+ /* The key pair a browser approval seals its answer to (RSA-OAEP, SHA-256):
4
+ a login's credential, or a key asked for with `keys create`. Made fresh
5
+ for each request, so only this process can open what the page sends. An
6
+ object, so a test file can make one pair and reuse it: each takes 80 ms of
7
+ CPU to make, most of the login and key tests' time (24 October 2026). */
8
+ export const sealing = {
9
+ pair: () => promisify(generateKeyPair)("rsa", {
10
+ modulusLength: 2048,
11
+ publicKeyEncoding: { type: "spki", format: "pem" },
12
+ privateKeyEncoding: { type: "pkcs8", format: "pem" },
13
+ }),
14
+ };
@@ -1,5 +1,5 @@
1
1
  import { RuntimeError } from "./errors.js";
2
- export declare const VERSION = "0.33.1";
2
+ export declare const VERSION = "0.33.3";
3
3
  export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
4
4
  /** Runtime's API as code inside a Runtime sandbox reaches it: the sandbox's
5
5
  * own host sends each request on to DEFAULT_BASE_URL over HTTPS. The API runs
package/dist/transport.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ConnectionError, DELIBERATE, errorFor, NOTHING_RAN, RuntimeError, WAITS_FOR_ROOM, } from "./errors.js";
2
2
  import { describeRoute, envFetch, openWebSocket, untrustedCertificate } from "./proxy.js";
3
3
  import { warningText } from "./product-renames.js";
4
- export const VERSION = "0.33.1";
4
+ export const VERSION = "0.33.3";
5
5
  export const DEFAULT_BASE_URL = "https://api.withruntime.com";
6
6
  /** Runtime's API as code inside a Runtime sandbox reaches it: the sandbox's
7
7
  * own host sends each request on to DEFAULT_BASE_URL over HTTPS. The API runs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "withruntime",
3
- "version": "0.33.1",
3
+ "version": "0.33.3",
4
4
  "license": "Apache-2.0",
5
5
  "mcpName": "com.withruntime/runtime",
6
6
  "type": "module",
@@ -258,7 +258,7 @@
258
258
  "llm"
259
259
  ],
260
260
  "dependencies": {
261
- "undici": "^7.29.1",
261
+ "undici": "^7.30.1",
262
262
  "yaml": "^2.8.1"
263
263
  },
264
264
  "peerDependencies": {