Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .agents/skills/manage-ci/references/current-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,9 +397,9 @@ warmer image and toolchain epoch, so mismatched native-runtime rows are cold and
do not restore it. These four high-fanout families disable per-object GHA
publication on every provider. Exact Linux static ABI, Swift ABI, macOS Metal unit ABI,
and Windows native ABI caches may publish into GitHub's isolated PR merge-ref
scope for same-PR reruns. The Website slice is the sole publisher for the
shared pnpm key and owns the website npm cache; platform UI producers restore
the pnpm store without racing to save it. Trusted main owns shared publication.
scope for same-PR reruns. UI installs (`ui_quality`, `ui_e2e`, `ui_artifact`) point pnpm at the runner
image's baked store instead of an Actions cache — there is no shared pnpm
key or publisher to race. Trusted main owns shared publication.

PR Rust-test, host, native-runtime, product, and platform-check matrices receive
`fail_fast: true`; main/manual pass `false`. Quality matrices remain
Expand Down
28 changes: 14 additions & 14 deletions .github/workflows/ci-ui-artifact-slice.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ permissions:
contents: read
packages: read

env:
CACHE_NAMESPACE: mesh-llm

jobs:
runner_policy:
name: Select console artifact runner
Expand Down Expand Up @@ -90,17 +87,20 @@ jobs:
with:
ref: ${{ inputs.source_sha || github.sha }}
persist-credentials: false
- name: Configure isolated pnpm store
run: pnpm config set store-dir ~/.pnpm-store
- name: Restore pnpm store
id: pnpm_cache
if: ${{ needs.runner_policy.outputs.allow_native_github_cache == 'true' }}
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ~/.pnpm-store
key: ${{ env.CACHE_NAMESPACE }}-pnpm-${{ runner.os }}-${{ hashFiles('crates/mesh-llm-ui/pnpm-lock.yaml', '.github/cache-version.txt') }}
restore-keys: |
${{ env.CACHE_NAMESPACE }}-pnpm-${{ runner.os }}-
# The container image bakes a warm pnpm store at
# /home/runner/.local/share/pnpm/store (mesh-llm-runner-images
# scripts/warm-dependencies.sh). Point pnpm at it directly instead of
# the Actions cache: containerized jobs fix $HOME to /github/home, so
# neither pnpm's default store-dir resolution nor a plain HOME
# override can find the baked store from in here, and restoring
# ~/.pnpm-store from the Actions cache reported a hit but the files
# didn't land where pnpm looks for them (#1392). Using the image's
# own store sidesteps both and needs no cache round trip at all — this
# also removes the restore-with-no-publisher race this job used to
# have against ui_quality's save (three lanes restored a key only
# ui_quality ever saved).
- name: Point pnpm at the image's baked store
run: pnpm config set store-dir /home/runner/.local/share/pnpm/store
- name: Install UI dependencies
run: pnpm i --frozen-lockfile
- name: Build console distribution
Expand Down
46 changes: 15 additions & 31 deletions .github/workflows/ci-web-slice.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ permissions:
contents: read
packages: read

env:
CACHE_NAMESPACE: mesh-llm

