Skip to content

Commit 602397b

Browse files
authored
fix(ci): grace-window the MCP known-latest audit so same-day releases stop redding every branch (#8179)
Three times in one night (3.4.0, 3.5.0, 3.6.0) a fresh @loopover/mcp publish turned ui:version-audit red for main and every in-flight branch/gate -- a mismatch that is neither the branch's fault nor actionable from it, while the scheduled sync PR (the real fix) lands within minutes anyway. The audit now reads the latest version's publish time from the same registry response: a mismatch within 24h of publish emits a ::warning and passes (the sync PR is the fix); older than that is genuine staleness and still hard-fails -- preserving the #3047 anti-drift discipline. Offline/env-override enforcement (LOOPOVER_MCP_LATEST_VERSION, no publish time available) deliberately stays strict, and --write self-healing is unchanged.
1 parent bd19df0 commit 602397b

2 files changed

Lines changed: 55 additions & 5 deletions

File tree

scripts/check-ui-mcp-version-copy.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@ export const SCAN_TARGETS = [
2222
"apps/loopover-ui/content",
2323
];
2424

25+
/** How long a freshly-published npm latest may lead the pinned copy before the mismatch hard-fails.
26+
* Rationale (#8179-era fix): on an active release evening the automation publishes several versions and
27+
* files its sync PR within minutes — but every branch/gate in flight goes red on a mismatch that is
28+
* neither the branch's fault nor actionable from it (three same-night cascades on 2026-07-23 alone).
29+
* A mismatch INSIDE the window warns (the sync PR lands the real fix); a mismatch OLDER than the window
30+
* is genuine staleness — the #3047 discipline — and still hard-fails. */
31+
export const KNOWN_LATEST_GRACE_WINDOW_MS = 24 * 60 * 60 * 1000;
32+
33+
/** PURE: does a pinned-copy mismatch fail, or merely warn? Fails when the latest's publish time is
34+
* unknown (offline enforcement stays strict) or older than the grace window. */
35+
export function isMismatchBeyondGrace(publishedAtIso: string | null, nowMs: number): boolean {
36+
if (!publishedAtIso) return true;
37+
const publishedMs = Date.parse(publishedAtIso);
38+
if (!Number.isFinite(publishedMs)) return true;
39+
return nowMs - publishedMs > KNOWN_LATEST_GRACE_WINDOW_MS;
40+
}
41+
2542
export type StaleVersionMatchers = {
2643
floorVersion: string;
2744
minorLabel: string;
@@ -39,10 +56,15 @@ async function main() {
3956
// required check one-shot-closes a contributor PR. Set LOOPOVER_MCP_LATEST_VERSION to make it fully
4057
// offline/deterministic. The deterministic stale-version-string scan below always runs regardless.
4158
let latest = process.env.LOOPOVER_MCP_LATEST_VERSION ?? null;
59+
// Publish time of `latest` (for the grace window below). Stays null under the env override, so offline
60+
// enforcement remains strict — a mismatch there always fails.
61+
let latestPublishedAt: string | null = null;
4262
let latestSkipReason = null;
4363
if (!latest) {
4464
try {
45-
latest = await fetchLatestVersion();
65+
const release = await fetchLatestRelease();
66+
latest = release.latest;
67+
latestPublishedAt = release.publishedAt;
4668
} catch (error) {
4769
latestSkipReason = error instanceof Error ? error.message : "unknown error";
4870
}
@@ -59,8 +81,14 @@ async function main() {
5981
if (write) {
6082
writeKnownLatestVersion(sourceLatestPath, latest);
6183
console.log(`${SOURCE_LATEST_PATH}: updated known latest ${sourceLatest} -> ${latest}`);
84+
} else if (isMismatchBeyondGrace(latestPublishedAt, Date.now())) {
85+
failures.push(
86+
`${SOURCE_LATEST_PATH}: known latest ${sourceLatest} does not match npm dist-tags.latest ${latest} (published ${latestPublishedAt ?? "unknown"}, beyond the ${KNOWN_LATEST_GRACE_WINDOW_MS / 3_600_000}h grace window)`,
87+
);
6288
} else {
63-
failures.push(`${SOURCE_LATEST_PATH}: known latest ${sourceLatest} does not match npm dist-tags.latest ${latest}`);
89+
console.warn(
90+
`::warning::${SOURCE_LATEST_PATH}: known latest ${sourceLatest} is behind npm dist-tags.latest ${latest}, published within the grace window -- the scheduled sync PR is the fix; not failing this build`,
91+
);
6492
}
6593
} else if (!latest) {
6694
console.warn(
@@ -177,7 +205,7 @@ export function readMinimumSupportedVersion(path: string): string {
177205
return match[1]!;
178206
}
179207

180-
export function fetchLatestVersion(): Promise<string> {
208+
export function fetchLatestRelease(): Promise<{ latest: string; publishedAt: string | null }> {
181209
return new Promise((resolve, reject) => {
182210
const request = get(registryUrl, { headers: { accept: "application/json" } }, (response) => {
183211
let body = "";
@@ -191,12 +219,14 @@ export function fetchLatestVersion(): Promise<string> {
191219
return;
192220
}
193221
try {
194-
const latest = JSON.parse(body)?.["dist-tags"]?.latest;
222+
const parsed = JSON.parse(body);
223+
const latest = parsed?.["dist-tags"]?.latest;
195224
if (typeof latest !== "string" || !/^\d+\.\d+\.\d+$/.test(latest)) {
196225
reject(new Error("npm registry did not return a stable latest version"));
197226
return;
198227
}
199-
resolve(latest);
228+
const publishedAt = typeof parsed?.time?.[latest] === "string" ? parsed.time[latest] : null;
229+
resolve({ latest, publishedAt });
200230
} catch (error) {
201231
reject(error);
202232
}

test/unit/check-ui-mcp-version-copy-script.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
buildStaleVersionMatchers,
88
collectSourceFiles,
99
collectVersionCopyFailures,
10+
isMismatchBeyondGrace,
11+
KNOWN_LATEST_GRACE_WINDOW_MS,
1012
isMinimumSupportedContext,
1113
isTextSource,
1214
readMinimumSupportedVersion,
@@ -252,3 +254,21 @@ describe("check-ui-mcp-version-copy script (#6292)", () => {
252254
});
253255
});
254256
});
257+
258+
describe("isMismatchBeyondGrace (#8179-era release-treadmill fix)", () => {
259+
const NOW = Date.parse("2026-07-23T09:00:00.000Z");
260+
261+
it("a mismatch against a freshly-published latest warns (within the grace window)", () => {
262+
expect(isMismatchBeyondGrace(new Date(NOW - 60_000).toISOString(), NOW)).toBe(false);
263+
expect(isMismatchBeyondGrace(new Date(NOW - KNOWN_LATEST_GRACE_WINDOW_MS + 1000).toISOString(), NOW)).toBe(false);
264+
});
265+
266+
it("a mismatch older than the grace window is genuine staleness and fails", () => {
267+
expect(isMismatchBeyondGrace(new Date(NOW - KNOWN_LATEST_GRACE_WINDOW_MS - 1000).toISOString(), NOW)).toBe(true);
268+
});
269+
270+
it("stays STRICT when the publish time is unknown or garbled — offline/env-override enforcement never softens", () => {
271+
expect(isMismatchBeyondGrace(null, NOW)).toBe(true);
272+
expect(isMismatchBeyondGrace("not-a-time", NOW)).toBe(true);
273+
});
274+
});

0 commit comments

Comments
 (0)