From 38ffe0f500b021895194d3fef2613fde47f98faf Mon Sep 17 00:00:00 2001 From: Delicious233 <101502465+DeliciousBuding@users.noreply.github.com> Date: Tue, 21 Jul 2026 17:06:46 +0800 Subject: [PATCH 1/3] ci: mobile light path-filter + perf/leak workflow_dispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PR path filter mobile → typecheck+unit (frontend-mobile-light) - Keep full mobile suite workflow_dispatch-only - Wire verify-backend-perf-leak-gates.ps1 as dispatch-only job (T5) - Update verify-ci-gates.ps1 policy --- .github/workflows/checks.yml | 64 +++++++++++++++++++- docs/analysis/post-polish-risk-assessment.md | 2 +- docs/progress/MASTER.md | 1 + docs/roadmap.md | 3 +- scripts/verify/verify-ci-gates.ps1 | 22 +++++++ 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 48989f3a..40656db5 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -315,6 +315,7 @@ jobs: web: ${{ steps.filter.outputs.web }} shell: ${{ steps.filter.outputs.shell }} go: ${{ steps.filter.outputs.go }} + mobile: ${{ steps.filter.outputs.mobile }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 @@ -352,6 +353,13 @@ jobs: - 'api/**' - 'scripts/verify/**' - '.github/workflows/checks.yml' + mobile: + - 'app/mobile-rn/**' + - 'app/shared/src/hubClient*.ts' + - 'app/shared/src/hubEvents*.ts' + - 'app/package.json' + - 'app/pnpm-lock.yaml' + - '.github/workflows/checks.yml' # ── Frontend: Desktop ──────────────────────── # Known debt: 15 pre-existing test failures (97.8% pass rate: 679/694). @@ -434,8 +442,39 @@ jobs: - name: Test run: pnpm test -- --run - # ── Frontend: Mobile ────────────────────────── - # 重 job(Playwright/Expo export);仅 workflow_dispatch 成本节流。 + # ── Frontend: Mobile light (PR path-filtered) ── + # typecheck + unit tests only. No Expo export / Playwright / doctor. + # Closes the post-#1341 gap where mobile PRs skipped all mobile CI. + frontend-mobile-light: + name: Frontend (mobile light) + needs: changes + if: >- + github.event_name == 'workflow_dispatch' || + needs.changes.outputs.mobile == 'true' + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: ./app + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: ${{ env.PNPM_VERSION }} + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: pnpm + cache-dependency-path: app/pnpm-lock.yaml + - name: Install + run: pnpm install --frozen-lockfile + - name: Type check (mobile) + run: pnpm --filter agenthub-mobile-rn typecheck + - name: Unit tests (mobile) + run: pnpm --filter agenthub-mobile-rn test + + # ── Frontend: Mobile full ─────────────────────── + # 重 job(Playwright/Expo export/coverage);仅 workflow_dispatch 成本节流。 # job 体保留以满足 verify-ci-gates.ps1;产品 LIVE,非停运。 frontend-mobile: name: Frontend (mobile) @@ -603,6 +642,27 @@ jobs: if-no-files-found: warn retention-days: 7 + # ── Backend perf / leak gates ───────────────── + # Behavior + short microbench only (not production capacity). + # Manual trigger only — residual T5 / post-polish risk assessment. + backend-perf-leak-gates: + name: Backend perf/leak gates + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + cache-dependency-path: | + hub-server/go.sum + edge-server/go.sum + - name: Run backend perf/leak gates + shell: pwsh + run: ./scripts/verify/verify-backend-perf-leak-gates.ps1 -Benchtime 100ms + # ── E2E Smoke ───────────────────────────────── # 重 job;仅 workflow_dispatch 成本节流,需要时手动触发。 e2e-smoke: diff --git a/docs/analysis/post-polish-risk-assessment.md b/docs/analysis/post-polish-risk-assessment.md index 4c98a301..79ab754a 100644 --- a/docs/analysis/post-polish-risk-assessment.md +++ b/docs/analysis/post-polish-risk-assessment.md @@ -58,7 +58,7 @@ | T2 | RN Hub-only boundary note | **Done** #1341 (AGENTS + mobile README) | | T3 | Docs hygiene plan/* + MASTER | **Done** #1340 / #1342 | | T4 | Optional: Desktop demo seed fail-closed | **Open optional** (AH-SR-043 residual) | -| T5 | Optional: perf-gate workflow_dispatch | **Open optional** | +| T5 | Optional: perf-gate workflow_dispatch | **Done** — `backend-perf-leak-gates` job in checks.yml (dispatch-only) | ## 6. Explicit Out of Scope diff --git a/docs/progress/MASTER.md b/docs/progress/MASTER.md index f723c347..79b616b6 100644 --- a/docs/progress/MASTER.md +++ b/docs/progress/MASTER.md @@ -97,3 +97,4 @@ Optional future: wire script as `workflow_dispatch` only — not every PR (see r | 2026-07-20 | SDD Phase 0–1: post-polish analysis trio committed | | 2026-07-21 | Residual program delivered: Phase 79 #1340 + Phase 80 #1341; milestones 98–99 closed; hubClient ~342 LOC | | 2026-07-21 | Closeout #1342 + SSOT sync #1343 · tip `1ac86aa5`; analysis inventory marked delivered | +| 2026-07-21 | CI: mobile light path-filter + backend perf/leak `workflow_dispatch` (T5) | diff --git a/docs/roadmap.md b/docs/roadmap.md index aa9526da..8ab00815 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -42,7 +42,8 @@ AgentHub 是 IM 形态的多 Agent 协作工作台。用户面对的是联系人 |---|---|---| | Web/Mobile client test lanes | Web 保持 Hub-only;Mobile 只澄清 RN-safe shared contract gate,不做 native/UI 深入重构 | Web data-boundary checks + Mobile mock-Hub/Expo Doctor boundary notes | | Desktop packaged boundary | 区分 Vite renderer、Tauri packaged、sidecar、icon、installer/signing | packaged-release gate 或明确 `real_tested=false` | -| Backend performance/leak | 行为/微基准门禁已绿;不声明生产容量 | [reference/backend-performance-gates.md](reference/backend-performance-gates.md) + `scripts/verify/verify-backend-perf-leak-gates.ps1` **PASS**(非 capacity) | +| Backend performance/leak | 行为/微基准门禁已绿;不声明生产容量;**手动** `workflow_dispatch` job `Backend perf/leak gates` | [reference/backend-performance-gates.md](reference/backend-performance-gates.md) + `scripts/verify/verify-backend-perf-leak-gates.ps1` **PASS**(非 capacity) | +| Mobile PR CI light | mobile-rn / shared hubClient 变更跑 typecheck+unit;重 suite 仍 dispatch | checks.yml `frontend-mobile-light` path-filter | | API contract hygiene | REST 以 `api/openapi.yaml` 为准,WS 以 `api/events.md` 为准 | OpenAPI parse + endpoint tests | ### P2 diff --git a/scripts/verify/verify-ci-gates.ps1 b/scripts/verify/verify-ci-gates.ps1 index cca03527..238218e2 100644 --- a/scripts/verify/verify-ci-gates.ps1 +++ b/scripts/verify/verify-ci-gates.ps1 @@ -59,10 +59,12 @@ $backendFocused = Get-JobBlock $workflow "backend-focused-subset" $desktop = Get-JobBlock $workflow "frontend-desktop" $web = Get-JobBlock $workflow "frontend-web" $mobile = Get-JobBlock $workflow "frontend-mobile" +$mobileLight = Get-JobBlock $workflow "frontend-mobile-light" $e2e = Get-JobBlock $workflow "e2e-smoke" $changes = Get-JobBlock $workflow "changes" $visualShell = Get-JobBlock $workflow "visual-qa-shell" $validate = Get-JobBlock $workflow "validate" +$backendPerf = Get-JobBlock $workflow "backend-perf-leak-gates" Assert-Contains $edge "Coverage check \(informational\)" "go-edge overall coverage must stay informational" Assert-Contains $edge "Coverage per-package minimums" "go-edge must keep per-package coverage minimums" @@ -132,6 +134,7 @@ foreach ($job in @( @{ Name = "frontend-desktop"; Body = $desktop; Lockfile = "app/pnpm-lock.yaml" }, @{ Name = "frontend-web"; Body = $web; Lockfile = "app/pnpm-lock.yaml" }, @{ Name = "frontend-mobile"; Body = $mobile; Lockfile = "app/pnpm-lock.yaml" }, + @{ Name = "frontend-mobile-light"; Body = $mobileLight; Lockfile = "app/pnpm-lock.yaml" }, @{ Name = "e2e-smoke"; Body = $e2e; Lockfile = "app/pnpm-lock.yaml" }, @{ Name = "visual-qa-shell"; Body = $visualShell; Lockfile = "app/pnpm-lock.yaml" } )) { @@ -154,6 +157,23 @@ Assert-Contains $validate "check-secrets\.sh" "validate job must keep secret gua Assert-Contains $mobile "(?m)^\s+timeout-minutes:\s+45\s*$" "frontend-mobile job must have a hard timeout" Assert-Contains (Get-StepBlock $mobile "Screenshot visual QA (mobile)") "(?m)^\s+timeout-minutes:\s+12\s*$" "mobile visual QA must have a hard timeout" Assert-Contains (Get-StepBlock $mobile "E2E (mock hub)") "(?m)^\s+timeout-minutes:\s+10\s*$" "mobile mock-hub E2E must have a hard timeout" +Assert-Contains $mobile "github.event_name == 'workflow_dispatch'" "frontend-mobile full suite must stay workflow_dispatch-only" + +Assert-Contains $mobileLight "Frontend \(mobile light\)" "frontend-mobile-light must use a clear job name" +Assert-Contains $mobileLight "needs:\s+changes" "frontend-mobile-light must depend on unified changes job" +Assert-Contains $mobileLight "needs.changes.outputs.mobile" "frontend-mobile-light must path-filter on mobile" +Assert-Contains $mobileLight "(?m)^\s+timeout-minutes:\s+15\s*$" "frontend-mobile-light job must have a hard timeout" +Assert-Contains $mobileLight ([regex]::Escape("pnpm --filter agenthub-mobile-rn typecheck")) "frontend-mobile-light must typecheck mobile" +Assert-Contains $mobileLight ([regex]::Escape("pnpm --filter agenthub-mobile-rn test")) "frontend-mobile-light must run mobile unit tests" +Assert-NotContains $mobileLight "npx expo export" "frontend-mobile-light must not run Expo export" +Assert-NotContains $mobileLight "scripts/visual-qa\.mjs" "frontend-mobile-light must not run mobile visual QA" +Assert-NotContains $mobileLight "playwright install --with-deps" "frontend-mobile-light must not install Playwright" + +Assert-Contains $backendPerf "Backend perf/leak gates" "backend-perf-leak-gates must use a clear job name" +Assert-Contains $backendPerf "github.event_name == 'workflow_dispatch'" "backend-perf-leak-gates must be workflow_dispatch-only" +Assert-Contains $backendPerf ([regex]::Escape("verify-backend-perf-leak-gates.ps1")) "backend-perf-leak-gates must run the perf/leak script" +Assert-Contains $backendPerf "(?m)^\s+timeout-minutes:\s+20\s*$" "backend-perf-leak-gates must have a hard timeout" +Assert-NotContains $backendPerf "load-test" "backend-perf-leak-gates must not claim load/capacity smoke" Assert-Contains $changes "dorny/paths-filter@v3" "changes job must use dorny/paths-filter" Assert-Contains $changes ([regex]::Escape("app/shared/src/workbench/**")) "changes job must watch workbench paths" @@ -163,6 +183,8 @@ Assert-Contains $changes ([regex]::Escape("app/desktop/scripts/visual-qa*")) "ch Assert-Contains $changes ([regex]::Escape(".github/workflows/checks.yml")) "changes job must watch checks.yml" Assert-Contains $changes ([regex]::Escape("hub-server/**")) "changes job must watch hub-server paths" Assert-Contains $changes ([regex]::Escape("edge-server/**")) "changes job must watch edge-server paths" +Assert-Contains $changes ([regex]::Escape("app/mobile-rn/**")) "changes job must watch mobile-rn paths" +Assert-Contains $changes "(?m)^\s+mobile:\s*$" "changes job must expose mobile output" Assert-Contains $visualShell "Visual QA shell \(web, path-filtered\)" "visual-qa-shell must use a clear job name" Assert-Contains $visualShell "needs:\s+changes" "visual-qa-shell must depend on unified changes job" From 42f1ce631235d9c8878414f4db489a5aae373666 Mon Sep 17 00:00:00 2001 From: Delicious233 <101502465+DeliciousBuding@users.noreply.github.com> Date: Tue, 21 Jul 2026 17:15:55 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(mobile):=20=E8=A1=A5=E9=BD=90=20DESKTOP?= =?UTF-8?q?=20glass=20=E5=88=AB=E5=90=8D=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/mobile-rn/src/theme/tokens.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/mobile-rn/src/theme/tokens.ts b/app/mobile-rn/src/theme/tokens.ts index e7021d02..7655aaf8 100644 --- a/app/mobile-rn/src/theme/tokens.ts +++ b/app/mobile-rn/src/theme/tokens.ts @@ -154,6 +154,11 @@ export const agentHubMobileTokenAliases = { '--td-shadow-lg': 'shadow.lg', '--td-shadow-panel': 'shadow.panel', '--td-shadow-hairline': 'shadow.hairline', + // Desktop glass contract (DESKTOP_GLASS_TOKEN_ALIASES). + // RN has no CSS backdrop-filter; map blur/elev to nearest elevation proxies. + '--td-glass-blur': 'shadow.lg', + '--td-glass-card': 'color.surface', + '--td-glass-elev': 'shadow.md', } as const; const semanticTypeRoles = { From b44fab148d77f39695d0dcc78dd3111f7e13fb3c Mon Sep 17 00:00:00 2001 From: Delicious233 <101502465+DeliciousBuding@users.noreply.github.com> Date: Tue, 21 Jul 2026 17:27:24 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(ci):=20=E5=AF=B9=E9=BD=90=20CORS=20?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E6=BA=90=E4=B8=8E=20P0=20=E8=BF=9C=E6=8E=A7?= =?UTF-8?q?=20roadmap=20=E5=90=88=E5=90=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/roadmap.md | 1 + hub-server/internal/middleware/middleware_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/roadmap.md b/docs/roadmap.md index 8ab00815..2bbffd06 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -32,6 +32,7 @@ AgentHub 是 IM 形态的多 Agent 协作工作台。用户面对的是联系人 | 方向 | 目标 | 验收 | |---|---|---| | 真实 E2E 合同 | `.agents/skills/real-e2e-acceptance/SKILL.md` 是唯一证据等级矩阵 | `scripts/verify/verify-real-e2e-contract.ps1` | +| 远控拓扑前置合同 | P0 remote-control fixture 验证 `Web -> Hub -> Desktop/Edge -> Local Edge -> CLI/SDK adapter` 的离线拓扑形状,不声明真实登录或真实执行 | `scripts/verify/verify-p0-remote-control-fixture.ps1` | | Chat flow 可靠性 | 发送不消失、消息线性排序、自动滚动、卡片合并、markdown/table 渲染 | Desktop/Web Playwright + Visual QA | | Hub/Edge 安全边界 | TokenDance ID 只做身份,AgentHub 权限由 Hub 本地资源检查决定 | 后端 auth/permission tests + security risk register | | 文档 SSOT 保持 | MASTER/roadmap 与 closed residual 对齐;不复活 cleanup Phase 61 叙事 | `scripts/verify/verify-doc-ssot.ps1` | diff --git a/hub-server/internal/middleware/middleware_test.go b/hub-server/internal/middleware/middleware_test.go index 6379847b..52f6a369 100644 --- a/hub-server/internal/middleware/middleware_test.go +++ b/hub-server/internal/middleware/middleware_test.go @@ -40,12 +40,12 @@ func TestDefaultCORSOrigins(t *testing.T) { env string want string }{ - {name: "production", env: "production", want: "https://hub.vectorcontrol.tech,https://tauri.localhost"}, - {name: "prod alias", env: "prod", want: "https://hub.vectorcontrol.tech,https://tauri.localhost"}, - {name: "release alias", env: "release", want: "https://hub.vectorcontrol.tech,https://tauri.localhost"}, - {name: "development", env: "development", want: "https://hub.vectorcontrol.tech,http://localhost:3000,http://localhost:5173,http://127.0.0.1:5173,http://localhost:5174,http://127.0.0.1:5174,https://tauri.localhost"}, - {name: "staging", env: "staging", want: "https://hub.vectorcontrol.tech,http://localhost:3000,http://localhost:5173,http://127.0.0.1:5173,http://localhost:5174,http://127.0.0.1:5174,https://tauri.localhost"}, - {name: "empty env", env: "", want: "https://hub.vectorcontrol.tech,http://localhost:3000,http://localhost:5173,http://127.0.0.1:5173,http://localhost:5174,http://127.0.0.1:5174,https://tauri.localhost"}, + {name: "production", env: "production", want: "https://hub.tokendancelab.com,https://hub.vectorcontrol.tech,https://tauri.localhost"}, + {name: "prod alias", env: "prod", want: "https://hub.tokendancelab.com,https://hub.vectorcontrol.tech,https://tauri.localhost"}, + {name: "release alias", env: "release", want: "https://hub.tokendancelab.com,https://hub.vectorcontrol.tech,https://tauri.localhost"}, + {name: "development", env: "development", want: "https://hub.tokendancelab.com,https://hub.vectorcontrol.tech,http://localhost:3000,http://localhost:5173,http://127.0.0.1:5173,http://localhost:5174,http://127.0.0.1:5174,https://tauri.localhost"}, + {name: "staging", env: "staging", want: "https://hub.tokendancelab.com,https://hub.vectorcontrol.tech,http://localhost:3000,http://localhost:5173,http://127.0.0.1:5173,http://localhost:5174,http://127.0.0.1:5174,https://tauri.localhost"}, + {name: "empty env", env: "", want: "https://hub.tokendancelab.com,https://hub.vectorcontrol.tech,http://localhost:3000,http://localhost:5173,http://127.0.0.1:5173,http://localhost:5174,http://127.0.0.1:5174,https://tauri.localhost"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {