ci: split architecture gate into parallel jobs and drop duplicate event policy run

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
InfinityPacer
2026-09-28 19:23:36 +08:00
committed by InfinityPacer
parent ec1c1ddcb4
commit 5d6e4b38b2
4 changed files with 31 additions and 9 deletions

View File

@@ -73,8 +73,6 @@ jobs:
tests/test_architecture_egress.py \
tests/test_architecture_event_facts.py \
tests/test_architecture_event_policy.py -q
uv run --locked --no-sync python \
scripts/architecture/event_policy.py
- name: Check host architecture snapshot
run: |
@@ -84,6 +82,30 @@ jobs:
uv run --locked --no-sync python \
scripts/architecture/baseline.py --check-host
# 与 architecture job 并行执行,两者合计即完整的宿主架构门禁
architecture-ratchets:
needs: reuse
if: needs.reuse.outputs.reused != 'true'
runs-on: ubuntu-latest
name: Architecture Ratchet Gate
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up uv
uses: astral-sh/setup-uv@v10.0.1
with:
python-version: '3.14'
enable-cache: true
cache-dependency-glob: |
pyproject.toml
uv.lock
- name: Install dependencies
run: uv sync --locked
- name: Check governed Python types
run: uv run --locked --no-sync mypy --config-file mypy.ini
@@ -215,8 +237,8 @@ jobs:
proof:
name: CI proof (${{ github.sha }})
needs: [architecture, coverage-shard, coverage-report]
if: github.event_name == 'pull_request' && needs.architecture.result == 'success' && needs.coverage-shard.result == 'success' && needs.coverage-report.result == 'success'
needs: [architecture, architecture-ratchets, coverage-shard, coverage-report]
if: github.event_name == 'pull_request' && needs.architecture.result == 'success' && needs.architecture-ratchets.result == 'success' && needs.coverage-shard.result == 'success' && needs.coverage-report.result == 'success'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:

View File

@@ -155,7 +155,6 @@ uv run --locked --no-sync pytest \
tests/test_architecture_egress.py \
tests/test_architecture_event_facts.py \
tests/test_architecture_event_policy.py -q
uv run --locked --no-sync python scripts/architecture/event_policy.py
uv run --locked --no-sync pytest \
tests/test_architecture_contract_baseline.py \
tests/test_architecture_baseline_cli.py -q

View File

