@mutmutco/cli 4.5.60 → 4.5.62

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 +180 -42
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -11949,6 +11949,9 @@ function describeSessionIdentity(env = process.env) {
11949
11949
  host: (0, import_node_os8.hostname)()
11950
11950
  };
11951
11951
  }
11952
+ function formatSessionIdentity(identity) {
11953
+ return `${identity.surface ?? "unknown-surface"}/${identity.session ?? "no-session"}@${identity.host ?? "unknown-host"}`;
11954
+ }
11952
11955
 
11953
11956
  // src/board-doctor-config.ts
11954
11957
  init_github_client();
@@ -13201,7 +13204,7 @@ function formatClaimAge(ageMs) {
13201
13204
  return `${Math.floor(hours / 24)}d`;
13202
13205
  }
13203
13206
  function describeClaimMarker(marker) {
13204
- return `${marker.surface ?? "unknown-surface"}/${marker.session ?? "no-session"}@${marker.host ?? "unknown-host"}`;
13207
+ return formatSessionIdentity(marker);
13205
13208
  }
13206
13209
  function boardItemClaim(item) {
13207
13210
  const marker = item.details ? latestClaimMarker(item.details.comments) : void 0;
@@ -14266,6 +14269,7 @@ function encodeGitHubContents(content) {
14266
14269
  // src/bootstrap-ruleset.ts
14267
14270
  init_github_client();
14268
14271
  var PRODUCT_RULESET_NAME = "mmi-product-required-checks";
14272
+ var PRODUCT_PENDING_RULESET_PREFIX = "mmi-product-required-checks-pending-";
14269
14273
  var PRODUCT_GATE_CONTEXT = "gate";
14270
14274
  var PRODUCT_RULESET_PATH = ".github/rulesets/mmi-product-required-checks.json";
14271
14275
  function reseedProductRulesetStrictness(current, seed, declared = {}) {
@@ -14441,7 +14445,8 @@ async function activateProductRuleset(repo, rulesetBody, client, enforcement = "
14441
14445
  const wantStrict = rulesetStrictPolicy({ rules: rulesetBody.rules });
14442
14446
  const wantBranches = rulesetBranchIncludes(rulesetBody);
14443
14447
  const list = await client.rest("GET", `repos/${repo}/rulesets`, { timeoutMs: 2e4 });
14444
- const existing = findProductRuleset(list ?? []);
14448
+ const wantName = rulesetBody.name ?? PRODUCT_RULESET_NAME;
14449
+ const existing = (list ?? []).find((r) => r.name === wantName);
14445
14450
  if (existing?.id != null) {
14446
14451
  const detail = await client.rest("GET", `repos/${repo}/rulesets/${existing.id}`, { timeoutMs: 2e4 });
14447
14452
  const keepActive = detail.enforcement === "active" && enforcement === "disabled";
@@ -17151,10 +17156,10 @@ var rollout_plan_default = {
17151
17156
  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)."
17152
17157
  },
17153
17158
  baseline: {
17154
- version: "4.5.60",
17155
- tag: "v4.5.60",
17156
- commit: "18fb882893af",
17157
- npm: "@mutmutco/cli@4.5.60"
17159
+ version: "4.5.62",
17160
+ tag: "v4.5.62",
17161
+ commit: "5328997bb80a",
17162
+ npm: "@mutmutco/cli@4.5.62"
17158
17163
  },
17159
17164
  exitCriterion: "fleet-n-of-n",
17160
17165
  hubOnlyShortcut: "forbidden",
@@ -17171,14 +17176,14 @@ var rollout_plan_default = {
17171
17176
  repo: "mutmutco/mmi-hub",
17172
17177
  role: "canary",
17173
17178
  schedule: "train",
17174
- v3Target: "v4.5.60"
17179
+ v3Target: "v4.5.62"
17175
17180
  }
17176
17181
  ],
17177
17182
  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.",
17178
17183
  rollback: {
17179
17184
  independent: true,
17180
- mechanism: "npm dist-tag latest -> 4.5.60 and redeploy the Hub Lambda from tag v4.5.60 (18fb882893af); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17181
- v3Target: "v4.5.60 (@mutmutco/cli@4.5.60, tag commit 18fb882893af \u2014 last known-good release carrying the repo-index v4-only contract)"
17185
+ mechanism: "npm dist-tag latest -> 4.5.62 and redeploy the Hub Lambda from tag v4.5.62 (5328997bb80a); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17186
+ v3Target: "v4.5.62 (@mutmutco/cli@4.5.62, tag commit 5328997bb80a \u2014 last known-good release carrying the repo-index v4-only contract)"
17182
17187
  }
17183
17188
  },
17184
17189
  {
@@ -22752,6 +22757,10 @@ function parseReleaseLedger(raw) {
22752
22757
  if (lane !== void 0 && (typeof lane !== "string" || !LANE_SET.has(lane))) {
22753
22758
  throw new ReleaseLedgerError("invalid-record", `release ledger has an unknown lane ${JSON.stringify(lane)}`);
22754
22759
  }
22760
+ const cutBy = obj.cutBy;
22761
+ if (cutBy !== void 0 && (typeof cutBy !== "object" || cutBy === null || Array.isArray(cutBy) || ["surface", "session", "host"].some((key) => typeof cutBy[key] !== "string" || !cutBy[key].trim()))) {
22762
+ throw new ReleaseLedgerError("invalid-record", "release ledger has a malformed cutBy identity");
22763
+ }
22755
22764
  const phasesRaw = obj.phases;
22756
22765
  if (typeof phasesRaw !== "object" || phasesRaw === null || Array.isArray(phasesRaw)) {
22757
22766
  throw new ReleaseLedgerError("unreadable", "release ledger phases block is not an object");
@@ -22761,7 +22770,7 @@ function parseReleaseLedger(raw) {
22761
22770
  if (!PHASE_SET.has(phase)) continue;
22762
22771
  phases[phase] = validatePhaseRecord(phase, rec);
22763
22772
  }
22764
- return { schema: RELEASE_LEDGER_SCHEMA, repo, tag, tagSha, ...lane !== void 0 ? { lane } : {}, phases, updatedAt: obj.updatedAt };
22773
+ return { schema: RELEASE_LEDGER_SCHEMA, repo, tag, tagSha, ...lane !== void 0 ? { lane } : {}, ...cutBy !== void 0 ? { cutBy } : {}, phases, updatedAt: obj.updatedAt };
22765
22774
  }
22766
22775
  function validateLedgerAnchors(ledger, expected) {
22767
22776
  const anchors = normalizeAnchorsForCompare(expected);
@@ -23042,6 +23051,7 @@ function safeRunAction(runId, runRepo, state) {
23042
23051
  }
23043
23052
  function formatReleaseLedgerReport(report) {
23044
23053
  const lines2 = [`release phase ledger${report.lane ? ` [${report.lane}]` : ""}: ${report.phaseStatus.toUpperCase()} (${report.path}) \u2014 phase status of the recorded boundaries, NOT the release verdict`];
23054
+ if (report.cutBy) lines2.push(` cut by: ${formatSessionIdentity(report.cutBy)}`);
23045
23055
  for (const leg of report.legs) {
23046
23056
  const detail = leg.error ?? leg.note;
23047
23057
  const run = [leg.workflow, leg.event ? `(${leg.event})` : void 0, leg.runId != null ? `run ${leg.runId}` : void 0, leg.runRepo ? `in ${leg.runRepo}` : void 0].filter(Boolean).join(" ");
@@ -23055,7 +23065,7 @@ function formatReleaseLedgerReport(report) {
23055
23065
  function releaseLedgerReport(ledger, path2) {
23056
23066
  if (!ledger) return void 0;
23057
23067
  const derived = deriveReleasePhaseStatus(ledger);
23058
- return { path: path2, ...ledger.lane ? { lane: ledger.lane } : {}, phases: ledger.phases, phaseStatus: derived.phaseStatus, legs: derived.legs };
23068
+ return { path: path2, ...ledger.lane ? { lane: ledger.lane } : {}, ...ledger.cutBy ? { cutBy: ledger.cutBy } : {}, phases: ledger.phases, phaseStatus: derived.phaseStatus, legs: derived.legs };
23059
23069
  }
23060
23070
  var TRANSIENT_TRANSPORT_MARKERS = [
23061
23071
  /connection (?:reset|refused|aborted|timed? ?out)/i,
@@ -23660,6 +23670,7 @@ async function startReleaseLedger(ledger, anchors, opts = {}) {
23660
23670
  tag: normalized.tag,
23661
23671
  tagSha: normalized.tagSha,
23662
23672
  ...opts.lane ? { lane: opts.lane } : {},
23673
+ cutBy: opts.cutBy ?? describeSessionIdentity(),
23663
23674
  phases: emptyPhases(),
23664
23675
  updatedAt: now().toISOString()
23665
23676
  };
@@ -25054,6 +25065,11 @@ function formatTrainDoctor(v) {
25054
25065
 
25055
25066
  // src/release-plain-words.ts
25056
25067
  var import_promises3 = require("node:fs/promises");
25068
+ function releaseCutByNotes(cutBy) {
25069
+ return `## Internal
25070
+ Cut by ${formatSessionIdentity(cutBy)}
25071
+ `;
25072
+ }
25057
25073
  var PLAIN_WORDS_HEADING = "## In plain words";
25058
25074
  var PLAIN_WORDS_MARKER = "<!-- mmi:big -->";
25059
25075
  function buildPlainWordsSection(lines2, big) {
@@ -26038,7 +26054,8 @@ ${recovery.note}`), recoveryInput);
26038
26054
  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" });
26039
26055
  deps.warn?.("Release: Creating the GitHub Release");
26040
26056
  const autoRunSince = (deps.now ?? Date.now)();
26041
- const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
26057
+ const cutBy = (await ledger?.load(ledgerAnchors))?.cutBy ?? describeSessionIdentity();
26058
+ const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--notes", releaseCutByNotes(cutBy), "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
26042
26059
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
26043
26060
  await writePlainWordsSection(deps, options, ctx.repo, meta, tag);
26044
26061
  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" });
@@ -26252,7 +26269,7 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
26252
26269
  runUrl: dispatch2.runUrl,
26253
26270
  workflowRuns: dispatch2.workflowRuns,
26254
26271
  deployStatus: dispatch2.deployStatus,
26255
- ...ledgerReport2 ? { ledger: ledgerReport2 } : {},
26272
+ ...ledgerReport2 ? { cutBy: ledgerReport2.cutBy, ledger: ledgerReport2 } : {},
26256
26273
  note: terminal ? `reused immutable ${tag} and existing origin/rc promotion; the ledger-owned rc deploy is ${deployLeg.state} \u2014 candidate complete, nothing re-pushed or re-dispatched` : `reused immutable ${tag} and existing origin/rc promotion; terminalized the ledger-owned rc deploy without re-pushing the branch`
26257
26274
  };
26258
26275
  }
@@ -26322,7 +26339,7 @@ ${spent.note}`);
26322
26339
  runUrl: dispatch.runUrl,
26323
26340
  workflowRuns: dispatch.workflowRuns,
26324
26341
  deployStatus: dispatch.deployStatus,
26325
- ...ledgerReport ? { ledger: ledgerReport } : {},
26342
+ ...ledgerReport ? { cutBy: ledgerReport.cutBy, ledger: ledgerReport } : {},
26326
26343
  note: `reused immutable ${tag}; pushed its exact SHA to rc and dispatched the rc deploy`
26327
26344
  };
26328
26345
  }
@@ -26380,7 +26397,7 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
26380
26397
  runUrl: dispatch.runUrl,
26381
26398
  workflowRuns: dispatch.workflowRuns,
26382
26399
  deployStatus: dispatch.deployStatus,
26383
- ...ledgerReport ? { ledger: ledgerReport } : {},
26400
+ ...ledgerReport ? { cutBy: ledgerReport.cutBy, ledger: ledgerReport } : {},
26384
26401
  note: `re-dispatched the failed rc deploy of immutable ${tag} at ${tagSha.slice(0, 12)}; nothing re-cut, re-tagged or re-pushed`
26385
26402
  };
26386
26403
  }
@@ -26541,7 +26558,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
26541
26558
  devRollForward: devRollForward2,
26542
26559
  rcAlignment: rcAlignment2,
26543
26560
  dispatch: dispatch2,
26544
- ...ledgerReport2 ? { ledger: ledgerReport2 } : {},
26561
+ ...ledgerReport2 ? { cutBy: ledgerReport2.cutBy, ledger: ledgerReport2 } : {},
26545
26562
  note: state2 === "failed" ? `release ${tag} was already published, but its deploy/publish on the release SHA FAILED \u2014 re-run the failed job (do NOT re-tag); the protected alignment PRs were reused` : state2 === "pending" ? `release ${tag} was already published; reused its protected alignment PRs, but a deploy/publish or alignment follow-up is unresolved \u2014 watch it to completion` : `release ${tag} was already published; alignment is complete and the deploy on the release SHA is proven green`
26546
26563
  };
26547
26564
  }
@@ -26596,7 +26613,8 @@ ${recovery.note}`), recoveryInput);
26596
26613
  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" });
26597
26614
  deps.warn?.("Release: Creating the GitHub Release");
26598
26615
  const autoRunSince = (deps.now ?? Date.now)();
26599
- const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
26616
+ const cutBy = (await ledger.load(anchors))?.cutBy ?? describeSessionIdentity();
26617
+ const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--notes", releaseCutByNotes(cutBy), "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
26600
26618
  steps.push(`created the GitHub Release for ${tag}`);
26601
26619
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
26602
26620
  steps.push("verified the Release published against the tagged commit");
@@ -26687,7 +26705,7 @@ ${recovery.note}`), recoveryInput);
26687
26705
  dispatch,
26688
26706
  devRollForward,
26689
26707
  rcAlignment,
26690
- ...ledgerReport ? { ledger: ledgerReport } : {},
26708
+ ...ledgerReport ? { cutBy: ledgerReport.cutBy, ledger: ledgerReport } : {},
26691
26709
  note: followUp === "failed" ? `resumed ${tag} (version preserved, not re-cut) \u2014 but a watched deploy/publish run FAILED; the Release stands, re-run the failed job (do NOT re-tag)` : followUp === "pending" ? `resumed ${tag} (version preserved, not re-cut) \u2014 a deploy/publish or alignment follow-up is unresolved; watch it to completion` : `resumed and completed ${tag} \u2014 the original version was preserved, not re-cut`
26692
26710
  };
26693
26711
  }
