terminalhire 0.42.19 → 0.42.20

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.
@@ -28557,6 +28557,18 @@ var init_gceIdentity = __esm({
28557
28557
  }
28558
28558
  });
28559
28559
 
28560
+ // ../../packages/attest/dist/confidentialSpace.js
28561
+ import { createHash as createHash7, createPublicKey as createPublicKey3, verify as cryptoVerify3 } from "crypto";
28562
+ var CONFIDENTIAL_SPACE_ISSUER, CONFIDENTIAL_SPACE_OIDC_DISCOVERY_URL;
28563
+ var init_confidentialSpace = __esm({
28564
+ "../../packages/attest/dist/confidentialSpace.js"() {
28565
+ "use strict";
28566
+ init_gceIdentity();
28567
+ CONFIDENTIAL_SPACE_ISSUER = "https://confidentialcomputing.googleapis.com";
28568
+ CONFIDENTIAL_SPACE_OIDC_DISCOVERY_URL = `${CONFIDENTIAL_SPACE_ISSUER}/.well-known/openid-configuration`;
28569
+ }
28570
+ });
28571
+
28560
28572
  // ../../packages/attest/dist/venueInstanceName.js
28561
28573
  function venueInstanceName(runId) {
28562
28574
  return `th-run-${runId}`.replace(/_/g, "-").toLowerCase().slice(0, 62);
@@ -28591,6 +28603,7 @@ var init_dist2 = __esm({
28591
28603
  init_attestation();
28592
28604
  init_nonce();
28593
28605
  init_gceIdentity();
28606
+ init_confidentialSpace();
28594
28607
  init_venueInstanceName();
28595
28608
  init_verify();
28596
28609
  }
@@ -28841,7 +28854,7 @@ var init_result = __esm({
28841
28854
  });
28842
28855
 
28843
28856
  // ../../packages/envrun/dist/attestation.js
28844
- import { createHash as createHash7, randomBytes as randomBytes10 } from "crypto";
28857
+ import { createHash as createHash8, randomBytes as randomBytes10 } from "crypto";
28845
28858
  function contradicts(outcome, counts, exitCode) {
28846
28859
  const budget = OUTCOME_TO_BUDGET[outcome];
28847
28860
  if (budget === null)
@@ -28883,7 +28896,7 @@ function imageDigestOf(ref) {
28883
28896
  return at === -1 ? null : ref.slice(at + 1);
28884
28897
  }
28885
28898
  function sha256Hex(data) {
28886
- return createHash7("sha256").update(typeof data === "string" ? Buffer.from(data, "utf8") : data).digest("hex");
28899
+ return createHash8("sha256").update(typeof data === "string" ? Buffer.from(data, "utf8") : data).digest("hex");
28887
28900
  }
28888
28901
  function toTestRunResult(result, outputSha256) {
28889
28902
  return {
@@ -29506,6 +29519,49 @@ var init_boundary = __esm({
29506
29519
  });
29507
29520
 
29508
29521
  // ../../packages/envrun/dist/gcpPlacement.js
29522
+ function assertWorkloadImage(fn, value) {
29523
+ if (typeof value !== "string" || !WORKLOAD_IMAGE_REFERENCE.test(value)) {
29524
+ throw new GcpPlacementError(`${fn}: the workload image is missing or is not one plain image reference (${String(WORKLOAD_IMAGE_REFERENCE)}). It lands inside a comma-separated --metadata= list, so anything that could start a second key is refused.`);
29525
+ }
29526
+ }
29527
+ function venueImageFromEnv(env) {
29528
+ const image = env["VENUE_IMAGE"] ?? "";
29529
+ if (image === "" || image === "cos-stable")
29530
+ return { venueImage: "cos-stable" };
29531
+ if (image !== "confidential-space") {
29532
+ throw new GcpPlacementError("VENUE_IMAGE is set to something other than cos-stable or confidential-space. An unknown value is refused rather than booted as the default.");
29533
+ }
29534
+ const workloadImage = env["VENUE_WORKLOAD_IMAGE"] ?? "";
29535
+ if (workloadImage === "") {
29536
+ throw new GcpPlacementError("VENUE_IMAGE=confidential-space needs VENUE_WORKLOAD_IMAGE, the image reference the Confidential Space launcher runs. Nothing boots without one.");
29537
+ }
29538
+ assertWorkloadImage("venueImageFromEnv", workloadImage);
29539
+ return { venueImage: "confidential-space", workloadImage };
29540
+ }
29541
+ function bootImageFlags(p) {
29542
+ const image = p.venueImage ?? "cos-stable";
29543
+ if (image === "cos-stable")
29544
+ return ["--image-family=cos-stable", "--image-project=cos-cloud"];
29545
+ if (image !== "confidential-space") {
29546
+ throw new GcpPlacementError("gcpBootArgv: venueImage is not one of cos-stable, confidential-space. An unknown image is refused rather than booted as the default.");
29547
+ }
29548
+ const workloadImage = p.workloadImage;
29549
+ assertWorkloadImage("gcpBootArgv", workloadImage);
29550
+ return [
29551
+ // The production family. The debug family reports `dbgstat: enabled`, which the
29552
+ // intake's verifier refuses, so a debug boot could never produce a run we record.
29553
+ "--image-family=confidential-space",
29554
+ "--image-project=confidential-space-images",
29555
+ "--confidential-compute-type=SEV",
29556
+ // The deploy-workloads page says N2D with SEV may set MIGRATE for live migration,
29557
+ // and every other machine type needs TERMINATE. The spec chose TERMINATE: a
29558
+ // maintenance event ends the VM instead of moving it, which for a VM that lives at
29559
+ // most GCP_MAX_RUN_DURATION_SECONDS costs one run. Whether GCE boots this exact
29560
+ // combination is phase 5's measurement.
29561
+ "--maintenance-policy=TERMINATE",
29562
+ `--metadata=tee-image-reference=${workloadImage},tee-container-log-redirect=true`
29563
+ ];
29564
+ }
29509
29565
  function assertInstanceIdentity(fn, id) {
29510
29566
  const fields = [
29511
29567
  ["vmName", id.vmName, GCE_INSTANCE_NAME],
@@ -29531,8 +29587,8 @@ function gcpBootArgv(p) {
29531
29587
  `--project=${p.project}`,
29532
29588
  `--zone=${p.zone}`,
29533
29589
  `--machine-type=${p.machineType}`,
29534
- "--image-family=cos-stable",
29535
- "--image-project=cos-cloud",
29590
+ // What the VM boots, and the one part of this argv the two images do not share.
29591
+ ...bootImageFlags(p),
29536
29592
  // ── THE OUTER REAP. This, not the `finally` below, is what bounds the bill. ──
29537
29593
  //
29538
29594
  // An in-process `finally` is a best effort, not a reap. SIGKILL, a crashed
@@ -29610,7 +29666,7 @@ function gcpDeleteArgv(p) {
29610
29666
  "--quiet"
29611
29667
  ];
29612
29668
  }
29613
- var DEFAULT_GCP_PROJECT, DEFAULT_GCP_ZONE, DEFAULT_GCP_MACHINE_TYPE, GcpPlacementError, GCP_MAX_RUN_DURATION_SECONDS, GCP_MANAGED_LABEL_KEY, GCP_RUN_LABEL_KEY, GCP_LABEL_VALUE, GCE_INSTANCE_NAME, GCP_RESOURCE_ID;
29669
+ var DEFAULT_GCP_PROJECT, DEFAULT_GCP_ZONE, DEFAULT_GCP_MACHINE_TYPE, GcpPlacementError, GCP_MAX_RUN_DURATION_SECONDS, GCP_MANAGED_LABEL_KEY, GCP_RUN_LABEL_KEY, CONFIDENTIAL_SPACE_MACHINE_TYPE, WORKLOAD_IMAGE_REFERENCE, GCP_LABEL_VALUE, GCE_INSTANCE_NAME, GCP_RESOURCE_ID;
29614
29670
  var init_gcpPlacement = __esm({
29615
29671
  "../../packages/envrun/dist/gcpPlacement.js"() {
29616
29672
  "use strict";
@@ -29627,6 +29683,8 @@ var init_gcpPlacement = __esm({
29627
29683
  GCP_MAX_RUN_DURATION_SECONDS = 3600;
29628
29684
  GCP_MANAGED_LABEL_KEY = "th-managed";
29629
29685
  GCP_RUN_LABEL_KEY = "th-run";
29686
+ CONFIDENTIAL_SPACE_MACHINE_TYPE = "n2d-standard-2";
29687
+ WORKLOAD_IMAGE_REFERENCE = /^[A-Za-z0-9][A-Za-z0-9._/:@-]{0,511}$/;
29630
29688
  GCP_LABEL_VALUE = /^[a-z0-9_-]{1,63}$/;
29631
29689
  GCE_INSTANCE_NAME = /^[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?$/;
29632
29690
  GCP_RESOURCE_ID = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
@@ -30209,6 +30267,7 @@ function hostedVenue(opts = {}, io = defaultHostedVenueIo) {
30209
30267
  const zone = opts.zone ?? DEFAULT_GCP_ZONE;
30210
30268
  const gcloudConfig = opts.gcloudConfig ?? VENUE_GCLOUD_CONFIG;
30211
30269
  const keyFile = resolveServiceAccountKeyFile(opts);
30270
+ const image = venueImageFromEnv(process.env);
30212
30271
  const env = venueGcloudEnv(gcloudConfig);
30213
30272
  return {
30214
30273
  kind: "hosted-pool",
@@ -30220,7 +30279,8 @@ function hostedVenue(opts = {}, io = defaultHostedVenueIo) {
30220
30279
  project,
30221
30280
  zone,
30222
30281
  runId,
30223
- machineType: opts.machineType ?? DEFAULT_GCP_MACHINE_TYPE
30282
+ machineType: opts.machineType ?? (image.venueImage === "confidential-space" ? CONFIDENTIAL_SPACE_MACHINE_TYPE : DEFAULT_GCP_MACHINE_TYPE),
30283
+ ...image
30224
30284
  });
30225
30285
  ensureVenueServiceCredentials(gcloudConfig, keyFile, io);
30226
30286
  const boot = io.exec("gcloud", bootArgv, BOOT_TIMEOUT_MS, env);
@@ -32346,13 +32406,16 @@ function git(repoDir, args, allowNonZero = false) {
32346
32406
  }
32347
32407
  return res.stdout ?? "";
32348
32408
  }
32349
- function collectWorkingDiff(repoDir) {
32409
+ function collectWorkingDiff(repoDir, opts = {}) {
32350
32410
  if (!existsSync12(join26(repoDir, ".git"))) {
32351
32411
  throw new ThRunError(`${repoDir} is not a git checkout (no .git). \`th run\` ships the working diff, so it needs a repository to read one from.`);
32352
32412
  }
32353
32413
  const headSha = git(repoDir, ["rev-parse", "HEAD"]).trim();
32354
- const tracked = git(repoDir, ["diff", "HEAD", "--binary"]);
32355
- const trackedChanged = git(repoDir, ["diff", "HEAD", "--name-only"]).split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
32414
+ const diffBase = opts.base === void 0 ? headSha : assertDiffBase(repoDir, opts.base);
32415
+ const trackedArgs = opts.base === void 0 ? ["diff", "HEAD"] : ["diff", diffBase];
32416
+ const pathsEnd = opts.base === void 0 ? [] : ["--"];
32417
+ const tracked = git(repoDir, [...trackedArgs, "--binary", ...pathsEnd]);
32418
+ const trackedChanged = git(repoDir, [...trackedArgs, "--name-only", ...pathsEnd]).split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
32356
32419
  const untracked = git(repoDir, ["ls-files", "--others", "--exclude-standard"]).split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
32357
32420
  const parts = [];
32358
32421
  if (tracked.trim() !== "")
@@ -32362,7 +32425,17 @@ function collectWorkingDiff(repoDir) {
32362
32425
  if (one.trim() !== "")
32363
32426
  parts.push(one.replace(/\n*$/, "\n"));
32364
32427
  }
32365
- return { patch: parts.join(""), headSha, trackedChanged, untracked };
32428
+ return { patch: parts.join(""), headSha, diffBase, trackedChanged, untracked };
32429
+ }
32430
+ function assertDiffBase(repoDir, base) {
32431
+ if (!FULL_COMMIT_ID.test(base)) {
32432
+ throw new ThRunError(`the diff base must be a full 40-character commit id, got ${JSON.stringify(base)}.`);
32433
+ }
32434
+ const resolved = git(repoDir, ["rev-parse", "--verify", "--quiet", `${base}^{commit}`], true);
32435
+ if (resolved.trim() !== base) {
32436
+ throw new ThRunError(`the diff base ${base} is not a commit in ${repoDir}.`);
32437
+ }
32438
+ return base;
32366
32439
  }
32367
32440
  function isLocalPath(p) {
32368
32441
  return !UNC_PATH.test(p) && (p.startsWith("/") || WINDOWS_ABSOLUTE.test(p));
@@ -32934,8 +33007,9 @@ async function runVerification(req, ctx) {
32934
33007
  let diff = null;
32935
33008
  if (source.kind === "working-diff") {
32936
33009
  progress("collect", `reading the working diff from ${source.localRepoDir}`);
32937
- diff = collectWorkingDiff(source.localRepoDir);
32938
- progress("collect", `${String(diff.trackedChanged.length)} tracked, ${String(diff.untracked.length)} untracked, ${String(diff.patch.length)} bytes`);
33010
+ diff = collectWorkingDiff(source.localRepoDir, source.diffBase === void 0 ? {} : { base: source.diffBase });
33011
+ const from = source.diffBase === void 0 ? "" : `, from delivered baseline ${diff.diffBase.slice(0, 12)}`;
33012
+ progress("collect", `${String(diff.trackedChanged.length)} tracked, ${String(diff.untracked.length)} untracked, ${String(diff.patch.length)} bytes${from}`);
32939
33013
  } else {
32940
33014
  progress("collect", "dispatched run: the tree is the stored commit itself");
32941
33015
  }
@@ -33137,7 +33211,7 @@ async function runVerification(req, ctx) {
33137
33211
  await releaseWithoutThrowing(lease, progress);
33138
33212
  }
33139
33213
  }
33140
- var ThRunError, OUTPUT_TAIL_BYTES, ALLOWED_URL_SCHEMES, SCP_STYLE, URL_SCHEME, WINDOWS_ABSOLUTE, UNC_PATH, TRANSPORT_REASON, SHA_REASON, FULL_SHA, MIN_GIT_VERSION_FOR_END_OF_OPTIONS, CloneUnavailableError, GIT_ENV_ALLOWLIST, credentialFreeHomeDir, SSH_ISOLATION_ARGS, WITHHELD_SEGMENT, UNPARSEABLE_TARGET, FAILURE_LINE, REDACTED_TARGET_REPO, REDACTED_TARGET_SHA;
33214
+ var ThRunError, OUTPUT_TAIL_BYTES, FULL_COMMIT_ID, ALLOWED_URL_SCHEMES, SCP_STYLE, URL_SCHEME, WINDOWS_ABSOLUTE, UNC_PATH, TRANSPORT_REASON, SHA_REASON, FULL_SHA, MIN_GIT_VERSION_FOR_END_OF_OPTIONS, CloneUnavailableError, GIT_ENV_ALLOWLIST, credentialFreeHomeDir, SSH_ISOLATION_ARGS, WITHHELD_SEGMENT, UNPARSEABLE_TARGET, FAILURE_LINE, REDACTED_TARGET_REPO, REDACTED_TARGET_SHA;
33141
33215
  var init_thrun = __esm({
33142
33216
  "../../packages/envrun/dist/thrun.js"() {
33143
33217
  "use strict";
@@ -33155,6 +33229,7 @@ var init_thrun = __esm({
33155
33229
  ThRunError = class extends Error {
33156
33230
  };
33157
33231
  OUTPUT_TAIL_BYTES = 4e3;
33232
+ FULL_COMMIT_ID = /^[0-9a-f]{40}$/;
33158
33233
  ALLOWED_URL_SCHEMES = /* @__PURE__ */ new Set(["https", "http", "ssh", "git"]);
33159
33234
  SCP_STYLE = /^[A-Za-z0-9._~+-]+@[A-Za-z0-9._-]+:[^:]/;
33160
33235
  URL_SCHEME = /^([A-Za-z][A-Za-z0-9+.-]*):\/\//;
@@ -34013,6 +34088,7 @@ __export(dist_exports, {
34013
34088
  ALEMBIC_IMAGE: () => ALEMBIC_IMAGE,
34014
34089
  ATTEST_REFUSAL_REASONS: () => ATTEST_REFUSAL_REASONS,
34015
34090
  BOOKKEEPING_TABLES: () => BOOKKEEPING_TABLES,
34091
+ CONFIDENTIAL_SPACE_MACHINE_TYPE: () => CONFIDENTIAL_SPACE_MACHINE_TYPE,
34016
34092
  CONTAINMENT_UNAVAILABLE_PREFIX: () => CONTAINMENT_UNAVAILABLE_PREFIX,
34017
34093
  CloneUnavailableError: () => CloneUnavailableError,
34018
34094
  DAEMON_FACTS_FORMAT: () => DAEMON_FACTS_FORMAT,
@@ -34154,6 +34230,7 @@ __export(dist_exports, {
34154
34230
  toolingImageFor: () => toolingImageFor,
34155
34231
  unquoteDiffPath: () => unquoteDiffPath,
34156
34232
  venueGcloudEnv: () => venueGcloudEnv,
34233
+ venueImageFromEnv: () => venueImageFromEnv,
34157
34234
  venueStagePaths: () => venueStagePaths,
34158
34235
  verifyWorkingDiff: () => verifyWorkingDiff
34159
34236
  });
@@ -34465,7 +34542,7 @@ import {
34465
34542
  readdirSync as readdirSync3
34466
34543
  } from "fs";
34467
34544
  import { join as join28, dirname as dirname8, isAbsolute as isAbsolute4, resolve as pathResolve } from "path";
34468
- import { createHash as createHash8 } from "crypto";
34545
+ import { createHash as createHash9 } from "crypto";
34469
34546
  import { homedir as homedir14, hostname as osHostname } from "os";
34470
34547
  import { execFile as execFile3, execFileSync as execFileSync2, spawnSync as spawnSync8 } from "child_process";
34471
34548
  import { promisify as promisify3 } from "util";
@@ -37849,16 +37926,26 @@ function writePackFile(destDir, relPath, content, what) {
37849
37926
  return { written: true, reason: null, sha256: sha256OfUtf8(content) };
37850
37927
  }
37851
37928
  function sha256OfUtf8(content) {
37852
- return createHash8("sha256").update(content, "utf8").digest("hex");
37929
+ return createHash9("sha256").update(content, "utf8").digest("hex");
37853
37930
  }
37854
- function writeWorkspacePack(destDir, spec, claim) {
37931
+ function writeWorkspacePack(destDir, spec, claim, delivery) {
37932
+ if (delivery !== "full" && delivery !== "sparse") {
37933
+ throw new TypeError(
37934
+ `writeWorkspacePack: delivery must be 'full' or 'sparse', got ${String(delivery)}`
37935
+ );
37936
+ }
37855
37937
  const gate = ensureExcludedPackDir(destDir);
37856
37938
  if (!gate.ok) {
37857
37939
  const refused = { written: false, reason: gate.reason };
37858
37940
  return { brief: refused, verify: refused, agents: refused };
37859
37941
  }
37860
37942
  const brief = typeof spec !== "string" || spec.trim() === "" ? { written: false, reason: "the server sent no task for this posting" } : writePackFile(destDir, BRIEF_REL_PATH, spec, "task");
37861
- const verify = writePackFile(destDir, VERIFY_REL_PATH, renderVerifyDoc(claim), "verify note");
37943
+ const verify = writePackFile(
37944
+ destDir,
37945
+ VERIFY_REL_PATH,
37946
+ renderVerifyDoc(claim, delivery),
37947
+ "verify note"
37948
+ );
37862
37949
  const agents = writePackFile(
37863
37950
  destDir,
37864
37951
  AGENTS_REL_PATH,
@@ -37872,20 +37959,30 @@ function packSafeId(claim) {
37872
37959
  const id = String(claim?.id ?? "");
37873
37960
  return PACK_SAFE_ID.test(id) ? id : "<your claim id \u2014 see: terminalhire claim list>";
37874
37961
  }
37875
- function renderVerifyDoc(claim) {
37962
+ function renderVerifyDoc(claim, delivery) {
37876
37963
  const id = packSafeId(claim);
37964
+ const runStep2 = delivery === "full" ? ` ${nextStep("terminalhire run")}
37965
+
37966
+ It tests every change since the delivered baseline, committed or not, which
37967
+ is what submit sends once it is all committed.` : ` ${nextStep("terminalhire run --target <clone URL of the full repository>")}
37968
+
37969
+ It tests every change since the delivered baseline, committed or not, which
37970
+ is what submit sends once it is all committed. This delivery contains only the
37971
+ files you were granted, so the local run needs the whole repository; if you
37972
+ don't have access to it, skip to step 4, because the poster-side run in step 5
37973
+ is the verdict that counts.`;
37877
37974
  return `# Verifying claim ${id}
37878
37975
 
37879
37976
  This workspace was delivered by terminalhire for a posting. "Done" is
37880
37977
  judged on the diff: the submitted patch is the change from the delivered
37881
37978
  baseline (this repo's root commit) to HEAD, so only committed, tracked changes
37882
- count.
37979
+ count. Submit needs a clean tree; run does not.
37883
37980
 
37884
37981
  1. Read the poster's task first, when there is one: ${BRIEF_REL_PATH}
37885
37982
  2. Work on the claim branch this delivery checked out, committing as you go.
37886
37983
  3. To verify locally in terminalhire's sandboxed runner, from this directory:
37887
37984
 
37888
- terminalhire run
37985
+ ${runStep2}
37889
37986
 
37890
37987
  4. Submit \u2014 the only step that sends anything off this machine. It prints what it
37891
37988
  is about to send first, and asks y/N only at an interactive terminal:
@@ -38264,11 +38361,13 @@ async function cmdSliceFullTier(claims, id, local, fullTierBody, flags, cloneRep
38264
38361
  );
38265
38362
  process.exit(1);
38266
38363
  }
38267
- const pack = writeWorkspacePack(dest, body.spec, claim);
38364
+ const pack = writeWorkspacePack(dest, body.spec, claim, "full");
38268
38365
  const working = claims.updateClaim(claim.id, {
38269
38366
  worktreePath: dest,
38270
38367
  branch,
38271
38368
  state: "working",
38369
+ // `terminalhire run` clones this commit when run from the workspace (TERM-1098).
38370
+ baseSha,
38272
38371
  workspacePack: {
38273
38372
  brief: pack.brief.written === true,
38274
38373
  verify: pack.verify.written === true,
@@ -38424,7 +38523,7 @@ async function attemptSliceDelivery(id, flags = {}) {
38424
38523
  let pack;
38425
38524
  try {
38426
38525
  await sh("git", ["-C", dest, "init"]);
38427
- pack = writeWorkspacePack(dest, body.spec, claim);
38526
+ pack = writeWorkspacePack(dest, body.spec, claim, "sparse");
38428
38527
  await sh("git", ["-C", dest, "add", "-A"]);
38429
38528
  await sh("git", [
38430
38529
  "-C",
@@ -38461,6 +38560,11 @@ async function attemptSliceDelivery(id, flags = {}) {
38461
38560
  worktreePath: dest,
38462
38561
  branch,
38463
38562
  state: "working",
38563
+ // The commit these files were cut from, so `terminalhire run` can clone it unasked
38564
+ // (TERM-1098). Recorded only when it is a full sha: a slice confirmed before the
38565
+ // server kept one answers null, and a value of any other shape is not a commit we
38566
+ // could clone, so neither is written and `run` asks for `--sha` instead.
38567
+ ...typeof body.baseSha === "string" && /^[0-9a-f]{40}$/.test(body.baseSha) ? { baseSha: body.baseSha } : {},
38464
38568
  workspacePack: {
38465
38569
  brief: pack.brief.written === true,
38466
38570
  verify: pack.verify.written === true,