border-customs 0.4.0 → 0.4.2
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/README.md +126 -12
- package/dist/assets/release-src/releaseCoherence.ts +423 -0
- package/dist/assets/residue-src/crates.ts +13 -0
- package/dist/assets/residue-src/npm.ts +14 -0
- package/dist/assets/residue-src/pypi.ts +20 -0
- package/dist/assets/residue-src/rubygems.ts +12 -0
- package/dist/index.js +1375 -587
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -13036,9 +13036,9 @@ import { parseArgs } from "node:util";
|
|
|
13036
13036
|
|
|
13037
13037
|
// src/config.ts
|
|
13038
13038
|
var import_yaml = __toESM(require_dist(), 1);
|
|
13039
|
-
import { spawnSync as
|
|
13039
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
13040
13040
|
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
|
|
13041
|
-
import { dirname as dirname8, join as
|
|
13041
|
+
import { dirname as dirname8, join as join20, resolve as resolve9 } from "node:path";
|
|
13042
13042
|
|
|
13043
13043
|
// node_modules/zod/v4/classic/external.js
|
|
13044
13044
|
var external_exports = {};
|
|
@@ -32047,8 +32047,8 @@ var gitChannel = {
|
|
|
32047
32047
|
import { spawn as spawn2 } from "node:child_process";
|
|
32048
32048
|
|
|
32049
32049
|
// src/artifacts/npm.ts
|
|
32050
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as
|
|
32051
|
-
import { join as
|
|
32050
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
|
|
32051
|
+
import { join as join10, relative as relative2 } from "node:path";
|
|
32052
32052
|
|
|
32053
32053
|
// src/check/lock.ts
|
|
32054
32054
|
import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -38386,126 +38386,624 @@ function sweepResidueNpmArtifact(o) {
|
|
|
38386
38386
|
return hits;
|
|
38387
38387
|
}
|
|
38388
38388
|
|
|
38389
|
-
// src/
|
|
38390
|
-
|
|
38391
|
-
|
|
38389
|
+
// src/rules/releaseCoherence.ts
|
|
38390
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
38391
|
+
import { basename, join as join9 } from "node:path";
|
|
38392
|
+
|
|
38393
|
+
// src/ledger/records.ts
|
|
38394
|
+
import { appendFileSync, readFileSync as readFileSync5 } from "node:fs";
|
|
38395
|
+
import { join as join8 } from "node:path";
|
|
38396
|
+
var LEDGER_FILE = "ledger.jsonl";
|
|
38397
|
+
var RUNS_SUBDIR = "runs";
|
|
38398
|
+
var HEX64 = /^[0-9a-f]{64}$/;
|
|
38399
|
+
var HEX40 = /^[0-9a-f]{40}$/;
|
|
38400
|
+
var HEX8 = /^[0-9a-f]{8}$/;
|
|
38401
|
+
var PUSH_CONFIRMED_VIA = ["ls-remote", "npm-view", "pypi-json", "crates-json", "rubygems-json"];
|
|
38402
|
+
function ledgerPath(repoDir) {
|
|
38403
|
+
return join8(repoDir, BORDER_STATE_DIR, LEDGER_FILE);
|
|
38404
|
+
}
|
|
38405
|
+
function runsDir(repoDir) {
|
|
38406
|
+
return join8(repoDir, BORDER_STATE_DIR, RUNS_SUBDIR);
|
|
38407
|
+
}
|
|
38408
|
+
function formatSkipLine(record2) {
|
|
38409
|
+
return `SKIP ${record2.key8} \u2014 PASS ${record2.ts} report ${record2.reportPath}`;
|
|
38410
|
+
}
|
|
38411
|
+
function buildPushRecord(i) {
|
|
38392
38412
|
return {
|
|
38393
|
-
|
|
38394
|
-
|
|
38395
|
-
target:
|
|
38396
|
-
|
|
38397
|
-
|
|
38398
|
-
|
|
38399
|
-
|
|
38400
|
-
|
|
38413
|
+
t: "push",
|
|
38414
|
+
key: i.key,
|
|
38415
|
+
target: i.target,
|
|
38416
|
+
remoteName: i.remoteName,
|
|
38417
|
+
url: sanitizeUrl(i.url),
|
|
38418
|
+
localSha: i.localSha,
|
|
38419
|
+
...i.remoteSha !== void 0 ? { remoteSha: i.remoteSha } : {},
|
|
38420
|
+
...i.version !== void 0 ? { version: i.version } : {},
|
|
38421
|
+
confirmedVia: i.confirmedVia,
|
|
38422
|
+
ts: i.ts ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
38401
38423
|
};
|
|
38402
38424
|
}
|
|
38403
|
-
function
|
|
38404
|
-
if (
|
|
38405
|
-
|
|
38406
|
-
|
|
38407
|
-
|
|
38408
|
-
|
|
38409
|
-
|
|
38410
|
-
|
|
38425
|
+
function buildRoundtripRecord(i) {
|
|
38426
|
+
if (!HEX64.test(i.artifactSha256)) bad("artifactSha256 is not hex-shaped");
|
|
38427
|
+
if (!HEX64.test(i.rulesHash)) bad("rulesHash is not hex-shaped");
|
|
38428
|
+
if (i.verdict !== "clean" && i.verdict !== "residue") bad(`verdict must be clean or residue, got '${i.verdict}'`);
|
|
38429
|
+
if (!Number.isInteger(i.rows) || i.rows < 0) bad("rows must be a non-negative integer");
|
|
38430
|
+
return {
|
|
38431
|
+
t: "roundtrip",
|
|
38432
|
+
artifactSha256: i.artifactSha256,
|
|
38433
|
+
verdict: i.verdict,
|
|
38434
|
+
rulesHash: i.rulesHash,
|
|
38435
|
+
rows: i.rows,
|
|
38436
|
+
ts: i.ts ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
38437
|
+
};
|
|
38411
38438
|
}
|
|
38412
|
-
function
|
|
38413
|
-
|
|
38414
|
-
const bang = f.path.indexOf("!");
|
|
38415
|
-
if (bang > 0) {
|
|
38416
|
-
const rel2 = relative2(extractDir, f.path.slice(0, bang));
|
|
38417
|
-
if (!rel2.startsWith("..")) return { ...f, path: rel2 === "" ? f.path.slice(bang + 1) : `${rel2}${f.path.slice(bang)}` };
|
|
38418
|
-
}
|
|
38419
|
-
const rel = relative2(extractDir, f.path);
|
|
38420
|
-
return rel === "" || rel.startsWith("..") ? f : { ...f, path: rel };
|
|
38439
|
+
function bad(msg) {
|
|
38440
|
+
throw new Error(msg);
|
|
38421
38441
|
}
|
|
38422
|
-
function
|
|
38423
|
-
const
|
|
38424
|
-
|
|
38425
|
-
|
|
38426
|
-
|
|
38427
|
-
|
|
38428
|
-
|
|
38429
|
-
|
|
38430
|
-
|
|
38431
|
-
|
|
38442
|
+
function needString(o, field) {
|
|
38443
|
+
const v = o[field];
|
|
38444
|
+
if (typeof v !== "string" || v === "") bad(`${field} must be a non-empty string`);
|
|
38445
|
+
return v;
|
|
38446
|
+
}
|
|
38447
|
+
function needHex(o, field, re) {
|
|
38448
|
+
const v = needString(o, field);
|
|
38449
|
+
if (!re.test(v)) bad(`${field} is not hex-shaped`);
|
|
38450
|
+
return v;
|
|
38451
|
+
}
|
|
38452
|
+
function needStringArray(o, field) {
|
|
38453
|
+
const v = o[field];
|
|
38454
|
+
if (!Array.isArray(v) || !v.every((x) => typeof x === "string")) bad(`${field} must be an array of strings`);
|
|
38455
|
+
return v;
|
|
38456
|
+
}
|
|
38457
|
+
function parseCounts(value) {
|
|
38458
|
+
if (typeof value !== "object" || value === null) bad("counts must be an object");
|
|
38459
|
+
const o = value;
|
|
38460
|
+
for (const field of ["INFO", "LOW", "MEDIUM", "HIGH", "CRITICAL", "total", "blocking", "warnings"]) {
|
|
38461
|
+
if (typeof o[field] !== "number" || !Number.isInteger(o[field])) bad(`counts.${field} must be an integer`);
|
|
38432
38462
|
}
|
|
38433
|
-
return
|
|
38463
|
+
return o;
|
|
38434
38464
|
}
|
|
38435
|
-
|
|
38436
|
-
if (o.
|
|
38437
|
-
|
|
38465
|
+
function parseArtifacts(o) {
|
|
38466
|
+
if (o.artifacts === null) return null;
|
|
38467
|
+
const v = o.artifacts;
|
|
38468
|
+
if (!Array.isArray(v)) bad("artifacts must be null or an array");
|
|
38469
|
+
return v.map((item, i) => {
|
|
38470
|
+
if (typeof item !== "object" || item === null) bad(`artifacts[${String(i)}] must be an object`);
|
|
38471
|
+
const a = item;
|
|
38472
|
+
return { file: needString(a, "file"), sha256: needHex(a, "sha256", HEX64) };
|
|
38473
|
+
});
|
|
38474
|
+
}
|
|
38475
|
+
function parseLedgerRecord(value) {
|
|
38476
|
+
if (typeof value !== "object" || value === null) bad("record must be an object");
|
|
38477
|
+
const o = value;
|
|
38478
|
+
const t = o.t;
|
|
38479
|
+
if (t === "check") {
|
|
38480
|
+
const key = needHex(o, "key", HEX64);
|
|
38481
|
+
const key8 = needHex(o, "key8", HEX8);
|
|
38482
|
+
if (key.slice(0, 8) !== key8) bad("key8 must be the first 8 hex chars of key");
|
|
38483
|
+
const verdict = needString(o, "verdict");
|
|
38484
|
+
if (verdict !== "PASS" && verdict !== "FAIL") bad(`verdict must be PASS or FAIL, got '${verdict}'`);
|
|
38485
|
+
if (o.degraded !== false) bad("degraded must be false \u2014 degraded runs are never ledgered");
|
|
38486
|
+
if (typeof o.llm !== "boolean") bad("llm must be a boolean");
|
|
38487
|
+
return {
|
|
38488
|
+
t: "check",
|
|
38489
|
+
key,
|
|
38490
|
+
key8,
|
|
38491
|
+
head: needHex(o, "head", HEX40),
|
|
38492
|
+
dirtyDigest: needHex(o, "dirtyDigest", HEX64),
|
|
38493
|
+
refSetHash: needHex(o, "refSetHash", HEX64),
|
|
38494
|
+
exposureSet: needStringArray(o, "exposureSet"),
|
|
38495
|
+
effectiveTargets: needStringArray(o, "effectiveTargets"),
|
|
38496
|
+
rulesHash: needHex(o, "rulesHash", HEX64),
|
|
38497
|
+
artifacts: parseArtifacts(o),
|
|
38498
|
+
llm: o.llm,
|
|
38499
|
+
verdict,
|
|
38500
|
+
counts: parseCounts(o.counts),
|
|
38501
|
+
reportPath: needString(o, "reportPath"),
|
|
38502
|
+
degraded: false,
|
|
38503
|
+
ts: needString(o, "ts")
|
|
38504
|
+
};
|
|
38438
38505
|
}
|
|
38439
|
-
|
|
38440
|
-
|
|
38441
|
-
|
|
38442
|
-
|
|
38506
|
+
if (t === "push") {
|
|
38507
|
+
const confirmedVia = needString(o, "confirmedVia");
|
|
38508
|
+
if (!PUSH_CONFIRMED_VIA.includes(confirmedVia)) {
|
|
38509
|
+
bad(`confirmedVia must be one of ${PUSH_CONFIRMED_VIA.join("|")}`);
|
|
38510
|
+
}
|
|
38511
|
+
const remoteSha = o.remoteSha;
|
|
38512
|
+
const version2 = o.version;
|
|
38513
|
+
if (remoteSha === void 0 && version2 === void 0) bad("push record needs remoteSha or version");
|
|
38514
|
+
if (remoteSha !== void 0 && typeof remoteSha !== "string") bad("remoteSha must be a string");
|
|
38515
|
+
if (version2 !== void 0 && typeof version2 !== "string") bad("version must be a string");
|
|
38516
|
+
return {
|
|
38517
|
+
t: "push",
|
|
38518
|
+
key: needHex(o, "key", HEX64),
|
|
38519
|
+
target: needString(o, "target"),
|
|
38520
|
+
remoteName: needString(o, "remoteName"),
|
|
38521
|
+
url: needString(o, "url"),
|
|
38522
|
+
localSha: needString(o, "localSha"),
|
|
38523
|
+
...remoteSha !== void 0 ? { remoteSha } : {},
|
|
38524
|
+
...version2 !== void 0 ? { version: version2 } : {},
|
|
38525
|
+
confirmedVia,
|
|
38526
|
+
ts: needString(o, "ts")
|
|
38527
|
+
};
|
|
38443
38528
|
}
|
|
38444
|
-
|
|
38445
|
-
|
|
38446
|
-
|
|
38529
|
+
if (t === "roundtrip") {
|
|
38530
|
+
const verdict = needString(o, "verdict");
|
|
38531
|
+
if (verdict !== "clean" && verdict !== "residue") bad(`verdict must be clean or residue, got '${verdict}'`);
|
|
38532
|
+
const rows = o.rows;
|
|
38533
|
+
if (typeof rows !== "number" || !Number.isInteger(rows) || rows < 0) bad("rows must be a non-negative integer");
|
|
38447
38534
|
return {
|
|
38448
|
-
|
|
38449
|
-
|
|
38450
|
-
|
|
38451
|
-
|
|
38452
|
-
|
|
38453
|
-
|
|
38454
|
-
o.sanitizer
|
|
38455
|
-
)],
|
|
38456
|
-
artifact: null
|
|
38535
|
+
t: "roundtrip",
|
|
38536
|
+
artifactSha256: needHex(o, "artifactSha256", HEX64),
|
|
38537
|
+
verdict,
|
|
38538
|
+
rulesHash: needHex(o, "rulesHash", HEX64),
|
|
38539
|
+
rows,
|
|
38540
|
+
ts: needString(o, "ts")
|
|
38457
38541
|
};
|
|
38458
38542
|
}
|
|
38459
|
-
|
|
38460
|
-
|
|
38461
|
-
|
|
38462
|
-
|
|
38463
|
-
|
|
38464
|
-
|
|
38465
|
-
|
|
38466
|
-
...o.env !== void 0 ? { env: o.env } : {},
|
|
38467
|
-
...o.npmBinPath !== void 0 ? { npmBinPath: o.npmBinPath } : {},
|
|
38468
|
-
...o.npmTimeoutMs !== void 0 ? { npmTimeoutMs: o.npmTimeoutMs } : {}
|
|
38469
|
-
});
|
|
38470
|
-
const findings = [];
|
|
38471
|
-
const extractDir = join8(stateDir, "tmp", sha2563.slice(0, 8));
|
|
38472
|
-
rmSync3(extractDir, { recursive: true, force: true });
|
|
38473
|
-
mkdirSync3(extractDir, { recursive: true });
|
|
38543
|
+
return bad(`unknown record type ${JSON.stringify(t)}`);
|
|
38544
|
+
}
|
|
38545
|
+
function codeOf2(err) {
|
|
38546
|
+
return typeof err === "object" && err !== null ? err.code : void 0;
|
|
38547
|
+
}
|
|
38548
|
+
function readLedger(repoDir) {
|
|
38549
|
+
let text;
|
|
38474
38550
|
try {
|
|
38475
|
-
|
|
38476
|
-
|
|
38477
|
-
|
|
38478
|
-
|
|
38479
|
-
|
|
38480
|
-
|
|
38481
|
-
|
|
38482
|
-
|
|
38483
|
-
|
|
38551
|
+
text = readFileSync5(ledgerPath(repoDir), "utf8");
|
|
38552
|
+
} catch (err) {
|
|
38553
|
+
if (codeOf2(err) === "ENOENT") return { records: [], warnings: [] };
|
|
38554
|
+
throw err;
|
|
38555
|
+
}
|
|
38556
|
+
const records = [];
|
|
38557
|
+
const warnings = [];
|
|
38558
|
+
text.split("\n").forEach((line, i) => {
|
|
38559
|
+
if (line.trim() === "") return;
|
|
38560
|
+
try {
|
|
38561
|
+
records.push(parseLedgerRecord(JSON.parse(line)));
|
|
38562
|
+
} catch (err) {
|
|
38563
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
38564
|
+
warnings.push(`ledger line ${String(i + 1)} unreadable (${reason.slice(0, 160)}) \u2014 line skipped`);
|
|
38484
38565
|
}
|
|
38485
|
-
|
|
38486
|
-
|
|
38487
|
-
|
|
38488
|
-
|
|
38489
|
-
|
|
38490
|
-
|
|
38491
|
-
|
|
38492
|
-
|
|
38493
|
-
|
|
38494
|
-
|
|
38495
|
-
|
|
38496
|
-
|
|
38497
|
-
|
|
38498
|
-
|
|
38499
|
-
|
|
38500
|
-
|
|
38501
|
-
|
|
38502
|
-
|
|
38503
|
-
|
|
38504
|
-
|
|
38505
|
-
|
|
38506
|
-
|
|
38507
|
-
|
|
38508
|
-
|
|
38566
|
+
});
|
|
38567
|
+
return { records, warnings };
|
|
38568
|
+
}
|
|
38569
|
+
function appendRecord(repoDir, record2) {
|
|
38570
|
+
ensureStateDir(repoDir);
|
|
38571
|
+
appendFileSync(ledgerPath(repoDir), `${JSON.stringify(record2)}
|
|
38572
|
+
`, "utf8");
|
|
38573
|
+
}
|
|
38574
|
+
function newestRecordOf(records, key) {
|
|
38575
|
+
for (let i = records.length - 1; i >= 0; i -= 1) {
|
|
38576
|
+
const r = records[i];
|
|
38577
|
+
if (r.t === "check" && r.key === key) return r;
|
|
38578
|
+
}
|
|
38579
|
+
return null;
|
|
38580
|
+
}
|
|
38581
|
+
function isSkippable(r) {
|
|
38582
|
+
return r !== null && r.verdict === "PASS" && r.degraded === false;
|
|
38583
|
+
}
|
|
38584
|
+
function lookupSkipRecord(records, key, llm) {
|
|
38585
|
+
const newest = newestRecordOf(records, key);
|
|
38586
|
+
return isSkippable(newest) && newest.llm === llm ? newest : null;
|
|
38587
|
+
}
|
|
38588
|
+
function latestPassCoveringTargets(records, key, targets) {
|
|
38589
|
+
const newest = newestRecordOf(records, key);
|
|
38590
|
+
return isSkippable(newest) && targets.every((t) => newest.effectiveTargets.includes(t)) ? newest : null;
|
|
38591
|
+
}
|
|
38592
|
+
function pushRecords(records, key) {
|
|
38593
|
+
return records.filter((r) => r.t === "push" && (key === void 0 || r.key === key));
|
|
38594
|
+
}
|
|
38595
|
+
function latestRoundtripForSha(records, artifactSha256) {
|
|
38596
|
+
for (let i = records.length - 1; i >= 0; i -= 1) {
|
|
38597
|
+
const r = records[i];
|
|
38598
|
+
if (r !== void 0 && r.t === "roundtrip" && r.artifactSha256 === artifactSha256) return r;
|
|
38599
|
+
}
|
|
38600
|
+
return null;
|
|
38601
|
+
}
|
|
38602
|
+
|
|
38603
|
+
// src/rules/releaseCoherence.ts
|
|
38604
|
+
var RELEASE_COHERENCE_DRIFT_RULE = "release-coherence-version-drift";
|
|
38605
|
+
var RELEASE_COHERENCE_TWIN_RULE = "release-coherence-twin-drift";
|
|
38606
|
+
var RELEASE_COHERENCE_UNVERIFIABLE_RULE = "release-coherence-unverifiable-source";
|
|
38607
|
+
var RELEASE_COHERENCE_SEVERITIES = {
|
|
38608
|
+
[RELEASE_COHERENCE_DRIFT_RULE]: "CRITICAL",
|
|
38609
|
+
[RELEASE_COHERENCE_TWIN_RULE]: "CRITICAL",
|
|
38610
|
+
[RELEASE_COHERENCE_UNVERIFIABLE_RULE]: "MEDIUM"
|
|
38611
|
+
};
|
|
38612
|
+
var TARGET = "artifact";
|
|
38613
|
+
var ENGINE = "native";
|
|
38614
|
+
var FOUNDING = "version truth must AGREE across every source packed in the artifact (aihr: a wheel named 0.2.2 shipped __version__ 0.2.1)";
|
|
38615
|
+
function probeDunderVersion(text) {
|
|
38616
|
+
for (const line of text.split(/\r?\n/)) {
|
|
38617
|
+
const assign = /^\s*__version__\s*=\s*(.+?)\s*$/.exec(line);
|
|
38618
|
+
if (assign?.[1] !== void 0) {
|
|
38619
|
+
const lit = /^(['"])((?:(?!\1).)+)\1(?:\s*#.*)?$/.exec(assign[1]);
|
|
38620
|
+
return lit?.[2] !== void 0 ? { status: "literal", value: lit[2] } : { status: "dynamic", reason: `__version__ assigned a non-literal (${assign[1].slice(0, 60)})` };
|
|
38621
|
+
}
|
|
38622
|
+
if (/^\s*(?:from\s+\S+\s+)?import\b.*\b__version__\b/.test(line)) {
|
|
38623
|
+
return { status: "dynamic", reason: "__version__ imported from another module" };
|
|
38624
|
+
}
|
|
38625
|
+
}
|
|
38626
|
+
return null;
|
|
38627
|
+
}
|
|
38628
|
+
function probeHeaderVersion(text) {
|
|
38629
|
+
const m = /^Version:[ \t]*(.*?)[ \t]*$/m.exec(text);
|
|
38630
|
+
if (m === null) return null;
|
|
38631
|
+
return m[1] !== void 0 && m[1] !== "" ? { status: "literal", value: m[1] } : { status: "dynamic", reason: "Version: header present but empty" };
|
|
38632
|
+
}
|
|
38633
|
+
function probePyprojectVersion(text) {
|
|
38634
|
+
const lines = text.split(/\r?\n/);
|
|
38635
|
+
let inProject = false;
|
|
38636
|
+
const slice = [];
|
|
38637
|
+
for (const line of lines) {
|
|
38638
|
+
if (/^\s*\[/.test(line)) {
|
|
38639
|
+
if (inProject) break;
|
|
38640
|
+
inProject = /^\s*\[project\]\s*$/.test(line);
|
|
38641
|
+
continue;
|
|
38642
|
+
}
|
|
38643
|
+
if (inProject) slice.push(line);
|
|
38644
|
+
}
|
|
38645
|
+
if (!inProject && slice.length === 0 && !text.includes("[project]")) return null;
|
|
38646
|
+
const body = slice.join("\n");
|
|
38647
|
+
const lit = /^\s*version\s*=\s*(['"])((?:(?!\1).)+)\1\s*(?:#.*)?$/m.exec(body);
|
|
38648
|
+
if (lit?.[2] !== void 0) return { status: "literal", value: lit[2] };
|
|
38649
|
+
const dyn = /dynamic\s*=\s*\[([\s\S]*?)\]/.exec(`${body}
|
|
38650
|
+
]`);
|
|
38651
|
+
if (dyn?.[1] !== void 0 && /["']version["']/.test(dyn[1])) return { status: "dynamic", reason: 'project.version listed in dynamic = ["version"]' };
|
|
38652
|
+
return { status: "dynamic", reason: "[project] declares no statically-readable version" };
|
|
38653
|
+
}
|
|
38654
|
+
function probeSetupCfgVersion(text) {
|
|
38655
|
+
const lines = text.split(/\r?\n/);
|
|
38656
|
+
let inSection = false;
|
|
38657
|
+
const slice = [];
|
|
38658
|
+
for (const line of lines) {
|
|
38659
|
+
if (/^\s*\[/.test(line)) {
|
|
38660
|
+
if (inSection) break;
|
|
38661
|
+
inSection = /^\s*\[metadata\]\s*$/.test(line);
|
|
38662
|
+
continue;
|
|
38663
|
+
}
|
|
38664
|
+
if (inSection) slice.push(line);
|
|
38665
|
+
}
|
|
38666
|
+
const v = /^[ \t]*version[ \t]*=[ \t]*(.+?)[ \t]*$/m.exec(slice.join("\n"));
|
|
38667
|
+
if (v?.[1] === void 0) return null;
|
|
38668
|
+
return /^[\w.+-]+$/.test(v[1]) ? { status: "literal", value: v[1] } : { status: "dynamic", reason: `setup.cfg version = '${v[1].slice(0, 40)}' is not a bare literal` };
|
|
38669
|
+
}
|
|
38670
|
+
function probeSetupPyVersion(text) {
|
|
38671
|
+
const lit = /(?:^|[,(]\s*)version\s*=\s*(['"])((?:(?!\1).)+)\1/.exec(text);
|
|
38672
|
+
if (lit?.[2] !== void 0) return { status: "literal", value: lit[2] };
|
|
38673
|
+
return /(?:^|[,(]\s*)version\s*=/.test(text) ? { status: "dynamic", reason: "setup.py version= is not a string literal" } : null;
|
|
38674
|
+
}
|
|
38675
|
+
function parseWheelFilenameVersion(fileName) {
|
|
38676
|
+
const base = basename(fileName).replace(/\.whl$/i, "");
|
|
38677
|
+
const parts = base.split("-");
|
|
38678
|
+
if (parts.length < 5) return null;
|
|
38679
|
+
const name = parts[0];
|
|
38680
|
+
const version2 = parts[1];
|
|
38681
|
+
return name !== "" && version2 !== "" ? { name, version: version2 } : null;
|
|
38682
|
+
}
|
|
38683
|
+
function parseDistInfoDirVersion(relPath) {
|
|
38684
|
+
const dir = /(?:^|\/)([^/]+\.dist-info)\//.exec(relPath)?.[1];
|
|
38685
|
+
if (dir === void 0) return null;
|
|
38686
|
+
const stem = dir.slice(0, -".dist-info".length);
|
|
38687
|
+
const idx = stem.lastIndexOf("-");
|
|
38688
|
+
return idx >= 0 ? stem.slice(idx + 1) : null;
|
|
38689
|
+
}
|
|
38690
|
+
var normName = (s) => s.toLowerCase().replace(/[-_.]+/g, "_");
|
|
38691
|
+
function toFindings2(hits, identity, sanitizer) {
|
|
38692
|
+
return hits.map((hit) => {
|
|
38693
|
+
const { valueDigest, snippet } = redact(`${identity}:release-coherence:${hit.rule}:${hit.disc}`);
|
|
38694
|
+
return {
|
|
38695
|
+
rule: hit.rule,
|
|
38696
|
+
severity: RELEASE_COHERENCE_SEVERITIES[hit.rule],
|
|
38697
|
+
target: TARGET,
|
|
38698
|
+
path: hit.path,
|
|
38699
|
+
engine: ENGINE,
|
|
38700
|
+
message: sanitizer?.sanitize(hit.message) ?? hit.message,
|
|
38701
|
+
valueDigest,
|
|
38702
|
+
snippet
|
|
38703
|
+
};
|
|
38704
|
+
});
|
|
38705
|
+
}
|
|
38706
|
+
function analyze(sources, pathPrefix, hits) {
|
|
38707
|
+
const primary = sources.find((s) => s.probe?.status === "literal");
|
|
38708
|
+
for (const s of sources) {
|
|
38709
|
+
if (s.probe === null) continue;
|
|
38710
|
+
if (s.probe.status === "dynamic") {
|
|
38711
|
+
hits.push({
|
|
38712
|
+
rule: RELEASE_COHERENCE_UNVERIFIABLE_RULE,
|
|
38713
|
+
message: `release coherence: ${s.label} (${s.path}) carries a version that cannot be verified statically \u2014 ${s.probe.reason}; surfaced, never silent-clean`,
|
|
38714
|
+
path: `${pathPrefix}${s.path}`,
|
|
38715
|
+
disc: `${s.path}:unverifiable`
|
|
38716
|
+
});
|
|
38717
|
+
continue;
|
|
38718
|
+
}
|
|
38719
|
+
if (primary === void 0 || s === primary) continue;
|
|
38720
|
+
if (s.probe.value !== primary.probe.value) {
|
|
38721
|
+
hits.push({
|
|
38722
|
+
rule: RELEASE_COHERENCE_DRIFT_RULE,
|
|
38723
|
+
message: `release coherence: ${primary.label} '${primary.probe.value}' disagrees with ${s.label} '${s.probe.value}' \u2014 ${FOUNDING}`,
|
|
38724
|
+
path: `${pathPrefix}${s.path}`,
|
|
38725
|
+
disc: `${s.path}:vs:${primary.label}`
|
|
38726
|
+
});
|
|
38727
|
+
}
|
|
38728
|
+
}
|
|
38729
|
+
}
|
|
38730
|
+
function npmPackCoherenceFindings(o) {
|
|
38731
|
+
if (o.lockText === null || o.packedVersion === null) return [];
|
|
38732
|
+
let lock;
|
|
38733
|
+
try {
|
|
38734
|
+
lock = JSON.parse(o.lockText);
|
|
38735
|
+
} catch {
|
|
38736
|
+
return toFindings2([{ rule: RELEASE_COHERENCE_UNVERIFIABLE_RULE, message: `release coherence: ${o.lockRelPath} is packed but not parseable JSON \u2014 cannot verify, never silent-clean`, path: o.lockRelPath, disc: "malformed" }], o.identity, o.sanitizer);
|
|
38737
|
+
}
|
|
38738
|
+
const hits = [];
|
|
38739
|
+
const rootVer = typeof lock["version"] === "string" ? lock["version"] : null;
|
|
38740
|
+
const pkgs = lock["packages"];
|
|
38741
|
+
const rootPkg = typeof pkgs === "object" && pkgs !== null ? pkgs[""] : void 0;
|
|
38742
|
+
const pkgEmptyVer = typeof rootPkg === "object" && rootPkg !== null ? rootPkg["version"] : null;
|
|
38743
|
+
const cmp = (label, value, disc) => {
|
|
38744
|
+
if (value !== null && value !== o.packedVersion) {
|
|
38745
|
+
hits.push({ rule: RELEASE_COHERENCE_DRIFT_RULE, message: `release coherence: packed package.json version '${String(o.packedVersion)}' disagrees with ${label} '${value}' \u2014 ${FOUNDING}`, path: o.lockRelPath, disc });
|
|
38746
|
+
}
|
|
38747
|
+
};
|
|
38748
|
+
cmp("package-lock.json root version", rootVer, "root");
|
|
38749
|
+
cmp('package-lock.json packages[""] version', typeof pkgEmptyVer === "string" ? pkgEmptyVer : null, "packages-empty");
|
|
38750
|
+
return toFindings2(hits, o.identity, o.sanitizer);
|
|
38751
|
+
}
|
|
38752
|
+
function pypiArtifactCoherenceFindings(o) {
|
|
38753
|
+
const sources = [];
|
|
38754
|
+
const inTree = (rel) => o.files.includes(rel);
|
|
38755
|
+
const textOf = (rel) => inTree(rel) ? o.read(rel) : null;
|
|
38756
|
+
if (o.kind === "wheel") {
|
|
38757
|
+
const metaRel = o.files.find((f) => /\.dist-info\/METADATA$/.test(f));
|
|
38758
|
+
if (metaRel !== void 0) {
|
|
38759
|
+
const body = o.read(metaRel);
|
|
38760
|
+
sources.push({ label: "wheel METADATA Version:", path: metaRel, probe: body === null ? null : probeHeaderVersion(body) ?? { status: "dynamic", reason: "METADATA has no Version: header" } });
|
|
38761
|
+
const dirVer = parseDistInfoDirVersion(metaRel);
|
|
38762
|
+
if (dirVer !== null) sources.push({ label: "wheel .dist-info directory name", path: metaRel, probe: { status: "literal", value: dirVer } });
|
|
38763
|
+
}
|
|
38764
|
+
const fn = parseWheelFilenameVersion(o.archiveName);
|
|
38765
|
+
if (fn !== null) sources.push({ label: "wheel filename", path: metaRel ?? o.archiveName, probe: { status: "literal", value: fn.version } });
|
|
38766
|
+
} else {
|
|
38767
|
+
const pyproject = textOf("pyproject.toml");
|
|
38768
|
+
if (pyproject !== null) sources.push({ label: "pyproject [project].version", path: "pyproject.toml", probe: probePyprojectVersion(pyproject) });
|
|
38769
|
+
const cfg = textOf("setup.cfg");
|
|
38770
|
+
if (cfg !== null) sources.push({ label: "setup.cfg [metadata] version", path: "setup.cfg", probe: probeSetupCfgVersion(cfg) });
|
|
38771
|
+
const py = textOf("setup.py");
|
|
38772
|
+
if (py !== null) sources.push({ label: "setup.py version literal", path: "setup.py", probe: probeSetupPyVersion(py) });
|
|
38773
|
+
const pkgInfoRel = o.files.find((f) => f === "PKG-INFO" || f.endsWith(".egg-info/PKG-INFO"));
|
|
38774
|
+
if (pkgInfoRel !== void 0) {
|
|
38775
|
+
const body = o.read(pkgInfoRel);
|
|
38776
|
+
if (body !== null) sources.push({ label: "PKG-INFO Version:", path: pkgInfoRel, probe: probeHeaderVersion(body) ?? { status: "dynamic", reason: "PKG-INFO has no Version: header" } });
|
|
38777
|
+
}
|
|
38778
|
+
}
|
|
38779
|
+
for (const initRel of o.files.filter((f) => /(?:^|\/)__init__\.py$/.test(f)).sort()) {
|
|
38780
|
+
const body = o.read(initRel);
|
|
38781
|
+
if (body === null) continue;
|
|
38782
|
+
const probe = probeDunderVersion(body);
|
|
38783
|
+
if (probe !== null) sources.push({ label: `__version__ literal in ${initRel}`, path: initRel, probe });
|
|
38784
|
+
}
|
|
38785
|
+
const hits = [];
|
|
38786
|
+
analyze(sources, `${o.archiveName}!`, hits);
|
|
38787
|
+
return toFindings2(hits, o.identity, o.sanitizer);
|
|
38788
|
+
}
|
|
38789
|
+
function cratesCoherenceFindings(o) {
|
|
38790
|
+
if (o.lockText === null) return [];
|
|
38791
|
+
const entries = o.lockText.split(/^\[\[package\]\][ \t]*$/m).map((block) => ({ name: /^\s*name\s*=\s*"([^"]*)"/m.exec(block)?.[1], version: /^\s*version\s*=\s*"([^"]*)"/m.exec(block)?.[1] })).filter((e) => e.name !== void 0 && e.version !== void 0).filter((e) => e.name === o.name);
|
|
38792
|
+
if (entries.length === 0) {
|
|
38793
|
+
return toFindings2([{ rule: RELEASE_COHERENCE_UNVERIFIABLE_RULE, message: `release coherence: ${o.lockRelPath} is packed but names no [[package]] '${o.name}' \u2014 cannot verify, never silent-clean`, path: o.lockRelPath, disc: "no-entry" }], o.identity, o.sanitizer);
|
|
38794
|
+
}
|
|
38795
|
+
const hits = [];
|
|
38796
|
+
for (const v of [...new Set(entries.map((e) => e.version))].sort()) {
|
|
38797
|
+
if (v !== o.version) {
|
|
38798
|
+
hits.push({ rule: RELEASE_COHERENCE_DRIFT_RULE, message: `release coherence: Cargo.toml [package].version '${o.version}' disagrees with Cargo.lock entry '${o.name}' version '${v}' \u2014 ${FOUNDING}`, path: o.lockRelPath, disc: v });
|
|
38799
|
+
}
|
|
38800
|
+
}
|
|
38801
|
+
return toFindings2(hits, o.identity, o.sanitizer);
|
|
38802
|
+
}
|
|
38803
|
+
function gemCoherenceFindings(o) {
|
|
38804
|
+
if (o.metadataText === null) return [];
|
|
38805
|
+
const name = /^name:[ \t]*(\S+)[ \t]*$/m.exec(o.metadataText)?.[1];
|
|
38806
|
+
const version2 = /^version:[ \t]*!ruby\/object:Gem::Version[ \t]*\n[ \t]+version:[ \t]*(\S+)[ \t]*$/m.exec(o.metadataText)?.[1];
|
|
38807
|
+
if (name === void 0 || version2 === void 0) {
|
|
38808
|
+
return toFindings2([{ rule: RELEASE_COHERENCE_UNVERIFIABLE_RULE, message: "release coherence: metadata.gz is present but its Gem::Specification name/version could not be read statically \u2014 cannot verify, never silent-clean", path: "metadata.gz", disc: "unparseable" }], o.identity, o.sanitizer);
|
|
38809
|
+
}
|
|
38810
|
+
const hits = [];
|
|
38811
|
+
if (name !== o.specName) hits.push({ rule: RELEASE_COHERENCE_DRIFT_RULE, message: `release coherence: built gem '${o.identity}' names '${o.specName}' but metadata.gz Gem::Specification name is '${name}' \u2014 ${FOUNDING}`, path: "metadata.gz", disc: "name" });
|
|
38812
|
+
if (version2 !== o.specVersion) hits.push({ rule: RELEASE_COHERENCE_DRIFT_RULE, message: `release coherence: built gem '${o.identity}' names version '${o.specVersion}' but metadata.gz Gem::Version is '${version2}' \u2014 ${FOUNDING}`, path: "metadata.gz", disc: "version" });
|
|
38813
|
+
return toFindings2(hits, o.identity, o.sanitizer);
|
|
38814
|
+
}
|
|
38815
|
+
function packedNpmIdentity(archiveAbs, env2) {
|
|
38816
|
+
const opts = { encoding: "utf8", timeout: 3e4, ...env2 !== void 0 ? { env: env2 } : {} };
|
|
38817
|
+
const listing = spawnSync3("tar", ["-tzf", archiveAbs], opts);
|
|
38818
|
+
if (listing.status !== 0) return null;
|
|
38819
|
+
const members2 = (listing.stdout ?? "").split("\n").filter((l) => /^[^/]+\/package\.json$/.test(l));
|
|
38820
|
+
const member = members2.includes("package/package.json") ? "package/package.json" : members2.length === 1 ? members2[0] : void 0;
|
|
38821
|
+
if (member === void 0) return null;
|
|
38822
|
+
const body = spawnSync3("tar", ["-xzOf", archiveAbs, member], opts);
|
|
38823
|
+
if (body.status !== 0) return null;
|
|
38824
|
+
try {
|
|
38825
|
+
const pkg = JSON.parse(body.stdout);
|
|
38826
|
+
return typeof pkg["name"] === "string" && typeof pkg["version"] === "string" ? { name: pkg["name"], version: pkg["version"] } : null;
|
|
38827
|
+
} catch {
|
|
38828
|
+
return null;
|
|
38829
|
+
}
|
|
38830
|
+
}
|
|
38831
|
+
async function twinCoherenceFindings(o) {
|
|
38832
|
+
const wheels = o.artifacts.filter((a) => a.file.endsWith(".whl")).sort((a, b) => a.file.localeCompare(b.file));
|
|
38833
|
+
const tgzs = o.artifacts.filter((a) => a.file.endsWith(".tgz")).sort((a, b) => a.file.localeCompare(b.file));
|
|
38834
|
+
const hits = [];
|
|
38835
|
+
for (const pair of o.pairs) {
|
|
38836
|
+
const wh = wheels.find((a) => {
|
|
38837
|
+
const parsed = parseWheelFilenameVersion(basename(a.file));
|
|
38838
|
+
return parsed !== null && normName(parsed.name) === normName(pair.pypi);
|
|
38839
|
+
});
|
|
38840
|
+
if (wh === void 0) {
|
|
38841
|
+
hits.push({ rule: RELEASE_COHERENCE_UNVERIFIABLE_RULE, message: `release coherence: release.twin pair '${pair.pypi}'/'${pair.npm}' \u2014 no staged wheel for '${pair.pypi}' in this run, so the declared cross-manager equality cannot be verified`, path: `${pair.pypi}(missing).whl`, disc: `twin:${pair.pypi}:no-wheel` });
|
|
38842
|
+
continue;
|
|
38843
|
+
}
|
|
38844
|
+
const wheelVersion = parseWheelFilenameVersion(basename(wh.file)).version;
|
|
38845
|
+
let npmSide = null;
|
|
38846
|
+
let npmUnreadable = false;
|
|
38847
|
+
for (const t of tgzs) {
|
|
38848
|
+
const id = packedNpmIdentity(join9(o.repoDir, t.file), o.env);
|
|
38849
|
+
if (id === null) {
|
|
38850
|
+
npmUnreadable = true;
|
|
38851
|
+
continue;
|
|
38852
|
+
}
|
|
38853
|
+
if (id.name === pair.npm) {
|
|
38854
|
+
npmSide = { file: t.file, version: id.version };
|
|
38855
|
+
break;
|
|
38856
|
+
}
|
|
38857
|
+
}
|
|
38858
|
+
if (npmSide === null) {
|
|
38859
|
+
hits.push({
|
|
38860
|
+
rule: RELEASE_COHERENCE_UNVERIFIABLE_RULE,
|
|
38861
|
+
message: `release coherence: release.twin pair '${pair.pypi}'/'${pair.npm}' \u2014 ${npmUnreadable ? `npm twin '${pair.npm}' is staged but its package.json could not be read from the tarball bytes` : `no staged npm artifact for twin '${pair.npm}' in this run`}, so the declared cross-manager equality cannot be verified`,
|
|
38862
|
+
path: basename(wh.file),
|
|
38863
|
+
disc: `twin:${pair.pypi}:no-npm`
|
|
38864
|
+
});
|
|
38865
|
+
continue;
|
|
38866
|
+
}
|
|
38867
|
+
if (wheelVersion !== npmSide.version) {
|
|
38868
|
+
hits.push({
|
|
38869
|
+
rule: RELEASE_COHERENCE_TWIN_RULE,
|
|
38870
|
+
message: `release coherence: release.twin pair '${pair.pypi}'/'${pair.npm}' \u2014 wheel '${basename(wh.file)}' says version '${wheelVersion}' but npm twin '${basename(npmSide.file)}' packed package.json says '${npmSide.version}' \u2014 ${FOUNDING}`,
|
|
38871
|
+
path: basename(wh.file),
|
|
38872
|
+
disc: `twin:${pair.pypi}:${basename(npmSide.file)}`
|
|
38873
|
+
});
|
|
38874
|
+
}
|
|
38875
|
+
}
|
|
38876
|
+
return toFindings2(hits, "release.twin", o.sanitizer);
|
|
38877
|
+
}
|
|
38878
|
+
|
|
38879
|
+
// src/artifacts/npm.ts
|
|
38880
|
+
function nativeFinding(rule, severity, message, path4, identity, sanitizer) {
|
|
38881
|
+
const digest2 = redact(identity);
|
|
38882
|
+
return {
|
|
38883
|
+
rule,
|
|
38884
|
+
severity,
|
|
38885
|
+
target: NPM_TARGET_LABEL,
|
|
38886
|
+
path: path4,
|
|
38887
|
+
engine: "native",
|
|
38888
|
+
message: sanitizeOut(sanitizer, message),
|
|
38889
|
+
valueDigest: digest2.valueDigest,
|
|
38890
|
+
snippet: digest2.snippet
|
|
38891
|
+
};
|
|
38892
|
+
}
|
|
38893
|
+
function attributeToTarball(findings, artifact) {
|
|
38894
|
+
if (artifact === null) return findings;
|
|
38895
|
+
const name = artifact.file.slice(artifact.file.lastIndexOf("/") + 1);
|
|
38896
|
+
return findings.map((f) => {
|
|
38897
|
+
const p = f.path ?? "";
|
|
38898
|
+
const rel = p === "package.json" ? p : p.startsWith("package/") ? p.slice("package/".length) : null;
|
|
38899
|
+
return rel === null || rel === "" ? f : { ...f, path: `${name}!${rel}` };
|
|
38900
|
+
});
|
|
38901
|
+
}
|
|
38902
|
+
function scopeFindingPath(f, extractDir) {
|
|
38903
|
+
if (f.path === void 0) return f;
|
|
38904
|
+
const bang = f.path.indexOf("!");
|
|
38905
|
+
if (bang > 0) {
|
|
38906
|
+
const rel2 = relative2(extractDir, f.path.slice(0, bang));
|
|
38907
|
+
if (!rel2.startsWith("..")) return { ...f, path: rel2 === "" ? f.path.slice(bang + 1) : `${rel2}${f.path.slice(bang)}` };
|
|
38908
|
+
}
|
|
38909
|
+
const rel = relative2(extractDir, f.path);
|
|
38910
|
+
return rel === "" || rel.startsWith("..") ? f : { ...f, path: rel };
|
|
38911
|
+
}
|
|
38912
|
+
function listFiles(dir) {
|
|
38913
|
+
const out = [];
|
|
38914
|
+
const stack = [dir];
|
|
38915
|
+
while (stack.length > 0) {
|
|
38916
|
+
const cur = stack.pop();
|
|
38917
|
+
for (const ent of readdirSync2(cur, { withFileTypes: true })) {
|
|
38918
|
+
const p = join10(cur, ent.name);
|
|
38919
|
+
if (ent.isDirectory()) stack.push(p);
|
|
38920
|
+
else if (ent.isFile()) out.push(p);
|
|
38921
|
+
}
|
|
38922
|
+
}
|
|
38923
|
+
return out;
|
|
38924
|
+
}
|
|
38925
|
+
async function runNpmArtifactStage(o) {
|
|
38926
|
+
if (o.cfg.targets.npm === void 0) {
|
|
38927
|
+
throw new EngineRunError("runNpmArtifactStage invoked without targets.npm configured \u2014 pipeline bug", null);
|
|
38928
|
+
}
|
|
38929
|
+
const { repoDir } = o;
|
|
38930
|
+
const manifestPath = join10(repoDir, "package.json");
|
|
38931
|
+
if (!existsSync6(manifestPath)) {
|
|
38932
|
+
throw new EngineRunError("targets.npm is configured but the repo has no package.json to pack", null);
|
|
38933
|
+
}
|
|
38934
|
+
const manifest = parseNpmManifest(readFileSync6(manifestPath, "utf8"));
|
|
38935
|
+
const identity = `${manifest.name ?? "?"}@${manifest.version ?? "?"}`;
|
|
38936
|
+
if (manifest.isPrivate) {
|
|
38937
|
+
return {
|
|
38938
|
+
findings: [nativeFinding(
|
|
38939
|
+
NPM_PRIVATE_RULE,
|
|
38940
|
+
"CRITICAL",
|
|
38941
|
+
`package.json is private:true but border.yaml configures targets.npm \u2014 nothing can ever publish from this repo; drop the private flag or the npm target`,
|
|
38942
|
+
"package.json",
|
|
38943
|
+
`${identity}:private`,
|
|
38944
|
+
o.sanitizer
|
|
38945
|
+
)],
|
|
38946
|
+
artifact: null
|
|
38947
|
+
};
|
|
38948
|
+
}
|
|
38949
|
+
ensureStateDir(repoDir);
|
|
38950
|
+
const stateDir = join10(repoDir, BORDER_STATE_DIR);
|
|
38951
|
+
const distDir = join10(stateDir, "dist");
|
|
38952
|
+
mkdirSync3(distDir, { recursive: true });
|
|
38953
|
+
const { filename, sha256: sha2563, bytes, tarballAbs } = packOnce({
|
|
38954
|
+
repoDir,
|
|
38955
|
+
distDir,
|
|
38956
|
+
...o.env !== void 0 ? { env: o.env } : {},
|
|
38957
|
+
...o.npmBinPath !== void 0 ? { npmBinPath: o.npmBinPath } : {},
|
|
38958
|
+
...o.npmTimeoutMs !== void 0 ? { npmTimeoutMs: o.npmTimeoutMs } : {}
|
|
38959
|
+
});
|
|
38960
|
+
const findings = [];
|
|
38961
|
+
const extractDir = join10(stateDir, "tmp", sha2563.slice(0, 8));
|
|
38962
|
+
rmSync3(extractDir, { recursive: true, force: true });
|
|
38963
|
+
mkdirSync3(extractDir, { recursive: true });
|
|
38964
|
+
try {
|
|
38965
|
+
extractArchive(tarballAbs, extractDir, {
|
|
38966
|
+
...o.env !== void 0 ? { env: o.env } : {},
|
|
38967
|
+
...o.timeoutMs !== void 0 ? { timeoutMs: o.timeoutMs } : {}
|
|
38968
|
+
});
|
|
38969
|
+
const relPaths = listFiles(extractDir).map((p) => relative2(extractDir, p));
|
|
38970
|
+
if (relPaths.length === 0) throw new EngineRunError("extracted artifact is empty \u2014 unusable npm pack output", null);
|
|
38971
|
+
const root = relPaths[0]?.split("/")[0] ?? "package";
|
|
38972
|
+
if (!relPaths.every((p) => p.startsWith(`${root}/`))) {
|
|
38973
|
+
throw new EngineRunError("extracted artifact mixes multiple top-level roots \u2014 unexpected tar layout, failing closed", null);
|
|
38974
|
+
}
|
|
38975
|
+
const packedManifestRel = `${root}/package.json`;
|
|
38976
|
+
if (!relPaths.includes(packedManifestRel)) throw new EngineRunError("extracted artifact has no package/package.json", null);
|
|
38977
|
+
const packedManifest = parseNpmManifest(readFileSync6(join10(extractDir, packedManifestRel), "utf8"));
|
|
38978
|
+
const lockRel = `${root}/package-lock.json`;
|
|
38979
|
+
findings.push(...npmPackCoherenceFindings({
|
|
38980
|
+
packedVersion: packedManifest.version,
|
|
38981
|
+
lockText: relPaths.includes(lockRel) ? readFileSync6(join10(extractDir, lockRel), "utf8") : null,
|
|
38982
|
+
lockRelPath: lockRel,
|
|
38983
|
+
identity,
|
|
38984
|
+
...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
|
|
38985
|
+
}));
|
|
38986
|
+
const pkgDir = join10(extractDir, root);
|
|
38987
|
+
const residueCtx = { root, identity, ...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {} };
|
|
38988
|
+
for (const key of LIFECYCLE_SCRIPT_KEYS) {
|
|
38989
|
+
const cmd = packedManifest.scripts[key];
|
|
38990
|
+
if (cmd === void 0) continue;
|
|
38991
|
+
const scan = scanNpmLifecycleHook({ pkgDir, hook: cmd });
|
|
38992
|
+
if (scan.signatureId !== null) {
|
|
38993
|
+
findings.push(nativeFinding(
|
|
38994
|
+
RESIDUE_T1_RULE,
|
|
38995
|
+
"MEDIUM",
|
|
38996
|
+
`package.json script '${key}' matches closed safe signature '${scan.signatureId}' (T1: install-root writes only, frozen probe/fallback argv literals, zero network/out-of-tree/persistence/cross-manager hits) \u2014 allowed but audit-visible; any deviation keeps the blocking lifecycle-script CRITICAL`,
|
|
38997
|
+
packedManifestRel,
|
|
38998
|
+
`${identity}:residue:t1:${key}`,
|
|
38999
|
+
o.sanitizer
|
|
39000
|
+
));
|
|
39001
|
+
} else {
|
|
39002
|
+
findings.push(nativeFinding(
|
|
39003
|
+
NPM_LIFECYCLE_RULE,
|
|
39004
|
+
"CRITICAL",
|
|
39005
|
+
`package.json script '${key}' is a lifecycle hook \u2014 npm executes it on every consumer install (G33): ${tail(sanitizeOut(o.sanitizer, cmd))}`,
|
|
39006
|
+
packedManifestRel,
|
|
38509
39007
|
`${identity}:lifecycle:${key}`,
|
|
38510
39008
|
o.sanitizer
|
|
38511
39009
|
));
|
|
@@ -38570,7 +39068,7 @@ async function runNpmArtifactStage(o) {
|
|
|
38570
39068
|
}
|
|
38571
39069
|
|
|
38572
39070
|
// src/check/context.ts
|
|
38573
|
-
import { spawnSync as
|
|
39071
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
38574
39072
|
import { createHash as createHash4 } from "node:crypto";
|
|
38575
39073
|
import { dirname as dirname4, resolve as resolve4 } from "node:path";
|
|
38576
39074
|
|
|
@@ -38597,7 +39095,7 @@ function isRecord(value) {
|
|
|
38597
39095
|
// src/check/context.ts
|
|
38598
39096
|
function gitSpawn(repoDir, args, o = {}) {
|
|
38599
39097
|
const abs = resolve4(repoDir);
|
|
38600
|
-
const r =
|
|
39098
|
+
const r = spawnSync4("git", ["-C", abs, ...args], {
|
|
38601
39099
|
encoding: "utf8",
|
|
38602
39100
|
env: { ...o.env ?? process.env, GIT_CEILING_DIRECTORIES: dirname4(abs) },
|
|
38603
39101
|
timeout: 6e4
|
|
@@ -38656,7 +39154,7 @@ function computeEffectiveTargets(cfg, requested) {
|
|
|
38656
39154
|
}
|
|
38657
39155
|
|
|
38658
39156
|
// src/rules/identity.ts
|
|
38659
|
-
import { spawnSync as
|
|
39157
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
38660
39158
|
import { dirname as dirname5, resolve as resolve5 } from "node:path";
|
|
38661
39159
|
|
|
38662
39160
|
// src/rules/allowlist.ts
|
|
@@ -38721,7 +39219,7 @@ function addRole(entries, key, entry, obj, role) {
|
|
|
38721
39219
|
found.objects.set(obj, prev === void 0 ? role : `${prev}+${role}`);
|
|
38722
39220
|
}
|
|
38723
39221
|
function runGit2(repoDir, args) {
|
|
38724
|
-
const r =
|
|
39222
|
+
const r = spawnSync5("git", args, {
|
|
38725
39223
|
cwd: repoDir,
|
|
38726
39224
|
encoding: "utf8",
|
|
38727
39225
|
env: { ...process.env, GIT_CEILING_DIRECTORIES: dirname5(resolve5(repoDir)) }
|
|
@@ -38767,7 +39265,7 @@ function collectCommits(s, revs) {
|
|
|
38767
39265
|
}
|
|
38768
39266
|
}
|
|
38769
39267
|
function revExists(repoDir, rev) {
|
|
38770
|
-
const r =
|
|
39268
|
+
const r = spawnSync5("git", ["rev-parse", "--verify", "--quiet", rev], {
|
|
38771
39269
|
cwd: repoDir,
|
|
38772
39270
|
encoding: "utf8",
|
|
38773
39271
|
env: { ...process.env, GIT_CEILING_DIRECTORIES: dirname5(resolve5(repoDir)) }
|
|
@@ -38871,12 +39369,12 @@ function scanIdentity(o) {
|
|
|
38871
39369
|
// src/ledger.ts
|
|
38872
39370
|
import { createHash as createHash8 } from "node:crypto";
|
|
38873
39371
|
import { writeFileSync as writeFileSync3 } from "node:fs";
|
|
38874
|
-
import { join as
|
|
39372
|
+
import { join as join15 } from "node:path";
|
|
38875
39373
|
|
|
38876
39374
|
// src/check/rulesHash.ts
|
|
38877
39375
|
import { createHash as createHash5 } from "node:crypto";
|
|
38878
39376
|
import { existsSync as existsSync7 } from "node:fs";
|
|
38879
|
-
import { dirname as dirname6, join as
|
|
39377
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
38880
39378
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
38881
39379
|
var RESIDUE_FINGERPRINT_SOURCES = [
|
|
38882
39380
|
{ dir: "rules", base: "residueMatchers.ts" },
|
|
@@ -38894,12 +39392,26 @@ function resolveResidueFingerprintFiles(env2 = process.env) {
|
|
|
38894
39392
|
const here = dirname6(fileURLToPath4(import.meta.url));
|
|
38895
39393
|
const seam = env2.BORDER_RESIDUE_SRC_DIR;
|
|
38896
39394
|
return RESIDUE_FINGERPRINT_SOURCES.map(({ dir, base }) => {
|
|
38897
|
-
if (seam !== void 0) return
|
|
38898
|
-
const live =
|
|
39395
|
+
if (seam !== void 0) return join11(seam, base);
|
|
39396
|
+
const live = join11(here, "..", dir, base);
|
|
38899
39397
|
if (existsSync7(live)) return live;
|
|
38900
39398
|
return resolveAsset(import.meta.url, ["residue-src", base]);
|
|
38901
39399
|
});
|
|
38902
39400
|
}
|
|
39401
|
+
var RELEASE_FINGERPRINT_SOURCES = [
|
|
39402
|
+
{ dir: "rules", base: "releaseCoherence.ts" }
|
|
39403
|
+
];
|
|
39404
|
+
var RELEASE_FINGERPRINT_BASENAMES = RELEASE_FINGERPRINT_SOURCES.map((s) => s.base);
|
|
39405
|
+
function resolveReleaseFingerprintFiles(env2 = process.env) {
|
|
39406
|
+
const here = dirname6(fileURLToPath4(import.meta.url));
|
|
39407
|
+
const seam = env2.BORDER_RELEASE_SRC_DIR;
|
|
39408
|
+
return RELEASE_FINGERPRINT_SOURCES.map(({ dir, base }) => {
|
|
39409
|
+
if (seam !== void 0) return join11(seam, base);
|
|
39410
|
+
const live = join11(here, "..", dir, base);
|
|
39411
|
+
if (existsSync7(live)) return live;
|
|
39412
|
+
return resolveAsset(import.meta.url, ["release-src", base]);
|
|
39413
|
+
});
|
|
39414
|
+
}
|
|
38903
39415
|
function resolvePromptTemplatePath(env2 = process.env) {
|
|
38904
39416
|
return env2.BORDER_PROMPT_TEMPLATE_PATH ?? resolveAsset(import.meta.url, ["prompts", "llm-review.md"]);
|
|
38905
39417
|
}
|
|
@@ -38918,7 +39430,11 @@ function computeConfigDigest(load) {
|
|
|
38918
39430
|
async function computeCheckRulesHash(input2) {
|
|
38919
39431
|
const template = resolvePromptTemplatePath(input2.env ?? process.env);
|
|
38920
39432
|
return computeRulesHash({
|
|
38921
|
-
bundledRulePaths: [
|
|
39433
|
+
bundledRulePaths: [
|
|
39434
|
+
GITLEAKS_VENDORED_CONFIG,
|
|
39435
|
+
...resolveResidueFingerprintFiles(input2.env ?? process.env),
|
|
39436
|
+
...resolveReleaseFingerprintFiles(input2.env ?? process.env)
|
|
39437
|
+
],
|
|
38922
39438
|
configDigest: input2.configDigest,
|
|
38923
39439
|
engineVersions: input2.engineVersions,
|
|
38924
39440
|
promptTemplatePaths: existsSync7(template) ? [template] : []
|
|
@@ -39187,23 +39703,24 @@ function renderReportMd(report) {
|
|
|
39187
39703
|
}
|
|
39188
39704
|
|
|
39189
39705
|
// src/ledger/freshness.ts
|
|
39190
|
-
import { spawnSync as
|
|
39706
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
39191
39707
|
import { createHash as createHash7 } from "node:crypto";
|
|
39192
|
-
import { mkdirSync as mkdirSync5, readFileSync as
|
|
39193
|
-
import { join as
|
|
39708
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, readdirSync as readdirSync4, rmSync as rmSync5 } from "node:fs";
|
|
39709
|
+
import { join as join13 } from "node:path";
|
|
39710
|
+
import { gunzipSync as gunzipSync2 } from "node:zlib";
|
|
39194
39711
|
|
|
39195
39712
|
// src/artifacts/rubygems.ts
|
|
39196
39713
|
import { createHash as createHash6 } from "node:crypto";
|
|
39197
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as
|
|
39198
|
-
import { join as
|
|
39199
|
-
import { spawnSync as
|
|
39714
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, readdirSync as readdirSync3, rmSync as rmSync4 } from "node:fs";
|
|
39715
|
+
import { join as join12, relative as relative3 } from "node:path";
|
|
39716
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
39200
39717
|
import { gunzipSync } from "node:zlib";
|
|
39201
39718
|
|
|
39202
39719
|
// src/artifacts/residueGem.ts
|
|
39203
|
-
import { basename } from "node:path";
|
|
39720
|
+
import { basename as basename2 } from "node:path";
|
|
39204
39721
|
var GEM_WORDING = { what: "gem install-time", ledger: "gem" };
|
|
39205
39722
|
function gemPersistenceHit(rel, text) {
|
|
39206
|
-
if (GEM_PERSISTENCE_BASENAMES.includes(
|
|
39723
|
+
if (GEM_PERSISTENCE_BASENAMES.includes(basename2(rel))) {
|
|
39207
39724
|
return {
|
|
39208
39725
|
rule: RESIDUE_T4_RULE,
|
|
39209
39726
|
severity: "CRITICAL",
|
|
@@ -39449,7 +39966,7 @@ function residueGemHits(pkgDir, rels, metadata) {
|
|
|
39449
39966
|
const rel = normalizeRel(raw);
|
|
39450
39967
|
const text = readText(pkgDir, rel);
|
|
39451
39968
|
if (text === null) continue;
|
|
39452
|
-
const base =
|
|
39969
|
+
const base = basename2(rel);
|
|
39453
39970
|
const isExtconf = base === GEM_EXTCONF_BASENAME;
|
|
39454
39971
|
const persistence = gemPersistenceHit(rel, text);
|
|
39455
39972
|
if (persistence !== null) hits.push(persistence);
|
|
@@ -39548,7 +40065,7 @@ function listFiles2(dir) {
|
|
|
39548
40065
|
while (stack.length > 0) {
|
|
39549
40066
|
const cur = stack.pop();
|
|
39550
40067
|
for (const ent of readdirSync3(cur, { withFileTypes: true })) {
|
|
39551
|
-
const p =
|
|
40068
|
+
const p = join12(cur, ent.name);
|
|
39552
40069
|
if (ent.isDirectory()) stack.push(p);
|
|
39553
40070
|
else if (ent.isFile()) out.push(p);
|
|
39554
40071
|
}
|
|
@@ -39561,11 +40078,11 @@ function treeGemspecCandidates(repoDir) {
|
|
|
39561
40078
|
while (stack.length > 0) {
|
|
39562
40079
|
const cur = stack.pop();
|
|
39563
40080
|
for (const ent of readdirSync3(cur, { withFileTypes: true })) {
|
|
39564
|
-
const p =
|
|
40081
|
+
const p = join12(cur, ent.name);
|
|
39565
40082
|
if (ent.isDirectory()) {
|
|
39566
40083
|
if (!SKIP_DIRECTORIES2.has(ent.name)) stack.push(p);
|
|
39567
40084
|
} else if (ent.isFile() && ent.name.toLowerCase().endsWith(".gemspec")) {
|
|
39568
|
-
out.push({ path: relative3(repoDir, p), body:
|
|
40085
|
+
out.push({ path: relative3(repoDir, p), body: readFileSync7(p, "utf8") });
|
|
39569
40086
|
}
|
|
39570
40087
|
}
|
|
39571
40088
|
}
|
|
@@ -39597,7 +40114,7 @@ async function runRubygemsArtifactStage(o) {
|
|
|
39597
40114
|
throw e;
|
|
39598
40115
|
}
|
|
39599
40116
|
const gemName = `${identity.name}-${identity.version}.gem`;
|
|
39600
|
-
const versionCheck =
|
|
40117
|
+
const versionCheck = spawnSync6(bin, ["--version"], {
|
|
39601
40118
|
encoding: "utf8",
|
|
39602
40119
|
stdio: "ignore",
|
|
39603
40120
|
...o.env === void 0 ? {} : { env: { ...o.env } }
|
|
@@ -39608,11 +40125,11 @@ async function runRubygemsArtifactStage(o) {
|
|
|
39608
40125
|
);
|
|
39609
40126
|
}
|
|
39610
40127
|
ensureStateDir(repoDir);
|
|
39611
|
-
const stateDir =
|
|
39612
|
-
const distDir =
|
|
40128
|
+
const stateDir = join12(repoDir, BORDER_STATE_DIR);
|
|
40129
|
+
const distDir = join12(stateDir, "dist");
|
|
39613
40130
|
mkdirSync4(distDir, { recursive: true });
|
|
39614
|
-
const dist =
|
|
39615
|
-
const result =
|
|
40131
|
+
const dist = join12(distDir, gemName);
|
|
40132
|
+
const result = spawnSync6(bin, ["build", candidates[0].path, "-o", dist], {
|
|
39616
40133
|
cwd: repoDir,
|
|
39617
40134
|
encoding: "utf8",
|
|
39618
40135
|
timeout: o.gemTimeoutMs ?? 12e4,
|
|
@@ -39627,10 +40144,10 @@ async function runRubygemsArtifactStage(o) {
|
|
|
39627
40144
|
if (!existsSync8(dist)) {
|
|
39628
40145
|
throw new EngineRunError(`gem build produced no ${gemName} in ${relative3(repoDir, distDir)} \u2014 failing closed (was the gemspec's literal name/version re-read?)`, null);
|
|
39629
40146
|
}
|
|
39630
|
-
const bytes =
|
|
40147
|
+
const bytes = readFileSync7(dist);
|
|
39631
40148
|
const sha2563 = createHash6("sha256").update(bytes).digest("hex");
|
|
39632
40149
|
const findings = [];
|
|
39633
|
-
const extractDir =
|
|
40150
|
+
const extractDir = join12(stateDir, "tmp", sha2563.slice(0, 8));
|
|
39634
40151
|
rmSync4(extractDir, { recursive: true, force: true });
|
|
39635
40152
|
mkdirSync4(extractDir, { recursive: true });
|
|
39636
40153
|
const dataDir = `${extractDir}-data`;
|
|
@@ -39669,362 +40186,240 @@ async function runRubygemsArtifactStage(o) {
|
|
|
39669
40186
|
if (members2.has("metadata.gz") && members2.has("data.tar.gz")) {
|
|
39670
40187
|
let metadata;
|
|
39671
40188
|
try {
|
|
39672
|
-
metadata = gunzipSync(
|
|
40189
|
+
metadata = gunzipSync(readFileSync7(join12(extractDir, "metadata.gz"))).toString("utf8");
|
|
39673
40190
|
} catch {
|
|
39674
40191
|
throw new EngineRunError(`${gemName}: metadata.gz is not gunzip-readable \u2014 the extensions declaration cannot be verified, failing closed`, null);
|
|
39675
40192
|
}
|
|
39676
40193
|
rmSync4(dataDir, { recursive: true, force: true });
|
|
39677
40194
|
mkdirSync4(dataDir, { recursive: true });
|
|
39678
|
-
extractArchive(
|
|
40195
|
+
extractArchive(join12(extractDir, "data.tar.gz"), dataDir, {
|
|
39679
40196
|
...o.env !== void 0 ? { env: o.env } : {},
|
|
39680
|
-
...o.timeoutMs !== void 0 ? { timeoutMs: o.timeoutMs } : {}
|
|
39681
|
-
});
|
|
39682
|
-
const dataRels = listFiles2(dataDir).map((p) => relative3(dataDir, p));
|
|
39683
|
-
let hits;
|
|
39684
|
-
try {
|
|
39685
|
-
hits = residueGemHits(dataDir, dataRels, metadata);
|
|
39686
|
-
} catch (e) {
|
|
39687
|
-
throw new EngineRunError(`${gemName}: ${e instanceof Error ? e.message : String(e)} \u2014 failing closed`, null);
|
|
39688
|
-
}
|
|
39689
|
-
for (const f of residueFindings(hits, {
|
|
39690
|
-
root: "data.tar.gz",
|
|
39691
|
-
sep: "!",
|
|
39692
|
-
identity: gemName,
|
|
39693
|
-
...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
|
|
39694
|
-
})) {
|
|
39695
|
-
findings.push(f);
|
|
39696
|
-
}
|
|
39697
|
-
|
|
39698
|
-
|
|
39699
|
-
|
|
39700
|
-
|
|
39701
|
-
|
|
39702
|
-
|
|
39703
|
-
|
|
39704
|
-
|
|
39705
|
-
}
|
|
39706
|
-
|
|
39707
|
-
|
|
39708
|
-
// src/ledger/freshness.ts
|
|
39709
|
-
function artifactTargets() {
|
|
39710
|
-
return publishChannels().map((c) => c.id);
|
|
39711
|
-
}
|
|
39712
|
-
function sha256File(path4) {
|
|
39713
|
-
return createHash7("sha256").update(readFileSync7(path4)).digest("hex");
|
|
39714
|
-
}
|
|
39715
|
-
var packSeq = 0;
|
|
39716
|
-
function packNpmArtifacts(repoDir, o = {}) {
|
|
39717
|
-
packSeq += 1;
|
|
39718
|
-
const dest = join11(repoDir, BORDER_STATE_DIR, "tmp", `pack-${String(process.pid)}-${String(packSeq)}`);
|
|
39719
|
-
mkdirSync5(dest, { recursive: true });
|
|
39720
|
-
try {
|
|
39721
|
-
const r = spawnSync6("npm", ["pack", "--ignore-scripts", "--silent", `--pack-destination=${dest}`], {
|
|
39722
|
-
cwd: repoDir,
|
|
39723
|
-
encoding: "utf8",
|
|
39724
|
-
env: { ...o.env ?? process.env },
|
|
39725
|
-
timeout: 12e4
|
|
39726
|
-
});
|
|
39727
|
-
if (r.error !== void 0 || r.status !== 0) return null;
|
|
39728
|
-
const files = readdirSync4(dest).filter((f) => f.endsWith(".tgz")).sort();
|
|
39729
|
-
if (files.length === 0) return null;
|
|
39730
|
-
return files.map((f) => ({ file: f, sha256: sha256File(join11(dest, f)) }));
|
|
39731
|
-
} finally {
|
|
39732
|
-
rmSync5(dest, { recursive: true, force: true });
|
|
39733
|
-
}
|
|
39734
|
-
}
|
|
39735
|
-
function packCrateArtifacts(repoDir, o = {}) {
|
|
39736
|
-
packSeq += 1;
|
|
39737
|
-
const dest = join11(repoDir, BORDER_STATE_DIR, "tmp", `cargo-pack-${String(process.pid)}-${String(packSeq)}`);
|
|
39738
|
-
mkdirSync5(dest, { recursive: true });
|
|
39739
|
-
try {
|
|
39740
|
-
const r = spawnSync6("cargo", ["package", "--allow-dirty", "--no-verify"], {
|
|
39741
|
-
cwd: repoDir,
|
|
39742
|
-
encoding: "utf8",
|
|
39743
|
-
env: { ...o.env ?? process.env, CARGO_TARGET_DIR: dest },
|
|
39744
|
-
timeout: 12e4
|
|
39745
|
-
});
|
|
39746
|
-
if (r.error !== void 0 || r.status !== 0) return null;
|
|
39747
|
-
const dir = join11(dest, "package");
|
|
39748
|
-
const files = readdirSync4(dir).filter((f) => f.endsWith(".crate")).sort();
|
|
39749
|
-
if (files.length === 0) return null;
|
|
39750
|
-
return files.map((f) => ({ file: f, sha256: sha256File(join11(dir, f)) }));
|
|
39751
|
-
} finally {
|
|
39752
|
-
rmSync5(dest, { recursive: true, force: true });
|
|
39753
|
-
}
|
|
39754
|
-
}
|
|
39755
|
-
function packRubygemsArtifacts(repoDir, o = {}) {
|
|
39756
|
-
packSeq += 1;
|
|
39757
|
-
const ident = singleTreeGemspec(repoDir);
|
|
39758
|
-
if (ident === null) return null;
|
|
39759
|
-
const dest = join11(repoDir, BORDER_STATE_DIR, "tmp", `gem-pack-${String(process.pid)}-${String(packSeq)}`);
|
|
39760
|
-
mkdirSync5(dest, { recursive: true });
|
|
39761
|
-
try {
|
|
39762
|
-
const gemName = `${ident.name}-${ident.version}.gem`;
|
|
39763
|
-
const r = spawnSync6("gem", ["build", ident.path, "-o", join11(dest, gemName)], {
|
|
39764
|
-
cwd: repoDir,
|
|
39765
|
-
encoding: "utf8",
|
|
39766
|
-
env: { ...o.env ?? process.env },
|
|
39767
|
-
timeout: 12e4
|
|
39768
|
-
});
|
|
39769
|
-
if (r.error !== void 0 || r.status !== 0) return null;
|
|
39770
|
-
const files = readdirSync4(dest).filter((f) => f.endsWith(".gem")).sort();
|
|
39771
|
-
if (files.length === 0) return null;
|
|
39772
|
-
return files.map((f) => ({ file: f, sha256: sha256File(join11(dest, f)) }));
|
|
39773
|
-
} finally {
|
|
39774
|
-
rmSync5(dest, { recursive: true, force: true });
|
|
39775
|
-
}
|
|
39776
|
-
}
|
|
39777
|
-
var REPACKERS = {
|
|
39778
|
-
npm: packNpmArtifacts,
|
|
39779
|
-
crates: packCrateArtifacts,
|
|
39780
|
-
rubygems: packRubygemsArtifacts
|
|
39781
|
-
};
|
|
39782
|
-
function repackChannels() {
|
|
39783
|
-
return publishChannels().filter((c) => c.freshness === "repack").map((c) => c.id);
|
|
39784
|
-
}
|
|
39785
|
-
function sameArtifacts(a, b, extensions) {
|
|
39786
|
-
const digests = (xs) => xs.filter((x) => extensions.some((ext) => x.file.slice(x.file.lastIndexOf("/") + 1).endsWith(ext))).map((x) => `${x.file.slice(x.file.lastIndexOf("/") + 1)}\0${x.sha256}`).sort();
|
|
39787
|
-
const ka = digests(a);
|
|
39788
|
-
const kb = digests(b);
|
|
39789
|
-
return ka.length === kb.length && ka.every((x, i) => x === kb[i]);
|
|
39790
|
-
}
|
|
39791
|
-
function verifyArtifactFreshness(record2, ctx, repoDir, o = {}) {
|
|
39792
|
-
if (!record2.effectiveTargets.some((t) => artifactTargets().includes(t))) return true;
|
|
39793
|
-
if (ctx.dirty) return false;
|
|
39794
|
-
const needed = repackChannels().filter((t) => record2.effectiveTargets.includes(t));
|
|
39795
|
-
if (needed.length === 0) return true;
|
|
39796
|
-
if (record2.artifacts === null) return false;
|
|
39797
|
-
const now = [];
|
|
39798
|
-
for (const id of needed) {
|
|
39799
|
-
const pack = REPACKERS[id];
|
|
39800
|
-
if (pack === void 0) return false;
|
|
39801
|
-
const got = pack(repoDir, o);
|
|
39802
|
-
if (got === null) return false;
|
|
39803
|
-
now.push(...got);
|
|
39804
|
-
}
|
|
39805
|
-
return sameArtifacts(
|
|
39806
|
-
record2.artifacts,
|
|
39807
|
-
now,
|
|
39808
|
-
needed.flatMap((id) => {
|
|
39809
|
-
const channel = publishChannels().find((c) => c.id === id);
|
|
39810
|
-
return channel?.artifactExtensions ?? [];
|
|
39811
|
-
})
|
|
39812
|
-
);
|
|
39813
|
-
}
|
|
39814
|
-
|
|
39815
|
-
// src/ledger/records.ts
|
|
39816
|
-
import { appendFileSync, readFileSync as readFileSync8 } from "node:fs";
|
|
39817
|
-
import { join as join12 } from "node:path";
|
|
39818
|
-
var LEDGER_FILE = "ledger.jsonl";
|
|
39819
|
-
var RUNS_SUBDIR = "runs";
|
|
39820
|
-
var HEX64 = /^[0-9a-f]{64}$/;
|
|
39821
|
-
var HEX40 = /^[0-9a-f]{40}$/;
|
|
39822
|
-
var HEX8 = /^[0-9a-f]{8}$/;
|
|
39823
|
-
var PUSH_CONFIRMED_VIA = ["ls-remote", "npm-view", "pypi-json", "crates-json", "rubygems-json"];
|
|
39824
|
-
function ledgerPath(repoDir) {
|
|
39825
|
-
return join12(repoDir, BORDER_STATE_DIR, LEDGER_FILE);
|
|
39826
|
-
}
|
|
39827
|
-
function runsDir(repoDir) {
|
|
39828
|
-
return join12(repoDir, BORDER_STATE_DIR, RUNS_SUBDIR);
|
|
39829
|
-
}
|
|
39830
|
-
function formatSkipLine(record2) {
|
|
39831
|
-
return `SKIP ${record2.key8} \u2014 PASS ${record2.ts} report ${record2.reportPath}`;
|
|
39832
|
-
}
|
|
39833
|
-
function buildPushRecord(i) {
|
|
39834
|
-
return {
|
|
39835
|
-
t: "push",
|
|
39836
|
-
key: i.key,
|
|
39837
|
-
target: i.target,
|
|
39838
|
-
remoteName: i.remoteName,
|
|
39839
|
-
url: sanitizeUrl(i.url),
|
|
39840
|
-
localSha: i.localSha,
|
|
39841
|
-
...i.remoteSha !== void 0 ? { remoteSha: i.remoteSha } : {},
|
|
39842
|
-
...i.version !== void 0 ? { version: i.version } : {},
|
|
39843
|
-
confirmedVia: i.confirmedVia,
|
|
39844
|
-
ts: i.ts ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
39845
|
-
};
|
|
39846
|
-
}
|
|
39847
|
-
function buildRoundtripRecord(i) {
|
|
39848
|
-
if (!HEX64.test(i.artifactSha256)) bad("artifactSha256 is not hex-shaped");
|
|
39849
|
-
if (!HEX64.test(i.rulesHash)) bad("rulesHash is not hex-shaped");
|
|
39850
|
-
if (i.verdict !== "clean" && i.verdict !== "residue") bad(`verdict must be clean or residue, got '${i.verdict}'`);
|
|
39851
|
-
if (!Number.isInteger(i.rows) || i.rows < 0) bad("rows must be a non-negative integer");
|
|
39852
|
-
return {
|
|
39853
|
-
t: "roundtrip",
|
|
39854
|
-
artifactSha256: i.artifactSha256,
|
|
39855
|
-
verdict: i.verdict,
|
|
39856
|
-
rulesHash: i.rulesHash,
|
|
39857
|
-
rows: i.rows,
|
|
39858
|
-
ts: i.ts ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
39859
|
-
};
|
|
39860
|
-
}
|
|
39861
|
-
function bad(msg) {
|
|
39862
|
-
throw new Error(msg);
|
|
39863
|
-
}
|
|
39864
|
-
function needString(o, field) {
|
|
39865
|
-
const v = o[field];
|
|
39866
|
-
if (typeof v !== "string" || v === "") bad(`${field} must be a non-empty string`);
|
|
39867
|
-
return v;
|
|
39868
|
-
}
|
|
39869
|
-
function needHex(o, field, re) {
|
|
39870
|
-
const v = needString(o, field);
|
|
39871
|
-
if (!re.test(v)) bad(`${field} is not hex-shaped`);
|
|
39872
|
-
return v;
|
|
39873
|
-
}
|
|
39874
|
-
function needStringArray(o, field) {
|
|
39875
|
-
const v = o[field];
|
|
39876
|
-
if (!Array.isArray(v) || !v.every((x) => typeof x === "string")) bad(`${field} must be an array of strings`);
|
|
39877
|
-
return v;
|
|
39878
|
-
}
|
|
39879
|
-
function parseCounts(value) {
|
|
39880
|
-
if (typeof value !== "object" || value === null) bad("counts must be an object");
|
|
39881
|
-
const o = value;
|
|
39882
|
-
for (const field of ["INFO", "LOW", "MEDIUM", "HIGH", "CRITICAL", "total", "blocking", "warnings"]) {
|
|
39883
|
-
if (typeof o[field] !== "number" || !Number.isInteger(o[field])) bad(`counts.${field} must be an integer`);
|
|
39884
|
-
}
|
|
39885
|
-
return o;
|
|
39886
|
-
}
|
|
39887
|
-
function parseArtifacts(o) {
|
|
39888
|
-
if (o.artifacts === null) return null;
|
|
39889
|
-
const v = o.artifacts;
|
|
39890
|
-
if (!Array.isArray(v)) bad("artifacts must be null or an array");
|
|
39891
|
-
return v.map((item, i) => {
|
|
39892
|
-
if (typeof item !== "object" || item === null) bad(`artifacts[${String(i)}] must be an object`);
|
|
39893
|
-
const a = item;
|
|
39894
|
-
return { file: needString(a, "file"), sha256: needHex(a, "sha256", HEX64) };
|
|
39895
|
-
});
|
|
39896
|
-
}
|
|
39897
|
-
function parseLedgerRecord(value) {
|
|
39898
|
-
if (typeof value !== "object" || value === null) bad("record must be an object");
|
|
39899
|
-
const o = value;
|
|
39900
|
-
const t = o.t;
|
|
39901
|
-
if (t === "check") {
|
|
39902
|
-
const key = needHex(o, "key", HEX64);
|
|
39903
|
-
const key8 = needHex(o, "key8", HEX8);
|
|
39904
|
-
if (key.slice(0, 8) !== key8) bad("key8 must be the first 8 hex chars of key");
|
|
39905
|
-
const verdict = needString(o, "verdict");
|
|
39906
|
-
if (verdict !== "PASS" && verdict !== "FAIL") bad(`verdict must be PASS or FAIL, got '${verdict}'`);
|
|
39907
|
-
if (o.degraded !== false) bad("degraded must be false \u2014 degraded runs are never ledgered");
|
|
39908
|
-
if (typeof o.llm !== "boolean") bad("llm must be a boolean");
|
|
39909
|
-
return {
|
|
39910
|
-
t: "check",
|
|
39911
|
-
key,
|
|
39912
|
-
key8,
|
|
39913
|
-
head: needHex(o, "head", HEX40),
|
|
39914
|
-
dirtyDigest: needHex(o, "dirtyDigest", HEX64),
|
|
39915
|
-
refSetHash: needHex(o, "refSetHash", HEX64),
|
|
39916
|
-
exposureSet: needStringArray(o, "exposureSet"),
|
|
39917
|
-
effectiveTargets: needStringArray(o, "effectiveTargets"),
|
|
39918
|
-
rulesHash: needHex(o, "rulesHash", HEX64),
|
|
39919
|
-
artifacts: parseArtifacts(o),
|
|
39920
|
-
llm: o.llm,
|
|
39921
|
-
verdict,
|
|
39922
|
-
counts: parseCounts(o.counts),
|
|
39923
|
-
reportPath: needString(o, "reportPath"),
|
|
39924
|
-
degraded: false,
|
|
39925
|
-
ts: needString(o, "ts")
|
|
39926
|
-
};
|
|
39927
|
-
}
|
|
39928
|
-
if (t === "push") {
|
|
39929
|
-
const confirmedVia = needString(o, "confirmedVia");
|
|
39930
|
-
if (!PUSH_CONFIRMED_VIA.includes(confirmedVia)) {
|
|
39931
|
-
bad(`confirmedVia must be one of ${PUSH_CONFIRMED_VIA.join("|")}`);
|
|
39932
|
-
}
|
|
39933
|
-
const remoteSha = o.remoteSha;
|
|
39934
|
-
const version2 = o.version;
|
|
39935
|
-
if (remoteSha === void 0 && version2 === void 0) bad("push record needs remoteSha or version");
|
|
39936
|
-
if (remoteSha !== void 0 && typeof remoteSha !== "string") bad("remoteSha must be a string");
|
|
39937
|
-
if (version2 !== void 0 && typeof version2 !== "string") bad("version must be a string");
|
|
39938
|
-
return {
|
|
39939
|
-
t: "push",
|
|
39940
|
-
key: needHex(o, "key", HEX64),
|
|
39941
|
-
target: needString(o, "target"),
|
|
39942
|
-
remoteName: needString(o, "remoteName"),
|
|
39943
|
-
url: needString(o, "url"),
|
|
39944
|
-
localSha: needString(o, "localSha"),
|
|
39945
|
-
...remoteSha !== void 0 ? { remoteSha } : {},
|
|
39946
|
-
...version2 !== void 0 ? { version: version2 } : {},
|
|
39947
|
-
confirmedVia,
|
|
39948
|
-
ts: needString(o, "ts")
|
|
39949
|
-
};
|
|
39950
|
-
}
|
|
39951
|
-
if (t === "roundtrip") {
|
|
39952
|
-
const verdict = needString(o, "verdict");
|
|
39953
|
-
if (verdict !== "clean" && verdict !== "residue") bad(`verdict must be clean or residue, got '${verdict}'`);
|
|
39954
|
-
const rows = o.rows;
|
|
39955
|
-
if (typeof rows !== "number" || !Number.isInteger(rows) || rows < 0) bad("rows must be a non-negative integer");
|
|
39956
|
-
return {
|
|
39957
|
-
t: "roundtrip",
|
|
39958
|
-
artifactSha256: needHex(o, "artifactSha256", HEX64),
|
|
39959
|
-
verdict,
|
|
39960
|
-
rulesHash: needHex(o, "rulesHash", HEX64),
|
|
39961
|
-
rows,
|
|
39962
|
-
ts: needString(o, "ts")
|
|
39963
|
-
};
|
|
40197
|
+
...o.timeoutMs !== void 0 ? { timeoutMs: o.timeoutMs } : {}
|
|
40198
|
+
});
|
|
40199
|
+
const dataRels = listFiles2(dataDir).map((p) => relative3(dataDir, p));
|
|
40200
|
+
let hits;
|
|
40201
|
+
try {
|
|
40202
|
+
hits = residueGemHits(dataDir, dataRels, metadata);
|
|
40203
|
+
} catch (e) {
|
|
40204
|
+
throw new EngineRunError(`${gemName}: ${e instanceof Error ? e.message : String(e)} \u2014 failing closed`, null);
|
|
40205
|
+
}
|
|
40206
|
+
for (const f of residueFindings(hits, {
|
|
40207
|
+
root: "data.tar.gz",
|
|
40208
|
+
sep: "!",
|
|
40209
|
+
identity: gemName,
|
|
40210
|
+
...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
|
|
40211
|
+
})) {
|
|
40212
|
+
findings.push(f);
|
|
40213
|
+
}
|
|
40214
|
+
findings.push(...gemCoherenceFindings({
|
|
40215
|
+
specName: identity.name,
|
|
40216
|
+
specVersion: identity.version,
|
|
40217
|
+
metadataText: metadata,
|
|
40218
|
+
identity: gemName,
|
|
40219
|
+
...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
|
|
40220
|
+
}));
|
|
40221
|
+
}
|
|
40222
|
+
} finally {
|
|
40223
|
+
removeSandbox(extractDir);
|
|
40224
|
+
removeSandbox(dataDir);
|
|
39964
40225
|
}
|
|
39965
|
-
return
|
|
40226
|
+
return {
|
|
40227
|
+
findings,
|
|
40228
|
+
artifact: { file: `${BORDER_STATE_DIR}/dist/${gemName}`, sha256: sha2563, bytes: bytes.length }
|
|
40229
|
+
};
|
|
39966
40230
|
}
|
|
39967
|
-
|
|
39968
|
-
|
|
40231
|
+
|
|
40232
|
+
// src/ledger/freshness.ts
|
|
40233
|
+
function artifactTargets() {
|
|
40234
|
+
return publishChannels().map((c) => c.id);
|
|
39969
40235
|
}
|
|
39970
|
-
function
|
|
39971
|
-
|
|
40236
|
+
function sha256File(path4) {
|
|
40237
|
+
return createHash7("sha256").update(readFileSync8(path4)).digest("hex");
|
|
40238
|
+
}
|
|
40239
|
+
function octalField(block, start, end) {
|
|
40240
|
+
const text = block.subarray(start, end).toString("utf8").replace(/[\0 ].*$/, "");
|
|
40241
|
+
if (text === "") return 0;
|
|
40242
|
+
if (!/^[0-7]+$/.test(text)) return null;
|
|
40243
|
+
return Number.parseInt(text, 8);
|
|
40244
|
+
}
|
|
40245
|
+
function tarEntries(buf) {
|
|
40246
|
+
const magic = buf.subarray(257, 263).toString("latin1");
|
|
40247
|
+
if (!magic.startsWith("ustar")) return null;
|
|
40248
|
+
const out = [];
|
|
40249
|
+
let off = 0;
|
|
40250
|
+
while (off + 512 <= buf.length) {
|
|
40251
|
+
const header = buf.subarray(off, off + 512);
|
|
40252
|
+
if (header.every((b) => b === 0)) return out;
|
|
40253
|
+
const size = octalField(header, 124, 136);
|
|
40254
|
+
if (size === null) return null;
|
|
40255
|
+
const mode = octalField(header, 100, 108);
|
|
40256
|
+
if (mode === null) return null;
|
|
40257
|
+
const typeFlag = header[156] ?? 48;
|
|
40258
|
+
const body = off + 512;
|
|
40259
|
+
if (body + size > buf.length) return null;
|
|
40260
|
+
const name = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
|
|
40261
|
+
const linkName = header.subarray(157, 257).toString("utf8").replace(/\0.*$/, "");
|
|
40262
|
+
if (typeFlag !== 120 && typeFlag !== 103 && typeFlag !== 76 && typeFlag !== 75) {
|
|
40263
|
+
out.push({ name, member: { content: buf.subarray(body, body + size), typeFlag, mode, linkName } });
|
|
40264
|
+
}
|
|
40265
|
+
off = body + Math.ceil(size / 512) * 512;
|
|
40266
|
+
}
|
|
40267
|
+
return out;
|
|
40268
|
+
}
|
|
40269
|
+
var GEM_METADATA_STAMP_RE = /^(rubygems_version|gem_version|date):/;
|
|
40270
|
+
var GEM_LEGACY_EMPTY_RE = /^(autorequire|description|email|homepage|post_install_message|signing_key):[ ]*$/;
|
|
40271
|
+
function sha256Hex(buf) {
|
|
40272
|
+
return createHash7("sha256").update(buf).digest("hex");
|
|
40273
|
+
}
|
|
40274
|
+
function gemContentDigest(bytes) {
|
|
40275
|
+
const outer = tarEntries(bytes);
|
|
40276
|
+
if (outer === null) return null;
|
|
40277
|
+
const meta3 = outer.find((e) => e.name === "metadata.gz")?.member;
|
|
40278
|
+
const data = outer.find((e) => e.name === "data.tar.gz")?.member;
|
|
40279
|
+
if (meta3 === void 0 || data === void 0) return null;
|
|
40280
|
+
let metadata;
|
|
40281
|
+
let inner;
|
|
39972
40282
|
try {
|
|
39973
|
-
|
|
39974
|
-
|
|
39975
|
-
|
|
39976
|
-
|
|
40283
|
+
metadata = gunzipSync2(meta3.content).toString("utf8");
|
|
40284
|
+
inner = gunzipSync2(data.content);
|
|
40285
|
+
} catch {
|
|
40286
|
+
return null;
|
|
39977
40287
|
}
|
|
39978
|
-
const
|
|
39979
|
-
|
|
39980
|
-
|
|
39981
|
-
|
|
39982
|
-
|
|
39983
|
-
|
|
39984
|
-
|
|
39985
|
-
|
|
39986
|
-
warnings.push(`ledger line ${String(i + 1)} unreadable (${reason.slice(0, 160)}) \u2014 line skipped`);
|
|
39987
|
-
}
|
|
39988
|
-
});
|
|
39989
|
-
return { records, warnings };
|
|
40288
|
+
const innerEntries = tarEntries(inner);
|
|
40289
|
+
if (innerEntries === null) return null;
|
|
40290
|
+
const chain = createHash7("sha256");
|
|
40291
|
+
for (const { name, member: m } of innerEntries) {
|
|
40292
|
+
chain.update(name).update("\0").update(String(m.typeFlag)).update("\0").update(String(m.mode & 73)).update("\0").update(m.linkName).update("\0").update(sha256Hex(m.content)).update("\0");
|
|
40293
|
+
}
|
|
40294
|
+
const metaStripped = metadata.split("\n").filter((line) => !GEM_METADATA_STAMP_RE.test(line) && !GEM_LEGACY_EMPTY_RE.test(line)).join("\n");
|
|
40295
|
+
return createHash7("sha256").update("border-gem-content-v1\0").update(chain.digest("hex")).update("\0").update(metaStripped).digest("hex");
|
|
39990
40296
|
}
|
|
39991
|
-
|
|
39992
|
-
|
|
39993
|
-
|
|
39994
|
-
|
|
40297
|
+
var packSeq = 0;
|
|
40298
|
+
function packNpmArtifacts(repoDir, o = {}) {
|
|
40299
|
+
packSeq += 1;
|
|
40300
|
+
const dest = join13(repoDir, BORDER_STATE_DIR, "tmp", `pack-${String(process.pid)}-${String(packSeq)}`);
|
|
40301
|
+
mkdirSync5(dest, { recursive: true });
|
|
40302
|
+
try {
|
|
40303
|
+
const r = spawnSync7("npm", ["pack", "--ignore-scripts", "--silent", `--pack-destination=${dest}`], {
|
|
40304
|
+
cwd: repoDir,
|
|
40305
|
+
encoding: "utf8",
|
|
40306
|
+
env: { ...o.env ?? process.env },
|
|
40307
|
+
timeout: 12e4
|
|
40308
|
+
});
|
|
40309
|
+
if (r.error !== void 0 || r.status !== 0) return null;
|
|
40310
|
+
const files = readdirSync4(dest).filter((f) => f.endsWith(".tgz")).sort();
|
|
40311
|
+
if (files.length === 0) return null;
|
|
40312
|
+
return files.map((f) => ({ file: f, sha256: sha256File(join13(dest, f)) }));
|
|
40313
|
+
} finally {
|
|
40314
|
+
rmSync5(dest, { recursive: true, force: true });
|
|
40315
|
+
}
|
|
39995
40316
|
}
|
|
39996
|
-
function
|
|
39997
|
-
|
|
39998
|
-
|
|
39999
|
-
|
|
40317
|
+
function packCrateArtifacts(repoDir, o = {}) {
|
|
40318
|
+
packSeq += 1;
|
|
40319
|
+
const dest = join13(repoDir, BORDER_STATE_DIR, "tmp", `cargo-pack-${String(process.pid)}-${String(packSeq)}`);
|
|
40320
|
+
mkdirSync5(dest, { recursive: true });
|
|
40321
|
+
try {
|
|
40322
|
+
const r = spawnSync7("cargo", ["package", "--allow-dirty", "--no-verify"], {
|
|
40323
|
+
cwd: repoDir,
|
|
40324
|
+
encoding: "utf8",
|
|
40325
|
+
env: { ...o.env ?? process.env, CARGO_TARGET_DIR: dest },
|
|
40326
|
+
timeout: 12e4
|
|
40327
|
+
});
|
|
40328
|
+
if (r.error !== void 0 || r.status !== 0) return null;
|
|
40329
|
+
const dir = join13(dest, "package");
|
|
40330
|
+
const files = readdirSync4(dir).filter((f) => f.endsWith(".crate")).sort();
|
|
40331
|
+
if (files.length === 0) return null;
|
|
40332
|
+
return files.map((f) => ({ file: f, sha256: sha256File(join13(dir, f)) }));
|
|
40333
|
+
} finally {
|
|
40334
|
+
rmSync5(dest, { recursive: true, force: true });
|
|
40000
40335
|
}
|
|
40001
|
-
return null;
|
|
40002
40336
|
}
|
|
40003
|
-
function
|
|
40004
|
-
|
|
40337
|
+
function packRubygemsArtifacts(repoDir, o = {}) {
|
|
40338
|
+
packSeq += 1;
|
|
40339
|
+
const ident = singleTreeGemspec(repoDir);
|
|
40340
|
+
if (ident === null) return null;
|
|
40341
|
+
const dest = join13(repoDir, BORDER_STATE_DIR, "tmp", `gem-pack-${String(process.pid)}-${String(packSeq)}`);
|
|
40342
|
+
mkdirSync5(dest, { recursive: true });
|
|
40343
|
+
try {
|
|
40344
|
+
const gemName = `${ident.name}-${ident.version}.gem`;
|
|
40345
|
+
const r = spawnSync7("gem", ["build", ident.path, "-o", join13(dest, gemName)], {
|
|
40346
|
+
cwd: repoDir,
|
|
40347
|
+
encoding: "utf8",
|
|
40348
|
+
env: { ...o.env ?? process.env },
|
|
40349
|
+
timeout: 12e4
|
|
40350
|
+
});
|
|
40351
|
+
if (r.error !== void 0 || r.status !== 0) return null;
|
|
40352
|
+
const files = readdirSync4(dest).filter((f) => f.endsWith(".gem")).sort();
|
|
40353
|
+
if (files.length === 0) return null;
|
|
40354
|
+
return files.map((f) => {
|
|
40355
|
+
const raw = readFileSync8(join13(dest, f));
|
|
40356
|
+
return { file: f, sha256: sha256Hex(raw), contentDigest: gemContentDigest(raw) };
|
|
40357
|
+
});
|
|
40358
|
+
} finally {
|
|
40359
|
+
rmSync5(dest, { recursive: true, force: true });
|
|
40360
|
+
}
|
|
40005
40361
|
}
|
|
40006
|
-
|
|
40007
|
-
|
|
40008
|
-
|
|
40362
|
+
var REPACKERS = {
|
|
40363
|
+
npm: packNpmArtifacts,
|
|
40364
|
+
crates: packCrateArtifacts,
|
|
40365
|
+
rubygems: packRubygemsArtifacts
|
|
40366
|
+
};
|
|
40367
|
+
function repackChannels() {
|
|
40368
|
+
return publishChannels().filter((c) => c.freshness === "repack").map((c) => c.id);
|
|
40009
40369
|
}
|
|
40010
|
-
function
|
|
40011
|
-
|
|
40012
|
-
|
|
40370
|
+
function gemParity(entry, repoDir) {
|
|
40371
|
+
if (entry.contentDigest !== void 0) return entry.contentDigest;
|
|
40372
|
+
try {
|
|
40373
|
+
return gemContentDigest(readFileSync8(join13(repoDir, entry.file)));
|
|
40374
|
+
} catch {
|
|
40375
|
+
return null;
|
|
40376
|
+
}
|
|
40013
40377
|
}
|
|
40014
|
-
function
|
|
40015
|
-
|
|
40378
|
+
function sameArtifacts(a, b, extensions, repoDir) {
|
|
40379
|
+
const digests = (xs) => {
|
|
40380
|
+
const out = [];
|
|
40381
|
+
for (const x of xs) {
|
|
40382
|
+
const base = x.file.slice(x.file.lastIndexOf("/") + 1);
|
|
40383
|
+
const ext = extensions.find((e) => base.endsWith(e));
|
|
40384
|
+
if (ext === void 0) continue;
|
|
40385
|
+
const parity = ext === ".gem" ? gemParity(x, repoDir) : x.sha256;
|
|
40386
|
+
if (parity === null) return null;
|
|
40387
|
+
out.push(`${base}\0${parity}`);
|
|
40388
|
+
}
|
|
40389
|
+
return out.sort();
|
|
40390
|
+
};
|
|
40391
|
+
const ka = digests(a);
|
|
40392
|
+
const kb = digests(b);
|
|
40393
|
+
return ka !== null && kb !== null && ka.length === kb.length && ka.every((x, i) => x === kb[i]);
|
|
40016
40394
|
}
|
|
40017
|
-
function
|
|
40018
|
-
|
|
40019
|
-
|
|
40020
|
-
|
|
40395
|
+
function verifyArtifactFreshness(record2, ctx, repoDir, o = {}) {
|
|
40396
|
+
if (!record2.effectiveTargets.some((t) => artifactTargets().includes(t))) return true;
|
|
40397
|
+
if (ctx.dirty) return false;
|
|
40398
|
+
const needed = repackChannels().filter((t) => record2.effectiveTargets.includes(t));
|
|
40399
|
+
if (needed.length === 0) return true;
|
|
40400
|
+
if (record2.artifacts === null) return false;
|
|
40401
|
+
const now = [];
|
|
40402
|
+
for (const id of needed) {
|
|
40403
|
+
const pack = REPACKERS[id];
|
|
40404
|
+
if (pack === void 0) return false;
|
|
40405
|
+
const got = pack(repoDir, o);
|
|
40406
|
+
if (got === null) return false;
|
|
40407
|
+
now.push(...got);
|
|
40021
40408
|
}
|
|
40022
|
-
return
|
|
40409
|
+
return sameArtifacts(
|
|
40410
|
+
record2.artifacts,
|
|
40411
|
+
now,
|
|
40412
|
+
needed.flatMap((id) => {
|
|
40413
|
+
const channel = publishChannels().find((c) => c.id === id);
|
|
40414
|
+
return channel?.artifactExtensions ?? [];
|
|
40415
|
+
}),
|
|
40416
|
+
repoDir
|
|
40417
|
+
);
|
|
40023
40418
|
}
|
|
40024
40419
|
|
|
40025
40420
|
// src/ledger/retention.ts
|
|
40026
40421
|
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync5, rmSync as rmSync6 } from "node:fs";
|
|
40027
|
-
import { join as
|
|
40422
|
+
import { join as join14 } from "node:path";
|
|
40028
40423
|
var KEEP_RUN_DIRS_PER_KEY8 = 20;
|
|
40029
40424
|
function codeOf3(err) {
|
|
40030
40425
|
return typeof err === "object" && err !== null ? err.code : void 0;
|
|
@@ -40035,13 +40430,13 @@ function runDirName(key8, ts) {
|
|
|
40035
40430
|
function uniqueRunDirName(repoDir, key8, ts) {
|
|
40036
40431
|
const base = runDirName(key8, ts);
|
|
40037
40432
|
let name = base;
|
|
40038
|
-
for (let n = 2; existsSync9(
|
|
40433
|
+
for (let n = 2; existsSync9(join14(runsDir(repoDir), name)) && n < 1e3; n += 1) {
|
|
40039
40434
|
name = `${base}-r${String(n)}`;
|
|
40040
40435
|
}
|
|
40041
40436
|
return name;
|
|
40042
40437
|
}
|
|
40043
40438
|
function ensureRunDir(repoDir, name) {
|
|
40044
|
-
const dir =
|
|
40439
|
+
const dir = join14(runsDir(repoDir), name);
|
|
40045
40440
|
mkdirSync6(dir, { recursive: true });
|
|
40046
40441
|
return dir;
|
|
40047
40442
|
}
|
|
@@ -40060,7 +40455,7 @@ function pruneRunDirs(repoDir, key8, keep = KEEP_RUN_DIRS_PER_KEY8) {
|
|
|
40060
40455
|
const mine = entries.filter((e) => e.startsWith(prefix)).sort();
|
|
40061
40456
|
const doomed = mine.slice(0, Math.max(0, mine.length - keep));
|
|
40062
40457
|
for (const dir of doomed) {
|
|
40063
|
-
rmSync6(
|
|
40458
|
+
rmSync6(join14(runsDir(repoDir), dir), { recursive: true, force: true });
|
|
40064
40459
|
}
|
|
40065
40460
|
return doomed;
|
|
40066
40461
|
}
|
|
@@ -40126,9 +40521,9 @@ function recordCheckRun(i) {
|
|
|
40126
40521
|
const envOpt = i.env !== void 0 ? { env: i.env } : {};
|
|
40127
40522
|
const artifacts = i.artifacts ?? (i.report.verdict === "PASS" && !i.ctx.dirty && i.effectiveTargets.includes("npm") ? packNpmArtifacts(i.repoDir, envOpt) : null);
|
|
40128
40523
|
const persisted = artifacts !== null ? { ...i.report, artifacts } : i.report;
|
|
40129
|
-
writeFileSync3(
|
|
40524
|
+
writeFileSync3(join15(i.repoDir, BORDER_STATE_DIR, relReport), `${JSON.stringify(persisted, null, 2)}
|
|
40130
40525
|
`, "utf8");
|
|
40131
|
-
writeFileSync3(
|
|
40526
|
+
writeFileSync3(join15(i.repoDir, BORDER_STATE_DIR, relReport.replace("report.json", "report.md")), renderReportMd(i.report), "utf8");
|
|
40132
40527
|
const record2 = {
|
|
40133
40528
|
t: "check",
|
|
40134
40529
|
key: i.report.key,
|
|
@@ -40153,13 +40548,13 @@ function recordCheckRun(i) {
|
|
|
40153
40548
|
}
|
|
40154
40549
|
|
|
40155
40550
|
// src/channels/pypi.ts
|
|
40156
|
-
import { spawnSync as
|
|
40551
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
40157
40552
|
import { relative as relative5 } from "node:path";
|
|
40158
40553
|
|
|
40159
40554
|
// src/artifacts/pypi.ts
|
|
40160
40555
|
import { createHash as createHash9 } from "node:crypto";
|
|
40161
40556
|
import { existsSync as existsSync10, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, readFileSync as readFileSync9, readdirSync as readdirSync6, rmdirSync as rmdirSync2, rmSync as rmSync7 } from "node:fs";
|
|
40162
|
-
import { basename as
|
|
40557
|
+
import { basename as basename3, dirname as dirname7, join as join16, relative as relative4, resolve as resolve7 } from "node:path";
|
|
40163
40558
|
|
|
40164
40559
|
// src/artifacts/residuePy.ts
|
|
40165
40560
|
function residuePypiHits(pkgDir, innerRels) {
|
|
@@ -40223,13 +40618,13 @@ function requireModule(o, module) {
|
|
|
40223
40618
|
function buildPypiArtifacts(o) {
|
|
40224
40619
|
requireModule(o, "build");
|
|
40225
40620
|
const repoDir = resolve7(o.repoDir);
|
|
40226
|
-
if (!existsSync10(
|
|
40621
|
+
if (!existsSync10(join16(repoDir, "pyproject.toml"))) {
|
|
40227
40622
|
throw new PypiPrerequisiteError(`no pyproject.toml in the repo root \u2014 the PyPI pipeline builds from pyproject's own [build-system]`);
|
|
40228
40623
|
}
|
|
40229
|
-
const distDir =
|
|
40624
|
+
const distDir = join16(resolve7(o.stateDir ?? join16(repoDir, ".border")), "dist");
|
|
40230
40625
|
mkdirSync7(distDir, { recursive: true });
|
|
40231
40626
|
for (const name of readdirSync6(distDir)) {
|
|
40232
|
-
if (name.endsWith(".tar.gz") || name.endsWith(".whl")) rmSync7(
|
|
40627
|
+
if (name.endsWith(".tar.gz") || name.endsWith(".whl")) rmSync7(join16(distDir, name), { force: true });
|
|
40233
40628
|
}
|
|
40234
40629
|
const out = spawnEngine(binaryCandidates("python3", o), ["-m", "build", "--no-isolation", "--outdir", distDir, repoDir], o);
|
|
40235
40630
|
if (out.status !== 0) {
|
|
@@ -40240,13 +40635,13 @@ function buildPypiArtifacts(o) {
|
|
|
40240
40635
|
throw new EngineRunError(`\`python3 -m build\` produced no .tar.gz/.whl in ${distDir} (border exit 2)`, null);
|
|
40241
40636
|
}
|
|
40242
40637
|
const artifacts = names.map((name) => {
|
|
40243
|
-
const path4 =
|
|
40638
|
+
const path4 = join16(distDir, name);
|
|
40244
40639
|
return { path: path4, kind: name.endsWith(".whl") ? "wheel" : "sdist", sha256: createHash9("sha256").update(readFileSync9(path4)).digest("hex") };
|
|
40245
40640
|
});
|
|
40246
40641
|
return { artifacts, distDir };
|
|
40247
40642
|
}
|
|
40248
40643
|
function walkFilesRel(root) {
|
|
40249
|
-
return readdirSync6(root, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? walkFilesRel(
|
|
40644
|
+
return readdirSync6(root, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? walkFilesRel(join16(root, e.name)).map((r) => `${e.name}/${r}`) : e.isFile() ? [e.name] : []);
|
|
40250
40645
|
}
|
|
40251
40646
|
function stripWrapper(entries) {
|
|
40252
40647
|
const tops = new Set(entries.map((e) => e.includes("/") ? e.slice(0, e.indexOf("/")) : "\0"));
|
|
@@ -40259,7 +40654,7 @@ function toInner(findingPath, scanRoot) {
|
|
|
40259
40654
|
const head = bang === -1 ? findingPath : findingPath.slice(0, bang);
|
|
40260
40655
|
const rest = bang === -1 ? "" : findingPath.slice(bang + 1);
|
|
40261
40656
|
const rel = relative4(scanRoot, head);
|
|
40262
|
-
const inner = rel !== "" && !rel.startsWith("..") ? rel :
|
|
40657
|
+
const inner = rel !== "" && !rel.startsWith("..") ? rel : basename3(head);
|
|
40263
40658
|
return rest === "" ? inner : `${inner}!${rest}`;
|
|
40264
40659
|
}
|
|
40265
40660
|
function gitTracked(o, repoDir) {
|
|
@@ -40276,7 +40671,7 @@ function toRe(globs) {
|
|
|
40276
40671
|
function explainMatchers(repoDir) {
|
|
40277
40672
|
const inc = [];
|
|
40278
40673
|
const exc = [];
|
|
40279
|
-
const manifest =
|
|
40674
|
+
const manifest = join16(repoDir, "MANIFEST.in");
|
|
40280
40675
|
if (existsSync10(manifest)) {
|
|
40281
40676
|
for (const line of readFileSync9(manifest, "utf8").split(/\r?\n/)) {
|
|
40282
40677
|
const t = line.split("#")[0]?.trim() ?? "";
|
|
@@ -40291,7 +40686,7 @@ function explainMatchers(repoDir) {
|
|
|
40291
40686
|
else if (cmd === "prune") exc.push(`${dir}/**`);
|
|
40292
40687
|
}
|
|
40293
40688
|
}
|
|
40294
|
-
const pyproject =
|
|
40689
|
+
const pyproject = join16(repoDir, "pyproject.toml");
|
|
40295
40690
|
if (existsSync10(pyproject)) {
|
|
40296
40691
|
const text = readFileSync9(pyproject, "utf8");
|
|
40297
40692
|
const readme = /^\s*readme\s*=\s*"([^"]+)"/m.exec(text);
|
|
@@ -40343,7 +40738,7 @@ ${out.stderr}`.replace(ANSI_RE, "");
|
|
|
40343
40738
|
}
|
|
40344
40739
|
return failed.map((m) => {
|
|
40345
40740
|
const abs = m[1] ?? "";
|
|
40346
|
-
const base =
|
|
40741
|
+
const base = basename3(abs);
|
|
40347
40742
|
const idx = text.indexOf(m[0]);
|
|
40348
40743
|
const next = text.indexOf("Checking ", idx + m[0].length);
|
|
40349
40744
|
const block = tail2(text.slice(idx, next === -1 ? void 0 : next), 400).replaceAll(abs, base);
|
|
@@ -40364,10 +40759,10 @@ async function scanPyPiArtifacts(o) {
|
|
|
40364
40759
|
requireModule(o, "build");
|
|
40365
40760
|
requireModule(o, "twine");
|
|
40366
40761
|
const repoDir = resolve7(o.repoDir);
|
|
40367
|
-
const stateDir = resolve7(o.stateDir ??
|
|
40762
|
+
const stateDir = resolve7(o.stateDir ?? join16(repoDir, ".border"));
|
|
40368
40763
|
const target = o.target ?? "artifact";
|
|
40369
40764
|
const { artifacts } = buildPypiArtifacts(o);
|
|
40370
|
-
const tmpParent =
|
|
40765
|
+
const tmpParent = join16(stateDir, "tmp");
|
|
40371
40766
|
mkdirSync7(tmpParent, { recursive: true });
|
|
40372
40767
|
const merged = /* @__PURE__ */ new Map();
|
|
40373
40768
|
const absorb = (f, path4) => {
|
|
@@ -40376,16 +40771,16 @@ async function scanPyPiArtifacts(o) {
|
|
|
40376
40771
|
};
|
|
40377
40772
|
try {
|
|
40378
40773
|
for (const artifact of artifacts) {
|
|
40379
|
-
const sandbox = mkdtempSync2(
|
|
40774
|
+
const sandbox = mkdtempSync2(join16(tmpParent, "pypi-"));
|
|
40380
40775
|
try {
|
|
40381
40776
|
if (artifact.kind === "wheel") {
|
|
40382
40777
|
const z2 = spawnEngine(binaryCandidates("python3", o), ["-m", "zipfile", "-e", artifact.path, sandbox], o);
|
|
40383
|
-
if (z2.status !== 0) throw new EngineRunError(`\`python3 -m zipfile -e ${
|
|
40778
|
+
if (z2.status !== 0) throw new EngineRunError(`\`python3 -m zipfile -e ${basename3(artifact.path)}\` exited ${String(z2.status)} (border exit 2): ${tail2(z2.stderr)}`, z2.status);
|
|
40384
40779
|
} else {
|
|
40385
40780
|
extractArchive(artifact.path, sandbox, o);
|
|
40386
40781
|
}
|
|
40387
40782
|
const { root, rest } = artifact.kind === "sdist" ? stripWrapper(walkFilesRel(sandbox)) : { root: "", rest: walkFilesRel(sandbox) };
|
|
40388
|
-
const scanRoot =
|
|
40783
|
+
const scanRoot = join16(sandbox, root);
|
|
40389
40784
|
const scanOpts = { ...o, target };
|
|
40390
40785
|
if (o.skipGitleaks !== true) {
|
|
40391
40786
|
for (const f of scanTree({ ...scanOpts, dir: scanRoot, stateDir })) absorb(f, toInner(f.path, scanRoot));
|
|
@@ -40394,9 +40789,23 @@ async function scanPyPiArtifacts(o) {
|
|
|
40394
40789
|
for (const f of await scanPaths({ ...scanOpts, ...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}, ...o.rules !== void 0 ? { rules: o.rules } : {}, dir: scanRoot, files: rest })) absorb(f, f.path ?? "");
|
|
40395
40790
|
}
|
|
40396
40791
|
for (const f of residueFindings(residuePypiHits(scanRoot, rest), {
|
|
40397
|
-
root:
|
|
40792
|
+
root: basename3(artifact.path),
|
|
40398
40793
|
sep: "!",
|
|
40399
|
-
identity:
|
|
40794
|
+
identity: basename3(artifact.path),
|
|
40795
|
+
...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
|
|
40796
|
+
})) absorb(f, f.path ?? "");
|
|
40797
|
+
for (const f of pypiArtifactCoherenceFindings({
|
|
40798
|
+
kind: artifact.kind,
|
|
40799
|
+
archiveName: basename3(artifact.path),
|
|
40800
|
+
files: rest,
|
|
40801
|
+
read: (rel) => {
|
|
40802
|
+
try {
|
|
40803
|
+
return readFileSync9(join16(scanRoot, rel), "utf8");
|
|
40804
|
+
} catch {
|
|
40805
|
+
return null;
|
|
40806
|
+
}
|
|
40807
|
+
},
|
|
40808
|
+
identity: basename3(artifact.path),
|
|
40400
40809
|
...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
|
|
40401
40810
|
})) absorb(f, f.path ?? "");
|
|
40402
40811
|
if (artifact.kind === "sdist") for (const f of manifestFindings(o, rest, target)) absorb(f, f.path ?? "");
|
|
@@ -40418,7 +40827,7 @@ async function scanPyPiArtifacts(o) {
|
|
|
40418
40827
|
import { spawn } from "node:child_process";
|
|
40419
40828
|
import { createHash as createHash10 } from "node:crypto";
|
|
40420
40829
|
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
|
|
40421
|
-
import { basename as
|
|
40830
|
+
import { basename as basename4, join as join17 } from "node:path";
|
|
40422
40831
|
|
|
40423
40832
|
// src/cli/exit.ts
|
|
40424
40833
|
var EXIT_PASS = 0;
|
|
@@ -40459,7 +40868,7 @@ function translateError(err) {
|
|
|
40459
40868
|
|
|
40460
40869
|
// src/push/core.ts
|
|
40461
40870
|
var PUBLISH_WARNING = "WARNING: published versions cannot be re-uploaded unchanged; deletion windows differ per registry \u2014 verify version bump policy";
|
|
40462
|
-
var DIST_DIR =
|
|
40871
|
+
var DIST_DIR = join17(BORDER_STATE_DIR, "dist");
|
|
40463
40872
|
var defaultOut = (line) => void process.stdout.write(`${line}
|
|
40464
40873
|
`);
|
|
40465
40874
|
var defaultErr = (line) => void process.stderr.write(`${line}
|
|
@@ -40488,8 +40897,8 @@ function rehashRecordedArtifacts(kind, extensions, record2, repoDir) {
|
|
|
40488
40897
|
}
|
|
40489
40898
|
const files = [];
|
|
40490
40899
|
for (const a of entries) {
|
|
40491
|
-
const rel =
|
|
40492
|
-
const abs =
|
|
40900
|
+
const rel = join17(DIST_DIR, basename4(a.file));
|
|
40901
|
+
const abs = join17(repoDir, rel);
|
|
40493
40902
|
if (!existsSync11(abs)) return fail3(`${rel} is missing from the working tree`);
|
|
40494
40903
|
const digest2 = createHash10("sha256").update(readFileSync10(abs)).digest("hex");
|
|
40495
40904
|
if (digest2 !== a.sha256) return fail3(`${rel} no longer hashes to the digest recorded by the check`);
|
|
@@ -40665,7 +41074,7 @@ function twineAvailable(i, command) {
|
|
|
40665
41074
|
let status = null;
|
|
40666
41075
|
let failed = false;
|
|
40667
41076
|
try {
|
|
40668
|
-
const res =
|
|
41077
|
+
const res = spawnSync8(command.bin, args, {
|
|
40669
41078
|
encoding: "utf8",
|
|
40670
41079
|
stdio: "ignore",
|
|
40671
41080
|
...i.env === void 0 ? {} : { env: { ...i.env } }
|
|
@@ -40980,7 +41389,7 @@ function npmNameClaimed(o) {
|
|
|
40980
41389
|
return true;
|
|
40981
41390
|
}
|
|
40982
41391
|
function npmViewJson(args, registry2, timeoutMs, env2) {
|
|
40983
|
-
return new Promise((
|
|
41392
|
+
return new Promise((resolve16, reject) => {
|
|
40984
41393
|
const child = spawn2("npm", ["view", ...args, "--registry", registry2, "--fetch-retries=0", "--json"], {
|
|
40985
41394
|
stdio: ["ignore", "pipe", "pipe"],
|
|
40986
41395
|
...env2 === void 0 ? {} : { env: env2 }
|
|
@@ -40999,7 +41408,7 @@ function npmViewJson(args, registry2, timeoutMs, env2) {
|
|
|
40999
41408
|
});
|
|
41000
41409
|
child.on("close", (status) => {
|
|
41001
41410
|
clearTimeout(timer);
|
|
41002
|
-
|
|
41411
|
+
resolve16({ status: status ?? -1, stdout, stderr });
|
|
41003
41412
|
});
|
|
41004
41413
|
});
|
|
41005
41414
|
}
|
|
@@ -41095,14 +41504,14 @@ var npmChannel = {
|
|
|
41095
41504
|
// src/channels/crates.ts
|
|
41096
41505
|
import { createHash as createHash12 } from "node:crypto";
|
|
41097
41506
|
import { existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync12, readdirSync as readdirSync8, rmSync as rmSync9 } from "node:fs";
|
|
41098
|
-
import { join as
|
|
41099
|
-
import { spawnSync as
|
|
41507
|
+
import { join as join19, resolve as resolve8 } from "node:path";
|
|
41508
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
41100
41509
|
|
|
41101
41510
|
// src/artifacts/crates.ts
|
|
41102
41511
|
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync11, readdirSync as readdirSync7, renameSync, rmSync as rmSync8 } from "node:fs";
|
|
41103
41512
|
import { createHash as createHash11 } from "node:crypto";
|
|
41104
|
-
import { join as
|
|
41105
|
-
import { spawnSync as
|
|
41513
|
+
import { join as join18, relative as relative6 } from "node:path";
|
|
41514
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
41106
41515
|
|
|
41107
41516
|
// src/artifacts/residueRust.ts
|
|
41108
41517
|
function residueCratesHits(pkgDir, innerRels) {
|
|
@@ -41301,7 +41710,7 @@ function listFiles3(dir) {
|
|
|
41301
41710
|
while (stack.length > 0) {
|
|
41302
41711
|
const cur = stack.pop();
|
|
41303
41712
|
for (const ent of readdirSync7(cur, { withFileTypes: true })) {
|
|
41304
|
-
const p =
|
|
41713
|
+
const p = join18(cur, ent.name);
|
|
41305
41714
|
if (ent.isDirectory()) stack.push(p);
|
|
41306
41715
|
else if (ent.isFile()) out.push(p);
|
|
41307
41716
|
}
|
|
@@ -41309,7 +41718,7 @@ function listFiles3(dir) {
|
|
|
41309
41718
|
return out;
|
|
41310
41719
|
}
|
|
41311
41720
|
function manifestIdentity(repoDir, cfg) {
|
|
41312
|
-
const manifestPath =
|
|
41721
|
+
const manifestPath = join18(repoDir, "Cargo.toml");
|
|
41313
41722
|
if (!existsSync12(manifestPath)) {
|
|
41314
41723
|
throw new EngineRunError("targets.crates is configured but the repo has no Cargo.toml to package", null);
|
|
41315
41724
|
}
|
|
@@ -41337,7 +41746,7 @@ async function runCargoArtifactStage(o) {
|
|
|
41337
41746
|
const bin = o.cargoBinPath ?? "cargo";
|
|
41338
41747
|
const identity = manifestIdentity(repoDir, o.cfg);
|
|
41339
41748
|
const crateName = `${identity.name}-${identity.version}.crate`;
|
|
41340
|
-
const versionCheck =
|
|
41749
|
+
const versionCheck = spawnSync9(bin, ["--version"], {
|
|
41341
41750
|
encoding: "utf8",
|
|
41342
41751
|
stdio: "ignore",
|
|
41343
41752
|
...o.env === void 0 ? {} : { env: { ...o.env } }
|
|
@@ -41348,14 +41757,14 @@ async function runCargoArtifactStage(o) {
|
|
|
41348
41757
|
);
|
|
41349
41758
|
}
|
|
41350
41759
|
ensureStateDir(repoDir);
|
|
41351
|
-
const stateDir =
|
|
41352
|
-
const distDir =
|
|
41353
|
-
const targetDir =
|
|
41760
|
+
const stateDir = join18(repoDir, BORDER_STATE_DIR);
|
|
41761
|
+
const distDir = join18(stateDir, "dist");
|
|
41762
|
+
const targetDir = join18(stateDir, "tmp", "cargo-target");
|
|
41354
41763
|
mkdirSync8(distDir, { recursive: true });
|
|
41355
41764
|
rmSync8(targetDir, { recursive: true, force: true });
|
|
41356
41765
|
mkdirSync8(targetDir, { recursive: true });
|
|
41357
41766
|
try {
|
|
41358
|
-
const result =
|
|
41767
|
+
const result = spawnSync9(bin, ["package", "--allow-dirty", "--no-verify"], {
|
|
41359
41768
|
cwd: repoDir,
|
|
41360
41769
|
encoding: "utf8",
|
|
41361
41770
|
timeout: o.cargoTimeoutMs ?? 12e4,
|
|
@@ -41370,17 +41779,17 @@ async function runCargoArtifactStage(o) {
|
|
|
41370
41779
|
result.status
|
|
41371
41780
|
);
|
|
41372
41781
|
}
|
|
41373
|
-
const packedDir =
|
|
41374
|
-
const packed =
|
|
41782
|
+
const packedDir = join18(targetDir, "package");
|
|
41783
|
+
const packed = join18(packedDir, crateName);
|
|
41375
41784
|
if (!existsSync12(packed)) {
|
|
41376
41785
|
throw new EngineRunError(`cargo package produced no ${crateName} in ${relative6(repoDir, packedDir)} \u2014 failing closed`, null);
|
|
41377
41786
|
}
|
|
41378
|
-
const dist =
|
|
41787
|
+
const dist = join18(distDir, crateName);
|
|
41379
41788
|
renameSync(packed, dist);
|
|
41380
41789
|
const bytes = readFileSync11(dist);
|
|
41381
41790
|
const sha2563 = createHash11("sha256").update(bytes).digest("hex");
|
|
41382
41791
|
const findings = [];
|
|
41383
|
-
const extractDir =
|
|
41792
|
+
const extractDir = join18(stateDir, "tmp", sha2563.slice(0, 8));
|
|
41384
41793
|
rmSync8(extractDir, { recursive: true, force: true });
|
|
41385
41794
|
mkdirSync8(extractDir, { recursive: true });
|
|
41386
41795
|
try {
|
|
@@ -41419,6 +41828,15 @@ async function runCargoArtifactStage(o) {
|
|
|
41419
41828
|
identity: crateName,
|
|
41420
41829
|
...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
|
|
41421
41830
|
}));
|
|
41831
|
+
const lockRel = `${identity.name}-${identity.version}/Cargo.lock`;
|
|
41832
|
+
findings.push(...cratesCoherenceFindings({
|
|
41833
|
+
name: identity.name,
|
|
41834
|
+
version: identity.version,
|
|
41835
|
+
lockText: relPaths.includes(lockRel) ? readFileSync11(join18(extractDir, lockRel), "utf8") : null,
|
|
41836
|
+
lockRelPath: lockRel,
|
|
41837
|
+
identity: crateName,
|
|
41838
|
+
...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
|
|
41839
|
+
}));
|
|
41422
41840
|
} finally {
|
|
41423
41841
|
removeSandbox(extractDir);
|
|
41424
41842
|
}
|
|
@@ -41574,11 +41992,11 @@ function cratesPublishArgv(files, _cfg) {
|
|
|
41574
41992
|
return files.map(() => ["publish", "--allow-dirty", "--no-verify"]);
|
|
41575
41993
|
}
|
|
41576
41994
|
function repackageSameBytes(i, bin) {
|
|
41577
|
-
const targetDir =
|
|
41995
|
+
const targetDir = join19(resolve8(i.repoDir), BORDER_STATE_DIR, "tmp", "publish-repackage");
|
|
41578
41996
|
rmSync9(targetDir, { recursive: true, force: true });
|
|
41579
41997
|
mkdirSync9(targetDir, { recursive: true });
|
|
41580
41998
|
try {
|
|
41581
|
-
const r =
|
|
41999
|
+
const r = spawnSync10(bin, ["package", "--allow-dirty", "--no-verify"], {
|
|
41582
42000
|
cwd: i.repoDir,
|
|
41583
42001
|
encoding: "utf8",
|
|
41584
42002
|
timeout: 12e4,
|
|
@@ -41590,14 +42008,14 @@ function repackageSameBytes(i, bin) {
|
|
|
41590
42008
|
if (r.status !== 0) {
|
|
41591
42009
|
return { ok: false, message: `cargo package --allow-dirty --no-verify (pre-publish repackage) exited ${String(r.status)} \u2014 no retry loop, push blocked (fail-closed): ${(r.stderr ?? "").trim().slice(0, 300)}`, exit: EXIT_ERROR };
|
|
41592
42010
|
}
|
|
41593
|
-
const packedDir =
|
|
42011
|
+
const packedDir = join19(targetDir, "package");
|
|
41594
42012
|
const crates = readdirSync8(packedDir).filter((f) => f.endsWith(".crate")).sort();
|
|
41595
42013
|
if (crates.length !== 1) {
|
|
41596
42014
|
return { ok: false, message: `pre-publish repackage produced ${String(crates.length)} .crate file(s) \u2014 expected exactly 1, push blocked (fail-closed)`, exit: EXIT_ERROR };
|
|
41597
42015
|
}
|
|
41598
42016
|
const crateFile = crates[0] ?? "";
|
|
41599
|
-
const freshSha = createHash12("sha256").update(readFileSync12(
|
|
41600
|
-
const staged =
|
|
42017
|
+
const freshSha = createHash12("sha256").update(readFileSync12(join19(packedDir, crateFile))).digest("hex");
|
|
42018
|
+
const staged = join19(resolve8(i.repoDir), BORDER_STATE_DIR, "dist", crateFile);
|
|
41601
42019
|
if (!existsSync13(staged)) {
|
|
41602
42020
|
return { ok: false, message: `${crateFile} is missing from .border/dist \u2014 the PASSED check recorded no such crate; re-run border check (push blocked)`, exit: EXIT_ERROR };
|
|
41603
42021
|
}
|
|
@@ -42001,6 +42419,21 @@ function buildBorderConfigSchema() {
|
|
|
42001
42419
|
residue: external_exports.object({
|
|
42002
42420
|
enabled: external_exports.boolean().default(true),
|
|
42003
42421
|
requireProof: external_exports.boolean().default(false)
|
|
42422
|
+
}).strict().optional(),
|
|
42423
|
+
// W4.1 release-coherence twin obligations (plan border-inspect-roadmap §127-138):
|
|
42424
|
+
// the ONLY cross-manager declaration border can statically enforce — when a project
|
|
42425
|
+
// ships a PyPI/npm twin pair under one release train, their versions must be equal.
|
|
42426
|
+
// The per-artifact internal coherence rules (release-coherence-*) are ALWAYS on and
|
|
42427
|
+
// need no opt-in; this list is opt-in and strict — unknown keys fail typed exit 2.
|
|
42428
|
+
// The pair list rides the whole-object configDigest, so editing it rotates the check
|
|
42429
|
+
// rulesHash exactly like the residue block above.
|
|
42430
|
+
release: external_exports.object({
|
|
42431
|
+
twin: external_exports.array(
|
|
42432
|
+
external_exports.object({
|
|
42433
|
+
pypi: external_exports.string().min(1),
|
|
42434
|
+
npm: external_exports.string().min(1)
|
|
42435
|
+
}).strict()
|
|
42436
|
+
).default([])
|
|
42004
42437
|
}).strict().optional()
|
|
42005
42438
|
}).strict();
|
|
42006
42439
|
}
|
|
@@ -42088,7 +42521,7 @@ function deepMergeRaw(base, overlay) {
|
|
|
42088
42521
|
return overlay;
|
|
42089
42522
|
}
|
|
42090
42523
|
function runGit3(cwd, args) {
|
|
42091
|
-
const r =
|
|
42524
|
+
const r = spawnSync11("git", args, { cwd, encoding: "utf8" });
|
|
42092
42525
|
return { ok: r.status === 0, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
42093
42526
|
}
|
|
42094
42527
|
function gitToplevel(cwd) {
|
|
@@ -42148,7 +42581,7 @@ function loadConfig(options = {}) {
|
|
|
42148
42581
|
throw new ConfigError("unreadable", `config file not found: ${basePath}`);
|
|
42149
42582
|
}
|
|
42150
42583
|
} else {
|
|
42151
|
-
for (const candidate of [
|
|
42584
|
+
for (const candidate of [join20(cwd, "border.yaml"), toplevel === void 0 ? "" : join20(toplevel, "border.yaml")]) {
|
|
42152
42585
|
if (candidate !== "" && existsSync14(candidate)) {
|
|
42153
42586
|
basePath = candidate;
|
|
42154
42587
|
baseText = readFileSync13(candidate, "utf8");
|
|
@@ -42163,7 +42596,7 @@ function loadConfig(options = {}) {
|
|
|
42163
42596
|
])];
|
|
42164
42597
|
let overlay;
|
|
42165
42598
|
for (const dir of overlayDirs) {
|
|
42166
|
-
const p =
|
|
42599
|
+
const p = join20(dir, ".border", "config.local.yaml");
|
|
42167
42600
|
const text = readFileOrUndefined(p);
|
|
42168
42601
|
if (text !== void 0) {
|
|
42169
42602
|
overlay = { path: p, doc: yamlToJson(text, p) };
|
|
@@ -42208,10 +42641,10 @@ function exposureSet(cfg, options = {}) {
|
|
|
42208
42641
|
}
|
|
42209
42642
|
|
|
42210
42643
|
// src/check.ts
|
|
42211
|
-
import { join as
|
|
42644
|
+
import { join as join22 } from "node:path";
|
|
42212
42645
|
|
|
42213
42646
|
// src/rules/aiArtifacts.ts
|
|
42214
|
-
import { spawnSync as
|
|
42647
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
42215
42648
|
import { dirname as dirname9, resolve as resolve10 } from "node:path";
|
|
42216
42649
|
var AI_ARTIFACTS_ENGINE = "ai-artifacts";
|
|
42217
42650
|
var BINARY_SNIFF_BYTES = 8e3;
|
|
@@ -42230,7 +42663,7 @@ function makeCtx(o) {
|
|
|
42230
42663
|
return { repoDir, env: env2, timeoutMs: o.timeoutMs ?? 3e5, maxBufferBytes: o.maxBufferBytes ?? 32 * 1024 * 1024 };
|
|
42231
42664
|
}
|
|
42232
42665
|
function gitSpawn2(c, args, input2) {
|
|
42233
|
-
const r =
|
|
42666
|
+
const r = spawnSync12("git", ["-C", c.repoDir, ...args], {
|
|
42234
42667
|
encoding: "buffer",
|
|
42235
42668
|
timeout: c.timeoutMs,
|
|
42236
42669
|
maxBuffer: c.maxBufferBytes,
|
|
@@ -42490,10 +42923,10 @@ function proofFindings(o) {
|
|
|
42490
42923
|
}
|
|
42491
42924
|
|
|
42492
42925
|
// src/check/tagScan.ts
|
|
42493
|
-
import { spawnSync as
|
|
42926
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
42494
42927
|
import { mkdtempSync as mkdtempSync3, rmSync as rmSync10, writeFileSync as writeFileSync4 } from "node:fs";
|
|
42495
42928
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
42496
|
-
import { join as
|
|
42929
|
+
import { join as join21, resolve as resolve12 } from "node:path";
|
|
42497
42930
|
var TAG_MESSAGE_RULE = "tag-message-secret";
|
|
42498
42931
|
function isEnoent2(error61) {
|
|
42499
42932
|
return typeof error61 === "object" && error61 !== null && error61.code === "ENOENT";
|
|
@@ -42513,7 +42946,7 @@ function stdinFindings(text, ignorePath, o) {
|
|
|
42513
42946
|
];
|
|
42514
42947
|
let lastError;
|
|
42515
42948
|
for (const bin of binaryCandidates("gitleaks", o)) {
|
|
42516
|
-
const r =
|
|
42949
|
+
const r = spawnSync13(bin, args, {
|
|
42517
42950
|
encoding: "utf8",
|
|
42518
42951
|
input: text,
|
|
42519
42952
|
timeout: o.timeoutMs ?? 3e5,
|
|
@@ -42584,8 +43017,8 @@ function scanTagMessages(o) {
|
|
|
42584
43017
|
const gitOpts = { ...o.env !== void 0 ? { env: o.env } : {} };
|
|
42585
43018
|
const refs = runGitChecked(repoDir, ["for-each-ref", "--format=%(refname)", "refs/tags"], gitOpts).split("\n").filter((ref) => ref !== "");
|
|
42586
43019
|
if (refs.length === 0) return [];
|
|
42587
|
-
const ignoreDir = mkdtempSync3(
|
|
42588
|
-
const ignorePath =
|
|
43020
|
+
const ignoreDir = mkdtempSync3(join21(tmpdir2(), "border-gitleaks-ignore-"));
|
|
43021
|
+
const ignorePath = join21(ignoreDir, ".gitleaksignore");
|
|
42589
43022
|
writeFileSync4(ignorePath, "");
|
|
42590
43023
|
try {
|
|
42591
43024
|
const findings = [];
|
|
@@ -42661,7 +43094,7 @@ async function runPipeline(o, ctx, lockWarning) {
|
|
|
42661
43094
|
if (!broken.has("gitleaks")) {
|
|
42662
43095
|
const eng = legOptions(o, sanitizer);
|
|
42663
43096
|
findings.push(...filterBorderStateFindings(scanGitHistory({ repoDir, refRange: historyRefRange(ctx), target: "git", ...eng }), repoDir));
|
|
42664
|
-
findings.push(...filterBorderStateFindings(scanTree({ dir: repoDir, stateDir:
|
|
43097
|
+
findings.push(...filterBorderStateFindings(scanTree({ dir: repoDir, stateDir: join22(repoDir, BORDER_STATE_DIR), target: "tree", ...eng }), repoDir));
|
|
42665
43098
|
findings.push(...filterBorderStateFindings(scanTagMessages({ repoDir, target: "git", ...eng }), repoDir));
|
|
42666
43099
|
}
|
|
42667
43100
|
if (!broken.has("secretlint")) {
|
|
@@ -42686,6 +43119,17 @@ async function runPipeline(o, ctx, lockWarning) {
|
|
|
42686
43119
|
findings.push(...stage.findings);
|
|
42687
43120
|
ledgerArtifacts.push(...stage.artifacts);
|
|
42688
43121
|
}
|
|
43122
|
+
if ((o.cfg.release?.twin.length ?? 0) > 0) {
|
|
43123
|
+
findings.push(
|
|
43124
|
+
...await twinCoherenceFindings({
|
|
43125
|
+
pairs: o.cfg.release?.twin ?? [],
|
|
43126
|
+
artifacts: ledgerArtifacts,
|
|
43127
|
+
repoDir,
|
|
43128
|
+
...envOpt,
|
|
43129
|
+
sanitizer
|
|
43130
|
+
})
|
|
43131
|
+
);
|
|
43132
|
+
}
|
|
42689
43133
|
findings.push(...await runRegistryProbes({ repoDir, cfg: o.cfg, effectiveTargets: o.effectiveTargets, ...envOpt }));
|
|
42690
43134
|
if (o.cfg.residue?.enabled === false) {
|
|
42691
43135
|
for (let i = findings.length - 1; i >= 0; i -= 1) {
|
|
@@ -42838,16 +43282,16 @@ var NO_TARGETS_PROBE_CFG = {
|
|
|
42838
43282
|
// src/llm/bundle.ts
|
|
42839
43283
|
import { createHash as createHash13 } from "node:crypto";
|
|
42840
43284
|
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "node:fs";
|
|
42841
|
-
import { dirname as dirname11, join as
|
|
43285
|
+
import { dirname as dirname11, join as join24 } from "node:path";
|
|
42842
43286
|
|
|
42843
43287
|
// src/llm/diff.ts
|
|
42844
|
-
import { spawnSync as
|
|
43288
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
42845
43289
|
import { mkdtempSync as mkdtempSync4, rmSync as rmSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
42846
|
-
import { dirname as dirname10, join as
|
|
43290
|
+
import { dirname as dirname10, join as join23, resolve as resolve13 } from "node:path";
|
|
42847
43291
|
var GIT_DIFF_MAX_BUFFER = 64 * 1024 * 1024;
|
|
42848
43292
|
function gitOut(repoDir, args, o, allowFail = false) {
|
|
42849
43293
|
const abs = resolve13(repoDir);
|
|
42850
|
-
const r =
|
|
43294
|
+
const r = spawnSync14("git", ["-C", abs, ...args], {
|
|
42851
43295
|
encoding: "utf8",
|
|
42852
43296
|
env: { ...o.env ?? process.env, GIT_CEILING_DIRECTORIES: dirname10(abs) },
|
|
42853
43297
|
timeout: 12e4,
|
|
@@ -42940,18 +43384,18 @@ function collectRawDeltas(repoDir, baseSha, headSha, o = {}) {
|
|
|
42940
43384
|
var MASK_CHUNK_CHARS = 2e6;
|
|
42941
43385
|
function maskViaEngineDetection(repoDir, texts, o = {}) {
|
|
42942
43386
|
const sanitizer = new TextSanitizer();
|
|
42943
|
-
const sandbox = mkdtempSync4(
|
|
43387
|
+
const sandbox = mkdtempSync4(join23(repoDir, BORDER_STATE_DIR, "llm-mask-"));
|
|
42944
43388
|
try {
|
|
42945
43389
|
let idx = 0;
|
|
42946
43390
|
for (const text of texts) {
|
|
42947
43391
|
if (text === "") continue;
|
|
42948
43392
|
for (let off = 0; off < text.length; off += MASK_CHUNK_CHARS, idx += 1) {
|
|
42949
|
-
writeFileSync5(
|
|
43393
|
+
writeFileSync5(join23(sandbox, `detect-${String(idx)}.txt`), text.slice(off, off + MASK_CHUNK_CHARS), "utf8");
|
|
42950
43394
|
}
|
|
42951
43395
|
}
|
|
42952
43396
|
scanTree({
|
|
42953
43397
|
dir: sandbox,
|
|
42954
|
-
stateDir:
|
|
43398
|
+
stateDir: join23(repoDir, BORDER_STATE_DIR),
|
|
42955
43399
|
target: "llm-mask",
|
|
42956
43400
|
sanitizer,
|
|
42957
43401
|
...engineEnv(o)
|
|
@@ -43009,7 +43453,7 @@ async function runLlmRequestCore(ctx) {
|
|
|
43009
43453
|
ctx.stderr("border: llm-request: no check record for the current state \u2014 run 'border check' first (the bundle derives from the recorded ctx)");
|
|
43010
43454
|
return EXIT_ERROR;
|
|
43011
43455
|
}
|
|
43012
|
-
const absReport =
|
|
43456
|
+
const absReport = join24(repoDir, record2.reportPath);
|
|
43013
43457
|
if (!existsSync15(absReport)) {
|
|
43014
43458
|
ctx.stderr(`border: llm-request: report archive missing (${record2.reportPath} pruned?) \u2014 run 'border check' again`);
|
|
43015
43459
|
return EXIT_ERROR;
|
|
@@ -43068,7 +43512,7 @@ async function runLlmRequestCore(ctx) {
|
|
|
43068
43512
|
deterministic: { verdict: report.verdict, counts: report.counts, findings: report.findings },
|
|
43069
43513
|
promptTemplate: { path: templatePath, sha256: createHash13("sha256").update(readFileSync14(templatePath)).digest("hex") }
|
|
43070
43514
|
};
|
|
43071
|
-
const outPath =
|
|
43515
|
+
const outPath = join24(dirname11(absReport), "llm-request.json");
|
|
43072
43516
|
mkdirSync10(dirname11(outPath), { recursive: true });
|
|
43073
43517
|
writeFileSync6(outPath, `${JSON.stringify(bundle, null, 2)}
|
|
43074
43518
|
`, "utf8");
|
|
@@ -43080,7 +43524,7 @@ async function runLlmRequestCore(ctx) {
|
|
|
43080
43524
|
// src/llm/ingest.ts
|
|
43081
43525
|
import { createHash as createHash14 } from "node:crypto";
|
|
43082
43526
|
import { existsSync as existsSync16, readFileSync as readFileSync15, readdirSync as readdirSync9 } from "node:fs";
|
|
43083
|
-
import { isAbsolute, join as
|
|
43527
|
+
import { isAbsolute, join as join25, resolve as resolve14 } from "node:path";
|
|
43084
43528
|
var RULE_RE = /^[a-z0-9-]+$/;
|
|
43085
43529
|
var MAX_TEXT = 4e3;
|
|
43086
43530
|
var ALLOWED_KEYS = /* @__PURE__ */ new Set(["rule", "severity", "target", "path", "line", "commit", "message", "snippet", "engine", "valueDigest"]);
|
|
@@ -43099,7 +43543,7 @@ function findLatestBundle(repoDir) {
|
|
|
43099
43543
|
return "no llm-request bundle found \u2014 run 'border llm-request' first";
|
|
43100
43544
|
}
|
|
43101
43545
|
for (const d of dirs.sort().reverse()) {
|
|
43102
|
-
const p =
|
|
43546
|
+
const p = join25(runsDir(repoDir), d, "llm-request.json");
|
|
43103
43547
|
if (!existsSync16(p)) continue;
|
|
43104
43548
|
let parsed;
|
|
43105
43549
|
try {
|
|
@@ -43112,7 +43556,7 @@ function findLatestBundle(repoDir) {
|
|
|
43112
43556
|
if (b.schemaVersion !== 1 || typeof b.key !== "string" || !/^[0-9a-f]{64}$/.test(b.key) || typeof b.head !== "string" || !/^[0-9a-f]{40}$/.test(b.head) || !Array.isArray(b.fileDeltas) || b.deterministic === void 0) {
|
|
43113
43557
|
return `bundle ${p} fails its own schema \u2014 re-run 'border llm-request'`;
|
|
43114
43558
|
}
|
|
43115
|
-
return { dir:
|
|
43559
|
+
return { dir: join25(runsDir(repoDir), d), bundle: b };
|
|
43116
43560
|
}
|
|
43117
43561
|
return "no llm-request bundle found \u2014 run 'border llm-request' first";
|
|
43118
43562
|
}
|
|
@@ -43257,7 +43701,7 @@ async function runLlmRequest(ctx) {
|
|
|
43257
43701
|
}
|
|
43258
43702
|
|
|
43259
43703
|
// src/push/git.ts
|
|
43260
|
-
import { spawnSync as
|
|
43704
|
+
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
43261
43705
|
var DIVERGED_MESSAGE = "history diverged; border never force-pushes; resolve manually";
|
|
43262
43706
|
function pushArgv(url2, branch, mode) {
|
|
43263
43707
|
return mode === "dry-run" ? ["push", "--dry-run", url2, branch, "--follow-tags"] : ["push", url2, branch, "--follow-tags"];
|
|
@@ -43292,7 +43736,7 @@ function lsRemoteMap(repoDir, remote, env2) {
|
|
|
43292
43736
|
return parseLsRemote(text, remote.target);
|
|
43293
43737
|
}
|
|
43294
43738
|
function isAncestor(repoDir, remoteSha, localSha, env2) {
|
|
43295
|
-
const r =
|
|
43739
|
+
const r = spawnSync15("git", ["-C", repoDir, "merge-base", "--is-ancestor", remoteSha, localSha], {
|
|
43296
43740
|
encoding: "utf8",
|
|
43297
43741
|
env: { ...env2, GIT_CEILING_DIRECTORIES: repoDir }
|
|
43298
43742
|
});
|
|
@@ -43327,7 +43771,7 @@ function pushTimeoutMs(env2) {
|
|
|
43327
43771
|
}
|
|
43328
43772
|
function executePush(i) {
|
|
43329
43773
|
const timeoutMs = pushTimeoutMs(i.env);
|
|
43330
|
-
const r =
|
|
43774
|
+
const r = spawnSync15("git", ["-C", i.repoDir, ...pushArgv(i.remote.url, i.branch, "execute")], {
|
|
43331
43775
|
stdio: "inherit",
|
|
43332
43776
|
env: i.env,
|
|
43333
43777
|
timeout: timeoutMs
|
|
@@ -43524,14 +43968,14 @@ async function executeYesPush(ctx, loaded) {
|
|
|
43524
43968
|
import { mkdtempSync as mkdtempSync5, rmSync as rmSync12, writeFileSync as writeFileSync7 } from "node:fs";
|
|
43525
43969
|
import { randomBytes } from "node:crypto";
|
|
43526
43970
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
43527
|
-
import { join as
|
|
43971
|
+
import { join as join26 } from "node:path";
|
|
43528
43972
|
|
|
43529
43973
|
// src/scan/fetch.ts
|
|
43530
43974
|
import { createHash as createHash15 } from "node:crypto";
|
|
43531
|
-
import { basename as
|
|
43975
|
+
import { basename as basename5 } from "node:path";
|
|
43532
43976
|
var SCAN_MAX_ARTIFACT_BYTES = 200 * 1024 * 1024;
|
|
43533
43977
|
var SCAN_REQUEST_TIMEOUT_MS = 6e4;
|
|
43534
|
-
var SCAN_USER_AGENT = "border-customs/0.4.
|
|
43978
|
+
var SCAN_USER_AGENT = "border-customs/0.4.2 (+https://github.com/sumteclab/border; border scan)";
|
|
43535
43979
|
var CRATES_DOWNLOAD_HOST = "https://static.crates.io";
|
|
43536
43980
|
var defaultScanFetcher = async (url2, init) => {
|
|
43537
43981
|
if (!/^https?:\/\//.test(url2)) throw new EngineRunError(`border scan refuses non-http(s) URL: ${url2}`, null);
|
|
@@ -43607,7 +44051,7 @@ function rubygemsDownloadUrl(name, version2, base = channelBaseUrl("rubygems"))
|
|
|
43607
44051
|
return `${base}/downloads/${name}-${version2}.gem`;
|
|
43608
44052
|
}
|
|
43609
44053
|
function safeArtifactName(raw, fallback) {
|
|
43610
|
-
const base =
|
|
44054
|
+
const base = basename5(raw.split("?")[0] ?? "").replace(/[^A-Za-z0-9._+-]/g, "_");
|
|
43611
44055
|
return base.length > 0 && base !== "." && base !== ".." ? base : fallback;
|
|
43612
44056
|
}
|
|
43613
44057
|
async function fetchArtifact(spec, o) {
|
|
@@ -43656,7 +44100,7 @@ async function fetchArtifact(spec, o) {
|
|
|
43656
44100
|
}
|
|
43657
44101
|
}
|
|
43658
44102
|
}
|
|
43659
|
-
function
|
|
44103
|
+
function sha256Hex2(data) {
|
|
43660
44104
|
return createHash15("sha256").update(data).digest("hex");
|
|
43661
44105
|
}
|
|
43662
44106
|
|
|
@@ -43699,6 +44143,109 @@ function parseScanSpec(raw) {
|
|
|
43699
44143
|
return { ecosystem, name, version: version2 };
|
|
43700
44144
|
}
|
|
43701
44145
|
|
|
44146
|
+
// src/roundtrip/localArtifact.ts
|
|
44147
|
+
import { createReadStream, existsSync as existsSync17, statSync as statSync2 } from "node:fs";
|
|
44148
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
44149
|
+
import { basename as basename6, resolve as resolve15 } from "node:path";
|
|
44150
|
+
var EXT_TABLE = /* @__PURE__ */ new Map([
|
|
44151
|
+
[".whl", "pypi"],
|
|
44152
|
+
[".tgz", "npm"],
|
|
44153
|
+
[".crate", "crates"],
|
|
44154
|
+
[".gem", "rubygems"]
|
|
44155
|
+
]);
|
|
44156
|
+
var EXT_LIST = ".whl (pypi), .tgz (npm), .crate (crates), .gem (rubygems)";
|
|
44157
|
+
var CONTAINER_NAME = /* @__PURE__ */ new Map([
|
|
44158
|
+
[".tgz", "/root/p.tgz"],
|
|
44159
|
+
[".crate", "/root/p.crate"],
|
|
44160
|
+
[".gem", "/root/p.gem"]
|
|
44161
|
+
]);
|
|
44162
|
+
var wheelContainerPath = (base, name, version2) => `/root/${safeArtifactName(base, `${name}-${version2}-py3-none-any.whl`)}`;
|
|
44163
|
+
var bad3 = (why) => {
|
|
44164
|
+
throw new UnknownArgError(`border roundtrip: ${why}`);
|
|
44165
|
+
};
|
|
44166
|
+
function identityFromFilename(base, eco) {
|
|
44167
|
+
if (eco === "pypi") {
|
|
44168
|
+
const parts = base.slice(0, -".whl".length).split("-");
|
|
44169
|
+
const name = parts[0] ?? "";
|
|
44170
|
+
const version2 = parts[1] ?? "";
|
|
44171
|
+
if (parts.length < 5 || !/^[A-Za-z0-9][A-Za-z0-9._]*$/.test(name) || !/^\d[A-Za-z0-9._+!]*$/.test(version2)) return null;
|
|
44172
|
+
return { name, version: version2 };
|
|
44173
|
+
}
|
|
44174
|
+
if (eco === "npm") {
|
|
44175
|
+
const m2 = /^(.+)-(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)\.tgz$/i.exec(base);
|
|
44176
|
+
if (m2 === null || m2[1] === void 0 || m2[2] === void 0) return null;
|
|
44177
|
+
return { name: m2[1], version: m2[2] };
|
|
44178
|
+
}
|
|
44179
|
+
if (eco === "crates") {
|
|
44180
|
+
const m2 = /^([a-z0-9_][a-z0-9._-]*?)-(\d[A-Za-z0-9._+-]*)\.crate$/i.exec(base);
|
|
44181
|
+
if (m2 === null || m2[1] === void 0 || m2[2] === void 0) return null;
|
|
44182
|
+
return { name: m2[1], version: m2[2] };
|
|
44183
|
+
}
|
|
44184
|
+
const m = /^([A-Za-z0-9][A-Za-z0-9._-]*?)-(\d[A-Za-z0-9._+-]*)\.gem$/i.exec(base);
|
|
44185
|
+
if (m === null || m[1] === void 0 || m[2] === void 0) return null;
|
|
44186
|
+
return { name: m[1], version: m[2] };
|
|
44187
|
+
}
|
|
44188
|
+
var GRAMMAR_HINT = {
|
|
44189
|
+
pypi: "PEP 427 wheel names, e.g. jupyter_core-5.8.1-py3-none-any.whl",
|
|
44190
|
+
npm: "npm pack names, e.g. node-fetch-2.6.1.tgz (scoped packages flatten to scope-pkg-1.0.0.tgz)",
|
|
44191
|
+
crates: "cargo package names, e.g. rt-crate-1.0.0.crate",
|
|
44192
|
+
rubygems: "gem package names, e.g. rt-gem-1.0.0.gem"
|
|
44193
|
+
};
|
|
44194
|
+
function classifyLocalInput(raw, cwd, parsesAsRegistrySpec) {
|
|
44195
|
+
const abs = resolve15(cwd, raw);
|
|
44196
|
+
if (existsSync17(abs)) {
|
|
44197
|
+
const st = statSync2(abs);
|
|
44198
|
+
if (st.isDirectory()) {
|
|
44199
|
+
bad3(`'${raw}' resolves to a directory (${abs}) \u2014 local mode needs a single artifact FILE, not a directory`);
|
|
44200
|
+
}
|
|
44201
|
+
if (!st.isFile()) {
|
|
44202
|
+
bad3(`'${raw}' resolves to something that is not a regular file (${abs})`);
|
|
44203
|
+
}
|
|
44204
|
+
const lower = basename6(abs).toLowerCase();
|
|
44205
|
+
const ext = [...EXT_TABLE.keys()].find((e) => lower.endsWith(e)) ?? null;
|
|
44206
|
+
if (ext === null) {
|
|
44207
|
+
const known = [...EXT_TABLE.keys()].map((e) => `\`${e}\``).join(", ");
|
|
44208
|
+
bad3(`'${basename6(abs)}' exists but its extension is not in the closed local-artifact table (${known}) \u2014 no content sniffing is done; supported: ${EXT_LIST}`);
|
|
44209
|
+
}
|
|
44210
|
+
const eco = EXT_TABLE.get(ext);
|
|
44211
|
+
const identity = identityFromFilename(basename6(abs), eco);
|
|
44212
|
+
if (identity === null) {
|
|
44213
|
+
bad3(`cannot derive package identity from '${basename6(abs)}' \u2014 expected ${GRAMMAR_HINT[eco]}`);
|
|
44214
|
+
}
|
|
44215
|
+
return {
|
|
44216
|
+
absPath: abs,
|
|
44217
|
+
ecosystem: eco,
|
|
44218
|
+
name: identity.name,
|
|
44219
|
+
version: identity.version,
|
|
44220
|
+
containerPath: ext === ".whl" ? wheelContainerPath(basename6(abs), identity.name, identity.version) : CONTAINER_NAME.get(ext),
|
|
44221
|
+
source: `local:${abs}`
|
|
44222
|
+
};
|
|
44223
|
+
}
|
|
44224
|
+
const looksLocal = raw.includes("/") || [...EXT_TABLE.keys()].some((e) => raw.toLowerCase().endsWith(e));
|
|
44225
|
+
if (looksLocal && !parsesAsRegistrySpec(raw)) {
|
|
44226
|
+
bad3(`'${raw}' (resolved: ${abs}) is not an existing file \u2014 a path-shaped argument is LOCAL mode and is never fetched; fix the path or pass a registry spec`);
|
|
44227
|
+
}
|
|
44228
|
+
return null;
|
|
44229
|
+
}
|
|
44230
|
+
async function readLocalArtifact(absPath, cap = SCAN_MAX_ARTIFACT_BYTES) {
|
|
44231
|
+
const hash2 = createHash16("sha256");
|
|
44232
|
+
const parts = [];
|
|
44233
|
+
let total = 0;
|
|
44234
|
+
for await (const chunk of createReadStream(absPath)) {
|
|
44235
|
+
const b = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
44236
|
+
total += b.byteLength;
|
|
44237
|
+
if (total > cap) {
|
|
44238
|
+
throw new EngineRunError(
|
|
44239
|
+
`border roundtrip: ${absPath} streams past the 200 MiB artifact size cap after ${String(total)} bytes \u2014 refusing to prove a partial read (fail-closed)`,
|
|
44240
|
+
null
|
|
44241
|
+
);
|
|
44242
|
+
}
|
|
44243
|
+
hash2.update(b);
|
|
44244
|
+
parts.push(b);
|
|
44245
|
+
}
|
|
44246
|
+
return { bytes: Buffer.concat(parts), sha256: hash2.digest("hex") };
|
|
44247
|
+
}
|
|
44248
|
+
|
|
43702
44249
|
// src/roundtrip/docker.ts
|
|
43703
44250
|
var DOCKER_PROBE_TIMEOUT_MS = 15e3;
|
|
43704
44251
|
var DOCKER_RUN_TIMEOUT_MS = 12e4;
|
|
@@ -43743,8 +44290,171 @@ function realDockerExec() {
|
|
|
43743
44290
|
};
|
|
43744
44291
|
}
|
|
43745
44292
|
|
|
44293
|
+
// src/roundtrip/calibrate.ts
|
|
44294
|
+
var RT_DEP_RULE = "residue-roundtrip-dep-owned";
|
|
44295
|
+
var PIP_REPORT_PATH = "/dev/shm/rt-report.json";
|
|
44296
|
+
function normalizePipName(name) {
|
|
44297
|
+
return name.toLowerCase().replace(/[-_.]+/g, "-");
|
|
44298
|
+
}
|
|
44299
|
+
function parsePipReport(text) {
|
|
44300
|
+
const die = (why) => {
|
|
44301
|
+
throw new EngineRunError(`roundtrip: pypi closure report unusable (${why}) \u2014 cannot verify (fail-closed)`, null);
|
|
44302
|
+
};
|
|
44303
|
+
let doc;
|
|
44304
|
+
try {
|
|
44305
|
+
doc = JSON.parse(text);
|
|
44306
|
+
} catch {
|
|
44307
|
+
die("not JSON");
|
|
44308
|
+
}
|
|
44309
|
+
if (typeof doc !== "object" || doc === null) die("not an object");
|
|
44310
|
+
const rec = doc;
|
|
44311
|
+
if (String(rec.version) !== "1") die(`report format v${String(rec.version)} is not the v1 surface we validated on pip 25.0.1`);
|
|
44312
|
+
if (!Array.isArray(rec.install)) die("install is not an array");
|
|
44313
|
+
const closure = /* @__PURE__ */ new Map();
|
|
44314
|
+
for (const item of rec.install) {
|
|
44315
|
+
const name = item?.metadata?.name;
|
|
44316
|
+
const version2 = item?.metadata?.version;
|
|
44317
|
+
if (typeof name !== "string" || name.length === 0 || typeof version2 !== "string" || version2.length === 0) {
|
|
44318
|
+
throw new EngineRunError("roundtrip: pypi closure report unusable (an install entry lacks metadata.name/version) \u2014 cannot verify (fail-closed)", null);
|
|
44319
|
+
}
|
|
44320
|
+
closure.set(normalizePipName(name), version2);
|
|
44321
|
+
}
|
|
44322
|
+
if (closure.size === 0) die("empty install list \u2014 a resolvable target must report at least itself");
|
|
44323
|
+
return closure;
|
|
44324
|
+
}
|
|
44325
|
+
function claim2(map2, ambiguous, path4, owner) {
|
|
44326
|
+
if (ambiguous.has(path4)) return;
|
|
44327
|
+
const cur = map2.get(path4);
|
|
44328
|
+
if (cur === void 0) map2.set(path4, owner);
|
|
44329
|
+
else if (cur !== owner) {
|
|
44330
|
+
map2.delete(path4);
|
|
44331
|
+
ambiguous.add(path4);
|
|
44332
|
+
}
|
|
44333
|
+
}
|
|
44334
|
+
function parentOf(path4) {
|
|
44335
|
+
const cut = path4.lastIndexOf("/");
|
|
44336
|
+
return cut <= 0 ? "/" : path4.slice(0, cut);
|
|
44337
|
+
}
|
|
44338
|
+
function buildAttribution(claimText, guard) {
|
|
44339
|
+
const exactOwners = /* @__PURE__ */ new Map();
|
|
44340
|
+
const dirOwners = /* @__PURE__ */ new Map();
|
|
44341
|
+
const ambiguous = /* @__PURE__ */ new Set();
|
|
44342
|
+
const claimants = /* @__PURE__ */ new Map();
|
|
44343
|
+
const normCount = /* @__PURE__ */ new Map();
|
|
44344
|
+
const lines = claimText.split("\n");
|
|
44345
|
+
if (guard !== void 0) {
|
|
44346
|
+
for (const line of lines) {
|
|
44347
|
+
if (!line.startsWith("@ ")) continue;
|
|
44348
|
+
const f = line.slice(2).split(" ");
|
|
44349
|
+
if (f.length !== 6) continue;
|
|
44350
|
+
const [owner, norm, ver, di] = f;
|
|
44351
|
+
normCount.set(norm, (normCount.get(norm) ?? 0) + 1);
|
|
44352
|
+
if (!claimants.has(owner)) claimants.set(owner, { owner, norm, ver, di, root: parentOf(di), dirName: f[4], dirVer: f[5] });
|
|
44353
|
+
}
|
|
44354
|
+
}
|
|
44355
|
+
const legit = (owner) => {
|
|
44356
|
+
if (guard === void 0) return {};
|
|
44357
|
+
const c = claimants.get(owner);
|
|
44358
|
+
if (c === void 0) return void 0;
|
|
44359
|
+
if (c.norm === guard.target) return void 0;
|
|
44360
|
+
if (guard.pins.get(c.norm) !== c.ver) return void 0;
|
|
44361
|
+
if (c.dirName !== c.norm || c.dirVer !== c.ver) return void 0;
|
|
44362
|
+
if ((normCount.get(c.norm) ?? 0) !== 1) return void 0;
|
|
44363
|
+
return c;
|
|
44364
|
+
};
|
|
44365
|
+
for (const line of lines) {
|
|
44366
|
+
if (line.trim().length === 0 || line.startsWith("@ ")) continue;
|
|
44367
|
+
const tab = line.indexOf(" ");
|
|
44368
|
+
if (tab <= 0 || line.indexOf(" ", tab + 1) !== -1) continue;
|
|
44369
|
+
const owner = line.slice(0, tab);
|
|
44370
|
+
const path4 = line.slice(tab + 1);
|
|
44371
|
+
if (!path4.startsWith("/") || path4.length === 1) continue;
|
|
44372
|
+
if (guard !== void 0) {
|
|
44373
|
+
const c = legit(owner);
|
|
44374
|
+
if (c === void 0 || c.root === void 0 || !path4.startsWith(`${c.root}/`)) continue;
|
|
44375
|
+
}
|
|
44376
|
+
claim2(exactOwners, ambiguous, path4, owner);
|
|
44377
|
+
for (let dir = parentOf(path4); dir.length > 1; dir = parentOf(dir)) {
|
|
44378
|
+
claim2(dirOwners, ambiguous, dir, owner);
|
|
44379
|
+
}
|
|
44380
|
+
}
|
|
44381
|
+
return { exactOwners, dirOwners, ambiguous };
|
|
44382
|
+
}
|
|
44383
|
+
function ownerOf(entry, attribution) {
|
|
44384
|
+
const exact = attribution.exactOwners.get(entry.path);
|
|
44385
|
+
if (exact !== void 0) return exact;
|
|
44386
|
+
return entry.kind === "D" ? attribution.dirOwners.get(entry.path) : void 0;
|
|
44387
|
+
}
|
|
44388
|
+
var CLAIM_SCANNER_PY = [
|
|
44389
|
+
"import csv, json, os, re, site, sys",
|
|
44390
|
+
"try:",
|
|
44391
|
+
" closure = json.loads(os.environ['BORDER_CLOSURE'])",
|
|
44392
|
+
" assert isinstance(closure, dict) and closure",
|
|
44393
|
+
"except Exception:",
|
|
44394
|
+
" sys.exit(4)",
|
|
44395
|
+
"def norm(n):",
|
|
44396
|
+
" return re.sub(r'[-_.]+', '-', n).lower()",
|
|
44397
|
+
"def read_meta(di):",
|
|
44398
|
+
" name = ''",
|
|
44399
|
+
" ver = ''",
|
|
44400
|
+
" try:",
|
|
44401
|
+
" f = open(os.path.join(di, 'METADATA'), encoding='utf-8', errors='replace')",
|
|
44402
|
+
" except OSError:",
|
|
44403
|
+
" return None",
|
|
44404
|
+
" with f:",
|
|
44405
|
+
" for raw in f:",
|
|
44406
|
+
" line = raw.rstrip('\\n')",
|
|
44407
|
+
" if line == '':",
|
|
44408
|
+
" break",
|
|
44409
|
+
" if not name and line.startswith('Name:'):",
|
|
44410
|
+
" name = line[5:].strip()",
|
|
44411
|
+
" elif not ver and line.startswith('Version:'):",
|
|
44412
|
+
" ver = line[8:].strip()",
|
|
44413
|
+
" return (name, ver) if name and ver else None",
|
|
44414
|
+
"roots = []",
|
|
44415
|
+
"try:",
|
|
44416
|
+
" roots.extend(site.getsitepackages())",
|
|
44417
|
+
"except Exception:",
|
|
44418
|
+
" pass",
|
|
44419
|
+
"out = []",
|
|
44420
|
+
"seen = set()",
|
|
44421
|
+
"for root in roots:",
|
|
44422
|
+
" if not os.path.isdir(root) or root in seen:",
|
|
44423
|
+
" continue",
|
|
44424
|
+
" seen.add(root)",
|
|
44425
|
+
" for d in sorted(os.listdir(root)):",
|
|
44426
|
+
" if not d.endswith('.dist-info'):",
|
|
44427
|
+
" continue",
|
|
44428
|
+
" di = os.path.join(root, d)",
|
|
44429
|
+
" meta = read_meta(di)",
|
|
44430
|
+
" if meta is None or norm(meta[0]) not in closure:",
|
|
44431
|
+
" continue",
|
|
44432
|
+
" owner = meta[0] + '==' + meta[1]",
|
|
44433
|
+
" core = d[:-10]",
|
|
44434
|
+
" cut = core.rfind('-')",
|
|
44435
|
+
" dn = norm(core[:cut]) if cut > 0 else ''",
|
|
44436
|
+
" dv = core[cut + 1:] if cut > 0 else ''",
|
|
44437
|
+
" out.append('@\\t' + owner + '\\t' + norm(meta[0]) + '\\t' + meta[1] + '\\t' + di + '\\t' + dn + '\\t' + dv)",
|
|
44438
|
+
" out.append(owner + '\\t' + di)",
|
|
44439
|
+
" for base, _dirs, files in os.walk(di):",
|
|
44440
|
+
" for fn in sorted(files):",
|
|
44441
|
+
" out.append(owner + '\\t' + os.path.join(base, fn))",
|
|
44442
|
+
" try:",
|
|
44443
|
+
" rf = open(os.path.join(di, 'RECORD'), newline='', encoding='utf-8', errors='replace')",
|
|
44444
|
+
" with rf:",
|
|
44445
|
+
" for row in csv.reader(rf):",
|
|
44446
|
+
" if not row or not row[0].strip():",
|
|
44447
|
+
" continue",
|
|
44448
|
+
" p = os.path.normpath(os.path.join(root, row[0].strip()))",
|
|
44449
|
+
" if p.startswith(root + os.sep):",
|
|
44450
|
+
" out.append(owner + '\\t' + p)",
|
|
44451
|
+
" except Exception:",
|
|
44452
|
+
" pass",
|
|
44453
|
+
"sys.stdout.write(''.join(x + '\\n' for x in out))"
|
|
44454
|
+
].join("\n");
|
|
44455
|
+
|
|
43746
44456
|
// src/roundtrip/manifest.ts
|
|
43747
|
-
import { createHash as
|
|
44457
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
43748
44458
|
var RT_ENGINE = "roundtrip";
|
|
43749
44459
|
var RT_RESIDUE_RULE = "roundtrip-residue-file";
|
|
43750
44460
|
var RT_MODIFY_RULE = "roundtrip-modified-file";
|
|
@@ -43795,10 +44505,24 @@ var PERSISTENCE_PATTERNS = [
|
|
|
43795
44505
|
function isPersistenceSurface(path4) {
|
|
43796
44506
|
return PERSISTENCE_PATTERNS.some((re) => re.test(path4));
|
|
43797
44507
|
}
|
|
43798
|
-
var digestOf = (what) =>
|
|
43799
|
-
function classifyResidue(label, diff) {
|
|
44508
|
+
var digestOf = (what) => createHash17("sha256").update(what).digest("hex");
|
|
44509
|
+
function classifyResidue(label, diff, attribution = null) {
|
|
43800
44510
|
const findings = [];
|
|
43801
44511
|
for (const e of diff.added) {
|
|
44512
|
+
const owner = attribution !== null ? ownerOf(e, attribution) : void 0;
|
|
44513
|
+
if (owner !== void 0) {
|
|
44514
|
+
findings.push({
|
|
44515
|
+
rule: RT_DEP_RULE,
|
|
44516
|
+
severity: "LOW",
|
|
44517
|
+
target: label,
|
|
44518
|
+
path: e.path,
|
|
44519
|
+
engine: RT_ENGINE,
|
|
44520
|
+
message: `ADDED path survives uninstall but is claimed by still-installed dependency '${owner}' (pip-accountable) \u2014 demoted, non-blocking`,
|
|
44521
|
+
valueDigest: digestOf(`ADDED:${e.path}:${e.value}`),
|
|
44522
|
+
snippet: `dep-owned: ${e.path}`
|
|
44523
|
+
});
|
|
44524
|
+
continue;
|
|
44525
|
+
}
|
|
43802
44526
|
findings.push({
|
|
43803
44527
|
rule: RT_RESIDUE_RULE,
|
|
43804
44528
|
severity: "HIGH",
|
|
@@ -43828,7 +44552,7 @@ function classifyResidue(label, diff) {
|
|
|
43828
44552
|
|
|
43829
44553
|
// src/roundtrip/orchestrate.ts
|
|
43830
44554
|
function containerNameFor(label, rand = randomBytes(3).toString("hex")) {
|
|
43831
|
-
return `rt-${
|
|
44555
|
+
return `rt-${sha256Hex2(label).slice(0, 8)}-${rand}`;
|
|
43832
44556
|
}
|
|
43833
44557
|
var shArgv = (name) => {
|
|
43834
44558
|
if (!/^[A-Za-z0-9@._+/-]+$/.test(name)) {
|
|
@@ -43889,7 +44613,46 @@ function execChecked(exec, args, timeoutMs, what) {
|
|
|
43889
44613
|
}
|
|
43890
44614
|
return r;
|
|
43891
44615
|
}
|
|
43892
|
-
function
|
|
44616
|
+
function resolvePipClosure(exec, profile, hostPath, label, spec) {
|
|
44617
|
+
const ctr = containerNameFor(`${label}#closure`);
|
|
44618
|
+
try {
|
|
44619
|
+
execChecked(exec, ["run", "-d", "--name", ctr, profile.image, "sleep", String(CONTAINER_TTL_SEC)], DOCKER_RUN_TIMEOUT_MS, "closure resolver start");
|
|
44620
|
+
execChecked(exec, ["cp", hostPath, `${ctr}:${profile.artifactPath}`], DOCKER_CP_TIMEOUT_MS, "resolver artifact copy");
|
|
44621
|
+
execChecked(
|
|
44622
|
+
exec,
|
|
44623
|
+
["exec", ctr, "pip", "install", "--dry-run", "--report", PIP_REPORT_PATH, "--no-input", "--root-user-action=ignore", profile.artifactPath],
|
|
44624
|
+
PHASE_TIMEOUT_MS,
|
|
44625
|
+
"pypi closure dry-run"
|
|
44626
|
+
);
|
|
44627
|
+
const rep = execChecked(exec, ["exec", ctr, "cat", PIP_REPORT_PATH], SNAPSHOT_TIMEOUT_MS, "pypi closure report read");
|
|
44628
|
+
const closure = parsePipReport(rep.stdout);
|
|
44629
|
+
if (!closure.has(normalizePipName(spec.name))) {
|
|
44630
|
+
throw new EngineRunError(`roundtrip: pip closure report does not list the target ${spec.name} \u2014 cannot verify (fail-closed)`, null);
|
|
44631
|
+
}
|
|
44632
|
+
return closure;
|
|
44633
|
+
} finally {
|
|
44634
|
+
try {
|
|
44635
|
+
exec(["rm", "-f", ctr], DOCKER_RM_TIMEOUT_MS);
|
|
44636
|
+
} catch {
|
|
44637
|
+
}
|
|
44638
|
+
}
|
|
44639
|
+
}
|
|
44640
|
+
function scanDepClaims(exec, container, closure, target, note) {
|
|
44641
|
+
const guard = { pins: closure, target };
|
|
44642
|
+
const argv = ["exec", "-e", `BORDER_CLOSURE=${JSON.stringify(Object.fromEntries(closure))}`, container, "python3", "-c", CLAIM_SCANNER_PY];
|
|
44643
|
+
try {
|
|
44644
|
+
const r = exec(argv, SNAPSHOT_TIMEOUT_MS);
|
|
44645
|
+
if (r.status !== 0) {
|
|
44646
|
+
note(`roundtrip: dep-attribution scan failed (exit ${String(r.status)}) \u2014 every row keeps its blocking grade (over-report tolerated)`);
|
|
44647
|
+
return buildAttribution("", guard);
|
|
44648
|
+
}
|
|
44649
|
+
return buildAttribution(r.stdout, guard);
|
|
44650
|
+
} catch {
|
|
44651
|
+
note("roundtrip: dep-attribution scan could not run \u2014 every row keeps its blocking grade (over-report tolerated)");
|
|
44652
|
+
return buildAttribution("", guard);
|
|
44653
|
+
}
|
|
44654
|
+
}
|
|
44655
|
+
function runPipeline2(exec, profile, spec, label, hostPath, container, closure, note) {
|
|
43893
44656
|
execChecked(exec, ["run", "-d", "--name", container, profile.image, "sleep", String(CONTAINER_TTL_SEC)], DOCKER_RUN_TIMEOUT_MS, "container start");
|
|
43894
44657
|
try {
|
|
43895
44658
|
execChecked(exec, ["cp", hostPath, `${container}:${profile.artifactPath}`], DOCKER_CP_TIMEOUT_MS, "artifact copy");
|
|
@@ -43906,10 +44669,11 @@ function runPipeline2(exec, profile, spec, label, hostPath, container) {
|
|
|
43906
44669
|
}
|
|
43907
44670
|
execChecked(exec, ["exec", container, ...profile.uninstall(spec)], PHASE_TIMEOUT_MS, `uninstall ${label}`);
|
|
43908
44671
|
const m3 = parseManifest(takeSnapshot(exec, container, profile.excludes));
|
|
44672
|
+
const attribution = closure !== null ? scanDepClaims(exec, container, closure, normalizePipName(spec.name), note) : null;
|
|
43909
44673
|
return {
|
|
43910
|
-
findings: classifyResidue(label, diffManifests(m1, m3)),
|
|
44674
|
+
findings: classifyResidue(label, diffManifests(m1, m3), attribution),
|
|
43911
44675
|
installDelta,
|
|
43912
|
-
baselineDigest:
|
|
44676
|
+
baselineDigest: sha256Hex2([...m1.keys()].join("\n"))
|
|
43913
44677
|
};
|
|
43914
44678
|
} finally {
|
|
43915
44679
|
try {
|
|
@@ -43930,36 +44694,45 @@ async function runRoundtripCore(ctx, deps = {}) {
|
|
|
43930
44694
|
const raw = ctx.positionals[0];
|
|
43931
44695
|
if (ctx.positionals.length !== 1 || raw === void 0) {
|
|
43932
44696
|
throw new UnknownArgError(
|
|
43933
|
-
`border roundtrip expects exactly one
|
|
44697
|
+
`border roundtrip expects exactly one argument \u2014 a registry spec or a local artifact file (got ${String(ctx.positionals.length)}); ${SCAN_SPEC_USAGE}`
|
|
43934
44698
|
);
|
|
43935
44699
|
}
|
|
43936
|
-
const
|
|
43937
|
-
const
|
|
44700
|
+
const local = classifyLocalInput(raw, ctx.cwd, parsesRegistrySpec);
|
|
44701
|
+
const spec = local !== null ? { ecosystem: local.ecosystem, name: local.name, version: local.version } : parseScanSpec(raw);
|
|
44702
|
+
const laneProfile = profileFor(spec.ecosystem);
|
|
44703
|
+
const profile = local !== null ? { ...laneProfile, artifactPath: local.containerPath } : laneProfile;
|
|
43938
44704
|
const label = `${spec.ecosystem}:${spec.name}@${spec.version}`;
|
|
43939
44705
|
const exec = deps.exec ?? realDockerExec();
|
|
43940
44706
|
if (!dockerAvailableGuard(exec)) {
|
|
43941
44707
|
ctx.stderr("roundtrip: docker unavailable \u2014 cannot verify");
|
|
43942
44708
|
return EXIT_ERROR;
|
|
43943
44709
|
}
|
|
43944
|
-
const baseDir = mkdtempSync5(
|
|
44710
|
+
const baseDir = mkdtempSync5(join26(tmpdir3(), "border-roundtrip-"));
|
|
43945
44711
|
try {
|
|
43946
|
-
const
|
|
43947
|
-
const
|
|
44712
|
+
const localRead = local !== null ? await readLocalArtifact(local.absPath) : null;
|
|
44713
|
+
const bytes = localRead?.bytes ?? (await fetchArtifact(spec, { fetcher: deps.fetcher ?? defaultScanFetcher })).bytes;
|
|
44714
|
+
const artifactSha256 = localRead !== null ? localRead.sha256 : sha256Hex2(bytes);
|
|
44715
|
+
const hostPath = join26(baseDir, "artifact");
|
|
43948
44716
|
writeFileSync7(hostPath, bytes);
|
|
43949
44717
|
const container = containerNameFor(label);
|
|
43950
|
-
const
|
|
44718
|
+
const closure = spec.ecosystem === "pypi" ? resolvePipClosure(exec, profile, hostPath, label, spec) : null;
|
|
44719
|
+
const { findings, installDelta, baselineDigest } = runPipeline2(exec, profile, spec, label, hostPath, container, closure, (msg) => ctx.stderr(msg));
|
|
43951
44720
|
const verdict = computeVerdict(findings);
|
|
43952
44721
|
const report = {
|
|
43953
44722
|
schemaVersion: 1,
|
|
43954
|
-
key:
|
|
44723
|
+
key: sha256Hex2(`roundtrip:${label}:${baselineDigest}:${verdict}`),
|
|
43955
44724
|
head: baselineDigest.slice(0, 40),
|
|
43956
44725
|
dirty: false,
|
|
43957
44726
|
exposureSet: [label],
|
|
43958
44727
|
refSet: [],
|
|
43959
|
-
rulesHash:
|
|
44728
|
+
rulesHash: sha256Hex2(`${profile.image}|${profile.install(profile.artifactPath, spec).join(" ")}|${profile.uninstall(spec).join(" ")}`),
|
|
43960
44729
|
verdict,
|
|
43961
44730
|
counts: countFindings(findings),
|
|
43962
44731
|
findings,
|
|
44732
|
+
// W2.4(b) provenance honesty: a local run proves ITS OWN bytes. The
|
|
44733
|
+
// fields are absent (byte-identical JSON) on the registry lane and
|
|
44734
|
+
// never phrase the artifact as registry-verified.
|
|
44735
|
+
...local !== null ? { source: local.source, artifactSha256 } : {},
|
|
43963
44736
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
43964
44737
|
};
|
|
43965
44738
|
const sanitizer = new TextSanitizer();
|
|
@@ -43967,19 +44740,34 @@ async function runRoundtripCore(ctx, deps = {}) {
|
|
|
43967
44740
|
if (ctx.flags.json) {
|
|
43968
44741
|
ctx.stdout(renderReportJson(report));
|
|
43969
44742
|
} else {
|
|
44743
|
+
if (local !== null) {
|
|
44744
|
+
ctx.stdout(line(`roundtrip: source ${local.source} artifact sha256 ${artifactSha256}`));
|
|
44745
|
+
}
|
|
43970
44746
|
for (const f of report.findings) {
|
|
43971
44747
|
ctx.stdout(line(` ${f.severity} ${f.rule} [${f.engine}] ${f.path ?? f.target} ${f.message}`));
|
|
43972
44748
|
}
|
|
44749
|
+
const depOwned = findings.reduce((n, f) => n + (f.rule === RT_DEP_RULE ? 1 : 0), 0);
|
|
44750
|
+
if (depOwned > 0) {
|
|
44751
|
+
ctx.stdout(line(`roundtrip: ${String(depOwned)} row(s) attributed to still-installed pip dependencies \u2014 demoted to ${RT_DEP_RULE} (non-blocking, informational)`));
|
|
44752
|
+
}
|
|
43973
44753
|
ctx.stdout(line(`roundtrip: ${String(report.findings.length)} residue row(s); install-delta ${String(installDelta)} files`));
|
|
43974
44754
|
}
|
|
43975
44755
|
if (ctx.flags.record !== false) {
|
|
43976
|
-
await recordRoundtripProof(ctx,
|
|
44756
|
+
await recordRoundtripProof(ctx, artifactSha256, report.findings.length, report.findings.length === 0 ? "clean" : "residue");
|
|
43977
44757
|
}
|
|
43978
44758
|
return exitCodeFromVerdict(verdict);
|
|
43979
44759
|
} finally {
|
|
43980
44760
|
rmSync12(baseDir, { recursive: true, force: true });
|
|
43981
44761
|
}
|
|
43982
44762
|
}
|
|
44763
|
+
function parsesRegistrySpec(raw) {
|
|
44764
|
+
try {
|
|
44765
|
+
parseScanSpec(raw);
|
|
44766
|
+
return true;
|
|
44767
|
+
} catch {
|
|
44768
|
+
return false;
|
|
44769
|
+
}
|
|
44770
|
+
}
|
|
43983
44771
|
async function recordRoundtripProof(ctx, artifactSha256, rows, verdict) {
|
|
43984
44772
|
const notice = (msg) => ctx.stderr(`roundtrip: ${msg}`);
|
|
43985
44773
|
try {
|
|
@@ -44027,7 +44815,7 @@ var runRoundtrip = (ctx) => runRoundtripCore(ctx);
|
|
|
44027
44815
|
// src/scan/scan.ts
|
|
44028
44816
|
import { mkdtempSync as mkdtempSync6 } from "node:fs";
|
|
44029
44817
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
44030
|
-
import { join as
|
|
44818
|
+
import { join as join28 } from "node:path";
|
|
44031
44819
|
|
|
44032
44820
|
// src/scan/config.ts
|
|
44033
44821
|
function buildScanConfigYaml(eco) {
|
|
@@ -44054,8 +44842,8 @@ function synthesizeScanConfig(eco) {
|
|
|
44054
44842
|
}
|
|
44055
44843
|
|
|
44056
44844
|
// src/scan/materialize.ts
|
|
44057
|
-
import { existsSync as
|
|
44058
|
-
import { join as
|
|
44845
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readdirSync as readdirSync10, readFileSync as readFileSync16, rmSync as rmSync13, writeFileSync as writeFileSync8 } from "node:fs";
|
|
44846
|
+
import { join as join27 } from "node:path";
|
|
44059
44847
|
var CRATES_ENVELOPE_FILES = ["Cargo.toml.orig", ".cargo-ok", ".cargo_vcs_info.json"];
|
|
44060
44848
|
var fail2 = (why) => {
|
|
44061
44849
|
throw new Error(`border scan cannot materialize the artifact (${why}) \u2014 refusing to stage an unusable tree (fail-closed)`);
|
|
@@ -44072,12 +44860,12 @@ function unwrapSingleDir(dir) {
|
|
|
44072
44860
|
const entries = readdirSync10(dir, { withFileTypes: true });
|
|
44073
44861
|
const dirs = entries.filter((e) => e.isDirectory());
|
|
44074
44862
|
if (entries.length === 1 && dirs.length === 1 && dirs[0] !== void 0) {
|
|
44075
|
-
return
|
|
44863
|
+
return join27(dir, dirs[0].name);
|
|
44076
44864
|
}
|
|
44077
44865
|
return dir;
|
|
44078
44866
|
}
|
|
44079
44867
|
function requireManifest(repoDir, ecosystem, candidates) {
|
|
44080
|
-
if (!candidates.some((rel) =>
|
|
44868
|
+
if (!candidates.some((rel) => existsSync18(join27(repoDir, rel)))) {
|
|
44081
44869
|
fail2(`${ecosystem} package tree has none of [${candidates.join(", ")}] at its root`);
|
|
44082
44870
|
}
|
|
44083
44871
|
}
|
|
@@ -44087,18 +44875,18 @@ function materializePackage(o) {
|
|
|
44087
44875
|
`);
|
|
44088
44876
|
});
|
|
44089
44877
|
const safe = safeArtifactName(o.filename, `${o.ecosystem}-artifact`);
|
|
44090
|
-
const archive =
|
|
44878
|
+
const archive = join27(o.baseDir, safe);
|
|
44091
44879
|
writeFileSync8(archive, o.bytes);
|
|
44092
|
-
const extractDir =
|
|
44880
|
+
const extractDir = join27(o.baseDir, "extract");
|
|
44093
44881
|
mkdirSync11(extractDir, { recursive: true });
|
|
44094
44882
|
extractFlat(archive, extractDir);
|
|
44095
44883
|
let repoDir = unwrapSingleDir(extractDir);
|
|
44096
44884
|
if (o.ecosystem === "rubygems") {
|
|
44097
44885
|
const outer = repoDir;
|
|
44098
44886
|
const dataCandidates = findNativeMissArchives(outer).filter((f) => f.endsWith("data.tar.gz"));
|
|
44099
|
-
const inner = dataCandidates[0] ??
|
|
44100
|
-
if (!
|
|
44101
|
-
const gemTree =
|
|
44887
|
+
const inner = dataCandidates[0] ?? join27(outer, "data.tar.gz");
|
|
44888
|
+
if (!existsSync18(inner)) fail2(".gem archive contains no data.tar.gz");
|
|
44889
|
+
const gemTree = join27(o.baseDir, "gem-tree");
|
|
44102
44890
|
mkdirSync11(gemTree, { recursive: true });
|
|
44103
44891
|
extractFlat(inner, gemTree);
|
|
44104
44892
|
repoDir = unwrapSingleDir(gemTree);
|
|
@@ -44109,11 +44897,11 @@ function materializePackage(o) {
|
|
|
44109
44897
|
} else {
|
|
44110
44898
|
requireManifest(repoDir, o.ecosystem, ["Cargo.toml"]);
|
|
44111
44899
|
for (const file2 of CRATES_ENVELOPE_FILES) {
|
|
44112
|
-
const path4 =
|
|
44113
|
-
if (!
|
|
44900
|
+
const path4 = join27(repoDir, file2);
|
|
44901
|
+
if (!existsSync18(path4)) continue;
|
|
44114
44902
|
const original = readFileSync16(path4);
|
|
44115
44903
|
rmSync13(path4, { force: true });
|
|
44116
|
-
note(`border scan: crates envelope normalized: ${file2} sha256=${
|
|
44904
|
+
note(`border scan: crates envelope normalized: ${file2} sha256=${sha256Hex2(original)}`);
|
|
44117
44905
|
}
|
|
44118
44906
|
}
|
|
44119
44907
|
const env2 = o.env;
|
|
@@ -44139,7 +44927,7 @@ async function runScanCore(ctx, deps = {}) {
|
|
|
44139
44927
|
}
|
|
44140
44928
|
const cfg = synthesizeScanConfig(spec.ecosystem);
|
|
44141
44929
|
const fetcher = deps.fetcher ?? defaultScanFetcher;
|
|
44142
|
-
const baseDir = mkdtempSync6(
|
|
44930
|
+
const baseDir = mkdtempSync6(join28(tmpdir4(), "border-scan-"));
|
|
44143
44931
|
try {
|
|
44144
44932
|
const { bytes, filename } = await fetchArtifact(spec, { fetcher });
|
|
44145
44933
|
const env2 = { ...ctx.env };
|
|
@@ -44150,12 +44938,12 @@ async function runScanCore(ctx, deps = {}) {
|
|
|
44150
44938
|
const verdict = computeVerdict(findings);
|
|
44151
44939
|
const report = {
|
|
44152
44940
|
schemaVersion: 1,
|
|
44153
|
-
key:
|
|
44941
|
+
key: sha256Hex2(`scan:${spec.ecosystem}:${spec.name}@${spec.version}:${head}`),
|
|
44154
44942
|
head,
|
|
44155
44943
|
dirty: false,
|
|
44156
44944
|
exposureSet: [`${spec.ecosystem}:${spec.name}@${spec.version}`],
|
|
44157
44945
|
refSet: [],
|
|
44158
|
-
rulesHash:
|
|
44946
|
+
rulesHash: sha256Hex2(buildScanConfigYaml(spec.ecosystem)),
|
|
44159
44947
|
verdict,
|
|
44160
44948
|
counts: countFindings(findings),
|
|
44161
44949
|
findings,
|
|
@@ -44285,7 +45073,7 @@ function usage() {
|
|
|
44285
45073
|
" llm-request emit the provenance request for LLM-authored commits",
|
|
44286
45074
|
" llm-ingest record provenance captured by the harness",
|
|
44287
45075
|
" scan inspect a third-party package ([ecosystem:]name@version) for residue",
|
|
44288
|
-
" roundtrip install+uninstall a package in a throwaway container, report uninstall residue",
|
|
45076
|
+
" roundtrip install+uninstall a package (registry spec or local artifact file) in a throwaway container, report uninstall residue",
|
|
44289
45077
|
"",
|
|
44290
45078
|
"flags:",
|
|
44291
45079
|
" --config <path> config file (default: ./border.yaml, then git-remote fallback)",
|