@mutmutco/cli 4.5.61 → 4.5.63

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 +256 -49
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -8489,6 +8489,22 @@ function shouldWithdrawSurfaceFlag(result) {
8489
8489
 
8490
8490
  // src/secrets-transport.ts
8491
8491
  init_client_version();
8492
+
8493
+ // ../shared/quota-attribution.ts
8494
+ var ATTRIBUTION_MAX = 120;
8495
+ function sanitizeAttribution(value, max = ATTRIBUTION_MAX) {
8496
+ if (typeof value !== "string" || !Number.isInteger(max) || max < 1) return void 0;
8497
+ const cleaned = value.replace(/[^A-Za-z0-9 ._/@:-]/g, "").replace(/\s+/g, " ").trim().slice(0, max);
8498
+ return cleaned.length > 0 ? cleaned : void 0;
8499
+ }
8500
+ function attributionFields(attr) {
8501
+ if (!attr) return {};
8502
+ const command = sanitizeAttribution(attr.command);
8503
+ const via = sanitizeAttribution(attr.via);
8504
+ return { ...command ? { for: command } : {}, ...via ? { via } : {} };
8505
+ }
8506
+
8507
+ // src/secrets-transport.ts
8492
8508
  var OWNER = "mutmutco";
8493
8509
  var SSM_ROOT = "/mmi-future";
8494
8510
  var PROJECT_TIER_SEGMENT = "dev";
@@ -8664,7 +8680,7 @@ async function fetchSecretValue(deps, key, opts, diagnose = false) {
8664
8680
  const res = await fetchVaultRead(deps, `${deps.apiUrl}/secrets/get`, {
8665
8681
  method: "POST",
8666
8682
  headers: await deps.headers({ "content-type": "application/json" }),
8667
- body: JSON.stringify({ repo, key, use: true, ...slug ? { slug } : {} })
8683
+ body: JSON.stringify({ repo, key, use: true, ...slug ? { slug } : {}, ...attributionFields(opts.attribution) })
8668
8684
  });
8669
8685
  if (!res.ok) {
8670
8686
  if (diagnose) {
@@ -11949,6 +11965,9 @@ function describeSessionIdentity(env = process.env) {
11949
11965
  host: (0, import_node_os8.hostname)()
11950
11966
  };
11951
11967
  }
11968
+ function formatSessionIdentity(identity) {
11969
+ return `${identity.surface ?? "unknown-surface"}/${identity.session ?? "no-session"}@${identity.host ?? "unknown-host"}`;
11970
+ }
11952
11971
 
11953
11972
  // src/board-doctor-config.ts
11954
11973
  init_github_client();
@@ -13201,7 +13220,7 @@ function formatClaimAge(ageMs) {
13201
13220
  return `${Math.floor(hours / 24)}d`;
13202
13221
  }
13203
13222
  function describeClaimMarker(marker) {
13204
- return `${marker.surface ?? "unknown-surface"}/${marker.session ?? "no-session"}@${marker.host ?? "unknown-host"}`;
13223
+ return formatSessionIdentity(marker);
13205
13224
  }
13206
13225
  function boardItemClaim(item) {
13207
13226
  const marker = item.details ? latestClaimMarker(item.details.comments) : void 0;
@@ -14266,6 +14285,7 @@ function encodeGitHubContents(content) {
14266
14285
  // src/bootstrap-ruleset.ts
14267
14286
  init_github_client();
14268
14287
  var PRODUCT_RULESET_NAME = "mmi-product-required-checks";
14288
+ var PRODUCT_PENDING_RULESET_PREFIX = "mmi-product-required-checks-pending-";
14269
14289
  var PRODUCT_GATE_CONTEXT = "gate";
14270
14290
  var PRODUCT_RULESET_PATH = ".github/rulesets/mmi-product-required-checks.json";
14271
14291
  function reseedProductRulesetStrictness(current, seed, declared = {}) {
@@ -14441,7 +14461,8 @@ async function activateProductRuleset(repo, rulesetBody, client, enforcement = "
14441
14461
  const wantStrict = rulesetStrictPolicy({ rules: rulesetBody.rules });
14442
14462
  const wantBranches = rulesetBranchIncludes(rulesetBody);
14443
14463
  const list = await client.rest("GET", `repos/${repo}/rulesets`, { timeoutMs: 2e4 });
14444
- const existing = findProductRuleset(list ?? []);
14464
+ const wantName = rulesetBody.name ?? PRODUCT_RULESET_NAME;
14465
+ const existing = (list ?? []).find((r) => r.name === wantName);
14445
14466
  if (existing?.id != null) {
14446
14467
  const detail = await client.rest("GET", `repos/${repo}/rulesets/${existing.id}`, { timeoutMs: 2e4 });
14447
14468
  const keepActive = detail.enforcement === "active" && enforcement === "disabled";
@@ -17151,10 +17172,10 @@ var rollout_plan_default = {
17151
17172
  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
17173
  },
17153
17174
  baseline: {
17154
- version: "4.5.61",
17155
- tag: "v4.5.61",
17156
- commit: "72f52b1d5bbb",
17157
- npm: "@mutmutco/cli@4.5.61"
17175
+ version: "4.5.63",
17176
+ tag: "v4.5.63",
17177
+ commit: "89c6b4bfae7e",
17178
+ npm: "@mutmutco/cli@4.5.63"
17158
17179
  },
17159
17180
  exitCriterion: "fleet-n-of-n",
17160
17181
  hubOnlyShortcut: "forbidden",
@@ -17171,14 +17192,14 @@ var rollout_plan_default = {
17171
17192
  repo: "mutmutco/mmi-hub",
17172
17193
  role: "canary",
17173
17194
  schedule: "train",
17174
- v3Target: "v4.5.61"
17195
+ v3Target: "v4.5.63"
17175
17196
  }
17176
17197
  ],
17177
17198
  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
17199
  rollback: {
17179
17200
  independent: true,
17180
- mechanism: "npm dist-tag latest -> 4.5.61 and redeploy the Hub Lambda from tag v4.5.61 (72f52b1d5bbb); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17181
- v3Target: "v4.5.61 (@mutmutco/cli@4.5.61, tag commit 72f52b1d5bbb \u2014 last known-good release carrying the repo-index v4-only contract)"
17201
+ mechanism: "npm dist-tag latest -> 4.5.63 and redeploy the Hub Lambda from tag v4.5.63 (89c6b4bfae7e); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17202
+ v3Target: "v4.5.63 (@mutmutco/cli@4.5.63, tag commit 89c6b4bfae7e \u2014 last known-good release carrying the repo-index v4-only contract)"
17182
17203
  }
17183
17204
  },
17184
17205
  {
@@ -22752,6 +22773,10 @@ function parseReleaseLedger(raw) {
22752
22773
  if (lane !== void 0 && (typeof lane !== "string" || !LANE_SET.has(lane))) {
22753
22774
  throw new ReleaseLedgerError("invalid-record", `release ledger has an unknown lane ${JSON.stringify(lane)}`);
22754
22775
  }
22776
+ const cutBy = obj.cutBy;
22777
+ if (cutBy !== void 0 && (typeof cutBy !== "object" || cutBy === null || Array.isArray(cutBy) || ["surface", "session", "host"].some((key) => typeof cutBy[key] !== "string" || !cutBy[key].trim()))) {
22778
+ throw new ReleaseLedgerError("invalid-record", "release ledger has a malformed cutBy identity");
22779
+ }
22755
22780
  const phasesRaw = obj.phases;
22756
22781
  if (typeof phasesRaw !== "object" || phasesRaw === null || Array.isArray(phasesRaw)) {
22757
22782
  throw new ReleaseLedgerError("unreadable", "release ledger phases block is not an object");
@@ -22761,7 +22786,7 @@ function parseReleaseLedger(raw) {
22761
22786
  if (!PHASE_SET.has(phase)) continue;
22762
22787
  phases[phase] = validatePhaseRecord(phase, rec);
22763
22788
  }
22764
- return { schema: RELEASE_LEDGER_SCHEMA, repo, tag, tagSha, ...lane !== void 0 ? { lane } : {}, phases, updatedAt: obj.updatedAt };
22789
+ return { schema: RELEASE_LEDGER_SCHEMA, repo, tag, tagSha, ...lane !== void 0 ? { lane } : {}, ...cutBy !== void 0 ? { cutBy } : {}, phases, updatedAt: obj.updatedAt };
22765
22790
  }
22766
22791
  function validateLedgerAnchors(ledger, expected) {
22767
22792
  const anchors = normalizeAnchorsForCompare(expected);
@@ -23042,6 +23067,7 @@ function safeRunAction(runId, runRepo, state) {
23042
23067
  }
23043
23068
  function formatReleaseLedgerReport(report) {
23044
23069
  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`];
23070
+ if (report.cutBy) lines2.push(` cut by: ${formatSessionIdentity(report.cutBy)}`);
23045
23071
  for (const leg of report.legs) {
23046
23072
  const detail = leg.error ?? leg.note;
23047
23073
  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 +23081,7 @@ function formatReleaseLedgerReport(report) {
23055
23081
  function releaseLedgerReport(ledger, path2) {
23056
23082
  if (!ledger) return void 0;
23057
23083
  const derived = deriveReleasePhaseStatus(ledger);
23058
- return { path: path2, ...ledger.lane ? { lane: ledger.lane } : {}, phases: ledger.phases, phaseStatus: derived.phaseStatus, legs: derived.legs };
23084
+ return { path: path2, ...ledger.lane ? { lane: ledger.lane } : {}, ...ledger.cutBy ? { cutBy: ledger.cutBy } : {}, phases: ledger.phases, phaseStatus: derived.phaseStatus, legs: derived.legs };
23059
23085
  }
23060
23086
  var TRANSIENT_TRANSPORT_MARKERS = [
23061
23087
  /connection (?:reset|refused|aborted|timed? ?out)/i,
@@ -23660,6 +23686,7 @@ async function startReleaseLedger(ledger, anchors, opts = {}) {
23660
23686
  tag: normalized.tag,
23661
23687
  tagSha: normalized.tagSha,
23662
23688
  ...opts.lane ? { lane: opts.lane } : {},
23689
+ cutBy: opts.cutBy ?? describeSessionIdentity(),
23663
23690
  phases: emptyPhases(),
23664
23691
  updatedAt: now().toISOString()
23665
23692
  };
@@ -25054,6 +25081,11 @@ function formatTrainDoctor(v) {
25054
25081
 
25055
25082
  // src/release-plain-words.ts
25056
25083
  var import_promises3 = require("node:fs/promises");
25084
+ function releaseCutByNotes(cutBy) {
25085
+ return `## Internal
25086
+ Cut by ${formatSessionIdentity(cutBy)}
25087
+ `;
25088
+ }
25057
25089
  var PLAIN_WORDS_HEADING = "## In plain words";
25058
25090
  var PLAIN_WORDS_MARKER = "<!-- mmi:big -->";
25059
25091
  function buildPlainWordsSection(lines2, big) {
@@ -26038,7 +26070,8 @@ ${recovery.note}`), recoveryInput);
26038
26070
  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
26071
  deps.warn?.("Release: Creating the GitHub Release");
26040
26072
  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;
26073
+ const cutBy = (await ledger?.load(ledgerAnchors))?.cutBy ?? describeSessionIdentity();
26074
+ const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--notes", releaseCutByNotes(cutBy), "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
26042
26075
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
26043
26076
  await writePlainWordsSection(deps, options, ctx.repo, meta, tag);
26044
26077
  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 +26285,7 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
26252
26285
  runUrl: dispatch2.runUrl,
26253
26286
  workflowRuns: dispatch2.workflowRuns,
26254
26287
  deployStatus: dispatch2.deployStatus,
26255
- ...ledgerReport2 ? { ledger: ledgerReport2 } : {},
26288
+ ...ledgerReport2 ? { cutBy: ledgerReport2.cutBy, ledger: ledgerReport2 } : {},
26256
26289
  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
26290
  };
26258
26291
  }
@@ -26322,7 +26355,7 @@ ${spent.note}`);
26322
26355
  runUrl: dispatch.runUrl,
26323
26356
  workflowRuns: dispatch.workflowRuns,
26324
26357
  deployStatus: dispatch.deployStatus,
26325
- ...ledgerReport ? { ledger: ledgerReport } : {},
26358
+ ...ledgerReport ? { cutBy: ledgerReport.cutBy, ledger: ledgerReport } : {},
26326
26359
  note: `reused immutable ${tag}; pushed its exact SHA to rc and dispatched the rc deploy`
26327
26360
  };
26328
26361
  }
@@ -26380,7 +26413,7 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
26380
26413
  runUrl: dispatch.runUrl,
26381
26414
  workflowRuns: dispatch.workflowRuns,
26382
26415
  deployStatus: dispatch.deployStatus,
26383
- ...ledgerReport ? { ledger: ledgerReport } : {},
26416
+ ...ledgerReport ? { cutBy: ledgerReport.cutBy, ledger: ledgerReport } : {},
26384
26417
  note: `re-dispatched the failed rc deploy of immutable ${tag} at ${tagSha.slice(0, 12)}; nothing re-cut, re-tagged or re-pushed`
26385
26418
  };
26386
26419
  }
@@ -26541,7 +26574,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
26541
26574
  devRollForward: devRollForward2,
26542
26575
  rcAlignment: rcAlignment2,
26543
26576
  dispatch: dispatch2,
26544
- ...ledgerReport2 ? { ledger: ledgerReport2 } : {},
26577
+ ...ledgerReport2 ? { cutBy: ledgerReport2.cutBy, ledger: ledgerReport2 } : {},
26545
26578
  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
26579
  };
26547
26580
  }
@@ -26596,7 +26629,8 @@ ${recovery.note}`), recoveryInput);
26596
26629
  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
26630
  deps.warn?.("Release: Creating the GitHub Release");
26598
26631
  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;
26632
+ const cutBy = (await ledger.load(anchors))?.cutBy ?? describeSessionIdentity();
26633
+ const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--notes", releaseCutByNotes(cutBy), "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
26600
26634
  steps.push(`created the GitHub Release for ${tag}`);
26601
26635
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
26602
26636
  steps.push("verified the Release published against the tagged commit");
@@ -26687,7 +26721,7 @@ ${recovery.note}`), recoveryInput);
26687
26721
  dispatch,
26688
26722
  devRollForward,
26689
26723
  rcAlignment,
26690
- ...ledgerReport ? { ledger: ledgerReport } : {},
26724
+ ...ledgerReport ? { cutBy: ledgerReport.cutBy, ledger: ledgerReport } : {},
26691
26725
  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
26726
  };
26693
26727
  }
@@ -27015,7 +27049,7 @@ ${spent.note}`), rcandRecovery(tag2));
27015
27049
  const d0 = annotateVersionEnvDispatch(await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha, "report"), versionEnv);
27016
27050
  const d2 = await recordRcandDeployLegs(ledger, deps, anchors, deployModel2, d0);
27017
27051
  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 } : {} };
27052
+ 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
27053
  }
27020
27054
  if (mode === "release-dev") {
27021
27055
  await requireBranch(deps, "development");
@@ -27097,7 +27131,7 @@ ${spent.note}`), rcandRecovery(tag2));
27097
27131
  announceNote: announceNote2,
27098
27132
  devNote: options.dev ? "--dev is a no-op on a direct-track repo \u2014 it already releases development -> main" : void 0,
27099
27133
  release: { tag: tag2, url: releaseUrl2, targetSha: releaseSha2 },
27100
- ...ledgerReport2 ? { ledger: ledgerReport2 } : {}
27134
+ ...ledgerReport2 ? { cutBy: ledgerReport2.cutBy, ledger: ledgerReport2 } : {}
27101
27135
  };
27102
27136
  }
27103
27137
  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 +27163,7 @@ ${spent.note}`), rcandRecovery(tag2));
27129
27163
  devNote: "released development -> main (--dev), skipping the rc candidate",
27130
27164
  release: { tag: tag2, url: releaseUrl2, targetSha: releaseSha2 },
27131
27165
  environments: environments2,
27132
- ...fullLedgerReport ? { ledger: fullLedgerReport } : {}
27166
+ ...fullLedgerReport ? { cutBy: fullLedgerReport.cutBy, ledger: fullLedgerReport } : {}
27133
27167
  };
