terminalhire 0.42.26 → 0.42.28

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.
@@ -106,7 +106,7 @@ function buildApprovalsNudge(awaitingApproval) {
106
106
  if (!Number.isInteger(awaitingApproval) || awaitingApproval <= 0) return null;
107
107
  const n = awaitingApproval;
108
108
  return ` \u26A0 ${n} claim${n === 1 ? "" : "s"} awaiting poster approval \u2014 terminalhire cannot check in the background until you enrol:
109
- terminalhire claim --push --keep-updated (or check one now: terminalhire claim slice <id>)`;
109
+ terminalhire claim --push --keep-updated (or check one now: terminalhire claim start <id>)`;
110
110
  }
111
111
  async function syncApprovedClaims({
112
112
  readAutoMarker,
@@ -29714,7 +29714,10 @@ var init_result = __esm({
29714
29714
  "detected",
29715
29715
  "none",
29716
29716
  "operator-declared",
29717
- "developer-declared"
29717
+ "developer-declared",
29718
+ // TERM-1221: the mapping chose the image from a runtime the POSTER declared, because the
29719
+ // repository's own files named none.
29720
+ "founder-declared"
29718
29721
  ];
29719
29722
  RUN_RESULT_SCHEMA = "terminalhire.verification-run/1";
29720
29723
  RUN_RESULT_FIELDS = [
@@ -30072,6 +30075,10 @@ var init_attestation2 = __esm({
30072
30075
  };
30073
30076
  IMAGE_SOURCE_IS_SIGNABLE = {
30074
30077
  detected: () => null,
30078
+ // The command table's `founder-declared` reasoning, applied to the environment: the poster
30079
+ // chose the runtime and the poster is paying, so a runtime they declared is theirs to stand
30080
+ // behind. The image itself is still the runtime mapping's pick, not a name anyone typed.
30081
+ "founder-declared": () => null,
30075
30082
  "operator-declared": () => "had its container image chosen by an operator out of band, and the signed vocabulary has no member for that. Signing it would present a hand-picked environment as the one the runtime mapping selected.",
30076
30083
  "developer-declared": () => "had its container image supplied by the developer whose work is under verification. A chosen interpreter turns a red suite green as surely as a chosen test command.",
30077
30084
  // `none` means no image was booted, so an image name beside it is a contradiction — the
@@ -30485,7 +30492,10 @@ var init_boundary = __esm({
30485
30492
  },
30486
30493
  {
30487
30494
  code: "lockfile",
30488
- matches: (_p, _segments, base) => LOCKFILE_NAMES.has(base),
30495
+ // The `.lock` suffix is the server's rule since TERM-412; this copy missed it, so
30496
+ // the pre-flight passed `uv.lock` and bun's text `bun.lock` that the server refuses
30497
+ // (TERM-1235). The name set only covers lockfiles whose names do not end in `.lock`.
30498
+ matches: (_p, _segments, base) => LOCKFILE_NAMES.has(base) || base.endsWith(".lock"),
30489
30499
  why: "is a dependency lockfile. Nobody reads a lockfile diff, so a change to one is refused rather than shown"
30490
30500
  }
30491
30501
  ];
@@ -30758,8 +30768,14 @@ import { createHash as createHash9, X509Certificate } from "crypto";
30758
30768
  import { chmodSync as chmodSync3, existsSync as existsSync11, mkdtempSync as mkdtempSync3, readFileSync as readFileSync11, rmSync as rmSync8, writeFileSync as writeFileSync13 } from "fs";
30759
30769
  import { request as httpsRequest } from "https";
30760
30770
  import { createServer } from "net";
30761
- import { join as join24 } from "path";
30771
+ import { posix as posix3 } from "path";
30762
30772
  import { tmpdir as tmpdir3 } from "os";
30773
+ function refuseUnsupportedHost(io) {
30774
+ const platform = io.platform ?? process.platform;
30775
+ if (platform === "win32") {
30776
+ throw new HostedVenueError("the hosted venue cannot run from a Windows host: it reaches the venue daemon through an ssh-forwarded Unix socket, and OpenSSH on Windows cannot forward to a local socket path. Run from Linux, macOS or WSL, or use --placement local-docker.", "ours");
30777
+ }
30778
+ }
30763
30779
  function credentialInGitConfig(text) {
30764
30780
  for (const match2 of text.matchAll(/\b([a-z][a-z0-9+.-]*):\/\/(\S+)/gi)) {
30765
30781
  const scheme = (match2[1] ?? "").toLowerCase();
@@ -31296,6 +31312,7 @@ function hostedVenueAvailable(opts = {}, io = defaultHostedVenueIo) {
31296
31312
  const config = opts.gcloudConfig ?? VENUE_GCLOUD_CONFIG;
31297
31313
  const env = venueGcloudEnv(config);
31298
31314
  try {
31315
+ refuseUnsupportedHost(io);
31299
31316
  assertServiceCredentials(config, io);
31300
31317
  } catch {
31301
31318
  return false;
@@ -31736,6 +31753,7 @@ function hostedVenue(opts = {}, io = defaultHostedVenueIo) {
31736
31753
  ...image
31737
31754
  };
31738
31755
  let bootArgv = gcpBootArgv(bootParams(CLIENT_CERT_STAND_IN));
31756
+ refuseUnsupportedHost(io);
31739
31757
  ensureVenueServiceCredentials(gcloudConfig, keyFile, io);
31740
31758
  const clientTls = csImageDigest === null ? null : { ...makeClientTls(io, allocated, vm), imageDigest: csImageDigest };
31741
31759
  if (clientTls !== null)
@@ -32029,7 +32047,8 @@ function makeLease(p) {
32029
32047
  throw new HostedVenueError(`could not stage ${from} onto ${p.vm}: ${execDetail(res).slice(0, 300)}`);
32030
32048
  }
32031
32049
  };
32032
- const { jail: localJail, tmp: localTmp } = localJailPaths(local.scratchRoot);
32050
+ const localJail = join24(local.scratchRoot, JAIL_SEGMENT);
32051
+ const localTmp = join24(local.scratchRoot, JAIL_TMP_SEGMENT);
32033
32052
  const required = [
32034
32053
  localTmp,
32035
32054
  join24(localJail, JAIL_PASSWD_FILE),
@@ -32178,7 +32197,7 @@ function makeLease(p) {
32178
32197
  }
32179
32198
  };
32180
32199
  }
32181
- var SSH_READY_BUDGET_MS, SSH_PROBE_INTERVAL_MS, SSH_PROBE_TIMEOUT_MS, TUNNEL_BUDGET_MS, TUNNEL_POLL_INTERVAL_MS, GOOGLE_JWKS_URL, JWKS_FETCH_TIMEOUT_MS, CREDENTIAL_QUERY_PARAM, UNDECODABLE, STAGE_PUSH_TIMEOUT_MS, DISPATCHED_PROBE_TIMEOUT_MS, DISPATCHED_STATUS_ARGV, DISPATCHED_GIT_CANDIDATES, PROXY_CLEANUP_TIMEOUT_MS, OWNER_PROBE_TIMEOUT_MS, BOOT_TIMEOUT_MS, MKDIR_TIMEOUT_MS, DELETE_TIMEOUT_MS, LOCAL_GCLOUD_TIMEOUT_MS, SERVICE_ACCOUNT_ACTIVATE_TIMEOUT_MS, SOCKET_DIR_PREFIX, VENUE_SOCKET_NAME, HostedVenueError, VENUE_GCLOUD_CONFIG, SERVICE_ACCOUNT_SUFFIX, GCLOUD_PRINCIPAL_OVERRIDES, defaultHostedVenueIo, VENUE_SSH_USER, GCE_METADATA_IDENTITY_URL, COMPACT_JWT, VOLUME_CREATE_TIMEOUT_MS, EXEC_PROBE_TIMEOUT_MS, POPULATE_TIMEOUT_MS, STAGE_PROOF_PREFIX, IAP_NOT_READY, IAP_BACKEND_UNREACHABLE, IAP_DENIED, TERMINAL_GCP, INSTANCE_NOT_RUNNING, PREEMPTED, HOST_KEY_MISMATCH, SSH_KEY_NOT_READY, DAEMON_NOT_READY, SSH_NOT_ANSWERING, CS_ATTEST_PORT, CS_DOCKER_PORT, CS_READY_BUDGET_MS, CS_ATTEST_TIMEOUT_MS, CS_PULL_TIMEOUT_MS, CS_ATTEST_INTERVAL_MS, OPENSSL_TIMEOUT_MS, CS_RUN_ARGS, CS_GUEST_USER, CS_STAGE_ROOT, CLIENT_CERT_STAND_IN;
32200
+ var join24, SSH_READY_BUDGET_MS, SSH_PROBE_INTERVAL_MS, SSH_PROBE_TIMEOUT_MS, TUNNEL_BUDGET_MS, TUNNEL_POLL_INTERVAL_MS, GOOGLE_JWKS_URL, JWKS_FETCH_TIMEOUT_MS, CREDENTIAL_QUERY_PARAM, UNDECODABLE, STAGE_PUSH_TIMEOUT_MS, DISPATCHED_PROBE_TIMEOUT_MS, DISPATCHED_STATUS_ARGV, DISPATCHED_GIT_CANDIDATES, PROXY_CLEANUP_TIMEOUT_MS, OWNER_PROBE_TIMEOUT_MS, BOOT_TIMEOUT_MS, MKDIR_TIMEOUT_MS, DELETE_TIMEOUT_MS, LOCAL_GCLOUD_TIMEOUT_MS, SERVICE_ACCOUNT_ACTIVATE_TIMEOUT_MS, SOCKET_DIR_PREFIX, VENUE_SOCKET_NAME, HostedVenueError, VENUE_GCLOUD_CONFIG, SERVICE_ACCOUNT_SUFFIX, GCLOUD_PRINCIPAL_OVERRIDES, defaultHostedVenueIo, VENUE_SSH_USER, GCE_METADATA_IDENTITY_URL, COMPACT_JWT, VOLUME_CREATE_TIMEOUT_MS, EXEC_PROBE_TIMEOUT_MS, POPULATE_TIMEOUT_MS, STAGE_PROOF_PREFIX, IAP_NOT_READY, IAP_BACKEND_UNREACHABLE, IAP_DENIED, TERMINAL_GCP, INSTANCE_NOT_RUNNING, PREEMPTED, HOST_KEY_MISMATCH, SSH_KEY_NOT_READY, DAEMON_NOT_READY, SSH_NOT_ANSWERING, CS_ATTEST_PORT, CS_DOCKER_PORT, CS_READY_BUDGET_MS, CS_ATTEST_TIMEOUT_MS, CS_PULL_TIMEOUT_MS, CS_ATTEST_INTERVAL_MS, OPENSSL_TIMEOUT_MS, CS_RUN_ARGS, CS_GUEST_USER, CS_STAGE_ROOT, CLIENT_CERT_STAND_IN;
32182
32201
  var init_hostedVenue = __esm({
32183
32202
  "../../packages/envrun/dist/hostedVenue.js"() {
32184
32203
  "use strict";
@@ -32190,6 +32209,7 @@ var init_hostedVenue = __esm({
32190
32209
  init_labels();
32191
32210
  init_venueProof();
32192
32211
  init_venue();
32212
+ ({ join: join24 } = posix3);
32193
32213
  SSH_READY_BUDGET_MS = 18e4;
32194
32214
  SSH_PROBE_INTERVAL_MS = 5e3;
32195
32215
  SSH_PROBE_TIMEOUT_MS = 25e3;
@@ -34340,8 +34360,11 @@ function publishableTarget(url) {
34340
34360
  if (separatorInTarget(url) !== null)
34341
34361
  return UNPARSEABLE_TARGET;
34342
34362
  const m = /^([A-Za-z][A-Za-z0-9+.-]*:\/\/)([^/?#]*)([^?#]*)/.exec(url);
34343
- if (m === null)
34344
- return url.includes("?") ? UNPARSEABLE_TARGET : redactPathCredentials(url);
34363
+ if (m === null) {
34364
+ if (url.includes("?"))
34365
+ return UNPARSEABLE_TARGET;
34366
+ return WINDOWS_ABSOLUTE.test(url) ? redactWindowsPathCredentials(url) : redactPathCredentials(url);
34367
+ }
34345
34368
  const authority = m[2] ?? "";
34346
34369
  const at = authority.lastIndexOf("@");
34347
34370
  const path5 = m[3] ?? "";
@@ -34355,6 +34378,16 @@ function redactPathCredentials(path5) {
34355
34378
  return /:[^@]*@/.test(seen) ? WITHHELD_SEGMENT : segment;
34356
34379
  });
34357
34380
  }
34381
+ function redactWindowsPathCredentials(path5) {
34382
+ const rest = path5.slice(2);
34383
+ for (const m of rest.matchAll(/[^\\/]+/g)) {
34384
+ const seen = decodeToFixedPoint(m[0]);
34385
+ if (seen === null || seen.includes(":")) {
34386
+ return path5.slice(0, 2) + rest.slice(0, m.index) + WITHHELD_SEGMENT;
34387
+ }
34388
+ }
34389
+ return path5;
34390
+ }
34358
34391
  function decodeToFixedPoint(s) {
34359
34392
  let current = s;
34360
34393
  for (let round = 0; round < 4; round += 1) {
@@ -34543,6 +34576,21 @@ async function releaseWithoutThrowing(lease, progress) {
34543
34576
  );
34544
34577
  }
34545
34578
  }
34579
+ function resolveRunEnvironment(derived, req) {
34580
+ const runtimeDeclared = req.runtimeOverride !== void 0 && req.runtimeOverride !== "unknown" && derived.runtime === "unknown";
34581
+ const withRuntime = runtimeDeclared ? {
34582
+ ...derived,
34583
+ runtime: req.runtimeOverride,
34584
+ unresolved: derived.unresolved.filter((r) => r.kind !== "unknown-runtime")
34585
+ } : derived;
34586
+ const spec = req.testCommandOverride === void 0 ? withRuntime : { ...withRuntime, testCommand: req.testCommandOverride };
34587
+ const imageSource = req.image !== void 0 ? req.imageOrigin === "operator" ? "operator-declared" : "developer-declared" : runtimeDeclared ? (
34588
+ // Attributed only when the caller says the poster chose it. An unattributed override
34589
+ // defaults the way `testCommandSource` does, to the developer, which does not sign.
34590
+ req.runtimeOverrideOrigin === "founder" ? "founder-declared" : "developer-declared"
34591
+ ) : "detected";
34592
+ return { spec, imageSource };
34593
+ }
34546
34594
  async function verifyWorkingDiff(req) {
34547
34595
  const ctx = {
34548
34596
  startedAt: Date.now(),
@@ -34740,7 +34788,7 @@ async function runVerification(req, ctx) {
34740
34788
  const treeDigest = patchedTreeDigest(cloneDir);
34741
34789
  const baselinePatchSha256 = hasBaselinePatch ? sha256Hex(baselinePatch ?? "") : null;
34742
34790
  const derived = deriveEnvironmentSpec(cloneDir);
34743
- const spec = req.testCommandOverride === void 0 ? derived : { ...derived, testCommand: req.testCommandOverride };
34791
+ const { spec, imageSource } = resolveRunEnvironment(derived, req);
34744
34792
  progress("derive", `runtime=${spec.runtime} install=${String(spec.installCommand)} test=${String(spec.testCommand)}`);
34745
34793
  refuseUnbuildableSpec(spec);
34746
34794
  const image = placement.imageFor(spec.runtime, req.image, spec.runtimeVersion, imageVariantFor(spec));
@@ -34845,7 +34893,7 @@ async function runVerification(req, ctx) {
34845
34893
  // `developer-declared` is deliberate and matches `testCommandSource`: `th run --image`
34846
34894
  // runs on the developer's machine, judging the developer's work, so attributing it to
34847
34895
  // the operator would sign our name onto their choice.
34848
- imageSource: req.image === void 0 ? "detected" : req.imageOrigin === "operator" ? "operator-declared" : "developer-declared",
34896
+ imageSource,
34849
34897
  leaksClean: verdict.leaks.clean,
34850
34898
  leakState: verdict.leaks.state,
34851
34899
  // Built from the LEASE, over the client that ran the steps — never from
@@ -35908,6 +35956,7 @@ __export(dist_exports, {
35908
35956
  renderVerdictLine: () => renderVerdictLine,
35909
35957
  resolveImageForSpec: () => resolveImageForSpec,
35910
35958
  resolveLease: () => resolveLease,
35959
+ resolveRunEnvironment: () => resolveRunEnvironment,
35911
35960
  runEnvironmentSpec: () => runEnvironmentSpec,
35912
35961
  runLabels: () => runLabels,
35913
35962
  setManifestProbe: () => setManifestProbe,
@@ -37807,9 +37856,13 @@ function fmtContestedWarning(b) {
37807
37856
  }
37808
37857
  return ` \u26A0 This issue looks taken: ${parts.join(" / ")}. A merged PR here is unlikely.`;
37809
37858
  }
37810
- async function mintRegistrationProof({ claimRef } = {}) {
37811
- console.log("\n This posting registers your claim with terminalhire, so your");
37812
- console.log(" GitHub identity has to be verified once in the browser.");
37859
+ async function mintRegistrationProof({ claimRef, intro } = {}) {
37860
+ console.log("");
37861
+ for (const line of intro ?? [
37862
+ "This posting registers your claim with terminalhire, so your",
37863
+ "GitHub identity has to be verified once in the browser."
37864
+ ])
37865
+ console.log(` ${line}`);
37813
37866
  let oauthBase;
37814
37867
  try {
37815
37868
  oauthBase = resolveOAuthBase();
@@ -38136,17 +38189,18 @@ function readCredentialDisposition({
38136
38189
  return "store-and-clear-stale-marker";
38137
38190
  }
38138
38191
  async function bootstrapFounderClaimEnrollment(claim, registration) {
38139
- if (!(claim.amountUSD > 0)) return { stored: false, reason: "free" };
38140
38192
  if (!registration.pushToken || !registration.claimantLogin) {
38141
38193
  return { stored: false, reason: "token-unavailable" };
38142
38194
  }
38195
+ const current = await readPushTokenEnc().catch(() => null);
38143
38196
  const disposition = readCredentialDisposition({
38144
38197
  markerExists: Boolean(readAutoMarker()),
38145
- enrolledTokenExists: Boolean(await readPushTokenEnc().catch(() => null))
38198
+ enrolledTokenExists: Boolean(current)
38146
38199
  });
38147
38200
  if (disposition === "keep-enrolled") {
38148
38201
  return { stored: false, reason: "kept-enrolled" };
38149
38202
  }
38203
+ if (current && current === registration.pushToken) return { stored: true, reason: "ok" };
38150
38204
  try {
38151
38205
  await writePushTokenEnc(registration.pushToken);
38152
38206
  if (disposition === "store-and-clear-stale-marker") clearAutoMarker();
@@ -38367,7 +38421,7 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
38367
38421
  console.log(
38368
38422
  ` registered with terminalhire${claim.approval.claimId ? ` (server claim ${claim.approval.claimId})` : ""}`
38369
38423
  );
38370
- if (claim.amountUSD > 0) {
38424
+ if (enrollment) {
38371
38425
  if (enrollment?.stored) {
38372
38426
  console.log(" \u2713 credential for your granted slice + CI results stored encrypted here");
38373
38427
  console.log(" Background dashboard updates are NOT on (claiming does not enable them).");
@@ -38380,7 +38434,7 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
38380
38434
  if (enrollment?.detail) {
38381
38435
  for (const line of String(enrollment.detail).split("\n")) console.log(` ${line}`);
38382
38436
  }
38383
- console.log(" Store it before fetching your slice: terminalhire claim --push");
38437
+ console.log(" `claim start` will ask you to confirm in the browser once more.");
38384
38438
  }
38385
38439
  }
38386
38440
  if (claim.approval.state === "pending") {
@@ -38542,7 +38596,7 @@ async function cmdList(active) {
38542
38596
  );
38543
38597
  return;
38544
38598
  }
38545
- await syncFounderApprovals(claims, list);
38599
+ const { approvalsChecked } = await syncFounderApprovals(claims, list);
38546
38600
  list = claims.listClaims({ active });
38547
38601
  await syncFounderVerdicts({
38548
38602
  claimsModule: claims,
@@ -38560,7 +38614,7 @@ ${list.length} ${active ? "active " : ""}claim${list.length === 1 ? "" : "s"}:
38560
38614
  `);
