@mutmutco/codex-plugin 4.5.99 → 4.5.100

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mmi",
3
- "version": "4.5.99",
3
+ "version": "4.5.100",
4
4
  "mmiCompat": "4.x",
5
5
  "description": "MMI workflow skills and org gates delivery.",
6
6
  "author": {
package/cli/dist/main.cjs CHANGED
@@ -8288,6 +8288,28 @@ async function postJson(pathSuffix, payload, deps, method = "POST", opts = {}) {
8288
8288
  return { ok: false, status: 0, body: null, error: e.message, unreachable: "transport" };
8289
8289
  }
8290
8290
  }
8291
+ async function edgeDnsForStage(slug, stage, deps, ensure = false) {
8292
+ if (!deps.baseUrl) throw new Error("Hub API URL unavailable");
8293
+ const token = await deps.token();
8294
+ if (!token) throw new Error("Hub session token unavailable");
8295
+ const path2 = `/projects/${encodeURIComponent(slug)}/edge-dns/${stage}`;
8296
+ let body;
8297
+ if (ensure) {
8298
+ const write = await postJson(path2, {}, deps, "POST", { noRetry: true });
8299
+ if (!write.ok) throw new Error(`Hub edge dns ensure failed: ${write.error ?? `HTTP ${write.status}`}`);
8300
+ body = write.body;
8301
+ } else {
8302
+ const res = await retriedFetch(
8303
+ deps,
8304
+ `${deps.baseUrl.replace(/\/$/, "")}${path2}`,
8305
+ { method: "GET", headers: { Authorization: `Bearer ${token}` } }
8306
+ );
8307
+ if (!res.ok) throw new Error(`Hub edge dns read failed: HTTP ${res.status}`);
8308
+ body = await res.json();
8309
+ }
8310
+ if (typeof body?.required !== "boolean") throw new Error("Hub edge dns response invalid");
8311
+ return body;
8312
+ }
8291
8313
  async function edgeCertificateForStage(slug, stage, deps, ensure = false) {
8292
8314
  if (!deps.baseUrl) throw new Error("Hub API URL unavailable");
8293
8315
  const token = await deps.token();
@@ -15861,8 +15883,11 @@ function edgeDomainsByStage(meta) {
15861
15883
  }
15862
15884
  return out;
15863
15885
  }
