@mutmutco/cli 4.3.55 → 4.3.57

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 +85 -31
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -15877,10 +15877,10 @@ var rollout_plan_default = {
15877
15877
  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)."
15878
15878
  },
15879
15879
  baseline: {
15880
- version: "4.3.55",
15881
- tag: "v4.3.55",
15882
- commit: "7f7f602ac9bc",
15883
- npm: "@mutmutco/cli@4.3.55"
15880
+ version: "4.3.57",
15881
+ tag: "v4.3.57",
15882
+ commit: "b988569fdfe3",
15883
+ npm: "@mutmutco/cli@4.3.57"
15884
15884
  },
15885
15885
  exitCriterion: "fleet-n-of-n",
15886
15886
  hubOnlyShortcut: "forbidden",
@@ -15897,14 +15897,14 @@ var rollout_plan_default = {
15897
15897
  repo: "mutmutco/mmi-hub",
15898
15898
  role: "canary",
15899
15899
  schedule: "train",
15900
- v3Target: "v4.3.55"
15900
+ v3Target: "v4.3.57"
15901
15901
  }
15902
15902
  ],
15903
15903
  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.",
15904
15904
  rollback: {
15905
15905
  independent: true,
15906
- mechanism: "npm dist-tag latest -> 4.3.55 and redeploy the Hub Lambda from tag v4.3.55 (7f7f602ac9bc); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15907
- v3Target: "v4.3.55 (@mutmutco/cli@4.3.55, tag commit 7f7f602ac9bc \u2014 last known-good release carrying the repo-index v4-only contract)"
15906
+ mechanism: "npm dist-tag latest -> 4.3.57 and redeploy the Hub Lambda from tag v4.3.57 (b988569fdfe3); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15907
+ v3Target: "v4.3.57 (@mutmutco/cli@4.3.57, tag commit b988569fdfe3 \u2014 last known-good release carrying the repo-index v4-only contract)"
15908
15908
  }
15909
15909
  },
15910
15910
  {
@@ -18916,6 +18916,21 @@ function prodReleaseTargets(model) {
18916
18916
  if (model === "registry-publish") return [{ workflow: "publish.yml", event: "release" }];
18917
18917
  return [];
18918
18918
  }
18919
+ function trainObservesShipment(model) {
18920
+ return isCentralDispatchModel(model) || prodReleaseTargets(model).length > 0;
18921
+ }
18922
+ function shipLegsGreen(deploy, publish) {
18923
+ const green = (s) => s === "complete" || s === "skipped";
18924
+ return green(deploy?.state) && green(publish?.state);
18925
+ }
18926
+ async function announceShippedRelease(deps, args, model, legs, deferred) {
18927
+ if (!deps.announce) return void 0;
18928
+ if (trainObservesShipment(model) && !shipLegsGreen(legs.deploy, legs.publish)) {
18929
+ const failed = legs.deploy.state === "failed" || legs.publish.state === "failed";
18930
+ return `not announced: deploy/publish ${failed ? "FAILED" : "not proven green"} \u2014 ${deferred}`;
18931
+ }
18932
+ return (await deps.announce(args)).note;
18933
+ }
18919
18934
  async function listTargetRun(deps, repo, target, headSha) {
18920
18935
  let rows;
18921
18936
  try {
@@ -22836,6 +22851,9 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
22836
22851
  await recordPhase(ledger, deps, anchors, "alignment", phaseEntry({ state: "skipped", sha, note: "rcand aligns no branch \u2014 alignment rides the release" }), { ...opts, landed });
22837
22852
  return dispatch;
22838
22853
  }
22854
+ function releaseAnnounceDeferral(model, summaryFile) {
22855
+ return prodReleaseTargets(model).length > 0 ? `\`mmi-cli devops release --resume --watch${summaryFile ? ` --announce-summary-file ${summaryFile}` : ""}\` announces it once they are green` : "a central deploy is proven only under --watch, so this release stays unannounced";
22856
+ }
22839
22857
  async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints, ledger, alignEarly) {
22840
22858
  const resumeCommand = options.dev ? "mmi-cli devops release --dev --apply" : "mmi-cli devops release --apply";
22841
22859
  const ledgerAnchors = { repo: ctx.repo, tag, tagSha: releaseSha };
@@ -22940,13 +22958,12 @@ ${recovery.note}`), recoveryInput);
22940
22958
  throw partialTrainRecoveryError(e, recoveryInput);
22941
22959
  }
22942
22960
  await recordPhase(ledger, deps, ledgerAnchors, "promotion", phaseEntry({ state: "complete", sha: releaseSha, note: `immutable ${tag} pushed at ${releaseSha.slice(0, 12)}; origin/main fast-forwarded to it` }), { landed: "the immutable tag and the green required-check wall" });
22943
- deps.warn?.("Release: Creating the release announcement");
22961
+ deps.warn?.("Release: Creating the GitHub Release");
22962
+ const autoRunSince = (deps.now ?? Date.now)();
22944
22963
  const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
22945
22964
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
22946
22965
  await recordPhase(ledger, deps, ledgerAnchors, "githubRelease", phaseEntry({ state: "complete", sha: releaseSha, runUrl: releaseUrl, note: `verified GitHub Release ${tag} against ${releaseSha.slice(0, 12)}` }), { landed: "the immutable tag, the green required-check wall, and the origin/main fast-forward" });
22947
- const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
22948
22966
  let alignment = alignEarly ? await alignEarly() : void 0;
22949
- const autoRunSince = (deps.now ?? Date.now)();
22950
22967
  const deployDispatch0 = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, releaseSha, "report", meta.publishDir);
22951
22968
  const deployRunRepo = releaseRunRepoFor(deployModel, ctx.repo);
22952
22969
  const failedReleaseRun = deployDispatch0.workflowRuns?.find((run) => run.conclusion === "failure");
@@ -22965,7 +22982,8 @@ ${recovery.note}`), recoveryInput);
22965
22982
  }
