terminalhire 0.42.40 → 0.42.41

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.
@@ -26435,7 +26435,19 @@ function isGreen(outcome) {
26435
26435
  function isOurFault(outcome) {
26436
26436
  return LIMIT_OWNER[outcome] === "ours";
26437
26437
  }
26438
- var VERIFICATION_OUTCOMES, int, withSuiteFailures, countLines, READERS, SUPPORTED_RUNNERS, COVERAGE_TABLE, EXEC_FAILURE, SUITE_REPORTED_FAILURE, MISSING_SYSTEM_DEPENDENCY, OFFLINE_BUILD_GAP, LIMIT_OWNER;
26438
+ function readStoppedAt(stdout, stderr = "") {
26439
+ return lastStepMarker(stdout) ?? lastStepMarker(stderr);
26440
+ }
26441
+ function isStepName(value) {
26442
+ return value.length > 0 && value.length <= STOPPED_AT_MAX && SCRIPT_NAME.test(value);
26443
+ }
26444
+ function lastStepMarker(text) {
26445
+ let last = null;
26446
+ for (const m of text.replace(/\r\n/g, "\n").matchAll(NPM_STEP_MARKER))
26447
+ last = m[1];
26448
+ return last !== null && isStepName(last) ? last : null;
26449
+ }
26450
+ var VERIFICATION_OUTCOMES, int, withSuiteFailures, countLines, READERS, SUPPORTED_RUNNERS, COVERAGE_TABLE, EXEC_FAILURE, SUITE_REPORTED_FAILURE, MISSING_SYSTEM_DEPENDENCY, OFFLINE_BUILD_GAP, LIMIT_OWNER, NPM_STEP_MARKER, STOPPED_AT_MAX, SCRIPT_NAME;
26439
26451
  var init_classify2 = __esm({
26440
26452
  "../../packages/envrun/dist/classify.js"() {
26441
26453
  "use strict";
@@ -26858,6 +26870,9 @@ var init_classify2 = __esm({
26858
26870
  "environment-exhausted": "ours",
26859
26871
  "budget-exceeded": "ours"
26860
26872
  };
26873
+ NPM_STEP_MARKER = /^> (?:@[^\s@/]+\/)?[^\s@]+@\S+ (\S+)/gm;
26874
+ STOPPED_AT_MAX = 200;
26875
+ SCRIPT_NAME = /^[A-Za-z0-9:_.\-/+@]+$/;
26861
26876
  }
26862
26877
  });
26863
26878
 
@@ -31033,7 +31048,8 @@ var init_result = __esm({
31033
31048
  "leaksClean",
31034
31049
  "leakState",
31035
31050
  "venue",
31036
- "refusalOrigin"
31051
+ "refusalOrigin",
31052
+ "stoppedAt"
31037
31053
  ];
31038
31054
  RENDER_NONE = null;
31039
31055
  FIELD_VIEWS = {
@@ -31096,7 +31112,9 @@ var init_result = __esm({
31096
31112
  venue: (r) => r.venue === null ? null : renderVenueLine(r.venue),
31097
31113
  // For the worker and the baseline intake, not the terminal: `reason` already says which
31098
31114
  // side refused in words a developer reads.
31099
- refusalOrigin: RENDER_NONE
31115
+ refusalOrigin: RENDER_NONE,
31116
+ // Only when the run did not pass: on a green run "the last step" is just the last step.
31117
+ stoppedAt: (r) => r.stoppedAt === null || r.outcome === "completed" ? null : `last step ${r.stoppedAt} (the last package script named in the output)`
31100
31118
  };
31101
31119
  }
31102
31120
  });
@@ -31472,6 +31490,317 @@ var init_lockfileCheck = __esm({
31472
31490
  }
31473
31491
  });
31474
31492
 
31493
+ // ../../packages/envrun/dist/binaryPatch.js
31494
+ import { createHash as createHash11 } from "crypto";
31495
+ import { inflateSync } from "zlib";
31496
+ function decodeBase85Line(line) {
31497
+ const first = line.charCodeAt(0);
31498
+ let length;
31499
+ if (first >= 65 && first <= 90)
31500
+ length = first - 64;
31501
+ else if (first >= 97 && first <= 122)
31502
+ length = first - 96 + 26;
31503
+ else
31504
+ return null;
31505
+ const groups = Math.ceil(length / 4);
31506
+ if (line.length !== 1 + groups * 5)
31507
+ return null;
31508
+ const out = new Uint8Array(groups * 4);
31509
+ for (let g = 0; g < groups; g += 1) {
31510
+ let acc = 0;
31511
+ for (let k = 0; k < 5; k += 1) {
31512
+ const code = line.charCodeAt(1 + g * 5 + k);
31513
+ const value = code < 128 ? B85_VALUE[code] ?? -1 : -1;
31514
+ if (value < 0)
31515
+ return null;
31516
+ acc = acc * 85 + value;
31517
+ }
31518
+ if (acc > 4294967295)
31519
+ return null;
31520
+ out[g * 4] = acc >>> 24 & 255;
31521
+ out[g * 4 + 1] = acc >>> 16 & 255;
31522
+ out[g * 4 + 2] = acc >>> 8 & 255;
31523
+ out[g * 4 + 3] = acc & 255;
31524
+ }
31525
+ return out.subarray(0, length);
31526
+ }
31527
+ function readBinaryPayload(lines) {
31528
+ const blocks = [];
31529
+ let i = 0;
31530
+ while (i < lines.length) {
31531
+ const line = lines[i] ?? "";
31532
+ if (line === "") {
31533
+ i += 1;
31534
+ continue;
31535
+ }
31536
+ if (blocks.length === 2)
31537
+ return null;
31538
+ const header = BLOCK_HEADER.exec(line);
31539
+ if (header === null)
31540
+ return null;
31541
+ const size = Number(header[2]);
31542
+ if (!Number.isSafeInteger(size))
31543
+ return null;
31544
+ i += 1;
31545
+ const parts = [];
31546
+ let total = 0;
31547
+ while (i < lines.length && (lines[i] ?? "") !== "") {
31548
+ const decoded = decodeBase85Line(lines[i] ?? "");
31549
+ if (decoded === null)
31550
+ return null;
31551
+ total += decoded.length;
31552
+ if (total > MAX_BINARY_DEFLATED_BLOCK_BYTES)
31553
+ return null;
31554
+ parts.push(decoded);
31555
+ i += 1;
31556
+ }
31557
+ if (parts.length === 0)
31558
+ return null;
31559
+ if (i >= lines.length)
31560
+ return null;
31561
+ const deflated = new Uint8Array(total);
31562
+ let at = 0;
31563
+ for (const p of parts) {
31564
+ deflated.set(p, at);
31565
+ at += p.length;
31566
+ }
31567
+ blocks.push({ kind: header[1] === "delta" ? "delta" : "literal", size, deflated });
31568
+ }
31569
+ const forward = blocks[0];
31570
+ if (forward === void 0)
31571
+ return null;
31572
+ return { forward, reverse: blocks[1] ?? null };
31573
+ }
31574
+ function inflateExact(block, limit2) {
31575
+ if (block.size > limit2)
31576
+ return null;
31577
+ try {
31578
+ const out = inflateSync(block.deflated, { maxOutputLength: block.size + 1 });
31579
+ return out.length === block.size ? new Uint8Array(out) : null;
31580
+ } catch {
31581
+ return null;
31582
+ }
31583
+ }
31584
+ function readVarint(delta, from) {
31585
+ let value = 0;
31586
+ let shift = 0;
31587
+ let pos = from;
31588
+ for (; ; ) {
31589
+ const byte = delta[pos];
31590
+ if (byte === void 0 || shift > 42)
31591
+ return null;
31592
+ value += (byte & 127) * 2 ** shift;
31593
+ pos += 1;
31594
+ shift += 7;
31595
+ if ((byte & 128) === 0)
31596
+ return { value, next: pos };
31597
+ }
31598
+ }
31599
+ function deltaHeader(delta) {
31600
+ const source = readVarint(delta, 0);
31601
+ if (source === null)
31602
+ return null;
31603
+ const target = readVarint(delta, source.next);
31604
+ if (target === null)
31605
+ return null;
31606
+ return { source: source.value, target: target.value, opsAt: target.next };
31607
+ }
31608
+ function deltaLeadingInserts(delta, opsAt) {
31609
+ const parts = [];
31610
+ let pos = opsAt;
31611
+ while (pos < delta.length && parts.length < 64) {
31612
+ const op = delta[pos] ?? 0;
31613
+ if (op === 0 || (op & 128) !== 0)
31614
+ break;
31615
+ const end = Math.min(pos + 1 + op, delta.length);
31616
+ for (let i = pos + 1; i < end; i += 1)
31617
+ parts.push(delta[i] ?? 0);
31618
+ pos = end;
31619
+ }
31620
+ return Uint8Array.from(parts);
31621
+ }
31622
+ function startsWith(bytes, magic, at = 0) {
31623
+ if (bytes.length < at + magic.length)
31624
+ return false;
31625
+ return magic.every((b, i) => bytes[at + i] === b);
31626
+ }
31627
+ function sniffBinaryType(bytes) {
31628
+ if (startsWith(bytes, [127, 69, 76, 70]))
31629
+ return { label: "ELF", program: true };
31630
+ for (const magic of [
31631
+ [254, 237, 250, 206],
31632
+ [254, 237, 250, 207],
31633
+ [206, 250, 237, 254],
31634
+ [207, 250, 237, 254],
31635
+ [202, 254, 186, 191],
31636
+ [190, 186, 254, 202],
31637
+ [191, 186, 254, 202]
31638
+ ]) {
31639
+ if (startsWith(bytes, magic))
31640
+ return { label: "Mach-O", program: true };
31641
+ }
31642
+ if (startsWith(bytes, [202, 254, 186, 190])) {
31643
+ const major = bytes.length >= 8 ? (bytes[6] ?? 0) << 8 | (bytes[7] ?? 0) : 0;
31644
+ return { label: major >= 45 ? "Java class" : "Mach-O", program: true };
31645
+ }
31646
+ if (startsWith(bytes, [0, 97, 115, 109]))
31647
+ return { label: "WebAssembly", program: true };
31648
+ if (startsWith(bytes, [77, 90]))
31649
+ return { label: "PE", program: true };
31650
+ if (startsWith(bytes, [137, 80, 78, 71, 13, 10, 26, 10])) {
31651
+ return { label: "PNG", program: false };
31652
+ }
31653
+ if (startsWith(bytes, [255, 216, 255]))
31654
+ return { label: "JPEG", program: false };
31655
+ if (startsWith(bytes, [71, 73, 70, 56, 55, 97]) || startsWith(bytes, [71, 73, 70, 56, 57, 97])) {
31656
+ return { label: "GIF", program: false };
31657
+ }
31658
+ if (startsWith(bytes, [82, 73, 70, 70]) && startsWith(bytes, [87, 69, 66, 80], 8)) {
31659
+ return { label: "WebP", program: false };
31660
+ }
31661
+ if (startsWith(bytes, [37, 80, 68, 70, 45]))
31662
+ return { label: "PDF", program: false };
31663
+ if (startsWith(bytes, [119, 79, 70, 70]))
31664
+ return { label: "WOFF", program: false };
31665
+ if (startsWith(bytes, [119, 79, 70, 50]))
31666
+ return { label: "WOFF2", program: false };
31667
+ if (startsWith(bytes, [0, 1, 0, 0]) || startsWith(bytes, [116, 114, 117, 101])) {
31668
+ return { label: "TTF", program: false };
31669
+ }
31670
+ if (startsWith(bytes, [79, 84, 84, 79]))
31671
+ return { label: "OTF", program: false };
31672
+ if (startsWith(bytes, [80, 75, 3, 4]) || startsWith(bytes, [80, 75, 5, 6]) || startsWith(bytes, [80, 75, 7, 8])) {
31673
+ return { label: "ZIP", program: false };
31674
+ }
31675
+ return { label: "unknown binary", program: false };
31676
+ }
31677
+ function formatByteSize(bytes) {
31678
+ if (bytes < 1024)
31679
+ return `${bytes} B`;
31680
+ if (bytes < 1024 * 1024)
31681
+ return `${(bytes / 1024).toFixed(1)} KB`;
31682
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
31683
+ }
31684
+ function parseFullIndexLine(line) {
31685
+ const m = /^index ([0-9a-f]+)\.\.([0-9a-f]+)(?: ([0-7]{6}))?$/.exec(line);
31686
+ if (m === null)
31687
+ return null;
31688
+ const pre = m[1] ?? "";
31689
+ const post = m[2] ?? "";
31690
+ if (!FULL_ID.test(pre) || !FULL_ID.test(post))
31691
+ return null;
31692
+ return { pre, post, mode: m[3] ?? null };
31693
+ }
31694
+ function isZeroId(id) {
31695
+ return id === ZERO_ID;
31696
+ }
31697
+ function refuse4(code, detail) {
31698
+ return { kind: "refuse", code, detail };
31699
+ }
31700
+ function judgeBinaryEntry(input) {
31701
+ const { path: path5 } = input;
31702
+ if (input.stub) {
31703
+ return refuse4("binary-content-missing", `${path5} is a binary file, but the patch leaves out its contents. Update terminalhire (npm install -g terminalhire@latest) and submit again.`);
31704
+ }
31705
+ if (input.textContent || input.indexLines.length !== 1)
31706
+ return { kind: "unreadable" };
31707
+ const index = parseFullIndexLine(input.indexLines[0] ?? "");
31708
+ if (index === null)
31709
+ return { kind: "unreadable" };
31710
+ if (isZeroId(index.pre) !== input.createsFile)
31711
+ return { kind: "unreadable" };
31712
+ if (isZeroId(index.post) !== input.deletesFile)
31713
+ return { kind: "unreadable" };
31714
+ const payload = readBinaryPayload(input.payload);
31715
+ if (payload === null)
31716
+ return { kind: "unreadable" };
31717
+ if (input.deletesFile) {
31718
+ const forward2 = payload.forward;
31719
+ if (forward2.kind !== "literal" || inflateExact(forward2, 0) === null) {
31720
+ return { kind: "unreadable" };
31721
+ }
31722
+ const reverse = payload.reverse;
31723
+ const removed = reverse !== null && reverse.kind === "literal" ? inflateExact(reverse, MAX_BINARY_FILE_BYTES) : null;
31724
+ return {
31725
+ kind: "hold",
31726
+ type: removed === null ? "unknown binary" : sniffBinaryType(removed).label,
31727
+ size: reverse !== null && reverse.kind === "literal" ? reverse.size : 0,
31728
+ writes: 0,
31729
+ hash: index.pre
31730
+ };
31731
+ }
31732
+ const mode = input.newMode ?? index.mode ?? "100644";
31733
+ if (mode === "100755")
31734
+ return refuse4("binary-executable", binaryExecutableDetail(path5));
31735
+ if (mode !== "100644")
31736
+ return refuse4("binary-mode", unsupportedModeDetail(path5, mode));
31737
+ const tooLarge = (size2) => refuse4("binary-too-large", `${path5} is ${formatByteSize(size2)}. One binary file can be at most 2 MB.`);
31738
+ let size;
31739
+ let head;
31740
+ const forward = payload.forward;
31741
+ if (forward.kind === "literal") {
31742
+ if (forward.size > MAX_BINARY_FILE_BYTES) {
31743
+ return tooLarge(forward.size);
31744
+ }
31745
+ const bytes = inflateExact(forward, MAX_BINARY_FILE_BYTES);
31746
+ if (bytes === null)
31747
+ return { kind: "unreadable" };
31748
+ size = bytes.length;
31749
+ head = bytes;
31750
+ } else {
31751
+ if (input.createsFile)
31752
+ return { kind: "unreadable" };
31753
+ const delta = inflateExact(forward, 2 * MAX_BINARY_FILE_BYTES);
31754
+ if (delta === null)
31755
+ return { kind: "unreadable" };
31756
+ const header = deltaHeader(delta);
31757
+ if (header === null)
31758
+ return { kind: "unreadable" };
31759
+ if (header.target > MAX_BINARY_FILE_BYTES)
31760
+ return tooLarge(header.target);
31761
+ size = header.target;
31762
+ head = deltaLeadingInserts(delta, header.opsAt);
31763
+ }
31764
+ const type = sniffBinaryType(head);
31765
+ if (type.program) {
31766
+ return refuse4("binary-program", binaryProgramDetail(path5, type.label));
31767
+ }
31768
+ return { kind: "hold", type: type.label, size, writes: size, hash: index.post };
31769
+ }
31770
+ function binaryTotalRefusal(totalBytes) {
31771
+ if (totalBytes <= MAX_BINARY_PATCH_BYTES)
31772
+ return null;
31773
+ return `The binary files in this patch add up to ${formatByteSize(totalBytes)}. A patch can carry at most 3 MB of binary files.`;
31774
+ }
31775
+ function binaryExecutableDetail(path5) {
31776
+ return `${path5} is a binary file marked executable. Binary files can't be marked executable in a patch.`;
31777
+ }
31778
+ function unsupportedModeDetail(path5, mode) {
31779
+ return `The patch changes ${path5} to a file type this platform will not apply on your behalf (mode ${mode}). Nothing on your repository was changed.`;
31780
+ }
31781
+ function binaryProgramDetail(path5, label) {
31782
+ return `${path5} is a program (${label}). Patches can't add or change programs, because the install or test step could run them.`;
31783
+ }
31784
+ var MAX_BINARY_FILE_BYTES, MAX_BINARY_PATCH_BYTES, MAX_BINARY_DEFLATED_BLOCK_BYTES, FULL_ID, ZERO_ID, B85_ALPHABET, B85_VALUE, BLOCK_HEADER;
31785
+ var init_binaryPatch = __esm({
31786
+ "../../packages/envrun/dist/binaryPatch.js"() {
31787
+ "use strict";
31788
+ MAX_BINARY_FILE_BYTES = 2 * 1024 * 1024;
31789
+ MAX_BINARY_PATCH_BYTES = 3 * 1024 * 1024;
31790
+ MAX_BINARY_DEFLATED_BLOCK_BYTES = 2 * MAX_BINARY_FILE_BYTES + 64 * 1024;
31791
+ FULL_ID = /^[0-9a-f]{40}$/;
31792
+ ZERO_ID = "0".repeat(40);
31793
+ B85_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
31794
+ B85_VALUE = (() => {
31795
+ const table = new Array(128).fill(-1);
31796
+ for (let i = 0; i < B85_ALPHABET.length; i += 1)
31797
+ table[B85_ALPHABET.charCodeAt(i)] = i;
31798
+ return table;
31799
+ })();
31800
+ BLOCK_HEADER = /^(literal|delta) (0|[1-9][0-9]{0,15})$/;
31801
+ }
31802
+ });
31803
+
31475
31804
  // ../../packages/envrun/dist/boundary.js
31476
31805
  function lockfileRefusal(base) {
31477
31806
  const kind = LOCKFILE_KINDS[base];
@@ -31596,6 +31925,11 @@ function parsePatchPaths(patch) {
31596
31925
  entry = {
31597
31926
  rawPaths: [],
31598
31927
  binary: false,
31928
+ binaryStub: false,
31929
+ binaryPayload: [],
31930
+ indexLines: [],
31931
+ newMode: null,
31932
+ textContent: false,
31599
31933
  headerOld: null,
31600
31934
  headerNew: null,
31601
31935
  plusPath: null,
@@ -31629,8 +31963,10 @@ function parsePatchPaths(patch) {
31629
31963
  }
31630
31964
  if (entry === null)
31631
31965
  continue;
31632
- if (skippingBinary)
31966
+ if (skippingBinary) {
31967
+ entry.binaryPayload.push(line);
31633
31968
  continue;
31969
+ }
31634
31970
  if (inHunk) {
31635
31971
  const marker = line === "" ? " " : line[0];
31636
31972
  if (marker === "\\")
@@ -31657,6 +31993,7 @@ function parsePatchPaths(patch) {
31657
31993
  if (line === "-- " || line === "--")
31658
31994
  break;
31659
31995
  if (line.startsWith("@@")) {
31996
+ entry.textContent = true;
31660
31997
  const m = HUNK_HEADER.exec(line);
31661
31998
  if (m === null) {
31662
31999
  return fail3("a change block header is malformed, so we cannot tell which lines belong to it");
@@ -31673,13 +32010,20 @@ function parsePatchPaths(patch) {
31673
32010
  const mode = /^(?:old mode|new mode|new file mode|deleted file mode) (\d+)$/.exec(line)?.[1] ?? /^index [0-9a-f]+\.\.[0-9a-f]+ (\d+)$/.exec(line)?.[1];
31674
32011
  if (mode !== void 0)
31675
32012
  entry.modes.push(mode);
32013
+ if (line.startsWith("index "))
32014
+ entry.indexLines.push(line);
32015
+ if (line.startsWith("new file mode ") || line.startsWith("new mode ")) {
32016
+ entry.newMode = line.slice(line.lastIndexOf(" ") + 1).trim();
32017
+ }
31676
32018
  if (line === "GIT binary patch" || line.startsWith("Binary files ")) {
31677
32019
  entry.binary = true;
32020
+ entry.binaryStub = line !== "GIT binary patch";
31678
32021
  skippingBinary = true;
31679
32022
  continue;
31680
32023
  }
31681
32024
  const sideMarker = line.startsWith("--- ") ? "a/" : line.startsWith("+++ ") ? "b/" : null;
31682
32025
  if (sideMarker !== null) {
32026
+ entry.textContent = true;
31683
32027
  const rest = line.slice(4);
31684
32028
  const tab = rest.indexOf(" ");
31685
32029
  const token = tab === -1 ? rest : rest.slice(0, tab);
@@ -31754,16 +32098,14 @@ function preflightBoundary(input) {
31754
32098
  const changedManifests = /* @__PURE__ */ new Set();
31755
32099
  const deniedPaths = /* @__PURE__ */ new Set();
31756
32100
  const nonFileLockfiles = /* @__PURE__ */ new Set();
32101
+ let binaryBytes = 0;
31757
32102
  for (const entry of parsed.entries) {
31758
- if (entry.binary) {
31759
- const name = entry.rawPaths[0] ?? "a file";
31760
- refusals.push(refusal("binary-patch", normalizePath(name)?.path ?? null, `${name} is changed as binary content, which nobody can review line by line. Binary changes are refused.`));
31761
- continue;
31762
- }
32103
+ let refusedByPath = false;
31763
32104
  const entryPaths = [];
31764
32105
  for (const raw of entry.rawPaths) {
31765
32106
  const normalized = normalizePath(raw);
31766
32107
  if (normalized === null) {
32108
+ refusedByPath = true;
31767
32109
  if (!seenPaths.has(raw)) {
31768
32110
  seenPaths.add(raw);
31769
32111
  refusals.push(refusal("unsafe-path", raw, `${raw} is not a file name a patch may name \u2014 it points outside the repository, or it is written in a form we cannot check.`));
@@ -31781,6 +32123,7 @@ function preflightBoundary(input) {
31781
32123
  seenPaths.add(path5);
31782
32124
  const denied = DENY_RULES.find((rule) => rule.matches(path5, segments, base));
31783
32125
  if (denied !== void 0) {
32126
+ refusedByPath = true;
31784
32127
  deniedPaths.add(path5);
31785
32128
  const why = typeof denied.why === "string" ? denied.why : denied.why(base);
31786
32129
  refusals.push(refusal(denied.code, path5, `${path5} ${why}.`));
@@ -31806,7 +32149,29 @@ function preflightBoundary(input) {
31806
32149
  if (contentPath !== null && contentPath.split("/").pop() === "package.json" && entry.changed) {
31807
32150
  changedManifests.add(contentPath);
31808
32151
  }
32152
+ if (entry.binary && !refusedByPath && contentPath !== null) {
32153
+ const verdict = judgeBinaryEntry({
32154
+ path: contentPath,
32155
+ stub: entry.binaryStub,
32156
+ payload: entry.binaryPayload,
32157
+ indexLines: entry.indexLines,
32158
+ newMode: entry.newMode,
32159
+ createsFile: entry.createsFile,
32160
+ deletesFile: entry.deletesFile,
32161
+ textContent: entry.textContent
32162
+ });
32163
+ if (verdict.kind === "refuse") {
32164
+ refusals.push(refusal(verdict.code, contentPath, verdict.detail));
32165
+ } else if (verdict.kind === "unreadable") {
32166
+ refusals.push(refusal("unparseable", contentPath, `The patch could not be read: the entry for ${contentPath} carries no changes we can apply. A patch we cannot read in full is refused rather than applied in part.`));
32167
+ } else {
32168
+ binaryBytes += verdict.writes;
32169
+ }
32170
+ }
31809
32171
  }
32172
+ const overBinaryCap = binaryTotalRefusal(binaryBytes);
32173
+ if (overBinaryCap !== null)
32174
+ refusals.push(refusal("binary-too-large", null, overBinaryCap));
31810
32175
  for (const [path5, { touches, edits }] of lockfileTouches) {
31811
32176
  if (deniedPaths.has(path5))
31812
32177
  continue;
@@ -31830,10 +32195,18 @@ var init_boundary = __esm({
31830
32195
  "../../packages/envrun/dist/boundary.js"() {
31831
32196
  "use strict";
31832
32197
  init_lockfileCheck();
32198
+ init_binaryPatch();
31833
32199
  PATH_REFUSAL_CODES = [
31834
32200
  "empty-patch",
31835
32201
  "unparseable",
31836
32202
  "binary-patch",
32203
+ // Not path-derived, but refused on both sides for the same bytes, so they belong on the
32204
+ // surface `boundary-oracle.test.mjs` compares (TERM-1342).
32205
+ "binary-content-missing",
32206
+ "binary-executable",
32207
+ "binary-mode",
32208
+ "binary-program",
32209
+ "binary-too-large",
31837
32210
  "unsafe-path",
31838
32211
  "out-of-slice",
31839
32212
  "ci-config",
@@ -32190,7 +32563,7 @@ var init_emptyGitConfig = __esm({
32190
32563
 
32191
32564
  // ../../packages/envrun/dist/hostedVenue.js
32192
32565
  import { spawn as spawn4, spawnSync as spawnSync5 } from "child_process";
32193
- import { createHash as createHash11, X509Certificate } from "crypto";
32566
+ import { createHash as createHash12, X509Certificate } from "crypto";
32194
32567
  import { chmodSync as chmodSync3, existsSync as existsSync12, mkdtempSync as mkdtempSync3, readFileSync as readFileSync12, rmSync as rmSync8, writeFileSync as writeFileSync14 } from "fs";
32195
32568
  import { request as httpsRequest } from "https";
32196
32569
  import { createServer } from "net";
@@ -32860,7 +33233,7 @@ function iapPortTunnelArgv(vm, project, zone, remotePort, localPort) {
32860
33233
  }
32861
33234
  function spkiSha256Hex(certDer) {
32862
33235
  const spki = new X509Certificate(certDer).publicKey.export({ type: "spki", format: "der" });
32863
- return createHash11("sha256").update(spki).digest("hex");
33236
+ return createHash12("sha256").update(spki).digest("hex");
32864
33237
  }
32865
33238
  function certMetadataValue(pem) {
32866
33239
  return new X509Certificate(pem).raw.toString("base64url");
@@ -37304,7 +37677,9 @@ function refusedRun(fields) {
37304
37677
  // reasoning as `leaksClean` above: null because nothing happened, and it must
37305
37678
  // not read as a venue we looked at and could not name.
37306
37679
  venue: null,
37307
- refusalOrigin: fields.origin
37680
+ refusalOrigin: fields.origin,
37681
+ // No test step ran, so there is no output to read a step from.
37682
+ stoppedAt: null
37308
37683
  };
37309
37684
  }
37310
37685
  function refusalOriginOf(refusal2) {
@@ -37755,7 +38130,9 @@ async function runVerification(req, ctx) {
37755
38130
  // reasoning and the #735 failure that makes the distinction load-bearing.
37756
38131
  venue: describeVenue(lease),
37757
38132
  // A run that reached a verdict was not refused, so neither side refused it.
37758
- refusalOrigin: null
38133
+ refusalOrigin: null,
38134
+ // Read from the full test output, not `outputTail`, which is an excerpt.
38135
+ stoppedAt: readStoppedAt(verdict.test?.stdout ?? "", verdict.test?.stderr ?? "")
37759
38136
  };
37760
38137
  let later = null;
37761
38138
  if (req.screenshots !== void 0) {
@@ -37962,6 +38339,7 @@ var init_thrun = __esm({
37962
38339
  init_screenshots();
37963
38340
  init_dist3();
37964
38341
  init_attestation2();
38342
+ init_classify2();
37965
38343
  init_boundary();
37966
38344
  init_lockfileCheck();
37967
38345
  init_emptyGitConfig();
@@ -38971,6 +39349,7 @@ __export(dist_exports, {
38971
39349
  readAlembicChain: () => readAlembicChain,
38972
39350
  readCounts: () => readCounts,
38973
39351
  readSchema: () => readSchema,
39352
+ readStoppedAt: () => readStoppedAt,
38974
39353
  recordedApplied: () => recordedApplied,
38975
39354
  refuseSshTransport: () => refuseSshTransport,
38976
39355
  refuseUnbuildableSpec: () => refuseUnbuildableSpec,
@@ -39314,7 +39693,7 @@ import {
39314
39693
  closeSync as closeSync6
39315
39694
  } from "fs";
39316
39695
  import { join as join31, dirname as dirname9, isAbsolute as isAbsolute5, resolve as pathResolve } from "path";
39317
- import { createHash as createHash12 } from "crypto";
39696
+ import { createHash as createHash13 } from "crypto";
39318
39697
  import { homedir as homedir14, hostname as osHostname } from "os";
39319
39698
  import { execFile as execFile3, execFileSync as execFileSync2, spawnSync as spawnSync8 } from "child_process";
39320
39699
  import { promisify as promisify3 } from "util";
@@ -41127,7 +41506,7 @@ async function registerFounderClaim(b) {
41127
41506
  let clearedLocalCredential = false;
41128
41507
  let refusedForPurpose = false;
41129
41508
  let preserveBackgroundToken = false;
41130
- const refuse4 = (reason) => {
41509
+ const refuse5 = (reason) => {
41131
41510
  console.error(
41132
41511
  `
41133
41512
  terminalhire claim: refusing to record \u2014 ${reason}
@@ -41152,7 +41531,7 @@ terminalhire claim: refusing to record \u2014 ${reason}
41152
41531
  try {
41153
41532
  expectLogin = await localLoginForPaidBrowserClaim();
41154
41533
  } catch (err) {
41155
- refuse4(
41534
+ refuse5(
41156
41535
  `could not verify which GitHub account this machine is signed in as: ${err instanceof Error ? err.message : String(err)}.
41157
41536
  Run \`terminalhire login\`, then retry this claim. Browser verification was
41158
41537
  not opened and no claim was sent.`
@@ -41168,16 +41547,16 @@ terminalhire claim: refusing to record \u2014 ${reason}
41168
41547
  claimRef: opportunityShortToken(b.bountyId)
41169
41548
  });
41170
41549
  if (!proofToken) {
41171
- refuse4("could not verify your GitHub identity with terminalhire (see above).");
41550
+ refuse5("could not verify your GitHub identity with terminalhire (see above).");
41172
41551
  }
41173
41552
  const verifiedLogin = proofTokenLogin(proofToken);
41174
41553
  if (!verifiedLogin) {
41175
- refuse4(
41554
+ refuse5(
41176
41555
  "the browser verification response did not name a GitHub account, so it could not be compared with this machine\u2019s sign-in. Nothing was sent; retry the claim."
41177
41556
  );
41178
41557
  }
41179
41558
  if (verifiedLogin.toLowerCase() !== expectLogin.toLowerCase()) {
41180
- refuse4(
41559
+ refuse5(
41181
41560
  `you verified in the browser as @${verifiedLogin}, but this machine is signed in as @${expectLogin}.
41182
41561
  Nothing was sent. Sign the browser into @${expectLogin} and run this again, or
41183
41562
  run \`terminalhire login\` if you meant to work as @${verifiedLogin}.`
@@ -41209,7 +41588,7 @@ terminalhire claim: refusing to record \u2014 ${reason}
41209
41588
  try {
41210
41589
  res = await sendRegistration(true);
41211
41590
  } catch (err) {
41212
- refuse4(
41591
+ refuse5(
41213
41592
  `terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}), so the posting could not be revalidated.`
41214
41593
  );
41215
41594
  }
@@ -41260,7 +41639,7 @@ terminalhire claim: refusing to record \u2014 ${reason}
41260
41639
  try {
41261
41640
  res = await sendRegistration(true);
41262
41641
  } catch (err) {
41263
- refuse4(
41642
+ refuse5(
41264
41643
  `terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}), so the posting could not be revalidated.`
41265
41644
  );
41266
41645
  }
@@ -41271,14 +41650,14 @@ terminalhire claim: refusing to record \u2014 ${reason}
41271
41650
  res = await sendRegistration(false);
41272
41651
  refusalBody = await readRefusal(res);
41273
41652
  } catch (err) {
41274
- refuse4(
41653
+ refuse5(
41275
41654
  `terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}), so the posting could not be revalidated.`
41276
41655
  );
41277
41656
  }
41278
41657
  }
41279
41658
  if (!res.ok) {
41280
41659
  const detail = refusalBody?.message || refusalBody?.error || "";
41281
- refuse4(
41660
+ refuse5(
41282
41661
  detail ? `the server refused this claim (${res.status}): ${detail}` : `the server refused this claim (${res.status}).`
41283
41662
  );
41284
41663
  }
@@ -41289,7 +41668,7 @@ terminalhire claim: refusing to record \u2014 ${reason}
41289
41668
  body = null;
41290
41669
  }
41291
41670
  if (!body || body.ok !== true) {
41292
- refuse4("malformed registration response from the server.");
41671
+ refuse5("malformed registration response from the server.");
41293
41672
  }
41294
41673
  const mintedToken = typeof body.pushToken === "string" && body.pushToken.length > 0 ? body.pushToken : null;
41295
41674
  if (refusedForPurpose && mintedToken && !preserveBackgroundToken) {
@@ -42821,7 +43200,7 @@ function writePackFile(destDir, relPath, content, what) {
42821
43200
  return { written: true, reason: null, sha256: sha256OfUtf8(content) };
42822
43201
  }
42823
43202
  function sha256OfUtf8(content) {
42824
- return createHash12("sha256").update(content, "utf8").digest("hex");
43203
+ return createHash13("sha256").update(content, "utf8").digest("hex");
42825
43204
  }
42826
43205
  function writeWorkspacePack(destDir, spec, claim, delivery) {
42827
43206
  if (delivery !== "full" && delivery !== "sparse") {
@@ -43724,7 +44103,7 @@ function loadClaimScreenshots(specArg) {
43724
44103
  items.push({
43725
44104
  file,
43726
44105
  caption: shot.caption,
43727
- sha256: createHash12("sha256").update(bytes).digest("hex"),
44106
+ sha256: createHash13("sha256").update(bytes).digest("hex"),
43728
44107
  bytes: bytes.byteLength,
43729
44108
  width,
43730
44109
  height
@@ -43809,10 +44188,14 @@ async function submitFounderPatch({ claims, claim, id, wt, flags }) {
43809
44188
  const base = roots[0];
43810
44189
  let patch;
43811
44190
  try {
43812
- ({ stdout: patch } = await pExecFile("git", ["-C", wt, "diff", base, "HEAD"], {
43813
- shell: false,
43814
- maxBuffer: 16 * 1024 * 1024
43815
- }));
44191
+ ({ stdout: patch } = await pExecFile(
44192
+ "git",
44193
+ ["-C", wt, "diff", "--binary", "--full-index", base, "HEAD"],
44194
+ {
44195
+ shell: false,
44196
+ maxBuffer: 16 * 1024 * 1024
44197
+ }
44198
+ ));
43816
44199
  patch = String(patch);
43817
44200
  } catch (err) {
43818
44201
  console.error(