15864
- function inferEdgeDomainsForWrite(slug) {
15886
+ function inferEdgeDomainsForWrite(slug, suite) {
15865
15887
  const subdomain = defaultSubdomain(slug);
15888
+ if (suite === "mm-work" && slug !== suite) {
15889
+ return { dev: `dev.${subdomain}.mutmut.work`, main: `${subdomain}.mutmut.work` };
15890
+ }
15866
15891
  return {
15867
15892
  dev: `dev.${subdomain}.mutatismutandis.co`,
15868
15893
  rc: `rc.${subdomain}.mutatismutandis.co`,
@@ -16236,10 +16261,11 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
16236
16261
  }
16237
16262
  const deployModel = inferDeployModelForWrite({ ...shape, projectType }, parsedRepo.fullName);
16238
16263
  const releaseTrack = (isReleaseTrack(options.releaseTrack) ? options.releaseTrack : void 0) ?? (isReleaseTrack(options.existing?.releaseTrack) ? options.existing.releaseTrack : void 0) ?? resolveBootstrapReleaseTrack(cls, void 0);
16239
- const inferredDomains = inferEdgeDomainsForWrite(slug);
16240
- const derivedDomains = Object.fromEntries(branchesForTrack(releaseTrack).map((branch) => {
16264
+ const suite = vars.SUITE || options.existing?.suite || void 0;
16265
+ const inferredDomains = inferEdgeDomainsForWrite(slug, suite);
16266
+ const derivedDomains = Object.fromEntries(branchesForTrack(releaseTrack).flatMap((branch) => {
16241
16267
  const stage = branch === "development" ? "dev" : branch;
16242
- return [stage, inferredDomains[stage]];
16268
+ return inferredDomains[stage] ? [[stage, inferredDomains[stage]]] : [];
16243
16269
  }));
16244
16270
  const num = (v) => {
16245
16271
  if (v == null || v === "") return void 0;
@@ -16263,6 +16289,7 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
16263
16289
  // Identity. name/division default off the repo name when the skill didn't pass them.
16264
16290
  name: vars.NAME || parsedRepo.name,
16265
16291
  division: vars.DIVISION || parsedRepo.name.split("-")[0] || void 0,
16292
+ suite: vars.SUITE || void 0,
16266
16293
  repos: [`${parsedRepo.owner}/${slug}`],
16267
16294
  branch: vars.BRANCH || (cls === "content" ? "main" : "development"),
16268
16295
  class: cls,
@@ -16362,6 +16389,11 @@ function decideSeedPrAction(openPrs) {
16362
16389
  }
16363
16390
  return { action: "create" };
16364
16391
  }
16392
+ function decideFirstDevDeploy(input) {
16393
+ if (!input.devRowWritten) return "skip";
16394
+ if (input.seedMode === "direct") return "dispatch";
16395
+ return (input.seedPrs ?? []).some((pr) => typeof pr?.mergedAt === "string" && pr.mergedAt !== "") ? "dispatch" : "defer";
16396
+ }
16365
16397
  function decideSeedDelivery(branchRules) {
16366
16398
  const rules = Array.isArray(branchRules) ? branchRules : [];
16367
16399
  const types = new Set(
@@ -17572,10 +17604,10 @@ var rollout_plan_default = {
17572
17604
  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)."
17573
17605
  },
17574
17606
  baseline: {
17575
- version: "4.5.99",
17576
- tag: "v4.5.99",
17577
- commit: "44a40b83250e",
17578
- npm: "@mutmutco/cli@4.5.99"
17607
+ version: "4.5.100",
17608
+ tag: "v4.5.100",
17609
+ commit: "ba367dd0d814",
17610
+ npm: "@mutmutco/cli@4.5.100"
17579
17611
  },
17580
17612
  exitCriterion: "fleet-n-of-n",
17581
17613
  hubOnlyShortcut: "forbidden",
@@ -17592,14 +17624,14 @@ var rollout_plan_default = {
17592
17624
  repo: "mutmutco/mmi-hub",
17593
17625
  role: "canary",
17594
17626
  schedule: "train",
17595
- v3Target: "v4.5.99"
17627
+ v3Target: "v4.5.100"
17596
17628
  }
17597
17629
  ],
17598
17630
  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.",
17599
17631
  rollback: {
17600
17632
  independent: true,
17601
- mechanism: "npm dist-tag latest -> 4.5.99 and redeploy the Hub Lambda from tag v4.5.99 (44a40b83250e); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17602
- v3Target: "v4.5.99 (@mutmutco/cli@4.5.99, tag commit 44a40b83250e \u2014 last known-good release carrying the repo-index v4-only contract)"
17633
+ mechanism: "npm dist-tag latest -> 4.5.100 and redeploy the Hub Lambda from tag v4.5.100 (ba367dd0d814); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17634
+ v3Target: "v4.5.100 (@mutmutco/cli@4.5.100, tag commit ba367dd0d814 \u2014 last known-good release carrying the repo-index v4-only contract)"
17603
17635
  }
17604
17636
  },
17605
17637
  {
@@ -33890,6 +33922,28 @@ function edgeCertificateChecks(results) {
33890
33922
  }];
33891
33923
  });
33892
33924
  }
33925
+ function edgeDnsChecks(results, repo) {
33926
+ return results.flatMap(({ stage, status, error }) => {
33927
+ if (!error && status?.required === false) return [];
33928
+ const bad = (status?.hosts ?? []).filter((h) => h.status === "missing" || h.status === "conflict");
33929
+ const ok = !error && status?.complete !== false && bad.length === 0;
33930
+ return [{
33931
+ ok,
33932
+ label: `Cloudflare edge DNS: ${stage}`,
33933
+ detail: ok ? void 0 : error ? `DNS records unreadable (${error})` : `${bad.map((h) => `${h.host} ${h.status}${h.hint ? ` (${h.hint})` : ""}`).join("; ")} \u2014 run: mmi-cli devops runtime edge dns ensure ${repo} ${stage}`
33934
+ }];
33935
+ });
33936
+ }
33937
+ function firstDevDeployCheck(repo, facts, factsRead) {
33938
+ const dev = facts?.stages?.dev;
33939
+ if (!factsRead || dev?.present !== true) return [];
33940
+ const ok = Boolean(dev.deployedAt || dev.deployedTag);
33941
+ return [{
33942
+ ok,
33943
+ label: "first dev deploy happened",
33944
+ detail: ok ? void 0 : `DEPLOY#dev has never deployed \u2014 run: mmi-cli devops runtime tenant redeploy ${repo} dev --watch`
33945
+ }];
33946
+ }
33893
33947
  async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
33894
33948
  const branchesWanted = expectedBranches(repoClass, releaseTrack);
33895
33949
  const baseBranch = releaseTrack === "trunk" || repoClass === "content" ? "main" : "development";
@@ -34012,6 +34066,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
34012
34066
  if (check) checks.push(check);
34013
34067
  }
34014
34068
  checks.push(...edgeCertificateChecks(deps.edgeCertificates ?? []));
34069
+ checks.push(...edgeDnsChecks(deps.edgeDns ?? [], repo));
34015
34070
  const portRangeCheck = centralContainerPortRangeCheck(deps.deployModel, deps.projectMeta?.portRange, repo);