27134
27168
  }
27135
27169
  await requireBranch(deps, "rc");
@@ -27210,7 +27244,7 @@ ${spent.note}`), rcandRecovery(tag2));
27210
27244
  announceNote,
27211
27245
  release: { tag, url: releaseUrl, targetSha: releaseSha },
27212
27246
  environments,
27213
- ...ledgerReport ? { ledger: ledgerReport } : {}
27247
+ ...ledgerReport ? { cutBy: ledgerReport.cutBy, ledger: ledgerReport } : {}
27214
27248
  };
27215
27249
  }
27216
27250
  async function latestLocalReleaseTag(deps, mergedInto) {
@@ -27597,6 +27631,41 @@ function parseAuthoritativeRuleset(raw, meta, repo) {
27597
27631
  coversReleaseBranches: rulesetCoversReleaseBranches(apiPayload, resolveReleaseTrack(meta, void 0, repo))
27598
27632
  };
27599
27633
  }
27634
+ function planBranchRequiredContexts(input) {
27635
+ const contexts = sortedUnique([...input.contexts]);
27636
+ const branches = [...new Set(input.branches.map((b) => b.replace(/^refs\/heads\//, "")))];
27637
+ const common = contexts.filter((context) => branches.every((branch) => input.emittedByBranch[branch]?.includes(context) === true));
27638
+ const pending = branches.map((branch) => ({
27639
+ branch,
27640
+ contexts: contexts.filter((context) => !common.includes(context) && input.emittedByBranch[branch]?.includes(context) === true)
27641
+ })).filter((entry) => entry.contexts.length > 0);
27642
+ const waiting = branches.map((branch) => ({
27643
+ branch,
27644
+ contexts: contexts.filter((context) => input.emittedByBranch[branch]?.includes(context) !== true)
27645
+ })).filter((entry) => entry.contexts.length > 0);
27646
+ return { common, pending, waiting };
27647
+ }
27648
+ async function branchContextPlan(deps, repo, authority, meta) {
27649
+ const branches = resolveRequiredCheckBranches(meta, repo);
27650
+ const emittedByBranch = {};
27651
+ const unreadable = [];
27652
+ for (const branch of branches) {
27653
+ const presence = await branchPresence(deps, repo, branch);
27654
+ if (presence === false) continue;
27655
+ emittedByBranch[branch] = await resolveEmittedPrContexts(deps, repo, branch);
27656
+ if (presence === void 0 || emittedByBranch[branch] === void 0) unreadable.push(branch);
27657
+ }
27658
+ return { ...planBranchRequiredContexts({ contexts: authority.contexts, branches, emittedByBranch }), unreadable };
27659
+ }
27660
+ function projectCommonAuthority(authority, common) {
27661
+ const apiPayload = patchRulesetRequiredContexts(authority.apiPayload, common);
27662
+ return {
27663
+ ...authority,
27664
+ contexts: common,
27665
+ apiPayload,
27666
+ filePayload: { ...authority.filePayload, rules: apiPayload.rules }
27667
+ };
27668
+ }
27600
27669
  function sameContexts(left, right) {
27601
27670
  return left.length === right.length && left.every((context, index) => context === right[index]);
27602
27671
  }
@@ -27771,6 +27840,7 @@ async function auditRepoCi(repo, callerDeps) {
27771
27840
  };
27772
27841
  const committedReferenceRaw = deployableGated ? await fetchFileContent(deps, repo, baseBranch, PRODUCT_RULESET_REF) : null;
27773
27842
  let authoritativeRuleset = null;
27843
+ let branchPlan = null;
27774
27844
  let authoritativeRulesetParseError = null;
27775
27845
  if (committedReferenceRaw != null) {
27776
27846
  try {
@@ -27818,6 +27888,33 @@ async function auditRepoCi(repo, callerDeps) {
27818
27888
  });
27819
27889
  } else if (deployableGated) {
27820
27890
  const productRuleset = rulesets.find((r) => r.name === PRODUCT_RULESET_NAME);
27891
+ if (authoritativeRuleset && authoritativeRuleset.source === "registry META requiredChecks") {
27892
+ branchPlan = await branchContextPlan(deps, repo, authoritativeRuleset, meta);
27893
+ if (branchPlan.unreadable.length) {
27894
+ checks.push({ ok: true, label: "branch-specific required checks cannot audit", detail: `workflow emitters unreadable on [${branchPlan.unreadable.join(", ")}] \u2014 no propagation conclusion` });
27895
+ branchPlan = null;
27896
+ }
27897
+ if (branchPlan) authoritativeRuleset = projectCommonAuthority(authoritativeRuleset, branchPlan.common);
27898
+ if (branchPlan?.waiting.length) checks.push({
27899
+ ok: true,
27900
+ label: "required checks pending branch propagation",
27901
+ 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`
27902
+ });
27903
+ if (branchPlan) {
27904
+ const expectedPending = new Set(branchPlan.pending.map((entry) => PRODUCT_PENDING_RULESET_PREFIX + entry.branch));
27905
+ const livePending = rulesets.filter((r) => r.name?.startsWith(PRODUCT_PENDING_RULESET_PREFIX));
27906
+ const pendingAligned = branchPlan.pending.every((entry) => {
27907
+ const live = livePending.find((r) => r.name === PRODUCT_PENDING_RULESET_PREFIX + entry.branch);
27908
+ return live?.enforcement === "active" && sameContexts(sortedUnique(rulesetRequiredContexts(live)), entry.contexts) && sameContexts(rulesetBranchIncludes(live), [`refs/heads/${entry.branch}`]) && rulesetStrictPolicy(live) === authoritativeRuleset?.committedStrict;
27909
+ }) && livePending.every((r) => expectedPending.has(r.name ?? ""));
27910
+ checks.push({
27911
+ ok: pendingAligned,
27912
+ label: "branch-specific required checks match workflow emitters",
27913
+ detail: pendingAligned ? void 0 : "pending ruleset contexts or branch conditions have not converged to the emitted registry requirements",
27914
+ remediation: pendingAligned ? void 0 : `mmi-cli devops ci reconcile --repo ${repo} --apply`
27915
+ });
27916
+ }
27917
+ }
27821
27918
  const liveContexts = productRuleset == null ? [] : sortedUnique(rulesetRequiredContexts(productRuleset));
27822
27919
  const liveBranchIncludes = productRuleset == null ? [] : rulesetBranchIncludes(productRuleset);
27823
27920
  const liveStrict = productRuleset != null && rulesetStrictPolicy(productRuleset);
@@ -27870,21 +27967,22 @@ async function auditRepoCi(repo, callerDeps) {
27870
27967
  }
27871
27968
  const contextAuthority = authoritativeRuleset ?? registryAuthorityWithoutReference(meta, repo);
27872
27969
  if (contextAuthority != null) {
27970
+ const declaredPolicy = registryAuthorityWithoutReference(meta, repo) ?? contextAuthority;
27873
27971
  const emitted = await getEmittedPrContexts();
27874
27972
  if (emitted !== void 0) {
27875
27973
  const emittedSet = new Set(emitted);
27876
- const missingEmitters = contextAuthority.contexts.filter((context) => !emittedSet.has(context));
27974
+ const missingEmitters = declaredPolicy.contexts.filter((context) => !emittedSet.has(context));
27877
27975
  checks.push({
27878
27976
  ok: missingEmitters.length === 0,
27879
27977
  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,
27978
+ detail: missingEmitters.length ? `${declaredPolicy.source} requires [${declaredPolicy.contexts.join(", ")}] but pull_request workflows do not emit [${missingEmitters.join(", ")}]` : void 0,
27881
27979
  remediation: missingEmitters.length ? `Fix the pull_request workflows or ${contextAuthority.source} before reconciling` : void 0
27882
27980
  });
27883
27981
  }
27884
27982
  if (hasRequiredChecks) {
27885
27983
  const gateFiles = new Set(gateWorkflowFiles(prWorkflowPaths));
27886
27984
  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));
