From c001421b53816664ee5ae6c7ec6d0ca6700eb78d Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Sat, 24 Oct 2026 18:06:36 +0300 Subject: [PATCH 01/10] =?UTF-8?q?[docs]=20deps-auto=203g:=20pc-battery=20?= =?UTF-8?q?=E2=80=94=20PC=5FBATTERY=5FWRAP=20has=20to=20be=20quoted=20in?= =?UTF-8?q?=20a=20systemd=20unit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unquoted, Environment=PC_BATTERY_WRAP=e2e-slot --exclusive ... is split at the spaces (found installing the timer on the desktop). Co-Authored-By: Claude Opus 5.5 (1M context) --- scripts/ci/pc-battery.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/ci/pc-battery.sh b/scripts/ci/pc-battery.sh index ab46729d..a350bc80 100755 --- a/scripts/ci/pc-battery.sh +++ b/scripts/ci/pc-battery.sh @@ -28,7 +28,9 @@ # PC_BATTERY_PORT first of two dev-server ports (default: 5610; head = +1) # PC_BATTERY_AUTHORS extra allowed authors, space-separated # PC_BATTERY_WRAP a command prefix that serialises GPU use with other jobs on the machine, e.g. an -# exclusive e2e slot; the measuring part runs inside it (default: none) +# exclusive e2e slot; the measuring part runs inside it (default: none). In a +# systemd unit quote the whole assignment, Environment="PC_BATTERY_WRAP=a b --", +# or systemd splits it at the spaces into separate assignments # PC_BATTERY_FPS_MAX allowed head/base p50 frame-time ratio (default: 1.15) # PC_BATTERY_STALE seconds after which a `pending` status of ours counts as abandoned (default: 10800) # Needs git, gh (logged in: repo status + gist), node/npm at the repo's .nvmrc, curl, flock. From af0b3288ee20ba07043bbfa856b5a714fc116939 Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Sat, 24 Oct 2026 18:42:53 +0300 Subject: [PATCH 02/10] [fix] deps-auto 2j: shards balance on the median runner time of runs 5-10 (16 shards; replayed slowest shard 49 -> 35-38 min) - refresh --results is repeatable and takes the median per suite: a suite's time moves by half between runs on a shared runner, and the 15-shard split fitted to run 9 alone put run 10's shards anywhere from 21 to 50 min - battery.json seconds from runs 5-10 (required + optional passes); replaying runs 8 / 9 / 10 on the new split: max shard 38 / 37 / 35 min (was 49 / 38 / 49) Co-Authored-By: Claude Opus 5.5 (1M context) --- RELEASING.md | 5 +- scripts/ci/battery.cjs | 16 +- tests/e2e/battery.json | 330 ++++++++++++++++++++--------------------- 3 files changed, 177 insertions(+), 174 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 34fa79c5..05b1ec28 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -96,8 +96,9 @@ deps.yml runs the same battery and labels on them there; nothing auto-merges int **When the battery list moves** (an integrator's battery added or dropped suites), refresh it from that run: `node scripts/ci/battery.cjs refresh --list ... --summary ...` -(names come from the lists, seconds from the summaries). `--results ` takes seconds measured on the -runners instead (deps.yml's `battery-durations` artifact). A suite goes into `optional` (runs, reported, +(names come from the lists, seconds from the summaries). `--results ` (repeatable) takes seconds measured on the +runners instead, the median across the runs given (deps.yml's `battery-durations` artifact; one suite's time +moves by half between runs, so balance on several). A suite goes into `optional` (runs, reported, does not gate) only when it is red or flaky on the unchanged base too, with that reason on its line — `--red ... --reason "…"` does it from downloaded `battery-*` artifacts of runs on unchanged code; `skip` is for what a public runner cannot do at all (the self-hosted signaling box, a hosts-mapped origin, diff --git a/scripts/ci/battery.cjs b/scripts/ci/battery.cjs index ddb71bb2..9dda2ec7 100755 --- a/scripts/ci/battery.cjs +++ b/scripts/ci/battery.cjs @@ -12,7 +12,7 @@ // node scripts/ci/battery.cjs run --set required --shard 3 --of 14 # one shard (serves the app + the signaling server) // node scripts/ci/battery.cjs run --set confirm --from --shard 1 --of 3 # re-run a run's required reds // node scripts/ci/battery.cjs report # merge shard results → summary, exit 1 on a required red -// node scripts/ci/battery.cjs refresh [--list f]... [--summary f]... [--results dir] +// node scripts/ci/battery.cjs refresh [--list f]... [--summary f]... [--results dir]... // // Two sets, two matrices: the REQUIRED suites gate (deps-gate waits for them, sized to finish inside the hour); // the OPTIONAL ones run in their own shards AFTER the required ones (the free-plan runner cap is 20 jobs), are @@ -399,12 +399,14 @@ function refresh(argv) { const median = (/** @type {number[]} */ v) => v.sort((x, y) => x - y)[Math.floor(v.length / 2)]; // a desk second is about two runner seconds (measured: the e2e-smoke suites on ubuntu-latest vs the Deck) for (const [n, v] of Object.entries(desk)) if (n in b.suites) b.suites[n] = Math.round(2 * median(v)); - const results = opt(argv, '--results'); - if (results) { - for (const f of walk(path.resolve(results)).filter((f) => f.endsWith('durations.json'))) { - for (const [n, s] of Object.entries(JSON.parse(fs.readFileSync(f, 'utf8')))) if (n in b.suites) b.suites[n] = Number(s); - } - } + // --results ...: seconds measured on the runners, the MEDIAN across every durations.json found — one + // suite's time moves by half from run to run on a shared runner, so a single run balances badly + /** @type {Record} */ + const ran = {}; + for (const dir of opts(argv, '--results')) + for (const f of walk(path.resolve(dir)).filter((f) => f.endsWith('durations.json'))) + for (const [n, s] of Object.entries(JSON.parse(fs.readFileSync(f, 'utf8')))) (ran[n] ||= []).push(Number(s)); + for (const [n, v] of Object.entries(ran)) if (n in b.suites) b.suites[n] = Math.round(median(v)); // --red ... --reason "…": suites red in those runs' results (fail / timeout / flaky) become optional with // the reason — for runs on unchanged code, i.e. what this runner cannot pass reliably. --green : a suite // that passed there is never moved. diff --git a/tests/e2e/battery.json b/tests/e2e/battery.json index b17208a9..8dd6ad5a 100644 --- a/tests/e2e/battery.json +++ b/tests/e2e/battery.json @@ -230,75 +230,75 @@ "agent-bridge": "agent" }, "suites": { - "a11y-axe": 80, + "a11y-axe": 129, "a11y-reader": 79, - "a11y-scale": 257, + "a11y-scale": 258, "agent-bridge": 48, - "ai-flow-physics": 67, + "ai-flow-physics": 118, "align-arrange": 62, "animated-models": 92, - "animation-clips-resize": 241, + "animation-clips-resize": 239, "animation-curves": 281, "animation-sync": 120, - "animation-window": 24, + "animation-window": 23, "aquarium-reef": 48, "arch-primitives": 98, "auto-quality-notice": 60, - "autosave-object-flows": 19, - "avatar-customise": 231, + "autosave-object-flows": 23, + "avatar-customise": 423, "avatar-feet": 48, "avatar-knocked": 140, "avatars-rigged": 52, "behaviours": 384, - "budget-chip-place": 286, - "budget-meter": 279, + "budget-chip-place": 354, + "budget-meter": 272, "burger-project-actions": 193, "bvh-picking": 28, - "camera-pip": 108, + "camera-pip": 110, "camera-rig": 175, "cc0-asset-browser": 1862, "char-controller": 116, - "character-studio": 242, + "character-studio": 243, "chat-v2": 70, "code-node": 64, "code-sidebars": 118, - "collider-custom": 20, + "collider-custom": 19, "collider-decompose": 244, "collider-defaults": 24, "collider-live": 120, "collider-trimesh": 270, "collider-viz": 20, "color-picker-15c": 38, - "command-palette": 106, + "command-palette": 122, "community-seams": 210, "connect-decision": 812, - "connect-overlay": 11, - "connect-states": 74, + "connect-overlay": 16, + "connect-states": 133, "console-hygiene": 22, "context-menu-overflow": 15, "context-menu-redesign": 117, - "context-menu-v2": 16, - "contrast-a2": 75, - "controls-clusters-44": 389, - "controls-clusters-45": 749, - "controls-connect-pin-44": 164, - "controls-move-touch-44": 96, - "controls-roster": 558, - "controls-sidebars-44": 129, + "context-menu-v2": 17, + "contrast-a2": 94, + "controls-clusters-44": 562, + "controls-clusters-45": 739, + "controls-connect-pin-44": 239, + "controls-move-touch-44": 95, + "controls-roster": 650, + "controls-sidebars-44": 206, "diagnostics": 48, - "dispose": 36, - "dock-add-registry": 63, + "dispose": 35, + "dock-add-registry": 62, "dock-chrome": 158, - "dock-chrome-40": 129, + "dock-chrome-40": 174, "dock-empty-focus": 280, - "dock-float-exclusivity": 17, - "dock-hosted-windows": 368, - "dock-inset": 83, + "dock-float-exclusivity": 22, + "dock-hosted-windows": 365, + "dock-inset": 112, "dock-sidebar-inset": 20, - "dock-split": 358, - "dock-strip-drop": 533, + "dock-split": 357, + "dock-strip-drop": 458, "dock-strip-empty": 79, - "dock-switch-flash": 38, + "dock-switch-flash": 58, "dock-tab-drag": 246, "docking": 242, "drawer-layering": 138, @@ -306,48 +306,48 @@ "editor-modes": 84, "editor-ui-33": 109, "embed-boot": 48, - "env-preset-broadcast": 99, + "env-preset-broadcast": 189, "environment": 362, "error-reports": 60, "explorer": 176, - "explorer-delete-confirm": 315, + "explorer-delete-confirm": 310, "explorer-drag-fixes": 366, - "explorer-drop": 163, + "explorer-drop": 172, "explorer-header-panels": 181, "explorer-keyboard": 27, - "explorer-multiselect": 231, + "explorer-multiselect": 383, "explorer-phone": 52, "explorer-storage": 608, - "explorer-views": 298, - "export-presets": 198, + "explorer-views": 305, + "export-presets": 301, "export-zip": 2, - "external-font-cdn": 17, + "external-font-cdn": 18, "fb-water": 80, "fb-water-phone": 260, - "fb-water-sync": 328, + "fb-water-sync": 315, "field-clipboard": 114, "file-preview": 459, - "files-format-row": 340, - "floating-window-focus": 66, + "files-format-row": 181, + "floating-window-focus": 77, "flow-debugger": 74, "flow-dock-toggle": 24, - "flow-impact-static": 16, + "flow-impact-static": 24, "flow-nodes-core": 90, - "flow-object-graphs": 207, - "flow-palette-touch": 18, + "flow-object-graphs": 204, + "flow-palette-touch": 24, "flow-physics-actions": 120, "flow-physics-collider": 78, "flow-physics-nodes": 120, "flow-runtime": 146, - "flow-socket-colors": 20, - "flow-typed-sockets": 8, - "flow-unknown-node": 32, + "flow-socket-colors": 25, + "flow-typed-sockets": 7, + "flow-unknown-node": 41, "fluid-diorama": 140, "fluid-emitter": 92, "fluid-flows": 94, "fluid-gpu": 42, "fly-shortcut-keys": 34, - "focus-mode": 163, + "focus-mode": 161, "focus-pip": 190, "focus-pip-toggle": 58, "game-audio": 24, @@ -371,38 +371,38 @@ "game-untangle": 86, "game-waves": 106, "gamepad-input": 102, - "gltf-export-selection": 16, + "gltf-export-selection": 17, "hdri": 301, - "heic-import": 284, - "hotkeys-layout": 57, - "hud-core": 39, + "heic-import": 251, + "hotkeys-layout": 61, + "hud-core": 55, "hud-editor-wysiwyg": 60, "hud-kinds": 48, - "hud-picker": 42, - "hud-play-keyboard": 27, + "hud-picker": 41, + "hud-play-keyboard": 40, "hud-sync": 286, "image-editor": 140, "import-button": 56, "import-decimate": 54, - "import-optimize": 567, - "import-small-skip": 28, + "import-optimize": 639, + "import-small-skip": 37, "ingest-gate": 87, - "inspector": 97, - "interact-clean-view": 22, - "invite-link-live": 341, + "inspector": 75, + "interact-clean-view": 18, + "invite-link-live": 295, "join-result": 324, "joints-fx": 183, "keyboard-walk": 48, "kit-core": 74, "kit-instancing": 139, "knock-node": 104, - "knock-physics": 218, - "ktx2-twins": 164, - "late-joiner-floor": 89, - "layout": 71, + "knock-physics": 216, + "ktx2-twins": 167, + "late-joiner-floor": 130, + "layout": 101, "lock-dragrow": 216, "lock-dragrow-sync": 58, - "lock-objectlist": 68, + "lock-objectlist": 108, "lock-panels": 206, "lock-search": 320, "lock-settings": 398, @@ -415,68 +415,68 @@ "lod-real-packs": 0, "logic-nodes": 198, "material-presets": 534, - "material-presets-big": 196, + "material-presets-big": 292, "measure-tool": 86, - "menu-keyboard-tab": 32, + "menu-keyboard-tab": 44, "menu-mobile-scroll": 98, "mesh-boolean": 174, "mesh-knife": 138, "mesh-sculpt": 17, "mesh-toolbox-redesign": 242, "mobile-editors": 75, - "mobile-hud": 19, - "mobile-map": 88, - "mobile-ui-fixes": 147, - "modal-layering": 25, + "mobile-hud": 20, + "mobile-map": 87, + "mobile-ui-fixes": 149, + "modal-layering": 35, "modal-reopen": 22, - "model-loader": 53, + "model-loader": 96, "model-preview": 120, - "module-gallery": 65, + "module-gallery": 120, "module-lifecycle": 176, "module-press-fanin": 20, - "module-toolbox": 117, + "module-toolbox": 170, "modules-manager": 72, "modules-phone-jobs": 33, - "move-wire-shape": 77, + "move-wire-shape": 88, "multiselect-v2": 706, "net-mesh": 8, - "net-peer-id": 78, + "net-peer-id": 116, "net-stress": 120, "network-disconnect": 152, "node-breadcrumb": 145, "node-ux-groups": 1059, - "node-ux-keymap": 124, - "node-ux-keys": 206, + "node-ux-keymap": 96, + "node-ux-keys": 204, "node-ux-notes": 278, - "notes-phone-drillin": 133, - "notes-v2": 422, - "number-fields": 203, + "notes-phone-drillin": 107, + "notes-v2": 275, + "number-fields": 199, "object-delete": 43, - "object-list-keys": 116, + "object-list-keys": 106, "object-list-modules": 68, - "overload-guard": 35, + "overload-guard": 55, "pack-refs": 42, - "packs": 49, - "packs-drop": 27, + "packs": 76, + "packs-drop": 40, "panel-deeplinks": 260, - "panel-shortcuts": 198, - "panel-toggle-keys": 56, + "panel-shortcuts": 177, + "panel-toggle-keys": 82, "panels": 120, "particles-fx": 100, - "particles-impact": 48, - "peers-panel-41b": 35, + "particles-impact": 46, + "peers-panel-41b": 52, "peers-popover": 18, "perf-beacon": 138, "perf-governor": 56, "perf-recorder": 26, "phone-bar-41b": 55, - "phone-bar-42": 137, + "phone-bar-42": 136, "phone-dock-tabs": 119, "phone-save-options": 41, - "phone-sheets": 120, - "phone-sidebars": 140, - "phone-stats-tab": 26, - "physics": 168, + "phone-sheets": 128, + "phone-sidebars": 137, + "phone-stats-tab": 28, + "physics": 157, "physics-ccd-object": 350, "physics-colliders": 108, "physics-discoverability": 120, @@ -485,28 +485,28 @@ "physics-kinematic": 120, "place-drag": 56, "place-drag-cache": 460, - "place-drag-extras": 157, + "place-drag-extras": 160, "placeholders": 98, - "play-interact": 201, + "play-interact": 191, "play-menu-mode": 74, "play-mode-menu": 53, - "player-access": 76, + "player-access": 77, "pool-toys-waterline": 42, - "post-backends": 29, + "post-backends": 28, "prefabs": 286, "profiler-sidebars": 148, "profiler-themes": 46, "profiler-ui": 56, "project-open-import": 92, "quality-phone": 28, - "quality-toast-preset": 69, - "race-vr-shadows": 123, + "quality-toast-preset": 70, + "race-vr-shadows": 125, "race-vr-shadows-iwer": 119, - "reset-windows": 25, - "resize-observer-loop": 446, + "reset-windows": 21, + "resize-observer-loop": 468, "ro-loop-guard": 633, "roadmap-13-ui-polish": 167, - "runtime-resilience": 31, + "runtime-resilience": 46, "scatter-colliders": 86, "scatter-convert": 178, "scatter-edit": 256, @@ -518,7 +518,7 @@ "scene-physics-state": 108, "scene-post": 120, "scene-post-effects": 120, - "scene-post-ui": 153, + "scene-post-ui": 149, "scene-switch-keep": 626, "scene-versions-in-file": 265, "script-nodes": 448, @@ -526,59 +526,59 @@ "search-menus": 120, "select-41": 114, "select-inside-36": 38, - "settings-autorestore-colors": 99, - "settings-inventory": 1011, - "settings-inventory-b": 36, + "settings-autorestore-colors": 95, + "settings-inventory": 1042, + "settings-inventory-b": 519, "settings-inventory-c": 26, "settings-inventory-d": 302, - "settings-keeps-sidebar": 72, - "settings-labels": 81, + "settings-keeps-sidebar": 111, + "settings-labels": 80, "settings-layout": 460, - "settings-narrow": 138, - "settings-narrow-devices": 179, - "settings-row-fit": 166, - "settings-search-36": 124, + "settings-narrow": 243, + "settings-narrow-devices": 178, + "settings-row-fit": 137, + "settings-search-36": 125, "settings-shell": 58, - "settings-toasts-ux": 30, + "settings-toasts-ux": 41, "shader-compile": 0, - "shader-graph": 55, - "shader-post-domain": 72, + "shader-graph": 93, + "shader-post-domain": 75, "shader-sync": 120, - "shader-window": 89, - "sheet-handoff": 214, + "shader-window": 141, + "sheet-handoff": 182, "shortcut-rebind": 159, - "shortcut-warnings": 27, + "shortcut-warnings": 41, "sidebar-any-window": 387, "sidebar-drop-targets": 34, "sidebar-full-height": 109, - "sidebar-lock": 125, - "sidebar-plus-empty": 185, - "sidebar-reorg": 42, + "sidebar-lock": 134, + "sidebar-plus-empty": 264, + "sidebar-reorg": 41, "sidebar-resize-follow": 42, "sidebar-swap-motion": 20, "sidebar-tabs": 52, - "signaling-reconnect": 47, - "sim-demos": 107, - "sim-fluid": 209, + "signaling-reconnect": 57, + "sim-demos": 154, + "sim-fluid": 166, "sim-join-pose": 150, - "sim-on-play": 28, - "sim-water": 73, + "sim-on-play": 29, + "sim-water": 67, "sound-node": 120, "sticky-panel-header": 173, "storage-hardening": 238, "strip-follow-41b": 26, - "tab-group-geometry": 53, + "tab-group-geometry": 42, "tab-group-stacking": 51, - "tab-strip-zorder": 15, + "tab-strip-zorder": 19, "tank-water-splash": 70, "templates-modal": 306, - "terrain-carve": 199, + "terrain-carve": 295, "terrain-paint": 554, "terrain-procedural": 460, "terrain-sculpt": 95, "text-selection": 16, - "theme-import": 16, - "theme-text-sweep": 361, + "theme-import": 22, + "theme-text-sweep": 363, "theme-tokens": 10, "themed-select": 24, "themes": 120, @@ -589,20 +589,20 @@ "timeline-export": 120, "toast-to-modal": 74, "toolbar-beside-logo-43": 507, - "toolbar-connect-43": 369, - "toolbar-edit-positions": 960, - "toolbar-more-menu-43": 578, - "toolbar-overflow-43": 272, + "toolbar-connect-43": 581, + "toolbar-edit-positions": 966, + "toolbar-more-menu-43": 568, + "toolbar-overflow-43": 260, "toolbar-position": 520, - "toolbar-reload-order": 1138, - "toolbar-rightclick-43": 861, - "toolbar-sidebars-43": 522, + "toolbar-reload-order": 866, + "toolbar-rightclick-43": 830, + "toolbar-sidebars-43": 934, "toolbar-slots-42": 633, "toolbar-topright-43": 285, - "toolbox-window": 59, - "touch-actions": 183, - "touch-tools": 332, - "tours": 270, + "toolbox-window": 115, + "touch-actions": 192, + "touch-tools": 323, + "tours": 487, "tpscene": 119, "trigger-log-sync": 154, "troika-fonts": 14, @@ -610,31 +610,31 @@ "ui-kit": 13, "undo": 148, "undo-toast": 752, - "union-47": 161, - "union-48": 127, + "union-47": 208, + "union-48": 117, "union-49": 22, - "union-53": 53, - "unsaved-toast-stable": 214, - "user-modules": 236, + "union-53": 73, + "unsaved-toast-stable": 182, + "user-modules": 338, "uv-editor": 43, - "uv-paint": 96, + "uv-paint": 135, "viewport-scale": 102, - "voice-ptt": 47, - "vr-comfort-iwer": 89, + "voice-ptt": 65, + "vr-comfort-iwer": 88, "vr-game-panel": 43, "vr-grab": 12, "vr-hud": 11, "vr-inset-confirm": 9, "vr-iwer": 44, - "vr-locomotion": 22, + "vr-locomotion": 23, "vr-panel-sharpness": 11, - "vr-panels-v3": 9, + "vr-panels-v3": 10, "vr-radial-menu": 35, "vr-settings-36": 155, "vr-settings-immediacy": 8, "vr-settings-panel": 12, - "vr-snapturn-mirror": 18, - "vr-teleport": 10, + "vr-snapturn-mirror": 16, + "vr-teleport": 13, "vr-world": 36, "water": 68, "water-migration": 72, @@ -642,19 +642,19 @@ "whats-new": 238, "whats-new-cloud": 25, "window-body-drag": 310, - "window-bottom-clamp": 291, - "window-chrome": 79, + "window-bottom-clamp": 552, + "window-chrome": 69, "window-dock-menu": 322, - "window-groups-touch": 121, + "window-groups-touch": 135, "window-header-ranking": 247, - "window-leftovers-48": 221, - "window-over-dock": 129, - "window-place-free": 521, + "window-leftovers-48": 212, + "window-over-dock": 127, + "window-place-free": 518, "window-size-clamp": 64, - "window-tabs": 156, - "window-tabs-move": 17, - "wire-hardening": 11, - "workspace-layouts": 125, + "window-tabs": 285, + "window-tabs-move": 25, + "wire-hardening": 16, + "workspace-layouts": 193, "z-scale-layering": 120 } } From 0ae13103d96c35fe2bb27354dabafd6caae0933f Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Sun, 25 Oct 2026 07:53:22 +0200 Subject: [PATCH 03/10] [fix] deps-auto 2k: battery only where it tests something; optional set by hand - deps-policy.cjs: new `battery` output = the PR moves the root app's npm packages and is not a major - deps.yml: perf budget + plan/e2e only when battery=true (Actions, tools/agent and majors get ci.yml alone); a major's deps-gate fails on purpose ("not tested here"); the optional set runs on workflow_dispatch only - why: Dependabot's first round after 1.43.0 opened 9 PRs at once = ~380 jobs on the account's 20-job cap, queueing the v1.43.0 release job and every lane's CI for hours (runs cancelled, re-run after this) - RELEASING.md + depsCi test (9/9); check-ratchet 299/39 = baseline; actionlint clean Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/deps.yml | 36 ++++++++++++++++++++++++++---------- RELEASING.md | 11 ++++++++--- scripts/ci/deps-policy.cjs | 22 ++++++++++++++-------- tests/unit/depsCi.test.js | 9 +++++++++ 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/.github/workflows/deps.yml b/.github/workflows/deps.yml index 4ea9ad45..c9668a2f 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 }} @@ -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: always() && github.event_name == 'workflow_dispatch' && needs.plan.result == 'success' && needs.plan.outputs.optional_shards != '0' uses: ./.github/workflows/battery.yml with: set: optional @@ -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 05b1ec28..eee7bfad 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 4339802a..d6419420 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/unit/depsCi.test.js b/tests/unit/depsCi.test.js index 985ea1a5..64af4f54 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] */ From ec4a8009b72eda80b654b6d66f405fbd50218874 Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Sun, 25 Oct 2026 07:54:13 +0200 Subject: [PATCH 04/10] [fix] deps-auto 2l: a cancelled deps run stays cancelled - confirm / e2e-optional / optional-report / deps-gate used always(), which also runs after a cancel: cancelling Dependabot's 9 first-round runs still started their optional sets (18 shards each); now !cancelled() Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/deps.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deps.yml b/.github/workflows/deps.yml index c9668a2f..80742e64 100644 --- a/.github/workflows/deps.yml +++ b/.github/workflows/deps.yml @@ -138,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 @@ -152,7 +152,7 @@ jobs: # 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() && github.event_name == 'workflow_dispatch' && needs.plan.result == 'success' && 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 @@ -163,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 @@ -178,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 From 84cc55341bf151c46efc32e80acafea032a76015 Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Sun, 25 Oct 2026 11:55:36 +0200 Subject: [PATCH 05/10] [fix] deps-auto 2m: three of 51's scatter suites optional on the GitHub runner; medians from runs 5-12 - run 12 (#468, released 1.43.2 app code, CI-only diff) was the scatter suites' first run on the runner: scatter-select + scatter-convert red twice (shard + fresh confirm runner: "B has the terrain" never true), scatter-sources at the 600 s suite timeout twice (all its checks green until then) -> optional with reasons - scatter-edit was flaky (green on the confirm runner) and stays required - refresh --results over runs 5-12 (one durations file per run): 16 required shards ~35 min each Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/e2e/battery.json | 239 +++++++++++++++++++++-------------------- 1 file changed, 121 insertions(+), 118 deletions(-) diff --git a/tests/e2e/battery.json b/tests/e2e/battery.json index 8dd6ad5a..537ac3ef 100644 --- a/tests/e2e/battery.json +++ b/tests/e2e/battery.json @@ -159,6 +159,9 @@ "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-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 +233,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 +247,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 +272,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 +307,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 +334,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 +352,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 +364,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 +374,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 +388,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 +400,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 +412,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 +420,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 +446,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 +484,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 +503,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 +515,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 +539,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 +580,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 +645,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 } } From bce6d52431e9f38f7092e719b26767368d421582 Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Sun, 25 Oct 2026 13:07:16 +0200 Subject: [PATCH 06/10] [fix] deps-auto 2n: scatter-edit optional on the GitHub runner too - run 13 (#468): scatter-edit red on its shard and both confirm tries, the same 'B has the terrain' as scatter-select/convert in run 12 (where it was green on confirm); scatter-surface flaky, stays required Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/e2e/battery.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/battery.json b/tests/e2e/battery.json index 537ac3ef..2a5046bf 100644 --- a/tests/e2e/battery.json +++ b/tests/e2e/battery.json @@ -160,6 +160,7 @@ "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)", From 4025faab3898492617091ec0d2fb6fc2a947dabd Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Sun, 25 Oct 2026 14:20:43 +0200 Subject: [PATCH 07/10] [fix] deps-auto 2o: auto-merge in its own job with contents: write - #460 (actions, class auto) went green through the gate on the 2k deps.yml, then the gate's auto-merge step failed: 'Resource not accessible by integration (enablePullRequestAutoMerge)' (it needs contents: write) - a separate automerge job after a green deps-gate holds contents + pull-requests write and checks nothing out, so the gate (which runs the PR's code) stays contents: read Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/deps.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deps.yml b/.github/workflows/deps.yml index 80742e64..b599c6bb 100644 --- a/.github/workflows/deps.yml +++ b/.github/workflows/deps.yml @@ -237,9 +237,20 @@ jobs: run: | gh label create deps-ready --color 0E8A16 --description 'Green; a runtime minor or a Playwright bump waiting for the deps lane' 2>/dev/null || true gh pr edit "${{ github.event.pull_request.number }}" --add-label deps-ready + + # its own job: turning auto-merge on needs contents: write, which the gate (it checks out and runs the PR's + # code) should not hold. No checkout here; it only runs after a green deps-gate. + automerge: + needs: [scope, gate] + if: needs.scope.outputs.cls == 'auto' && github.base_ref == 'deps' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: - name: auto-merge - if: needs.scope.outputs.cls == 'auto' && github.base_ref == 'deps' env: GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} # waits for whatever the ruleset still wants (pc-battery from the desktop for a runtime patch) run: gh pr merge "${{ github.event.pull_request.number }}" --auto --squash From dde9eb6166357fea78c0105eec003712b8bda9c5 Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Sun, 25 Oct 2026 14:37:52 +0200 Subject: [PATCH 08/10] [fix] deps-auto 2p: battery.cjs run with no --deadline runs every suite - the deadline was Date.now() + 0, which is truthy, so '|| Infinity' never applied: by hand (no flag) every suite after the first came out notrun. CI always passes --deadline, so the workflows were unaffected. Found by lane scatter-sync. Co-Authored-By: Claude Opus 5.5 (1M context) --- scripts/ci/battery.cjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ci/battery.cjs b/scripts/ci/battery.cjs index 9dda2ec7..15767f9d 100755 --- a/scripts/ci/battery.cjs +++ b/scripts/ci/battery.cjs @@ -231,7 +231,8 @@ async function run(argv) { // loaded shard fails the same suite twice), and a shard without retries stays inside its estimate const retry = set === 'confirm' || argv.includes('--retry'); // past the deadline no suite starts: the rest report `notrun` instead of the job being killed with nothing said - const deadline = Date.now() + Number(opt(argv, '--deadline') || 0) * 60000 || Infinity; + const dl = Number(opt(argv, '--deadline') || 0); + const deadline = dl ? Date.now() + dl * 60000 : Infinity; const save = () => fs.writeFileSync(path.join(out, `${set === 'confirm' ? 'confirm' : 'results'}-${shard}.json`), JSON.stringify({ shard, of, set, results }, null, 1)); console.log(`shard ${shard}/${of}: ${mine.length} suites, est ${Math.round(mine.reduce((t, s) => t + (b.suites[s] || 60), 0) / 60)} min, ${par} at a time`); /** @type {Promise | null} */ From 979c60d28ee4bbd645b52fc26a09eaac490b6ffb Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Sun, 25 Oct 2026 15:41:17 +0200 Subject: [PATCH 09/10] [fix] deps-auto 2q: scatter-surface optional on the GitHub runner - red on its shard in runs 13, 14 and 16 (#473 run 16: shard + both confirm tries): the Add scatter click times out (locator.click 30 s) - a different failure from the late-joiner terrain race Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/e2e/battery.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/battery.json b/tests/e2e/battery.json index 2a5046bf..aac05f7f 100644 --- a/tests/e2e/battery.json +++ b/tests/e2e/battery.json @@ -163,6 +163,7 @@ "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)", + "scatter-surface": "red on the GitHub runner (SwiftShader) on its shard in deps-auto runs 13, 14 and 16 (green on confirm in 13/14, red on both confirm tries in 16), 2026-10-25: the Add scatter click times out (locator.click 30 s), not the terrain race", "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", From 19bf276ef3f66fc3bb609eca31e52902458eb680 Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Sun, 25 Oct 2026 16:54:15 +0200 Subject: [PATCH 10/10] [fix] deps-auto 2r: knock-physics + env-preset-broadcast optional on the GitHub runner - the two required suites that fail on their shard about half the time (runs 10-17): knock-physics (shard red in 8, 9, 12, 14, 17; run 17 also both confirm tries, '2.3 (premise) B sees the ball parked') and env-preset-broadcast (shard red in 10, 12, 13); at ~50% a run loses all three tries ~1 in 8, so each alone turned deps-gate red every few runs. The rest of the flaky set is ~25% or less Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/e2e/battery.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/e2e/battery.json b/tests/e2e/battery.json index aac05f7f..1c251560 100644 --- a/tests/e2e/battery.json +++ b/tests/e2e/battery.json @@ -50,6 +50,7 @@ "drawer-layering": "red on the GitHub runner (SwiftShader, no retry) with unchanged code: deps-auto run 5, 2026-10-24", "edit-move-games": "red on the unchanged base too (deps-1 A/B on 1f3b9dd5, 2026-10-23)", "editor-modes": "red on the GitHub runner (SwiftShader, no retry) with unchanged code: deps-auto run 5, 2026-10-24", + "env-preset-broadcast": "flaky on the GitHub runner (SwiftShader): red on its shard in deps-auto runs 10, 12 and 13 (green on confirm), 2026-10-25; a two-peer broadcast check like knock-physics", "environment": "red on the GitHub runner (SwiftShader, no retry) with unchanged code: deps-auto run 5, 2026-10-24", "explorer-drop": "flaky on the GitHub runner (SwiftShader): red or green-on-retry in deps-auto run 5 or 6 with unchanged code, 2026-10-24", "explorer-header-panels": "red on the GitHub runner in deps-auto run 7 or 8 after a retry / the fresh-runner confirm, unchanged code (two-peer replication stalls on SwiftShader), 2026-10-24", @@ -105,6 +106,7 @@ "kit-core": "red on the GitHub runner (SwiftShader, no retry) with unchanged code: deps-auto run 5, 2026-10-24", "kit-instancing": "red on the unchanged base too (deps-1 A/B on 1f3b9dd5, 2026-10-23)", "knock-node": "red on the GitHub runner (SwiftShader, no retry) with unchanged code: deps-auto run 5, 2026-10-24", + "knock-physics": "flaky on the GitHub runner (SwiftShader), about half its shards red since deps-auto run 8 (8, 9, 12, 14, 17), red on both confirm tries in run 17, 2026-10-25: \"2.3 (premise) B sees the ball parked\"", "lock-dragrow": "red on the GitHub runner (SwiftShader, no retry) with unchanged code: deps-auto run 5, 2026-10-24", "lock-dragrow-sync": "red on the GitHub runner (SwiftShader, no retry) with unchanged code: deps-auto run 5, 2026-10-24", "lock-panels": "red on the GitHub runner (SwiftShader, no retry) with unchanged code: deps-auto run 5, 2026-10-24",