34016
34071
  if (portRangeCheck) checks.push(portRangeCheck);
34017
34072
  checks.push(...deployRowChecks(
@@ -34022,6 +34077,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
34022
34077
  deps.deployFactsRead === true,
34023
34078
  deps.projectMeta?.suite
34024
34079
  ));
34080
+ checks.push(...firstDevDeployCheck(repo, deps.deployFacts, deps.deployFactsRead === true));
34025
34081
  const readme = await contentText(deps, repo, baseBranch, "README.md");
34026
34082
  checks.push({
34027
34083
  ok: readme !== null && readme.includes("## Agent context"),
@@ -34460,6 +34516,13 @@ function registerBootstrapCommands(program3) {
34460
34516
  return { stage, error: e.message };
34461
34517
  }
34462
34518
  })),
34519
+ edgeDns: await Promise.all(["dev", "rc", "main"].filter((stage) => deployFacts?.stages?.[stage]?.present).map(async (stage) => {
34520
+ try {
34521
+ return { stage, status: await edgeDnsForStage(slug, stage, reg) };
34522
+ } catch (e) {
34523
+ return { stage, error: e.message };
34524
+ }
34525
+ })),
34463
34526
  pushAllowlistOwners: verifyOwners,
34464
34527
  pushAllowlistOwnersRead: verifyOwnersRead,
34465
34528
  readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs28.existsSync)(path2) ? (0, import_node_fs28.readFileSync)(path2, "utf8") : null,
@@ -34857,7 +34920,7 @@ function registerBootstrapCommands(program3) {
34857
34920
  github,
34858
34921
  deployFacts: controlDeployFacts,
34859
34922
  deployFactsRead: controlDeployFacts !== null,
34860
- plannedMainDomain: registryMeta?.edgeDomains?.main ?? inferEdgeDomainsForWrite(slug).main
34923
+ plannedMainDomain: registryMeta?.edgeDomains?.main ?? inferEdgeDomainsForWrite(slug, suiteSlug || vars.SUITE).main
34861
34924
  });
34862
34925
  if (o.box) {
34863
34926
  if (applyDeployModel !== "tenant-container" && applyDeployModel !== "solo-container")
@@ -34878,7 +34941,7 @@ function registerBootstrapCommands(program3) {
34878
34941
  host: found.box.address,
34879
34942
  box: found.box.name,
34880
34943
  owners: scan.rows,
34881
- domains: { ...inferEdgeDomainsForWrite(slug), ...domains, ...boxDomains },
34944
+ domains: { ...inferEdgeDomainsForWrite(slug, suiteSlug || vars.SUITE), ...domains, ...boxDomains },
34882
34945
  fileless: applyDeployModel === "tenant-container"
34883
34946
  });
34884
34947
  controlRows = planned.rows;
@@ -35309,6 +35372,42 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
35309
35372
  ddbWrites.push({ slug, action: `deploy-${coord.stage}`, record: payload });
35310
35373
  applied.push(`DEPLOY#${coord.stage} ${coord.host}:${coord.port}${coord.domain ? ` ${coord.domain}` : ""}`);
35311
35374
  }
