@mutmutco/cli 3.139.11 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.cjs +436 -343
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -5747,6 +5747,16 @@ function rulesetRequiredContexts(ruleset) {
5747
5747
  }
5748
5748
  return contexts;
5749
5749
  }
5750
+ function rulesetBranchIncludes(ruleset) {
5751
+ const raw = ruleset.conditions?.ref_name?.include;
5752
+ return Array.isArray(raw) ? [...new Set(raw.filter((ref) => typeof ref === "string" && ref.length > 0))].sort((a, b) => a.localeCompare(b)) : [];
5753
+ }
5754
+ function patchRulesetBranchIncludes(body, branches) {
5755
+ const conditions = body.conditions && typeof body.conditions === "object" && !Array.isArray(body.conditions) ? body.conditions : {};
5756
+ const currentRef = conditions.ref_name && typeof conditions.ref_name === "object" && !Array.isArray(conditions.ref_name) ? conditions.ref_name : {};
5757
+ const include = [...new Set(branches)].map((branch) => branch.startsWith("refs/") ? branch : `refs/heads/${branch}`);
5758
+ return { ...body, conditions: { ...conditions, ref_name: { ...currentRef, include } } };
5759
+ }
5750
5760
  function patchRulesetRequiredContexts(body, contexts) {
5751
5761
  const sorted = [...new Set(contexts)].sort((a, b) => a.localeCompare(b));
5752
5762
  const rules2 = (body.rules ?? []).map((rule) => {
@@ -5844,13 +5854,16 @@ async function gateIsProvenGreen(repo, client, baseBranch, gateFiles = DEFAULT_G
5844
5854
  async function activateProductRuleset(repo, rulesetBody, client, enforcement = "active") {
5845
5855
  const body = { ...rulesetBody, enforcement };
5846
5856
  const want = new Set(rulesetRequiredContexts({ rules: body.rules }));
5857
+ const wantBranches = rulesetBranchIncludes(body);
5847
5858
  const list = await client.rest("GET", `repos/${repo}/rulesets`, { timeoutMs: 2e4 });
5848
5859
  const existing = findProductRuleset(list ?? []);
5849
5860
  if (existing?.id != null) {
5850
5861
  const detail = await client.rest("GET", `repos/${repo}/rulesets/${existing.id}`, { timeoutMs: 2e4 });
5851
5862
  const have = new Set(rulesetRequiredContexts(detail));
5852
- if (detail.enforcement === enforcement && have.size === want.size && [...want].every((c) => have.has(c))) {
5853
- return { action: "skipped", enforcement, detail: `${enforcement} ruleset already matches required contexts` };
5863
+ const haveBranches = rulesetBranchIncludes(detail);
5864
+ const branchesMatch = haveBranches.length === wantBranches.length && wantBranches.every((ref, index) => ref === haveBranches[index]);
5865
+ if (detail.enforcement === enforcement && branchesMatch && have.size === want.size && [...want].every((c) => have.has(c))) {
5866
+ return { action: "skipped", enforcement, detail: `${enforcement} ruleset already matches required contexts and branches` };
5854
5867
  }
5855
5868
  await client.rest("PUT", `repos/${repo}/rulesets/${existing.id}`, { body, timeoutMs: 2e4 });
5856
5869
  return { action: "updated", enforcement, detail: `ruleset ${existing.id}` };
@@ -6826,9 +6839,11 @@ function checkGateBudget(files, opts = {}) {
6826
6839
  var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-app", "desktop-game", "mobile-app", "non-deployable", "cli-tool", "worker"];
6827
6840
  var DEPLOY_MODELS = ["hub-serverless", "serverless", "tenant-container", "solo-container", "static-cdn", "registry-publish", "content", "none"];
6828
6841
  var RELEASE_TRACKS = ["full", "direct", "trunk"];
6842
+ var REQUIRED_CHECK_BRANCHES = ["development", "rc", "main"];
6829
6843
  var PROJECT_TYPE_SET = new Set(PROJECT_TYPES);
6830
6844
  var DEPLOY_MODEL_SET = new Set(DEPLOY_MODELS);
6831
6845
  var RELEASE_TRACK_SET = new Set(RELEASE_TRACKS);
6846
+ var REQUIRED_CHECK_BRANCH_SET = new Set(REQUIRED_CHECK_BRANCHES);
6832
6847
  function isProjectType(value) {
6833
6848
  return Boolean(value && PROJECT_TYPE_SET.has(value));
6834
6849
  }
@@ -6838,6 +6853,9 @@ function isDeployModel(value) {
6838
6853
  function isReleaseTrack(value) {
6839
6854
  return Boolean(value && RELEASE_TRACK_SET.has(value));
6840
6855
  }
6856
+ function isRequiredCheckBranch(value) {
6857
+ return Boolean(value && REQUIRED_CHECK_BRANCH_SET.has(value));
6858
+ }
6841
6859
  function repoIsHub(repo) {
6842
6860
  return repo.toLowerCase().endsWith("/mmi-hub") || repo.toLowerCase() === "mmi-hub";
6843
6861
  }
@@ -6906,6 +6924,13 @@ function branchesForTrack(track) {
6906
6924
  if (track === "direct") return ["development", "main"];
6907
6925
  return ["development", "rc", "main"];
6908
6926
  }
6927
+ function resolveRequiredCheckBranches(meta, repo) {
6928
+ const explicit = Array.isArray(meta?.requiredCheckBranches) ? meta.requiredCheckBranches.filter((branch) => typeof branch === "string" && isRequiredCheckBranch(branch)) : [];
6929
+ if (explicit.length > 0 && explicit.length === meta?.requiredCheckBranches?.length && new Set(explicit).size === explicit.length) {
6930
+ return REQUIRED_CHECK_BRANCHES.filter((branch) => explicit.includes(branch));
6931
+ }
6932
+ return branchesForTrack(resolveReleaseTrack(meta, void 0, repo));
6933
+ }
6909
6934
  function promotionBranchesForTrack(track) {
6910
6935
  if (track === "trunk") return [];
6911
6936
  if (track === "direct") return ["main"];
@@ -7064,7 +7089,7 @@ var GATE_RUNTIME_DEFAULTS = {
7064
7089
  node: { cmd: DEFAULT_GATE_CMD, install: "npm ci" },
7065
7090
  python: { cmd: "pytest", install: 'pip install -e ".[dev]"' }
7066
7091
  };
7067
- function gateSeedVars(cls, releaseTrack, runtime = "node") {
7092
+ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches) {
7068
7093
  const rt = GATE_RUNTIME_DEFAULTS[runtime] ?? GATE_RUNTIME_DEFAULTS.node;
7069
7094
  const runtimeVars = {
7070
7095
  GATE_RUNTIME: runtime,
@@ -7078,6 +7103,9 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
7078
7103
  GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA
7079
7104
  };
7080
7105
  const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
7106
+ const trackBranches = track === "trunk" ? ["main"] : track === "direct" ? ["development", "main"] : ["development", "rc", "main"];
7107
+ const rulesetBranches = requiredCheckBranches?.length ? [...requiredCheckBranches] : trackBranches;
7108
+ const rulesetRefs = JSON.stringify(rulesetBranches.map((branch) => `refs/heads/${branch}`));
7081
7109
  const windowsCompat = {
7082
7110
  // #5113: opt-in informational windows-latest proof. Default OFF — the CLI fills the rendered job
7083
7111
  // YAML (or '') at the final layering step; never hand-passed.
@@ -7089,7 +7117,7 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
7089
7117
  ...windowsCompat,
7090
7118
  GATE_PUSH_BRANCHES_YAML: "[main]",
7091
7119
  GATE_FULL_RUN_BRANCH: "main",
7092
- GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/main"]'
7120
+ GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
7093
7121
  };
7094
7122
  }
7095
7123
  if (track === "direct") {
@@ -7098,7 +7126,7 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
7098
7126
  ...windowsCompat,
7099
7127
  GATE_PUSH_BRANCHES_YAML: "[development, main]",
7100
7128
  GATE_FULL_RUN_BRANCH: "development",
7101
- GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/development", "refs/heads/main"]'
7129
+ GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
7102
7130
  };
7103
7131
  }
7104
7132
  return {
@@ -7106,10 +7134,10 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
7106
7134
  ...windowsCompat,
7107
7135
  GATE_PUSH_BRANCHES_YAML: "[development, rc, main]",
7108
7136
  GATE_FULL_RUN_BRANCH: "development",
7109
- GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/development", "refs/heads/rc", "refs/heads/main"]'
7137
+ GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
7110
7138
  };
7111
7139
  }
7112
- function withDerivedRepoVars(vars, parsed, cls, releaseTrack) {
7140
+ function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranches) {
7113
7141
  const out = { ...vars };
7114
7142
  out.REPO_NAME ??= parsed.name;
7115
7143
  out.REPO_SLUG ??= parsed.slug;
@@ -7117,7 +7145,7 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack) {
7117
7145
  out.PROJECT_OWNER ??= parsed.owner;
7118
7146
  const track = releaseTrack ?? resolveBootstrapReleaseTrack(cls);
7119
7147
  const runtime = out.GATE_RUNTIME === "python" ? "python" : "node";
7120
- for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime))) {
7148
+ for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime, requiredCheckBranches))) {
7121
7149
  out[key] ??= value;
7122
7150
  }
7123
7151
  if (out.GATE_WINDOWS_COMPAT === "true" && !out.GATE_WINDOWS_COMPAT_JOB_YAML) {
@@ -10624,7 +10652,7 @@ function validateRolloutPlan(planRaw, fleet) {
10624
10652
  } else {
10625
10653
  if (nonEmpty(hubReleaseVersion) && baselineVersion !== hubReleaseVersion.trim()) {
10626
10654
  violations.push(
10627
- `rollback baseline v${baselineVersion} is stale \u2014 the Hub's current release is v${hubReleaseVersion.trim()}; a v4 rollback would drop every fix shipped since. Bump infra/rollout-plan.json baseline (and the hub-serverless cohort v3 target) to the latest release.`
10655
+ `rollback baseline v${baselineVersion} is stale \u2014 the Hub's current release is v${hubReleaseVersion.trim()}; a v4 rollback would drop every fix shipped since. Bump infra/rollout-plan.json baseline (and the hub-serverless cohort rollback target) to the latest known-good v4-only release.`
10628
10656
  );
10629
10657
  }
10630
10658
  if (baselineTag && baselineTag !== `v${baselineVersion}`) {
@@ -10651,7 +10679,7 @@ function validateRolloutPlan(planRaw, fleet) {
10651
10679
  if (!nonEmpty(cohort?.deployModel)) violations.push(`cohort ${label} names no deployModel`);
10652
10680
  if (!nonEmpty(cohort?.rollbackTrigger)) violations.push(`cohort ${label} names no rollback trigger`);
10653
10681
  if (!nonEmpty(cohort?.rollback?.mechanism)) violations.push(`cohort ${label} names no rollback mechanism`);
10654
- if (!nonEmpty(cohort?.rollback?.v3Target)) violations.push(`cohort ${label} names no compatible v3 target`);
10682
+ if (!nonEmpty(cohort?.rollback?.v3Target)) violations.push(`cohort ${label} names no compatible rollback target`);
10655
10683
  if (cohort?.rollback?.independent !== true) violations.push(`cohort ${label} does not declare an independent rollback`);
10656
10684
  if (!Array.isArray(cohort?.dependsOn) || cohort.dependsOn.some((d) => !nonEmpty(d))) {
10657
10685
  violations.push(`cohort ${label} has a malformed dependsOn list`);
@@ -10677,10 +10705,10 @@ function validateRolloutPlan(planRaw, fleet) {
10677
10705
  violations.push(`${repo} has schedule ${JSON.stringify(member.schedule)} \u2014 expected "train" or "own"`);
10678
10706
  }
10679
10707
  if (member.v3Target === null && !nonEmpty(member.note)) {
10680
- violations.push(`${repo} has no v3 target and no note naming why`);
10708
+ violations.push(`${repo} has no rollback target and no note naming why`);
10681
10709
  }
10682
10710
  if (member.v3Target !== null && !nonEmpty(member.v3Target)) {
10683
- violations.push(`${repo} has an empty v3 target \u2014 use null plus a note, or a real tag`);
10711
+ violations.push(`${repo} has an empty rollback target \u2014 use null plus a note, or a real tag`);
10684
10712
  }
10685
10713
  }
10686
10714
  }
@@ -10689,7 +10717,7 @@ function validateRolloutPlan(planRaw, fleet) {
10689
10717
  const hubCanary = (Array.isArray(hubCohort?.members) ? hubCohort.members : []).find((m) => m?.role === "canary");
10690
10718
  if (hubCanary && nonEmpty(hubCanary.v3Target) && hubCanary.v3Target.trim() !== baselineTag) {
10691
10719
  violations.push(
10692
- `hub-serverless canary ${hubCanary.repo} names v3 target ${hubCanary.v3Target}, but the rollback baseline is ${baselineTag} \u2014 they must match`
10720
+ `hub-serverless canary ${hubCanary.repo} names rollback target ${hubCanary.v3Target}, but the rollback baseline is ${baselineTag} \u2014 they must match`
10693
10721
  );
10694
10722
  }
10695
10723
  }
@@ -10746,10 +10774,10 @@ var rollout_plan_default = {
10746
10774
  note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
10747
10775
  },
10748
10776
  baseline: {
10749
- version: "3.139.11",
10750
- tag: "v3.139.11",
10751
- commit: "6e2f220fab7d",
10752
- npm: "@mutmutco/cli@3.139.11"
10777
+ version: "4.0.0",
10778
+ tag: "v4.0.0",
10779
+ commit: "71c720103455",
10780
+ npm: "@mutmutco/cli@4.0.0"
10753
10781
  },
10754
10782
  exitCriterion: "fleet-n-of-n",
10755
10783
  hubOnlyShortcut: "forbidden",
@@ -10766,14 +10794,14 @@ var rollout_plan_default = {
10766
10794
  repo: "mutmutco/mmi-hub",
10767
10795
  role: "canary",
10768
10796
  schedule: "train",
10769
- v3Target: "v3.139.11"
10797
+ v3Target: "v4.0.0"
10770
10798
  }
10771
10799
  ],
10772
- rollbackTrigger: "Any red inside the post-cut soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a v3 client refused while the compat window must still admit it (SUPPORTED_MINOR_WINDOW=2, MIN_CLIENT_VERSION 0.0.0 \u2014 D6a), or npm consumer install/doctor failure on the v4 dist.",
10800
+ rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
10773
10801
  rollback: {
10774
10802
  independent: true,
10775
- mechanism: "npm dist-tag latest -> 3.139.11 and redeploy the Hub Lambda from tag v3.139.11 (6e2f220fab7d); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
10776
- v3Target: "v3.139.11 (@mutmutco/cli@3.139.11, tag commit 6e2f220fab7d \u2014 the preserved latest-v3 distribution, D6b)"
10803
+ mechanism: "npm dist-tag latest -> 4.0.0 and redeploy the Hub Lambda from tag v4.0.0 (71c720103455); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
10804
+ v3Target: "v4.0.0 (@mutmutco/cli@4.0.0, tag commit 71c720103455 \u2014 last known-good release carrying the repo-index v4-only contract)"
10777
10805
  }
10778
10806
  },
10779
10807
  {
@@ -10792,17 +10820,17 @@ var rollout_plan_default = {
10792
10820
  v3Target: "v1.25.0"
10793
10821
  },
10794
10822
  {
10795
- repo: "mutmutco/jerv-powertools",
10823
+ repo: "mutmutco/jerv-hub",
10796
10824
  role: "member",
10797
10825
  schedule: "train",
10798
- v3Target: "v2.53.25"
10826
+ v3Target: "v3.1.1"
10799
10827
  }
10800
10828
  ],
10801
10829
  rollbackTrigger: "The first publish minted on the v4 train breaks an installed consumer: plugin install/upgrade failure, doctor red, or a version-lag alarm against the registered plugin surface.",
10802
10830
  rollback: {
10803
10831
  independent: true,
10804
10832
  mechanism: "Per-package npm dist-tag revert to the member's named v3-era version; publishes are additive, so nothing is unpublished and no other cohort is touched.",
10805
- v3Target: "The last release each package minted on the v3 train: jerv-jervcode v1.25.0, jerv-powertools v2.53.25 (named per member)"
10833
+ v3Target: "The last release each package minted on the pre-v4 train: jerv-jervcode v1.25.0, jerv-hub v3.1.1 (named per member)"
10806
10834
  }
10807
10835
  },
10808
10836
  {
@@ -12113,6 +12141,8 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
12113
12141
  ci: {
12114
12142
  declared: typeof project2?.ci === "string" ? project2.ci : null,
12115
12143
  requiredChecks: Array.isArray(project2?.requiredChecks) ? project2.requiredChecks : null,
12144
+ requiredCheckBranches: resolveRequiredCheckBranches(project2, repo),
12145
+ requiredCheckBranchesDeclared: Array.isArray(project2?.requiredCheckBranches),
12116
12146
  exemptReason: typeof project2?.ciExemptReason === "string" ? project2.ciExemptReason : null
12117
12147
  },
12118
12148
  source: { registry: "hub-registry-live", release: "github-releases-live", plugin: "github-contents-live", onboarding: "github-readme-live" },
@@ -15390,6 +15420,7 @@ var RULESET_REFERENCE_MATCH_LABEL = "committed ruleset reference matches the liv
15390
15420
  var GATE_GREEN_ON_BASE_LABEL = "gate is green on the default branch (#3819)";
15391
15421
  var REQUIRED_CONTEXTS_EMITTED_LABEL = "required check contexts match PR workflows";
15392
15422
  var TAG_ADDRESSABLE_CONTEXTS_LABEL = "rc/main required contexts are tag-addressable (#3880)";
15423
+ var RELEASE_BRANCH_REACHABLE_LABEL = "rc/main required contexts are reachable from a release-base head (#5193)";
15393
15424
  var RECONCILE_SEED_BRANCH_PREFIX = "ci-reconcile-ruleset-ref";
15394
15425
  function slugFromRepo(repo) {
15395
15426
  return (repo.includes("/") ? repo.split("/")[1] : repo).toLowerCase();
@@ -15485,30 +15516,60 @@ function rulesetCoversReleaseBranches(payload, track) {
15485
15516
  const releaseRefs = track === "full" ? ["refs/heads/rc", "refs/heads/main"] : ["refs/heads/main"];
15486
15517
  return include.some((entry) => typeof entry === "string" && (entry === "~DEFAULT_BRANCH" ? track === "trunk" : releaseRefs.some((ref) => refPatternCovers(entry, ref))));
15487
15518
  }
15519
+ function releaseBranchRefsCovered(conditions, track) {
15520
+ const include = conditions?.ref_name?.include;
15521
+ if (!Array.isArray(include)) return [];
15522
+ const releaseRefs = track === "full" ? ["refs/heads/rc", "refs/heads/main"] : ["refs/heads/main"];
15523
+ return releaseRefs.filter((ref) => include.some((entry) => typeof entry === "string" && refPatternCovers(entry, ref)));
15524
+ }
15525
+ function unreachableReleaseContexts(input) {
15526
+ const ignore = input.ignore ?? /* @__PURE__ */ new Set();
15527
+ const required = input.requiredContexts.filter((context) => !ignore.has(context));
15528
+ const out = [];
15529
+ if (required.length === 0) return out;
15530
+ for (const branch of input.releaseBranches) {
15531
+ const emitted = input.emittedByBranch[branch];
15532
+ if (emitted === void 0) continue;
15533
+ const emittedSet = new Set(emitted);
15534
+ const unreachable = required.filter((context) => !emittedSet.has(context));
15535
+ if (unreachable.length) out.push({ branch, unreachable });
15536
+ }
15537
+ return out;
15538
+ }
15488
15539
  function registryAuthorityWithoutReference(meta, repo) {
15489
15540
  const contexts = registryRequiredContexts(meta);
15490
15541
  if (contexts == null) return null;
15491
- const track = resolveReleaseTrack(meta, void 0, repo);
15542
+ const requiredBranches = resolveRequiredCheckBranches(meta, repo);
15492
15543
  return {
15493
15544
  contexts: sortedUnique(contexts),
15494
15545
  source: "registry META requiredChecks",
15495
- // With no committed conditions to narrow scope, the registry track is the only available authority.
15496
- coversReleaseBranches: track === "full" || track === "direct" || track === "trunk"
15546
+ coversReleaseBranches: requiredBranches.some((branch) => branch === "rc" || branch === "main")
15497
15547
  };
15498
15548
  }
15499
15549
  function parseAuthoritativeRuleset(raw, meta, repo) {
15500
15550
  const filePayload = JSON.parse(raw);
15501
15551
  const committedPayload = stripRulesetComment(raw);
15502
15552
  const committedContexts = sortedUnique(rulesetRequiredContexts(committedPayload));
15553
+ const committedBranchIncludes = rulesetBranchIncludes(committedPayload);
15503
15554
  const registryContexts = registryRequiredContexts(meta);
15504
15555
  const contexts = sortedUnique(registryContexts ?? committedContexts);
15505
- const apiPayload = registryContexts == null ? committedPayload : patchRulesetRequiredContexts(committedPayload, contexts);
15506
- const authoritativeFilePayload = registryContexts == null ? filePayload : { ...filePayload, rules: apiPayload.rules };
15556
+ const explicitBranches = Array.isArray(meta?.requiredCheckBranches) && meta.requiredCheckBranches.length > 0 ? resolveRequiredCheckBranches(meta, repo) : null;
15557
+ let apiPayload = registryContexts == null ? committedPayload : patchRulesetRequiredContexts(committedPayload, contexts);
15558
+ if (explicitBranches) apiPayload = patchRulesetBranchIncludes(apiPayload, explicitBranches);
15559
+ const branchIncludes = rulesetBranchIncludes(apiPayload);
15560
+ const authoritativeFilePayload = {
15561
+ ...filePayload,
15562
+ ...registryContexts == null ? {} : { rules: apiPayload.rules },
15563
+ ...explicitBranches == null ? {} : { conditions: apiPayload.conditions }
15564
+ };
15507
15565
  return {
15508
15566
  filePayload: authoritativeFilePayload,
15509
15567
  apiPayload,
15510
15568
  committedContexts,
15511
15569
  contexts,
15570
+ branchIncludes,
15571
+ committedBranchIncludes,
15572
+ branchSource: explicitBranches ? "registry META requiredCheckBranches" : "committed ruleset reference",
15512
15573
  source: registryContexts == null ? "committed ruleset reference" : "registry META requiredChecks",
15513
15574
  coversReleaseBranches: rulesetCoversReleaseBranches(apiPayload, resolveReleaseTrack(meta, void 0, repo))
15514
15575
  };
@@ -15518,8 +15579,9 @@ function sameContexts(left, right) {
15518
15579
  }
15519
15580
  function resolveProductRulesetReconcilePlan(input) {
15520
15581
  const liveNeedsContextConvergence = !sameContexts(input.liveContexts, input.authorityContexts);
15582
+ const liveNeedsBranchConvergence = input.liveBranchIncludes !== void 0 && input.authorityBranchIncludes !== void 0 && !sameContexts(input.liveBranchIncludes, input.authorityBranchIncludes);
15521
15583
  const liveIsActive = input.liveEnforcement === "active";
15522
- if (liveIsActive && !liveNeedsContextConvergence) {
15584
+ if (liveIsActive && !liveNeedsContextConvergence && !liveNeedsBranchConvergence) {
15523
15585
  return { shouldActivate: false, targetEnforcement: "active" };
15524
15586
  }
15525
15587
  if (input.unsafeContexts.length > 0) {
@@ -15706,6 +15768,7 @@ async function auditRepoCi(repo, deps) {
15706
15768
  } else if (deployableGated) {
15707
15769
  const productRuleset = rulesets.find((r) => r.name === PRODUCT_RULESET_NAME);
15708
15770
  const liveContexts = productRuleset == null ? [] : sortedUnique(rulesetRequiredContexts(productRuleset));
15771
+ const liveBranchIncludes = productRuleset == null ? [] : rulesetBranchIncludes(productRuleset);
15709
15772
  const hasRequiredChecks = productRuleset?.enforcement === "active" && liveContexts.length > 0;
15710
15773
  checks.push({
15711
15774
  ok: hasRequiredChecks,
@@ -15722,13 +15785,15 @@ async function auditRepoCi(repo, deps) {
15722
15785
  remediation: `Fix ${PRODUCT_RULESET_REF}, then run mmi-cli devops ci reconcile --repo ${repo} --apply`
15723
15786
  });
15724
15787
  } else {
15725
- const fileAligned = sameContexts(authoritativeRuleset.committedContexts, authoritativeRuleset.contexts);
15726
- const liveAligned = sameContexts(liveContexts, authoritativeRuleset.contexts);
15727
- const aligned = fileAligned && liveAligned;
15788
+ const fileContextsAligned = sameContexts(authoritativeRuleset.committedContexts, authoritativeRuleset.contexts);
15789
+ const liveContextsAligned = sameContexts(liveContexts, authoritativeRuleset.contexts);
15790
+ const fileBranchesAligned = sameContexts(authoritativeRuleset.committedBranchIncludes, authoritativeRuleset.branchIncludes);
15791
+ const liveBranchesAligned = sameContexts(liveBranchIncludes, authoritativeRuleset.branchIncludes);
15792
+ const aligned = fileContextsAligned && liveContextsAligned && fileBranchesAligned && liveBranchesAligned;
15728
15793
  checks.push({
15729
15794
  ok: aligned,
15730
15795
  label: RULESET_REFERENCE_MATCH_LABEL,
15731
- detail: aligned ? `[${authoritativeRuleset.contexts.join(", ")}] from ${authoritativeRuleset.source} on both sides` : `${authoritativeRuleset.source} requires [${authoritativeRuleset.contexts.join(", ")}]; ${PRODUCT_RULESET_REF} declares [${authoritativeRuleset.committedContexts.join(", ")}] and the live ${PRODUCT_RULESET_NAME} ruleset requires [${liveContexts.join(", ")}]`,
15796
+ detail: aligned ? `contexts [${authoritativeRuleset.contexts.join(", ")}] and branches [${authoritativeRuleset.branchIncludes.join(", ")}] match declared authority` : `${authoritativeRuleset.source} requires contexts [${authoritativeRuleset.contexts.join(", ")}]; ${authoritativeRuleset.branchSource} requires branches [${authoritativeRuleset.branchIncludes.join(", ")}]; ${PRODUCT_RULESET_REF} declares contexts [${authoritativeRuleset.committedContexts.join(", ")}] / branches [${authoritativeRuleset.committedBranchIncludes.join(", ")}] and live ${PRODUCT_RULESET_NAME} has contexts [${liveContexts.join(", ")}] / branches [${liveBranchIncludes.join(", ")}]`,
15732
15797
  remediation: aligned ? void 0 : `mmi-cli devops ci reconcile --repo ${repo} --apply`
15733
15798
  });
15734
15799
  }
@@ -15753,6 +15818,23 @@ async function auditRepoCi(repo, deps) {
15753
15818
  detail: prOnly.length ? `${contextAuthority.source} requires [${prOnly.join(", ")}] on rc/main, but those contexts can only materialize for pull_request` : void 0,
15754
15819
  remediation: prOnly.length ? `Remove [${prOnly.join(", ")}] from rc/main requirements in registry META or ${PRODUCT_RULESET_REF}` : void 0
15755
15820
  });
15821
+ if (productRuleset != null && liveContexts.length > 0) {
15822
+ const releaseBranches = releaseBranchRefsCovered(productRuleset.conditions, resolveReleaseTrack(meta, void 0, repo));
15823
+ if (releaseBranches.length > 0) {
15824
+ const emittedByBranch = {};
15825
+ for (const ref of releaseBranches) {
15826
+ emittedByBranch[ref] = await resolveEmittedPrContexts(deps, repo, ref.replace("refs/heads/", ""));
15827
+ }
15828
+ const ignore = /* @__PURE__ */ new Set([...AGENT_PR_BOOKKEEPING_CONTEXTS, ...TRAIN_PR_ONLY_CONTEXTS]);
15829
+ const unreachable = unreachableReleaseContexts({ requiredContexts: liveContexts, releaseBranches, emittedByBranch, ignore });
15830
+ checks.push({
15831
+ ok: unreachable.length === 0,
15832
+ label: RELEASE_BRANCH_REACHABLE_LABEL,
15833
+ detail: unreachable.length === 0 ? void 0 : unreachable.map((u) => `${u.branch.replace("refs/heads/", "")} requires [${u.unreachable.join(", ")}] but its own workflows emit none of them`).join("; ") + " \u2014 a base-" + unreachable.map((u) => u.branch.replace("refs/heads/", "")).join("/") + " PR (e.g. a hotfix) can never satisfy the gate and stays permanently BLOCKED",
15834
+ remediation: unreachable.length === 0 ? void 0 : `Remove the unreachable context(s) from those branches in the ${PRODUCT_RULESET_NAME} ruleset conditions and ${PRODUCT_RULESET_REF}, or add the emitting workflow to the branch`
15835
+ });
15836
+ }
15837
+ }
15756
15838
  }
15757
15839
  }
15758
15840
  if (repoClass === "deployable") {
@@ -16026,7 +16108,13 @@ async function seedGateYml(repo, deps, meta, result) {
16026
16108
  const baseBranch = "development";
16027
16109
  const releaseTrack = isReleaseTrack(meta?.releaseTrack) ? meta?.releaseTrack : void 0;
16028
16110
  const parsed = parseOwnerRepo(repo);
16029
- const refOnlyVars = () => withDerivedRepoVars({ REPO_SLUG: parsed.slug }, parsed, "deployable", releaseTrack);
16111
+ const refOnlyVars = () => withDerivedRepoVars(
16112
+ { REPO_SLUG: parsed.slug },
16113
+ parsed,
16114
+ "deployable",
16115
+ releaseTrack,
16116
+ meta?.requiredCheckBranches
16117
+ );
16030
16118
  if (await contentExists(deps, repo, baseBranch, PRODUCT_GATE_PATH)) {
16031
16119
  return await seedRulesetRefIfMissing(repo, deps, refOnlyVars(), baseBranch, result);
16032
16120
  }
@@ -16047,7 +16135,13 @@ async function seedGateYml(repo, deps, meta, result) {
16047
16135
  result.skipped.push(`gate.yml missing \u2014 no registry gate config; set \`mmi-cli oracle org project set ${repo} --var gate={...}\` first, then re-run reconcile`);
16048
16136
  return "none";
16049
16137
  }
16050
- const derivedVars = withDerivedRepoVars({ ...gateConfigToVars(gate), REPO_SLUG: parsed.slug }, parsed, "deployable", releaseTrack);
16138
+ const derivedVars = withDerivedRepoVars(
16139
+ { ...gateConfigToVars(gate), REPO_SLUG: parsed.slug },
16140
+ parsed,
16141
+ "deployable",
16142
+ releaseTrack,
16143
+ meta?.requiredCheckBranches
16144
+ );
16051
16145
  const rendered = renderSeedBody(deps, GATE_TEMPLATE_SEED, PRODUCT_GATE_PATH, derivedVars);
16052
16146
  if (rendered == null) {
16053
16147
  result.errors.push(`gate.yml re-seed: could not render ${GATE_TEMPLATE_SEED} (template unreadable or unfilled)`);
@@ -16223,7 +16317,8 @@ async function applyCiReconcileRepo(repo, deps) {
16223
16317
  result.errors.push(`${authority.source} declares no required contexts`);
16224
16318
  return finalizeCiReconcile(repo, deps, result, report);
16225
16319
  }
16226
- if (!sameContexts(authority.committedContexts, authority.contexts)) {
16320
+ const sourceNeedsConvergence = !sameContexts(authority.committedContexts, authority.contexts) || !sameContexts(authority.committedBranchIncludes, authority.branchIncludes);
16321
+ if (sourceNeedsConvergence) {
16227
16322
  try {
16228
16323
  const delivery = await deliverSeedFile(
16229
16324
  deps,
@@ -16237,19 +16332,19 @@ async function applyCiReconcileRepo(repo, deps) {
16237
16332
  result.skipped.push(`${PRODUCT_RULESET_REF} already byte-equivalent on ${baseBranch}`);
16238
16333
  } else if (delivery.plan.mode === "pr") {
16239
16334
  result.applied.push(
16240
- `${PRODUCT_RULESET_REF} authoritative contexts \u2192 [${authority.contexts.join(", ")}] delivered on ${delivery.plan.branch} (${delivery.plan.reason})${delivery.prUrl ? `: ${delivery.prUrl}` : ""}`
16335
+ `${PRODUCT_RULESET_REF} authority \u2192 contexts [${authority.contexts.join(", ")}], branches [${authority.branchIncludes.join(", ")}] delivered on ${delivery.plan.branch} (${delivery.plan.reason})${delivery.prUrl ? `: ${delivery.prUrl}` : ""}`
16241
16336
  );
16242
16337
  const reason = `${PRODUCT_RULESET_REF} authority is pending on its protected-base source PR; live ruleset convergence waits for that source to merge`;
16243
16338
  return finalizeCiReconcile(repo, deps, result, report, reason);
16244
16339
  } else {
16245
- result.applied.push(`reconciled ${PRODUCT_RULESET_REF} authoritative contexts \u2192 [${authority.contexts.join(", ")}]`);
16340
+ result.applied.push(`reconciled ${PRODUCT_RULESET_REF} authority \u2192 contexts [${authority.contexts.join(", ")}], branches [${authority.branchIncludes.join(", ")}]`);
16246
16341
  }
16247
16342
  } catch (e) {
16248
16343
  result.errors.push(`ruleset reference delivery failed: ${e.message}`);
16249
16344
  return finalizeCiReconcile(repo, deps, result, report);
16250
16345
  }
16251
16346
  } else {
16252
- result.skipped.push(`${PRODUCT_RULESET_REF} already matches ${authority.source}`);
16347
+ result.skipped.push(`${PRODUCT_RULESET_REF} already matches declared context and branch authority`);
16253
16348
  }
16254
16349
  let live;
16255
16350
  try {
@@ -16259,6 +16354,7 @@ async function applyCiReconcileRepo(repo, deps) {
16259
16354
  return finalizeCiReconcile(repo, deps, result, report);
16260
16355
  }
16261
16356
  const liveContexts = live == null ? [] : sortedUnique(rulesetRequiredContexts(live));
16357
+ const liveBranchIncludes = live == null ? [] : rulesetBranchIncludes(live);
16262
16358
  const prWorkflows = await prTriggeredWorkflowsOnRef(deps, repo, baseBranch, "deployable") ?? [];
16263
16359
  const gateFiles = gateWorkflowFiles(prWorkflows);
16264
16360
  const allPrWorkflows = await listWorkflowPaths(deps, repo, baseBranch) ?? [];
@@ -16274,6 +16370,8 @@ async function applyCiReconcileRepo(repo, deps) {
16274
16370
  liveEnforcement: live?.enforcement,
16275
16371
  liveContexts,
16276
16372
  authorityContexts: authority.contexts,
16373
+ liveBranchIncludes,
16374
+ authorityBranchIncludes: authority.branchIncludes,
16277
16375
  gateProvenGreen: await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles),
16278
16376
  unsafeContexts: unsafe
16279
16377
  });
@@ -16282,7 +16380,7 @@ async function applyCiReconcileRepo(repo, deps) {
16282
16380
  result.skipped.push(plan.holdReason);
16283
16381
  return finalizeCiReconcile(repo, deps, result, report, plan.holdReason);
16284
16382
  }
16285
- result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts already match ${authority.source}`);
16383
+ result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts and branches already match declared authority`);
16286
16384
  return finalizeCiReconcile(repo, deps, result, report);
16287
16385
  }
16288
16386
  try {
@@ -23412,57 +23510,35 @@ function shardRepoIndexV4(envelope) {
23412
23510
 
23413
23511
  // src/repo-index-cloud-client.ts
23414
23512
  var RETRY_ATTEMPTS2 = 3;
23415
- async function publishRepoIndexCloud(payload, deps) {
23513
+ async function probeRepoIndexV4ReadinessCloud(queries, deps) {
23416
23514
  if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
23417
23515
  const token = await deps.token();
23418
23516
  if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)" };
23517
+ const baseUrl = deps.baseUrl.replace(/\/$/, "");
23518
+ const headers = { ...clientVersionHeaders(), Authorization: ["Bearer", token].join(" "), "content-type": "application/json" };
23419
23519
  try {
23420
- const res = await fetchWithRetry(
23421
- deps.fetch ?? fetch,
23422
- `${deps.baseUrl.replace(/\/$/, "")}/repo-index/publish`,
23423
- {
23520
+ for (const query of queries) {
23521
+ const res2 = await fetchWithRetry(deps.fetch ?? fetch, `${baseUrl}/repo-index/v4/shadow`, {
23424
23522
  method: "POST",
23425
- headers: {
23426
- ...clientVersionHeaders(),
23427
- Authorization: `Bearer ${token}`,
23428
- "content-type": "application/json"
23429
- },
23430
- body: JSON.stringify(payload)
23431
- },
23432
- {
23433
- attempts: RETRY_ATTEMPTS2,
23434
- // Embed publish can take longer than a registry read.
23435
- timeoutMs: payload.embed ? 12e4 : deps.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS,
23436
- sleep: deps.retrySleep
23437
- }
23438
- );
23439
- if (res.status === 426) return { ok: false, error: upgradeRequiredError(res, await res.json().catch(() => null)), status: 426 };
23440
- const body = await res.json().catch(() => ({}));
23441
- if (!res.ok) {
23442
- const v42 = body.v4;
23443
- const detail = v42?.error?.message;
23444
- return { ok: false, error: typeof detail === "string" ? detail : typeof body.error === "string" ? body.error : `publish HTTP ${res.status}`, status: res.status };
23523
+ headers,
23524
+ body: JSON.stringify({ q: query.query, mode: query.mode, readinessProbe: true })
23525
+ }, { attempts: RETRY_ATTEMPTS2, timeoutMs: 12e4, sleep: deps.retrySleep });
23526
+ const body2 = await res2.json().catch(() => ({}));
23527
+ if (!res2.ok) return { ok: false, error: `${query.id}: ${body2.error ?? `v4 readiness probe HTTP ${res2.status}`}${body2.errorClass ? ` (${body2.errorClass})` : ""}`, status: res2.status };
23445
23528
  }
23446
- const v4 = body.v4;
23447
- if (v4?.ok === false) return { ok: false, error: typeof v4.error?.message === "string" ? v4.error.message : "v4 publication rejected", status: res.status };
23448
- return { ok: true, body };
23449
- } catch (e) {
23450
- return { ok: false, error: e.message };
23451
- }
23452
- }
23453
- async function setRepoIndexV4ReadPercentCloud(percent, deps) {
23454
- if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
23455
- const token = await deps.token();
23456
- if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)" };
23457
- try {
23458
- const res = await fetchWithRetry(deps.fetch ?? fetch, `${deps.baseUrl.replace(/\/$/, "")}/repo-index/v4/cutover`, {
23459
- method: "POST",
23460
- headers: { ...clientVersionHeaders(), Authorization: `Bearer ${token}`, "content-type": "application/json" },
23461
- body: JSON.stringify({ percent })
23529
+ const res = await fetchWithRetry(deps.fetch ?? fetch, `${baseUrl}/repo-index/status`, {
23530
+ method: "GET",
23531
+ headers: { ...clientVersionHeaders(), Authorization: ["Bearer", token].join(" ") }
23462
23532
  }, { attempts: RETRY_ATTEMPTS2, timeoutMs: deps.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS, sleep: deps.retrySleep });
23463
23533
  const body = await res.json().catch(() => ({}));
23464
- if (!res.ok) return { ok: false, error: body.error ?? `cutover HTTP ${res.status}`, status: res.status };
23465
- return { ok: true, percent: body.percent, cohort: body.cohort, rollbackAction: body.rollbackAction };
23534
+ if (!res.ok) return { ok: false, error: body.error ?? `v4 readiness status HTTP ${res.status}`, status: res.status };
23535
+ const readiness = body.v4Readiness;
23536
+ const complete = !!readiness && ["ready", "not-ready"].includes(readiness.verdict) && Array.isArray(readiness.reasons) && Number.isFinite(readiness.goldenCount) && Number.isFinite(readiness.evidenceCount) && !!readiness.modes && ["lexical", "semantic", "hybrid"].every((mode) => {
23537
+ const verdict = readiness.modes[mode]?.verdict;
23538
+ return verdict === "ready" || verdict === "not-ready";
23539
+ });
23540
+ if (!complete) return { ok: false, error: "Hub status omitted the explicit v4 readiness verdict/reasons/modes contract", status: res.status };
23541
+ return { ok: true, readiness };
23466
23542
  } catch (error) {
23467
23543
  return { ok: false, error: error.message };
23468
23544
  }
@@ -23568,7 +23644,7 @@ async function searchRepoIndexCloud(query, opts, deps) {
23568
23644
  void fetchWithRetry(
23569
23645
  deps.fetch ?? fetch,
23570
23646
  `${deps.baseUrl.replace(/\/$/, "")}/repo-index/v4/shadow`,
23571
- { method: "POST", headers: { ...clientVersionHeaders(), Authorization: `Bearer ${token}`, "content-type": "application/json" }, body: JSON.stringify({ q: query, mode: opts.mode ?? "hybrid", limit: opts.limit ?? 20, cohort: `shadow-${Math.round(shadowRate * 100)}`, ...opts.repo ? { repo: opts.repo } : {} }) },
23647
+ { method: "POST", headers: { ...clientVersionHeaders(), Authorization: ["Bearer", token].join(" "), "content-type": "application/json" }, body: JSON.stringify({ q: query, mode: opts.mode ?? "hybrid", limit: opts.limit ?? 20, ...opts.repo ? { repo: opts.repo } : {} }) },
23572
23648
  { attempts: 1, timeoutMs: 12e4, sleep: deps.retrySleep }
23573
23649
  ).catch(() => {
23574
23650
  });
@@ -23797,7 +23873,9 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos2) {
23797
23873
  }
23798
23874
 
23799
23875
  // src/repo-index-sync.ts
23800
- var MAX_EMBED_BACKFILL_ROUNDS = 40;
23876
+ var COMMIT2 = /^[a-f0-9]{40}$/;
23877
+ var SHA256 = /^[a-f0-9]{64}$/;
23878
+ var UTC_MILLIS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
23801
23879
  function normalizeRepo(raw) {
23802
23880
  const normalized = normalizeRepoIndexRepo(raw);
23803
23881
  if (!normalized) throw new Error(`invalid repository: ${raw}`);
@@ -23814,6 +23892,28 @@ function shallowClone(repo, dest, token) {
23814
23892
  { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
23815
23893
  );
23816
23894
  }
23895
+ function remoteHead(repo, token) {
23896
+ const basic = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64");
23897
+ const output = (0, import_node_child_process14.execFileSync)(
23898
+ "git",
23899
+ ["-c", `http.extraHeader=Authorization: Basic ${basic}`, "ls-remote", "--exit-code", `https://github.com/${repo}.git`, "HEAD"],
23900
+ { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
23901
+ );
23902
+ const match = String(output).match(/^([a-f0-9]{40})\s+HEAD$/m);
23903
+ if (!match) throw new Error(`could not resolve remote HEAD for ${repo}`);
23904
+ return match[1];
23905
+ }
23906
+ function verifiedReadyCommit(statusValue, repo) {
23907
+ if (!statusValue || typeof statusValue !== "object" || Array.isArray(statusValue)) return null;
23908
+ const status = statusValue;
23909
+ const v4 = status.v4;
23910
+ if (!v4 || typeof v4 !== "object" || Array.isArray(v4)) return null;
23911
+ const pointer = v4;
23912
+ const artifactDigests = pointer.artifactDigests;
23913
+ const counts = [pointer.chunkCount, pointer.embeddingCount, pointer.tombstoneCount];
23914
+ const structurallyVerified = status.repo === repo && pointer.repo === repo && pointer.state === "ready" && pointer.integrity === "verified" && typeof pointer.commit === "string" && COMMIT2.test(pointer.commit) && typeof pointer.digest === "string" && SHA256.test(pointer.digest) && Array.isArray(artifactDigests) && artifactDigests.length === 2 && new Set(artifactDigests).size === artifactDigests.length && artifactDigests.every((digest) => typeof digest === "string" && SHA256.test(digest)) && counts.every((count) => Number.isInteger(count) && Number(count) >= 0) && pointer.embeddingCount === pointer.chunkCount && typeof pointer.activatedAt === "string" && UTC_MILLIS.test(pointer.activatedAt) && Number.isFinite(Date.parse(pointer.activatedAt)) && new Date(pointer.activatedAt).toISOString() === pointer.activatedAt;
23915
+ return structurallyVerified && typeof pointer.commit === "string" ? pointer.commit : null;
23916
+ }
23817
23917
  async function syncEstateRepoIndex(opts) {
23818
23918
  const projects = await fetchProjectsList(opts.deps);
23819
23919
  if (!projects) {
@@ -23832,6 +23932,18 @@ async function syncEstateRepoIndex(opts) {
23832
23932
  const failed = [];
23833
23933
  const skipped = [];
23834
23934
  for (const repo of repos) {
23935
+ try {
23936
+ const status = await statusRepoIndexCloud(repo, opts.deps);
23937
+ const activeCommit = verifiedReadyCommit(status, repo);
23938
+ if (activeCommit) {
23939
+ const head = remoteHead(repo, opts.githubToken);
23940
+ if (head === activeCommit) {
23941
+ skipped.push(`${repo}: unchanged verified-ready authority at ${head}`);
23942
+ continue;
23943
+ }
23944
+ }
23945
+ } catch {
23946
+ }
23835
23947
  const dir = (0, import_node_fs27.mkdtempSync)((0, import_node_path25.join)((0, import_node_os12.tmpdir)(), "mmi-repo-index-"));
23836
23948
  try {
23837
23949
  const reconciled = await reconcileRepoIndexCloud(repo, opts.deps);
@@ -23840,51 +23952,19 @@ async function syncEstateRepoIndex(opts) {
23840
23952
  continue;
23841
23953
  }
23842
23954
  shallowClone(repo, dir, opts.githubToken);
23843
- const built = rebuildRepoIndex(dir, repo);
23844
- let pub = await publishRepoIndexCloud(
23845
- {
23846
- repo,
23847
- builtAt: built.builtAt,
23848
- entries: built.entries,
23849
- embed: opts.embed === true
23850
- },
23851
- opts.deps
23852
- );
23853
- if (!pub.ok) {
23854
- failed.push({ repo, error: pub.error });
23855
- continue;
23856
- }
23857
- let rounds = 1;
23858
- while (opts.embed === true && pub.ok && pub.body.embTruncated === true && rounds < MAX_EMBED_BACKFILL_ROUNDS) {
23859
- pub = await publishRepoIndexCloud(
23860
- {
23861
- repo,
23862
- builtAt: built.builtAt,
23863
- entries: built.entries,
23864
- embed: true
23865
- },
23866
- opts.deps
23867
- );
23868
- if (!pub.ok) {
23869
- failed.push({ repo, error: pub.error });
23870
- break;
23871
- }
23872
- rounds++;
23873
- }
23874
- if (!pub.ok) continue;
23875
- let v4Commit;
23955
+ let v4;
23876
23956
  try {
23877
- const v4 = await buildRepoIndexV4(dir, repo, { modelDirectory: process.env.MMI_REPO_INDEXER_MODEL_DIR });
23878
- v4Commit = v4.manifest.commit;
23957
+ v4 = await buildRepoIndexV4(dir, repo, { modelDirectory: process.env.MMI_REPO_INDEXER_MODEL_DIR });
23879
23958
  const v4Published = await publishRepoIndexV4Cloud(v4, opts.deps);
23880
23959
  if (!v4Published.ok) {
23881
- failed.push({ repo, error: `v3 published; v4 failed: ${v4Published.error}` });
23960
+ failed.push({ repo, error: `v4 failed: ${v4Published.error}` });
23882
23961
  continue;
23883
23962
  }
23884
23963
  } catch (error) {
23885
- failed.push({ repo, error: `v3 published; v4 failed: ${error.message}` });
23964
+ failed.push({ repo, error: `v4 failed: ${error.message}` });
23886
23965
  continue;
23887
23966
  }
23967
+ const v4Commit = v4.manifest.commit;
23888
23968
  let graphEdges;
23889
23969
  try {
23890
23970
  const edges = buildGraphEdges(dir, repo, v4Commit, allRosterRepos);
@@ -23894,16 +23974,14 @@ async function syncEstateRepoIndex(opts) {
23894
23974
  } catch (error) {
23895
23975
  skipped.push(`${repo}: optional v4 graph unavailable: ${error.message}`);
23896
23976
  }
23897
- const fileCount = Number(pub.body.fileCount ?? built.entries.length);
23898
- const embCount = typeof pub.body.embCount === "number" ? pub.body.embCount : void 0;
23899
- const embGap = typeof pub.body.embGap === "number" ? pub.body.embGap : typeof embCount === "number" ? Math.max(0, fileCount - embCount) : void 0;
23977
+ const fileCount = v4.manifest.chunks.length;
23978
+ const embCount = v4.manifest.embeddings.length;
23979
+ const embGap = Math.max(0, fileCount - embCount);
23900
23980
  published.push({
23901
23981
  repo,
23902
23982
  fileCount,
23903
23983
  embCount,
23904
23984
  embGap,
23905
- embTruncated: pub.body.embTruncated === true,
23906
- embedRounds: opts.embed ? rounds : void 0,
23907
23985
  graphEdges
23908
23986
  });
23909
23987
  } catch (e) {
@@ -23951,6 +24029,68 @@ var repo_index_golden_queries_default = {
23951
24029
  ]
23952
24030
  };
23953
24031
 
24032
+ // testdata/repo-index-golden-queries-v4.json
24033
+ var repo_index_golden_queries_v4_default = {
24034
+ schemaVersion: 4,
24035
+ suite: "repo-index-golden-queries",
24036
+ authority: "docs/schemas/repo-index-v4.schema.json#/$defs/goldenBenchmarkRecord",
24037
+ queries: [
24038
+ {
24039
+ id: "rebuild-symbol-lexical-v4",
24040
+ version: 4,
24041
+ query: "rebuildRepoIndex",
24042
+ mode: "lexical",
24043
+ expected: {
24044
+ repo: "mutmutco/MMI-Hub",
24045
+ path: "cli/src/repo-index.ts",
24046
+ minScore: 0.1,
24047
+ citationPolicy: {
24048
+ commit: "indexed-head",
24049
+ lineRange: "required",
24050
+ symbol: "rebuildRepoIndex"
24051
+ }
24052
+ },
24053
+ latency: { p95Ms: 1e3 },
24054
+ degraded: { allowed: false, fallbackMode: "none", maxLatencyMs: 1e3 }
24055
+ },
24056
+ {
24057
+ id: "schedules-register-semantic-v4",
24058
+ version: 4,
24059
+ query: "where do schedules get registered",
24060
+ mode: "semantic",
24061
+ expected: {
24062
+ repo: "mutmutco/MMI-Hub",
24063
+ path: "cli/src/schedules.ts",
24064
+ minScore: 0.05,
24065
+ citationPolicy: {
24066
+ commit: "indexed-head",
24067
+ lineRange: "required"
24068
+ }
24069
+ },
24070
+ latency: { p95Ms: 5e3 },
24071
+ degraded: { allowed: true, fallbackMode: "lexical", maxLatencyMs: 1500 }
24072
+ },
24073
+ {
24074
+ id: "pointer-safe-hybrid-v4",
24075
+ version: 4,
24076
+ query: "pointer-safe repository index embeddings",
24077
+ mode: "hybrid",
24078
+ expected: {
24079
+ repo: "mutmutco/MMI-Hub",
24080
+ path: "cli/src/repo-index-v4/chunks.ts",
24081
+ minScore: 0.05,
24082
+ citationPolicy: {
24083
+ commit: "indexed-head",
24084
+ lineRange: "required",
24085
+ symbol: "pointerId"
24086
+ }
24087
+ },
24088
+ latency: { p95Ms: 5e3 },
24089
+ degraded: { allowed: true, fallbackMode: "lexical", maxLatencyMs: 1500 }
24090
+ }
24091
+ ]
24092
+ };
24093
+
23954
24094
  // src/repo-index-health.ts
23955
24095
  function assertGoldenSuite(raw, source) {
23956
24096
  if (!raw || raw.schema !== 1 || !Array.isArray(raw.queries)) {
@@ -23970,6 +24110,64 @@ function loadGoldenSuite(path2) {
23970
24110
  function defaultGoldenSuite() {
23971
24111
  return assertGoldenSuite(repo_index_golden_queries_default, "bundled repo-index-golden-queries.json");
23972
24112
  }
24113
+ function repoIndexHealthGoldenSource(opts) {
24114
+ if (opts.live) return "bundled repo-index-golden-queries-v4.json";
24115
+ return opts.customPath || "bundled repo-index-golden-queries.json";
24116
+ }
24117
+ function formatRepoIndexV4ReadinessSummary(readiness) {
24118
+ const modes = Object.entries(readiness.modes).sort(([a], [b]) => a.localeCompare(b)).map(([mode, result]) => `${mode} ${result.verdict} (${result.evidenceCount}/${result.goldenCount})`).join(", ");
24119
+ return `v4 readiness ${readiness.verdict} \u2014 ${readiness.evidenceCount}/${readiness.goldenCount} checked-in goldens${modes ? `; ${modes}` : ""}`;
24120
+ }
24121
+ function formatRepoIndexCloudStatus(status) {
24122
+ const repo = typeof status.repo === "string" ? status.repo : void 0;
24123
+ const v4 = status.v4 && typeof status.v4 === "object" ? status.v4 : void 0;
24124
+ const state = typeof v4?.state === "string" ? v4.state : status.present === false ? "absent" : "unknown";
24125
+ const commit = typeof v4?.commit === "string" ? ` @ ${v4.commit.slice(0, 12)}` : "";
24126
+ const rawReadiness = status.v4Readiness && typeof status.v4Readiness === "object" ? status.v4Readiness : void 0;
24127
+ const readiness = rawReadiness && (rawReadiness.verdict === "ready" || rawReadiness.verdict === "not-ready") && typeof rawReadiness.goldenCount === "number" && typeof rawReadiness.evidenceCount === "number" && rawReadiness.modes && typeof rawReadiness.modes === "object" ? formatRepoIndexV4ReadinessSummary(rawReadiness) : "v4 readiness unavailable";
24128
+ if (!repo) {
24129
+ const authority = status.v4Authority && typeof status.v4Authority === "object" ? status.v4Authority : void 0;
24130
+ const roster = typeof authority?.rosterCount === "number" ? authority.rosterCount : 0;
24131
+ const ready = typeof authority?.readyAuthorities === "number" ? authority.readyAuthorities : 0;
24132
+ const authorityState = authority?.nOfN === true && authority?.activeNOfN === true ? "ready" : "not-ready";
24133
+ return `repo-index: cloud v4 authorities ${authorityState} ${ready}/${roster}; ${readiness}`;
24134
+ }
24135
+ return `repo-index: cloud ${repo} v4 ${state}${commit}; ${readiness}`;
24136
+ }
24137
+ function defaultV4ReadinessSuite() {
24138
+ const suite = repo_index_golden_queries_v4_default;
24139
+ const modes = new Set(suite.queries?.map((query) => query.mode));
24140
+ if (suite.schemaVersion !== 4 || !Array.isArray(suite.queries) || !["lexical", "semantic", "hybrid"].every((mode) => modes.has(mode))) {
24141
+ throw new Error("invalid bundled repo-index-golden-queries-v4.json");
24142
+ }
24143
+ return suite;
24144
+ }
24145
+ async function runRepoIndexV4ReadinessGate(opts) {
24146
+ const queries = opts.suite.queries.map(({ id, query, mode }) => ({ id, query, mode }));
24147
+ const probed = await opts.probe(queries);
24148
+ if (!probed.ok) return { ok: false, findings: [{ ok: false, code: "v4-probe-error", detail: probed.error }] };
24149
+ const ok = probed.readiness.verdict === "ready";
24150
+ return {
24151
+ ok,
24152
+ findings: [{
24153
+ ok,
24154
+ code: ok ? "v4-ready" : "v4-not-ready",
24155
+ detail: `v4 readiness verdict=${probed.readiness.verdict} evidence=${probed.readiness.evidenceCount}/${probed.readiness.goldenCount} reasons=${JSON.stringify(probed.readiness.reasons)} modes=${JSON.stringify(probed.readiness.modes)}`
24156
+ }],
24157
+ readiness: probed.readiness
24158
+ };
24159
+ }
24160
+ async function runRepoIndexLiveHealth(opts) {
24161
+ const readiness = await runRepoIndexV4ReadinessGate(opts.v4Readiness);
24162
+ if (opts.queriesOnly) return readiness;
24163
+ const status = await opts.live.status();
24164
+ const statusFindings = status.ok === false && typeof status.error === "string" ? [{ ok: true, code: "status-error", detail: status.error, severity: "warn" }] : evaluateCloudStatus(status);
24165
+ return {
24166
+ ok: statusFindings.every((finding) => finding.ok) && readiness.ok,
24167
+ findings: [...statusFindings, ...readiness.findings],
24168
+ ...readiness.readiness ? { readiness: readiness.readiness } : {}
24169
+ };
24170
+ }
23973
24171
  function evaluateQueryHits(query, hits) {
23974
24172
  if (!hits.length) {
23975
24173
  return { ok: false, code: "empty-hits", detail: `${query.id}: no hits for ${JSON.stringify(query.q)}` };
@@ -24005,85 +24203,25 @@ function evaluateQueryHits(query, hits) {
24005
24203
  }
24006
24204
  return { ok: true, code: "ok", detail: `${query.id}: top hit accepted` };
24007
24205
  }
24008
- function evaluateCloudStatus(status, golden) {
24009
- const findings = [];
24010
- const count = typeof status.count === "number" ? status.count : Array.isArray(status.repos) ? status.repos.length : null;
24011
- if (count === 0 || Array.isArray(status.repos) && status.repos.length === 0 && status.present !== true) {
24012
- findings.push({ ok: false, code: "projections-empty", detail: "cloud repo-index has zero projections" });
24013
- }
24014
- const repos = [];
24015
- if (Array.isArray(status.repos)) {
24016
- for (const r of status.repos) {
24017
- if (r && typeof r === "object") repos.push(r);
24018
- }
24019
- } else if (status.present === true && typeof status.repo === "string") {
24020
- repos.push(status);
24021
- }
24022
- const stale = repos.filter((r) => r.stale === true);
24023
- if (stale.length) {
24024
- findings.push({
24025
- ok: false,
24026
- code: "stale-projections",
24027
- detail: `${stale.length} stale projection(s) (threshold ${golden.staleThresholdHours}h): ${stale.slice(0, 5).map((r) => r.repo).join(", ")}`
24028
- });
24029
- }
24030
- const hub = repos.find((r) => r.repo?.toLowerCase() === golden.hubRepo.toLowerCase()) ?? (typeof status.repo === "string" && status.repo.toLowerCase() === golden.hubRepo.toLowerCase() ? status : void 0);
24031
- if (!hub || hub.present === false) {
24032
- findings.push({ ok: false, code: "hub-missing", detail: `${golden.hubRepo} projection missing` });
24033
- } else {
24034
- const fileCount = Number(hub.fileCount ?? 0);
24035
- const embCount = Number(hub.embCount ?? 0);
24036
- if (fileCount <= 0) {
24037
- findings.push({ ok: false, code: "hub-empty", detail: `${golden.hubRepo} has fileCount=${fileCount}` });
24038
- }
24039
- const denom = Math.min(fileCount, golden.maxSyncEmbeds);
24040
- const coverage = denom > 0 ? embCount / denom : 0;
24041
- if (fileCount > 0 && coverage < golden.hubEmbMinCoverage) {
24042
- findings.push({
24043
- ok: false,
24044
- code: "hub-emb-gap",
24045
- detail: `${golden.hubRepo} embCoverage=${coverage.toFixed(2)} (emb=${embCount} files=${fileCount} cap=${golden.maxSyncEmbeds}) < ${golden.hubEmbMinCoverage}`
24046
- });
24047
- }
24048
- }
24049
- if (!findings.length) {
24050
- findings.push({ ok: true, code: "status-ok", detail: `status healthy (${repos.length || count || 0} repo projection(s))` });
24051
- }
24052
- return findings;
24206
+ function evaluateCloudStatus(status) {
24207
+ const authority = status.v4Authority && typeof status.v4Authority === "object" ? status.v4Authority : void 0;
24208
+ const rosterCount = authority?.rosterCount;
24209
+ const activeAuthorities = authority?.activeAuthorities;
24210
+ const readyAuthorities = authority?.readyAuthorities;
24211
+ const ready = typeof rosterCount === "number" && rosterCount > 0 && activeAuthorities === rosterCount && readyAuthorities === rosterCount && authority?.activeNOfN === true && authority.nOfN === true;
24212
+ if (ready) {
24213
+ return [{ ok: true, code: "v4-authority-ready", detail: `v4 authorities ready ${readyAuthorities}/${rosterCount}` }];
24214
+ }
24215
+ const failures = Array.isArray(authority?.authorityFailures) ? JSON.stringify(authority.authorityFailures) : "unavailable";
24216
+ return [{
24217
+ ok: false,
24218
+ code: "v4-authority-not-ready",
24219
+ detail: `v4 authorities not ready: active=${String(activeAuthorities ?? "unknown")}/${String(rosterCount ?? "unknown")} ready=${String(readyAuthorities ?? "unknown")}/${String(rosterCount ?? "unknown")} failures=${failures}`
24220
+ }];
24053
24221
  }
24054
24222
  async function runRepoIndexHealth(opts) {
24055
24223
  const findings = [];
24056
- const searchAttempts = Math.max(1, opts.searchRetry?.attempts ?? 3);
24057
- const sleep2 = opts.searchRetry?.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
24058
- if (opts.live) {
24059
- if (!opts.queriesOnly) {
24060
- const st = await opts.live.status();
24061
- if (st.ok === false && typeof st.error === "string") {
24062
- findings.push({ ok: true, code: "status-error", detail: st.error, severity: "warn" });
24063
- } else {
24064
- findings.push(...evaluateCloudStatus(st, opts.golden));
24065
- }
24066
- }
24067
- for (const q of opts.golden.queries) {
24068
- let res = await opts.live.search(q.q, q.mode);
24069
- let attempts = 1;
24070
- while (!res.ok && attempts < searchAttempts) {
24071
- await sleep2(500 * attempts);
24072
- res = await opts.live.search(q.q, q.mode);
24073
- attempts += 1;
24074
- }
24075
- if (!res.ok) {
24076
- const semantic503 = q.mode === "semantic" && (res.status === 503 || /semantic unavailable|503/i.test(res.error));
24077
- findings.push({
24078
- ok: false,
24079
- code: semantic503 ? "semantic-503" : "search-error",
24080
- detail: `${q.id}: ${res.error}${attempts > 1 ? ` (no answer after ${attempts} attempts)` : ""}`
24081
- });
24082
- continue;
24083
- }
24084
- findings.push(evaluateQueryHits(q, res.hits));
24085
- }
24086
- } else if (opts.fixtures) {
24224
+ if (opts.fixtures) {
24087
24225
  for (const q of opts.golden.queries) {
24088
24226
  const fx = opts.fixtures[q.id];
24089
24227
  if (!fx) {
@@ -24637,7 +24775,7 @@ async function syncProjectInfo(plan, client, apply) {
24637
24775
  }
24638
24776
 
24639
24777
  // src/project-set.ts
24640
- var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "dsManifestPath", "fofuEnabled", "consumesDesignSystem", "ci", "requiredChecks", "ciExemptReason", "gate", "seedCanary", "repoIndexExcludedRepos"];
24778
+ var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "dsManifestPath", "fofuEnabled", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary", "repoIndexExcludedRepos"];
24641
24779
  var UNSET_KEY_SET = new Set(UNSET_KEYS);
24642
24780
  var RUNTIME_SECRET_STAGES = ["dev", "rc", "main"];
24643
24781
  var SECRET_CONSUMERS = ["runtime", "build", "lambda", "actions", "agent", "box"];
@@ -24932,6 +25070,21 @@ function parseRequiredChecksVar(raw) {
24932
25070
  }
24933
25071
  return parsed.map((c) => c.trim());
24934
25072
  }
25073
+ function parseRequiredCheckBranchesVar(raw) {
25074
+ let parsed;
25075
+ try {
25076
+ parsed = JSON.parse(raw);
25077
+ } catch {
25078
+ throw new Error('org project set: requiredCheckBranches must be a JSON array, e.g. ["development"]');
25079
+ }
25080
+ if (!Array.isArray(parsed) || parsed.length === 0 || parsed.some((b) => typeof b !== "string" || !isRequiredCheckBranch(b))) {
25081
+ throw new Error(`org project set: requiredCheckBranches must be a non-empty JSON array of: ${REQUIRED_CHECK_BRANCHES.join(", ")}`);
25082
+ }
25083
+ if (new Set(parsed).size !== parsed.length) {
25084
+ throw new Error("org project set: requiredCheckBranches must not contain duplicates");
25085
+ }
25086
+ return REQUIRED_CHECK_BRANCHES.filter((branch) => parsed.includes(branch));
25087
+ }
24935
25088
  function parseCiExemptReasonVar(raw) {
24936
25089
  const trimmed = raw.trim();
24937
25090
  if (!trimmed) throw new Error("org project set: ciExemptReason must be a non-empty reason (or use --unset ciExemptReason to clear it)");
@@ -25028,6 +25181,7 @@ var SETTABLE_VAR_KEYS = [
25028
25181
  "portRange",
25029
25182
  "ci",
25030
25183
  "requiredChecks",
25184
+ "requiredCheckBranches",
25031
25185
  "ciExemptReason",
25032
25186
  "gate",
25033
25187
  "secrets",
@@ -25057,6 +25211,7 @@ var SETTABLE_VAR_HINTS = {
25057
25211
  portRange: "JSON {start,end} or [start,end]",
25058
25212
  ci: "none \u2014 declare intentional no-ci",
25059
25213
  requiredChecks: 'JSON array, e.g. ["gate"] or [] for no-ci',
25214
+ requiredCheckBranches: "non-empty JSON branch-name array; exact product-ruleset scope (#5210)",
25060
25215
  ciExemptReason: "non-empty string \u2014 declares this deployable repo has no CI surface (#4265)",
25061
25216
  gate: "JSON {runtime,cmd,workdir,cacheDepPath,pyVersion}"
25062
25217
  };
@@ -25157,6 +25312,8 @@ function buildProjectSetPatch(input) {
25157
25312
  patch[key] = raw;
25158
25313
  } else if (key === "requiredChecks") {
25159
25314
  patch[key] = parseRequiredChecksVar(raw);
25315
+ } else if (key === "requiredCheckBranches") {
25316
+ patch[key] = parseRequiredCheckBranchesVar(raw);
25160
25317
  } else if (key === "ciExemptReason") {
25161
25318
  patch[key] = parseCiExemptReasonVar(raw);
25162
25319
  } else if (key === "gate") {
@@ -27996,12 +28153,14 @@ function registerBootstrapCommands(program3) {
27996
28153
  const eq = value.indexOf("=");
27997
28154
  if (eq > 0) rawVars[value.slice(0, eq)] = value.slice(eq + 1);
27998
28155
  }
28156
+ let registryRequiredCheckBranches;
27999
28157
  try {
28000
28158
  const meta = await fetchProjectBySlug(slug, registryClientDeps(await loadConfig()));
28159
+ registryRequiredCheckBranches = meta?.requiredCheckBranches;
28001
28160
  for (const [k, v] of Object.entries(gateConfigToVars(meta?.gate))) if (rawVars[k] == null) rawVars[k] = v;
28002
28161
  } catch {
28003
28162
  }
28004
- const vars = withDerivedRepoVars(rawVars, parsedRepo, o.class, bootstrapReleaseTrack);
28163
+ const vars = withDerivedRepoVars(rawVars, parsedRepo, o.class, bootstrapReleaseTrack, registryRequiredCheckBranches);
28005
28164
  if (!vars.PROJECT_ID) {
28006
28165
  try {
28007
28166
  const r = await gh(linkedProjectsQueryArgs(parsedRepo.owner, parsedRepo.name));
@@ -28362,7 +28521,16 @@ LIVE apply to ${repo}:
28362
28521
  content = null;
28363
28522
  }
28364
28523
  let desired;
28365
- if (seed.managedBlock) {
28524
+ if (seed.source.startsWith("seed:")) {
28525
+ const project2 = projects.find((p) => (p.repos ?? []).some((repo) => repo.toLowerCase() === r.repo.toLowerCase()));
28526
+ const track = resolveReleaseTrack(project2, void 0, r.repo);
28527
+ const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass, track, project2?.requiredCheckBranches);
28528
+ const resolved = resolveSeedWriteContent(seed, vars, readSeedFile, content);
28529
+ if (!resolved.ok || resolved.content == null) {
28530
+ return fail(`bootstrap propagate: ${r.repo} ${seed.target}: ${resolved.ok ? "rendered no content" : resolved.reason} \u2014 refusing an incomplete per-repo render`);
28531
+ }
28532
+ desired = resolved.content;
28533
+ } else if (seed.managedBlock) {
28366
28534
  const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass);
28367
28535
  const resolved = resolveSeedWriteContent(seed, vars, readSeedFile, content);
28368
28536
  if (!resolved.ok) {
@@ -33168,12 +33336,12 @@ var surfaces_default = {
33168
33336
  reload: "restart"
33169
33337
  },
33170
33338
  skills: {
33171
- delivery: "plugin",
33339
+ delivery: "provisioned",
33172
33340
  sourceSurfaceId: "mmi-skills",
33173
33341
  artifactId: "mmi-hermes-plugin",
33174
33342
  invocation: {
33175
33343
  entry: "/mmi",
33176
- any: "mmi:<skill>"
33344
+ any: "skill_view(name='<skill>')"
33177
33345
  }
33178
33346
  },
33179
33347
  hooks: {
@@ -33217,7 +33385,7 @@ var surfaces_default = {
33217
33385
  enforcementCeilings: [
33218
33386
  "Hermes pre_tool_call blocks only mapped shell and edit/write/patch tool families; disabled plugins enforce nothing.",
33219
33387
  "Hermes user plugins are opt-in through plugins.enabled; operator consent and host process isolation remain Hermes-owned.",
33220
- "npm is MMI transport only: MMI-Hub extracts @mutmutco/hermes-plugin into $HERMES_HOME/plugins/mmi, and a fresh Hermes Agent process is required for discovery.",
33388
+ "npm is MMI transport only: MMI-Hub extracts @mutmutco/hermes-plugin into $HERMES_HOME/plugins/mmi and provisions the canonical skills into $HERMES_HOME/skills/mmi/; a fresh Hermes Agent process is required for discovery.",
33221
33389
  "Final assistant output is outside Hermes pre_tool_call hook control."
33222
33390
  ]
33223
33391
  }
@@ -34329,29 +34497,35 @@ function checkDocsIndex(probe) {
34329
34497
  }
34330
34498
  function checkRepoIndexCloud(probe) {
34331
34499
  if (!probe) return null;
34500
+ const percent = (value) => value == null ? "n/a" : `${Math.round(value * 100)}%`;
34501
+ const readiness = probe.v4Readiness;
34502
+ const readinessEvidence = readiness ? [
34503
+ `v4 readiness: ${readiness.verdict} \u2014 ${readiness.evidenceCount}/${readiness.goldenCount} checked-in goldens`,
34504
+ ...Object.entries(readiness.modes).sort(([a], [b]) => a.localeCompare(b)).flatMap(([mode, result]) => {
34505
+ const errors = Object.entries(result.errors).map(([kind, count]) => `${kind}:${count}`).join(",") || "none";
34506
+ return [
34507
+ `v4 ${mode}: ${result.verdict} \u2014 evidence=${result.evidenceCount}/${result.goldenCount} relevance=${percent(result.expectedTop10CitationRelevance)} citations=${percent(result.citationValidity)} embeddings=${percent(result.embeddingCoverage)} p95=${result.p95V4Ms ?? "n/a"}ms errorRate=${percent(result.errorRate)} errors=${errors}`,
34508
+ `v4 ${mode} ceilings relevance>=${percent(result.ceilings.minimumExpectedTop10CitationRelevance)} citations>=${percent(result.ceilings.minimumCitationValidity)} embeddings>=${percent(result.ceilings.minimumEmbeddingCoverage)} p95<=${result.ceilings.maximumP95Ms}ms errorRate<=${percent(result.ceilings.maximumErrorRate)}`
34509
+ ];
34510
+ }),
34511
+ ...readiness.reasons?.length ? [`v4 readiness reasons: ${readiness.reasons.join(", ")}`] : []
34512
+ ] : ["v4 readiness: unavailable"];
34332
34513
  const evidence = [
34333
34514
  probe.repo ? `repo: ${probe.repo}` : "repo: (estate)",
34334
- probe.builtAt ? `builtAt: ${probe.builtAt}` : "builtAt: n/a",
34335
- typeof probe.fileCount === "number" ? `fileCount: ${probe.fileCount}` : "fileCount: n/a",
34336
- typeof probe.embCount === "number" ? `embCount: ${probe.embCount}` : "embCount: n/a",
34337
- typeof probe.localPresent === "boolean" ? `local v3 cache: ${probe.localPresent ? "present" : "absent"}` : "local v3 cache: n/a",
34338
34515
  `local v4: ${probe.localV4State ?? "unknown"}${probe.localV4Commit ? ` @ ${probe.localV4Commit.slice(0, 12)}` : ""}${typeof probe.localV4EmbeddingCoverage === "number" ? ` (${Math.round(probe.localV4EmbeddingCoverage * 100)}% embeddings)` : ""}`,
34339
34516
  `cloud v4: ${probe.cloudV4State ?? "unknown"}${probe.cloudV4Commit ? ` @ ${probe.cloudV4Commit.slice(0, 12)}` : ""}`,
34340
- `v4 cohort: ${probe.v4ReadPercent ?? 0}%`,
34341
- `v4 shadows: ${probe.v4ShadowComparisons ?? 0}/${probe.v4ShadowMinimum ?? 50} \u2014 ${probe.v4ShadowCutoverReady ? "cutover-ready" : "not ready"}`,
34342
- ...Object.entries(probe.v4ShadowErrors ?? {}).map(([kind, count]) => `v4 shadow error ${kind}: ${count}`)
34517
+ ...readinessEvidence
34343
34518
  ];
34344
34519
  switch (probe.kind) {
34345
34520
  case "healthy": {
34346
- const v4Unready = probe.cloudV4State === "invalid" || probe.cloudV4State === "tombstoned" || probe.cloudV4State === "degraded" && (probe.localV4EmbeddingCoverage ?? 1) < 0.95;
34347
- const detail = probe.detail ?? `cloud v3 ok${typeof probe.fileCount === "number" ? ` \u2014 ${probe.fileCount} files` : ""}; v4 ${probe.cloudV4State ?? "absent"}, cohort ${probe.v4ReadPercent ?? 0}%`;
34521
+ const v4Unready = probe.cloudV4State === "invalid" || probe.cloudV4State === "tombstoned" || probe.cloudV4State === "degraded" && (probe.localV4EmbeddingCoverage ?? 1) < 0.95 || !readiness || readiness.verdict === "not-ready";
34522
+ const detail = probe.detail ?? `cloud v4 ${probe.cloudV4State ?? "absent"}, readiness ${readiness?.verdict ?? "unknown"}`;
34348
34523
  return {
34349
- ok: !v4Unready || (probe.v4ReadPercent ?? 0) === 0,
34350
- ...v4Unready && (probe.v4ReadPercent ?? 0) === 0 ? { warn: true, verified: false } : {},
34524
+ ok: !v4Unready,
34351
34525
  id: "repo-index",
34352
34526
  label: "repo-index",
34353
34527
  detail,
34354
- ...v4Unready ? { fix: "run `mmi-hub update`, then dispatch `mmi-cli harbour org schedules run MMI-Hub/repo-index-reconcile`; keep the v4 cohort at 0 until shadows pass" } : {},
34528
+ ...v4Unready ? { fix: "run `mmi-hub update`, then dispatch `mmi-cli harbour org schedules run MMI-Hub/repo-index-reconcile`; require v4 authority and readiness to become ready" } : {},
34355
34529
  verbose: evidence
34356
34530
  };
34357
34531
  }
@@ -34374,24 +34548,15 @@ function checkRepoIndexCloud(probe) {
34374
34548
  reportOnly: true,
34375
34549
  verbose: evidence
34376
34550
  };
34377
- case "missing-projection":
34551
+ case "missing-authority":
34378
34552
  return {
34379
34553
  ok: false,
34380
34554
  id: "repo-index",
34381
34555
  label: "repo-index",
34382
- detail: probe.detail ?? "no cloud projection for this repo",
34556
+ detail: probe.detail ?? "no active v4 authority for this repo",
34383
34557
  fix: "dispatch `mmi-cli harbour org schedules run MMI-Hub/repo-index-reconcile` or run the authenticated per-repo sync",
34384
34558
  verbose: evidence
34385
34559
  };
34386
- case "stale":
34387
- return {
34388
- ok: false,
34389
- id: "repo-index",
34390
- label: "repo-index",
34391
- detail: probe.detail ?? "cloud projection looks stale vs recent pushes",
34392
- fix: "dispatch `mmi-cli harbour org schedules run MMI-Hub/repo-index-reconcile` or wait for the registered 6h schedule",
34393
- verbose: evidence
34394
- };
34395
34560
  case "auth":
34396
34561
  return {
34397
34562
  ok: false,
@@ -34425,7 +34590,7 @@ function checkRepoIndexCloud(probe) {
34425
34590
  ok: true,
34426
34591
  id: "repo-index",
34427
34592
  label: "repo-index",
34428
- detail: probe.detail ?? "local cache absent (cloud search does not need it)",
34593
+ detail: probe.detail ?? "local v4 authority absent (cloud search does not need it)",
34429
34594
  warn: true,
34430
34595
  verbose: evidence
34431
34596
  };
@@ -35639,7 +35804,6 @@ function mmiDoctorDeps(opts = {}) {
35639
35804
  },
35640
35805
  // #4156: short-timeout Hub status for the current repo. Fail-soft — never throws to doctor.
35641
35806
  repoIndexCloudState: async (root) => {
35642
- const local = repoIndexStatus(root);
35643
35807
  let localV4 = { state: "absent" };
35644
35808
  try {
35645
35809
  const parsed = JSON.parse((0, import_node_fs42.readFileSync)(repoIndexV4StorePath(root), "utf8"));
@@ -35654,7 +35818,7 @@ function mmiDoctorDeps(opts = {}) {
35654
35818
  } else localV4 = { state: "invalid" };
35655
35819
  } catch {
35656
35820
  }
35657
- const localFields = { localPresent: local.present, localV4State: localV4.state, localV4Commit: localV4.commit, localV4EmbeddingCoverage: localV4.embeddingCoverage };
35821
+ const localFields = { localV4State: localV4.state, localV4Commit: localV4.commit, localV4EmbeddingCoverage: localV4.embeddingCoverage };
35658
35822
  try {
35659
35823
  const cfg = await loadConfig();
35660
35824
  const repo = inferRepoSlug(root);
@@ -35669,47 +35833,22 @@ function mmiDoctorDeps(opts = {}) {
35669
35833
  return { kind: "network", repo, detail: st.error, ...localFields };
35670
35834
  }
35671
35835
  const present = st.present;
35672
- const builtAt = typeof st.builtAt === "string" ? st.builtAt : void 0;
35673
- const fileCount = typeof st.fileCount === "number" ? st.fileCount : void 0;
35674
- const embCount = typeof st.embCount === "number" ? st.embCount : void 0;
35675
35836
  const v4 = st.v4;
35676
- const migration = st.v4Migration;
35677
- const shadow = st.v4Shadow;
35678
- const shadowThresholds = shadow?.thresholds;
35837
+ const readinessRaw = st.v4Readiness;
35838
+ const v4Readiness = readinessRaw && (readinessRaw.verdict === "ready" || readinessRaw.verdict === "not-ready") && typeof readinessRaw.goldenCount === "number" && typeof readinessRaw.evidenceCount === "number" && readinessRaw.modes && typeof readinessRaw.modes === "object" ? readinessRaw : void 0;
35679
35839
  const cloudV4State = typeof v4?.state === "string" && ["ready", "degraded", "invalid", "tombstoned"].includes(v4.state) ? v4.state : "absent";
35680
35840
  const v4Fields = {
35681
35841
  ...localFields,
35682
35842
  cloudV4State,
35683
35843
  ...typeof v4?.commit === "string" ? { cloudV4Commit: v4.commit } : {},
35684
- v4ReadPercent: typeof migration?.readPercent === "number" ? migration.readPercent : 0,
35685
- v4ShadowComparisons: typeof shadow?.comparisonCount === "number" ? shadow.comparisonCount : 0,
35686
- v4ShadowMinimum: typeof shadowThresholds?.minimumComparisons === "number" ? shadowThresholds.minimumComparisons : 50,
35687
- v4ShadowCutoverReady: shadow?.cutoverReady === true,
35688
- v4ShadowErrors: shadow?.errors && typeof shadow.errors === "object" ? shadow.errors : {}
35844
+ ...v4Readiness ? { v4Readiness } : {}
35689
35845
  };
35690
35846
  if (present === false) {
35691
- return { kind: "missing-projection", repo, ...v4Fields, fileCount: 0, embCount: 0 };
35692
- }
35693
- if (builtAt) {
35694
- const ageMs = Date.now() - Date.parse(builtAt);
35695
- if (Number.isFinite(ageMs) && ageMs > 48 * 60 * 60 * 1e3) {
35696
- return {
35697
- kind: "stale",
35698
- repo,
35699
- builtAt,
35700
- fileCount,
35701
- embCount,
35702
- ...v4Fields,
35703
- detail: `projection builtAt ${builtAt} (>48h old)`
35704
- };
35705
- }
35847
+ return { kind: "missing-authority", repo, ...v4Fields };
35706
35848
  }
35707
35849
  return {
35708
35850
  kind: "healthy",
35709
35851
  repo: typeof st.repo === "string" ? st.repo : repo,
35710
- builtAt,
35711
- fileCount,
35712
- embCount,
35713
35852
  ...v4Fields
35714
35853
  };
35715
35854
  } catch (e) {
@@ -36141,30 +36280,20 @@ repoIndex.command("rebuild").description("walk the checkout (git ls-files + giti
36141
36280
  await failGraceful(e.message);
36142
36281
  }
36143
36282
  });
36144
- repoIndex.command("publish").description("publish this checkout's projection to Hub cloud (rebuilds locally first; estate indexer also runs on a schedule)").option("--embed", "request Titan file embeddings on the Hub (capped per publish)").option("--json", "machine-readable result").action(async (o) => {
36283
+ repoIndex.command("publish").description("build and publish this checkout's immutable v4 authority to Hub cloud").option("--json", "machine-readable result").action(async (o) => {
36145
36284
  try {
36146
36285
  const root = await repoRoot();
36147
36286
  const repo = inferRepoSlug(root);
36148
- const built = rebuildRepoIndex(root, repo);
36149
36287
  const cfg = await loadConfig();
36150
36288
  const deps = registryClientDeps(cfg);
36151
- const res = await publishRepoIndexCloud(
36152
- { repo, builtAt: built.builtAt, entries: built.entries, embed: o.embed === true },
36153
- deps
36154
- );
36155
- if (!res.ok) return await failGraceful(res.error);
36156
36289
  const v4 = await buildRepoIndexV4(root, repo, { modelDirectory: process.env.MMI_REPO_INDEXER_MODEL_DIR });
36157
36290
  const v4Result = await publishRepoIndexV4Cloud(v4, deps);
36158
- if (!v4Result.ok) return await failGraceful(`v3 published; v4 failed: ${v4Result.error}`);
36291
+ if (!v4Result.ok) return await failGraceful(v4Result.error);
36159
36292
  if (o.json) {
36160
- consoleIo.log(JSON.stringify(res.body, null, 2));
36293
+ consoleIo.log(JSON.stringify(v4Result, null, 2));
36161
36294
  return;
36162
36295
  }
36163
- const embCount = Number(res.body.embCount ?? 0);
36164
- const embRequested = Number(res.body.embRequested ?? 0);
36165
- const embCap = Number(res.body.embCap ?? 0);
36166
- const capNote = o.embed && embRequested > embCap && embCap > 0 ? ` (emb=${embCount} of ${embRequested} requested \u2014 capped at ${embCap}/publish, run \`--embed\` again to continue)` : "";
36167
- console.log(`repo-index: published ${res.body.repo} \u2014 ${res.body.fileCount} files, emb=${embCount}${capNote}`);
36296
+ console.log(`repo-index: published v4 ${repo} \u2014 ${v4.manifest.chunks.length} chunks, ${v4.manifest.embeddings.length} embeddings`);
36168
36297
  } catch (e) {
36169
36298
  return await failGraceful(e.message);
36170
36299
  }
@@ -36214,7 +36343,7 @@ async function runRepoIndexSearchCommand(query, o, defaultMode) {
36214
36343
  }
36215
36344
  var REPO_INDEX_SEARCH_WHEN = 'beats grep for cross-repo questions, "where does X happen", and unknown filenames; grep wins inside a known checkout';
36216
36345
  function repoIndexSearchOptions(cmd) {
36217
- return cmd.option("--limit <n>", "max hits", "20").option("--json", "machine-readable hits").option("--local", "search only the local projection (rebuild if missing)").option("--cloud", "force Hub cloud search (default when not --local)").option("--semantic", "semantic mode (Titan cosine over path+symbols embeddings)").option("--lexical", "lexical-only mode (paths/symbols)").option("--repo <owner/name>", "limit cloud search to one repo");
36346
+ return cmd.option("--limit <n>", "max hits", "20").option("--json", "machine-readable hits").option("--local", "search only the local projection (rebuild if missing)").option("--cloud", "force Hub cloud search (default when not --local)").option("--semantic", "v4 semantic mode (pinned BGE over structural chunks)").option("--lexical", "lexical-only mode (paths/symbols)").option("--repo <owner/name>", "limit cloud search to one repo");
36218
36347
  }
36219
36348
  repoIndexSearchOptions(repoIndex.command("search").description(`search Hub cloud by default (lexical/hybrid/semantic); use --local for checkout-only \u2014 ${REPO_INDEX_SEARCH_WHEN}`).argument("<query>", "path fragment, symbol, or meaning phrase")).action(async (query, o) => runRepoIndexSearchCommand(query, o, "hybrid"));
36220
36349
  repoIndexSearchOptions(program2.command("find").description(`estate code search across every registered repo (repo-index search, semantic by default) \u2014 ${REPO_INDEX_SEARCH_WHEN}`).argument("<query>", "path fragment, symbol, or meaning phrase")).action(async (query, o) => runRepoIndexSearchCommand(query, o, "semantic"));
@@ -36234,24 +36363,7 @@ repoIndex.command("status").description("show local and/or cloud repo-index stat
36234
36363
  consoleIo.log(JSON.stringify(st2, null, 2));
36235
36364
  return;
36236
36365
  }
36237
- if (st2.present === true && typeof st2.repo === "string") {
36238
- const fileCount = Number(st2.fileCount ?? 0);
36239
- const embCount = Number(st2.embCount ?? 0);
36240
- const embGap = Number(st2.embGap ?? Math.max(0, fileCount - embCount));
36241
- const stale = st2.staleBuiltAt === true ? " stale-builtAt" : "";
36242
- console.log(
36243
- `repo-index: cloud ${st2.repo} built ${st2.builtAt} \u2014 ${fileCount} files, emb=${embCount}/${fileCount} gap=${embGap}${stale}`
36244
- );
36245
- return;
36246
- }
36247
- consoleIo.log(JSON.stringify(st2, null, 2));
36248
- const count = typeof st2.count === "number" ? st2.count : void 0;
36249
- const present = st2.present;
36250
- if (count === 0 || present === false) {
36251
- console.error(
36252
- "repo-index: no cloud projection yet \u2014 after Hub deploy, run harbour `repo-index-reconcile` or `mmi-cli oracle repo-index sync-estate` (see docs/Guides/repo-index-runbook.md)."
36253
- );
36254
- }
36366
+ console.log(formatRepoIndexCloudStatus(st2));
36255
36367
  return;
36256
36368
  }
36257
36369
  const root = await repoRoot();
@@ -36286,49 +36398,30 @@ repoIndex.command("graph").description("optional bounded cross-repo relationship
36286
36398
  return await failGraceful(error.message);
36287
36399
  }
36288
36400
  });
36289
- repoIndex.command("cutover").description("set the schema-v4 read cohort; --percent 0 is the one-action rollback to v3").requiredOption("--percent <n>", "integer cohort percentage from 0 through 100").option("--json", "machine-readable result").action(async (o) => {
36401
+ repoIndex.command("health").description("post-deploy health gate: checked-in v4 readiness plus active-authority status").option("--live", "hit Hub cloud (v4 readiness and authority status); omit for offline golden-shape check").option("--golden <path>", "offline-only golden queries JSON").option("--json", "machine-readable findings").option("--queries-only", "release-gate mode: execute checked-in v4 goldens and consume the explicit readiness verdict; skip authority status").action(async (o) => {
36290
36402
  try {
36291
- const percent = Number(o.percent);
36292
- if (!Number.isInteger(percent) || percent < 0 || percent > 100) return await failGraceful("repo-index cutover --percent must be an integer from 0 through 100");
36293
- const cfg = await loadConfig();
36294
- const result = await setRepoIndexV4ReadPercentCloud(percent, registryClientDeps(cfg));
36295
- if (!result.ok) return await failGraceful(result.error);
36296
- if (o.json) consoleIo.log(JSON.stringify(result, null, 2));
36297
- else console.log(`repo-index: v4 read cohort ${result.percent}% (${result.cohort}); rollback: mmi-cli oracle repo-index cutover --percent 0`);
36298
- } catch (error) {
36299
- return await failGraceful(error.message);
36300
- }
36301
- });
36302
- repoIndex.command("health").description("post-deploy health gate: status + golden lexical/semantic queries (Hub#4149)").option("--live", "hit Hub cloud (status + searches); omit for offline golden-shape check").option("--golden <path>", "golden queries JSON (default: the suite bundled with the CLI, so the gate runs from any repo)").option("--json", "machine-readable findings").option("--queries-only", "deploy-gate mode: evaluate golden queries only, no estate/status reporting (Hub#4753)").action(async (o) => {
36303
- try {
36304
- const goldenPath = o.golden || "bundled repo-index-golden-queries.json";
36305
- const golden = o.golden ? loadGoldenSuite(o.golden) : defaultGoldenSuite();
36306
- const result = o.live ? await runRepoIndexHealth({
36307
- golden,
36308
- queriesOnly: o.queriesOnly,
36309
- live: {
36310
- status: async () => {
36311
- const cfg = await loadConfig();
36312
- const st = await statusRepoIndexCloud(void 0, registryClientDeps(cfg));
36313
- if ("error" in st && st.ok === false) return { ok: false, error: st.error };
36314
- return st;
36403
+ const goldenPath = repoIndexHealthGoldenSource({ live: o.live, queriesOnly: o.queriesOnly, customPath: o.golden });
36404
+ const golden = !o.live && o.golden ? loadGoldenSuite(o.golden) : defaultGoldenSuite();
36405
+ const result = o.live ? await (async () => {
36406
+ const cfg = await loadConfig();
36407
+ const deps = registryClientDeps(cfg);
36408
+ return runRepoIndexLiveHealth({
36409
+ queriesOnly: o.queriesOnly,
36410
+ v4Readiness: {
36411
+ suite: defaultV4ReadinessSuite(),
36412
+ probe: (queries) => probeRepoIndexV4ReadinessCloud(queries, deps)
36315
36413
  },
36316
- search: async (q, mode) => {
36317
- const cfg = await loadConfig();
36318
- const res = await searchRepoIndexCloud(q, { mode, limit: 10 }, registryClientDeps(cfg));
36319
- if (!res.ok) {
36320
- return {
36321
- ok: false,
36322
- error: res.error,
36323
- status: res.status
36324
- };
36414
+ live: {
36415
+ status: async () => {
36416
+ const st = await statusRepoIndexCloud(void 0, deps);
36417
+ if ("error" in st && st.ok === false) return { ok: false, error: st.error };
36418
+ return st;
36325
36419
  }
36326
- return { ok: true, hits: res.hits };
36327
36420
  }
36328
- }
36329
- }) : await runRepoIndexHealth({ golden });
36421
+ });
36422
+ })() : await runRepoIndexHealth({ golden });
36330
36423
  if (o.json) {
36331
- consoleIo.log(JSON.stringify({ ok: result.ok, findings: result.findings, golden: goldenPath }, null, 2));
36424
+ consoleIo.log(JSON.stringify({ ok: result.ok, findings: result.findings, golden: goldenPath, ..."readiness" in result && result.readiness ? { v4Readiness: result.readiness } : {} }, null, 2));
36332
36425
  } else {
36333
36426
  for (const f of result.findings) {
36334
36427
  const label = f.severity === "warn" ? "WARN" : f.ok ? "ok" : "FAIL";
@@ -36341,7 +36434,7 @@ repoIndex.command("health").description("post-deploy health gate: status + golde
36341
36434
  return await failGraceful(e.message);
36342
36435
  }
36343
36436
  });
36344
- repoIndex.command("gc").description("remove cloud projections for repos no longer on the registry roster").option("--cloud", "required \u2014 GC only applies to Hub cloud").option("--json", "machine-readable result").action(async (o) => {
36437
+ repoIndex.command("gc").description("remove v4 cloud authority material for repos no longer on the registry roster").option("--cloud", "required \u2014 GC only applies to Hub cloud").option("--json", "machine-readable result").action(async (o) => {
36345
36438
  try {
36346
36439
  if (!o.cloud) return await failGraceful("repo-index gc requires --cloud");
36347
36440
  const cfg = await loadConfig();
@@ -36351,12 +36444,12 @@ repoIndex.command("gc").description("remove cloud projections for repos no longe
36351
36444
  consoleIo.log(JSON.stringify(res, null, 2));
36352
36445
  return;
36353
36446
  }
36354
- console.log(`repo-index: gc removed ${res.removed.length} orphan projection(s)`);
36447
+ console.log(`repo-index: gc removed ${res.removed.length} orphan v4 authority record(s)`);
36355
36448
  } catch (e) {
36356
36449
  return await failGraceful(e.message);
36357
36450
  }
36358
36451
  });
36359
- repoIndex.command("sync-estate").description("Hub indexer: shallow-clone registry repos, rebuild, publish to cloud (CI / operator)").option("--repo <owner/name>", "limit to one registered repo").option("--embed", "request Titan embeddings on publish").option("--json", "machine-readable result").action(async (o) => {
36452
+ repoIndex.command("sync-estate").description("Hub indexer: shallow-clone registry repos, build and publish v4 authorities (CI / operator)").option("--repo <owner/name>", "limit to one registered repo").option("--json", "machine-readable result").action(async (o) => {
36360
36453
  try {
36361
36454
  const cfg = await loadConfig();
36362
36455
  const gh = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "";
@@ -36364,7 +36457,6 @@ repoIndex.command("sync-estate").description("Hub indexer: shallow-clone registr
36364
36457
  const res = await syncEstateRepoIndex({
36365
36458
  deps: registryClientDeps(cfg),
36366
36459
  repo: o.repo,
36367
- embed: o.embed === true,
36368
36460
  githubToken: gh
36369
36461
  });
36370
36462
  if (o.json) {
@@ -36374,8 +36466,7 @@ repoIndex.command("sync-estate").description("Hub indexer: shallow-clone registr
36374
36466
  }
36375
36467
  for (const p of res.published) {
36376
36468
  const gap = p.embGap ?? Math.max(0, p.fileCount - (p.embCount ?? 0));
36377
- const trunc = p.embTruncated ? " truncated" : "";
36378
- console.log(`repo-index: published ${p.repo} \u2014 ${p.fileCount} files emb=${p.embCount ?? 0}/${p.fileCount} gap=${gap}${trunc} graph=${p.graphEdges ?? "unavailable"}`);
36469
+ console.log(`repo-index: published v4 ${p.repo} \u2014 ${p.fileCount} chunks emb=${p.embCount ?? 0}/${p.fileCount} gap=${gap} graph=${p.graphEdges ?? "unavailable"}`);
36379
36470
  }
36380
36471
  for (const warning of res.skipped) console.error(`repo-index: WARN ${warning}`);
36381
36472
  for (const f of res.failed) {
@@ -36687,11 +36778,13 @@ project.command("get [owner/repo]").description("a project's META (board ids + p
36687
36778
  const m = read.project;
36688
36779
  const track = resolveReleaseTrack(m, void 0, target);
36689
36780
  const stages = branchesForTrack(track).join(" -> ");
36781
+ const requiredCheckBranches = resolveRequiredCheckBranches(m, target).join(", ");
36690
36782
  const note = track === "direct" ? " (direct \u2014 no rc; /rcand refuses, /release ships development -> main)" : track === "trunk" ? " (trunk \u2014 main only)" : "";
36691
36783
  const deployNote = m.deployModel === "registry-publish" ? "\ndeploys are repository-owned publish workflows (registry-publish); no central tenant deploy runs for this repo." : "\ndeploys run centrally (tenant-deploy.yml); product repos carry no deploy files.";
36692
36784
  console.error(
36693
36785
  `${m.name ?? target} \u2014 class ${m.class ?? "?"} \u2014 deploy ${m.deployModel ?? "?"}
36694
- release track: ${track} \u2014 stages: ${stages}${note}` + deployNote + " Inspect nonsecret DEPLOY facts with `mmi-cli oracle org project deploy get`; full coords remain OIDC-gated."
36786
+ release track: ${track} \u2014 stages: ${stages}${note}
36787
+ required-check branches: ${requiredCheckBranches}${m.requiredCheckBranches ? " (explicit)" : " (release-track fallback)"}` + deployNote + " Inspect nonsecret DEPLOY facts with `mmi-cli oracle org project deploy get`; full coords remain OIDC-gated."
36695
36788
  );
36696
36789
  }
36697
36790
  });