@@ -27015,7 +27033,7 @@ ${spent.note}`), rcandRecovery(tag2));
27015
27033
  const d0 = annotateVersionEnvDispatch(await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha, "report"), versionEnv);
27016
27034
  const d2 = await recordRcandDeployLegs(ledger, deps, anchors, deployModel2, d0);
27017
27035
  const rcLedgerReport = releaseLedgerReport(await ledger?.load(anchors).catch(() => void 0), ledger?.path ?? "(no ledger)");
27018
- return { ...ctx, command, stage: "rc", ref: "rc", tag: tag2, bumpIntent, deployModel: deployModel2, promoted: true, checks: checks2, resumeNote, dispatch: d2.note, runId: d2.runId, runUrl: d2.runUrl, workflowRuns: d2.workflowRuns, deployStatus: d2.deployStatus, ...rcLedgerReport ? { ledger: rcLedgerReport } : {} };
27036
+ return { ...ctx, command, stage: "rc", ref: "rc", tag: tag2, bumpIntent, deployModel: deployModel2, promoted: true, checks: checks2, resumeNote, dispatch: d2.note, runId: d2.runId, runUrl: d2.runUrl, workflowRuns: d2.workflowRuns, deployStatus: d2.deployStatus, ...rcLedgerReport ? { cutBy: rcLedgerReport.cutBy, ledger: rcLedgerReport } : {} };
27019
27037
  }
27020
27038
  if (mode === "release-dev") {
27021
27039
  await requireBranch(deps, "development");
@@ -27097,7 +27115,7 @@ ${spent.note}`), rcandRecovery(tag2));
27097
27115
  announceNote: announceNote2,
