@mutmutco/codex-plugin 4.6.16 → 4.6.18
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/.codex-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +439 -324
- package/cli/package.json +1 -1
- package/package.json +1 -1
package/cli/dist/main.cjs
CHANGED
|
@@ -2998,9 +2998,9 @@ ${indent}`) + "'";
|
|
|
2998
2998
|
return quotedString(value, ctx);
|
|
2999
2999
|
}
|
|
3000
3000
|
const indent = ctx.indent || (ctx.forceBlockIndent || containsDocumentMarker(value) ? " " : "");
|
|
3001
|
-
const
|
|
3001
|
+
const literal = blockQuote === "literal" ? true : blockQuote === "folded" || type === Scalar.Scalar.BLOCK_FOLDED ? false : type === Scalar.Scalar.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, lineWidth, indent.length);
|
|
3002
3002
|
if (!value)
|
|
3003
|
-
return
|
|
3003
|
+
return literal ? "|\n" : ">\n";
|
|
3004
3004
|
let chomp;
|
|
3005
3005
|
let endStart;
|
|
3006
3006
|
for (endStart = value.length; endStart > 0; --endStart) {
|
|
@@ -3049,7 +3049,7 @@ ${indent}`) + "'";
|
|
|
3049
3049
|
if (onComment)
|
|
3050
3050
|
onComment();
|
|
3051
3051
|
}
|
|
3052
|
-
if (!
|
|
3052
|
+
if (!literal) {
|
|
3053
3053
|
const foldedValue = value.replace(/\n+/g, "\n$&").replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, "$1$2").replace(/\n+/g, `$&${indent}`);
|
|
3054
3054
|
let literalFallback = false;
|
|
3055
3055
|
const foldOptions = getFoldOptions(ctx, true);
|
|
@@ -13637,13 +13637,16 @@ function isExemptPorcelainLine(status, path2) {
|
|
|
13637
13637
|
return isLiveSessionPath(path2);
|
|
13638
13638
|
}
|
|
13639
13639
|
function porcelainBlockingPaths(porcelain) {
|
|
13640
|
-
|
|
13640
|
+
return porcelainBlockingEntries(porcelain).map((e) => e.path);
|
|
13641
|
+
}
|
|
13642
|
+
function porcelainBlockingEntries(porcelain) {
|
|
13643
|
+
const entries = [];
|
|
13641
13644
|
for (const line of porcelain.split("\n")) {
|
|
13642
13645
|
if (!line.trim()) continue;
|
|
13643
13646
|
const { status, path: path2 } = splitPorcelainLine(line);
|
|
13644
|
-
if (path2 !== "" && !isExemptPorcelainLine(status, path2))
|
|
13647
|
+
if (path2 !== "" && !isExemptPorcelainLine(status, path2)) entries.push({ status, path: path2 });
|
|
13645
13648
|
}
|
|
13646
|
-
return
|
|
13649
|
+
return entries;
|
|
13647
13650
|
}
|
|
13648
13651
|
function porcelainHasBlockingChanges(porcelain) {
|
|
13649
13652
|
return porcelainBlockingPaths(porcelain).length > 0;
|
|
@@ -17607,6 +17610,279 @@ var REGISTRY_FETCH_TIMEOUT_MS = 8e3;
|
|
|
17607
17610
|
init_fetch_retry();
|
|
17608
17611
|
init_client_version();
|
|
17609
17612
|
|
|
17613
|
+
// src/hub-release-ci-authority.ts
|
|
17614
|
+
var HUB_RELEASE_REPO = "mutmutco/MMI-Hub";
|
|
17615
|
+
var HUB_RELEASE_WORKFLOW = ".github/workflows/hub-release.yml";
|
|
17616
|
+
var HUB_RELEASE_REF = "refs/heads/development";
|
|
17617
|
+
var WORKFLOW_REF = `${HUB_RELEASE_REPO}/${HUB_RELEASE_WORKFLOW}@${HUB_RELEASE_REF}`;
|
|
17618
|
+
var ID = /^[1-9][0-9]*$/;
|
|
17619
|
+
function hubReleaseCiRun(env) {
|
|
17620
|
+
if (env.MMI_HUB_RELEASE_CI !== "1" || env.GITHUB_ACTIONS !== "true") return null;
|
|
17621
|
+
if (env.GITHUB_REPOSITORY !== HUB_RELEASE_REPO || env.GITHUB_EVENT_NAME !== "workflow_dispatch") return null;
|
|
17622
|
+
if (env.GITHUB_WORKFLOW_REF !== WORKFLOW_REF || env.GITHUB_REF !== HUB_RELEASE_REF) return null;
|
|
17623
|
+
const runId = env.GITHUB_RUN_ID ?? "";
|
|
17624
|
+
const runAttempt2 = env.GITHUB_RUN_ATTEMPT ?? "";
|
|
17625
|
+
if (!ID.test(runId) || !ID.test(runAttempt2)) return null;
|
|
17626
|
+
return { runId: Number(runId), runAttempt: Number(runAttempt2) };
|
|
17627
|
+
}
|
|
17628
|
+
|
|
17629
|
+
// ../shared/runner-fallback.ts
|
|
17630
|
+
var HOSTED_RUNNER_LABEL_RE = /\b(?:ubuntu|windows|macos)-(?:latest|\d[\w.-]*)\b/i;
|
|
17631
|
+
var STANDARD_HOSTED_LABEL_RE = /^(?:ubuntu-(?:latest|24\.04|22\.04|24\.04-arm|22\.04-arm)|windows-(?:latest|2025|2022|11-arm)|macos-(?:latest|\d+|\d+-intel))$/i;
|
|
17632
|
+
var BLESSED_RUNNER_CHOICE_SHA = "cf35db6cdf053746bc5b2cd75f241a7d93cf7f48";
|
|
17633
|
+
function classifyRunnerCost(selector) {
|
|
17634
|
+
const raw = selector.replace(/\s+#.*$/gm, "").trim();
|
|
17635
|
+
if (STANDARD_HOSTED_LABEL_RE.test(raw.replace(/^(['"])(.*)\1$/, "$2"))) return "standard-hosted";
|
|
17636
|
+
if (HOSTED_RUNNER_LABEL_RE.test(raw)) return "hosted";
|
|
17637
|
+
const json = /^\$\{\{\s*fromJSON\('([^']+)'\)\s*\}\}$/.exec(raw);
|
|
17638
|
+
if (json) {
|
|
17639
|
+
try {
|
|
17640
|
+
const labels2 = JSON.parse(json[1]);
|
|
17641
|
+
return Array.isArray(labels2) && labels2.length > 0 && labels2.every((l) => typeof l === "string" && /^[\w-]+$/.test(l)) && labels2.some((l) => l.toLowerCase() === "self-hosted") ? "self-hosted" : "unknown";
|
|
17642
|
+
} catch {
|
|
17643
|
+
return "unknown";
|
|
17644
|
+
}
|
|
17645
|
+
}
|
|
17646
|
+
if (raw.includes("${{")) return "unknown";
|
|
17647
|
+
const labels = /^\[[^\[\]]+\]$/.test(raw) ? raw.slice(1, -1).split(",").map((s) => s.trim().replace(/^(['"])(.*)\1$/, "$2")) : /^(?:-\s*[^\n]+\s*)+$/.test(raw) ? raw.split("\n").map((s) => s.trim().replace(/^-\s*/, "").replace(/^(['"])(.*)\1$/, "$2")) : [raw.replace(/^(['"])(.*)\1$/, "$2")];
|
|
17648
|
+
return labels.every((l) => /^[\w-]+$/.test(l)) && labels.some((l) => l.toLowerCase() === "self-hosted") ? "self-hosted" : "unknown";
|
|
17649
|
+
}
|
|
17650
|
+
var STANDARD_HOSTED_ALLOWLIST = [
|
|
17651
|
+
["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"],
|
|
17652
|
+
["mutmutco/Jerv-JervCode", ".github/workflows/release-archives.yml", "release-platform-arm64", "windows-11-arm"]
|
|
17653
|
+
];
|
|
17654
|
+
var HOSTED_SPILL_RE = /^\$\{\{\s*(?:matrix\.[\w-]+\s*==\s*'[\w-]+'\s*&&\s*(?:'(?<leg>[^']+)'|fromJSON\(needs\.(?<legDecider>[\w-]+)\.outputs\.(?<legOutput>\w+_runs_on)\s*\|\|\s*(?<legOwned>'[^']+')\))\s*\|\|\s*)?fromJSON\(needs\.(?<decider>[\w-]+)\.outputs\.(?<output>\w+_runs_on)\s*\|\|\s*(?<owned>'[^']+')\)\s*\}\}$/;
|
|
17655
|
+
var RUNNER_CHOICE_ACTION = "mutmutco/MMI-Hub/.github/actions/runner-choice";
|
|
17656
|
+
function runnerChoiceProduces(job, decider, output, source) {
|
|
17657
|
+
const needs = job.needs;
|
|
17658
|
+
if (!(needs === decider || Array.isArray(needs) && needs.includes(decider))) return false;
|
|
17659
|
+
const producer = source.jobs.get(decider);
|
|
17660
|
+
if (!record(producer) || !record(producer.outputs) || !Array.isArray(producer.steps)) return false;
|
|
17661
|
+
const step = /^\$\{\{\s*steps\.([\w-]+)\.outputs\.runs-on\s*\}\}$/.exec(String(producer.outputs[output] ?? ""))?.[1];
|
|
17662
|
+
const steps = producer.steps.filter((s) => record(s) && s.id === step);
|
|
17663
|
+
if (steps.length !== 1 || !record(steps[0])) return false;
|
|
17664
|
+
const { uses } = steps[0], inputs = steps[0].with ?? {};
|
|
17665
|
+
const pinned = /^[0-9a-f]{40}$/.test(source.blessedSha) && uses === `${RUNNER_CHOICE_ACTION}@${source.blessedSha}`;
|
|
17666
|
+
if (!pinned && !(source.repo === "mutmutco/MMI-Hub" && uses === "./.github/actions/runner-choice")) return false;
|
|
17667
|
+
const hosted = record(inputs) ? inputs.hosted ?? "ubuntu-latest" : void 0;
|
|
17668
|
+
return typeof hosted === "string" && STANDARD_HOSTED_LABEL_RE.test(hosted);
|
|
17669
|
+
}
|
|
17670
|
+
function standardHostedLabelFor(repo, path2, jobId) {
|
|
17671
|
+
if (repo === "mutmutco/Jerv-Hub") return ["ubuntu-latest", "windows-latest", "macos-14"];
|
|
17672
|
+
return STANDARD_HOSTED_ALLOWLIST.find(([r, p, j]) => r === repo && p === path2 && j === jobId)?.[3];
|
|
17673
|
+
}
|
|
17674
|
+
var record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
17675
|
+
function classifyJobRunnerCost(job, allowance2, source) {
|
|
17676
|
+
if (!record(job) || !Object.hasOwn(job, "runs-on")) return "unknown";
|
|
17677
|
+
const selector = job["runs-on"];
|
|
17678
|
+
const raw = typeof selector === "string" ? selector.trim() : Array.isArray(selector) && selector.length && selector.every((label) => typeof label === "string") ? JSON.stringify(selector) : "";
|
|
17679
|
+
if (!raw) return "unknown";
|
|
17680
|
+
const approved = (value) => Array.isArray(allowance2) && allowance2.includes(value);
|
|
17681
|
+
if (approved(raw) || allowance2 === "windows-11-arm" && raw === allowance2) return "standard-hosted";
|
|
17682
|
+
const fallback = /^\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'([^']+)'\s*\|\|\s*(fromJSON\('[^']+'\))\s*\}\}$/.exec(raw);
|
|
17683
|
+
if (fallback && approved(fallback[1]) && classifyRunnerCost("${{ " + fallback[2] + " }}") === "self-hosted") return "standard-hosted";
|
|
17684
|
+
const layerZero = /^\$\{\{\s*vars\.[\w-]+\s*==\s*'off'\s*&&\s*(fromJSON\('[^']+'\))\s*\|\|\s*'([^']+)'\s*\}\}$/.exec(raw);
|
|
17685
|
+
if (layerZero && classifyRunnerCost("${{ " + layerZero[1] + " }}") === "self-hosted" && STANDARD_HOSTED_LABEL_RE.test(layerZero[2])) return "standard-hosted";
|
|
17686
|
+
const spill = HOSTED_SPILL_RE.exec(raw)?.groups;
|
|
17687
|
+
const legOk = (g) => g.legDecider ? classifyRunnerCost(`\${{ fromJSON(${g.legOwned}) }}`) === "self-hosted" && Boolean(source) && runnerChoiceProduces(job, g.legDecider, g.legOutput, source) : !g.leg || STANDARD_HOSTED_LABEL_RE.test(g.leg);
|
|
17688
|
+
if (spill && source && legOk(spill) && classifyRunnerCost(`\${{ fromJSON(${spill.owned}) }}`) === "self-hosted" && runnerChoiceProduces(job, spill.decider, spill.output, source)) return "standard-hosted";
|
|
17689
|
+
const direct = classifyRunnerCost(raw);
|
|
17690
|
+
const matrix = /^\$\{\{\s*matrix\.([\w-]+)(?:\.([\w-]+))?\s*\}\}$/.exec(raw);
|
|
17691
|
+
if (!matrix || direct === "hosted") return direct;
|
|
17692
|
+
if (!Object.hasOwn(job, "strategy") || !record(job.strategy) || !Object.hasOwn(job.strategy, "matrix") || !record(job.strategy.matrix)) return "unknown";
|
|
17693
|
+
const axes = job.strategy.matrix;
|
|
17694
|
+
if (Object.hasOwn(axes, "exclude")) return "unknown";
|
|
17695
|
+
let alternatives;
|
|
17696
|
+
if (Object.hasOwn(axes, "include")) {
|
|
17697
|
+
if (Object.keys(axes).length !== 1 || !Array.isArray(axes.include) || !axes.include.length) return "unknown";
|
|
17698
|
+
alternatives = axes.include.map((row) => record(row) && Object.hasOwn(row, matrix[1]) ? row[matrix[1]] : void 0);
|
|
17699
|
+
} else {
|
|
17700
|
+
const axis = axes[matrix[1]];
|
|
17701
|
+
if (!Object.hasOwn(axes, matrix[1]) || !Array.isArray(axis) || !axis.length) return "unknown";
|
|
17702
|
+
alternatives = axis;
|
|
17703
|
+
}
|
|
17704
|
+
if (matrix[2]) alternatives = alternatives.map((row) => record(row) && Object.hasOwn(row, matrix[2]) ? row[matrix[2]] : void 0);
|
|
17705
|
+
const costs = alternatives.map((value) => {
|
|
17706
|
+
if (typeof value === "string") {
|
|
17707
|
+
if (value.includes("${{")) return "unknown";
|
|
17708
|
+
if (approved(value) || !matrix[2] && allowance2 === "windows-latest" && value === allowance2) return "standard-hosted";
|
|
17709
|
+
return classifyRunnerCost(value);
|
|
17710
|
+
}
|
|
17711
|
+
if (!Array.isArray(value) || !value.length || !value.every((label) => typeof label === "string" && /^[\w-]+$/.test(label))) return "unknown";
|
|
17712
|
+
return classifyRunnerCost(JSON.stringify(value));
|
|
17713
|
+
});
|
|
17714
|
+
if (costs.includes("hosted")) return "hosted";
|
|
17715
|
+
if (costs.includes("unknown")) return "unknown";
|
|
17716
|
+
return costs.includes("standard-hosted") ? "standard-hosted" : "self-hosted";
|
|
17717
|
+
}
|
|
17718
|
+
|
|
17719
|
+
// ../shared/github-contents.ts
|
|
17720
|
+
function decodeGitHubContents(content) {
|
|
17721
|
+
return Buffer.from(content, "base64").toString("utf8");
|
|
17722
|
+
}
|
|
17723
|
+
function encodeGitHubContents(content) {
|
|
17724
|
+
return Buffer.from(content, "utf8").toString("base64");
|
|
17725
|
+
}
|
|
17726
|
+
|
|
17727
|
+
// ../shared/workflow-runner-source.ts
|
|
17728
|
+
var WORKFLOW_PATH = /^\.github\/workflows\/[\w.-]+\.ya?ml$/;
|
|
17729
|
+
var REPO = /^[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9_][\w.-]*$/;
|
|
17730
|
+
var REF = /^[\w./-]+$/;
|
|
17731
|
+
var MAX_FILES = 64;
|
|
17732
|
+
var MAX_DEPTH = 8;
|
|
17733
|
+
var WorkflowSourceError = class extends Error {
|
|
17734
|
+
/** Value-free category (safeReadCategory vocabulary) — routes/logs carry it, never a location or body. */
|
|
17735
|
+
category;
|
|
17736
|
+
constructor(location, category) {
|
|
17737
|
+
super(`${location}: ${category}`);
|
|
17738
|
+
this.category = category;
|
|
17739
|
+
}
|
|
17740
|
+
};
|
|
17741
|
+
function safeReadCategory(error) {
|
|
17742
|
+
const e = error;
|
|
17743
|
+
if (e?.rateLimited === true) return "rate-limited";
|
|
17744
|
+
if (typeof e?.status === "number" && Number.isInteger(e.status) && e.status >= 400 && e.status <= 599) return `HTTP ${e.status}`;
|
|
17745
|
+
if (e?.name === "TimeoutError" || e?.name === "AbortError" || e?.code === "ETIMEDOUT") return "read-timeout";
|
|
17746
|
+
if (e?.code === "ENOENT") return "reader-unavailable";
|
|
17747
|
+
if (e?.code === "ENOBUFS" || e?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") return "reader-output-limit";
|
|
17748
|
+
if (error instanceof SyntaxError) return "invalid-json";
|
|
17749
|
+
return "read-failed";
|
|
17750
|
+
}
|
|
17751
|
+
function locationOf(file) {
|
|
17752
|
+
return `${file.repo}/${file.path}${file.ref ? `@${file.ref}` : ""}`;
|
|
17753
|
+
}
|
|
17754
|
+
function contentsPath(file) {
|
|
17755
|
+
if (!REPO.test(file.repo) || !(WORKFLOW_PATH.test(file.path) || file.path === ".github/workflows") || file.ref !== void 0 && !REF.test(file.ref)) {
|
|
17756
|
+
throw new WorkflowSourceError("workflow source", "invalid-location");
|
|
17757
|
+
}
|
|
17758
|
+
return `repos/${file.repo}/contents/${file.path}${file.ref ? `?ref=${encodeURIComponent(file.ref)}` : ""}`;
|
|
17759
|
+
}
|
|
17760
|
+
async function readSource(read, file) {
|
|
17761
|
+
const endpoint = contentsPath(file);
|
|
17762
|
+
try {
|
|
17763
|
+
return await read(endpoint);
|
|
17764
|
+
} catch (e) {
|
|
17765
|
+
throw new WorkflowSourceError(locationOf(file), safeReadCategory(e));
|
|
17766
|
+
}
|
|
17767
|
+
}
|
|
17768
|
+
async function readBody(read, file) {
|
|
17769
|
+
const value = await readSource(read, file);
|
|
17770
|
+
if (!value || value.encoding !== "base64" || typeof value.content !== "string" || !value.content.trim() || value.content.length > 15e5 || !/^[A-Za-z0-9+/=\r\n]+$/.test(value.content)) {
|
|
17771
|
+
throw new WorkflowSourceError(locationOf(file), "invalid-content");
|
|
17772
|
+
}
|
|
17773
|
+
return { ...file, body: decodeGitHubContents(value.content) };
|
|
17774
|
+
}
|
|
17775
|
+
function resolveCallee(caller, uses) {
|
|
17776
|
+
if (uses.startsWith("./") && WORKFLOW_PATH.test(uses.slice(2))) return { repo: caller.repo, path: uses.slice(2), ref: caller.ref };
|
|
17777
|
+
const remote = /^([\w.-]+\/[\w.-]+)\/(\.github\/workflows\/[\w.-]+\.ya?ml)@([\w./-]+)$/.exec(uses);
|
|
17778
|
+
return remote ? { repo: remote[1], path: remote[2], ref: remote[3] } : void 0;
|
|
17779
|
+
}
|
|
17780
|
+
async function readRunnerWorkflowClosure(parse2, read, repo, ref, roots, bounds) {
|
|
17781
|
+
let totalBodyBytes = 0;
|
|
17782
|
+
const readBounded = async (file) => {
|
|
17783
|
+
const child2 = await readBody(read, file);
|
|
17784
|
+
totalBodyBytes += Buffer.byteLength(child2.body, "utf8");
|
|
17785
|
+
if (bounds?.maxTotalBodyBytes !== void 0 && totalBodyBytes > bounds.maxTotalBodyBytes) {
|
|
17786
|
+
throw new WorkflowSourceError(locationOf(child2), "source-read-limit");
|
|
17787
|
+
}
|
|
17788
|
+
return child2;
|
|
17789
|
+
};
|
|
17790
|
+
let files;
|
|
17791
|
+
if (roots) {
|
|
17792
|
+
files = roots.map((file) => ({ ...file, repo, ref }));
|
|
17793
|
+
} else {
|
|
17794
|
+
const listing = await readSource(read, { repo, ref, path: ".github/workflows" });
|
|
17795
|
+
if (!Array.isArray(listing)) throw new WorkflowSourceError(`${repo}/.github/workflows`, "invalid-listing");
|
|
17796
|
+
const paths = listing.filter((f) => f?.type === "file" && /\.ya?ml$/i.test(f?.path ?? "")).map((f) => f.path);
|
|
17797
|
+
if (!paths.length || paths.length > MAX_FILES) throw new WorkflowSourceError(`${repo}/.github/workflows`, "empty-or-oversized-listing");
|
|
17798
|
+
files = [];
|
|
17799
|
+
for (const path2 of paths) files.push(await readBounded({ repo, ref, path: path2 }));
|
|
17800
|
+
}
|
|
17801
|
+
if (!files.length || files.length > MAX_FILES) throw new WorkflowSourceError("workflow source", "empty-or-oversized-listing");
|
|
17802
|
+
for (const file of files) contentsPath(file);
|
|
17803
|
+
const byLocation = new Map(files.map((file) => [locationOf(file), file]));
|
|
17804
|
+
const active = /* @__PURE__ */ new Set();
|
|
17805
|
+
const completed = /* @__PURE__ */ new Set();
|
|
17806
|
+
async function visit2(file, depth) {
|
|
17807
|
+
const key = locationOf(file);
|
|
17808
|
+
if (active.has(key)) throw new WorkflowSourceError(key, "reusable-cycle");
|
|
17809
|
+
if (depth > MAX_DEPTH) throw new WorkflowSourceError(key, "reusable-depth-limit");
|
|
17810
|
+
if (completed.has(key)) return;
|
|
17811
|
+
active.add(key);
|
|
17812
|
+
const jobs = parse2(file.body);
|
|
17813
|
+
if (!jobs) throw new WorkflowSourceError(key, "invalid-workflow-yaml");
|
|
17814
|
+
for (const job of jobs.values()) {
|
|
17815
|
+
if (!Object.hasOwn(job, "uses")) continue;
|
|
17816
|
+
const uses = job.uses;
|
|
17817
|
+
if (typeof uses !== "string" || Object.hasOwn(job, "runs-on")) throw new WorkflowSourceError(key, "unresolved-reusable-ref");
|
|
17818
|
+
const target = resolveCallee(file, uses);
|
|
17819
|
+
if (!target) throw new WorkflowSourceError(key, "unresolved-reusable-ref");
|
|
17820
|
+
if (bounds?.calleeAllowed && !bounds.calleeAllowed(target)) throw new WorkflowSourceError(key, "unauthorized-callee");
|
|
17821
|
+
const targetKey = locationOf(target);
|
|
17822
|
+
let child2 = byLocation.get(targetKey);
|
|
17823
|
+
if (!child2) {
|
|
17824
|
+
if (byLocation.size >= MAX_FILES) throw new WorkflowSourceError(key, "workflow-read-limit");
|
|
17825
|
+
child2 = await readBounded(target);
|
|
17826
|
+
byLocation.set(targetKey, child2);
|
|
17827
|
+
}
|
|
17828
|
+
await visit2(child2, depth + 1);
|
|
17829
|
+
}
|
|
17830
|
+
active.delete(key);
|
|
17831
|
+
completed.add(key);
|
|
17832
|
+
}
|
|
17833
|
+
for (const file of files) await visit2(file, 0);
|
|
17834
|
+
return [...byLocation.values()];
|
|
17835
|
+
}
|
|
17836
|
+
function classifyRunnerWorkflows(parse2, files) {
|
|
17837
|
+
const known = new Map(files.map((file) => [locationOf(file), file]));
|
|
17838
|
+
const parsed = new Map(files.map((file) => [locationOf(file), parse2(file.body)]));
|
|
17839
|
+
const resolvedCache = /* @__PURE__ */ new Map();
|
|
17840
|
+
function resolved(file, active) {
|
|
17841
|
+
const key = locationOf(file);
|
|
17842
|
+
if (active.has(key)) return false;
|
|
17843
|
+
if (resolvedCache.has(key)) return resolvedCache.get(key);
|
|
17844
|
+
const next = new Set(active).add(key);
|
|
17845
|
+
const jobs = parsed.get(key);
|
|
17846
|
+
const result = Boolean(jobs && [...jobs.values()].every((job) => {
|
|
17847
|
+
if (!Object.hasOwn(job, "uses")) return true;
|
|
17848
|
+
const uses = job.uses;
|
|
17849
|
+
if (typeof uses !== "string" || Object.hasOwn(job, "runs-on")) return false;
|
|
17850
|
+
const target = resolveCallee(file, uses);
|
|
17851
|
+
const child2 = target && known.get(locationOf(target));
|
|
17852
|
+
return Boolean(child2 && resolved(child2, next));
|
|
17853
|
+
}));
|
|
17854
|
+
resolvedCache.set(key, result);
|
|
17855
|
+
return result;
|
|
17856
|
+
}
|
|
17857
|
+
return files.flatMap((file) => {
|
|
17858
|
+
const jobs = parsed.get(locationOf(file));
|
|
17859
|
+
if (!jobs) return [{ repo: file.repo, path: file.path, job: "(invalid YAML)", cost: "unknown", callee: false }];
|
|
17860
|
+
return [...jobs].flatMap(([id, job]) => {
|
|
17861
|
+
const hasCallee = Object.hasOwn(job, "uses");
|
|
17862
|
+
const uses = hasCallee && typeof job.uses === "string" && !Object.hasOwn(job, "runs-on") ? job.uses : void 0;
|
|
17863
|
+
const target = uses && resolveCallee(file, uses);
|
|
17864
|
+
if (target && known.has(locationOf(target)) && resolved(file, /* @__PURE__ */ new Set())) return [];
|
|
17865
|
+
const cost = hasCallee ? "unknown" : classifyJobRunnerCost(job, standardHostedLabelFor(file.repo, file.path, id), { repo: file.repo, jobs, blessedSha: BLESSED_RUNNER_CHOICE_SHA });
|
|
17866
|
+
return [{ repo: file.repo, path: file.path, job: id, cost, callee: hasCallee }];
|
|
17867
|
+
});
|
|
17868
|
+
});
|
|
17869
|
+
}
|
|
17870
|
+
function runnerWorkflowFromContents(value) {
|
|
17871
|
+
const file = value;
|
|
17872
|
+
if (!file || typeof file.repo !== "string" || typeof file.path !== "string" || file.ref !== void 0 && typeof file.ref !== "string") {
|
|
17873
|
+
throw new WorkflowSourceError("workflow source", "invalid-content");
|
|
17874
|
+
}
|
|
17875
|
+
if (!REPO.test(file.repo) || !WORKFLOW_PATH.test(file.path) || file.ref !== void 0 && !REF.test(file.ref)) {
|
|
17876
|
+
throw new WorkflowSourceError("workflow source", "invalid-content");
|
|
17877
|
+
}
|
|
17878
|
+
if (file.encoding !== "base64" || typeof file.content !== "string" || !file.content.trim() || file.content.length > 15e5 || !/^[A-Za-z0-9+/=\r\n]+$/.test(file.content)) {
|
|
17879
|
+
throw new WorkflowSourceError("workflow source", "invalid-content");
|
|
17880
|
+
}
|
|
17881
|
+
const body = decodeGitHubContents(file.content);
|
|
17882
|
+
if (!body.trim()) throw new WorkflowSourceError("workflow source", "invalid-content");
|
|
17883
|
+
return { repo: file.repo, path: file.path, ref: file.ref, body };
|
|
17884
|
+
}
|
|
17885
|
+
|
|
17610
17886
|
// ../shared/schedule-inventory.ts
|
|
17611
17887
|
function classifyLlmValue(raw) {
|
|
17612
17888
|
const value = raw.trim().toLowerCase();
|
|
@@ -18669,6 +18945,23 @@ async function tenantDeploy(payload, deps) {
|
|
|
18669
18945
|
async function releaseAnnouncement(payload, deps) {
|
|
18670
18946
|
return postJson("/release-announcement", payload, deps, "POST", { noRetry: true, timeoutMs: 3e4 });
|
|
18671
18947
|
}
|
|
18948
|
+
async function trainWorkflowSourceClosure(repo, deps) {
|
|
18949
|
+
const verdict = await fetchTrainAuthority(repo, deps);
|
|
18950
|
+
if (!verdict.ok) throw new WorkflowSourceError("workflow source", "authority-unverified");
|
|
18951
|
+
const delegatedTrain = verdict.authority.train === true && verdict.authority.authenticatedDelegation === true;
|
|
18952
|
+
if (!delegatedTrain || repo.toLowerCase() === HUB_RELEASE_REPO.toLowerCase()) return void 0;
|
|
18953
|
+
const SAFE_CHECK = /^[A-Za-z0-9 -]{1,64}$/;
|
|
18954
|
+
const res = await postJson("/train-workflow-source", { repo }, deps, "POST", { noRetry: true, timeoutMs: 3e4 });
|
|
18955
|
+
if (res.status === 422) {
|
|
18956
|
+
const body2 = res.body;
|
|
18957
|
+
const check = typeof body2?.check === "string" && SAFE_CHECK.test(body2.check) ? body2.check : "read-failed";
|
|
18958
|
+
throw new WorkflowSourceError("workflow source", check);
|
|
18959
|
+
}
|
|
18960
|
+
if (!res.ok || res.status !== 200) throw new WorkflowSourceError("workflow source", "source-endpoint-unavailable");
|
|
18961
|
+
const body = res.body;
|
|
18962
|
+
if (!body || !Array.isArray(body.files)) throw new WorkflowSourceError("workflow source", "invalid-response");
|
|
18963
|
+
return body.files.map(runnerWorkflowFromContents);
|
|
18964
|
+
}
|
|
18672
18965
|
async function actionsCanary(payload, deps) {
|
|
18673
18966
|
return postJson("/actions-canary", payload, deps, "POST", { noRetry: true });
|
|
18674
18967
|
}
|
|
@@ -24389,14 +24682,6 @@ async function archiveClosedDoneBoardItems(options, deps = {}) {
|
|
|
24389
24682
|
return result;
|
|
24390
24683
|
}
|
|
24391
24684
|
|
|
24392
|
-
// src/github-contents.ts
|
|
24393
|
-
function decodeGitHubContents(content) {
|
|
24394
|
-
return Buffer.from(content, "base64").toString("utf8");
|
|
24395
|
-
}
|
|
24396
|
-
function encodeGitHubContents(content) {
|
|
24397
|
-
return Buffer.from(content, "utf8").toString("base64");
|
|
24398
|
-
}
|
|
24399
|
-
|
|
24400
24685
|
// src/bootstrap-ruleset.ts
|
|
24401
24686
|
var PRODUCT_RULESET_NAME = "mmi-product-required-checks";
|
|
24402
24687
|
var PRODUCT_PENDING_RULESET_PREFIX = "mmi-product-required-checks-pending-";
|
|
@@ -25538,7 +25823,6 @@ var BLESSED_RUNNER_CHECK_POLICY_SHA = "67bb2012710a96d21be825c29a5d5c2b4b8001e2"
|
|
|
25538
25823
|
var BLESSED_KIT_AGENT_FILES_SHA = "e976df208d891b215844386c1cac0f161946b6fa";
|
|
25539
25824
|
var BLESSED_JEV_FAILURES_SHA = "d5278c6133318b33727483ddfdfddc8f841e3ef6";
|
|
25540
25825
|
var BLESSED_VERDICT_RECEIPT_SHA = "";
|
|
25541
|
-
var BLESSED_RUNNER_CHOICE_SHA = "cf35db6cdf053746bc5b2cd75f241a7d93cf7f48";
|
|
25542
25826
|
var BLESSED_RUNBOOK_GUARD_SHA = "e661e0aedc0e58b73c738bf6cdfeac65e5914584";
|
|
25543
25827
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
25544
25828
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
@@ -25705,9 +25989,9 @@ function checkGateBudget(files, opts = {}) {
|
|
|
25705
25989
|
if (!rawMs) {
|
|
25706
25990
|
failures.push(`${path2}: run-with-budget step has no max-seconds input \u2014 the budget step guards nothing`);
|
|
25707
25991
|
} else {
|
|
25708
|
-
const
|
|
25709
|
-
const seconds =
|
|
25710
|
-
if (!
|
|
25992
|
+
const literal = /^['"]?(\d+)['"]?$/.exec(rawMs);
|
|
25993
|
+
const seconds = literal ? Number(literal[1]) : NaN;
|
|
25994
|
+
if (!literal || seconds < 1) {
|
|
25711
25995
|
failures.push(`${path2}: max-seconds '${rawMs}' is not a literal positive integer`);
|
|
25712
25996
|
} else if (seconds > MAX_SECONDS_CEILING) {
|
|
25713
25997
|
failures.push(`${path2}: max-seconds ${seconds} exceeds the org ceiling ${MAX_SECONDS_CEILING} \u2014 a budget that generous guards nothing; measure the gate and set a real ceiling`);
|
|
@@ -27805,10 +28089,10 @@ var rollout_plan_default = {
|
|
|
27805
28089
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
27806
28090
|
},
|
|
27807
28091
|
baseline: {
|
|
27808
|
-
version: "4.6.
|
|
27809
|
-
tag: "v4.6.
|
|
27810
|
-
commit: "
|
|
27811
|
-
npm: "@mutmutco/cli@4.6.
|
|
28092
|
+
version: "4.6.18",
|
|
28093
|
+
tag: "v4.6.18",
|
|
28094
|
+
commit: "24b7f2455176",
|
|
28095
|
+
npm: "@mutmutco/cli@4.6.18"
|
|
27812
28096
|
},
|
|
27813
28097
|
exitCriterion: "fleet-n-of-n",
|
|
27814
28098
|
hubOnlyShortcut: "forbidden",
|
|
@@ -27825,14 +28109,14 @@ var rollout_plan_default = {
|
|
|
27825
28109
|
repo: "mutmutco/mmi-hub",
|
|
27826
28110
|
role: "canary",
|
|
27827
28111
|
schedule: "train",
|
|
27828
|
-
v3Target: "v4.6.
|
|
28112
|
+
v3Target: "v4.6.18"
|
|
27829
28113
|
}
|
|
27830
28114
|
],
|
|
27831
28115
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
27832
28116
|
rollback: {
|
|
27833
28117
|
independent: true,
|
|
27834
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
27835
|
-
v3Target: "v4.6.
|
|
28118
|
+
mechanism: "npm dist-tag latest -> 4.6.18 and redeploy the Hub Lambda from tag v4.6.18 (24b7f2455176); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
28119
|
+
v3Target: "v4.6.18 (@mutmutco/cli@4.6.18, tag commit 24b7f2455176 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
27836
28120
|
}
|
|
27837
28121
|
},
|
|
27838
28122
|
{
|
|
@@ -32387,290 +32671,100 @@ var import_node_fs31 = require("node:fs");
|
|
|
32387
32671
|
var import_promises4 = require("node:fs/promises");
|
|
32388
32672
|
var import_node_path28 = require("node:path");
|
|
32389
32673
|
|
|
32390
|
-
// ../shared/runner-fallback.ts
|
|
32391
|
-
var HOSTED_RUNNER_LABEL_RE = /\b(?:ubuntu|windows|macos)-(?:latest|\d[\w.-]*)\b/i;
|
|
32392
|
-
var STANDARD_HOSTED_LABEL_RE = /^(?:ubuntu-(?:latest|24\.04|22\.04|24\.04-arm|22\.04-arm)|windows-(?:latest|2025|2022|11-arm)|macos-(?:latest|\d+|\d+-intel))$/i;
|
|
32393
|
-
function classifyRunnerCost(selector) {
|
|
32394
|
-
const raw = selector.replace(/\s+#.*$/gm, "").trim();
|
|
32395
|
-
if (STANDARD_HOSTED_LABEL_RE.test(raw.replace(/^(['"])(.*)\1$/, "$2"))) return "standard-hosted";
|
|
32396
|
-
if (HOSTED_RUNNER_LABEL_RE.test(raw)) return "hosted";
|
|
32397
|
-
const json = /^\$\{\{\s*fromJSON\('([^']+)'\)\s*\}\}$/.exec(raw);
|
|
32398
|
-
if (json) {
|
|
32399
|
-
try {
|
|
32400
|
-
const labels2 = JSON.parse(json[1]);
|
|
32401
|
-
return Array.isArray(labels2) && labels2.length > 0 && labels2.every((l) => typeof l === "string" && /^[\w-]+$/.test(l)) && labels2.some((l) => l.toLowerCase() === "self-hosted") ? "self-hosted" : "unknown";
|
|
32402
|
-
} catch {
|
|
32403
|
-
return "unknown";
|
|
32404
|
-
}
|
|
32405
|
-
}
|
|
32406
|
-
if (raw.includes("${{")) return "unknown";
|
|
32407
|
-
const labels = /^\[[^\[\]]+\]$/.test(raw) ? raw.slice(1, -1).split(",").map((s) => s.trim().replace(/^(['"])(.*)\1$/, "$2")) : /^(?:-\s*[^\n]+\s*)+$/.test(raw) ? raw.split("\n").map((s) => s.trim().replace(/^-\s*/, "").replace(/^(['"])(.*)\1$/, "$2")) : [raw.replace(/^(['"])(.*)\1$/, "$2")];
|
|
32408
|
-
return labels.every((l) => /^[\w-]+$/.test(l)) && labels.some((l) => l.toLowerCase() === "self-hosted") ? "self-hosted" : "unknown";
|
|
32409
|
-
}
|
|
32410
|
-
var STANDARD_HOSTED_ALLOWLIST = [
|
|
32411
|
-
["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"],
|
|
32412
|
-
["mutmutco/Jerv-JervCode", ".github/workflows/release-archives.yml", "release-platform-arm64", "windows-11-arm"]
|
|
32413
|
-
];
|
|
32414
|
-
var HOSTED_SPILL_RE = /^\$\{\{\s*(?:matrix\.[\w-]+\s*==\s*'[\w-]+'\s*&&\s*(?:'(?<leg>[^']+)'|fromJSON\(needs\.(?<legDecider>[\w-]+)\.outputs\.(?<legOutput>\w+_runs_on)\s*\|\|\s*(?<legOwned>'[^']+')\))\s*\|\|\s*)?fromJSON\(needs\.(?<decider>[\w-]+)\.outputs\.(?<output>\w+_runs_on)\s*\|\|\s*(?<owned>'[^']+')\)\s*\}\}$/;
|
|
32415
|
-
var RUNNER_CHOICE_ACTION = "mutmutco/MMI-Hub/.github/actions/runner-choice";
|
|
32416
|
-
function runnerChoiceProduces(job, decider, output, source) {
|
|
32417
|
-
const needs = job.needs;
|
|
32418
|
-
if (!(needs === decider || Array.isArray(needs) && needs.includes(decider))) return false;
|
|
32419
|
-
const producer = source.jobs.get(decider);
|
|
32420
|
-
if (!record(producer) || !record(producer.outputs) || !Array.isArray(producer.steps)) return false;
|
|
32421
|
-
const step = /^\$\{\{\s*steps\.([\w-]+)\.outputs\.runs-on\s*\}\}$/.exec(String(producer.outputs[output] ?? ""))?.[1];
|
|
32422
|
-
const steps = producer.steps.filter((s) => record(s) && s.id === step);
|
|
32423
|
-
if (steps.length !== 1 || !record(steps[0])) return false;
|
|
32424
|
-
const { uses } = steps[0], inputs = steps[0].with ?? {};
|
|
32425
|
-
const pinned = /^[0-9a-f]{40}$/.test(source.blessedSha) && uses === `${RUNNER_CHOICE_ACTION}@${source.blessedSha}`;
|
|
32426
|
-
if (!pinned && !(source.repo === "mutmutco/MMI-Hub" && uses === "./.github/actions/runner-choice")) return false;
|
|
32427
|
-
const hosted = record(inputs) ? inputs.hosted ?? "ubuntu-latest" : void 0;
|
|
32428
|
-
return typeof hosted === "string" && STANDARD_HOSTED_LABEL_RE.test(hosted);
|
|
32429
|
-
}
|
|
32430
|
-
function standardHostedLabelFor(repo, path2, jobId) {
|
|
32431
|
-
if (repo === "mutmutco/Jerv-Hub") return ["ubuntu-latest", "windows-latest", "macos-14"];
|
|
32432
|
-
return STANDARD_HOSTED_ALLOWLIST.find(([r, p, j]) => r === repo && p === path2 && j === jobId)?.[3];
|
|
32433
|
-
}
|
|
32434
|
-
var record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
32435
|
-
function classifyJobRunnerCost(job, allowance2, source) {
|
|
32436
|
-
if (!record(job) || !Object.hasOwn(job, "runs-on")) return "unknown";
|
|
32437
|
-
const selector = job["runs-on"];
|
|
32438
|
-
const raw = typeof selector === "string" ? selector.trim() : Array.isArray(selector) && selector.length && selector.every((label) => typeof label === "string") ? JSON.stringify(selector) : "";
|
|
32439
|
-
if (!raw) return "unknown";
|
|
32440
|
-
const approved = (value) => Array.isArray(allowance2) && allowance2.includes(value);
|
|
32441
|
-
if (approved(raw) || allowance2 === "windows-11-arm" && raw === allowance2) return "standard-hosted";
|
|
32442
|
-
const fallback = /^\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'([^']+)'\s*\|\|\s*(fromJSON\('[^']+'\))\s*\}\}$/.exec(raw);
|
|
32443
|
-
if (fallback && approved(fallback[1]) && classifyRunnerCost("${{ " + fallback[2] + " }}") === "self-hosted") return "standard-hosted";
|
|
32444
|
-
const layerZero = /^\$\{\{\s*vars\.[\w-]+\s*==\s*'off'\s*&&\s*(fromJSON\('[^']+'\))\s*\|\|\s*'([^']+)'\s*\}\}$/.exec(raw);
|
|
32445
|
-
if (layerZero && classifyRunnerCost("${{ " + layerZero[1] + " }}") === "self-hosted" && STANDARD_HOSTED_LABEL_RE.test(layerZero[2])) return "standard-hosted";
|
|
32446
|
-
const spill = HOSTED_SPILL_RE.exec(raw)?.groups;
|
|
32447
|
-
const legOk = (g) => g.legDecider ? classifyRunnerCost(`\${{ fromJSON(${g.legOwned}) }}`) === "self-hosted" && Boolean(source) && runnerChoiceProduces(job, g.legDecider, g.legOutput, source) : !g.leg || STANDARD_HOSTED_LABEL_RE.test(g.leg);
|
|
32448
|
-
if (spill && source && legOk(spill) && classifyRunnerCost(`\${{ fromJSON(${spill.owned}) }}`) === "self-hosted" && runnerChoiceProduces(job, spill.decider, spill.output, source)) return "standard-hosted";
|
|
32449
|
-
const direct = classifyRunnerCost(raw);
|
|
32450
|
-
const matrix = /^\$\{\{\s*matrix\.([\w-]+)(?:\.([\w-]+))?\s*\}\}$/.exec(raw);
|
|
32451
|
-
if (!matrix || direct === "hosted") return direct;
|
|
32452
|
-
if (!Object.hasOwn(job, "strategy") || !record(job.strategy) || !Object.hasOwn(job.strategy, "matrix") || !record(job.strategy.matrix)) return "unknown";
|
|
32453
|
-
const axes = job.strategy.matrix;
|
|
32454
|
-
if (Object.hasOwn(axes, "exclude")) return "unknown";
|
|
32455
|
-
let alternatives;
|
|
32456
|
-
if (Object.hasOwn(axes, "include")) {
|
|
32457
|
-
if (Object.keys(axes).length !== 1 || !Array.isArray(axes.include) || !axes.include.length) return "unknown";
|
|
32458
|
-
alternatives = axes.include.map((row) => record(row) && Object.hasOwn(row, matrix[1]) ? row[matrix[1]] : void 0);
|
|
32459
|
-
} else {
|
|
32460
|
-
const axis = axes[matrix[1]];
|
|
32461
|
-
if (!Object.hasOwn(axes, matrix[1]) || !Array.isArray(axis) || !axis.length) return "unknown";
|
|
32462
|
-
alternatives = axis;
|
|
32463
|
-
}
|
|
32464
|
-
if (matrix[2]) alternatives = alternatives.map((row) => record(row) && Object.hasOwn(row, matrix[2]) ? row[matrix[2]] : void 0);
|
|
32465
|
-
const costs = alternatives.map((value) => {
|
|
32466
|
-
if (typeof value === "string") {
|
|
32467
|
-
if (value.includes("${{")) return "unknown";
|
|
32468
|
-
if (approved(value) || !matrix[2] && allowance2 === "windows-latest" && value === allowance2) return "standard-hosted";
|
|
32469
|
-
return classifyRunnerCost(value);
|
|
32470
|
-
}
|
|
32471
|
-
if (!Array.isArray(value) || !value.length || !value.every((label) => typeof label === "string" && /^[\w-]+$/.test(label))) return "unknown";
|
|
32472
|
-
return classifyRunnerCost(JSON.stringify(value));
|
|
32473
|
-
});
|
|
32474
|
-
if (costs.includes("hosted")) return "hosted";
|
|
32475
|
-
if (costs.includes("unknown")) return "unknown";
|
|
32476
|
-
return costs.includes("standard-hosted") ? "standard-hosted" : "self-hosted";
|
|
32477
|
-
}
|
|
32478
|
-
|
|
32479
32674
|
// src/workflow-runner-yaml.ts
|
|
32480
32675
|
var import_yaml = __toESM(require_dist(), 1);
|
|
32481
|
-
|
|
32482
|
-
|
|
32483
|
-
function
|
|
32484
|
-
|
|
32485
|
-
|
|
32486
|
-
|
|
32487
|
-
if (
|
|
32488
|
-
|
|
32489
|
-
|
|
32490
|
-
|
|
32491
|
-
|
|
32492
|
-
|
|
32493
|
-
|
|
32494
|
-
|
|
32495
|
-
|
|
32496
|
-
|
|
32497
|
-
const jobs = doc.contents.get("jobs", true);
|
|
32498
|
-
if (!literalMap(jobs) || !jobs.items.length) return void 0;
|
|
32499
|
-
const result = /* @__PURE__ */ new Map();
|
|
32500
|
-
for (const { key, value: job } of jobs.items) {
|
|
32501
|
-
if (!(0, import_yaml.isScalar)(key) || typeof key.value !== "string" || !literalMap(job)) return void 0;
|
|
32502
|
-
const projected = /* @__PURE__ */ Object.create(null);
|
|
32503
|
-
for (const field of ["runs-on", "uses", "strategy", "needs", "outputs", "steps"]) {
|
|
32504
|
-
if (!job.has(field)) continue;
|
|
32505
|
-
const optional = field === "needs" || field === "outputs" || field === "steps";
|
|
32506
|
-
const value = job.get(field, true);
|
|
32507
|
-
if (value !== null && !(0, import_yaml.isNode)(value)) {
|
|
32508
|
-
if (optional) continue;
|
|
32509
|
-
return void 0;
|
|
32676
|
+
|
|
32677
|
+
// ../shared/workflow-runner-yaml-core.ts
|
|
32678
|
+
function createParseRunnerJobs(ops) {
|
|
32679
|
+
const literal = (node) => !ops.isAlias(node) && !(ops.isNode(node) && (node.tag || "anchor" in node && node.anchor));
|
|
32680
|
+
const literalMap = (node) => ops.isMap(node) && literal(node) && node.items.every(({ key }) => ops.isScalar(key) && literal(key) && typeof key.value === "string" && key.value !== "<<");
|
|
32681
|
+
return (source) => {
|
|
32682
|
+
if (!source || source.length > 15e5) return void 0;
|
|
32683
|
+
try {
|
|
32684
|
+
const doc = ops.parseDocument(source, { uniqueKeys: true, stringKeys: true, merge: false, prettyErrors: false });
|
|
32685
|
+
if (doc.errors.length || doc.warnings.length) return void 0;
|
|
32686
|
+
let invalid = false;
|
|
32687
|
+
let nodes = 0;
|
|
32688
|
+
ops.visit(doc, (_key, _node, path2) => {
|
|
32689
|
+
if (++nodes > 1e5 || path2.length > 64) {
|
|
32690
|
+
invalid = true;
|
|
32691
|
+
return ops.BREAK;
|
|
32510
32692
|
}
|
|
32511
|
-
|
|
32512
|
-
|
|
32513
|
-
|
|
32514
|
-
|
|
32693
|
+
});
|
|
32694
|
+
if (invalid || !literalMap(doc.contents)) return void 0;
|
|
32695
|
+
const jobs = doc.contents.get("jobs", true);
|
|
32696
|
+
if (!literalMap(jobs) || !jobs.items.length) return void 0;
|
|
32697
|
+
const result = /* @__PURE__ */ new Map();
|
|
32698
|
+
for (const { key, value: job } of jobs.items) {
|
|
32699
|
+
if (!ops.isScalar(key) || typeof key.value !== "string" || !literalMap(job)) return void 0;
|
|
32700
|
+
const projected = /* @__PURE__ */ Object.create(null);
|
|
32701
|
+
for (const field of ["runs-on", "uses", "strategy", "needs", "outputs", "steps"]) {
|
|
32702
|
+
if (!job.has(field)) continue;
|
|
32703
|
+
const optional = field === "needs" || field === "outputs" || field === "steps";
|
|
32704
|
+
const value = job.get(field, true);
|
|
32705
|
+
if (value !== null && !ops.isNode(value)) {
|
|
32706
|
+
if (optional) continue;
|
|
32707
|
+
return void 0;
|
|
32515
32708
|
}
|
|
32516
|
-
|
|
32517
|
-
|
|
32518
|
-
|
|
32519
|
-
|
|
32709
|
+
ops.visit(value, (_key, node) => {
|
|
32710
|
+
if (!literal(node) || ops.isPair(node) && ops.isScalar(node.key) && node.key.value === "<<") {
|
|
32711
|
+
invalid = true;
|
|
32712
|
+
return ops.BREAK;
|
|
32713
|
+
}
|
|
32714
|
+
});
|
|
32715
|
+
if (invalid && optional) {
|
|
32716
|
+
invalid = false;
|
|
32717
|
+
continue;
|
|
32718
|
+
}
|
|
32719
|
+
if (invalid) return void 0;
|
|
32720
|
+
projected[field] = value === null ? null : value.toJS(doc, { maxAliasCount: 0 });
|
|
32520
32721
|
}
|
|
32521
|
-
|
|
32522
|
-
projected[field] = value === null ? null : value.toJS(doc, { maxAliasCount: 0 });
|
|
32722
|
+
result.set(key.value, projected);
|
|
32523
32723
|
}
|
|
32524
|
-
result
|
|
32724
|
+
return result;
|
|
32725
|
+
} catch {
|
|
32726
|
+
return void 0;
|
|
32525
32727
|
}
|
|
32526
|
-
|
|
32527
|
-
} catch {
|
|
32528
|
-
return void 0;
|
|
32529
|
-
}
|
|
32728
|
+
};
|
|
32530
32729
|
}
|
|
32531
32730
|
|
|
32731
|
+
// src/workflow-runner-yaml.ts
|
|
32732
|
+
var parseRunnerJobs = createParseRunnerJobs({ isAlias: import_yaml.isAlias, isMap: import_yaml.isMap, isNode: import_yaml.isNode, isPair: import_yaml.isPair, isScalar: import_yaml.isScalar, parseDocument: import_yaml.parseDocument, visit: import_yaml.visit, BREAK: import_yaml.visit.BREAK });
|
|
32733
|
+
|
|
32532
32734
|
// src/workflow-runner-source.ts
|
|
32533
|
-
|
|
32534
|
-
|
|
32535
|
-
var REF = /^[\w./-]+$/;
|
|
32536
|
-
var MAX_FILES = 64;
|
|
32537
|
-
var MAX_DEPTH = 8;
|
|
32538
|
-
var WorkflowSourceError = class extends Error {
|
|
32539
|
-
constructor(location, category) {
|
|
32540
|
-
super(`${location}: ${category}`);
|
|
32541
|
-
}
|
|
32542
|
-
};
|
|
32543
|
-
function safeReadCategory(error) {
|
|
32544
|
-
const e = error;
|
|
32545
|
-
if (e?.rateLimited === true) return "rate-limited";
|
|
32546
|
-
if (typeof e?.status === "number" && Number.isInteger(e.status) && e.status >= 400 && e.status <= 599) return `HTTP ${e.status}`;
|
|
32547
|
-
if (e?.name === "TimeoutError" || e?.name === "AbortError" || e?.code === "ETIMEDOUT") return "read-timeout";
|
|
32548
|
-
if (e?.code === "ENOENT") return "reader-unavailable";
|
|
32549
|
-
if (e?.code === "ENOBUFS" || e?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") return "reader-output-limit";
|
|
32550
|
-
if (error instanceof SyntaxError) return "invalid-json";
|
|
32551
|
-
return "read-failed";
|
|
32552
|
-
}
|
|
32553
|
-
function locationOf(file) {
|
|
32554
|
-
return `${file.repo}/${file.path}${file.ref ? `@${file.ref}` : ""}`;
|
|
32555
|
-
}
|
|
32556
|
-
function contentsPath(file) {
|
|
32557
|
-
if (!REPO.test(file.repo) || !(WORKFLOW_PATH.test(file.path) || file.path === ".github/workflows") || file.ref !== void 0 && !REF.test(file.ref)) {
|
|
32558
|
-
throw new WorkflowSourceError("workflow source", "invalid-location");
|
|
32559
|
-
}
|
|
32560
|
-
return `repos/${file.repo}/contents/${file.path}${file.ref ? `?ref=${encodeURIComponent(file.ref)}` : ""}`;
|
|
32561
|
-
}
|
|
32562
|
-
async function readSource(read, file) {
|
|
32563
|
-
const endpoint = contentsPath(file);
|
|
32564
|
-
try {
|
|
32565
|
-
return await read(endpoint);
|
|
32566
|
-
} catch (e) {
|
|
32567
|
-
throw new WorkflowSourceError(locationOf(file), safeReadCategory(e));
|
|
32568
|
-
}
|
|
32569
|
-
}
|
|
32570
|
-
async function readBody(read, file) {
|
|
32571
|
-
const value = await readSource(read, file);
|
|
32572
|
-
if (!value || value.encoding !== "base64" || typeof value.content !== "string" || !value.content.trim() || value.content.length > 15e5 || !/^[A-Za-z0-9+/=\r\n]+$/.test(value.content)) {
|
|
32573
|
-
throw new WorkflowSourceError(locationOf(file), "invalid-content");
|
|
32574
|
-
}
|
|
32575
|
-
return { ...file, body: decodeGitHubContents(value.content) };
|
|
32576
|
-
}
|
|
32577
|
-
function resolveCallee(caller, uses) {
|
|
32578
|
-
if (uses.startsWith("./") && WORKFLOW_PATH.test(uses.slice(2))) return { repo: caller.repo, path: uses.slice(2), ref: caller.ref };
|
|
32579
|
-
const remote = /^([\w.-]+\/[\w.-]+)\/(\.github\/workflows\/[\w.-]+\.ya?ml)@([\w./-]+)$/.exec(uses);
|
|
32580
|
-
return remote ? { repo: remote[1], path: remote[2], ref: remote[3] } : void 0;
|
|
32581
|
-
}
|
|
32582
|
-
async function readRunnerWorkflowClosure(read, repo, ref, roots) {
|
|
32583
|
-
let files;
|
|
32584
|
-
if (roots) {
|
|
32585
|
-
files = roots.map((file) => ({ ...file, repo, ref }));
|
|
32586
|
-
} else {
|
|
32587
|
-
const listing = await readSource(read, { repo, ref, path: ".github/workflows" });
|
|
32588
|
-
if (!Array.isArray(listing)) throw new WorkflowSourceError(`${repo}/.github/workflows`, "invalid-listing");
|
|
32589
|
-
const paths = listing.filter((f) => f?.type === "file" && /\.ya?ml$/i.test(f?.path ?? "")).map((f) => f.path);
|
|
32590
|
-
if (!paths.length || paths.length > MAX_FILES) throw new WorkflowSourceError(`${repo}/.github/workflows`, "empty-or-oversized-listing");
|
|
32591
|
-
files = [];
|
|
32592
|
-
for (const path2 of paths) files.push(await readBody(read, { repo, ref, path: path2 }));
|
|
32593
|
-
}
|
|
32594
|
-
if (!files.length || files.length > MAX_FILES) throw new WorkflowSourceError("workflow source", "empty-or-oversized-listing");
|
|
32595
|
-
for (const file of files) contentsPath(file);
|
|
32596
|
-
const byLocation = new Map(files.map((file) => [locationOf(file), file]));
|
|
32597
|
-
const active = /* @__PURE__ */ new Set();
|
|
32598
|
-
const completed = /* @__PURE__ */ new Set();
|
|
32599
|
-
async function visit2(file, depth) {
|
|
32600
|
-
const key = locationOf(file);
|
|
32601
|
-
if (active.has(key)) throw new WorkflowSourceError(key, "reusable-cycle");
|
|
32602
|
-
if (depth > MAX_DEPTH) throw new WorkflowSourceError(key, "reusable-depth-limit");
|
|
32603
|
-
if (completed.has(key)) return;
|
|
32604
|
-
active.add(key);
|
|
32605
|
-
const jobs = parseRunnerJobs(file.body);
|
|
32606
|
-
if (!jobs) throw new WorkflowSourceError(key, "invalid-workflow-yaml");
|
|
32607
|
-
for (const job of jobs.values()) {
|
|
32608
|
-
if (!Object.hasOwn(job, "uses")) continue;
|
|
32609
|
-
const uses = job.uses;
|
|
32610
|
-
if (typeof uses !== "string" || Object.hasOwn(job, "runs-on")) throw new WorkflowSourceError(key, "unresolved-reusable-ref");
|
|
32611
|
-
const target = resolveCallee(file, uses);
|
|
32612
|
-
if (!target) throw new WorkflowSourceError(key, "unresolved-reusable-ref");
|
|
32613
|
-
const targetKey = locationOf(target);
|
|
32614
|
-
let child2 = byLocation.get(targetKey);
|
|
32615
|
-
if (!child2) {
|
|
32616
|
-
if (byLocation.size >= MAX_FILES) throw new WorkflowSourceError(key, "workflow-read-limit");
|
|
32617
|
-
child2 = await readBody(read, target);
|
|
32618
|
-
byLocation.set(targetKey, child2);
|
|
32619
|
-
}
|
|
32620
|
-
await visit2(child2, depth + 1);
|
|
32621
|
-
}
|
|
32622
|
-
active.delete(key);
|
|
32623
|
-
completed.add(key);
|
|
32624
|
-
}
|
|
32625
|
-
for (const file of files) await visit2(file, 0);
|
|
32626
|
-
return [...byLocation.values()];
|
|
32735
|
+
function readRunnerWorkflowClosure2(read, repo, ref, roots, bounds) {
|
|
32736
|
+
return readRunnerWorkflowClosure(parseRunnerJobs, read, repo, ref, roots, bounds);
|
|
32627
32737
|
}
|
|
32628
|
-
function
|
|
32629
|
-
|
|
32630
|
-
const parsed = new Map(files.map((file) => [locationOf(file), parseRunnerJobs(file.body)]));
|
|
32631
|
-
const resolvedCache = /* @__PURE__ */ new Map();
|
|
32632
|
-
function resolved(file, active) {
|
|
32633
|
-
const key = locationOf(file);
|
|
32634
|
-
if (active.has(key)) return false;
|
|
32635
|
-
if (resolvedCache.has(key)) return resolvedCache.get(key);
|
|
32636
|
-
const next = new Set(active).add(key);
|
|
32637
|
-
const jobs = parsed.get(key);
|
|
32638
|
-
const result = Boolean(jobs && [...jobs.values()].every((job) => {
|
|
32639
|
-
if (!Object.hasOwn(job, "uses")) return true;
|
|
32640
|
-
const uses = job.uses;
|
|
32641
|
-
if (typeof uses !== "string" || Object.hasOwn(job, "runs-on")) return false;
|
|
32642
|
-
const target = resolveCallee(file, uses);
|
|
32643
|
-
const child2 = target && known.get(locationOf(target));
|
|
32644
|
-
return Boolean(child2 && resolved(child2, next));
|
|
32645
|
-
}));
|
|
32646
|
-
resolvedCache.set(key, result);
|
|
32647
|
-
return result;
|
|
32648
|
-
}
|
|
32649
|
-
return files.flatMap((file) => {
|
|
32650
|
-
const jobs = parsed.get(locationOf(file));
|
|
32651
|
-
if (!jobs) return [{ repo: file.repo, path: file.path, job: "(invalid YAML)", cost: "unknown", callee: false }];
|
|
32652
|
-
return [...jobs].flatMap(([id, job]) => {
|
|
32653
|
-
const hasCallee = Object.hasOwn(job, "uses");
|
|
32654
|
-
const uses = hasCallee && typeof job.uses === "string" && !Object.hasOwn(job, "runs-on") ? job.uses : void 0;
|
|
32655
|
-
const target = uses && resolveCallee(file, uses);
|
|
32656
|
-
if (target && known.has(locationOf(target)) && resolved(file, /* @__PURE__ */ new Set())) return [];
|
|
32657
|
-
const cost = hasCallee ? "unknown" : classifyJobRunnerCost(job, standardHostedLabelFor(file.repo, file.path, id), { repo: file.repo, jobs, blessedSha: BLESSED_RUNNER_CHOICE_SHA });
|
|
32658
|
-
return [{ repo: file.repo, path: file.path, job: id, cost, callee: hasCallee }];
|
|
32659
|
-
});
|
|
32660
|
-
});
|
|
32738
|
+
function classifyRunnerWorkflows2(files) {
|
|
32739
|
+
return classifyRunnerWorkflows(parseRunnerJobs, files);
|
|
32661
32740
|
}
|
|
32662
32741
|
|
|
32663
32742
|
// src/actions-billing-preflight.ts
|
|
32664
32743
|
async function assertActionsJobsCanStart(deps, targetRepo2) {
|
|
32665
|
-
const read = deps.readWorkflowContents ?? (async (path2) => JSON.parse(await deps.run("gh", ["api", path2])));
|
|
32666
32744
|
let files;
|
|
32667
32745
|
try {
|
|
32668
|
-
|
|
32746
|
+
const served = await deps.readTrainWorkflowClosure?.(targetRepo2);
|
|
32747
|
+
if (served && !served.some((f) => f.repo.toLowerCase() === targetRepo2.toLowerCase())) {
|
|
32748
|
+
throw new WorkflowSourceError("workflow source", "invalid-response");
|
|
32749
|
+
}
|
|
32750
|
+
if (served) {
|
|
32751
|
+
files = served;
|
|
32752
|
+
} else {
|
|
32753
|
+
const read = deps.readWorkflowContents ?? (async (path2) => JSON.parse(await deps.run("gh", ["api", path2])));
|
|
32754
|
+
try {
|
|
32755
|
+
files = await readRunnerWorkflowClosure2(read, targetRepo2);
|
|
32756
|
+
} catch (e) {
|
|
32757
|
+
const detail = e instanceof WorkflowSourceError ? e.message : "workflow source: read-failed";
|
|
32758
|
+
throw new Error("release refused (#8911): workflow source inventory unreadable; " + detail + ". Resolve this source read before tagging.");
|
|
32759
|
+
}
|
|
32760
|
+
}
|
|
32669
32761
|
} catch (e) {
|
|
32670
|
-
|
|
32671
|
-
|
|
32762
|
+
if (e instanceof WorkflowSourceError) {
|
|
32763
|
+
throw new Error("release refused (#8911): workflow source inventory unreadable; " + e.message + ". Resolve this source read before tagging.");
|
|
32764
|
+
}
|
|
32765
|
+
throw e;
|
|
32672
32766
|
}
|
|
32673
|
-
const unresolved =
|
|
32767
|
+
const unresolved = classifyRunnerWorkflows2(files).filter((f) => f.cost !== "self-hosted" && f.cost !== "standard-hosted");
|
|
32674
32768
|
if (unresolved.length) throw new Error("release refused (#8911): paid or unresolved runner path(s): " + unresolved.map((f) => f.repo + "/" + f.path + ":" + f.job).join(", ") + ". Standard included GitHub-hosted labels are allowed (#8977, Jerv-Hub#2459); only larger/paid runners and unresolved selectors/callees are refused. Resolve them before tagging; no paid capacity probe is permitted.");
|
|
32675
32769
|
process.stderr.write("actions preflight: " + targetRepo2 + " source selectors are self-hosted or standard included GitHub-hosted (#8977); no canary dispatch; the org spending limit guards over-allowance spend (unprovable from source); access/isolation/execution unproven (#8958)\n");
|
|
32676
32770
|
}
|
|
@@ -35904,18 +35998,23 @@ async function runTrainDoctor(input) {
|
|
|
35904
35998
|
}
|
|
35905
35999
|
add(healedBlock ? { code: "scratch-untracked", severity: "healed", source: "local", title: `wrote the org-managed .gitignore block for ${liveScratch.join(", ")} (#5409)`, remedy: "nothing \u2014 the live-session dirs are ignored from now on" } : { code: "scratch-untracked", severity: "warning", source: "local", title: `untracked live-session scratch: ${liveScratch.join(", ")}`, remedy: "run `mmi-cli devops train doctor --heal` (or `mmi-cli doctor --apply`) to write the org-managed .gitignore block (#5409); the train gate already exempts these (#5397)" });
|
|
35906
36000
|
}
|
|
35907
|
-
const
|
|
35908
|
-
const modifiedOnly = new Set(
|
|
36001
|
+
const blockingEntries = porcelainBlockingEntries(porcelain);
|
|
36002
|
+
const modifiedOnly = new Set(blockingEntries.filter((e) => e.status === " M").map((e) => e.path));
|
|
35909
36003
|
const churn = [];
|
|
35910
|
-
for (const path2 of
|
|
36004
|
+
for (const path2 of blockingEntries.map((e) => e.path).filter((p) => modifiedOnly.has(p))) {
|
|
35911
36005
|
try {
|
|
35912
36006
|
if (!clean2(await train.run("git", ["diff", "HEAD", "--numstat", "--", path2]))) churn.push(path2);
|
|
35913
36007
|
} catch {
|
|
35914
36008
|
}
|
|
35915
36009
|
}
|
|
35916
|
-
const dirty =
|
|
35917
|
-
|
|
35918
|
-
|
|
36010
|
+
const dirty = blockingEntries.filter((e) => !churn.includes(e.path));
|
|
36011
|
+
const dirtyTracked = dirty.filter((e) => e.status !== "??");
|
|
36012
|
+
const dirtyUntracked = dirty.filter((e) => e.status === "??");
|
|
36013
|
+
if (dirtyTracked.length) {
|
|
36014
|
+
add({ code: "tree-dirty-work", severity: "blocker", source: "local", title: `working tree carries tracked changes \u2014 blocking: ${dirtyTracked.map((e) => e.path).join(", ")}`, remedy: "commit or stash the work, then rerun; the train refuses a dirty tree before any mutation" });
|
|
36015
|
+
}
|
|
36016
|
+
if (dirtyUntracked.length) {
|
|
36017
|
+
add({ code: "tree-dirty-work", severity: "blocker", source: "local", title: `working tree carries untracked content \u2014 blocking: ${dirtyUntracked.map((e) => e.path).join(", ")}`, remedy: "inspect ownership first \u2014 another session's content stays untouched; commit intended new source through its owning lane; gitignore or move only verified own scratch to the gitignored repo-root tmp/, then rerun" });
|
|
35919
36018
|
}
|
|
35920
36019
|
if (churn.length) {
|
|
35921
36020
|
const restored = [];
|
|
@@ -39318,7 +39417,7 @@ var SHARED_ACTION_PINS = [
|
|
|
39318
39417
|
{ path: "npm-publish", floor: NPM_PUBLISH_SAFE_FLOOR_SHA }
|
|
39319
39418
|
];
|
|
39320
39419
|
function auditRunnerFallbacks(repo, files, closure) {
|
|
39321
|
-
return
|
|
39420
|
+
return classifyRunnerWorkflows2(closure ?? files.map((file) => ({ ...file, repo }))).map((f) => ({
|
|
39322
39421
|
ok: f.cost === "self-hosted" || f.cost === "standard-hosted",
|
|
39323
39422
|
label: f.cost === "standard-hosted" ? `runner uses approved standard-hosted source (#8912/#8958/#8977): ${f.repo}/${f.path}:${f.job}` : `runner is self-hosted only (#8911): ${f.repo}/${f.path}:${f.job}`,
|
|
39324
39423
|
detail: f.callee ? "unknown callee runner policy; source must be read at its referenced revision" : f.cost === "hosted" ? "larger/paid GitHub-hosted runner is forbidden; only standard included GitHub-hosted labels are allowed (#8977, Jerv-Hub#2459)" : f.cost === "unknown" ? "unknown runner selector; source unresolved" : f.cost === "standard-hosted" ? "standard GitHub-hosted labels within included Enterprise minutes only (#8977, Jerv-Hub#2459); the org spending limit guards over-allowance spend (unprovable from source); runner access and isolation not established" : "self-hosted selection only; runner access and isolation not established",
|
|
@@ -40381,7 +40480,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
40381
40480
|
const workflows = await getBranchWorkflows();
|
|
40382
40481
|
if (workflows) {
|
|
40383
40482
|
try {
|
|
40384
|
-
const closure = workflows.length ? await
|
|
40483
|
+
const closure = workflows.length ? await readRunnerWorkflowClosure2((path2) => callerDeps.client.rest("GET", path2), repo, baseBranch, workflows) : [];
|
|
40385
40484
|
checks.push(...auditRunnerFallbacks(repo, workflows, closure));
|
|
40386
40485
|
} catch (e) {
|
|
40387
40486
|
checks.push({ ok: false, label: "runner workflow source is readable (#8958)", detail: e instanceof WorkflowSourceError ? e.message : "workflow source: read-failed" });
|
|
@@ -47165,7 +47264,7 @@ function sshRemoteCommand(remoteCommand) {
|
|
|
47165
47264
|
function shellSingleQuote(value) {
|
|
47166
47265
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
47167
47266
|
}
|
|
47168
|
-
var SSH_RECIPE_AGENT_NOTE =
|
|
47267
|
+
var SSH_RECIPE_AGENT_NOTE = '# agent callers: this is multi-line, so running it inline is denied (#1473/#2125).\n# Use `mmi-cli devops runtime box get <name> --ssh --script <path>` to write it to a file, then `bash <path>`.\n# For a multi-line remote body add --stdin (or --body <file>): the recipe then runs `sh -s` on the box,\n# and you stream the body over stdin \u2014 `bash "<path>" < "<body-file>"` \u2014 with no remote re-quoting (#9750).';
|
|
47169
47268
|
function sshRecipeScript(box, remoteCommand) {
|
|
47170
47269
|
return `#!/usr/bin/env bash
|
|
47171
47270
|
${formatSshRecipe(box, remoteCommand)}
|
|
@@ -47249,8 +47348,16 @@ function registerBoxCommands(program3) {
|
|
|
47249
47348
|
await failGraceful(e.message);
|
|
47250
47349
|
}
|
|
47251
47350
|
});
|
|
47252
|
-
box.command("get <name> [command...]").description("one box by name \u2014 its address and ssh key path; --ssh prints a ready-to-run connect recipe").option("--json", "machine-readable output").option("--ssh", "print a copy-paste ssh recipe (runs under the keyless vault wrapper and loads the key into an ssh-agent; the key is never written to a file or echoed)").option("--script <path>", "with --ssh: write the recipe to <path> and print the one line to run \u2014 the runnable form for agent callers, whose transport denies a multi-line inline body (#3385)").action(async (name, command, o) => {
|
|
47351
|
+
box.command("get <name> [command...]").description("one box by name \u2014 its address and ssh key path; --ssh prints a ready-to-run connect recipe").option("--json", "machine-readable output").option("--ssh", "print a copy-paste ssh recipe (runs under the keyless vault wrapper and loads the key into an ssh-agent; the key is never written to a file or echoed)").option("--script <path>", "with --ssh: write the recipe to <path> and print the one line to run \u2014 the runnable form for agent callers, whose transport denies a multi-line inline body (#3385)").option("--stdin", "with --ssh --script: write the recipe with remote command sh -s so a multi-line script can be piped or redirected on stdin without remote shell re-quoting (#9750)").option("--body <file>", 'with --ssh --script: same as --stdin, and name <file> in the printed run line: bash "<script>" < "<file>" (#9750)').action(async (name, command, o) => {
|
|
47253
47352
|
try {
|
|
47353
|
+
if ((o.stdin || o.body) && (!o.ssh || !o.script)) {
|
|
47354
|
+
await failGraceful("runtime box get: --stdin/--body requires --ssh --script <path> (it writes an sh -s recipe that reads the remote body from stdin)");
|
|
47355
|
+
return;
|
|
47356
|
+
}
|
|
47357
|
+
if ((o.stdin || o.body) && command && command.length) {
|
|
47358
|
+
await failGraceful("runtime box get: cannot combine --stdin/--body with trailing [command...] arguments \u2014 put the commands in the stdin body file instead");
|
|
47359
|
+
return;
|
|
47360
|
+
}
|
|
47254
47361
|
const { boxes, incomplete } = await fetchInventory();
|
|
47255
47362
|
const result = lookupBox(name, boxes, incomplete);
|
|
47256
47363
|
if (result.kind === "unknown") {
|
|
@@ -47269,16 +47376,19 @@ function registerBoxCommands(program3) {
|
|
|
47269
47376
|
await failGraceful("runtime box get: --script requires --ssh (it writes the ssh connect recipe)");
|
|
47270
47377
|
return;
|
|
47271
47378
|
}
|
|
47272
|
-
const
|
|
47379
|
+
const useStdin = Boolean(o.stdin || o.body);
|
|
47380
|
+
const remote = useStdin ? "sh -s" : command && command.length ? command.join(" ") : void 0;
|
|
47381
|
+
const stdinSource = o.body ? `"${o.body}"` : "<body.sh>";
|
|
47273
47382
|
const wroteScript = o.ssh && o.script ? o.script : null;
|
|
47383
|
+
const runLine = wroteScript ? useStdin ? `bash "${wroteScript}" < ${stdinSource}` : `bash "${wroteScript}"` : void 0;
|
|
47274
47384
|
if (wroteScript) (0, import_node_fs37.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
|
|
47275
47385
|
if (o.json) {
|
|
47276
47386
|
console.log(JSON.stringify({
|
|
47277
47387
|
box: found,
|
|
47278
47388
|
incomplete,
|
|
47279
|
-
...wroteScript ? { script: { path: wroteScript, run:
|
|
47389
|
+
...wroteScript ? { script: { path: wroteScript, run: runLine, ...useStdin ? { stdin: o.body ?? true } : {} } } : {}
|
|
47280
47390
|
}, null, 2));
|
|
47281
|
-
} else if (wroteScript) console.log(`wrote ${wroteScript} \u2014 run:
|
|
47391
|
+
} else if (wroteScript) console.log(`wrote ${wroteScript} \u2014 run: ${runLine}`);
|
|
47282
47392
|
else if (o.ssh) console.log(`${formatSshRecipe(found, remote)}
|
|
47283
47393
|
${SSH_RECIPE_AGENT_NOTE}`);
|
|
47284
47394
|
else console.log(formatBoxTable([found]));
|
|
@@ -54211,22 +54321,6 @@ async function deleteMergedRemoteBranch(options) {
|
|
|
54211
54321
|
// src/command-register-train-operations.ts
|
|
54212
54322
|
init_github_client();
|
|
54213
54323
|
|
|
54214
|
-
// src/hub-release-ci-authority.ts
|
|
54215
|
-
var HUB_RELEASE_REPO = "mutmutco/MMI-Hub";
|
|
54216
|
-
var HUB_RELEASE_WORKFLOW = ".github/workflows/hub-release.yml";
|
|
54217
|
-
var HUB_RELEASE_REF = "refs/heads/development";
|
|
54218
|
-
var WORKFLOW_REF = `${HUB_RELEASE_REPO}/${HUB_RELEASE_WORKFLOW}@${HUB_RELEASE_REF}`;
|
|
54219
|
-
var ID = /^[1-9][0-9]*$/;
|
|
54220
|
-
function hubReleaseCiRun(env) {
|
|
54221
|
-
if (env.MMI_HUB_RELEASE_CI !== "1" || env.GITHUB_ACTIONS !== "true") return null;
|
|
54222
|
-
if (env.GITHUB_REPOSITORY !== HUB_RELEASE_REPO || env.GITHUB_EVENT_NAME !== "workflow_dispatch") return null;
|
|
54223
|
-
if (env.GITHUB_WORKFLOW_REF !== WORKFLOW_REF || env.GITHUB_REF !== HUB_RELEASE_REF) return null;
|
|
54224
|
-
const runId = env.GITHUB_RUN_ID ?? "";
|
|
54225
|
-
const runAttempt2 = env.GITHUB_RUN_ATTEMPT ?? "";
|
|
54226
|
-
if (!ID.test(runId) || !ID.test(runAttempt2)) return null;
|
|
54227
|
-
return { runId: Number(runId), runAttempt: Number(runAttempt2) };
|
|
54228
|
-
}
|
|
54229
|
-
|
|
54230
54324
|
// src/release-custody.ts
|
|
54231
54325
|
var import_node_crypto18 = require("node:crypto");
|
|
54232
54326
|
var import_node_fs41 = require("node:fs");
|
|
@@ -62659,6 +62753,9 @@ function trainApplyDeps() {
|
|
|
62659
62753
|
return withHubReleaseCustody({
|
|
62660
62754
|
// #8958: same authenticated bounded read boundary as devops api; no raw gh contents subprocess.
|
|
62661
62755
|
readWorkflowContents: (path2) => defaultGitHubClient().rest("GET", path2, { timeoutMs: GH_TRAIN_TIMEOUT_MS }),
|
|
62756
|
+
// #9755: delegated Jerv trains require the Hub's sourceSha-pinned closure and fail closed on errors.
|
|
62757
|
+
// Humans and the Hub binding retain the caller-side read above; no producer token is widened.
|
|
62758
|
+
readTrainWorkflowClosure: async (repo) => trainWorkflowSourceClosure(repo, registryClientDeps(await loadConfig())),
|
|
62662
62759
|
// #7027: every mutating train verb holds this checkout's train writer lock for its whole run.
|
|
62663
62760
|
trainWriter: (verb, cwd) => acquireTrainWriterLock(cwd, `mmi-cli devops ${verb}`),
|
|
62664
62761
|
run: async (file, args) => {
|
|
@@ -68400,6 +68497,22 @@ function stageAllowlistConfig(project2) {
|
|
|
68400
68497
|
const stages = cfg.stages.filter((s) => ALLOWLIST_STAGES.includes(s));
|
|
68401
68498
|
return stages.length > 0 ? { key: cfg.key, stages } : null;
|
|
68402
68499
|
}
|
|
68500
|
+
function resolveStageAllowlistTarget(repo, projects) {
|
|
68501
|
+
if (!projects) return { kind: "registry-unreadable", exitCode: 1, message: "org access sync: could not read the org project registry \u2014 nothing written" };
|
|
68502
|
+
const project2 = projects.find((p) => (p.repos ?? []).some((r) => r.toLowerCase() === repo.toLowerCase())) ?? null;
|
|
68503
|
+
if (!project2) return { kind: "unregistered", exitCode: 1, message: `org access sync: ${repo} is not in the org project registry \u2014 nothing written` };
|
|
68504
|
+
if (project2.stageAllowlist == null) {
|
|
68505
|
+
return {
|
|
68506
|
+
kind: "no-opt-in",
|
|
68507
|
+
exitCode: 0,
|
|
68508
|
+
json: { repo, optIn: null, stages: [], skipped: "no-stageAllowlist" },
|
|
68509
|
+
message: `org access sync: ${repo} has no stageAllowlist opt-in \u2014 nothing to rebuild (set it with \`mmi-cli oracle org project set ${repo} --var 'stageAllowlist={"key":"<KEY>","stages":["dev"]}'\`)`
|
|
68510
|
+
};
|
|
68511
|
+
}
|
|
68512
|
+
const config = stageAllowlistConfig(project2);
|
|
68513
|
+
if (!config) return { kind: "malformed", exitCode: 1, message: `org access sync: ${repo} has a malformed stageAllowlist in the registry \u2014 set it with \`mmi-cli oracle org project set ${repo} --var 'stageAllowlist={"key":"<KEY>","stages":["dev"]}'\`` };
|
|
68514
|
+
return { kind: "sync", exitCode: 0, config };
|
|
68515
|
+
}
|
|
68403
68516
|
function developerAdminLogins(collaborators) {
|
|
68404
68517
|
const logins = /* @__PURE__ */ new Set();
|
|
68405
68518
|
for (const c of collaborators) {
|
|
@@ -72215,11 +72328,13 @@ access.command("sync [repo]").description("rebuild the stage allowlist (e.g. MM-
|
|
|
72215
72328
|
if (!repo) return fail("org access sync: pass <owner/repo> or run inside a repo checkout");
|
|
72216
72329
|
const cfg = await loadConfig();
|
|
72217
72330
|
if (!cfg.sagaApiUrl) return fail("org access sync: Hub API URL not configured");
|
|
72218
|
-
const
|
|
72219
|
-
if (
|
|
72220
|
-
|
|
72221
|
-
|
|
72222
|
-
|
|
72331
|
+
const target = resolveStageAllowlistTarget(repo, await fetchProjectsList(registryClientDeps(cfg)));
|
|
72332
|
+
if (target.kind === "no-opt-in") {
|
|
72333
|
+
console.log(o.json ? JSON.stringify(target.json, null, 2) : target.message);
|
|
72334
|
+
return;
|
|
72335
|
+
}
|
|
72336
|
+
if (target.kind !== "sync") return failGraceful(target.message);
|
|
72337
|
+
const config = target.config;
|
|
72223
72338
|
const secretsDeps = makeSecretsDeps(cfg);
|
|
72224
72339
|
const client = defaultGitHubClient();
|
|
72225
72340
|
const result = await syncStageAllowlist({
|
package/cli/package.json
CHANGED