jobs:
runner_policy:
name: Select website runner
Expand Down Expand Up @@ -89,24 +86,19 @@ jobs:
with:
ref: ${{ inputs.source_sha || github.sha }}
persist-credentials: false
- name: Configure isolated pnpm store
run: pnpm config set store-dir ~/.pnpm-store
- name: Restore pnpm store
id: pnpm_cache
if: ${{ needs.runner_policy.outputs.allow_native_github_cache == 'true' }}
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ~/.pnpm-store
key: ${{ env.CACHE_NAMESPACE }}-pnpm-${{ runner.os }}-${{ hashFiles('crates/mesh-llm-ui/pnpm-lock.yaml', '.github/cache-version.txt') }}
restore-keys: ${{ env.CACHE_NAMESPACE }}-pnpm-${{ runner.os }}-
# The container image bakes a warm pnpm store at
# /home/runner/.local/share/pnpm/store (mesh-llm-runner-images
# scripts/warm-dependencies.sh). Point pnpm at it directly instead of
# the Actions cache: containerized jobs fix $HOME to /github/home, so
# neither pnpm's default store-dir resolution nor a plain HOME
# override can find the baked store from in here, and restoring
# ~/.pnpm-store from the Actions cache reported a hit but the files
# didn't land where pnpm looks for them (#1392). Using the image's
# own store sidesteps both and needs no cache round trip at all.
- name: Point pnpm at the image's baked store
run: pnpm config set store-dir /home/runner/.local/share/pnpm/store
- name: Install UI dependencies
run: pnpm i --frozen-lockfile
- name: Save pnpm store
if: ${{ needs.runner_policy.outputs.allow_native_github_cache == 'true' && steps.pnpm_cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ~/.pnpm-store
key: ${{ steps.pnpm_cache.outputs.cache-primary-key }}
- name: Lint UI
run: pnpm run lint
- name: Type-check UI
Expand Down Expand Up @@ -136,18 +128,10 @@ jobs:
with:
ref: ${{ inputs.source_sha || github.sha }}
persist-credentials: false
- name: Configure isolated pnpm store
run: pnpm config set store-dir ~/.pnpm-store
- name: Restore pnpm store
# ui_quality is this cache entry's one publisher (see its own Save pnpm
# store step). This job only restores it; restore-keys falls back to
# the closest prior entry on a miss instead of racing a second upload.
if: ${{ needs.runner_policy.outputs.allow_native_github_cache == 'true' }}
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ~/.pnpm-store
key: ${{ env.CACHE_NAMESPACE }}-pnpm-${{ runner.os }}-${{ hashFiles('crates/mesh-llm-ui/pnpm-lock.yaml', '.github/cache-version.txt') }}
restore-keys: ${{ env.CACHE_NAMESPACE }}-pnpm-${{ runner.os }}-
# See ui_quality's "Point pnpm at the image's baked store" step for why
# this doesn't use the Actions cache (#1392).
- name: Point pnpm at the image's baked store
run: pnpm config set store-dir /home/runner/.local/share/pnpm/store
- name: Install UI dependencies
run: pnpm i --frozen-lockfile
- name: Verify prebuilt web environment
Expand Down
2 changes: 1 addition & 1 deletion ci/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ The implemented policy uses that isolation selectively:
| Linux Cargo `target` directories | Disabled for Clippy, Rust tests, host, and runtime | Avoids sharded multi-GiB generations and their restore/upload latency |
| Static Linux ABI and Swift native ABI | Exact PR-scoped cache on miss | Same-PR reruns reuse the verified native input when its full recipe/toolchain key is unchanged |
| macOS Metal unit ABI and Windows native ABI | Exact PR-scoped cache on miss | Same-PR reruns avoid the native rebuild; no restore prefixes cross an ABI boundary |
| Console pnpm store | `ui_quality` is the sole publisher; `ui_e2e` restores only | Avoids the two `ci-web-slice.yml` UI jobs racing to upload the same entry; later same-PR runs reuse a lockfile-keyed store |
| Console pnpm store | None -- `ui_quality`, `ui_e2e`, and `ui_artifact` all point `store-dir` at the runner image's baked pnpm store instead of an Actions cache | Every run installs warm from the image; no cache to publish, restore, or race |
| Website npm store | None -- the `website` job runs in the prebuilt `public web` image (baked npm/node) with no bare-metal row, so its `setup-node` cache was deleted outright rather than kept | Every run does a fresh `npm ci`; no cache to invalidate or race |
| GitHub artifacts | Never used as cross-run caches | Immutable producers/consumers remain correct within one run; reruns recreate run-scoped artifacts |

Expand Down
18 changes: 11 additions & 7 deletions scripts/tests/test_ci_artifact_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2566,11 +2566,12 @@ def test_dispatched_pr_cache_writes_remain_blocked_with_depot(
def test_depot_pr_native_cache_consumers_obey_central_policy(self) -> None:
eligible_consumers = {
"ci-quality-slice.yml": ("uses: ./.github/actions/restore-sccache-seed",),
"ci-web-slice.yml": (
"uses: actions/cache/restore@",
"uses: actions/cache/save@",
),
"ci-ui-artifact-slice.yml": ("uses: actions/cache/restore@",),
# ci-web-slice.yml and ci-ui-artifact-slice.yml have no native
# GitHub cache consumers left: their pnpm jobs (ui_quality,
# ui_e2e, ui_artifact) point store-dir at the runner image's
# baked store instead of the Actions cache (#1392), and
# `website` was already deleted-outright rather than gated (see
# the comment on that job's entry below).
"ci-linux-host-slice.yml": ("uses: ./.github/actions/restore-sccache-seed",),
"ci-linux-runtime-slice.yml": ("uses: ./.github/actions/restore-sccache-seed",),
"ci-rust-tests-slice.yml": ("uses: ./.github/actions/restore-sccache-seed",),
Expand Down Expand Up @@ -2687,8 +2688,11 @@ def step_block(workflow: str, marker: str) -> str:
# image (no bare-metal row), so its setup-node native-cache
# consumer was deleted outright rather than gated -- there is
# nothing left in that job for the depot/native cache policy to
# govern. See `eligible_consumers["ci-web-slice.yml"]` above for
# the jobs in this file that still participate (ui_quality, ui_e2e).
# govern. The other jobs in that file (ui_quality, ui_e2e) and in
# ci-ui-artifact-slice.yml (ui_artifact) have no native-cache
# consumer left either now that they point at the runner image's
# baked pnpm store instead (#1392); see the comment on
# `eligible_consumers` above.
self.assertIn(
f"cache: ${{{{ {native_cache_expression} && 'pnpm' || '' }}}}",
swift,
Expand Down
23 changes: 20 additions & 3 deletions scripts/tests/test_ci_workflow_artifacts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import re
from pathlib import Path
import unittest

Expand Down Expand Up @@ -175,9 +176,25 @@ def test_plan_action_emits_real_platform_matrices_and_optional_affected_crates(s
def test_ui_cache_and_website_dependencies_are_explicit(self):
ui = (ROOT / ".github/workflows/ci-ui-artifact-slice.yml").read_text()
web = (ROOT / ".github/workflows/ci-web-slice.yml").read_text()
self.assertIn("uses: actions/cache/restore@", ui)
self.assertNotIn("uses: actions/cache/save@", ui)
self.assertIn("uses: actions/cache/save@", web)
# UI installs point at the runner image's baked pnpm store instead
# of the Actions cache (#1392) -- there is nothing left here to
# restore or save.
# Matches quoted or unquoted `cache: pnpm`/`cache: npm` -- a plain
# substring check would miss `cache: "pnpm"` / `cache: 'npm'`, which
# would still enable setup-node's own dependency cache.
cache_config = re.compile(
r"(?m)^[ \t]*cache:[ \t]*"
r"(?:pnpm|npm|'pnpm'|'npm'|\"pnpm\"|\"npm\")"
r"[ \t]*(?:#.*)?$"
)
self.assertIn("run: pnpm config set store-dir /home/runner/.local/share/pnpm/store", ui)
self.assertNotIn("uses: actions/cache", ui)
self.assertNotRegex(ui, cache_config)
self.assertNotIn("CACHE_NAMESPACE", ui)
self.assertIn("run: pnpm config set store-dir /home/runner/.local/share/pnpm/store", web)
self.assertNotIn("uses: actions/cache", web)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
self.assertNotRegex(web, cache_config)
self.assertNotIn("CACHE_NAMESPACE", web)
# The `website` job runs in the prebuilt public-web image with no
# bare-metal row, so setup-node's own npm cache and the `just`
# install-action were deleted outright (both are baked in the
Expand Down
35 changes: 34 additions & 1 deletion scripts/tests/test_pr_workflow_artifacts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pathlib import Path
import re
import unittest

import yaml
Expand Down Expand Up @@ -26,6 +27,18 @@ class PrWorkflowArtifactTests(unittest.TestCase):
def workflow(self, name: str) -> str:
return (WORKFLOWS / name).read_text()

def job_blocks(self, workflow: str) -> dict[str, str]:
"""Split a workflow's raw text into {job_name: block_text} by
top-level (2-space-indented) job keys under `jobs:`."""
pattern = re.compile(r"^ ([A-Za-z_][\w-]*):$", re.MULTILINE)
matches = list(pattern.finditer(workflow))
blocks = {}
for index, match in enumerate(matches):
start = match.end()
end = matches[index + 1].start() if index + 1 < len(matches) else len(workflow)
blocks[match.group(1)] = workflow[start:end]
return blocks

def test_windows_log_store_privacy_checks_are_platform_owned(self):
workflow = PLATFORM.read_text()
self.assertIn("name: Test Windows log artifact privacy ACL", workflow)
Expand Down Expand Up @@ -250,8 +263,28 @@ def test_pr_platform_critical_matrices_fail_fast_by_profile(self):
def test_pr_cache_publishers_are_exact_and_bounded(self):
ui_artifact = self.workflow("ci-ui-artifact-slice.yml")
website = self.workflow("ci-web-slice.yml")
# UI installs no longer round-trip through the Actions cache at all
# (#1392): the runner image bakes a warm pnpm store and every pnpm
# job in these two files points store-dir at it directly, so there
# is nothing here to save or restore.
self.assertNotIn("name: Save pnpm store", ui_artifact)
self.assertEqual(1, website.count("name: Save pnpm store"))
self.assertNotIn("name: Restore pnpm store", ui_artifact)
self.assertNotIn("actions/cache", ui_artifact)
self.assertNotIn("name: Save pnpm store", website)
self.assertNotIn("name: Restore pnpm store", website)
self.assertNotIn("actions/cache", website)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
# Every pnpm job in the two files points store-dir at the image's
# baked store directly: once for ui_artifact, and once each for
# ui_quality and ui_e2e.
store_dir_config = "run: pnpm config set store-dir /home/runner/.local/share/pnpm/store"
self.assertEqual(1, ui_artifact.count(store_dir_config))
website_jobs = self.job_blocks(website)
for job in ("ui_quality", "ui_e2e"):
self.assertEqual(
1,
website_jobs[job].count(store_dir_config),
f"expected exactly one baked-store config in {job}",
)
# The `website` job itself runs in the prebuilt public-web image with
# no bare-metal row, so it has no native-cache-gated npm consumer
# left to publish or bound -- setup-node's own cache was deleted
Expand Down
Loading