27985
+ 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
27986
  checks.push({
27889
27987
  ok: unblocking.length === 0,
27890
27988
  label: UNBLOCKING_GATE_JOBS_LABEL,
@@ -27892,8 +27990,8 @@ async function auditRepoCi(repo, callerDeps) {
27892
27990
  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
27991
  });
27894
27992
  }
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) : [];
27993
+ const prOnly = declaredPolicy.coversReleaseBranches ? declaredPolicy.contexts.filter((context) => TRAIN_PR_ONLY_CONTEXTS.has(context)) : [];
27994
+ const tagBlind = declaredPolicy.coversReleaseBranches ? contextsUnreachableOnTagPush(declaredPolicy.contexts, await getBranchWorkflows() ?? [], TRAIN_PR_ONLY_CONTEXTS) : [];
27897
27995
  checks.push({
27898
27996
  ok: prOnly.length === 0 && tagBlind.length === 0,
27899
27997
  label: TAG_ADDRESSABLE_CONTEXTS_LABEL,
@@ -28312,7 +28410,7 @@ async function parkProductRuleset(repo, deps) {
28312
28410
  return result;
28313
28411
  }
28314
28412
  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);
28413
+ 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
28414
  }
28317
28415
  async function finalizeCiReconcile(repo, deps, result, before, pendingReason) {
28318
28416
  if (result.errors.length > 0) {
@@ -28372,7 +28470,7 @@ async function authoritativeSafetyHold(authority, deps, repo, baseBranch, meta)
28372
28470
  if (missing.length > 0) {
28373
28471
  return `${authority.source} requires context(s) with no pull_request workflow emitter [${missing.join(", ")}]; no source or live mutation was made`;
28374
28472
  }
28375
- if (authority.coversReleaseBranches) {
28473
+ if (authority.coversReleaseBranches && authority.source !== "registry META requiredChecks") {
28376
28474
  const track = resolveReleaseTrack(meta, void 0, repo);
28377
28475
  const releaseBranches = branchesForTrack(track).filter((b) => b !== baseBranch);
28378
28476
  const ignore = /* @__PURE__ */ new Set([...AGENT_PR_BOOKKEEPING_CONTEXTS, ...TRAIN_PR_ONLY_CONTEXTS]);
@@ -28451,6 +28549,21 @@ async function applyCiReconcileRepo(repo, deps) {
28451
28549
  result.errors.push(`${authority.source} declares no required contexts`);
28452
28550
  return finalizeCiReconcile(repo, deps, result, report);
28453
28551
  }
28552
+ let pendingPlan = null;
28553
+ const declaredContexts = authority.contexts;
28554
+ if (authority.source === "registry META requiredChecks") {
28555
+ pendingPlan = await branchContextPlan(deps, repo, authority, meta);
28556
+ if (pendingPlan.unreadable.length) {
28557
+ const reason = `required branch workflow emitters unreadable on [${pendingPlan.unreadable.join(", ")}] \u2014 ruleset convergence deferred`;
28558
+ result.skipped.push(reason);
28559
+ return finalizeCiReconcile(repo, deps, result, report, reason);
28560
+ }
28561
+ if (pendingPlan.common.length === 0) {
28562
+ result.errors.push("no common required context is emitted by every required branch \u2014 ruleset convergence deferred");
28563
+ return finalizeCiReconcile(repo, deps, result, report);
28564
+ }
28565
+ authority = projectCommonAuthority(authority, pendingPlan.common);
28566
+ }
28454
28567
  const sourceNeedsConvergence = !sameContexts(authority.committedContexts, authority.contexts) || !sameContexts(authority.committedBranchIncludes, authority.branchIncludes) || !authority.committedStrict;
28455
28568
  if (sourceNeedsConvergence) {
28456
28569
  try {
@@ -28499,7 +28612,13 @@ async function applyCiReconcileRepo(repo, deps) {
28499
28612
  }
28500
28613
  const filteredPaths = new Set(pathFilteredPullRequestWorkflows(bodies).filter((p) => !p.endsWith("/agent-pr.yml")));
28501
28614
  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));
28615
+ const unsafe = collectPullRequestWorkflowContexts(bodies.filter((b) => filteredPaths.has(b.path))).filter((c) => declaredContexts.includes(c) && !safeContexts.has(c));
28616
+ if (pendingPlan?.pending.some((entry) => entry.contexts.some((context) => unsafe.includes(context)))) {
28617
+ const reason = `pending required-check rulesets left non-enforcing \u2014 [${unsafe.join(", ")}] emitted only by path-filtered workflows`;
28618
+ result.skipped.push(reason);
28619
+ return finalizeCiReconcile(repo, deps, result, report, reason);
28620
+ }
28621
+ const gateProvenGreen = await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles);
28503
28622
  const plan = resolveProductRulesetReconcilePlan({
28504
28623
  liveEnforcement: live?.enforcement,
28505
28624
  liveContexts,
@@ -28508,7 +28627,7 @@ async function applyCiReconcileRepo(repo, deps) {
28508
28627
  authorityBranchIncludes: authority.branchIncludes,
28509
28628
  liveStrict: live == null ? void 0 : rulesetStrictPolicy(live),
28510
28629
  authorityStrict: authority.committedStrict,
28511
- gateProvenGreen: await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles),
28630
+ gateProvenGreen,
28512
28631
  unsafeContexts: unsafe
28513
28632
  });
28514
28633
  if (!plan.shouldActivate) {
@@ -28517,15 +28636,49 @@ async function applyCiReconcileRepo(repo, deps) {
28517
28636
  return finalizeCiReconcile(repo, deps, result, report, plan.holdReason);
28518
28637
  }
28519
28638
  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
28639
  }
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);
28640
+ if (plan.shouldActivate) {
28641
+ try {
28642
+ const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, plan.targetEnforcement);
28643
+ if (activation.action === "skipped") result.skipped.push(activation.detail ?? "product ruleset");
28644
+ else result.applied.push(`product ruleset ${activation.action}${activation.detail ? `: ${activation.detail}` : ""}`);
28645
+ } catch (e) {
28646
+ result.errors.push(e.message);
28647
+ return finalizeCiReconcile(repo, deps, result, report);
28648
+ }
28649
+ }
28650
+ if (pendingPlan) {
28651
+ if (pendingPlan.pending.length > 0 && !gateProvenGreen) {
28652
+ const reason = "pending required-check rulesets left non-enforcing \u2014 the gate is not green on development";
28653
+ result.skipped.push(reason);
28654
+ return finalizeCiReconcile(repo, deps, result, report, reason);
28655
+ }
28656
+ try {
28657
+ const desired = /* @__PURE__ */ new Set();
28658
+ for (const entry of pendingPlan.pending) {
28659
+ const name = PRODUCT_PENDING_RULESET_PREFIX + entry.branch;
28660
+ desired.add(name);
28661
+ const statusOnly = {
28662
+ ...authority.apiPayload,
28663
+ name,
28664
+ rules: authority.apiPayload.rules.filter((rule) => rule.type === "required_status_checks")
28665
+ };
28666
+ const payload = patchRulesetBranchIncludes(
28667
+ patchRulesetRequiredContexts(statusOnly, entry.contexts),
28668
+ [entry.branch]
28669
+ );
28670
+ const activation = await activateProductRuleset(repo, payload, deps.client);
28671
+ (activation.action === "skipped" ? result.skipped : result.applied).push(`${name}: ${activation.action}`);
28672
+ }
28673
+ const list = await deps.client.rest("GET", `repos/${repo}/rulesets`);
28674
+ for (const stale of list ?? []) {
28675
+ if (!stale.name?.startsWith(PRODUCT_PENDING_RULESET_PREFIX) || desired.has(stale.name) || stale.id == null) continue;
28676
+ await deps.client.rest("DELETE", `repos/${repo}/rulesets/${stale.id}`);
28677
+ result.applied.push(`deleted converged ${stale.name}`);
28678
+ }
28679
+ } catch (e) {
28680
+ result.errors.push(`pending ruleset convergence failed: ${e.message}`);
28681
+ }
28529
28682
  }
28530
28683
  return finalizeCiReconcile(repo, deps, result, report);
28531
28684
  }
