@mutmutco/cli 3.139.10 → 3.139.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +436 -343
- 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
|
-
|
|
5853
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
10750
|
-
tag: "v3.139.
|
|
10751
|
-
commit: "
|
|
10752
|
-
npm: "@mutmutco/cli@3.139.
|
|
10777
|
+
version: "3.139.12",
|
|
10778
|
+
tag: "v3.139.12",
|
|
10779
|
+
commit: "2e7c01a927e2",
|
|
10780
|
+
npm: "@mutmutco/cli@3.139.12"
|
|
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.
|
|
10797
|
+
v3Target: "v3.139.12"
|
|
10770
10798
|
}
|
|
10771
10799
|
],
|
|
10772
|
-
rollbackTrigger: "Any red inside the post-
|
|
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.
|
|
10776
|
-
v3Target: "v3.139.
|
|
10803
|
+
mechanism: "npm dist-tag latest -> 3.139.12 and redeploy the Hub Lambda from tag v3.139.12 (2e7c01a927e2); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
10804
|
+
v3Target: "v3.139.12 (@mutmutco/cli@3.139.12, tag commit 2e7c01a927e2 \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-
|
|
10823
|
+
repo: "mutmutco/jerv-hub",
|
|
10796
10824
|
role: "member",
|
|
10797
10825
|
schedule: "train",
|
|
10798
|
-
v3Target: "
|
|
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
|
|
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
|
|
15542
|
+
const requiredBranches = resolveRequiredCheckBranches(meta, repo);
|
|
15492
15543
|
return {
|
|
15493
15544
|
contexts: sortedUnique(contexts),
|
|
15494
15545
|
source: "registry META requiredChecks",
|
|
15495
|
-
|
|
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
|
|
15506
|
-
|
|
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
|
|
15726
|
-
const
|
|
15727
|
-
const
|
|
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(", ")}]
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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}
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
23427
|
-
|
|
23428
|
-
|
|
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
|
|
23447
|
-
|
|
23448
|
-
|
|
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 ?? `
|
|
23465
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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: `
|
|
23960
|
+
failed.push({ repo, error: `v4 failed: ${v4Published.error}` });
|
|
23882
23961
|
continue;
|
|
23883
23962
|
}
|
|
23884
23963
|
} catch (error) {
|
|
23885
|
-
failed.push({ repo, error: `
|
|
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 =
|
|
23898
|
-
const embCount =
|
|
23899
|
-
const embGap =
|
|
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
|
|
24009
|
-
const
|
|
24010
|
-
const
|
|
24011
|
-
|
|
24012
|
-
|
|
24013
|
-
|
|
24014
|
-
|
|
24015
|
-
|
|
24016
|
-
|
|
24017
|
-
|
|
24018
|
-
|
|
24019
|
-
|
|
24020
|
-
|
|
24021
|
-
|
|
24022
|
-
|
|
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
|
-
|
|
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.
|
|
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: "
|
|
33339
|
+
delivery: "provisioned",
|
|
33172
33340
|
sourceSurfaceId: "mmi-skills",
|
|
33173
33341
|
artifactId: "mmi-hermes-plugin",
|
|
33174
33342
|
invocation: {
|
|
33175
33343
|
entry: "/mmi",
|
|
33176
|
-
any: "
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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`;
|
|
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-
|
|
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
|
|
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
|
|
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 = {
|
|
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
|
|
35677
|
-
const
|
|
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
|
-
|
|
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-
|
|
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
|
|
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(
|
|
36291
|
+
if (!v4Result.ok) return await failGraceful(v4Result.error);
|
|
36159
36292
|
if (o.json) {
|
|
36160
|
-
consoleIo.log(JSON.stringify(
|
|
36293
|
+
consoleIo.log(JSON.stringify(v4Result, null, 2));
|
|
36161
36294
|
return;
|
|
36162
36295
|
}
|
|
36163
|
-
|
|
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 (
|
|
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
|
-
|
|
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("
|
|
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
|
|
36292
|
-
|
|
36293
|
-
const
|
|
36294
|
-
|
|
36295
|
-
|
|
36296
|
-
|
|
36297
|
-
|
|
36298
|
-
|
|
36299
|
-
|
|
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
|
-
|
|
36317
|
-
|
|
36318
|
-
|
|
36319
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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}
|
|
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
|
});
|