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
104 changes: 84 additions & 20 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,26 @@ jobs:
# profiles with scripts/verify/merge-coverprofiles.py (repo-local).
go-edge:
needs: [changes, go-edge-test]
if: >-
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.go == 'true'
# Required check: always report a result so a path-filtered skip cannot
# block branch protection. Real gates below run only for Go changes.
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: edge-server
steps:
- uses: actions/checkout@v7
- if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
uses: actions/checkout@v7

- uses: actions/setup-go@v7
- if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
uses: actions/setup-go@v7
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: edge-server/go.sum

- name: Lint
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
# Wave 10: advisory — gocognit findings in pre-existing complex functions
continue-on-error: true
uses: golangci/golangci-lint-action@v9
Expand All @@ -61,25 +64,29 @@ jobs:
args: --timeout=5m

- name: Download shard coverage data
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
uses: actions/download-artifact@v7
with:
pattern: go-edge-coverage-shard-*
path: edge-server/cov-shards

- name: Merge shard coverage (coverprofile text)
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
run: |
python ../scripts/verify/merge-coverprofiles.py \
cov-shards/go-edge-coverage-shard-1/shard-1.out \
cov-shards/go-edge-coverage-shard-2/shard-2.out \
-o coverage.out

- name: Coverage check (informational)
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
run: |
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
echo "Overall coverage: ${COVERAGE}%"
echo "::notice::edge-server overall coverage: ${COVERAGE}%"