22966
22983
  );
22967
22984
  const deployDispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, releaseSha, recoveredDeployDispatch);
22968
- await recordPhase(ledger, deps, ledgerAnchors, "deploy", phaseEntry(deployPhaseInput(deployModel, deployDispatch, { releaseSha })), {
22985
+ const deployLeg = phaseEntry(deployPhaseInput(deployModel, deployDispatch, { releaseSha }));
22986
+ await recordPhase(ledger, deps, ledgerAnchors, "deploy", deployLeg, {
22969
22987
  landed: "the immutable tag, the green required-check wall, the origin/main fast-forward, and the verified GitHub Release"
22970
22988
  });
22971
22989
  const publishDispatch0 = deployDispatch.deployStatus === "success" ? await dispatchPublishIfRequired(deps, ctx, meta, deployModel, "main", tag, watch, "report") : null;
@@ -22981,21 +22999,30 @@ ${recovery.note}`), recoveryInput);
22981
22999
  if (!publishDispatch && deployDispatch.deployStatus !== "success" && meta.publishRequired && (deployModel === "tenant-container" || deployModel === "solo-container")) {
22982
23000
  publishSkipNote = deployDispatch.deployStatus === "failure" ? "box deploy failed - redeploy the box before publishing" : "box deploy not confirmed (run with --watch) - publish after the box deploy lands";
22983
23001
  }
22984
- await recordPhase(ledger, deps, ledgerAnchors, "publish", phaseEntry(publishPhaseInput(deployModel, deployDispatch, publishDispatch, meta.publishRequired === true, {
23002
+ const publishLeg = phaseEntry(publishPhaseInput(deployModel, deployDispatch, publishDispatch, meta.publishRequired === true, {
22985
23003
  releaseSha,
22986
23004
  repo: ctx.repo,
22987
23005
  ...publishSkipNote !== void 0 ? { skipPublishNote: publishSkipNote } : {}
22988
- })), {
23006
+ }));
23007
+ await recordPhase(ledger, deps, ledgerAnchors, "publish", publishLeg, {
22989
23008
  landed: "the immutable tag, the green required-check wall, origin/main, the verified GitHub Release, and the dispatched deploy path"
22990
23009
  });
22991
23010
  let dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
22992
23011
  if (publishSkipNote) dispatch = { ...dispatch, note: `${dispatch.note}; tenant-publish.yml skipped (${publishSkipNote})` };
23012
+ const announcing = announceShippedRelease(
23013
+ deps,
23014
+ { repo: ctx.repo, tag, summaryFile: options.announceSummaryFile },
23015
+ deployModel,
23016
+ { deploy: deployLeg, publish: publishLeg },
23017
+ releaseAnnounceDeferral(deployModel, options.announceSummaryFile)
23018
+ );
22993
23019
  if (watch && alignment) {
22994
23020
  alignment = {
22995
23021
  dev: await awaitAlignmentLanded(deps, ctx, alignment.dev, "development"),
22996
23022
  ...alignment.rc ? { rc: await awaitAlignmentLanded(deps, ctx, alignment.rc, "rc") } : {}
22997
23023
  };
22998
23024
  }
23025
+ const announceNote = await announcing;
22999
23026
  return { checks, releaseUrl, announceNote, dispatch, ledgerAnchors, ...alignment ? { alignment } : {} };
23000
23027
  }
23001
23028
  function resumeStateOf(status) {
@@ -23243,6 +23270,7 @@ async function runReleaseResumePipeline(deps, options, prepared) {
23243
23270
  Live origin/tag/Release proof stays authoritative; nothing was written. Inspect ${ledger.path} before retrying.`
23244
23271
  );
23245
23272
  }
