diff --git a/.github/workflows/deps.yml b/.github/workflows/deps.yml index 4ea9ad455..80742e644 100644 --- a/.github/workflows/deps.yml +++ b/.github/workflows/deps.yml @@ -5,8 +5,11 @@ # svelte-check ratchet, vitest, scene-lint, kit-diff, a11y, supply chain) and perf-budget.yml, both # called as reusable workflows, then the integrators' e2e battery split into shards on # GitHub-hosted runners (SwiftShader: no GPU, battery.yml): the REQUIRED set gates; the OPTIONAL set (red on -# SwiftShader with unchanged code, each with its reason in tests/e2e/battery.json) runs after it and is only -# reported. `deps-gate` is the one required check; the ruleset on +# SwiftShader with unchanged code, each with its reason in tests/e2e/battery.json) runs only when started by hand +# (workflow_dispatch) and is only reported. Perf budget + battery run only for a non-major change to the root +# app's npm packages (deps-policy.cjs `battery`): Actions, the agent and majors get ci.yml alone, so one monthly +# Dependabot round does not hold the account's 20 concurrent jobs for half a day. `deps-gate` is the one +# required check; the ruleset on # `deps` also requires the `pc-battery` commit status, which the GPU check on a desktop posts for a # PR that changes what the app ships (scripts/ci/pc-battery.sh — pulled from there, no runner of ours # in this public repo) and this workflow posts as "not needed" for every other PR. @@ -47,6 +50,7 @@ jobs: cls: ${{ steps.policy.outputs.class }} needs_pc: ${{ steps.policy.outputs.needs_pc }} reason: ${{ steps.policy.outputs.reason }} + battery: ${{ steps.policy.outputs.battery }} gate_ci: ${{ github.event_name != 'pull_request' || github.base_ref == 'deps' }} steps: - uses: actions/checkout@v4 @@ -68,7 +72,7 @@ jobs: # a person's PR (or a dispatch): no automation; the PC check runs when the app's lockfile moved pc=false if [ -n "$BASE" ] && git diff --name-only "origin/$BASE...HEAD" | grep -qxE 'package(-lock)?\.json'; then pc=true; fi - printf 'class=manual\nneeds_pc=%s\nreason=not a Dependabot PR\n' "$pc" | tee -a "$GITHUB_OUTPUT" + printf 'class=manual\nneeds_pc=%s\nbattery=true\nreason=not a Dependabot PR\n' "$pc" | tee -a "$GITHUB_OUTPUT" fi - name: majors wait for a planned migration if: steps.policy.outputs.class == 'major' @@ -90,7 +94,7 @@ jobs: # the budget gate in three parallel parts (~20 min each instead of ~60 in one) perf: needs: scope - if: needs.scope.outputs.gate_ci == 'true' + if: needs.scope.outputs.gate_ci == 'true' && needs.scope.outputs.battery == 'true' strategy: fail-fast: false matrix: @@ -102,6 +106,7 @@ jobs: plan: needs: scope + if: needs.scope.outputs.battery == 'true' runs-on: ubuntu-latest outputs: matrix: ${{ steps.plan.outputs.matrix }} @@ -133,7 +138,7 @@ jobs: # the deadline left unrun) gets two more tries on a FRESH runner before it counts confirm: needs: [plan, e2e] - if: always() && needs.e2e.result == 'failure' + if: ${{ !cancelled() && needs.e2e.result == 'failure' }} uses: ./.github/workflows/battery.yml with: set: confirm @@ -142,11 +147,12 @@ jobs: refs: ${{ needs.plan.outputs.refs }} deadline: 20 - # red or flaky on SwiftShader with the same red on unchanged code: run, reported, never waited for. They start - # AFTER the required shards and the confirm step (the free plan runs 20 jobs at once); deps-gate does not need them. + # red or flaky on SwiftShader with the same red on unchanged code: reported, never waited for, and only on a run + # started by hand (refreshing battery.json's classification); ~750 runner-min a time is too much for every PR. + # They start AFTER the required shards and the confirm step (the free plan runs 20 jobs at once). e2e-optional: needs: [plan, e2e, confirm] - if: always() && needs.plan.outputs.optional_shards != '0' + if: ${{ !cancelled() && github.event_name == 'workflow_dispatch' && needs.plan.result == 'success' && needs.plan.outputs.optional_shards != '0' }} uses: ./.github/workflows/battery.yml with: set: optional @@ -157,7 +163,7 @@ jobs: optional-report: needs: e2e-optional - if: always() && needs.e2e-optional.result != 'skipped' + if: ${{ !cancelled() && needs.e2e-optional.result != 'skipped' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -172,7 +178,7 @@ jobs: gate: name: deps-gate needs: [scope, ci, perf, plan, e2e, confirm] - if: always() && needs.scope.result == 'success' + if: ${{ !cancelled() && needs.scope.result == 'success' }} runs-on: ubuntu-latest permissions: contents: read @@ -182,25 +188,35 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 + if: needs.scope.outputs.battery == 'true' with: pattern: battery-required-* path: battery - uses: actions/download-artifact@v4 - if: needs.confirm.result != 'skipped' + if: needs.scope.outputs.battery == 'true' && needs.confirm.result != 'skipped' with: pattern: battery-confirm-* path: battery - name: battery report id: report + if: needs.scope.outputs.battery == 'true' run: node scripts/ci/battery.cjs report battery - name: the release gate env: GATE_CI: ${{ needs.scope.outputs.gate_ci }} + BATTERY: ${{ needs.scope.outputs.battery }} + CLS: ${{ needs.scope.outputs.cls }} CI: ${{ needs.ci.result }} PERF: ${{ needs.perf.result }} + E2E: ${{ needs.e2e.result }} run: | - echo "### Release gate: ci=$CI perf-budget=$PERF (class ${{ needs.scope.outputs.cls }}: ${{ needs.scope.outputs.reason }})" >> "$GITHUB_STEP_SUMMARY" - if [ "$GATE_CI" = true ] && { [ "$CI" != success ] || [ "$PERF" != success ]; }; then exit 1; fi + echo "### Release gate: ci=$CI perf-budget=$PERF battery=$BATTERY (class $CLS: ${{ needs.scope.outputs.reason }})" >> "$GITHUB_STEP_SUMMARY" + if [ "$CLS" = major ]; then + echo "A major is not tested here (no perf budget, no battery): its planned migration runs them. Red on purpose." >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + if [ "$BATTERY" = true ] && [ "$E2E" = skipped ]; then exit 1; fi + if [ "$GATE_CI" = true ] && { [ "$CI" != success ] || { [ "$BATTERY" = true ] && [ "$PERF" != success ]; }; }; then exit 1; fi - uses: actions/upload-artifact@v4 with: name: battery-durations diff --git a/RELEASING.md b/RELEASING.md index 05b1ec285..eee7bfad0 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -60,9 +60,14 @@ plus GitHub Actions), and `.github/workflows/deps.yml` decides what happens to e runner; green there = `flaky` (reported), red there = a red. SwiftShader fails any one suite now and then, and twice on the same loaded runner too, so a same-shard retry proves nothing; - the **optional** set (red or flaky on SwiftShader with unchanged code, each with its reason on its line) - runs AFTER that and is only reported (`optional-report`); nothing waits for it. + runs only on a run started by hand (Actions → deps → Run workflow, e.g. before refreshing `battery.json`), + AFTER the rest, and is only reported (`optional-report`); nothing waits for it. + The perf budget and the battery run only for a PR that moves the root app's npm packages and is not a major; + an Actions bump, an agent (`tools/agent`) bump and a major get ci.yml alone. One battery is about 30 jobs + and 600 runner-minutes against the account's cap of 20 concurrent jobs, and Dependabot opens its monthly + round all at once (9 PRs in the first one, 2026-10-25), so everything else on the account queues behind it. One check sums it all up: **`deps-gate`**. Measured on unchanged code (2026-10-24): about an hour from push - to `deps-gate`, the optional set ~45 min more. The shard runner is `scripts/ci/battery.cjs`. + to `deps-gate` on an idle account. The shard runner is `scripts/ci/battery.cjs`. 2. **The GPU half.** `pc-battery` is a commit status posted from a desktop with a real GPU (`scripts/ci/pc-battery.sh`): /kit, the app surfaces (base shot twice for the noise floor), the level shots (head against the closer of two base runs; under 2 % of the pixels is animation) and frame time on the @@ -81,7 +86,7 @@ plus GitHub Actions), and `.github/workflows/deps.yml` decides what happens to e | patch, a development-only package, any GitHub Action | GitHub auto-merge into `deps` | | a minor of something that ships (a `dependency`, or svelte / vite / kit / tailwind / flowbite) | label `deps-ready`; the monthly deps lane merges it after the release notes + a visual review | | any Playwright bump | `deps-ready`: the e2e hosts outside GitHub must serve the same browser first | - | a major | `needs-migration` + a comment; a planned migration, never automation | + | a major | `needs-migration` + a comment; `deps-gate` red on purpose (not tested here); a planned migration, never automation | The ruleset on `deps` requires `deps-gate` and `pc-battery`, so an auto-merge of a runtime patch waits for the desktop. The ruleset also forbids deleting `deps`: it is never cleaned up with merged branches. diff --git a/scripts/ci/deps-policy.cjs b/scripts/ci/deps-policy.cjs index 4339802a6..d6419420a 100755 --- a/scripts/ci/deps-policy.cjs +++ b/scripts/ci/deps-policy.cjs @@ -13,11 +13,15 @@ // major labelled `needs-migration` with a pointer to the planned-migration notes; never merged // by automation. // +// The e2e battery (and the perf budget) run only when the PR moves the root app's npm packages and is +// not a major: an Actions bump, the agent under tools/ and a major (the migration lane runs the battery +// itself) get ci.yml alone; a major's deps-gate then fails on purpose, so nothing reads it as tested. +// // "Ships in the app" is not the same as package.json's `dependencies`: svelte, vite, kit and tailwind // are devDependencies of a static site and still decide every byte of the bundle, so they are listed // in BUILD below. A new dependency of that kind belongs on the list. // -// node scripts/ci/deps-policy.cjs '' # prints class=… needs_pc=… reason=… +// node scripts/ci/deps-policy.cjs '' # prints class=… needs_pc=… battery=… reason=… /** devDependencies that end up in (or produce) the shipped bundle */ const BUILD = [ @@ -37,15 +41,16 @@ const HOLD = /^(playwright|playwright-core|@playwright\/.*|@axe-core\/playwright /** * @typedef {{ dependencyName: string, dependencyType?: string, updateType?: string, directory?: string, packageEcosystem?: string }} Dep * @param {Dep[]} deps - * @returns {{ cls: 'auto'|'ready'|'major', needsPc: boolean, reason: string }} + * @returns {{ cls: 'auto'|'ready'|'major', needsPc: boolean, battery: boolean, reason: string }} */ function classify(deps) { - if (!deps.length) return { cls: 'ready', needsPc: true, reason: 'no dependency metadata: a person decides' }; + if (!deps.length) return { cls: 'ready', needsPc: true, battery: true, reason: 'no dependency metadata: a person decides' }; const rank = { auto: 0, ready: 1, major: 2 }; /** @type {'auto'|'ready'|'major'} */ let cls = 'auto'; const why = []; let needsPc = false; + let app = false; for (const d of deps) { const name = d.dependencyName; const eco = d.packageEcosystem || 'npm_and_yarn'; @@ -53,9 +58,10 @@ function classify(deps) { const up = (d.updateType || '').replace('version-update:semver-', ''); const actions = eco === 'github_actions' || eco === 'github-actions'; // app = the root package (the shipped site); the agent under tools/ is a separate CLI - const app = !actions && dir === '/'; - const runtime = !actions && (d.dependencyType === 'direct:production' || d.dependencyType === 'indirect' || (app && BUILD.includes(name))); - if (app && (runtime || d.dependencyType === 'indirect')) needsPc = true; + const inApp = !actions && dir === '/'; + if (inApp) app = true; + const runtime = !actions && (d.dependencyType === 'direct:production' || d.dependencyType === 'indirect' || (inApp && BUILD.includes(name))); + if (inApp && (runtime || d.dependencyType === 'indirect')) needsPc = true; /** @type {'auto'|'ready'|'major'} */ let c; if (actions) c = 'auto'; @@ -68,13 +74,13 @@ function classify(deps) { if (c !== 'auto') why.push(`${name} ${up || '?'}${HOLD.test(name) ? ' (e2e hosts pin it)' : runtime ? ' (ships)' : ''}`); } const reason = cls === 'auto' ? 'patch / development-only / actions' : why.join(', '); - return { cls, needsPc, reason }; + return { cls, needsPc, battery: app && cls !== 'major', reason }; } if (require.main === module) { const raw = process.argv[2] || process.env.DEPS_JSON || '[]'; const r = classify(JSON.parse(raw)); - const line = `class=${r.cls}\nneeds_pc=${r.needsPc}\nreason=${r.reason}\n`; + const line = `class=${r.cls}\nneeds_pc=${r.needsPc}\nbattery=${r.battery}\nreason=${r.reason}\n`; if (process.env.GITHUB_OUTPUT) require('fs').appendFileSync(process.env.GITHUB_OUTPUT, line); process.stdout.write(line); } diff --git a/tests/e2e/battery.json b/tests/e2e/battery.json index 8dd6ad5a8..2a5046bfb 100644 --- a/tests/e2e/battery.json +++ b/tests/e2e/battery.json @@ -159,6 +159,10 @@ "quality-phone": "red on the GitHub runner (SwiftShader, no retry) with unchanged code: deps-auto run 5, 2026-10-24", "quality-toast-preset": "flaky on the GitHub runner (SwiftShader): red or green-on-retry in deps-auto run 5 or 6 with unchanged code, 2026-10-24", "scatter-colliders": "red on the GitHub runner (SwiftShader, no retry) with unchanged code: deps-auto run 5, 2026-10-24", + "scatter-convert": "red on the GitHub runner (SwiftShader) twice, its shard + a fresh confirm runner, on released 1.43.2 code: deps-auto run 12, 2026-10-25 (peer B never gets the terrain)", + "scatter-edit": "red on the GitHub runner (SwiftShader) on released 1.43.2 code: deps-auto run 12 (shard; green on confirm) and run 13 (shard + both confirm tries), 2026-10-25 (peer B never gets the terrain)", + "scatter-select": "red on the GitHub runner (SwiftShader) twice, its shard + a fresh confirm runner, on released 1.43.2 code: deps-auto run 12, 2026-10-25 (peer B never gets the terrain)", + "scatter-sources": "red on the GitHub runner (SwiftShader) twice, its shard + a fresh confirm runner, on released 1.43.2 code: deps-auto run 12, 2026-10-25 (600 s suite timeout, all checks reached green)", "scene-budget": "red on the unchanged base too (deps-1 A/B on 1f3b9dd5, 2026-10-23)", "scene-open-guard": "flaky on the GitHub runner (SwiftShader): red or green-on-retry in deps-auto run 5 or 6 with unchanged code, 2026-10-24", "scene-physics-state": "red on the GitHub runner (SwiftShader, no retry) with unchanged code: deps-auto run 5, 2026-10-24", @@ -230,8 +234,8 @@ "agent-bridge": "agent" }, "suites": { - "a11y-axe": 129, - "a11y-reader": 79, + "a11y-axe": 127, + "a11y-reader": 78, "a11y-scale": 258, "agent-bridge": 48, "ai-flow-physics": 118, @@ -244,21 +248,21 @@ "aquarium-reef": 48, "arch-primitives": 98, "auto-quality-notice": 60, - "autosave-object-flows": 23, - "avatar-customise": 423, + "autosave-object-flows": 26, + "avatar-customise": 420, "avatar-feet": 48, "avatar-knocked": 140, "avatars-rigged": 52, "behaviours": 384, - "budget-chip-place": 354, - "budget-meter": 272, + "budget-chip-place": 369, + "budget-meter": 216, "burger-project-actions": 193, "bvh-picking": 28, "camera-pip": 110, - "camera-rig": 175, + "camera-rig": 317, "cc0-asset-browser": 1862, "char-controller": 116, - "character-studio": 243, + "character-studio": 245, "chat-v2": 70, "code-node": 64, "code-sidebars": 118, @@ -269,33 +273,33 @@ "collider-trimesh": 270, "collider-viz": 20, "color-picker-15c": 38, - "command-palette": 122, + "command-palette": 158, "community-seams": 210, "connect-decision": 812, - "connect-overlay": 16, - "connect-states": 133, + "connect-overlay": 15, + "connect-states": 131, "console-hygiene": 22, "context-menu-overflow": 15, "context-menu-redesign": 117, "context-menu-v2": 17, - "contrast-a2": 94, - "controls-clusters-44": 562, - "controls-clusters-45": 739, + "contrast-a2": 124, + "controls-clusters-44": 578, + "controls-clusters-45": 716, "controls-connect-pin-44": 239, "controls-move-touch-44": 95, - "controls-roster": 650, + "controls-roster": 717, "controls-sidebars-44": 206, "diagnostics": 48, "dispose": 35, "dock-add-registry": 62, - "dock-chrome": 158, - "dock-chrome-40": 174, + "dock-chrome": 116, + "dock-chrome-40": 172, "dock-empty-focus": 280, - "dock-float-exclusivity": 22, + "dock-float-exclusivity": 19, "dock-hosted-windows": 365, "dock-inset": 112, "dock-sidebar-inset": 20, - "dock-split": 357, + "dock-split": 350, "dock-strip-drop": 458, "dock-strip-empty": 79, "dock-switch-flash": 58, @@ -304,24 +308,24 @@ "drawer-layering": 138, "edit-move-games": 270, "editor-modes": 84, - "editor-ui-33": 109, + "editor-ui-33": 155, "embed-boot": 48, - "env-preset-broadcast": 189, + "env-preset-broadcast": 167, "environment": 362, "error-reports": 60, - "explorer": 176, - "explorer-delete-confirm": 310, - "explorer-drag-fixes": 366, + "explorer": 175, + "explorer-delete-confirm": 311, + "explorer-drag-fixes": 367, "explorer-drop": 172, "explorer-header-panels": 181, - "explorer-keyboard": 27, - "explorer-multiselect": 383, - "explorer-phone": 52, + "explorer-keyboard": 26, + "explorer-multiselect": 439, + "explorer-phone": 32, "explorer-storage": 608, "explorer-views": 305, - "export-presets": 301, + "export-presets": 288, "export-zip": 2, - "external-font-cdn": 18, + "external-font-cdn": 19, "fb-water": 80, "fb-water-phone": 260, "fb-water-sync": 315, @@ -331,16 +335,16 @@ "floating-window-focus": 77, "flow-debugger": 74, "flow-dock-toggle": 24, - "flow-impact-static": 24, - "flow-nodes-core": 90, - "flow-object-graphs": 204, + "flow-impact-static": 25, + "flow-nodes-core": 70, + "flow-object-graphs": 203, "flow-palette-touch": 24, "flow-physics-actions": 120, "flow-physics-collider": 78, "flow-physics-nodes": 120, "flow-runtime": 146, "flow-socket-colors": 25, - "flow-typed-sockets": 7, + "flow-typed-sockets": 8, "flow-unknown-node": 41, "fluid-diorama": 140, "fluid-emitter": 92, @@ -349,7 +353,7 @@ "fly-shortcut-keys": 34, "focus-mode": 161, "focus-pip": 190, - "focus-pip-toggle": 58, + "focus-pip-toggle": 56, "game-audio": 24, "game-dungeon-realms": 142, "game-escape-room": 60, @@ -361,8 +365,8 @@ "game-menu-layout": 60, "game-mini-golf": 114, "game-race": 96, - "game-shell": 117, - "game-shell-games": 164, + "game-shell": 118, + "game-shell-games": 583, "game-sky-run": 94, "game-stars-room": 84, "game-state": 136, @@ -371,11 +375,11 @@ "game-untangle": 86, "game-waves": 106, "gamepad-input": 102, - "gltf-export-selection": 17, + "gltf-export-selection": 22, "hdri": 301, "heic-import": 251, "hotkeys-layout": 61, - "hud-core": 55, + "hud-core": 50, "hud-editor-wysiwyg": 60, "hud-kinds": 48, "hud-picker": 41, @@ -385,9 +389,9 @@ "import-button": 56, "import-decimate": 54, "import-optimize": 639, - "import-small-skip": 37, - "ingest-gate": 87, - "inspector": 75, + "import-small-skip": 35, + "ingest-gate": 85, + "inspector": 93, "interact-clean-view": 18, "invite-link-live": 295, "join-result": 324, @@ -397,9 +401,9 @@ "kit-instancing": 139, "knock-node": 104, "knock-physics": 216, - "ktx2-twins": 167, - "late-joiner-floor": 130, - "layout": 101, + "ktx2-twins": 231, + "late-joiner-floor": 167, + "layout": 116, "lock-dragrow": 216, "lock-dragrow-sync": 58, "lock-objectlist": 108, @@ -409,7 +413,7 @@ "lock-settings-b": 894, "lock-shortcuts": 86, "lock-ux": 180, - "lod": 39, + "lod": 38, "lod-group": 20, "lod-panel": 56, "lod-real-packs": 0, @@ -417,15 +421,15 @@ "material-presets": 534, "material-presets-big": 292, "measure-tool": 86, - "menu-keyboard-tab": 44, + "menu-keyboard-tab": 43, "menu-mobile-scroll": 98, "mesh-boolean": 174, "mesh-knife": 138, "mesh-sculpt": 17, "mesh-toolbox-redesign": 242, "mobile-editors": 75, - "mobile-hud": 20, - "mobile-map": 87, + "mobile-hud": 19, + "mobile-map": 86, "mobile-ui-fixes": 149, "modal-layering": 35, "modal-reopen": 22, @@ -443,35 +447,35 @@ "net-peer-id": 116, "net-stress": 120, "network-disconnect": 152, - "node-breadcrumb": 145, + "node-breadcrumb": 144, "node-ux-groups": 1059, - "node-ux-keymap": 96, - "node-ux-keys": 204, - "node-ux-notes": 278, - "notes-phone-drillin": 107, + "node-ux-keymap": 98, + "node-ux-keys": 205, + "node-ux-notes": 277, + "notes-phone-drillin": 104, "notes-v2": 275, "number-fields": 199, "object-delete": 43, "object-list-keys": 106, "object-list-modules": 68, - "overload-guard": 55, + "overload-guard": 60, "pack-refs": 42, - "packs": 76, - "packs-drop": 40, - "panel-deeplinks": 260, - "panel-shortcuts": 177, - "panel-toggle-keys": 82, + "packs": 70, + "packs-drop": 41, + "panel-deeplinks": 259, + "panel-shortcuts": 179, + "panel-toggle-keys": 70, "panels": 120, "particles-fx": 100, - "particles-impact": 46, - "peers-panel-41b": 52, + "particles-impact": 47, + "peers-panel-41b": 43, "peers-popover": 18, "perf-beacon": 138, "perf-governor": 56, "perf-recorder": 26, "phone-bar-41b": 55, "phone-bar-42": 136, - "phone-dock-tabs": 119, + "phone-dock-tabs": 118, "phone-save-options": 41, "phone-sheets": 128, "phone-sidebars": 137, @@ -481,18 +485,18 @@ "physics-colliders": 108, "physics-discoverability": 120, "physics-ground-bounds": 226, - "physics-joints": 186, + "physics-joints": 160, "physics-kinematic": 120, "place-drag": 56, "place-drag-cache": 460, - "place-drag-extras": 160, + "place-drag-extras": 158, "placeholders": 98, "play-interact": 191, "play-menu-mode": 74, "play-mode-menu": 53, - "player-access": 77, + "player-access": 80, "pool-toys-waterline": 42, - "post-backends": 28, + "post-backends": 29, "prefabs": 286, "profiler-sidebars": 148, "profiler-themes": 46, @@ -500,11 +504,11 @@ "project-open-import": 92, "quality-phone": 28, "quality-toast-preset": 70, - "race-vr-shadows": 125, - "race-vr-shadows-iwer": 119, + "race-vr-shadows": 123, + "race-vr-shadows-iwer": 121, "reset-windows": 21, "resize-observer-loop": 468, - "ro-loop-guard": 633, + "ro-loop-guard": 623, "roadmap-13-ui-polish": 167, "runtime-resilience": 46, "scatter-colliders": 86, @@ -512,23 +516,23 @@ "scatter-edit": 256, "scatter-select": 302, "scatter-sources": 126, - "scatter-surface": 252, + "scatter-surface": 238, "scene-budget": 14, "scene-open-guard": 240, - "scene-physics-state": 108, + "scene-physics-state": 80, "scene-post": 120, "scene-post-effects": 120, - "scene-post-ui": 149, + "scene-post-ui": 119, "scene-switch-keep": 626, "scene-versions-in-file": 265, - "script-nodes": 448, + "script-nodes": 263, "scrub-undo": 116, "search-menus": 120, "select-41": 114, "select-inside-36": 38, - "settings-autorestore-colors": 95, + "settings-autorestore-colors": 96, "settings-inventory": 1042, - "settings-inventory-b": 519, + "settings-inventory-b": 739, "settings-inventory-c": 26, "settings-inventory-d": 302, "settings-keeps-sidebar": 111, @@ -536,40 +540,40 @@ "settings-layout": 460, "settings-narrow": 243, "settings-narrow-devices": 178, - "settings-row-fit": 137, + "settings-row-fit": 136, "settings-search-36": 125, - "settings-shell": 58, + "settings-shell": 321, "settings-toasts-ux": 41, "shader-compile": 0, - "shader-graph": 93, - "shader-post-domain": 75, + "shader-graph": 90, + "shader-post-domain": 77, "shader-sync": 120, - "shader-window": 141, + "shader-window": 142, "sheet-handoff": 182, "shortcut-rebind": 159, - "shortcut-warnings": 41, - "sidebar-any-window": 387, + "shortcut-warnings": 40, + "sidebar-any-window": 370, "sidebar-drop-targets": 34, - "sidebar-full-height": 109, + "sidebar-full-height": 136, "sidebar-lock": 134, "sidebar-plus-empty": 264, "sidebar-reorg": 41, "sidebar-resize-follow": 42, "sidebar-swap-motion": 20, "sidebar-tabs": 52, - "signaling-reconnect": 57, + "signaling-reconnect": 82, "sim-demos": 154, - "sim-fluid": 166, - "sim-join-pose": 150, + "sim-fluid": 168, + "sim-join-pose": 171, "sim-on-play": 29, - "sim-water": 67, + "sim-water": 65, "sound-node": 120, "sticky-panel-header": 173, "storage-hardening": 238, "strip-follow-41b": 26, - "tab-group-geometry": 42, + "tab-group-geometry": 43, "tab-group-stacking": 51, - "tab-strip-zorder": 19, + "tab-strip-zorder": 22, "tank-water-splash": 70, "templates-modal": 306, "terrain-carve": 295, @@ -577,63 +581,63 @@ "terrain-procedural": 460, "terrain-sculpt": 95, "text-selection": 16, - "theme-import": 22, - "theme-text-sweep": 363, + "theme-import": 20, + "theme-text-sweep": 361, "theme-tokens": 10, "themed-select": 24, "themes": 120, - "third-party-notices": 34, + "third-party-notices": 29, "throw-peer": 344, "throw-velocity": 45, "timeline": 132, "timeline-export": 120, "toast-to-modal": 74, - "toolbar-beside-logo-43": 507, - "toolbar-connect-43": 581, - "toolbar-edit-positions": 966, - "toolbar-more-menu-43": 568, + "toolbar-beside-logo-43": 479, + "toolbar-connect-43": 523, + "toolbar-edit-positions": 960, + "toolbar-more-menu-43": 573, "toolbar-overflow-43": 260, "toolbar-position": 520, - "toolbar-reload-order": 866, - "toolbar-rightclick-43": 830, + "toolbar-reload-order": 1128, + "toolbar-rightclick-43": 837, "toolbar-sidebars-43": 934, "toolbar-slots-42": 633, "toolbar-topright-43": 285, - "toolbox-window": 115, + "toolbox-window": 112, "touch-actions": 192, - "touch-tools": 323, + "touch-tools": 333, "tours": 487, - "tpscene": 119, + "tpscene": 113, "trigger-log-sync": 154, "troika-fonts": 14, "ui-fixes-15lmno": 116, "ui-kit": 13, "undo": 148, "undo-toast": 752, - "union-47": 208, - "union-48": 117, + "union-47": 225, + "union-48": 121, "union-49": 22, - "union-53": 73, - "unsaved-toast-stable": 182, - "user-modules": 338, - "uv-editor": 43, - "uv-paint": 135, + "union-53": 81, + "unsaved-toast-stable": 152, + "user-modules": 303, + "uv-editor": 38, + "uv-paint": 162, "viewport-scale": 102, - "voice-ptt": 65, + "voice-ptt": 69, "vr-comfort-iwer": 88, - "vr-game-panel": 43, + "vr-game-panel": 41, "vr-grab": 12, "vr-hud": 11, "vr-inset-confirm": 9, "vr-iwer": 44, - "vr-locomotion": 23, + "vr-locomotion": 22, "vr-panel-sharpness": 11, "vr-panels-v3": 10, "vr-radial-menu": 35, "vr-settings-36": 155, "vr-settings-immediacy": 8, "vr-settings-panel": 12, - "vr-snapturn-mirror": 16, + "vr-snapturn-mirror": 17, "vr-teleport": 13, "vr-world": 36, "water": 68, @@ -642,19 +646,19 @@ "whats-new": 238, "whats-new-cloud": 25, "window-body-drag": 310, - "window-bottom-clamp": 552, - "window-chrome": 69, + "window-bottom-clamp": 579, + "window-chrome": 74, "window-dock-menu": 322, - "window-groups-touch": 135, + "window-groups-touch": 136, "window-header-ranking": 247, - "window-leftovers-48": 212, + "window-leftovers-48": 172, "window-over-dock": 127, "window-place-free": 518, "window-size-clamp": 64, "window-tabs": 285, "window-tabs-move": 25, - "wire-hardening": 16, + "wire-hardening": 14, "workspace-layouts": 193, - "z-scale-layering": 120 + "z-scale-layering": 117 } } diff --git a/tests/unit/depsCi.test.js b/tests/unit/depsCi.test.js index 985ea1a54..64af4f546 100644 --- a/tests/unit/depsCi.test.js +++ b/tests/unit/depsCi.test.js @@ -39,6 +39,15 @@ describe('deps-policy: one class per PR, the most careful part wins', () => { expect(classify([dep('ws', 'direct:production', 'patch', '/tools/agent')])).toMatchObject({ cls: 'auto', needsPc: false }); expect(classify([]).cls).toBe('ready'); }); + it('runs the battery only for a non-major change to the app root', () => { + expect(classify([dep('three', 'direct:production', 'patch')]).battery).toBe(true); + expect(classify([dep('prettier', 'direct:development', 'minor')]).battery).toBe(true); + expect(classify([dep('@sveltejs/kit', 'direct:development', 'major')]).battery).toBe(false); + expect(classify([dep('actions/checkout', 'direct:production', 'minor', '/', 'github_actions')]).battery).toBe(false); + expect(classify([dep('ws', 'direct:production', 'minor', '/tools/agent')]).battery).toBe(false); + expect(classify([dep('ws', 'direct:production', 'patch', '/tools/agent'), dep('three', 'direct:production', 'patch')]).battery).toBe(true); + expect(classify([]).battery).toBe(true); + }); }); /** @param {Record} suites @param {Record} [skip] */