@mutmutco/cli 4.3.22 → 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.
Files changed (2) hide show
  1. package/dist/main.cjs +76 -29
  2. 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.22",
15523
- tag: "v4.3.22",
15524
- commit: "ab0293be4361",
15525
- npm: "@mutmutco/cli@4.3.22"
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.22"
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.22 and redeploy the Hub Lambda from tag v4.3.22 (ab0293be4361); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15549
- v3Target: "v4.3.22 (@mutmutco/cli@4.3.22, tag commit ab0293be4361 \u2014 last known-good release carrying the repo-index v4-only contract)"
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 assertActionsJobsCanStart(deps, targetRepo2) {
19012
- const scanned = await scanRepoForBillingBlock(deps, targetRepo2);
19013
- if (scanned) throw actionsBillingRefusal(scanned);
19014
- if (targetRepo2.toLowerCase() !== CANARY_REPO.toLowerCase()) {
19015
- const hubScan = await scanRepoForBillingBlock(deps, CANARY_REPO);
19016
- if (hubScan) throw actionsBillingRefusal(hubScan);
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
- throw actionsBillingRefusal(
19031
- `could not dispatch ${CANARY_WORKFLOW} on ${CANARY_REPO}: ${e instanceof Error ? e.message : String(e)}. A hosted publish job may not start (the v1.54.2 class).`
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
- ...!hints.hasRcBranch && track === "full" ? ["rc"] : []
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"]);
@@ -36537,8 +36569,9 @@ var WAIVABLE_KINDS = [
36537
36569
  var TEST_WORK_KIND = "unrequested-test-file";
36538
36570
  var TEST_FILE_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
36539
36571
  var PY_TEST_FILE_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
36572
+ var GO_TEST_FILE_RE = /_test\.go$/;
36540
36573
  function isTestPath(path2) {
36541
- return typeof path2 === "string" && (TEST_FILE_RE.test(path2) || PY_TEST_FILE_RE.test(path2));
36574
+ return typeof path2 === "string" && (TEST_FILE_RE.test(path2) || PY_TEST_FILE_RE.test(path2) || GO_TEST_FILE_RE.test(path2));
36542
36575
  }
36543
36576
  function editsExistingTest(paths, addedPaths = []) {
36544
36577
  const added = new Set(Array.isArray(addedPaths) ? addedPaths : []);
@@ -36693,6 +36726,7 @@ var HOTFIX_POLICY_REF = "origin/development";
36693
36726
  var HOTFIX_DIFF_BASE = "origin/main";
36694
36727
  var TEST_RE = TEST_FILE_RE;
36695
36728
  var PY_TEST_RE = PY_TEST_FILE_RE;
36729
+ var GO_TEST_RE = GO_TEST_FILE_RE;
36696
36730
  function translate(glob) {
36697
36731
  let out = "";
36698
36732
  for (let i = 0; i < glob.length; i++) {
@@ -36728,7 +36762,7 @@ function globToRegExp2(glob) {
36728
36762
  return new RegExp(`^${translate(glob)}$`);
36729
36763
  }
36730
36764
  function isTestPath2(path2) {
36731
- return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
36765
+ return TEST_RE.test(path2) || PY_TEST_RE.test(path2) || GO_TEST_RE.test(path2);
36732
36766
  }
36733
36767
  var SUPPORTED_SOURCE = /\.[cm]?[jt]s$/;
36734
36768
  var MAX_ANALYZED_BYTES = 512 * 1024;
@@ -43132,10 +43166,13 @@ function readRepoVersion() {
43132
43166
  }
43133
43167
  function registerTrainCommands(program3, trainDeps) {
43134
43168
  const train = program3.command("train").description("release-train observability \u2014 track position, version lag, and the shared preflight-and-heal doctor (#2688, #6067)");
43135
- 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) => {
43136
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
+ }
43137
43174
  if (o.repo) {
43138
- 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");
43139
43176
  if (!guard.ok) return failGraceful(`train doctor: ${guard.message}`);
43140
43177
  }
43141
43178
  const root = (await execFileP("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
@@ -43151,7 +43188,7 @@ function registerTrainCommands(program3, trainDeps) {
43151
43188
  }
43152
43189
  }
43153
43190
  };
43154
- 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 });
43155
43192
  if (verdict.reexecCode !== void 0) return process.exit(verdict.reexecCode);
43156
43193
  console.log(o.json ? JSON.stringify(verdict, null, 2) : formatTrainDoctor(verdict));
43157
43194
  if (!verdict.ready) process.exitCode = 1;
@@ -43260,6 +43297,16 @@ function trainApplyDeps() {
43260
43297
  throw new Error(`tenant deploy dispatch failed: ${detail}`);
43261
43298
  }
43262
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
+ },
43263
43310
  // Hub-App-authority dispatch of the central tenant-control.yml (#1717) — the Hub fires the
43264
43311
  // workflow_dispatch with its App token. Never throws for an expected rejection: it returns the dispatch
43265
43312
  // outcome so runTenantControl can map a 5xx (transport-failed, retryable) vs a 4xx (rejected) vs ok.
@@ -43733,7 +43780,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
43733
43780
  let doctor;
43734
43781
  if (repo && (!o.repo || (await resolveRepo())?.toLowerCase() === repo.toLowerCase())) {
43735
43782
  try {
43736
- 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 });
43737
43784
  } catch (e) {
43738
43785
  consoleIo.err(`mmi-cli: train doctor did not complete: ${e.message}`);
43739
43786
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.22",
3
+ "version": "4.3.24",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",