terminalhire 0.42.37 → 0.42.39
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/bin/jpi-claim.js +739 -103
- package/dist/bin/jpi-dispatch.js +753 -117
- package/dist/bin/jpi-mcp.js +750 -114
- package/dist/bin/jpi-run.js +782 -146
- package/package.json +1 -1
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -5181,20 +5181,20 @@ var init_github_issue_status = __esm({
|
|
|
5181
5181
|
// ../../packages/core/src/credit.ts
|
|
5182
5182
|
function verifyClaimCredit(claim, facts) {
|
|
5183
5183
|
const reasons = [];
|
|
5184
|
-
const
|
|
5184
|
+
const fail3 = (code, message2) => reasons.push({ code, message: message2 });
|
|
5185
5185
|
const norm = (r) => r.trim().toLowerCase();
|
|
5186
5186
|
if (norm(facts.repo) !== norm(claim.repo))
|
|
5187
|
-
|
|
5188
|
-
if (!facts.merged)
|
|
5187
|
+
fail3("repo-mismatch", `PR is in ${facts.repo}, claim is against ${claim.repo}`);
|
|
5188
|
+
if (!facts.merged) fail3("not-merged", `PR #${facts.prNumber} is not merged`);
|
|
5189
5189
|
if (facts.authorId == null || facts.authorId !== claim.claimantId)
|
|
5190
|
-
|
|
5190
|
+
fail3("author-mismatch", `PR author id ${facts.authorId} !== claimant id ${claim.claimantId}`);
|
|
5191
5191
|
if (facts.merged && facts.mergedById != null && facts.authorId != null && facts.mergedById === facts.authorId)
|
|
5192
|
-
|
|
5192
|
+
fail3("self-merged", `PR was merged by its own author (id ${facts.authorId})`);
|
|
5193
5193
|
if (claim.claimedIssueNumber != null) {
|
|
5194
5194
|
if (facts.closesIssues.length === 0)
|
|
5195
|
-
|
|
5195
|
+
fail3("issue-linkage-missing", `PR closes no issue; claim names #${claim.claimedIssueNumber}`);
|
|
5196
5196
|
else if (!facts.closesIssues.includes(claim.claimedIssueNumber))
|
|
5197
|
-
|
|
5197
|
+
fail3(
|
|
5198
5198
|
"issue-linkage-mismatch",
|
|
5199
5199
|
`PR closes ${facts.closesIssues.map((n) => "#" + n).join(", ")}; claim names #${claim.claimedIssueNumber}`
|
|
5200
5200
|
);
|
|
@@ -26408,10 +26408,10 @@ var init_classify2 = __esm({
|
|
|
26408
26408
|
runner: "node --test (TAP)",
|
|
26409
26409
|
read: (out) => {
|
|
26410
26410
|
const pass = /^# pass (\d+)$/m.exec(out);
|
|
26411
|
-
const
|
|
26412
|
-
if (!pass || !
|
|
26411
|
+
const fail3 = /^# fail (\d+)$/m.exec(out);
|
|
26412
|
+
if (!pass || !fail3)
|
|
26413
26413
|
return null;
|
|
26414
|
-
return { tests_passed: int(pass), tests_failed: int(
|
|
26414
|
+
return { tests_passed: int(pass), tests_failed: int(fail3) };
|
|
26415
26415
|
},
|
|
26416
26416
|
// One `# pass N` per invocation, so a script that runs `node --test` once per file
|
|
26417
26417
|
// prints many. envrun's own offline leg printed 67 and was read as the first one's 4.
|
|
@@ -29349,7 +29349,7 @@ function resolvePublishedImage(image, runtime, version, variant) {
|
|
|
29349
29349
|
if (shape && shape.declaredIsFloor && atLeast(shape.defaultVersion, version)) {
|
|
29350
29350
|
return unversionedImage(shape);
|
|
29351
29351
|
}
|
|
29352
|
-
throw new RunRefusalError(`the repo declares ${runtime} ${version}, and no image is published at ${image}` + (shape && shape.declaredIsFloor ? `. Our default is ${shape.defaultVersion}, which is OLDER than that, so falling back would run the repo under a toolchain it says it cannot use` : `. ${runtime} treats a declared version as an exact pin, not a minimum, so a different one is a different environment`) + ". Refusing rather than booting a version the repo did not ask for \u2014 that substitution is what made this class of failure unattributable (TERM-643). Pass an explicit image to override.");
|
|
29352
|
+
throw new RunRefusalError(`the repo declares ${runtime} ${version}, and no image is published at ${image}` + (shape && shape.declaredIsFloor ? `. Our default is ${shape.defaultVersion}, which is OLDER than that, so falling back would run the repo under a toolchain it says it cannot use` : `. ${runtime} treats a declared version as an exact pin, not a minimum, so a different one is a different environment`) + ". Refusing rather than booting a version the repo did not ask for \u2014 that substitution is what made this class of failure unattributable (TERM-643). Pass an explicit image to override.", { origin: "repository" });
|
|
29353
29353
|
}
|
|
29354
29354
|
function resolveImageForSpec(spec, override) {
|
|
29355
29355
|
const variant = imageVariantFor(spec);
|
|
@@ -29588,6 +29588,17 @@ var init_execute = __esm({
|
|
|
29588
29588
|
EnvRunError = class extends Error {
|
|
29589
29589
|
};
|
|
29590
29590
|
RunRefusalError = class extends EnvRunError {
|
|
29591
|
+
/**
|
|
29592
|
+
* Whose side refused (TERM-1313), set at the throw and never read back out of the message.
|
|
29593
|
+
* A baseline intake labels a posting not-runnable on `repository`, so that value is passed
|
|
29594
|
+
* only where envrun can show the repository asked for something we cannot supply. Every
|
|
29595
|
+
* other refusal, including one nobody has classified yet, is `ours` by default.
|
|
29596
|
+
*/
|
|
29597
|
+
origin;
|
|
29598
|
+
constructor(message2, options) {
|
|
29599
|
+
super(message2, options);
|
|
29600
|
+
this.origin = options?.origin ?? "ours";
|
|
29601
|
+
}
|
|
29591
29602
|
};
|
|
29592
29603
|
MAX_CAUSE_FRAMES = 16;
|
|
29593
29604
|
CHAIN_UNREADABLE = "<the cause chain stopped: a value refused to be read>";
|
|
@@ -29815,6 +29826,17 @@ var init_dsse = __esm({
|
|
|
29815
29826
|
}
|
|
29816
29827
|
});
|
|
29817
29828
|
|
|
29829
|
+
// ../../packages/attest/dist/dispatchedRun.js
|
|
29830
|
+
import { createHash as createHash7, createPublicKey as createPublicKey2 } from "crypto";
|
|
29831
|
+
var init_dispatchedRun = __esm({
|
|
29832
|
+
"../../packages/attest/dist/dispatchedRun.js"() {
|
|
29833
|
+
"use strict";
|
|
29834
|
+
init_keys();
|
|
29835
|
+
init_pae();
|
|
29836
|
+
init_types4();
|
|
29837
|
+
}
|
|
29838
|
+
});
|
|
29839
|
+
|
|
29818
29840
|
// ../../packages/attest/dist/sealedbox.js
|
|
29819
29841
|
import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, diffieHellman, generateKeyPairSync as generateKeyPairSync2, hkdfSync as hkdfSync2, randomBytes as randomBytes9 } from "crypto";
|
|
29820
29842
|
var init_sealedbox = __esm({
|
|
@@ -29861,7 +29883,7 @@ var init_nonce = __esm({
|
|
|
29861
29883
|
});
|
|
29862
29884
|
|
|
29863
29885
|
// ../../packages/attest/dist/gceIdentity.js
|
|
29864
|
-
import { createHash as
|
|
29886
|
+
import { createHash as createHash8, createPublicKey as createPublicKey3, verify as cryptoVerify2 } from "crypto";
|
|
29865
29887
|
function expectedAudience(dispatchId) {
|
|
29866
29888
|
if (dispatchId === "")
|
|
29867
29889
|
throw new TypeError("expectedAudience: dispatchId is empty");
|
|
@@ -29946,7 +29968,7 @@ async function verifyGceIdentityToken(token, expectations, deps) {
|
|
|
29946
29968
|
}
|
|
29947
29969
|
let signatureValid = false;
|
|
29948
29970
|
try {
|
|
29949
|
-
const publicKey =
|
|
29971
|
+
const publicKey = createPublicKey3({ key: jwk, format: "jwk" });
|
|
29950
29972
|
signatureValid = cryptoVerify2("sha256", Buffer.from(`${headerB64}.${payloadB64}`, "utf8"), publicKey, signature);
|
|
29951
29973
|
} catch {
|
|
29952
29974
|
signatureValid = false;
|
|
@@ -30007,7 +30029,7 @@ async function verifyGceIdentityToken(token, expectations, deps) {
|
|
|
30007
30029
|
email: expectations.serviceAccountEmail,
|
|
30008
30030
|
iat,
|
|
30009
30031
|
exp,
|
|
30010
|
-
tokenSha256:
|
|
30032
|
+
tokenSha256: createHash8("sha256").update(token, "utf8").digest("hex")
|
|
30011
30033
|
}
|
|
30012
30034
|
};
|
|
30013
30035
|
}
|
|
@@ -30022,7 +30044,7 @@ var init_gceIdentity = __esm({
|
|
|
30022
30044
|
});
|
|
30023
30045
|
|
|
30024
30046
|
// ../../packages/attest/dist/confidentialSpace.js
|
|
30025
|
-
import { createHash as
|
|
30047
|
+
import { createHash as createHash9, createPublicKey as createPublicKey4, verify as cryptoVerify3 } from "crypto";
|
|
30026
30048
|
function refuse2(reason, detail) {
|
|
30027
30049
|
return { ok: false, reason, detail };
|
|
30028
30050
|
}
|
|
@@ -30096,7 +30118,7 @@ async function verifyConfidentialSpaceToken(token, expectations, deps) {
|
|
|
30096
30118
|
}
|
|
30097
30119
|
let signatureValid = false;
|
|
30098
30120
|
try {
|
|
30099
|
-
const publicKey =
|
|
30121
|
+
const publicKey = createPublicKey4({ key: jwk, format: "jwk" });
|
|
30100
30122
|
signatureValid = cryptoVerify3("sha256", Buffer.from(`${headerB64}.${payloadB64}`, "utf8"), publicKey, signature);
|
|
30101
30123
|
} catch {
|
|
30102
30124
|
signatureValid = false;
|
|
@@ -30196,7 +30218,7 @@ async function verifyConfidentialSpaceToken(token, expectations, deps) {
|
|
|
30196
30218
|
hwmodel,
|
|
30197
30219
|
iat,
|
|
30198
30220
|
exp,
|
|
30199
|
-
tokenSha256:
|
|
30221
|
+
tokenSha256: createHash9("sha256").update(token, "utf8").digest("hex")
|
|
30200
30222
|
}
|
|
30201
30223
|
};
|
|
30202
30224
|
}
|
|
@@ -30240,6 +30262,7 @@ var init_dist2 = __esm({
|
|
|
30240
30262
|
init_pae();
|
|
30241
30263
|
init_keys();
|
|
30242
30264
|
init_dsse();
|
|
30265
|
+
init_dispatchedRun();
|
|
30243
30266
|
init_sealedbox();
|
|
30244
30267
|
init_aead();
|
|
30245
30268
|
init_manifest();
|
|
@@ -30497,7 +30520,8 @@ var init_result = __esm({
|
|
|
30497
30520
|
"imageSource",
|
|
30498
30521
|
"leaksClean",
|
|
30499
30522
|
"leakState",
|
|
30500
|
-
"venue"
|
|
30523
|
+
"venue",
|
|
30524
|
+
"refusalOrigin"
|
|
30501
30525
|
];
|
|
30502
30526
|
RENDER_NONE = null;
|
|
30503
30527
|
FIELD_VIEWS = {
|
|
@@ -30557,13 +30581,16 @@ var init_result = __esm({
|
|
|
30557
30581
|
// Absent on most runs, so it prints only when there is something to say. Silence
|
|
30558
30582
|
// here is the honest rendering of "no venue answered": a placeholder line would
|
|
30559
30583
|
// invite a reader to treat an unanswered probe as a described venue.
|
|
30560
|
-
venue: (r) => r.venue === null ? null : renderVenueLine(r.venue)
|
|
30584
|
+
venue: (r) => r.venue === null ? null : renderVenueLine(r.venue),
|
|
30585
|
+
// For the worker and the baseline intake, not the terminal: `reason` already says which
|
|
30586
|
+
// side refused in words a developer reads.
|
|
30587
|
+
refusalOrigin: RENDER_NONE
|
|
30561
30588
|
};
|
|
30562
30589
|
}
|
|
30563
30590
|
});
|
|
30564
30591
|
|
|
30565
30592
|
// ../../packages/envrun/dist/attestation.js
|
|
30566
|
-
import { createHash as
|
|
30593
|
+
import { createHash as createHash10, randomBytes as randomBytes11 } from "crypto";
|
|
30567
30594
|
function contradicts(outcome, counts, exitCode) {
|
|
30568
30595
|
const budget = OUTCOME_TO_BUDGET[outcome];
|
|
30569
30596
|
if (budget === null)
|
|
@@ -30605,7 +30632,7 @@ function imageDigestOf(ref) {
|
|
|
30605
30632
|
return at === -1 ? null : ref.slice(at + 1);
|
|
30606
30633
|
}
|
|
30607
30634
|
function sha256Hex(data) {
|
|
30608
|
-
return
|
|
30635
|
+
return createHash10("sha256").update(typeof data === "string" ? Buffer.from(data, "utf8") : data).digest("hex");
|
|
30609
30636
|
}
|
|
30610
30637
|
function toTestRunResult(result, outputSha256) {
|
|
30611
30638
|
return {
|
|
@@ -30900,7 +30927,382 @@ var init_attestation2 = __esm({
|
|
|
30900
30927
|
}
|
|
30901
30928
|
});
|
|
30902
30929
|
|
|
30930
|
+
// ../../packages/envrun/dist/lockfileCheck.js
|
|
30931
|
+
function supportedLockfile(basename7) {
|
|
30932
|
+
return basename7 === "package-lock.json" || basename7 === "yarn.lock" ? basename7 : null;
|
|
30933
|
+
}
|
|
30934
|
+
function fail2(detail) {
|
|
30935
|
+
return { ok: false, detail };
|
|
30936
|
+
}
|
|
30937
|
+
function tarballBasename(name) {
|
|
30938
|
+
return name.startsWith("@") ? name.slice(name.indexOf("/") + 1) : name;
|
|
30939
|
+
}
|
|
30940
|
+
function resolvedProblem(label, path5, name, version, resolved) {
|
|
30941
|
+
if (typeof resolved !== "string") {
|
|
30942
|
+
return `\`${label}\` in ${path5} records no download address, so where it installs from could not be checked.`;
|
|
30943
|
+
}
|
|
30944
|
+
const tail2 = `${name}/-/${tarballBasename(name)}-${version}.tgz`;
|
|
30945
|
+
if (REGISTRY_ORIGINS.some((origin) => resolved === origin + tail2))
|
|
30946
|
+
return null;
|
|
30947
|
+
if (REGISTRY_ORIGINS.some((origin) => resolved.startsWith(origin))) {
|
|
30948
|
+
return `\`${label}\` in ${path5} resolves to a registry tarball for a different package or version than the entry records.`;
|
|
30949
|
+
}
|
|
30950
|
+
return `\`${label}\` in ${path5} resolves outside the npm registry. ${RULE}`;
|
|
30951
|
+
}
|
|
30952
|
+
function integrityProblem(label, path5, integrity, sha1Only) {
|
|
30953
|
+
if (typeof integrity === "string") {
|
|
30954
|
+
const tokens = integrity.split(" ");
|
|
30955
|
+
if (tokens.length > 0 && tokens.every((t) => SRI_TOKEN.test(t)))
|
|
30956
|
+
return null;
|
|
30957
|
+
return `\`${label}\` in ${path5} has an integrity field we could not read as a sha256, sha384 or sha512 hash.`;
|
|
30958
|
+
}
|
|
30959
|
+
const strength = "A change is accepted only when every new or changed entry records a sha256, sha384 or sha512 integrity hash.";
|
|
30960
|
+
return sha1Only ? `\`${label}\` in ${path5} carries only a SHA-1 checksum and no integrity field. ${strength}` : `\`${label}\` in ${path5} has no integrity field. ${strength}`;
|
|
30961
|
+
}
|
|
30962
|
+
function checkLockfileChange(input) {
|
|
30963
|
+
const kind = supportedLockfile(input.path.split("/").pop() ?? "");
|
|
30964
|
+
if (kind === "yarn.lock")
|
|
30965
|
+
return checkYarn(input);
|
|
30966
|
+
if (kind === "package-lock.json")
|
|
30967
|
+
return checkNpm(input);
|
|
30968
|
+
return fail2(`${input.path} is not a lockfile this check can read.`);
|
|
30969
|
+
}
|
|
30970
|
+
function isObject(value) {
|
|
30971
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30972
|
+
}
|
|
30973
|
+
function sameJson(a, b) {
|
|
30974
|
+
if (a === b)
|
|
30975
|
+
return true;
|
|
30976
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
30977
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
|
|
30978
|
+
return false;
|
|
30979
|
+
return a.every((x, i) => sameJson(x, b[i]));
|
|
30980
|
+
}
|
|
30981
|
+
if (!isObject(a) || !isObject(b))
|
|
30982
|
+
return false;
|
|
30983
|
+
const keys = Object.keys(a);
|
|
30984
|
+
if (keys.length !== Object.keys(b).length)
|
|
30985
|
+
return false;
|
|
30986
|
+
return keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && sameJson(a[k], b[k]));
|
|
30987
|
+
}
|
|
30988
|
+
function parseObject(text) {
|
|
30989
|
+
try {
|
|
30990
|
+
const value = JSON.parse(text);
|
|
30991
|
+
return isObject(value) ? value : null;
|
|
30992
|
+
} catch {
|
|
30993
|
+
return null;
|
|
30994
|
+
}
|
|
30995
|
+
}
|
|
30996
|
+
function checkNpm({ path: path5, base, post }) {
|
|
30997
|
+
const before = parseObject(base);
|
|
30998
|
+
const after = parseObject(post);
|
|
30999
|
+
if (before === null || after === null) {
|
|
31000
|
+
return fail2(`${path5} could not be read as JSON, so its entries could not be checked.`);
|
|
31001
|
+
}
|
|
31002
|
+
for (const doc of [before, after]) {
|
|
31003
|
+
const version = doc["lockfileVersion"];
|
|
31004
|
+
if (version === 1) {
|
|
31005
|
+
return fail2(`${path5} is an npm lockfile at lockfileVersion 1. Only versions 2 and 3 record every package in a form we can check, so a change to it is refused.`);
|
|
31006
|
+
}
|
|
31007
|
+
if (version !== 2 && version !== 3) {
|
|
31008
|
+
return fail2(`${path5} does not declare lockfileVersion 2 or 3, so its entries could not be checked.`);
|
|
31009
|
+
}
|
|
31010
|
+
}
|
|
31011
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
|
|
31012
|
+
if (!NPM_TOP_LEVEL.has(key) && !sameJson(before[key], after[key])) {
|
|
31013
|
+
return fail2(`${path5} changes its top-level "${key}" field, which is not a package entry this check can read.`);
|
|
31014
|
+
}
|
|
31015
|
+
}
|
|
31016
|
+
const beforePackages = before["packages"];
|
|
31017
|
+
const afterPackages = after["packages"];
|
|
31018
|
+
if (!isObject(beforePackages) || !isObject(afterPackages)) {
|
|
31019
|
+
return fail2(`${path5} has no "packages" section, so its entries could not be checked.`);
|
|
31020
|
+
}
|
|
31021
|
+
let judged = 0;
|
|
31022
|
+
for (const [key, entry] of Object.entries(afterPackages)) {
|
|
31023
|
+
if (Object.prototype.hasOwnProperty.call(beforePackages, key) && sameJson(beforePackages[key], entry)) {
|
|
31024
|
+
continue;
|
|
31025
|
+
}
|
|
31026
|
+
judged += 1;
|
|
31027
|
+
if (!isObject(entry))
|
|
31028
|
+
return fail2(`${path5} has an entry "${key}" that is not an object.`);
|
|
31029
|
+
if (key === "") {
|
|
31030
|
+
if ("resolved" in entry || "link" in entry) {
|
|
31031
|
+
return fail2(`${path5} gives the root project a download address or a link, which this check does not accept.`);
|
|
31032
|
+
}
|
|
31033
|
+
continue;
|
|
31034
|
+
}
|
|
31035
|
+
const problem = npmPackageProblem(path5, key, entry);
|
|
31036
|
+
if (problem !== null)
|
|
31037
|
+
return fail2(problem);
|
|
31038
|
+
}
|
|
31039
|
+
const legacy = after["dependencies"];
|
|
31040
|
+
if (legacy !== void 0) {
|
|
31041
|
+
const problem = npmLegacyProblem(path5, before["dependencies"], legacy);
|
|
31042
|
+
if (problem !== null)
|
|
31043
|
+
return fail2(problem);
|
|
31044
|
+
}
|
|
31045
|
+
return { ok: true, judged };
|
|
31046
|
+
}
|
|
31047
|
+
function npmPackageProblem(path5, key, entry) {
|
|
31048
|
+
const match2 = NPM_INSTALL_KEY.exec(key);
|
|
31049
|
+
const name = match2?.[1];
|
|
31050
|
+
if (name === void 0 || !PACKAGE_NAME.test(name)) {
|
|
31051
|
+
return `${path5} changes the entry "${key}", which is a workspace or local path rather than a package from the registry, so the change is refused.`;
|
|
31052
|
+
}
|
|
31053
|
+
if (entry["link"] !== void 0) {
|
|
31054
|
+
return `${path5} changes "${name}" into a link to a local directory, which this check does not accept.`;
|
|
31055
|
+
}
|
|
31056
|
+
if (entry["inBundle"] !== void 0) {
|
|
31057
|
+
return `${path5} changes "${name}", which comes bundled inside another package and so records no download address to check.`;
|
|
31058
|
+
}
|
|
31059
|
+
if (entry["name"] !== void 0 && entry["name"] !== name) {
|
|
31060
|
+
return `${path5} installs a package under the alias "${name}", which this check does not accept.`;
|
|
31061
|
+
}
|
|
31062
|
+
const version = entry["version"];
|
|
31063
|
+
if (typeof version !== "string" || !VERSION2.test(version)) {
|
|
31064
|
+
return `${path5} records "${name}" with no published version we can check.`;
|
|
31065
|
+
}
|
|
31066
|
+
const label = `${name}@${version}`;
|
|
31067
|
+
return resolvedProblem(label, path5, name, version, entry["resolved"]) ?? integrityProblem(label, path5, entry["integrity"], false);
|
|
31068
|
+
}
|
|
31069
|
+
function withoutNested(entry) {
|
|
31070
|
+
const copy = { ...entry };
|
|
31071
|
+
delete copy["dependencies"];
|
|
31072
|
+
return copy;
|
|
31073
|
+
}
|
|
31074
|
+
function npmLegacyProblem(path5, before, after) {
|
|
31075
|
+
if (!isObject(after))
|
|
31076
|
+
return `${path5} has a "dependencies" section that is not an object.`;
|
|
31077
|
+
const previous = isObject(before) ? before : {};
|
|
31078
|
+
for (const [name, entry] of Object.entries(after)) {
|
|
31079
|
+
if (!isObject(entry))
|
|
31080
|
+
return `${path5} has a "dependencies" entry "${name}" that is not an object.`;
|
|
31081
|
+
const old = previous[name];
|
|
31082
|
+
const unchanged = isObject(old) && sameJson(withoutNested(old), withoutNested(entry));
|
|
31083
|
+
if (!unchanged) {
|
|
31084
|
+
if (!PACKAGE_NAME.test(name))
|
|
31085
|
+
return `${path5} names a dependency "${name}" we could not read.`;
|
|
31086
|
+
if (entry["bundled"] !== void 0) {
|
|
31087
|
+
return `${path5} changes "${name}", which comes bundled inside another package and so records no download address to check.`;
|
|
31088
|
+
}
|
|
31089
|
+
const version = entry["version"];
|
|
31090
|
+
if (typeof version !== "string" || !VERSION2.test(version)) {
|
|
31091
|
+
return `${path5} records "${name}" with no published version we can check.`;
|
|
31092
|
+
}
|
|
31093
|
+
const label = `${name}@${version}`;
|
|
31094
|
+
const problem = resolvedProblem(label, path5, name, version, entry["resolved"]) ?? integrityProblem(label, path5, entry["integrity"], false);
|
|
31095
|
+
if (problem !== null)
|
|
31096
|
+
return problem;
|
|
31097
|
+
}
|
|
31098
|
+
const nested = entry["dependencies"];
|
|
31099
|
+
if (nested !== void 0) {
|
|
31100
|
+
const problem = npmLegacyProblem(path5, isObject(old) ? old["dependencies"] : void 0, nested);
|
|
31101
|
+
if (problem !== null)
|
|
31102
|
+
return problem;
|
|
31103
|
+
}
|
|
31104
|
+
}
|
|
31105
|
+
return null;
|
|
31106
|
+
}
|
|
31107
|
+
function unquote(value) {
|
|
31108
|
+
if (value.startsWith('"')) {
|
|
31109
|
+
try {
|
|
31110
|
+
const parsed = JSON.parse(value);
|
|
31111
|
+
return typeof parsed === "string" ? parsed : null;
|
|
31112
|
+
} catch {
|
|
31113
|
+
return null;
|
|
31114
|
+
}
|
|
31115
|
+
}
|
|
31116
|
+
return BARE_WORD.test(value) ? value : null;
|
|
31117
|
+
}
|
|
31118
|
+
function leadingPart(text) {
|
|
31119
|
+
if (text.startsWith('"')) {
|
|
31120
|
+
for (let i = 1; i < text.length; i += 1) {
|
|
31121
|
+
if (text[i] === "\\") {
|
|
31122
|
+
i += 1;
|
|
31123
|
+
continue;
|
|
31124
|
+
}
|
|
31125
|
+
if (text[i] === '"') {
|
|
31126
|
+
const value = unquote(text.slice(0, i + 1));
|
|
31127
|
+
return value === null ? null : { value, rest: text.slice(i + 1) };
|
|
31128
|
+
}
|
|
31129
|
+
}
|
|
31130
|
+
return null;
|
|
31131
|
+
}
|
|
31132
|
+
const bare = /^[^\s"]+/.exec(text)?.[0];
|
|
31133
|
+
if (bare === void 0 || !BARE_WORD.test(bare))
|
|
31134
|
+
return null;
|
|
31135
|
+
return { value: bare, rest: text.slice(bare.length) };
|
|
31136
|
+
}
|
|
31137
|
+
function mapLine(text) {
|
|
31138
|
+
const key = leadingPart(text);
|
|
31139
|
+
if (key === null || !key.rest.startsWith(" "))
|
|
31140
|
+
return null;
|
|
31141
|
+
const value = leadingPart(key.rest.slice(1));
|
|
31142
|
+
if (value === null || value.rest !== "")
|
|
31143
|
+
return null;
|
|
31144
|
+
return { key: key.value, value: value.value };
|
|
31145
|
+
}
|
|
31146
|
+
function parseYarn(path5, text, when) {
|
|
31147
|
+
const lines = text.split("\n");
|
|
31148
|
+
if (text.includes("\r")) {
|
|
31149
|
+
return { problem: `${path5} ${when} uses carriage returns, which this check does not read.` };
|
|
31150
|
+
}
|
|
31151
|
+
if (lines.some((l) => l === "__metadata:")) {
|
|
31152
|
+
return {
|
|
31153
|
+
problem: `${path5} ${when} is a lockfile from Yarn 2 or later. Only yarn v1 lockfiles are checked, so a change to it is refused.`
|
|
31154
|
+
};
|
|
31155
|
+
}
|
|
31156
|
+
if (!lines.some((l) => l === "# yarn lockfile v1")) {
|
|
31157
|
+
return {
|
|
31158
|
+
problem: `${path5} ${when} is not a yarn v1 lockfile, so its entries could not be checked.`
|
|
31159
|
+
};
|
|
31160
|
+
}
|
|
31161
|
+
const entries = /* @__PURE__ */ new Map();
|
|
31162
|
+
let header = null;
|
|
31163
|
+
let block = [];
|
|
31164
|
+
let fields = /* @__PURE__ */ new Map();
|
|
31165
|
+
let inMap = false;
|
|
31166
|
+
const close = () => {
|
|
31167
|
+
if (header === null)
|
|
31168
|
+
return null;
|
|
31169
|
+
if (entries.has(header))
|
|
31170
|
+
return `${path5} ${when} lists the entry ${header} twice.`;
|
|
31171
|
+
entries.set(header, { raw: block.join("\n"), fields });
|
|
31172
|
+
header = null;
|
|
31173
|
+
return null;
|
|
31174
|
+
};
|
|
31175
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
31176
|
+
const line = lines[i] ?? "";
|
|
31177
|
+
const unreadable = `${path5} ${when} could not be read at line ${String(i + 1)}, so its entries could not be checked.`;
|
|
31178
|
+
if (line === "") {
|
|
31179
|
+
const problem2 = close();
|
|
31180
|
+
if (problem2 !== null)
|
|
31181
|
+
return { problem: problem2 };
|
|
31182
|
+
continue;
|
|
31183
|
+
}
|
|
31184
|
+
if (header === null && line.startsWith("#"))
|
|
31185
|
+
continue;
|
|
31186
|
+
if (!line.startsWith(" ")) {
|
|
31187
|
+
const problem2 = close();
|
|
31188
|
+
if (problem2 !== null)
|
|
31189
|
+
return { problem: problem2 };
|
|
31190
|
+
if (!line.endsWith(":") || line.length < 2)
|
|
31191
|
+
return { problem: unreadable };
|
|
31192
|
+
header = line.slice(0, -1);
|
|
31193
|
+
block = [line];
|
|
31194
|
+
fields = /* @__PURE__ */ new Map();
|
|
31195
|
+
inMap = false;
|
|
31196
|
+
continue;
|
|
31197
|
+
}
|
|
31198
|
+
if (header === null)
|
|
31199
|
+
return { problem: unreadable };
|
|
31200
|
+
block.push(line);
|
|
31201
|
+
if (line.startsWith(" ")) {
|
|
31202
|
+
if (!inMap || mapLine(line.slice(4)) === null)
|
|
31203
|
+
return { problem: unreadable };
|
|
31204
|
+
continue;
|
|
31205
|
+
}
|
|
31206
|
+
const field = /^ {2}([A-Za-z]+)(?: (.+)|:)$/.exec(line);
|
|
31207
|
+
const key = field?.[1];
|
|
31208
|
+
if (field === null || key === void 0 || fields.has(key))
|
|
31209
|
+
return { problem: unreadable };
|
|
31210
|
+
if (field[2] === void 0) {
|
|
31211
|
+
if (!YARN_MAP_FIELDS.has(key))
|
|
31212
|
+
return { problem: unreadable };
|
|
31213
|
+
fields.set(key, "");
|
|
31214
|
+
inMap = true;
|
|
31215
|
+
continue;
|
|
31216
|
+
}
|
|
31217
|
+
const value = unquote(field[2]);
|
|
31218
|
+
if (!YARN_VALUE_FIELDS.has(key) || value === null)
|
|
31219
|
+
return { problem: unreadable };
|
|
31220
|
+
fields.set(key, value);
|
|
31221
|
+
inMap = false;
|
|
31222
|
+
}
|
|
31223
|
+
const problem = close();
|
|
31224
|
+
if (problem !== null)
|
|
31225
|
+
return { problem };
|
|
31226
|
+
return { entries };
|
|
31227
|
+
}
|
|
31228
|
+
function headerName(header) {
|
|
31229
|
+
let name = null;
|
|
31230
|
+
for (const part of header.split(", ")) {
|
|
31231
|
+
const spec = unquote(part);
|
|
31232
|
+
if (spec === null)
|
|
31233
|
+
return null;
|
|
31234
|
+
const at = spec.lastIndexOf("@");
|
|
31235
|
+
if (at <= 0)
|
|
31236
|
+
return null;
|
|
31237
|
+
const candidate = spec.slice(0, at);
|
|
31238
|
+
const range = spec.slice(at + 1);
|
|
31239
|
+
if (!PACKAGE_NAME.test(candidate) || /[:/#\\]/.test(range))
|
|
31240
|
+
return null;
|
|
31241
|
+
if (name !== null && name !== candidate)
|
|
31242
|
+
return null;
|
|
31243
|
+
name = candidate;
|
|
31244
|
+
}
|
|
31245
|
+
return name;
|
|
31246
|
+
}
|
|
31247
|
+
function checkYarn({ path: path5, base, post }) {
|
|
31248
|
+
const before = parseYarn(path5, base, "as it was");
|
|
31249
|
+
if ("problem" in before)
|
|
31250
|
+
return fail2(before.problem);
|
|
31251
|
+
const after = parseYarn(path5, post, "after the change");
|
|
31252
|
+
if ("problem" in after)
|
|
31253
|
+
return fail2(after.problem);
|
|
31254
|
+
let judged = 0;
|
|
31255
|
+
for (const [header, entry] of after.entries) {
|
|
31256
|
+
if (before.entries.get(header)?.raw === entry.raw)
|
|
31257
|
+
continue;
|
|
31258
|
+
judged += 1;
|
|
31259
|
+
const name = headerName(header);
|
|
31260
|
+
if (name === null) {
|
|
31261
|
+
return fail2(`The entry ${header} in ${path5} is not a plain package range from the registry, so the change is refused.`);
|
|
31262
|
+
}
|
|
31263
|
+
const version = entry.fields.get("version");
|
|
31264
|
+
if (version === void 0 || !VERSION2.test(version)) {
|
|
31265
|
+
return fail2(`${path5} records ${name} with no published version we can check.`);
|
|
31266
|
+
}
|
|
31267
|
+
const label = `${name}@${version}`;
|
|
31268
|
+
const resolved = entry.fields.get("resolved");
|
|
31269
|
+
const fragment = resolved === void 0 ? null : /^(.*)#([0-9a-f]{40})$/.exec(resolved);
|
|
31270
|
+
const url = fragment?.[1] ?? resolved;
|
|
31271
|
+
const problem = resolvedProblem(label, path5, name, version, url) ?? integrityProblem(label, path5, entry.fields.get("integrity"), fragment !== null);
|
|
31272
|
+
if (problem !== null)
|
|
31273
|
+
return fail2(problem);
|
|
31274
|
+
}
|
|
31275
|
+
return { ok: true, judged };
|
|
31276
|
+
}
|
|
31277
|
+
var REGISTRY_ORIGINS, PACKAGE_NAME, VERSION2, SRI_TOKEN, RULE, NPM_TOP_LEVEL, NPM_INSTALL_KEY, YARN_VALUE_FIELDS, YARN_MAP_FIELDS, BARE_WORD;
|
|
31278
|
+
var init_lockfileCheck = __esm({
|
|
31279
|
+
"../../packages/envrun/dist/lockfileCheck.js"() {
|
|
31280
|
+
"use strict";
|
|
31281
|
+
REGISTRY_ORIGINS = ["https://registry.npmjs.org/", "https://registry.yarnpkg.com/"];
|
|
31282
|
+
PACKAGE_NAME = /^(?:@[A-Za-z0-9][A-Za-z0-9._~-]*\/)?[A-Za-z0-9][A-Za-z0-9._~-]*$/;
|
|
31283
|
+
VERSION2 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
31284
|
+
SRI_TOKEN = /^sha(?:256|384|512)-[A-Za-z0-9+/]+={0,2}$/;
|
|
31285
|
+
RULE = "Only a tarball on registry.npmjs.org or registry.yarnpkg.com, for the exact name and version the entry records, is accepted.";
|
|
31286
|
+
NPM_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
31287
|
+
"name",
|
|
31288
|
+
"version",
|
|
31289
|
+
"lockfileVersion",
|
|
31290
|
+
"requires",
|
|
31291
|
+
"packages",
|
|
31292
|
+
"dependencies"
|
|
31293
|
+
]);
|
|
31294
|
+
NPM_INSTALL_KEY = /^(?:node_modules\/(?:@[^/]+\/)?[^/]+\/)*node_modules\/((?:@[^/]+\/)?[^/]+)$/;
|
|
31295
|
+
YARN_VALUE_FIELDS = /* @__PURE__ */ new Set(["version", "resolved", "integrity"]);
|
|
31296
|
+
YARN_MAP_FIELDS = /* @__PURE__ */ new Set(["dependencies", "optionalDependencies"]);
|
|
31297
|
+
BARE_WORD = /^(?!true|false)[A-Za-z/.-][^:\s,"]*$/;
|
|
31298
|
+
}
|
|
31299
|
+
});
|
|
31300
|
+
|
|
30903
31301
|
// ../../packages/envrun/dist/boundary.js
|
|
31302
|
+
function lockfileRefusal(base) {
|
|
31303
|
+
const kind = LOCKFILE_KINDS[base];
|
|
31304
|
+
return kind === void 0 ? "is a dependency lockfile of a kind we do not check. Only package-lock.json (lockfileVersion 2 or 3) and yarn.lock (v1) changes are checked entry by entry, so a change to it is refused" : `is ${kind}. Only package-lock.json (lockfileVersion 2 or 3) and yarn.lock (v1) changes are checked entry by entry, so a change to it is refused`;
|
|
31305
|
+
}
|
|
30904
31306
|
function unquoteDiffPath(token) {
|
|
30905
31307
|
if (!token.startsWith('"'))
|
|
30906
31308
|
return token === "" ? null : token;
|
|
@@ -31006,18 +31408,29 @@ function parsePatchPaths(patch) {
|
|
|
31006
31408
|
let oldRemaining = 0;
|
|
31007
31409
|
let newRemaining = 0;
|
|
31008
31410
|
let inHunk = false;
|
|
31009
|
-
const
|
|
31411
|
+
const fail3 = (detail) => ({ failure: { code: "unparseable", detail } });
|
|
31010
31412
|
for (let i = 0; i < lines.length; i += 1) {
|
|
31011
31413
|
const line = lines[i];
|
|
31012
31414
|
if (line === void 0)
|
|
31013
31415
|
continue;
|
|
31014
31416
|
if (line.startsWith(DIFF_HEADER)) {
|
|
31015
31417
|
if (inHunk && (oldRemaining > 0 || newRemaining > 0)) {
|
|
31016
|
-
return
|
|
31418
|
+
return fail3("a change block ended before it delivered the lines it announced");
|
|
31017
31419
|
}
|
|
31018
31420
|
inHunk = false;
|
|
31019
31421
|
skippingBinary = false;
|
|
31020
|
-
entry = {
|
|
31422
|
+
entry = {
|
|
31423
|
+
rawPaths: [],
|
|
31424
|
+
binary: false,
|
|
31425
|
+
headerOld: null,
|
|
31426
|
+
headerNew: null,
|
|
31427
|
+
plusPath: null,
|
|
31428
|
+
createsFile: false,
|
|
31429
|
+
deletesFile: false,
|
|
31430
|
+
moved: false,
|
|
31431
|
+
changed: false,
|
|
31432
|
+
modes: []
|
|
31433
|
+
};
|
|
31021
31434
|
entries.push(entry);
|
|
31022
31435
|
const split2 = splitDiffHeader(line.slice(DIFF_HEADER.length));
|
|
31023
31436
|
if (split2 !== null) {
|
|
@@ -31025,14 +31438,18 @@ function parsePatchPaths(patch) {
|
|
|
31025
31438
|
const left = unquoteDiffPath(leftToken);
|
|
31026
31439
|
const right = unquoteDiffPath(rightToken);
|
|
31027
31440
|
if (left === null || right === null) {
|
|
31028
|
-
return
|
|
31441
|
+
return fail3("a file name in the patch is quoted in a form we cannot read back");
|
|
31029
31442
|
}
|
|
31030
31443
|
const oldPath = stripSidePrefix(left, "a/");
|
|
31031
31444
|
const newPath = stripSidePrefix(right, "b/");
|
|
31032
31445
|
if (oldPath === null || newPath === null) {
|
|
31033
|
-
return
|
|
31446
|
+
return fail3("the patch was written without the standard a/ and b/ file-name prefixes, so we cannot tell a prefix from a real directory");
|
|
31034
31447
|
}
|
|
31035
31448
|
entry.rawPaths.push(oldPath, newPath);
|
|
31449
|
+
entry.headerOld = oldPath;
|
|
31450
|
+
entry.headerNew = newPath;
|
|
31451
|
+
if (oldPath !== newPath)
|
|
31452
|
+
entry.moved = true;
|
|
31036
31453
|
}
|
|
31037
31454
|
continue;
|
|
31038
31455
|
}
|
|
@@ -31049,13 +31466,15 @@ function parsePatchPaths(patch) {
|
|
|
31049
31466
|
newRemaining -= 1;
|
|
31050
31467
|
} else if (marker === "-") {
|
|
31051
31468
|
oldRemaining -= 1;
|
|
31469
|
+
entry.changed = true;
|
|
31052
31470
|
} else if (marker === "+") {
|
|
31053
31471
|
newRemaining -= 1;
|
|
31472
|
+
entry.changed = true;
|
|
31054
31473
|
} else {
|
|
31055
|
-
return
|
|
31474
|
+
return fail3("a change block contains a line that is neither an addition, a removal nor context");
|
|
31056
31475
|
}
|
|
31057
31476
|
if (oldRemaining < 0 || newRemaining < 0) {
|
|
31058
|
-
return
|
|
31477
|
+
return fail3("a change block delivered more lines than it announced");
|
|
31059
31478
|
}
|
|
31060
31479
|
if (oldRemaining === 0 && newRemaining === 0)
|
|
31061
31480
|
inHunk = false;
|
|
@@ -31066,13 +31485,20 @@ function parsePatchPaths(patch) {
|
|
|
31066
31485
|
if (line.startsWith("@@")) {
|
|
31067
31486
|
const m = HUNK_HEADER.exec(line);
|
|
31068
31487
|
if (m === null) {
|
|
31069
|
-
return
|
|
31488
|
+
return fail3("a change block header is malformed, so we cannot tell which lines belong to it");
|
|
31070
31489
|
}
|
|
31071
31490
|
oldRemaining = m[2] === void 0 ? 1 : Number(m[2]);
|
|
31072
31491
|
newRemaining = m[4] === void 0 ? 1 : Number(m[4]);
|
|
31073
31492
|
inHunk = oldRemaining > 0 || newRemaining > 0;
|
|
31074
31493
|
continue;
|
|
31075
31494
|
}
|
|
31495
|
+
if (line.startsWith("new file mode"))
|
|
31496
|
+
entry.createsFile = true;
|
|
31497
|
+
if (line.startsWith("deleted file mode"))
|
|
31498
|
+
entry.deletesFile = true;
|
|
31499
|
+
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];
|
|
31500
|
+
if (mode !== void 0)
|
|
31501
|
+
entry.modes.push(mode);
|
|
31076
31502
|
if (line === "GIT binary patch" || line.startsWith("Binary files ")) {
|
|
31077
31503
|
entry.binary = true;
|
|
31078
31504
|
skippingBinary = true;
|
|
@@ -31083,41 +31509,49 @@ function parsePatchPaths(patch) {
|
|
|
31083
31509
|
const rest = line.slice(4);
|
|
31084
31510
|
const tab = rest.indexOf(" ");
|
|
31085
31511
|
const token = tab === -1 ? rest : rest.slice(0, tab);
|
|
31086
|
-
if (token === "/dev/null")
|
|
31512
|
+
if (token === "/dev/null") {
|
|
31513
|
+
if (sideMarker === "a/")
|
|
31514
|
+
entry.createsFile = true;
|
|
31515
|
+
else
|
|
31516
|
+
entry.deletesFile = true;
|
|
31087
31517
|
continue;
|
|
31518
|
+
}
|
|
31088
31519
|
const decoded = unquoteDiffPath(token);
|
|
31089
31520
|
if (decoded === null) {
|
|
31090
|
-
return
|
|
31521
|
+
return fail3("a file name in the patch is quoted in a form we cannot read back");
|
|
31091
31522
|
}
|
|
31092
31523
|
const stripped = stripSidePrefix(decoded, sideMarker);
|
|
31093
31524
|
if (stripped === null) {
|
|
31094
|
-
return
|
|
31525
|
+
return fail3("the patch was written without the standard a/ and b/ file-name prefixes, so we cannot tell a prefix from a real directory");
|
|
31095
31526
|
}
|
|
31096
31527
|
entry.rawPaths.push(stripped);
|
|
31528
|
+
if (sideMarker === "b/")
|
|
31529
|
+
entry.plusPath = stripped;
|
|
31097
31530
|
continue;
|
|
31098
31531
|
}
|
|
31099
31532
|
const moveMarker = ["rename from ", "rename to ", "copy from ", "copy to "].find((p) => line.startsWith(p));
|
|
31100
31533
|
if (moveMarker !== void 0) {
|
|
31101
31534
|
const decoded = unquoteDiffPath(line.slice(moveMarker.length));
|
|
31102
31535
|
if (decoded === null) {
|
|
31103
|
-
return
|
|
31536
|
+
return fail3("a file name in the patch is quoted in a form we cannot read back");
|
|
31104
31537
|
}
|
|
31105
31538
|
entry.rawPaths.push(decoded);
|
|
31539
|
+
entry.moved = true;
|
|
31106
31540
|
continue;
|
|
31107
31541
|
}
|
|
31108
31542
|
if (line.startsWith("+") || line.startsWith("-")) {
|
|
31109
|
-
return
|
|
31543
|
+
return fail3("the patch contains a changed line that belongs to no change block");
|
|
31110
31544
|
}
|
|
31111
31545
|
}
|
|
31112
31546
|
if (inHunk && (oldRemaining > 0 || newRemaining > 0)) {
|
|
31113
|
-
return
|
|
31547
|
+
return fail3("the patch ends in the middle of a change block");
|
|
31114
31548
|
}
|
|
31115
31549
|
if (entries.length === 0) {
|
|
31116
31550
|
return { failure: { code: "empty-patch", detail: "The patch contains no changes at all." } };
|
|
31117
31551
|
}
|
|
31118
31552
|
for (const e of entries) {
|
|
31119
31553
|
if (e.rawPaths.length === 0)
|
|
31120
|
-
return
|
|
31554
|
+
return fail3("a change in the patch names no file");
|
|
31121
31555
|
}
|
|
31122
31556
|
return { entries };
|
|
31123
31557
|
}
|
|
@@ -31142,6 +31576,10 @@ function preflightBoundary(input) {
|
|
|
31142
31576
|
const refusals = [];
|
|
31143
31577
|
const touched = /* @__PURE__ */ new Set();
|
|
31144
31578
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
31579
|
+
const lockfileTouches = /* @__PURE__ */ new Map();
|
|
31580
|
+
const changedManifests = /* @__PURE__ */ new Set();
|
|
31581
|
+
const deniedPaths = /* @__PURE__ */ new Set();
|
|
31582
|
+
const nonFileLockfiles = /* @__PURE__ */ new Set();
|
|
31145
31583
|
for (const entry of parsed.entries) {
|
|
31146
31584
|
if (entry.binary) {
|
|
31147
31585
|
const name = entry.rawPaths[0] ?? "a file";
|
|
@@ -31169,20 +31607,55 @@ function preflightBoundary(input) {
|
|
|
31169
31607
|
seenPaths.add(path5);
|
|
31170
31608
|
const denied = DENY_RULES.find((rule) => rule.matches(path5, segments, base));
|
|
31171
31609
|
if (denied !== void 0) {
|
|
31172
|
-
|
|
31610
|
+
deniedPaths.add(path5);
|
|
31611
|
+
const why = typeof denied.why === "string" ? denied.why : denied.why(base);
|
|
31612
|
+
refusals.push(refusal(denied.code, path5, `${path5} ${why}.`));
|
|
31173
31613
|
continue;
|
|
31174
31614
|
}
|
|
31175
31615
|
if (!slice.has(path5)) {
|
|
31176
31616
|
refusals.push(refusal("out-of-slice", path5, `${path5} is not one of the files shared for this bounty, so a change to it is outside what the developer was asked to work on.`));
|
|
31177
31617
|
}
|
|
31178
31618
|
}
|
|
31619
|
+
const contentRaw = entry.deletesFile ? entry.headerOld : entry.plusPath ?? entry.headerNew;
|
|
31620
|
+
const contentPath = contentRaw === null ? null : normalizePath(contentRaw)?.path ?? null;
|
|
31621
|
+
const plainEdit = entry.changed && !entry.createsFile && !entry.deletesFile && !entry.moved;
|
|
31622
|
+
const nonFile = entry.modes.some((m) => NON_FILE_MODES.has(m));
|
|
31623
|
+
for (const { path: path5, base } of entryPaths) {
|
|
31624
|
+
if (supportedLockfile(base) === null)
|
|
31625
|
+
continue;
|
|
31626
|
+
if (nonFile)
|
|
31627
|
+
nonFileLockfiles.add(path5);
|
|
31628
|
+
const seen = lockfileTouches.get(path5) ?? { touches: 0, edits: 0 };
|
|
31629
|
+
const edit = plainEdit && contentPath === path5 ? 1 : 0;
|
|
31630
|
+
lockfileTouches.set(path5, { touches: seen.touches + 1, edits: seen.edits + edit });
|
|
31631
|
+
}
|
|
31632
|
+
if (contentPath !== null && contentPath.split("/").pop() === "package.json" && entry.changed) {
|
|
31633
|
+
changedManifests.add(contentPath);
|
|
31634
|
+
}
|
|
31635
|
+
}
|
|
31636
|
+
for (const [path5, { touches, edits }] of lockfileTouches) {
|
|
31637
|
+
if (deniedPaths.has(path5))
|
|
31638
|
+
continue;
|
|
31639
|
+
if (nonFileLockfiles.has(path5)) {
|
|
31640
|
+
refusals.push(refusal("lockfile", path5, `${path5} is a symbolic link or a submodule in this patch, not a regular file. A lockfile must be a file whose entries can be checked, so the change is refused.`));
|
|
31641
|
+
continue;
|
|
31642
|
+
}
|
|
31643
|
+
if (touches !== 1 || edits !== 1) {
|
|
31644
|
+
refusals.push(refusal("lockfile", path5, `${path5} is created, deleted, moved or copied by this patch, or named more than once. Only an edit to the contents of an existing lockfile is checked, so this change is refused.`));
|
|
31645
|
+
continue;
|
|
31646
|
+
}
|
|
31647
|
+
const manifest = `${path5.slice(0, path5.lastIndexOf("/") + 1)}package.json`;
|
|
31648
|
+
if (!changedManifests.has(manifest)) {
|
|
31649
|
+
refusals.push(refusal("lockfile", path5, `${path5} changed but ${manifest} did not. A lockfile change is accepted only beside a change to the package.json it records.`));
|
|
31650
|
+
}
|
|
31179
31651
|
}
|
|
31180
31652
|
return { refused: refusals.length > 0, refusals, touchedPaths: [...touched].sort() };
|
|
31181
31653
|
}
|
|
31182
|
-
var PATH_REFUSAL_CODES, LOCKFILE_NAMES, DENY_RULES, TEXT_ENCODER, TEXT_DECODER, C_ESCAPES, DIFF_HEADER, HUNK_HEADER;
|
|
31654
|
+
var PATH_REFUSAL_CODES, LOCKFILE_NAMES, LOCKFILE_KINDS, REGISTRY_CONFIG_NAMES, DENY_RULES, TEXT_ENCODER, TEXT_DECODER, C_ESCAPES, NON_FILE_MODES, DIFF_HEADER, HUNK_HEADER;
|
|
31183
31655
|
var init_boundary = __esm({
|
|
31184
31656
|
"../../packages/envrun/dist/boundary.js"() {
|
|
31185
31657
|
"use strict";
|
|
31658
|
+
init_lockfileCheck();
|
|
31186
31659
|
PATH_REFUSAL_CODES = [
|
|
31187
31660
|
"empty-patch",
|
|
31188
31661
|
"unparseable",
|
|
@@ -31191,6 +31664,7 @@ var init_boundary = __esm({
|
|
|
31191
31664
|
"out-of-slice",
|
|
31192
31665
|
"ci-config",
|
|
31193
31666
|
"lockfile",
|
|
31667
|
+
"registry-config",
|
|
31194
31668
|
"build-file",
|
|
31195
31669
|
"git-internals"
|
|
31196
31670
|
];
|
|
@@ -31207,6 +31681,22 @@ var init_boundary = __esm({
|
|
|
31207
31681
|
"go.sum",
|
|
31208
31682
|
"composer.lock"
|
|
31209
31683
|
]);
|
|
31684
|
+
LOCKFILE_KINDS = {
|
|
31685
|
+
"package-lock.json": "an npm lockfile",
|
|
31686
|
+
"npm-shrinkwrap.json": "an npm shrinkwrap file",
|
|
31687
|
+
"yarn.lock": "a yarn lockfile",
|
|
31688
|
+
"pnpm-lock.yaml": "a pnpm lockfile",
|
|
31689
|
+
"bun.lockb": "a bun lockfile",
|
|
31690
|
+
"bun.lock": "a bun lockfile",
|
|
31691
|
+
"Cargo.lock": "a Cargo lockfile",
|
|
31692
|
+
"poetry.lock": "a Poetry lockfile",
|
|
31693
|
+
"Pipfile.lock": "a Pipenv lockfile",
|
|
31694
|
+
"Gemfile.lock": "a Bundler lockfile",
|
|
31695
|
+
"go.sum": "a Go checksum file",
|
|
31696
|
+
"composer.lock": "a Composer lockfile",
|
|
31697
|
+
"uv.lock": "a uv lockfile"
|
|
31698
|
+
};
|
|
31699
|
+
REGISTRY_CONFIG_NAMES = /* @__PURE__ */ new Set([".npmrc", ".yarnrc", ".yarnrc.yml"]);
|
|
31210
31700
|
DENY_RULES = [
|
|
31211
31701
|
{
|
|
31212
31702
|
code: "git-internals",
|
|
@@ -31248,8 +31738,15 @@ var init_boundary = __esm({
|
|
|
31248
31738
|
// The `.lock` suffix is the server's rule since TERM-412; this copy missed it, so
|
|
31249
31739
|
// the pre-flight passed `uv.lock` and bun's text `bun.lock` that the server refuses
|
|
31250
31740
|
// (TERM-1235). The name set only covers lockfiles whose names do not end in `.lock`.
|
|
31251
|
-
|
|
31252
|
-
|
|
31741
|
+
// TERM-1331: package-lock.json and yarn.lock are judged in `preflightBoundary` below,
|
|
31742
|
+
// as the source judges them in `validatePatch`.
|
|
31743
|
+
matches: (_p, _segments, base) => (LOCKFILE_NAMES.has(base) || base.endsWith(".lock")) && supportedLockfile(base) === null,
|
|
31744
|
+
why: lockfileRefusal
|
|
31745
|
+
},
|
|
31746
|
+
{
|
|
31747
|
+
code: "registry-config",
|
|
31748
|
+
matches: (_p, _segments, base) => REGISTRY_CONFIG_NAMES.has(base),
|
|
31749
|
+
why: "configures where packages are installed from. A patch may not change it, because it could point the install at another registry"
|
|
31253
31750
|
}
|
|
31254
31751
|
];
|
|
31255
31752
|
TEXT_ENCODER = new TextEncoder();
|
|
@@ -31265,6 +31762,7 @@ var init_boundary = __esm({
|
|
|
31265
31762
|
'"': 34,
|
|
31266
31763
|
"\\": 92
|
|
31267
31764
|
};
|
|
31765
|
+
NON_FILE_MODES = /* @__PURE__ */ new Set(["120000", "160000"]);
|
|
31268
31766
|
DIFF_HEADER = "diff --git ";
|
|
31269
31767
|
HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
31270
31768
|
}
|
|
@@ -31518,7 +32016,7 @@ var init_emptyGitConfig = __esm({
|
|
|
31518
32016
|
|
|
31519
32017
|
// ../../packages/envrun/dist/hostedVenue.js
|
|
31520
32018
|
import { spawn as spawn4, spawnSync as spawnSync5 } from "child_process";
|
|
31521
|
-
import { createHash as
|
|
32019
|
+
import { createHash as createHash11, X509Certificate } from "crypto";
|
|
31522
32020
|
import { chmodSync as chmodSync3, existsSync as existsSync12, mkdtempSync as mkdtempSync3, readFileSync as readFileSync12, rmSync as rmSync8, writeFileSync as writeFileSync14 } from "fs";
|
|
31523
32021
|
import { request as httpsRequest } from "https";
|
|
31524
32022
|
import { createServer } from "net";
|
|
@@ -32188,7 +32686,7 @@ function iapPortTunnelArgv(vm, project, zone, remotePort, localPort) {
|
|
|
32188
32686
|
}
|
|
32189
32687
|
function spkiSha256Hex(certDer) {
|
|
32190
32688
|
const spki = new X509Certificate(certDer).publicKey.export({ type: "spki", format: "der" });
|
|
32191
|
-
return
|
|
32689
|
+
return createHash11("sha256").update(spki).digest("hex");
|
|
32192
32690
|
}
|
|
32193
32691
|
function certMetadataValue(pem) {
|
|
32194
32692
|
return new X509Certificate(pem).raw.toString("base64url");
|
|
@@ -36083,7 +36581,7 @@ allprojects {
|
|
|
36083
36581
|
|
|
36084
36582
|
// ../../packages/envrun/dist/thrun.js
|
|
36085
36583
|
import { execFileSync, spawnSync as spawnSync6 } from "child_process";
|
|
36086
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync4, rmSync as rmSync9 } from "fs";
|
|
36584
|
+
import { closeSync as closeSync5, constants as fsConstants, existsSync as existsSync13, fstatSync as fstatSync2, lstatSync as lstatSync5, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync4, openSync as openSync5, readSync, rmSync as rmSync9 } from "fs";
|
|
36087
36585
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
36088
36586
|
import { tmpdir as tmpdir4 } from "os";
|
|
36089
36587
|
import { join as join29 } from "path";
|
|
@@ -36121,6 +36619,59 @@ function collectWorkingDiff(repoDir, opts = {}) {
|
|
|
36121
36619
|
}
|
|
36122
36620
|
return { patch: parts.join(""), headSha, diffBase, trackedChanged, untracked };
|
|
36123
36621
|
}
|
|
36622
|
+
function readLockfileBytes(file, path5) {
|
|
36623
|
+
const notRegular = `${path5} is a symbolic link or another special file, not a regular file, so its entries could not be checked.`;
|
|
36624
|
+
const tooLarge = `${path5} is larger than 64MB, so its entries were not checked.`;
|
|
36625
|
+
try {
|
|
36626
|
+
const before = lstatSync5(file);
|
|
36627
|
+
if (!before.isFile())
|
|
36628
|
+
return { refusal: notRegular };
|
|
36629
|
+
if (before.size > LOCKFILE_READ_CAP)
|
|
36630
|
+
return { refusal: tooLarge };
|
|
36631
|
+
const fd = openSync5(file, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
|
|
36632
|
+
try {
|
|
36633
|
+
const opened = fstatSync2(fd);
|
|
36634
|
+
if (!opened.isFile())
|
|
36635
|
+
return { refusal: notRegular };
|
|
36636
|
+
if (opened.size > LOCKFILE_READ_CAP)
|
|
36637
|
+
return { refusal: tooLarge };
|
|
36638
|
+
const buf = Buffer.alloc(LOCKFILE_READ_CAP + 1);
|
|
36639
|
+
let length = 0;
|
|
36640
|
+
for (; ; ) {
|
|
36641
|
+
const n = readSync(fd, buf, length, buf.length - length, null);
|
|
36642
|
+
if (n === 0)
|
|
36643
|
+
break;
|
|
36644
|
+
length += n;
|
|
36645
|
+
if (length > LOCKFILE_READ_CAP)
|
|
36646
|
+
return { refusal: tooLarge };
|
|
36647
|
+
}
|
|
36648
|
+
return { text: buf.subarray(0, length).toString("utf8") };
|
|
36649
|
+
} finally {
|
|
36650
|
+
closeSync5(fd);
|
|
36651
|
+
}
|
|
36652
|
+
} catch {
|
|
36653
|
+
return { refusal: `${path5} could not be read, so its entries could not be checked.` };
|
|
36654
|
+
}
|
|
36655
|
+
}
|
|
36656
|
+
function lockfileContentRefusals(cloneDir, before) {
|
|
36657
|
+
const refusals = [];
|
|
36658
|
+
for (const [path5, base] of before) {
|
|
36659
|
+
const post = readLockfileBytes(join29(cloneDir, path5), path5);
|
|
36660
|
+
const problem = "refusal" in base ? base.refusal : "refusal" in post ? post.refusal : null;
|
|
36661
|
+
if (problem !== null) {
|
|
36662
|
+
refusals.push({ code: "lockfile", path: path5, detail: problem });
|
|
36663
|
+
continue;
|
|
36664
|
+
}
|
|
36665
|
+
const result = checkLockfileChange({
|
|
36666
|
+
path: path5,
|
|
36667
|
+
base: base.text,
|
|
36668
|
+
post: post.text
|
|
36669
|
+
});
|
|
36670
|
+
if (!result.ok)
|
|
36671
|
+
refusals.push({ code: "lockfile", path: path5, detail: result.detail });
|
|
36672
|
+
}
|
|
36673
|
+
return refusals;
|
|
36674
|
+
}
|
|
36124
36675
|
function assertDiffBase(repoDir, base) {
|
|
36125
36676
|
if (!FULL_COMMIT_ID.test(base)) {
|
|
36126
36677
|
throw new ThRunError(`the diff base must be a full 40-character commit id, got ${JSON.stringify(base)}.`);
|
|
@@ -36511,6 +37062,18 @@ function applyPatch(repoDir, patch, what) {
|
|
|
36511
37062
|
throw new ThRunError(`the ${what} did not apply cleanly to the target checkout: ${(res.stderr ?? "").trim()}. The diff was built against a different commit than the one being verified.`);
|
|
36512
37063
|
}
|
|
36513
37064
|
}
|
|
37065
|
+
function imageInputsAt(repoDir) {
|
|
37066
|
+
try {
|
|
37067
|
+
const pinned = deriveEnvironmentSpec(repoDir);
|
|
37068
|
+
return {
|
|
37069
|
+
runtime: pinned.runtime,
|
|
37070
|
+
runtimeVersion: pinned.runtimeVersion,
|
|
37071
|
+
variant: imageVariantFor(pinned)
|
|
37072
|
+
};
|
|
37073
|
+
} catch {
|
|
37074
|
+
return null;
|
|
37075
|
+
}
|
|
37076
|
+
}
|
|
36514
37077
|
function tail(text, bytes = OUTPUT_TAIL_BYTES) {
|
|
36515
37078
|
return text.length <= bytes ? text : text.slice(-bytes);
|
|
36516
37079
|
}
|
|
@@ -36566,9 +37129,17 @@ function refusedRun(fields) {
|
|
|
36566
37129
|
// A refused run never held a lease, so there is no venue to describe. Same
|
|
36567
37130
|
// reasoning as `leaksClean` above: null because nothing happened, and it must
|
|
36568
37131
|
// not read as a venue we looked at and could not name.
|
|
36569
|
-
venue: null
|
|
37132
|
+
venue: null,
|
|
37133
|
+
refusalOrigin: fields.origin
|
|
36570
37134
|
};
|
|
36571
37135
|
}
|
|
37136
|
+
function refusalOriginOf(refusal2) {
|
|
37137
|
+
try {
|
|
37138
|
+
return refusal2.origin === "repository" ? "repository" : "ours";
|
|
37139
|
+
} catch {
|
|
37140
|
+
return "ours";
|
|
37141
|
+
}
|
|
37142
|
+
}
|
|
36572
37143
|
function unacceptableTarget(req) {
|
|
36573
37144
|
try {
|
|
36574
37145
|
assertSafeTargetUrl(req.targetRepo);
|
|
@@ -36649,7 +37220,8 @@ async function verifyWorkingDiff(req) {
|
|
|
36649
37220
|
const ctx = {
|
|
36650
37221
|
startedAt: Date.now(),
|
|
36651
37222
|
runId: req.runId ?? `run-${randomUUID3().slice(0, 8)}`,
|
|
36652
|
-
touchedPaths: []
|
|
37223
|
+
touchedPaths: [],
|
|
37224
|
+
imageChosenByRepository: false
|
|
36653
37225
|
};
|
|
36654
37226
|
const target = {
|
|
36655
37227
|
claimId: req.claimId,
|
|
@@ -36664,6 +37236,10 @@ async function verifyWorkingDiff(req) {
|
|
|
36664
37236
|
throw err;
|
|
36665
37237
|
return {
|
|
36666
37238
|
result: refusedRun({
|
|
37239
|
+
// `repository` survives only when the repository's files alone chose the image.
|
|
37240
|
+
// `resolvePublishedImage` knows the version came from the repo, but not whether an
|
|
37241
|
+
// override picked the image variant the missing tag belongs to (Codex, TERM-1313).
|
|
37242
|
+
origin: refusalOriginOf(refusal2) === "repository" && ctx.imageChosenByRepository ? "repository" : "ours",
|
|
36667
37243
|
runId: ctx.runId,
|
|
36668
37244
|
claimId: target.claimId,
|
|
36669
37245
|
// `describeThrown`, not `refusal.message`. Classifying a value does not make
|
|
@@ -36731,6 +37307,7 @@ async function runVerification(req, ctx) {
|
|
|
36731
37307
|
if (badTarget !== null) {
|
|
36732
37308
|
return {
|
|
36733
37309
|
result: refusedRun({
|
|
37310
|
+
origin: "ours",
|
|
36734
37311
|
runId,
|
|
36735
37312
|
claimId: req.claimId,
|
|
36736
37313
|
reason: badTarget,
|
|
@@ -36754,6 +37331,7 @@ async function runVerification(req, ctx) {
|
|
|
36754
37331
|
if (placement.refusal !== null) {
|
|
36755
37332
|
return {
|
|
36756
37333
|
result: refusedRun({
|
|
37334
|
+
origin: "ours",
|
|
36757
37335
|
runId,
|
|
36758
37336
|
claimId: req.claimId,
|
|
36759
37337
|
reason: placement.refusal,
|
|
@@ -36787,15 +37365,17 @@ async function runVerification(req, ctx) {
|
|
|
36787
37365
|
progress("collect", "dispatched run: the tree is the stored commit itself");
|
|
36788
37366
|
}
|
|
36789
37367
|
const boundary = source.kind !== "working-diff" || diff === null || diff.patch.trim() === "" ? { refused: false, refusals: [], touchedPaths: [] } : preflightBoundary({ patch: diff.patch, sliceFiles: source.sliceFiles });
|
|
36790
|
-
const
|
|
37368
|
+
const pathPre = source.kind === "working-diff" && source.sliceFiles.length > 0 ? boundary : (() => {
|
|
36791
37369
|
const refusals = boundary.refusals.filter((r) => r.code !== "out-of-slice");
|
|
36792
37370
|
return { refused: refusals.length > 0, refusals, touchedPaths: boundary.touchedPaths };
|
|
36793
37371
|
})();
|
|
37372
|
+
const pre = pathPre;
|
|
36794
37373
|
ctx.touchedPaths = pre.touchedPaths;
|
|
36795
37374
|
if (pre.refused) {
|
|
36796
37375
|
const first = pre.refusals[0];
|
|
36797
37376
|
return {
|
|
36798
37377
|
result: refusedRun({
|
|
37378
|
+
origin: "ours",
|
|
36799
37379
|
runId,
|
|
36800
37380
|
claimId: req.claimId,
|
|
36801
37381
|
reason: first?.detail ?? "the diff was refused by the local slice pre-flight, but no reason was recorded",
|
|
@@ -36830,19 +37410,52 @@ async function runVerification(req, ctx) {
|
|
|
36830
37410
|
// there and passing it unconditionally keeps one code path instead of two.
|
|
36831
37411
|
...req.targetAuth ? { auth: req.targetAuth } : {}
|
|
36832
37412
|
});
|
|
37413
|
+
const pinnedImageInputs = imageInputsAt(cloneDir);
|
|
36833
37414
|
const baselinePatch = source.kind === "working-diff" ? source.baselinePatch : void 0;
|
|
36834
37415
|
const hasBaselinePatch = baselinePatch !== void 0 && baselinePatch.trim() !== "";
|
|
36835
37416
|
if (hasBaselinePatch) {
|
|
36836
37417
|
applyPatch(cloneDir, baselinePatch ?? "", "baseline patch");
|
|
36837
37418
|
}
|
|
37419
|
+
const lockfileBase = /* @__PURE__ */ new Map();
|
|
37420
|
+
if (diff !== null) {
|
|
37421
|
+
for (const path5 of pre.touchedPaths) {
|
|
37422
|
+
if (supportedLockfile(path5.split("/").pop() ?? "") === null)
|
|
37423
|
+
continue;
|
|
37424
|
+
lockfileBase.set(path5, readLockfileBytes(join29(cloneDir, path5), path5));
|
|
37425
|
+
}
|
|
37426
|
+
}
|
|
36838
37427
|
if (diff !== null) {
|
|
36839
37428
|
applyPatch(cloneDir, diff.patch, "developer's working diff");
|
|
36840
37429
|
}
|
|
37430
|
+
const lockfileRefusals = lockfileContentRefusals(cloneDir, lockfileBase);
|
|
37431
|
+
if (lockfileRefusals.length > 0) {
|
|
37432
|
+
return {
|
|
37433
|
+
result: refusedRun({
|
|
37434
|
+
// The developer's diff, not the repository's environment, so never `repository`
|
|
37435
|
+
// (TERM-1313). Only a working diff is judged here; a dispatched baseline has none.
|
|
37436
|
+
origin: "ours",
|
|
37437
|
+
runId,
|
|
37438
|
+
claimId: req.claimId,
|
|
37439
|
+
reason: lockfileRefusals[0]?.detail ?? "a lockfile in the diff was refused",
|
|
37440
|
+
wallMs: Date.now() - startedAt,
|
|
37441
|
+
targetRepo: publishableTarget(req.targetRepo),
|
|
37442
|
+
targetSha: req.targetSha,
|
|
37443
|
+
boundaryRefusals: lockfileRefusals,
|
|
37444
|
+
touchedPaths: pre.touchedPaths
|
|
37445
|
+
}),
|
|
37446
|
+
preview: null,
|
|
37447
|
+
spec: null,
|
|
37448
|
+
verdict: null,
|
|
37449
|
+
diagnostic: null,
|
|
37450
|
+
venueIdentity: null
|
|
37451
|
+
};
|
|
37452
|
+
}
|
|
36841
37453
|
const patchSha256 = diff === null ? null : sha256Hex(diff.patch);
|
|
36842
37454
|
const treeDigest = patchedTreeDigest(cloneDir);
|
|
36843
37455
|
const baselinePatchSha256 = hasBaselinePatch ? sha256Hex(baselinePatch ?? "") : null;
|
|
36844
37456
|
const derived = deriveEnvironmentSpec(cloneDir);
|
|
36845
37457
|
const { spec, imageSource } = resolveRunEnvironment(derived, req);
|
|
37458
|
+
ctx.imageChosenByRepository = imageSource === "detected" && imageVariantFor(spec) === imageVariantFor(derived) && pinnedImageInputs !== null && pinnedImageInputs.runtime === spec.runtime && pinnedImageInputs.runtimeVersion === spec.runtimeVersion && pinnedImageInputs.variant === imageVariantFor(spec);
|
|
36846
37459
|
progress("derive", `runtime=${spec.runtime} install=${String(spec.installCommand)} test=${String(spec.testCommand)}`);
|
|
36847
37460
|
const unfrozenNote = installUnfrozenNote(spec);
|
|
36848
37461
|
if (unfrozenNote !== null)
|
|
@@ -36853,6 +37466,7 @@ async function runVerification(req, ctx) {
|
|
|
36853
37466
|
if (!resolved.ok) {
|
|
36854
37467
|
return {
|
|
36855
37468
|
result: refusedRun({
|
|
37469
|
+
origin: "ours",
|
|
36856
37470
|
runId,
|
|
36857
37471
|
claimId: req.claimId,
|
|
36858
37472
|
reason: resolved.refusal,
|
|
@@ -36964,7 +37578,9 @@ async function runVerification(req, ctx) {
|
|
|
36964
37578
|
// Built from the LEASE, over the client that ran the steps — never from
|
|
36965
37579
|
// `req.placement`, which is a request. `venueDescriptor.ts` carries the
|
|
36966
37580
|
// reasoning and the #735 failure that makes the distinction load-bearing.
|
|
36967
|
-
venue: describeVenue(lease)
|
|
37581
|
+
venue: describeVenue(lease),
|
|
37582
|
+
// A run that reached a verdict was not refused, so neither side refused it.
|
|
37583
|
+
refusalOrigin: null
|
|
36968
37584
|
};
|
|
36969
37585
|
let later = null;
|
|
36970
37586
|
if (req.screenshots !== void 0) {
|
|
@@ -37163,7 +37779,7 @@ async function prepareBaseTree(o) {
|
|
|
37163
37779
|
throw err;
|
|
37164
37780
|
}
|
|
37165
37781
|
}
|
|
37166
|
-
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, DEFERRED_GATE_TIMEOUT_MS, FAILURE_LINE, REDACTED_TARGET_REPO, REDACTED_TARGET_SHA;
|
|
37782
|
+
var ThRunError, OUTPUT_TAIL_BYTES, LOCKFILE_READ_CAP, 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, DEFERRED_GATE_TIMEOUT_MS, FAILURE_LINE, REDACTED_TARGET_REPO, REDACTED_TARGET_SHA;
|
|
37167
37783
|
var init_thrun = __esm({
|
|
37168
37784
|
"../../packages/envrun/dist/thrun.js"() {
|
|
37169
37785
|
"use strict";
|
|
@@ -37172,6 +37788,7 @@ var init_thrun = __esm({
|
|
|
37172
37788
|
init_dist3();
|
|
37173
37789
|
init_attestation2();
|
|
37174
37790
|
init_boundary();
|
|
37791
|
+
init_lockfileCheck();
|
|
37175
37792
|
init_emptyGitConfig();
|
|
37176
37793
|
init_labels();
|
|
37177
37794
|
init_execute();
|
|
@@ -37183,6 +37800,7 @@ var init_thrun = __esm({
|
|
|
37183
37800
|
ThRunError = class extends Error {
|
|
37184
37801
|
};
|
|
37185
37802
|
OUTPUT_TAIL_BYTES = 4e3;
|
|
37803
|
+
LOCKFILE_READ_CAP = 64 * 1024 * 1024;
|
|
37186
37804
|
FULL_COMMIT_ID = /^[0-9a-f]{40}$/;
|
|
37187
37805
|
ALLOWED_URL_SCHEMES = /* @__PURE__ */ new Set(["https", "http", "ssh", "git"]);
|
|
37188
37806
|
SCP_STYLE = /^[A-Za-z0-9._~+-]+@[A-Za-z0-9._-]+:[^:]/;
|
|
@@ -38114,6 +38732,7 @@ __export(dist_exports, {
|
|
|
38114
38732
|
census: () => census,
|
|
38115
38733
|
censusReport: () => censusReport,
|
|
38116
38734
|
censusTotal: () => censusTotal,
|
|
38735
|
+
checkLockfileChange: () => checkLockfileChange,
|
|
38117
38736
|
classifyBootFailure: () => classifyBootFailure,
|
|
38118
38737
|
classifyProbeFailure: () => classifyProbeFailure,
|
|
38119
38738
|
classifySingleRun: () => classifySingleRun,
|
|
@@ -38196,6 +38815,7 @@ __export(dist_exports, {
|
|
|
38196
38815
|
startDatabase: () => startDatabase,
|
|
38197
38816
|
startLocalPreview: () => startLocalPreview,
|
|
38198
38817
|
startPreview: () => startPreview,
|
|
38818
|
+
supportedLockfile: () => supportedLockfile,
|
|
38199
38819
|
targetCarriesCredential: () => targetCarriesCredential,
|
|
38200
38820
|
toAcceptancePredicate: () => toAcceptancePredicate,
|
|
38201
38821
|
toolingImageFor: () => toolingImageFor,
|
|
@@ -38214,6 +38834,7 @@ var init_dist4 = __esm({
|
|
|
38214
38834
|
init_result();
|
|
38215
38835
|
init_attestation2();
|
|
38216
38836
|
init_boundary();
|
|
38837
|
+
init_lockfileCheck();
|
|
38217
38838
|
init_placement();
|
|
38218
38839
|
init_gcpPlacement();
|
|
38219
38840
|
init_venue();
|
|
@@ -38508,17 +39129,17 @@ import {
|
|
|
38508
39129
|
mkdtempSync as mkdtempSync5,
|
|
38509
39130
|
renameSync as renameSync6,
|
|
38510
39131
|
existsSync as existsSync14,
|
|
38511
|
-
lstatSync as
|
|
39132
|
+
lstatSync as lstatSync6,
|
|
38512
39133
|
realpathSync as realpathSync3,
|
|
38513
39134
|
rmSync as rmSync10,
|
|
38514
39135
|
readdirSync as readdirSync5,
|
|
38515
39136
|
statSync as statSync6,
|
|
38516
|
-
openSync as
|
|
38517
|
-
readSync,
|
|
38518
|
-
closeSync as
|
|
39137
|
+
openSync as openSync6,
|
|
39138
|
+
readSync as readSync2,
|
|
39139
|
+
closeSync as closeSync6
|
|
38519
39140
|
} from "fs";
|
|
38520
39141
|
import { join as join31, dirname as dirname9, isAbsolute as isAbsolute5, resolve as pathResolve } from "path";
|
|
38521
|
-
import { createHash as
|
|
39142
|
+
import { createHash as createHash12 } from "crypto";
|
|
38522
39143
|
import { homedir as homedir14, hostname as osHostname } from "os";
|
|
38523
39144
|
import { execFile as execFile3, execFileSync as execFileSync2, spawnSync as spawnSync8 } from "child_process";
|
|
38524
39145
|
import { promisify as promisify3 } from "util";
|
|
@@ -39169,13 +39790,9 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
39169
39790
|
"open",
|
|
39170
39791
|
"reason",
|
|
39171
39792
|
"note",
|
|
39172
|
-
// TERM-1044. `--body` carries a
|
|
39173
|
-
//
|
|
39174
|
-
//
|
|
39175
|
-
//
|
|
39176
|
-
// `--sha` was here for `claim note approve` and went with it: the send moved to the
|
|
39177
|
-
// dashboard, so no CLI verb names a digest any more. Left in place it would parse a
|
|
39178
|
-
// flag nothing reads, which is how a removed feature looks half-removed.
|
|
39793
|
+
// TERM-1044. `--body` carries a note's text and takes the next token. `--note` above
|
|
39794
|
+
// is already taken by `claim resolve`, where it means something different, so this
|
|
39795
|
+
// does NOT reuse it. (`--sha` went with `claim note approve`; no verb names a digest.)
|
|
39179
39796
|
"body",
|
|
39180
39797
|
// TERM-1259. `claim resolve --reason setup-failed --requirement <field>`.
|
|
39181
39798
|
"requirement",
|
|
@@ -41986,7 +42603,7 @@ function writeDeliveredBrief(destDir, spec) {
|
|
|
41986
42603
|
function ensureExcludedPackDir(destDir) {
|
|
41987
42604
|
let occupant = null;
|
|
41988
42605
|
try {
|
|
41989
|
-
occupant =
|
|
42606
|
+
occupant = lstatSync6(join31(destDir, BRIEF_DIR));
|
|
41990
42607
|
} catch (err) {
|
|
41991
42608
|
if (err?.code !== "ENOENT") {
|
|
41992
42609
|
return {
|
|
@@ -42029,7 +42646,7 @@ function writePackFile(destDir, relPath, content, what) {
|
|
|
42029
42646
|
return { written: true, reason: null, sha256: sha256OfUtf8(content) };
|
|
42030
42647
|
}
|
|
42031
42648
|
function sha256OfUtf8(content) {
|
|
42032
|
-
return
|
|
42649
|
+
return createHash12("sha256").update(content, "utf8").digest("hex");
|
|
42033
42650
|
}
|
|
42034
42651
|
function writeWorkspacePack(destDir, spec, claim, delivery) {
|
|
42035
42652
|
if (delivery !== "full" && delivery !== "sparse") {
|
|
@@ -42332,7 +42949,7 @@ terminalhire claim: ${what} needs the stored credential, and the server says it
|
|
|
42332
42949
|
return true;
|
|
42333
42950
|
}
|
|
42334
42951
|
async function enrolHeldClaimReadToken(postingId) {
|
|
42335
|
-
const
|
|
42952
|
+
const fail3 = (lines) => {
|
|
42336
42953
|
for (const line of lines) console.error(line);
|
|
42337
42954
|
process.exit(1);
|
|
42338
42955
|
};
|
|
@@ -42345,7 +42962,7 @@ async function enrolHeldClaimReadToken(postingId) {
|
|
|
42345
42962
|
]
|
|
42346
42963
|
});
|
|
42347
42964
|
if (!proofToken) {
|
|
42348
|
-
|
|
42965
|
+
fail3(["terminalhire claim: could not link this machine to your claim (see above)."]);
|
|
42349
42966
|
}
|
|
42350
42967
|
let res;
|
|
42351
42968
|
try {
|
|
@@ -42356,7 +42973,7 @@ async function enrolHeldClaimReadToken(postingId) {
|
|
|
42356
42973
|
signal: AbortSignal.timeout(CLAIM_SYNC_WRITE_TIMEOUT_MS)
|
|
42357
42974
|
});
|
|
42358
42975
|
} catch (err) {
|
|
42359
|
-
|
|
42976
|
+
fail3([
|
|
42360
42977
|
`terminalhire claim: terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}).`,
|
|
42361
42978
|
" Nothing was stored. Run the same command again."
|
|
42362
42979
|
]);
|
|
@@ -42368,21 +42985,21 @@ async function enrolHeldClaimReadToken(postingId) {
|
|
|
42368
42985
|
}
|
|
42369
42986
|
if (res.status === 404 && body?.error === "no-live-claim") {
|
|
42370
42987
|
const who = typeof body.claimantLogin === "string" ? `@${body.claimantLogin}` : "that account";
|
|
42371
|
-
|
|
42988
|
+
fail3([
|
|
42372
42989
|
`terminalhire claim: you confirmed in the browser as ${who}, and ${who} does not`,
|
|
42373
42990
|
" hold a live claim on this posting. Nothing was stored.",
|
|
42374
42991
|
" Sign the browser into the GitHub account that claimed it, then run this again."
|
|
42375
42992
|
]);
|
|
42376
42993
|
}
|
|
42377
42994
|
if (!res.ok || typeof body?.pushToken !== "string" || body.pushToken.length === 0) {
|
|
42378
|
-
|
|
42995
|
+
fail3([
|
|
42379
42996
|
`terminalhire claim: linking this machine failed (${res.status}${typeof body?.message === "string" ? `: ${body.message}` : ""}). Nothing was stored.`
|
|
42380
42997
|
]);
|
|
42381
42998
|
}
|
|
42382
42999
|
try {
|
|
42383
43000
|
await writePushTokenEnc(body.pushToken);
|
|
42384
43001
|
} catch (err) {
|
|
42385
|
-
|
|
43002
|
+
fail3([
|
|
42386
43003
|
`terminalhire claim: the credential could not be stored on this machine: ${err instanceof Error ? err.message : String(err)}`
|
|
42387
43004
|
]);
|
|
42388
43005
|
}
|
|
@@ -42877,18 +43494,18 @@ function refuseScreenshots(detail) {
|
|
|
42877
43494
|
}
|
|
42878
43495
|
function readFileCapped(path5, limit2) {
|
|
42879
43496
|
if (statSync6(path5).size > limit2) return null;
|
|
42880
|
-
const fd =
|
|
43497
|
+
const fd = openSync6(path5, "r");
|
|
42881
43498
|
try {
|
|
42882
43499
|
const buf = Buffer.allocUnsafe(limit2 + 1);
|
|
42883
43500
|
let n = 0;
|
|
42884
43501
|
while (n < buf.length) {
|
|
42885
|
-
const got =
|
|
43502
|
+
const got = readSync2(fd, buf, n, buf.length - n, n);
|
|
42886
43503
|
if (got === 0) break;
|
|
42887
43504
|
n += got;
|
|
42888
43505
|
}
|
|
42889
43506
|
return n > limit2 ? null : buf.subarray(0, n);
|
|
42890
43507
|
} finally {
|
|
42891
|
-
|
|
43508
|
+
closeSync6(fd);
|
|
42892
43509
|
}
|
|
42893
43510
|
}
|
|
42894
43511
|
function loadClaimScreenshots(specArg) {
|
|
@@ -42932,7 +43549,7 @@ function loadClaimScreenshots(specArg) {
|
|
|
42932
43549
|
items.push({
|
|
42933
43550
|
file,
|
|
42934
43551
|
caption: shot.caption,
|
|
42935
|
-
sha256:
|
|
43552
|
+
sha256: createHash12("sha256").update(bytes).digest("hex"),
|
|
42936
43553
|
bytes: bytes.byteLength,
|
|
42937
43554
|
width,
|
|
42938
43555
|
height
|
|
@@ -44201,26 +44818,27 @@ function resolutionReasonHelpLines() {
|
|
|
44201
44818
|
}
|
|
44202
44819
|
function noteUsageLines() {
|
|
44203
44820
|
return [
|
|
44204
|
-
'Usage: terminalhire claim note <id> --body "..."
|
|
44821
|
+
'Usage: terminalhire claim note <id> --body "..." send a note to the poster',
|
|
44205
44822
|
"",
|
|
44206
44823
|
" Tell the poster something about a claim you hold. Needs `terminalhire link`.",
|
|
44207
44824
|
"",
|
|
44208
|
-
" This
|
|
44209
|
-
"
|
|
44210
|
-
"
|
|
44825
|
+
" This SENDS. The server checks the text for credentials and pasted",
|
|
44826
|
+
" instructions, sends it to the poster, and this prints back the exact text",
|
|
44827
|
+
" that went out.",
|
|
44211
44828
|
"",
|
|
44212
|
-
"
|
|
44213
|
-
|
|
44214
|
-
"
|
|
44215
|
-
" one script could draft and send with nobody reading anything.",
|
|
44829
|
+
" Sending from the CLI is off until you turn it on once, in your browser:",
|
|
44830
|
+
' on the dashboard, Settings > "Let my CLI send notes to posters". That switch',
|
|
44831
|
+
" cannot be changed from this terminal, and you can turn it off there any time.",
|
|
44216
44832
|
"",
|
|
44217
|
-
"
|
|
44218
|
-
"
|
|
44219
|
-
|
|
44833
|
+
" Why a switch: a note is often written by an agent that has just read a",
|
|
44834
|
+
" repository nobody has checked, and that repository can carry text aimed at",
|
|
44835
|
+
' the agent. The check catches known shapes (keys, tokens, "paste your .env"),',
|
|
44836
|
+
" not everything. Turning the switch on says your CLI may send under your name.",
|
|
44837
|
+
" It does not prove a person read each note.",
|
|
44220
44838
|
"",
|
|
44221
|
-
"
|
|
44222
|
-
"
|
|
44223
|
-
"
|
|
44839
|
+
" Limits: 3 notes per claim and 10 per poster in any 24 hours. The same text",
|
|
44840
|
+
" sent twice on one claim within 24 hours goes once. The poster can report a",
|
|
44841
|
+
" note to us."
|
|
44224
44842
|
];
|
|
44225
44843
|
}
|
|
44226
44844
|
async function cmdNote(id, flags = {}, deps = {}) {
|
|
@@ -44256,11 +44874,33 @@ async function cmdNote(id, flags = {}, deps = {}) {
|
|
|
44256
44874
|
if (!linked) return 1;
|
|
44257
44875
|
const res = await postJson(
|
|
44258
44876
|
fetchImpl,
|
|
44259
|
-
`${CLAIM_SYNC_BASE4}/api/claim/note
|
|
44877
|
+
`${CLAIM_SYNC_BASE4}/api/claim/note`,
|
|
44260
44878
|
{ bountyId: founderPostingIdOf(claim), claimId, body },
|
|
44261
44879
|
linked
|
|
44262
44880
|
);
|
|
44263
44881
|
if (!res.ok) {
|
|
44882
|
+
const answer = res.answer ?? {};
|
|
44883
|
+
if (answer.reason === "no-grant") {
|
|
44884
|
+
err("terminalhire claim note: sending notes from the CLI is not turned on for your account.");
|
|
44885
|
+
err(" Turn it on once, in your browser, then run this again:");
|
|
44886
|
+
err(
|
|
44887
|
+
` ${CLAIM_SYNC_BASE4}${typeof answer.grantPath === "string" ? answer.grantPath : "/dashboard?tab=settings"}`
|
|
44888
|
+
);
|
|
44889
|
+
err(' Settings > "Let my CLI send notes to posters". Nothing was sent.');
|
|
44890
|
+
return 1;
|
|
44891
|
+
}
|
|
44892
|
+
if (answer.reason === "capped") {
|
|
44893
|
+
const where = answer.scope === "poster" ? "to this poster" : "on this claim";
|
|
44894
|
+
err(
|
|
44895
|
+
`terminalhire claim note: you have sent ${answer.limit ?? "the most"} notes ${where} in the last 24 hours, the most allowed.`
|
|
44896
|
+
);
|
|
44897
|
+
if (typeof answer.nextAllowedAt === "string") {
|
|
44898
|
+
err(` The next one can go at ${answer.nextAllowedAt}. Nothing was sent.`);
|
|
44899
|
+
} else {
|
|
44900
|
+
err(" Nothing was sent.");
|
|
44901
|
+
}
|
|
44902
|
+
return 1;
|
|
44903
|
+
}
|
|
44264
44904
|
err(`terminalhire claim note: ${res.message}`);
|
|
44265
44905
|
for (const f of res.findings ?? []) {
|
|
44266
44906
|
err(` line ${f.line}: ${f.label}`);
|
|
@@ -44268,18 +44908,16 @@ async function cmdNote(id, flags = {}, deps = {}) {
|
|
|
44268
44908
|
return 1;
|
|
44269
44909
|
}
|
|
44270
44910
|
log("");
|
|
44271
|
-
|
|
44911
|
+
if (res.body.alreadySent) {
|
|
44912
|
+
log(` Already sent. This exact text went to the poster at ${res.body.sentAt};`);
|
|
44913
|
+
log(" nothing new was sent.");
|
|
44914
|
+
} else {
|
|
44915
|
+
log(" Sent to the poster:");
|
|
44916
|
+
}
|
|
44272
44917
|
log("");
|
|
44273
44918
|
for (const line of String(res.body.body).split("\n")) log(` \u2502 ${line}`);
|
|
44274
44919
|
log("");
|
|
44275
|
-
log(`
|
|
44276
|
-
log(` sha: ${res.body.bodySha256}`);
|
|
44277
|
-
log("");
|
|
44278
|
-
log(" Read it. If it says what you mean and carries nothing private, send it here:");
|
|
44279
|
-
log(` ${CLAIM_SYNC_BASE4}/dashboard/notes/${res.body.draftId}`);
|
|
44280
|
-
log("");
|
|
44281
|
-
log(" There is no approve command. The page needs a value only it can issue, which");
|
|
44282
|
-
log(" is what stops this tool from sending a note it just wrote.");
|
|
44920
|
+
log(` note: ${res.body.eventId} sent: ${res.body.sentAt}`);
|
|
44283
44921
|
log("");
|
|
44284
44922
|
return 0;
|
|
44285
44923
|
}
|
|
@@ -44321,7 +44959,9 @@ async function postJson(fetchImpl, url, body, linked) {
|
|
|
44321
44959
|
return {
|
|
44322
44960
|
ok: false,
|
|
44323
44961
|
message: typeof answer?.error === "string" && answer.error ? answer.error : `terminalhire answered ${res.status}`,
|
|
44324
|
-
findings: Array.isArray(answer?.findings) ? answer.findings : []
|
|
44962
|
+
findings: Array.isArray(answer?.findings) ? answer.findings : [],
|
|
44963
|
+
// The whole refusal, for a caller that branches on its `reason` (TERM-1322).
|
|
44964
|
+
answer: answer && typeof answer === "object" ? answer : null
|
|
44325
44965
|
};
|
|
44326
44966
|
}
|
|
44327
44967
|
return { ok: true, body: answer ?? {} };
|
|
@@ -44540,12 +45180,8 @@ async function run() {
|
|
|
44540
45180
|
// is three years of muscle memory and `note` is what you leave.
|
|
44541
45181
|
case "note": {
|
|
44542
45182
|
if (positional[0] === "approve") {
|
|
44543
|
-
console.error(
|
|
44544
|
-
|
|
44545
|
-
);
|
|
44546
|
-
console.error(
|
|
44547
|
-
' Draft with `terminalhire claim note <id> --body "..."` \u2014 it prints the link.'
|
|
44548
|
-
);
|
|
45183
|
+
console.error("terminalhire claim note approve: removed. `claim note` sends directly.");
|
|
45184
|
+
console.error(' Run `terminalhire claim note <id> --body "..."`.');
|
|
44549
45185
|
process.exit(1);
|
|
44550
45186
|
}
|
|
44551
45187
|
const code = await cmdNote(positional[0], flags);
|