27098
27116
  devNote: options.dev ? "--dev is a no-op on a direct-track repo \u2014 it already releases development -> main" : void 0,
27099
27117
  release: { tag: tag2, url: releaseUrl2, targetSha: releaseSha2 },
27100
- ...ledgerReport2 ? { ledger: ledgerReport2 } : {}
27118
+ ...ledgerReport2 ? { cutBy: ledgerReport2.cutBy, ledger: ledgerReport2 } : {}
27101
27119
  };
27102
27120
  }
27103
27121
  const retirement2 = hasRcBranch ? await retireRcRuntime(deps, ctx, deployModel2, d2.deployStatus, rcShaAtRelease) : { status: "not-applicable", note: "no origin/rc branch \u2014 rc runtime retirement skipped" };
@@ -27129,7 +27147,7 @@ ${spent.note}`), rcandRecovery(tag2));
27129
27147
  devNote: "released development -> main (--dev), skipping the rc candidate",
27130
27148
  release: { tag: tag2, url: releaseUrl2, targetSha: releaseSha2 },
27131
27149
  environments: environments2,
27132
- ...fullLedgerReport ? { ledger: fullLedgerReport } : {}
27150
+ ...fullLedgerReport ? { cutBy: fullLedgerReport.cutBy, ledger: fullLedgerReport } : {}
27133
27151
  };
27134
27152
  }
27135
27153
  await requireBranch(deps, "rc");
@@ -27210,7 +27228,7 @@ ${spent.note}`), rcandRecovery(tag2));
27210
27228
  announceNote,
