From 42f993378fa259eb5afae12650c053979817ceaa Mon Sep 17 00:00:00 2001 From: suyx Date: Thu, 6 Aug 2026 00:42:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(POSTV1-browser):=20=E5=90=8C=E6=97=B6?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20Chrome=20150=20=E4=B8=8E=20151?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RELEASE.json | 3 +- apps/api/src/admin-state.ts | 11 ++++--- apps/api/src/browser-support.ts | 35 ++++++++++++++++----- packages/shared-contracts/src/admin.ts | 2 +- packages/shared-contracts/src/api.ts | 2 +- scripts/lib/wp7-07-final-release.mjs | 23 +++++++++++++- tests/api/wp0-03-browser-gate.test.ts | 30 ++++++++++++++++++ tests/helpers/browser-support-fixture.ts | 2 +- tests/package/wp7-07-final-release.test.mjs | 28 ++++++++++++++++- 9 files changed, 119 insertions(+), 17 deletions(-) diff --git a/RELEASE.json b/RELEASE.json index 0116eee..e0579e7 100644 --- a/RELEASE.json +++ b/RELEASE.json @@ -3,7 +3,8 @@ "browsers": [ { "brand": "Google Chrome", - "fullVersion": "150.0.7871.187" + "fullVersion": "150.0.7871.187", + "supportedMajorVersions": [150, 151] }, { "brand": "Microsoft Edge", diff --git a/apps/api/src/admin-state.ts b/apps/api/src/admin-state.ts index 5a884aa..b6c7a17 100644 --- a/apps/api/src/admin-state.ts +++ b/apps/api/src/admin-state.ts @@ -151,10 +151,13 @@ export function createAdminDiagnosticsProvider(input: { const system: AdminDiagnosticsResponse["system"] = { api_status: "ready", app_version: input.appVersion ?? input.browserSupportRelease?.appVersion ?? "0.0.0", - browser_support: (input.browserSupportRelease?.browsers ?? []).map((browser) => ({ - brand: browser.brand, - major: Number.parseInt(browser.fullVersion.split(".")[0] ?? "0", 10), - })).filter((browser) => Number.isSafeInteger(browser.major) && browser.major > 0), + browser_support: (input.browserSupportRelease?.browsers ?? []).flatMap((browser) => { + const majors = browser.supportedMajorVersions + ?? [Number.parseInt(browser.fullVersion.split(".")[0] ?? "0", 10)]; + return majors + .map((major) => ({ brand: browser.brand, major })) + .filter((entry) => Number.isSafeInteger(entry.major) && entry.major > 0); + }), worker_status: services.services.find((service) => service.service_id === "worker")?.status === "active" ? "ready" : services.services.find((service) => service.service_id === "worker")?.status === "unavailable" diff --git a/apps/api/src/browser-support.ts b/apps/api/src/browser-support.ts index d21a9eb..b954562 100644 --- a/apps/api/src/browser-support.ts +++ b/apps/api/src/browser-support.ts @@ -43,7 +43,7 @@ export const BrowserSupportSuccessSchema = Type.Object( app_version: Type.String({ maxLength: 80 }), browser: SupportedBrowserSummarySchema, status: Type.Literal("supported"), - supported_browsers: Type.Array(SupportedBrowserSummarySchema, { maxItems: 2 }), + supported_browsers: Type.Array(SupportedBrowserSummarySchema, { maxItems: 8 }), }, { additionalProperties: false, $id: "BrowserSupportSuccess" }, ); @@ -57,6 +57,7 @@ export interface BrowserSupportRelease { browsers: ReadonlyArray<{ brand: SupportedBrand; fullVersion: string; + supportedMajorVersions?: ReadonlyArray; }>; } @@ -115,7 +116,14 @@ function supportedIdentity(entries: Array<{ brand: string; version: string }>) { export function supportedBrowserSummary(release: BrowserSupportRelease | undefined) { if (!release) return []; - return release.browsers.map(({ brand, fullVersion }) => ({ brand, major: major(fullVersion)! })); + return release.browsers.flatMap(({ brand, fullVersion, supportedMajorVersions }) => { + const majors = supportedMajorVersions ?? [major(fullVersion)!]; + return majors.map((supportedMajor) => ({ brand, major: supportedMajor })); + }); +} + +function acceptedMajorVersions(browser: BrowserSupportRelease["browsers"][number]) { + return browser.supportedMajorVersions ?? [major(browser.fullVersion)!]; } export function validateBrowserSupportRelease(value: unknown): value is BrowserSupportRelease { @@ -126,13 +134,26 @@ export function validateBrowserSupportRelease(value: unknown): value is BrowserS } if (!Array.isArray(release.browsers) || release.browsers.length !== 2) return false; const brands = new Set(release.browsers.map(({ brand }) => brand)); + const supportedMajorCount = release.browsers.reduce( + (count, browser) => count + (browser.supportedMajorVersions?.length ?? 1), + 0, + ); return ( brands.size === 2 && brands.has("Google Chrome") && brands.has("Microsoft Edge") && - release.browsers.every( - ({ brand, fullVersion }) => supportedBrands.has(brand) && fullVersionPattern.test(fullVersion), - ) + supportedMajorCount <= 8 && + release.browsers.every(({ brand, fullVersion, supportedMajorVersions }) => { + if (!supportedBrands.has(brand) || !fullVersionPattern.test(fullVersion)) return false; + const baselineMajor = major(fullVersion); + if (!baselineMajor) return false; + if (supportedMajorVersions === undefined) return true; + return supportedMajorVersions.length > 0 + && supportedMajorVersions.length <= 8 + && supportedMajorVersions.every((value: number) => Number.isSafeInteger(value) && value >= 1) + && new Set(supportedMajorVersions).size === supportedMajorVersions.length + && supportedMajorVersions.includes(baselineMajor); + }) ); } @@ -188,7 +209,7 @@ export function checkBrowserSupport( } const supported = release?.browsers.find(({ brand }) => brand === fullIdentity.brand); - if (!supported || major(supported.fullVersion) !== fullIdentity.major) { + if (!supported || !acceptedMajorVersions(supported).includes(fullIdentity.major)) { return { reason: "version_unsupported", supported: false }; } return { identity: fullIdentity, supported: true }; @@ -268,7 +289,7 @@ export function verifyBrowserSupportCookie(input: { return { reason: "identity_unavailable" as const, supported: false as const }; } const supported = input.release.browsers.find(({ brand }) => brand === currentIdentity.brand); - if (currentIdentity.major !== payload.major || major(supported?.fullVersion ?? "") !== currentIdentity.major) { + if (!supported || currentIdentity.major !== payload.major || !acceptedMajorVersions(supported).includes(currentIdentity.major)) { return { reason: "version_unsupported" as const, supported: false as const }; } return { identity: currentIdentity, supported: true as const }; diff --git a/packages/shared-contracts/src/admin.ts b/packages/shared-contracts/src/admin.ts index eb73916..22c61a8 100644 --- a/packages/shared-contracts/src/admin.ts +++ b/packages/shared-contracts/src/admin.ts @@ -281,7 +281,7 @@ export const AdminDiagnosticsResponseSchema = Type.Object({ browser_support: Type.Array(Type.Object({ brand: Type.Union([Type.Literal("Google Chrome"), Type.Literal("Microsoft Edge")]), major: Type.Integer({ minimum: 1 }), - }, { additionalProperties: false }), { maxItems: 2 }), + }, { additionalProperties: false }), { maxItems: 8 }), worker_status: Type.Union([Type.Literal("ready"), Type.Literal("degraded"), Type.Literal("unavailable")]), }, { additionalProperties: false }), }, { additionalProperties: false, $id: "AdminDiagnosticsResponse" }); diff --git a/packages/shared-contracts/src/api.ts b/packages/shared-contracts/src/api.ts index 00d4385..107ed53 100644 --- a/packages/shared-contracts/src/api.ts +++ b/packages/shared-contracts/src/api.ts @@ -83,7 +83,7 @@ export const ErrorDetailsSchema = Type.Object( }, { additionalProperties: false }, ), - { maxItems: 2 }, + { maxItems: 8 }, ), ), capacity_status: Type.Optional( diff --git a/scripts/lib/wp7-07-final-release.mjs b/scripts/lib/wp7-07-final-release.mjs index 62acaa7..d67afa3 100644 --- a/scripts/lib/wp7-07-final-release.mjs +++ b/scripts/lib/wp7-07-final-release.mjs @@ -14,7 +14,11 @@ export const DEFERRED_EXTERNAL_TASKS = Object.freeze(["TASK-WP7-03", "TASK-WP7-0 export function buildFinalReleaseRecord({ appVersion, browsers, buildCommit, frozenFromCommit, recordedAt, windows }) { const record = { appVersion, - browsers: browsers.map(({ brand, fullVersion }) => ({ brand, fullVersion })), + browsers: browsers.map(({ brand, fullVersion, supportedMajorVersions }) => ({ + brand, + fullVersion, + ...(supportedMajorVersions ? { supportedMajorVersions: [...supportedMajorVersions] } : {}), + })), buildCommit: buildCommit.toLowerCase(), deferredExternalTasks: [...DEFERRED_EXTERNAL_TASKS], finalRelease: true, @@ -44,8 +48,25 @@ export function validateFinalReleaseRecord(record) { } else { const brands = record.browsers.map(({ brand }) => brand).sort(); if (brands.join("|") !== "Google Chrome|Microsoft Edge") errors.push("browserBrands"); + const supportedMajorCount = record.browsers.reduce( + (count, browser) => count + (Array.isArray(browser.supportedMajorVersions) + ? browser.supportedMajorVersions.length + : 1), + 0, + ); + if (supportedMajorCount > 8) errors.push("supportedMajorVersions.total"); for (const browser of record.browsers) { if (!VERSION.test(browser.fullVersion ?? "")) errors.push(`${browser.brand}.fullVersion`); + if (browser.supportedMajorVersions !== undefined) { + const values = browser.supportedMajorVersions; + const baselineMajor = Number.parseInt(browser.fullVersion.split(".")[0] ?? "0", 10); + if (!Array.isArray(values) || values.length === 0 || values.length > 8 + || values.some((value) => !Number.isSafeInteger(value) || value < 1) + || new Set(values).size !== values.length + || !values.includes(baselineMajor)) { + errors.push(`${browser.brand}.supportedMajorVersions`); + } + } if ("path" in browser || "executablePath" in browser || "executableSha256" in browser) errors.push(`${browser.brand}.privateMetadata`); } } diff --git a/tests/api/wp0-03-browser-gate.test.ts b/tests/api/wp0-03-browser-gate.test.ts index a472533..181fcc7 100644 --- a/tests/api/wp0-03-browser-gate.test.ts +++ b/tests/api/wp0-03-browser-gate.test.ts @@ -13,6 +13,14 @@ const supportedEdge = browserSupportFixture({ brand: "Microsoft Edge", fullVersion: "150.0.4078.99", }); +const supportedChrome150 = browserSupportFixture({ + brand: "Google Chrome", + fullVersion: "150.0.7871.187", +}); +const supportedChrome151 = browserSupportFixture({ + brand: "Google Chrome", + fullVersion: "151.0.0.0", +}); const rejectedIdentityCases = [ { expectedReason: "platform_unsupported", @@ -145,6 +153,7 @@ describe("TDD-WP0-BRW-001 supported browser contract", () => { status: "supported", supported_browsers: [ { brand: "Google Chrome", major: 150 }, + { brand: "Google Chrome", major: 151 }, { brand: "Microsoft Edge", major: 150 }, ], }); @@ -188,6 +197,26 @@ describe("TDD-WP0-BRW-001 supported browser contract", () => { expect(staleCookie.statusCode).toBe(426); await restarted.close(); }); + + it.each([ + { expectedMajor: 150, fixture: supportedChrome150 }, + { expectedMajor: 151, fixture: supportedChrome151 }, + ])("accepts explicitly declared Chrome $expectedMajor", async ({ expectedMajor, fixture }) => { + const app = await createApp({ browserSupportRelease: testBrowserSupportRelease } as never); + const checked = await app.inject({ + headers: fixture.headers, + method: "POST", + payload: fixture.body, + url: "/api/v1/support/check", + }); + + expect(checked.statusCode).toBe(200); + expect(checked.json()).toMatchObject({ + browser: { brand: "Google Chrome", major: expectedMajor }, + status: "supported", + }); + await app.close(); + }); }); describe("TDD-WP0-BRW-002 hard block", () => { @@ -208,6 +237,7 @@ describe("TDD-WP0-BRW-002 hard block", () => { reason: expectedReason, supported_browsers: [ { brand: "Google Chrome", major: 150 }, + { brand: "Google Chrome", major: 151 }, { brand: "Microsoft Edge", major: 150 }, ], }, diff --git a/tests/helpers/browser-support-fixture.ts b/tests/helpers/browser-support-fixture.ts index 61e0ce9..da9d25a 100644 --- a/tests/helpers/browser-support-fixture.ts +++ b/tests/helpers/browser-support-fixture.ts @@ -1,7 +1,7 @@ export const testBrowserSupportRelease = { appVersion: "1.2.3-test", browsers: [ - { brand: "Google Chrome", fullVersion: "150.0.7339.1" }, + { brand: "Google Chrome", fullVersion: "150.0.7339.1", supportedMajorVersions: [150, 151] }, { brand: "Microsoft Edge", fullVersion: "150.0.4078.99" }, ], } as const; diff --git a/tests/package/wp7-07-final-release.test.mjs b/tests/package/wp7-07-final-release.test.mjs index 51e6bd7..55595d5 100644 --- a/tests/package/wp7-07-final-release.test.mjs +++ b/tests/package/wp7-07-final-release.test.mjs @@ -10,7 +10,7 @@ function release() { return buildFinalReleaseRecord({ appVersion: "0.0.0", browsers: [ - { brand: "Google Chrome", fullVersion: "150.0.7871.187" }, + { brand: "Google Chrome", fullVersion: "150.0.7871.187", supportedMajorVersions: [150, 151] }, { brand: "Microsoft Edge", fullVersion: "151.0.4129.59" }, ], buildCommit: "a".repeat(40), @@ -26,6 +26,32 @@ test("TDD-WP7-REL-001 creates a browser-gate compatible first-version record", ( assert.equal(record.fixedPort, 43121); assert.deepEqual(record.deferredExternalTasks, ["TASK-WP7-03", "TASK-WP7-04"]); assert.deepEqual(record.browsers.map(({ brand }) => brand).sort(), ["Google Chrome", "Microsoft Edge"]); + assert.deepEqual(record.browsers[0].supportedMajorVersions, [150, 151]); +}); + +test("TDD-WP7-REL-001 rejects unsafe or duplicate browser major lists", () => { + assert.throws(() => buildFinalReleaseRecord({ + appVersion: "0.0.0", + browsers: [ + { brand: "Google Chrome", fullVersion: "150.0.7871.187", supportedMajorVersions: [150, 150] }, + { brand: "Microsoft Edge", fullVersion: "151.0.4129.59" }, + ], + buildCommit: "a".repeat(40), + frozenFromCommit: "b".repeat(40), + recordedAt: "2026-08-04T06:00:00.000Z", + windows: { arch: "x64", build: "26200.8875", displayVersion: "25H2" }, + }), /Google Chrome\.supportedMajorVersions/); + assert.throws(() => buildFinalReleaseRecord({ + appVersion: "0.0.0", + browsers: [ + { brand: "Google Chrome", fullVersion: "150.0.7871.187", supportedMajorVersions: [150, 151, 152, 153, 154, 155, 156, 157] }, + { brand: "Microsoft Edge", fullVersion: "151.0.4129.59" }, + ], + buildCommit: "a".repeat(40), + frozenFromCommit: "b".repeat(40), + recordedAt: "2026-08-04T06:00:00.000Z", + windows: { arch: "x64", build: "26200.8875", displayVersion: "25H2" }, + }), /supportedMajorVersions\.total/); }); test("TDD-WP7-SEC-001 rejects credential shapes and absolute user paths", () => {