@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.
- package/dist/main.cjs +256 -49
- 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
|
|
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
|
|
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.
|
|
17155
|
-
tag: "v4.5.
|
|
17156
|
-
commit: "
|
|
17157
|
-
npm: "@mutmutco/cli@4.5.
|
|
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.
|
|
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.
|
|
17181
|
-
v3Target: "v4.5.
|
|
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
|
|
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
|
|
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 =
|
|
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 ? `${
|
|
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 =
|
|
27896
|
-
const tagBlind =
|
|
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) =>
|
|
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
|
|
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
|
-
|
|
28523
|
-
|
|
28524
|
-
|
|
28525
|
-
|
|
28526
|
-
|
|
28527
|
-
|
|
28528
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
45561
|
-
await git3(["merge", "--ff-only",
|
|
45562
|
-
const [head, origin] = (await git3(["rev-parse", "HEAD",
|
|
45563
|
-
if (!head || head !== origin) throw new Error(`${repo}: local
|
|
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, {
|
|
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