23273
+ const shippedBefore = shipLegsGreen(persisted?.phases.deploy, persisted?.phases.publish);
23246
23274
  if (persisted?.lane && persisted.lane !== "release") {
23247
23275
  throw new Error(`release --resume refused: the ${tag} ledger belongs to the ${persisted.lane} lane \u2014 finish it with ${ledgerLaneResumeCommand(persisted)} (#6068). Nothing was written.`);
23248
23276
  }
@@ -23299,8 +23327,8 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
23299
23327
  deployStatus: historicalDeploy,
23300
23328
  workflowRuns: historicalRows
23301
23329
  });
23330
+ const phaseInputs = phaseInputsFromRunRows(deployModel, dispatch2.workflowRuns ?? historicalRows, tagSha, { repo: ctx.repo });
23302
23331
  if (persisted) {
23303
- const phaseInputs = phaseInputsFromRunRows(deployModel, dispatch2.workflowRuns ?? historicalRows, tagSha, { repo: ctx.repo });
23304
23332
  if (persisted.phases.deploy.superseded !== true) {
23305
23333
  await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(phaseInputs.deploy), { strict: true, landed: "the verified GitHub Release and origin/main at the immutable tag SHA" });
23306
23334
  }
@@ -23309,6 +23337,13 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
23309
23337
  await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified live: origin/main contains the immutable tag SHA" }), { strict: true, landed: "nothing beyond the already-public release" });
23310
23338
  }
23311
23339
  if (isJervHubRepo(ctx.repo)) steps2.push(dispatch2.note);
23340
+ const announceNote2 = persisted && !shippedBefore && persisted.phases.deploy.superseded !== true && prodReleaseTargets(deployModel).length > 0 ? await announceShippedRelease(
23341
+ deps,
23342
+ { repo: ctx.repo, tag, summaryFile: options.announceSummaryFile },
23343
+ deployModel,
23344
+ phaseInputs,
23345
+ releaseAnnounceDeferral(deployModel, options.announceSummaryFile)
23346
+ ) : void 0;
23312
23347
  if (persisted) {
23313
23348
  await recordPhase(ledger, deps, anchors, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward2, rcAlignment2)), { strict: true, landed: "the released, verified, deploy-resolved release (alignment PRs reused/landed)" });
23314
23349
  }
@@ -23326,6 +23361,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
23326
23361
  state: state2,
23327
23362
  resumed: true,
23328
23363
  steps: steps2,
23364
+ ...announceNote2 ? { announceNote: announceNote2 } : {},
23329
23365
  devRollForward: devRollForward2,
23330
23366
  rcAlignment: rcAlignment2,
23331
23367
  dispatch: dispatch2,
