border-customs 0.4.0 → 0.4.1

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,23 @@ 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";
39194
39710
 
39195
39711
  // src/artifacts/rubygems.ts
39196
39712
  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";
39713
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, readdirSync as readdirSync3, rmSync as rmSync4 } from "node:fs";
39714
+ import { join as join12, relative as relative3 } from "node:path";
39715
+ import { spawnSync as spawnSync6 } from "node:child_process";
39200
39716
  import { gunzipSync } from "node:zlib";
39201
39717
 
39202
39718
  // src/artifacts/residueGem.ts
39203
- import { basename } from "node:path";
39719
+ import { basename as basename2 } from "node:path";
39204
39720
  var GEM_WORDING = { what: "gem install-time", ledger: "gem" };
39205
39721
  function gemPersistenceHit(rel, text) {
39206
- if (GEM_PERSISTENCE_BASENAMES.includes(basename(rel))) {
39722
+ if (GEM_PERSISTENCE_BASENAMES.includes(basename2(rel))) {
39207
39723
  return {
39208
39724
  rule: RESIDUE_T4_RULE,
39209
39725
  severity: "CRITICAL",
@@ -39449,7 +39965,7 @@ function residueGemHits(pkgDir, rels, metadata) {
39449
39965
  const rel = normalizeRel(raw);
39450
39966
  const text = readText(pkgDir, rel);
39451
39967
  if (text === null) continue;
39452
- const base = basename(rel);
39968
+ const base = basename2(rel);
39453
39969
  const isExtconf = base === GEM_EXTCONF_BASENAME;
39454
39970
  const persistence = gemPersistenceHit(rel, text);
39455
39971
  if (persistence !== null) hits.push(persistence);
@@ -39548,7 +40064,7 @@ function listFiles2(dir) {
39548
40064
  while (stack.length > 0) {
39549
40065
  const cur = stack.pop();
39550
40066
  for (const ent of readdirSync3(cur, { withFileTypes: true })) {
39551
- const p = join10(cur, ent.name);
40067
+ const p = join12(cur, ent.name);
39552
40068
  if (ent.isDirectory()) stack.push(p);
39553
40069
  else if (ent.isFile()) out.push(p);
39554
40070
  }
@@ -39561,11 +40077,11 @@ function treeGemspecCandidates(repoDir) {
39561
40077
  while (stack.length > 0) {
39562
40078
  const cur = stack.pop();
39563
40079
  for (const ent of readdirSync3(cur, { withFileTypes: true })) {
39564
- const p = join10(cur, ent.name);
40080
+ const p = join12(cur, ent.name);
39565
40081
  if (ent.isDirectory()) {
39566
40082
  if (!SKIP_DIRECTORIES2.has(ent.name)) stack.push(p);
39567
40083
  } else if (ent.isFile() && ent.name.toLowerCase().endsWith(".gemspec")) {
39568
- out.push({ path: relative3(repoDir, p), body: readFileSync6(p, "utf8") });
40084
+ out.push({ path: relative3(repoDir, p), body: readFileSync7(p, "utf8") });
39569
40085
  }
39570
40086
  }
39571
40087
  }
@@ -39597,7 +40113,7 @@ async function runRubygemsArtifactStage(o) {
39597
40113
  throw e;
39598
40114
  }
39599
40115
  const gemName = `${identity.name}-${identity.version}.gem`;
39600
- const versionCheck = spawnSync5(bin, ["--version"], {
40116
+ const versionCheck = spawnSync6(bin, ["--version"], {
39601
40117
  encoding: "utf8",
39602
40118
  stdio: "ignore",
39603
40119
  ...o.env === void 0 ? {} : { env: { ...o.env } }
@@ -39608,11 +40124,11 @@ async function runRubygemsArtifactStage(o) {
39608
40124
  );
39609
40125
  }
39610
40126
  ensureStateDir(repoDir);
39611
- const stateDir = join10(repoDir, BORDER_STATE_DIR);
39612
- const distDir = join10(stateDir, "dist");
40127
+ const stateDir = join12(repoDir, BORDER_STATE_DIR);
40128
+ const distDir = join12(stateDir, "dist");
39613
40129
  mkdirSync4(distDir, { recursive: true });
39614
- const dist = join10(distDir, gemName);
39615
- const result = spawnSync5(bin, ["build", candidates[0].path, "-o", dist], {
40130
+ const dist = join12(distDir, gemName);
40131
+ const result = spawnSync6(bin, ["build", candidates[0].path, "-o", dist], {
39616
40132
  cwd: repoDir,
39617
40133
  encoding: "utf8",
39618
40134
  timeout: o.gemTimeoutMs ?? 12e4,
@@ -39627,10 +40143,10 @@ async function runRubygemsArtifactStage(o) {
39627
40143
  if (!existsSync8(dist)) {
39628
40144
  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
40145
  }
39630
- const bytes = readFileSync6(dist);
40146
+ const bytes = readFileSync7(dist);
39631
40147
  const sha2563 = createHash6("sha256").update(bytes).digest("hex");
39632
40148
  const findings = [];
39633
- const extractDir = join10(stateDir, "tmp", sha2563.slice(0, 8));
40149
+ const extractDir = join12(stateDir, "tmp", sha2563.slice(0, 8));
39634
40150
  rmSync4(extractDir, { recursive: true, force: true });
39635
40151
  mkdirSync4(extractDir, { recursive: true });
39636
40152
  const dataDir = `${extractDir}-data`;
@@ -39669,13 +40185,13 @@ async function runRubygemsArtifactStage(o) {
39669
40185
  if (members2.has("metadata.gz") && members2.has("data.tar.gz")) {
39670
40186
  let metadata;
39671
40187
  try {
39672
- metadata = gunzipSync(readFileSync6(join10(extractDir, "metadata.gz"))).toString("utf8");
40188
+ metadata = gunzipSync(readFileSync7(join12(extractDir, "metadata.gz"))).toString("utf8");
39673
40189
  } catch {
39674
40190
  throw new EngineRunError(`${gemName}: metadata.gz is not gunzip-readable \u2014 the extensions declaration cannot be verified, failing closed`, null);
39675
40191
  }
39676
40192
  rmSync4(dataDir, { recursive: true, force: true });
39677
40193
  mkdirSync4(dataDir, { recursive: true });
39678
- extractArchive(join10(extractDir, "data.tar.gz"), dataDir, {
40194
+ extractArchive(join12(extractDir, "data.tar.gz"), dataDir, {
39679
40195
  ...o.env !== void 0 ? { env: o.env } : {},
39680
40196
  ...o.timeoutMs !== void 0 ? { timeoutMs: o.timeoutMs } : {}
39681
40197
  });
@@ -39694,337 +40210,134 @@ async function runRubygemsArtifactStage(o) {
39694
40210
  })) {
39695
40211
  findings.push(f);
39696
40212
  }
40213
+ findings.push(...gemCoherenceFindings({
40214
+ specName: identity.name,
40215
+ specVersion: identity.version,
40216
+ metadataText: metadata,
40217
+ identity: gemName,
40218
+ ...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
40219
+ }));
39697
40220
  }
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
- };
40221
+ } finally {
40222
+ removeSandbox(extractDir);
40223
+ removeSandbox(dataDir);
39964
40224
  }
39965
- return bad(`unknown record type ${JSON.stringify(t)}`);
40225
+ return {
40226
+ findings,
40227
+ artifact: { file: `${BORDER_STATE_DIR}/dist/${gemName}`, sha256: sha2563, bytes: bytes.length }
40228
+ };
39966
40229
  }
39967
- function codeOf2(err) {
39968
- return typeof err === "object" && err !== null ? err.code : void 0;
40230
+
40231
+ // src/ledger/freshness.ts
40232
+ function artifactTargets() {
40233
+ return publishChannels().map((c) => c.id);
39969
40234
  }
39970
- function readLedger(repoDir) {
39971
- let text;
40235
+ function sha256File(path4) {
40236
+ return createHash7("sha256").update(readFileSync8(path4)).digest("hex");
40237
+ }
40238
+ var packSeq = 0;
40239
+ function packNpmArtifacts(repoDir, o = {}) {
40240
+ packSeq += 1;
40241
+ const dest = join13(repoDir, BORDER_STATE_DIR, "tmp", `pack-${String(process.pid)}-${String(packSeq)}`);
40242
+ mkdirSync5(dest, { recursive: true });
39972
40243
  try {
39973
- text = readFileSync8(ledgerPath(repoDir), "utf8");
39974
- } catch (err) {
39975
- if (codeOf2(err) === "ENOENT") return { records: [], warnings: [] };
39976
- throw err;
40244
+ const r = spawnSync7("npm", ["pack", "--ignore-scripts", "--silent", `--pack-destination=${dest}`], {
40245
+ cwd: repoDir,
40246
+ encoding: "utf8",
40247
+ env: { ...o.env ?? process.env },
40248
+ timeout: 12e4
40249
+ });
40250
+ if (r.error !== void 0 || r.status !== 0) return null;
40251
+ const files = readdirSync4(dest).filter((f) => f.endsWith(".tgz")).sort();
40252
+ if (files.length === 0) return null;
40253
+ return files.map((f) => ({ file: f, sha256: sha256File(join13(dest, f)) }));
40254
+ } finally {
40255
+ rmSync5(dest, { recursive: true, force: true });
39977
40256
  }
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 };
39990
40257
  }
39991
- function appendRecord(repoDir, record2) {
39992
- ensureStateDir(repoDir);
39993
- appendFileSync(ledgerPath(repoDir), `${JSON.stringify(record2)}
39994
- `, "utf8");
39995
- }
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;
40258
+ function packCrateArtifacts(repoDir, o = {}) {
40259
+ packSeq += 1;
40260
+ const dest = join13(repoDir, BORDER_STATE_DIR, "tmp", `cargo-pack-${String(process.pid)}-${String(packSeq)}`);
40261
+ mkdirSync5(dest, { recursive: true });
40262
+ try {
40263
+ const r = spawnSync7("cargo", ["package", "--allow-dirty", "--no-verify"], {
40264
+ cwd: repoDir,
40265
+ encoding: "utf8",
40266
+ env: { ...o.env ?? process.env, CARGO_TARGET_DIR: dest },
40267
+ timeout: 12e4
40268
+ });
40269
+ if (r.error !== void 0 || r.status !== 0) return null;
40270
+ const dir = join13(dest, "package");
40271
+ const files = readdirSync4(dir).filter((f) => f.endsWith(".crate")).sort();
40272
+ if (files.length === 0) return null;
40273
+ return files.map((f) => ({ file: f, sha256: sha256File(join13(dir, f)) }));
40274
+ } finally {
40275
+ rmSync5(dest, { recursive: true, force: true });
40000
40276
  }
40001
- return null;
40002
40277
  }
40003
- function isSkippable(r) {
40004
- return r !== null && r.verdict === "PASS" && r.degraded === false;
40005
- }
40006
- function lookupSkipRecord(records, key, llm) {
40007
- const newest = newestRecordOf(records, key);
40008
- return isSkippable(newest) && newest.llm === llm ? newest : null;
40278
+ function packRubygemsArtifacts(repoDir, o = {}) {
40279
+ packSeq += 1;
40280
+ const ident = singleTreeGemspec(repoDir);
40281
+ if (ident === null) return null;
40282
+ const dest = join13(repoDir, BORDER_STATE_DIR, "tmp", `gem-pack-${String(process.pid)}-${String(packSeq)}`);
40283
+ mkdirSync5(dest, { recursive: true });
40284
+ try {
40285
+ const gemName = `${ident.name}-${ident.version}.gem`;
40286
+ const r = spawnSync7("gem", ["build", ident.path, "-o", join13(dest, gemName)], {
40287
+ cwd: repoDir,
40288
+ encoding: "utf8",
40289
+ env: { ...o.env ?? process.env },
40290
+ timeout: 12e4
40291
+ });
40292
+ if (r.error !== void 0 || r.status !== 0) return null;
40293
+ const files = readdirSync4(dest).filter((f) => f.endsWith(".gem")).sort();
40294
+ if (files.length === 0) return null;
40295
+ return files.map((f) => ({ file: f, sha256: sha256File(join13(dest, f)) }));
40296
+ } finally {
40297
+ rmSync5(dest, { recursive: true, force: true });
40298
+ }
40009
40299
  }
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;
40300
+ var REPACKERS = {
40301
+ npm: packNpmArtifacts,
40302
+ crates: packCrateArtifacts,
40303
+ rubygems: packRubygemsArtifacts
40304
+ };
40305
+ function repackChannels() {
40306
+ return publishChannels().filter((c) => c.freshness === "repack").map((c) => c.id);
40013
40307
  }
40014
- function pushRecords(records, key) {
40015
- return records.filter((r) => r.t === "push" && (key === void 0 || r.key === key));
40308
+ function sameArtifacts(a, b, extensions) {
40309
+ 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();
40310
+ const ka = digests(a);
40311
+ const kb = digests(b);
40312
+ return ka.length === kb.length && ka.every((x, i) => x === kb[i]);
40016
40313
  }
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;
40314
+ function verifyArtifactFreshness(record2, ctx, repoDir, o = {}) {
40315
+ if (!record2.effectiveTargets.some((t) => artifactTargets().includes(t))) return true;
40316
+ if (ctx.dirty) return false;
40317
+ const needed = repackChannels().filter((t) => record2.effectiveTargets.includes(t));
40318
+ if (needed.length === 0) return true;
40319
+ if (record2.artifacts === null) return false;
40320
+ const now = [];
40321
+ for (const id of needed) {
40322
+ const pack = REPACKERS[id];
40323
+ if (pack === void 0) return false;
40324
+ const got = pack(repoDir, o);
40325
+ if (got === null) return false;
40326
+ now.push(...got);
40021
40327
  }
40022
- return null;
40328
+ return sameArtifacts(
40329
+ record2.artifacts,
40330
+ now,
40331
+ needed.flatMap((id) => {
40332
+ const channel = publishChannels().find((c) => c.id === id);
40333
+ return channel?.artifactExtensions ?? [];
40334
+ })
40335
+ );
40023
40336
  }
40024
40337
 
40025
40338
  // src/ledger/retention.ts
40026
40339
  import { existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync5, rmSync as rmSync6 } from "node:fs";
40027
- import { join as join13 } from "node:path";
40340
+ import { join as join14 } from "node:path";
40028
40341
  var KEEP_RUN_DIRS_PER_KEY8 = 20;
40029
40342
  function codeOf3(err) {
40030
40343
  return typeof err === "object" && err !== null ? err.code : void 0;
@@ -40035,13 +40348,13 @@ function runDirName(key8, ts) {
40035
40348
  function uniqueRunDirName(repoDir, key8, ts) {
40036
40349
  const base = runDirName(key8, ts);
40037
40350
  let name = base;
40038
- for (let n = 2; existsSync9(join13(runsDir(repoDir), name)) && n < 1e3; n += 1) {
40351
+ for (let n = 2; existsSync9(join14(runsDir(repoDir), name)) && n < 1e3; n += 1) {
40039
40352
  name = `${base}-r${String(n)}`;
40040
40353
  }
40041
40354
  return name;
40042
40355
  }
40043
40356
  function ensureRunDir(repoDir, name) {
40044
- const dir = join13(runsDir(repoDir), name);
40357
+ const dir = join14(runsDir(repoDir), name);
40045
40358
  mkdirSync6(dir, { recursive: true });
40046
40359
  return dir;
40047
40360
  }
@@ -40060,7 +40373,7 @@ function pruneRunDirs(repoDir, key8, keep = KEEP_RUN_DIRS_PER_KEY8) {
40060
40373
  const mine = entries.filter((e) => e.startsWith(prefix)).sort();
40061
40374
  const doomed = mine.slice(0, Math.max(0, mine.length - keep));
40062
40375
  for (const dir of doomed) {
40063
- rmSync6(join13(runsDir(repoDir), dir), { recursive: true, force: true });
40376
+ rmSync6(join14(runsDir(repoDir), dir), { recursive: true, force: true });
40064
40377
  }
40065
40378
  return doomed;
40066
40379
  }
@@ -40126,9 +40439,9 @@ function recordCheckRun(i) {
40126
40439
  const envOpt = i.env !== void 0 ? { env: i.env } : {};
40127
40440
  const artifacts = i.artifacts ?? (i.report.verdict === "PASS" && !i.ctx.dirty && i.effectiveTargets.includes("npm") ? packNpmArtifacts(i.repoDir, envOpt) : null);
40128
40441
  const persisted = artifacts !== null ? { ...i.report, artifacts } : i.report;
40129
- writeFileSync3(join14(i.repoDir, BORDER_STATE_DIR, relReport), `${JSON.stringify(persisted, null, 2)}
40442
+ writeFileSync3(join15(i.repoDir, BORDER_STATE_DIR, relReport), `${JSON.stringify(persisted, null, 2)}
40130
40443
  `, "utf8");
40131
- writeFileSync3(join14(i.repoDir, BORDER_STATE_DIR, relReport.replace("report.json", "report.md")), renderReportMd(i.report), "utf8");
40444
+ writeFileSync3(join15(i.repoDir, BORDER_STATE_DIR, relReport.replace("report.json", "report.md")), renderReportMd(i.report), "utf8");
40132
40445
  const record2 = {
40133
40446
  t: "check",
40134
40447
  key: i.report.key,
@@ -40153,13 +40466,13 @@ function recordCheckRun(i) {
40153
40466
  }
40154
40467
 
40155
40468
  // src/channels/pypi.ts
40156
- import { spawnSync as spawnSync7 } from "node:child_process";
40469
+ import { spawnSync as spawnSync8 } from "node:child_process";
40157
40470
  import { relative as relative5 } from "node:path";
40158
40471
 
40159
40472
  // src/artifacts/pypi.ts
40160
40473
  import { createHash as createHash9 } from "node:crypto";
40161
40474
  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";
40475
+ import { basename as basename3, dirname as dirname7, join as join16, relative as relative4, resolve as resolve7 } from "node:path";
40163
40476
 
40164
40477
  // src/artifacts/residuePy.ts
40165
40478
  function residuePypiHits(pkgDir, innerRels) {
@@ -40223,13 +40536,13 @@ function requireModule(o, module) {
40223
40536
  function buildPypiArtifacts(o) {
40224
40537
  requireModule(o, "build");
40225
40538
  const repoDir = resolve7(o.repoDir);
40226
- if (!existsSync10(join15(repoDir, "pyproject.toml"))) {
40539
+ if (!existsSync10(join16(repoDir, "pyproject.toml"))) {
40227
40540
  throw new PypiPrerequisiteError(`no pyproject.toml in the repo root \u2014 the PyPI pipeline builds from pyproject's own [build-system]`);
40228
40541
  }
40229
- const distDir = join15(resolve7(o.stateDir ?? join15(repoDir, ".border")), "dist");
40542
+ const distDir = join16(resolve7(o.stateDir ?? join16(repoDir, ".border")), "dist");
40230
40543
  mkdirSync7(distDir, { recursive: true });
40231
40544
  for (const name of readdirSync6(distDir)) {
40232
- if (name.endsWith(".tar.gz") || name.endsWith(".whl")) rmSync7(join15(distDir, name), { force: true });
40545
+ if (name.endsWith(".tar.gz") || name.endsWith(".whl")) rmSync7(join16(distDir, name), { force: true });
40233
40546
  }
40234
40547
  const out = spawnEngine(binaryCandidates("python3", o), ["-m", "build", "--no-isolation", "--outdir", distDir, repoDir], o);
40235
40548
  if (out.status !== 0) {
@@ -40240,13 +40553,13 @@ function buildPypiArtifacts(o) {
40240
40553
  throw new EngineRunError(`\`python3 -m build\` produced no .tar.gz/.whl in ${distDir} (border exit 2)`, null);
40241
40554
  }
40242
40555
  const artifacts = names.map((name) => {
40243
- const path4 = join15(distDir, name);
40556
+ const path4 = join16(distDir, name);
40244
40557
  return { path: path4, kind: name.endsWith(".whl") ? "wheel" : "sdist", sha256: createHash9("sha256").update(readFileSync9(path4)).digest("hex") };
40245
40558
  });
40246
40559
  return { artifacts, distDir };
40247
40560
  }
40248
40561
  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] : []);
40562
+ return readdirSync6(root, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? walkFilesRel(join16(root, e.name)).map((r) => `${e.name}/${r}`) : e.isFile() ? [e.name] : []);
40250
40563
  }
40251
40564
  function stripWrapper(entries) {
40252
40565
  const tops = new Set(entries.map((e) => e.includes("/") ? e.slice(0, e.indexOf("/")) : "\0"));
@@ -40259,7 +40572,7 @@ function toInner(findingPath, scanRoot) {
40259
40572
  const head = bang === -1 ? findingPath : findingPath.slice(0, bang);
40260
40573
  const rest = bang === -1 ? "" : findingPath.slice(bang + 1);
40261
40574
  const rel = relative4(scanRoot, head);
40262
- const inner = rel !== "" && !rel.startsWith("..") ? rel : basename2(head);
40575
+ const inner = rel !== "" && !rel.startsWith("..") ? rel : basename3(head);
40263
40576
  return rest === "" ? inner : `${inner}!${rest}`;
40264
40577
  }
40265
40578
  function gitTracked(o, repoDir) {
@@ -40276,7 +40589,7 @@ function toRe(globs) {
40276
40589
  function explainMatchers(repoDir) {
40277
40590
  const inc = [];
40278
40591
  const exc = [];
40279
- const manifest = join15(repoDir, "MANIFEST.in");
40592
+ const manifest = join16(repoDir, "MANIFEST.in");
40280
40593
  if (existsSync10(manifest)) {
40281
40594
  for (const line of readFileSync9(manifest, "utf8").split(/\r?\n/)) {
40282
40595
  const t = line.split("#")[0]?.trim() ?? "";
@@ -40291,7 +40604,7 @@ function explainMatchers(repoDir) {
40291
40604
  else if (cmd === "prune") exc.push(`${dir}/**`);
40292
40605
  }
40293
40606
  }
40294
- const pyproject = join15(repoDir, "pyproject.toml");
40607
+ const pyproject = join16(repoDir, "pyproject.toml");
40295
40608
  if (existsSync10(pyproject)) {
40296
40609
  const text = readFileSync9(pyproject, "utf8");
40297
40610
  const readme = /^\s*readme\s*=\s*"([^"]+)"/m.exec(text);
@@ -40343,7 +40656,7 @@ ${out.stderr}`.replace(ANSI_RE, "");
40343
40656
  }
40344
40657
  return failed.map((m) => {
40345
40658
  const abs = m[1] ?? "";
40346
- const base = basename2(abs);
40659
+ const base = basename3(abs);
40347
40660
  const idx = text.indexOf(m[0]);
40348
40661
  const next = text.indexOf("Checking ", idx + m[0].length);
40349
40662
  const block = tail2(text.slice(idx, next === -1 ? void 0 : next), 400).replaceAll(abs, base);
@@ -40364,10 +40677,10 @@ async function scanPyPiArtifacts(o) {
40364
40677
  requireModule(o, "build");
40365
40678
  requireModule(o, "twine");
40366
40679
  const repoDir = resolve7(o.repoDir);
40367
- const stateDir = resolve7(o.stateDir ?? join15(repoDir, ".border"));
40680
+ const stateDir = resolve7(o.stateDir ?? join16(repoDir, ".border"));
40368
40681
  const target = o.target ?? "artifact";
40369
40682
  const { artifacts } = buildPypiArtifacts(o);
40370
- const tmpParent = join15(stateDir, "tmp");
40683
+ const tmpParent = join16(stateDir, "tmp");
40371
40684
  mkdirSync7(tmpParent, { recursive: true });
40372
40685
  const merged = /* @__PURE__ */ new Map();
40373
40686
  const absorb = (f, path4) => {
@@ -40376,16 +40689,16 @@ async function scanPyPiArtifacts(o) {
40376
40689
  };
40377
40690
  try {
40378
40691
  for (const artifact of artifacts) {
40379
- const sandbox = mkdtempSync2(join15(tmpParent, "pypi-"));
40692
+ const sandbox = mkdtempSync2(join16(tmpParent, "pypi-"));
40380
40693
  try {
40381
40694
  if (artifact.kind === "wheel") {
40382
40695
  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);
40696
+ 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
40697
  } else {
40385
40698
  extractArchive(artifact.path, sandbox, o);
40386
40699
  }
40387
40700
  const { root, rest } = artifact.kind === "sdist" ? stripWrapper(walkFilesRel(sandbox)) : { root: "", rest: walkFilesRel(sandbox) };
40388
- const scanRoot = join15(sandbox, root);
40701
+ const scanRoot = join16(sandbox, root);
40389
40702
  const scanOpts = { ...o, target };
40390
40703
  if (o.skipGitleaks !== true) {
40391
40704
  for (const f of scanTree({ ...scanOpts, dir: scanRoot, stateDir })) absorb(f, toInner(f.path, scanRoot));
@@ -40394,9 +40707,23 @@ async function scanPyPiArtifacts(o) {
40394
40707
  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
40708
  }
40396
40709
  for (const f of residueFindings(residuePypiHits(scanRoot, rest), {
40397
- root: basename2(artifact.path),
40710
+ root: basename3(artifact.path),
40398
40711
  sep: "!",
40399
- identity: basename2(artifact.path),
40712
+ identity: basename3(artifact.path),
40713
+ ...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
40714
+ })) absorb(f, f.path ?? "");
40715
+ for (const f of pypiArtifactCoherenceFindings({
40716
+ kind: artifact.kind,
40717
+ archiveName: basename3(artifact.path),
40718
+ files: rest,
40719
+ read: (rel) => {
40720
+ try {
40721
+ return readFileSync9(join16(scanRoot, rel), "utf8");
40722
+ } catch {
40723
+ return null;
40724
+ }
40725
+ },
40726
+ identity: basename3(artifact.path),
40400
40727
  ...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
40401
40728
  })) absorb(f, f.path ?? "");
40402
40729
  if (artifact.kind === "sdist") for (const f of manifestFindings(o, rest, target)) absorb(f, f.path ?? "");
@@ -40418,7 +40745,7 @@ async function scanPyPiArtifacts(o) {
40418
40745
  import { spawn } from "node:child_process";
40419
40746
  import { createHash as createHash10 } from "node:crypto";
40420
40747
  import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
40421
- import { basename as basename3, join as join16 } from "node:path";
40748
+ import { basename as basename4, join as join17 } from "node:path";
40422
40749
 
40423
40750
  // src/cli/exit.ts
40424
40751
  var EXIT_PASS = 0;
@@ -40459,7 +40786,7 @@ function translateError(err) {
40459
40786
 
40460
40787
  // src/push/core.ts
40461
40788
  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");
40789
+ var DIST_DIR = join17(BORDER_STATE_DIR, "dist");
40463
40790
  var defaultOut = (line) => void process.stdout.write(`${line}
40464
40791
  `);
40465
40792
  var defaultErr = (line) => void process.stderr.write(`${line}
@@ -40488,8 +40815,8 @@ function rehashRecordedArtifacts(kind, extensions, record2, repoDir) {
40488
40815
  }
40489
40816
  const files = [];
40490
40817
  for (const a of entries) {
40491
- const rel = join16(DIST_DIR, basename3(a.file));
40492
- const abs = join16(repoDir, rel);
40818
+ const rel = join17(DIST_DIR, basename4(a.file));
40819
+ const abs = join17(repoDir, rel);
40493
40820
  if (!existsSync11(abs)) return fail3(`${rel} is missing from the working tree`);
40494
40821
  const digest2 = createHash10("sha256").update(readFileSync10(abs)).digest("hex");
40495
40822
  if (digest2 !== a.sha256) return fail3(`${rel} no longer hashes to the digest recorded by the check`);
@@ -40665,7 +40992,7 @@ function twineAvailable(i, command) {
40665
40992
  let status = null;
40666
40993
  let failed = false;
40667
40994
  try {
40668
- const res = spawnSync7(command.bin, args, {
40995
+ const res = spawnSync8(command.bin, args, {
40669
40996
  encoding: "utf8",
40670
40997
  stdio: "ignore",
40671
40998
  ...i.env === void 0 ? {} : { env: { ...i.env } }
@@ -40980,7 +41307,7 @@ function npmNameClaimed(o) {
40980
41307
  return true;
40981
41308
  }
40982
41309
  function npmViewJson(args, registry2, timeoutMs, env2) {
40983
- return new Promise((resolve15, reject) => {
41310
+ return new Promise((resolve16, reject) => {
40984
41311
  const child = spawn2("npm", ["view", ...args, "--registry", registry2, "--fetch-retries=0", "--json"], {
40985
41312
  stdio: ["ignore", "pipe", "pipe"],
40986
41313
  ...env2 === void 0 ? {} : { env: env2 }
@@ -40999,7 +41326,7 @@ function npmViewJson(args, registry2, timeoutMs, env2) {
40999
41326
  });
41000
41327
  child.on("close", (status) => {
41001
41328
  clearTimeout(timer);
41002
- resolve15({ status: status ?? -1, stdout, stderr });
41329
+ resolve16({ status: status ?? -1, stdout, stderr });
41003
41330
  });
41004
41331
  });
41005
41332
  }
@@ -41095,14 +41422,14 @@ var npmChannel = {
41095
41422
  // src/channels/crates.ts
41096
41423
  import { createHash as createHash12 } from "node:crypto";
41097
41424
  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";
41425
+ import { join as join19, resolve as resolve8 } from "node:path";
41426
+ import { spawnSync as spawnSync10 } from "node:child_process";
41100
41427
 
41101
41428
  // src/artifacts/crates.ts
41102
41429
  import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync11, readdirSync as readdirSync7, renameSync, rmSync as rmSync8 } from "node:fs";
41103
41430
  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";
41431
+ import { join as join18, relative as relative6 } from "node:path";
41432
+ import { spawnSync as spawnSync9 } from "node:child_process";
41106
41433
 
41107
41434
  // src/artifacts/residueRust.ts
41108
41435
  function residueCratesHits(pkgDir, innerRels) {
@@ -41301,7 +41628,7 @@ function listFiles3(dir) {
41301
41628
  while (stack.length > 0) {
41302
41629
  const cur = stack.pop();
41303
41630
  for (const ent of readdirSync7(cur, { withFileTypes: true })) {
41304
- const p = join17(cur, ent.name);
41631
+ const p = join18(cur, ent.name);
41305
41632
  if (ent.isDirectory()) stack.push(p);
41306
41633
  else if (ent.isFile()) out.push(p);
41307
41634
  }
@@ -41309,7 +41636,7 @@ function listFiles3(dir) {
41309
41636
  return out;
41310
41637
  }
41311
41638
  function manifestIdentity(repoDir, cfg) {
41312
- const manifestPath = join17(repoDir, "Cargo.toml");
41639
+ const manifestPath = join18(repoDir, "Cargo.toml");
41313
41640
  if (!existsSync12(manifestPath)) {
41314
41641
  throw new EngineRunError("targets.crates is configured but the repo has no Cargo.toml to package", null);
41315
41642
  }
@@ -41337,7 +41664,7 @@ async function runCargoArtifactStage(o) {
41337
41664
  const bin = o.cargoBinPath ?? "cargo";
41338
41665
  const identity = manifestIdentity(repoDir, o.cfg);
41339
41666
  const crateName = `${identity.name}-${identity.version}.crate`;
41340
- const versionCheck = spawnSync8(bin, ["--version"], {
41667
+ const versionCheck = spawnSync9(bin, ["--version"], {
41341
41668
  encoding: "utf8",
41342
41669
  stdio: "ignore",
41343
41670
  ...o.env === void 0 ? {} : { env: { ...o.env } }
@@ -41348,14 +41675,14 @@ async function runCargoArtifactStage(o) {
41348
41675
  );
41349
41676
  }
41350
41677
  ensureStateDir(repoDir);
41351
- const stateDir = join17(repoDir, BORDER_STATE_DIR);
41352
- const distDir = join17(stateDir, "dist");
41353
- const targetDir = join17(stateDir, "tmp", "cargo-target");
41678
+ const stateDir = join18(repoDir, BORDER_STATE_DIR);
41679
+ const distDir = join18(stateDir, "dist");
41680
+ const targetDir = join18(stateDir, "tmp", "cargo-target");
41354
41681
  mkdirSync8(distDir, { recursive: true });
41355
41682
  rmSync8(targetDir, { recursive: true, force: true });
41356
41683
  mkdirSync8(targetDir, { recursive: true });
41357
41684
  try {
41358
- const result = spawnSync8(bin, ["package", "--allow-dirty", "--no-verify"], {
41685
+ const result = spawnSync9(bin, ["package", "--allow-dirty", "--no-verify"], {
41359
41686
  cwd: repoDir,
41360
41687
  encoding: "utf8",
41361
41688
  timeout: o.cargoTimeoutMs ?? 12e4,
@@ -41370,17 +41697,17 @@ async function runCargoArtifactStage(o) {
41370
41697
  result.status
41371
41698
  );
41372
41699
  }
41373
- const packedDir = join17(targetDir, "package");
41374
- const packed = join17(packedDir, crateName);
41700
+ const packedDir = join18(targetDir, "package");
41701
+ const packed = join18(packedDir, crateName);
41375
41702
  if (!existsSync12(packed)) {
41376
41703
  throw new EngineRunError(`cargo package produced no ${crateName} in ${relative6(repoDir, packedDir)} \u2014 failing closed`, null);
41377
41704
  }
41378
- const dist = join17(distDir, crateName);
41705
+ const dist = join18(distDir, crateName);
41379
41706
  renameSync(packed, dist);
41380
41707
  const bytes = readFileSync11(dist);
41381
41708
  const sha2563 = createHash11("sha256").update(bytes).digest("hex");
41382
41709
  const findings = [];
41383
- const extractDir = join17(stateDir, "tmp", sha2563.slice(0, 8));
41710
+ const extractDir = join18(stateDir, "tmp", sha2563.slice(0, 8));
41384
41711
  rmSync8(extractDir, { recursive: true, force: true });
41385
41712
  mkdirSync8(extractDir, { recursive: true });
41386
41713
  try {
@@ -41419,6 +41746,15 @@ async function runCargoArtifactStage(o) {
41419
41746
  identity: crateName,
41420
41747
  ...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
41421
41748
  }));
41749
+ const lockRel = `${identity.name}-${identity.version}/Cargo.lock`;
41750
+ findings.push(...cratesCoherenceFindings({
41751
+ name: identity.name,
41752
+ version: identity.version,
41753
+ lockText: relPaths.includes(lockRel) ? readFileSync11(join18(extractDir, lockRel), "utf8") : null,
41754
+ lockRelPath: lockRel,
41755
+ identity: crateName,
41756
+ ...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {}
41757
+ }));
41422
41758
  } finally {
41423
41759
  removeSandbox(extractDir);
41424
41760
  }
@@ -41574,11 +41910,11 @@ function cratesPublishArgv(files, _cfg) {
41574
41910
  return files.map(() => ["publish", "--allow-dirty", "--no-verify"]);
41575
41911
  }
41576
41912
  function repackageSameBytes(i, bin) {
41577
- const targetDir = join18(resolve8(i.repoDir), BORDER_STATE_DIR, "tmp", "publish-repackage");
41913
+ const targetDir = join19(resolve8(i.repoDir), BORDER_STATE_DIR, "tmp", "publish-repackage");
41578
41914
  rmSync9(targetDir, { recursive: true, force: true });
41579
41915
  mkdirSync9(targetDir, { recursive: true });
41580
41916
  try {
41581
- const r = spawnSync9(bin, ["package", "--allow-dirty", "--no-verify"], {
41917
+ const r = spawnSync10(bin, ["package", "--allow-dirty", "--no-verify"], {
41582
41918
  cwd: i.repoDir,
41583
41919
  encoding: "utf8",
41584
41920
  timeout: 12e4,
@@ -41590,14 +41926,14 @@ function repackageSameBytes(i, bin) {
41590
41926
  if (r.status !== 0) {
41591
41927
  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
41928
  }
41593
- const packedDir = join18(targetDir, "package");
41929
+ const packedDir = join19(targetDir, "package");
41594
41930
  const crates = readdirSync8(packedDir).filter((f) => f.endsWith(".crate")).sort();
41595
41931
  if (crates.length !== 1) {
41596
41932
  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
41933
  }
41598
41934
  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);
41935
+ const freshSha = createHash12("sha256").update(readFileSync12(join19(packedDir, crateFile))).digest("hex");
41936
+ const staged = join19(resolve8(i.repoDir), BORDER_STATE_DIR, "dist", crateFile);
41601
41937
  if (!existsSync13(staged)) {
41602
41938
  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
41939
  }
@@ -42001,6 +42337,21 @@ function buildBorderConfigSchema() {
42001
42337
  residue: external_exports.object({
42002
42338
  enabled: external_exports.boolean().default(true),
42003
42339
  requireProof: external_exports.boolean().default(false)
42340
+ }).strict().optional(),
42341
+ // W4.1 release-coherence twin obligations (plan border-inspect-roadmap §127-138):
42342
+ // the ONLY cross-manager declaration border can statically enforce — when a project
42343
+ // ships a PyPI/npm twin pair under one release train, their versions must be equal.
42344
+ // The per-artifact internal coherence rules (release-coherence-*) are ALWAYS on and
42345
+ // need no opt-in; this list is opt-in and strict — unknown keys fail typed exit 2.
42346
+ // The pair list rides the whole-object configDigest, so editing it rotates the check
42347
+ // rulesHash exactly like the residue block above.
42348
+ release: external_exports.object({
42349
+ twin: external_exports.array(
42350
+ external_exports.object({
42351
+ pypi: external_exports.string().min(1),
42352
+ npm: external_exports.string().min(1)
42353
+ }).strict()
42354
+ ).default([])
42004
42355
  }).strict().optional()
42005
42356
  }).strict();
42006
42357
  }
@@ -42088,7 +42439,7 @@ function deepMergeRaw(base, overlay) {
42088
42439
  return overlay;
42089
42440
  }
42090
42441
  function runGit3(cwd, args) {
42091
- const r = spawnSync10("git", args, { cwd, encoding: "utf8" });
42442
+ const r = spawnSync11("git", args, { cwd, encoding: "utf8" });
42092
42443
  return { ok: r.status === 0, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
42093
42444
  }
42094
42445
  function gitToplevel(cwd) {
@@ -42148,7 +42499,7 @@ function loadConfig(options = {}) {
42148
42499
  throw new ConfigError("unreadable", `config file not found: ${basePath}`);
42149
42500
  }
42150
42501
  } else {
42151
- for (const candidate of [join19(cwd, "border.yaml"), toplevel === void 0 ? "" : join19(toplevel, "border.yaml")]) {
42502
+ for (const candidate of [join20(cwd, "border.yaml"), toplevel === void 0 ? "" : join20(toplevel, "border.yaml")]) {
42152
42503
  if (candidate !== "" && existsSync14(candidate)) {
42153
42504
  basePath = candidate;
42154
42505
  baseText = readFileSync13(candidate, "utf8");
@@ -42163,7 +42514,7 @@ function loadConfig(options = {}) {
42163
42514
  ])];
42164
42515
  let overlay;
42165
42516
  for (const dir of overlayDirs) {
42166
- const p = join19(dir, ".border", "config.local.yaml");
42517
+ const p = join20(dir, ".border", "config.local.yaml");
42167
42518
  const text = readFileOrUndefined(p);
42168
42519
  if (text !== void 0) {
42169
42520
  overlay = { path: p, doc: yamlToJson(text, p) };
@@ -42208,10 +42559,10 @@ function exposureSet(cfg, options = {}) {
42208
42559
  }
42209
42560
 
42210
42561
  // src/check.ts
42211
- import { join as join21 } from "node:path";
42562
+ import { join as join22 } from "node:path";
42212
42563
 
42213
42564
  // src/rules/aiArtifacts.ts
42214
- import { spawnSync as spawnSync11 } from "node:child_process";
42565
+ import { spawnSync as spawnSync12 } from "node:child_process";
42215
42566
  import { dirname as dirname9, resolve as resolve10 } from "node:path";
42216
42567
  var AI_ARTIFACTS_ENGINE = "ai-artifacts";
42217
42568
  var BINARY_SNIFF_BYTES = 8e3;
@@ -42230,7 +42581,7 @@ function makeCtx(o) {
42230
42581
  return { repoDir, env: env2, timeoutMs: o.timeoutMs ?? 3e5, maxBufferBytes: o.maxBufferBytes ?? 32 * 1024 * 1024 };
42231
42582
  }
42232
42583
  function gitSpawn2(c, args, input2) {
42233
- const r = spawnSync11("git", ["-C", c.repoDir, ...args], {
42584
+ const r = spawnSync12("git", ["-C", c.repoDir, ...args], {
42234
42585
  encoding: "buffer",
42235
42586
  timeout: c.timeoutMs,
42236
42587
  maxBuffer: c.maxBufferBytes,
@@ -42490,10 +42841,10 @@ function proofFindings(o) {
42490
42841
  }
42491
42842
 
42492
42843
  // src/check/tagScan.ts
42493
- import { spawnSync as spawnSync12 } from "node:child_process";
42844
+ import { spawnSync as spawnSync13 } from "node:child_process";
42494
42845
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync10, writeFileSync as writeFileSync4 } from "node:fs";
42495
42846
  import { tmpdir as tmpdir2 } from "node:os";
42496
- import { join as join20, resolve as resolve12 } from "node:path";
42847
+ import { join as join21, resolve as resolve12 } from "node:path";
42497
42848
  var TAG_MESSAGE_RULE = "tag-message-secret";
42498
42849
  function isEnoent2(error61) {
42499
42850
  return typeof error61 === "object" && error61 !== null && error61.code === "ENOENT";
@@ -42513,7 +42864,7 @@ function stdinFindings(text, ignorePath, o) {
42513
42864
  ];
42514
42865
  let lastError;
42515
42866
  for (const bin of binaryCandidates("gitleaks", o)) {
42516
- const r = spawnSync12(bin, args, {
42867
+ const r = spawnSync13(bin, args, {
42517
42868
  encoding: "utf8",
42518
42869
  input: text,
42519
42870
  timeout: o.timeoutMs ?? 3e5,
@@ -42584,8 +42935,8 @@ function scanTagMessages(o) {
42584
42935
  const gitOpts = { ...o.env !== void 0 ? { env: o.env } : {} };
42585
42936
  const refs = runGitChecked(repoDir, ["for-each-ref", "--format=%(refname)", "refs/tags"], gitOpts).split("\n").filter((ref) => ref !== "");
42586
42937
  if (refs.length === 0) return [];
42587
- const ignoreDir = mkdtempSync3(join20(tmpdir2(), "border-gitleaks-ignore-"));
42588
- const ignorePath = join20(ignoreDir, ".gitleaksignore");
42938
+ const ignoreDir = mkdtempSync3(join21(tmpdir2(), "border-gitleaks-ignore-"));
42939
+ const ignorePath = join21(ignoreDir, ".gitleaksignore");
42589
42940
  writeFileSync4(ignorePath, "");
42590
42941
  try {
42591
42942
  const findings = [];
@@ -42661,7 +43012,7 @@ async function runPipeline(o, ctx, lockWarning) {
42661
43012
  if (!broken.has("gitleaks")) {
42662
43013
  const eng = legOptions(o, sanitizer);
42663
43014
  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));
43015
+ findings.push(...filterBorderStateFindings(scanTree({ dir: repoDir, stateDir: join22(repoDir, BORDER_STATE_DIR), target: "tree", ...eng }), repoDir));
42665
43016
  findings.push(...filterBorderStateFindings(scanTagMessages({ repoDir, target: "git", ...eng }), repoDir));
42666
43017
  }
42667
43018
  if (!broken.has("secretlint")) {
@@ -42686,6 +43037,17 @@ async function runPipeline(o, ctx, lockWarning) {
42686
43037
  findings.push(...stage.findings);
42687
43038
  ledgerArtifacts.push(...stage.artifacts);
42688
43039
  }
43040
+ if ((o.cfg.release?.twin.length ?? 0) > 0) {
43041
+ findings.push(
43042
+ ...await twinCoherenceFindings({
43043
+ pairs: o.cfg.release?.twin ?? [],
43044
+ artifacts: ledgerArtifacts,
43045
+ repoDir,
43046
+ ...envOpt,
43047
+ sanitizer
43048
+ })
43049
+ );
43050
+ }
42689
43051
  findings.push(...await runRegistryProbes({ repoDir, cfg: o.cfg, effectiveTargets: o.effectiveTargets, ...envOpt }));
42690
43052
  if (o.cfg.residue?.enabled === false) {
42691
43053
  for (let i = findings.length - 1; i >= 0; i -= 1) {
@@ -42838,16 +43200,16 @@ var NO_TARGETS_PROBE_CFG = {
42838
43200
  // src/llm/bundle.ts
42839
43201
  import { createHash as createHash13 } from "node:crypto";
42840
43202
  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";
43203
+ import { dirname as dirname11, join as join24 } from "node:path";
42842
43204
 
42843
43205
  // src/llm/diff.ts
42844
- import { spawnSync as spawnSync13 } from "node:child_process";
43206
+ import { spawnSync as spawnSync14 } from "node:child_process";
42845
43207
  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";
43208
+ import { dirname as dirname10, join as join23, resolve as resolve13 } from "node:path";
42847
43209
  var GIT_DIFF_MAX_BUFFER = 64 * 1024 * 1024;
42848
43210
  function gitOut(repoDir, args, o, allowFail = false) {
42849
43211
  const abs = resolve13(repoDir);
42850
- const r = spawnSync13("git", ["-C", abs, ...args], {
43212
+ const r = spawnSync14("git", ["-C", abs, ...args], {
42851
43213
  encoding: "utf8",
42852
43214
  env: { ...o.env ?? process.env, GIT_CEILING_DIRECTORIES: dirname10(abs) },
42853
43215
  timeout: 12e4,
@@ -42940,18 +43302,18 @@ function collectRawDeltas(repoDir, baseSha, headSha, o = {}) {
42940
43302
  var MASK_CHUNK_CHARS = 2e6;
42941
43303
  function maskViaEngineDetection(repoDir, texts, o = {}) {
42942
43304
  const sanitizer = new TextSanitizer();
42943
- const sandbox = mkdtempSync4(join22(repoDir, BORDER_STATE_DIR, "llm-mask-"));
43305
+ const sandbox = mkdtempSync4(join23(repoDir, BORDER_STATE_DIR, "llm-mask-"));
42944
43306
  try {
42945
43307
  let idx = 0;
42946
43308
  for (const text of texts) {
42947
43309
  if (text === "") continue;
42948
43310
  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");
43311
+ writeFileSync5(join23(sandbox, `detect-${String(idx)}.txt`), text.slice(off, off + MASK_CHUNK_CHARS), "utf8");
42950
43312
  }
42951
43313
  }
42952
43314
  scanTree({
42953
43315
  dir: sandbox,
42954
- stateDir: join22(repoDir, BORDER_STATE_DIR),
43316
+ stateDir: join23(repoDir, BORDER_STATE_DIR),
42955
43317
  target: "llm-mask",
42956
43318
  sanitizer,
42957
43319
  ...engineEnv(o)
@@ -43009,7 +43371,7 @@ async function runLlmRequestCore(ctx) {
43009
43371
  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
43372
  return EXIT_ERROR;
43011
43373
  }
43012
- const absReport = join23(repoDir, record2.reportPath);
43374
+ const absReport = join24(repoDir, record2.reportPath);
43013
43375
  if (!existsSync15(absReport)) {
43014
43376
  ctx.stderr(`border: llm-request: report archive missing (${record2.reportPath} pruned?) \u2014 run 'border check' again`);
43015
43377
  return EXIT_ERROR;
@@ -43068,7 +43430,7 @@ async function runLlmRequestCore(ctx) {
43068
43430
  deterministic: { verdict: report.verdict, counts: report.counts, findings: report.findings },
43069
43431
  promptTemplate: { path: templatePath, sha256: createHash13("sha256").update(readFileSync14(templatePath)).digest("hex") }
43070
43432
  };
43071
- const outPath = join23(dirname11(absReport), "llm-request.json");
43433
+ const outPath = join24(dirname11(absReport), "llm-request.json");
43072
43434
  mkdirSync10(dirname11(outPath), { recursive: true });
43073
43435
  writeFileSync6(outPath, `${JSON.stringify(bundle, null, 2)}
43074
43436
  `, "utf8");
@@ -43080,7 +43442,7 @@ async function runLlmRequestCore(ctx) {
43080
43442
  // src/llm/ingest.ts
43081
43443
  import { createHash as createHash14 } from "node:crypto";
43082
43444
  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";
43445
+ import { isAbsolute, join as join25, resolve as resolve14 } from "node:path";
43084
43446
  var RULE_RE = /^[a-z0-9-]+$/;
43085
43447
  var MAX_TEXT = 4e3;
43086
43448
  var ALLOWED_KEYS = /* @__PURE__ */ new Set(["rule", "severity", "target", "path", "line", "commit", "message", "snippet", "engine", "valueDigest"]);
@@ -43099,7 +43461,7 @@ function findLatestBundle(repoDir) {
43099
43461
  return "no llm-request bundle found \u2014 run 'border llm-request' first";
43100
43462
  }
43101
43463
  for (const d of dirs.sort().reverse()) {
43102
- const p = join24(runsDir(repoDir), d, "llm-request.json");
43464
+ const p = join25(runsDir(repoDir), d, "llm-request.json");
43103
43465
  if (!existsSync16(p)) continue;
43104
43466
  let parsed;
43105
43467
  try {
@@ -43112,7 +43474,7 @@ function findLatestBundle(repoDir) {
43112
43474
  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
43475
  return `bundle ${p} fails its own schema \u2014 re-run 'border llm-request'`;
43114
43476
  }
43115
- return { dir: join24(runsDir(repoDir), d), bundle: b };
43477
+ return { dir: join25(runsDir(repoDir), d), bundle: b };
43116
43478
  }
43117
43479
  return "no llm-request bundle found \u2014 run 'border llm-request' first";
43118
43480
  }
@@ -43257,7 +43619,7 @@ async function runLlmRequest(ctx) {
43257
43619
  }
43258
43620
 
43259
43621
  // src/push/git.ts
43260
- import { spawnSync as spawnSync14 } from "node:child_process";
43622
+ import { spawnSync as spawnSync15 } from "node:child_process";
43261
43623
  var DIVERGED_MESSAGE = "history diverged; border never force-pushes; resolve manually";
43262
43624
  function pushArgv(url2, branch, mode) {
43263
43625
  return mode === "dry-run" ? ["push", "--dry-run", url2, branch, "--follow-tags"] : ["push", url2, branch, "--follow-tags"];
@@ -43292,7 +43654,7 @@ function lsRemoteMap(repoDir, remote, env2) {
43292
43654
  return parseLsRemote(text, remote.target);
43293
43655
  }
43294
43656
  function isAncestor(repoDir, remoteSha, localSha, env2) {
43295
- const r = spawnSync14("git", ["-C", repoDir, "merge-base", "--is-ancestor", remoteSha, localSha], {
43657
+ const r = spawnSync15("git", ["-C", repoDir, "merge-base", "--is-ancestor", remoteSha, localSha], {
43296
43658
  encoding: "utf8",
43297
43659
  env: { ...env2, GIT_CEILING_DIRECTORIES: repoDir }
43298
43660
  });
@@ -43327,7 +43689,7 @@ function pushTimeoutMs(env2) {
43327
43689
  }
43328
43690
  function executePush(i) {
43329
43691
  const timeoutMs = pushTimeoutMs(i.env);
43330
- const r = spawnSync14("git", ["-C", i.repoDir, ...pushArgv(i.remote.url, i.branch, "execute")], {
43692
+ const r = spawnSync15("git", ["-C", i.repoDir, ...pushArgv(i.remote.url, i.branch, "execute")], {
43331
43693
  stdio: "inherit",
43332
43694
  env: i.env,
43333
43695
  timeout: timeoutMs
@@ -43524,14 +43886,14 @@ async function executeYesPush(ctx, loaded) {
43524
43886
  import { mkdtempSync as mkdtempSync5, rmSync as rmSync12, writeFileSync as writeFileSync7 } from "node:fs";
43525
43887
  import { randomBytes } from "node:crypto";
43526
43888
  import { tmpdir as tmpdir3 } from "node:os";
43527
- import { join as join25 } from "node:path";
43889
+ import { join as join26 } from "node:path";
43528
43890
 
43529
43891
  // src/scan/fetch.ts
43530
43892
  import { createHash as createHash15 } from "node:crypto";
43531
- import { basename as basename4 } from "node:path";
43893
+ import { basename as basename5 } from "node:path";
43532
43894
  var SCAN_MAX_ARTIFACT_BYTES = 200 * 1024 * 1024;
43533
43895
  var SCAN_REQUEST_TIMEOUT_MS = 6e4;
43534
- var SCAN_USER_AGENT = "border-customs/0.4.0 (+https://github.com/sumteclab/border; border scan)";
43896
+ var SCAN_USER_AGENT = "border-customs/0.4.1 (+https://github.com/sumteclab/border; border scan)";
43535
43897
  var CRATES_DOWNLOAD_HOST = "https://static.crates.io";
43536
43898
  var defaultScanFetcher = async (url2, init) => {
43537
43899
  if (!/^https?:\/\//.test(url2)) throw new EngineRunError(`border scan refuses non-http(s) URL: ${url2}`, null);
@@ -43607,7 +43969,7 @@ function rubygemsDownloadUrl(name, version2, base = channelBaseUrl("rubygems"))
43607
43969
  return `${base}/downloads/${name}-${version2}.gem`;
43608
43970
  }
43609
43971
  function safeArtifactName(raw, fallback) {
43610
- const base = basename4(raw.split("?")[0] ?? "").replace(/[^A-Za-z0-9._+-]/g, "_");
43972
+ const base = basename5(raw.split("?")[0] ?? "").replace(/[^A-Za-z0-9._+-]/g, "_");
43611
43973
  return base.length > 0 && base !== "." && base !== ".." ? base : fallback;
43612
43974
  }
43613
43975
  async function fetchArtifact(spec, o) {
@@ -43699,6 +44061,109 @@ function parseScanSpec(raw) {
43699
44061
  return { ecosystem, name, version: version2 };
43700
44062
  }
43701
44063
 
44064
+ // src/roundtrip/localArtifact.ts
44065
+ import { createReadStream, existsSync as existsSync17, statSync as statSync2 } from "node:fs";
44066
+ import { createHash as createHash16 } from "node:crypto";
44067
+ import { basename as basename6, resolve as resolve15 } from "node:path";
44068
+ var EXT_TABLE = /* @__PURE__ */ new Map([
44069
+ [".whl", "pypi"],
44070
+ [".tgz", "npm"],
44071
+ [".crate", "crates"],
44072
+ [".gem", "rubygems"]
44073
+ ]);
44074
+ var EXT_LIST = ".whl (pypi), .tgz (npm), .crate (crates), .gem (rubygems)";
44075
+ var CONTAINER_NAME = /* @__PURE__ */ new Map([
44076
+ [".tgz", "/root/p.tgz"],
44077
+ [".crate", "/root/p.crate"],
44078
+ [".gem", "/root/p.gem"]
44079
+ ]);
44080
+ var wheelContainerPath = (base, name, version2) => `/root/${safeArtifactName(base, `${name}-${version2}-py3-none-any.whl`)}`;
44081
+ var bad3 = (why) => {
44082
+ throw new UnknownArgError(`border roundtrip: ${why}`);
44083
+ };
44084
+ function identityFromFilename(base, eco) {
44085
+ if (eco === "pypi") {
44086
+ const parts = base.slice(0, -".whl".length).split("-");
44087
+ const name = parts[0] ?? "";
44088
+ const version2 = parts[1] ?? "";
44089
+ if (parts.length < 5 || !/^[A-Za-z0-9][A-Za-z0-9._]*$/.test(name) || !/^\d[A-Za-z0-9._+!]*$/.test(version2)) return null;
44090
+ return { name, version: version2 };
44091
+ }
44092
+ if (eco === "npm") {
44093
+ const m2 = /^(.+)-(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)\.tgz$/i.exec(base);
44094
+ if (m2 === null || m2[1] === void 0 || m2[2] === void 0) return null;
44095
+ return { name: m2[1], version: m2[2] };
44096
+ }
44097
+ if (eco === "crates") {
44098
+ const m2 = /^([a-z0-9_][a-z0-9._-]*?)-(\d[A-Za-z0-9._+-]*)\.crate$/i.exec(base);
44099
+ if (m2 === null || m2[1] === void 0 || m2[2] === void 0) return null;
44100
+ return { name: m2[1], version: m2[2] };
44101
+ }
44102
+ const m = /^([A-Za-z0-9][A-Za-z0-9._-]*?)-(\d[A-Za-z0-9._+-]*)\.gem$/i.exec(base);
44103
+ if (m === null || m[1] === void 0 || m[2] === void 0) return null;
44104
+ return { name: m[1], version: m[2] };
44105
+ }
44106
+ var GRAMMAR_HINT = {
44107
+ pypi: "PEP 427 wheel names, e.g. jupyter_core-5.8.1-py3-none-any.whl",
44108
+ npm: "npm pack names, e.g. node-fetch-2.6.1.tgz (scoped packages flatten to scope-pkg-1.0.0.tgz)",
44109
+ crates: "cargo package names, e.g. rt-crate-1.0.0.crate",
44110
+ rubygems: "gem package names, e.g. rt-gem-1.0.0.gem"
44111
+ };
44112
+ function classifyLocalInput(raw, cwd, parsesAsRegistrySpec) {
44113
+ const abs = resolve15(cwd, raw);
44114
+ if (existsSync17(abs)) {
44115
+ const st = statSync2(abs);
44116
+ if (st.isDirectory()) {
44117
+ bad3(`'${raw}' resolves to a directory (${abs}) \u2014 local mode needs a single artifact FILE, not a directory`);
44118
+ }
44119
+ if (!st.isFile()) {
44120
+ bad3(`'${raw}' resolves to something that is not a regular file (${abs})`);
44121
+ }
44122
+ const lower = basename6(abs).toLowerCase();
44123
+ const ext = [...EXT_TABLE.keys()].find((e) => lower.endsWith(e)) ?? null;
44124
+ if (ext === null) {
44125
+ const known = [...EXT_TABLE.keys()].map((e) => `\`${e}\``).join(", ");
44126
+ 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}`);
44127
+ }
44128
+ const eco = EXT_TABLE.get(ext);
44129
+ const identity = identityFromFilename(basename6(abs), eco);
44130
+ if (identity === null) {
44131
+ bad3(`cannot derive package identity from '${basename6(abs)}' \u2014 expected ${GRAMMAR_HINT[eco]}`);
44132
+ }
44133
+ return {
44134
+ absPath: abs,
44135
+ ecosystem: eco,
44136
+ name: identity.name,
44137
+ version: identity.version,
44138
+ containerPath: ext === ".whl" ? wheelContainerPath(basename6(abs), identity.name, identity.version) : CONTAINER_NAME.get(ext),
44139
+ source: `local:${abs}`
44140
+ };
44141
+ }
44142
+ const looksLocal = raw.includes("/") || [...EXT_TABLE.keys()].some((e) => raw.toLowerCase().endsWith(e));
44143
+ if (looksLocal && !parsesAsRegistrySpec(raw)) {
44144
+ 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`);
44145
+ }
44146
+ return null;
44147
+ }
44148
+ async function readLocalArtifact(absPath, cap = SCAN_MAX_ARTIFACT_BYTES) {
44149
+ const hash2 = createHash16("sha256");
44150
+ const parts = [];
44151
+ let total = 0;
44152
+ for await (const chunk of createReadStream(absPath)) {
44153
+ const b = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
44154
+ total += b.byteLength;
44155
+ if (total > cap) {
44156
+ throw new EngineRunError(
44157
+ `border roundtrip: ${absPath} streams past the 200 MiB artifact size cap after ${String(total)} bytes \u2014 refusing to prove a partial read (fail-closed)`,
44158
+ null
44159
+ );
44160
+ }
44161
+ hash2.update(b);
44162
+ parts.push(b);
44163
+ }
44164
+ return { bytes: Buffer.concat(parts), sha256: hash2.digest("hex") };
44165
+ }
44166
+
43702
44167
  // src/roundtrip/docker.ts
43703
44168
  var DOCKER_PROBE_TIMEOUT_MS = 15e3;
43704
44169
  var DOCKER_RUN_TIMEOUT_MS = 12e4;
@@ -43743,8 +44208,171 @@ function realDockerExec() {
43743
44208
  };
43744
44209
  }
43745
44210
 
44211
+ // src/roundtrip/calibrate.ts
44212
+ var RT_DEP_RULE = "residue-roundtrip-dep-owned";
44213
+ var PIP_REPORT_PATH = "/dev/shm/rt-report.json";
44214
+ function normalizePipName(name) {
44215
+ return name.toLowerCase().replace(/[-_.]+/g, "-");
44216
+ }
44217
+ function parsePipReport(text) {
44218
+ const die = (why) => {
44219
+ throw new EngineRunError(`roundtrip: pypi closure report unusable (${why}) \u2014 cannot verify (fail-closed)`, null);
44220
+ };
44221
+ let doc;
44222
+ try {
44223
+ doc = JSON.parse(text);
44224
+ } catch {
44225
+ die("not JSON");
44226
+ }
44227
+ if (typeof doc !== "object" || doc === null) die("not an object");
44228
+ const rec = doc;
44229
+ if (String(rec.version) !== "1") die(`report format v${String(rec.version)} is not the v1 surface we validated on pip 25.0.1`);
44230
+ if (!Array.isArray(rec.install)) die("install is not an array");
44231
+ const closure = /* @__PURE__ */ new Map();
44232
+ for (const item of rec.install) {
44233
+ const name = item?.metadata?.name;
44234
+ const version2 = item?.metadata?.version;
44235
+ if (typeof name !== "string" || name.length === 0 || typeof version2 !== "string" || version2.length === 0) {
44236
+ throw new EngineRunError("roundtrip: pypi closure report unusable (an install entry lacks metadata.name/version) \u2014 cannot verify (fail-closed)", null);
44237
+ }
44238
+ closure.set(normalizePipName(name), version2);
44239
+ }
44240
+ if (closure.size === 0) die("empty install list \u2014 a resolvable target must report at least itself");
44241
+ return closure;
44242
+ }
44243
+ function claim2(map2, ambiguous, path4, owner) {
44244
+ if (ambiguous.has(path4)) return;
44245
+ const cur = map2.get(path4);
44246
+ if (cur === void 0) map2.set(path4, owner);
44247
+ else if (cur !== owner) {
44248
+ map2.delete(path4);
44249
+ ambiguous.add(path4);
44250
+ }
44251
+ }
44252
+ function parentOf(path4) {
44253
+ const cut = path4.lastIndexOf("/");
44254
+ return cut <= 0 ? "/" : path4.slice(0, cut);
44255
+ }
44256
+ function buildAttribution(claimText, guard) {
44257
+ const exactOwners = /* @__PURE__ */ new Map();
44258
+ const dirOwners = /* @__PURE__ */ new Map();
44259
+ const ambiguous = /* @__PURE__ */ new Set();
44260
+ const claimants = /* @__PURE__ */ new Map();
44261
+ const normCount = /* @__PURE__ */ new Map();
44262
+ const lines = claimText.split("\n");
44263
+ if (guard !== void 0) {
44264
+ for (const line of lines) {
44265
+ if (!line.startsWith("@ ")) continue;
44266
+ const f = line.slice(2).split(" ");
44267
+ if (f.length !== 6) continue;
44268
+ const [owner, norm, ver, di] = f;
44269
+ normCount.set(norm, (normCount.get(norm) ?? 0) + 1);
44270
+ if (!claimants.has(owner)) claimants.set(owner, { owner, norm, ver, di, root: parentOf(di), dirName: f[4], dirVer: f[5] });
44271
+ }
44272
+ }
44273
+ const legit = (owner) => {
44274
+ if (guard === void 0) return {};
44275
+ const c = claimants.get(owner);
44276
+ if (c === void 0) return void 0;
44277
+ if (c.norm === guard.target) return void 0;
44278
+ if (guard.pins.get(c.norm) !== c.ver) return void 0;
44279
+ if (c.dirName !== c.norm || c.dirVer !== c.ver) return void 0;
44280
+ if ((normCount.get(c.norm) ?? 0) !== 1) return void 0;
44281
+ return c;
44282
+ };
44283
+ for (const line of lines) {
44284
+ if (line.trim().length === 0 || line.startsWith("@ ")) continue;
44285
+ const tab = line.indexOf(" ");
44286
+ if (tab <= 0 || line.indexOf(" ", tab + 1) !== -1) continue;
44287
+ const owner = line.slice(0, tab);
44288
+ const path4 = line.slice(tab + 1);
44289
+ if (!path4.startsWith("/") || path4.length === 1) continue;
44290
+ if (guard !== void 0) {
44291
+ const c = legit(owner);
44292
+ if (c === void 0 || c.root === void 0 || !path4.startsWith(`${c.root}/`)) continue;
44293
+ }
44294
+ claim2(exactOwners, ambiguous, path4, owner);
44295
+ for (let dir = parentOf(path4); dir.length > 1; dir = parentOf(dir)) {
44296
+ claim2(dirOwners, ambiguous, dir, owner);
44297
+ }
44298
+ }
44299
+ return { exactOwners, dirOwners, ambiguous };
44300
+ }
44301
+ function ownerOf(entry, attribution) {
44302
+ const exact = attribution.exactOwners.get(entry.path);
44303
+ if (exact !== void 0) return exact;
44304
+ return entry.kind === "D" ? attribution.dirOwners.get(entry.path) : void 0;
44305
+ }
44306
+ var CLAIM_SCANNER_PY = [
44307
+ "import csv, json, os, re, site, sys",
44308
+ "try:",
44309
+ " closure = json.loads(os.environ['BORDER_CLOSURE'])",
44310
+ " assert isinstance(closure, dict) and closure",
44311
+ "except Exception:",
44312
+ " sys.exit(4)",
44313
+ "def norm(n):",
44314
+ " return re.sub(r'[-_.]+', '-', n).lower()",
44315
+ "def read_meta(di):",
44316
+ " name = ''",
44317
+ " ver = ''",
44318
+ " try:",
44319
+ " f = open(os.path.join(di, 'METADATA'), encoding='utf-8', errors='replace')",
44320
+ " except OSError:",
44321
+ " return None",
44322
+ " with f:",
44323
+ " for raw in f:",
44324
+ " line = raw.rstrip('\\n')",
44325
+ " if line == '':",
44326
+ " break",
44327
+ " if not name and line.startswith('Name:'):",
44328
+ " name = line[5:].strip()",
44329
+ " elif not ver and line.startswith('Version:'):",
44330
+ " ver = line[8:].strip()",
44331
+ " return (name, ver) if name and ver else None",
44332
+ "roots = []",
44333
+ "try:",
44334
+ " roots.extend(site.getsitepackages())",
44335
+ "except Exception:",
44336
+ " pass",
44337
+ "out = []",
44338
+ "seen = set()",
44339
+ "for root in roots:",
44340
+ " if not os.path.isdir(root) or root in seen:",
44341
+ " continue",
44342
+ " seen.add(root)",
44343
+ " for d in sorted(os.listdir(root)):",
44344
+ " if not d.endswith('.dist-info'):",
44345
+ " continue",
44346
+ " di = os.path.join(root, d)",
44347
+ " meta = read_meta(di)",
44348
+ " if meta is None or norm(meta[0]) not in closure:",
44349
+ " continue",
44350
+ " owner = meta[0] + '==' + meta[1]",
44351
+ " core = d[:-10]",
44352
+ " cut = core.rfind('-')",
44353
+ " dn = norm(core[:cut]) if cut > 0 else ''",
44354
+ " dv = core[cut + 1:] if cut > 0 else ''",
44355
+ " out.append('@\\t' + owner + '\\t' + norm(meta[0]) + '\\t' + meta[1] + '\\t' + di + '\\t' + dn + '\\t' + dv)",
44356
+ " out.append(owner + '\\t' + di)",
44357
+ " for base, _dirs, files in os.walk(di):",
44358
+ " for fn in sorted(files):",
44359
+ " out.append(owner + '\\t' + os.path.join(base, fn))",
44360
+ " try:",
44361
+ " rf = open(os.path.join(di, 'RECORD'), newline='', encoding='utf-8', errors='replace')",
44362
+ " with rf:",
44363
+ " for row in csv.reader(rf):",
44364
+ " if not row or not row[0].strip():",
44365
+ " continue",
44366
+ " p = os.path.normpath(os.path.join(root, row[0].strip()))",
44367
+ " if p.startswith(root + os.sep):",
44368
+ " out.append(owner + '\\t' + p)",
44369
+ " except Exception:",
44370
+ " pass",
44371
+ "sys.stdout.write(''.join(x + '\\n' for x in out))"
44372
+ ].join("\n");
44373
+
43746
44374
  // src/roundtrip/manifest.ts
43747
- import { createHash as createHash16 } from "node:crypto";
44375
+ import { createHash as createHash17 } from "node:crypto";
43748
44376
  var RT_ENGINE = "roundtrip";
43749
44377
  var RT_RESIDUE_RULE = "roundtrip-residue-file";
43750
44378
  var RT_MODIFY_RULE = "roundtrip-modified-file";
@@ -43795,10 +44423,24 @@ var PERSISTENCE_PATTERNS = [
43795
44423
  function isPersistenceSurface(path4) {
43796
44424
  return PERSISTENCE_PATTERNS.some((re) => re.test(path4));
43797
44425
  }
43798
- var digestOf = (what) => createHash16("sha256").update(what).digest("hex");
43799
- function classifyResidue(label, diff) {
44426
+ var digestOf = (what) => createHash17("sha256").update(what).digest("hex");
44427
+ function classifyResidue(label, diff, attribution = null) {
43800
44428
  const findings = [];
43801
44429
  for (const e of diff.added) {
44430
+ const owner = attribution !== null ? ownerOf(e, attribution) : void 0;
44431
+ if (owner !== void 0) {
44432
+ findings.push({
44433
+ rule: RT_DEP_RULE,
44434
+ severity: "LOW",
44435
+ target: label,
44436
+ path: e.path,
44437
+ engine: RT_ENGINE,
44438
+ message: `ADDED path survives uninstall but is claimed by still-installed dependency '${owner}' (pip-accountable) \u2014 demoted, non-blocking`,
44439
+ valueDigest: digestOf(`ADDED:${e.path}:${e.value}`),
44440
+ snippet: `dep-owned: ${e.path}`
44441
+ });
44442
+ continue;
44443
+ }
43802
44444
  findings.push({
43803
44445
  rule: RT_RESIDUE_RULE,
43804
44446
  severity: "HIGH",
@@ -43889,7 +44531,46 @@ function execChecked(exec, args, timeoutMs, what) {
43889
44531
  }
43890
44532
  return r;
43891
44533
  }
43892
- function runPipeline2(exec, profile, spec, label, hostPath, container) {
44534
+ function resolvePipClosure(exec, profile, hostPath, label, spec) {
44535
+ const ctr = containerNameFor(`${label}#closure`);
44536
+ try {
44537
+ execChecked(exec, ["run", "-d", "--name", ctr, profile.image, "sleep", String(CONTAINER_TTL_SEC)], DOCKER_RUN_TIMEOUT_MS, "closure resolver start");
44538
+ execChecked(exec, ["cp", hostPath, `${ctr}:${profile.artifactPath}`], DOCKER_CP_TIMEOUT_MS, "resolver artifact copy");
44539
+ execChecked(
44540
+ exec,
44541
+ ["exec", ctr, "pip", "install", "--dry-run", "--report", PIP_REPORT_PATH, "--no-input", "--root-user-action=ignore", profile.artifactPath],
44542
+ PHASE_TIMEOUT_MS,
44543
+ "pypi closure dry-run"
44544
+ );
44545
+ const rep = execChecked(exec, ["exec", ctr, "cat", PIP_REPORT_PATH], SNAPSHOT_TIMEOUT_MS, "pypi closure report read");
44546
+ const closure = parsePipReport(rep.stdout);
44547
+ if (!closure.has(normalizePipName(spec.name))) {
44548
+ throw new EngineRunError(`roundtrip: pip closure report does not list the target ${spec.name} \u2014 cannot verify (fail-closed)`, null);
44549
+ }
44550
+ return closure;
44551
+ } finally {
44552
+ try {
44553
+ exec(["rm", "-f", ctr], DOCKER_RM_TIMEOUT_MS);
44554
+ } catch {
44555
+ }
44556
+ }
44557
+ }
44558
+ function scanDepClaims(exec, container, closure, target, note) {
44559
+ const guard = { pins: closure, target };
44560
+ const argv = ["exec", "-e", `BORDER_CLOSURE=${JSON.stringify(Object.fromEntries(closure))}`, container, "python3", "-c", CLAIM_SCANNER_PY];
44561
+ try {
44562
+ const r = exec(argv, SNAPSHOT_TIMEOUT_MS);
44563
+ if (r.status !== 0) {
44564
+ note(`roundtrip: dep-attribution scan failed (exit ${String(r.status)}) \u2014 every row keeps its blocking grade (over-report tolerated)`);
44565
+ return buildAttribution("", guard);
44566
+ }
44567
+ return buildAttribution(r.stdout, guard);
44568
+ } catch {
44569
+ note("roundtrip: dep-attribution scan could not run \u2014 every row keeps its blocking grade (over-report tolerated)");
44570
+ return buildAttribution("", guard);
44571
+ }
44572
+ }
44573
+ function runPipeline2(exec, profile, spec, label, hostPath, container, closure, note) {
43893
44574
  execChecked(exec, ["run", "-d", "--name", container, profile.image, "sleep", String(CONTAINER_TTL_SEC)], DOCKER_RUN_TIMEOUT_MS, "container start");
43894
44575
  try {
43895
44576
  execChecked(exec, ["cp", hostPath, `${container}:${profile.artifactPath}`], DOCKER_CP_TIMEOUT_MS, "artifact copy");
@@ -43906,8 +44587,9 @@ function runPipeline2(exec, profile, spec, label, hostPath, container) {
43906
44587
  }
43907
44588
  execChecked(exec, ["exec", container, ...profile.uninstall(spec)], PHASE_TIMEOUT_MS, `uninstall ${label}`);
43908
44589
  const m3 = parseManifest(takeSnapshot(exec, container, profile.excludes));
44590
+ const attribution = closure !== null ? scanDepClaims(exec, container, closure, normalizePipName(spec.name), note) : null;
43909
44591
  return {
43910
- findings: classifyResidue(label, diffManifests(m1, m3)),
44592
+ findings: classifyResidue(label, diffManifests(m1, m3), attribution),
43911
44593
  installDelta,
43912
44594
  baselineDigest: sha256Hex([...m1.keys()].join("\n"))
43913
44595
  };
@@ -43930,24 +44612,29 @@ async function runRoundtripCore(ctx, deps = {}) {
43930
44612
  const raw = ctx.positionals[0];
43931
44613
  if (ctx.positionals.length !== 1 || raw === void 0) {
43932
44614
  throw new UnknownArgError(
43933
- `border roundtrip expects exactly one <spec> argument (got ${String(ctx.positionals.length)}); ${SCAN_SPEC_USAGE}`
44615
+ `border roundtrip expects exactly one argument \u2014 a registry spec or a local artifact file (got ${String(ctx.positionals.length)}); ${SCAN_SPEC_USAGE}`
43934
44616
  );
43935
44617
  }
43936
- const spec = parseScanSpec(raw);
43937
- const profile = profileFor(spec.ecosystem);
44618
+ const local = classifyLocalInput(raw, ctx.cwd, parsesRegistrySpec);
44619
+ const spec = local !== null ? { ecosystem: local.ecosystem, name: local.name, version: local.version } : parseScanSpec(raw);
44620
+ const laneProfile = profileFor(spec.ecosystem);
44621
+ const profile = local !== null ? { ...laneProfile, artifactPath: local.containerPath } : laneProfile;
43938
44622
  const label = `${spec.ecosystem}:${spec.name}@${spec.version}`;
43939
44623
  const exec = deps.exec ?? realDockerExec();
43940
44624
  if (!dockerAvailableGuard(exec)) {
43941
44625
  ctx.stderr("roundtrip: docker unavailable \u2014 cannot verify");
43942
44626
  return EXIT_ERROR;
43943
44627
  }
43944
- const baseDir = mkdtempSync5(join25(tmpdir3(), "border-roundtrip-"));
44628
+ const baseDir = mkdtempSync5(join26(tmpdir3(), "border-roundtrip-"));
43945
44629
  try {
43946
- const { bytes } = await fetchArtifact(spec, { fetcher: deps.fetcher ?? defaultScanFetcher });
43947
- const hostPath = join25(baseDir, "artifact");
44630
+ const localRead = local !== null ? await readLocalArtifact(local.absPath) : null;
44631
+ const bytes = localRead?.bytes ?? (await fetchArtifact(spec, { fetcher: deps.fetcher ?? defaultScanFetcher })).bytes;
44632
+ const artifactSha256 = localRead !== null ? localRead.sha256 : sha256Hex(bytes);
44633
+ const hostPath = join26(baseDir, "artifact");
43948
44634
  writeFileSync7(hostPath, bytes);
43949
44635
  const container = containerNameFor(label);
43950
- const { findings, installDelta, baselineDigest } = runPipeline2(exec, profile, spec, label, hostPath, container);
44636
+ const closure = spec.ecosystem === "pypi" ? resolvePipClosure(exec, profile, hostPath, label, spec) : null;
44637
+ const { findings, installDelta, baselineDigest } = runPipeline2(exec, profile, spec, label, hostPath, container, closure, (msg) => ctx.stderr(msg));
43951
44638
  const verdict = computeVerdict(findings);
43952
44639
  const report = {
43953
44640
  schemaVersion: 1,
@@ -43960,6 +44647,10 @@ async function runRoundtripCore(ctx, deps = {}) {
43960
44647
  verdict,
43961
44648
  counts: countFindings(findings),
43962
44649
  findings,
44650
+ // W2.4(b) provenance honesty: a local run proves ITS OWN bytes. The
44651
+ // fields are absent (byte-identical JSON) on the registry lane and
44652
+ // never phrase the artifact as registry-verified.
44653
+ ...local !== null ? { source: local.source, artifactSha256 } : {},
43963
44654
  ts: (/* @__PURE__ */ new Date()).toISOString()
43964
44655
  };
43965
44656
  const sanitizer = new TextSanitizer();
@@ -43967,19 +44658,34 @@ async function runRoundtripCore(ctx, deps = {}) {
43967
44658
  if (ctx.flags.json) {
43968
44659
  ctx.stdout(renderReportJson(report));
43969
44660
  } else {
44661
+ if (local !== null) {
44662
+ ctx.stdout(line(`roundtrip: source ${local.source} artifact sha256 ${artifactSha256}`));
44663
+ }
43970
44664
  for (const f of report.findings) {
43971
44665
  ctx.stdout(line(` ${f.severity} ${f.rule} [${f.engine}] ${f.path ?? f.target} ${f.message}`));
43972
44666
  }
44667
+ const depOwned = findings.reduce((n, f) => n + (f.rule === RT_DEP_RULE ? 1 : 0), 0);
44668
+ if (depOwned > 0) {
44669
+ ctx.stdout(line(`roundtrip: ${String(depOwned)} row(s) attributed to still-installed pip dependencies \u2014 demoted to ${RT_DEP_RULE} (non-blocking, informational)`));
44670
+ }
43973
44671
  ctx.stdout(line(`roundtrip: ${String(report.findings.length)} residue row(s); install-delta ${String(installDelta)} files`));
43974
44672
  }
43975
44673
  if (ctx.flags.record !== false) {
43976
- await recordRoundtripProof(ctx, sha256Hex(bytes), report.findings.length, report.findings.length === 0 ? "clean" : "residue");
44674
+ await recordRoundtripProof(ctx, artifactSha256, report.findings.length, report.findings.length === 0 ? "clean" : "residue");
43977
44675
  }
43978
44676
  return exitCodeFromVerdict(verdict);
43979
44677
  } finally {
43980
44678
  rmSync12(baseDir, { recursive: true, force: true });
43981
44679
  }
43982
44680
  }
44681
+ function parsesRegistrySpec(raw) {
44682
+ try {
44683
+ parseScanSpec(raw);
44684
+ return true;
44685
+ } catch {
44686
+ return false;
44687
+ }
44688
+ }
43983
44689
  async function recordRoundtripProof(ctx, artifactSha256, rows, verdict) {
43984
44690
  const notice = (msg) => ctx.stderr(`roundtrip: ${msg}`);
43985
44691
  try {
@@ -44027,7 +44733,7 @@ var runRoundtrip = (ctx) => runRoundtripCore(ctx);
44027
44733
  // src/scan/scan.ts
44028
44734
  import { mkdtempSync as mkdtempSync6 } from "node:fs";
44029
44735
  import { tmpdir as tmpdir4 } from "node:os";
44030
- import { join as join27 } from "node:path";
44736
+ import { join as join28 } from "node:path";
44031
44737
 
44032
44738
  // src/scan/config.ts
44033
44739
  function buildScanConfigYaml(eco) {
@@ -44054,8 +44760,8 @@ function synthesizeScanConfig(eco) {
44054
44760
  }
44055
44761
 
44056
44762
  // 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";
44763
+ import { existsSync as existsSync18, mkdirSync as mkdirSync11, readdirSync as readdirSync10, readFileSync as readFileSync16, rmSync as rmSync13, writeFileSync as writeFileSync8 } from "node:fs";
44764
+ import { join as join27 } from "node:path";
44059
44765
  var CRATES_ENVELOPE_FILES = ["Cargo.toml.orig", ".cargo-ok", ".cargo_vcs_info.json"];
44060
44766
  var fail2 = (why) => {
44061
44767
  throw new Error(`border scan cannot materialize the artifact (${why}) \u2014 refusing to stage an unusable tree (fail-closed)`);
@@ -44072,12 +44778,12 @@ function unwrapSingleDir(dir) {
44072
44778
  const entries = readdirSync10(dir, { withFileTypes: true });
44073
44779
  const dirs = entries.filter((e) => e.isDirectory());
44074
44780
  if (entries.length === 1 && dirs.length === 1 && dirs[0] !== void 0) {
44075
- return join26(dir, dirs[0].name);
44781
+ return join27(dir, dirs[0].name);
44076
44782
  }
44077
44783
  return dir;
44078
44784
  }
44079
44785
  function requireManifest(repoDir, ecosystem, candidates) {
44080
- if (!candidates.some((rel) => existsSync17(join26(repoDir, rel)))) {
44786
+ if (!candidates.some((rel) => existsSync18(join27(repoDir, rel)))) {
44081
44787
  fail2(`${ecosystem} package tree has none of [${candidates.join(", ")}] at its root`);
44082
44788
  }
44083
44789
  }
@@ -44087,18 +44793,18 @@ function materializePackage(o) {
44087
44793
  `);
44088
44794
  });
44089
44795
  const safe = safeArtifactName(o.filename, `${o.ecosystem}-artifact`);
44090
- const archive = join26(o.baseDir, safe);
44796
+ const archive = join27(o.baseDir, safe);
44091
44797
  writeFileSync8(archive, o.bytes);
44092
- const extractDir = join26(o.baseDir, "extract");
44798
+ const extractDir = join27(o.baseDir, "extract");
44093
44799
  mkdirSync11(extractDir, { recursive: true });
44094
44800
  extractFlat(archive, extractDir);
44095
44801
  let repoDir = unwrapSingleDir(extractDir);
44096
44802
  if (o.ecosystem === "rubygems") {
44097
44803
  const outer = repoDir;
44098
44804
  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");
44805
+ const inner = dataCandidates[0] ?? join27(outer, "data.tar.gz");
44806
+ if (!existsSync18(inner)) fail2(".gem archive contains no data.tar.gz");
44807
+ const gemTree = join27(o.baseDir, "gem-tree");
44102
44808
  mkdirSync11(gemTree, { recursive: true });
44103
44809
  extractFlat(inner, gemTree);
44104
44810
  repoDir = unwrapSingleDir(gemTree);
@@ -44109,8 +44815,8 @@ function materializePackage(o) {
44109
44815
  } else {
44110
44816
  requireManifest(repoDir, o.ecosystem, ["Cargo.toml"]);
44111
44817
  for (const file2 of CRATES_ENVELOPE_FILES) {
44112
- const path4 = join26(repoDir, file2);
44113
- if (!existsSync17(path4)) continue;
44818
+ const path4 = join27(repoDir, file2);
44819
+ if (!existsSync18(path4)) continue;
44114
44820
  const original = readFileSync16(path4);
44115
44821
  rmSync13(path4, { force: true });
44116
44822
  note(`border scan: crates envelope normalized: ${file2} sha256=${sha256Hex(original)}`);
@@ -44139,7 +44845,7 @@ async function runScanCore(ctx, deps = {}) {
44139
44845
  }
44140
44846
  const cfg = synthesizeScanConfig(spec.ecosystem);
44141
44847
  const fetcher = deps.fetcher ?? defaultScanFetcher;
44142
- const baseDir = mkdtempSync6(join27(tmpdir4(), "border-scan-"));
44848
+ const baseDir = mkdtempSync6(join28(tmpdir4(), "border-scan-"));
44143
44849
  try {
44144
44850
  const { bytes, filename } = await fetchArtifact(spec, { fetcher });
44145
44851
  const env2 = { ...ctx.env };
@@ -44285,7 +44991,7 @@ function usage() {
44285
44991
  " llm-request emit the provenance request for LLM-authored commits",
44286
44992
  " llm-ingest record provenance captured by the harness",
44287
44993
  " scan inspect a third-party package ([ecosystem:]name@version) for residue",
44288
- " roundtrip install+uninstall a package in a throwaway container, report uninstall residue",
44994
+ " roundtrip install+uninstall a package (registry spec or local artifact file) in a throwaway container, report uninstall residue",
44289
44995
  "",
44290
44996
  "flags:",
44291
44997
  " --config <path> config file (default: ./border.yaml, then git-remote fallback)",