27211
27229
  release: { tag, url: releaseUrl, targetSha: releaseSha },
27212
27230
  environments,
27213
- ...ledgerReport ? { ledger: ledgerReport } : {}
27231
+ ...ledgerReport ? { cutBy: ledgerReport.cutBy, ledger: ledgerReport } : {}
27214
27232
  };
27215
27233
  }
27216
27234
  async function latestLocalReleaseTag(deps, mergedInto) {
@@ -27597,6 +27615,41 @@ function parseAuthoritativeRuleset(raw, meta, repo) {
27597
27615
  coversReleaseBranches: rulesetCoversReleaseBranches(apiPayload, resolveReleaseTrack(meta, void 0, repo))
27598
27616
  };
27599
27617
  }
27618
+ function planBranchRequiredContexts(input) {
27619
+ const contexts = sortedUnique([...input.contexts]);
27620
+ const branches = [...new Set(input.branches.map((b) => b.replace(/^refs\/heads\//, "")))];
27621
+ const common = contexts.filter((context) => branches.every((branch) => input.emittedByBranch[branch]?.includes(context) === true));
27622
+ const pending = branches.map((branch) => ({
27623
+ branch,
27624
+ contexts: contexts.filter((context) => !common.includes(context) && input.emittedByBranch[branch]?.includes(context) === true)
27625
+ })).filter((entry) => entry.contexts.length > 0);
27626
+ const waiting = branches.map((branch) => ({
27627
+ branch,
27628
+ contexts: contexts.filter((context) => input.emittedByBranch[branch]?.includes(context) !== true)
27629
+ })).filter((entry) => entry.contexts.length > 0);
27630
+ return { common, pending, waiting };
27631
+ }
27632
+ async function branchContextPlan(deps, repo, authority, meta) {
27633
+ const branches = resolveRequiredCheckBranches(meta, repo);
27634
+ const emittedByBranch = {};
27635
+ const unreadable = [];
27636
+ for (const branch of branches) {
27637
+ const presence = await branchPresence(deps, repo, branch);
27638
+ if (presence === false) continue;
27639
+ emittedByBranch[branch] = await resolveEmittedPrContexts(deps, repo, branch);
27640
+ if (presence === void 0 || emittedByBranch[branch] === void 0) unreadable.push(branch);
27641
+ }
27642
+ return { ...planBranchRequiredContexts({ contexts: authority.contexts, branches, emittedByBranch }), unreadable };
27643
+ }
27644
+ function projectCommonAuthority(authority, common) {
27645
+ const apiPayload = patchRulesetRequiredContexts(authority.apiPayload, common);
27646
+ return {
27647
+ ...authority,
27648
+ contexts: common,
27649
+ apiPayload,
27650
+ filePayload: { ...authority.filePayload, rules: apiPayload.rules }
27651
+ };
27652
+ }
27600
27653
  function sameContexts(left, right) {
27601
27654
  return left.length === right.length && left.every((context, index) => context === right[index]);
27602
27655
  }
@@ -27771,6 +27824,7 @@ async function auditRepoCi(repo, callerDeps) {
27771
27824
  };
27772
27825
  const committedReferenceRaw = deployableGated ? await fetchFileContent(deps, repo, baseBranch, PRODUCT_RULESET_REF) : null;
27773
27826
  let authoritativeRuleset = null;
27827
+ let branchPlan = null;
27774
27828
  let authoritativeRulesetParseError = null;
27775
27829
  if (committedReferenceRaw != null) {
27776
27830
  try {
@@ -27818,6 +27872,33 @@ async function auditRepoCi(repo, callerDeps) {
27818
27872
  });
27819
27873
  } else if (deployableGated) {
27820
27874
  const productRuleset = rulesets.find((r) => r.name === PRODUCT_RULESET_NAME);
27875
+ if (authoritativeRuleset && authoritativeRuleset.source === "registry META requiredChecks") {
27876
+ branchPlan = await branchContextPlan(deps, repo, authoritativeRuleset, meta);
27877
+ if (branchPlan.unreadable.length) {
27878
+ checks.push({ ok: true, label: "branch-specific required checks cannot audit", detail: `workflow emitters unreadable on [${branchPlan.unreadable.join(", ")}] \u2014 no propagation conclusion` });
27879
+ branchPlan = null;
27880
+ }
27881
+ if (branchPlan) authoritativeRuleset = projectCommonAuthority(authoritativeRuleset, branchPlan.common);
27882
+ if (branchPlan?.waiting.length) checks.push({
27883
+ ok: true,
27884
+ label: "required checks pending branch propagation",
27885
+ detail: `warning: ${branchPlan.waiting.map((entry) => `${entry.branch} waits for workflow emitter(s) [${entry.contexts.join(", ")}]`).join("; ")} \u2014 reconcile enforces these only where the branch workflows emit them`
27886
+ });
27887
+ if (branchPlan) {
27888
+ const expectedPending = new Set(branchPlan.pending.map((entry) => PRODUCT_PENDING_RULESET_PREFIX + entry.branch));
27889
+ const livePending = rulesets.filter((r) => r.name?.startsWith(PRODUCT_PENDING_RULESET_PREFIX));
27890
+ const pendingAligned = branchPlan.pending.every((entry) => {
27891
+ const live = livePending.find((r) => r.name === PRODUCT_PENDING_RULESET_PREFIX + entry.branch);
27892
+ return live?.enforcement === "active" && sameContexts(sortedUnique(rulesetRequiredContexts(live)), entry.contexts) && sameContexts(rulesetBranchIncludes(live), [`refs/heads/${entry.branch}`]) && rulesetStrictPolicy(live) === authoritativeRuleset?.committedStrict;
27893
+ }) && livePending.every((r) => expectedPending.has(r.name ?? ""));
27894
+ checks.push({
27895
+ ok: pendingAligned,
27896
+ label: "branch-specific required checks match workflow emitters",
27897
+ detail: pendingAligned ? void 0 : "pending ruleset contexts or branch conditions have not converged to the emitted registry requirements",
27898
+ remediation: pendingAligned ? void 0 : `mmi-cli devops ci reconcile --repo ${repo} --apply`
27899
+ });
27900
+ }
27901
+ }
27821
27902
  const liveContexts = productRuleset == null ? [] : sortedUnique(rulesetRequiredContexts(productRuleset));
27822
27903
  const liveBranchIncludes = productRuleset == null ? [] : rulesetBranchIncludes(productRuleset);
27823
27904
  const liveStrict = productRuleset != null && rulesetStrictPolicy(productRuleset);
@@ -27870,21 +27951,22 @@ async function auditRepoCi(repo, callerDeps) {
27870
27951
  }
27871
27952
  const contextAuthority = authoritativeRuleset ?? registryAuthorityWithoutReference(meta, repo);
27872
27953
  if (contextAuthority != null) {
27954
+ const declaredPolicy = registryAuthorityWithoutReference(meta, repo) ?? contextAuthority;
27873
27955
  const emitted = await getEmittedPrContexts();
27874
27956
  if (emitted !== void 0) {
27875
27957
  const emittedSet = new Set(emitted);
27876
- const missingEmitters = contextAuthority.contexts.filter((context) => !emittedSet.has(context));
27958
+ const missingEmitters = declaredPolicy.contexts.filter((context) => !emittedSet.has(context));
27877
27959
  checks.push({
27878
27960
  ok: missingEmitters.length === 0,
27879
27961
  label: REQUIRED_CONTEXTS_EMITTED_LABEL,
27880
- detail: missingEmitters.length ? `${contextAuthority.source} requires [${contextAuthority.contexts.join(", ")}] but pull_request workflows do not emit [${missingEmitters.join(", ")}]` : void 0,
27962
+ detail: missingEmitters.length ? `${declaredPolicy.source} requires [${declaredPolicy.contexts.join(", ")}] but pull_request workflows do not emit [${missingEmitters.join(", ")}]` : void 0,
27881
27963
  remediation: missingEmitters.length ? `Fix the pull_request workflows or ${contextAuthority.source} before reconciling` : void 0
27882
27964
  });
27883
27965
  }
27884
27966
  if (hasRequiredChecks) {
27885
27967
  const gateFiles = new Set(gateWorkflowFiles(prWorkflowPaths));
27886
27968
  const gateWorkflows = (await getBranchWorkflows() ?? []).filter((wf) => gateFiles.has(wf.path.slice(wf.path.lastIndexOf("/") + 1)));
27887
- const unblocking = unblockingGateJobs(gateWorkflows, new Set(liveContexts)).filter((job) => !AGENT_PR_BOOKKEEPING_CONTEXTS.has(job));
27969
+ const unblocking = unblockingGateJobs(gateWorkflows, new Set(liveContexts)).filter((job) => !AGENT_PR_BOOKKEEPING_CONTEXTS.has(job) && !branchPlan?.pending.some((entry) => entry.branch === baseBranch && entry.contexts.includes(job)));
27888
27970
  checks.push({
27889
27971
  ok: unblocking.length === 0,
27890
27972
  label: UNBLOCKING_GATE_JOBS_LABEL,
@@ -27892,8 +27974,8 @@ async function auditRepoCi(repo, callerDeps) {
27892
27974
  remediation: unblocking.length ? `mmi-cli oracle org project set ${repo} --var requiredChecks=${JSON.stringify(sortedUnique([...liveContexts, ...unblocking]))} then mmi-cli devops ci reconcile --repo ${repo} --apply \u2014 or make a required job \`needs:\` them` : void 0
27893
27975
  });
27894
27976
  }
27895
- const prOnly = contextAuthority.coversReleaseBranches ? contextAuthority.contexts.filter((context) => TRAIN_PR_ONLY_CONTEXTS.has(context)) : [];
27896
- const tagBlind = contextAuthority.coversReleaseBranches ? contextsUnreachableOnTagPush(contextAuthority.contexts, await getBranchWorkflows() ?? [], TRAIN_PR_ONLY_CONTEXTS) : [];
27977
+ const prOnly = declaredPolicy.coversReleaseBranches ? declaredPolicy.contexts.filter((context) => TRAIN_PR_ONLY_CONTEXTS.has(context)) : [];
27978
+ const tagBlind = declaredPolicy.coversReleaseBranches ? contextsUnreachableOnTagPush(declaredPolicy.contexts, await getBranchWorkflows() ?? [], TRAIN_PR_ONLY_CONTEXTS) : [];
27897
27979
  checks.push({
27898
27980
  ok: prOnly.length === 0 && tagBlind.length === 0,
27899
27981
  label: TAG_ADDRESSABLE_CONTEXTS_LABEL,
@@ -28312,7 +28394,7 @@ async function parkProductRuleset(repo, deps) {
28312
28394
  return result;
28313
28395
  }
28314
28396
  function reconcileOwnedFailures(report) {
28315
- return report.checks.filter((check) => !check.ok).filter((check) => check.label.startsWith("allow_") || check.label.startsWith("delete_branch") || check.label.startsWith("has_wiki") || check.label.startsWith("gate workflow committed on ") || check.label.startsWith("product ruleset reference committed on ") || check.label === "product required status checks active" || check.label === RULESET_REFERENCE_MATCH_LABEL || check.label === REQUIRED_CONTEXTS_EMITTED_LABEL || check.label === TAG_ADDRESSABLE_CONTEXTS_LABEL).map((check) => check.label);
28397
+ return report.checks.filter((check) => !check.ok).filter((check) => check.label.startsWith("allow_") || check.label.startsWith("delete_branch") || check.label.startsWith("has_wiki") || check.label.startsWith("gate workflow committed on ") || check.label.startsWith("product ruleset reference committed on ") || check.label === "product required status checks active" || check.label === RULESET_REFERENCE_MATCH_LABEL || check.label === "branch-specific required checks match workflow emitters" || check.label === REQUIRED_CONTEXTS_EMITTED_LABEL || check.label === TAG_ADDRESSABLE_CONTEXTS_LABEL).map((check) => check.label);
28316
28398
  }
28317
28399
  async function finalizeCiReconcile(repo, deps, result, before, pendingReason) {
28318
28400
  if (result.errors.length > 0) {
@@ -28372,7 +28454,7 @@ async function authoritativeSafetyHold(authority, deps, repo, baseBranch, meta)
28372
28454
  if (missing.length > 0) {
28373
28455
  return `${authority.source} requires context(s) with no pull_request workflow emitter [${missing.join(", ")}]; no source or live mutation was made`;
28374
28456
  }
28375
- if (authority.coversReleaseBranches) {
28457
+ if (authority.coversReleaseBranches && authority.source !== "registry META requiredChecks") {
28376
28458
  const track = resolveReleaseTrack(meta, void 0, repo);
28377
28459
  const releaseBranches = branchesForTrack(track).filter((b) => b !== baseBranch);
28378
28460
  const ignore = /* @__PURE__ */ new Set([...AGENT_PR_BOOKKEEPING_CONTEXTS, ...TRAIN_PR_ONLY_CONTEXTS]);
@@ -28451,6 +28533,21 @@ async function applyCiReconcileRepo(repo, deps) {
28451
28533
  result.errors.push(`${authority.source} declares no required contexts`);
28452
28534
  return finalizeCiReconcile(repo, deps, result, report);
28453
28535
  }
28536
+ let pendingPlan = null;
28537
+ const declaredContexts = authority.contexts;
28538
+ if (authority.source === "registry META requiredChecks") {
28539
+ pendingPlan = await branchContextPlan(deps, repo, authority, meta);
28540
+ if (pendingPlan.unreadable.length) {
28541
+ const reason = `required branch workflow emitters unreadable on [${pendingPlan.unreadable.join(", ")}] \u2014 ruleset convergence deferred`;
28542
+ result.skipped.push(reason);
28543
+ return finalizeCiReconcile(repo, deps, result, report, reason);
28544
+ }
28545
+ if (pendingPlan.common.length === 0) {
28546
+ result.errors.push("no common required context is emitted by every required branch \u2014 ruleset convergence deferred");
28547
+ return finalizeCiReconcile(repo, deps, result, report);
28548
+ }
28549
+ authority = projectCommonAuthority(authority, pendingPlan.common);
28550
+ }
28454
28551
  const sourceNeedsConvergence = !sameContexts(authority.committedContexts, authority.contexts) || !sameContexts(authority.committedBranchIncludes, authority.branchIncludes) || !authority.committedStrict;
28455
28552
  if (sourceNeedsConvergence) {
28456
28553
  try {
@@ -28499,7 +28596,13 @@ async function applyCiReconcileRepo(repo, deps) {
28499
28596
  }
28500
28597
  const filteredPaths = new Set(pathFilteredPullRequestWorkflows(bodies).filter((p) => !p.endsWith("/agent-pr.yml")));
28501
28598
  const safeContexts = new Set(collectPullRequestWorkflowContexts(bodies.filter((b) => !filteredPaths.has(b.path))));
28502
- const unsafe = collectPullRequestWorkflowContexts(bodies.filter((b) => filteredPaths.has(b.path))).filter((c) => authority.contexts.includes(c) && !safeContexts.has(c));
28599
+ const unsafe = collectPullRequestWorkflowContexts(bodies.filter((b) => filteredPaths.has(b.path))).filter((c) => declaredContexts.includes(c) && !safeContexts.has(c));
28600
+ if (pendingPlan?.pending.some((entry) => entry.contexts.some((context) => unsafe.includes(context)))) {
28601
+ const reason = `pending required-check rulesets left non-enforcing \u2014 [${unsafe.join(", ")}] emitted only by path-filtered workflows`;
28602
+ result.skipped.push(reason);
28603
+ return finalizeCiReconcile(repo, deps, result, report, reason);
28604
+ }
28605
+ const gateProvenGreen = await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles);
28503
28606
  const plan = resolveProductRulesetReconcilePlan({
28504
28607
  liveEnforcement: live?.enforcement,
28505
28608
  liveContexts,
@@ -28508,7 +28611,7 @@ async function applyCiReconcileRepo(repo, deps) {
28508
28611
  authorityBranchIncludes: authority.branchIncludes,
28509
28612
  liveStrict: live == null ? void 0 : rulesetStrictPolicy(live),
28510
28613
  authorityStrict: authority.committedStrict,
28511
- gateProvenGreen: await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles),
28614
+ gateProvenGreen,
28512
28615
  unsafeContexts: unsafe
28513
28616
  });
28514
28617
  if (!plan.shouldActivate) {
@@ -28517,15 +28620,49 @@ async function applyCiReconcileRepo(repo, deps) {
28517
28620
  return finalizeCiReconcile(repo, deps, result, report, plan.holdReason);
28518
28621
  }
28519
28622
  result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts, branches and up-to-date policy already match declared authority`);
28520
- return finalizeCiReconcile(repo, deps, result, report);
28521
28623
  }
28522
- try {
28523
- const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, plan.targetEnforcement);
28524
- if (activation.action === "skipped") result.skipped.push(activation.detail ?? "product ruleset");
28525
- else result.applied.push(`product ruleset ${activation.action}${activation.detail ? `: ${activation.detail}` : ""}`);
28526
- } catch (e) {
28527
- result.errors.push(e.message);
28528
- return finalizeCiReconcile(repo, deps, result, report);
28624
+ if (plan.shouldActivate) {
28625
+ try {
28626
+ const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, plan.targetEnforcement);
28627
+ if (activation.action === "skipped") result.skipped.push(activation.detail ?? "product ruleset");
28628
+ else result.applied.push(`product ruleset ${activation.action}${activation.detail ? `: ${activation.detail}` : ""}`);
28629
+ } catch (e) {
28630
+ result.errors.push(e.message);
28631
+ return finalizeCiReconcile(repo, deps, result, report);
28632
+ }
28633
+ }
28634
+ if (pendingPlan) {
28635
+ if (pendingPlan.pending.length > 0 && !gateProvenGreen) {
28636
+ const reason = "pending required-check rulesets left non-enforcing \u2014 the gate is not green on development";
28637
+ result.skipped.push(reason);
28638
+ return finalizeCiReconcile(repo, deps, result, report, reason);
28639
+ }
28640
+ try {
28641
+ const desired = /* @__PURE__ */ new Set();
28642
+ for (const entry of pendingPlan.pending) {
28643
+ const name = PRODUCT_PENDING_RULESET_PREFIX + entry.branch;
28644
+ desired.add(name);
28645
+ const statusOnly = {
28646
+ ...authority.apiPayload,
28647
+ name,
28648
+ rules: authority.apiPayload.rules.filter((rule) => rule.type === "required_status_checks")
28649
+ };
28650
+ const payload = patchRulesetBranchIncludes(
28651
+ patchRulesetRequiredContexts(statusOnly, entry.contexts),
28652
+ [entry.branch]
28653
+ );
28654
+ const activation = await activateProductRuleset(repo, payload, deps.client);
28655
+ (activation.action === "skipped" ? result.skipped : result.applied).push(`${name}: ${activation.action}`);
28656
+ }
28657
+ const list = await deps.client.rest("GET", `repos/${repo}/rulesets`);
28658
+ for (const stale of list ?? []) {
28659
+ if (!stale.name?.startsWith(PRODUCT_PENDING_RULESET_PREFIX) || desired.has(stale.name) || stale.id == null) continue;
28660
+ await deps.client.rest("DELETE", `repos/${repo}/rulesets/${stale.id}`);
28661
+ result.applied.push(`deleted converged ${stale.name}`);
28662
+ }
28663
+ } catch (e) {
28664
+ result.errors.push(`pending ruleset convergence failed: ${e.message}`);
28665
+ }
28529
28666
  }
28530
28667
  return finalizeCiReconcile(repo, deps, result, report);
28531
28668
  }
@@ -36704,7 +36841,8 @@ ${decision.note}`);
36704
36841
  if (releaseExists) {
36705
36842
  releaseNote = `Release ${tag} already exists \u2014 resumed without recreating`;
36706
36843
  } else {
36707
- await deps.run("gh", ["release", "create", tag, "--repo", ctx.repo, "--target", "main", "--generate-notes", "--latest"]);
36844
+ const cutBy = (await ledger.load(anchors))?.cutBy ?? describeSessionIdentity();
36845
+ await deps.run("gh", ["release", "create", tag, "--repo", ctx.repo, "--target", "main", "--notes", releaseCutByNotes(cutBy), "--generate-notes", "--latest"]);
36708
36846
  releaseNote = `Release ${tag} created (target main)`;
36709
36847
  }
36710
36848
  await writePlainWordsSection(deps, options, ctx.repo, meta, tag);
@@ -36828,7 +36966,7 @@ ${decision.note}`);
36828
36966
  foldNote,
36829
36967
  foldStatus,
36830
36968
  announceNote,
36831
- ...ledgerReport ? { ledger: ledgerReport } : {}
36969
+ ...ledgerReport ? { cutBy: ledgerReport.cutBy, ledger: ledgerReport } : {}
36832
36970
  };
36833
36971
  if (TRAIN_BRANCHES.includes(startBranch)) {
36834
36972
  return withCheckoutRestoredAfterRelease(deps, result, startBranch);
@@ -36878,7 +37016,7 @@ async function runHotfixRetryDeployLocked(deps, versionInput) {
36878
37016
  ...foldNote ? { foldNote } : {},
36879
37017
  ...foldStatus ? { foldStatus } : {},
36880
37018
  ...runs ? { runs } : {},
36881
- ...report ? { ledger: report } : {}
37019
+ ...report ? { cutBy: report.cutBy, ledger: report } : {}
36882
37020
  };
36883
37021
  }
36884
37022
  function deriveHotfixState(f) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.60",
3
+ "version": "4.5.62",
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",