38561
38615
  for (const c of list) {
38562
38616
  const pr = c.prUrl ? ` \xB7 ${c.prUrl}` : "";
38563
- console.log(` [${claimTurnLabel(c.state)}] ${fmtClaimAmount(c)} \xB7 ${c.title}`);
38617
+ console.log(` [${claimListTurnLabel(c, approvalsChecked)}] ${fmtClaimAmount(c)} \xB7 ${c.title}`);
38564
38618
  console.log(` id: ${c.id} \xB7 ${c.state}${pr}`);
38565
38619
  }
38566
38620
  printMetric(claims.acceptedPRRate());
@@ -38668,6 +38722,12 @@ async function syncFounderApprovals(claimsModule, targets) {
38668
38722
  }
38669
38723
  return { pushToken, approvalsChecked, approvalsUnavailable };
38670
38724
  }
38725
+ function claimListTurnLabel(claim, approvalsChecked) {
38726
+ if (approvalsChecked && claim.state === "claimed" && claim.approval?.mode === "approval-only" && claim.approval?.state === "pending") {
38727
+ return "waiting on poster";
38728
+ }
38729
+ return claimTurnLabel(claim.state);
38730
+ }
38671
38731
  function founderClaimStanding(claim, approvalsChecked) {
38672
38732
  const turn = claimTurnLabel(claim.state);
38673
38733
  switch (claim.state) {
@@ -39910,12 +39970,74 @@ terminalhire claim: ${what} needs the stored credential, and the server says it
39910
39970
  }
39911
39971
  return true;
39912
39972
  }