@@ -35471,6 +35624,16 @@ async function suiteRcand(deps, opts) {
35471
35624
  ];
35472
35625
  if (opts.dryRun) return { plan };
35473
35626
  let record = readSuiteCut(deps.recordPath);
35627
+ if (record?.leadReleased && record.announcement === "pending") {
35628
+ const reason = await deps.announceSkipReason(record);
35629
+ if (!reason) throw new Error(`the finished cut ${record.leadRcTag} has an unverified announcement (pending); verify whether it was posted, then finish it with suite release --owner-go before a new cut (never post twice)`);
35630
+ record.announcement = "skipped";
35631
+ record.announcementNote = reason;
35632
+ writeSuiteCut(deps.recordPath, record);
35633
+ }
35634
+ if (record?.leadReleased && !record.announcement) {
35635
+ throw new Error(`the cut ${record.leadRcTag} already released the lead but never announced; finish it with suite release --owner-go before a new cut`);
35636
+ }
35474
35637
  if ((record?.announcement === "sent" || record?.announcement === "skipped") && record.leadReleased) {
35475
35638
  const archive = `${deps.recordPath}.${record.leadRcTag}.json`;
35476
35639
  if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
@@ -35512,6 +35675,17 @@ async function suiteRcand(deps, opts) {
35512
35675
  writeSuiteCut(deps.recordPath, record);
35513
35676
  continue;
35514
35677
  }
35678
+ const onHead = [];
35679
+ for (const tag of (await deps.rcTagsAt(member.repo, member.previousRc)).filter((t) => RC_TAG.test(t))) {
35680
+ if (!await deps.tagSha(member.repo, suiteReleaseTag(tag))) onHead.push(tag);
35681
+ }
35682
+ if (onHead.length > 1) throw new Error(`${member.repo}: several unreleased rc tags sit on its rc head (${onHead.join(", ")}); pin the one to reuse before the suite cut`);
35683
+ if (onHead.length === 1) {
35684
+ member.tag = onHead[0];
35685
+ member.sha = member.previousRc;
35686
+ writeSuiteCut(deps.recordPath, record);
35687
+ continue;
35688
+ }
35515
35689
  quiet.push(member);
35516
35690
  }
