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/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 spawnSync10 } from "node:child_process";
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 join19, resolve as resolve9 } from "node:path";
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 readFileSync5, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
32051
- import { join as join8, relative as relative2 } from "node:path";
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/artifacts/npm.ts
38390
- function nativeFinding(rule, severity, message, path4, identity, sanitizer) {
38391
- const digest2 = redact(identity);
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
- rule,
38394
- severity,
38395
- target: NPM_TARGET_LABEL,
38396
- path: path4,
38397
- engine: "native",
38398
- message: sanitizeOut(sanitizer, message),
38399
- valueDigest: digest2.valueDigest,
38400
- snippet: digest2.snippet
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 attributeToTarball(findings, artifact) {
38404
- if (artifact === null) return findings;
38405
- const name = artifact.file.slice(artifact.file.lastIndexOf("/") + 1);
38406
- return findings.map((f) => {
38407
- const p = f.path ?? "";
38408
- const rel = p === "package.json" ? p : p.startsWith("package/") ? p.slice("package/".length) : null;
38409
- return rel === null || rel === "" ? f : { ...f, path: `${name}!${rel}` };
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 scopeFindingPath(f, extractDir) {
38413
- if (f.path === void 0) return f;
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 listFiles(dir) {
38423
- const out = [];
38424
- const stack = [dir];
38425
- while (stack.length > 0) {
38426
- const cur = stack.pop();
38427
- for (const ent of readdirSync2(cur, { withFileTypes: true })) {
38428
- const p = join8(cur, ent.name);
38429
- if (ent.isDirectory()) stack.push(p);
38430
- else if (ent.isFile()) out.push(p);
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 out;
38463
+ return o;
38434
38464
  }
38435
- async function runNpmArtifactStage(o) {
38436
- if (o.cfg.targets.npm === void 0) {
38437
- throw new EngineRunError("runNpmArtifactStage invoked without targets.npm configured \u2014 pipeline bug", null);
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
- const { repoDir } = o;
38440
- const manifestPath = join8(repoDir, "package.json");
38441
- if (!existsSync6(manifestPath)) {
38442
- throw new EngineRunError("targets.npm is configured but the repo has no package.json to pack", null);
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
- const manifest = parseNpmManifest(readFileSync5(manifestPath, "utf8"));
38445
- const identity = `${manifest.name ?? "?"}@${manifest.version ?? "?"}`;
38446
- if (manifest.isPrivate) {
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
- findings: [nativeFinding(
38449
- NPM_PRIVATE_RULE,
38450
- "CRITICAL",
38451
- `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`,
38452
- "package.json",
38453
- `${identity}:private`,
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
- ensureStateDir(repoDir);
38460
- const stateDir = join8(repoDir, BORDER_STATE_DIR);
38461
- const distDir = join8(stateDir, "dist");
38462
- mkdirSync3(distDir, { recursive: true });
38463
- const { filename, sha256: sha2563, bytes, tarballAbs } = packOnce({
38464
- repoDir,
38465
- distDir,
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
- extractArchive(tarballAbs, extractDir, {
38476
- ...o.env !== void 0 ? { env: o.env } : {},
38477
- ...o.timeoutMs !== void 0 ? { timeoutMs: o.timeoutMs } : {}
38478
- });
38479
- const relPaths = listFiles(extractDir).map((p) => relative2(extractDir, p));
38480
- if (relPaths.length === 0) throw new EngineRunError("extracted artifact is empty \u2014 unusable npm pack output", null);
38481
- const root = relPaths[0]?.split("/")[0] ?? "package";
38482
- if (!relPaths.every((p) => p.startsWith(`${root}/`))) {
38483
- throw new EngineRunError("extracted artifact mixes multiple top-level roots \u2014 unexpected tar layout, failing closed", null);
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
- const packedManifestRel = `${root}/package.json`;
38486
- if (!relPaths.includes(packedManifestRel)) throw new EngineRunError("extracted artifact has no package/package.json", null);
38487
- const packedManifest = parseNpmManifest(readFileSync5(join8(extractDir, packedManifestRel), "utf8"));
38488
- const pkgDir = join8(extractDir, root);
38489
- const residueCtx = { root, identity, ...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {} };
38490
- for (const key of LIFECYCLE_SCRIPT_KEYS) {
38491
- const cmd = packedManifest.scripts[key];
38492
- if (cmd === void 0) continue;
38493
- const scan = scanNpmLifecycleHook({ pkgDir, hook: cmd });
38494
- if (scan.signatureId !== null) {
38495
- findings.push(nativeFinding(
38496
- RESIDUE_T1_RULE,
38497
- "MEDIUM",
38498
- `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`,
38499
- packedManifestRel,
38500
- `${identity}:residue:t1:${key}`,
38501
- o.sanitizer
38502
- ));
38503
- } else {
38504
- findings.push(nativeFinding(
38505
- NPM_LIFECYCLE_RULE,
38506
- "CRITICAL",
38507
- `package.json script '${key}' is a lifecycle hook \u2014 npm executes it on every consumer install (G33): ${tail(sanitizeOut(o.sanitizer, cmd))}`,
38508
- packedManifestRel,
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 spawnSync3 } from "node:child_process";
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 = spawnSync3("git", ["-C", abs, ...args], {
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 spawnSync4 } from "node:child_process";
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 = spawnSync4("git", args, {
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 = spawnSync4("git", ["rev-parse", "--verify", "--quiet", rev], {
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 join14 } from "node:path";
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 join9 } from "node:path";
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 join9(seam, base);
38898
- const live = join9(here, "..", dir, base);
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: [GITLEAKS_VENDORED_CONFIG, ...resolveResidueFingerprintFiles(input2.env ?? process.env)],
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 spawnSync6 } from "node:child_process";
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 readFileSync7, readdirSync as readdirSync4, rmSync as rmSync5 } from "node:fs";
39193
- import { join as join11 } from "node:path";
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 readFileSync6, readdirSync as readdirSync3, rmSync as rmSync4 } from "node:fs";
39198
- import { join as join10, relative as relative3 } from "node:path";
39199
- import { spawnSync as spawnSync5 } from "node:child_process";
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(basename(rel))) {
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 = basename(rel);
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 = join10(cur, ent.name);
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 = join10(cur, ent.name);
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: readFileSync6(p, "utf8") });
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 = spawnSync5(bin, ["--version"], {
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 = join10(repoDir, BORDER_STATE_DIR);
39612
- const distDir = join10(stateDir, "dist");
40128
+ const stateDir = join12(repoDir, BORDER_STATE_DIR);
40129
+ const distDir = join12(stateDir, "dist");
39613
40130
  mkdirSync4(distDir, { recursive: true });
39614
- const dist = join10(distDir, gemName);
39615
- const result = spawnSync5(bin, ["build", candidates[0].path, "-o", dist], {
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 = readFileSync6(dist);
40147
+ const bytes = readFileSync7(dist);
39631
40148
  const sha2563 = createHash6("sha256").update(bytes).digest("hex");
39632
40149
  const findings = [];
39633
- const extractDir = join10(stateDir, "tmp", sha2563.slice(0, 8));
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(readFileSync6(join10(extractDir, "metadata.gz"))).toString("utf8");
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(join10(extractDir, "data.tar.gz"), dataDir, {
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
- } finally {
39699
- removeSandbox(extractDir);
39700
- removeSandbox(dataDir);
39701
- }
39702
- return {
39703
- findings,
39704
- artifact: { file: `${BORDER_STATE_DIR}/dist/${gemName}`, sha256: sha2563, bytes: bytes.length }
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 bad(`unknown record type ${JSON.stringify(t)}`);
40226
+ return {
40227
+ findings,
40228
+ artifact: { file: `${BORDER_STATE_DIR}/dist/${gemName}`, sha256: sha2563, bytes: bytes.length }
40229
+ };
39966
40230
  }
39967
- function codeOf2(err) {
39968
- return typeof err === "object" && err !== null ? err.code : void 0;
40231
+
40232
+ // src/ledger/freshness.ts
40233
+ function artifactTargets() {
40234
+ return publishChannels().map((c) => c.id);
39969
40235
  }
39970
- function readLedger(repoDir) {
39971
- let text;
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
- text = readFileSync8(ledgerPath(repoDir), "utf8");
39974
- } catch (err) {
39975
- if (codeOf2(err) === "ENOENT") return { records: [], warnings: [] };
39976
- throw err;
40283
+ metadata = gunzipSync2(meta3.content).toString("utf8");
40284
+ inner = gunzipSync2(data.content);
40285
+ } catch {
40286
+ return null;
39977
40287
  }
39978
- const records = [];
39979
- const warnings = [];
39980
- text.split("\n").forEach((line, i) => {
39981
- if (line.trim() === "") return;
39982
- try {
39983
- records.push(parseLedgerRecord(JSON.parse(line)));
39984
- } catch (err) {
39985
- const reason = err instanceof Error ? err.message : String(err);
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
- function appendRecord(repoDir, record2) {
39992
- ensureStateDir(repoDir);
39993
- appendFileSync(ledgerPath(repoDir), `${JSON.stringify(record2)}
39994
- `, "utf8");
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 newestRecordOf(records, key) {
39997
- for (let i = records.length - 1; i >= 0; i -= 1) {
39998
- const r = records[i];
39999
- if (r.t === "check" && r.key === key) return r;
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 isSkippable(r) {
40004
- return r !== null && r.verdict === "PASS" && r.degraded === false;
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
- function lookupSkipRecord(records, key, llm) {
40007
- const newest = newestRecordOf(records, key);
40008
- return isSkippable(newest) && newest.llm === llm ? newest : null;
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 latestPassCoveringTargets(records, key, targets) {
40011
- const newest = newestRecordOf(records, key);
40012
- return isSkippable(newest) && targets.every((t) => newest.effectiveTargets.includes(t)) ? newest : null;
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 pushRecords(records, key) {
40015
- return records.filter((r) => r.t === "push" && (key === void 0 || r.key === key));
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 latestRoundtripForSha(records, artifactSha256) {
40018
- for (let i = records.length - 1; i >= 0; i -= 1) {
40019
- const r = records[i];
40020
- if (r !== void 0 && r.t === "roundtrip" && r.artifactSha256 === artifactSha256) return r;
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 null;
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 join13 } from "node:path";
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(join13(runsDir(repoDir), name)) && n < 1e3; n += 1) {
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 = join13(runsDir(repoDir), name);
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(join13(runsDir(repoDir), dir), { recursive: true, force: true });
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(join14(i.repoDir, BORDER_STATE_DIR, relReport), `${JSON.stringify(persisted, null, 2)}
40524
+ writeFileSync3(join15(i.repoDir, BORDER_STATE_DIR, relReport), `${JSON.stringify(persisted, null, 2)}
40130
40525
  `, "utf8");
40131
- writeFileSync3(join14(i.repoDir, BORDER_STATE_DIR, relReport.replace("report.json", "report.md")), renderReportMd(i.report), "utf8");
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 spawnSync7 } from "node:child_process";
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 basename2, dirname as dirname7, join as join15, relative as relative4, resolve as resolve7 } from "node:path";
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(join15(repoDir, "pyproject.toml"))) {
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 = join15(resolve7(o.stateDir ?? join15(repoDir, ".border")), "dist");
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(join15(distDir, name), { force: true });
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 = join15(distDir, name);
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(join15(root, e.name)).map((r) => `${e.name}/${r}`) : e.isFile() ? [e.name] : []);
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 : basename2(head);
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 = join15(repoDir, "MANIFEST.in");
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 = join15(repoDir, "pyproject.toml");
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 = basename2(abs);
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 ?? join15(repoDir, ".border"));
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 = join15(stateDir, "tmp");
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(join15(tmpParent, "pypi-"));
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 ${basename2(artifact.path)}\` exited ${String(z2.status)} (border exit 2): ${tail2(z2.stderr)}`, z2.status);
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 = join15(sandbox, root);
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: basename2(artifact.path),
40792
+ root: basename3(artifact.path),
40398
40793
  sep: "!",
40399
- identity: basename2(artifact.path),
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 basename3, join as join16 } from "node:path";
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 = join16(BORDER_STATE_DIR, "dist");
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 = join16(DIST_DIR, basename3(a.file));
40492
- const abs = join16(repoDir, rel);
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 = spawnSync7(command.bin, args, {
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((resolve15, reject) => {
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
- resolve15({ status: status ?? -1, stdout, stderr });
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 join18, resolve as resolve8 } from "node:path";
41099
- import { spawnSync as spawnSync9 } from "node:child_process";
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 join17, relative as relative6 } from "node:path";
41105
- import { spawnSync as spawnSync8 } from "node:child_process";
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 = join17(cur, ent.name);
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 = join17(repoDir, "Cargo.toml");
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 = spawnSync8(bin, ["--version"], {
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 = join17(repoDir, BORDER_STATE_DIR);
41352
- const distDir = join17(stateDir, "dist");
41353
- const targetDir = join17(stateDir, "tmp", "cargo-target");
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 = spawnSync8(bin, ["package", "--allow-dirty", "--no-verify"], {
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 = join17(targetDir, "package");
41374
- const packed = join17(packedDir, crateName);
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 = join17(distDir, crateName);
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 = join17(stateDir, "tmp", sha2563.slice(0, 8));
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 = join18(resolve8(i.repoDir), BORDER_STATE_DIR, "tmp", "publish-repackage");
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 = spawnSync9(bin, ["package", "--allow-dirty", "--no-verify"], {
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 = join18(targetDir, "package");
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(join18(packedDir, crateFile))).digest("hex");
41600
- const staged = join18(resolve8(i.repoDir), BORDER_STATE_DIR, "dist", crateFile);
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 = spawnSync10("git", args, { cwd, encoding: "utf8" });
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 [join19(cwd, "border.yaml"), toplevel === void 0 ? "" : join19(toplevel, "border.yaml")]) {
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 = join19(dir, ".border", "config.local.yaml");
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 join21 } from "node:path";
42644
+ import { join as join22 } from "node:path";
42212
42645
 
42213
42646
  // src/rules/aiArtifacts.ts
42214
- import { spawnSync as spawnSync11 } from "node:child_process";
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 = spawnSync11("git", ["-C", c.repoDir, ...args], {
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 spawnSync12 } from "node:child_process";
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 join20, resolve as resolve12 } from "node:path";
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 = spawnSync12(bin, args, {
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(join20(tmpdir2(), "border-gitleaks-ignore-"));
42588
- const ignorePath = join20(ignoreDir, ".gitleaksignore");
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: join21(repoDir, BORDER_STATE_DIR), target: "tree", ...eng }), repoDir));
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 join23 } from "node:path";
43285
+ import { dirname as dirname11, join as join24 } from "node:path";
42842
43286
 
42843
43287
  // src/llm/diff.ts
42844
- import { spawnSync as spawnSync13 } from "node:child_process";
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 join22, resolve as resolve13 } from "node:path";
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 = spawnSync13("git", ["-C", abs, ...args], {
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(join22(repoDir, BORDER_STATE_DIR, "llm-mask-"));
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(join22(sandbox, `detect-${String(idx)}.txt`), text.slice(off, off + MASK_CHUNK_CHARS), "utf8");
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: join22(repoDir, BORDER_STATE_DIR),
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 = join23(repoDir, record2.reportPath);
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 = join23(dirname11(absReport), "llm-request.json");
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 join24, resolve as resolve14 } from "node:path";
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 = join24(runsDir(repoDir), d, "llm-request.json");
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: join24(runsDir(repoDir), d), bundle: b };
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 spawnSync14 } from "node:child_process";
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 = spawnSync14("git", ["-C", repoDir, "merge-base", "--is-ancestor", remoteSha, localSha], {
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 = spawnSync14("git", ["-C", i.repoDir, ...pushArgv(i.remote.url, i.branch, "execute")], {
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 join25 } from "node:path";
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 basename4 } from "node:path";
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.0 (+https://github.com/sumteclab/border; border scan)";
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 = basename4(raw.split("?")[0] ?? "").replace(/[^A-Za-z0-9._+-]/g, "_");
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 sha256Hex(data) {
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 createHash16 } from "node:crypto";
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) => createHash16("sha256").update(what).digest("hex");
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-${sha256Hex(label).slice(0, 8)}-${rand}`;
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 runPipeline2(exec, profile, spec, label, hostPath, container) {
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: sha256Hex([...m1.keys()].join("\n"))
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 <spec> argument (got ${String(ctx.positionals.length)}); ${SCAN_SPEC_USAGE}`
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 spec = parseScanSpec(raw);
43937
- const profile = profileFor(spec.ecosystem);
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(join25(tmpdir3(), "border-roundtrip-"));
44710
+ const baseDir = mkdtempSync5(join26(tmpdir3(), "border-roundtrip-"));
43945
44711
  try {
43946
- const { bytes } = await fetchArtifact(spec, { fetcher: deps.fetcher ?? defaultScanFetcher });
43947
- const hostPath = join25(baseDir, "artifact");
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 { findings, installDelta, baselineDigest } = runPipeline2(exec, profile, spec, label, hostPath, container);
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: sha256Hex(`roundtrip:${label}:${baselineDigest}:${verdict}`),
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: sha256Hex(`${profile.image}|${profile.install(profile.artifactPath, spec).join(" ")}|${profile.uninstall(spec).join(" ")}`),
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, sha256Hex(bytes), report.findings.length, report.findings.length === 0 ? "clean" : "residue");
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 join27 } from "node:path";
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 existsSync17, mkdirSync as mkdirSync11, readdirSync as readdirSync10, readFileSync as readFileSync16, rmSync as rmSync13, writeFileSync as writeFileSync8 } from "node:fs";
44058
- import { join as join26 } from "node:path";
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 join26(dir, dirs[0].name);
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) => existsSync17(join26(repoDir, 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 = join26(o.baseDir, safe);
44878
+ const archive = join27(o.baseDir, safe);
44091
44879
  writeFileSync8(archive, o.bytes);
44092
- const extractDir = join26(o.baseDir, "extract");
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] ?? join26(outer, "data.tar.gz");
44100
- if (!existsSync17(inner)) fail2(".gem archive contains no data.tar.gz");
44101
- const gemTree = join26(o.baseDir, "gem-tree");
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 = join26(repoDir, file2);
44113
- if (!existsSync17(path4)) continue;
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=${sha256Hex(original)}`);
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(join27(tmpdir4(), "border-scan-"));
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: sha256Hex(`scan:${spec.ecosystem}:${spec.name}@${spec.version}:${head}`),
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: sha256Hex(buildScanConfigYaml(spec.ecosystem)),
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)",