@@ -23382,16 +23418,15 @@ ${recovery.note}`), recoveryInput);
23382
23418
  await runGitPush(deps, ["push", "origin", `${tagSha}:refs/heads/main`]);
23383
23419
  steps.push(`pushed ${tagSha.slice(0, 12)} to origin/main`);
23384
23420
  await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: `resumed: ${tagSha.slice(0, 12)} pushed to origin/main (tag preserved, never re-cut)` }), { strict: ledgerMode === "strict", landed: "the immutable tag and the re-proven required-check wall" });
23385
- deps.warn?.("Release: Creating the release announcement");
23421
+ deps.warn?.("Release: Creating the GitHub Release");
23422
+ const autoRunSince = (deps.now ?? Date.now)();
23386
23423
  const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
23387
23424
  steps.push(`created the GitHub Release for ${tag}`);
23388
23425
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
23389
23426
  steps.push("verified the Release published against the tagged commit");
23390
23427
  await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "complete", sha: tagSha, runUrl: releaseUrl, note: "verified the Release published against the tagged commit" }), { strict: ledgerMode === "strict", landed: "the immutable tag, the re-proven wall, and the origin/main push" });
23391
- const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
23392
23428
  let devRollForward = await rollDevelopmentForward(deps, ctx, tag);
23393
23429
  let rcAlignment = !directTrack && branchHints.hasRcBranch ? await alignRcForward(deps, ctx, tag) : void 0;
23394
- const autoRunSince = (deps.now ?? Date.now)();
23395
23430
  const deployDispatch0 = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, tagSha, "report", meta.publishDir);
23396
23431
  const recoveredDeployDispatch = await recoverFailedDispatchPhase(
23397
23432
  deps,
@@ -23408,7 +23443,8 @@ ${recovery.note}`), recoveryInput);
23408
23443
  );
23409
23444
  const deployDispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, recoveredDeployDispatch);
23410
23445
  steps.push(`dispatched the ${deployModel} deploy path`);
23411
- await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(deployPhaseInput(deployModel, deployDispatch, { releaseSha: tagSha })), {
23446
+ const deployLeg = phaseEntry(deployPhaseInput(deployModel, deployDispatch, { releaseSha: tagSha }));
23447
+ await recordPhase(ledger, deps, anchors, "deploy", deployLeg, {
23412
23448
  strict: ledgerMode === "strict",
23413
23449
  landed: "the immutable tag, the re-proven wall, the origin/main push, and the verified GitHub Release"
23414
23450
  });
@@ -23421,15 +23457,24 @@ ${recovery.note}`), recoveryInput);
23421
23457
  publishDispatch0,
23422
23458
  { repo: releaseRunRepoFor(deployModel, ctx.repo), sha: tagSha, watched: isCentralDispatchModel(deployModel), workflow: isCentralDispatchModel(deployModel) ? "tenant-publish.yml" : "publish.yml" }
23423
23459
  ) : null;
23424
- await recordPhase(ledger, deps, anchors, "publish", phaseEntry(publishPhaseInput(deployModel, deployDispatch, publishDispatch, meta.publishRequired === true, { releaseSha: tagSha, repo: ctx.repo })), {
23460
+ const publishLeg = phaseEntry(publishPhaseInput(deployModel, deployDispatch, publishDispatch, meta.publishRequired === true, { releaseSha: tagSha, repo: ctx.repo }));
23461
+ await recordPhase(ledger, deps, anchors, "publish", publishLeg, {
23425
23462
  strict: ledgerMode === "strict",
23426
23463
  landed: "the immutable tag, the re-proven wall, origin/main, the verified GitHub Release, and the dispatched deploy path"
23427
23464
  });
23428
23465
  const dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
23466
+ const announcing = announceShippedRelease(
23467
+ deps,
23468
+ { repo: ctx.repo, tag, summaryFile: options.announceSummaryFile },
23469
+ deployModel,
23470
+ { deploy: deployLeg, publish: publishLeg },
23471
+ releaseAnnounceDeferral(deployModel, options.announceSummaryFile)
23472
+ );
23429
23473
  if (watch) {
23430
23474
  devRollForward = await awaitAlignmentLanded(deps, ctx, devRollForward, "development");
23431
23475
  if (rcAlignment) rcAlignment = await awaitAlignmentLanded(deps, ctx, rcAlignment, "rc");
23432
23476
  }
23477
+ const announceNote = await announcing;
23433
23478
  steps.push(`development roll-forward: ${devRollForward.status}`);
23434
23479
  if (rcAlignment) steps.push(`rc alignment: ${rcAlignment.status}`);
23435
23480
  const followUp = deriveTrainFollowUpStatus({
@@ -26494,6 +26539,11 @@ function rcandVersionStep(targets) {
26494
26539
  function trainPlan(command, options = {}) {
26495
26540
  const isHub = options.repo?.toLowerCase() === "mutmutco/mmi-hub";
26496
26541
  const isDirect = options.releaseTrack === "direct" || options.releaseTrack === void 0 && isHub;
26542
+ const authority = {
26543
+ label: isHub ? "verify Hub master-admin train authority" : "verify project-admin or master train authority on this repo",
26544
+ command: "mmi-cli oracle org access role <owner/repo> --json",
26545
+ gated: true
26546
+ };
26497
26547
  if (command === "rcand") {
26498
26548
  if (isDirect) {
26499
26549
  return [
@@ -26501,7 +26551,7 @@ function trainPlan(command, options = {}) {
26501
26551
  ];
26502
26552
  }
26503
26553
  return [
26504
- { label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
26554
+ authority,
26505
26555
  { label: "verify current branch is development", gated: true },
26506
26556
  rcandVersionStep(options),
26507
26557
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
@@ -26514,7 +26564,7 @@ function trainPlan(command, options = {}) {
26514
26564
  if (command === "release") {
26515
26565
  if (isDirect) {
26516
26566
  return [
26517
- { label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
26567
+ authority,
26518
26568
  { label: "verify current branch is development", gated: true },
26519
26569
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
26520
26570
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
@@ -26529,7 +26579,7 @@ function trainPlan(command, options = {}) {
26529
26579
  }
26530
26580
  if (options.dev) {
26531
26581
  return [
26532
- { label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
26582
+ authority,
26533
26583
  { label: "verify current branch is development", gated: true },
26534
26584
  { label: "guard: refuse if origin/rc carries content not in development (a dev -> main release would drop it)", command: "git rev-list --count --right-only --cherry-pick --no-merges origin/development...origin/rc", gated: true },
26535
26585
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
@@ -26545,7 +26595,7 @@ function trainPlan(command, options = {}) {
26545
26595
  ];
26546
26596
  }
26547
26597
  return [
26548
- { label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
26598
+ authority,
26549
26599
  { label: "verify current branch is rc", gated: true },
26550
26600
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
26551
26601
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
@@ -26560,6 +26610,7 @@ function trainPlan(command, options = {}) {
26560
26610
  ];
26561
26611
  }
26562
26612
  return [
26613
+ authority,
26563
26614
  { label: "verify the fix is merged on development (the only hotfix origin)", gated: true },
26564
26615
  // #6068: hotfix start/release run the SAME shared preflight as release/rcand, before any git mutation.
26565
26616
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
@@ -30069,7 +30120,7 @@ function buildSetDeployPatch(_slug, input) {
30069
30120
  }
30070
30121
  var DEPLOY_STAGE_BRANCH = { dev: "development", rc: "rc", main: "main" };
30071
30122
  function filelessTransitionGuide(repo, stage) {
30072
- const authority = stage === "main" ? "Authority: main is master-admin only." : "Authority: a project-admin may complete this for their own repo; master may also do it.";
30123
+ const authority = "Authority: a project-admin may complete this for their own repo; master may also do it.";
30073
30124
  return [
30074
30125
  `Fileless transition for ${repo} (${stage}):`,
30075
30126
  ` ${authority}`,
@@ -43473,15 +43524,11 @@ ${decision.note}`);
43473
43524
  );
