@m8t-stack/cli 0.2.17 → 0.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +1229 -347
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1227,7 +1227,7 @@ var init_enable_hosted_brain = __esm({
1227
1227
  import { Builtins, Cli } from "clipanion";
1228
1228
 
1229
1229
  // src/lib/package-version.ts
1230
- var CLI_VERSION = "0.2.17";
1230
+ var CLI_VERSION = "0.2.19";
1231
1231
 
1232
1232
  // src/lib/render-error.ts
1233
1233
  init_errors();
@@ -3326,12 +3326,12 @@ function encodeURIPath(str4) {
3326
3326
  return str4.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
3327
3327
  }
3328
3328
  var EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
3329
- var createPathTagFunction = (pathEncoder = encodeURIPath) => function path32(statics, ...params) {
3329
+ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path33(statics, ...params) {
3330
3330
  if (statics.length === 1)
3331
3331
  return statics[0];
3332
3332
  let postPath = false;
3333
3333
  const invalidSegments = [];
3334
- const path33 = statics.reduce((previousValue, currentValue, index) => {
3334
+ const path34 = statics.reduce((previousValue, currentValue, index) => {
3335
3335
  if (/[?#]/.test(currentValue)) {
3336
3336
  postPath = true;
3337
3337
  }
@@ -3348,7 +3348,7 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path32(sta
3348
3348
  }
3349
3349
  return previousValue + currentValue + (index === params.length ? "" : encoded);
3350
3350
  }, "");
3351
- const pathOnly = path33.split(/[?#]/, 1)[0];
3351
+ const pathOnly = path34.split(/[?#]/, 1)[0];
3352
3352
  const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
3353
3353
  let match;
3354
3354
  while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
@@ -3369,10 +3369,10 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path32(sta
3369
3369
  }, "");
3370
3370
  throw new OpenAIError(`Path parameters result in path with invalid segments:
3371
3371
  ${invalidSegments.map((e) => e.error).join("\n")}
3372
- ${path33}
3372
+ ${path34}
3373
3373
  ${underline}`);
3374
3374
  }
3375
- return path33;
3375
+ return path34;
3376
3376
  };
3377
3377
  var path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
3378
3378
 
@@ -10889,9 +10889,9 @@ var OpenAI = class {
10889
10889
  this.apiKey = token;
10890
10890
  return true;
10891
10891
  }
10892
- buildURL(path32, query, defaultBaseURL) {
10892
+ buildURL(path33, query, defaultBaseURL) {
10893
10893
  const baseURL = !__classPrivateFieldGet(this, _OpenAI_instances, "m", _OpenAI_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
10894
- const url = isAbsoluteURL(path32) ? new URL(path32) : new URL(baseURL + (baseURL.endsWith("/") && path32.startsWith("/") ? path32.slice(1) : path32));
10894
+ const url = isAbsoluteURL(path33) ? new URL(path33) : new URL(baseURL + (baseURL.endsWith("/") && path33.startsWith("/") ? path33.slice(1) : path33));
10895
10895
  const defaultQuery = this.defaultQuery();
10896
10896
  const pathQuery = Object.fromEntries(url.searchParams);
10897
10897
  if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
@@ -10919,24 +10919,24 @@ var OpenAI = class {
10919
10919
  */
10920
10920
  async prepareRequest(request, { url, options }) {
10921
10921
  }
10922
- get(path32, opts) {
10923
- return this.methodRequest("get", path32, opts);
10922
+ get(path33, opts) {
10923
+ return this.methodRequest("get", path33, opts);
10924
10924
  }
10925
- post(path32, opts) {
10926
- return this.methodRequest("post", path32, opts);
10925
+ post(path33, opts) {
10926
+ return this.methodRequest("post", path33, opts);
10927
10927
  }
10928
- patch(path32, opts) {
10929
- return this.methodRequest("patch", path32, opts);
10928
+ patch(path33, opts) {
10929
+ return this.methodRequest("patch", path33, opts);
10930
10930
  }
10931
- put(path32, opts) {
10932
- return this.methodRequest("put", path32, opts);
10931
+ put(path33, opts) {
10932
+ return this.methodRequest("put", path33, opts);
10933
10933
  }
10934
- delete(path32, opts) {
10935
- return this.methodRequest("delete", path32, opts);
10934
+ delete(path33, opts) {
10935
+ return this.methodRequest("delete", path33, opts);
10936
10936
  }
10937
- methodRequest(method, path32, opts) {
10937
+ methodRequest(method, path33, opts) {
10938
10938
  return this.request(Promise.resolve(opts).then((opts2) => {
10939
- return { method, path: path32, ...opts2 };
10939
+ return { method, path: path33, ...opts2 };
10940
10940
  }));
10941
10941
  }
10942
10942
  request(options, remainingRetries = null) {
@@ -11058,8 +11058,8 @@ var OpenAI = class {
11058
11058
  }));
11059
11059
  return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
11060
11060
  }
11061
- getAPIList(path32, Page2, opts) {
11062
- return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path32, ...opts2 })) : { method: "get", path: path32, ...opts });
11061
+ getAPIList(path33, Page2, opts) {
11062
+ return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path33, ...opts2 })) : { method: "get", path: path33, ...opts });
11063
11063
  }
11064
11064
  requestAPIList(Page2, options) {
11065
11065
  const request = this.makeRequest(options, null, void 0);
@@ -11153,8 +11153,8 @@ var OpenAI = class {
11153
11153
  }
11154
11154
  async buildRequest(inputOptions, { retryCount = 0 } = {}) {
11155
11155
  const options = { ...inputOptions };
11156
- const { method, path: path32, query, defaultBaseURL } = options;
11157
- const url = this.buildURL(path32, query, defaultBaseURL);
11156
+ const { method, path: path33, query, defaultBaseURL } = options;
11157
+ const url = this.buildURL(path33, query, defaultBaseURL);
11158
11158
  if ("timeout" in options)
11159
11159
  validatePositiveInteger("timeout", options.timeout);
11160
11160
  options.timeout = options.timeout ?? this.timeout;
@@ -12156,10 +12156,10 @@ var BindListCommand = class extends M8tCommand {
12156
12156
  const qs = new URLSearchParams();
12157
12157
  if (this.channel) qs.set("channel", this.channel);
12158
12158
  if (this.status) qs.set("status", this.status);
12159
- const path32 = qs.toString().length > 0 ? `/api/bindings?${qs.toString()}` : "/api/bindings";
12159
+ const path33 = qs.toString().length > 0 ? `/api/bindings?${qs.toString()}` : "/api/bindings";
12160
12160
  const data = await apiCall(
12161
12161
  { gatewayUrl: ctx.gatewayUrl, gatewayClientId: ctx.gatewayClientId },
12162
- { method: "GET", path: path32 },
12162
+ { method: "GET", path: path33 },
12163
12163
  (msg) => this.context.stderr.write(msg + "\n")
12164
12164
  );
12165
12165
  if (mode === "json") {
@@ -13197,6 +13197,13 @@ var defaultGhExec = (cmd, args) => new Promise((resolve3) => {
13197
13197
  const child = spawn2(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
13198
13198
  let stdout = "";
13199
13199
  let stderr = "";
13200
+ let settled = false;
13201
+ const done = (r) => {
13202
+ if (!settled) {
13203
+ settled = true;
13204
+ resolve3(r);
13205
+ }
13206
+ };
13200
13207
  child.stdout.on("data", (d) => {
13201
13208
  stdout += d.toString();
13202
13209
  });
@@ -13204,7 +13211,10 @@ var defaultGhExec = (cmd, args) => new Promise((resolve3) => {
13204
13211
  stderr += d.toString();
13205
13212
  });
13206
13213
  child.on("close", (code) => {
13207
- resolve3({ stdout, stderr, exitCode: code ?? -1 });
13214
+ done({ stdout, stderr, exitCode: code ?? -1 });
13215
+ });
13216
+ child.on("error", (err) => {
13217
+ done({ stdout, stderr: stderr || err.message, exitCode: -1 });
13208
13218
  });
13209
13219
  });
13210
13220
  async function isGhAuthed(exec = defaultGhExec) {
@@ -13360,8 +13370,8 @@ ${r.stderr}`;
13360
13370
  message: `gh api /repos/${slug} failed (exit ${r.exitCode.toString()}): ${(r.stderr || r.stdout).slice(0, 200)}`
13361
13371
  });
13362
13372
  },
13363
- getFile: async (path32) => {
13364
- const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${path32}`]);
13373
+ getFile: async (path33) => {
13374
+ const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${path33}`]);
13365
13375
  return r.exitCode === 0 ? r.stdout : null;
13366
13376
  },
13367
13377
  isEmpty: async () => {
@@ -13535,15 +13545,21 @@ function discoverKvUri(env, override) {
13535
13545
  hint: "Pass --kv-uri or set AZURE_KEYVAULT_URI / KEYVAULT_URI."
13536
13546
  });
13537
13547
  }
13538
- function resolveRepoRoot() {
13548
+ function tryResolveRepoRoot() {
13539
13549
  const marker = path5.join(os2.homedir(), ".m8t-stack", "repo-root");
13540
- if (!fs5.existsSync(marker)) {
13550
+ if (!fs5.existsSync(marker)) return null;
13551
+ const root = fs5.readFileSync(marker, "utf8").trim();
13552
+ return root.length > 0 ? root : null;
13553
+ }
13554
+ function resolveRepoRoot() {
13555
+ const root = tryResolveRepoRoot();
13556
+ if (root === null) {
13541
13557
  throw new LocalCliError({
13542
13558
  code: "M8T_REPO_ROOT_MISSING",
13543
13559
  message: "~/.m8t-stack/repo-root marker missing \u2014 m8t install never completed."
13544
13560
  });
13545
13561
  }
13546
- return fs5.readFileSync(marker, "utf8").trim();
13562
+ return root;
13547
13563
  }
13548
13564
 
13549
13565
  // src/commands/brain/check-app.ts
@@ -14309,7 +14325,7 @@ function appRepoProbe(args) {
14309
14325
  const res = await doFetch(`${GH_API}/repos/${args.repo}`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
14310
14326
  return res.status;
14311
14327
  },
14312
- getFile: (path32) => readRepoFileViaApp({ token: args.token, repo: args.repo, path: path32, fetchImpl: args.fetchImpl }),
14328
+ getFile: (path33) => readRepoFileViaApp({ token: args.token, repo: args.repo, path: path33, fetchImpl: args.fetchImpl }),
14313
14329
  isEmpty: async () => {
14314
14330
  const res = await doFetch(`${GH_API}/repos/${args.repo}/contents`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
14315
14331
  return res.status === 404;
@@ -17447,8 +17463,8 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
17447
17463
  hint: "Pass a ghcr.io/<owner>/<name> repo, or use the BYOC private-ACR deploy flow."
17448
17464
  });
17449
17465
  }
17450
- const path32 = repo.replace(/^ghcr\.io\//, "");
17451
- const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${path32}:pull`);
17466
+ const path33 = repo.replace(/^ghcr\.io\//, "");
17467
+ const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${path33}:pull`);
17452
17468
  if (!tokenRes.ok) {
17453
17469
  throw new LocalCliError({
17454
17470
  code: "PLATFORM_GHCR_TOKEN_FAILED",
@@ -17464,7 +17480,7 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
17464
17480
  hint: "Make the package public (deploy/ghcr-package-visibility.md), then retry."
17465
17481
  });
17466
17482
  }
17467
- const tagsRes = await fetchImpl(`https://ghcr.io/v2/${path32}/tags/list`, {
17483
+ const tagsRes = await fetchImpl(`https://ghcr.io/v2/${path33}/tags/list`, {
17468
17484
  headers: { Authorization: `Bearer ${token}` }
17469
17485
  });
17470
17486
  if (!tagsRes.ok) {
@@ -17480,7 +17496,7 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
17480
17496
  // src/lib/release-channel.ts
17481
17497
  import * as fs18 from "fs";
17482
17498
 
17483
- // src/lib/release-manifest.ts
17499
+ // ../../packages/platform-release/dist/esm/manifest.js
17484
17500
  var SEVERITIES = ["critical", "recommended", "optional"];
17485
17501
  var IMAGE_KEYS = ["gateway", "codingAgent", "azureExecutor", "installer"];
17486
17502
  function isObj2(v) {
@@ -17488,16 +17504,20 @@ function isObj2(v) {
17488
17504
  }
17489
17505
  function validateManifest(m) {
17490
17506
  const errors = [];
17491
- if (!isObj2(m)) return ["manifest is not an object"];
17492
- if (m.schemaVersion !== 1) errors.push("schemaVersion must be the integer 1");
17507
+ if (!isObj2(m))
17508
+ return ["manifest is not an object"];
17509
+ if (m.schemaVersion !== 1)
17510
+ errors.push("schemaVersion must be the integer 1");
17493
17511
  const p = m.platform;
17494
17512
  if (!isObj2(p)) {
17495
17513
  errors.push("platform is required");
17496
17514
  } else {
17497
17515
  for (const f of ["version", "tag", "releasedAt", "commit", "notes", "notesUrl"]) {
17498
- if (typeof p[f] !== "string" || p[f].length === 0) errors.push(`platform.${f} is required`);
17516
+ if (typeof p[f] !== "string" || p[f].length === 0)
17517
+ errors.push(`platform.${f} is required`);
17499
17518
  }
17500
- if (!SEVERITIES.includes(p.severity)) errors.push(`platform.severity must be one of ${SEVERITIES.join("|")}`);
17519
+ if (!SEVERITIES.includes(p.severity))
17520
+ errors.push(`platform.severity must be one of ${SEVERITIES.join("|")}`);
17501
17521
  if (!(typeof p.previousVersion === "string" || p.previousVersion === null)) {
17502
17522
  errors.push("platform.previousVersion must be a string or null");
17503
17523
  }
@@ -17513,9 +17533,11 @@ function validateManifest(m) {
17513
17533
  errors.push(`components.${key2} is required`);
17514
17534
  continue;
17515
17535
  }
17516
- if (img.kind !== "image") errors.push(`components.${key2}.kind must be "image"`);
17536
+ if (img.kind !== "image")
17537
+ errors.push(`components.${key2}.kind must be "image"`);
17517
17538
  for (const f of ["ref", "tag", "version"]) {
17518
- if (typeof img[f] !== "string" || img[f].length === 0) errors.push(`components.${key2}.${f} is required`);
17539
+ if (typeof img[f] !== "string" || img[f].length === 0)
17540
+ errors.push(`components.${key2}.${f} is required`);
17519
17541
  }
17520
17542
  if (typeof img.digest !== "string" || !/^sha256:[0-9a-f]+$/.test(img.digest)) {
17521
17543
  errors.push(`components.${key2}.digest must be a sha256:\u2026 digest`);
@@ -17526,7 +17548,8 @@ function validateManifest(m) {
17526
17548
  errors.push("components.cli (kind: npm) is required");
17527
17549
  } else {
17528
17550
  for (const f of ["package", "version", "min", "recommended"]) {
17529
- if (typeof cli2[f] !== "string" || cli2[f].length === 0) errors.push(`components.cli.${f} is required`);
17551
+ if (typeof cli2[f] !== "string" || cli2[f].length === 0)
17552
+ errors.push(`components.cli.${f} is required`);
17530
17553
  }
17531
17554
  }
17532
17555
  const personas = c.personas;
@@ -17567,11 +17590,75 @@ function validateManifest(m) {
17567
17590
  return errors;
17568
17591
  }
17569
17592
 
17593
+ // ../../packages/platform-release/dist/esm/stamp.js
17594
+ var TABLE = "Metadata";
17595
+ var PK = "system";
17596
+ var RK = "platform";
17597
+ function stampToEntity(s) {
17598
+ return {
17599
+ partitionKey: PK,
17600
+ rowKey: RK,
17601
+ platformVersion: s.platformVersion,
17602
+ updatedAt: s.updatedAt,
17603
+ lastResult: s.lastResult,
17604
+ value: JSON.stringify(s)
17605
+ };
17606
+ }
17607
+ function entityToStamp(e) {
17608
+ const raw = e.value;
17609
+ if (typeof raw !== "string")
17610
+ return null;
17611
+ try {
17612
+ return JSON.parse(raw);
17613
+ } catch {
17614
+ return null;
17615
+ }
17616
+ }
17617
+
17618
+ // ../../packages/platform-release/dist/esm/channel-url.js
17619
+ var CHANNEL_LATEST_URL = "https://github.com/m8t-run/m8t/releases/latest/download/manifest.json";
17620
+ function platformTag(version) {
17621
+ const bare = version.trim().replace(/^platform-/, "").replace(/^v/, "");
17622
+ return `platform-v${bare}`;
17623
+ }
17624
+ function channelUrlForVersion(version) {
17625
+ return `https://github.com/m8t-run/m8t/releases/download/${platformTag(version)}/manifest.json`;
17626
+ }
17627
+
17628
+ // ../../packages/platform-release/dist/esm/apply-request.js
17629
+ var APPLY_REQUEST_PK = "system";
17630
+ var APPLY_REQUEST_RK = "apply-request";
17631
+ var IN_FLIGHT = ["claimed", "applying"];
17632
+ function isInFlight(status) {
17633
+ return IN_FLIGHT.includes(status);
17634
+ }
17635
+ function applyRequestToEntity(r) {
17636
+ return { partitionKey: APPLY_REQUEST_PK, rowKey: APPLY_REQUEST_RK, value: JSON.stringify(r), updatedAt: r.updatedAt };
17637
+ }
17638
+ function entityToApplyRequest(e) {
17639
+ return JSON.parse(e.value);
17640
+ }
17641
+
17642
+ // ../../packages/platform-release/dist/esm/infra-params.js
17643
+ var INFRA_PARAMS_PK = "system";
17644
+ var INFRA_PARAMS_RK = "infra-params";
17645
+ function infraParamsToEntity(p) {
17646
+ return { partitionKey: INFRA_PARAMS_PK, rowKey: INFRA_PARAMS_RK, value: JSON.stringify(p) };
17647
+ }
17648
+ function entityToInfraParams(e) {
17649
+ return JSON.parse(e.value);
17650
+ }
17651
+
17570
17652
  // src/lib/release-channel.ts
17571
17653
  init_errors();
17572
- var CHANNEL_LATEST_URL = "https://github.com/m8t-run/m8t/releases/latest/download/manifest.json";
17573
- function channelUrlForVersion(tag) {
17574
- return `https://github.com/m8t-run/m8t/releases/download/${tag}/manifest.json`;
17654
+ function manifestSourceForVersionTag(channelUrl, version) {
17655
+ const tag = platformTag(version);
17656
+ const custom = channelUrl.trim();
17657
+ if (!custom || custom === CHANNEL_LATEST_URL) return { channel: true, version: tag };
17658
+ if (custom.includes("/releases/latest/download/")) {
17659
+ return { url: custom.replace("/releases/latest/download/", `/releases/download/${tag}/`) };
17660
+ }
17661
+ return { url: custom };
17575
17662
  }
17576
17663
  async function fetchManifest(source, deps = {}) {
17577
17664
  const readFile8 = deps.readFile ?? ((p) => fs18.readFileSync(p, "utf8"));
@@ -17744,28 +17831,6 @@ async function discoverStampStorage(opts) {
17744
17831
  }
17745
17832
 
17746
17833
  // src/lib/platform-stamp.ts
17747
- var TABLE = "Metadata";
17748
- var PK = "system";
17749
- var RK = "platform";
17750
- function stampToEntity(s) {
17751
- return {
17752
- partitionKey: PK,
17753
- rowKey: RK,
17754
- platformVersion: s.platformVersion,
17755
- updatedAt: s.updatedAt,
17756
- lastResult: s.lastResult,
17757
- value: JSON.stringify(s)
17758
- };
17759
- }
17760
- function entityToStamp(e) {
17761
- const raw = e.value;
17762
- if (typeof raw !== "string") return null;
17763
- try {
17764
- return JSON.parse(raw);
17765
- } catch {
17766
- return null;
17767
- }
17768
- }
17769
17834
  async function makeSharedKeyClient(opts) {
17770
17835
  const rg = /\/resourceGroups\/([^/]+)\//i.exec(opts.accountResourceId)?.[1];
17771
17836
  if (!rg) return null;
@@ -17887,6 +17952,24 @@ function buildBicepParams(p) {
17887
17952
  `acrResourceId=${p.acrResourceId}`
17888
17953
  ];
17889
17954
  if (p.foundryTracingMode) params.push(`foundryTracingMode=${p.foundryTracingMode}`);
17955
+ if (p.assignSubscriptionRoles !== void 0) params.push(`assignSubscriptionRoles=${String(p.assignSubscriptionRoles)}`);
17956
+ if (p.provisionUpdater !== void 0) params.push(`provisionUpdater=${String(p.provisionUpdater)}`);
17957
+ if (p.installerImage !== void 0) params.push(`installerImage=${p.installerImage}`);
17958
+ if (p.updateCron !== void 0) params.push(`updateCron=${p.updateCron}`);
17959
+ if (p.channelUrl !== void 0) params.push(`channelUrl=${p.channelUrl}`);
17960
+ if (p.gatewayCpu) params.push(`gatewayCpu=${p.gatewayCpu}`);
17961
+ if (p.gatewayMemory) params.push(`gatewayMemory=${p.gatewayMemory}`);
17962
+ if (p.refereeEnabled) params.push(`refereeEnabled=${p.refereeEnabled}`);
17963
+ if (p.refereeBrainRepos) params.push(`refereeBrainRepos=${p.refereeBrainRepos}`);
17964
+ if (p.refereeFeedRepo) params.push(`refereeFeedRepo=${p.refereeFeedRepo}`);
17965
+ if (p.refereeInstallationId) params.push(`refereeInstallationId=${p.refereeInstallationId}`);
17966
+ if (p.refereeWebhookHmacKvUri) params.push(`refereeWebhookHmacKvUri=${p.refereeWebhookHmacKvUri}`);
17967
+ if (p.examKvUri) params.push(`examKvUri=${p.examKvUri}`);
17968
+ if (p.examLaWorkspaceId) params.push(`examLaWorkspaceId=${p.examLaWorkspaceId}`);
17969
+ if (p.brainEvalDeployment) params.push(`brainEvalDeployment=${p.brainEvalDeployment}`);
17970
+ if (p.brainAppLogin) params.push(`brainAppLogin=${p.brainAppLogin}`);
17971
+ if (p.refereeCheckpointDir) params.push(`refereeCheckpointDir=${p.refereeCheckpointDir}`);
17972
+ if (p.examApiBase) params.push(`examApiBase=${p.examApiBase}`);
17890
17973
  return params;
17891
17974
  }
17892
17975
  async function resolveRepoRoot2(home = os9.homedir()) {
@@ -18142,22 +18225,22 @@ async function applyPlan(plan, deps, ctx) {
18142
18225
  stamp.components.gateway = await deps.applyGateway(a, ctx);
18143
18226
  } else if (a.component === "codingAgent" || a.component === "azureExecutor") {
18144
18227
  const r = await deps.applyAgent(a, ctx);
18145
- if (r !== "absent") {
18146
- stamp.components[a.component] = { ...r, state: "managed" };
18147
- } else {
18148
- ctx.onProgress?.(`${a.component}: not deployed on this install \u2014 stamp left unchanged.`);
18228
+ if (r === "absent") {
18229
+ ctx.onProgress?.(`${a.component}: not deployed on this install \u2014 skipped (not gated).`);
18230
+ continue;
18149
18231
  }
18232
+ stamp.components[a.component] = { ...r, state: "managed" };
18150
18233
  } else {
18151
18234
  if (!a.personaName) {
18152
18235
  throw new LocalCliError({ code: "PLATFORM_PERSONA_NO_NAME", message: "persona action missing personaName" });
18153
18236
  }
18154
18237
  const r = await deps.applyPersona(a, ctx);
18155
18238
  if (r === "absent") {
18156
- ctx.onProgress?.(`persona '${a.personaName}': not deployed on this install \u2014 stamp left unchanged.`);
18157
- } else {
18158
- const prev = Object.hasOwn(stamp.components.personas, a.personaName) ? stamp.components.personas[a.personaName] : void 0;
18159
- stamp.components.personas[a.personaName] = r === "noop" ? { treeSha: a.to, foundryVersion: prev?.foundryVersion ?? "" } : r;
18239
+ ctx.onProgress?.(`persona '${a.personaName}': not deployed on this install \u2014 skipped (not gated).`);
18240
+ continue;
18160
18241
  }
18242
+ const prev = Object.hasOwn(stamp.components.personas, a.personaName) ? stamp.components.personas[a.personaName] : void 0;
18243
+ stamp.components.personas[a.personaName] = r === "noop" ? { treeSha: a.to, foundryVersion: prev?.foundryVersion ?? "" } : r;
18161
18244
  }
18162
18245
  applied.push(a);
18163
18246
  stamp.lastResult = "partial";
@@ -18174,6 +18257,9 @@ async function applyPlan(plan, deps, ctx) {
18174
18257
  }
18175
18258
  stamp.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
18176
18259
  await deps.writeStamp(stamp);
18260
+ if (!isTargeted && ctx.suffix && deps.writeInfraParams) {
18261
+ await deps.writeInfraParams(ctx, ctx.suffix);
18262
+ }
18177
18263
  return { stamp, applied };
18178
18264
  } catch (e) {
18179
18265
  stamp.lastResult = "failed";
@@ -18265,7 +18351,8 @@ async function resolveBicepParamsForConverge(ctx, opts = {}) {
18265
18351
  foundryProjectEndpoint: opts.foundryEndpoint ?? "",
18266
18352
  acrPullIdentityResourceId,
18267
18353
  acrResourceId,
18268
- foundryTracingMode: opts.foundryTracingMode
18354
+ foundryTracingMode: opts.foundryTracingMode,
18355
+ ...opts.assignSubscriptionRoles !== void 0 ? { assignSubscriptionRoles: opts.assignSubscriptionRoles } : {}
18269
18356
  };
18270
18357
  }
18271
18358
  function swapHostedImage(def, newImage) {
@@ -18532,6 +18619,59 @@ import { DefaultAzureCredential as DefaultAzureCredential17 } from "@azure/ident
18532
18619
 
18533
18620
  // src/lib/platform-converge-cli.ts
18534
18621
  import * as path21 from "path";
18622
+
18623
+ // src/lib/platform-infra-params.ts
18624
+ import { TableClient as TableClient2 } from "@azure/data-tables";
18625
+ async function openInfraParamsTable(opts) {
18626
+ const { tableEndpoint } = await discoverStampStorage(opts);
18627
+ return new TableClient2(tableEndpoint, "Metadata", opts.credential);
18628
+ }
18629
+ async function readInfraParams(client) {
18630
+ try {
18631
+ return entityToInfraParams(await client.getEntity(INFRA_PARAMS_PK, INFRA_PARAMS_RK));
18632
+ } catch {
18633
+ return null;
18634
+ }
18635
+ }
18636
+ async function writeInfraParams(client, p) {
18637
+ await client.upsertEntity(infraParamsToEntity(p), "Replace");
18638
+ }
18639
+ async function deriveSuffixVerified(opts) {
18640
+ const list = JSON.parse(
18641
+ await runAz([
18642
+ "containerapp",
18643
+ "list",
18644
+ "-g",
18645
+ opts.resourceGroup,
18646
+ "--subscription",
18647
+ opts.subscriptionId,
18648
+ "--query",
18649
+ "[?starts_with(name, 'm8t-gateway-')].name",
18650
+ "-o",
18651
+ "json"
18652
+ ])
18653
+ );
18654
+ if (list.length !== 1) return null;
18655
+ const suffix = list[0].replace(/^m8t-gateway-/, "");
18656
+ const cae = JSON.parse(
18657
+ await runAz([
18658
+ "containerapp",
18659
+ "env",
18660
+ "list",
18661
+ "-g",
18662
+ opts.resourceGroup,
18663
+ "--subscription",
18664
+ opts.subscriptionId,
18665
+ "--query",
18666
+ `[?name=='m8t-cae-${suffix}'].name`,
18667
+ "-o",
18668
+ "json"
18669
+ ])
18670
+ );
18671
+ return cae.length === 1 ? suffix : null;
18672
+ }
18673
+
18674
+ // src/lib/platform-converge-cli.ts
18535
18675
  init_errors();
18536
18676
  var HOSTED_AGENT_PERSONA_DIR = {
18537
18677
  codingAgent: "coding-agent",
@@ -18591,7 +18731,23 @@ async function buildConvergeDeps(args) {
18591
18731
  },
18592
18732
  async applyInfra(a, ctx) {
18593
18733
  const target = infraTargetSha(ctx.manifest, args.tree);
18594
- return applyInfraDeploy(a, ctx, target, { suffix: args.suffix });
18734
+ const infraTable = await openInfraParamsTable({ credential: args.credential, subscriptionId: args.subscriptionId, resourceGroup: args.resourceGroup });
18735
+ const stamped = (await readInfraParams(infraTable))?.bicep;
18736
+ args.onProgress?.("subscription-scoped modules skipped: updater is RG-scoped; sub-level changes require a founder-run CLI converge.");
18737
+ const opts = {
18738
+ suffix: args.suffix,
18739
+ assignSubscriptionRoles: false,
18740
+ ...stamped ? {
18741
+ tenantId: stamped.tenantId,
18742
+ clientId: stamped.clientId,
18743
+ foundryEndpoint: stamped.foundryProjectEndpoint,
18744
+ foundryResourceId: stamped.foundryResourceId,
18745
+ acrPullIdentity: stamped.acrPullIdentityResourceId,
18746
+ acrResourceId: stamped.acrResourceId,
18747
+ ...stamped.foundryTracingMode ? { foundryTracingMode: stamped.foundryTracingMode } : {}
18748
+ } : {}
18749
+ };
18750
+ return applyInfraDeploy(a, ctx, target, opts);
18595
18751
  },
18596
18752
  async healthGate(_ctx, applied) {
18597
18753
  for (const a of applied) {
@@ -18632,6 +18788,22 @@ async function buildConvergeDeps(args) {
18632
18788
  stamp: { ...s, cli: CLI_VERSION },
18633
18789
  onProgress: args.onProgress
18634
18790
  });
18791
+ },
18792
+ async writeInfraParams(ctx, _suffix) {
18793
+ const table = await openInfraParamsTable({ credential: args.credential, subscriptionId: args.subscriptionId, resourceGroup: args.resourceGroup });
18794
+ const existing = await readInfraParams(table);
18795
+ if (!existing) {
18796
+ warn("infra-params re-stamp skipped: no existing row to refresh (deploy/enable-auto-update writes the authoritative row).");
18797
+ return;
18798
+ }
18799
+ await writeInfraParams(table, {
18800
+ ...existing,
18801
+ bicep: {
18802
+ ...existing.bicep,
18803
+ imageRef: `${ctx.manifest.components.gateway.ref}:${ctx.manifest.components.gateway.tag}`
18804
+ },
18805
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
18806
+ });
18635
18807
  }
18636
18808
  };
18637
18809
  }
@@ -18787,7 +18959,7 @@ var PlatformUpdateCommand = class extends M8tCommand {
18787
18959
  onProgress,
18788
18960
  onWarn
18789
18961
  });
18790
- await applyPlan(plan, deps, { credential: credential2, subscriptionId, resourceGroup, contentDir, manifest, onProgress });
18962
+ await applyPlan(plan, deps, { credential: credential2, subscriptionId, resourceGroup, contentDir, manifest, onProgress, suffix });
18791
18963
  if (mode === "json") this.context.stdout.write(renderJson({ ...plan, applied: true }) + "\n");
18792
18964
  else log(colors.success(`converged \u2192 ${plan.targetVersion} \u2713`));
18793
18965
  return 0;
@@ -18819,8 +18991,497 @@ var PlatformUpdateCommand = class extends M8tCommand {
18819
18991
  }
18820
18992
  };
18821
18993
 
18994
+ // src/commands/platform/converge.ts
18995
+ import { Command as Command33 } from "clipanion";
18996
+
18997
+ // src/lib/platform-converge-mi.ts
18998
+ import { ManagedIdentityCredential } from "@azure/identity";
18999
+ init_errors();
19000
+ function resolveHeadlessContextFromEnv(env) {
19001
+ const need = (k) => {
19002
+ const v = (env[k] ?? "").trim();
19003
+ if (!v) throw new LocalCliError({ code: "HEADLESS_ENV_MISSING", message: `converge: required env ${k} is unset` });
19004
+ return v;
19005
+ };
19006
+ const miClientId = need("MI_CLIENT_ID");
19007
+ return {
19008
+ credential: new ManagedIdentityCredential({ clientId: miClientId }),
19009
+ miClientId,
19010
+ subscriptionId: need("SUBSCRIPTION_ID"),
19011
+ resourceGroup: need("RESOURCE_GROUP"),
19012
+ endpoint: need("FOUNDRY_ENDPOINT"),
19013
+ channelUrl: (env.M8T_UPDATE_CHANNEL_URL ?? "").trim() || CHANNEL_LATEST_URL
19014
+ };
19015
+ }
19016
+
19017
+ // src/lib/apply-request-store.ts
19018
+ import { TableClient as TableClient3 } from "@azure/data-tables";
19019
+ async function openApplyTable(opts) {
19020
+ const { tableEndpoint } = await discoverStampStorage(opts);
19021
+ return new TableClient3(tableEndpoint, "Metadata", opts.credential);
19022
+ }
19023
+ function hasStatus(e, n) {
19024
+ return e.statusCode === n;
19025
+ }
19026
+ async function readApplyRequest(client) {
19027
+ try {
19028
+ const row = await client.getEntity(APPLY_REQUEST_PK, APPLY_REQUEST_RK);
19029
+ return { ...entityToApplyRequest(row), etag: row.etag };
19030
+ } catch (e) {
19031
+ if (hasStatus(e, 404)) return null;
19032
+ throw e;
19033
+ }
19034
+ }
19035
+ async function claimApplyRequest(client, executionId, nowIso, leaseMs) {
19036
+ const cur = await readApplyRequest(client);
19037
+ if (!cur) return null;
19038
+ const leaseExpired = cur.status === "claimed" && cur.leaseUntil !== null && Date.parse(cur.leaseUntil) < Date.parse(nowIso);
19039
+ const claimable = cur.status === "pending" || cur.status === "awaiting-engine-update" || leaseExpired;
19040
+ if (!claimable) return null;
19041
+ const nowMs = Date.parse(nowIso);
19042
+ const leaseUntil = new Date((Number.isNaN(nowMs) ? Date.now() : nowMs) + leaseMs).toISOString();
19043
+ const claimed = { ...cur, status: "claimed", claimedBy: executionId, leaseUntil, updatedAt: nowIso };
19044
+ try {
19045
+ await client.updateEntity({ ...applyRequestToEntity(claimed), etag: cur.etag }, "Merge", { etag: cur.etag });
19046
+ return claimed;
19047
+ } catch (e) {
19048
+ if (hasStatus(e, 412)) return null;
19049
+ throw e;
19050
+ }
19051
+ }
19052
+ async function patchApplyRequest(client, mutate) {
19053
+ const cur = await readApplyRequest(client);
19054
+ if (!cur) return;
19055
+ const next = mutate(cur);
19056
+ await client.updateEntity({ ...applyRequestToEntity(next), etag: cur.etag }, "Merge", { etag: cur.etag });
19057
+ }
19058
+
19059
+ // src/lib/platform-self-update.ts
19060
+ import * as fs22 from "fs";
19061
+ import * as path22 from "path";
19062
+ function toVTag2(v) {
19063
+ return v.startsWith("v") ? v : `v${v}`;
19064
+ }
19065
+ function readBakedReleaseVersion(repoRoot) {
19066
+ if (!repoRoot) return null;
19067
+ try {
19068
+ const raw = fs22.readFileSync(path22.join(repoRoot, "installer", "version.json"), "utf8");
19069
+ const v = JSON.parse(raw).version;
19070
+ return typeof v === "string" && v.length > 0 ? v : null;
19071
+ } catch {
19072
+ return null;
19073
+ }
19074
+ }
19075
+ function needsEngineUpdate(manifest, runningVersion, bakedRelease) {
19076
+ if (runningVersion === "0.0.0-dev") return false;
19077
+ if (bakedRelease != null && bakedRelease.length > 0 && bakedRelease !== manifest.components.installer.version) return true;
19078
+ const running = toVTag2(runningVersion);
19079
+ const min = toVTag2(manifest.components.cli.min);
19080
+ return compareSemver(running, min) < 0;
19081
+ }
19082
+ async function updateOwnJobImage(opts) {
19083
+ const inst = opts.manifest.components.installer;
19084
+ const image = `${inst.ref}:${inst.tag}`;
19085
+ const job = opts.jobName ?? process.env.UPDATER_JOB_NAME;
19086
+ if (!job) throw new Error("UPDATER_JOB_NAME unset \u2014 cannot self-update the updater job image");
19087
+ await runAz([
19088
+ "containerapp",
19089
+ "job",
19090
+ "update",
19091
+ "-n",
19092
+ job,
19093
+ "-g",
19094
+ opts.resourceGroup,
19095
+ "--subscription",
19096
+ opts.subscriptionId,
19097
+ "--image",
19098
+ image,
19099
+ "--only-show-errors"
19100
+ ]);
19101
+ }
19102
+
19103
+ // src/lib/platform-health-gate.ts
19104
+ function nextBreaker(cur) {
19105
+ return cur === "none" ? "rolled-back" : "held";
19106
+ }
19107
+ function gateTargetsFor(applied) {
19108
+ const targets = [];
19109
+ for (const a of applied) {
19110
+ if (a.component === "gateway") {
19111
+ targets.push({ kind: "gateway-version", expected: a.to });
19112
+ } else if (a.component === "codingAgent" || a.component === "azureExecutor") {
19113
+ targets.push({ kind: "agent-queryable", component: a.component });
19114
+ } else if (a.component === "personas" && a.personaName) {
19115
+ targets.push({ kind: "agent-queryable", persona: a.personaName });
19116
+ }
19117
+ }
19118
+ return targets;
19119
+ }
19120
+ function gatePassed(probes) {
19121
+ return probes.every((p) => p.ok);
19122
+ }
19123
+ async function runHealthGateWithRollback(args) {
19124
+ const notify = args.notify ?? (async () => {
19125
+ });
19126
+ const budgetMs = args.gateBudgetMs ?? 5 * 6e4;
19127
+ const target = args.plan.targetVersion;
19128
+ let applied = [];
19129
+ try {
19130
+ const result = await applyPlan(args.plan, args.deps, args.ctx);
19131
+ applied = result.applied;
19132
+ const outcomes = await runGate(args, applied, budgetMs);
19133
+ if (!gatePassed(outcomes)) {
19134
+ const detail = outcomes.find((o) => !o.ok)?.detail ?? "a post-apply health probe failed";
19135
+ return await rollbackOrHold(args, notify, detail);
19136
+ }
19137
+ await patchApplyRequest(args.client, (r) => ({
19138
+ ...r,
19139
+ status: "success",
19140
+ breaker: r.breaker,
19141
+ // unchanged — a clean apply never trips the breaker
19142
+ result: { appliedVersion: target, error: null },
19143
+ updatedAt: args.now().toISOString()
19144
+ }));
19145
+ await notify({ outcome: "success", target });
19146
+ return "success";
19147
+ } catch (e) {
19148
+ return await rollbackOrHold(args, notify, e.message);
19149
+ }
19150
+ }
19151
+ async function runGate(args, applied, budgetMs) {
19152
+ const deadline = args.now().getTime() + budgetMs;
19153
+ const probeGateway = args.probeGatewayVersion ?? defaultGatewayProbe;
19154
+ const probeAgent = args.probeAgentQueryable ?? defaultAgentProbe;
19155
+ const outcomes = [];
19156
+ for (const t of gateTargetsFor(applied)) {
19157
+ if (args.now().getTime() > deadline) {
19158
+ outcomes.push({ ok: false, detail: "health gate exceeded its time budget" });
19159
+ break;
19160
+ }
19161
+ if (t.kind === "gateway-version") {
19162
+ if (!args.gatewayUrl) continue;
19163
+ outcomes.push(await probeGateway(args.gatewayUrl, t.expected));
19164
+ } else {
19165
+ const agentName = "persona" in t ? t.persona : hostedAgentName(t.component);
19166
+ outcomes.push(await probeAgent({ credential: args.credential, endpoint: args.endpoint, agentName }));
19167
+ }
19168
+ }
19169
+ return outcomes;
19170
+ }
19171
+ async function rollbackOrHold(args, notify, error) {
19172
+ const target = args.plan.targetVersion;
19173
+ const rollbackTarget = args.stamp.previousPlatformVersion;
19174
+ const current = await readBreaker(args.client);
19175
+ const next = nextBreaker(current);
19176
+ if (next === "held") {
19177
+ await patchApplyRequest(args.client, (r) => ({
19178
+ ...r,
19179
+ status: "held",
19180
+ breaker: "held",
19181
+ result: { appliedVersion: r.result?.appliedVersion ?? null, error },
19182
+ updatedAt: args.now().toISOString()
19183
+ }));
19184
+ await notify({ outcome: "held", target, error });
19185
+ return "held";
19186
+ }
19187
+ if (rollbackTarget) {
19188
+ await reconvergeToPrevious(args, rollbackTarget);
19189
+ }
19190
+ await patchApplyRequest(args.client, (r) => ({
19191
+ ...r,
19192
+ status: "rolled-back",
19193
+ breaker: "rolled-back",
19194
+ result: { appliedVersion: rollbackTarget, error },
19195
+ updatedAt: args.now().toISOString()
19196
+ }));
19197
+ await notify({ outcome: "rolled-back", target, error });
19198
+ return "rolled-back";
19199
+ }
19200
+ async function reconvergeToPrevious(args, previousVersion) {
19201
+ const fetchManifestImpl = args.fetchManifestImpl ?? fetchManifest;
19202
+ const resolveContentImpl = args.resolveReleaseContentImpl ?? resolveReleaseContent;
19203
+ const fetchTreeImpl = args.fetchRepoTreeImpl ?? fetchRepoTree;
19204
+ const prevManifest = await fetchManifestImpl(manifestSourceForVersionTag(args.channelUrl ?? "", previousVersion));
19205
+ const contentDir = await resolveContentImpl(prevManifest, args.repoRoot ? { contentDir: args.repoRoot } : {});
19206
+ const tree = prevManifest.components.infra?.treeSha ? { treeShaOf: () => void 0 } : await fetchTreeImpl(prevManifest.platform.commit);
19207
+ const liveStamp = await args.deps.readStamp();
19208
+ const rollbackPlan = diffPlan(prevManifest, liveStamp, tree);
19209
+ const rollbackCtx = { ...args.ctx, manifest: prevManifest, contentDir };
19210
+ await applyPlan(rollbackPlan, args.deps, rollbackCtx);
19211
+ }
19212
+ async function defaultGatewayProbe(gatewayUrl, expected) {
19213
+ try {
19214
+ const res = await fetch(`${gatewayUrl}/api/version`, { method: "GET" });
19215
+ if (res.status === 401 || res.status === 403) return { ok: true };
19216
+ if (!res.ok) return { ok: false, detail: `gateway /api/version returned HTTP ${res.status.toString()}` };
19217
+ const body = await res.json().catch(() => ({}));
19218
+ if (body.version && !versionMatches(body.version, expected)) {
19219
+ return { ok: false, detail: `gateway reports version '${body.version}', expected '${expected}'` };
19220
+ }
19221
+ return { ok: true };
19222
+ } catch (e) {
19223
+ return { ok: false, detail: `gateway unreachable: ${e.message}` };
19224
+ }
19225
+ }
19226
+ function versionMatches(running, expected) {
19227
+ const norm = (s) => s.replace(/^v/, "").trim();
19228
+ const r = norm(running);
19229
+ const e = norm(expected);
19230
+ return r === e || r.startsWith(e) || e.startsWith(r);
19231
+ }
19232
+ async function defaultAgentProbe(args) {
19233
+ try {
19234
+ await awaitAgentQueryable({ credential: args.credential, projectEndpoint: args.endpoint, agentName: args.agentName });
19235
+ return { ok: true };
19236
+ } catch (e) {
19237
+ return { ok: false, detail: `agent '${args.agentName}' not queryable: ${e.message}` };
19238
+ }
19239
+ }
19240
+ function hostedAgentName(component) {
19241
+ return component === "codingAgent" ? "coder" : "azure-executor";
19242
+ }
19243
+ async function readBreaker(client) {
19244
+ const row = await readApplyRequest(client);
19245
+ return row?.breaker ?? "none";
19246
+ }
19247
+
19248
+ // src/commands/platform/converge.ts
19249
+ init_errors();
19250
+ function canonicalVersion(v) {
19251
+ const bare = v.replace(/^platform-/, "");
19252
+ return bare.startsWith("v") ? bare : `v${bare}`;
19253
+ }
19254
+ function isForwardTarget(target, installed) {
19255
+ if (!installed) return true;
19256
+ return compareSemver(canonicalVersion(target), canonicalVersion(installed)) >= 0;
19257
+ }
19258
+ async function failIfStillInFlight(client, now, error) {
19259
+ const cur = await readApplyRequest(client);
19260
+ if (!cur || !isInFlight(cur.status)) return;
19261
+ await patchApplyRequest(client, (r) => ({
19262
+ ...r,
19263
+ status: "failed",
19264
+ result: { appliedVersion: null, error },
19265
+ updatedAt: now()
19266
+ }));
19267
+ }
19268
+ var PlatformConvergeCommand = class extends M8tCommand {
19269
+ static paths = [["platform", "converge"]];
19270
+ static usage = Command33.Usage({
19271
+ category: "Platform",
19272
+ description: "Headless updater-job driver: claim a pending apply-request and converge the platform to it.",
19273
+ details: "The Managed-Identity-authenticated entry point run by the updater Container Apps job. One invocation is one tick: it claims the pending apply-request row (if any), refuses a downgrade against the installed stamp, self-fetches and validates the target manifest, self-updates its own engine image when the manifest requires a newer CLI, then drives the F02 converge engine behind the T15 health-gate + auto-rollback. Reads its subscription/RG/endpoint/channel from env (MI_CLIENT_ID, SUBSCRIPTION_ID, RESOURCE_GROUP, FOUNDRY_ENDPOINT, M8T_UPDATE_CHANNEL_URL). Not intended for interactive use \u2014 the founder-facing path is 'm8t platform update'."
19274
+ });
19275
+ async executeCommand() {
19276
+ const ctx = resolveHeadlessContextFromEnv(process.env);
19277
+ const executionId = process.env.CONTAINER_APP_JOB_EXECUTION_NAME ?? `exec-${process.pid.toString()}`;
19278
+ const now = () => (/* @__PURE__ */ new Date()).toISOString();
19279
+ const log = (m) => {
19280
+ this.context.stdout.write(m + "\n");
19281
+ };
19282
+ const client = await openApplyTable({
19283
+ credential: ctx.credential,
19284
+ subscriptionId: ctx.subscriptionId,
19285
+ resourceGroup: ctx.resourceGroup
19286
+ });
19287
+ const claimed = await claimApplyRequest(client, executionId, now(), 30 * 60 * 1e3);
19288
+ if (!claimed) {
19289
+ log("no pending apply-request \u2014 nothing to converge.");
19290
+ return 0;
19291
+ }
19292
+ log(`claimed apply-request \u2192 target ${claimed.target} (execution ${executionId}).`);
19293
+ const stamp = await readStamp({
19294
+ credential: ctx.credential,
19295
+ subscriptionId: ctx.subscriptionId,
19296
+ resourceGroup: ctx.resourceGroup
19297
+ });
19298
+ const installed = stamp ? stamp.platformVersion : null;
19299
+ if (!isForwardTarget(claimed.target, installed)) {
19300
+ await patchApplyRequest(client, (r) => ({
19301
+ ...r,
19302
+ status: "failed",
19303
+ result: { appliedVersion: null, error: `refused downgrade ${installed ?? "(none)"} \u2192 ${claimed.target}` },
19304
+ updatedAt: now()
19305
+ }));
19306
+ log(`refused: target ${claimed.target} is a downgrade from installed ${installed ?? "(none)"}.`);
19307
+ return 1;
19308
+ }
19309
+ const manifest = await fetchManifest(targetManifestSource(ctx, claimed.target));
19310
+ if (manifest.platform.version !== claimed.target) {
19311
+ await patchApplyRequest(client, (r) => ({
19312
+ ...r,
19313
+ status: "failed",
19314
+ result: { appliedVersion: null, error: `channel manifest is version ${manifest.platform.version}, expected the claimed target ${claimed.target}` },
19315
+ updatedAt: now()
19316
+ }));
19317
+ log(`refused: fetched manifest version ${manifest.platform.version} \u2260 claimed target ${claimed.target}.`);
19318
+ return 1;
19319
+ }
19320
+ const repoRoot = tryResolveRepoRoot();
19321
+ const bakedRelease = readBakedReleaseVersion(repoRoot);
19322
+ if (needsEngineUpdate(manifest, CLI_VERSION, bakedRelease)) {
19323
+ log(
19324
+ `self-updating the updater image (baked release ${bakedRelease ?? "(none)"} / engine ${CLI_VERSION} \u2192 target installer ${manifest.components.installer.version}, cli.min ${manifest.components.cli.min}).`
19325
+ );
19326
+ await updateOwnJobImage({
19327
+ subscriptionId: ctx.subscriptionId,
19328
+ resourceGroup: ctx.resourceGroup,
19329
+ manifest
19330
+ });
19331
+ await patchApplyRequest(client, (r) => ({
19332
+ ...r,
19333
+ status: "awaiting-engine-update",
19334
+ phase: "updating the updater engine image",
19335
+ updatedAt: now()
19336
+ }));
19337
+ log("updater image bumped \u2014 awaiting-engine-update. The next tick re-claims with the new engine.");
19338
+ return 0;
19339
+ }
19340
+ assertCliVersionOk(manifest, CLI_VERSION, (w) => this.context.stderr.write(w + "\n"));
19341
+ await patchApplyRequest(client, (r) => ({ ...r, status: "applying", phase: "starting", updatedAt: now() }));
19342
+ let plan;
19343
+ let applyCtx;
19344
+ let deps;
19345
+ try {
19346
+ const project = await resolveFoundryProject({
19347
+ credential: ctx.credential,
19348
+ subscriptionId: ctx.subscriptionId,
19349
+ interactive: false,
19350
+ endpoint: ctx.endpoint
19351
+ });
19352
+ const contentDir = await resolveReleaseContent(manifest, repoRoot ? { contentDir: repoRoot } : {});
19353
+ const tree = manifest.components.infra?.treeSha ? { treeShaOf: () => void 0 } : await fetchRepoTree(manifest.platform.commit);
19354
+ const suffix = await this.recoverSuffix(ctx);
19355
+ const gatewayResourceId = await this.resolveGatewayResourceId(ctx, suffix);
19356
+ deps = await buildConvergeDeps({
19357
+ credential: ctx.credential,
19358
+ subscriptionId: ctx.subscriptionId,
19359
+ resourceGroup: ctx.resourceGroup,
19360
+ contentDir,
19361
+ manifest,
19362
+ tree,
19363
+ project,
19364
+ gatewayResourceId,
19365
+ suffix,
19366
+ onProgress: (m) => {
19367
+ void patchApplyRequest(client, (r) => ({ ...r, phase: m, updatedAt: now() })).catch(() => void 0);
19368
+ log(m);
19369
+ },
19370
+ onWarn: (m) => this.context.stderr.write(m + "\n")
19371
+ });
19372
+ plan = diffPlan(manifest, stamp, tree, { forceInfra: true });
19373
+ applyCtx = {
19374
+ credential: ctx.credential,
19375
+ subscriptionId: ctx.subscriptionId,
19376
+ resourceGroup: ctx.resourceGroup,
19377
+ contentDir,
19378
+ manifest,
19379
+ suffix,
19380
+ onProgress: (m) => {
19381
+ log(m);
19382
+ }
19383
+ };
19384
+ } catch (e) {
19385
+ await failIfStillInFlight(client, now, e.message);
19386
+ throw e;
19387
+ }
19388
+ const outcome = await runHealthGateWithRollback({
19389
+ client,
19390
+ deps,
19391
+ plan,
19392
+ ctx: applyCtx,
19393
+ stamp: stamp ?? seedStampFor(plan),
19394
+ manifest,
19395
+ credential: ctx.credential,
19396
+ endpoint: ctx.endpoint,
19397
+ // The rollback re-converge needs the SAME zero-repo-auth inputs as the
19398
+ // forward path: baked content (repoRoot) + the channel to resolve the
19399
+ // PREVIOUS version's manifest (never GitHub repo auth). Without these the
19400
+ // rollback would 404 on the private pre-OSS repo just like the forward
19401
+ // path did before this fix.
19402
+ channelUrl: ctx.channelUrl,
19403
+ repoRoot,
19404
+ now: () => /* @__PURE__ */ new Date()
19405
+ });
19406
+ log(`converge outcome: ${outcome}.`);
19407
+ return outcome === "success" ? 0 : 1;
19408
+ }
19409
+ /**
19410
+ * Recover the real bicep suffix WITHOUT guessing: prefer the stamped
19411
+ * `system/infra-params` row (written by deploy + every full converge), and
19412
+ * fall back to deriving it from the live gateway container-app name — but only
19413
+ * when that derivation VERIFIES (exactly one gateway + a matching CAE). If
19414
+ * both fail, throw rather than provision a duplicate stack.
19415
+ */
19416
+ async recoverSuffix(ctx) {
19417
+ const infraTable = await openInfraParamsTable({
19418
+ credential: ctx.credential,
19419
+ subscriptionId: ctx.subscriptionId,
19420
+ resourceGroup: ctx.resourceGroup
19421
+ });
19422
+ const stamped = (await readInfraParams(infraTable))?.suffix;
19423
+ const suffix = stamped ?? await deriveSuffixVerified({
19424
+ credential: ctx.credential,
19425
+ subscriptionId: ctx.subscriptionId,
19426
+ resourceGroup: ctx.resourceGroup
19427
+ });
19428
+ if (!suffix) {
19429
+ throw new LocalCliError({
19430
+ code: "PLATFORM_INFRA_SUFFIX_UNRECOVERABLE",
19431
+ message: "Could not recover the deployment's resource-name suffix: no system/infra-params row is stamped, and deriving it from the live gateway did not verify (0 or >1 gateway, or no matching CAE).",
19432
+ hint: "Run a full 'm8t platform update --force-infra --suffix <suffix>' once to stamp system/infra-params, then re-run the updater."
19433
+ });
19434
+ }
19435
+ return suffix;
19436
+ }
19437
+ /** Resolve the gateway container-app ARM resource id for the recovered suffix. */
19438
+ async resolveGatewayResourceId(ctx, suffix) {
19439
+ const id = (await runAz([
19440
+ "containerapp",
19441
+ "show",
19442
+ "-n",
19443
+ `m8t-gateway-${suffix}`,
19444
+ "-g",
19445
+ ctx.resourceGroup,
19446
+ "--subscription",
19447
+ ctx.subscriptionId,
19448
+ "--query",
19449
+ "id",
19450
+ "-o",
19451
+ "tsv"
19452
+ ])).trim();
19453
+ if (!id) {
19454
+ throw new LocalCliError({
19455
+ code: "PLATFORM_GATEWAY_NOT_FOUND",
19456
+ message: `Could not resolve the gateway container app 'm8t-gateway-${suffix}' in resource group ${ctx.resourceGroup}.`
19457
+ });
19458
+ }
19459
+ return id;
19460
+ }
19461
+ };
19462
+ function seedStampFor(plan) {
19463
+ return {
19464
+ schemaVersion: 1,
19465
+ platformVersion: plan.targetVersion,
19466
+ previousPlatformVersion: null,
19467
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
19468
+ lastResult: "success",
19469
+ cli: CLI_VERSION,
19470
+ components: {
19471
+ gateway: { tag: "", digest: "", state: "managed" },
19472
+ codingAgent: { tag: "", digest: "", state: "managed" },
19473
+ azureExecutor: { tag: "", digest: "", state: "managed" },
19474
+ personas: {},
19475
+ infra: { treeSha: "" }
19476
+ }
19477
+ };
19478
+ }
19479
+ function targetManifestSource(ctx, target) {
19480
+ return manifestSourceForVersionTag(ctx.channelUrl, target);
19481
+ }
19482
+
18822
19483
  // src/commands/platform/enable-cost-report.ts
18823
- import { Command as Command33, Option as Option31 } from "clipanion";
19484
+ import { Command as Command34, Option as Option31 } from "clipanion";
18824
19485
 
18825
19486
  // src/lib/wire-gateway-acs.ts
18826
19487
  init_rbac();
@@ -18860,7 +19521,7 @@ async function wireGatewayForAcs(args) {
18860
19521
  init_errors();
18861
19522
  var PlatformEnableCostReportCommand = class extends M8tCommand {
18862
19523
  static paths = [["platform", "enable-cost-report"]];
18863
- static usage = Command33.Usage({
19524
+ static usage = Command34.Usage({
18864
19525
  description: "Wire the deployed gateway to send the bi-weekly cost report via ACS Email.",
18865
19526
  details: "Discovers the live gateway Container App, grants its managed identity Contributor at the ACS resource scope (authorising the ACS Email send), and sets M8T_ACS_ENDPOINT / M8T_ACS_SENDER / M8T_ENABLE_COST_REPORTER=1 on the gateway. Idempotent \u2014 safely re-runnable. ACS is created during the executor deploy; pass its endpoint, sender, and resource id (from that deploy or 'az communication list')."
18866
19527
  });
@@ -18944,8 +19605,161 @@ var PlatformEnableCostReportCommand = class extends M8tCommand {
18944
19605
  }
18945
19606
  };
18946
19607
 
19608
+ // src/commands/platform/enable-auto-update.ts
19609
+ import { Command as Command35, Option as Option32 } from "clipanion";
19610
+ import { confirm as confirm6 } from "@inquirer/prompts";
19611
+ import { DefaultAzureCredential as DefaultAzureCredential18 } from "@azure/identity";
19612
+ init_errors();
19613
+ var PlatformEnableAutoUpdateCommand = class extends M8tCommand {
19614
+ static paths = [["platform", "enable-auto-update"]];
19615
+ static usage = Command35.Usage({
19616
+ category: "Platform",
19617
+ description: "Retrofit the auto-updater (MI + cron job + role assignments) onto an existing install.",
19618
+ details: "For an install that predates the Platform Update Framework: recovers the deployment's resource-name suffix (from the stamped system/infra-params row, or by verified derivation from the live gateway), recovers the other bicep params from live state (WITHOUT changing the deployed gateway image), and re-runs deploy/main.bicep with provisionUpdater=true \u2014 which provisions the updater managed identity, the cron Container Apps Job, and every role assignment (Owner@RG + Foundry User@account + Storage Table + Storage Blob + AcrPull), all scoped to the resource group. On success, backfills system/infra-params so future converges never have to re-derive the suffix. Idempotent \u2014 safe to re-run."
19619
+ });
19620
+ subscription = Option32.String("--subscription");
19621
+ resourceGroup = Option32.String("--resource-group", {
19622
+ description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
19623
+ });
19624
+ suffix = Option32.String("--suffix", {
19625
+ description: "The existing deployment's resource-name suffix. Required when it cannot be recovered from system/infra-params or the live gateway."
19626
+ });
19627
+ installerImage = Option32.String("--installer-image", {
19628
+ required: true,
19629
+ description: "Updater CA-Job image ref (the converge engine at the current release). Required \u2014 an empty image would skip provisioning."
19630
+ });
19631
+ updateCron = Option32.String("--update-cron", {
19632
+ description: "Cron schedule for the updater job (bicep default applies when omitted)."
19633
+ });
19634
+ channelUrl = Option32.String("--channel-url", {
19635
+ description: "Release-channel URL the updater job polls (bicep default applies when omitted)."
19636
+ });
19637
+ endpoint = Option32.String("--endpoint", {
19638
+ description: "Foundry project endpoint URL. Disambiguates the project in a multi-project subscription."
19639
+ });
19640
+ yes = Option32.Boolean("--yes", false);
19641
+ output = Option32.String("--output");
19642
+ async executeCommand() {
19643
+ const mode = resolveOutputMode(
19644
+ this.output,
19645
+ this.context.stdout
19646
+ );
19647
+ const interactive = this.context.stdout.isTTY === true;
19648
+ const log = (msg) => {
19649
+ if (mode !== "json") this.context.stdout.write(msg + "\n");
19650
+ };
19651
+ const onProgress = (m) => {
19652
+ log(colors.dim(m));
19653
+ };
19654
+ const gw = await resolveGatewayContext({
19655
+ interactive: mode !== "json",
19656
+ subscriptionId: this.subscription,
19657
+ resourceGroup: this.resourceGroup
19658
+ });
19659
+ const { resourceGroup, name: gatewayName } = parseContainerAppResourceId(gw.containerAppResourceId);
19660
+ const credential2 = new DefaultAzureCredential18();
19661
+ const account = await getAzAccount();
19662
+ const subscriptionId = this.subscription ?? account.subscriptionId;
19663
+ const explicitSuffix = typeof this.suffix === "string" && this.suffix.length > 0 ? this.suffix : void 0;
19664
+ onProgress("recovering the deployment's resource-name suffix\u2026");
19665
+ const infraTable = await openInfraParamsTable({ credential: credential2, subscriptionId, resourceGroup });
19666
+ const stampedParams = await readInfraParams(infraTable);
19667
+ const derivedSuffix = explicitSuffix ? void 0 : stampedParams?.suffix ?? await deriveSuffixVerified({ credential: credential2, subscriptionId, resourceGroup }) ?? void 0;
19668
+ const suffix = explicitSuffix ?? derivedSuffix;
19669
+ if (!suffix) {
19670
+ throw new LocalCliError({
19671
+ code: "PLATFORM_ENABLE_AUTO_UPDATE_SUFFIX_UNRECOVERABLE",
19672
+ message: "Could not recover the deployment's resource-name suffix: no --suffix was given, no system/infra-params row is stamped, and deriving it from the live gateway did not verify (0 or >1 gateway, or no matching CAE).",
19673
+ hint: "Pass --suffix <existing-suffix> explicitly (from the deployed resource names, e.g. m8t-gateway-<suffix>)."
19674
+ });
19675
+ }
19676
+ if (!explicitSuffix && !stampedParams && !this.yes && interactive) {
19677
+ const ok = await confirm6({
19678
+ message: `Derived suffix '${suffix}' from the live gateway \u2014 proceed?`,
19679
+ default: true
19680
+ });
19681
+ if (!ok) {
19682
+ log(colors.dim("aborted \u2014 no change. Re-run with --suffix <suffix> to specify it explicitly."));
19683
+ return 0;
19684
+ }
19685
+ }
19686
+ onProgress("resolving Foundry project\u2026");
19687
+ const project = await resolveFoundryProject({
19688
+ credential: credential2,
19689
+ subscriptionId,
19690
+ interactive,
19691
+ endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0
19692
+ });
19693
+ onProgress("reading the live gateway image (left unchanged)\u2026");
19694
+ const imageRef = (await runAz(["containerapp", "show", "-n", gatewayName, "-g", resourceGroup, "--query", "properties.template.containers[0].image", "-o", "tsv"])).trim();
19695
+ if (!imageRef) {
19696
+ throw new LocalCliError({
19697
+ code: "PLATFORM_ENABLE_AUTO_UPDATE_NO_IMAGE",
19698
+ message: `Could not read the live image for gateway '${gatewayName}' in resource group ${resourceGroup}.`
19699
+ });
19700
+ }
19701
+ const location = (await runAz(["resource", "list", "-g", resourceGroup, "--subscription", subscriptionId, "--query", "[0].location", "-o", "tsv"])).trim();
19702
+ if (!location) {
19703
+ throw new LocalCliError({
19704
+ code: "PLATFORM_ENABLE_AUTO_UPDATE_NO_LOCATION",
19705
+ message: `Could not resolve a location for resource group ${resourceGroup} \u2014 it may be empty.`
19706
+ });
19707
+ }
19708
+ const foundryResourceId = await resolveFoundryResourceId(project.endpoint);
19709
+ const acrPullIdentityResourceId = resolveAcrPullIdentity({});
19710
+ const acrResourceId = acrPullIdentityResourceId ? "" : await resolveAcrResourceId({ imageRef });
19711
+ const recoveredParams = {
19712
+ location,
19713
+ suffix,
19714
+ imageRef,
19715
+ tenantId: gw.gatewayTenantId,
19716
+ clientId: gw.gatewayClientId,
19717
+ foundryResourceId,
19718
+ foundryProjectEndpoint: project.endpoint,
19719
+ acrPullIdentityResourceId,
19720
+ acrResourceId
19721
+ };
19722
+ onProgress("running Bicep deployment to provision the updater (~2-4 min)\u2026");
19723
+ const repoRoot = await resolveRepoRoot2();
19724
+ await runBicepDeployment({
19725
+ resourceGroup,
19726
+ repoRoot,
19727
+ params: buildBicepParams({
19728
+ ...recoveredParams,
19729
+ provisionUpdater: true,
19730
+ installerImage: this.installerImage,
19731
+ ...typeof this.updateCron === "string" ? { updateCron: this.updateCron } : {},
19732
+ ...typeof this.channelUrl === "string" ? { channelUrl: this.channelUrl } : {}
19733
+ }),
19734
+ deploymentName: `m8t-enable-auto-update-${suffix.slice(0, 12)}`
19735
+ });
19736
+ onProgress("backfilling system/infra-params\u2026");
19737
+ await writeInfraParams(infraTable, {
19738
+ schemaVersion: 1,
19739
+ suffix,
19740
+ bicep: recoveredParams,
19741
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
19742
+ });
19743
+ if (mode === "json") {
19744
+ this.context.stdout.write(renderJson({ suffix, resourceGroup, installerImage: this.installerImage, enabled: true }) + "\n");
19745
+ return 0;
19746
+ }
19747
+ this.context.stdout.write(
19748
+ renderKeyValueBlock([
19749
+ { key: "suffix", value: suffix },
19750
+ { key: "resource group", value: resourceGroup },
19751
+ { key: "installer image", value: this.installerImage },
19752
+ { key: "infra-params", value: "backfilled" }
19753
+ ]) + "\n"
19754
+ );
19755
+ log(colors.success("auto-update enabled \u2014 the updater job will start converging on its next cron tick \u2713"));
19756
+ log(colors.dim("next: 'm8t platform status' to check drift, or 'm8t platform update' to converge interactively at any time."));
19757
+ return 0;
19758
+ }
19759
+ };
19760
+
18947
19761
  // src/commands/deploy.ts
18948
- import { Command as Command34, Option as Option32 } from "clipanion";
19762
+ import { Command as Command36, Option as Option33 } from "clipanion";
18949
19763
 
18950
19764
  // src/lib/app-reg.ts
18951
19765
  init_errors();
@@ -19206,7 +20020,7 @@ async function patchRedirectUris(appObjectId, fqdn) {
19206
20020
  init_errors();
19207
20021
 
19208
20022
  // src/lib/whatif.ts
19209
- import * as path22 from "path";
20023
+ import * as path23 from "path";
19210
20024
  function deriveResourceType(resourceId) {
19211
20025
  if (resourceId.startsWith("[")) return "";
19212
20026
  const afterProviders = resourceId.split("/providers/")[1];
@@ -19217,7 +20031,7 @@ function deriveResourceType(resourceId) {
19217
20031
  return parts.join("/");
19218
20032
  }
19219
20033
  async function runWhatIf(opts) {
19220
- const bicepPath = path22.join(opts.repoRoot, "deploy", "main.bicep");
20034
+ const bicepPath = path23.join(opts.repoRoot, "deploy", "main.bicep");
19221
20035
  const out = await runAz([
19222
20036
  "deployment",
19223
20037
  "group",
@@ -19282,9 +20096,9 @@ function flattenDelta(delta, prefix = "") {
19282
20096
  const out = [];
19283
20097
  for (const d of delta) {
19284
20098
  const segment = /^[0-9]+$/.test(d.path) ? `[${d.path}]` : prefix ? `.${d.path}` : d.path;
19285
- const path32 = prefix ? `${prefix}${segment}` : d.path;
19286
- if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children, path32));
19287
- else out.push({ ...d, path: path32 });
20099
+ const path33 = prefix ? `${prefix}${segment}` : d.path;
20100
+ if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children, path33));
20101
+ else out.push({ ...d, path: path33 });
19288
20102
  }
19289
20103
  return out;
19290
20104
  }
@@ -19334,24 +20148,40 @@ function classifyWhatIf(changes) {
19334
20148
  var DEFAULT_IMAGE_REF = "ghcr.io/m8t-run/m8t:latest";
19335
20149
  var DeployCommand = class extends M8tCommand {
19336
20150
  static paths = [["deploy"]];
19337
- static usage = Command34.Usage({
20151
+ static usage = Command36.Usage({
19338
20152
  description: "Deploy (or update) the m8t gateway/webapp stack via Bicep.",
19339
20153
  details: "Ensures the Entra app reg (or pass --client-id to reuse an existing one \u2014 required if you can't create app regs), writes ~/.m8t-stack/config.yaml, ensures the resource group, and runs deploy/main.bicep. The repo is located via ~/.m8t-stack/repo-root."
19340
20154
  });
19341
- subscription = Option32.String("--subscription");
19342
- resourceGroup = Option32.String("--resource-group", "rg-m8t-stack");
19343
- location = Option32.String("--location", "eastus");
19344
- suffix = Option32.String("--suffix", "");
19345
- imageRef = Option32.String("--image-ref", DEFAULT_IMAGE_REF);
19346
- acrPullIdentity = Option32.String("--acrpull-identity");
19347
- acrResourceId = Option32.String("--acr-resource-id");
19348
- foundryEndpoint = Option32.String("--foundry-endpoint");
19349
- foundryResourceId = Option32.String("--foundry-resource-id");
19350
- foundryTracing = Option32.String("--foundry-tracing");
20155
+ subscription = Option33.String("--subscription");
20156
+ resourceGroup = Option33.String("--resource-group", "rg-m8t-stack");
20157
+ location = Option33.String("--location", "eastus");
20158
+ suffix = Option33.String("--suffix", "");
20159
+ imageRef = Option33.String("--image-ref", DEFAULT_IMAGE_REF);
20160
+ acrPullIdentity = Option33.String("--acrpull-identity");
20161
+ acrResourceId = Option33.String("--acr-resource-id");
20162
+ foundryEndpoint = Option33.String("--foundry-endpoint");
20163
+ foundryResourceId = Option33.String("--foundry-resource-id");
20164
+ foundryTracing = Option33.String("--foundry-tracing");
19351
20165
  // project | account | skip (bicep default: project)
19352
- clientId = Option32.String("--client-id");
19353
- whatIf = Option32.Boolean("--what-if", false);
19354
- output = Option32.String("--output");
20166
+ clientId = Option33.String("--client-id");
20167
+ whatIf = Option33.Boolean("--what-if", false);
20168
+ output = Option33.String("--output");
20169
+ // Referee (E2-F5) — all optional, undefined by default ⇒ the bicep defaults
20170
+ // apply (dormant: gatewayCpu=0.25, gatewayMemory=0.5Gi, every referee/exam var
20171
+ // empty). Only pass these when explicitly enabling the referee exam stack.
20172
+ gatewayCpu = Option33.String("--gateway-cpu");
20173
+ gatewayMemory = Option33.String("--gateway-memory");
20174
+ refereeEnabled = Option33.String("--referee-enabled");
20175
+ refereeBrainRepos = Option33.String("--referee-brain-repos");
20176
+ refereeFeedRepo = Option33.String("--referee-feed-repo");
20177
+ refereeInstallationId = Option33.String("--referee-installation-id");
20178
+ refereeWebhookHmacKvUri = Option33.String("--referee-webhook-hmac-kv-uri");
20179
+ examKvUri = Option33.String("--exam-kv-uri");
20180
+ examLaWorkspaceId = Option33.String("--exam-la-workspace-id");
20181
+ brainEvalDeployment = Option33.String("--brain-eval-deployment");
20182
+ brainAppLogin = Option33.String("--brain-app-login");
20183
+ refereeCheckpointDir = Option33.String("--referee-checkpoint-dir");
20184
+ examApiBase = Option33.String("--exam-api-base");
19355
20185
  async executeCommand() {
19356
20186
  const mode = resolveOutputMode(
19357
20187
  this.output,
@@ -19395,7 +20225,20 @@ var DeployCommand = class extends M8tCommand {
19395
20225
  foundryProjectEndpoint: foundryEndpoint,
19396
20226
  acrPullIdentityResourceId: acrPullIdentityResourceId2,
19397
20227
  acrResourceId: acrResourceId2,
19398
- foundryTracingMode: parseFoundryTracingMode(this.foundryTracing)
20228
+ foundryTracingMode: parseFoundryTracingMode(this.foundryTracing),
20229
+ gatewayCpu: this.gatewayCpu,
20230
+ gatewayMemory: this.gatewayMemory,
20231
+ refereeEnabled: this.refereeEnabled,
20232
+ refereeBrainRepos: this.refereeBrainRepos,
20233
+ refereeFeedRepo: this.refereeFeedRepo,
20234
+ refereeInstallationId: this.refereeInstallationId,
20235
+ refereeWebhookHmacKvUri: this.refereeWebhookHmacKvUri,
20236
+ examKvUri: this.examKvUri,
20237
+ examLaWorkspaceId: this.examLaWorkspaceId,
20238
+ brainEvalDeployment: this.brainEvalDeployment,
20239
+ brainAppLogin: this.brainAppLogin,
20240
+ refereeCheckpointDir: this.refereeCheckpointDir,
20241
+ examApiBase: this.examApiBase
19399
20242
  });
19400
20243
  const changes = await runWhatIf({ resourceGroup: this.resourceGroup, repoRoot: repoRoot2, params: params2 });
19401
20244
  const classified = classifyWhatIf(changes);
@@ -19430,7 +20273,20 @@ var DeployCommand = class extends M8tCommand {
19430
20273
  foundryProjectEndpoint: foundryEndpoint,
19431
20274
  acrPullIdentityResourceId,
19432
20275
  acrResourceId,
19433
- foundryTracingMode: parseFoundryTracingMode(this.foundryTracing)
20276
+ foundryTracingMode: parseFoundryTracingMode(this.foundryTracing),
20277
+ gatewayCpu: this.gatewayCpu,
20278
+ gatewayMemory: this.gatewayMemory,
20279
+ refereeEnabled: this.refereeEnabled,
20280
+ refereeBrainRepos: this.refereeBrainRepos,
20281
+ refereeFeedRepo: this.refereeFeedRepo,
20282
+ refereeInstallationId: this.refereeInstallationId,
20283
+ refereeWebhookHmacKvUri: this.refereeWebhookHmacKvUri,
20284
+ examKvUri: this.examKvUri,
20285
+ examLaWorkspaceId: this.examLaWorkspaceId,
20286
+ brainEvalDeployment: this.brainEvalDeployment,
20287
+ brainAppLogin: this.brainAppLogin,
20288
+ refereeCheckpointDir: this.refereeCheckpointDir,
20289
+ examApiBase: this.examApiBase
19434
20290
  });
19435
20291
  log("running Bicep deployment (~3-5 min)\u2026");
19436
20292
  const outputs = await runBicepDeployment({
@@ -19465,7 +20321,7 @@ var DeployCommand = class extends M8tCommand {
19465
20321
 
19466
20322
  // src/commands/eval/skill.ts
19467
20323
  import { spawnSync as spawnSync3 } from "child_process";
19468
- import { Command as Command35, Option as Option33 } from "clipanion";
20324
+ import { Command as Command37, Option as Option34 } from "clipanion";
19469
20325
  init_errors();
19470
20326
  var DECISIONS = /* @__PURE__ */ new Set(["promote", "reject", "needs_review"]);
19471
20327
  var JUDGE_STATUSES = /* @__PURE__ */ new Set(["ok", "skipped", "unavailable"]);
@@ -19490,14 +20346,14 @@ function parseVerdict(stdout) {
19490
20346
  }
19491
20347
  var EvalSkillCommand = class extends M8tCommand {
19492
20348
  static paths = [["eval", "skill"]];
19493
- static usage = Command35.Usage({
20349
+ static usage = Command37.Usage({
19494
20350
  description: "Vet one inbox skill candidate (Brain Faculties Eval F1): promote / reject / needs_review. Shells out to the Python `brain-eval` core (override its path with $BRAIN_EVAL_BIN)."
19495
20351
  });
19496
- candidate = Option33.String();
19497
- skillsDir = Option33.String("--skills-dir");
19498
- noJudge = Option33.Boolean("--no-judge", false);
19499
- deployment = Option33.String("--deployment");
19500
- output = Option33.String("--output");
20352
+ candidate = Option34.String();
20353
+ skillsDir = Option34.String("--skills-dir");
20354
+ noJudge = Option34.Boolean("--no-judge", false);
20355
+ deployment = Option34.String("--deployment");
20356
+ output = Option34.String("--output");
19501
20357
  executeCommand() {
19502
20358
  return Promise.resolve(this._runCommand());
19503
20359
  }
@@ -19554,9 +20410,9 @@ var EvalSkillCommand = class extends M8tCommand {
19554
20410
 
19555
20411
  // src/commands/eval/exam.ts
19556
20412
  import { spawnSync as spawnSync4 } from "child_process";
19557
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync15, existsSync as existsSync13, readdirSync } from "fs";
19558
- import { join as join22 } from "path";
19559
- import { Command as Command36, Option as Option34 } from "clipanion";
20413
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync16, existsSync as existsSync13, readdirSync } from "fs";
20414
+ import { join as join23 } from "path";
20415
+ import { Command as Command38, Option as Option35 } from "clipanion";
19560
20416
  init_errors();
19561
20417
  init_esm();
19562
20418
  function parseArmToken(tok, opts) {
@@ -19644,7 +20500,7 @@ function parseExamVerdict(stdout) {
19644
20500
  }
19645
20501
  return v;
19646
20502
  }
19647
- var DEFAULT_REPS = { impact: 3, dream_delta: 5 };
20503
+ var DEFAULT_REPS = { impact: 3, dream_delta: 5, skill_abstraction: 5 };
19648
20504
  function inferExamType(arms, explicit) {
19649
20505
  if (explicit !== void 0) {
19650
20506
  if (!EXAM_TYPES.has(explicit)) {
@@ -19665,6 +20521,22 @@ function inferExamType(arms, explicit) {
19665
20521
  message: "could not infer --exam-type from arms \u2014 pass --exam-type impact|dream_delta|tournament"
19666
20522
  });
19667
20523
  }
20524
+ function assertAbstractionArmsExplicit(examType, arms, skill) {
20525
+ if (examType !== "skill_abstraction") return;
20526
+ if (typeof skill !== "string" || skill.length === 0) {
20527
+ throw new LocalCliError({
20528
+ code: "USAGE",
20529
+ message: "--exam-type skill_abstraction requires --skill <slug> (the promoted skill's slug)"
20530
+ });
20531
+ }
20532
+ const bothPinned = arms.length === 2 && arms.every((a) => typeof a.state === "object" && "pinned" in a.state);
20533
+ if (!bothPinned) {
20534
+ throw new LocalCliError({
20535
+ code: "USAGE",
20536
+ message: "--exam-type skill_abstraction requires exactly 2 explicit pinned arms (the promotion event's base/merge shas), e.g. --arms 'pinned:<base>+pinned:<merge>'"
20537
+ });
20538
+ }
20539
+ }
19668
20540
  function resolveJudgeDeployment(flag, env) {
19669
20541
  if (typeof flag === "string" && flag.length > 0) return { deployment: flag };
19670
20542
  const fromEnv = env.EXAM_JUDGE_DEPLOYMENT;
@@ -19721,9 +20593,9 @@ function resolveRefereeHome(refereeHomeOut, env) {
19721
20593
  return null;
19722
20594
  }
19723
20595
  function resolveTaskSetDir(base, worker, version) {
19724
- if (existsSync13(join22(base, version))) return version;
20596
+ if (existsSync13(join23(base, version))) return version;
19725
20597
  const prefixed = `${worker}-${version}`;
19726
- if (existsSync13(join22(base, prefixed))) return prefixed;
20598
+ if (existsSync13(join23(base, prefixed))) return prefixed;
19727
20599
  if (version === "latest" && existsSync13(base)) {
19728
20600
  const dirs = readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith(`${worker}-`)).map((d) => d.name).sort();
19729
20601
  if (dirs.length > 0) return dirs[dirs.length - 1];
@@ -19739,11 +20611,11 @@ function readSealedTaskIds(taskSetVersion, refereeHomeOut, env = process.env) {
19739
20611
  message: "cannot resolve the referee home to read the sealed-task manifest \u2014 set $REFEREE_HOME (mirroring the Python runner) so the feed can be redacted; refusing to emit a possibly-unredacted feed (Law-1)"
19740
20612
  });
19741
20613
  }
19742
- const versionDir = resolveTaskSetDir(join22(home, "tasksets", worker), worker, version);
19743
- const manifestPath = join22(home, "tasksets", worker, versionDir, "manifest.yaml");
20614
+ const versionDir = resolveTaskSetDir(join23(home, "tasksets", worker), worker, version);
20615
+ const manifestPath = join23(home, "tasksets", worker, versionDir, "manifest.yaml");
19744
20616
  let text;
19745
20617
  try {
19746
- text = readFileSync15(manifestPath, "utf-8");
20618
+ text = readFileSync16(manifestPath, "utf-8");
19747
20619
  } catch (e) {
19748
20620
  throw new LocalCliError({
19749
20621
  code: "EXAM_REDACTION_UNSAFE",
@@ -19770,23 +20642,24 @@ function buildPlan(args) {
19770
20642
  }
19771
20643
  var EvalExamCommand = class extends M8tCommand {
19772
20644
  static paths = [["eval", "exam"]];
19773
- static usage = Command36.Usage({
20645
+ static usage = Command38.Usage({
19774
20646
  description: "Run a brain exam (Brain Referee E2-F2): impact A/B + dream-delta. Resolves the plan, then shells out to the Python `brain-exam` orchestrator (override its path with $BRAIN_EXAM_BIN). Renders an ExamVerdict: three-valued verdict + power note + per-task flips."
19775
20647
  });
19776
- worker = Option34.String();
19777
- arms = Option34.String("--arms");
19778
- taskSet = Option34.String("--task-set");
19779
- examType = Option34.String("--exam-type");
19780
- reps = Option34.String("-n,--reps");
19781
- probes = Option34.String("--probes");
19782
- pool = Option34.String("--pool");
19783
- out = Option34.String("--out");
19784
- dryRun = Option34.Boolean("--dry-run", false);
19785
- keepArms = Option34.Boolean("--keep-arms", false);
19786
- allowStub = Option34.Boolean("--allow-stub", false);
19787
- deployment = Option34.String("--deployment");
19788
- output = Option34.String("--output");
19789
- observeWaitS = Option34.String("--observe-wait-s");
20648
+ worker = Option35.String();
20649
+ arms = Option35.String("--arms");
20650
+ taskSet = Option35.String("--task-set");
20651
+ examType = Option35.String("--exam-type");
20652
+ skill = Option35.String("--skill");
20653
+ reps = Option35.String("-n,--reps");
20654
+ probes = Option35.String("--probes");
20655
+ pool = Option35.String("--pool");
20656
+ out = Option35.String("--out");
20657
+ dryRun = Option35.Boolean("--dry-run", false);
20658
+ keepArms = Option35.Boolean("--keep-arms", false);
20659
+ allowStub = Option35.Boolean("--allow-stub", false);
20660
+ deployment = Option35.String("--deployment");
20661
+ output = Option35.String("--output");
20662
+ observeWaitS = Option35.String("--observe-wait-s");
19790
20663
  async executeCommand() {
19791
20664
  await Promise.resolve();
19792
20665
  const worker = typeof this.worker === "string" ? this.worker : void 0;
@@ -19795,6 +20668,7 @@ var EvalExamCommand = class extends M8tCommand {
19795
20668
  if (!armsRaw) throw new LocalCliError({ code: "USAGE", message: "--arms <spec> is required" });
19796
20669
  const arms = parseArmSpec(armsRaw, "git", { allowStub: this.allowStub === true });
19797
20670
  const examType = inferExamType(arms, typeof this.examType === "string" ? this.examType : void 0);
20671
+ assertAbstractionArmsExplicit(examType, arms, typeof this.skill === "string" ? this.skill : void 0);
19798
20672
  let reps = DEFAULT_REPS[examType] ?? 3;
19799
20673
  if (typeof this.reps === "string") {
19800
20674
  const parsed = Number.parseInt(this.reps, 10);
@@ -19862,9 +20736,9 @@ var EvalExamCommand = class extends M8tCommand {
19862
20736
  if (out !== void 0) {
19863
20737
  const sealedTaskIds = taskSetVersion ? readSealedTaskIds(taskSetVersion, out) : /* @__PURE__ */ new Set();
19864
20738
  mkdirSync4(out, { recursive: true });
19865
- writeFileSync4(join22(out, "verdict.json"), JSON.stringify(verdict, null, 2));
20739
+ writeFileSync4(join23(out, "verdict.json"), JSON.stringify(verdict, null, 2));
19866
20740
  const feed = redactForFeed(verdict, sealedTaskIds);
19867
- writeFileSync4(join22(out, "feed.json"), JSON.stringify(feed, null, 2));
20741
+ writeFileSync4(join23(out, "feed.json"), JSON.stringify(feed, null, 2));
19868
20742
  }
19869
20743
  if (mode === "json") {
19870
20744
  this.context.stdout.write(renderJson(verdict) + "\n");
@@ -19900,10 +20774,10 @@ var EvalExamCommand = class extends M8tCommand {
19900
20774
  };
19901
20775
 
19902
20776
  // src/commands/version.ts
19903
- import { Command as Command37, Option as Option35 } from "clipanion";
20777
+ import { Command as Command39, Option as Option36 } from "clipanion";
19904
20778
  var VersionCommand = class extends M8tCommand {
19905
20779
  static paths = [["version"], ["--version"], ["-v"]];
19906
- static usage = Command37.Usage({
20780
+ static usage = Command39.Usage({
19907
20781
  description: "Print the CLI version.",
19908
20782
  details: "Prints the m8t CLI version. With --verbose, also prints Node version and platform.",
19909
20783
  examples: [
@@ -19911,8 +20785,8 @@ var VersionCommand = class extends M8tCommand {
19911
20785
  ["Print as JSON", "$0 version --output json"]
19912
20786
  ]
19913
20787
  });
19914
- output = Option35.String("--output", { description: "pretty | json | auto (default)" });
19915
- verbose = Option35.Boolean("--verbose", false);
20788
+ output = Option36.String("--output", { description: "pretty | json | auto (default)" });
20789
+ verbose = Option36.Boolean("--verbose", false);
19916
20790
  executeCommand() {
19917
20791
  const mode = resolveOutputMode(
19918
20792
  this.output ?? "auto",
@@ -19943,18 +20817,18 @@ var VersionCommand = class extends M8tCommand {
19943
20817
  };
19944
20818
 
19945
20819
  // src/commands/whoami.ts
19946
- import { Command as Command38, Option as Option36 } from "clipanion";
20820
+ import { Command as Command40, Option as Option37 } from "clipanion";
19947
20821
  var WhoamiCommand = class extends M8tCommand {
19948
20822
  static paths = [["whoami"]];
19949
- static usage = Command38.Usage({
20823
+ static usage = Command40.Usage({
19950
20824
  description: "Show your identity + the gateway you'll talk to. Probes the backend."
19951
20825
  });
19952
- output = Option36.String("--output");
19953
- verbose = Option36.Boolean("--verbose", false);
19954
- subscription = Option36.String("--subscription", {
20826
+ output = Option37.String("--output");
20827
+ verbose = Option37.Boolean("--verbose", false);
20828
+ subscription = Option37.String("--subscription", {
19955
20829
  description: "Azure subscription ID to discover gateway in (defaults to active az subscription)."
19956
20830
  });
19957
- resourceGroup = Option36.String("--resource-group", {
20831
+ resourceGroup = Option37.String("--resource-group", {
19958
20832
  description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
19959
20833
  });
19960
20834
  async executeCommand() {
@@ -20019,7 +20893,7 @@ var WhoamiCommand = class extends M8tCommand {
20019
20893
  };
20020
20894
 
20021
20895
  // src/commands/status.ts
20022
- import { Command as Command39, Option as Option37 } from "clipanion";
20896
+ import { Command as Command41, Option as Option38 } from "clipanion";
20023
20897
 
20024
20898
  // src/lib/azd.ts
20025
20899
  init_errors();
@@ -20084,10 +20958,10 @@ async function resolveLocalContext() {
20084
20958
  // src/commands/status.ts
20085
20959
  var StatusCommand = class extends M8tCommand {
20086
20960
  static paths = [["status"]];
20087
- static usage = Command39.Usage({
20961
+ static usage = Command41.Usage({
20088
20962
  description: "Show the local m8t context: identity, config.yaml, gateway cache, azd mode."
20089
20963
  });
20090
- output = Option37.String("--output");
20964
+ output = Option38.String("--output");
20091
20965
  async executeCommand() {
20092
20966
  const mode = resolveOutputMode(
20093
20967
  this.output,
@@ -20125,11 +20999,11 @@ var StatusCommand = class extends M8tCommand {
20125
20999
  };
20126
21000
 
20127
21001
  // src/commands/doctor.ts
20128
- import { Command as Command40, Option as Option38 } from "clipanion";
20129
- import { DefaultAzureCredential as DefaultAzureCredential18 } from "@azure/identity";
20130
- import * as fs22 from "fs";
21002
+ import { Command as Command42, Option as Option39 } from "clipanion";
21003
+ import { DefaultAzureCredential as DefaultAzureCredential19 } from "@azure/identity";
21004
+ import * as fs23 from "fs";
20131
21005
  import * as os10 from "os";
20132
- import * as path23 from "path";
21006
+ import * as path24 from "path";
20133
21007
 
20134
21008
  // src/lib/doctor-checks.ts
20135
21009
  function checkAzSignedIn(az) {
@@ -20354,34 +21228,34 @@ async function resolveAccountLocation(accountId) {
20354
21228
  }
20355
21229
  }
20356
21230
  function probeRepoRoot() {
20357
- const marker = path23.join(os10.homedir(), ".m8t-stack", "repo-root");
20358
- if (!fs22.existsSync(marker)) {
21231
+ const marker = path24.join(os10.homedir(), ".m8t-stack", "repo-root");
21232
+ if (!fs23.existsSync(marker)) {
20359
21233
  return { markerExists: false, path: null, isDir: false, isGitCheckout: false };
20360
21234
  }
20361
21235
  let repoPath;
20362
21236
  try {
20363
- repoPath = fs22.readFileSync(marker, "utf8").trim();
21237
+ repoPath = fs23.readFileSync(marker, "utf8").trim();
20364
21238
  } catch {
20365
21239
  return { markerExists: true, path: null, isDir: false, isGitCheckout: false };
20366
21240
  }
20367
21241
  if (!repoPath) return { markerExists: true, path: null, isDir: false, isGitCheckout: false };
20368
21242
  let isDir = false;
20369
21243
  try {
20370
- isDir = fs22.statSync(repoPath).isDirectory();
21244
+ isDir = fs23.statSync(repoPath).isDirectory();
20371
21245
  } catch {
20372
21246
  isDir = false;
20373
21247
  }
20374
- const isGitCheckout = isDir && fs22.existsSync(path23.join(repoPath, ".git"));
21248
+ const isGitCheckout = isDir && fs23.existsSync(path24.join(repoPath, ".git"));
20375
21249
  return { markerExists: true, path: repoPath, isDir, isGitCheckout };
20376
21250
  }
20377
21251
  var DoctorCommand = class extends M8tCommand {
20378
21252
  static paths = [["doctor"]];
20379
- static usage = Command40.Usage({
21253
+ static usage = Command42.Usage({
20380
21254
  description: "Diagnose the local m8t setup: az login, config.yaml, gateway, Foundry data-plane."
20381
21255
  });
20382
- output = Option38.String("--output");
20383
- agent = Option38.String("--agent");
20384
- resourceGroup = Option38.String("--resource-group", {
21256
+ output = Option39.String("--output");
21257
+ agent = Option39.String("--agent");
21258
+ resourceGroup = Option39.String("--resource-group", {
20385
21259
  description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
20386
21260
  });
20387
21261
  async executeCommand() {
@@ -20454,7 +21328,7 @@ var DoctorCommand = class extends M8tCommand {
20454
21328
  if (typeof this.agent === "string" && this.agent) {
20455
21329
  checking(`delivery grant for ${this.agent}`);
20456
21330
  try {
20457
- const credential2 = new DefaultAzureCredential18();
21331
+ const credential2 = new DefaultAzureCredential19();
20458
21332
  const cur = await getAgentVersion({
20459
21333
  credential: credential2,
20460
21334
  projectEndpoint: foundry.projectEndpoint,
@@ -20497,18 +21371,18 @@ var DoctorCommand = class extends M8tCommand {
20497
21371
  };
20498
21372
 
20499
21373
  // src/commands/switch.ts
20500
- import { Command as Command41, Option as Option39 } from "clipanion";
21374
+ import { Command as Command43, Option as Option40 } from "clipanion";
20501
21375
 
20502
21376
  // src/lib/profiles.ts
20503
- import * as fs23 from "fs/promises";
20504
- import * as path24 from "path";
21377
+ import * as fs24 from "fs/promises";
21378
+ import * as path25 from "path";
20505
21379
  import { parse as parseYaml11, stringify as stringifyYaml4 } from "yaml";
20506
21380
  function profilesDir() {
20507
21381
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
20508
- return path24.join(home, ".m8t-stack", "profiles");
21382
+ return path25.join(home, ".m8t-stack", "profiles");
20509
21383
  }
20510
21384
  function getProfilePath(name) {
20511
- return path24.join(profilesDir(), `${path24.basename(name)}.yaml`);
21385
+ return path25.join(profilesDir(), `${path25.basename(name)}.yaml`);
20512
21386
  }
20513
21387
  function slugifyTenant(domainOrId) {
20514
21388
  const base = domainOrId.split(".")[0].toLowerCase();
@@ -20517,7 +21391,7 @@ function slugifyTenant(domainOrId) {
20517
21391
  }
20518
21392
  async function listProfiles() {
20519
21393
  try {
20520
- const entries = await fs23.readdir(profilesDir());
21394
+ const entries = await fs24.readdir(profilesDir());
20521
21395
  return entries.filter((e) => e.endsWith(".yaml")).map((e) => e.replace(/\.yaml$/, "")).sort();
20522
21396
  } catch (e) {
20523
21397
  if (e.code === "ENOENT") return [];
@@ -20526,7 +21400,7 @@ async function listProfiles() {
20526
21400
  }
20527
21401
  async function readProfile(name) {
20528
21402
  try {
20529
- const raw = await fs23.readFile(getProfilePath(name), "utf8");
21403
+ const raw = await fs24.readFile(getProfilePath(name), "utf8");
20530
21404
  const parsed = parseYaml11(raw);
20531
21405
  if (!parsed || typeof parsed !== "object") return null;
20532
21406
  return parsed;
@@ -20537,14 +21411,14 @@ async function readProfile(name) {
20537
21411
  }
20538
21412
  async function exists(p) {
20539
21413
  try {
20540
- await fs23.stat(p);
21414
+ await fs24.stat(p);
20541
21415
  return true;
20542
21416
  } catch {
20543
21417
  return false;
20544
21418
  }
20545
21419
  }
20546
21420
  async function saveProfile(p) {
20547
- await fs23.mkdir(profilesDir(), { recursive: true });
21421
+ await fs24.mkdir(profilesDir(), { recursive: true });
20548
21422
  let name = p.name;
20549
21423
  let n = 2;
20550
21424
  while (await exists(getProfilePath(name))) {
@@ -20553,8 +21427,8 @@ async function saveProfile(p) {
20553
21427
  }
20554
21428
  const target = getProfilePath(name);
20555
21429
  const tmp = `${target}.tmp`;
20556
- await fs23.writeFile(tmp, stringifyYaml4({ ...p, name }, { indent: 2 }), "utf8");
20557
- await fs23.rename(tmp, target);
21430
+ await fs24.writeFile(tmp, stringifyYaml4({ ...p, name }, { indent: 2 }), "utf8");
21431
+ await fs24.rename(tmp, target);
20558
21432
  return name;
20559
21433
  }
20560
21434
 
@@ -20637,14 +21511,14 @@ async function profileSwitch(name, asName) {
20637
21511
  init_errors();
20638
21512
  var SwitchCommand = class extends M8tCommand {
20639
21513
  static paths = [["switch"]];
20640
- static usage = Command41.Usage({
21514
+ static usage = Command43.Usage({
20641
21515
  description: "Re-point local config at another deployment: --subscription <id|name> (discovery) or <profile>."
20642
21516
  });
20643
- profile = Option39.String({ required: false });
20644
- subscription = Option39.String("--subscription");
20645
- list = Option39.Boolean("--list", false);
20646
- as = Option39.String("--as");
20647
- output = Option39.String("--output");
21517
+ profile = Option40.String({ required: false });
21518
+ subscription = Option40.String("--subscription");
21519
+ list = Option40.Boolean("--list", false);
21520
+ as = Option40.String("--as");
21521
+ output = Option40.String("--output");
20648
21522
  async executeCommand() {
20649
21523
  const mode = resolveOutputMode(
20650
21524
  this.output,
@@ -20701,7 +21575,7 @@ var SwitchCommand = class extends M8tCommand {
20701
21575
 
20702
21576
  // src/commands/open.ts
20703
21577
  import { spawn as spawn5 } from "child_process";
20704
- import { Command as Command42, Option as Option40 } from "clipanion";
21578
+ import { Command as Command44, Option as Option41 } from "clipanion";
20705
21579
 
20706
21580
  // src/lib/open-targets.ts
20707
21581
  init_errors();
@@ -20747,14 +21621,14 @@ function openUrl(url) {
20747
21621
  }
20748
21622
  var OpenCommand = class extends M8tCommand {
20749
21623
  static paths = [["open"]];
20750
- static usage = Command42.Usage({
21624
+ static usage = Command44.Usage({
20751
21625
  description: "Open the deployed webapp (default), the Foundry portal, or the resource group.",
20752
21626
  details: "Targets: webapp (deployed app, default) | foundry (ai.azure.com) | portal (resource group in the Azure portal). Pass --print to emit the URL instead of launching a browser (also the default when stdout isn't a TTY)."
20753
21627
  });
20754
- target = Option40.String({ required: false });
20755
- print = Option40.Boolean("--print", false);
20756
- output = Option40.String("--output");
20757
- resourceGroup = Option40.String("--resource-group", {
21628
+ target = Option41.String({ required: false });
21629
+ print = Option41.Boolean("--print", false);
21630
+ output = Option41.String("--output");
21631
+ resourceGroup = Option41.String("--resource-group", {
20758
21632
  description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
20759
21633
  });
20760
21634
  async executeCommand() {
@@ -20798,9 +21672,9 @@ var OpenCommand = class extends M8tCommand {
20798
21672
  };
20799
21673
 
20800
21674
  // src/commands/dream/run.ts
20801
- import { Command as Command43, Option as Option41 } from "clipanion";
21675
+ import { Command as Command45, Option as Option42 } from "clipanion";
20802
21676
  import { AzureCliCredential } from "@azure/identity";
20803
- import { TableClient as TableClient3 } from "@azure/data-tables";
21677
+ import { TableClient as TableClient5 } from "@azure/data-tables";
20804
21678
  import { AIProjectClient as AIProjectClient3 } from "@azure/ai-projects";
20805
21679
  import { LogsQueryClient as LogsQueryClient3, LogsQueryResultStatus as LogsQueryResultStatus3 } from "@azure/monitor-query";
20806
21680
 
@@ -21107,7 +21981,7 @@ var ConvFetchError = class extends Error {
21107
21981
  };
21108
21982
 
21109
21983
  // ../../packages/brain/engine/dist/esm/cursor.js
21110
- import { TableClient as TableClient2 } from "@azure/data-tables";
21984
+ import { TableClient as TableClient4 } from "@azure/data-tables";
21111
21985
 
21112
21986
  // ../../packages/agent-ledger/dist/esm/row-key.js
21113
21987
  var MAX_MS = 864e13;
@@ -21229,7 +22103,7 @@ async function commitCursorAdvance(plan, cursor) {
21229
22103
  }
21230
22104
  var CURSOR_TABLE_NAME = "BrainDreamCursor";
21231
22105
  function makeTableCursor(credential2, tableEndpoint) {
21232
- const client = new TableClient2(tableEndpoint, CURSOR_TABLE_NAME, credential2);
22106
+ const client = new TableClient4(tableEndpoint, CURSOR_TABLE_NAME, credential2);
21233
22107
  return new TableCursor(client);
21234
22108
  }
21235
22109
  function formatCursorPreview(cursor) {
@@ -21723,7 +22597,7 @@ function buildAdvancePlan(worker, cursor, physicalPks, consumedRowsByPk, failedT
21723
22597
  // ../../packages/brain/engine/dist/esm/dream/writer.js
21724
22598
  var API2 = "https://api.github.com";
21725
22599
  var WRITER_ALLOWED_PREFIX = /^(memory|inbox|quarantine|artifacts)\//;
21726
- var isAllowedWritePath = (path32) => WRITER_ALLOWED_PREFIX.test(path32) && !path32.split("/").includes("..");
22600
+ var isAllowedWritePath = (path33, allowedPrefix) => allowedPrefix.test(path33) && !path33.split("/").includes("..");
21727
22601
  var unquote = (s) => s.trim().replace(/^["']|["']$/g, "");
21728
22602
  function parseFrontmatter(content) {
21729
22603
  const m = /^---\n([\s\S]*?)\n---/.exec(content);
@@ -21762,8 +22636,12 @@ function parseFrontmatter(content) {
21762
22636
  function makeGitDataWriter(args) {
21763
22637
  const doFetch = args.fetchImpl ?? fetch;
21764
22638
  const repoPath = args.repository;
21765
- async function gh(token, method, path32, body) {
21766
- const res = await doFetch(`${API2}/repos/${repoPath}${path32}`, {
22639
+ const allowedPathPrefix = args.allowedPathPrefix ?? WRITER_ALLOWED_PREFIX;
22640
+ if (args.allowedPathPrefix && !args.allowedPathPrefix.source.startsWith("^")) {
22641
+ throw new Error(`makeGitDataWriter: allowedPathPrefix must be anchored with "^" (got /${args.allowedPathPrefix.source}/) \u2014 an unanchored regex would widen the B1 write-path allowlist`);
22642
+ }
22643
+ async function gh(token, method, path33, body) {
22644
+ const res = await doFetch(`${API2}/repos/${repoPath}${path33}`, {
21767
22645
  method,
21768
22646
  headers: {
21769
22647
  Authorization: `Bearer ${token}`,
@@ -21776,8 +22654,8 @@ function makeGitDataWriter(args) {
21776
22654
  const text = await res.text();
21777
22655
  return { status: res.status, ok: res.ok, json: text ? JSON.parse(text) : {} };
21778
22656
  }
21779
- async function readFileWith(token, path32) {
21780
- const encodedPath = path32.split("/").map(encodeURIComponent).join("/");
22657
+ async function readFileWith(token, path33) {
22658
+ const encodedPath = path33.split("/").map(encodeURIComponent).join("/");
21781
22659
  const r = await gh(token, "GET", `/contents/${encodedPath}?ref=${args.branch}`);
21782
22660
  if (r.status === 404)
21783
22661
  return null;
@@ -21793,9 +22671,9 @@ function makeGitDataWriter(args) {
21793
22671
  throw new Error(`fetchTip: HTTP ${String(r.status)}`);
21794
22672
  return r.json.object.sha;
21795
22673
  },
21796
- async readFile(path32) {
22674
+ async readFile(path33) {
21797
22675
  const token = await args.mintToken();
21798
- return readFileWith(token, path32);
22676
+ return readFileWith(token, path33);
21799
22677
  },
21800
22678
  async listMemory() {
21801
22679
  const token = await args.mintToken();
@@ -21804,15 +22682,15 @@ function makeGitDataWriter(args) {
21804
22682
  return [];
21805
22683
  const paths = r.json.tree.filter((e) => e.type === "blob" && e.path.startsWith("memory/") && e.path.endsWith(".md")).map((e) => e.path);
21806
22684
  const out = [];
21807
- for (const path32 of paths) {
21808
- const content = await readFileWith(token, path32);
21809
- out.push({ path: path32, frontmatter: content ? parseFrontmatter(content) : {} });
22685
+ for (const path33 of paths) {
22686
+ const content = await readFileWith(token, path33);
22687
+ out.push({ path: path33, frontmatter: content ? parseFrontmatter(content) : {} });
21810
22688
  }
21811
22689
  return out;
21812
22690
  },
21813
22691
  async commitBatch(a) {
21814
22692
  try {
21815
- const escaping = a.changes.filter((c) => !isAllowedWritePath(c.path));
22693
+ const escaping = a.changes.filter((c) => !isAllowedWritePath(c.path, allowedPathPrefix));
21816
22694
  if (escaping.length > 0) {
21817
22695
  return {
21818
22696
  ok: false,
@@ -21864,9 +22742,11 @@ function makeFoundryModelClient(openai, model) {
21864
22742
  const create = openai.responses.create;
21865
22743
  const stream = await create({
21866
22744
  model,
22745
+ // Explicit `type: "message"` — the Responses API rejects untyped input
22746
+ // items (400 `Invalid value: ''`).
21867
22747
  input: [
21868
- { role: "system", content: system },
21869
- { role: "user", content: user }
22748
+ { type: "message", role: "system", content: system },
22749
+ { type: "message", role: "user", content: user }
21870
22750
  ],
21871
22751
  stream: true
21872
22752
  }, signal ? { signal } : void 0);
@@ -22243,7 +23123,7 @@ async function loadMemoryContext(brain) {
22243
23123
  return {
22244
23124
  files,
22245
23125
  indexEntries,
22246
- originOf: (path32) => originByPath.get(path32) ?? "worker"
23126
+ originOf: (path33) => originByPath.get(path33) ?? "worker"
22247
23127
  };
22248
23128
  }
22249
23129
 
@@ -22264,14 +23144,14 @@ function checkEvidenceMembership(delta, harvested) {
22264
23144
  }
22265
23145
  var BatchAbort = class extends Error {
22266
23146
  path;
22267
- constructor(path32, message) {
23147
+ constructor(path33, message) {
22268
23148
  super(message);
22269
- this.path = path32;
23149
+ this.path = path33;
22270
23150
  this.name = "BatchAbort";
22271
23151
  }
22272
23152
  };
22273
- var isIndexFile = (path32) => /(^|\/)[^/]*_index\.md$/.test(path32);
22274
- var isMemoryIndex = (path32) => path32 === "memory/MEMORY.md";
23153
+ var isIndexFile = (path33) => /(^|\/)[^/]*_index\.md$/.test(path33);
23154
+ var isMemoryIndex = (path33) => path33 === "memory/MEMORY.md";
22275
23155
  function targetPath(delta) {
22276
23156
  switch (delta.verb) {
22277
23157
  case "new":
@@ -22288,29 +23168,29 @@ function targetPath(delta) {
22288
23168
  }
22289
23169
  async function checkOriginTier(delta, lookup) {
22290
23170
  const evidence = evidenceOf(delta) ?? [];
22291
- const path32 = targetPath(delta);
22292
- if (path32 === null)
23171
+ const path33 = targetPath(delta);
23172
+ if (path33 === null)
22293
23173
  return { ok: true };
22294
- if (isIndexFile(path32) && !isMemoryIndex(path32)) {
22295
- return { ok: false, flagged: { kind: "flagged", path: path32, tension: `refused: ${path32} is an index file (only memory/MEMORY.md is editable)`, evidence } };
23174
+ if (isIndexFile(path33) && !isMemoryIndex(path33)) {
23175
+ return { ok: false, flagged: { kind: "flagged", path: path33, tension: `refused: ${path33} is an index file (only memory/MEMORY.md is editable)`, evidence } };
22296
23176
  }
22297
- const target = await lookup(path32);
22298
- const isStructural = path32.startsWith("references/");
23177
+ const target = await lookup(path33);
23178
+ const isStructural = path33.startsWith("references/");
22299
23179
  const origin = target?.frontmatter.origin ?? (isStructural ? "operator" : "worker");
22300
23180
  const editable = origin === "worker" || origin === "dream";
22301
23181
  if (editable)
22302
23182
  return { ok: true };
22303
23183
  const removesOperatorTarget = delta.verb === "retract" || delta.verb === "supersede" && delta.oldPath !== delta.newPath;
22304
23184
  if (removesOperatorTarget) {
22305
- throw new BatchAbort(path32, `supersede/retract OLD target ${path32} is origin: ${origin} \u2014 aborting batch (never half-apply)`);
23185
+ throw new BatchAbort(path33, `supersede/retract OLD target ${path33} is origin: ${origin} \u2014 aborting batch (never half-apply)`);
22306
23186
  }
22307
- return { ok: false, flagged: { kind: "flagged", path: path32, tension: `operator-origin (${origin}); flag-only`, evidence } };
23187
+ return { ok: false, flagged: { kind: "flagged", path: path33, tension: `operator-origin (${origin}); flag-only`, evidence } };
22308
23188
  }
22309
23189
  var ALLOWED_MEMORY = /^memory\/.+\.md$/;
22310
23190
  var ALLOWED_INBOX = /^inbox\//;
22311
23191
  var ALLOWED_QUARANTINE = /^quarantine\//;
22312
23192
  var ALLOWED_FLAG = /^memory\//;
22313
- var hasTraversal = (path32) => path32.split("/").includes("..");
23193
+ var hasTraversal = (path33) => path33.split("/").includes("..");
22314
23194
  function checkPathAllowed(delta) {
22315
23195
  const evidence = evidenceOf(delta) ?? [];
22316
23196
  const reject = (detail) => ({ ok: false, rejected: { kind: "rejected", detail, evidence } });
@@ -22417,7 +23297,7 @@ ${inject}
22417
23297
  ---
22418
23298
  `);
22419
23299
  }
22420
- var indexLine = (path32, title, date, tags) => `- \`${path32}\` \u2014 **${title}**: ${title}. (${date} \xB7 ${tags.join(", ")})`;
23300
+ var indexLine = (path33, title, date, tags) => `- \`${path33}\` \u2014 **${title}**: ${title}. (${date} \xB7 ${tags.join(", ")})`;
22421
23301
  function splitIndex(index) {
22422
23302
  const all = index.split("\n");
22423
23303
  const firstLineIdx = all.findIndex((l) => l.startsWith("- `memory/"));
@@ -22590,16 +23470,16 @@ function defaultDreamSeams(opts = {}) {
22590
23470
  const mem = await memory(deps.brain);
22591
23471
  const harvested = harvestedIds(input);
22592
23472
  const ctx = { readFile: (p) => deps.brain.readFile(p), at: deps.clock() };
22593
- const lookup = (path32) => Promise.resolve(mem.files.find((f) => f.path === path32) ?? null);
23473
+ const lookup = (path33) => Promise.resolve(mem.files.find((f) => f.path === path33) ?? null);
22594
23474
  const digest = [...pendingRejected];
22595
23475
  let changes = [];
22596
23476
  let indexEdit;
22597
23477
  try {
22598
23478
  for (const raw of deltas) {
22599
23479
  const delta = forcePolicyQuarantine(raw);
22600
- const path32 = checkPathAllowed(delta);
22601
- if (!path32.ok) {
22602
- digest.push(path32.rejected);
23480
+ const path33 = checkPathAllowed(delta);
23481
+ if (!path33.ok) {
23482
+ digest.push(path33.rejected);
22603
23483
  continue;
22604
23484
  }
22605
23485
  const ev = checkEvidenceMembership(delta, harvested);
@@ -22942,20 +23822,20 @@ function redactTranscripts(input) {
22942
23822
  }
22943
23823
  var DreamRunCommand = class extends M8tCommand {
22944
23824
  static paths = [["dream", "run"]];
22945
- static usage = Command43.Usage({
23825
+ static usage = Command45.Usage({
22946
23826
  description: "Dry-run the brain consumption pipeline for one worker (no model call, no writes).",
22947
23827
  details: "Builds AzureCliCredential + resolves the Foundry project, ledger table, and Log Analytics workspace, runs the consumption pipeline, and prints the skip-ledger, the partition invariant, and harvest stats. Transcripts are metadata-only unless --show-transcripts is passed."
22948
23828
  });
22949
- worker = Option41.String("--worker", { description: "Worker (canonical name) to harvest. Required." });
22950
- dryRun = Option41.Boolean("--dry-run", false, { description: "Read-only harvest; no model call, no writes." });
22951
- since = Option41.String("--since", { description: "ISO-8601 start override (rejected if malformed or future)." });
22952
- reset = Option41.Boolean("--reset", false, { description: "Ignore the stored cursor; read from the beginning." });
22953
- showTranscripts = Option41.Boolean("--show-transcripts", false, {
23829
+ worker = Option42.String("--worker", { description: "Worker (canonical name) to harvest. Required." });
23830
+ dryRun = Option42.Boolean("--dry-run", false, { description: "Read-only harvest; no model call, no writes." });
23831
+ since = Option42.String("--since", { description: "ISO-8601 start override (rejected if malformed or future)." });
23832
+ reset = Option42.Boolean("--reset", false, { description: "Ignore the stored cursor; read from the beginning." });
23833
+ showTranscripts = Option42.Boolean("--show-transcripts", false, {
22954
23834
  description: "Print transcript bodies (default: metadata only)."
22955
23835
  });
22956
- subscription = Option41.String("--subscription");
22957
- endpoint = Option41.String("--endpoint");
22958
- output = Option41.String("--output");
23836
+ subscription = Option42.String("--subscription");
23837
+ endpoint = Option42.String("--endpoint");
23838
+ output = Option42.String("--output");
22959
23839
  // Resolution seam — overridden by tests; built lazily at runtime otherwise.
22960
23840
  deps;
22961
23841
  async executeCommand() {
@@ -23150,7 +24030,7 @@ AppDependencies
23150
24030
  };
23151
24031
  }
23152
24032
  function buildLiveSources(context, credential2) {
23153
- const ledgerClient = new TableClient3(context.ledgerTableEndpoint, LEDGER_TABLE_NAME, credential2);
24033
+ const ledgerClient = new TableClient5(context.ledgerTableEndpoint, LEDGER_TABLE_NAME, credential2);
23154
24034
  const ledger = makeLedgerSource(
23155
24035
  async (pk, sinceTs) => fetchLedgerRows(
23156
24036
  { workers: [pk], source: "all", from: sinceTs, to: "9999-12-31T23:59:59.999Z" },
@@ -23303,7 +24183,7 @@ function defaultDeps(overrides) {
23303
24183
  }
23304
24184
 
23305
24185
  // src/commands/foundry/create.ts
23306
- import { Command as Command44, Option as Option42 } from "clipanion";
24186
+ import { Command as Command46, Option as Option43 } from "clipanion";
23307
24187
 
23308
24188
  // src/lib/foundry-create.ts
23309
24189
  init_errors();
@@ -23541,7 +24421,7 @@ async function createFoundryProject(args) {
23541
24421
  init_errors();
23542
24422
  var FoundryCreateCommand = class extends M8tCommand {
23543
24423
  static paths = [["foundry", "create"]];
23544
- static usage = Command44.Usage({
24424
+ static usage = Command46.Usage({
23545
24425
  description: "Create an AI Foundry (AIServices) account + project + model deployment from scratch.",
23546
24426
  details: "Non-interactive and idempotent. Creates the AIServices account (custom subdomain + project management), a project, and a model deployment (default gpt-4.1-mini @ capacity 50). Region must be hosted-agent-eligible. Emits the project endpoint as structured output. Re-run is a clean no-op (account/project skipped if present; deployment capacity converges UP, never down).",
23547
24427
  examples: [
@@ -23550,16 +24430,16 @@ var FoundryCreateCommand = class extends M8tCommand {
23550
24430
  ["Higher capacity for a reasoning model", "$0 foundry create --resource-group rg-m8t-stack --location eastus2 --model gpt-5-mini --capacity 250"]
23551
24431
  ]
23552
24432
  });
23553
- resourceGroup = Option42.String("--resource-group");
23554
- location = Option42.String("--location");
23555
- account = Option42.String("--account");
23556
- project = Option42.String("--project", "m8t");
23557
- model = Option42.String("--model", "gpt-4.1-mini");
23558
- modelVersion = Option42.String("--model-version", "2025-04-14");
23559
- capacity = Option42.String("--capacity", "50");
23560
- subscription = Option42.String("--subscription");
23561
- skipQuotaCheck = Option42.Boolean("--skip-quota-check", false);
23562
- output = Option42.String("--output");
24433
+ resourceGroup = Option43.String("--resource-group");
24434
+ location = Option43.String("--location");
24435
+ account = Option43.String("--account");
24436
+ project = Option43.String("--project", "m8t");
24437
+ model = Option43.String("--model", "gpt-4.1-mini");
24438
+ modelVersion = Option43.String("--model-version", "2025-04-14");
24439
+ capacity = Option43.String("--capacity", "50");
24440
+ subscription = Option43.String("--subscription");
24441
+ skipQuotaCheck = Option43.Boolean("--skip-quota-check", false);
24442
+ output = Option43.String("--output");
23563
24443
  async executeCommand() {
23564
24444
  const mode = resolveOutputMode(
23565
24445
  this.output,
@@ -23631,22 +24511,22 @@ var FoundryCreateCommand = class extends M8tCommand {
23631
24511
  };
23632
24512
 
23633
24513
  // src/commands/foundry/await-ready.ts
23634
- import { Command as Command45, Option as Option43 } from "clipanion";
24514
+ import { Command as Command47, Option as Option44 } from "clipanion";
23635
24515
  import { AzureCliCredential as AzureCliCredential2 } from "@azure/identity";
23636
24516
  init_errors();
23637
24517
  var FoundryAwaitReadyCommand = class extends M8tCommand {
23638
24518
  static paths = [["foundry", "await-ready"]];
23639
- static usage = Command45.Usage({
24519
+ static usage = Command47.Usage({
23640
24520
  description: "Wait until a freshly-created Foundry project's data plane reliably serves it.",
23641
24521
  details: "Probes the project (GET /agents) until it returns 200 on a few consecutive tries, or fails clearly after a bounded budget. A newly-created account can serve intermittent 404 'Project not found' for minutes; run this after 'foundry create' and before deploying agents so the worker phase doesn't catch the unstable window.",
23642
24522
  examples: [["Wait for a project to be ready", "$0 foundry await-ready --endpoint https://acc.services.ai.azure.com/api/projects/m8t"]]
23643
24523
  });
23644
- endpoint = Option43.String("--endpoint");
23645
- consecutive = Option43.String("--consecutive", "3");
23646
- attempts = Option43.String("--attempts", "60");
23647
- interval = Option43.String("--interval", "5");
23648
- subscription = Option43.String("--subscription");
23649
- output = Option43.String("--output");
24524
+ endpoint = Option44.String("--endpoint");
24525
+ consecutive = Option44.String("--consecutive", "3");
24526
+ attempts = Option44.String("--attempts", "60");
24527
+ interval = Option44.String("--interval", "5");
24528
+ subscription = Option44.String("--subscription");
24529
+ output = Option44.String("--output");
23650
24530
  async executeCommand() {
23651
24531
  const mode = resolveOutputMode(this.output, this.context.stdout);
23652
24532
  const endpoint = typeof this.endpoint === "string" ? this.endpoint : void 0;
@@ -23680,7 +24560,7 @@ var FoundryAwaitReadyCommand = class extends M8tCommand {
23680
24560
  };
23681
24561
 
23682
24562
  // src/commands/bootstrap/preflight.ts
23683
- import { Command as Command46, Option as Option44 } from "clipanion";
24563
+ import { Command as Command48, Option as Option45 } from "clipanion";
23684
24564
  init_errors();
23685
24565
 
23686
24566
  // src/lib/bootstrap-preflight.ts
@@ -23754,7 +24634,7 @@ function buildPreflightBanner(who) {
23754
24634
  // src/commands/bootstrap/preflight.ts
23755
24635
  var BootstrapPreflightCommand = class extends M8tCommand {
23756
24636
  static paths = [["bootstrap", "preflight"]];
23757
- static usage = Command46.Usage({
24637
+ static usage = Command48.Usage({
23758
24638
  description: "Loudly verify you can install m8t-stack (Owner/UAA + directory admin) and hard-stop if not.",
23759
24639
  details: "Step 1 of `m8t bootstrap`. Prints an unmissable admin-credentials notice, then checks: Owner or User Access Administrator at subscription scope (hard requirement), directory-admin capability to register the app (or pass --client-id), and registers Microsoft.ContainerInstance. Exits non-zero with the exact failing check + remedy.",
23760
24640
  examples: [
@@ -23762,8 +24642,8 @@ var BootstrapPreflightCommand = class extends M8tCommand {
23762
24642
  ["BYO app registration (directory guests)", "$0 bootstrap preflight --client-id <appId>"]
23763
24643
  ]
23764
24644
  });
23765
- clientId = Option44.String("--client-id");
23766
- subscription = Option44.String("--subscription");
24645
+ clientId = Option45.String("--client-id");
24646
+ subscription = Option45.String("--subscription");
23767
24647
  async executeCommand() {
23768
24648
  const clientId = typeof this.clientId === "string" ? this.clientId : void 0;
23769
24649
  const account = await getAzAccount();
@@ -23819,10 +24699,10 @@ ${colors.error(" " + why)}
23819
24699
  }
23820
24700
 
23821
24701
  // src/commands/bootstrap/launch.ts
23822
- import * as fs26 from "fs";
24702
+ import * as fs27 from "fs";
23823
24703
  import * as os13 from "os";
23824
- import * as path27 from "path";
23825
- import { Command as Command47, Option as Option45 } from "clipanion";
24704
+ import * as path28 from "path";
24705
+ import { Command as Command49, Option as Option46 } from "clipanion";
23826
24706
  init_errors();
23827
24707
 
23828
24708
  // src/lib/bootstrap-mi.ts
@@ -23967,9 +24847,9 @@ async function kickInstaller(s) {
23967
24847
 
23968
24848
  // src/lib/bootstrap-status.ts
23969
24849
  import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
23970
- import * as fs24 from "fs/promises";
24850
+ import * as fs25 from "fs/promises";
23971
24851
  import * as os11 from "os";
23972
- import * as path25 from "path";
24852
+ import * as path26 from "path";
23973
24853
  init_errors();
23974
24854
  var STATUS_CONTAINER = "status";
23975
24855
  var STATUS_BLOB = "status.json";
@@ -24008,7 +24888,7 @@ async function readStatusBlob(opts) {
24008
24888
  cause: e
24009
24889
  });
24010
24890
  }
24011
- const tmpFile = path25.join(os11.tmpdir(), `m8t-status-${randomUUID2()}.json`);
24891
+ const tmpFile = path26.join(os11.tmpdir(), `m8t-status-${randomUUID2()}.json`);
24012
24892
  try {
24013
24893
  await runAz([
24014
24894
  "storage",
@@ -24027,7 +24907,7 @@ async function readStatusBlob(opts) {
24027
24907
  "--no-progress",
24028
24908
  "--only-show-errors"
24029
24909
  ]);
24030
- const raw = await fs24.readFile(tmpFile, "utf8");
24910
+ const raw = await fs25.readFile(tmpFile, "utf8");
24031
24911
  return JSON.parse(raw.trim());
24032
24912
  } catch (e) {
24033
24913
  throw new LocalCliError({
@@ -24037,23 +24917,23 @@ async function readStatusBlob(opts) {
24037
24917
  cause: e
24038
24918
  });
24039
24919
  } finally {
24040
- await fs24.rm(tmpFile, { force: true });
24920
+ await fs25.rm(tmpFile, { force: true });
24041
24921
  }
24042
24922
  }
24043
24923
 
24044
24924
  // src/lib/bootstrap-state.ts
24045
- import * as fs25 from "fs/promises";
24925
+ import * as fs26 from "fs/promises";
24046
24926
  import * as os12 from "os";
24047
- import * as path26 from "path";
24927
+ import * as path27 from "path";
24048
24928
  function stateDir(home) {
24049
- return path26.join(home, ".m8t-stack");
24929
+ return path27.join(home, ".m8t-stack");
24050
24930
  }
24051
24931
  function statePath(home) {
24052
- return path26.join(stateDir(home), "bootstrap.json");
24932
+ return path27.join(stateDir(home), "bootstrap.json");
24053
24933
  }
24054
24934
  async function readBootstrapState(home = os12.homedir()) {
24055
24935
  try {
24056
- const raw = await fs25.readFile(statePath(home), "utf8");
24936
+ const raw = await fs26.readFile(statePath(home), "utf8");
24057
24937
  return JSON.parse(raw);
24058
24938
  } catch (e) {
24059
24939
  if (e.code === "ENOENT") return null;
@@ -24061,10 +24941,10 @@ async function readBootstrapState(home = os12.homedir()) {
24061
24941
  }
24062
24942
  }
24063
24943
  async function writeBootstrapState(state, home = os12.homedir()) {
24064
- await fs25.mkdir(stateDir(home), { recursive: true });
24944
+ await fs26.mkdir(stateDir(home), { recursive: true });
24065
24945
  const tmp = `${statePath(home)}.tmp`;
24066
- await fs25.writeFile(tmp, JSON.stringify(state, null, 2), "utf8");
24067
- await fs25.rename(tmp, statePath(home));
24946
+ await fs26.writeFile(tmp, JSON.stringify(state, null, 2), "utf8");
24947
+ await fs26.rename(tmp, statePath(home));
24068
24948
  }
24069
24949
 
24070
24950
  // src/commands/bootstrap/launch.ts
@@ -24075,7 +24955,7 @@ var ACI_NAME = "m8t-installer";
24075
24955
  var MI_NAME = "m8t-installer-mi";
24076
24956
  var BootstrapLaunchCommand = class extends M8tCommand {
24077
24957
  static paths = [["bootstrap", "launch"]];
24078
- static usage = Command47.Usage({
24958
+ static usage = Command49.Usage({
24079
24959
  description: "Create + authorize the installer managed identity, then kick the cloud installer.",
24080
24960
  details: "Step 2 of `m8t bootstrap` (run after `preflight`). Ensures the resource group, creates the m8t-stack app registration (or uses --client-id), creates a user-assigned managed identity granted Owner at subscription scope, and launches the published m8t-installer image as an ACI run-to-completion job under that identity. Writes ~/.m8t-stack/bootstrap.json for `status`/`reap`/`finish`.",
24081
24961
  examples: [
@@ -24084,13 +24964,13 @@ var BootstrapLaunchCommand = class extends M8tCommand {
24084
24964
  ["Pin a specific installer tag", "$0 bootstrap launch --location eastus2 --installer-tag v0.1.0"]
24085
24965
  ]
24086
24966
  });
24087
- location = Option45.String("--location");
24088
- resourceGroup = Option45.String("--resource-group");
24089
- clientId = Option45.String("--client-id");
24090
- subscription = Option45.String("--subscription");
24091
- installerTag = Option45.String("--installer-tag");
24092
- gatewayImageRef = Option45.String("--gateway-image-ref");
24093
- githubAppCreds = Option45.String("--github-app-creds");
24967
+ location = Option46.String("--location");
24968
+ resourceGroup = Option46.String("--resource-group");
24969
+ clientId = Option46.String("--client-id");
24970
+ subscription = Option46.String("--subscription");
24971
+ installerTag = Option46.String("--installer-tag");
24972
+ gatewayImageRef = Option46.String("--gateway-image-ref");
24973
+ githubAppCreds = Option46.String("--github-app-creds");
24094
24974
  async executeCommand() {
24095
24975
  const location = typeof this.location === "string" ? this.location : void 0;
24096
24976
  if (!location) {
@@ -24104,12 +24984,12 @@ var BootstrapLaunchCommand = class extends M8tCommand {
24104
24984
  const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
24105
24985
  const out = (m) => this.context.stderr.write(` ${colors.dim(m)}
24106
24986
  `);
24107
- const credsPath = typeof this.githubAppCreds === "string" ? this.githubAppCreds : path27.join(os13.homedir(), ".m8t-stack", "github-app.json");
24987
+ const credsPath = typeof this.githubAppCreds === "string" ? this.githubAppCreds : path28.join(os13.homedir(), ".m8t-stack", "github-app.json");
24108
24988
  let githubApp;
24109
- if (fs26.existsSync(credsPath)) {
24989
+ if (fs27.existsSync(credsPath)) {
24110
24990
  let parsed;
24111
24991
  try {
24112
- parsed = JSON.parse(fs26.readFileSync(credsPath, "utf8"));
24992
+ parsed = JSON.parse(fs27.readFileSync(credsPath, "utf8"));
24113
24993
  } catch {
24114
24994
  throw new LocalCliError({
24115
24995
  code: "GITHUB_APP_CREDS_INVALID",
@@ -24119,7 +24999,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
24119
24999
  }
24120
25000
  let pem;
24121
25001
  try {
24122
- pem = fs26.readFileSync(parsed.pemPath, "utf8");
25002
+ pem = fs27.readFileSync(parsed.pemPath, "utf8");
24123
25003
  } catch {
24124
25004
  throw new LocalCliError({
24125
25005
  code: "GITHUB_APP_PEM_MISSING",
@@ -24191,7 +25071,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
24191
25071
  };
24192
25072
 
24193
25073
  // src/commands/bootstrap/status.ts
24194
- import { Command as Command48, Option as Option46 } from "clipanion";
25074
+ import { Command as Command50, Option as Option47 } from "clipanion";
24195
25075
  init_errors();
24196
25076
 
24197
25077
  // src/lib/bootstrap-aci-state.ts
@@ -24219,13 +25099,13 @@ async function getAciState(opts) {
24219
25099
  // src/commands/bootstrap/status.ts
24220
25100
  var BootstrapStatusCommand = class extends M8tCommand {
24221
25101
  static paths = [["bootstrap", "status"]];
24222
- static usage = Command48.Usage({
25102
+ static usage = Command50.Usage({
24223
25103
  description: "Show the cloud installer's live status (phase, progress, result).",
24224
25104
  details: "Reads the durable status blob written by the installer. --watch polls until the install reaches done or failed.",
24225
25105
  examples: [["One read", "$0 bootstrap status"], ["Watch to completion", "$0 bootstrap status --watch"]]
24226
25106
  });
24227
- watch = Option46.Boolean("--watch", false);
24228
- output = Option46.String("--output");
25107
+ watch = Option47.Boolean("--watch", false);
25108
+ output = Option47.String("--output");
24229
25109
  async executeCommand() {
24230
25110
  const state = await readBootstrapState();
24231
25111
  if (!state) {
@@ -24296,7 +25176,7 @@ function formatStatus(d) {
24296
25176
  }
24297
25177
 
24298
25178
  // src/commands/bootstrap/reap.ts
24299
- import { Command as Command49, Option as Option47 } from "clipanion";
25179
+ import { Command as Command51, Option as Option48 } from "clipanion";
24300
25180
  init_errors();
24301
25181
 
24302
25182
  // src/lib/bootstrap-reap.ts
@@ -24372,14 +25252,14 @@ async function reapInstaller(opts) {
24372
25252
  // src/commands/bootstrap/reap.ts
24373
25253
  var BootstrapReapCommand = class extends M8tCommand {
24374
25254
  static paths = [["bootstrap", "reap"]];
24375
- static usage = Command49.Usage({
25255
+ static usage = Command51.Usage({
24376
25256
  description: "Tear down the installer scaffolding (ACI \u2192 MI \u2192 its role assignments) after a successful install.",
24377
25257
  details: "Runs locally on the 'done' signal (the installer can't delete its own identity). The platform RG and the gateway's own assignments persist. A failed install is left intact for diagnosis unless --force.",
24378
25258
  examples: [["Reap after done", "$0 bootstrap reap"]]
24379
25259
  });
24380
- force = Option47.Boolean("--force", false);
24381
- sweepOrphans = Option47.Boolean("--sweep-orphans", false);
24382
- yes = Option47.Boolean("--yes", false);
25260
+ force = Option48.Boolean("--force", false);
25261
+ sweepOrphans = Option48.Boolean("--sweep-orphans", false);
25262
+ yes = Option48.Boolean("--yes", false);
24383
25263
  async executeCommand() {
24384
25264
  if (this.sweepOrphans === true) {
24385
25265
  const { subscriptionId: sub } = await getAzAccount();
@@ -24444,17 +25324,17 @@ Found ${String(found.length)} orphaned Owner@sub assignment(s) (dry-run). ${colo
24444
25324
  };
24445
25325
 
24446
25326
  // src/commands/bootstrap/finish.ts
24447
- import * as fs27 from "fs/promises";
25327
+ import * as fs28 from "fs/promises";
24448
25328
  import * as os15 from "os";
24449
- import * as path29 from "path";
24450
- import { Command as Command50, Option as Option48 } from "clipanion";
25329
+ import * as path30 from "path";
25330
+ import { Command as Command52, Option as Option49 } from "clipanion";
24451
25331
  init_errors();
24452
25332
 
24453
25333
  // src/lib/company-profile-seed.ts
24454
25334
  import { spawn as spawn6 } from "child_process";
24455
- import { closeSync, openSync, readFileSync as readFileSync18 } from "fs";
25335
+ import { closeSync, openSync, readFileSync as readFileSync19 } from "fs";
24456
25336
  import * as os14 from "os";
24457
- import * as path28 from "path";
25337
+ import * as path29 from "path";
24458
25338
  init_errors();
24459
25339
 
24460
25340
  // src/lib/onboarding-profile.ts
@@ -24635,9 +25515,9 @@ function composeFounderIdentityNote(id) {
24635
25515
 
24636
25516
  // src/lib/company-profile-seed.ts
24637
25517
  function readGithubAppCreds(credsPath) {
24638
- const p = credsPath ?? path28.join(os14.homedir(), ".m8t-stack", "github-app.json");
25518
+ const p = credsPath ?? path29.join(os14.homedir(), ".m8t-stack", "github-app.json");
24639
25519
  try {
24640
- return JSON.parse(readFileSync18(p, "utf8"));
25520
+ return JSON.parse(readFileSync19(p, "utf8"));
24641
25521
  } catch {
24642
25522
  return null;
24643
25523
  }
@@ -24665,7 +25545,7 @@ async function resolveSeedContext(opts) {
24665
25545
  async function applyProfileToBrain(args) {
24666
25546
  const token = await mintInstallationTokenFromPem({
24667
25547
  appId: args.appCreds.appId,
24668
- privateKeyPem: readFileSync18(args.appCreds.pemPath, "utf8"),
25548
+ privateKeyPem: readFileSync19(args.appCreds.pemPath, "utf8"),
24669
25549
  installationId: args.appCreds.installationId,
24670
25550
  fetchImpl: args.fetchImpl
24671
25551
  });
@@ -24700,7 +25580,7 @@ async function applyProfileToBrain(args) {
24700
25580
  });
24701
25581
  }
24702
25582
  function spawnDetachedSeedWatch() {
24703
- const logPath = path28.join(os14.homedir(), ".m8t-stack", "seed-profile.log");
25583
+ const logPath = path29.join(os14.homedir(), ".m8t-stack", "seed-profile.log");
24704
25584
  const fd = openSync(logPath, "a");
24705
25585
  try {
24706
25586
  const child = spawn6(process.execPath, [process.argv[1] ?? "", "bootstrap", "seed-profile", "--watch"], {
@@ -24777,14 +25657,14 @@ function renderInstallSummary(args) {
24777
25657
  // src/commands/bootstrap/finish.ts
24778
25658
  var BootstrapFinishCommand = class extends M8tCommand {
24779
25659
  static paths = [["bootstrap", "finish"]];
24780
- static usage = Command50.Usage({
25660
+ static usage = Command52.Usage({
24781
25661
  description: "Point your local tools at the now-live platform (repo-root marker, discovery cache, next steps).",
24782
25662
  details: "Final step of `m8t bootstrap` (after the install reaches done). Writes ~/.m8t-stack/repo-root, points your local tools at the live gateway, and \u2014 if you completed the onboarding questionnaire \u2014 seeds your brain-backed advisor's brain with your company profile (best-effort; never blocks finish). Run `m8t bootstrap seed-profile` to seed manually later.",
24783
25663
  examples: [["Finish local", "$0 bootstrap finish --repo-root /path/to/m8t"]]
24784
25664
  });
24785
- repoRoot = Option48.String("--repo-root");
24786
- subscription = Option48.String("--subscription");
24787
- resourceGroup = Option48.String("--resource-group");
25665
+ repoRoot = Option49.String("--repo-root");
25666
+ subscription = Option49.String("--subscription");
25667
+ resourceGroup = Option49.String("--resource-group");
24788
25668
  async executeCommand() {
24789
25669
  const state = await readBootstrapState();
24790
25670
  if (!state) {
@@ -24799,9 +25679,9 @@ var BootstrapFinishCommand = class extends M8tCommand {
24799
25679
  });
24800
25680
  }
24801
25681
  const repoRoot = (typeof this.repoRoot === "string" ? this.repoRoot : void 0) ?? process.cwd();
24802
- const markerDir = path29.join(os15.homedir(), ".m8t-stack");
24803
- await fs27.mkdir(markerDir, { recursive: true });
24804
- await fs27.writeFile(path29.join(markerDir, "repo-root"), `${repoRoot}
25682
+ const markerDir = path30.join(os15.homedir(), ".m8t-stack");
25683
+ await fs28.mkdir(markerDir, { recursive: true });
25684
+ await fs28.writeFile(path30.join(markerDir, "repo-root"), `${repoRoot}
24805
25685
  `, "utf8");
24806
25686
  const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? state.subscriptionId;
24807
25687
  const resourceGroup = (typeof this.resourceGroup === "string" ? this.resourceGroup : void 0) ?? state.resourceGroup;
@@ -24837,7 +25717,7 @@ var BootstrapFinishCommand = class extends M8tCommand {
24837
25717
  }
24838
25718
  let brainOrg = null;
24839
25719
  try {
24840
- const credsRaw = await fs27.readFile(path29.join(markerDir, "github-app.json"), "utf8");
25720
+ const credsRaw = await fs28.readFile(path30.join(markerDir, "github-app.json"), "utf8");
24841
25721
  const creds = JSON.parse(credsRaw);
24842
25722
  brainOrg = typeof creds.org === "string" ? creds.org : null;
24843
25723
  } catch {
@@ -24868,18 +25748,18 @@ ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP se
24868
25748
  };
24869
25749
 
24870
25750
  // src/commands/bootstrap/ui.ts
24871
- import * as fs29 from "fs";
25751
+ import * as fs30 from "fs";
24872
25752
  import * as os17 from "os";
24873
- import * as path31 from "path";
24874
- import { Command as Command51, Option as Option49 } from "clipanion";
24875
- import { DefaultAzureCredential as DefaultAzureCredential19 } from "@azure/identity";
25753
+ import * as path32 from "path";
25754
+ import { Command as Command53, Option as Option50 } from "clipanion";
25755
+ import { DefaultAzureCredential as DefaultAzureCredential20 } from "@azure/identity";
24876
25756
  init_errors();
24877
25757
 
24878
25758
  // src/lib/bootstrap-ui.ts
24879
- import * as fs28 from "fs";
25759
+ import * as fs29 from "fs";
24880
25760
  import * as net from "net";
24881
25761
  import * as os16 from "os";
24882
- import * as path30 from "path";
25762
+ import * as path31 from "path";
24883
25763
  import { spawn as spawn7 } from "child_process";
24884
25764
  init_errors();
24885
25765
  init_rbac();
@@ -24946,9 +25826,9 @@ async function ensureFounderFoundryRole(args) {
24946
25826
  });
24947
25827
  }
24948
25828
  function writeWebEnvLocal(args) {
24949
- const envPath = path30.join(args.repoRoot, "apps", "web", ".env.local");
24950
- if (fs28.existsSync(envPath)) {
24951
- fs28.copyFileSync(envPath, envPath + ".bak");
25829
+ const envPath = path31.join(args.repoRoot, "apps", "web", ".env.local");
25830
+ if (fs29.existsSync(envPath)) {
25831
+ fs29.copyFileSync(envPath, envPath + ".bak");
24952
25832
  }
24953
25833
  const body = [
24954
25834
  "# Written by `m8t bootstrap ui` \u2014 onboarding chat-only run (Simple Stacey).",
@@ -24962,7 +25842,7 @@ function writeWebEnvLocal(args) {
24962
25842
  "NEXT_PUBLIC_M8T_ONBOARDING=1",
24963
25843
  ""
24964
25844
  ].join("\n");
24965
- fs28.writeFileSync(envPath, body, "utf8");
25845
+ fs29.writeFileSync(envPath, body, "utf8");
24966
25846
  return envPath;
24967
25847
  }
24968
25848
  function assertNodeVersion(versionString = process.version) {
@@ -25004,10 +25884,10 @@ async function installWebDeps(repoRoot) {
25004
25884
  await runInherit("pnpm", ["install"], repoRoot, "BOOTSTRAP_UI_INSTALL_FAILED");
25005
25885
  }
25006
25886
  function onboardingUiPaths(home = os16.homedir()) {
25007
- const dir = path30.join(home, ".m8t-stack");
25887
+ const dir = path31.join(home, ".m8t-stack");
25008
25888
  return {
25009
- logPath: path30.join(dir, "onboarding-ui.log"),
25010
- pidPath: path30.join(dir, "onboarding-ui.pid")
25889
+ logPath: path31.join(dir, "onboarding-ui.log"),
25890
+ pidPath: path31.join(dir, "onboarding-ui.pid")
25011
25891
  };
25012
25892
  }
25013
25893
  async function serveOnboardingUiDetached(args) {
@@ -25031,8 +25911,8 @@ async function serveOnboardingUiDetached(args) {
25031
25911
  if (await isPortOpen()) {
25032
25912
  return { alreadyRunning: true, logPath };
25033
25913
  }
25034
- fs28.mkdirSync(path30.dirname(logPath), { recursive: true });
25035
- const fd = fs28.openSync(logPath, "a");
25914
+ fs29.mkdirSync(path31.dirname(logPath), { recursive: true });
25915
+ const fd = fs29.openSync(logPath, "a");
25036
25916
  const child = spawn7("pnpm", ["--filter", "web", "dev"], {
25037
25917
  cwd: args.repoRoot,
25038
25918
  detached: true,
@@ -25040,7 +25920,7 @@ async function serveOnboardingUiDetached(args) {
25040
25920
  env: { ...process.env, PORT: args.port },
25041
25921
  shell: false
25042
25922
  });
25043
- fs28.closeSync(fd);
25923
+ fs29.closeSync(fd);
25044
25924
  if (child.pid == null) {
25045
25925
  throw new LocalCliError({
25046
25926
  code: "BOOTSTRAP_UI_DEV_SPAWN_FAILED",
@@ -25049,7 +25929,7 @@ async function serveOnboardingUiDetached(args) {
25049
25929
  });
25050
25930
  }
25051
25931
  child.unref();
25052
- fs28.writeFileSync(pidPath, String(child.pid), "utf8");
25932
+ fs29.writeFileSync(pidPath, String(child.pid), "utf8");
25053
25933
  const deadline = Date.now() + 45e3;
25054
25934
  const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
25055
25935
  while (Date.now() < deadline) {
@@ -25072,7 +25952,7 @@ function stopOnboardingUi(home = os16.homedir()) {
25072
25952
  const { pidPath } = onboardingUiPaths(home);
25073
25953
  let pidStr;
25074
25954
  try {
25075
- pidStr = fs28.readFileSync(pidPath, "utf8").trim();
25955
+ pidStr = fs29.readFileSync(pidPath, "utf8").trim();
25076
25956
  } catch {
25077
25957
  return false;
25078
25958
  }
@@ -25085,7 +25965,7 @@ function stopOnboardingUi(home = os16.homedir()) {
25085
25965
  }
25086
25966
  }
25087
25967
  try {
25088
- fs28.unlinkSync(pidPath);
25968
+ fs29.unlinkSync(pidPath);
25089
25969
  } catch {
25090
25970
  }
25091
25971
  return true;
@@ -25142,7 +26022,7 @@ async function deploySimpleStacey(args) {
25142
26022
  // src/commands/bootstrap/ui.ts
25143
26023
  var BootstrapUiCommand = class extends M8tCommand {
25144
26024
  static paths = [["bootstrap", "ui"]];
25145
- static usage = Command51.Usage({
26025
+ static usage = Command53.Usage({
25146
26026
  description: "Deploy Simple Stacey + start the local onboarding chat UI in the background (returns immediately).",
25147
26027
  details: [
25148
26028
  "Run after `m8t bootstrap launch`, in parallel with `status --watch`. Waits for the cloud",
@@ -25160,15 +26040,15 @@ var BootstrapUiCommand = class extends M8tCommand {
25160
26040
  ["Foreground (blocking, old behaviour)", "$0 bootstrap ui --repo-root /path/to/m8t --foreground"]
25161
26041
  ]
25162
26042
  });
25163
- repoRoot = Option49.String("--repo-root");
25164
- port = Option49.String("--port", "3000");
25165
- endpoint = Option49.String("--endpoint", {
26043
+ repoRoot = Option50.String("--repo-root");
26044
+ port = Option50.String("--port", "3000");
26045
+ endpoint = Option50.String("--endpoint", {
25166
26046
  description: "Foundry project endpoint to target \u2014 disambiguates when the subscription has multiple projects."
25167
26047
  });
25168
- prepOnly = Option49.Boolean("--prep-only", false);
25169
- skipInstall = Option49.Boolean("--skip-install", false);
25170
- stop = Option49.Boolean("--stop", false);
25171
- foreground = Option49.Boolean("--foreground", false);
26048
+ prepOnly = Option50.Boolean("--prep-only", false);
26049
+ skipInstall = Option50.Boolean("--skip-install", false);
26050
+ stop = Option50.Boolean("--stop", false);
26051
+ foreground = Option50.Boolean("--foreground", false);
25172
26052
  async executeCommand() {
25173
26053
  if (this.stop === true) {
25174
26054
  const stopped = stopOnboardingUi();
@@ -25201,10 +26081,10 @@ var BootstrapUiCommand = class extends M8tCommand {
25201
26081
  this.context.stderr.write(` ${colors.dim(m)}
25202
26082
  `);
25203
26083
  };
25204
- if (fs29.existsSync(path31.join(os17.homedir(), ".m8t-stack", "config.yaml"))) {
26084
+ if (fs30.existsSync(path32.join(os17.homedir(), ".m8t-stack", "config.yaml"))) {
25205
26085
  out(colors.error("\u26A0 an existing ~/.m8t-stack/config.yaml will override the onboarding app registration in apps/web \u2014 if Microsoft sign-in fails, move it aside and retry."));
25206
26086
  }
25207
- const credential2 = new DefaultAzureCredential19();
26087
+ const credential2 = new DefaultAzureCredential20();
25208
26088
  const account = await getAzAccount();
25209
26089
  assertNodeVersion();
25210
26090
  out("waiting for Foundry (the installer's foundry-create phase)\u2026");
@@ -25275,10 +26155,10 @@ var BootstrapUiCommand = class extends M8tCommand {
25275
26155
  };
25276
26156
 
25277
26157
  // src/commands/bootstrap/seed-profile.ts
25278
- import { Command as Command52, Option as Option50 } from "clipanion";
26158
+ import { Command as Command54, Option as Option51 } from "clipanion";
25279
26159
  var BootstrapSeedProfileCommand = class extends M8tCommand {
25280
26160
  static paths = [["bootstrap", "seed-profile"]];
25281
- static usage = Command52.Usage({
26161
+ static usage = Command54.Usage({
25282
26162
  description: "Seed your advisors' brains with the founder + company profile from the onboarding questionnaire.",
25283
26163
  details: "Reads the latest stacey-intake conversation, renders memory/founder.md + memory/company-profile.md (+ their MEMORY.md index lines), and commits them to both <org>/stacey-brain and <org>/azzy-brain via the GitHub App. Idempotent. --watch polls until the founder completes the questionnaire.",
25284
26164
  examples: [
@@ -25286,11 +26166,11 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
25286
26166
  ["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
25287
26167
  ]
25288
26168
  });
25289
- endpoint = Option50.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
25290
- brain = Option50.String("--brain", { description: "Override the brain repo (default <org>/stacey-brain)." });
25291
- watch = Option50.Boolean("--watch", false, { description: "Poll until the questionnaire completes (or --timeout)." });
25292
- timeout = Option50.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
25293
- githubAppCreds = Option50.String("--github-app-creds");
26169
+ endpoint = Option51.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
26170
+ brain = Option51.String("--brain", { description: "Override the brain repo (default <org>/stacey-brain)." });
26171
+ watch = Option51.Boolean("--watch", false, { description: "Poll until the questionnaire completes (or --timeout)." });
26172
+ timeout = Option51.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
26173
+ githubAppCreds = Option51.String("--github-app-creds");
25294
26174
  async executeCommand() {
25295
26175
  const ctx = await resolveSeedContext({
25296
26176
  endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
@@ -25375,7 +26255,9 @@ cli.register(CoderTeardownCommand);
25375
26255
  cli.register(AzureExecDeployCommand);
25376
26256
  cli.register(PlatformStatusCommand);
25377
26257
  cli.register(PlatformUpdateCommand);
26258
+ cli.register(PlatformConvergeCommand);
25378
26259
  cli.register(PlatformEnableCostReportCommand);
26260
+ cli.register(PlatformEnableAutoUpdateCommand);
25379
26261
  cli.register(DeployCommand);
25380
26262
  cli.register(AgentDeployAdvisorCommand);
25381
26263
  cli.register(AgentRemoveCommand);