@mutmutco/claude-plugin 4.5.209 → 4.5.211
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/.claude-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +455 -313
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/skills/board/SKILL.md +7 -4
- package/skills/bootstrap/seeds/gate.template.yml +2 -2
- package/skills/bootstrap/seeds/runbook-unaffected.json +2 -2
- package/skills/bootstrap/seeds/suite-gate.template.yml +1 -1
- package/skills/bootstrap/seeds/suite-guide.template.md +1 -1
- package/skills/epic/SKILL.md +2 -2
- package/skills/hotfix/SKILL.md +6 -4
- package/skills/mmi-help/SKILL.md +1 -2
- package/skills/org-guide/SKILL.md +3 -2
- package/skills/org-guide/references/work-and-releases.md +53 -9
- package/skills/rcand/SKILL.md +3 -3
- package/skills/rcand/recovery.md +1 -1
- package/skills/release/SKILL.md +3 -3
- package/skills/release/recovery.md +1 -1
- package/skills/release/verification.md +6 -6
package/cli/dist/main.cjs
CHANGED
|
@@ -6365,21 +6365,177 @@ function splitInlineSelectors(argv, flags) {
|
|
|
6365
6365
|
}
|
|
6366
6366
|
return out;
|
|
6367
6367
|
}
|
|
6368
|
+
var BOARD_MOVE_OWN_FLAGS = ["--repo", "--allow-partial", "--json"];
|
|
6369
|
+
var BOARD_SHOW_OWN_FLAGS = ["--json", "--repo", "--allow-partial", "--out"];
|
|
6370
|
+
var BOARD_READ_OWN_FLAGS = [
|
|
6371
|
+
"--json",
|
|
6372
|
+
"--repo",
|
|
6373
|
+
"--direct",
|
|
6374
|
+
"--snapshot-only",
|
|
6375
|
+
"--min-cursor",
|
|
6376
|
+
"--bundle-details",
|
|
6377
|
+
"--bodies",
|
|
6378
|
+
"--allow-partial",
|
|
6379
|
+
"--out"
|
|
6380
|
+
];
|
|
6381
|
+
var BOARD_OTHER_OWN_FLAGS = {
|
|
6382
|
+
unclaim: ["--json", "--repo", "--to-status", "--force", "--holder-dead", "--allow-partial"],
|
|
6383
|
+
attach: ["--repo", "--json"],
|
|
6384
|
+
"set-priority": ["--json", "--repo"],
|
|
6385
|
+
"archive-done": ["--json", "--repo", "--apply"],
|
|
6386
|
+
doctor: ["--json", "--repo", "--fix", "--concurrency"],
|
|
6387
|
+
events: ["--since", "--wait", "--repo", "--json"]
|
|
6388
|
+
};
|
|
6389
|
+
var BOARD_TARGET_PLACEHOLDER = {
|
|
6390
|
+
unclaim: "<issue>",
|
|
6391
|
+
attach: "<issues>",
|
|
6392
|
+
"set-priority": "<issue> <priority>"
|
|
6393
|
+
};
|
|
6394
|
+
var WORKBOARD_VERB_ALIASES = {
|
|
6395
|
+
claim: "claim",
|
|
6396
|
+
move: "move",
|
|
6397
|
+
status: "move",
|
|
6398
|
+
"set-status": "move",
|
|
6399
|
+
show: "show",
|
|
6400
|
+
view: "show",
|
|
6401
|
+
get: "show",
|
|
6402
|
+
read: "read",
|
|
6403
|
+
list: "read",
|
|
6404
|
+
ls: "read",
|
|
6405
|
+
items: "read",
|
|
6406
|
+
unclaim: "unclaim",
|
|
6407
|
+
release: "unclaim",
|
|
6408
|
+
attach: "attach",
|
|
6409
|
+
add: "attach",
|
|
6410
|
+
"set-priority": "set-priority",
|
|
6411
|
+
priority: "set-priority",
|
|
6412
|
+
"archive-done": "archive-done",
|
|
6413
|
+
archive: "archive-done",
|
|
6414
|
+
doctor: "doctor",
|
|
6415
|
+
events: "events"
|
|
6416
|
+
};
|
|
6417
|
+
var WORKBOARD_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
6418
|
+
"--status",
|
|
6419
|
+
"--to-status",
|
|
6420
|
+
"--issue",
|
|
6421
|
+
"--number",
|
|
6422
|
+
"--repo",
|
|
6423
|
+
"--for",
|
|
6424
|
+
"--out",
|
|
6425
|
+
"--min-cursor",
|
|
6426
|
+
"--since",
|
|
6427
|
+
"--wait",
|
|
6428
|
+
"--concurrency"
|
|
6429
|
+
]);
|
|
6430
|
+
var ISSUE_TOKEN = /^(?:\d+|[\w.-]+\/[\w.-]+#\d+|#\d+)$/;
|
|
6431
|
+
function scanWorkboardArgs(rest) {
|
|
6432
|
+
const positionals = [];
|
|
6433
|
+
const values = /* @__PURE__ */ new Map();
|
|
6434
|
+
const flags = [];
|
|
6435
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
6436
|
+
const tok = rest[i];
|
|
6437
|
+
if (!tok.startsWith("-")) {
|
|
6438
|
+
positionals.push(tok);
|
|
6439
|
+
continue;
|
|
6440
|
+
}
|
|
6441
|
+
const eq = tok.startsWith("--") ? tok.indexOf("=") : -1;
|
|
6442
|
+
const name = eq > 0 ? tok.slice(0, eq) : tok;
|
|
6443
|
+
let value;
|
|
6444
|
+
if (eq > 0) value = tok.slice(eq + 1);
|
|
6445
|
+
else if (WORKBOARD_VALUE_FLAGS.has(name) && rest[i + 1] !== void 0 && !rest[i + 1].startsWith("-")) {
|
|
6446
|
+
value = rest[i + 1];
|
|
6447
|
+
i += 1;
|
|
6448
|
+
}
|
|
6449
|
+
if (value !== void 0) values.set(name, [...values.get(name) ?? [], value]);
|
|
6450
|
+
flags.push({ name, tokens: eq > 0 ? [tok] : value !== void 0 ? [name, value] : [name] });
|
|
6451
|
+
}
|
|
6452
|
+
return {
|
|
6453
|
+
positionals,
|
|
6454
|
+
values,
|
|
6455
|
+
flagArgs: (ownFlags) => flags.filter((f) => ownFlags.includes(f.name)).flatMap((f) => f.tokens)
|
|
6456
|
+
};
|
|
6457
|
+
}
|
|
6458
|
+
var quoteArg = (v) => /\s/.test(v) ? `"${v}"` : v;
|
|
6368
6459
|
function workboardClaimRecovery(unknownToken, argv, paths, ownFlags = BOARD_CLAIM_OWN_FLAGS) {
|
|
6369
6460
|
if (unknownToken !== "workboard") return void 0;
|
|
6370
6461
|
const catalog = [...paths];
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6462
|
+
const inCatalog = (sub2) => catalog.includes(`oracle board ${sub2}`) || catalog.includes(`board ${sub2}`);
|
|
6463
|
+
let tail = unknownCommandTail(argv, "workboard");
|
|
6464
|
+
let skip = 0;
|
|
6465
|
+
if ((tail[0] === "item" || tail[0] === "items") && !(tail.length === 1 && tail[0] === "items")) {
|
|
6466
|
+
tail = tail.slice(1);
|
|
6467
|
+
skip = 1;
|
|
6468
|
+
}
|
|
6469
|
+
const verb = tail[0];
|
|
6470
|
+
let sub = verb === void 0 ? "read" : WORKBOARD_VERB_ALIASES[verb] ?? (inCatalog(verb) ? verb : void 0);
|
|
6471
|
+
if (sub === void 0) return void 0;
|
|
6472
|
+
if (sub === "claim") {
|
|
6473
|
+
if (!inCatalog("claim")) return void 0;
|
|
6474
|
+
const canonicalPath2 = "oracle board claim";
|
|
6475
|
+
const positionals = tail.slice(1);
|
|
6476
|
+
let corrected2;
|
|
6477
|
+
if (positionals.length > 0) {
|
|
6478
|
+
corrected2 = [`mmi-cli ${canonicalPath2}`, ...positionals, ...survivingFlagArgs(argv, "", ownFlags)].join(" ");
|
|
6479
|
+
} else {
|
|
6480
|
+
const normalized = splitInlineSelectors(argv, ["--issue", "--number"]);
|
|
6481
|
+
const usedFlag = normalized.includes("--issue") ? "--issue" : normalized.includes("--number") ? "--number" : "--issue";
|
|
6482
|
+
corrected2 = formatPositionalTarget(canonicalPath2, "issues", { flag: usedFlag, argv: normalized, ownFlags });
|
|
6483
|
+
}
|
|
6484
|
+
return { canonicalPath: canonicalPath2, corrected: corrected2, hint: `did you mean \`${corrected2}\`?` };
|
|
6485
|
+
}
|
|
6486
|
+
const verbAt = argv.indexOf("workboard") + 1 + skip;
|
|
6487
|
+
const scan = scanWorkboardArgs(argv.slice(verb === void 0 ? verbAt : verbAt + 1));
|
|
6488
|
+
const issueValues = [...scan.values.get("--issue") ?? [], ...scan.values.get("--number") ?? []];
|
|
6489
|
+
if (sub === "read" && verb !== void 0 && (scan.positionals.length > 0 || issueValues.length > 0)) sub = "show";
|
|
6490
|
+
if (!inCatalog(sub)) return void 0;
|
|
6491
|
+
const canonicalPath = `oracle board ${sub}`;
|
|
6492
|
+
const head = `mmi-cli ${canonicalPath}`;
|
|
6376
6493
|
let corrected;
|
|
6377
|
-
if (
|
|
6378
|
-
|
|
6494
|
+
if (sub === "move") {
|
|
6495
|
+
const flagStatus = scan.values.get("--status")?.[0] ?? scan.values.get("--to-status")?.[0];
|
|
6496
|
+
let status = flagStatus;
|
|
6497
|
+
let issues = [];
|
|
6498
|
+
const pos = scan.positionals;
|
|
6499
|
+
if (status !== void 0) {
|
|
6500
|
+
issues = pos;
|
|
6501
|
+
} else if (pos.length >= 2) {
|
|
6502
|
+
if (ISSUE_TOKEN.test(pos[0]) && !ISSUE_TOKEN.test(pos[pos.length - 1])) {
|
|
6503
|
+
status = pos[pos.length - 1];
|
|
6504
|
+
issues = pos.slice(0, -1);
|
|
6505
|
+
} else {
|
|
6506
|
+
status = pos[0];
|
|
6507
|
+
issues = pos.slice(1);
|
|
6508
|
+
}
|
|
6509
|
+
} else if (pos.length === 1) {
|
|
6510
|
+
if (ISSUE_TOKEN.test(pos[0])) issues = pos;
|
|
6511
|
+
else status = pos[0];
|
|
6512
|
+
}
|
|
6513
|
+
if (issues.length === 0) issues = issueValues;
|
|
6514
|
+
corrected = [
|
|
6515
|
+
head,
|
|
6516
|
+
status !== void 0 ? quoteArg(status) : "<status>",
|
|
6517
|
+
...issues.length > 0 ? issues : ["<issues>"],
|
|
6518
|
+
...scan.flagArgs(BOARD_MOVE_OWN_FLAGS)
|
|
6519
|
+
].join(" ");
|
|
6520
|
+
} else if (sub === "show") {
|
|
6521
|
+
if (scan.positionals.length > 0) {
|
|
6522
|
+
corrected = [head, ...scan.positionals, ...scan.flagArgs(BOARD_SHOW_OWN_FLAGS)].join(" ");
|
|
6523
|
+
} else {
|
|
6524
|
+
const normalized = splitInlineSelectors(argv, ["--issue", "--number"]);
|
|
6525
|
+
const usedFlag = normalized.includes("--issue") ? "--issue" : normalized.includes("--number") ? "--number" : "--issue";
|
|
6526
|
+
corrected = formatPositionalTarget(canonicalPath, "issue", { flag: usedFlag, argv: normalized, ownFlags: BOARD_SHOW_OWN_FLAGS });
|
|
6527
|
+
}
|
|
6528
|
+
} else if (sub === "read") {
|
|
6529
|
+
corrected = [head, ...survivingFlagArgs(argv, "", BOARD_READ_OWN_FLAGS)].join(" ");
|
|
6379
6530
|
} else {
|
|
6380
|
-
const
|
|
6381
|
-
const
|
|
6382
|
-
|
|
6531
|
+
const declared = BOARD_OTHER_OWN_FLAGS[sub] ?? ["--json", "--repo"];
|
|
6532
|
+
const targets = [...issueValues, ...scan.positionals];
|
|
6533
|
+
const placeholder = BOARD_TARGET_PLACEHOLDER[sub];
|
|
6534
|
+
corrected = [
|
|
6535
|
+
head,
|
|
6536
|
+
...targets.length > 0 ? targets.map(quoteArg) : placeholder ? [placeholder] : [],
|
|
6537
|
+
...scan.flagArgs(declared)
|
|
6538
|
+
].join(" ");
|
|
6383
6539
|
}
|
|
6384
6540
|
return { canonicalPath, corrected, hint: `did you mean \`${corrected}\`?` };
|
|
6385
6541
|
}
|
|
@@ -6427,6 +6583,45 @@ function normalizeBoardClaimIssueArgv(argv) {
|
|
|
6427
6583
|
argv.splice(claimAt + 1, 0, value);
|
|
6428
6584
|
return true;
|
|
6429
6585
|
}
|
|
6586
|
+
function normalizeWorkboardClaimArgv(argv) {
|
|
6587
|
+
const workboardAt = argv[2] === "oracle" ? 3 : 2;
|
|
6588
|
+
if (argv[workboardAt] !== "workboard" || argv[workboardAt + 1] !== "claim") return false;
|
|
6589
|
+
const tail = argv.slice(workboardAt + 2);
|
|
6590
|
+
let selectorAt = -1;
|
|
6591
|
+
let selectorSpan = 0;
|
|
6592
|
+
let selected;
|
|
6593
|
+
let targets = 0;
|
|
6594
|
+
for (let i = 0; i < tail.length; i += 1) {
|
|
6595
|
+
const tok = tail[i];
|
|
6596
|
+
const eq = tok.indexOf("=");
|
|
6597
|
+
const name = eq > 0 ? tok.slice(0, eq) : tok;
|
|
6598
|
+
if (name === "--issue" || name === "--number") {
|
|
6599
|
+
if (selectorAt >= 0) return false;
|
|
6600
|
+
selectorAt = i;
|
|
6601
|
+
selectorSpan = eq > 0 ? 1 : 2;
|
|
6602
|
+
selected = eq > 0 ? tok.slice(eq + 1) : tail[++i];
|
|
6603
|
+
if (!selected || selected.startsWith("-")) return false;
|
|
6604
|
+
} else if (BOARD_CLAIM_VALUE_FLAGS.has(name)) {
|
|
6605
|
+
const value = eq > 0 ? tok.slice(eq + 1) : tail[++i];
|
|
6606
|
+
if (!value || value.startsWith("-")) return false;
|
|
6607
|
+
} else if (BOARD_CLAIM_BOOLEAN_FLAGS.has(tok) || tok === "--dry-run" || tok === "--validate-only") {
|
|
6608
|
+
continue;
|
|
6609
|
+
} else if (tok.startsWith("-") || !tok) {
|
|
6610
|
+
return false;
|
|
6611
|
+
} else {
|
|
6612
|
+
targets += 1;
|
|
6613
|
+
}
|
|
6614
|
+
}
|
|
6615
|
+
if (selected !== void 0) {
|
|
6616
|
+
if (targets > 0) return false;
|
|
6617
|
+
tail.splice(selectorAt, selectorSpan);
|
|
6618
|
+
tail.unshift(selected);
|
|
6619
|
+
} else if (targets === 0) {
|
|
6620
|
+
return false;
|
|
6621
|
+
}
|
|
6622
|
+
argv.splice(2, argv.length - 2, "oracle", "board", "claim", ...tail);
|
|
6623
|
+
return true;
|
|
6624
|
+
}
|
|
6430
6625
|
function classifyParseError(plain) {
|
|
6431
6626
|
if (/unknown command/i.test(plain)) return "unknown-command";
|
|
6432
6627
|
if (/unknown option|missing required argument|too many arguments|argument missing|is invalid\. Allowed choices are|cannot be used with/i.test(plain)) {
|
|
@@ -10413,6 +10608,10 @@ async function fetchTrainAuthority(repo, deps) {
|
|
|
10413
10608
|
}
|
|
10414
10609
|
const body = await res.json();
|
|
10415
10610
|
if (typeof body?.train !== "boolean" || !body.role) return { ok: false, error: "malformed train-authority response" };
|
|
10611
|
+
if (body.authenticatedDelegation !== void 0 && (body.authenticatedDelegation !== true || !body.train || body.repo !== repo || body.delegatedActor !== body.login || !/^[A-Za-z0-9-]{1,39}$/.test(body.delegatedActor ?? ""))) {
|
|
10612
|
+
return { ok: false, error: "malformed delegated train-authority response" };
|
|
10613
|
+
}
|
|
10614
|
+
if (body.delegatedActor !== void 0 && body.authenticatedDelegation !== true) return { ok: false, error: "unverified delegated actor" };
|
|
10416
10615
|
return { ok: true, authority: body };
|
|
10417
10616
|
} catch (e) {
|
|
10418
10617
|
return { ok: false, error: e.message };
|
|
@@ -15142,6 +15341,12 @@ function renderClaimReleaseBody(release) {
|
|
|
15142
15341
|
|
|
15143
15342
|
<!-- mmi-claim-release: ${JSON.stringify(release)} -->`;
|
|
15144
15343
|
}
|
|
15344
|
+
function formatClaimMarker(actor, ts = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
15345
|
+
const marker = { v: 1, session: actor.session, surface: actor.surface, host: actor.host, ts };
|
|
15346
|
+
return `Claimed by \`${describeClaimMarker(marker)}\` at ${marker.ts}.
|
|
15347
|
+
|
|
15348
|
+
<!-- mmi-claim: ${JSON.stringify(marker)} -->`;
|
|
15349
|
+
}
|
|
15145
15350
|
function latestClaimMarker(comments) {
|
|
15146
15351
|
for (let i = comments.length - 1; i >= 0; i -= 1) {
|
|
15147
15352
|
const body = comments[i].body;
|
|
@@ -15466,6 +15671,7 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
|
|
|
15466
15671
|
`repos/${repo}/branches/${encodeURIComponent(out.branch)}`
|
|
15467
15672
|
);
|
|
15468
15673
|
out.branchAt = head?.commit?.commit?.committer?.date ?? void 0;
|
|
15674
|
+
out.branchSha = head?.commit?.sha ?? void 0;
|
|
15469
15675
|
} catch {
|
|
15470
15676
|
}
|
|
15471
15677
|
}
|
|
@@ -15532,13 +15738,27 @@ function isSeatClaimMarker(marker, current, context = {}) {
|
|
|
15532
15738
|
const state = context.sessionState ?? probeLocalClaimSession(marker, context.now);
|
|
15533
15739
|
return state === "dead";
|
|
15534
15740
|
}
|
|
15741
|
+
function isSameBranchClaimResume(contest, repo, landing = {}) {
|
|
15742
|
+
const headBranch = landing.headBranch?.replace(/^refs\/heads\//, "").replace(/^[^/:]+:/, "").trim();
|
|
15743
|
+
const headSha = landing.headSha?.trim();
|
|
15744
|
+
if (!headBranch && !headSha) return false;
|
|
15745
|
+
if (!contest.marker || contest.unverifiable.length > 0 || contest.live.length === 0) return false;
|
|
15746
|
+
const branchLine = /^live branch ([^\s]+) on /;
|
|
15747
|
+
const fromContest = contest.branch?.replace(/ \(head of open PR .*\)$/, "").trim();
|
|
15748
|
+
const fromLive = contest.live.map((line) => branchLine.exec(line)?.[1]).find(Boolean);
|
|
15749
|
+
const liveBranch = fromContest || fromLive;
|
|
15750
|
+
const matches = Boolean(headBranch && liveBranch && liveBranch === headBranch) || Boolean(headSha && contest.branchSha && contest.branchSha === headSha);
|
|
15751
|
+
if (!matches) return false;
|
|
15752
|
+
const ownBranchLine = liveBranch ? `live branch ${liveBranch} ` : "live branch ";
|
|
15753
|
+
return contest.live.every((line) => /^claim marker from /.test(line) || line.startsWith(ownBranchLine) && line.endsWith(` on ${repo}`));
|
|
15754
|
+
}
|
|
15535
15755
|
function laneResumeEvidence(evidence) {
|
|
15536
15756
|
if (!evidence.marker || evidence.sessionState !== "dead") return void 0;
|
|
15537
15757
|
if (!isHostSessionId(evidence.marker.session)) return void 0;
|
|
15538
15758
|
const artifacts = [evidence.openPr, evidence.branch ? `live branch ${evidence.branch}` : void 0].filter(Boolean);
|
|
15539
15759
|
return `prior claim by lane ${describeClaimMarker(evidence.marker)} (@${evidence.markerBy}, ${formatClaimAge(evidence.markerAgeMs)} old) is verifiably not running on this host \u2014 its local session transcript is gone or stale${artifacts.length ? `; resuming over ${artifacts.join("; ")}` : ""}`;
|
|
15540
15760
|
}
|
|
15541
|
-
async function checkLaneContest(client, item, actor = describeSessionIdentity(), viewerLogin, appClient) {
|
|
15761
|
+
async function checkLaneContest(client, item, actor = describeSessionIdentity(), viewerLogin, appClient, landing) {
|
|
15542
15762
|
const livenessClient = await selectLivenessClient(client, appClient);
|
|
15543
15763
|
let evidence = await gatherClaimLiveness(livenessClient, item.repository, item.number, openPullsFetcher(livenessClient));
|
|
15544
15764
|
if (evidence.failed.some((channel) => channel !== "session")) {
|
|
@@ -15550,10 +15770,21 @@ async function checkLaneContest(client, item, actor = describeSessionIdentity(),
|
|
|
15550
15770
|
markerBy: evidence.markerBy,
|
|
15551
15771
|
sessionState: evidence.sessionState
|
|
15552
15772
|
});
|
|
15553
|
-
|
|
15773
|
+
let resume = ownership !== "mine" && ownSeat ? laneResumeEvidence(evidence) : void 0;
|
|
15554
15774
|
const live = ownSeat ? [] : liveEvidenceLines(evidence, item.repository);
|
|
15555
15775
|
const unverifiable = ownSeat ? [] : evidence.failed;
|
|
15556
|
-
|
|
15776
|
+
let contested = !ownSeat && !resume && (live.length > 0 || unverifiable.length > 0);
|
|
15777
|
+
if (contested && landing && isSameBranchClaimResume({
|
|
15778
|
+
contested: true,
|
|
15779
|
+
live,
|
|
15780
|
+
unverifiable,
|
|
15781
|
+
marker: evidence.marker,
|
|
15782
|
+
branch: evidence.branch,
|
|
15783
|
+
branchSha: evidence.branchSha
|
|
15784
|
+
}, item.repository, landing)) {
|
|
15785
|
+
contested = false;
|
|
15786
|
+
resume = `same-branch resume over live branch ${evidence.branch ?? landing.headBranch}`;
|
|
15787
|
+
}
|
|
15557
15788
|
const carry = staleWorkLines(evidence);
|
|
15558
15789
|
const staleStamp = !contested && !resume && !ownSeat && evidence.marker ? `stale claim stamp ${describeClaimMarker(evidence.marker)} (idle ${formatClaimAge(claimStampStaleness(evidence.marker, { activityAt: [evidence.lastCommentAt] }).idleMs)})${carry.length ? `; continue its ${carry.join(" and ")}` : ""}` : void 0;
|
|
15559
15790
|
return {
|
|
@@ -15563,7 +15794,9 @@ async function checkLaneContest(client, item, actor = describeSessionIdentity(),
|
|
|
15563
15794
|
ownership,
|
|
15564
15795
|
marker: evidence.marker,
|
|
15565
15796
|
resume,
|
|
15566
|
-
...staleStamp ? { staleStamp } : {}
|
|
15797
|
+
...staleStamp ? { staleStamp } : {},
|
|
15798
|
+
...evidence.branch ? { branch: evidence.branch } : {},
|
|
15799
|
+
...evidence.branchSha ? { branchSha: evidence.branchSha } : {}
|
|
15567
15800
|
};
|
|
15568
15801
|
}
|
|
15569
15802
|
function laneContestParts(contest) {
|
|
@@ -15576,12 +15809,16 @@ function isUnknownHold(contest) {
|
|
|
15576
15809
|
return !contest.marker && contest.live.length === 0 && contest.unverifiable.length > 0;
|
|
15577
15810
|
}
|
|
15578
15811
|
var CONTESTED_CLAIM_RECOVERY = "leave the holder's work untouched and choose another unclaimed item or coordinate a handoff (use --force only for an intentional takeover)";
|
|
15812
|
+
function contestedClaimRecovery(ref) {
|
|
15813
|
+
const repo = /^([^/\s#]+\/[^/\s#]+)#\d+$/.exec(ref)?.[1];
|
|
15814
|
+
return `${CONTESTED_CLAIM_RECOVERY}; list free items with: mmi-cli oracle board read --json${repo ? ` --repo ${repo}` : ""} and pick from its claimable list`;
|
|
15815
|
+
}
|
|
15579
15816
|
function laneContestMessage(ref, contest, verb) {
|
|
15580
15817
|
const { holder, why } = laneContestParts(contest);
|
|
15581
15818
|
if (isUnknownHold(contest)) {
|
|
15582
|
-
return verb === "claim" ? `${ref} cannot be verified as free: ${holder} (${why}); the liveness reads failed so the claim is refused fail-closed \u2014 ${
|
|
15819
|
+
return verb === "claim" ? `${ref} cannot be verified as free: ${holder} (${why}); the liveness reads failed so the claim is refused fail-closed \u2014 ${contestedClaimRecovery(ref)}` : `refusing to unclaim ${ref}: ${holder} (${why}); the liveness reads failed so the unclaim is refused fail-closed \u2014 re-run with --force --holder-dead once you have evidence the holder lane is dead, or wait for it to go stale (#6625)`;
|
|
15583
15820
|
}
|
|
15584
|
-
return verb === "claim" ? `${ref} is already claimed by ${holder} with live evidence of active work (${why}); ${
|
|
15821
|
+
return verb === "claim" ? `${ref} is already claimed by ${holder} with live evidence of active work (${why}); ${contestedClaimRecovery(ref)}` : `refusing to unclaim ${ref}: ${holder} shows live evidence of active work (${why}); re-run with --force --holder-dead once you have evidence the holder lane is dead, or wait for it to go stale (#6625)`;
|
|
15585
15822
|
}
|
|
15586
15823
|
function ghError(e) {
|
|
15587
15824
|
const err = e;
|
|
@@ -15714,6 +15951,7 @@ async function prepareClaimContext(options, selectors, deps, collected, snapshot
|
|
|
15714
15951
|
cfg,
|
|
15715
15952
|
client,
|
|
15716
15953
|
items: collected.items,
|
|
15954
|
+
selectedRefs: new Set(selectors.map((selector) => `${selector.repo}#${selector.number}`.toLowerCase())),
|
|
15717
15955
|
writable: writableOrUnknown(writable),
|
|
15718
15956
|
report,
|
|
15719
15957
|
session: deps.session ?? describeSessionIdentity(),
|
|
@@ -15825,7 +16063,9 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
15825
16063
|
return;
|
|
15826
16064
|
}
|
|
15827
16065
|
if (!options.force) {
|
|
15828
|
-
const
|
|
16066
|
+
const candidateRefs = [...report.primary.claimable, ...report.secondary.claimable].filter((candidate) => candidate.contentType === "Issue" && candidate.state === "OPEN" && !candidate.claim?.currentlyClaimed && !ctx.selectedRefs.has(candidate.ref.toLowerCase())).map((candidate) => candidate.ref);
|
|
16067
|
+
const alternatives = candidateRefs.length ? `Board snapshot candidates (not guaranteed free): ${candidateRefs.join(", ")}` : "Board snapshot candidates: none in this read";
|
|
16068
|
+
const refusal = `${laneContestMessage(item.ref, contest, "claim")}. ${alternatives}; refresh mmi-cli oracle board read before claiming.`;
|
|
15829
16069
|
throw new Error(options.bulk ? refusal : withGatedWriteChainHint(refusal));
|
|
15830
16070
|
}
|
|
15831
16071
|
previousHolder = displaced();
|
|
@@ -19421,10 +19661,10 @@ var rollout_plan_default = {
|
|
|
19421
19661
|
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)."
|
|
19422
19662
|
},
|
|
19423
19663
|
baseline: {
|
|
19424
|
-
version: "4.5.
|
|
19425
|
-
tag: "v4.5.
|
|
19426
|
-
commit: "
|
|
19427
|
-
npm: "@mutmutco/cli@4.5.
|
|
19664
|
+
version: "4.5.211",
|
|
19665
|
+
tag: "v4.5.211",
|
|
19666
|
+
commit: "cbbcc24fc015",
|
|
19667
|
+
npm: "@mutmutco/cli@4.5.211"
|
|
19428
19668
|
},
|
|
19429
19669
|
exitCriterion: "fleet-n-of-n",
|
|
19430
19670
|
hubOnlyShortcut: "forbidden",
|
|
@@ -19441,14 +19681,14 @@ var rollout_plan_default = {
|
|
|
19441
19681
|
repo: "mutmutco/mmi-hub",
|
|
19442
19682
|
role: "canary",
|
|
19443
19683
|
schedule: "train",
|
|
19444
|
-
v3Target: "v4.5.
|
|
19684
|
+
v3Target: "v4.5.211"
|
|
19445
19685
|
}
|
|
19446
19686
|
],
|
|
19447
19687
|
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.",
|
|
19448
19688
|
rollback: {
|
|
19449
19689
|
independent: true,
|
|
19450
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
19451
|
-
v3Target: "v4.5.
|
|
19690
|
+
mechanism: "npm dist-tag latest -> 4.5.211 and redeploy the Hub Lambda from tag v4.5.211 (cbbcc24fc015); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
19691
|
+
v3Target: "v4.5.211 (@mutmutco/cli@4.5.211, tag commit cbbcc24fc015 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
19452
19692
|
}
|
|
19453
19693
|
},
|
|
19454
19694
|
{
|
|
@@ -21592,9 +21832,12 @@ async function buildTrainApplyContext(deps) {
|
|
|
21592
21832
|
const repo = requireValue(clean2(await deps.run("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])), "repo");
|
|
21593
21833
|
const [owner, name] = repo.split("/");
|
|
21594
21834
|
if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
|
|
21595
|
-
const login = await readTrainLogin(deps);
|
|
21596
21835
|
const verdict = await deps.trainAuthority(repo);
|
|
21597
21836
|
if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
|
|
21837
|
+
if (verdict.authenticatedDelegation === true && (!verdict.train || !/^[A-Za-z0-9-]{1,39}$/.test(verdict.delegatedActor ?? ""))) {
|
|
21838
|
+
throw new Error("invalid delegated train authority");
|
|
21839
|
+
}
|
|
21840
|
+
const login = verdict.authenticatedDelegation === true ? verdict.delegatedActor : await readTrainLogin(deps);
|
|
21598
21841
|
if (!verdict.train) {
|
|
21599
21842
|
throw new Error(`${commandAuthorityLabel(owner)}: @${login} is ${verdict.role} \u2014 no train authority on ${repo}`);
|
|
21600
21843
|
}
|
|
@@ -23952,46 +24195,51 @@ var import_node_fs30 = require("node:fs");
|
|
|
23952
24195
|
var import_promises4 = require("node:fs/promises");
|
|
23953
24196
|
var import_node_path28 = require("node:path");
|
|
23954
24197
|
|
|
23955
|
-
//
|
|
23956
|
-
var
|
|
23957
|
-
|
|
23958
|
-
|
|
23959
|
-
|
|
23960
|
-
|
|
23961
|
-
|
|
23962
|
-
|
|
23963
|
-
|
|
23964
|
-
|
|
23965
|
-
|
|
23966
|
-
|
|
23967
|
-
|
|
23968
|
-
if (jobs.some((job) => isActionsBillingBlockText(JSON.stringify(job)))) return "billing-blocked";
|
|
23969
|
-
if (jobs.some((job) => Array.isArray(job.steps) && job.steps.length > 0 || Boolean(job.startedAt))) {
|
|
23970
|
-
return "started";
|
|
23971
|
-
}
|
|
23972
|
-
if (jobs.length > 0 && jobs.every((job) => {
|
|
23973
|
-
const finished = job.conclusion === "failure" || job.status === "completed";
|
|
23974
|
-
const empty = !Array.isArray(job.steps) || job.steps.length === 0;
|
|
23975
|
-
return finished && empty && !job.startedAt;
|
|
23976
|
-
})) {
|
|
23977
|
-
return "never-started";
|
|
24198
|
+
// ../shared/runner-fallback.ts
|
|
24199
|
+
var HOSTED_RUNNER_LABEL_RE = /\b(?:ubuntu|windows|macos)-(?:latest|\d[\w.-]*)\b/i;
|
|
24200
|
+
function classifyRunnerCost(selector) {
|
|
24201
|
+
const raw = selector.replace(/\s+#.*$/gm, "").trim();
|
|
24202
|
+
if (HOSTED_RUNNER_LABEL_RE.test(raw)) return "hosted";
|
|
24203
|
+
const json = /^\$\{\{\s*fromJSON\('([^']+)'\)\s*\}\}$/.exec(raw);
|
|
24204
|
+
if (json) {
|
|
24205
|
+
try {
|
|
24206
|
+
const labels2 = JSON.parse(json[1]);
|
|
24207
|
+
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";
|
|
24208
|
+
} catch {
|
|
24209
|
+
return "unknown";
|
|
24210
|
+
}
|
|
23978
24211
|
}
|
|
23979
|
-
return "
|
|
23980
|
-
|
|
23981
|
-
|
|
23982
|
-
function isCanaryAccessBlockText(text) {
|
|
23983
|
-
return !isActionsBillingBlockText(text) && CANARY_ACCESS_BLOCK_RE.test(text);
|
|
23984
|
-
}
|
|
23985
|
-
function actionsCanaryUndispatchedRefusal(detail) {
|
|
23986
|
-
return new Error(
|
|
23987
|
-
`release refused: the hosted-job canary (#5604) was not dispatched, so a hosted job start is UNPROVEN \u2014 this is NOT a billing verdict. ${detail} The Hub dispatches ${CANARY_WORKFLOW} with its App token for a train-authorized caller (#6319): refresh the Hub session (\`gh auth login\`) and retry, and confirm \`mmi-cli oracle org access role <owner/repo>\` reports train authority. Nothing was tagged or pushed \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#actions-job-start-canary`
|
|
23988
|
-
);
|
|
24212
|
+
if (raw.includes("${{")) return "unknown";
|
|
24213
|
+
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")];
|
|
24214
|
+
return labels.every((l) => /^[\w-]+$/.test(l)) && labels.some((l) => l.toLowerCase() === "self-hosted") ? "self-hosted" : "unknown";
|
|
23989
24215
|
}
|
|
23990
|
-
|
|
23991
|
-
|
|
23992
|
-
|
|
23993
|
-
|
|
24216
|
+
var STANDARD_HOSTED_ALLOWLIST = [
|
|
24217
|
+
["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"]
|
|
24218
|
+
];
|
|
24219
|
+
function standardHostedLabelFor(repo, path2, jobId) {
|
|
24220
|
+
return STANDARD_HOSTED_ALLOWLIST.find(([r, p, j]) => r === repo && p === path2 && j === jobId)?.[3];
|
|
24221
|
+
}
|
|
24222
|
+
function classifyJobRunnerCost(selector, jobBody, standardHostedLabel) {
|
|
24223
|
+
const direct = classifyRunnerCost(selector);
|
|
24224
|
+
const matrix = /^\$\{\{\s*matrix\.([\w-]+)\s*\}\}$/.exec(selector.trim());
|
|
24225
|
+
if (!matrix || direct === "hosted") return direct;
|
|
24226
|
+
const strategy = /^ strategy:\s*\n((?: {6,}[^\n]*\n?|\s*\n)*)/m.exec(jobBody)?.[1];
|
|
24227
|
+
const matrixBody = strategy && /^ matrix:[^\S\n]*\n((?: {8,}[^\n]*\n?|\s*\n)*)/m.exec(strategy)?.[1];
|
|
24228
|
+
const include = matrixBody && /^ include:[^\S\n]*\n((?: {10,}[^\n]*\n?|\s*\n)*)$/.exec(matrixBody)?.[1];
|
|
24229
|
+
if (!include) return "unknown";
|
|
24230
|
+
const rows = include.split(/^ {10}- /m).slice(1);
|
|
24231
|
+
if (!rows.length) return "unknown";
|
|
24232
|
+
const costs = rows.map((row) => {
|
|
24233
|
+
const match = new RegExp(`^(?: {12})?${matrix[1]}:\\s*(.+)$`, "m").exec(row);
|
|
24234
|
+
if (standardHostedLabel && match?.[1].trim() === standardHostedLabel) return "standard-hosted";
|
|
24235
|
+
return match ? classifyRunnerCost(match[1]) : "unknown";
|
|
24236
|
+
});
|
|
24237
|
+
if (costs.includes("hosted")) return "hosted";
|
|
24238
|
+
if (costs.includes("unknown")) return "unknown";
|
|
24239
|
+
return costs.includes("standard-hosted") ? "standard-hosted" : "self-hosted";
|
|
23994
24240
|
}
|
|
24241
|
+
|
|
24242
|
+
// src/actions-billing-preflight.ts
|
|
23995
24243
|
function parseJson(raw, label) {
|
|
23996
24244
|
try {
|
|
23997
24245
|
return JSON.parse(raw);
|
|
@@ -23999,153 +24247,31 @@ function parseJson(raw, label) {
|
|
|
23999
24247
|
throw new Error(`${label} was not valid JSON`);
|
|
24000
24248
|
}
|
|
24001
24249
|
}
|
|
24002
|
-
|
|
24003
|
-
|
|
24004
|
-
|
|
24005
|
-
|
|
24006
|
-
|
|
24007
|
-
|
|
24008
|
-
|
|
24009
|
-
|
|
24010
|
-
|
|
24011
|
-
|
|
24012
|
-
"--json",
|
|
24013
|
-
"databaseId,conclusion,status,displayTitle,url"
|
|
24014
|
-
]);
|
|
24015
|
-
} catch {
|
|
24016
|
-
return void 0;
|
|
24017
|
-
}
|
|
24018
|
-
const rows = parseJson(raw, `gh run list --repo ${repo}`);
|
|
24019
|
-
if (!Array.isArray(rows)) return void 0;
|
|
24020
|
-
for (const row of rows) {
|
|
24021
|
-
const blob = `${row.displayTitle ?? ""} ${row.conclusion ?? ""} ${row.url ?? ""}`;
|
|
24022
|
-
if (isActionsBillingBlockText(blob)) {
|
|
24023
|
-
return `${repo} run ${row.databaseId ?? row.url ?? "(unknown)"} already names a billing/spending block`;
|
|
24024
|
-
}
|
|
24025
|
-
if (row.conclusion !== "failure" && row.status !== "completed") continue;
|
|
24026
|
-
if (typeof row.databaseId !== "number") continue;
|
|
24027
|
-
try {
|
|
24028
|
-
const view = await deps.run("gh", [
|
|
24029
|
-
"run",
|
|
24030
|
-
"view",
|
|
24031
|
-
String(row.databaseId),
|
|
24032
|
-
"--repo",
|
|
24033
|
-
repo,
|
|
24034
|
-
"--json",
|
|
24035
|
-
"jobs,conclusion,displayTitle,url"
|
|
24036
|
-
]);
|
|
24037
|
-
const parsed = parseJson(view, `gh run view ${row.databaseId}`);
|
|
24038
|
-
const verdict = interpretActionsJobStart({
|
|
24039
|
-
jobs: parsed.jobs,
|
|
24040
|
-
text: `${parsed.displayTitle ?? ""} ${parsed.conclusion ?? ""} ${view}`
|
|
24041
|
-
});
|
|
24042
|
-
if (verdict === "billing-blocked" || verdict === "never-started") {
|
|
24043
|
-
return `${repo} run ${row.databaseId} (${parsed.url ?? row.url ?? "no url"}): hosted job never started` + (verdict === "billing-blocked" ? " (billing/spending refusal)" : " (empty steps)");
|
|
24044
|
-
}
|
|
24045
|
-
} catch {
|
|
24046
|
-
}
|
|
24047
|
-
}
|
|
24048
|
-
return void 0;
|
|
24049
|
-
}
|
|
24050
|
-
async function correlateCanaryRun(deps, nonce) {
|
|
24051
|
-
const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
24052
|
-
let lastError = "no matching canary run";
|
|
24053
|
-
for (let attempt = 0; attempt < 8; attempt++) {
|
|
24054
|
-
if (attempt > 0) await sleep2(CANARY_POLL_MS);
|
|
24055
|
-
let raw;
|
|
24056
|
-
try {
|
|
24057
|
-
raw = await deps.run("gh", [
|
|
24058
|
-
"run",
|
|
24059
|
-
"list",
|
|
24060
|
-
"--repo",
|
|
24061
|
-
CANARY_REPO,
|
|
24062
|
-
"--workflow",
|
|
24063
|
-
CANARY_WORKFLOW,
|
|
24064
|
-
"--limit",
|
|
24065
|
-
"10",
|
|
24066
|
-
"--json",
|
|
24067
|
-
"databaseId,displayTitle,status,createdAt"
|
|
24068
|
-
]);
|
|
24069
|
-
} catch (e) {
|
|
24070
|
-
lastError = e instanceof Error ? e.message : String(e);
|
|
24071
|
-
continue;
|
|
24072
|
-
}
|
|
24073
|
-
const rows = parseJson(
|
|
24074
|
-
raw,
|
|
24075
|
-
"canary gh run list"
|
|
24076
|
-
);
|
|
24077
|
-
const match = rows.find((row) => (row.displayTitle ?? "").includes(nonce) && typeof row.databaseId === "number");
|
|
24078
|
-
if (match?.databaseId) return match.databaseId;
|
|
24079
|
-
}
|
|
24080
|
-
throw new Error(
|
|
24081
|
-
`could not correlate ${CANARY_WORKFLOW} on ${CANARY_REPO} (nonce ${nonce}): ${lastError}`
|
|
24082
|
-
);
|
|
24083
|
-
}
|
|
24084
|
-
async function dispatchCanary(deps, targetRepo2, nonce) {
|
|
24085
|
-
const attempts = [];
|
|
24086
|
-
if (deps.dispatchActionsCanary) {
|
|
24087
|
-
try {
|
|
24088
|
-
await deps.dispatchActionsCanary({ repo: targetRepo2, nonce });
|
|
24089
|
-
return;
|
|
24090
|
-
} catch (e) {
|
|
24091
|
-
attempts.push(`Hub App dispatch: ${e instanceof Error ? e.message : String(e)}`);
|
|
24092
|
-
}
|
|
24093
|
-
}
|
|
24094
|
-
try {
|
|
24095
|
-
await deps.run("gh", [
|
|
24096
|
-
"workflow",
|
|
24097
|
-
"run",
|
|
24098
|
-
CANARY_WORKFLOW,
|
|
24099
|
-
"--repo",
|
|
24100
|
-
CANARY_REPO,
|
|
24101
|
-
"-f",
|
|
24102
|
-
`nonce=${nonce}`
|
|
24103
|
-
]);
|
|
24104
|
-
return;
|
|
24105
|
-
} catch (e) {
|
|
24106
|
-
attempts.push(`gh workflow run --repo ${CANARY_REPO}: ${e instanceof Error ? e.message : String(e)}`);
|
|
24107
|
-
}
|
|
24108
|
-
const detail = `could not dispatch ${CANARY_WORKFLOW} on ${CANARY_REPO} \u2014 ${attempts.join(" | ")}.`;
|
|
24109
|
-
if (attempts.some((a) => isActionsBillingBlockText(a))) {
|
|
24110
|
-
throw actionsBillingRefusal(`${detail} A hosted publish job may not start (the v1.54.2 class).`);
|
|
24111
|
-
}
|
|
24112
|
-
throw actionsCanaryUndispatchedRefusal(
|
|
24113
|
-
attempts.every((a) => isCanaryAccessBlockText(a)) ? `${detail} Every door answered with an access refusal \u2014 the caller may read MMI-Hub but not start its workflows.` : detail
|
|
24114
|
-
);
|
|
24115
|
-
}
|
|
24116
|
-
var HOSTED_LABEL_RE = /\b(?:ubuntu|windows|macos)-(?:latest|\d[\w.-]*)\b/;
|
|
24117
|
-
var FALLBACK_EXPR_GLOBAL_RE = /\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'(?:ubuntu|windows|macos)-(?:latest|\d[\w.-]*)'\s*\|\|\s*fromJSON\('\[[^\n']+\]'\)\s*\}\}/g;
|
|
24118
|
-
var AUTOMATIC_TRIGGERS = /* @__PURE__ */ new Set(["push", "pull_request", "pull_request_target", "release", "schedule", "workflow_run", "merge_group", "create", "deployment", "repository_dispatch", "check_run", "check_suite", "issues", "issue_comment"]);
|
|
24119
|
-
function workflowTriggers(yaml) {
|
|
24120
|
-
const lines2 = yaml.split(/\r?\n/);
|
|
24121
|
-
const i = lines2.findIndex((l) => /^(?:on|"on"|'on'):(?:\s|$)/.test(l));
|
|
24122
|
-
if (i < 0) return [];
|
|
24123
|
-
const inline = lines2[i].replace(/^[^:]+:\s*/, "").replace(/\s+#.*$/, "").trim();
|
|
24124
|
-
if (inline) return inline.replace(/^\[|\]$/g, "").split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
24125
|
-
const out = [];
|
|
24126
|
-
for (const line of lines2.slice(i + 1)) {
|
|
24127
|
-
if (/^\S/.test(line)) break;
|
|
24128
|
-
const m = /^ {2}([A-Za-z_]+):/.exec(line) ?? /^ {2}- ([A-Za-z_]+)\s*$/.exec(line);
|
|
24129
|
-
if (m) out.push(m[1]);
|
|
24130
|
-
}
|
|
24131
|
-
return out;
|
|
24132
|
-
}
|
|
24133
|
-
function workflowNeedsHostedRunner(yaml) {
|
|
24134
|
-
const body = yaml.split(/\r?\n/).filter((l) => !/^\s*#/.test(l)).join("\n").replace(FALLBACK_EXPR_GLOBAL_RE, "");
|
|
24135
|
-
return HOSTED_LABEL_RE.test(body) && workflowTriggers(yaml).some((t) => AUTOMATIC_TRIGGERS.has(t));
|
|
24250
|
+
function workflowNeedsHostedRunner(yaml, localWorkflows = /* @__PURE__ */ new Set(), where) {
|
|
24251
|
+
const jobs = parseWorkflowJobs(yaml.split(/\r?\n/).filter((l) => !/^\s*#/.test(l)).join("\n"));
|
|
24252
|
+
return !jobs.length || jobs.some((job) => {
|
|
24253
|
+
const callee = /^ uses:\s*\.\/(.+)$/m.exec(job.body)?.[1];
|
|
24254
|
+
if (callee && localWorkflows.has(callee)) return false;
|
|
24255
|
+
const section = /^ runs-on:([^\n]*(?:\n {6,}[^\n]*)*)/m.exec(job.body)?.[1] ?? "";
|
|
24256
|
+
const selector = section.split("\n").map((line) => line.trim()).join("\n");
|
|
24257
|
+
const cost = classifyJobRunnerCost(selector, job.body, where && standardHostedLabelFor(where.repo, where.path, job.id));
|
|
24258
|
+
return cost !== "self-hosted" && cost !== "standard-hosted";
|
|
24259
|
+
});
|
|
24136
24260
|
}
|
|
24137
24261
|
async function hostedWorkflowsOf(deps, repo) {
|
|
24138
24262
|
const list = parseJson(
|
|
24139
24263
|
await deps.run("gh", ["api", `repos/${repo}/contents/.github/workflows`]),
|
|
24140
24264
|
`${repo} workflow listing`
|
|
24141
24265
|
);
|
|
24142
|
-
|
|
24266
|
+
if (!Array.isArray(list) || !list.some((f) => f.type === "file" && /\.ya?ml$/.test(f.path ?? ""))) throw new Error("no workflow inventory");
|
|
24267
|
+
const workflows = [];
|
|
24143
24268
|
for (const f of list) {
|
|
24144
|
-
if (f.type !== "file" || !f.path || !/\.ya?ml$/.test(f.path)
|
|
24269
|
+
if (f.type !== "file" || !f.path || !/\.ya?ml$/.test(f.path)) continue;
|
|
24145
24270
|
const raw = parseJson(await deps.run("gh", ["api", `repos/${repo}/contents/${f.path}`]), `${repo} ${f.path}`);
|
|
24146
|
-
|
|
24271
|
+
workflows.push({ path: f.path, body: Buffer.from(raw.content ?? "", "base64").toString("utf8") });
|
|
24147
24272
|
}
|
|
24148
|
-
|
|
24273
|
+
const local = new Set(workflows.map((file) => file.path));
|
|
24274
|
+
return workflows.filter((file) => workflowNeedsHostedRunner(file.body, local, { repo, path: file.path })).map((file) => file.path);
|
|
24149
24275
|
}
|
|
24150
24276
|
async function assertActionsJobsCanStart(deps, targetRepo2) {
|
|
24151
24277
|
let hosted;
|
|
@@ -24155,50 +24281,11 @@ async function assertActionsJobsCanStart(deps, targetRepo2) {
|
|
|
24155
24281
|
hosted = void 0;
|
|
24156
24282
|
}
|
|
24157
24283
|
if (hosted && hosted.length === 0) {
|
|
24158
|
-
process.stderr.write(`actions preflight: ${targetRepo2}
|
|
24284
|
+
process.stderr.write(`actions preflight: ${targetRepo2} source selectors are self-hosted only; no canary dispatch; access/isolation/execution unproven (#8911)
|
|
24159
24285
|
`);
|
|
24160
24286
|
return;
|
|
24161
24287
|
}
|
|
24162
|
-
|
|
24163
|
-
if (scanned) throw actionsBillingRefusal(scanned);
|
|
24164
|
-
if (targetRepo2.toLowerCase() !== CANARY_REPO.toLowerCase()) {
|
|
24165
|
-
const hubScan = await scanRepoForBillingBlock(deps, CANARY_REPO);
|
|
24166
|
-
if (hubScan) throw actionsBillingRefusal(hubScan);
|
|
24167
|
-
}
|
|
24168
|
-
const nonce = `5604-${(deps.now ?? Date.now)().toString(36)}`;
|
|
24169
|
-
await dispatchCanary(deps, targetRepo2, nonce);
|
|
24170
|
-
const runId = await correlateCanaryRun(deps, nonce);
|
|
24171
|
-
const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
24172
|
-
let last = "pending";
|
|
24173
|
-
let url = `https://github.com/${CANARY_REPO}/actions/runs/${runId}`;
|
|
24174
|
-
for (let attempt = 0; attempt < CANARY_POLLS; attempt++) {
|
|
24175
|
-
if (attempt > 0) await sleep2(CANARY_POLL_MS);
|
|
24176
|
-
const view = await deps.run("gh", [
|
|
24177
|
-
"run",
|
|
24178
|
-
"view",
|
|
24179
|
-
String(runId),
|
|
24180
|
-
"--repo",
|
|
24181
|
-
CANARY_REPO,
|
|
24182
|
-
"--json",
|
|
24183
|
-
"jobs,status,conclusion,url,displayTitle"
|
|
24184
|
-
]);
|
|
24185
|
-
const parsed = parseJson(view, `canary gh run view ${runId}`);
|
|
24186
|
-
if (parsed.url) url = parsed.url;
|
|
24187
|
-
last = interpretActionsJobStart({
|
|
24188
|
-
jobs: parsed.jobs,
|
|
24189
|
-
text: `${parsed.displayTitle ?? ""} ${parsed.conclusion ?? ""} ${view}`
|
|
24190
|
-
});
|
|
24191
|
-
if (last === "started") {
|
|
24192
|
-
await deps.run("gh", ["run", "cancel", String(runId), "--repo", CANARY_REPO]).catch(() => "");
|
|
24193
|
-
return;
|
|
24194
|
-
}
|
|
24195
|
-
if (last === "billing-blocked" || last === "never-started") {
|
|
24196
|
-
throw actionsBillingRefusal(`${url}: hosted canary ${last} \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#actions-job-start-canary`);
|
|
24197
|
-
}
|
|
24198
|
-
}
|
|
24199
|
-
throw actionsBillingRefusal(
|
|
24200
|
-
`${url}: hosted canary stayed ${last} after ${CANARY_POLLS} polls \u2014 cannot prove a hosted job can start \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#actions-job-start-canary`
|
|
24201
|
-
);
|
|
24288
|
+
throw new Error(`release refused (#8911): ${hosted ? `paid or unresolved runner path(s): ${hosted.join(", ")}` : "runner inventory unreadable; self-hosted-only selection unproven"}. Remove paid selectors/fallbacks and verify existing self-hosted access and isolation before tagging.`);
|
|
24202
24289
|
}
|
|
24203
24290
|
|
|
24204
24291
|
// src/train-apply-recovery.ts
|
|
@@ -25734,6 +25821,9 @@ function failedRunIdentity(run, repo) {
|
|
|
25734
25821
|
function manualRunAction(run, repo) {
|
|
25735
25822
|
return `safe next action: diagnose with gh run view ${run.runId} --repo ${repo} --log-failed; rerun by hand only when the cause is fixed: gh run rerun ${run.runId} --repo ${repo} --failed (never re-tag)`;
|
|
25736
25823
|
}
|
|
25824
|
+
function runsOnShaAction(repo, sha) {
|
|
25825
|
+
return `safe next action: mmi-cli devops read run list --repo ${repo} --commit ${sha} --json databaseId,workflowName,event,status,conclusion to identify the run`;
|
|
25826
|
+
}
|
|
25737
25827
|
async function readFailedRunsOnSha(deps, repo, sha) {
|
|
25738
25828
|
try {
|
|
25739
25829
|
const rows = JSON.parse(await deps.run("gh", [
|
|
@@ -25809,7 +25899,7 @@ async function correlateCandidateRunsToRequiredContexts(deps, repo, sha, candida
|
|
|
25809
25899
|
if (contexts.length === 0) {
|
|
25810
25900
|
return {
|
|
25811
25901
|
unique: false,
|
|
25812
|
-
note: `the failure names no failed required context \u2014 rerunning the first failed run could rerun an unrelated workflow; manual intervention required;
|
|
25902
|
+
note: `the failure names no failed required context \u2014 rerunning the first failed run could rerun an unrelated workflow; manual intervention required; ${runsOnShaAction(repo, sha)}`
|
|
25813
25903
|
};
|
|
25814
25904
|
}
|
|
25815
25905
|
const failedChecks = await readFailedCheckRunsOnSha(deps, repo, sha);
|
|
@@ -25862,7 +25952,7 @@ async function transientRecoveryForFailedPhase(deps, input) {
|
|
|
25862
25952
|
};
|
|
25863
25953
|
}
|
|
25864
25954
|
if (candidates.length === 0) {
|
|
25865
|
-
return { reran: false, recovered: false, note: `no failed run with an id could be classified on ${sha.slice(0, 12)} \u2014 manual intervention required (no marker proof, no automatic rerun);
|
|
25955
|
+
return { reran: false, recovered: false, note: `no failed run with an id could be classified on ${sha.slice(0, 12)} \u2014 manual intervention required (no marker proof, no automatic rerun); ${runsOnShaAction(input.repo, sha)}` };
|
|
25866
25956
|
}
|
|
25867
25957
|
if (enumerated && phase === "ordinaryCi") {
|
|
25868
25958
|
const correlation = await correlateCandidateRunsToRequiredContexts(deps, input.repo, sha, candidates, extractFailedRequiredContexts(failure));
|
|
@@ -30301,33 +30391,6 @@ function registryRepoKey(repo) {
|
|
|
30301
30391
|
return repo.toLowerCase();
|
|
30302
30392
|
}
|
|
30303
30393
|
|
|
30304
|
-
// ../shared/runner-fallback.ts
|
|
30305
|
-
var RUNNER_FALLBACK_EXPR_RE = /^\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'((?:ubuntu|windows|macos)-(?:latest|\d[\w.-]*))'\s*\|\|\s*fromJSON\('(\[[^\n']+\])'\)\s*\}\}$/;
|
|
30306
|
-
var FLEET_ONLY_RUNNER_JOBS = {
|
|
30307
|
-
"backup-health.yml:judge": "SSH backup inventory; hosted source-IP access unverified",
|
|
30308
|
-
"browser-pass.yml:browser-pass": "owner-bounded two-lane browser capacity",
|
|
30309
|
-
"app-publish.yml:publish": "SSH tenant publish; hosted source-IP access unverified",
|
|
30310
|
-
"get-publish.yml:publish": "SSH installer publish; hosted source-IP access unverified",
|
|
30311
|
-
"get-publish-desktop.yml:publish": "SSH installer publish; hosted source-IP access unverified",
|
|
30312
|
-
"hetzner-knownhosts-pin.yml:pin": "SSH host-key probe; hosted source-IP access unverified",
|
|
30313
|
-
"module-rc-publish.yml:publish": "SSH live-container snapshot and tenant persistent state",
|
|
30314
|
-
"tenant-control.yml:control": "SSH tenant control; hosted source-IP access unverified",
|
|
30315
|
-
"tenant-deploy.yml:deploy": "SSH tenant deploy; hosted source-IP access unverified",
|
|
30316
|
-
"tenant-reconcile.yml:reconcile": "SSH tenant reconciliation; hosted source-IP access unverified"
|
|
30317
|
-
};
|
|
30318
|
-
function parseRunnerFallbackExpression(runsOn) {
|
|
30319
|
-
const match = RUNNER_FALLBACK_EXPR_RE.exec(runsOn.trim());
|
|
30320
|
-
if (!match) return void 0;
|
|
30321
|
-
try {
|
|
30322
|
-
const parsed = JSON.parse(match[2]);
|
|
30323
|
-
if (!Array.isArray(parsed) || !parsed.length || !parsed.every((item) => typeof item === "string")) return void 0;
|
|
30324
|
-
if (!parsed.some((item) => item.toLowerCase() === "self-hosted")) return void 0;
|
|
30325
|
-
return { hosted: match[1], labels: parsed };
|
|
30326
|
-
} catch {
|
|
30327
|
-
return void 0;
|
|
30328
|
-
}
|
|
30329
|
-
}
|
|
30330
|
-
|
|
30331
30394
|
// src/ci-audit.ts
|
|
30332
30395
|
var HUB_REPO4 = "mutmutco/MMI-Hub";
|
|
30333
30396
|
var HUB_GATE_CONTEXTS = ["cli", "infra", "docs"];
|
|
@@ -30392,20 +30455,18 @@ function auditRunnerFallbacks(repo, files) {
|
|
|
30392
30455
|
for (const { path: path2, body } of files) {
|
|
30393
30456
|
const jobs = parseWorkflowJobs(body.split(/\r?\n/).filter((line) => !/^\s*#/.test(line)).join("\n"));
|
|
30394
30457
|
for (const job of jobs) {
|
|
30395
|
-
const runsOn = /^ runs-on:\s*(.+)$/m.exec(job.body)?.[1] ?? "";
|
|
30396
30458
|
const runnerSection = /^ runs-on:([^\n]*(?:\n {6,}[^\n]*)*)/m.exec(job.body)?.[1] ?? "";
|
|
30397
|
-
const
|
|
30398
|
-
|
|
30399
|
-
if (
|
|
30400
|
-
const
|
|
30401
|
-
const
|
|
30402
|
-
const
|
|
30403
|
-
const safe = !!fallback;
|
|
30459
|
+
const callee = /^ uses:\s*(.+)$/m.exec(job.body)?.[1];
|
|
30460
|
+
if (callee?.startsWith("./") && files.some((file) => file.path === callee.slice(2))) continue;
|
|
30461
|
+
if (callee && /^mutmutco\/MMI-Hub\/\.github\/workflows\/[\w.-]+\.ya?ml@[\w./-]+$/.test(callee)) continue;
|
|
30462
|
+
const standardHostedLabel = standardHostedLabelFor(repo, path2, job.id);
|
|
30463
|
+
const cost = classifyJobRunnerCost(runnerSection.split("\n").map((line) => line.trim()).join("\n"), job.body, standardHostedLabel);
|
|
30464
|
+
const ok = !callee && (cost === "self-hosted" || cost === "standard-hosted");
|
|
30404
30465
|
checks.push({
|
|
30405
|
-
ok
|
|
30406
|
-
label: `runner
|
|
30407
|
-
detail:
|
|
30408
|
-
remediation:
|
|
30466
|
+
ok,
|
|
30467
|
+
label: cost === "standard-hosted" ? `runner uses exact standard-hosted exception (#8912): ${path2}:${job.id}` : `runner is self-hosted only (#8911): ${path2}:${job.id}`,
|
|
30468
|
+
detail: callee ? `unknown callee runner policy: ${callee}` : cost === "hosted" ? "paid GitHub-hosted runner selection is forbidden" : cost === "unknown" ? "unknown runner selector; not proven self-hosted" : cost === "standard-hosted" ? "exact Hub launcher windows-latest matrix leg permitted within included Enterprise minutes; runner access and isolation not established" : "self-hosted selection only; runner access and isolation not established",
|
|
30469
|
+
remediation: ok ? void 0 : `Use only a verified accessible self-hosted tuple, with no hosted fallback; resolve unknown selectors/callees. Preserve checks and artifacts. See gh-runner-runbook.md (${repo}).`
|
|
30409
30470
|
});
|
|
30410
30471
|
}
|
|
30411
30472
|
}
|
|
@@ -30630,7 +30691,7 @@ function rulesetCoversReleaseBranches(payload, track) {
|
|
|
30630
30691
|
function releaseBranchRefsCovered(conditions, track) {
|
|
30631
30692
|
const include = conditions?.ref_name?.include;
|
|
30632
30693
|
if (!Array.isArray(include)) return [];
|
|
30633
|
-
const releaseRefs = track
|
|
30694
|
+
const releaseRefs = branchesForTrack(track).filter((branch) => branch !== "development").map((branch) => `refs/heads/${branch}`);
|
|
30634
30695
|
return releaseRefs.filter((ref) => include.some((entry) => typeof entry === "string" && refPatternCovers(entry, ref)));
|
|
30635
30696
|
}
|
|
30636
30697
|
function unreachableReleaseContexts(input) {
|
|
@@ -30859,6 +30920,18 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
30859
30920
|
const explicitNoCi = registryCi === "none" || registryRequiredChecks === null || Array.isArray(registryRequiredChecks) && registryRequiredChecks.length === 0;
|
|
30860
30921
|
const deployableGated = repoClass === "deployable" && !explicitNoCi;
|
|
30861
30922
|
const info = await restJson2(deps, `repos/${repo}`, {});
|
|
30923
|
+
if (info.archived === true) {
|
|
30924
|
+
return {
|
|
30925
|
+
repo,
|
|
30926
|
+
class: repoClass,
|
|
30927
|
+
mergePolicy: "wait-for-checks",
|
|
30928
|
+
ok: false,
|
|
30929
|
+
checks: [],
|
|
30930
|
+
explicitNoCi,
|
|
30931
|
+
excludedReason: "GitHub archived repository (read-only)"
|
|
30932
|
+
};
|
|
30933
|
+
}
|
|
30934
|
+
let releaseReadFailure;
|
|
30862
30935
|
const baseBranch = repoClass === "content" ? "main" : "development";
|
|
30863
30936
|
checks.push({
|
|
30864
30937
|
ok: info.allow_auto_merge === true,
|
|
@@ -31061,14 +31134,29 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
31061
31134
|
const releaseBranches = releaseBranchRefsCovered(productRuleset.conditions, resolveReleaseTrack(meta, void 0, repo));
|
|
31062
31135
|
if (releaseBranches.length > 0) {
|
|
31063
31136
|
const emittedByBranch = {};
|
|
31137
|
+
const absent = [];
|
|
31138
|
+
const unreadable = [];
|
|
31064
31139
|
for (const ref of releaseBranches) {
|
|
31065
|
-
|
|
31140
|
+
const branch = ref.replace("refs/heads/", "");
|
|
31141
|
+
const present = await branchPresence(deps, repo, branch);
|
|
31142
|
+
if (present === false) absent.push(branch);
|
|
31143
|
+
else if (present === void 0) unreadable.push(branch);
|
|
31144
|
+
else {
|
|
31145
|
+
emittedByBranch[ref] = await resolveEmittedPrContexts(deps, repo, branch);
|
|
31146
|
+
if (emittedByBranch[ref] === void 0) unreadable.push(branch);
|
|
31147
|
+
}
|
|
31066
31148
|
}
|
|
31149
|
+
if (unreadable.length) releaseReadFailure = `release branch reachability unreadable: [${unreadable.join(", ")}] \u2014 no emitter conclusion`;
|
|
31150
|
+
const presenceNote = [
|
|
31151
|
+
absent.length ? `absent: [${absent.join(", ")}] \u2014 no emitter requirement audited` : "",
|
|
31152
|
+
unreadable.length ? `unreadable: [${unreadable.join(", ")}] \u2014 cannot audit emitters` : ""
|
|
31153
|
+
].filter(Boolean).join("; ");
|
|
31067
31154
|
const unreachable = unreachableReleaseContexts({ requiredContexts: liveContexts, releaseBranches, emittedByBranch, ignore: TRAIN_PR_ONLY_CONTEXTS });
|
|
31068
31155
|
checks.push({
|
|
31069
31156
|
ok: unreachable.length === 0,
|
|
31070
31157
|
label: RELEASE_BRANCH_REACHABLE_LABEL,
|
|
31071
|
-
|
|
31158
|
+
severity: unreadable.length ? "warn" : absent.length ? "info" : void 0,
|
|
31159
|
+
detail: unreachable.length === 0 ? presenceNote || void 0 : unreachable.map((u) => `${u.branch.replace("refs/heads/", "")} requires [${u.unreachable.join(", ")}] but its own workflows emit none of them`).join("; ") + " \u2014 a base-" + unreachable.map((u) => u.branch.replace("refs/heads/", "")).join("/") + " PR (e.g. a hotfix) can never satisfy the gate and stays permanently BLOCKED" + (presenceNote ? `; ${presenceNote}` : ""),
|
|
31072
31160
|
remediation: unreachable.length === 0 ? void 0 : `Remove the unreachable context(s) from those branches in the ${PRODUCT_RULESET_NAME} ruleset conditions and ${PRODUCT_RULESET_REF}, or add the emitting workflow to the branch`
|
|
31073
31161
|
});
|
|
31074
31162
|
}
|
|
@@ -31124,7 +31212,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
31124
31212
|
const body = await fetchFileContent(deps, repo, baseBranch, path2);
|
|
31125
31213
|
if (body == null) continue;
|
|
31126
31214
|
if (isGateWorkflowPath(path2)) gateBodies.push({ path: path2, body });
|
|
31127
|
-
if (!/^\s*concurrency:/m.test(body) || !/^\s*cancel-in-progress:[ \t]*(?:true|\$\{\{\s*github\.event_name\s
|
|
31215
|
+
if (!/^\s*concurrency:/m.test(body) || !/^\s*cancel-in-progress:[ \t]*(?:true|\$\{\{\s*github\.event_name\s*(?:==\s*(['"])pull_request\1|!=\s*(['"])merge_group\2)\s*\}\})[ \t]*(?:#.*)?$/m.test(body)) {
|
|
31128
31216
|
if (NO_CANCEL_WAIVER.test(body)) waived.push(path2);
|
|
31129
31217
|
else missing.push(path2);
|
|
31130
31218
|
}
|
|
@@ -31183,34 +31271,45 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
31183
31271
|
if (failure) {
|
|
31184
31272
|
return { repo, class: repoClass, mergePolicy: policy, ok: false, checks: [], explicitNoCi, unauditedReason: failure };
|
|
31185
31273
|
}
|
|
31186
|
-
const ok = checks.every((c) => c.ok);
|
|
31187
|
-
return {
|
|
31274
|
+
const ok = !releaseReadFailure && checks.every((c) => c.ok);
|
|
31275
|
+
return {
|
|
31276
|
+
repo,
|
|
31277
|
+
class: repoClass,
|
|
31278
|
+
mergePolicy: policy,
|
|
31279
|
+
ok,
|
|
31280
|
+
checks,
|
|
31281
|
+
explicitNoCi,
|
|
31282
|
+
...releaseReadFailure ? { unauditedReason: releaseReadFailure } : {}
|
|
31283
|
+
};
|
|
31188
31284
|
}
|
|
31189
31285
|
var REGISTRY_UNREADABLE_DETAIL = `the registry roster read returned nothing \u2014 the fleet was NOT audited. 0 repos read from the registry; the number left unaudited is unknown because the roster is what names them. This is "cannot audit the fleet", never "the fleet is green". Under GitHub Actions the CLI needs a CI Hub session (MMI_HUB_TOKEN, minted from the job's OIDC claim via POST /auth/session-ci) \u2014 an App installation token has no /user identity and can never pass /auth/session (#3215).`;
|
|
31190
31286
|
async function auditOrgCi(deps, repoFilter) {
|
|
31191
31287
|
if (repoFilter) {
|
|
31192
31288
|
const report = await auditRepoCi(repoFilter, deps);
|
|
31193
|
-
return { ok: report.ok, scope: "single-repo", repos: [report] };
|
|
31289
|
+
return { ok: report.ok, scope: "single-repo", repos: [report], excludedCount: report.excludedReason ? 1 : 0 };
|
|
31194
31290
|
}
|
|
31195
31291
|
const projects = await deps.listProjects();
|
|
31196
31292
|
if (!projects || projects.length === 0) {
|
|
31197
|
-
return { ok: false, scope: "registry-unreadable", repos: [], scopeDetail: REGISTRY_UNREADABLE_DETAIL };
|
|
31293
|
+
return { ok: false, scope: "registry-unreadable", repos: [], excludedCount: 0, scopeDetail: REGISTRY_UNREADABLE_DETAIL };
|
|
31198
31294
|
}
|
|
31199
31295
|
const targets = collectRegistryRepos(projects);
|
|
31200
31296
|
const repos = [];
|
|
31201
31297
|
for (const repo of targets) {
|
|
31202
31298
|
repos.push(await auditRepoCi(repo, deps));
|
|
31203
31299
|
}
|
|
31204
|
-
|
|
31300
|
+
const audited = repos.filter((r) => !r.excludedReason);
|
|
31301
|
+
return { ok: audited.length > 0 && audited.every((r) => r.ok), scope: "fleet", repos, excludedCount: repos.length - audited.length };
|
|
31205
31302
|
}
|
|
31206
31303
|
function renderCiAuditScopeLine(report) {
|
|
31207
31304
|
if (report.scope === "registry-unreadable") {
|
|
31208
31305
|
return `scope: REGISTRY UNREADABLE \u2014 repos audited: 0 \u2014 ${report.scopeDetail ?? REGISTRY_UNREADABLE_DETAIL}`;
|
|
31209
31306
|
}
|
|
31307
|
+
const excluded = report.repos.filter((r) => r.excludedReason).length;
|
|
31308
|
+
const count = `repos audited: ${report.repos.length - excluded}${excluded ? ` \u2014 repos excluded: ${excluded}` : ""}`;
|
|
31210
31309
|
if (report.scope === "single-repo") {
|
|
31211
|
-
return `scope: single-repo (explicit --repo) \u2014
|
|
31310
|
+
return `scope: single-repo (explicit --repo) \u2014 ${count} \u2014 this is NOT a fleet verdict`;
|
|
31212
31311
|
}
|
|
31213
|
-
return `scope: fleet (registry roster) \u2014
|
|
31312
|
+
return `scope: fleet (registry roster) \u2014 ${count}`;
|
|
31214
31313
|
}
|
|
31215
31314
|
function renderCiAuditMarkdown(report) {
|
|
31216
31315
|
const lines2 = [
|
|
@@ -31218,16 +31317,16 @@ function renderCiAuditMarkdown(report) {
|
|
|
31218
31317
|
"",
|
|
31219
31318
|
renderCiAuditScopeLine(report),
|
|
31220
31319
|
"",
|
|
31221
|
-
`Fleet: ${report.ok ? "OK" : "GAPS"} (${report.repos.filter((r) => r.ok).length}/${report.repos.length} repos ready)`,
|
|
31320
|
+
`Fleet: ${report.ok ? "OK" : "GAPS"} (${report.repos.filter((r) => r.ok && !r.excludedReason).length}/${report.repos.filter((r) => !r.excludedReason).length} repos ready)`,
|
|
31222
31321
|
"",
|
|
31223
31322
|
"| Repo | Class | Policy | OK | Top gap |",
|
|
31224
31323
|
"|------|-------|--------|----|---------|"
|
|
31225
31324
|
];
|
|
31226
31325
|
for (const r of report.repos) {
|
|
31227
|
-
const gap = r.unauditedReason ? `CANNOT AUDIT \u2014 ${r.unauditedReason}` : r.checks.find((c) => !c.ok)?.label ?? "\u2014";
|
|
31228
|
-
lines2.push(`| ${r.repo} | ${r.class} | ${r.mergePolicy} | ${r.ok ? "yes" : "no"} | ${gap} |`);
|
|
31326
|
+
const gap = r.excludedReason ? `EXCLUDED \u2014 ${r.excludedReason}` : r.unauditedReason ? `CANNOT AUDIT \u2014 ${r.unauditedReason}` : r.checks.find((c) => !c.ok)?.label ?? "\u2014";
|
|
31327
|
+
lines2.push(`| ${r.repo} | ${r.class} | ${r.excludedReason ? "\u2014" : r.mergePolicy} | ${r.excludedReason ? "excluded" : r.ok ? "yes" : "no"} | ${gap} |`);
|
|
31229
31328
|
}
|
|
31230
|
-
const advisories = report.repos.flatMap((r) => r.checks.filter((c) => c.severity
|
|
31329
|
+
const advisories = report.repos.flatMap((r) => r.checks.filter((c) => c.severity).map((c) => `- ${c.severity === "warn" ? "WARN" : "INFO"} ${r.repo}: ${c.label} \u2014 ${c.detail ?? ""}`));
|
|
31231
31330
|
if (advisories.length) lines2.push("", ...advisories);
|
|
31232
31331
|
return lines2.join("\n");
|
|
31233
31332
|
}
|
|
@@ -31237,12 +31336,15 @@ function renderCiAuditText(report) {
|
|
|
31237
31336
|
renderCiAuditScopeLine(report)
|
|
31238
31337
|
];
|
|
31239
31338
|
for (const r of report.repos) {
|
|
31240
|
-
const verdict = r.unauditedReason ? "CANNOT AUDIT" : r.ok ? "OK" : "GAP";
|
|
31339
|
+
const verdict = r.excludedReason ? "EXCLUDED" : r.unauditedReason ? "CANNOT AUDIT" : r.ok ? "OK" : "GAP";
|
|
31241
31340
|
lines2.push(`
|
|
31242
|
-
${r.repo} (${r.class}, policy=${r.mergePolicy}) ${verdict}`);
|
|
31341
|
+
${r.repo} (${r.class}, policy=${r.excludedReason ? "not audited" : r.mergePolicy}) ${verdict}`);
|
|
31342
|
+
if (r.excludedReason) {
|
|
31343
|
+
lines2.push(` EXCLUDED \u2014 ${r.excludedReason}`);
|
|
31344
|
+
continue;
|
|
31345
|
+
}
|
|
31243
31346
|
if (r.unauditedReason) {
|
|
31244
31347
|
lines2.push(` UNAUDITED \u2014 ${r.unauditedReason}`);
|
|
31245
|
-
continue;
|
|
31246
31348
|
}
|
|
31247
31349
|
for (const c of r.checks) {
|
|
31248
31350
|
lines2.push(` ${!c.ok ? "FAIL" : c.severity === "warn" ? "WARN" : c.severity === "info" ? "INFO" : "OK"} ${c.label}${c.detail ? ` \u2014 ${c.detail}` : ""}`);
|
|
@@ -31261,6 +31363,10 @@ async function applyCiReconcileMergeSettingsFromReport(repo, deps, report) {
|
|
|
31261
31363
|
errors.push(unauditedReconcileError(report.unauditedReason));
|
|
31262
31364
|
return { repo, applied, skipped, errors };
|
|
31263
31365
|
}
|
|
31366
|
+
if (report.excludedReason) {
|
|
31367
|
+
skipped.push(`excluded \u2014 ${report.excludedReason}; no reconcile change made`);
|
|
31368
|
+
return { repo, applied, skipped, errors };
|
|
31369
|
+
}
|
|
31264
31370
|
const mergeChecks = report.checks.filter((c) => c.label.startsWith("allow_") || c.label.startsWith("delete_branch") || c.label.startsWith("has_wiki"));
|
|
31265
31371
|
const needsPatch = mergeChecks.some((c) => !c.ok);
|
|
31266
31372
|
if (!needsPatch) {
|
|
@@ -31564,6 +31670,9 @@ async function applyCiReconcileRepo(repo, deps, options = {}) {
|
|
|
31564
31670
|
return { repo, applied: [], skipped: [], errors: ["product-ruleset requires registry META requiredChecks"] };
|
|
31565
31671
|
}
|
|
31566
31672
|
const report = await auditRepoCi(repo, deps);
|
|
31673
|
+
if (report.excludedReason) {
|
|
31674
|
+
return { repo, applied: [], skipped: [`excluded \u2014 ${report.excludedReason}; no reconcile change made`], errors: [] };
|
|
31675
|
+
}
|
|
31567
31676
|
if (report.unauditedReason) {
|
|
31568
31677
|
return { repo, applied: [], skipped: [], errors: [unauditedReconcileError(report.unauditedReason)] };
|
|
31569
31678
|
}
|
|
@@ -45976,7 +46085,7 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
45976
46085
|
return {
|
|
45977
46086
|
ok: false,
|
|
45978
46087
|
detail: `${DOCS_INDEX_PATH} is stale at the commit this PR would open from \u2014 it no longer matches the docs/ tree there (checked ${ref} over ${relDocs.length} indexed doc${relDocs.length === 1 ? "" : "s"})`,
|
|
45979
|
-
fix: "run `mmi-cli oracle docs index --write`, commit docs/index.md, and push again before retrying `pr create`"
|
|
46088
|
+
fix: "run `mmi-cli oracle docs index --write`, commit docs/index.md, and push again before retrying `pr create`; freshness is checked at the pushed commit regardless of merge-queue state, so regenerate and commit docs/index.md BEFORE `pr create`, never defer it to after merge"
|
|
45980
46089
|
};
|
|
45981
46090
|
}
|
|
45982
46091
|
|
|
@@ -46066,6 +46175,10 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
|
|
|
46066
46175
|
if (state?.toLowerCase() === "closed") continue;
|
|
46067
46176
|
const contest = await checkContest(client, { repository: repo, number }, actor, viewerLogin, deps.appClient);
|
|
46068
46177
|
if (!contest.contested) continue;
|
|
46178
|
+
if (isSameBranchClaimResume(contest, repo, { headBranch: deps.head, headSha: deps.headSha })) {
|
|
46179
|
+
await client.rest("POST", `repos/${repo}/issues/${number}/comments`, { body: { body: formatClaimMarker(actor) } }).catch(() => void 0);
|
|
46180
|
+
continue;
|
|
46181
|
+
}
|
|
46069
46182
|
const ref = `${repo}#${number}`;
|
|
46070
46183
|
const commentsPath = `repos/${repo}/issues/${number}/comments`;
|
|
46071
46184
|
const now = deps.now ?? Date.now;
|
|
@@ -48588,9 +48701,14 @@ ${list}`);
|
|
|
48588
48701
|
if (docsCheck && !docsCheck.ok) {
|
|
48589
48702
|
return fail(`pr create: ${docsCheck.detail} \u2014 ${docsCheck.fix}`);
|
|
48590
48703
|
}
|
|
48591
|
-
const claimRefusal = await prCreateClaimRefusal(body, o.repo);
|
|
48592
|
-
if (claimRefusal) return fail(claimRefusal);
|
|
48593
48704
|
const createHead = o.head ?? await gitOut(["symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => "");
|
|
48705
|
+
const shaRef = o.head ? `refs/heads/${o.head.replace(/^[^/:]+:/, "")}` : "HEAD";
|
|
48706
|
+
const createHeadSha = await gitOut(["rev-parse", "--verify", "--quiet", shaRef]).catch(() => "");
|
|
48707
|
+
const claimRefusal = await prCreateClaimRefusal(body, o.repo, {
|
|
48708
|
+
head: createHead || void 0,
|
|
48709
|
+
headSha: createHeadSha || void 0
|
|
48710
|
+
});
|
|
48711
|
+
if (claimRefusal) return fail(claimRefusal);
|
|
48594
48712
|
const mergedHeadWarning = await prCreateMergedHeadWarning({ head: createHead || void 0, repo: o.repo });
|
|
48595
48713
|
if (mergedHeadWarning) process.stderr.write(`${mergedHeadWarning}
|
|
48596
48714
|
`);
|
|
@@ -49743,7 +49861,12 @@ function trainApplyDeps() {
|
|
|
49743
49861
|
runSelf: async (args) => (await execFileP(process.execPath, [process.argv[1], ...canonicalArgvFor(args)], { timeout: 3e4 })).stdout,
|
|
49744
49862
|
trainAuthority: async (repo) => {
|
|
49745
49863
|
const verdict = await fetchTrainAuthority(repo, registryClientDeps(await loadConfig()));
|
|
49746
|
-
return verdict.ok ? {
|
|
49864
|
+
return verdict.ok ? {
|
|
49865
|
+
ok: true,
|
|
49866
|
+
role: verdict.authority.role,
|
|
49867
|
+
train: verdict.authority.train,
|
|
49868
|
+
...verdict.authority.authenticatedDelegation === true ? { authenticatedDelegation: true, delegatedActor: verdict.authority.delegatedActor } : {}
|
|
49869
|
+
} : verdict;
|
|
49747
49870
|
},
|
|
49748
49871
|
// Hub-App-authority dispatch of the central tenant deploy (#953) — the Hub fires the
|
|
49749
49872
|
// workflow_dispatch with its App token, so the caller needs no MMI-Hub Actions write.
|
|
@@ -54737,9 +54860,13 @@ function parseBrokerAssignee(raw) {
|
|
|
54737
54860
|
var SINGLE_SHOT_TIMEOUT_MS = 6e4;
|
|
54738
54861
|
var BROKER_MAX_BUFFER = 64 * 1024 * 1024;
|
|
54739
54862
|
var PR_QUEUE_QUERY = "query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){mergeQueueEntry{state position enqueuedAt estimatedTimeToMerge headCommit{oid}}}}}";
|
|
54863
|
+
function missingBrokerPrRepoMessage(subcommand, number) {
|
|
54864
|
+
const example = subcommand === "list" ? "mmi-cli devops read pr list --repo <owner/repo> --state open --json number,title,headRefName" : subcommand === "diff" ? `mmi-cli devops read pr diff ${number ?? "<number>"} --repo <owner/repo> --name-only` : `mmi-cli devops read pr queue ${number ?? "<number>"} --repo <owner/repo> --json`;
|
|
54865
|
+
return `read pr ${subcommand}: required option '--repo <owner/repo>' not specified \u2014 broker PR ${subcommand} reads do not infer the repo from a worktree's git origin; pass --repo <owner/repo> explicitly (e.g. \`${example}\`)`;
|
|
54866
|
+
}
|
|
54740
54867
|
function buildPrQueueSpec(number, o) {
|
|
54741
54868
|
const repo = parseBrokerRepo(o.repo, "--repo");
|
|
54742
|
-
if (!repo) throw new Error("
|
|
54869
|
+
if (!repo) throw new Error(missingBrokerPrRepoMessage("queue", number));
|
|
54743
54870
|
const [owner, name] = repo.split("/");
|
|
54744
54871
|
return { program: "gh", args: ["api", "graphql", "-f", `query=${PR_QUEUE_QUERY}`, "-f", `owner=${owner}`, "-f", `name=${name}`, "-F", `number=${parseBrokerId(number, "PR number")}`], timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
|
|
54745
54872
|
}
|
|
@@ -54793,12 +54920,19 @@ function parseBrokerCreated(v) {
|
|
|
54793
54920
|
if (!CREATED_RE.test(v)) throw new Error(`--created must be a GitHub created filter like ">=2026-10-11T00:30:00Z" (got ${JSON.stringify(v)})`);
|
|
54794
54921
|
return v;
|
|
54795
54922
|
}
|
|
54923
|
+
function parseBrokerCommit(v) {
|
|
54924
|
+
if (v === void 0) return void 0;
|
|
54925
|
+
if (!/^[0-9a-f]{40}$/i.test(v)) throw new Error(`read: --commit must be a full 40-character commit SHA (received '${v}')`);
|
|
54926
|
+
return v;
|
|
54927
|
+
}
|
|
54796
54928
|
function buildRunListSpec(o) {
|
|
54797
54929
|
const args = withJson(withRepo(["run", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
|
|
54798
54930
|
const workflow = parseBrokerWorkflow(o.workflow);
|
|
54799
54931
|
if (workflow) args.push("--workflow", workflow);
|
|
54800
54932
|
const branch = parseBrokerBranch(o.branch);
|
|
54801
54933
|
if (branch) args.push("--branch", branch);
|
|
54934
|
+
const commit = parseBrokerCommit(o.commit);
|
|
54935
|
+
if (commit) args.push("--commit", commit);
|
|
54802
54936
|
const status = parseBrokerRunStatus(o.status);
|
|
54803
54937
|
if (status) args.push("--status", status);
|
|
54804
54938
|
const created = parseBrokerCreated(o.created);
|
|
@@ -54895,7 +55029,7 @@ async function runBrokerGh(run, spec, write = (chunk) => process.stdout.write(ch
|
|
|
54895
55029
|
return 0;
|
|
54896
55030
|
} catch (e) {
|
|
54897
55031
|
const err = e;
|
|
54898
|
-
if (typeof err.stdout === "string" && err.stdout) write(redactPrivateCredential(err.stdout, env));
|
|
55032
|
+
if (typeof err.stdout === "string" && err.stdout) process.stderr.write(redactPrivateCredential(err.stdout, env));
|
|
54899
55033
|
const code = ghExitCodeOf(e);
|
|
54900
55034
|
if (code !== null) {
|
|
54901
55035
|
if (typeof err.stderr === "string" && err.stderr) process.stderr.write(redactPrivateCredential(err.stderr, env));
|
|
@@ -55002,7 +55136,7 @@ function registerAppReadBrokerCommands(program3, resolveRepo2, overrides) {
|
|
|
55002
55136
|
}
|
|
55003
55137
|
await dispatchGh(spec, "read run view");
|
|
55004
55138
|
});
|
|
55005
|
-
run.command("list").description("list workflow runs (workflow/branch/status/created/limit/json; formatters refused)").option("--repo <owner/repo>", "target repo (defaults to the current repo)", repoOpt).option("--json [fields]", "machine-readable output (comma-separated gh fields)", jsonOpt).option("--workflow <name>", "filter by workflow name or file", workflowOpt).option("--branch <name>", "filter by branch", branchOpt).option("--status <status>", "filter by run status", runStatusOpt).option("--created <filter>", 'only runs created in this window, e.g. ">=2026-10-11T00:30:00Z" (#8096)', (v) => parseBrokerCreated(v)).option("--limit <n>", "maximum runs to fetch (1-100)", intOpt("--limit", 1, 100)).action(async (o) => {
|
|
55139
|
+
run.command("list").description("list workflow runs (workflow/branch/commit/status/created/limit/json; formatters refused)").option("--repo <owner/repo>", "target repo (defaults to the current repo)", repoOpt).option("--json [fields]", "machine-readable output (comma-separated gh fields)", jsonOpt).option("--workflow <name>", "filter by workflow name or file", workflowOpt).option("--branch <name>", "filter by branch", branchOpt).option("--commit <sha>", "filter by the full 40-character head commit SHA (#8937)", (v) => parseBrokerCommit(v)).option("--status <status>", "filter by run status", runStatusOpt).option("--created <filter>", 'only runs created in this window, e.g. ">=2026-10-11T00:30:00Z" (#8096)', (v) => parseBrokerCreated(v)).option("--limit <n>", "maximum runs to fetch (1-100)", intOpt("--limit", 1, 100)).action(async (o) => {
|
|
55006
55140
|
let spec;
|
|
55007
55141
|
try {
|
|
55008
55142
|
spec = buildRunListSpec(o);
|
|
@@ -55021,13 +55155,16 @@ function registerAppReadBrokerCommands(program3, resolveRepo2, overrides) {
|
|
|
55021
55155
|
await dispatchGh(spec, "read run watch");
|
|
55022
55156
|
});
|
|
55023
55157
|
const pr = read.command("pr").description("pull-request reads \u2014 view/list/diff ride gh; queue uses fixed GraphQL; checks is REST-backed (deterministic closed-custody adapter; native was EMPTY only on the historical #3035 fixture)");
|
|
55024
|
-
pr.command("list").description("list viewer-free PRs (author/viewer filters stay personal)").
|
|
55158
|
+
pr.command("list").description("list viewer-free PRs (author/viewer filters stay personal)").option("--repo <owner/repo>", "target repo (required \u2014 no git-origin default)", repoOpt).option("--json [fields]", "native gh JSON fields", jsonOpt).addOption(new Option("--state <state>", "open|closed|merged|all").choices(["open", "closed", "merged", "all"])).option("--base <branch>", "base branch", branchOpt).option("--head <branch>", "head branch", branchOpt).option("--label <label>", "filter by label", labelOpt).option("--search <query>", "viewer-free search", (v) => parseBrokerPrSearch(v)).option("--assignee <login>", "explicit assignee (viewer refused)", assigneeOpt).option("--limit <n>", "maximum PRs (1-100)", intOpt("--limit", 1, 100)).action(async (o) => {
|
|
55159
|
+
if (!o.repo) return fail(missingBrokerPrRepoMessage("list"));
|
|
55025
55160
|
await dispatchGh(buildPrListSpec(o), "read pr list");
|
|
55026
55161
|
});
|
|
55027
|
-
pr.command("diff").description("read a bounded PR diff on the App pool").
|
|
55162
|
+
pr.command("diff").description("read a bounded PR diff on the App pool").option("--repo <owner/repo>", "target repo (required \u2014 no git-origin default)", repoOpt).option("--name-only", "only changed file names").argument("<number>", "PR number", idOpt("PR number")).action(async (number, o) => {
|
|
55163
|
+
if (!o.repo) return fail(missingBrokerPrRepoMessage("diff", number));
|
|
55028
55164
|
await dispatchGh(buildPrDiffSpec(number, o), "read pr diff");
|
|
55029
55165
|
});
|
|
55030
|
-
pr.command("queue").description("read one PR mergeQueueEntry (fixed GraphQL query, no permission widening)").
|
|
55166
|
+
pr.command("queue").description("read one PR mergeQueueEntry (fixed GraphQL query, no permission widening)").option("--repo <owner/repo>", "target repo (required \u2014 no git-origin default)", repoOpt).option("--json", "machine-readable mergeQueueEntry (null when not queued)").argument("<number>", "PR number", idOpt("PR number")).action(async (number, o) => {
|
|
55167
|
+
if (!o.repo) return fail(missingBrokerPrRepoMessage("queue", number));
|
|
55031
55168
|
const spec = buildPrQueueSpec(number, o);
|
|
55032
55169
|
requireBrokerApp("read pr queue");
|
|
55033
55170
|
try {
|
|
@@ -55160,9 +55297,13 @@ var RELEASE_LATEST_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/latest$`);
|
|
|
55160
55297
|
var RELEASE_TAG_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/tags/(${NAME})$`);
|
|
55161
55298
|
var RELEASE_ASSET_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/assets/\\d+$`);
|
|
55162
55299
|
var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs[?per_page=1-100&page=<n>&status=&branch=&event=&head_sha=<40-hex>&created=<date-range>&actor=, each key at most once], repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs, repos/{owner}/{repo}/actions/runs/{id}/attempts/{n}/jobs, repos/{owner}/{repo}/actions/runs/{id}/artifacts, repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>), repos/{owner}/{repo}/releases[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/releases/latest, repos/{owner}/{repo}/releases/tags/{tag}, repos/{owner}/{repo}/releases/assets/{id} (needs --out <file>)";
|
|
55300
|
+
var API_GET_RECOVERY = " No other GET read exists \u2014 do not retry path variants; use a dedicated mmi-cli subcommand or ask a human token holder. This refusal exits 1 on stderr with nothing on stdout: check the exit code and never merge stderr into stdout (2>&1) before JSON-parsing.";
|
|
55163
55301
|
function parseApiGetPath(rawPath) {
|
|
55164
55302
|
const path2 = rawPath.trim();
|
|
55165
|
-
const refuse = () =>
|
|
55303
|
+
const refuse = () => {
|
|
55304
|
+
const hint = /^[A-Za-z]:[/\\]/.test(path2) ? " Git Bash/MSYS may have rewritten a leading-slash argument; retry with MSYS_NO_PATHCONV=1 and the slash-free repos/... path. The endpoint must still be allowlisted." : "";
|
|
55305
|
+
return new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.${API_GET_RECOVERY}${hint}`);
|
|
55306
|
+
};
|
|
55166
55307
|
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
55167
55308
|
const q = path2.indexOf("?");
|
|
55168
55309
|
const base = q === -1 ? path2 : path2.slice(0, q);
|
|
@@ -55183,7 +55324,7 @@ function parseApiGetPath(rawPath) {
|
|
|
55183
55324
|
if (CONTENTS_BASE_RE.test(base) && (query === "" || CONTENTS_QUERY_RE.test(query))) {
|
|
55184
55325
|
return { kind: "contents", path: path2, paginate: false };
|
|
55185
55326
|
}
|
|
55186
|
-
throw
|
|
55327
|
+
throw refuse();
|
|
55187
55328
|
}
|
|
55188
55329
|
var ZIP_TIMEOUT_MS = 12e4;
|
|
55189
55330
|
var ZIP_MAX_BUFFER = 512 * 1024 * 1024;
|
|
@@ -59180,7 +59321,7 @@ ci.command("reconcile").description("audit + optionally apply merge settings and
|
|
|
59180
59321
|
}
|
|
59181
59322
|
const deps = ciAuditDeps2();
|
|
59182
59323
|
const audit = await auditOrgCi(deps, o.repo);
|
|
59183
|
-
const applyResults = o.apply ? await Promise.all(audit.repos.map((r) => applyCiReconcileRepo(r.repo, deps))) : o.parkRuleset ? await Promise.all(audit.repos.map((r) => parkProductRuleset(r.repo, deps))) : [];
|
|
59324
|
+
const applyResults = o.apply ? await Promise.all(audit.repos.filter((r) => !r.excludedReason).map((r) => applyCiReconcileRepo(r.repo, deps))) : o.parkRuleset ? await Promise.all(audit.repos.filter((r) => !r.excludedReason).map((r) => parkProductRuleset(r.repo, deps))) : [];
|
|
59184
59325
|
const payload = { audit, apply: applyResults };
|
|
59185
59326
|
if (o.json) console.log(JSON.stringify(payload, null, 2));
|
|
59186
59327
|
else {
|
|
@@ -59511,6 +59652,7 @@ function printHouseRootHelp(house) {
|
|
|
59511
59652
|
consoleIo.log(lines2.join("\n"));
|
|
59512
59653
|
}
|
|
59513
59654
|
resolveVerbAliasShim(process.argv);
|
|
59655
|
+
normalizeWorkboardClaimArgv(process.argv);
|
|
59514
59656
|
resolveHouseShim(process.argv);
|
|
59515
59657
|
normalizeBoardClaimIssueArgv(process.argv);
|
|
59516
59658
|
program2.parseAsync(process.argv).then(() => finishCliRun()).catch((e) => failGraceful(e.message));
|