151 lines
7.6 KiB
YAML
151 lines
7.6 KiB
YAML
name: governance
|
||
on:
|
||
pull_request:
|
||
push:
|
||
branches: [main]
|
||
|
||
jobs:
|
||
governance:
|
||
runs-on: ubuntu-latest
|
||
env:
|
||
GOPROXY: https://goproxy.cn,direct
|
||
steps:
|
||
- name: Checkout
|
||
env:
|
||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
|
||
run: |
|
||
set -euo pipefail
|
||
# 只物化两个治理 gitlink(Guidelines 与 deps/development-manager),非递归;
|
||
# 其它 gitlink 不物化、不取回。凭据只以 URL 限定的 extraheader 存在于本进程树内。
|
||
python3 - <<'PY_CHECKOUT'
|
||
import os
|
||
from pathlib import Path
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
from urllib.parse import urlsplit
|
||
|
||
# 服务端基址取运行器注入的 GITHUB_SERVER_URL(平台控制、候选 PR 不可影响)。
|
||
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
|
||
parsed_server = urlsplit(SERVER)
|
||
if parsed_server.scheme not in {"http", "https"} or not parsed_server.netloc:
|
||
sys.exit("Checkout server must be the platform-injected http(s) URL")
|
||
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
|
||
# 审查过的白名单,独立于候选 .gitmodules:仅列出两个治理 gitlink。
|
||
REPOSITORIES = {
|
||
"deps/development-guidelines": "Development-Guidelines",
|
||
"deps/development-manager": "Development-Manager",
|
||
}
|
||
TARGET = "weichen/Agent-System-Backend"
|
||
root_token = os.environ.pop("TOKEN", "")
|
||
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
|
||
if not root_token or not component_token:
|
||
sys.exit("Checkout requires TOKEN and SUBMODULE_TOKEN")
|
||
if os.environ.get("GITHUB_REPOSITORY") != TARGET:
|
||
sys.exit("Checkout repository is outside the reviewed allowlist")
|
||
sha = os.environ.get("GITHUB_SHA", "")
|
||
if not re.fullmatch(r"[0-9a-fA-F]{40}", sha):
|
||
sys.exit("Checkout requires a fixed commit SHA")
|
||
|
||
# 丢弃继承的 Git 追踪/配置/SSH 钩子;凭据只存在于单个 Git 进程树的精确 URL 头中。
|
||
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("GIT_")}
|
||
clean_env.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null",
|
||
GIT_CONFIG_GLOBAL="/dev/null", GIT_TERMINAL_PROMPT="0")
|
||
config = {
|
||
"core.hooksPath": "/dev/null", "core.fsmonitor": "false",
|
||
"init.templateDir": "", "credential.helper": "",
|
||
"fetch.recurseSubmodules": "false", "submodule.recurse": "false",
|
||
"http.followRedirects": "false", "protocol.allow": "never",
|
||
"protocol." + urlsplit(SERVER).scheme + ".allow": "always",
|
||
}
|
||
|
||
|
||
def git(repo, *args, extra=None):
|
||
values = config | (extra or {})
|
||
env = clean_env | {"GIT_CONFIG_COUNT": str(len(values))}
|
||
for index, (key, value) in enumerate(values.items()):
|
||
env[f"GIT_CONFIG_KEY_{index}"] = key
|
||
env[f"GIT_CONFIG_VALUE_{index}"] = value
|
||
return subprocess.run(["git", "-C", str(repo), *args], env=env,
|
||
check=True, stdout=subprocess.PIPE).stdout
|
||
|
||
|
||
def materialize_governance(root):
|
||
# -r 列出任意深度的 gitlink,但不进入 submodule 内部;其它 gitlink 只记录、从不取回。
|
||
links = {}
|
||
for entry in git(root, "ls-tree", "-rz", "HEAD").split(b"\0"):
|
||
if not entry:
|
||
continue
|
||
metadata, path = entry.split(b"\t", 1)
|
||
mode, kind, commit = metadata.decode().split()
|
||
if mode == "160000":
|
||
links[path.decode()] = commit
|
||
for name in REPOSITORIES:
|
||
if name not in links:
|
||
raise ValueError("Governance gitlink is missing from the fixed commit")
|
||
raw = git(root, "config", "--no-includes", "--blob", "HEAD:.gitmodules", "--null", "--list")
|
||
entries = {}
|
||
for item in raw.split(b"\0"):
|
||
if not item:
|
||
continue
|
||
key, value = item.decode().split("\n", 1)
|
||
match = re.fullmatch(r"submodule\.(.+)\.(path|url)", key)
|
||
if not match:
|
||
continue
|
||
name, field = match.groups()
|
||
if name not in REPOSITORIES:
|
||
continue
|
||
if field in entries.setdefault(name, {}):
|
||
raise ValueError("Duplicate candidate .gitmodules setting")
|
||
entries[name][field] = value
|
||
# 先整体校验两个治理条目,再进行任何带凭据的取回。
|
||
for name, repository in REPOSITORIES.items():
|
||
fields = entries.get(name, {})
|
||
if fields.get("path") != name:
|
||
raise ValueError("Governance submodule path is outside the reviewed allowlist")
|
||
trusted_url = f"{SERVER}/weichen/{repository}.git"
|
||
if fields.get("url") not in {SSH + repository + ".git", trusted_url}:
|
||
raise ValueError("Governance submodule URL is outside the reviewed allowlist")
|
||
for name, repository in REPOSITORIES.items():
|
||
url = f"{SERVER}/weichen/{repository}.git"
|
||
# 仅持久化审查过的公开 URL,使 Git 在进程级配置消失后仍识别该 submodule。
|
||
git(root, "config", f"submodule.{name}.url", url)
|
||
git(root, "submodule", "update", "--init", "--checkout", "--", name,
|
||
extra={f"submodule.{name}.url": url,
|
||
f"submodule.{name}.update": "checkout",
|
||
f"http.{url}.extraheader": "AUTHORIZATION: token " + component_token})
|
||
child = root / name
|
||
if git(child, "rev-parse", "HEAD").decode().strip() != links[name]:
|
||
raise ValueError("Materialized governance gitlink does not match the fixed SHA")
|
||
|
||
|
||
try:
|
||
root = Path.cwd()
|
||
# 必须是干净的临时工作区;不复用既有 Git 状态。
|
||
if (root / ".git").exists():
|
||
raise ValueError("Checkout requires a fresh workspace")
|
||
git(root, "init", "-q")
|
||
url = f"{SERVER}/{TARGET}.git"
|
||
git(root, "remote", "add", "origin", url)
|
||
git(root, "fetch", "--depth=1", "origin", sha,
|
||
extra={f"http.{url}.extraheader": "AUTHORIZATION: token " + root_token})
|
||
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
|
||
materialize_governance(root)
|
||
except (ValueError, OSError, subprocess.CalledProcessError) as error:
|
||
sys.exit(f"Checkout refused: {error}")
|
||
print("Checkout complete; credentials were not persisted in Git configuration")
|
||
PY_CHECKOUT
|
||
unset TOKEN SUBMODULE_TOKEN
|
||
- name: Install Go 1.27.1
|
||
run: |
|
||
set -euo pipefail
|
||
curl -fsSL -o "$RUNNER_TEMP/go.tgz" https://mirrors.aliyun.com/golang/go1.27.1.linux-amd64.tar.gz
|
||
tar -C /usr/local -xzf "$RUNNER_TEMP/go.tgz"
|
||
ln -sfn /usr/local/go/bin/go /usr/local/bin/go
|
||
ln -sfn /usr/local/go/bin/gofmt /usr/local/bin/gofmt
|
||
- name: Validate agent execution configuration
|
||
run: scripts/agent-exec validate --repo .
|
||
- name: Governance check
|
||
run: scripts/dm governance check --repo .
|