Skip to content

docs: session close——roadmap inventory 更新(AAV-1298 闭环 + 遗留清理 + 1301/1… #2102

docs: session close——roadmap inventory 更新(AAV-1298 闭环 + 遗留清理 + 1301/1…

docs: session close——roadmap inventory 更新(AAV-1298 闭环 + 遗留清理 + 1301/1… #2102

Workflow file for this run

name: CI
on:
pull_request:
branches:
- main
- dev
- railway
- lovable
push:
branches:
- main
- dev
- railway
- lovable
- feature/**
workflow_dispatch:
inputs:
target_ref:
description: 'Ref (branch or SHA) to run CI against'
required: false
schedule:
- cron: '0 6 * * *' # daily UTC 6:00 — detect backend spec drift
permissions:
contents: read
pull-requests: write
jobs:
peer-dep-check:
name: peer-dep-check
runs-on: ubuntu-latest
env:
CI_REF: ${{ github.event.pull_request.head.sha || github.event.inputs.target_ref || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm run ci:lockcheck
- name: Check react/react-dom version match
run: |
REACT_V=$(node -p "require('react/package.json').version")
RDOM_V=$(node -p "require('react-dom/package.json').version")
echo "react=$REACT_V react-dom=$RDOM_V"
if [ "$REACT_V" != "$RDOM_V" ]; then
echo "::error::react ($REACT_V) and react-dom ($RDOM_V) version mismatch!"
exit 1
fi
- name: Check for peer dependency conflicts
run: |
INVALID=$(npm ls --all 2>&1 | grep -i "invalid" | grep -v "wagmi@3" || true)
if [ -n "$INVALID" ]; then
echo "::error::$INVALID"
exit 1
fi
echo "No peer dep conflicts (wagmi@3 + rainbowkit@2 known mismatch, tracked for rainbowkit v3)"
socket-firewall:
name: socket-firewall
runs-on: ubuntu-latest
env:
CI_REF: ${{ github.event.pull_request.head.sha || github.event.inputs.target_ref || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Setup Socket Firewall
uses: socketdev/action@v1
with:
mode: firewall
- name: Install dependencies through Socket Firewall
run: sfw npm ci
lint:
name: lint
runs-on: ubuntu-latest
env:
CI_REF: ${{ github.event.pull_request.head.sha || github.event.inputs.target_ref || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Sync lock file (Lovable compat)
run: |
if [ "$(git log -1 --format='%an')" = "gpt-engineer-app[bot]" ]; then
echo "Lovable commit detected – syncing lock file"
npm install --package-lock-only
fi
- name: Install dependencies
run: npm run ci:lockcheck
# PR: one ESLint pass with Reviewdog annotations (avoids a duplicate workflow + second npm ci).
# push / workflow_dispatch: plain eslint (Reviewdog reporter is PR-only).
- name: Run ESLint (Reviewdog)
if: github.event_name == 'pull_request'
uses: reviewdog/action-eslint@b6303df522851d0e9abaea654da0222037d97e75 # v1
env:
REVIEWDOG_VERSION: v0.20.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
eslint_flags: '.'
fail_level: error
- name: Run ESLint
if: github.event_name != 'pull_request'
run: npm run lint
build:
name: build
runs-on: ubuntu-latest
env:
CI_REF: ${{ github.event.pull_request.head.sha || github.event.inputs.target_ref || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Sync lock file (Lovable compat)
run: |
if [ "$(git log -1 --format='%an')" = "gpt-engineer-app[bot]" ]; then
echo "Lovable commit detected – syncing lock file"
npm install --package-lock-only
fi
- name: Install dependencies
run: npm run ci:lockcheck
- name: Run build with performance measurement
run: npm run build:metrics
- name: Archive build metrics
uses: actions/upload-artifact@v7
with:
name: build-metrics
path: dist/build-metrics.json
retention-days: 90
- name: Run unit tests
run: npm test
e2e-desktop:
name: e2e-desktop (${{ matrix.shard }}/${{ matrix.total }})
runs-on: ubuntu-latest
timeout-minutes: 20
# E2E tests depend on live staging API data and are inherently flaky.
# On `lovable` push (no PR review), make them non-blocking so transient
# failures don't trigger ci-failure-alert issues. They remain blocking
# on PRs and on `dev`/`main` pushes.
continue-on-error: ${{ github.event_name == 'push' && github.ref == 'refs/heads/lovable' }}
strategy:
fail-fast: false
matrix:
shard: [1, 2]
total: [2]
env:
CI_REF: ${{ github.event.pull_request.head.sha || github.event.inputs.target_ref || github.sha }}
# Use Railway direct URL (from LIVE_TEST_API_BASE_CI secret) to bypass Cloudflare/WAF.
# Falls back to staging-api.aaveapy.com when secret is unset.
VITE_API_BASE_URL: ${{ secrets.LIVE_TEST_API_BASE_CI || 'https://staging-api.aaveapy.com/api' }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm run ci:lockcheck
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run desktop E2E tests
run: npx playwright test --project=chromium --shard=${{ matrix.shard }}/${{ matrix.total }}
- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
name: playwright-results-desktop-${{ matrix.shard }}
path: |
test-results/
e2e/*-snapshots/*-linux.png
retention-days: 7
e2e-mobile:
name: e2e-mobile (${{ matrix.shard }}/${{ matrix.total }})
runs-on: ubuntu-latest
timeout-minutes: 20
# E2E tests depend on live staging API data and are inherently flaky.
# On `lovable` push (no PR review), make them non-blocking so transient
# failures don't trigger ci-failure-alert issues. They remain blocking
# on PRs and on `dev`/`main` pushes.
continue-on-error: ${{ github.event_name == 'push' && github.ref == 'refs/heads/lovable' }}
strategy:
fail-fast: false
matrix:
shard: [1, 2]
total: [2]
env:
CI_REF: ${{ github.event.pull_request.head.sha || github.event.inputs.target_ref || github.sha }}
# Use Railway direct URL (from LIVE_TEST_API_BASE_CI secret) to bypass Cloudflare/WAF.
# Falls back to staging-api.aaveapy.com when secret is unset.
VITE_API_BASE_URL: ${{ secrets.LIVE_TEST_API_BASE_CI || 'https://staging-api.aaveapy.com/api' }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm run ci:lockcheck
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run mobile E2E tests
run: npx playwright test --project=mobile-chromium --shard=${{ matrix.shard }}/${{ matrix.total }}
- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
name: playwright-results-mobile-${{ matrix.shard }}
path: |
test-results/
e2e/*-snapshots/*-linux.png
retention-days: 7
generate-snapshots:
name: generate-snapshots
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 15
env:
CI_REF: ${{ github.event.pull_request.head_sha || github.event.inputs.target_ref || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm run ci:lockcheck
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Generate Linux screenshot baselines
run: npx playwright test e2e/segmented-toggle-visual.spec.ts e2e/portfolio-panel-header-visual.spec.ts --update-snapshots
- name: Upload Linux baselines
uses: actions/upload-artifact@v7
with:
name: linux-screenshot-baselines
path: e2e/*-snapshots/*-linux.png
retention-days: 30
live-schema-validation:
name: live-schema-validation
if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/lovable') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
env:
CI_REF: ${{ github.event.pull_request.head.sha || github.event.inputs.target_ref || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm run ci:lockcheck
- name: Validate API schemas against live endpoints
env:
LIVE_TEST_API_BASE: ${{ secrets.LIVE_TEST_API_BASE_CI || 'https://staging-api.aaveapy.com/api' }}
RUN_LIVE_TESTS: 'true'
run: bash scripts/ci-live-schema-validate.sh
live-simulation-validation:
name: live-simulation-validation
if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/lovable') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
continue-on-error: true
env:
CI_REF: ${{ github.event.pull_request.head.sha || github.event.inputs.target_ref || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm run ci:lockcheck
- name: Run simulation Golden Rule tests against live API
env:
LIVE_TEST_API_BASE: ${{ secrets.LIVE_TEST_API_BASE_CI || 'https://staging-api.aaveapy.com/api' }}
RUN_LIVE_TESTS: 'true'
run: npm run test:live:simulation:staging
content-security-check:
name: content-security-check
runs-on: ubuntu-latest
env:
CI_REF: ${{ github.event.pull_request.head.sha || github.event.inputs.target_ref || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm run ci:lockcheck
- name: Check external URL whitelist
run: npx tsx scripts/check-external-urls.ts
repo-policy:
name: repo-policy (AGENTS.md / TODO markers / dep release age)
runs-on: ubuntu-latest
env:
CI_REF: ${{ github.event.pull_request.head.sha || github.event.inputs.target_ref || github.sha }}
# PRs diff against the merge base; pushes have no lockfile delta to check.
BASE_REF: ${{ github.event.pull_request.base.sha || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
fetch-depth: 0
# The three policy scripts are dependency-free node builtins — no npm ci.
- name: Validate AGENTS.md references (commands + doc paths)
run: npm run check:agents-md
- name: Technical-debt marker policy (ratchet)
run: npm run check:todos
- name: Dependency release-age gate (lockfile PRs only)
run: |
if git diff --name-only "${{ env.BASE_REF }}...${{ env.CI_REF }}" -- package-lock.json | grep -q .; then
npm run check:dep-age
else
echo "package-lock.json unchanged — skipping release-age gate"
fi
openapi-check:
name: openapi-check
# Dependabot PRs cannot access LIVE_TEST_API_BASE_CI secret (GitHub treats
# them as fork PRs), so the fallback URL gets 403'd by Cloudflare WAF.
# Dependabot only updates dependencies — it never changes the API spec —
# so skipping is safe. The daily schedule job will catch any real drift.
if: github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest
outputs:
drift: ${{ steps.diff.outputs.drift }}
env:
CI_REF: ${{ github.event.pull_request.head.sha || github.event.inputs.target_ref || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm run ci:lockcheck
- name: Fetch OpenAPI spec and check for drift
id: diff
env:
LIVE_API_BASE: ${{ secrets.LIVE_TEST_API_BASE_CI || 'https://staging-api.aaveapy.com/api' }}
# Non-blocking: spec drift from backend is an expected event, not a code bug.
# The openapi-sync job creates a PR against dev (bot PR 收敛 — AAV-1298);
# daily schedule is the safety net.
continue-on-error: true
run: |
set +e
npm run openapi:fetch
FETCH_EXIT=$?
if [ $FETCH_EXIT -ne 0 ]; then
echo "drift=false" >> "$GITHUB_OUTPUT"
echo "::warning::Failed to fetch OpenAPI spec — skipping drift check"
exit 0
fi
if git diff --quiet -- public/openapi.json; then
echo "drift=false" >> "$GITHUB_OUTPUT"
else
echo "drift=true" >> "$GITHUB_OUTPUT"
echo "::warning::OpenAPI spec drift detected — openapi-sync job will create a PR"
fi
security-audit:
name: security-audit
runs-on: ubuntu-latest
env:
CI_REF: ${{ github.event.pull_request.head.sha || github.event.inputs.target_ref || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.CI_REF }}
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm run ci:lockcheck
- name: Audit runtime dependencies (High/Critical)
# Known vulnerabilities that do not affect this project:
# - react-router RSC CSRF (GHSA-qwww-vcr4-c8h2): affects RSC mode only; this is a client-side SPA
# - ws Memory exhaustion DoS (GHSA-96hv-2xvq-fx4p) via viem — upstream fix pending
continue-on-error: true
run: npm audit --omit=dev --audit-level=high
openapi-sync:
name: openapi-sync
needs:
- openapi-check
# Bot PR 收敛 (AAV-1298): sync PRs target dev only, never lovable —
# see docs/archive/2026-09-24-hardcode-sync-resilience.md §3. Branches
# other than dev get read-only drift detection from openapi-check.
# Schedule fires on the default branch ref, hence the explicit checkout/base below.
if: >
always() &&
((github.event_name == 'push' && github.ref_name == 'dev') || github.event_name == 'schedule') &&
needs.openapi-check.outputs.drift == 'true'
runs-on: ubuntu-latest
env:
LIVE_API_BASE: ${{ secrets.LIVE_TEST_API_BASE_CI || 'https://staging-api.aaveapy.com/api' }}
HAS_SYNC_PAT: ${{ secrets.TOKEN_ICON_SYNC_PAT != '' || secrets.HARDCODE_SYNC_PAT != '' }}
permissions:
contents: write
pull-requests: write
actions: write
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: dev
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm run ci:lockcheck
- name: Fetch OpenAPI spec from backend
run: npm run openapi:fetch
- name: Generate Zod schemas from spec
run: npm run schema:codegen
- name: Check for spec changes
id: diff
run: |
if git diff --quiet -- public/openapi.json src/generated/api/; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Generate icon manifests for typecheck
# chainIconManifest.generated.ts / tokenIconManifest.generated.ts are
# gitignored; husky's pre-commit typecheck needs them present (AAV-1298).
run: node scripts/generate-icon-manifests.mjs
- name: Create spec sync PR
id: cpr
if: steps.diff.outputs.changed == 'true'
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
# Use PAT to ensure pull_request workflows (CI lint/build) are triggered on created PRs.
# Fallback to GITHUB_TOKEN keeps behavior but may leave required checks in "Expected".
token: ${{ secrets.TOKEN_ICON_SYNC_PAT != '' && secrets.TOKEN_ICON_SYNC_PAT || secrets.HARDCODE_SYNC_PAT != '' && secrets.HARDCODE_SYNC_PAT || github.token }}
commit-message: 'chore(openapi): sync spec + generated schemas from backend'
branch: 'bot/openapi-sync-${{ github.run_id }}'
delete-branch: true
title: 'chore(openapi): sync spec + generated schemas from backend'
body: |
Auto-generated OpenAPI spec + Zod schema sync.
Trigger: `${{ github.event_name }}` → `dev`
The `openapi-check` job detected a spec drift; this PR syncs:
- `public/openapi.json` from the live backend
- `src/generated/api/schemas.ts` via `npm run schema:codegen`
⚠️ Generated code may break compilation — review before merge.
labels: |
automerge
openapi-sync
base: dev
- name: Warn when PAT is missing
if: steps.cpr.outputs.pull-request-number != '' && env.HAS_SYNC_PAT != 'true'
run: |
echo "::warning::TOKEN_ICON_SYNC_PAT/HARDCODE_SYNC_PAT is not configured. PR was created with GITHUB_TOKEN, so pull_request CI may stay in 'Expected'. Fallback workflow_dispatch CI will be triggered on the PR branch."
- name: Trigger fallback CI when PAT is missing
if: steps.cpr.outputs.pull-request-number != '' && env.HAS_SYNC_PAT != 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const workflowRef = `${{ steps.cpr.outputs.pull-request-branch }}`;
const targetRef = `${{ steps.cpr.outputs.pull-request-head-sha }}` || workflowRef;
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'ci.yml',
ref: workflowRef,
inputs: {
target_ref: targetRef
}
});
core.info(`Triggered fallback workflow_dispatch for ci.yml on ref ${workflowRef}, target_ref ${targetRef}`);