43474
43525
  await recordHotfixPhase(ledger, anchors, "promotion", { state: "complete", sha: mergedSha, note: `immutable ${tag} pushed at ${mergedSha.slice(0, 12)}; origin/main already carries it (merged hotfix PR #${pr.number})` }, "the immutable tag and the green required-check wall");
43475
43526
  let releaseNote;
43476
- let announceNote;
43477
43527
  if (releaseExists) {
43478
43528
  releaseNote = `Release ${tag} already exists \u2014 resumed without recreating`;
43479
43529
  } else {
43480
43530
  await deps.run("gh", ["release", "create", tag, "--repo", ctx.repo, "--target", "main", "--generate-notes", "--latest"]);
43481
43531
  releaseNote = `Release ${tag} created (target main)`;
43482
- if (deps.announce) {
43483
- announceNote = (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note;
43484
- }
43485
43532
  }
43486
43533
  await recordHotfixPhase(ledger, anchors, "githubRelease", { state: "complete", sha: mergedSha, note: releaseNote }, "the immutable tag, the green wall, and origin/main");
43487
43534
  const runs = [];
@@ -43554,6 +43601,13 @@ ${decision.note}`);
43554
43601
  );
43555
43602
  await recordHotfixPhase(ledger, anchors, "deploy", ledgerInputs.deploy, "the immutable tag, the green wall, origin/main, and the GitHub Release");
43556
43603
  await recordHotfixPhase(ledger, anchors, "publish", ledgerInputs.publish, "the immutable tag, the green wall, origin/main, the GitHub Release, and the deploy path");
43604
+ const announceNote = releaseExists ? void 0 : await announceShippedRelease(
43605
+ deps,
43606
+ { repo: ctx.repo, tag, summaryFile: options.announceSummaryFile },
43607
+ deployModel,
43608
+ ledgerInputs,
43609
+ "a hotfix is announced only by the run that proves it shipped, so this one stays unannounced"
43610
+ );
43557
43611
  let verifyNote;
43558
43612
  if (deployModel === "hub-serverless") {
43559
43613
  const previousRef = clean3(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
@@ -44525,7 +44579,7 @@ function registerTrainCommandsAndInventory(program3) {
44525
44579
  }
44526
44580
  function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44527
44581
  for (const commandName of ["rcand", "release"]) {
44528
- const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit master-admin approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded master-only train after explicit approval").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)").option("--out <path>", "write the terminal result (--apply, --resume, --abort, --retry-publish) to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)");
44582
+ const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit project-admin or master approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded repo-authorized train after explicit approval (Hub: master only)").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)").option("--out <path>", "write the terminal result (--apply, --resume, --abort, --retry-publish) to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)");
44529
44583
  const RELEASE_ONLY_FLAGS = [
44530
44584
  { flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line release Slack summary; required for a new MMI-Hub --apply (#883/#3901) and for a product repo whose registry META sets `releaseChannel` (#6523), in its `releaseLanguage`; nothing is posted to a project channel without it" },
44531
44585
  { flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
@@ -45866,8 +45920,8 @@ ${filelessTransitionGuide(target, o.stage)}`
45866
45920
  }
