Gate svc lifecycles on real Dev and RDM artifacts in CI
Why: stub suites proved the script logic but not real artifact behavior (the 2026-10-12 incidents passed all stub tests); the report's U3 requires real-combination gates, and realcombo dry-runs exposed two real gaps: fresh deploys closed identity governance one lifecycle late, and pg-bound identity calls put the DSN on the command line. Evidence: make test exit 0; local realcombo dry-run 16 OK (rows in the task card); CI adds svc-realcombo (SQLite, both real binaries, full RDM track) and a real-PG identity step on test-pg with env-delivered DSN. Impact: tests/test_svc_realcombo.py (SVC_TEST_REAL gate, explicit NOT_RUN otherwise); ci.yaml new job + test-pg step (RDM clone via trusted-base URL-scoped token header, allowlist addition reviewed in this PR); svc.sh delivers identity DSN via AGENT_SYSTEM_DSN and runs manifest adoption in the same deploy that first registers svc.env; modules/backend pinned to main (env fallback, backend #182). Refs: .task/feature-svc-u3-realcombo.md; user goal directive 2026-10-12 (U3); backend #182; RDM #285.
This commit is contained in:
@@ -377,3 +377,246 @@ jobs:
|
||||
env:
|
||||
AGENT_PG_TEST_DSN: postgres://ci_agent:ci_job_local_only@127.0.0.1:5432/agent_test?sslmode=disable
|
||||
run: make test-multi-instance
|
||||
- name: Clone Agent-System-RDM with nested pins (reviewed allowlist addition)
|
||||
env:
|
||||
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# 凭据纪律与 Checkout 块同源:经 GIT_CONFIG_* 环境注入 URL 级 token 头,
|
||||
# 不落文件/命令行;allowlist 扩项 = Agent-System-RDM 及其 pins(同一
|
||||
# 组件集合),由本 PR 评审背书。SSH pins URL 以 insteadOf 改写到受信
|
||||
# https 基址后再取。
|
||||
SERVER="$(printf '%s' "$GITHUB_SERVER_URL" | sed 's:/*$::')"
|
||||
scheme="${GITHUB_SERVER_URL%%://*}"
|
||||
export GIT_CONFIG_COUNT=4
|
||||
export GIT_CONFIG_KEY_0="protocol.$scheme.allow"
|
||||
export GIT_CONFIG_VALUE_0="always"
|
||||
export GIT_CONFIG_KEY_1="http.$SERVER/.extraheader"
|
||||
export GIT_CONFIG_VALUE_1="AUTHORIZATION: token $SUBMODULE_TOKEN"
|
||||
export GIT_CONFIG_KEY_2="url.$SERVER/weichen/.insteadOf"
|
||||
export GIT_CONFIG_VALUE_2="ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
|
||||
export GIT_CONFIG_KEY_3="core.hooksPath"
|
||||
export GIT_CONFIG_VALUE_3="/dev/null"
|
||||
git clone --depth=1 "$SERVER/weichen/Agent-System-RDM.git" rdm
|
||||
unset GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0 \
|
||||
GIT_CONFIG_KEY_1 GIT_CONFIG_VALUE_1 GIT_CONFIG_KEY_2 \
|
||||
GIT_CONFIG_VALUE_2 GIT_CONFIG_KEY_3 GIT_CONFIG_VALUE_3
|
||||
unset SUBMODULE_TOKEN
|
||||
git -C rdm submodule update --init --recursive
|
||||
echo "RDM commit: $(git -C rdm rev-parse HEAD)"
|
||||
make -C rdm build
|
||||
- name: Real-combination identity gates over real PostgreSQL (U3, required)
|
||||
env:
|
||||
SVC_TEST_REAL: "1"
|
||||
SVC_TEST_PG: "1"
|
||||
SVC_REAL_RDM_BIN: $PWD/rdm/bin/agent-system-rdm
|
||||
AGENT_PG_TEST_DSN: postgres://ci_agent:ci_job_local_only@127.0.0.1:5432/agent_test?sslmode=disable
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 -m unittest tests.test_svc_realcombo -v
|
||||
|
||||
# U3(统一部署方案):真实 Dev/RDM 制品的 svc.sh 生命周期门禁——桩套件
|
||||
# (test_svc*.py)只证明脚本逻辑;本作业证明「声明≠能力」的最后一道:真实
|
||||
# identity 领养/verify、release 切换、伴生工作台、RDM 全轨部署。缺件即失败,
|
||||
# 不静默跳过(SVC_TEST_REAL=1 硬开关)。
|
||||
svc-realcombo:
|
||||
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
|
||||
# Credential handling stays inline: no file from the candidate is executed.
|
||||
# Workflow provenance and runner isolation still require platform controls.
|
||||
python3 - <<'PY_CHECKOUT'
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
# 服务端基址取运行器注入的 GITHUB_SERVER_URL(平台控制、候选 PR 不可影响)。
|
||||
# 2026-10-05 修复(run 948/949 实测):注入值为内网直连形态
|
||||
# 'http://172.17.0.1:3000'(docker 网关 → 宿主 Gitea),与公网拼写不同且为
|
||||
# http——此前硬编码常量严格比对会拒绝合法检出。校验收宽为「http(s) + 非空
|
||||
# netloc」;凭据仍只附着于「该基址 + 下方仓库白名单」构造的 URL。
|
||||
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/"
|
||||
# Reviewed allowlist, independent of candidate .gitmodules. Add repositories
|
||||
# here only with an explicit review of the checkout credential's scope.
|
||||
REPOSITORIES = {
|
||||
"modules/core": "Agent-System-Core",
|
||||
"modules/backend": "Agent-System-Backend",
|
||||
"modules/frontend": "Agent-System-Frontend",
|
||||
"modules/ui": "Agent-System-UI",
|
||||
"modules/documents": "Agent-System-Documents",
|
||||
"deps/agent-system-core": "Agent-System-Core",
|
||||
"deps/development-guidelines": "Development-Guidelines",
|
||||
"deps/agent-native": "Agent-Native",
|
||||
"deps/agent-squad": "Agent-Squad",
|
||||
"deps/agent-intent": "Agent-Intent",
|
||||
"deps/agent-command": "Agent-Command",
|
||||
"deps/error-code-system": "Error-Code-System",
|
||||
"deps/logging-system": "Logging-System",
|
||||
}
|
||||
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") != "weichen/Agent-System-Dev":
|
||||
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")
|
||||
|
||||
# Discard inherited Git tracing/config/SSH hooks. Auth lives only in one
|
||||
# Git process tree, in an exact URL-scoped header, never in a URL or file.
|
||||
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(repo, depth=0):
|
||||
if depth > 16:
|
||||
raise ValueError("Submodule depth exceeds the reviewed checkout limit")
|
||||
links = {}
|
||||
for entry in git(repo, "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
|
||||
if not links:
|
||||
return
|
||||
raw = git(repo, "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:
|
||||
raise ValueError("Unsupported candidate .gitmodules setting")
|
||||
name, field = match.groups()
|
||||
if field in entries.setdefault(name, {}):
|
||||
raise ValueError("Duplicate candidate .gitmodules setting")
|
||||
entries[name][field] = value
|
||||
if set(entries) != set(links):
|
||||
raise ValueError("Submodule names must match the fixed gitlink paths")
|
||||
# Validate the entire level before any authenticated component fetch.
|
||||
for name, fields in entries.items():
|
||||
repository = REPOSITORIES.get(name)
|
||||
if not repository or fields.get("path") != name:
|
||||
raise ValueError("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("Submodule URL is outside the reviewed allowlist")
|
||||
for name in entries:
|
||||
url = f"{SERVER}/weichen/{REPOSITORIES[name]}.git"
|
||||
# Persist only the reviewed public URL so Git recognizes the
|
||||
# initialized submodule after process-scoped config is gone.
|
||||
git(repo, "config", f"submodule.{name}.url", url)
|
||||
git(repo, "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 = repo / name
|
||||
if git(child, "rev-parse", "HEAD").decode().strip() != links[name]:
|
||||
raise ValueError("Materialized submodule does not match the fixed SHA")
|
||||
materialize(child, depth + 1)
|
||||
|
||||
|
||||
try:
|
||||
root = Path.cwd()
|
||||
# An ephemeral clean checkout is required; do not reuse prior Git state.
|
||||
if (root / ".git").exists():
|
||||
raise ValueError("Checkout requires a fresh workspace")
|
||||
git(root, "init", "-q")
|
||||
url = SERVER + "/weichen/Agent-System-Dev.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(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: Install Node 22
|
||||
run: |
|
||||
set -euo pipefail
|
||||
curl -fsSL -o "$RUNNER_TEMP/node.tar.xz" https://npmmirror.com/mirrors/node/v22.23.3/node-v22.23.3-linux-x64.tar.xz
|
||||
tar -C /usr/local --strip-components=1 -xJf "$RUNNER_TEMP/node.tar.xz"
|
||||
- name: Build the real agent-system binary
|
||||
run: make build
|
||||
- name: Clone Agent-System-RDM with nested pins (reviewed allowlist addition)
|
||||
env:
|
||||
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# 凭据纪律与 Checkout 块同源:经 GIT_CONFIG_* 环境注入 URL 级 token 头,
|
||||
# 不落文件/命令行;allowlist 扩项 = Agent-System-RDM 及其 pins(同一
|
||||
# 组件集合),由本 PR 评审背书。SSH pins URL 以 insteadOf 改写到受信
|
||||
# https 基址后再取。
|
||||
SERVER="$(printf '%s' "$GITHUB_SERVER_URL" | sed 's:/*$::')"
|
||||
scheme="${GITHUB_SERVER_URL%%://*}"
|
||||
export GIT_CONFIG_COUNT=4
|
||||
export GIT_CONFIG_KEY_0="protocol.$scheme.allow"
|
||||
export GIT_CONFIG_VALUE_0="always"
|
||||
export GIT_CONFIG_KEY_1="http.$SERVER/.extraheader"
|
||||
export GIT_CONFIG_VALUE_1="AUTHORIZATION: token $SUBMODULE_TOKEN"
|
||||
export GIT_CONFIG_KEY_2="url.$SERVER/weichen/.insteadOf"
|
||||
export GIT_CONFIG_VALUE_2="ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
|
||||
export GIT_CONFIG_KEY_3="core.hooksPath"
|
||||
export GIT_CONFIG_VALUE_3="/dev/null"
|
||||
git clone --depth=1 "$SERVER/weichen/Agent-System-RDM.git" rdm
|
||||
unset GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0 \
|
||||
GIT_CONFIG_KEY_1 GIT_CONFIG_VALUE_1 GIT_CONFIG_KEY_2 \
|
||||
GIT_CONFIG_VALUE_2 GIT_CONFIG_KEY_3 GIT_CONFIG_VALUE_3
|
||||
unset SUBMODULE_TOKEN
|
||||
git -C rdm submodule update --init --recursive
|
||||
echo "RDM commit: $(git -C rdm rev-parse HEAD)"
|
||||
make -C rdm build
|
||||
- name: Run real-combination lifecycle gates (SQLite, U3, required)
|
||||
env:
|
||||
SVC_TEST_REAL: "1"
|
||||
SVC_REAL_BIN: $PWD/bin/agent-system
|
||||
SVC_REAL_RDM_BIN: $PWD/rdm/bin/agent-system-rdm
|
||||
SVC_RDM_ORIGIN: $PWD/rdm
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 -m unittest tests.test_svc_realcombo -v
|
||||
|
||||
Reference in New Issue
Block a user