- name: Coverage per-package minimums
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
run: |
echo "=== Per-package coverage minimums ==="
check_pkg() {
Expand All @@ -106,6 +113,7 @@ jobs:
# Restore must precede both gosec and staticcheck steps.
- name: Cache static-analysis binaries (staticcheck + gosec)
id: sa-cache
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
uses: actions/cache@v5
with:
path: |
Expand All @@ -114,6 +122,7 @@ jobs:
key: sa-tools-2026-08

- name: Security scan (gosec)
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
# #1574: hard fail (findings zero-baselined). Fail-closed wrapper
# verify-gosec-gates.sh rejects empty/unparseable output (no false green).
run: |
Expand All @@ -122,9 +131,11 @@ jobs:
fi
~/go/bin/gosec -fmt=json ./... 2>/dev/null | bash ../scripts/verify/verify-gosec-gates.sh
- name: Vet
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
run: go vet ./...

- name: staticcheck
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
run: |
if [ ! -x ~/go/bin/staticcheck ]; then
go install honnef.co/go/tools/cmd/staticcheck@2026.1
Expand All @@ -136,13 +147,33 @@ jobs:
# internal/orchestration 不得 import 任何 adapters。此前该脚本只在
# 本地跑、CI 未接线——治理声明与执行不一致,现接进 go-edge(已 setup-go)。
- name: Verify orchestrator dependency direction (#1566)
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
working-directory: .
run: python scripts/verify/verify-orchestrator-deps.py

- name: Self-test orchestrator dependency direction gate
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
working-directory: .
run: python scripts/verify/tests/verify-orchestrator-deps.Tests.py

# Fallback: keep the required check reported even when the Go path
# filter deselects this job (skipped jobs never satisfy branch
# protection and permanently block frontend-only PRs).
- name: Report no-Go-changes skip (required check)
if: ${{ !cancelled() && github.event_name != 'workflow_dispatch' && needs.changes.result == 'success' && needs.changes.outputs.go != 'true' }}
run: |
echo "skipped: no Go changes; reporting success for required check go-edge"
exit 0

Comment thread
coderabbitai[bot] marked this conversation as resolved.
# Fail-closed: when the path filter itself fails, the Go verdict is
# unknown. Report failure instead of letting the skip fallback mask
# the skipped real gates as green.
- name: Fail when Go path filter failed
if: ${{ !cancelled() && github.event_name != 'workflow_dispatch' && needs.changes.result != 'success' }}
run: |
echo "::error::changes job result: ${{ needs.changes.result }}; cannot decide Go gates for go-edge"
exit 1

# ── Go: Edge Server tests (shard×2) ──────────
# CI5 wave2 (#1689): the single-pass `go test ./... -race -coverprofile`
# (~116s wall) is split into a 2-shard matrix; each shard gets its own
Expand Down Expand Up @@ -197,28 +228,34 @@ jobs:
# ── Go: Hub Server ──────────────────────────
go-hub:
needs: [changes, go-hub-test]
if: >-
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.go == 'true'
# Required check: always report a result so a path-filtered skip cannot
# block branch protection. Real gates below run only for Go changes.
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: hub-server
steps:
- uses: actions/checkout@v7
- if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
uses: actions/checkout@v7

- uses: actions/setup-go@v7
- if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
uses: actions/setup-go@v7
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: hub-server/go.sum

- name: Lint
# #1657: hard gate — lint failures now block merges. The 17
# pre-existing complexity findings remain baseline-registered in the
# #1573 fingerprint ratchet, so only-new-issues keeps those from
# failing this step until the debt is repaid; once the baseline is
# empty this knob can be dropped for a full-report hard fail.
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
# #1657/#1832: report-only — the action's raw exit code cannot consult
# the #1573 baseline, and GitHub refuses PR diffs >300 files, so the
# action falls back to a full-repo lint that reports the 17
# baseline-registered complexity findings and would hard-fail a
# debt-clean large PR. The hard gate lives in the fingerprint ratchet
# step below. only-new-issues stays so small PRs still produce a
# new-findings-only report.
continue-on-error: true
uses: golangci/golangci-lint-action@v9
with:
working-directory: hub-server
Expand All @@ -228,16 +265,18 @@ jobs:

# #1573: finding fingerprint ratchet — any finding NOT in the baseline
# (new linter, new rule, escalated severity, replacement with same count)
# hard-fails this job even though pre-existing baseline findings stay
# advisory until the debt is repaid to zero. Reads the same JSON report
# the Lint step just produced (single source, no version drift).
# hard-fails this job. Baseline-registered findings pass whether the
# report is patch-filtered or the >300-file full-lint fallback, so the
# 17 pre-existing complexity findings never block a debt-clean PR.
# Reads the same JSON report the Lint step just produced (single source,
# no version drift; gofmt is excluded as toolchain-drift-prone).
- name: Verify Hub lint fingerprint ratchet (#1573)
# Wave 10: CI golangci-lint version produces different findings; re-baseline on CI
continue-on-error: true
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
working-directory: .
run: python ./scripts/verify/verify-hub-lint-ratchet.py --LintJsonPath "${{ runner.temp }}/hub-lint-report.json"

- name: Self-test Hub lint fingerprint ratchet
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
working-directory: .
run: python ./scripts/verify/tests/verify-hub-lint-ratchet.Tests.py

Expand All @@ -246,19 +285,22 @@ jobs:
# scripts/verify/merge-coverprofiles.py and keeps the overall >=40%
# gate hard-blocking.
- name: Download shard coverage data
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
uses: actions/download-artifact@v7
with:
pattern: go-hub-coverage-shard-*
path: hub-server/cov-shards

- name: Merge shard coverage (coverprofile text)
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
run: |
python ../scripts/verify/merge-coverprofiles.py \
cov-shards/go-hub-coverage-shard-1/shard-1.out \
cov-shards/go-hub-coverage-shard-2/shard-2.out \
-o coverage.out

- name: Coverage check (overall >= 40%)
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
run: |
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
THRESHOLD=40
Expand All @@ -274,6 +316,7 @@ jobs:
# Restore must precede both gosec and staticcheck steps.
- name: Cache static-analysis binaries (staticcheck + gosec)
id: sa-cache
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
uses: actions/cache@v5
with:
path: |
Expand All @@ -282,6 +325,7 @@ jobs:
key: sa-tools-2026-08

- name: Security scan (gosec)
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
# #1574: hard fail (findings zero-baselined), matching go-edge.
run: |
if [ ! -x ~/go/bin/gosec ]; then
Expand All @@ -290,15 +334,35 @@ jobs:
~/go/bin/gosec -fmt=json ./... 2>/dev/null | bash ../scripts/verify/verify-gosec-gates.sh

- name: Vet
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
run: go vet ./...

- name: staticcheck
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.go == 'true'
run: |
if [ ! -x ~/go/bin/staticcheck ]; then
go install honnef.co/go/tools/cmd/staticcheck@2026.1
fi
staticcheck ./... 2>&1

# Fallback: keep the required check reported even when the Go path
# filter deselects this job (skipped jobs never satisfy branch
# protection and permanently block frontend-only PRs).
- name: Report no-Go-changes skip (required check)
if: ${{ !cancelled() && github.event_name != 'workflow_dispatch' && needs.changes.result == 'success' && needs.changes.outputs.go != 'true' }}
run: |
echo "skipped: no Go changes; reporting success for required check go-hub"
exit 0

# Fail-closed: when the path filter itself fails, the Go verdict is
# unknown. Report failure instead of letting the skip fallback mask
# the skipped real gates as green.
- name: Fail when Go path filter failed
if: ${{ !cancelled() && github.event_name != 'workflow_dispatch' && needs.changes.result != 'success' }}
run: |
echo "::error::changes job result: ${{ needs.changes.result }}; cannot decide Go gates for go-hub"
exit 1

# ── Go: Hub Server tests (shard×2) ───────────
# CI5 wave2 (#1689): same 2-shard package split as go-edge-test
# (Go has no native -shard flag; round-robin on `go list ./...`).
Expand Down
4 changes: 2 additions & 2 deletions docs/governance/verifier-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

| 规则 | 验证脚本 | CI job |
|---|---|---|
| CI 路径筛选与 job 结构(统一 `changes` job;design-css fail-closed 变异测试) | `scripts/verify/verify-ci-gates.py`(负向自测 `scripts/verify/tests/verify-ci-gates.Tests.py`) | checks.yml → validate |
| CI 路径筛选与 job 结构(统一 `changes` job;design-css fail-closed 变异测试;go-edge/go-hub 恒报 report 防 required check 跳过阻塞) | `scripts/verify/verify-ci-gates.py`(负向自测 `scripts/verify/tests/verify-ci-gates.Tests.py`) | checks.yml → validate |
| action runtime 只允许 node24(防 Node-20 major 回退,#1580) | `scripts/verify/verify-action-runtimes.py`(负向自测 `scripts/verify/tests/verify-action-runtimes.Tests.py`) | checks.yml → validate |
| Hub lint finding fingerprint ratchet(防新增/替换,#1573) | `scripts/verify/verify-hub-lint-ratchet.py`(负向自测 `scripts/verify/tests/verify-hub-lint-ratchet.Tests.py`,baseline `scripts/verify/hub-lint-baseline.json`) | checks.yml → go-hub |
| Hub lint finding fingerprint ratchet(防新增/替换,#1573;go-hub 硬门禁,全量 lint 回退模式对 baseline 存量豁免) | `scripts/verify/verify-hub-lint-ratchet.py`(负向自测 `scripts/verify/tests/verify-hub-lint-ratchet.Tests.py`,baseline `scripts/verify/hub-lint-baseline.json`) | checks.yml → go-hub |
| skill 白名单只提交 active skill | `scripts/verify/verify-project-skills.py` | checks.yml → validate |
| 文档与 Agent 入口 SSOT:根级入口/路径/行数/标记/映射表保鲜 | `scripts/verify/verify-doc-ssot.py`(负向自测 `scripts/verify/tests/verify-doc-entrypoints.Tests.py`) | checks.yml → validate |
| Web Hub-only 边界(不直连 Local Edge) | `scripts/verify/verify-web-hub-boundary.py` | checks.yml → validate |
Expand Down
6 changes: 3 additions & 3 deletions scripts/verify/quality-debt-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
"review_by": "2026-10-01"
},
{
"location": "go-hub: Verify Hub lint fingerprint ratchet (#1573)",
"location": "go-hub: Lint",
"kind": "continue-on-error",
"reason": "CI golangci-lint version produces findings differing from local baseline; re-baseline on CI before re-hardening (#1573)",
"reason": "lint 硬门禁已移到 fingerprint ratchet step(#1573);Lint step 因 golangci-lint-action 退出码不读 baseline 且 >300 文件 PR 全量回退,保持 report-only (#1657/#1832)",
"issue": 1573,
"owner": "hub-owners",
"introduced_at": "2026-08-11",
"introduced_at": "2026-08-21",
"review_by": "2026-10-01"
},
{
Expand Down
Loading
Loading