39913
- async function requireReadPushToken(what) {
39973
+ async function enrolHeldClaimReadToken(postingId) {
39974
+ const fail = (lines) => {
39975
+ for (const line of lines) console.error(line);
39976
+ process.exit(1);
39977
+ };
39978
+ const proofToken = await mintRegistrationProof({
39979
+ claimRef: opportunityShortToken(`bounty:founder:${postingId}`),
39980
+ intro: [
39981
+ "This machine holds no terminalhire credential yet. Confirm once in the",
39982
+ "browser, signed in as the GitHub account that claimed this work, and",
39983
+ "this machine is linked to your claim."
39984
+ ]
39985
+ });
39986
+ if (!proofToken) {
39987
+ fail(["terminalhire claim: could not link this machine to your claim (see above)."]);
39988
+ }
39989
+ let res;
39990
+ try {
39991
+ res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/enrol`, {
39992
+ method: "POST",
39993
+ headers: { "Content-Type": "application/json" },
39994
+ body: JSON.stringify({ bountyId: postingId, proofToken }),
39995
+ signal: AbortSignal.timeout(CLAIM_SYNC_WRITE_TIMEOUT_MS)
39996
+ });
39997
+ } catch (err) {
39998
+ fail([
39999
+ `terminalhire claim: terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}).`,
40000
+ " Nothing was stored. Run the same command again."
40001
+ ]);
40002
+ }
40003
+ let body = null;
40004
+ try {
40005
+ body = await res.json();
40006
+ } catch {
40007
+ }
40008
+ if (res.status === 404 && body?.error === "no-live-claim") {
40009
+ const who = typeof body.claimantLogin === "string" ? `@${body.claimantLogin}` : "that account";
40010
+ fail([
40011
+ `terminalhire claim: you confirmed in the browser as ${who}, and ${who} does not`,
40012
+ " hold a live claim on this posting. Nothing was stored.",
40013
+ " Sign the browser into the GitHub account that claimed it, then run this again."
40014
+ ]);
40015
+ }
40016
+ if (!res.ok || typeof body?.pushToken !== "string" || body.pushToken.length === 0) {
40017
+ fail([
40018
+ `terminalhire claim: linking this machine failed (${res.status}${typeof body?.message === "string" ? `: ${body.message}` : ""}). Nothing was stored.`
40019
+ ]);
40020
+ }
40021
+ try {
40022
+ await writePushTokenEnc(body.pushToken);
40023
+ } catch (err) {
40024
+ fail([
40025
+ `terminalhire claim: the credential could not be stored on this machine: ${err instanceof Error ? err.message : String(err)}`
40026
+ ]);
40027
+ }
40028
+ console.log(" \u2713 This machine is linked to your claim (credential stored encrypted here).");
40029
+ return body.pushToken;
40030
+ }
40031
+ var FOUNDER_POSTING_ID = /^fb_[A-Za-z0-9_-]{1,80}$/;
40032
+ async function requireReadPushToken(what, { enrolFor } = {}) {
39914
40033
  let stored = null;
39915
40034
  try {
39916
40035
  stored = await readPushTokenEnc();
39917
40036
  } catch {
39918
40037
  }
40038
+ if (!stored && typeof enrolFor === "string" && FOUNDER_POSTING_ID.test(enrolFor)) {
40039
+ return enrolHeldClaimReadToken(enrolFor);
40040
+ }
39919
40041
  if (!stored) {
39920
40042
  console.error(
39921
40043
  `terminalhire claim: ${what} needs the persistent push token, and none is enrolled on this machine.
@@ -39938,7 +40060,9 @@ function cloneFullTierRepo({ engine, dest, url, sha, token, branch }) {
39938
40060
  async function cmdSliceFullTier(claims, id, local, fullTierBody, flags, cloneRepo = cloneFullTierRepo) {
39939
40061
  const claimId = fullTierBody.claimId;
39940
40062
  const bountyId = fullTierBody.bountyId;
39941
- const pushToken = await requireReadPushToken("fetching your full-repo clone credential");
40063
+ const pushToken = await requireReadPushToken("fetching your full-repo clone credential", {
40064
+ enrolFor: bountyId
40065
+ });
39942
40066
  let res;
39943
40067
  try {
39944
40068
  res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/clone-token`, {
@@ -40114,7 +40238,9 @@ async function attemptSliceDelivery(id, flags = {}) {
40114
40238
  requireFounderLoopClaim(claims, id, "slice");
40115
40239
  }
40116
40240
  const bountyId = local ? founderPostingIdOf(local) : String(id).replace(/^bounty:founder:/, "");
40117
- const pushToken = await requireReadPushToken("fetching your granted slice");
40241
+ const pushToken = await requireReadPushToken("fetching your granted slice", {
40242
+ enrolFor: bountyId
40243
+ });
40118
40244
  let res;
40119
40245
  try {
40120
40246
  res = await fetch(`${CLAIM_SYNC_BASE4}/api/claim/slice`, {
@@ -41932,6 +42058,7 @@ export {
41932
42058
  buildStakeComment,
41933
42059
  buildStandDownComment,
41934
42060
  buildSubmitBody,
42061
+ claimListTurnLabel,
41935
42062
  claimUpdatePatch,
41936
42063
  cloneFullTierRepo,
41937
42064
  cmdNote,