mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-27 04:07:05 +00:00
🎭 ci: Gate Playwright Lanes and Docker Smokes on Codegraph Selection (Stage 2) (#15136)
* 🎭 ci: Gate Playwright Lanes and Docker Smokes on Codegraph Selection (Stage 2)
* ci: surface the fail-open reason in the stage-2 select summaries
* ci: log the stage-2 select decision for harvesting
* ci: fail open on fetch failure or truncated file list; type-strict skip decisions (Codex)
* ci: check curl's exit status before honoring a selection (Codex r2)
This commit is contained in:
parent
44d97f859d
commit
f7b65cb7ae
4 changed files with 246 additions and 20 deletions
25
.github/workflows/backend-review.yml
vendored
25
.github/workflows/backend-review.yml
vendored
|
|
@ -159,17 +159,32 @@ jobs:
|
|||
PR: ${{ github.event.pull_request.number }}
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
CHANGED: ${{ github.event.pull_request.changed_files }}
|
||||
run: |
|
||||
set +e
|
||||
note() { echo "$1" >> "$GITHUB_STEP_SUMMARY"; }
|
||||
note "### Codegraph select — GATING (backend jest)"
|
||||
if [ -z "$URL" ] || [ -z "$TOKEN" ]; then note "_no codegraph config; running FULL_"; exit 0; fi
|
||||
gh api "repos/$REPO/pulls/$PR/files" --paginate --jq '.[] | {path: .filename, status, patch}' | jq -s . > files.json
|
||||
if [ ! -s files.json ]; then note "_could not fetch changed files; running FULL_"; exit 0; fi
|
||||
# A failed or truncated page must not become a shorter file list: the pipeline would hide
|
||||
# gh's exit status behind jq, and a partial list can turn a required lane off. Check the
|
||||
# fetch status AND the count against the PR's own changed_files (Codex P1, #15136).
|
||||
if ! gh api "repos/$REPO/pulls/$PR/files" --paginate \
|
||||
--jq '.[] | {path: .filename, status, patch}' > files.ndjson; then
|
||||
note "_could not fetch changed files; running FULL_"; exit 0
|
||||
fi
|
||||
jq -s . files.ndjson > files.json
|
||||
N=$(jq 'length' files.json)
|
||||
if [ "$N" -eq 0 ] || { [ -n "$CHANGED" ] && [ "$N" -ne "$CHANGED" ]; }; then
|
||||
note "_changed-file list incomplete ($N of ${CHANGED:-?}); running FULL_"; exit 0
|
||||
fi
|
||||
jq -c --arg b "$BASE_SHA" --arg h "$HEAD_SHA" '{files: ., mode: "safe", lockBaseSha: $b, lockHeadSha: $h}' files.json > body.json
|
||||
RESP=$(curl -sS -m 45 -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' --data-binary @body.json "$URL/v1/select")
|
||||
if [ -z "$RESP" ] || ! echo "$RESP" | jq -e '.selected.api.mode' >/dev/null 2>&1; then
|
||||
note "_codegraph unavailable (${RESP:0:120}); running FULL_"
|
||||
# curl's status is checked explicitly: a transfer that times out or truncates after a
|
||||
# parseable body must fail open, not be honoured (Codex P1, #15136). --fail-with-body
|
||||
# also turns HTTP errors into a failure while keeping the error text for the summary.
|
||||
RESP=$(curl -sS --fail-with-body -m 45 -H "Authorization: Bearer $TOKEN" \
|
||||
-H 'content-type: application/json' --data-binary @body.json "$URL/v1/select"); RC=$?
|
||||
if [ "$RC" -ne 0 ] || [ -z "$RESP" ] || ! echo "$RESP" | jq -e '.selected.api.mode' >/dev/null 2>&1; then
|
||||
note "_codegraph unavailable (curl exit $RC: ${RESP:0:120}); running FULL_"
|
||||
exit 0
|
||||
fi
|
||||
# Emit per-workspace run flag + workspace-relative file list. A workspace emits
|
||||
|
|
|
|||
27
.github/workflows/codegraph-select.yml
vendored
27
.github/workflows/codegraph-select.yml
vendored
|
|
@ -31,22 +31,35 @@ jobs:
|
|||
PR: ${{ github.event.pull_request.number }}
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
CHANGED: ${{ github.event.pull_request.changed_files }}
|
||||
run: |
|
||||
set +e
|
||||
note() { echo "$1" >> "$GITHUB_STEP_SUMMARY"; }
|
||||
note "### Codegraph select — observe-only"
|
||||
if [ -z "$URL" ] || [ -z "$TOKEN" ]; then note "_secrets not configured; skipped_"; exit 0; fi
|
||||
|
||||
gh api "repos/$REPO/pulls/$PR/files" --paginate \
|
||||
--jq '.[] | {path: .filename, status, patch}' | jq -s . > files.json
|
||||
if [ ! -s files.json ]; then note "_could not fetch changed files; skipped_"; exit 0; fi
|
||||
# A failed or truncated page must not become a shorter file list: the pipeline would hide
|
||||
# gh's exit status behind jq, and a partial list can turn a required lane off. Check the
|
||||
# fetch status AND the count against the PR's own changed_files (Codex P1, #15136).
|
||||
if ! gh api "repos/$REPO/pulls/$PR/files" --paginate \
|
||||
--jq '.[] | {path: .filename, status, patch}' > files.ndjson; then
|
||||
note "_could not fetch changed files; skipped_"; exit 0
|
||||
fi
|
||||
jq -s . files.ndjson > files.json
|
||||
N=$(jq 'length' files.json)
|
||||
if [ "$N" -eq 0 ] || { [ -n "$CHANGED" ] && [ "$N" -ne "$CHANGED" ]; }; then
|
||||
note "_changed-file list incomplete ($N of ${CHANGED:-?}); skipped_"; exit 0
|
||||
fi
|
||||
|
||||
jq -c --arg b "$BASE_SHA" --arg h "$HEAD_SHA" \
|
||||
'{files: ., lockBaseSha: $b, lockHeadSha: $h}' files.json > body.json
|
||||
RESP=$(curl -sS -m 45 -H "Authorization: Bearer $TOKEN" \
|
||||
-H 'content-type: application/json' --data-binary @body.json "$URL/v1/select")
|
||||
if [ -z "$RESP" ] || ! echo "$RESP" | jq -e .selected >/dev/null 2>&1; then
|
||||
note "_codegraph unavailable (${RESP:0:120}); skipped — full CI runs as always_"
|
||||
# curl's status is checked explicitly: a transfer that times out or truncates after a
|
||||
# parseable body must fail open, not be honoured (Codex P1, #15136). --fail-with-body
|
||||
# also turns HTTP errors into a failure while keeping the error text for the summary.
|
||||
RESP=$(curl -sS --fail-with-body -m 45 -H "Authorization: Bearer $TOKEN" \
|
||||
-H 'content-type: application/json' --data-binary @body.json "$URL/v1/select"); RC=$?
|
||||
if [ "$RC" -ne 0 ] || [ -z "$RESP" ] || ! echo "$RESP" | jq -e .selected >/dev/null 2>&1; then
|
||||
note "_codegraph unavailable (curl exit $RC: ${RESP:0:120}); skipped — full CI runs as always_"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
|
|
|||
89
.github/workflows/docker-smoke.yml
vendored
89
.github/workflows/docker-smoke.yml
vendored
|
|
@ -29,8 +29,95 @@ concurrency:
|
|||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Stage 2 of codegraph gating (stage 1 = backend jest in backend-review.yml). Two of the three
|
||||
# smokes are graph-decidable: the client package build only matters when the change reaches the
|
||||
# client build context, and the production-image boot only when it reaches the api image's build
|
||||
# context (Dockerfile.multi's api-build stage never builds client). Monotone and fail-open: a
|
||||
# smoke is dropped ONLY on an explicit `false`; unavailable/unconfigured/non-synchronize events
|
||||
# run everything. Lock attribution rides along so a dependency bump keeps the image smoke.
|
||||
# Kill switch: repo variable CODEGRAPH_GATING=off.
|
||||
codegraph_select:
|
||||
name: Codegraph select
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
if: >-
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.action == 'synchronize' &&
|
||||
vars.CODEGRAPH_GATING != 'off'
|
||||
outputs:
|
||||
decided: ${{ steps.sel.outputs.decided }}
|
||||
client_run: ${{ steps.sel.outputs.client_run }}
|
||||
api_run: ${{ steps.sel.outputs.api_run }}
|
||||
steps:
|
||||
- name: Select smokes, fail open on any doubt
|
||||
id: sel
|
||||
env:
|
||||
URL: ${{ secrets.CODEGRAPH_URL }}
|
||||
TOKEN: ${{ secrets.CODEGRAPH_TOKEN }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
PR: ${{ github.event.pull_request.number }}
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
CHANGED: ${{ github.event.pull_request.changed_files }}
|
||||
run: |
|
||||
set +e
|
||||
note() { echo "$1" >> "$GITHUB_STEP_SUMMARY"; }
|
||||
note "### Codegraph select — GATING (docker smokes)"
|
||||
if [ -z "$URL" ] || [ -z "$TOKEN" ]; then note "_no codegraph config; running FULL_"; exit 0; fi
|
||||
# A failed or truncated page must not become a shorter file list: the pipeline would hide
|
||||
# gh's exit status behind jq, and a partial list can turn a required lane off. Check the
|
||||
# fetch status AND the count against the PR's own changed_files (Codex P1, #15136).
|
||||
if ! gh api "repos/$REPO/pulls/$PR/files" --paginate \
|
||||
--jq '.[] | {path: .filename, status, patch}' > files.ndjson; then
|
||||
note "_could not fetch changed files; running FULL_"; exit 0
|
||||
fi
|
||||
jq -s . files.ndjson > files.json
|
||||
N=$(jq 'length' files.json)
|
||||
if [ "$N" -eq 0 ] || { [ -n "$CHANGED" ] && [ "$N" -ne "$CHANGED" ]; }; then
|
||||
note "_changed-file list incomplete ($N of ${CHANGED:-?}); running FULL_"; exit 0
|
||||
fi
|
||||
jq -c --arg b "$BASE_SHA" --arg h "$HEAD_SHA" \
|
||||
'{files: ., mode: "safe", lockBaseSha: $b, lockHeadSha: $h}' files.json > body.json
|
||||
# curl's status is checked explicitly: a transfer that times out or truncates after a
|
||||
# parseable body must fail open, not be honoured (Codex P1, #15136). --fail-with-body
|
||||
# also turns HTTP errors into a failure while keeping the error text for the summary.
|
||||
RESP=$(curl -sS --fail-with-body -m 45 -H "Authorization: Bearer $TOKEN" \
|
||||
-H 'content-type: application/json' --data-binary @body.json "$URL/v1/select"); RC=$?
|
||||
if [ "$RC" -ne 0 ] || [ -z "$RESP" ] || ! echo "$RESP" | jq -e '.matrix["docker-smoke"]' >/dev/null 2>&1; then
|
||||
note "_codegraph unavailable (curl exit $RC: ${RESP:0:120}); running FULL_"
|
||||
exit 0
|
||||
fi
|
||||
# A smoke is skipped only on the JSON boolean false — tested inside jq, because `jq -r`
|
||||
# prints the string "false" and the boolean identically (Codex P1, #15136). Anything
|
||||
# else (true, null, a string, missing) runs.
|
||||
if echo "$RESP" | jq -e '.e2e.fail_open == true' >/dev/null 2>&1; then
|
||||
note "_fail-open decision (root/workflow/lockfile change or stale graph): everything runs_"
|
||||
fi
|
||||
note "| smoke | decision |"
|
||||
note "|---|---|"
|
||||
emit() {
|
||||
key="$1"; hint="$2"; label="$3"
|
||||
if echo "$RESP" | jq -e --arg h "$hint" '.matrix["docker-smoke"][$h] == false' >/dev/null 2>&1; then
|
||||
echo "${key}_run=false" >> "$GITHUB_OUTPUT"
|
||||
note "| $label | skip (no reach into its build context) |"
|
||||
else
|
||||
echo "${key}_run=true" >> "$GITHUB_OUTPUT"
|
||||
note "| $label | run |"
|
||||
fi
|
||||
}
|
||||
emit client client_package_target "client package build"
|
||||
emit api api_runtime_smoke "api runtime smoke"
|
||||
echo "codegraph-select: $(echo "$RESP" | jq -c '.matrix["docker-smoke"]')"
|
||||
echo "decided=true" >> "$GITHUB_OUTPUT"
|
||||
note ""
|
||||
note "node image smoke keeps its own path filter · kill switch: repo variable \`CODEGRAPH_GATING=off\` · everything runs on PR open"
|
||||
exit 0
|
||||
|
||||
client-package-target:
|
||||
name: Build Docker client package target
|
||||
needs: [codegraph_select]
|
||||
if: ${{ !cancelled() && needs.codegraph_select.outputs.client_run != 'false' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
|
|
@ -88,6 +175,8 @@ jobs:
|
|||
|
||||
api-runtime-smoke:
|
||||
name: API runtime smoke (production image boots)
|
||||
needs: [codegraph_select]
|
||||
if: ${{ !cancelled() && needs.codegraph_select.outputs.api_run != 'false' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
|
|
|
|||
125
.github/workflows/playwright-mock.yml
vendored
125
.github/workflows/playwright-mock.yml
vendored
|
|
@ -28,22 +28,126 @@ env:
|
|||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
|
||||
jobs:
|
||||
# Stage 2 of codegraph gating (stage 1 = backend jest in backend-review.yml). The graph decides
|
||||
# two matrix lanes whose relevance is a reachability question: the redis-transport lane (the ten
|
||||
# stream-boundary specs under a real Redis round-trip) and the MCP list_changed lanes. Memory
|
||||
# shards always run. Monotone and fail-open: a lane is dropped ONLY on an explicit `false` from
|
||||
# the service; unavailable/unconfigured/non-synchronize events keep the full matrix. Lock
|
||||
# attribution rides along so a backend dependency bump (reaches nothing in the graph) still
|
||||
# earns both lanes. Kill switch: repo variable CODEGRAPH_GATING=off.
|
||||
codegraph_select:
|
||||
name: Codegraph select
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
if: >-
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.action == 'synchronize' &&
|
||||
vars.CODEGRAPH_GATING != 'off' &&
|
||||
github.event.pull_request != null &&
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association)
|
||||
outputs:
|
||||
decided: ${{ steps.sel.outputs.decided }}
|
||||
e2e_include: ${{ steps.sel.outputs.e2e_include }}
|
||||
mcp_run: ${{ steps.sel.outputs.mcp_run }}
|
||||
steps:
|
||||
- name: Select matrix lanes, fail open on any doubt
|
||||
id: sel
|
||||
env:
|
||||
URL: ${{ secrets.CODEGRAPH_URL }}
|
||||
TOKEN: ${{ secrets.CODEGRAPH_TOKEN }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
PR: ${{ github.event.pull_request.number }}
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
CHANGED: ${{ github.event.pull_request.changed_files }}
|
||||
FULL_INCLUDE: '{"include":[{"name":"memory, shard 1/3","stream_store":"memory","redis_image":"","suite":"full","shard":"1/3","artifact":"memory-1-of-3"},{"name":"memory, shard 2/3","stream_store":"memory","redis_image":"","suite":"full","shard":"2/3","artifact":"memory-2-of-3"},{"name":"memory, shard 3/3","stream_store":"memory","redis_image":"","suite":"full","shard":"3/3","artifact":"memory-3-of-3"},{"name":"redis transport","stream_store":"redis","redis_image":"redis:7-alpine","suite":"transport","shard":"","artifact":"redis-transport"}]}'
|
||||
run: |
|
||||
set +e
|
||||
note() { echo "$1" >> "$GITHUB_STEP_SUMMARY"; }
|
||||
note "### Codegraph select — GATING (Playwright matrix lanes)"
|
||||
if [ -z "$URL" ] || [ -z "$TOKEN" ]; then note "_no codegraph config; running FULL_"; exit 0; fi
|
||||
# A failed or truncated page must not become a shorter file list: the pipeline would hide
|
||||
# gh's exit status behind jq, and a partial list can turn a required lane off. Check the
|
||||
# fetch status AND the count against the PR's own changed_files (Codex P1, #15136).
|
||||
if ! gh api "repos/$REPO/pulls/$PR/files" --paginate \
|
||||
--jq '.[] | {path: .filename, status, patch}' > files.ndjson; then
|
||||
note "_could not fetch changed files; running FULL_"; exit 0
|
||||
fi
|
||||
jq -s . files.ndjson > files.json
|
||||
N=$(jq 'length' files.json)
|
||||
if [ "$N" -eq 0 ] || { [ -n "$CHANGED" ] && [ "$N" -ne "$CHANGED" ]; }; then
|
||||
note "_changed-file list incomplete ($N of ${CHANGED:-?}); running FULL_"; exit 0
|
||||
fi
|
||||
jq -c --arg b "$BASE_SHA" --arg h "$HEAD_SHA" \
|
||||
'{files: ., mode: "safe", lockBaseSha: $b, lockHeadSha: $h}' files.json > body.json
|
||||
# curl's status is checked explicitly: a transfer that times out or truncates after a
|
||||
# parseable body must fail open, not be honoured (Codex P1, #15136). --fail-with-body
|
||||
# also turns HTTP errors into a failure while keeping the error text for the summary.
|
||||
RESP=$(curl -sS --fail-with-body -m 45 -H "Authorization: Bearer $TOKEN" \
|
||||
-H 'content-type: application/json' --data-binary @body.json "$URL/v1/select"); RC=$?
|
||||
if [ "$RC" -ne 0 ] || [ -z "$RESP" ] || ! echo "$RESP" | jq -e '.matrix["playwright-mock"]' >/dev/null 2>&1; then
|
||||
note "_codegraph unavailable (curl exit $RC: ${RESP:0:120}); running FULL_"
|
||||
exit 0
|
||||
fi
|
||||
# A lane is skipped only on the JSON boolean false — tested inside jq, because `jq -r`
|
||||
# prints the string "false" and the boolean identically (Codex P1, #15136). Anything
|
||||
# else (true, null, a string, missing) runs.
|
||||
if echo "$RESP" | jq -e '.e2e.fail_open == true' >/dev/null 2>&1; then
|
||||
note "_fail-open decision (root/workflow/lockfile change or stale graph): everything runs_"
|
||||
fi
|
||||
REDIS=$(echo "$RESP" | jq -c '.matrix["playwright-mock"].redis_transport')
|
||||
MCP=$(echo "$RESP" | jq -c '.matrix["playwright-mock"].mcp_tool_list_changed')
|
||||
REDIS_SKIP=0; MCP_SKIP=0
|
||||
echo "$RESP" | jq -e '.matrix["playwright-mock"].redis_transport == false' >/dev/null 2>&1 && REDIS_SKIP=1
|
||||
echo "$RESP" | jq -e '.matrix["playwright-mock"].mcp_tool_list_changed == false' >/dev/null 2>&1 && MCP_SKIP=1
|
||||
note "| lane | decision |"
|
||||
note "|---|---|"
|
||||
if [ "$REDIS_SKIP" = 1 ]; then
|
||||
INCLUDE=$(echo "$FULL_INCLUDE" | jq -c '.include |= map(select(.suite != "transport"))')
|
||||
note "| redis transport | skip (no reach into the stream boundary) |"
|
||||
else
|
||||
INCLUDE="$FULL_INCLUDE"
|
||||
note "| redis transport | run |"
|
||||
fi
|
||||
if ! echo "$INCLUDE" | jq -e '.include | length >= 3' >/dev/null 2>&1; then
|
||||
note "_matrix assembly failed; running FULL_"
|
||||
exit 0
|
||||
fi
|
||||
echo "e2e_include=$INCLUDE" >> "$GITHUB_OUTPUT"
|
||||
echo "codegraph-select: redis_transport=$REDIS mcp_tool_list_changed=$MCP matrix_entries=$(echo "$INCLUDE" | jq '.include | length')"
|
||||
if [ "$MCP_SKIP" = 1 ]; then
|
||||
echo "mcp_run=false" >> "$GITHUB_OUTPUT"
|
||||
note "| MCP list_changed | skip (no reach into MCP) |"
|
||||
else
|
||||
echo "mcp_run=true" >> "$GITHUB_OUTPUT"
|
||||
note "| MCP list_changed | run |"
|
||||
fi
|
||||
echo "decided=true" >> "$GITHUB_OUTPUT"
|
||||
note ""
|
||||
note "memory shards always run · kill switch: repo variable \`CODEGRAPH_GATING=off\` · full matrix on PR open and nightly"
|
||||
exit 0
|
||||
|
||||
e2e_shards:
|
||||
name: e2e (${{ matrix.name }})
|
||||
needs: [codegraph_select]
|
||||
if: >-
|
||||
github.event_name == 'schedule' ||
|
||||
!cancelled() &&
|
||||
(github.event_name == 'schedule' ||
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request != null &&
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association))
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association)))
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: >-
|
||||
${{
|
||||
github.event_name == 'pull_request' &&
|
||||
fromJSON('{"include":[{"name":"memory, shard 1/3","stream_store":"memory","redis_image":"","suite":"full","shard":"1/3","artifact":"memory-1-of-3"},{"name":"memory, shard 2/3","stream_store":"memory","redis_image":"","suite":"full","shard":"2/3","artifact":"memory-2-of-3"},{"name":"memory, shard 3/3","stream_store":"memory","redis_image":"","suite":"full","shard":"3/3","artifact":"memory-3-of-3"},{"name":"redis transport","stream_store":"redis","redis_image":"redis:7-alpine","suite":"transport","shard":"","artifact":"redis-transport"}]}') ||
|
||||
(github.event_name == 'pull_request' && needs.codegraph_select.outputs.e2e_include != '' &&
|
||||
fromJSON(needs.codegraph_select.outputs.e2e_include)) ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
fromJSON('{"include":[{"name":"memory, shard 1/3","stream_store":"memory","redis_image":"","suite":"full","shard":"1/3","artifact":"memory-1-of-3"},{"name":"memory, shard 2/3","stream_store":"memory","redis_image":"","suite":"full","shard":"2/3","artifact":"memory-2-of-3"},{"name":"memory, shard 3/3","stream_store":"memory","redis_image":"","suite":"full","shard":"3/3","artifact":"memory-3-of-3"},{"name":"redis transport","stream_store":"redis","redis_image":"redis:7-alpine","suite":"transport","shard":"","artifact":"redis-transport"}]}')) ||
|
||||
fromJSON('{"include":[{"name":"memory, shard 1/2","stream_store":"memory","redis_image":"","suite":"full","shard":"1/2","artifact":"memory-1-of-2"},{"name":"memory, shard 2/2","stream_store":"memory","redis_image":"","suite":"full","shard":"2/2","artifact":"memory-2-of-2"},{"name":"redis, shard 1/2","stream_store":"redis","redis_image":"redis:7-alpine","suite":"full","shard":"1/2","artifact":"redis-1-of-2"},{"name":"redis, shard 2/2","stream_store":"redis","redis_image":"redis:7-alpine","suite":"full","shard":"2/2","artifact":"redis-2-of-2"}]}')
|
||||
}}
|
||||
services:
|
||||
|
|
@ -231,12 +335,15 @@ jobs:
|
|||
|
||||
mcp_tool_list_changed:
|
||||
name: MCP list_changed (replica count ${{ matrix.replicas }})
|
||||
needs: [codegraph_select]
|
||||
if: >-
|
||||
github.event_name == 'schedule' ||
|
||||
!cancelled() &&
|
||||
needs.codegraph_select.outputs.mcp_run != 'false' &&
|
||||
(github.event_name == 'schedule' ||
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request != null &&
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association))
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association)))
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
|
|
@ -387,11 +494,13 @@ jobs:
|
|||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request != null &&
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association)))
|
||||
needs: [e2e_shards, mcp_tool_list_changed]
|
||||
needs: [codegraph_select, e2e_shards, mcp_tool_list_changed]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# A codegraph-skipped MCP lane reports `skipped`; that is a decision, not a failure.
|
||||
- name: Verify every Playwright job passed
|
||||
if: >-
|
||||
needs.e2e_shards.result != 'success' ||
|
||||
needs.mcp_tool_list_changed.result != 'success'
|
||||
(needs.mcp_tool_list_changed.result != 'success' &&
|
||||
!(needs.mcp_tool_list_changed.result == 'skipped' && needs.codegraph_select.outputs.mcp_run == 'false'))
|
||||
run: exit 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue