diff --git a/.github/workflows/mantis-telegram-desktop-proof.yml b/.github/workflows/mantis-telegram-desktop-proof.yml index 6c9501b23e25..be8fb202f13a 100644 --- a/.github/workflows/mantis-telegram-desktop-proof.yml +++ b/.github/workflows/mantis-telegram-desktop-proof.yml @@ -554,14 +554,14 @@ jobs: id: baseline_build_cache uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: - path: .artifacts/mantis-baseline-build.tar - key: ${{ runner.os }}-${{ runner.arch }}-mantis-baseline-v3-${{ steps.proof_worktrees.outputs.lockfile_sha256 }}-${{ steps.proof_worktrees.outputs.node_version }}-${{ steps.proof_worktrees.outputs.pnpm_version }}-${{ needs.resolve_request.outputs.baseline_revision }} + path: .artifacts/mantis-runtime-build.tar + key: ${{ runner.os }}-${{ runner.arch }}-mantis-runtime-v1-${{ steps.proof_worktrees.outputs.lockfile_sha256 }}-${{ steps.proof_worktrees.outputs.node_version }}-${{ steps.proof_worktrees.outputs.pnpm_version }}-${{ needs.resolve_request.outputs.baseline_revision }} restore-keys: | - ${{ runner.os }}-${{ runner.arch }}-mantis-baseline-v3-${{ steps.proof_worktrees.outputs.lockfile_sha256 }}-${{ steps.proof_worktrees.outputs.node_version }}-${{ steps.proof_worktrees.outputs.pnpm_version }}- + ${{ runner.os }}-${{ runner.arch }}-mantis-runtime-v1-${{ steps.proof_worktrees.outputs.lockfile_sha256 }}-${{ steps.proof_worktrees.outputs.node_version }}-${{ steps.proof_worktrees.outputs.pnpm_version }}- - name: Prepare baseline and candidate proof builds env: - BASELINE_BUILD_ARCHIVE: ${{ github.workspace }}/.artifacts/mantis-baseline-build.tar + BASELINE_BUILD_ARCHIVE: ${{ github.workspace }}/.artifacts/mantis-runtime-build.tar BASELINE_BUILD_CACHE_HIT: ${{ steps.baseline_build_cache.outputs.cache-hit }} BASELINE_SHA: ${{ needs.resolve_request.outputs.baseline_revision }} CANDIDATE_SHA: ${{ steps.proof_worktrees.outputs.candidate_revision }} @@ -590,8 +590,8 @@ jobs: pnpm-lock.yaml \ pnpm-workspace.yaml \ tsconfig.json; then - # Observed 2026-08: cache-only seeding still rebuilt tsdown-unified for 3m04s; - # its cache contract also requires the live plugin-SDK outputs it will replace. + # The runtime graph always rebuilds; seed only trusted build metadata and + # live plugin-SDK outputs when the candidate kept the cache engine intact. tar --no-same-owner -C "$candidate_root" -xf "$BASELINE_BUILD_ARCHIVE" \ .artifacts/build-all-cache dist/plugin-sdk test -z "$(find "$candidate_root/.artifacts/build-all-cache" -type l -print -quit)" @@ -625,9 +625,11 @@ jobs: HOME="${RUNNER_TEMP}/mantis-baseline-home" \ OPENCLAW_BUILD_PRIVATE_QA=1 \ OPENCLAW_ENABLE_PRIVATE_QA_CLI=1 \ + OPENCLAW_RUN_NODE_SKIP_DTS_BUILD=1 \ PATH="$toolchain_dir:/usr/bin:/bin" \ "$toolchain_dir/pnpm" build mkdir -p "$(dirname "$BASELINE_BUILD_ARCHIVE")" + mkdir -p "$baseline_root/.artifacts/build-all-cache" baseline_archive_new="${BASELINE_BUILD_ARCHIVE}.new" test ! -e "$baseline_archive_new" tar -cf "$baseline_archive_new" \ @@ -642,18 +644,6 @@ jobs: test -f dist/control-ui/index.html test -f dist/index.js -o -f dist/index.mjs build_cache_root="$baseline_root/.artifacts/build-all-cache" - for phase in tsdown-ai tsdown-packages tsdown-unified; do - stamp="$build_cache_root/$phase/stamp.json" - outputs="$build_cache_root/$phase/outputs" - test -s "$stamp" - jq -e ' - (.version | type) == "number" and - (.signature | type) == "string" and (.signature | length) == 64 and - (.outputs | type) == "array" and (.outputs | length) > 0 - ' "$stamp" >/dev/null - test -d "$outputs" - test -n "$(find "$outputs" -type f -print -quit)" - done test -z "$(find "$build_cache_root" -type l -print -quit)" test -z "$(find "$build_cache_root" -type f -links +1 -print -quit)" } @@ -692,7 +682,7 @@ jobs: continue-on-error: true uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: - path: .artifacts/mantis-baseline-build.tar + path: .artifacts/mantis-runtime-build.tar key: ${{ steps.baseline_build_cache.outputs.cache-primary-key }} - name: Install TDLib and restore Telegram QA user diff --git a/scripts/build-all.mts b/scripts/build-all.mts index f3c1ba8c39ea..8f3ff37c869d 100644 --- a/scripts/build-all.mts +++ b/scripts/build-all.mts @@ -63,6 +63,7 @@ type BuildAllStepParams = { }; type BuildAllCacheParams = { rootDir?: string; fs?: BuildAllFs; env?: NodeJS.ProcessEnv }; const BUILD_CACHE_VERSION = 4; +const RUN_NODE_SKIP_DTS_BUILD_ENV = "OPENCLAW_RUN_NODE_SKIP_DTS_BUILD"; const TSDOWN_DECLARATION_EXTENSIONS = [".d.ts", ".d.mts", ".d.cts"]; const TSDOWN_SOURCE_EXTENSIONS = [ ".cjs", @@ -362,8 +363,25 @@ export const BUILD_ALL_PROFILES: Record = { ], }; +const FULL_RUNTIME_ONLY_STEPS = [ + "plugins:assets:build", + "tsdown", + "external-plugins:local-dist", + "check-cli-bootstrap-imports", + "plugins:assets:copy", + "runtime-postbuild", + "build-stamp", + "runtime-postbuild-stamp", + "ui:build", + "write-build-info", + "write-cli-startup-metadata", +]; + export const BUILD_ALL_PROFILE_STEP_ENV: Record> = { full: { + tsdown: { + OPENCLAW_PRESERVE_CLI_STARTUP_METADATA: "1", + }, "tsdown-unified": { OPENCLAW_PRESERVE_CLI_STARTUP_METADATA: "1", }, @@ -449,11 +467,20 @@ export function parseBuildAllArgs(argv: string[]) { return args; } -export function resolveBuildAllSteps(profile = "full"): BuildAllStep[] { - const labels = BUILD_ALL_PROFILES[profile]; - if (!labels) { +export function resolveBuildAllSteps( + profile = "full", + buildEnv: NodeJS.ProcessEnv = process.env, +): BuildAllStep[] { + const profileLabels = BUILD_ALL_PROFILES[profile]; + if (!profileLabels) { throw new Error(`Unknown build profile: ${profile}`); } + // A cold runtime-only build has no declarations for the canonical SDK gates. + // Keep the full runtime artifact surface, but use the uncached runtime graph. + const labels = + profile === "full" && buildEnv[RUN_NODE_SKIP_DTS_BUILD_ENV] === "1" + ? FULL_RUNTIME_ONLY_STEPS + : profileLabels; const selected = labels.map((label) => BUILD_ALL_STEPS.find((step) => step.label === label)); if (selected.some((step) => !step)) { const missing = labels.filter((label) => !BUILD_ALL_STEPS.some((step) => step.label === label)); @@ -950,7 +977,7 @@ if (isMainModule()) { const buildEnv = resolveBuildAllEnvironment(); const timings: BuildAllTiming[] = []; let exitCode = 0; - for (const step of resolveBuildAllSteps(args.profile)) { + for (const step of resolveBuildAllSteps(args.profile, buildEnv)) { const startedAt = performance.now(); const cacheState = resolveBuildAllStepCacheState(step, { env: buildEnv }); let stepToRun = step; diff --git a/scripts/mantis/mantis-sut-container.sh b/scripts/mantis/mantis-sut-container.sh index cff95affc5f6..85cb2dcbf030 100644 --- a/scripts/mantis/mantis-sut-container.sh +++ b/scripts/mantis/mantis-sut-container.sh @@ -605,7 +605,7 @@ readonly build_command=' } trap cleanup EXIT INT TERM corepack pnpm install --frozen-lockfile --store-dir "$store" - corepack pnpm build + OPENCLAW_RUN_NODE_SKIP_DTS_BUILD=1 corepack pnpm build ' run_network_probe() { diff --git a/test/scripts/build-all.test.ts b/test/scripts/build-all.test.ts index edea75255b43..8a07476d3106 100644 --- a/test/scripts/build-all.test.ts +++ b/test/scripts/build-all.test.ts @@ -587,6 +587,30 @@ describe("resolveBuildAllSteps", () => { ]); }); + it("uses the full runtime artifact surface without declaration work when DTS is disabled", () => { + const steps = resolveBuildAllSteps("full", { + OPENCLAW_RUN_NODE_SKIP_DTS_BUILD: "1", + }); + const labels = steps.map((step) => step.label); + + expect(labels).toEqual([ + "plugins:assets:build", + "tsdown", + "external-plugins:local-dist", + "check-cli-bootstrap-imports", + "plugins:assets:copy", + "runtime-postbuild", + "build-stamp", + "runtime-postbuild-stamp", + "ui:build", + "write-build-info", + "write-cli-startup-metadata", + ]); + expect(steps.find((step) => step.label === "tsdown")?.cache).toBeUndefined(); + expect(labels).not.toContain("write-plugin-sdk-entry-dts"); + expect(labels).not.toContain("check-plugin-sdk-exports"); + }); + it("uses a source performance profile with QA assets and immutable build provenance", () => { expect(resolveBuildAllSteps("sourcePerformance").map((step) => step.label)).toEqual([ "plugins:assets:build", @@ -1121,27 +1145,32 @@ describe("resolveBuildAllStepCacheState", () => { }); }); - it("marks cacheable steps stale when a tracked env input changes", () => { + it("never reuses a runtime-only cache as a declaration-complete full-build cache", () => { withBuildCacheFixture(({ rootDir, step }) => { const envStep = { ...step, cache: { ...step.cache, - env: ["OPENCLAW_BUILD_PRIVATE_QA"], + env: ["OPENCLAW_RUN_NODE_SKIP_DTS_BUILD"], + restore: "always" as const, }, }; const cacheState = resolveBuildAllStepCacheState(envStep, { rootDir, - env: { OPENCLAW_BUILD_PRIVATE_QA: "0" }, + env: { OPENCLAW_RUN_NODE_SKIP_DTS_BUILD: "1" }, + }); + writeBuildAllStepCacheStamp(envStep, cacheState, { + rootDir, + env: { OPENCLAW_RUN_NODE_SKIP_DTS_BUILD: "1" }, }); - writeBuildAllStepCacheStamp(envStep, cacheState, { rootDir }); const stale = resolveBuildAllStepCacheState(envStep, { rootDir, - env: { OPENCLAW_BUILD_PRIVATE_QA: "1" }, + env: {}, }); expect(stale.cacheable).toBe(true); expect(stale.fresh).toBe(false); + expect(stale.restorable).toBe(false); expect(stale.reason).toBe("stale"); }); }); diff --git a/test/scripts/mantis-telegram-desktop-proof-workflow.test.ts b/test/scripts/mantis-telegram-desktop-proof-workflow.test.ts index cbb02ae9bff3..2d55e769469e 100644 --- a/test/scripts/mantis-telegram-desktop-proof-workflow.test.ts +++ b/test/scripts/mantis-telegram-desktop-proof-workflow.test.ts @@ -901,12 +901,13 @@ describe("Mantis Telegram Desktop proof workflow", () => { expect(restore.with?.key).toContain("steps.proof_worktrees.outputs.lockfile_sha256"); expect(restore.with?.key).toContain("steps.proof_worktrees.outputs.node_version"); expect(restore.with?.key).toContain("steps.proof_worktrees.outputs.pnpm_version"); - expect(restore.with?.key).toContain("mantis-baseline-v3"); + expect(restore.with?.key).toContain("mantis-runtime-v1"); expect(restore.with?.key).toMatch(/pnpm_version.*baseline_revision/u); expect(restore.with?.["restore-keys"]).toBe( - "${{ runner.os }}-${{ runner.arch }}-mantis-baseline-v3-${{ steps.proof_worktrees.outputs.lockfile_sha256 }}-${{ steps.proof_worktrees.outputs.node_version }}-${{ steps.proof_worktrees.outputs.pnpm_version }}-\n", + "${{ runner.os }}-${{ runner.arch }}-mantis-runtime-v1-${{ steps.proof_worktrees.outputs.lockfile_sha256 }}-${{ steps.proof_worktrees.outputs.node_version }}-${{ steps.proof_worktrees.outputs.pnpm_version }}-\n", ); - expect(restore.with?.path).toBe(".artifacts/mantis-baseline-build.tar"); + expect(restore.with?.path).toBe(".artifacts/mantis-runtime-build.tar"); + expect(setup.with?.["build-all-cache-scope"]).toBeUndefined(); expect(builds.if).toBeUndefined(); expect(builds.env?.HOST_PNPM_STORE).toBe( "${{ steps.setup-node-env.outputs.pnpm-store-cache-path }}", @@ -916,10 +917,13 @@ describe("Mantis Telegram Desktop proof workflow", () => { expect(buildRun).toContain("baseline_archive_restored=true"); expect(buildRun).toContain('"$toolchain_dir/pnpm" install --frozen-lockfile'); expect(buildRun).toContain('if [[ "$BASELINE_BUILD_CACHE_HIT" != "true" ]]'); - expect(buildRun).toContain('"$toolchain_dir/pnpm" build'); + expect(buildRun).toMatch( + /OPENCLAW_RUN_NODE_SKIP_DTS_BUILD=1 \\\n\s+PATH="\$toolchain_dir:\/usr\/bin:\/bin" \\\n\s+"\$toolchain_dir\/pnpm" build/u, + ); expect(buildRun).toContain('mv -T "$baseline_archive_new" "$BASELINE_BUILD_ARCHIVE"'); + expect(buildRun).toContain('mkdir -p "$baseline_root/.artifacts/build-all-cache"'); expect(buildRun).toContain(".artifacts/build-all-cache"); - expect(buildRun).toContain("for phase in tsdown-ai tsdown-packages tsdown-unified"); + expect(buildRun).not.toContain("for phase in tsdown-ai tsdown-packages tsdown-unified"); expect(buildRun).toContain("-type f -links +1"); expect(save.if).toContain("steps.baseline_build_cache.outputs.cache-hit != 'true'"); expect(save.uses).toContain("actions/cache/save@"); @@ -1253,6 +1257,8 @@ describe("Mantis Telegram Desktop proof workflow", () => { expect(wrapper).toContain("--memory 8g"); expect(wrapper).toContain("--cpus 4"); expect(wrapper).toContain("--memory 16g"); + expect(wrapper).toContain("OPENCLAW_RUN_NODE_SKIP_DTS_BUILD=1 corepack pnpm build"); + expect(wrapper).not.toContain("\n corepack pnpm build\n"); expect(sutScript).not.toContain("CODEX_HOME"); expect(sutScript).not.toContain("codexProxyPort"); expect(wrapper).toContain('connects("runner-host", 9)');