45867
45921
  return reportWrite("org project set-deploy", res);
45868
45922
  });
45869
- var projectTenantTasks = project.command("tenant-tasks").description("project-admin self-declare of tenant tasks for your own repo's dev/rc stages (#5316, #5308) \u2014 new-task-only, dev/rc-only; editing an existing task or declaring a main-stage task stays master-only (`org project set --var tenantTasks=...`)");
45870
- projectTenantTasks.command("declare <name>").description('register a NEW tenant task \u2014 becomes runnable via `runtime tenant control \u2026 run-task` on dev/rc. Body shape {service,command[],stages[],timeoutSeconds?,artifact?} \u2014 stages limited to dev/rc here; artifact "required" tasks must carry {artifact} in the command argv.').option("--service <name>", "service in the tenant runtime the task runs in (required unless --var carries it)").option("--command <argv...>", "task command as argv (repeatable; required unless --var carries it)").option("--stages <list>", "comma-separated dev,rc stages this task may run on (required unless --var carries it)").option("--timeout-seconds <n>", "optional wall-clock ceiling 1..3600").option("--artifact <none|required>", "artifact contract (default none; required tasks must contain {artifact} in argv)").option("--var <json>", "full task object {service,command[],stages[],timeoutSeconds?,artifact?} \u2014 use instead of the individual flags").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (name, o) => {
45923
+ var projectTenantTasks = project.command("tenant-tasks").description("project-admin self-declare of tenant tasks for every stage of your own repo \u2014 new-task-only; editing an existing task stays master-only (`org project set --var tenantTasks=...`)");
45924
+ projectTenantTasks.command("declare <name>").description("register a NEW tenant task \u2014 becomes runnable via `runtime tenant control \u2026 run-task` on its declared dev/rc/main stages. Body shape {service,command[],stages[],timeoutSeconds?,artifact?}; required artifact tasks must carry {artifact} in the command argv.").option("--service <name>", "service in the tenant runtime the task runs in (required unless --var carries it)").option("--command <argv...>", "task command as argv (repeatable; required unless --var carries it)").option("--stages <list>", "comma-separated dev,rc,main stages this task may run on (required unless --var carries it)").option("--timeout-seconds <n>", "optional wall-clock ceiling 1..3600").option("--artifact <none|required>", "artifact contract (default none; required tasks must contain {artifact} in argv)").option("--var <json>", "full task object {service,command[],stages[],timeoutSeconds?,artifact?} \u2014 use instead of the individual flags").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (name, o) => {
45871
45925
  const cfg = await loadConfig();
45872
45926
  let target;
45873
45927
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.55",
3
+ "version": "4.3.57",
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",