diff --git a/scripts/check-plugin-sdk-exports.mjs b/scripts/check-plugin-sdk-exports.mjs index 11af131c1fb2..31cda1dd1c17 100755 --- a/scripts/check-plugin-sdk-exports.mjs +++ b/scripts/check-plugin-sdk-exports.mjs @@ -14,6 +14,7 @@ import { fileURLToPath, pathToFileURL } from "node:url"; import { MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, + PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES, evaluatePluginSdkDeclarationBudget, isPrivateQaPluginSdkBuild, } from "./lib/plugin-sdk-declaration-budget.mjs"; @@ -186,15 +187,18 @@ if (declarationBudget.shouldFail) { console.error( `${budgetLabel} DTS TOO LARGE: ${declarationBytes} bytes exceeds ${declarationBudget.budgetBytes} bytes.`, ); + console.error( + `Budget: ${declarationBudget.ratchetBytes}-byte ratchet + ${declarationBudget.varianceBytes}-byte Rolldown output variance.`, + ); console.error("Keep plugin SDK declarations in the canonical unified tsdown graph."); missing += 1; } else if (declarationBudget.budgetKind === "private-qa-public-entry") { console.log( - `Private QA build public-entry declaration graph: ${declarationBytes}/${MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES} bytes; publication budget ${MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES} bytes is not applied.`, + `Private QA build public-entry declaration graph: ${declarationBytes}/${declarationBudget.budgetBytes} bytes (${MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES}-byte ratchet + ${PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES}-byte output variance); publication ratchet ${MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES} bytes is not applied.`, ); } else { console.log( - `Public plugin SDK declaration graph: ${declarationBytes}/${MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES} bytes.`, + `Public plugin SDK declaration graph: ${declarationBytes}/${declarationBudget.budgetBytes} bytes (${MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES}-byte ratchet + ${PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES}-byte output variance).`, ); } diff --git a/scripts/lib/plugin-sdk-declaration-budget.d.mts b/scripts/lib/plugin-sdk-declaration-budget.d.mts index bde50e334fdc..c3129f8ee11f 100644 --- a/scripts/lib/plugin-sdk-declaration-budget.d.mts +++ b/scripts/lib/plugin-sdk-declaration-budget.d.mts @@ -8,7 +8,10 @@ export function evaluatePluginSdkDeclarationBudget({ }): { budgetBytes: number; budgetKind: string; + ratchetBytes: number; shouldFail: boolean; + varianceBytes: number; }; export const MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES: 5200000; export const MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES: 5225000; +export const PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES: 65536; diff --git a/scripts/lib/plugin-sdk-declaration-budget.mjs b/scripts/lib/plugin-sdk-declaration-budget.mjs index 259291a0f150..3fa0be1bc583 100644 --- a/scripts/lib/plugin-sdk-declaration-budget.mjs +++ b/scripts/lib/plugin-sdk-declaration-budget.mjs @@ -4,18 +4,24 @@ export const MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES = 5_200_000; // Private-only entrypoints reshape chunks reachable from public roots but are never published. // Bound that topology overhead without counting local-only declarations as package surface. export const MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES = 5_225_000; +// Rolldown can repartition equivalent declaration chunks between clean builds; measured spread is +// about 29 KiB across hosts. Keep one 64 KiB scheduling window above the intentional size ratchet. +export const PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES = 64 * 1024; export function isPrivateQaPluginSdkBuild(env) { return env.OPENCLAW_BUILD_PRIVATE_QA === "1"; } export function evaluatePluginSdkDeclarationBudget({ declarationBytes, buildPrivateQa }) { - const budgetBytes = buildPrivateQa + const ratchetBytes = buildPrivateQa ? MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES : MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES; + const budgetBytes = ratchetBytes + PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES; return { budgetBytes, budgetKind: buildPrivateQa ? "private-qa-public-entry" : "public", + ratchetBytes, shouldFail: declarationBytes > budgetBytes, + varianceBytes: PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES, }; } diff --git a/test/scripts/plugin-sdk-declaration-budget.test.ts b/test/scripts/plugin-sdk-declaration-budget.test.ts index 0d1dddeab2d9..838ffdf87952 100644 --- a/test/scripts/plugin-sdk-declaration-budget.test.ts +++ b/test/scripts/plugin-sdk-declaration-budget.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, + PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES, evaluatePluginSdkDeclarationBudget, isPrivateQaPluginSdkBuild, } from "../../scripts/lib/plugin-sdk-declaration-budget.mjs"; @@ -14,48 +15,61 @@ describe("plugin SDK declaration budget", () => { }); it("enforces the publication budget at its exact boundary", () => { + const budgetBytes = + MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES + PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES; expect( evaluatePluginSdkDeclarationBudget({ buildPrivateQa: false, - declarationBytes: MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, + declarationBytes: budgetBytes, }), ).toEqual({ - budgetBytes: MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, + budgetBytes, budgetKind: "public", + ratchetBytes: MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, shouldFail: false, + varianceBytes: PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES, }); expect( evaluatePluginSdkDeclarationBudget({ buildPrivateQa: false, - declarationBytes: MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES + 1, + declarationBytes: budgetBytes + 1, }), ).toEqual({ - budgetBytes: MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, + budgetBytes, budgetKind: "public", + ratchetBytes: MAX_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, shouldFail: true, + varianceBytes: PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES, }); }); it("tracks private-build public-entry chunk growth under a separate budget", () => { + const budgetBytes = + MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES + + PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES; expect( evaluatePluginSdkDeclarationBudget({ buildPrivateQa: true, - declarationBytes: MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, + declarationBytes: budgetBytes, }), ).toEqual({ - budgetBytes: MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, + budgetBytes, budgetKind: "private-qa-public-entry", + ratchetBytes: MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, shouldFail: false, + varianceBytes: PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES, }); expect( evaluatePluginSdkDeclarationBudget({ buildPrivateQa: true, - declarationBytes: MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES + 1, + declarationBytes: budgetBytes + 1, }), ).toEqual({ - budgetBytes: MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, + budgetBytes, budgetKind: "private-qa-public-entry", + ratchetBytes: MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES, shouldFail: true, + varianceBytes: PLUGIN_SDK_DECLARATION_OUTPUT_VARIANCE_BYTES, }); }); });