@mutmutco/cli 4.3.23 → 4.3.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +72 -27
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -6862,6 +6862,9 @@ async function tenantReconcile(payload, deps) {
|
|
|
6862
6862
|
async function tenantDeploy(payload, deps) {
|
|
6863
6863
|
return postJson("/tenant-deploy", payload, deps, "POST", { noRetry: true, timeoutMs: TENANT_DEPLOY_TIMEOUT_MS });
|
|
6864
6864
|
}
|
|
6865
|
+
async function actionsCanary(payload, deps) {
|
|
6866
|
+
return postJson("/actions-canary", payload, deps, "POST", { noRetry: true });
|
|
6867
|
+
}
|
|
6865
6868
|
|
|
6866
6869
|
// src/config-discovery.ts
|
|
6867
6870
|
function stripMutableBoardConfig(cfg) {
|
|
@@ -15519,10 +15522,10 @@ var rollout_plan_default = {
|
|
|
15519
15522
|
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)."
|
|
15520
15523
|
},
|
|
15521
15524
|
baseline: {
|
|
15522
|
-
version: "4.3.
|
|
15523
|
-
tag: "v4.3.
|
|
15524
|
-
commit: "
|
|
15525
|
-
npm: "@mutmutco/cli@4.3.
|
|
15525
|
+
version: "4.3.24",
|
|
15526
|
+
tag: "v4.3.24",
|
|
15527
|
+
commit: "48a4cf4f0ad0",
|
|
15528
|
+
npm: "@mutmutco/cli@4.3.24"
|
|
15526
15529
|
},
|
|
15527
15530
|
exitCriterion: "fleet-n-of-n",
|
|
15528
15531
|
hubOnlyShortcut: "forbidden",
|
|
@@ -15539,14 +15542,14 @@ var rollout_plan_default = {
|
|
|
15539
15542
|
repo: "mutmutco/mmi-hub",
|
|
15540
15543
|
role: "canary",
|
|
15541
15544
|
schedule: "train",
|
|
15542
|
-
v3Target: "v4.3.
|
|
15545
|
+
v3Target: "v4.3.24"
|
|
15543
15546
|
}
|
|
15544
15547
|
],
|
|
15545
15548
|
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.",
|
|
15546
15549
|
rollback: {
|
|
15547
15550
|
independent: true,
|
|
15548
|
-
mechanism: "npm dist-tag latest -> 4.3.
|
|
15549
|
-
v3Target: "v4.3.
|
|
15551
|
+
mechanism: "npm dist-tag latest -> 4.3.24 and redeploy the Hub Lambda from tag v4.3.24 (48a4cf4f0ad0); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
15552
|
+
v3Target: "v4.3.24 (@mutmutco/cli@4.3.24, tag commit 48a4cf4f0ad0 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
15550
15553
|
}
|
|
15551
15554
|
},
|
|
15552
15555
|
{
|
|
@@ -18914,6 +18917,15 @@ function interpretActionsJobStart(input) {
|
|
|
18914
18917
|
}
|
|
18915
18918
|
return "pending";
|
|
18916
18919
|
}
|
|
18920
|
+
var CANARY_ACCESS_BLOCK_RE = /HTTP 404|HTTP 403|Not Found|Resource not accessible|must have admin rights|not authorized|no identity|401/i;
|
|
18921
|
+
function isCanaryAccessBlockText(text) {
|
|
18922
|
+
return !isActionsBillingBlockText(text) && CANARY_ACCESS_BLOCK_RE.test(text);
|
|
18923
|
+
}
|
|
18924
|
+
function actionsCanaryUndispatchedRefusal(detail) {
|
|
18925
|
+
return new Error(
|
|
18926
|
+
`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 docs/Guides/train-troubleshooting.md#actions-job-start-canary`
|
|
18927
|
+
);
|
|
18928
|
+
}
|
|
18917
18929
|
function actionsBillingRefusal(detail) {
|
|
18918
18930
|
return new Error(
|
|
18919
18931
|
`release refused: GitHub Actions cannot start a hosted job (billing/spending). ${detail} Fix Billing & plans / the org spending limit, then rerun. Do not mint a new tag. If a tag is already on origin, use \`mmi-cli devops release --retry-publish <run-id> --apply\` on that exact run \u2014 never recut.`
|
|
@@ -19008,14 +19020,16 @@ async function correlateCanaryRun(deps, nonce) {
|
|
|
19008
19020
|
`could not correlate ${CANARY_WORKFLOW} on ${CANARY_REPO} (nonce ${nonce}): ${lastError}`
|
|
19009
19021
|
);
|
|
19010
19022
|
}
|
|
19011
|
-
async function
|
|
19012
|
-
const
|
|
19013
|
-
if (
|
|
19014
|
-
|
|
19015
|
-
|
|
19016
|
-
|
|
19023
|
+
async function dispatchCanary(deps, targetRepo2, nonce) {
|
|
19024
|
+
const attempts = [];
|
|
19025
|
+
if (deps.dispatchActionsCanary) {
|
|
19026
|
+
try {
|
|
19027
|
+
await deps.dispatchActionsCanary({ repo: targetRepo2, nonce });
|
|
19028
|
+
return;
|
|
19029
|
+
} catch (e) {
|
|
19030
|
+
attempts.push(`Hub App dispatch: ${e instanceof Error ? e.message : String(e)}`);
|
|
19031
|
+
}
|
|
19017
19032
|
}
|
|
19018
|
-
const nonce = `5604-${(deps.now ?? Date.now)().toString(36)}`;
|
|
19019
19033
|
try {
|
|
19020
19034
|
await deps.run("gh", [
|
|
19021
19035
|
"workflow",
|
|
@@ -19026,11 +19040,27 @@ async function assertActionsJobsCanStart(deps, targetRepo2) {
|
|
|
19026
19040
|
"-f",
|
|
19027
19041
|
`nonce=${nonce}`
|
|
19028
19042
|
]);
|
|
19043
|
+
return;
|
|
19029
19044
|
} catch (e) {
|
|
19030
|
-
|
|
19031
|
-
|
|
19032
|
-
|
|
19045
|
+
attempts.push(`gh workflow run --repo ${CANARY_REPO}: ${e instanceof Error ? e.message : String(e)}`);
|
|
19046
|
+
}
|
|
19047
|
+
const detail = `could not dispatch ${CANARY_WORKFLOW} on ${CANARY_REPO} \u2014 ${attempts.join(" | ")}.`;
|
|
19048
|
+
if (attempts.some((a) => isActionsBillingBlockText(a))) {
|
|
19049
|
+
throw actionsBillingRefusal(`${detail} A hosted publish job may not start (the v1.54.2 class).`);
|
|
19033
19050
|
}
|
|
19051
|
+
throw actionsCanaryUndispatchedRefusal(
|
|
19052
|
+
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
|
|
19053
|
+
);
|
|
19054
|
+
}
|
|
19055
|
+
async function assertActionsJobsCanStart(deps, targetRepo2) {
|
|
19056
|
+
const scanned = await scanRepoForBillingBlock(deps, targetRepo2);
|
|
19057
|
+
if (scanned) throw actionsBillingRefusal(scanned);
|
|
19058
|
+
if (targetRepo2.toLowerCase() !== CANARY_REPO.toLowerCase()) {
|
|
19059
|
+
const hubScan = await scanRepoForBillingBlock(deps, CANARY_REPO);
|
|
19060
|
+
if (hubScan) throw actionsBillingRefusal(hubScan);
|
|
19061
|
+
}
|
|
19062
|
+
const nonce = `5604-${(deps.now ?? Date.now)().toString(36)}`;
|
|
19063
|
+
await dispatchCanary(deps, targetRepo2, nonce);
|
|
19034
19064
|
const runId = await correlateCanaryRun(deps, nonce);
|
|
19035
19065
|
const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
19036
19066
|
let last = "pending";
|
|
@@ -21277,10 +21307,10 @@ function message(e) {
|
|
|
21277
21307
|
function laneStage(lane) {
|
|
21278
21308
|
return lane === "rcand" ? "rc" : "main";
|
|
21279
21309
|
}
|
|
21280
|
-
function laneStartBranch(lane, track) {
|
|
21310
|
+
function laneStartBranch(lane, track, dev) {
|
|
21281
21311
|
if (lane === "hotfix") return void 0;
|
|
21282
21312
|
if (lane === "rcand") return "development";
|
|
21283
|
-
return track === "full" ? "rc" : "development";
|
|
21313
|
+
return track === "full" && !dev ? "rc" : "development";
|
|
21284
21314
|
}
|
|
21285
21315
|
function defaultLane(track, currentBranch2) {
|
|
21286
21316
|
if (track === "full") return currentBranch2 === "rc" ? "release" : "rcand";
|
|
@@ -21361,7 +21391,8 @@ async function runTrainDoctor(input) {
|
|
|
21361
21391
|
} catch {
|
|
21362
21392
|
currentBranch2 = "";
|
|
21363
21393
|
}
|
|
21364
|
-
const lane = input.lane ?? defaultLane(track, currentBranch2);
|
|
21394
|
+
const lane = input.lane ?? (input.dev === true ? "release" : defaultLane(track, currentBranch2));
|
|
21395
|
+
const dev = input.dev === true && lane === "release";
|
|
21365
21396
|
if (lane === "rcand" && track === "direct") {
|
|
21366
21397
|
add({ code: "lane-mismatch", severity: "blocker", source: "origin", title: `${repo} is direct-track (no rc) \u2014 rcand does not apply`, remedy: "run `mmi-cli devops release --apply` from development instead of rcand" });
|
|
21367
21398
|
} else if (track === "trunk") {
|
|
@@ -21449,7 +21480,7 @@ async function runTrainDoctor(input) {
|
|
|
21449
21480
|
if (restored.length) add({ code: "tree-churn", severity: "healed", source: "local", title: `restored content-identical churn: ${restored.join(", ")}`, remedy: "nothing \u2014 the paths carried no content change (mode/EOL churn)" });
|
|
21450
21481
|
if (left.length) add({ code: "tree-churn", severity: "blocker", source: "local", title: `content-identical churn blocks the clean-tree gate: ${left.join(", ")}`, remedy: `run \`mmi-cli devops train doctor --heal\` or \`git checkout -- ${left.join(" ")}\`` });
|
|
21451
21482
|
}
|
|
21452
|
-
const startBranch = laneStartBranch(lane, track);
|
|
21483
|
+
const startBranch = laneStartBranch(lane, track, dev);
|
|
21453
21484
|
if (startBranch && currentBranch2 !== startBranch) {
|
|
21454
21485
|
let linked = false;
|
|
21455
21486
|
try {
|
|
@@ -21468,7 +21499,8 @@ async function runTrainDoctor(input) {
|
|
|
21468
21499
|
const missing = [
|
|
21469
21500
|
...!hints.hasDevelopmentBranch && lane !== "hotfix" ? ["development"] : [],
|
|
21470
21501
|
...!hints.hasMainBranch ? ["main"] : [],
|
|
21471
|
-
|
|
21502
|
+
// #6318: a `--dev` release never reads, merges or tags rc — a missing rc branch cannot block it.
|
|
21503
|
+
...!hints.hasRcBranch && track === "full" && !dev ? ["rc"] : []
|
|
21472
21504
|
];
|
|
21473
21505
|
if (missing.length) add({ code: "bootstrap-gap", severity: "blocker", source: "origin", title: `train branch(es) missing on origin: ${missing.join(", ")}`, remedy: `bootstrap the repo train: \`mmi-cli devops bootstrap apply ${repo} --execute\` (from the MMI-Hub root), then rerun` });
|
|
21474
21506
|
}
|
|
@@ -23410,7 +23442,7 @@ async function resolveHotfixVersion(deps, env = process.env) {
|
|
|
23410
23442
|
}
|
|
23411
23443
|
async function runTrainApply(command, deps, options = {}) {
|
|
23412
23444
|
const watch = options.watch ?? false;
|
|
23413
|
-
await runTrainDoctor({ lane: command === "rcand" ? "rcand" : "release", heal: true, train: deps, refuse: true });
|
|
23445
|
+
await runTrainDoctor({ lane: command === "rcand" ? "rcand" : "release", dev: command === "release" && options.dev === true, heal: true, train: deps, refuse: true });
|
|
23414
23446
|
const ctx = await buildTrainApplyContext(deps);
|
|
23415
23447
|
await requireCleanTree(deps);
|
|
23416
23448
|
await runGitRemoteRead(deps, ["fetch", "origin"]);
|
|
@@ -43134,10 +43166,13 @@ function readRepoVersion() {
|
|
|
43134
43166
|
}
|
|
43135
43167
|
function registerTrainCommands(program3, trainDeps) {
|
|
43136
43168
|
const train = program3.command("train").description("release-train observability \u2014 track position, version lag, and the shared preflight-and-heal doctor (#2688, #6067)");
|
|
43137
|
-
train.command("doctor").description("one shared preflight-and-heal verdict for the release, rcand and hotfix lanes; read-only unless --heal (#6067)").addOption(new Option("--lane <lane>", "train lane to check (defaults from the release track: direct \u2192 release)").choices([...TRAIN_LANES])).option("--heal", "repair safe local reconstructible state (scratch gitignore, churn, stray local tags, ff-only branch lag, finished scratch branches, stale alignment ledger leg)").option("--repo <owner/repo>", "target repo (defaults to the current checkout)").option("--json", "machine-readable output").addHelpText("after", "\nExit codes:\n 0 ready \u2014 no blocker and origin verified\n 1 a blocker remains, or origin could not be verified (never green on an unread origin)\n\nEvery finding carries code, severity (blocker|warning|healed|info), source (local|origin), remedy and a\ndocs/Guides/train-troubleshooting.md#<code> anchor. `release --apply` and `rcand --apply` run this at step 0 with --heal.\n").action(async (o) => {
|
|
43169
|
+
train.command("doctor").description("one shared preflight-and-heal verdict for the release, rcand and hotfix lanes; read-only unless --heal (#6067)").addOption(new Option("--lane <lane>", "train lane to check (defaults from the release track: direct \u2192 release)").choices([...TRAIN_LANES])).option("--dev", "judge the `release --dev` lane (development -> main, skipping rc): the lane starts from development and is read on development's workflows (#6318)").option("--heal", "repair safe local reconstructible state (scratch gitignore, churn, stray local tags, ff-only branch lag, finished scratch branches, stale alignment ledger leg)").option("--repo <owner/repo>", "target repo (defaults to the current checkout)").option("--json", "machine-readable output").addHelpText("after", "\nExit codes:\n 0 ready \u2014 no blocker and origin verified\n 1 a blocker remains, or origin could not be verified (never green on an unread origin)\n\nEvery finding carries code, severity (blocker|warning|healed|info), source (local|origin), remedy and a\ndocs/Guides/train-troubleshooting.md#<code> anchor. `release --apply` and `rcand --apply` run this at step 0 with --heal.\n").action(async (o) => {
|
|
43138
43170
|
try {
|
|
43171
|
+
if (o.dev && o.lane && o.lane !== "release") {
|
|
43172
|
+
return failGraceful(`train doctor: --dev applies only to the release lane \u2014 it names the development -> main variant that skips rc, which the ${o.lane} lane does not have`);
|
|
43173
|
+
}
|
|
43139
43174
|
if (o.repo) {
|
|
43140
|
-
const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli devops train doctor${o.lane ? ` --lane ${o.lane}` : ""}${o.heal ? " --heal" : ""}${o.json ? " --json" : ""}`, "train doctor");
|
|
43175
|
+
const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli devops train doctor${o.lane ? ` --lane ${o.lane}` : ""}${o.dev ? " --dev" : ""}${o.heal ? " --heal" : ""}${o.json ? " --json" : ""}`, "train doctor");
|
|
43141
43176
|
if (!guard.ok) return failGraceful(`train doctor: ${guard.message}`);
|
|
43142
43177
|
}
|
|
43143
43178
|
const root = (await execFileP("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
@@ -43153,7 +43188,7 @@ function registerTrainCommands(program3, trainDeps) {
|
|
|
43153
43188
|
}
|
|
43154
43189
|
}
|
|
43155
43190
|
};
|
|
43156
|
-
const verdict = await runTrainDoctor({ lane: o.lane, heal: o.heal === true, train: deps, repo: o.repo, cwd: root, argv: INVOKED_ARGV });
|
|
43191
|
+
const verdict = await runTrainDoctor({ lane: o.lane, dev: o.dev === true, heal: o.heal === true, train: deps, repo: o.repo, cwd: root, argv: INVOKED_ARGV });
|
|
43157
43192
|
if (verdict.reexecCode !== void 0) return process.exit(verdict.reexecCode);
|
|
43158
43193
|
console.log(o.json ? JSON.stringify(verdict, null, 2) : formatTrainDoctor(verdict));
|
|
43159
43194
|
if (!verdict.ready) process.exitCode = 1;
|
|
@@ -43262,6 +43297,16 @@ function trainApplyDeps() {
|
|
|
43262
43297
|
throw new Error(`tenant deploy dispatch failed: ${detail}`);
|
|
43263
43298
|
}
|
|
43264
43299
|
},
|
|
43300
|
+
// Hub-App-authority dispatch of the #5604 hosted-job canary (#6319) — the Hub fires the
|
|
43301
|
+
// workflow_dispatch with its App token, so a train-authorized project-admin (pull-only on MMI-Hub)
|
|
43302
|
+
// runs the probe instead of being refused with a 404 misread as a billing block (#6320).
|
|
43303
|
+
dispatchActionsCanary: async ({ repo, nonce }) => {
|
|
43304
|
+
const res = await actionsCanary({ repo, nonce }, registryClientDeps(await loadConfig()));
|
|
43305
|
+
if (!res.ok) {
|
|
43306
|
+
const detail = res.body?.error ?? res.error ?? `HTTP ${res.status}`;
|
|
43307
|
+
throw new Error(`Hub /actions-canary refused: ${detail}`);
|
|
43308
|
+
}
|
|
43309
|
+
},
|
|
43265
43310
|
// Hub-App-authority dispatch of the central tenant-control.yml (#1717) — the Hub fires the
|
|
43266
43311
|
// workflow_dispatch with its App token. Never throws for an expected rejection: it returns the dispatch
|
|
43267
43312
|
// outcome so runTenantControl can map a 5xx (transport-failed, retryable) vs a 4xx (rejected) vs ok.
|
|
@@ -43735,7 +43780,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
43735
43780
|
let doctor;
|
|
43736
43781
|
if (repo && (!o.repo || (await resolveRepo())?.toLowerCase() === repo.toLowerCase())) {
|
|
43737
43782
|
try {
|
|
43738
|
-
doctor = await runTrainDoctor({ lane: commandName, heal: false, train: trainApplyDeps(), repo });
|
|
43783
|
+
doctor = await runTrainDoctor({ lane: commandName, dev: commandName === "release" && o.dev === true, heal: false, train: trainApplyDeps(), repo });
|
|
43739
43784
|
} catch (e) {
|
|
43740
43785
|
consoleIo.err(`mmi-cli: train doctor did not complete: ${e.message}`);
|
|
43741
43786
|
}
|
package/package.json
CHANGED