35375
+ for (const coord of boxDeployPlan) {
35376
+ try {
35377
+ const dns = await edgeDnsForStage(slug, coord.stage, registryClientDeps(await loadConfig()), true);
35378
+ const bad = (dns.hosts ?? []).filter((h) => h.status === "conflict");
35379
+ applied.push(`edge dns ${coord.stage}: ${dns.required ? (dns.hosts ?? []).map((h) => `${h.host} ${h.status}`).join(", ") : "not required"}`);
35380
+ for (const h of bad) deferredFailures.push(`edge dns ${coord.stage} ${h.host}: ${h.hint}`);
35381
+ } catch (e) {
35382
+ applied.push(`edge dns ${coord.stage} (failed: ${e.message})`);
35383
+ deferredFailures.push(`edge dns ${coord.stage} not ensured: ${e.message}; re-run: mmi-cli devops runtime edge dns ensure ${repo} ${coord.stage}`);
35384
+ }
35385
+ }
35386
+ let seedPrs = [];
35387
+ if (seedPlan.mode === "pr" && seedPlan.branch) {
35388
+ try {
35389
+ seedPrs = JSON.parse((await gh2(["pr", "list", "--repo", repo, "--head", seedPlan.branch, "--base", baseBranch, "--state", "all", "--json", "number,url,state,mergedAt"])).stdout || "[]");
35390
+ } catch {
35391
+ seedPrs = [];
35392
+ }
35393
+ }
35394
+ const firstDeploy = decideFirstDevDeploy({ devRowWritten: boxDeployPlan.some((c) => c.stage === "dev"), seedMode: seedPlan.mode, seedPrs });
35395
+ if (firstDeploy === "defer") {
35396
+ applied.push("first dev deploy: deferred \u2014 the seed PR is not merged; the Hub dev auto-deploy runs on its merge (DEPLOY#dev now exists)");
35397
+ } else if (firstDeploy === "dispatch") {
35398
+ try {
35399
+ const out = await execFileP(process.execPath, [process.argv[1], ...canonicalArgvFor(["runtime", "tenant", "redeploy", repo, "dev", "--json"])], { timeout: 12e4 });
35400
+ let run = {};
35401
+ try {
35402
+ run = JSON.parse(out.stdout);
35403
+ } catch {
35404
+ }
35405
+ applied.push(`first dev deploy dispatched at development: ${run.runUrl ?? run.dispatch ?? "run not yet correlated"}`);
35406
+ } catch (e) {
35407
+ applied.push(`first dev deploy (failed: ${e.message.split("\n")[0]})`);
35408
+ deferredFailures.push(`first dev deploy not dispatched; run: mmi-cli devops runtime tenant redeploy ${repo} dev --watch`);
35409
+ }
35410
+ }
35312
35411
  controlRows = controlRows.map((row) => {
35313
35412
  const coord = boxDeployPlan.find((p) => row.target === `DEPLOY#${p.stage}`);
35314
35413
  return coord ? {
@@ -53143,6 +53242,17 @@ function registerEdgeCommands(program3) {
53143
53242
  process.exitCode = 1;
53144
53243
  }
53145
53244
  });
53245
+ 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) => {
53246
+ if (action !== "status" && action !== "ensure") return fail("runtime edge dns: <action> must be status or ensure");
53247
+ if (stage !== "dev" && stage !== "rc" && stage !== "main") return fail("runtime edge dns: <stage> must be dev, rc, or main");
53248
+ try {
53249
+ const result = await edgeDnsForStage(slugOf(repo), stage, registryClientDeps(await loadConfig()), action === "ensure");
53250
+ console.log(JSON.stringify(result, null, 2));
53251
+ if (result.required && result.complete === false) process.exitCode = 1;
53252
+ } catch (e) {
53253
+ fail(`runtime edge dns: ${e.message}`);
53254
+ }
53255
+ });
53146
53256
  }
53147
53257
 
53148
53258
  // src/command-register-developer.ts
package/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.99",
3
+ "version": "4.5.100",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/codex-plugin",
3
- "version": "4.5.99",
3
+ "version": "4.5.100",
4
4
  "description": "MMI workflow skills and org gates delivery.",
5
5
  "author": {
6
6
  "name": "MMI Future",
@@ -481,6 +481,9 @@ web tenants, `--project-type desktop-game --deploy-model none --clear-web-profil
481
481
  `--project-type mobile-app --deploy-model none --clear-web-profile` for a phone app distributed through the
482
482
  app stores, and
483
483
  `--class content --project-type content --deploy-model content --clear-web-profile` for a content/KB repo.
484
+ An **MM-Work module** adds `--var SUITE=mm-work` (#7944): it registers `suite: mm-work` and derives its
485
+ own `dev.<sub>.mutmut.work` + `<sub>.mutmut.work` hosts, with no rc host (the suite serves its rc from the
486
+ live host). Without it, apply derives the `*.mutatismutandis.co` web-app hosts, which a module never uses.
484
487
  Run the apply path with the board variables discovered above, or register the same values with
485
488
  `mmi-cli oracle org project set` from the Hub or from the target project checkout:
486
489
 
@@ -4,9 +4,13 @@ MMI owns the central project registry and provisioning workflows. Read
4
4
  `mmi-cli oracle org project get <owner/repo> --json` before changing hostnames or login settings.
5
5
  Use the declared edge domains for each stage; do not guess hostnames from a slug or create a
6
6
  second DNS/deployment control plane in a product repository. New project setup goes through
7
- [bootstrap](../../bootstrap/SKILL.md).
7
+ [bootstrap](../../bootstrap/SKILL.md). An MM-Work suite module serves from its own
8
+ `dev.<name>.mutmut.work` and `<name>.mutmut.work` hosts, with no rc host; bootstrap declares them
9
+ with `--var SUITE=mm-work`.
8
10
 
9
11
  Cloudflare owns public DNS and edge TLS. Preserve unrelated records during an authorized change.
12
+ The Hub creates each registered host's proxied record during bootstrap and never edits an existing
13
+ one; check or re-run it with `mmi-cli devops runtime edge dns status|ensure <owner/repo> <stage>`.
10
14
  A dev stage is private only after its independent edge-block check succeeds. A configured record
11
15
  or successful deployment alone does not prove access control. Use [stage](../../stage/SKILL.md)
12
16
  for the supported private live workflow.