@mutmutco/cli 4.5.160 → 4.5.162

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 CHANGED
@@ -76,19 +76,19 @@ function hardExit(code) {
76
76
  process.exit(code);
77
77
  }
78
78
  function flushStream(stream) {
79
- return new Promise((resolve8) => {
79
+ return new Promise((resolve7) => {
80
80
  try {
81
- stream.write("", () => resolve8());
81
+ stream.write("", () => resolve7());
82
82
  } catch {
83
- resolve8();
83
+ resolve7();
84
84
  }
85
85
  });
86
86
  }
87
87
  async function flushStdio(timeoutMs = STDIO_FLUSH_TIMEOUT_MS) {
88
88
  await Promise.race([
89
89
  Promise.all([flushStream(process.stdout), flushStream(process.stderr)]),
90
- new Promise((resolve8) => {
91
- setTimeout(resolve8, timeoutMs).unref?.();
90
+ new Promise((resolve7) => {
91
+ setTimeout(resolve7, timeoutMs).unref?.();
92
92
  })
93
93
  ]);
94
94
  }
@@ -96,7 +96,7 @@ async function cleanExit(code) {
96
96
  process.exitCode = code;
97
97
  await closeHttpPool();
98
98
  await flushStdio();
99
- await new Promise((resolve8) => setImmediate(resolve8));
99
+ await new Promise((resolve7) => setImmediate(resolve7));
100
100
  return void 0;
101
101
  }
102
102
  async function finishCliRun(watchdogMs = CLI_EXIT_WATCHDOG_MS) {
@@ -590,6 +590,11 @@ var init_github_client = __esm({
590
590
  });
591
591
 
592
592
  // src/gh-read-route.ts
593
+ function isAppFilingWrite(args) {
594
+ if (args.some((a) => VIEWER.test(a))) return false;
595
+ const [group, verb] = args;
596
+ return (group === "issue" || group === "label") && verb === "create";
597
+ }
593
598
  function isViewerFreeGhRead(args) {
594
599
  if (args.some((a) => VIEWER.test(a))) return false;
595
600
  const [group, verb] = args;
@@ -692,7 +697,7 @@ function rateLimitedReceipt(opts) {
692
697
  };
693
698
  }
694
699
  async function runWithRateLimitBackoff(operation, opts) {
695
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
700
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
696
701
  const now = opts.now ?? Date.now;
697
702
  const log = opts.log ?? ((message2) => console.warn(message2));
698
703
  const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
@@ -782,7 +787,7 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
782
787
  const attempts = opts.attempts ?? 3;
783
788
  const baseDelayMs = opts.baseDelayMs ?? 250;
784
789
  const retryOn = opts.retryOn ?? ((res) => res.status >= 500);
785
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
790
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
786
791
  let lastErr;
787
792
  for (let i = 0; i < attempts; i++) {
788
793
  const isLast = i === attempts - 1;
@@ -1109,8 +1114,8 @@ async function readStdin(opts = {}) {
1109
1114
  })().catch(() => {
1110
1115
  });
1111
1116
  let timer;
1112
- const timeout = new Promise((resolve8) => {
1113
- timer = setTimeout(resolve8, timeoutMs);
1117
+ const timeout = new Promise((resolve7) => {
1118
+ timer = setTimeout(resolve7, timeoutMs);
1114
1119
  });
1115
1120
  try {
1116
1121
  await Promise.race([drain, timeout]);
@@ -1161,11 +1166,21 @@ __export(cli_shared_exports, {
1161
1166
  readStdin: () => readStdin,
1162
1167
  repoProject: () => repoProject,
1163
1168
  setGhReadTokenProvider: () => setGhReadTokenProvider,
1169
+ setGhWriteTokenProvider: () => setGhWriteTokenProvider,
1164
1170
  setInjectedStdin: () => setInjectedStdin
1165
1171
  });
1166
1172
  function setGhReadTokenProvider(provider) {
1167
1173
  ghReadTokenProvider = provider;
1168
1174
  }
1175
+ function setGhWriteTokenProvider(provider) {
1176
+ ghWriteTokenProvider = provider;
1177
+ }
1178
+ async function ghFilingAppEnv(file, args) {
1179
+ if (!ghWriteTokenProvider || !/(^|[\\/])gh(\.exe)?$/i.test(file) || process.env.MMI_ACTOR?.trim() === "personal") return void 0;
1180
+ if (!isAppFilingWrite(args)) return void 0;
1181
+ const token = await ghWriteTokenProvider().catch(() => void 0);
1182
+ return token ? { ...process.env, GH_TOKEN: token } : void 0;
1183
+ }
1169
1184
  async function ghReadAppEnv(file, args) {
1170
1185
  if (!ghReadTokenProvider || !/(^|[\\/])gh(\.exe)?$/i.test(file) || process.env.MMI_ACTOR?.trim() === "personal") return void 0;
1171
1186
  if (!isViewerFreeGhRead(args)) return void 0;
@@ -1187,12 +1202,12 @@ function killProcessTree(pid) {
1187
1202
  function execFileHard(file, args, options) {
1188
1203
  const { timeout, step, ...rest } = options;
1189
1204
  const started = Date.now();
1190
- return new Promise((resolve8, reject) => {
1205
+ return new Promise((resolve7, reject) => {
1191
1206
  const child2 = (0, import_node_child_process2.execFile)(file, args, { encoding: "utf8", windowsHide: true, ...rest, timeout: 0 }, (error, stdout, stderr) => {
1192
1207
  clearTimeout(timer);
1193
1208
  if (expired) return;
1194
1209
  if (error) reject(error);
1195
- else resolve8({ stdout: String(stdout), stderr: String(stderr) });
1210
+ else resolve7({ stdout: String(stdout), stderr: String(stderr) });
1196
1211
  });
1197
1212
  let expired = false;
1198
1213
  const timer = setTimeout(() => {
@@ -1340,7 +1355,7 @@ function printLine(value) {
1340
1355
  (0, import_node_fs5.writeSync)(1, `${value}
1341
1356
  `);
1342
1357
  }
1343
- var import_node_child_process2, import_node_fs5, import_node_util3, rawExecFileP, DEFAULT_EXEC_TIMEOUT_MS, execFileP, ghReadTokenProvider, GIT_TIMEOUT_MS, ExecDeadlineError, cachedGithubLogin, gitOut, consoleIo;
1358
+ var import_node_child_process2, import_node_fs5, import_node_util3, rawExecFileP, DEFAULT_EXEC_TIMEOUT_MS, execFileP, ghReadTokenProvider, ghWriteTokenProvider, GIT_TIMEOUT_MS, ExecDeadlineError, cachedGithubLogin, gitOut, consoleIo;
1344
1359
  var init_cli_shared = __esm({
1345
1360
  "src/cli-shared.ts"() {
1346
1361
  "use strict";
@@ -1364,7 +1379,7 @@ var init_cli_shared = __esm({
1364
1379
  // promisify(execFile)'s overloads widen to string|Buffer when options is spread in.
1365
1380
  rawExecFileP(file, args, { encoding: "utf8", windowsHide: true, timeout: DEFAULT_EXEC_TIMEOUT_MS, killSignal: "SIGTERM", ...options, ...env ? { env } : {} })
1366
1381
  );
1367
- const appEnv = options.env ? void 0 : await ghReadAppEnv(file, args);
1382
+ const appEnv = options.env ? void 0 : await ghReadAppEnv(file, args) ?? await ghFilingAppEnv(file, args);
1368
1383
  if (!appEnv) return run();
1369
1384
  try {
1370
1385
  return await run(appEnv);
@@ -6664,7 +6679,6 @@ var import_node_fs14 = require("node:fs");
6664
6679
  var import_node_path11 = require("node:path");
6665
6680
  var PLAN_ADVISORY_AGE_MS = 30 * 24 * 36e5;
6666
6681
  var ROOT_SCRATCH_STALE_MS = 24 * 36e5;
6667
- var SCRATCH_GC_THROTTLE_MS = 24 * 36e5;
6668
6682
  function executeScratchGc(repoRoot2, opts, now = Date.now()) {
6669
6683
  const snap = collectScratchSnapshot(repoRoot2);
6670
6684
  const plan = planScratchGc(snap, now);
@@ -7239,7 +7253,9 @@ async function mintInstallationToken(deps) {
7239
7253
  return mintScopedInstallationToken(deps, APP_POLL_PERMISSIONS);
7240
7254
  }
7241
7255
  var APP_WRITE_COMMANDS = /* @__PURE__ */ new Map([
7242
- ["issue comment", { metadata: "read", issues: "write", pull_requests: "write" }]
7256
+ ["issue comment", { metadata: "read", issues: "write", pull_requests: "write" }],
7257
+ // #8401: the detached related-issue lookup (issue list/view reads + one marker comment) — no identity contract.
7258
+ ["issue discover-related", { metadata: "read", issues: "write" }]
7243
7259
  ]);
7244
7260
  async function mintCommandWriteToken(deps, commandPath3) {
7245
7261
  const permissions = APP_WRITE_COMMANDS.get(commandPath3);
@@ -7251,6 +7267,14 @@ async function mintCommandWriteToken(deps, commandPath3) {
7251
7267
  throw new AppActorError(`app actor: write token mint failed (${e?.constructor?.name ?? "Error"})`);
7252
7268
  }
7253
7269
  }
7270
+ async function mintFilingWriteToken(deps) {
7271
+ try {
7272
+ return await mintFromMember(deps, { metadata: "read", issues: "write" }, APP_VAULT_KEYS);
7273
+ } catch (e) {
7274
+ if (e instanceof AppActorError) throw e;
7275
+ throw new AppActorError(`app actor: filing token mint failed (${e?.constructor?.name ?? "Error"})`);
7276
+ }
7277
+ }
7254
7278
  async function mintStatusWriteToken(deps) {
7255
7279
  return mintScopedInstallationToken(deps, { metadata: "read", statuses: "write" });
7256
7280
  }
@@ -8201,7 +8225,7 @@ async function readRestPrSnapshotWithRetry(prNumber, repo, gh2 = defaultGhApi, o
8201
8225
  const retries = options?.retries ?? PR_SNAPSHOT_READ_RETRIES;
8202
8226
  const untilMs = options?.retryTransientUntilMs;
8203
8227
  const delayMs = options?.delayMs ?? (untilMs !== void 0 ? PR_CHECKS_POLL_MS : PR_SNAPSHOT_READ_DELAY_MS);
8204
- const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
8228
+ const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
8205
8229
  const now = options?.now ?? (() => Date.now());
8206
8230
  let lastError = "no attempt completed";
8207
8231
  let attempt = 0;
@@ -10181,15 +10205,15 @@ async function postJson(pathSuffix, payload, deps, method = "POST", opts = {}) {
10181
10205
  return { ok: false, status: 0, body: null, error: e.message, unreachable: "transport" };
10182
10206
  }
10183
10207
  }
10184
- async function edgeDnsForStage(slug, stage, deps, ensure = false) {
10208
+ async function edgeDnsForStage(slug, stage, deps, ensure = false, deletion) {
10185
10209
  if (!deps.baseUrl) throw new Error("Hub API URL unavailable");
10186
10210
  const token = await deps.token();
10187
10211
  if (!token) throw new Error("Hub session token unavailable");
10188
10212
  const path2 = `/projects/${encodeURIComponent(slug)}/edge-dns/${stage}`;
10189
10213
  let body;
10190
- if (ensure) {
10191
- const write = await postJson(path2, {}, deps, "POST", { noRetry: true });
10192
- if (!write.ok) throw new Error(`Hub edge dns ensure failed: ${write.error ?? `HTTP ${write.status}`}`);
10214
+ if (ensure || deletion) {
10215
+ const write = await postJson(path2, deletion ?? {}, deps, deletion ? "DELETE" : "POST", { noRetry: true });
10216
+ if (!write.ok) throw new Error(`Hub edge dns ${deletion ? "delete" : "ensure"} failed: ${write.error ?? `HTTP ${write.status}`}`);
10193
10217
  body = write.body;
10194
10218
  } else {
10195
10219
  const res = await retriedFetch(
@@ -10203,6 +10227,20 @@ async function edgeDnsForStage(slug, stage, deps, ensure = false) {
10203
10227
  if (typeof body?.required !== "boolean") throw new Error("Hub edge dns response invalid");
10204
10228
  return body;
10205
10229
  }
10230
+ async function boxPreviewsForHost(box, opts, deps) {
10231
+ const payload = {
10232
+ box,
10233
+ ...opts.portStart !== void 0 ? { portStart: opts.portStart } : {},
10234
+ ...opts.cap !== void 0 ? { cap: opts.cap } : {},
10235
+ apply: opts.apply === true
10236
+ };
10237
+ const write = await postJson("/projects/box-previews", payload, deps, "POST", { noRetry: opts.apply === true });
10238
+ if (!write.ok) {
10239
+ const detail = write.body?.error;
10240
+ throw new Error(`Hub box previews failed: ${detail ?? write.error ?? `HTTP ${write.status}`}`);
10241
+ }
10242
+ return write.body;
10243
+ }
10206
10244
  async function edgeCertificateForStage(slug, stage, deps, ensure = false) {
10207
10245
  if (!deps.baseUrl) throw new Error("Hub API URL unavailable");
10208
10246
  const token = await deps.token();
@@ -10312,7 +10350,7 @@ init_github_client();
10312
10350
  // src/project-model.ts
10313
10351
  var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-app", "desktop-game", "mobile-app", "non-deployable", "cli-tool", "worker"];
10314
10352
  var DEPLOY_MODELS = ["hub-serverless", "serverless", "tenant-container", "solo-container", "static-cdn", "registry-publish", "content", "none", "vercel"];
10315
- var RELEASE_TRACKS = ["full", "direct", "trunk"];
10353
+ var RELEASE_TRACKS = ["full", "direct", "trunk", "single"];
10316
10354
  var REQUIRED_CHECK_BRANCHES = ["development", "rc", "main"];
10317
10355
  var PROJECT_TYPE_SET = new Set(PROJECT_TYPES);
10318
10356
  var DEPLOY_MODEL_SET = new Set(DEPLOY_MODELS);
@@ -10395,6 +10433,7 @@ function metaWithDeclaredClass(meta, declaredClass) {
10395
10433
  }
10396
10434
  function branchesForTrack(track) {
10397
10435
  if (track === "trunk") return ["main"];
10436
+ if (track === "single") return ["development"];
10398
10437
  if (track === "direct") return ["development", "main"];
10399
10438
  return ["development", "rc", "main"];
10400
10439
  }
@@ -10406,7 +10445,7 @@ function resolveRequiredCheckBranches(meta, repo) {
10406
10445
  return branchesForTrack(resolveReleaseTrack(meta, void 0, repo));
10407
10446
  }
10408
10447
  function promotionBranchesForTrack(track) {
10409
- if (track === "trunk") return [];
10448
+ if (track === "trunk" || track === "single") return [];
10410
10449
  if (track === "direct") return ["main"];
10411
10450
  return ["rc", "main"];
10412
10451
  }
@@ -12062,7 +12101,7 @@ async function createPrViaRestFallback(args, swappedArgs, deps, knownPools) {
12062
12101
  }
12063
12102
  async function ghCreate(args, deps = {}) {
12064
12103
  const exec3 = deps.exec ?? execFileP;
12065
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
12104
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
12066
12105
  const now = deps.now ?? Date.now;
12067
12106
  const read = deps.readFile ?? import_promises.readFile;
12068
12107
  const restCreatePr = deps.restCreatePr ?? ((input) => defaultRestCreatePr(exec3, input));
@@ -12890,8 +12929,8 @@ var PRE_SPAWN_DRAIN_TIMEOUT_MS = 2e3;
12890
12929
  async function drainHttpPoolBeforeSpawn() {
12891
12930
  const drained = await Promise.race([
12892
12931
  closeHttpPool().then(() => true),
12893
- new Promise((resolve8) => {
12894
- setTimeout(() => resolve8(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
12932
+ new Promise((resolve7) => {
12933
+ setTimeout(() => resolve7(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
12895
12934
  })
12896
12935
  ]);
12897
12936
  if (!drained) destroyHttpPool();
@@ -15579,7 +15618,7 @@ async function setBoardItemPriority(client, cfg, itemId, priority) {
15579
15618
  await updateItemSingleSelect(client, cfg.projectId, itemId, cfg.priorityFieldId, optionId);
15580
15619
  return cliPriorityToFieldName(priority);
15581
15620
  }
15582
- var defaultRetrySleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
15621
+ var defaultRetrySleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
15583
15622
  async function resolveProjectItemIdWithRetry(client, cfg, selector, opts = {}) {
15584
15623
  const attempts = Math.max(1, opts.attempts ?? 5);
15585
15624
  const delayMs = opts.delayMs ?? 300;
@@ -16499,7 +16538,7 @@ function checkRunbookCoverage(docs, base, git3) {
16499
16538
  const globs = lines2.slice(heading.start, markerEnd - 1).flatMap((text) => /<!--\s*runbook-covers:\s*(.*?)\s*-->/.exec(text)?.[1].split(",").map((s) => s.trim()) ?? []);
16500
16539
  const affected = changed.filter((path2) => globs.some((glob) => matches(glob, path2)));
16501
16540
  if (affected.length && !ranges.some((r) => r.count > 0 && r.start < end && r.start + r.count > heading.start)) {
16502
- findings.push({ kind: "runbook-section-stale", doc, line: heading.start, detail: `${heading.title}: changed ${affected.join(", ")}; update this section or declare Runbook-Unaffected: <reason>` });
16541
+ findings.push({ kind: "runbook-section-stale", doc, line: heading.start, detail: `${heading.title}: changed ${affected.join(", ")}; update this section or declare Runbook-Unaffected: <reason>`, section: { heading: heading.title, level: heading.level, startLine: heading.start, endLine: end - 1 }, sources: affected });
16503
16542
  }
16504
16543
  }
16505
16544
  }
@@ -17182,7 +17221,7 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
17182
17221
  GATE_EXTRA_STEPS_YAML: ""
17183
17222
  };
17184
17223
  const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
17185
- const trackBranches = track === "trunk" ? ["main"] : track === "direct" ? ["development", "main"] : ["development", "rc", "main"];
17224
+ const trackBranches = track === "trunk" ? ["main"] : track === "single" ? ["development"] : track === "direct" ? ["development", "main"] : ["development", "rc", "main"];
17186
17225
  const rulesetBranches = requiredCheckBranches?.length ? [...requiredCheckBranches] : trackBranches;
17187
17226
  const rulesetRefs = JSON.stringify(rulesetBranches.map((branch) => `refs/heads/${branch}`));
17188
17227
  const rulesetContexts = JSON.stringify((requiredChecks?.length ? requiredChecks : ["gate"]).map((context) => ({ context })));
@@ -17195,6 +17234,15 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
17195
17234
  GATE_RULESET_CONTEXTS_JSON: rulesetContexts
17196
17235
  };
17197
17236
  }
17237
+ if (track === "single") {
17238
+ return {
17239
+ ...runtimeVars,
17240
+ GATE_PUSH_BRANCHES_YAML: "[development]",
17241
+ GATE_FULL_RUN_BRANCH: "development",
17242
+ GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs,
17243
+ GATE_RULESET_CONTEXTS_JSON: rulesetContexts
17244
+ };
17245
+ }
17198
17246
  if (track === "direct") {
17199
17247
  return {
17200
17248
  ...runtimeVars,
@@ -17519,7 +17567,7 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
17519
17567
  throw new Error(`deployModel must be one of: ${DEPLOY_MODELS.join(", ")}`);
17520
17568
  }
17521
17569
  if (options.releaseTrack && !isReleaseTrack(options.releaseTrack)) {
17522
- throw new Error("releaseTrack must be full, direct, or trunk");
17570
+ throw new Error("releaseTrack must be full, direct, trunk, or single");
17523
17571
  }
17524
17572
  const shape = {
17525
17573
  class: cls,
@@ -18647,7 +18695,7 @@ function classifyRow(row) {
18647
18695
  } else if (row.onboarding.status === "unknown") {
18648
18696
  reasons.add(`canonical onboarding link evidence unknown: ${row.onboarding.detail}`);
18649
18697
  }
18650
- const releaseRequired = row.track !== "trunk" && !row.classifications.includes("no-deploy-surface");
18698
+ const releaseRequired = row.track !== "trunk" && row.track !== "single" && !row.classifications.includes("no-deploy-surface");
18651
18699
  const tagIsVersion = Boolean(row.release.version) && row.unknowns.some((u) => u.startsWith(NO_VERSION_LOCK_PREFIX));
18652
18700
  for (const unknown of row.unknowns) {
18653
18701
  if (!releaseRequired && unknown === NO_RELEASE_UNKNOWN) continue;
@@ -18909,10 +18957,10 @@ var rollout_plan_default = {
18909
18957
  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)."
18910
18958
  },
18911
18959
  baseline: {
18912
- version: "4.5.160",
18913
- tag: "v4.5.160",
18914
- commit: "05c3cc8d56ee",
18915
- npm: "@mutmutco/cli@4.5.160"
18960
+ version: "4.5.162",
18961
+ tag: "v4.5.162",
18962
+ commit: "5d12949c1714",
18963
+ npm: "@mutmutco/cli@4.5.162"
18916
18964
  },
18917
18965
  exitCriterion: "fleet-n-of-n",
18918
18966
  hubOnlyShortcut: "forbidden",
@@ -18929,14 +18977,14 @@ var rollout_plan_default = {
18929
18977
  repo: "mutmutco/mmi-hub",
18930
18978
  role: "canary",
18931
18979
  schedule: "train",
18932
- v3Target: "v4.5.160"
18980
+ v3Target: "v4.5.162"
18933
18981
  }
18934
18982
  ],
18935
18983
  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.",
18936
18984
  rollback: {
18937
18985
  independent: true,
18938
- mechanism: "npm dist-tag latest -> 4.5.160 and redeploy the Hub Lambda from tag v4.5.160 (05c3cc8d56ee); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
18939
- v3Target: "v4.5.160 (@mutmutco/cli@4.5.160, tag commit 05c3cc8d56ee \u2014 last known-good release carrying the repo-index v4-only contract)"
18986
+ mechanism: "npm dist-tag latest -> 4.5.162 and redeploy the Hub Lambda from tag v4.5.162 (5d12949c1714); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
18987
+ v3Target: "v4.5.162 (@mutmutco/cli@4.5.162, tag commit 5d12949c1714 \u2014 last known-good release carrying the repo-index v4-only contract)"
18940
18988
  }
18941
18989
  },
18942
18990
  {
@@ -20227,7 +20275,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
20227
20275
  unknowns
20228
20276
  };
20229
20277
  });
20230
- const tracks = { full: 0, direct: 0, trunk: 0 };
20278
+ const tracks = { full: 0, direct: 0, trunk: 0, single: 0 };
20231
20279
  for (const row of repos) tracks[row.track] += 1;
20232
20280
  return {
20233
20281
  generatedAt,
@@ -20420,13 +20468,13 @@ var FLEET_LOCKSTEP_REF_INCLUDE = ["refs/heads/rc", "refs/heads/main"];
20420
20468
  var STATUS_DESCRIPTION_MAX = 140;
20421
20469
  function classifyFleetEnrollment(rows) {
20422
20470
  return rows.map((row) => {
20423
- if (row.track === "trunk") {
20471
+ if (row.track === "trunk" || row.track === "single") {
20424
20472
  return {
20425
20473
  repo: row.repo,
20426
20474
  track: row.track,
20427
20475
  deployModel: row.deployModel,
20428
20476
  enrolled: false,
20429
- reason: "excluded: trunk track has no release train \u2014 no rc/main surface exists to gate (declared shape, not an exemption from conformance: the fleet verdict still counts this repo)"
20477
+ reason: "excluded: trunk/single track has no release train \u2014 no rc/main surface exists to gate (declared shape, not an exemption from conformance: the fleet verdict still counts this repo)"
20430
20478
  };
20431
20479
  }
20432
20480
  const noDeploy = row.classifications.includes("no-deploy-surface");
@@ -21023,7 +21071,7 @@ async function runMergeTreePreflight(deps, ours, theirs) {
21023
21071
  async function predictMergeConflicts(deps, ours, theirs) {
21024
21072
  return runMergeTreePreflight(deps, ours, theirs);
21025
21073
  }
21026
- async function mergeWithToleratedResolution(deps, sourceRef, label, resolve8, extraTolerated = []) {
21074
+ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve7, extraTolerated = []) {
21027
21075
  try {
21028
21076
  await deps.run("git", ["merge", sourceRef, "--no-edit"]);
21029
21077
  return;
@@ -21037,7 +21085,7 @@ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve8, ex
21037
21085
  unmerged.length === 0 ? `${label} merge failed without conflicted paths \u2014 merge aborted; inspect the repo state and rerun` : `${label} merge conflicts on untolerated path(s): ${blocking.join(", ")} \u2014 merge aborted (the train is misaligned; reconcile the branches via an approved alignment PR, then rerun)`
21038
21086
  );
21039
21087
  }
21040
- await deps.run("git", ["checkout", `--${resolve8}`, "--", ...unmerged]);
21088
+ await deps.run("git", ["checkout", `--${resolve7}`, "--", ...unmerged]);
21041
21089
  await deps.run("git", ["add", "--", ...unmerged]);
21042
21090
  await deps.run("git", ["commit", "--no-edit"]);
21043
21091
  }
@@ -21231,7 +21279,7 @@ var CORRELATE_SKEW_SLACK_MS = 1e4;
21231
21279
  var CORRELATE_PAGE_LIMIT = 50;
21232
21280
  var RUN_CONFIRM_ATTEMPTS = 3;
21233
21281
  var RUN_CONFIRM_DELAY_MS = 1e3;
21234
- var defaultSleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
21282
+ var defaultSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
21235
21283
  function resolveSleep(deps) {
21236
21284
  return deps.sleep ?? defaultSleep;
21237
21285
  }
@@ -21535,7 +21583,7 @@ async function enqueueAlignmentAutoMerge(deps, ctx, prNumber, prUrl, openedNote)
21535
21583
  }
21536
21584
  return { ...base, note: manual };
21537
21585
  }
21538
- var ALIGNMENT_LAND_POLL_MS = 15e3;
21586
+ var ALIGNMENT_LAND_POLL_MS = 6e4;
21539
21587
  var ALIGNMENT_LAND_TIMEOUT_MS = 20 * 6e4;
21540
21588
  async function awaitAlignmentLanded(deps, ctx, leg, base, opts = {}) {
21541
21589
  if (leg.status !== "pr-pending" || !leg.autoMergeEnqueued || leg.prNumber === void 0) return leg;
@@ -23152,7 +23200,7 @@ async function runTenantReconcile(deps, options) {
23152
23200
  };
23153
23201
  }
23154
23202
  function tenantControlWatches(action) {
23155
- return action === "status" || action === "retire" || action === "verify-secrets" || action === "verify-broker" || action === "logs" || action === "run-task";
23203
+ return action === "status" || action === "retire" || action === "undo" || action === "verify-secrets" || action === "verify-broker" || action === "logs" || action === "run-task";
23156
23204
  }
23157
23205
  async function runTenantControl(deps, options) {
23158
23206
  const { repo, stage, action } = options;
@@ -23799,8 +23847,6 @@ var import_node_path23 = require("node:path");
23799
23847
  // src/jerv-cli-spawn.ts
23800
23848
  var import_node_os13 = require("node:os");
23801
23849
  var import_node_path22 = require("node:path");
23802
- init_cli_shared();
23803
- var JERV_CLI_ENTRY = (0, import_node_path22.join)("node_modules", "@jervaise", "jerv-cli", "dist", "index.cjs");
23804
23850
  function pathEnvEntries(pathEnv, platform2 = process.platform) {
23805
23851
  if (platform2 !== "win32") {
23806
23852
  return pathEnv.split(import_node_path22.delimiter).map((e) => e.trim()).filter(Boolean);
@@ -24434,7 +24480,7 @@ function lockTokenPid(token) {
24434
24480
  const m = /^(\d+)-/.exec(token);
24435
24481
  return m ? Number(m[1]) : void 0;
24436
24482
  }
24437
- var sleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
24483
+ var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
24438
24484
  var IMMEDIATE_RETRY_BUDGET = 3;
24439
24485
  var FileLockBusyError = class extends Error {
24440
24486
  lockPath;
@@ -25310,7 +25356,7 @@ async function defaultWatchRun(deps, runId, repo) {
25310
25356
  }
25311
25357
  let confirmError;
25312
25358
  for (let attempt = 0; attempt < RUN_CONFIRM_ATTEMPTS2; attempt++) {
25313
- if (attempt > 0) await new Promise((resolve8) => setTimeout(resolve8, RUN_CONFIRM_DELAY_MS2));
25359
+ if (attempt > 0) await new Promise((resolve7) => setTimeout(resolve7, RUN_CONFIRM_DELAY_MS2));
25314
25360
  try {
25315
25361
  const parsed = JSON.parse(await deps.run("gh", ["run", "view", String(runId), "--repo", repo, "--json", "status,conclusion"]));
25316
25362
  if (parsed.status === "completed") return parsed.conclusion === "success" ? "success" : "failure";
@@ -26352,11 +26398,11 @@ var defaultDeps = {
26352
26398
  // #5813: the caller supplies that command line. `process.argv` is NOT it — the house shim splices
26353
26399
  // the house root out in place, so re-reading it here re-ran the removed flat alias (`release …`
26354
26400
  // instead of `devops release …`) and the converged child refused with exit 2.
26355
- reexec: (argv) => new Promise((resolve8) => {
26401
+ reexec: (argv) => new Promise((resolve7) => {
26356
26402
  const args = [...argv];
26357
26403
  const child2 = isWin2 ? (0, import_node_child_process8.spawn)("cmd.exe", ["/c", "mmi-cli", ...args], { stdio: "inherit", windowsHide: true }) : (0, import_node_child_process8.spawn)("mmi-cli", args, { stdio: "inherit", windowsHide: true });
26358
- child2.on("exit", (code) => resolve8(code ?? 1));
26359
- child2.on("error", () => resolve8(1));
26404
+ child2.on("exit", (code) => resolve7(code ?? 1));
26405
+ child2.on("error", () => resolve7(1));
26360
26406
  }),
26361
26407
  // stderr only — stdout may be carrying --json output.
26362
26408
  warn: (line) => console.error(line)
@@ -26464,7 +26510,7 @@ var defaultDeps2 = {
26464
26510
  }
26465
26511
  },
26466
26512
  registryWhoami: probeRegistryWhoami,
26467
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
26513
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
26468
26514
  env: process.env
26469
26515
  };
26470
26516
  function message(e) {
@@ -27479,6 +27525,7 @@ async function preflight(deps, ctx, stage, meta, lane) {
27479
27525
  if (model === "content") {
27480
27526
  throw new Error(`${ctx.repo} is a content repo (deployModel=content) \u2014 the release train does not apply (trunk-based; PR to main)`);
27481
27527
  }
27528
+ if (meta.releaseTrack === "single") throw new Error("single-track repos ship on merge; there is no release train");
27482
27529
  if (model === "none") {
27483
27530
  throw new Error(`${ctx.repo} is not Hub-deployed (deployModel=none) \u2014 the release train does not apply; use the project's own release path`);
27484
27531
  }
@@ -29727,9 +29774,9 @@ function rulesetCoversReleaseBranches(payload, track) {
29727
29774
  const conditions = payload.conditions;
29728
29775
  const include = conditions?.ref_name?.include;
29729
29776
  if (!Array.isArray(include)) {
29730
- return track === "full" || track === "direct" || track === "trunk";
29777
+ return track === "full" || track === "direct" || track === "trunk" || track === "single";
29731
29778
  }
29732
- const releaseRefs = track === "full" ? ["refs/heads/rc", "refs/heads/main"] : ["refs/heads/main"];
29779
+ const releaseRefs = track === "full" ? ["refs/heads/rc", "refs/heads/main"] : track === "single" ? ["refs/heads/development"] : ["refs/heads/main"];
29733
29780
  return include.some((entry) => typeof entry === "string" && (entry === "~DEFAULT_BRANCH" ? track === "trunk" : releaseRefs.some((ref) => refPatternCovers(entry, ref))));
29734
29781
  }
29735
29782
  function releaseBranchRefsCovered(conditions, track) {
@@ -32717,6 +32764,7 @@ function applyRemoteScript(items, date) {
32717
32764
 
32718
32765
  // src/box-commands.ts
32719
32766
  init_clean_exit();
32767
+ init_cli_shared();
32720
32768
 
32721
32769
  // src/secrets-commands.ts
32722
32770
  var import_node_fs31 = require("node:fs");
@@ -33592,6 +33640,14 @@ function buildSetDeployPatch(_slug, input) {
33592
33640
  if (!DEPLOY_DOMAIN_RE.test(a)) throw new Error(`org project set-deploy: --alias must be a DNS hostname, got ${JSON.stringify(a)}`);
33593
33641
  }
33594
33642
  const uniqueAliases = [...new Set(aliases)];
33643
+ const redirectFrom = [...new Set((input.redirectFrom ?? []).map((a) => clean4(a)).filter((a) => a !== void 0))];
33644
+ for (const entry of redirectFrom) {
33645
+ const [host, target, extra] = entry.split("=");
33646
+ if (!host || !DEPLOY_DOMAIN_RE.test(host) || extra !== void 0 || target !== void 0 && !DEPLOY_DOMAIN_RE.test(target)) throw new Error(`org project set-deploy: invalid --redirect-from ${JSON.stringify(entry)}`);
33647
+ const serving = [domain, ...uniqueAliases].map((a) => a?.toLowerCase());
33648
+ if (serving.includes(host.toLowerCase())) throw new Error("org project set-deploy: --redirect-from must not equal the primary domain or an alias");
33649
+ if (target !== void 0 && domain !== void 0 && (input.aliases !== void 0 || input.clearAliases) && !serving.includes(target.toLowerCase())) throw new Error("org project set-deploy: --redirect-from target must be the primary domain or an alias");
33650
+ }
33595
33651
  if (input.clearAliases && uniqueAliases.length) {
33596
33652
  throw new Error("org project set-deploy: --clear-aliases cannot be combined with --alias (they contradict)");
33597
33653
  }
@@ -33631,6 +33687,7 @@ function buildSetDeployPatch(_slug, input) {
33631
33687
  // #2986: an explicit `[]` is what the route reads as "clear them"; omitting the key is what it reads as
33632
33688
  // "keep them". Those are different instructions and they now have different flags.
33633
33689
  ...input.clearAliases ? { aliases: [] } : uniqueAliases.length ? { aliases: uniqueAliases } : {},
33690
+ ...input.redirectFrom !== void 0 ? { redirectFrom } : {},
33634
33691
  ...input.noEnvFile !== void 0 ? { noEnvFile: input.noEnvFile } : {},
33635
33692
  // #7353: an explicit `[]` is what the route reads as "clear them"; omitting the key is what it reads as
33636
33693
  // "keep them" — same patch contract as aliases (#2986/#2984).
@@ -34393,6 +34450,48 @@ ${SSH_RECIPE_AGENT_NOTE}`);
34393
34450
  await failGraceful(e.message);
34394
34451
  }
34395
34452
  });
34453
+ box.command("previews <name>").description("inspect or configure a box preview port range in BOX#<sshHost>/META (#8444; master-only, dry-run unless --apply)").option("--port-start <port>", "first port in the dedicated preview interval (>= 1024)", (v) => Number(v)).option("--cap <n>", "maximum concurrent PR previews on this box (1..20, default 3)", (v) => Number(v)).option("--apply", "write the preview port range to BOX#<sshHost>/META (default: dry-run)").option("--json", "machine-readable output").action(async (name, o) => {
34454
+ try {
34455
+ const { boxes, incomplete } = await fetchInventory();
34456
+ const result = lookupBox(name, boxes, incomplete);
34457
+ if (result.kind === "unknown") {
34458
+ warnIncomplete(result.reasons);
34459
+ await failGraceful(
34460
+ `runtime box: cannot confirm whether '${name}' exists \u2014 part of the inventory could not be read (see above). Refusing to answer "no such box" from a list I know is incomplete.`
34461
+ );
34462
+ return;
34463
+ }
34464
+ if (result.kind === "absent") {
34465
+ await failGraceful(`runtime box: no box named '${name}'. Known boxes: ${result.known.join(", ") || "(none)"}`);
34466
+ return;
34467
+ }
34468
+ const found = result.box;
34469
+ if (!found.address) {
34470
+ await failGraceful(`runtime box previews: box '${found.name}' has no public IPv4 address`);
34471
+ return;
34472
+ }
34473
+ const status = await boxPreviewsForHost(
34474
+ found.address,
34475
+ { portStart: o.portStart, cap: o.cap, apply: o.apply === true },
34476
+ registryClientDeps(await loadConfig())
34477
+ );
34478
+ const fmt = (r) => r ? `${r.ports.start}..${r.ports.end} (portStart=${r.previewPortStart}, cap=${r.previewCap})` : "(unconfigured)";
34479
+ if (o.json) {
34480
+ console.log(JSON.stringify({ ...status, box: found.name, address: found.address, incomplete }, null, 2));
34481
+ } else {
34482
+ console.log(
34483
+ `${found.name} (${found.address}) \u2014 ${status.apply ? "applied" : "dry-run"}
34484
+ current: ${fmt(status.current)}
34485
+ planned: ${fmt(status.planned)}
34486
+ live reservations: ${status.activeReservations}`
34487
+ );
34488
+ warnIncomplete(incomplete);
34489
+ }
34490
+ if (incomplete.length) process.exitCode = 1;
34491
+ } catch (e) {
34492
+ await failGraceful(e.message);
34493
+ }
34494
+ });
34396
34495
  box.command("control <name>").description("box-side control files (#7402): --plan (default, read-only) reports drift from the repo; --apply --only <src> installs with backups").option("--plan", "read-only drift report (the default)").option("--apply", "install the files named by --only on this box, backing up each previous copy").option("--only <src>", "with --apply: a manifest src to install (repeatable; approval is file by file)", (v, acc) => [...acc, v], []).option("--dest <path>", "with --apply: a destination of a multi-target --only source to install (repeatable, #7409)", (v, acc) => [...acc, v], []).option("--all-targets", "with --apply: install a multi-target --only source to every one of its destinations on this box").option("--ref <ref>", "git ref the repo content is read from", "origin/development").option("--json", "machine-readable output").action(async (name, o) => {
34397
34496
  try {
34398
34497
  if (o.apply && o.plan) throw new UsageError("box control: --plan and --apply are exclusive");
@@ -34905,6 +35004,10 @@ function classifyEnvironmentDrift(drift) {
34905
35004
  }
34906
35005
  var TRACK_ENVIRONMENT_EXCEPTIONS = {
34907
35006
  direct: { rc: "direct release track has no rc stage" },
35007
+ single: {
35008
+ rc: "single release track has no rc stage",
35009
+ main: "single release track has no main branch; merges deploy the one stage"
35010
+ },
34908
35011
  trunk: {
34909
35012
  dev: "trunk release track has no dev stage",
34910
35013
  rc: "trunk release track has no rc stage"
@@ -35073,6 +35176,7 @@ function branchProtectionPayload(current, owners, app = LOCKED_APP) {
35073
35176
  }
35074
35177
  function deployStagesForBootstrap(deployModel, track) {
35075
35178
  if (deployModel !== "tenant-container" && deployModel !== "solo-container") return [];
35179
+ if (track === "single") return ["main"];
35076
35180
  return branchesForTrack(track).map((branch) => branch === "development" ? "dev" : branch);
35077
35181
  }
35078
35182
  function bootstrapSetDeployCommand(repo, stage, deployModel) {
@@ -38311,7 +38415,7 @@ Each module must ship a live release that bind-mounts <deployPath>/data read-onl
38311
38415
  let running = 0;
38312
38416
  const waiting = [];
38313
38417
  const results = await Promise.allSettled(record.members.map(async (member) => {
38314
- if (running >= 4) await new Promise((resolve8) => waiting.push(resolve8));
38418
+ if (running >= 4) await new Promise((resolve7) => waiting.push(resolve7));
38315
38419
  else running++;
38316
38420
  try {
38317
38421
  if ((await deps.head(member.repo, "development"))?.toLowerCase() !== member.development.toLowerCase())
@@ -38982,7 +39086,7 @@ function clean3(out) {
38982
39086
  return out.trim();
38983
39087
  }
38984
39088
  function sleeper(deps) {
38985
- return deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
39089
+ return deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
38986
39090
  }
38987
39091
  function normalizeHotfixVersion(input) {
38988
39092
  const m = /^v?(\d+\.\d+\.\d+)$/.exec(input.trim());
@@ -40656,7 +40760,7 @@ async function updatePrHeadFromBase(input) {
40656
40760
  } catch (e) {
40657
40761
  throw new Error(`GitHub could not update ${input.head} from ${input.base} (${e.message.trim()}) \u2014 ${remedy}`);
40658
40762
  }
40659
- const sleep2 = input.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
40763
+ const sleep2 = input.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
40660
40764
  for (let attempt = 0; attempt < PR_HEAD_UPDATE_API_READ_RETRIES; attempt++) {
40661
40765
  const to = await readHead2();
40662
40766
  if (to && to !== from) return { from, to, base: input.base, via: "update-branch-api" };
@@ -40681,7 +40785,7 @@ async function mergeAutoWithTransientRetry(prNumber, repo, deps) {
40681
40785
  if (first.mergeStatus !== "failed") return first;
40682
40786
  const ready = await deps.probeMergeReady(prNumber, repo).catch(() => ({ open: false, mergeable: false, checksPassing: false }));
40683
40787
  if (!ready.open || !ready.mergeable || !ready.checksPassing) return first;
40684
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
40788
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
40685
40789
  await sleep2(PR_LAND_MERGE_RETRY_DELAY_MS);
40686
40790
  const retried = await deps.mergeAuto(prNumber, repo);
40687
40791
  if (retried.mergeStatus !== "failed") return retried;
@@ -40692,7 +40796,7 @@ var AUTO_MERGE_CONFIRM_DELAY_MS = 15e3;
40692
40796
  async function confirmAutoMergeEnqueued(deps, options) {
40693
40797
  const retries = options?.retries ?? AUTO_MERGE_CONFIRM_RETRIES;
40694
40798
  const delayMs = options?.delayMs ?? AUTO_MERGE_CONFIRM_DELAY_MS;
40695
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
40799
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
40696
40800
  for (let attempt = 0; attempt < retries; attempt++) {
40697
40801
  if (await deps.readMerged().catch(() => false)) return "merged";
40698
40802
  const stuck = await deps.readAutoMergeRequest().then((s) => s.trim()).catch(() => "");
@@ -40709,7 +40813,7 @@ async function confirmAutoMergeEnqueued(deps, options) {
40709
40813
  async function readGhPrStateWithRetry(fetchState, options) {
40710
40814
  const retries = options?.retries ?? PR_LAND_STATE_READ_RETRIES;
40711
40815
  const delayMs = options?.delayMs ?? PR_LAND_STATE_READ_DELAY_MS;
40712
- const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
40816
+ const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
40713
40817
  let lastError = "empty state";
40714
40818
  for (let attempt = 0; attempt < retries; attempt++) {
40715
40819
  try {
@@ -40793,7 +40897,7 @@ async function runPrLand(prNumber, options, deps) {
40793
40897
  return queueMerged(base, deps);
40794
40898
  }
40795
40899
  const deadline = (deps.now ?? (() => Date.now()))() + (options.queueTimeoutMs ?? PR_LAND_QUEUE_TIMEOUT_MS);
40796
- await (deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms))))(deps.pollDelayMs?.(0) ?? PR_LAND_POLL_MS);
40900
+ await (deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms))))(deps.pollDelayMs?.(0) ?? PR_LAND_POLL_MS);
40797
40901
  return pollPrMergeQueue(prNumber, options, deps, base, void 0, deadline);
40798
40902
  }
40799
40903
  if (merge.mergeStatus === "merged") {
@@ -40831,7 +40935,7 @@ function queueMerged(base, deps) {
40831
40935
  }
40832
40936
  async function pollPrMergeQueue(pr, options, deps, base, snapshot, deadlineMs) {
40833
40937
  const now = deps.now ?? (() => Date.now());
40834
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
40938
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
40835
40939
  const timeoutMs = options.queueTimeoutMs ?? PR_LAND_QUEUE_TIMEOUT_MS;
40836
40940
  const deadline = deadlineMs ?? now() + timeoutMs;
40837
40941
  let attempt = 0;
@@ -41974,7 +42078,7 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh2, opts) {
41974
42078
  async function ghAdminTrueMergeWhenClean(prNumber, repo, opts = {}) {
41975
42079
  const io = opts.io ?? defaultGhMergeAutoIo;
41976
42080
  const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
41977
- const pollMs = opts.pollMs ?? 15e3;
42081
+ const pollMs = opts.pollMs ?? 6e4;
41978
42082
  const deadline = Date.now() + (opts.timeoutMs ?? 20 * 6e4);
41979
42083
  for (; ; ) {
41980
42084
  const raw = await io.gh(["pr", "view", prNumber, "--repo", repo, "--json", "state,mergeStateStatus"], GC_GH_TIMEOUT_MS).catch(() => "");
@@ -42191,6 +42295,7 @@ var readApi = async (args) => {
42191
42295
  const env = await ghReadEnv();
42192
42296
  return (await execFileP("gh", ["api", ...args], {
42193
42297
  timeout: 2e4,
42298
+ maxBuffer: 16 * 1024 * 1024,
42194
42299
  ...env ? { env } : {}
42195
42300
  })).stdout;
42196
42301
  };
@@ -42220,7 +42325,7 @@ async function readPrMergeQueue(pr, repo, gh2 = readApi) {
42220
42325
  return result.pullRequest;
42221
42326
  }
42222
42327
  async function findPrMergeGroupRun(pr, repo, base, gh2 = readApi) {
42223
- const pages = JSON.parse(await gh2([`repos/${repo}/actions/runs?event=merge_group&per_page=100`, "--paginate", "--slurp"]));
42328
+ const pages = [JSON.parse(await gh2([`repos/${repo}/actions/runs?event=merge_group&per_page=100&exclude_pull_requests=true`]))];
42224
42329
  const prefix = `gh-readonly-queue/${base}/pr-${pr}-`;
42225
42330
  const run = pages.flatMap((page) => page.workflow_runs).filter((run2) => run2.head_branch?.startsWith(prefix)).sort((a, b) => b.created_at.localeCompare(a.created_at) || b.id - a.id)[0];
42226
42331
  return run ? { id: run.id, url: run.html_url, conclusion: run.conclusion } : void 0;
@@ -44000,7 +44105,7 @@ var import_promises7 = require("node:fs/promises");
44000
44105
  init_cli_shared();
44001
44106
  init_error_codes();
44002
44107
  var GC_GH_TIMEOUT_MS2 = 2e4;
44003
- var CHECKS_WATCH_POLL_MS = 15e3;
44108
+ var CHECKS_WATCH_POLL_MS = 6e4;
44004
44109
  var CHECKS_WATCH_TIMEOUT_MS = 10 * 6e4;
44005
44110
  function prCommentMarker(markerId) {
44006
44111
  return `<!-- mmi-pr-marker:${markerId} -->`;
@@ -44539,7 +44644,7 @@ async function checkPrReview(number, repo, head, deps = {
44539
44644
  }
44540
44645
  }
44541
44646
  function computePrPatchId(number, repo) {
44542
- return new Promise((resolve8, reject) => {
44647
+ return new Promise((resolve7, reject) => {
44543
44648
  const gh2 = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
44544
44649
  const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
44545
44650
  let out = "";
@@ -44577,7 +44682,7 @@ function computePrPatchId(number, repo) {
44577
44682
  if (gitCode !== 0) return reject(new Error(`git patch-id --stable exited ${gitCode}: ${gitErr.trim()}`));
44578
44683
  const id = out.trim().split(/\s+/)[0];
44579
44684
  if (!/^[0-9a-f]{40}$/.test(id ?? "")) return reject(new Error(`patch-id: PR #${number} diff is empty or unreadable (gh: ${ghErr.trim() || "no stderr"})`));
44580
- resolve8(id);
44685
+ resolve7(id);
44581
44686
  };
44582
44687
  gh2.on("close", (code) => {
44583
44688
  ghCode = code;
@@ -44628,7 +44733,7 @@ init_cli_shared();
44628
44733
  var GIT_TIMEOUT_MS2 = 15e3;
44629
44734
  function catFileBatch(root, ref, paths) {
44630
44735
  if (paths.length === 0) return Promise.resolve([]);
44631
- return new Promise((resolve8) => {
44736
+ return new Promise((resolve7) => {
44632
44737
  const chunks = [];
44633
44738
  let settled = false;
44634
44739
  const child2 = (0, import_node_child_process15.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
@@ -44636,7 +44741,7 @@ function catFileBatch(root, ref, paths) {
44636
44741
  if (settled) return;
44637
44742
  settled = true;
44638
44743
  clearTimeout(timer);
44639
- resolve8(parseCatFileBatch(Buffer.concat(chunks), paths.length));
44744
+ resolve7(parseCatFileBatch(Buffer.concat(chunks), paths.length));
44640
44745
  };
44641
44746
  const timer = setTimeout(() => {
44642
44747
  child2.kill("SIGKILL");
@@ -45061,7 +45166,7 @@ function healthPollIntervalMs() {
45061
45166
  return HEALTH_POLL_INTERVAL_MS;
45062
45167
  }
45063
45168
  function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
45064
- return new Promise((resolve8, reject) => {
45169
+ return new Promise((resolve7, reject) => {
45065
45170
  let settled = false;
45066
45171
  const finish = (fn) => {
45067
45172
  if (settled) return;
@@ -45071,7 +45176,7 @@ function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
45071
45176
  child2.removeAllListeners("exit");
45072
45177
  fn();
45073
45178
  };
45074
- const timer = setTimeout(() => finish(resolve8), graceMs);
45179
+ const timer = setTimeout(() => finish(resolve7), graceMs);
45075
45180
  child2.on("error", (err) => finish(() => reject(new Error(`stage process failed to start: ${err.message}`))));
45076
45181
  child2.on("exit", (code, signal) => {
45077
45182
  const detail = code != null ? `code ${code}` : signal ? `signal ${signal}` : "unknown reason";
@@ -45264,10 +45369,10 @@ function pickStagePort(range, isFree) {
45264
45369
  throw new Error(`no free stage port in range ${start}-${end} \u2014 every port is in use`);
45265
45370
  }
45266
45371
  function isPortFree(port) {
45267
- return new Promise((resolve8) => {
45372
+ return new Promise((resolve7) => {
45268
45373
  const srv = (0, import_node_net.createServer)();
45269
- srv.once("error", () => resolve8(false));
45270
- srv.once("listening", () => srv.close(() => resolve8(true)));
45374
+ srv.once("error", () => resolve7(false));
45375
+ srv.once("listening", () => srv.close(() => resolve7(true)));
45271
45376
  srv.listen(port, "127.0.0.1");
45272
45377
  });
45273
45378
  }
@@ -45545,7 +45650,7 @@ async function killTree(pid) {
45545
45650
  } catch {
45546
45651
  }
45547
45652
  }
45548
- await new Promise((resolve8) => setTimeout(resolve8, 500));
45653
+ await new Promise((resolve7) => setTimeout(resolve7, 500));
45549
45654
  try {
45550
45655
  process.kill(-pid, "SIGKILL");
45551
45656
  } catch {
@@ -45618,7 +45723,7 @@ async function waitForHealth(url, timeoutMs, anyStatus = false) {
45618
45723
  } catch (e) {
45619
45724
  last = e.message;
45620
45725
  }
45621
- await new Promise((resolve8) => setTimeout(resolve8, healthPollIntervalMs()));
45726
+ await new Promise((resolve7) => setTimeout(resolve7, healthPollIntervalMs()));
45622
45727
  }
45623
45728
  throw new Error(`stage health check timed out for ${url}${last ? ` (${last})` : ""}`);
45624
45729
  }
@@ -45805,7 +45910,7 @@ function unlinkNodeModulesJunction(wtPath) {
45805
45910
  }
45806
45911
  }
45807
45912
  function defaultSleep2(ms) {
45808
- return new Promise((resolve8) => setTimeout(resolve8, ms));
45913
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
45809
45914
  }
45810
45915
  async function removeResidueDirectory(wtPath, options = {}) {
45811
45916
  const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs44.rmdirSync, exists = import_node_fs44.existsSync } = options;
@@ -47555,7 +47660,7 @@ ${list}`);
47555
47660
  // #5400: after grace, name "GitHub delivered zero runs" instead of burning the full budget as pending.
47556
47661
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr checks-wait", number, repo),
47557
47662
  baseBranch,
47558
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
47663
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
47559
47664
  log: (message2) => console.warn(message2),
47560
47665
  timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
47561
47666
  // Liveness on stderr, one line per poll. A silent bounded wait is indistinguishable from a hang, and
@@ -47725,7 +47830,7 @@ ${list}`);
47725
47830
  // #5400: same zero-runs delivery probe as checks-wait — do not burn the land budget on silence.
47726
47831
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr land", prNumber, repo),
47727
47832
  baseBranch: "development",
47728
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
47833
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
47729
47834
  log: (message2) => console.warn(message2),
47730
47835
  // `pr land` inherits the same (raised) checks budget, so it needs the same liveness — otherwise the
47731
47836
  // 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
@@ -47777,7 +47882,7 @@ ${list}`);
47777
47882
  } else {
47778
47883
  lastFailure = void 0;
47779
47884
  }
47780
- await new Promise((resolve8) => setTimeout(resolve8, PR_LAND_POLL_MS));
47885
+ await new Promise((resolve7) => setTimeout(resolve7, PR_LAND_POLL_MS));
47781
47886
  }
47782
47887
  if (lastFailure) {
47783
47888
  throw new Error(
@@ -47968,7 +48073,7 @@ ${list}`);
47968
48073
  checksDiagnosticCommand: `gh pr checks ${number} --repo ${repo}`,
47969
48074
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr merge --wait", number, repo),
47970
48075
  baseBranch,
47971
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
48076
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
47972
48077
  log: (message2) => console.warn(message2),
47973
48078
  timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
47974
48079
  progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks \u2014 ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
@@ -48058,7 +48163,7 @@ ${list}`);
48058
48163
  }
48059
48164
  if (!o.auto && mergeRejectedBaseBranchModified(message2)) {
48060
48165
  console.warn(`pr merge: the base branch was modified by a concurrent merge \u2014 waiting ${PR_MERGE_BASE_RACE_RETRY_DELAY_MS / 1e3}s and retrying PR #${number}'s merge once (#6052).`);
48061
- await new Promise((resolve8) => setTimeout(resolve8, PR_MERGE_BASE_RACE_RETRY_DELAY_MS));
48166
+ await new Promise((resolve7) => setTimeout(resolve7, PR_MERGE_BASE_RACE_RETRY_DELAY_MS));
48062
48167
  await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: false, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
48063
48168
  const m2 = String(e2.message || "");
48064
48169
  if (/already been merged/i.test(m2)) {
@@ -48320,7 +48425,7 @@ ${list}`);
48320
48425
  console.warn(`ci checks-wait: zero-runs probe FAILED (${read.error}) \u2014 not treating as delivery failure this cycle`);
48321
48426
  return null;
48322
48427
  },
48323
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
48428
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
48324
48429
  log: (message2) => console.warn(message2),
48325
48430
  timeoutMs: timeoutMs ?? PR_CHECKS_TIMEOUT_MS,
48326
48431
  progress: ({ state, elapsedMs, remainingMs }) => console.warn(`ci checks-wait: ${state} \u2014 ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
@@ -48697,7 +48802,7 @@ function hotfixFollowUpLegs(runs, foldPort, alignment, foldNote, deployNote) {
48697
48802
  }
48698
48803
  return legs;
48699
48804
  }
48700
- async function runProjectInfoSyncLeg(cb, repo, sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms))) {
48805
+ async function runProjectInfoSyncLeg(cb, repo, sleep2 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms))) {
48701
48806
  for (let attempt = 0; ; attempt++) {
48702
48807
  try {
48703
48808
  return await cb(repo, true);
@@ -49622,7 +49727,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
49622
49727
  if (!reuse) await gh(["workflow", "run", "module-rc-publish.yml", "--repo", "mutmutco/MMI-Hub", ...fields.flatMap((f) => ["--field", f])]);
49623
49728
  let candidate = reuse;
49624
49729
  for (let i = 0; !candidate && i < 25; i++) {
49625
- await new Promise((resolve8) => setTimeout(resolve8, 1e3));
49730
+ await new Promise((resolve7) => setTimeout(resolve7, 1e3));
49626
49731
  candidate = (await rcRuns()).find((r) => r.displayTitle === title && Date.parse(r.createdAt) >= since);
49627
49732
  }
49628
49733
  if (!candidate) throw new Error(`workflow dispatch outcome ambiguous for "${title}"; inspect Actions before retrying`);
@@ -49778,7 +49883,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
49778
49883
  for (let i = 0; ; i++) {
49779
49884
  const pin = await openSuitePinPr({ root, run: (file, args, cwd) => exec(file, args, cwd, GIT_TIMEOUT_MS) }, { repo, tag, stage: "main" });
49780
49885
  if (pin.status !== "pending" || i >= 40) return pin;
49781
- await new Promise((resolve8) => setTimeout(resolve8, 3e4));
49886
+ await new Promise((resolve7) => setTimeout(resolve7, 3e4));
49782
49887
  }
49783
49888
  },
49784
49889
  suiteMapping: async (leadTag) => {
@@ -49860,7 +49965,7 @@ function suiteRcDataProdDeps() {
49860
49965
  const rc = facts.stages.rc;
49861
49966
  return rc?.present ? rc.substrate ?? "an unnamed substrate" : null;
49862
49967
  },
49863
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms))
49968
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms))
49864
49969
  };
49865
49970
  }
49866
49971
  function registerSuiteCommands(program3) {
@@ -52648,6 +52753,12 @@ async function runDoctorClean(opts, io, deps) {
52648
52753
  init_cli_shared();
52649
52754
  init_error_codes();
52650
52755
  init_house_map();
52756
+ var PR_PREFLIGHT_STEPS = [
52757
+ { label: "Review the docs and runbook sections affected by the code" },
52758
+ { label: "Update affected instructions, or record a precise Runbook-Unaffected explanation when they genuinely remain unchanged" },
52759
+ { label: "Commit code and documentation first (coverage compares committed base...HEAD)", command: "git commit" },
52760
+ { label: "Run the repository's existing fast local preflight (check:local where declared, otherwise its documented equivalent)", command: "npm run check:local" }
52761
+ ];
52651
52762
  var LOOP_PLAYBOOKS = {
52652
52763
  agent: {
52653
52764
  title: "Agent",
@@ -52659,7 +52770,8 @@ var LOOP_PLAYBOOKS = {
52659
52770
  { label: "Claim board work (board mutation only \u2014 never `oracle issue claim` or the flat `board claim`; ground unknown write routes via `commands` / `explain` first)", command: "mmi-cli oracle board claim <ref>" },
52660
52771
  { label: "Prepare the local workspace through the host surface" },
52661
52772
  { label: "Before broad docs rewrites, follow docs/Guides/doc-ceremony-cut.md" },
52662
- { label: "Apply the repository test policy, then build the touched package", command: "mmi-cli tests policy --base origin/development && npm run build" },
52773
+ { label: "Apply the repository test policy and run only its permitted focused checks, without a broad local build or full suite", command: "mmi-cli tests policy --base origin/development" },
52774
+ ...PR_PREFLIGHT_STEPS,
52663
52775
  { label: "Publish the branch", command: "git push origin <branch>:<branch>" },
52664
52776
  { label: "Open the development-base PR", command: 'mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
52665
52777
  { label: "Land to development (waits for checks itself)", command: "mmi-cli devops pr land <PR-number>" },
@@ -52681,6 +52793,7 @@ var LOOP_PLAYBOOKS = {
52681
52793
  "ship-pr": {
52682
52794
  title: "Ship PR",
52683
52795
  steps: [
52796
+ ...PR_PREFLIGHT_STEPS,
52684
52797
  { label: "Publish the branch", command: "git push origin <branch>:<branch>" },
52685
52798
  { label: "Open the development-base PR", command: 'mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
52686
52799
  { label: "Land the PR (merge to development \u2014 waits for checks itself)", command: "mmi-cli devops pr land <PR-number>" }
@@ -55839,7 +55952,7 @@ function createRunbookLiveDeps(cliBin) {
55839
55952
  };
55840
55953
  const first = await probe().catch(() => 599);
55841
55954
  if (first < 500) return first;
55842
- await new Promise((resolve8) => setTimeout(resolve8, 2e4));
55955
+ await new Promise((resolve7) => setTimeout(resolve7, 2e4));
55843
55956
  return probe();
55844
55957
  }
55845
55958
  };
@@ -55912,11 +56025,13 @@ function registerEdgeCommands(program3) {
55912
56025
  process.exitCode = 1;
55913
56026
  }
55914
56027
  });
55915
- edge.command("dns <action> <owner/repo> <stage>").description("read (`status`) or create-when-missing (`ensure`, master-only) the proxied Cloudflare A records for a registered DEPLOY# row; existing records are never modified").action(async (action, repo, stage) => {
55916
- if (action !== "status" && action !== "ensure") return fail("runtime edge dns: <action> must be status or ensure");
56028
+ edge.command("dns <action> <owner/repo> <stage>").description("read (`status`), create missing (`ensure`) or guarded delete (`delete`, dry-run unless --apply) stage DNS; writes are master-only").option("--host <host>", "declared stage host to delete").option("--apply", "apply the guarded DNS deletion").action(async (action, repo, stage, options) => {
56029
+ if (!["status", "ensure", "delete"].includes(action)) return fail("runtime edge dns: <action> must be status, ensure or delete");
56030
+ if (action === "delete" && !options.host) return fail("runtime edge dns delete: --host is required");
56031
+ if (action !== "delete" && (options.host || options.apply)) return fail("runtime edge dns: --host and --apply are only for delete");
55917
56032
  if (stage !== "dev" && stage !== "rc" && stage !== "main") return fail("runtime edge dns: <stage> must be dev, rc, or main");
55918
56033
  try {
55919
- const result = await edgeDnsForStage(slugOf(repo), stage, registryClientDeps(await loadConfig()), action === "ensure");
56034
+ const result = await edgeDnsForStage(slugOf(repo), stage, registryClientDeps(await loadConfig()), action === "ensure", action === "delete" ? { host: options.host, apply: options.apply === true } : void 0);
55920
56035
  console.log(JSON.stringify(result, null, 2));
55921
56036
  if (result.required && result.complete === false) process.exitCode = 1;
55922
56037
  } catch (e) {
@@ -56656,8 +56771,8 @@ function mmiDoctorDeps(opts = {}) {
56656
56771
  });
56657
56772
  const raced = await Promise.race([
56658
56773
  work.then((r) => ({ ...r, timedOut: false })),
56659
- new Promise((resolve8) => {
56660
- ceiling = setTimeout(() => resolve8({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
56774
+ new Promise((resolve7) => {
56775
+ ceiling = setTimeout(() => resolve7({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
56661
56776
  })
56662
56777
  ]);
56663
56778
  if (raced.timedOut) return { scanned: 0, findings: 0, fixed: 0, failed: 0, timedOut: true };
@@ -56690,8 +56805,8 @@ function mmiDoctorDeps(opts = {}) {
56690
56805
  incomplete: nb.incomplete,
56691
56806
  timedOut: false
56692
56807
  })),
56693
- new Promise((resolve8) => {
56694
- ceiling = setTimeout(() => resolve8({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
56808
+ new Promise((resolve7) => {
56809
+ ceiling = setTimeout(() => resolve7({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
56695
56810
  })
56696
56811
  ]);
56697
56812
  if (!raced.timedOut && raced.incomplete.length === 0) writeSchedulesDriftCache(cachePath, raced.driftLines);
@@ -56971,7 +57086,6 @@ function envelopeAwareWriteErr(str2) {
56971
57086
  }
56972
57087
  writeHumanParseError(str2);
56973
57088
  }
56974
- var INVOKED_ARGV3 = process.argv.slice(2);
56975
57089
  var program2 = new Command();
56976
57090
  program2.name("mmi-cli").description("MMI Future Hub CLI \u2014 the org control plane for agentic coding.").version(resolveClientVersion()).configureOutput({ writeErr: envelopeAwareWriteErr }).showHelpAfterError(PARSE_HINT_SENTINEL);
56977
57091
  program2.addHelpText(
@@ -56996,6 +57110,8 @@ function appActorDeps() {
56996
57110
  }
56997
57111
  setPollTokenProvider(async () => activeAppToken() ?? pollTokenOrUndefined(appActorDeps()));
56998
57112
  setGhReadTokenProvider(pollToken);
57113
+ var filingToken;
57114
+ setGhWriteTokenProvider(() => filingToken ??= mintFilingWriteToken(appActorDeps()).then((t) => t.token, () => void 0));
56999
57115
  setStatusWriteTokenProvider(() => mintStatusWriteToken(appActorDeps()));
57000
57116
  var registerPreAction = Reflect.get(program2, ["h", "ook"].join(""));
57001
57117
  registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand) => {
@@ -57073,6 +57189,15 @@ Dry run: pass --confirm-data-loss ${repo}/${stage} to decommission.`);
57073
57189
  return failGraceful(`runtime tenant control: ${e.message}`);
57074
57190
  }
57075
57191
  });
57192
+ tenant.command("undo <owner/repo> <stage>").description("switch a tenant stage back to its previous release (warm standby: seconds; else recreated from the retained release, no rebuild); watches by default").option("--json", "machine-readable output").action(async (repo, stage, o) => {
57193
+ try {
57194
+ const result = await runTenantControl(trainApplyDeps(), { repo, stage, action: "undo", watch: true });
57195
+ printLine(o.json ? JSON.stringify(result, null, 2) : renderTenantControl(result));
57196
+ if (result.conclusion === "failure") return failGraceful(`runtime tenant undo ${stage}: ${result.category ?? "failed"} \u2014 ${result.note}`);
57197
+ } catch (e) {
57198
+ return failGraceful(`runtime tenant undo: ${e.message}`);
57199
+ }
57200
+ });
57076
57201
  var tenantArtifact = tenant.command("artifact").description("private stage-bound inputs for declared tenant tasks");
57077
57202
  tenantArtifact.command("put <owner/repo> <stage> <path>").description("upload a private file through a short-lived Hub grant; prints only an opaque artifact id").option("--json", "machine-readable output").action(async (repo, stage, path2, o) => {
57078
57203
  try {
@@ -57515,7 +57640,7 @@ fullTrack.command("readiness <owner/repo>").description("aggregate branch topolo
57515
57640
  console.log(JSON.stringify(report, null, 2));
57516
57641
  if (!report.rcand.canApply) process.exitCode = 1;
57517
57642
  });
57518
- project.command("set-deploy [owner/repo]").description("patch a tenant DEPLOY row \u2014 project-admin may set only --no-env-file/--compose-profiles on their own existing dev/rc row; master may seed/change all coords and main; defaults to the current repo").addOption(new Option("--stage <stage>", "dev | rc | main").makeOptionMandatory().choices(["dev", "rc", "main"])).option("--ssh-host <host>", "the box address the deploy ssh-es into; omit to keep the stored value (required only for a NEW hetzner-ssh row \u2014 `mmi-cli devops runtime box list` finds it)").option("--ssh-user <user>", "ssh user; omit to leave the row unchanged (default root on a new row)").option("--port <port>", "loopback port the container binds / Caddy upstream (1..65535); omit to leave the row unchanged").addOption(new Option("--substrate <substrate>", "hetzner-ssh | s3-cloudfront; omit to leave the row unchanged").choices([...DEPLOY_SUBSTRATES])).option("--deploy-role-arn <arn>", "s3-cloudfront: the scoped OIDC deployer role tenant-deploy.yml assumes for this stage").option("--app-bucket <bucket>", "s3-cloudfront: the S3 bucket the built dist/ is synced into").option("--distribution-id <id>", "s3-cloudfront: the CloudFront distribution invalidated after the sync").option("--distribution-domain <domain>", "s3-cloudfront: the host the post-deploy security-header proof is measured against").option("--deploy-path <path>", "on-box per-stage release root; omit to leave the row unchanged (default /opt/mmi/<slug>/<stage> on a new row)").option("--service <name>", "systemd/compose service name; omit to leave the row unchanged (default the slug on a new row)").option("--domain <domain>", "canonical serving host; omit to leave the row unchanged").option("--alias <domain...>", "extra serving hostname the box Caddy answers (repeatable); omit to leave the stored aliases unchanged").option("--clear-aliases", "remove EVERY serving alias from the row (#2986) \u2014 omitting --alias preserves them, so clearing needs saying out loud").option("--no-env-file <bool>", "set the DEPLOY# fileless flag (true|false) \u2014 own-repo project-admin dev/rc; master main; true = env passthrough with no .env symlink").option("--compose-profiles <profiles>", "comma-separated compose profiles to enable for this stage (e.g. huddles,slack-bridge); omit to leave unchanged").option("--clear-compose-profiles", "remove EVERY compose profile from the row \u2014 omitting --compose-profiles preserves them, so clearing needs saying out loud").option("--force", "explicit recovery override: skip fileless-compose verification only after independent proof").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
57643
+ project.command("set-deploy [owner/repo]").description("patch a tenant DEPLOY row \u2014 project-admin may set only --no-env-file/--compose-profiles on their own existing dev/rc row; master may seed/change all coords and main; defaults to the current repo").addOption(new Option("--stage <stage>", "dev | rc | main").makeOptionMandatory().choices(["dev", "rc", "main"])).option("--ssh-host <host>", "the box address the deploy ssh-es into; omit to keep the stored value (required only for a NEW hetzner-ssh row \u2014 `mmi-cli devops runtime box list` finds it)").option("--ssh-user <user>", "ssh user; omit to leave the row unchanged (default root on a new row)").option("--port <port>", "loopback port the container binds / Caddy upstream (1..65535); omit to leave the row unchanged").addOption(new Option("--substrate <substrate>", "hetzner-ssh | s3-cloudfront; omit to leave the row unchanged").choices([...DEPLOY_SUBSTRATES])).option("--deploy-role-arn <arn>", "s3-cloudfront: the scoped OIDC deployer role tenant-deploy.yml assumes for this stage").option("--app-bucket <bucket>", "s3-cloudfront: the S3 bucket the built dist/ is synced into").option("--distribution-id <id>", "s3-cloudfront: the CloudFront distribution invalidated after the sync").option("--distribution-domain <domain>", "s3-cloudfront: the host the post-deploy security-header proof is measured against").option("--deploy-path <path>", "on-box per-stage release root; omit to leave the row unchanged (default /opt/mmi/<slug>/<stage> on a new row)").option("--service <name>", "systemd/compose service name; omit to leave the row unchanged (default the slug on a new row)").option("--domain <domain>", "canonical serving host; omit to leave the row unchanged").option("--alias <domain...>", "extra serving hostname the box Caddy answers (repeatable); omit to leave the stored aliases unchanged").option("--redirect-from <host>", "permanent redirect host or host=target (primary or alias); repeatable", (host, hosts) => [...hosts, host], []).option("--clear-aliases", "remove EVERY serving alias from the row (#2986) \u2014 omitting --alias preserves them, so clearing needs saying out loud").option("--no-env-file <bool>", "set the DEPLOY# fileless flag (true|false) \u2014 own-repo project-admin dev/rc; master main; true = env passthrough with no .env symlink").option("--compose-profiles <profiles>", "comma-separated compose profiles to enable for this stage (e.g. huddles,slack-bridge); omit to leave unchanged").option("--clear-compose-profiles", "remove EVERY compose profile from the row \u2014 omitting --compose-profiles preserves them, so clearing needs saying out loud").option("--force", "explicit recovery override: skip fileless-compose verification only after independent proof").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
57519
57644
  const cfg = await loadConfig();
57520
57645
  let target;
57521
57646
  try {
@@ -57570,6 +57695,7 @@ ${filelessTransitionGuide(target, o.stage)}`
57570
57695
  distributionId: o.distributionId,
57571
57696
  distributionDomain: o.distributionDomain,
57572
57697
  aliases: o.alias,
57698
+ redirectFrom: o.redirectFrom?.length ? o.redirectFrom : void 0,
57573
57699
  clearAliases: o.clearAliases,
57574
57700
  noEnvFile,
57575
57701
  composeProfiles,
@@ -191,6 +191,11 @@ var init_error_codes = __esm({
191
191
  });
192
192
 
193
193
  // src/gh-read-route.ts
194
+ function isAppFilingWrite(args) {
195
+ if (args.some((a) => VIEWER.test(a))) return false;
196
+ const [group, verb] = args;
197
+ return (group === "issue" || group === "label") && verb === "create";
198
+ }
194
199
  function isViewerFreeGhRead(args) {
195
200
  if (args.some((a) => VIEWER.test(a))) return false;
196
201
  const [group, verb] = args;
@@ -285,6 +290,12 @@ var init_stdin_inject = __esm({
285
290
  });
286
291
 
287
292
  // src/cli-shared.ts
293
+ async function ghFilingAppEnv(file, args) {
294
+ if (!ghWriteTokenProvider || !/(^|[\\/])gh(\.exe)?$/i.test(file) || process.env.MMI_ACTOR?.trim() === "personal") return void 0;
295
+ if (!isAppFilingWrite(args)) return void 0;
296
+ const token = await ghWriteTokenProvider().catch(() => void 0);
297
+ return token ? { ...process.env, GH_TOKEN: token } : void 0;
298
+ }
288
299
  async function ghReadAppEnv(file, args) {
289
300
  if (!ghReadTokenProvider || !/(^|[\\/])gh(\.exe)?$/i.test(file) || process.env.MMI_ACTOR?.trim() === "personal") return void 0;
290
301
  if (!isViewerFreeGhRead(args)) return void 0;
@@ -325,7 +336,7 @@ function execFileHard(file, args, options) {
325
336
  timer.unref?.();
326
337
  });
327
338
  }
328
- var import_node_child_process3, import_node_util2, rawExecFileP, DEFAULT_EXEC_TIMEOUT_MS, execFileP, ghReadTokenProvider, ExecDeadlineError;
339
+ var import_node_child_process3, import_node_util2, rawExecFileP, DEFAULT_EXEC_TIMEOUT_MS, execFileP, ghReadTokenProvider, ghWriteTokenProvider, ExecDeadlineError;
329
340
  var init_cli_shared = __esm({
330
341
  "src/cli-shared.ts"() {
331
342
  "use strict";
@@ -348,7 +359,7 @@ var init_cli_shared = __esm({
348
359
  // promisify(execFile)'s overloads widen to string|Buffer when options is spread in.
349
360
  rawExecFileP(file, args, { encoding: "utf8", windowsHide: true, timeout: DEFAULT_EXEC_TIMEOUT_MS, killSignal: "SIGTERM", ...options, ...env ? { env } : {} })
350
361
  );
351
- const appEnv = options.env ? void 0 : await ghReadAppEnv(file, args);
362
+ const appEnv = options.env ? void 0 : await ghReadAppEnv(file, args) ?? await ghFilingAppEnv(file, args);
352
363
  if (!appEnv) return run();
353
364
  try {
354
365
  return await run(appEnv);
@@ -775,7 +786,7 @@ function checkRunbookCoverage(docs, base, git) {
775
786
  const globs = lines.slice(heading.start, markerEnd - 1).flatMap((text) => /<!--\s*runbook-covers:\s*(.*?)\s*-->/.exec(text)?.[1].split(",").map((s) => s.trim()) ?? []);
776
787
  const affected = changed.filter((path2) => globs.some((glob) => matches(glob, path2)));
777
788
  if (affected.length && !ranges.some((r) => r.count > 0 && r.start < end && r.start + r.count > heading.start)) {
778
- findings.push({ kind: "runbook-section-stale", doc, line: heading.start, detail: `${heading.title}: changed ${affected.join(", ")}; update this section or declare Runbook-Unaffected: <reason>` });
789
+ findings.push({ kind: "runbook-section-stale", doc, line: heading.start, detail: `${heading.title}: changed ${affected.join(", ")}; update this section or declare Runbook-Unaffected: <reason>`, section: { heading: heading.title, level: heading.level, startLine: heading.start, endLine: end - 1 }, sources: affected });
779
790
  }
780
791
  }
781
792
  }
@@ -1084,7 +1095,7 @@ var TEST_RUNNER_AT_START = new RegExp(
1084
1095
  // src/project-model.ts
1085
1096
  var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-app", "desktop-game", "mobile-app", "non-deployable", "cli-tool", "worker"];
1086
1097
  var DEPLOY_MODELS = ["hub-serverless", "serverless", "tenant-container", "solo-container", "static-cdn", "registry-publish", "content", "none", "vercel"];
1087
- var RELEASE_TRACKS = ["full", "direct", "trunk"];
1098
+ var RELEASE_TRACKS = ["full", "direct", "trunk", "single"];
1088
1099
  var REQUIRED_CHECK_BRANCHES = ["development", "rc", "main"];
1089
1100
  var PROJECT_TYPE_SET = new Set(PROJECT_TYPES);
1090
1101
  var DEPLOY_MODEL_SET = new Set(DEPLOY_MODELS);
@@ -4849,7 +4860,7 @@ async function discoverShaWorkflowRuns(deps, repo, headSha, seenRunIds) {
4849
4860
  var import_node_child_process7 = require("node:child_process");
4850
4861
  var import_node_fs7 = require("node:fs");
4851
4862
  var import_node_os = require("node:os");
4852
- var import_node_path9 = require("node:path");
4863
+ var import_node_path8 = require("node:path");
4853
4864
 
4854
4865
  // src/doctor-io.ts
4855
4866
  var import_node_fs5 = require("node:fs");
@@ -4942,23 +4953,18 @@ function writeGitignore(content) {
4942
4953
  // src/doctor-fleet-drift.ts
4943
4954
  var JOURNAL_TAIL_BYTES = 256 * 1024;
4944
4955
 
4945
- // src/jerv-cli-spawn.ts
4946
- var import_node_path6 = require("node:path");
4947
- init_cli_shared();
4948
- var JERV_CLI_ENTRY = (0, import_node_path6.join)("node_modules", "@jervaise", "jerv-cli", "dist", "index.cjs");
4949
-
4950
4956
  // src/claude-binary-doctor.ts
4951
4957
  var PACKAGE = "@anthropic-ai/claude-code";
4952
4958
  var CLAUDE_BINARY_INSTALL_COMMAND = `npm install -g ${PACKAGE}`;
4953
4959
 
4954
4960
  // src/release-ledger.ts
4955
4961
  var import_node_fs6 = require("node:fs");
4956
- var import_node_path8 = require("node:path");
4962
+ var import_node_path7 = require("node:path");
4957
4963
  var import_node_crypto = require("node:crypto");
4958
4964
 
4959
4965
  // src/file-lock.ts
4960
4966
  var import_promises = require("node:fs/promises");
4961
- var import_node_path7 = require("node:path");
4967
+ var import_node_path6 = require("node:path");
4962
4968
  function pidIsDead(pid) {
4963
4969
  if (!Number.isInteger(pid) || pid <= 0) return false;
4964
4970
  if (pid === process.pid) return false;
@@ -5073,7 +5079,7 @@ async function releaseFileLock(lockPath, guard) {
5073
5079
  }
5074
5080
  async function withFencedFileLock(lockPath, opts, fn) {
5075
5081
  const resolved = resolveFileLockOpts(opts);
5076
- await (0, import_promises.mkdir)((0, import_node_path7.dirname)(lockPath), { recursive: true }).catch(() => void 0);
5082
+ await (0, import_promises.mkdir)((0, import_node_path6.dirname)(lockPath), { recursive: true }).catch(() => void 0);
5077
5083
  const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
5078
5084
  try {
5079
5085
  return await fn(() => fileLockHeldBy(lockPath, guard.token));
@@ -5228,26 +5234,26 @@ function resolvePrimaryGitDir(cwd) {
5228
5234
  }
5229
5235
  let common;
5230
5236
  try {
5231
- common = (0, import_node_fs6.readFileSync)((0, import_node_path8.join)(gitDir, "commondir"), "utf8").trim();
5237
+ common = (0, import_node_fs6.readFileSync)((0, import_node_path7.join)(gitDir, "commondir"), "utf8").trim();
5232
5238
  } catch (e) {
5233
5239
  if (e.code !== "ENOENT") {
5234
5240
  throw new ReleaseLedgerError("unreadable", `release ledger: the worktree gitdir's commondir pointer could not be READ (${e instanceof Error ? e.message : String(e)}) \u2014 a failed read is unverified, not primary (#5987/#4713)`);
5235
5241
  }
5236
5242
  }
5237
5243
  if (!common) return gitDir;
5238
- const commonDir = (0, import_node_path8.isAbsolute)(common) ? common : (0, import_node_path8.resolve)(gitDir, common);
5244
+ const commonDir = (0, import_node_path7.isAbsolute)(common) ? common : (0, import_node_path7.resolve)(gitDir, common);
5239
5245
  try {
5240
- (0, import_node_fs6.readFileSync)((0, import_node_path8.join)(commonDir, "HEAD"), "utf8");
5246
+ (0, import_node_fs6.readFileSync)((0, import_node_path7.join)(commonDir, "HEAD"), "utf8");
5241
5247
  } catch (e) {
5242
5248
  throw new ReleaseLedgerError("no-checkout", `release ledger: the worktree's commondir pointer names ${commonDir}, which has no readable HEAD (${e instanceof Error ? e.message : String(e)}) \u2014 the primary checkout may have been removed; refusing to write the ledger into a doomed location`);
5243
5249
  }
5244
5250
  return commonDir;
5245
5251
  }
5246
5252
  function releaseLedgerPath(cwd) {
5247
- return (0, import_node_path8.join)(resolvePrimaryGitDir(cwd), "mmi-runtime", "release", "phases.json");
5253
+ return (0, import_node_path7.join)(resolvePrimaryGitDir(cwd), "mmi-runtime", "release", "phases.json");
5248
5254
  }
5249
5255
  function atomicWriteJson(path2, ledger) {
5250
- (0, import_node_fs6.mkdirSync)((0, import_node_path8.dirname)(path2), { recursive: true });
5256
+ (0, import_node_fs6.mkdirSync)((0, import_node_path7.dirname)(path2), { recursive: true });
5251
5257
  const tmp = `${path2}.tmp-${process.pid}-${(0, import_node_crypto.randomBytes)(4).toString("hex")}`;
5252
5258
  (0, import_node_fs6.writeFileSync)(tmp, `${JSON.stringify(ledger, null, 2)}
5253
5259
  `, "utf8");
@@ -5263,16 +5269,16 @@ async function withLedgerLock(path2, fn, opts = {}) {
5263
5269
  }));
5264
5270
  }
5265
5271
  function checkoutRootFrom(cwd) {
5266
- let dir = (0, import_node_path8.resolve)(cwd);
5272
+ let dir = (0, import_node_path7.resolve)(cwd);
5267
5273
  for (; ; ) {
5268
5274
  if (resolveGitDir(dir)) return dir;
5269
- const parent = (0, import_node_path8.dirname)(dir);
5275
+ const parent = (0, import_node_path7.dirname)(dir);
5270
5276
  if (parent === dir) throw new ReleaseLedgerError("no-checkout", `train writer lock: no git checkout found above ${cwd}`);
5271
5277
  dir = parent;
5272
5278
  }
5273
5279
  }
5274
5280
  function trainWriterLockPath(cwd) {
5275
- return (0, import_node_path8.join)((0, import_node_path8.dirname)(releaseLedgerPath(checkoutRootFrom(cwd))), "train.lock");
5281
+ return (0, import_node_path7.join)((0, import_node_path7.dirname)(releaseLedgerPath(checkoutRootFrom(cwd))), "train.lock");
5276
5282
  }
5277
5283
  var TRAIN_WRITER_STALE_MS = 12 * 60 * 6e4;
5278
5284
  function describeTrainWriterToken(token) {
@@ -5663,8 +5669,8 @@ function readOriginWorkflowsDefault(root, ref) {
5663
5669
  }
5664
5670
  var NPM_REGISTRY = "https://registry.npmjs.org";
5665
5671
  async function probeRegistryWhoami(train) {
5666
- const dir = (0, import_node_fs7.mkdtempSync)((0, import_node_path9.join)((0, import_node_os.tmpdir)(), "mmi-npmrc-"));
5667
- const rc = (0, import_node_path9.join)(dir, "npmrc");
5672
+ const dir = (0, import_node_fs7.mkdtempSync)((0, import_node_path8.join)((0, import_node_os.tmpdir)(), "mmi-npmrc-"));
5673
+ const rc = (0, import_node_path8.join)(dir, "npmrc");
5668
5674
  try {
5669
5675
  (0, import_node_fs7.writeFileSync)(rc, "//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n");
5670
5676
  await train.runSelf(["secrets", "use", "npm/NPM_TOKEN", "--slug", "_org", "--", "npm", "whoami", "--registry", NPM_REGISTRY, "--userconfig", rc]);
@@ -5701,7 +5707,7 @@ var defaultDeps2 = {
5701
5707
  trainWriterHolder: liveTrainWriterHolder,
5702
5708
  readSurfacesRaw: (cwd) => {
5703
5709
  try {
5704
- return (0, import_node_fs7.readFileSync)((0, import_node_path9.join)(cwd, "surfaces.json"), "utf8");
5710
+ return (0, import_node_fs7.readFileSync)((0, import_node_path8.join)(cwd, "surfaces.json"), "utf8");
5705
5711
  } catch {
5706
5712
  return void 0;
5707
5713
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.160",
3
+ "version": "4.5.162",
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",