@@ -186,7 +186,7 @@ def test_recognize_prefers_explicit_identity(sample_meta, monkeypatch):
- **合并检查复用**:单测/架构与 Pylint 工作流各自保留 PR、push 和手动入口。PR 完整通过全部门禁后,末尾 `CI proof (<github.sha>)` job 记录实际验证的模拟合并提交。合并 push 只在同一工作流的最新 PR 运行完整成功、证明成功、代码树完全相同、模拟合并父提交分别等于 push 前的目标分支和 PR head 时跳过重复门禁。直接 push、强制 push、基线变化、旧工作流缺少证明、失败/未完成运行或 API 异常都执行全量;不依赖提交消息,也不把 PR 的 head SHA 当作测试的合并 SHA。标准 merge/squash/rebase 仅在上述证据一致时复用。GitHub 构建与发布工作流保持独立。
- **去重脚本验证**:`node --test .github/scripts/reuse.test.mjs` 离线覆盖成功复用及保守回退,两个检查工作流均在判定前执行。复用 job 仅持有 contents/actions/pull-requests 读取权限;没有修改分支保护设置。
- **门禁**:`.github/workflows/test.yml` 在指向 `v3` 的 `pull_request` / `push` 及手动触发时,从 `uv.lock` 同步环境。独立 `architecture` job 先运行宿主依赖、运行契约和基线 CLI 快速门禁;其余测试通过 `coverage run --parallel-mode tests/run.py --exclude-architecture-gate --shard N/6` 分到 6 个 job,一次执行同时验证单测并采集覆盖率;门禁已运行的架构测试不在分片中重复执行。每个分片都有独立进程和临时 `CONFIG_DIR`,不共用 SQLite 或进程级状态,由单一报告 job 合并后检查 Application 与 Domain 的固定 80% 基线。
- **门禁**:`.github/workflows/test.yml` 在指向 `v3` 的 `pull_request` / `push` 及手动触发时,从 `uv.lock` 同步环境。宿主架构门禁分为两个并行 job,`architecture` 运行宿主依赖、运行契约、事件政策测试和基线快照,`architecture-ratchets` 运行类型、复杂度、并发、Ruff/mypy ratchet 与启动性能检查;其余测试通过 `coverage run --parallel-mode tests/run.py --exclude-architecture-gate --shard N/6` 分到 6 个 job,一次执行同时验证单测并采集覆盖率;门禁已运行的架构测试不在分片中重复执行。每个分片都有独立进程和临时 `CONFIG_DIR`,不共用 SQLite 或进程级状态,由单一报告 job 合并后检查 Application 与 Domain 的固定 80% 基线。
- **跨仓观察**:`.github/workflows/architecture-observe.yml` 每周或手工检出官方插件仓最新 `main`,使用 `--check-plugins` 比较公开导入、Hook 和动态 API 契约。它只上传 `official-plugin-architecture-report.json`,不会自动刷新 fixture;语义变化必须人工审查后显式执行 `--write-plugins`。
- **静态检查**:`.github/workflows/pylint.yml` 对指向 `v3` 的 PR、推送和手工触发运行 Pylint。PR/推送改动到的 Python 文件是硬门禁;`app/` 全量扫描保留为建议性 JSON 构建工件,存量告警不会掩盖或阻塞本次增量治理。
- **PR 本地验证**:按上文「验证范围与维护者安排」选择 contributor 默认检查或已记录的维护者安排,统一处理证据复用与失败归属;需要断点、输出顺序或测试污染诊断时使用 `--serial`。确认受影响路径与零真实出站,准确标注验证范围;本地执行安排不改变 CI 的全量验证与复用合同。

View File

@@ -28,7 +28,9 @@ def _step_commands(workflow: dict, job_name: str) -> str:
def test_unit_test_workflow_has_independent_host_architecture_gate():
"""主仓 PR 与推送必须在全量分片外快速执行宿主架构门禁。"""
workflow = _load_workflow("test.yml")
commands = _step_commands(workflow, "architecture")
commands = "\n".join(
_step_commands(workflow, job_name) for job_name in ("architecture", "architecture-ratchets")
)
steps = workflow["jobs"]["architecture"]["steps"]
semantic_step = next(
step for step in steps if step.get("name") == "Check event semantic policy"
@@ -47,7 +49,6 @@ def test_unit_test_workflow_has_independent_host_architecture_gate():
assert "tests/test_architecture_dependencies.py" in semantic_step["run"]
assert "tests/test_architecture_adapter_imports.py" in semantic_step["run"]
assert "tests/test_architecture_egress.py" in semantic_step["run"]
assert "scripts/architecture/event_policy.py" in semantic_step["run"]
assert "scripts/architecture/baseline.py" not in semantic_step["run"]
assert not {
"tests/test_architecture_dependencies.py",
@@ -178,7 +179,7 @@ def test_coverage_jobs_parallelize_data_and_keep_one_global_ratchet() -> None:
def test_ci_reuse_requires_successful_proof_for_every_gate() -> None:
"""证明必须依赖全部硬门禁,推送只能通过保守判定跳过昂贵任务。"""
for filename, gates in {
"test.yml": ["architecture", "coverage-shard", "coverage-report"],
"test.yml": ["architecture", "architecture-ratchets", "coverage-shard", "coverage-report"],
"pylint.yml": ["pylint"],
}.items():
jobs = _load_workflow(filename)["jobs"]