35517
35691
  if (record.members.every((m) => m.carried || quiet.includes(m))) {
@@ -36704,7 +36878,8 @@ ${decision.note}`);
36704
36878
  if (releaseExists) {
36705
36879
  releaseNote = `Release ${tag} already exists \u2014 resumed without recreating`;
36706
36880
  } else {
36707
- await deps.run("gh", ["release", "create", tag, "--repo", ctx.repo, "--target", "main", "--generate-notes", "--latest"]);
36881
+ const cutBy = (await ledger.load(anchors))?.cutBy ?? describeSessionIdentity();
36882
+ await deps.run("gh", ["release", "create", tag, "--repo", ctx.repo, "--target", "main", "--notes", releaseCutByNotes(cutBy), "--generate-notes", "--latest"]);
36708
36883
  releaseNote = `Release ${tag} created (target main)`;
36709
36884
  }
36710
36885
  await writePlainWordsSection(deps, options, ctx.repo, meta, tag);
@@ -36828,7 +37003,7 @@ ${decision.note}`);
36828
37003
  foldNote,
36829
37004
  foldStatus,
36830
37005
  announceNote,
36831
- ...ledgerReport ? { ledger: ledgerReport } : {}
37006
+ ...ledgerReport ? { cutBy: ledgerReport.cutBy, ledger: ledgerReport } : {}
36832
37007
  };
36833
37008
  if (TRAIN_BRANCHES.includes(startBranch)) {
36834
37009
  return withCheckoutRestoredAfterRelease(deps, result, startBranch);
@@ -36878,7 +37053,7 @@ async function runHotfixRetryDeployLocked(deps, versionInput) {
36878
37053
  ...foldNote ? { foldNote } : {},
36879
37054
  ...foldStatus ? { foldStatus } : {},
36880
37055
  ...runs ? { runs } : {},
36881
- ...report ? { ledger: report } : {}
37056
+ ...report ? { cutBy: report.cutBy, ledger: report } : {}
36882
37057
  };
36883
37058
  }
36884
37059
  function deriveHotfixState(f) {
@@ -44630,6 +44805,16 @@ var GIT_COMMIT_TIMEOUT_MS = 6e4;
44630
44805
  function trainGitArgs(args) {
44631
44806
  return ["-c", "gc.auto=0", "-c", "maintenance.auto=false", ...args];
44632
44807
  }
44808
+ async function announceSummaryLineCountRefusal(path2) {
44809
+ let content;
44810
+ try {
44811
+ content = await (0, import_promises9.readFile)(path2, "utf8");
44812
+ } catch (e) {
44813
+ return `release: could not read --announce-summary-file ${path2}: ${e.message} \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`;
44814
+ }
44815
+ const lines2 = summaryFileLines(content);
44816
+ return lines2.length < 3 || lines2.length > 6 ? `release: --announce-summary-file must contain 3-6 non-empty LLM-curated lines (found ${lines2.length}) \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing` : null;
44817
+ }
44633
44818
  async function announceSummaryRefusal(path2, isHub = false) {
44634
44819
  let lines2;
44635
44820
  try {
@@ -45083,6 +45268,10 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
45083
45268
  const retryDeploy = new Option("--retry-deploy", "re-dispatch the FAILED rc deploy of the promoted candidate in the active rcand ledger \u2014 same tag and SHA, never re-cut or re-pushed (#7053)");
45084
45269
  trainCmd.addOption(commandName === "rcand" ? retryDeploy : retryDeploy.hideHelp());
45085
45270
  trainCmd.option("--repo <owner/repo>", "dry-run plan for a target repo without relying on the current checkout; --apply still uses the current checkout").action(async (o) => {
45271
+ if (commandName === "release" && o.announceSummaryFile) {
45272
+ const refusal = await announceSummaryLineCountRefusal(o.announceSummaryFile);
45273
+ if (refusal) return fail(refusal);
45274
+ }
45086
45275
  try {
45087
45276
  await requireFreshTrainCli(commandName);
45088
45277
  } catch (e) {
@@ -45555,12 +45744,18 @@ async function withEnv(name, value, fn) {
45555
45744
  }
45556
45745
  }
45557
45746
  async function prepareRcCheckout(git3, repo) {
45558
- if ((await git3(["status", "--porcelain"])).trim()) throw new Error(`${repo}: checkout has uncommitted changes; refuse to switch it to rc for release`);
45747
+ await prepareLaneCheckout(git3, repo, "rc", "release");
45748
+ }
45749
+ async function prepareDevelopmentCheckout(git3, repo) {
45750
+ await prepareLaneCheckout(git3, repo, "development", "rcand");
45751
+ }
45752
+ async function prepareLaneCheckout(git3, repo, branch, lane) {
45753
+ if ((await git3(["status", "--porcelain"])).trim()) throw new Error(`${repo}: checkout has uncommitted changes; refuse to switch it to ${branch} for ${lane}`);
45559
45754
  await git3(["fetch", "origin", "--tags"]);
45560
- await git3(["switch", "rc"]);
45561
- await git3(["merge", "--ff-only", "origin/rc"]);
45562
- const [head, origin] = (await git3(["rev-parse", "HEAD", "origin/rc"])).trim().split(/\s+/);
45563
- if (!head || head !== origin) throw new Error(`${repo}: local rc ${head?.slice(0, 12)} is not origin/rc ${origin?.slice(0, 12)}; stop`);
45755
+ await git3(["switch", branch]);
45756
+ await git3(["merge", "--ff-only", `origin/${branch}`]);
45757
+ const [head, origin] = (await git3(["rev-parse", "HEAD", `origin/${branch}`])).trim().split(/\s+/);
45758
+ if (!head || head !== origin) throw new Error(`${repo}: local ${branch} ${head?.slice(0, 12)} is not origin/${branch} ${origin?.slice(0, 12)}; stop`);
45564
45759
  }
45565
45760
  function rcRunCauseFromLog(log) {
45566
45761
  const causes = [...new Set([...log.matchAll(/##\[error\](?!Process completed with exit code)([^\r\n]+)/g)].map((m) => m[1].trim()).filter(Boolean))];
@@ -45637,6 +45832,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}) {
45637
45832
  }
45638
45833
  };
45639
45834
  const runRcand = async (repo, previousRc, rcWasAbsent) => inTrain(repo, async (deps) => {
45835
+ if (repo !== SUITE_REPO) await prepareDevelopmentCheckout((args) => deps.run("git", args), repo);
45640
45836
  await deps.run("git", ["fetch", "origin", "--tags"]);
45641
45837
  let rcNow;
45642
45838
  try {
@@ -45786,6 +45982,12 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}) {
45786
45982
  }
45787
45983
  },
45788
45984
  tagSha,
45985
+ rcTagsAt: async (repo, sha) => {
45986
+ if (!/^[0-9a-f]{40}$/i.test(sha)) return [];
45987
+ const target = await checkout(repo);
45988
+ await exec("git", ["fetch", "--quiet", "origin", "--tags"], target, WATCH_TIMEOUT);
45989
+ return (await exec("git", ["tag", "--points-at", sha, "--list", "v*-rc.*"], target)).split("\n").map((t) => t.trim()).filter(Boolean);
45990
+ },
45789
45991
  contains: async (repo, ancestor, descendant) => {
45790
45992
  const target = await checkout(repo);
45791
45993
  await exec("git", ["fetch", "origin", "--tags"], target);
@@ -51396,9 +51598,13 @@ program2.addHelpText(
51396
51598
  \`mmi-cli commands\` shows the agentic map; \`mmi-cli <house> --help\` lists one house.
51397
51599
  `
51398
51600
  );
51601
+ var appActorCommand = "poll";
51399
51602
  function appActorDeps() {
51400
51603
  return {
51401
- fetchSecret: async (key) => fetchSecretValue(makeSecretsDeps(await loadConfig()), key, { repo: APP_VAULT_REPO }),
51604
+ fetchSecret: async (key) => fetchSecretValue(makeSecretsDeps(await loadConfig()), key, {
51605
+ repo: APP_VAULT_REPO,
51606
+ attribution: { command: appActorCommand, via: formatSessionIdentity(describeSessionIdentity()) }
51607
+ }),
51402
51608
  fetch: (url, init) => fetch(url, init)
51403
51609
  };
51404
51610
  }
@@ -51406,6 +51612,7 @@ setPollTokenProvider(() => pollTokenOrUndefined(appActorDeps()));
51406
51612
  var registerPreAction = Reflect.get(program2, ["h", "ook"].join(""));
51407
51613
  registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand) => {
51408
51614
  const commandPath3 = commandPathOf(actionCommand);
51615
+ appActorCommand = commandPath3;
51409
51616
  emitCliCallTelemetry(commandPath3);
51410
51617
  try {
51411
51618
  await activateAppActor(commandPath3, process.env, () => mintInstallationToken(appActorDeps()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.61",
3
+ "version": "4.5.63",
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",