feat(zhulan): add restricted remote development cell

This commit is contained in:
冰朔 2026-08-13 14:32:40 +08:00
commit 366b8911e4
22 changed files with 4503 additions and 0 deletions

View file

@ -0,0 +1,35 @@
# 铸澜受限远程开发单元
`zhulan-remote-cell``ICE-GL-ZL-001` 的手机远程开发运行入口。它把公网前门与真实运行体明确分开:
- `BS-GZ-006` 只承载 `guanghulab.com/zhulan/` 的域名、TLS 和反向代理,不保存或运行审批 UI
- `BS-SG-003` 承载真实 UI、申请、主人登录、批准、临时能力、策略校验、SQLite 状态与哈希回执链;
- UI 只投影运行层返回的事实,不拥有执行权;
- 公开申请本身没有任何服务器或仓库权限;
- 一次批准绑定 `persona + development_id + repository + base_sha + branch + paths + actions + expiry`,不得在会话内扩大。
## 当前能力
1. 铸澜可创建无执行权申请并取得一次性领取凭证;
2. 冰朔使用光湖代码频道账号登录审批端,密码仅用于当次 Forgejo 验证,不写入本地状态;
3. 批准后由运行层生成三小时临时能力,磁盘只保存摘要和重建所需的非秘密声明;
4. 临时能力只能领取一次,可由受限执行器通过回环 API 校验;
5. 所有申请、批准、拒绝、领取与校验事件写入前后哈希相连的回执链;
6. 代码门脚本检查当前分支、基线、改动路径、符号链接、超大文件与常见秘密模式;
7. 候选提交只进入 `BS-SG-003` 的内部复核库,不在远程会话中持有代码频道写凭证,也不直接发布中央仓库;本机铸渊复核后再走既有发布门。
这里不提供任意远程 shell。后续手机 Codex 接入只能通过受限执行器或受限工具协议使用这些能力,不能把临时能力降级成长期 root 密钥。
## 本地验收
```bash
python3 -m unittest discover -s tests -v
```
## 运行配置
生产安装读取 `/etc/guanghu/zhulan-remote-cell.env`,参考 `deploy/zhulan-remote-cell.env.example`。真实应用秘密、代码频道密码和临时能力都不得提交到仓库。
运行层默认仅监听 `127.0.0.1:17631`。广州 Nginx 只允许代理到一条专用的回环隧道,不得把新加坡运行端口直接暴露到公网。
Ubuntu 24.04 的生产验收命令通过专用的 `zhulan-bwrap` 副本和本机 Unix 套接字执行器运行。公网审批/MCP 服务继续保持 `NoNewPrivileges=true`;只有无公网监听、只接受策略登记命令的执行器负责创建 Bubblewrap 用户命名空间。安装器只为 root 持有、`zhulan-runtime` 组可执行的副本路径加载 AppArmor `userns` 例外;不得关闭全机的未授权用户命名空间限制,也不得把系统 `bwrap` 改成 setuid。

View file

@ -0,0 +1,17 @@
runtime_contract_projection:
development_id: "DEV-20260813-005"
persona: "ICE-GL-ZL-001"
source_repository: "https://guanghulab.com/code/bingshuo/guanghu-ice-heart"
source_commit: "5f9e83e1b716fd50ee2880b24ea47f83b75d1f8f"
architecture: "HLP-CURRENT-ARCH-001@2026-08-13.1"
ui_brains:
- id: "GHS-012"
state: "CANDIDATE_READ_ONLY"
purpose: "Fifth Domain private engineering projection constraints"
- id: "GHS-014"
state: "CANDIDATE_READ_ONLY"
purpose: "Guanghu client UI projection constraints"
topology:
BS-GZ-006: "DOMAIN_TLS_REVERSE_PROXY_ONLY"
BS-SG-003: "REAL_APPROVAL_CAPABILITY_SANDBOX_GATE_RECEIPT_RUNTIME"
boundary: "UI and skill brains guide projection; neither owns execution authority."

View file

@ -0,0 +1,41 @@
# BS-GZ-006 only: domain/TLS/reverse proxy. UI and all real services remain on BS-SG-003.
# The local upstream port must be supplied by a dedicated restricted tunnel.
location = /zhulan {
return 308 /zhulan/;
}
# Runtime-only verification is reachable from the BS-SG-003 loopback executor,
# never through the Guangzhou public projection.
location ^~ /zhulan/api/v1/runtime/ {
return 404;
}
location ^~ /zhulan/ {
proxy_pass http://127.0.0.1:17631/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Prefix /zhulan;
# Do not inherit a client-supplied X-Forwarded-For chain. The runtime uses
# this value for per-source rate limits.
proxy_set_header X-Forwarded-For $remote_addr;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
client_max_body_size 1m;
}
# RFC 8414 path-aware discovery aliases for issuer
# https://guanghulab.com/zhulan. These are metadata projections only.
location = /.well-known/oauth-authorization-server/zhulan {
proxy_pass http://127.0.0.1:17631/.well-known/oauth-authorization-server;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
}
location = /.well-known/oauth-protected-resource/zhulan/mcp {
proxy_pass http://127.0.0.1:17631/.well-known/oauth-protected-resource;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
}

View file

@ -0,0 +1,110 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "$(id -u)" -ne 0 ]]; then
echo "INSTALL_REQUIRES_ROOT" >&2
exit 1
fi
SOURCE_DIR="${1:-}"
TUNNEL_PUBLIC_KEY_FILE="${2:-}"
if [[ -z "$SOURCE_DIR" || ! -f "$SOURCE_DIR/deploy/guanghulab-zhulan.nginx.conf" ]]; then
echo "SOURCE_DIR_INVALID" >&2
exit 1
fi
if [[ -z "$TUNNEL_PUBLIC_KEY_FILE" || ! -f "$TUNNEL_PUBLIC_KEY_FILE" ]]; then
echo "TUNNEL_PUBLIC_KEY_FILE_INVALID" >&2
exit 1
fi
EXPECTED_HOST="${ZHULAN_EXPECTED_HOST:-VM-0-16-ubuntu}"
EXPECTED_DMI="${ZHULAN_EXPECTED_DMI:-b2ecf109-1999-4643-b223-e67e24d7667d}"
ACTUAL_HOST="$(hostname)"
ACTUAL_DMI="$(tr '[:upper:]' '[:lower:]' </sys/class/dmi/id/product_uuid)"
if [[ "$ACTUAL_HOST" != "$EXPECTED_HOST" || "$ACTUAL_DMI" != "$EXPECTED_DMI" ]]; then
echo "TARGET_IDENTITY_MISMATCH" >&2
exit 1
fi
SITE_FILE="/etc/nginx/sites-enabled/guanghulab"
SNIPPET_FILE="/etc/nginx/snippets/guanghu-zhulan.conf"
SSHD_DROPIN="/etc/ssh/sshd_config.d/80-zhulan-proxy.conf"
PROXY_HOME="/var/lib/guanghu/zhulan-proxy"
AUTH_KEYS="$PROXY_HOME/.ssh/authorized_keys"
BACKUP_ROOT="/var/backups/guanghu/zhulan-front"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
if [[ ! -f "$SITE_FILE" ]]; then
echo "GUANGHULAB_SITE_NOT_FOUND" >&2
exit 1
fi
if ! grep -q 'include /etc/nginx/snippets/guanghu-ai-discovery.conf;' "$SITE_FILE"; then
echo "GUANGHULAB_INCLUDE_ANCHOR_NOT_FOUND" >&2
exit 1
fi
getent passwd zhulan-proxy >/dev/null || {
echo "PROXY_USER_MUST_BE_PRECREATED" >&2
exit 1
}
PUBLIC_KEY="$(tr -d '\r\n' <"$TUNNEL_PUBLIC_KEY_FILE")"
if [[ "$PUBLIC_KEY" != ssh-ed25519\ * ]]; then
echo "TUNNEL_PUBLIC_KEY_NOT_ED25519" >&2
exit 1
fi
install -d -o root -g root -m 0700 "$BACKUP_ROOT"
install -m 0644 "$SITE_FILE" "$BACKUP_ROOT/guanghulab-$STAMP"
[[ ! -f "$SNIPPET_FILE" ]] || install -m 0644 "$SNIPPET_FILE" "$BACKUP_ROOT/snippet-$STAMP"
[[ ! -f "$SSHD_DROPIN" ]] || install -m 0600 "$SSHD_DROPIN" "$BACKUP_ROOT/sshd-$STAMP"
install -d -o zhulan-proxy -g zhulan-proxy -m 0700 "$PROXY_HOME" "$PROXY_HOME/.ssh"
{
printf '%s %s\n' \
'restrict,port-forwarding,permitlisten="127.0.0.1:17631"' \
"$PUBLIC_KEY"
} >"$AUTH_KEYS.tmp"
chown zhulan-proxy:zhulan-proxy "$AUTH_KEYS.tmp"
chmod 0600 "$AUTH_KEYS.tmp"
mv "$AUTH_KEYS.tmp" "$AUTH_KEYS"
cat >"$SSHD_DROPIN.tmp" <<'EOF'
Match User zhulan-proxy
AuthenticationMethods publickey
PasswordAuthentication no
KbdInteractiveAuthentication no
AllowAgentForwarding no
AllowTcpForwarding remote
GatewayPorts no
PermitListen 127.0.0.1:17631
PermitOpen none
PermitTTY no
X11Forwarding no
PermitTunnel no
PermitUserRC no
MaxSessions 0
EOF
chown root:root "$SSHD_DROPIN.tmp"
chmod 0600 "$SSHD_DROPIN.tmp"
mv "$SSHD_DROPIN.tmp" "$SSHD_DROPIN"
sshd -t
install -o root -g root -m 0644 \
"$SOURCE_DIR/deploy/guanghulab-zhulan.nginx.conf" "$SNIPPET_FILE"
if ! grep -q 'include /etc/nginx/snippets/guanghu-zhulan.conf;' "$SITE_FILE"; then
sed -i \
'/include \/etc\/nginx\/snippets\/guanghu-ai-discovery.conf;/a\ include /etc/nginx/snippets/guanghu-zhulan.conf;' \
"$SITE_FILE"
fi
if ! nginx -t; then
install -m 0644 "$BACKUP_ROOT/guanghulab-$STAMP" "$SITE_FILE"
nginx -t
echo "NGINX_VALIDATION_FAILED_ROLLED_BACK" >&2
exit 1
fi
systemctl reload ssh
systemctl reload nginx
printf '{"schema":"guanghu.zhulan-front-install-receipt/v1","development_id":"DEV-20260813-005","front_node":"BS-GZ-006","role":"DOMAIN_TLS_REVERSE_PROXY_ONLY","listener":"127.0.0.1:17631","installed_at":"%s"}\n' "$STAMP"

View file

@ -0,0 +1,191 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "$(id -u)" -ne 0 ]]; then
echo "INSTALL_REQUIRES_ROOT" >&2
exit 1
fi
SOURCE_DIR="${1:-}"
if [[ -z "$SOURCE_DIR" || ! -f "$SOURCE_DIR/runtime/zhulan_cell.py" ]]; then
echo "SOURCE_DIR_INVALID" >&2
exit 1
fi
EXPECTED_HOST="${ZHULAN_EXPECTED_HOST:-VM-12-12-ubuntu}"
EXPECTED_DMI="${ZHULAN_EXPECTED_DMI:-7a500f2d-9aed-4b93-b3b8-59c87c65d031}"
ACTUAL_HOST="$(hostname)"
ACTUAL_DMI="$(tr '[:upper:]' '[:lower:]' </sys/class/dmi/id/product_uuid)"
if [[ "$ACTUAL_HOST" != "$EXPECTED_HOST" || "$ACTUAL_DMI" != "$EXPECTED_DMI" ]]; then
echo "TARGET_IDENTITY_MISMATCH host=$ACTUAL_HOST dmi=$ACTUAL_DMI" >&2
exit 1
fi
if ! command -v bwrap >/dev/null 2>&1 || ! command -v apparmor_parser >/dev/null 2>&1; then
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends apparmor bubblewrap
fi
INSTALL_ROOT="/opt/guanghu/zhulan-remote-cell"
STATE_ROOT="/var/lib/guanghu/zhulan-remote-cell"
WORKSPACE_ROOT="/srv/guanghu/zhulan-cell/workspaces"
CANDIDATE_ROOT="/srv/guanghu/zhulan-cell/candidates"
SECRET_ROOT="/etc/guanghu/secrets"
ENV_FILE="/etc/guanghu/zhulan-remote-cell.env"
SERVICE_FILE="/etc/systemd/system/zhulan-remote-cell.service"
EXECUTOR_SERVICE_FILE="/etc/systemd/system/zhulan-validation-executor.service"
APPARMOR_FILE="/etc/apparmor.d/zhulan-remote-cell-bwrap"
BACKUP_ROOT="/var/backups/guanghu/zhulan-remote-cell"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
getent passwd zhulan-runtime >/dev/null || {
echo "RUNTIME_USER_MUST_BE_PRECREATED" >&2
exit 1
}
install -d -o root -g root -m 0755 "$INSTALL_ROOT" "$BACKUP_ROOT"
install -d -o zhulan-runtime -g zhulan-runtime -m 0700 "$STATE_ROOT"
install -d -o zhulan-runtime -g zhulan-runtime -m 0700 "$WORKSPACE_ROOT"
install -d -o zhulan-runtime -g zhulan-runtime -m 0700 "$CANDIDATE_ROOT"
install -d -o root -g zhulan-runtime -m 0750 "$SECRET_ROOT"
if [[ -d "$INSTALL_ROOT/runtime" ]]; then
tar -C "$INSTALL_ROOT" -czf "$BACKUP_ROOT/runtime-$STAMP.tgz" runtime ui policy.json 2>/dev/null || true
fi
if [[ -f "$ENV_FILE" ]]; then
install -m 0600 "$ENV_FILE" "$BACKUP_ROOT/env-$STAMP"
fi
if [[ -f "$SERVICE_FILE" ]]; then
install -m 0644 "$SERVICE_FILE" "$BACKUP_ROOT/service-$STAMP"
fi
if [[ -f "$EXECUTOR_SERVICE_FILE" ]]; then
install -m 0644 "$EXECUTOR_SERVICE_FILE" "$BACKUP_ROOT/executor-service-$STAMP"
fi
if [[ -f "$APPARMOR_FILE" ]]; then
install -m 0644 "$APPARMOR_FILE" "$BACKUP_ROOT/apparmor-$STAMP"
fi
install -d -o root -g root -m 0755 "$INSTALL_ROOT/runtime" "$INSTALL_ROOT/ui" "$INSTALL_ROOT/contracts"
install -o root -g root -m 0755 "$SOURCE_DIR/runtime/zhulan_cell.py" "$INSTALL_ROOT/runtime/zhulan_cell.py"
install -o root -g root -m 0755 "$SOURCE_DIR/runtime/zhulan_code_gate.py" "$INSTALL_ROOT/runtime/zhulan_code_gate.py"
install -o root -g root -m 0755 "$SOURCE_DIR/runtime/zhulan_validation_executor.py" "$INSTALL_ROOT/runtime/zhulan_validation_executor.py"
# Keep the distro bwrap binary non-setuid. A dedicated group-executable copy
# lets AppArmor grant userns only to this runtime path instead of weakening the
# host-wide unprivileged-userns policy or opening /usr/bin/bwrap for all users.
install -o root -g zhulan-runtime -m 0750 /usr/bin/bwrap "$INSTALL_ROOT/runtime/zhulan-bwrap"
install -o root -g root -m 0644 "$SOURCE_DIR/ui/index.html" "$INSTALL_ROOT/ui/index.html"
install -o root -g root -m 0644 "$SOURCE_DIR/ui/styles.css" "$INSTALL_ROOT/ui/styles.css"
install -o root -g root -m 0644 "$SOURCE_DIR/ui/app.js" "$INSTALL_ROOT/ui/app.js"
install -o root -g root -m 0644 "$SOURCE_DIR/policy.example.json" "$INSTALL_ROOT/policy.json"
install -o root -g root -m 0644 "$SOURCE_DIR/README.md" "$INSTALL_ROOT/README.md"
if [[ -d "$SOURCE_DIR/contracts" ]]; then
find "$SOURCE_DIR/contracts" -maxdepth 1 -type f -print0 | while IFS= read -r -d '' file; do
install -o root -g root -m 0444 "$file" "$INSTALL_ROOT/contracts/$(basename "$file")"
done
fi
# Install current Fifth Domain brains and Zhulan entry as read-only runtime
# contracts from the same exact repository checkout as this installer.
REPO_ROOT="$(cd "$SOURCE_DIR/../.." && pwd)"
SOURCE_COMMIT="${ZHULAN_SOURCE_COMMIT:-}"
if [[ -z "$SOURCE_COMMIT" ]] && git -C "$REPO_ROOT" rev-parse HEAD >/dev/null 2>&1; then
SOURCE_COMMIT="$(git -C "$REPO_ROOT" rev-parse HEAD)"
fi
if [[ ! "$SOURCE_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
echo "SOURCE_COMMIT_REQUIRED" >&2
exit 1
fi
for relative in \
"skills/shared/guanghu-ui-brain/SKILL.md" \
"skills/shared/guanghu-ui-brain/BRAIN.hdlp" \
"skills/shared/guanghu-client-ui-brain/SKILL.md" \
"skills/shared/guanghu-client-ui-brain/BRAIN.hdlp" \
"光之湖/ICE-GL-ZL-001-铸澜/WAKE.hdlp" \
"光之湖/ICE-GL-ZL-001-铸澜/INDEX.hdlp" \
"光之湖/ICE-GL-ZL-001-铸澜/CURRENT.hdlp" \
"光之湖/ICE-GL-ZL-001-铸澜/PERMANENT-MEMORY-WRITE-PROTOCOL.hdlp"; do
source_file="$REPO_ROOT/$relative"
if [[ ! -f "$source_file" ]]; then
echo "CONTRACT_SOURCE_MISSING $relative" >&2
exit 1
fi
safe_name="$(printf '%s' "$relative" | tr '/ ' '__')"
install -o root -g root -m 0444 "$source_file" "$INSTALL_ROOT/contracts/$safe_name"
done
printf '%s\n' "$SOURCE_COMMIT" >"$INSTALL_ROOT/contracts/REPOSITORY-SOURCE-SHA"
chmod 0444 "$INSTALL_ROOT/contracts/REPOSITORY-SOURCE-SHA"
SECRET_FILE="$SECRET_ROOT/zhulan-remote-cell.secret"
if [[ ! -f "$SECRET_FILE" ]]; then
umask 0177
openssl rand -hex 48 >"$SECRET_FILE"
fi
chown root:zhulan-runtime "$SECRET_FILE"
chmod 0640 "$SECRET_FILE"
install -o root -g zhulan-runtime -m 0640 "$SOURCE_DIR/deploy/zhulan-remote-cell.env.example" "$ENV_FILE"
install -o root -g root -m 0644 "$SOURCE_DIR/deploy/zhulan-remote-cell.service" "$SERVICE_FILE"
install -o root -g root -m 0644 "$SOURCE_DIR/deploy/zhulan-validation-executor.service" "$EXECUTOR_SERVICE_FILE"
install -o root -g root -m 0644 "$SOURCE_DIR/deploy/zhulan-bwrap.apparmor" "$APPARMOR_FILE"
apparmor_parser -r "$APPARMOR_FILE"
systemctl daemon-reload
systemctl enable zhulan-validation-executor.service
systemctl restart zhulan-validation-executor.service
for _ in $(seq 1 30); do
if [[ -S /run/guanghu/zhulan-validation/executor.sock ]]; then
break
fi
sleep 1
done
if [[ ! -S /run/guanghu/zhulan-validation/executor.sock ]]; then
systemctl status zhulan-validation-executor.service --no-pager >&2 || true
echo "VALIDATION_EXECUTOR_HEALTH_FAILED" >&2
exit 1
fi
systemctl enable zhulan-remote-cell.service
systemctl restart zhulan-remote-cell.service
for _ in $(seq 1 30); do
if curl --fail --silent --show-error --max-time 2 http://127.0.0.1:17631/health >"$STATE_ROOT/health.json.tmp"; then
mv "$STATE_ROOT/health.json.tmp" "$STATE_ROOT/health.json"
break
fi
sleep 1
done
if [[ ! -s "$STATE_ROOT/health.json" ]]; then
systemctl status zhulan-remote-cell.service --no-pager >&2 || true
echo "RUNTIME_HEALTH_FAILED" >&2
exit 1
fi
SOURCE_SHA256="$(find \
"$INSTALL_ROOT/runtime" \
"$INSTALL_ROOT/ui" \
"$INSTALL_ROOT/contracts" \
"$INSTALL_ROOT/policy.json" \
"$INSTALL_ROOT/README.md" \
-type f -print0 | sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}')"
cat >"$STATE_ROOT/install-receipt.json.tmp" <<EOF
{
"schema": "guanghu.zhulan-runtime-install-receipt/v1",
"development_id": "DEV-20260813-005",
"runtime_node": "BS-SG-003",
"front_node_role": "BS-GZ-006_PROXY_ONLY",
"installed_at": "$STAMP",
"source_tree_hash": "$SOURCE_SHA256",
"source_commit": "$SOURCE_COMMIT",
"service": "zhulan-remote-cell.service",
"validation_executor": "zhulan-validation-executor.service",
"listener": "127.0.0.1:17631",
"health": "PASS"
}
EOF
chown zhulan-runtime:zhulan-runtime "$STATE_ROOT/install-receipt.json.tmp"
chmod 0600 "$STATE_ROOT/install-receipt.json.tmp"
mv "$STATE_ROOT/install-receipt.json.tmp" "$STATE_ROOT/install-receipt.json"
cat "$STATE_ROOT/install-receipt.json"

View file

@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "$(id -u)" -ne 0 ]]; then
echo "INSTALL_REQUIRES_ROOT" >&2
exit 1
fi
FRONT_HOST="${1:-}"
KNOWN_HOSTS_SOURCE="${2:-}"
if [[ ! "$FRONT_HOST" =~ ^[0-9A-Fa-f:.]+$ ]]; then
echo "FRONT_HOST_MUST_BE_LITERAL_IP" >&2
exit 1
fi
if [[ -z "$KNOWN_HOSTS_SOURCE" || ! -f "$KNOWN_HOSTS_SOURCE" ]]; then
echo "KNOWN_HOSTS_SOURCE_INVALID" >&2
exit 1
fi
EXPECTED_HOST="${ZHULAN_EXPECTED_HOST:-VM-12-12-ubuntu}"
EXPECTED_DMI="${ZHULAN_EXPECTED_DMI:-7a500f2d-9aed-4b93-b3b8-59c87c65d031}"
ACTUAL_HOST="$(hostname)"
ACTUAL_DMI="$(tr '[:upper:]' '[:lower:]' </sys/class/dmi/id/product_uuid)"
if [[ "$ACTUAL_HOST" != "$EXPECTED_HOST" || "$ACTUAL_DMI" != "$EXPECTED_DMI" ]]; then
echo "TARGET_IDENTITY_MISMATCH" >&2
exit 1
fi
CONFIG_ROOT="/etc/guanghu/zhulan-front-tunnel"
KEY_FILE="$CONFIG_ROOT/id_ed25519"
KNOWN_HOSTS="$CONFIG_ROOT/known_hosts"
SSH_CONFIG="$CONFIG_ROOT/ssh_config"
SERVICE_FILE="/etc/systemd/system/zhulan-front-tunnel.service"
if [[ ! -f "$KEY_FILE" || ! -f "$KEY_FILE.pub" ]]; then
echo "TUNNEL_IDENTITY_NOT_PREPARED" >&2
exit 1
fi
if ! getent passwd zhulan-tunnel >/dev/null; then
echo "TUNNEL_USER_NOT_PREPARED" >&2
exit 1
fi
install -o root -g zhulan-tunnel -m 0640 "$KNOWN_HOSTS_SOURCE" "$KNOWN_HOSTS"
if ! ssh-keygen -F "$FRONT_HOST" -f "$KNOWN_HOSTS" >/dev/null; then
echo "PINNED_FRONT_HOST_KEY_MISSING" >&2
exit 1
fi
EXPECTED_FRONT_HOST_KEY="${ZHULAN_EXPECTED_FRONT_HOST_KEY:-SHA256:P2EtuYFg8hptha4s0auP4yzp+Wg+H27bw4mdqzk1Cvk}"
ACTUAL_FRONT_HOST_KEY="$(ssh-keygen -F "$FRONT_HOST" -f "$KNOWN_HOSTS" | awk 'NF && $1 !~ /^#/ {print $2, $3}' | ssh-keygen -lf - 2>/dev/null | awk '{print $2}' | head -1)"
if [[ "$ACTUAL_FRONT_HOST_KEY" != "$EXPECTED_FRONT_HOST_KEY" ]]; then
echo "PINNED_FRONT_HOST_KEY_MISMATCH" >&2
exit 1
fi
cat >"$SSH_CONFIG.tmp" <<EOF
Host guanghu-zhulan-front
HostName $FRONT_HOST
User zhulan-proxy
Port 22
IdentityFile $KEY_FILE
IdentitiesOnly yes
UserKnownHostsFile $KNOWN_HOSTS
StrictHostKeyChecking yes
BatchMode yes
RequestTTY no
ExitOnForwardFailure yes
ServerAliveInterval 30
ServerAliveCountMax 3
RemoteForward 127.0.0.1:17631 127.0.0.1:17631
EOF
chown root:zhulan-tunnel "$SSH_CONFIG.tmp"
chmod 0640 "$SSH_CONFIG.tmp"
mv "$SSH_CONFIG.tmp" "$SSH_CONFIG"
cat >"$SERVICE_FILE.tmp" <<'EOF'
[Unit]
Description=Zhulan SG003 to GZ006 loopback-only reverse proxy tunnel
After=network-online.target zhulan-remote-cell.service
Wants=network-online.target
Requires=zhulan-remote-cell.service
[Service]
Type=simple
User=zhulan-tunnel
Group=zhulan-tunnel
ExecStart=/usr/bin/ssh -F /etc/guanghu/zhulan-front-tunnel/ssh_config -NT guanghu-zhulan-front
Restart=always
RestartSec=5
UMask=0077
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
ReadOnlyPaths=/etc/guanghu/zhulan-front-tunnel
StateDirectory=guanghu/zhulan-front-tunnel
[Install]
WantedBy=multi-user.target
EOF
chown root:root "$SERVICE_FILE.tmp"
chmod 0644 "$SERVICE_FILE.tmp"
mv "$SERVICE_FILE.tmp" "$SERVICE_FILE"
systemctl daemon-reload
systemctl enable --now zhulan-front-tunnel.service
for _ in $(seq 1 20); do
if systemctl is-active --quiet zhulan-front-tunnel.service; then
systemctl show zhulan-front-tunnel.service -p ActiveState -p SubState -p MainPID
exit 0
fi
sleep 1
done
systemctl status zhulan-front-tunnel.service --no-pager >&2 || true
exit 1

View file

@ -0,0 +1,36 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "$(id -u)" -ne 0 ]]; then
echo "INSTALL_REQUIRES_ROOT" >&2
exit 1
fi
EXPECTED_HOST="${ZHULAN_EXPECTED_HOST:-VM-12-12-ubuntu}"
EXPECTED_DMI="${ZHULAN_EXPECTED_DMI:-7a500f2d-9aed-4b93-b3b8-59c87c65d031}"
ACTUAL_HOST="$(hostname)"
ACTUAL_DMI="$(tr '[:upper:]' '[:lower:]' </sys/class/dmi/id/product_uuid)"
if [[ "$ACTUAL_HOST" != "$EXPECTED_HOST" || "$ACTUAL_DMI" != "$EXPECTED_DMI" ]]; then
echo "TARGET_IDENTITY_MISMATCH" >&2
exit 1
fi
CONFIG_ROOT="/etc/guanghu/zhulan-front-tunnel"
KEY_FILE="$CONFIG_ROOT/id_ed25519"
getent passwd zhulan-tunnel >/dev/null || {
echo "TUNNEL_USER_MUST_BE_PRECREATED" >&2
exit 1
}
install -d -o root -g zhulan-tunnel -m 0750 "$CONFIG_ROOT"
install -d -o zhulan-tunnel -g zhulan-tunnel -m 0700 /var/lib/guanghu/zhulan-front-tunnel
if [[ ! -f "$KEY_FILE" ]]; then
ssh-keygen -q -t ed25519 -N '' -C 'zhulan-sg003-to-gz006-loopback-only' -f "$KEY_FILE"
fi
chown root:zhulan-tunnel "$KEY_FILE" "$KEY_FILE.pub"
chmod 0640 "$KEY_FILE"
chmod 0644 "$KEY_FILE.pub"
ssh-keygen -lf "$KEY_FILE.pub"
echo "TUNNEL_PUBLIC_KEY_FILE=$KEY_FILE.pub"

View file

@ -0,0 +1,10 @@
# Ubuntu 24.04 restricts unprivileged user namespaces unless the executable
# has an AppArmor profile that explicitly allows userns. This profile applies
# only to the root-owned Zhulan bwrap copy; that binary is executable only by
# root and the zhulan-runtime group.
abi <abi/4.0>,
include <tunables/global>
profile zhulan-remote-cell-bwrap /opt/guanghu/zhulan-remote-cell/runtime/zhulan-bwrap flags=(unconfined) {
userns,
}

View file

@ -0,0 +1,17 @@
ZHULAN_BIND=127.0.0.1
ZHULAN_PORT=17631
ZHULAN_DB=/var/lib/guanghu/zhulan-remote-cell/state.sqlite3
ZHULAN_SECRET_FILE=/etc/guanghu/secrets/zhulan-remote-cell.secret
ZHULAN_POLICY=/opt/guanghu/zhulan-remote-cell/policy.json
ZHULAN_UI_DIR=/opt/guanghu/zhulan-remote-cell/ui
ZHULAN_WORKSPACE_ROOT=/srv/guanghu/zhulan-cell/workspaces
ZHULAN_CANDIDATE_ROOT=/srv/guanghu/zhulan-cell/candidates
ZHULAN_FORGEJO_VERIFY_URL=https://guanghulab.com/code/api/v1/user
ZHULAN_OWNER_LOGIN=bingshuo
ZHULAN_COOKIE_SECURE=1
ZHULAN_COOKIE_PATH=/zhulan/
ZHULAN_PUBLIC_CREATE_LIMIT=12
ZHULAN_OWNER_LOGIN_LIMIT=8
ZHULAN_OAUTH_REGISTER_LIMIT=20
ZHULAN_BWRAP=/opt/guanghu/zhulan-remote-cell/runtime/zhulan-bwrap
ZHULAN_VALIDATION_SOCKET=/run/guanghu/zhulan-validation/executor.sock

View file

@ -0,0 +1,37 @@
[Unit]
Description=Guanghu Zhulan Restricted Remote Development Cell
After=network-online.target zhulan-validation-executor.service
Wants=network-online.target
Requires=zhulan-validation-executor.service
[Service]
Type=simple
User=zhulan-runtime
Group=zhulan-runtime
EnvironmentFile=/etc/guanghu/zhulan-remote-cell.env
ExecStart=/usr/bin/python3 /opt/guanghu/zhulan-remote-cell/runtime/zhulan_cell.py
Restart=on-failure
RestartSec=3
UMask=0077
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RestrictNamespaces=user mnt pid net ipc uts
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
SystemCallArchitectures=native
ReadOnlyPaths=/opt/guanghu/zhulan-remote-cell /etc/guanghu/zhulan-remote-cell.env /etc/guanghu/secrets/zhulan-remote-cell.secret
ReadWritePaths=/var/lib/guanghu/zhulan-remote-cell
ReadWritePaths=/srv/guanghu/zhulan-cell/workspaces
ReadWritePaths=/srv/guanghu/zhulan-cell/candidates
StateDirectory=guanghu/zhulan-remote-cell
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,37 @@
[Unit]
Description=Zhulan local-only allowlisted validation executor
After=local-fs.target
Before=zhulan-remote-cell.service
[Service]
Type=simple
User=zhulan-runtime
Group=zhulan-runtime
EnvironmentFile=/etc/guanghu/zhulan-remote-cell.env
ExecStart=/usr/bin/python3 /opt/guanghu/zhulan-remote-cell/runtime/zhulan_validation_executor.py
Restart=always
RestartSec=3
UMask=0077
PrivateTmp=true
PrivateDevices=true
PrivateNetwork=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
RestrictNamespaces=user mnt pid net ipc uts
SystemCallArchitectures=native
RuntimeDirectory=guanghu/zhulan-validation
RuntimeDirectoryMode=0700
ReadOnlyPaths=/opt/guanghu/zhulan-remote-cell /etc/guanghu/zhulan-remote-cell.env
ReadOnlyPaths=/srv/guanghu/zhulan-cell/workspaces
InaccessiblePaths=/etc/guanghu/secrets
ReadOnlyPaths=/var/lib/guanghu/zhulan-remote-cell
InaccessiblePaths=/srv/guanghu/zhulan-cell/candidates
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,41 @@
{
"schema": "guanghu.zhulan-remote-cell-policy/v1",
"persona_id": "ICE-GL-ZL-001",
"node_id": "BS-SG-003",
"front_door": "https://guanghulab.com/zhulan/",
"default_ttl_seconds": 10800,
"max_ttl_seconds": 86400,
"max_request_paths": 24,
"max_changed_file_bytes": 20971520,
"max_read_file_bytes": 262144,
"workspace_root": "/srv/guanghu/zhulan-cell/workspaces",
"candidate_root": "/srv/guanghu/zhulan-cell/candidates",
"repository_base_url": "https://guanghulab.com/code/",
"allowed_actions": [
"read",
"edit",
"test",
"commit",
"push_candidate"
],
"repositories": {
"bingshuo/guanghu-ice-heart": {
"candidate_branch_prefix": "zhulan/",
"allowed_path_prefixes": ["."],
"validation_commands": [
["git", "diff", "--check"],
["python3", "-m", "unittest", "discover", "-s", "server-tools/zhulan-remote-cell/tests", "-v"]
]
},
"bingshuo/hololake-system-architecture": {
"candidate_branch_prefix": "zhulan/",
"allowed_path_prefixes": ["."],
"validation_commands": [["git", "diff", "--check"]]
},
"bingshuo/grok-build-upstream-mirror": {
"candidate_branch_prefix": "zhulan/",
"allowed_path_prefixes": ["."],
"validation_commands": [["git", "diff", "--check"]]
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,140 @@
#!/usr/bin/env python3
"""铸澜工作区代码门:在 commit / candidate push 前校验能力与实际 Git 变化。"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import subprocess
import sys
import urllib.error
import urllib.request
from pathlib import Path
from typing import Any
SECRET_PATTERNS = [
re.compile(rb"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
re.compile(rb"(?i)(?:api[_-]?key|secret|password|token)\s*[:=]\s*['\"][^'\"\s]{12,}"),
re.compile(rb"AKID[A-Z0-9]{13,}"),
]
def run_git(workspace: Path, *args: str) -> str:
result = subprocess.run(
["git", "-C", str(workspace), *args],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=30,
)
return result.stdout.strip()
def within(path: str, prefixes: list[str]) -> bool:
return any(prefix == "." or path == prefix or path.startswith(prefix + "/") for prefix in prefixes)
def verify_remote(url: str, token: str, action: str) -> dict[str, Any]:
raw = json.dumps({"capability": token, "action": action}).encode("utf-8")
request = urllib.request.Request(
url.rstrip("/") + "/api/v1/runtime/verify",
data=raw,
headers={"Content-Type": "application/json", "User-Agent": "zhulan-code-gate/1"},
)
try:
with urllib.request.urlopen(request, timeout=8) as response:
return json.loads(response.read(262144))
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, ValueError) as exc:
raise RuntimeError("capability_verify_failed") from exc
def changed_files(workspace: Path, base_sha: str) -> list[str]:
run_git(workspace, "merge-base", "--is-ancestor", base_sha, "HEAD")
committed = run_git(workspace, "diff", "--name-only", f"{base_sha}...HEAD")
pending = run_git(workspace, "diff", "--name-only", "HEAD")
untracked = run_git(workspace, "ls-files", "--others", "--exclude-standard")
return sorted({line for block in (committed, pending, untracked) for line in block.splitlines() if line})
def scan_file(workspace: Path, path: Path, max_bytes: int) -> list[str]:
errors: list[str] = []
if path.is_symlink():
target = path.resolve()
try:
target.relative_to(workspace)
except ValueError:
errors.append("symlink_outside_workspace")
return errors
if not path.is_file():
return errors
size = path.stat().st_size
if size > max_bytes:
return [f"file_too_large:{size}"]
if size > 2_000_000:
return errors
raw = path.read_bytes()
for pattern in SECRET_PATTERNS:
if pattern.search(raw):
errors.append("possible_secret")
break
return errors
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--workspace", required=True)
parser.add_argument("--capability-file", required=True)
parser.add_argument("--action", choices=["edit", "test", "commit", "push_candidate"], required=True)
parser.add_argument("--verify-url", default=os.getenv("ZHULAN_VERIFY_URL", "http://127.0.0.1:17631"))
parser.add_argument("--max-file-bytes", type=int, default=20 * 1024 * 1024)
args = parser.parse_args()
workspace = Path(args.workspace).resolve()
token = Path(args.capability_file).read_text(encoding="utf-8").strip()
verified = verify_remote(args.verify_url, token, args.action)
claims = verified["claims"]
errors: list[dict[str, str]] = []
if run_git(workspace, "rev-parse", "--is-inside-work-tree") != "true":
errors.append({"gate": "git", "error": "not_a_worktree"})
branch = run_git(workspace, "branch", "--show-current")
if branch != claims["branch"]:
errors.append({"gate": "branch", "error": f"expected:{claims['branch']}:actual:{branch}"})
try:
files = changed_files(workspace, claims["base_sha"])
except subprocess.CalledProcessError:
files = []
errors.append({"gate": "base_sha", "error": "base_not_ancestor"})
for name in files:
if not within(name, claims["paths"]):
errors.append({"gate": "path", "error": name})
continue
for finding in scan_file(workspace, workspace / name, args.max_file_bytes):
errors.append({"gate": finding, "error": name})
result = {
"schema": "guanghu.zhulan-code-gate-receipt/v1",
"decision": "PASS" if not errors else "FAIL",
"request_id": claims["request_id"],
"development_id": claims["development_id"],
"repository": claims["repository"],
"branch": branch,
"base_sha": claims["base_sha"],
"changed_files": files,
"errors": errors,
"workspace_fingerprint": hashlib.sha256(str(workspace).encode()).hexdigest(),
"capability_receipt": verified["receipt"],
}
print(json.dumps(result, ensure_ascii=False, sort_keys=True, indent=2))
return 0 if not errors else 2
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -0,0 +1,262 @@
#!/usr/bin/env python3
"""Local-only allowlisted Bubblewrap validation broker for the Zhulan runtime."""
from __future__ import annotations
import json
import os
import signal
import socketserver
import sqlite3
import subprocess
import time
from pathlib import Path
from typing import Any
MAX_REQUEST_BYTES = 64 * 1024
def safe_relative(value: Any) -> str:
if not isinstance(value, str):
raise PermissionError("validation_path_invalid")
raw = value.replace("\\", "/").strip("/")
if raw in {"", "."}:
return "."
parts = raw.split("/")
if any(part in {"", ".", ".."} for part in parts):
raise PermissionError("validation_path_invalid")
return "/".join(parts)
def path_within(path: str, prefix: str) -> bool:
return prefix == "." or path == prefix or path.startswith(prefix + "/")
class ValidationExecutor:
def __init__(self) -> None:
self.workspace_root = Path(
os.getenv("ZHULAN_WORKSPACE_ROOT", "/srv/guanghu/zhulan-cell/workspaces")
).resolve()
self.policy_path = Path(
os.getenv("ZHULAN_POLICY", "/opt/guanghu/zhulan-remote-cell/policy.json")
)
self.state_db = Path(
os.getenv("ZHULAN_DB", "/var/lib/guanghu/zhulan-remote-cell/state.sqlite3")
)
self.bwrap = Path(
os.getenv(
"ZHULAN_BWRAP", "/opt/guanghu/zhulan-remote-cell/runtime/zhulan-bwrap"
)
)
def policy(self) -> dict[str, Any]:
return json.loads(self.policy_path.read_text(encoding="utf-8"))
def validate(self, request: dict[str, Any]) -> tuple[Path, list[str], list[str]]:
if set(request) != {
"operation",
"request_id",
"repository",
"development_id",
"workspace",
"paths",
"command",
}:
raise PermissionError("validation_request_fields_invalid")
if request.get("operation") != "run":
raise PermissionError("validation_operation_invalid")
repository = str(request.get("repository", ""))
request_id = str(request.get("request_id", ""))
development_id = str(request.get("development_id", ""))
command = request.get("command")
paths = request.get("paths")
if not isinstance(command, list) or not command or not all(
isinstance(item, str) for item in command
):
raise PermissionError("validation_command_invalid")
if not isinstance(paths, list) or not paths:
raise PermissionError("validation_paths_invalid")
clean_paths = sorted(set(safe_relative(item) for item in paths))
policy = self.policy()
repository_policy = policy.get("repositories", {}).get(repository)
if not isinstance(repository_policy, dict):
raise PermissionError("validation_repository_not_registered")
allowed = repository_policy.get("validation_commands", [])
if command not in allowed or command == ["git", "diff", "--check"]:
raise PermissionError("validation_command_not_registered_for_broker")
allowed_prefixes = [safe_relative(item) for item in repository_policy.get("allowed_path_prefixes", ["."])]
if any(not any(path_within(path, prefix) for prefix in allowed_prefixes) for path in clean_paths):
raise PermissionError("validation_path_outside_repository_policy")
uri = f"file:{self.state_db}?mode=ro"
with sqlite3.connect(uri, uri=True, timeout=5) as db:
db.row_factory = sqlite3.Row
row = db.execute(
"""SELECT id, state, picked_up_at, capability_expires_at,
development_id, repository, paths_json, actions_json
FROM requests WHERE id=?""",
(request_id,),
).fetchone()
if (
not row
or row["state"] != "APPROVED"
or not row["picked_up_at"]
or int(row["capability_expires_at"] or 0) <= int(time.time())
):
raise PermissionError("validation_approval_not_active")
if (
row["development_id"] != development_id
or row["repository"] != repository
or json.loads(row["paths_json"]) != clean_paths
or "test" not in json.loads(row["actions_json"])
):
raise PermissionError("validation_approval_binding_mismatch")
workspace = Path(str(request.get("workspace", ""))).resolve()
workspace.relative_to(self.workspace_root)
if (
not workspace.is_dir()
or not (workspace / ".git").is_dir()
or (workspace / ".git").is_symlink()
):
raise PermissionError("validation_workspace_invalid")
owner, name = repository.split("/", 1)
if (
workspace.name != f"{owner}__{name}"
or workspace.parent.name != development_id
):
raise PermissionError("validation_workspace_repository_mismatch")
if not self.bwrap.is_file():
raise RuntimeError("validation_bwrap_missing")
return workspace, clean_paths, command
@staticmethod
def projection(workspace: Path, paths: list[str]) -> list[str]:
if paths == ["."]:
return ["--ro-bind", str(workspace), "/workspace"]
result: list[str] = []
created = {"/workspace"}
for relative in paths:
target = workspace / relative
destination = Path("/workspace") / relative
parents = [item for item in reversed(destination.parents) if str(item).startswith("/workspace")]
for parent in parents:
text = str(parent)
if text not in created:
result.extend(["--dir", text])
created.add(text)
if target.exists():
target.resolve().relative_to(workspace)
result.extend(["--ro-bind", str(target), str(destination)])
elif str(destination) not in created:
result.extend(["--dir", str(destination)])
created.add(str(destination))
return result
def execute(self, request: dict[str, Any]) -> dict[str, Any]:
if request.get("operation") == "ping":
if set(request) != {"operation"}:
raise PermissionError("validation_ping_fields_invalid")
return {"ok": True, "executor": "zhulan-validation"}
workspace, paths, command = self.validate(request)
argv = [
str(self.bwrap),
"--die-with-parent",
"--new-session",
"--unshare-user",
"--unshare-pid",
"--unshare-net",
"--unshare-ipc",
"--unshare-uts",
"--ro-bind",
"/usr",
"/usr",
"--ro-bind",
"/bin",
"/bin",
"--ro-bind-try",
"/lib",
"/lib",
"--ro-bind-try",
"/lib64",
"/lib64",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--dir",
"/home",
"--dir",
"/home/zhulan",
*self.projection(workspace, paths),
"--chdir",
"/workspace",
"--clearenv",
"--setenv",
"HOME",
"/home/zhulan",
"--setenv",
"PATH",
"/usr/local/bin:/usr/bin:/bin",
"--",
*command,
]
result = subprocess.run(
argv,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
timeout=180,
env={"PATH": "/usr/local/bin:/usr/bin:/bin", "LANG": "C.UTF-8"},
)
return {"ok": True, "exit_code": result.returncode, "output": result.stdout[-12000:]}
class Handler(socketserver.StreamRequestHandler):
def handle(self) -> None:
raw = self.rfile.readline(MAX_REQUEST_BYTES + 1)
if not raw or len(raw) > MAX_REQUEST_BYTES:
return
try:
request = json.loads(raw)
if not isinstance(request, dict):
raise ValueError("request_not_object")
response = self.server.executor.execute(request) # type: ignore[attr-defined]
except Exception as exc:
response = {"ok": False, "error": str(exc)[:300]}
self.wfile.write((json.dumps(response, sort_keys=True) + "\n").encode("utf-8"))
class Server(socketserver.UnixStreamServer):
allow_reuse_address = True
def main() -> None:
socket_path = Path(
os.getenv(
"ZHULAN_VALIDATION_SOCKET", "/run/guanghu/zhulan-validation/executor.sock"
)
)
socket_path.parent.mkdir(parents=True, exist_ok=True)
if socket_path.exists():
socket_path.unlink()
server = Server(str(socket_path), Handler)
server.executor = ValidationExecutor() # type: ignore[attr-defined]
os.chmod(socket_path, 0o600)
def stop(_signum: int, _frame: object) -> None:
server.server_close()
raise SystemExit(0)
signal.signal(signal.SIGTERM, stop)
signal.signal(signal.SIGINT, stop)
try:
server.serve_forever()
finally:
server.server_close()
socket_path.unlink(missing_ok=True)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,32 @@
import tempfile
import unittest
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "runtime"))
from zhulan_code_gate import scan_file, within # noqa: E402
class CodeGateTest(unittest.TestCase):
def test_paths_fail_closed(self):
self.assertTrue(within("server-tools/zhulan-remote-cell/ui/app.js", ["server-tools/zhulan-remote-cell"]))
self.assertFalse(within("server-tools/lake-lamp-authz/server.js", ["server-tools/zhulan-remote-cell"]))
def test_private_key_is_rejected(self):
with tempfile.TemporaryDirectory() as root:
workspace = Path(root)
target = workspace / "leak.txt"
target.write_text("-----BEGIN OPENSSH PRIVATE KEY-----\nnot-real\n", encoding="utf-8")
self.assertIn("possible_secret", scan_file(workspace, target, 1024 * 1024))
def test_symlink_outside_workspace_is_rejected(self):
with tempfile.TemporaryDirectory() as root, tempfile.TemporaryDirectory() as outside:
workspace = Path(root)
target = workspace / "outside-link"
target.symlink_to(Path(outside) / "secret")
self.assertIn("symlink_outside_workspace", scan_file(workspace, target, 1024 * 1024))
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,59 @@
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
class DeploymentContractTest(unittest.TestCase):
def test_runtime_upgrade_restarts_service(self):
installer = (ROOT / "deploy" / "install-runtime.sh").read_text(encoding="utf-8")
self.assertIn("systemctl enable zhulan-remote-cell.service", installer)
self.assertIn("systemctl restart zhulan-remote-cell.service", installer)
self.assertIn("systemctl restart zhulan-validation-executor.service", installer)
self.assertNotIn("systemctl enable --now zhulan-remote-cell.service", installer)
def test_bwrap_exception_is_scoped_to_runtime_copy(self):
env = (ROOT / "deploy" / "zhulan-remote-cell.env.example").read_text(encoding="utf-8")
profile = (ROOT / "deploy" / "zhulan-bwrap.apparmor").read_text(encoding="utf-8")
installer = (ROOT / "deploy" / "install-runtime.sh").read_text(encoding="utf-8")
private_copy = "/opt/guanghu/zhulan-remote-cell/runtime/zhulan-bwrap"
self.assertIn(f"ZHULAN_BWRAP={private_copy}", env)
self.assertIn(f"profile zhulan-remote-cell-bwrap {private_copy}", profile)
self.assertIn(" userns,", profile)
self.assertIn('install -o root -g zhulan-runtime -m 0750 /usr/bin/bwrap', installer)
self.assertNotIn("kernel.apparmor_restrict_unprivileged_userns=0", installer)
self.assertNotIn("chmod 4755", installer)
def test_only_executor_allows_bwrap_required_netlink_addition(self):
service = (ROOT / "deploy" / "zhulan-remote-cell.service").read_text(encoding="utf-8")
executor = (ROOT / "deploy" / "zhulan-validation-executor.service").read_text(encoding="utf-8")
self.assertIn(
"RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6",
service,
)
self.assertNotIn("AF_NETLINK", service)
self.assertIn("RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK", executor)
self.assertIn("RestrictNamespaces=user mnt pid net ipc uts", service)
def test_public_runtime_keeps_no_new_privileges_and_uses_local_executor(self):
public_service = (ROOT / "deploy" / "zhulan-remote-cell.service").read_text(encoding="utf-8")
executor_service = (ROOT / "deploy" / "zhulan-validation-executor.service").read_text(encoding="utf-8")
runtime = (ROOT / "runtime" / "zhulan_cell.py").read_text(encoding="utf-8")
self.assertIn("NoNewPrivileges=true", public_service)
self.assertIn("ProtectKernelTunables=true", public_service)
self.assertIn("Requires=zhulan-validation-executor.service", public_service)
self.assertNotIn("NoNewPrivileges=true", executor_service)
self.assertNotIn("ProtectKernelTunables=true", executor_service)
self.assertIn("RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK", executor_service)
self.assertIn("ReadOnlyPaths=/srv/guanghu/zhulan-cell/workspaces", executor_service)
self.assertIn("PrivateNetwork=true", executor_service)
self.assertIn("InaccessiblePaths=/etc/guanghu/secrets", executor_service)
self.assertIn("ReadOnlyPaths=/var/lib/guanghu/zhulan-remote-cell", executor_service)
self.assertIn("InaccessiblePaths=/srv/guanghu/zhulan-cell/candidates", executor_service)
self.assertIn("socket.AF_UNIX", runtime)
self.assertNotIn('argv = [\n str(self.settings.bwrap_path)', runtime)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,129 @@
import json
import sqlite3
import tempfile
import time
import unittest
from pathlib import Path
from unittest.mock import patch
from importlib.util import module_from_spec, spec_from_file_location
ROOT = Path(__file__).resolve().parents[1]
SPEC = spec_from_file_location(
"zhulan_validation_executor",
ROOT / "runtime" / "zhulan_validation_executor.py",
)
MODULE = module_from_spec(SPEC)
assert SPEC and SPEC.loader
SPEC.loader.exec_module(MODULE)
class ValidationExecutorTest(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
root = Path(self.temp.name)
workspace_root = root / "workspaces"
self.workspace = workspace_root / "DEV-20260813-099" / "bingshuo__guanghu-ice-heart"
(self.workspace / ".git").mkdir(parents=True)
self.policy = root / "policy.json"
self.policy.write_text(
json.dumps(
{
"repositories": {
"bingshuo/guanghu-ice-heart": {
"allowed_path_prefixes": ["server-tools/zhulan-remote-cell"],
"validation_commands": [["python3", "-m", "unittest"]],
}
}
}
),
encoding="utf-8",
)
self.bwrap = root / "zhulan-bwrap"
self.bwrap.write_text("test", encoding="utf-8")
self.state_db = root / "state.sqlite3"
with sqlite3.connect(self.state_db) as db:
db.execute(
"""CREATE TABLE requests (
id TEXT PRIMARY KEY, state TEXT, picked_up_at INTEGER,
capability_expires_at INTEGER, development_id TEXT,
repository TEXT, paths_json TEXT, actions_json TEXT
)"""
)
db.execute(
"INSERT INTO requests VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
(
"ZLR-20260813-AAAAAAAAAA",
"APPROVED",
int(time.time()),
int(time.time()) + 3600,
"DEV-20260813-099",
"bingshuo/guanghu-ice-heart",
json.dumps(["server-tools/zhulan-remote-cell"]),
json.dumps(["read", "test"]),
),
)
with patch.dict(
"os.environ",
{
"ZHULAN_WORKSPACE_ROOT": str(workspace_root),
"ZHULAN_POLICY": str(self.policy),
"ZHULAN_BWRAP": str(self.bwrap),
"ZHULAN_DB": str(self.state_db),
},
):
self.executor = MODULE.ValidationExecutor()
def tearDown(self):
self.temp.cleanup()
def valid(self):
return {
"operation": "run",
"request_id": "ZLR-20260813-AAAAAAAAAA",
"repository": "bingshuo/guanghu-ice-heart",
"development_id": "DEV-20260813-099",
"workspace": str(self.workspace),
"paths": ["server-tools/zhulan-remote-cell"],
"command": ["python3", "-m", "unittest"],
}
def test_accepts_only_exact_registered_request(self):
workspace, paths, command = self.executor.validate(self.valid())
self.assertEqual(workspace, self.workspace.resolve())
self.assertEqual(paths, ["server-tools/zhulan-remote-cell"])
self.assertEqual(command, ["python3", "-m", "unittest"])
def test_rejects_extra_fields_arbitrary_command_and_path(self):
for key, value in [
("extra", True),
("command", ["sh", "-c", "id"]),
("paths", ["deployment"]),
]:
request = self.valid()
request[key] = value
with self.assertRaises(PermissionError):
self.executor.validate(request)
def test_rejects_workspace_repository_mismatch(self):
request = self.valid()
wrong = self.workspace.parent / "bingshuo__other"
(wrong / ".git").mkdir(parents=True)
request["workspace"] = str(wrong)
with self.assertRaises(PermissionError):
self.executor.validate(request)
def test_rejects_inactive_or_mismatched_approval_binding(self):
for key, value in [
("request_id", "ZLR-20260813-BBBBBBBBBB"),
("development_id", "DEV-20260813-098"),
]:
request = self.valid()
request[key] = value
with self.assertRaises(PermissionError):
self.executor.validate(request)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,589 @@
import http.cookiejar
import base64
import hashlib
import json
import os
import re
import socket
import subprocess
import tempfile
import time
import unittest
import urllib.error
import urllib.request
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def free_port():
with socket.socket() as sock:
sock.bind(("127.0.0.1", 0))
return sock.getsockname()[1]
class ZhulanCellTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.temp = tempfile.TemporaryDirectory()
temp_root = Path(cls.temp.name)
seed = temp_root / "seed"
forge_root = temp_root / "forge"
bare = forge_root / "bingshuo" / "guanghu-ice-heart.git"
allowed = seed / "server-tools" / "zhulan-remote-cell"
allowed.mkdir(parents=True)
(allowed / "hello.txt").write_text("before\n", encoding="utf-8")
subprocess.run(["git", "init", "-b", "main", str(seed)], check=True, capture_output=True)
subprocess.run(["git", "-C", str(seed), "add", "."], check=True, capture_output=True)
subprocess.run(
[
"git", "-C", str(seed),
"-c", "user.name=Zhulan Test",
"-c", "user.email=zhulan-test@invalid",
"commit", "-m", "test source",
],
check=True,
capture_output=True,
)
cls.source_sha = subprocess.run(
["git", "-C", str(seed), "rev-parse", "HEAD"],
check=True,
capture_output=True,
text=True,
).stdout.strip()
bare.parent.mkdir(parents=True)
subprocess.run(["git", "clone", "--bare", str(seed), str(bare)], check=True, capture_output=True)
policy = json.loads((ROOT / "policy.example.json").read_text(encoding="utf-8"))
policy["repository_base_url"] = forge_root.as_uri()
policy["repositories"]["bingshuo/guanghu-ice-heart"]["validation_commands"] = [
["git", "diff", "--check"]
]
cls.policy_file = temp_root / "policy.json"
cls.policy_file.write_text(json.dumps(policy), encoding="utf-8")
cls.port = free_port()
env = os.environ.copy()
env.update(
{
"ZHULAN_BIND": "127.0.0.1",
"ZHULAN_PORT": str(cls.port),
"ZHULAN_DB": str(Path(cls.temp.name) / "state.sqlite3"),
"ZHULAN_SECRET_FILE": str(Path(cls.temp.name) / "missing-test-secret"),
"ZHULAN_POLICY": str(cls.policy_file),
"ZHULAN_UI_DIR": str(ROOT / "ui"),
"ZHULAN_WORKSPACE_ROOT": str(Path(cls.temp.name) / "workspaces"),
"ZHULAN_CANDIDATE_ROOT": str(Path(cls.temp.name) / "candidates"),
"ZHULAN_COOKIE_SECURE": "0",
"ZHULAN_COOKIE_PATH": "/",
"ZHULAN_TEST_MODE": "1",
"ZHULAN_TEST_OWNER_PASSWORD": "correct-horse-test-only",
}
)
cls.process = subprocess.Popen(
["python3", str(ROOT / "runtime" / "zhulan_cell.py")],
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
cls.base = f"http://127.0.0.1:{cls.port}"
deadline = time.time() + 8
while time.time() < deadline:
try:
with urllib.request.urlopen(cls.base + "/health", timeout=0.5) as response:
if response.status == 200:
break
except Exception:
time.sleep(0.08)
else:
out, err = cls.process.communicate(timeout=2)
raise RuntimeError(f"test server did not start\n{out}\n{err}")
@classmethod
def tearDownClass(cls):
cls.process.terminate()
cls.process.wait(timeout=5)
if cls.process.stdout:
cls.process.stdout.close()
if cls.process.stderr:
cls.process.stderr.close()
cls.temp.cleanup()
def setUp(self):
self.jar = http.cookiejar.CookieJar()
self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.jar))
def request(self, path, body=None, headers=None, expected=200, form=False):
if form and body is not None:
from urllib.parse import urlencode
raw = urlencode(body).encode()
else:
raw = json.dumps(body).encode() if body is not None else None
request = urllib.request.Request(
self.base + path,
data=raw,
headers={"Content-Type": "application/x-www-form-urlencoded" if form else "application/json", **(headers or {})},
)
try:
with self.opener.open(request, timeout=3) as response:
self.assertEqual(response.status, expected)
return json.loads(response.read())
except urllib.error.HTTPError as exc:
with exc:
self.assertEqual(exc.code, expected)
return json.loads(exc.read())
def create(
self,
dev="DEV-20260813-005",
slug="approval-ui",
base_sha="5f9e83e1b716fd50ee2880b24ea47f83b75d1f8f",
):
return self.request(
"/api/v1/public/requests",
{
"persona_id": "ICE-GL-ZL-001",
"development_id": dev,
"repository": "bingshuo/guanghu-ice-heart",
"base_sha": base_sha,
"branch": f"zhulan/{dev}/{slug}",
"paths": ["server-tools/zhulan-remote-cell"],
"actions": ["read", "edit", "test", "commit", "push_candidate"],
"description": "完善铸澜手机审批入口与代码门禁",
},
expected=201,
)
def login(self):
result = self.request(
"/api/v1/owner/login",
{"username": "bingshuo", "password": "correct-horse-test-only"},
)
return result["csrf"]
def approved_oauth_token(self, created):
csrf = self.login()
self.request(
f"/api/v1/owner/requests/{created['request_id']}/approve",
{"ttl_seconds": 10800},
headers={"X-CSRF-Token": csrf},
)
registered = self.request(
"/oauth/register",
{
"client_name": "ChatGPT test connector",
"redirect_uris": ["https://chatgpt.com/connector/oauth/test-callback"],
"token_endpoint_auth_method": "none",
},
expected=201,
)
verifier = "v" * 64
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).decode().rstrip("=")
oauth = {
"client_id": registered["client_id"],
"redirect_uri": registered["redirect_uris"][0],
"state": "state-test-123",
"code_challenge": challenge,
"resource": "https://guanghulab.com/zhulan/mcp",
"scope": "zhulan.develop",
"csrf": self.request("/api/v1/owner/session")["csrf"],
"request_id": created["request_id"],
}
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
request = urllib.request.Request(
self.base + "/oauth/authorize",
data=__import__("urllib.parse").parse.urlencode(oauth).encode(),
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
no_redirect = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(self.jar), NoRedirect()
)
try:
no_redirect.open(request, timeout=3)
self.fail("authorize should redirect")
except urllib.error.HTTPError as exc:
with exc:
self.assertEqual(exc.code, 302)
location = exc.headers["Location"]
from urllib.parse import parse_qs, urlparse
code = parse_qs(urlparse(location).query)["code"][0]
token = self.request(
"/oauth/token",
{
"grant_type": "authorization_code",
"code": code,
"client_id": registered["client_id"],
"redirect_uri": registered["redirect_uris"][0],
"code_verifier": verifier,
"resource": "https://guanghulab.com/zhulan/mcp",
},
form=True,
)
return token["access_token"]
def mcp_tool(self, token, name, arguments=None):
response = self.request(
"/mcp",
{
"jsonrpc": "2.0",
"id": 91,
"method": "tools/call",
"params": {"name": name, "arguments": arguments or {}},
},
headers={"Authorization": f"Bearer {token}"},
)
return response["result"]
def test_public_config_declares_front_and_runtime(self):
config = self.request("/api/v1/public/config")
self.assertEqual(config["runtime_node"], "BS-SG-003")
self.assertIn("BS-GZ-006", config["topology"]["front"])
self.assertIn("GHS-012", [x["id"] for x in config["ui_brains"]])
def test_wrong_persona_is_denied(self):
result = self.request(
"/api/v1/public/requests",
{
"persona_id": "ICE-GL-ZY001",
"development_id": "DEV-20260813-005",
"repository": "bingshuo/guanghu-ice-heart",
"base_sha": "5f9e83e1b716fd50ee2880b24ea47f83b75d1f8f",
"branch": "zhulan/DEV-20260813-005/nope",
"paths": ["server-tools"],
"actions": ["read"],
"description": "错误人格不得进入铸澜车道",
},
expected=400,
)
self.assertEqual(result["error"], "persona_not_allowed")
def test_approval_pickup_and_capability_verification(self):
created = self.create(slug=f"flow-{int(time.time())}")
self.assertRegex(created["request_id"], r"^ZLR-[0-9]{8}-[A-F0-9]{10}$")
status = self.request(
f"/api/v1/public/requests/{created['request_id']}/status",
{"claim_token": created["claim_token"]},
)
self.assertEqual(status["state"], "PENDING")
csrf = self.login()
listing = self.request("/api/v1/owner/requests")
self.assertTrue(any(item["id"] == created["request_id"] for item in listing["requests"]))
approved = self.request(
f"/api/v1/owner/requests/{created['request_id']}/approve",
{"ttl_seconds": 10800},
headers={"X-CSRF-Token": csrf},
)
self.assertEqual(approved["state"], "APPROVED")
pickup = self.request(
f"/api/v1/public/requests/{created['request_id']}/pickup",
{"claim_token": created["claim_token"]},
)
verified = self.request(
"/api/v1/runtime/verify",
{"capability": pickup["capability"], "action": "push_candidate"},
)
self.assertEqual(verified["claims"]["branch"], status["branch"])
self.assertEqual(verified["claims"]["node_id"], "BS-SG-003")
second = self.request(
f"/api/v1/public/requests/{created['request_id']}/pickup",
{"claim_token": created["claim_token"]},
expected=409,
)
self.assertEqual(second["error"], "capability_already_picked_up")
filtered = self.request(f"/api/v1/owner/requests/{created['request_id']}/receipts")
self.assertGreaterEqual(len(filtered["receipts"]), 4)
receipts = self.request("/api/v1/owner/receipts")
previous = "0" * 64
for receipt in receipts["receipts"]:
self.assertEqual(receipt["previous_hash"], previous)
previous = receipt["receipt_hash"]
def test_csrf_is_required_for_decision(self):
created = self.create(slug=f"csrf-{int(time.time())}")
self.login()
result = self.request(
f"/api/v1/owner/requests/{created['request_id']}/approve",
{"ttl_seconds": 10800},
expected=403,
)
self.assertEqual(result["error"], "csrf_invalid")
def test_owner_can_revoke_active_oauth_and_capability(self):
created = self.create(slug=f"revoke-{int(time.time())}")
token = self.approved_oauth_token(created)
restored = self.mcp_tool(token, "zhulan_restore_lane")
self.assertFalse(restored["isError"])
session = self.request("/api/v1/owner/session")
revoked = self.request(
f"/api/v1/owner/requests/{created['request_id']}/revoke",
{"reason": "test owner stop"},
headers={"X-CSRF-Token": session["csrf"]},
)
self.assertEqual(revoked["state"], "REVOKED")
denied = self.mcp_tool(token, "zhulan_restore_lane")
self.assertTrue(denied["isError"])
self.assertIn("mcp/www_authenticate", denied["_meta"])
def test_mcp_requires_oauth_for_development_tools(self):
response = self.request(
"/mcp",
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {"name": "zhulan_restore_lane", "arguments": {}},
},
)
result = response["result"]
self.assertTrue(result["isError"])
self.assertIn("mcp/www_authenticate", result["_meta"])
tools = self.request(
"/mcp", {"jsonrpc": "2.0", "id": 5, "method": "tools/list", "params": {}}
)["result"]["tools"]
protected = next(tool for tool in tools if tool["name"] == "zhulan_restore_lane")
public = next(tool for tool in tools if tool["name"] == "zhulan_request_development")
self.assertEqual(protected["securitySchemes"][0]["type"], "oauth2")
self.assertEqual(public["securitySchemes"][0]["type"], "noauth")
self.assertNotIn("capability", protected["inputSchema"]["properties"])
def test_oauth_pkce_binds_to_approved_request_and_code_is_single_use(self):
created = self.create(slug=f"oauth-{int(time.time())}")
csrf = self.login()
self.request(
f"/api/v1/owner/requests/{created['request_id']}/approve",
{"ttl_seconds": 10800},
headers={"X-CSRF-Token": csrf},
)
registered = self.request(
"/oauth/register",
{
"client_name": "ChatGPT test connector",
"redirect_uris": ["https://chatgpt.com/connector/oauth/test-callback"],
"token_endpoint_auth_method": "none",
},
expected=201,
)
verifier = "v" * 64
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).decode().rstrip("=")
oauth = {
"client_id": registered["client_id"],
"redirect_uri": registered["redirect_uris"][0],
"state": "state-test-123",
"code_challenge": challenge,
"resource": "https://guanghulab.com/zhulan/mcp",
"scope": "zhulan.develop",
}
session = self.request("/api/v1/owner/session")
oauth["csrf"] = session["csrf"]
oauth["request_id"] = created["request_id"]
request = urllib.request.Request(
self.base + "/oauth/authorize",
data=__import__("urllib.parse").parse.urlencode(oauth).encode(),
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
no_redirect = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(self.jar),
urllib.request.HTTPHandler(),
)
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
no_redirect = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.jar), NoRedirect())
try:
no_redirect.open(request, timeout=3)
self.fail("authorize should redirect")
except urllib.error.HTTPError as exc:
with exc:
self.assertEqual(exc.code, 302)
location = exc.headers["Location"]
from urllib.parse import parse_qs, urlparse
code = parse_qs(urlparse(location).query)["code"][0]
token_request = {
"grant_type": "authorization_code",
"code": code,
"client_id": registered["client_id"],
"redirect_uri": registered["redirect_uris"][0],
"code_verifier": verifier,
"resource": "https://guanghulab.com/zhulan/mcp",
}
token = self.request("/oauth/token", token_request, form=True)
self.assertEqual(token["token_type"], "Bearer")
replay = self.request("/oauth/token", token_request, expected=403, form=True)
self.assertEqual(replay["error"], "invalid_grant")
restored = self.request(
"/mcp",
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {"name": "zhulan_restore_lane", "arguments": {}},
},
headers={"Authorization": f"Bearer {token['access_token']}"},
)
content = restored["result"]["structuredContent"]
self.assertEqual(content["development_id"], "DEV-20260813-005")
self.assertEqual(content["topology"]["front"], "BS-GZ-006_PROXY_ONLY")
def test_oauth_registration_rejects_untrusted_callback(self):
result = self.request(
"/oauth/register",
{
"client_name": "evil",
"redirect_uris": ["https://example.com/steal"],
"token_endpoint_auth_method": "none",
},
expected=400,
)
self.assertEqual(result["error"], "redirect_uri_not_allowed")
def test_existing_workspace_binding_mismatch_fails_before_git_changes(self):
created = self.create(
dev="DEV-20260813-098", slug="binding", base_sha=self.source_sha
)
token = self.approved_oauth_token(created)
prepared = self.mcp_tool(token, "zhulan_prepare_workspace")["structuredContent"]
workspace = (
Path(self.temp.name)
/ "workspaces"
/ "DEV-20260813-098"
/ "bingshuo__guanghu-ice-heart"
)
before = subprocess.run(
["git", "-C", str(workspace), "rev-parse", "HEAD"],
check=True,
capture_output=True,
text=True,
).stdout.strip()
binding = workspace / ".git" / "zhulan-binding.json"
altered = json.loads(binding.read_text(encoding="utf-8"))
altered["branch"] = "zhulan/DEV-20260813-098/tampered"
binding.write_text(json.dumps(altered), encoding="utf-8")
denied = self.mcp_tool(token, "zhulan_prepare_workspace")
self.assertTrue(denied["isError"])
self.assertEqual(denied["structuredContent"]["error"], "workspace_binding_mismatch")
after = subprocess.run(
["git", "-C", str(workspace), "rev-parse", "HEAD"],
check=True,
capture_output=True,
text=True,
).stdout.strip()
self.assertEqual(before, prepared["head"])
self.assertEqual(after, before)
def test_exact_reapproval_resumes_same_workspace(self):
dev = "DEV-20260813-097"
first = self.create(dev=dev, slug="renew", base_sha=self.source_sha)
first_token = self.approved_oauth_token(first)
first_prepared = self.mcp_tool(first_token, "zhulan_prepare_workspace")[
"structuredContent"
]
second = self.create(dev=dev, slug="renew", base_sha=self.source_sha)
second_token = self.approved_oauth_token(second)
resumed = self.mcp_tool(second_token, "zhulan_prepare_workspace")[
"structuredContent"
]
self.assertEqual(resumed["head"], first_prepared["head"])
binding = (
Path(self.temp.name)
/ "workspaces"
/ dev
/ "bingshuo__guanghu-ice-heart"
/ ".git"
/ "zhulan-binding.json"
)
rebound = json.loads(binding.read_text(encoding="utf-8"))
self.assertEqual(rebound["request_id"], second["request_id"])
self.assertEqual(rebound["renewed_from_request_id"], first["request_id"])
def test_full_workspace_candidate_flow_is_scoped_and_repeatable(self):
dev = "DEV-20260813-099"
created = self.request(
"/api/v1/public/requests",
{
"persona_id": "ICE-GL-ZL-001",
"development_id": dev,
"repository": "bingshuo/guanghu-ice-heart",
"base_sha": self.source_sha,
"branch": f"zhulan/{dev}/full-flow",
"paths": ["server-tools/zhulan-remote-cell"],
"actions": ["read", "edit", "test", "commit", "push_candidate"],
"description": "端到端验证受限工作区和内部候选复核库",
},
expected=201,
)
token = self.approved_oauth_token(created)
prepared = self.mcp_tool(token, "zhulan_prepare_workspace")["structuredContent"]
self.assertEqual(prepared["head"], self.source_sha)
self.assertEqual(prepared["candidate_store"], "BS-SG-003_INTERNAL_REVIEW_ONLY")
read = self.mcp_tool(
token,
"zhulan_read_file",
{"path": "server-tools/zhulan-remote-cell/hello.txt"},
)["structuredContent"]
self.assertEqual(read["content"], "before\n")
written = self.mcp_tool(
token,
"zhulan_write_file",
{
"path": "server-tools/zhulan-remote-cell/hello.txt",
"content": "after\n",
"expected_sha256": read["sha256"],
},
)["structuredContent"]
self.assertEqual(written["sha256"], hashlib.sha256(b"after\n").hexdigest())
escaped = self.mcp_tool(
token,
"zhulan_write_file",
{"path": "../escape", "content": "no", "expected_sha256": ""},
)
self.assertTrue(escaped["isError"])
self.assertEqual(escaped["structuredContent"]["error"], "path_traversal")
initial_validation = self.mcp_tool(token, "zhulan_run_validation")["structuredContent"]
self.assertEqual(initial_validation["decision"], "PASS")
self.assertFalse(initial_validation["clean"])
committed = self.mcp_tool(
token, "zhulan_commit_candidate", {"message": "test: update bounded file"}
)["structuredContent"]
premature = self.mcp_tool(token, "zhulan_push_candidate")
self.assertTrue(premature["isError"])
final_validation = self.mcp_tool(token, "zhulan_run_validation")["structuredContent"]
self.assertTrue(final_validation["clean"])
self.assertEqual(final_validation["head"], committed["commit_sha"])
pushed = self.mcp_tool(token, "zhulan_push_candidate")["structuredContent"]
self.assertEqual(pushed["commit_sha"], committed["commit_sha"])
self.assertEqual(pushed["central_publication"], "NOT_PERFORMED_REQUIRES_ZHUYUAN_REVIEW")
prepared_again = self.mcp_tool(token, "zhulan_prepare_workspace")["structuredContent"]
self.assertEqual(prepared_again["head"], committed["commit_sha"])
candidate = Path(self.temp.name) / "candidates" / "bingshuo__guanghu-ice-heart.git"
remote_sha = subprocess.run(
["git", "--git-dir", str(candidate), "rev-parse", f"refs/heads/zhulan/{dev}/full-flow"],
check=True,
capture_output=True,
text=True,
).stdout.strip()
self.assertEqual(remote_sha, committed["commit_sha"])
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,213 @@
const $ = (id) => document.getElementById(id);
const state = { csrf: "", requests: [], selected: null };
const eventNames = {
request_created: "铸澜提交申请",
human_approved: "主人批准边界",
human_rejected: "主人拒绝申请",
human_revoked: "主人撤销能力",
capability_picked_up: "铸澜领取临时能力",
capability_verified: "运行层校验能力",
capability_expired: "临时能力到期",
request_expired: "申请单到期"
};
async function api(path, options = {}) {
const headers = { "Content-Type": "application/json", ...(options.headers || {}) };
const response = await fetch(path, { credentials: "same-origin", ...options, headers });
let data = {};
try { data = await response.json(); } catch (_) {}
if (!response.ok) throw new Error(data.error || `HTTP_${response.status}`);
return data;
}
function toast(message) {
const node = $("toast");
node.textContent = message;
node.classList.add("show");
window.setTimeout(() => node.classList.remove("show"), 2600);
}
function humanAction(value) {
return ({ read: "读取", edit: "编辑", test: "运行测试", commit: "提交", push_candidate: "推送候选分支" })[value] || value;
}
function stateLabel(value) {
return ({ PENDING: "等待批准", APPROVED: "已批准", REJECTED: "已拒绝", REVOKED: "已撤销", EXPIRED: "已过期" })[value] || value;
}
function renderRequest(request) {
state.selected = request;
$("empty-state").classList.add("hidden");
$("request-card").classList.remove("hidden");
$("request-id").textContent = request.id;
$("request-description").textContent = request.description;
$("persona").textContent = request.persona_id;
$("development-id").textContent = request.development_id;
$("repository").textContent = request.repository;
$("branch").textContent = request.branch;
$("base-sha").textContent = request.base_sha;
$("actions").replaceChildren(...request.actions.map((action) => {
const node = document.createElement("span");
node.className = "chip";
node.textContent = humanAction(action);
return node;
}));
$("paths").replaceChildren(...request.paths.map((path) => {
const node = document.createElement("span");
node.className = "path";
node.textContent = path;
return node;
}));
const label = stateLabel(request.state);
for (const id of ["request-state", "card-state"]) {
const node = $(id);
node.textContent = label;
node.className = `state ${request.state === "APPROVED" ? "approved" : request.state === "REJECTED" ? "rejected" : "waiting"}`;
}
$("decision-area").classList.toggle("hidden", request.state !== "PENDING");
$("revoke-area").classList.toggle("hidden", request.state !== "APPROVED");
loadReceipts(request.id);
}
function renderEmpty(message, detail) {
state.selected = null;
$("request-card").classList.add("hidden");
const empty = $("empty-state");
empty.classList.remove("hidden");
empty.querySelector("h3").textContent = message;
empty.querySelector("p").textContent = detail;
$("request-state").textContent = "湖面安静";
}
async function loadReceipts(requestId) {
try {
const data = await api(`api/v1/owner/requests/${encodeURIComponent(requestId)}/receipts`);
const nodes = data.receipts.map((receipt) => {
const li = document.createElement("li");
li.className = receipt.result === "PASS" || receipt.result === "APPROVED" || receipt.result === "PENDING" ? "pass" : "fail";
const dot = document.createElement("i");
const body = document.createElement("div");
const title = document.createElement("strong");
title.textContent = eventNames[receipt.event] || receipt.event;
const detail = document.createElement("span");
detail.textContent = `${receipt.created_at} · ${receipt.result} · ${receipt.receipt_hash.slice(0, 12)}`;
body.append(title, detail);
li.append(dot, body);
return li;
});
$("timeline").replaceChildren(...nodes);
} catch (_) {
$("timeline").innerHTML = "<li class='fail'><i></i><div><strong>回执读取失败</strong><span>运行层没有返回可验证结果。</span></div></li>";
}
}
async function loadRequests(preferredId = "") {
const data = await api("api/v1/owner/requests");
state.requests = data.requests;
const preferred = preferredId && data.requests.find((item) => item.id === preferredId);
const pending = data.requests.find((item) => item.state === "PENDING");
const selected = preferred || pending || data.requests[0];
if (selected) renderRequest(selected);
else renderEmpty("暂无真实申请", "铸澜提交结构化申请后会出现在这里;公开建单本身没有执行权。");
}
async function restoreSession() {
try {
const session = await api("api/v1/owner/session");
state.csrf = session.csrf;
$("auth-title").textContent = `主人 ${session.owner_login} · 已验证`;
$("auth-detail").textContent = "审批会话有效。任何扩大仓库、路径、动作或期限的请求都必须重新批准。";
$("login-open").classList.add("hidden");
$("logout").classList.remove("hidden");
const preferred = new URLSearchParams(location.search).get("request") || "";
await loadRequests(preferred);
const oauthReturn = new URLSearchParams(location.search).get("oauth_return");
if (oauthReturn) {
try {
const normalized = oauthReturn.replace(/-/g, "+").replace(/_/g, "/");
const decoded = atob(normalized + "=".repeat((4 - normalized.length % 4) % 4));
if (decoded.startsWith("/oauth/authorize?")) location.assign(`/zhulan${decoded}`);
} catch (_) {}
}
} catch (_) {
state.csrf = "";
$("login-open").classList.remove("hidden");
$("logout").classList.add("hidden");
}
}
$("login-open").addEventListener("click", () => $("login-dialog").showModal());
$("login-form").addEventListener("submit", async (event) => {
event.preventDefault();
$("login-error").textContent = "";
const button = event.currentTarget.querySelector("button[type=submit]");
button.disabled = true;
try {
const result = await api("api/v1/owner/login", {
method: "POST",
body: JSON.stringify({ username: $("username").value, password: $("password").value })
});
state.csrf = result.csrf;
$("password").value = "";
$("login-dialog").close();
toast("代码频道身份验证通过");
await restoreSession();
} catch (_) {
$("login-error").textContent = "账号验证失败。没有创建审批会话。";
} finally { button.disabled = false; }
});
$("approve").addEventListener("click", async () => {
if (!state.selected || state.selected.state !== "PENDING") return;
const exact = `${state.selected.repository}\n${state.selected.branch}\n${state.selected.paths.join("")}`;
if (!window.confirm(`批准这一段开发?\n\n${exact}\n\n批准不会开放 root 或任意 shell。`)) return;
$("approve").disabled = true;
try {
await api(`api/v1/owner/requests/${state.selected.id}/approve`, {
method: "POST",
headers: { "X-CSRF-Token": state.csrf },
body: JSON.stringify({ ttl_seconds: Number($("ttl").value) })
});
toast("已批准。铸澜可领取一次临时能力。");
await restoreSession();
} catch (error) { toast(`批准失败:${error.message}`); }
finally { $("approve").disabled = false; }
});
$("reject").addEventListener("click", async () => {
if (!state.selected || state.selected.state !== "PENDING") return;
const reason = window.prompt("写一句拒绝原因,铸澜会据此修正申请:", "范围需要重新确认");
if (reason === null) return;
try {
await api(`api/v1/owner/requests/${state.selected.id}/reject`, {
method: "POST",
headers: { "X-CSRF-Token": state.csrf },
body: JSON.stringify({ reason })
});
toast("已拒绝并留下回执。");
await restoreSession();
} catch (error) { toast(`拒绝失败:${error.message}`); }
});
$("revoke").addEventListener("click", async () => {
if (!state.selected || state.selected.state !== "APPROVED") return;
if (!window.confirm("立即撤销这段开发能力?已连接会话会在下一次调用时被服务器驳回。")) return;
try {
await api(`api/v1/owner/requests/${state.selected.id}/revoke`, {
method: "POST",
headers: { "X-CSRF-Token": state.csrf },
body: JSON.stringify({ reason: "主人在审批端主动撤销" })
});
toast("能力已撤销,原连接不再有效。");
await restoreSession();
} catch (error) { toast(`撤销失败:${error.message}`); }
});
$("logout").addEventListener("click", async () => {
try {
await api("api/v1/owner/logout", { method: "POST", headers: { "X-CSRF-Token": state.csrf }, body: "{}" });
} catch (_) {}
location.reload();
});
restoreSession();

View file

@ -0,0 +1,144 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="description" content="铸澜手机端受限远程开发的人类审批入口。">
<title>铸澜 · 远程开发入口</title>
<link rel="stylesheet" href="assets/styles.css">
</head>
<body>
<div class="stars" aria-hidden="true"></div>
<header class="topbar">
<a class="brand" href="/" aria-label="返回光湖国内入口"><span></span> 光湖</a>
<a class="back" href="/">返回国内入口</a>
</header>
<main>
<section class="intro" aria-labelledby="page-title">
<p class="eyebrow">ZHULAN · HUMAN APPROVAL</p>
<h1 id="page-title">铸澜 · 远程开发入口</h1>
<p>广州只提供域名、TLS 与反向代理;本页面和你看到的每一项申请、批准、回执,都来自新加坡真实运行单元。</p>
<div class="topology" aria-label="运行拓扑">
<span><i class="dot front"></i><b>广州</b> 仅域名 · TLS · 反向代理</span>
<span><i class="dot runtime"></i><b>新加坡</b> 真实 UI · 审批 · 能力 · 沙箱 · 门禁 · 回执</span>
</div>
</section>
<section class="auth-strip" id="auth-strip">
<div>
<p class="label">HUMAN IDENTITY</p>
<strong id="auth-title">主人尚未登录</strong>
<small id="auth-detail">使用光湖代码频道账号完成一次验证,密码不会保存。</small>
</div>
<button class="button secondary" id="login-open" type="button">登录审批端</button>
<button class="text-button hidden" id="logout" type="button">退出</button>
</section>
<section class="workspace" id="workspace" aria-live="polite">
<div class="section-head">
<div>
<p class="label">CURRENT REQUEST</p>
<h2>当前开发申请</h2>
</div>
<span class="state waiting" id="request-state">等待登录</span>
</div>
<div class="empty" id="empty-state">
<div class="empty-star" aria-hidden="true"></div>
<h3>登录后读取真实申请</h3>
<p>这里不会生成演示工单。没有来自铸澜的真实申请时,湖面保持安静。</p>
</div>
<article class="request hidden" id="request-card">
<div class="request-lead">
<div>
<p class="label" id="request-id"></p>
<h3 id="request-description"></h3>
</div>
<span class="state waiting" id="card-state">等待批准</span>
</div>
<dl class="facts">
<div><dt>人格体</dt><dd id="persona"></dd></div>
<div><dt>开发编号</dt><dd id="development-id"></dd></div>
<div><dt>真实运行</dt><dd>BS-SG-003 · 受限开发单元</dd></div>
<div><dt>目标仓库</dt><dd id="repository"></dd></div>
<div><dt>候选分支</dt><dd id="branch"></dd></div>
<div><dt>基线提交</dt><dd class="mono" id="base-sha"></dd></div>
</dl>
<div class="binding-grid">
<div>
<p class="label">ALLOWED ACTIONS</p>
<div class="chips" id="actions"></div>
</div>
<div>
<p class="label">ALLOWED PATHS</p>
<div class="paths" id="paths"></div>
</div>
</div>
<div class="decision" id="decision-area">
<div>
<p class="label">SESSION WINDOW</p>
<select id="ttl" aria-label="临时能力时长">
<option value="10800">3 小时 · 推荐</option>
<option value="21600">6 小时</option>
<option value="43200">12 小时</option>
</select>
</div>
<div class="decision-buttons">
<button class="button ghost" id="reject" type="button">拒绝</button>
<button class="button primary" id="approve" type="button">批准这一段开发</button>
</div>
</div>
<div class="decision hidden" id="revoke-area">
<p>这段能力仍在有效期内。撤销后,已连接的手机会话也会立即失效。</p>
<button class="button ghost" id="revoke" type="button">立即撤销这段能力</button>
</div>
</article>
</section>
<section class="timeline-section">
<div class="section-head compact">
<div>
<p class="label">LIVE RECEIPTS</p>
<h2>同一条真实回执链</h2>
</div>
</div>
<ol class="timeline" id="timeline">
<li><i></i><div><strong>尚未读取</strong><span>登录后显示新加坡运行层的真实事件。</span></div></li>
</ol>
</section>
<aside class="why">
<p class="label">WHY APPROVE ONCE</p>
<h2>批准的是边界,不是每一步。</h2>
<p>临时能力已经锁定人格、开发编号、仓库、基线、候选分支、可改路径、动作与期限。铸澜在这条边界里连续开发;越界时服务器直接驳回并留下回执。</p>
</aside>
</main>
<dialog id="login-dialog">
<form method="dialog" id="login-form">
<button class="dialog-close" value="cancel" aria-label="关闭">×</button>
<p class="label">HUMAN IDENTITY</p>
<h2>登录人类审批端</h2>
<p>使用光湖代码频道主人账号。密码只参与这一次验证,不写入服务器状态。</p>
<label for="username">账号</label>
<input id="username" name="username" autocomplete="username" required>
<label for="password">密码</label>
<input id="password" name="password" type="password" autocomplete="current-password" required>
<p class="form-error" id="login-error"></p>
<button class="button primary full" type="submit">进入审批端</button>
</form>
</dialog>
<div class="toast" id="toast" role="status"></div>
<footer>
<span>铸澜 ICE-GL-ZL-001 · 私有审批投影</span>
<span>前门 ≠ 运行体 · UI ≠ 权限</span>
</footer>
<script src="assets/app.js" defer></script>
</body>
</html>

View file

@ -0,0 +1,205 @@
:root {
color-scheme: dark;
--ink: #070916;
--ink-soft: #0c1025;
--panel: rgba(16, 20, 47, .76);
--panel-strong: rgba(20, 25, 58, .94);
--line: rgba(226, 231, 255, .13);
--line-bright: rgba(236, 239, 255, .28);
--text: #f2f1ed;
--muted: #9fa6bf;
--quiet: #69718f;
--star: #efe7d5;
--violet: #a6a5ff;
--lake: #86b9de;
--ok: #9cd9bd;
--warn: #e6c68d;
--bad: #e19791;
--shadow: 0 30px 80px rgba(0, 0, 0, .38);
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", "Microsoft YaHei", sans-serif;
}
* { box-sizing: border-box; }
html { min-height: 100%; background: var(--ink); }
body {
min-height: 100vh;
margin: 0;
color: var(--text);
background:
radial-gradient(circle at 14% 9%, rgba(99, 95, 185, .18), transparent 31%),
radial-gradient(circle at 84% 20%, rgba(73, 124, 176, .13), transparent 28%),
radial-gradient(ellipse at 50% 105%, rgba(68, 102, 166, .22), transparent 46%),
linear-gradient(155deg, #070915 0%, #0a0d21 44%, #070915 100%);
overflow-x: hidden;
}
body::after {
content: "";
position: fixed;
inset: auto 0 0;
height: 34vh;
pointer-events: none;
background: linear-gradient(to bottom, transparent, rgba(45, 66, 124, .08));
mask-image: linear-gradient(to bottom, transparent, black);
}
.stars, .stars::before, .stars::after {
position: fixed;
inset: 0;
content: "";
pointer-events: none;
background-image:
radial-gradient(circle at 9% 17%, rgba(255,255,255,.75) 0 1px, transparent 1.4px),
radial-gradient(circle at 34% 8%, rgba(255,255,255,.48) 0 1px, transparent 1.4px),
radial-gradient(circle at 72% 13%, rgba(255,255,255,.62) 0 1px, transparent 1.4px),
radial-gradient(circle at 91% 35%, rgba(255,255,255,.40) 0 1px, transparent 1.4px),
radial-gradient(circle at 18% 68%, rgba(255,255,255,.35) 0 1px, transparent 1.4px),
radial-gradient(circle at 79% 79%, rgba(255,255,255,.46) 0 1px, transparent 1.4px);
opacity: .55;
}
.stars::before { transform: translate(5vw, 8vh) scale(.78); opacity: .34; }
.stars::after { transform: translate(-8vw, 15vh) scale(1.22); opacity: .22; }
a { color: inherit; }
button, input, select { font: inherit; }
button { color: inherit; }
.hidden { display: none !important; }
.topbar, main, footer { width: min(760px, calc(100% - 36px)); margin-inline: auto; position: relative; z-index: 1; }
.topbar {
min-height: 68px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--line);
}
.brand { display: inline-flex; align-items: center; gap: 9px; text-decoration: none; font-weight: 740; letter-spacing: .08em; }
.brand span { color: var(--star); font-size: 27px; text-shadow: 0 0 18px rgba(239, 231, 213, .45); }
.back { color: var(--muted); font-size: 12px; text-decoration: none; letter-spacing: .07em; }
main { padding: 54px 0 78px; }
.intro { text-align: center; }
.eyebrow, .label { margin: 0; color: var(--violet); font: 700 10px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .19em; }
.intro h1 { margin: 15px 0 0; font-size: clamp(31px, 7vw, 48px); line-height: 1.15; letter-spacing: -.04em; }
.intro > p:last-of-type { max-width: 610px; margin: 20px auto 0; color: var(--muted); font-size: 14px; line-height: 1.9; }
.topology { margin: 28px auto 0; display: inline-flex; flex-wrap: wrap; justify-content: center; gap: 10px 20px; color: var(--muted); font-size: 11px; }
.topology span { display: inline-flex; align-items: center; gap: 7px; }
.topology b { color: var(--text); }
.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--lake); box-shadow: 0 0 12px rgba(134,185,222,.75); }
.dot.runtime { background: var(--ok); box-shadow: 0 0 12px rgba(156,217,189,.75); }
.auth-strip, .workspace, .timeline-section, .why {
margin-top: 28px;
border: 1px solid var(--line);
border-radius: 20px;
background: linear-gradient(160deg, rgba(22, 26, 58, .74), rgba(10, 13, 31, .83));
box-shadow: var(--shadow);
backdrop-filter: blur(18px);
}
.auth-strip { min-height: 92px; padding: 18px 20px; display: flex; align-items: center; gap: 16px; }
.auth-strip > div { flex: 1; min-width: 0; }
.auth-strip strong { display: block; margin-top: 7px; font-size: 15px; }
.auth-strip small { display: block; margin-top: 5px; color: var(--muted); line-height: 1.55; }
.button {
min-height: 42px;
padding: 0 18px;
border: 1px solid var(--line-bright);
border-radius: 999px;
background: rgba(255,255,255,.045);
font-weight: 700;
font-size: 13px;
cursor: pointer;
transition: transform .18s ease, background .18s ease, border-color .18s ease;
}
.button:hover { transform: translateY(-1px); border-color: rgba(255,255,255,.5); }
.button:active { transform: translateY(0) scale(.98); }
.button:disabled { opacity: .45; cursor: wait; }
.button.primary { color: #111426; background: var(--star); border-color: var(--star); box-shadow: 0 8px 26px rgba(239,231,213,.13); }
.button.ghost { color: var(--muted); }
.button.full { width: 100%; margin-top: 22px; }
.text-button { border: 0; background: transparent; color: var(--muted); cursor: pointer; font-size: 12px; }
.workspace, .timeline-section { padding: 24px; }
.section-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; padding-bottom: 20px; border-bottom: 1px solid var(--line); }
.section-head.compact { padding-bottom: 16px; }
.section-head h2, .why h2 { margin: 8px 0 0; font-size: 21px; letter-spacing: -.02em; }
.state { display: inline-flex; align-items: center; min-height: 28px; padding: 0 11px; border-radius: 999px; border: 1px solid var(--line); color: var(--muted); font: 700 10px/1 ui-monospace, monospace; letter-spacing: .09em; }
.state.waiting { color: var(--warn); border-color: rgba(230,198,141,.28); }
.state.approved { color: var(--ok); border-color: rgba(156,217,189,.3); }
.state.rejected { color: var(--bad); border-color: rgba(225,151,145,.3); }
.empty { padding: 58px 20px 48px; text-align: center; }
.empty-star { color: var(--star); font-size: 25px; text-shadow: 0 0 23px rgba(239,231,213,.5); }
.empty h3 { margin: 14px 0 0; font-size: 17px; }
.empty p { max-width: 420px; margin: 10px auto 0; color: var(--muted); font-size: 13px; line-height: 1.8; }
.request { padding-top: 22px; }
.request-lead { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
.request-lead h3 { max-width: 520px; margin: 9px 0 0; font-size: 18px; line-height: 1.55; }
.facts { margin: 24px 0 0; padding: 0; display: grid; grid-template-columns: 1fr 1fr; border-top: 1px solid var(--line); }
.facts div { min-width: 0; padding: 16px 12px 16px 0; border-bottom: 1px solid var(--line); }
.facts div:nth-child(even) { padding-left: 18px; border-left: 1px solid var(--line); }
.facts dt { color: var(--quiet); font-size: 11px; }
.facts dd { margin: 7px 0 0; overflow-wrap: anywhere; font-size: 13px; font-weight: 650; line-height: 1.55; }
.mono { font: 600 11px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--muted); }
.binding-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 22px 0; border-bottom: 1px solid var(--line); }
.chips, .paths { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.chip, .path { padding: 7px 9px; border-radius: 8px; color: var(--muted); background: rgba(255,255,255,.045); font: 600 10px/1.3 ui-monospace, monospace; }
.path { width: 100%; overflow-wrap: anywhere; }
.decision { display: flex; justify-content: space-between; align-items: flex-end; gap: 20px; padding-top: 22px; }
select { min-width: 150px; margin-top: 9px; padding: 10px 34px 10px 11px; color: var(--text); border: 1px solid var(--line); border-radius: 10px; background: var(--ink-soft); }
.decision-buttons { display: flex; gap: 10px; }
.timeline { margin: 0; padding: 19px 0 2px; list-style: none; }
.timeline li { position: relative; display: grid; grid-template-columns: 14px 1fr; gap: 12px; padding: 8px 0 14px; }
.timeline li:not(:last-child)::after { content: ""; position: absolute; left: 4px; top: 21px; bottom: -4px; width: 1px; background: var(--line); }
.timeline i { width: 9px; height: 9px; margin-top: 4px; border-radius: 50%; background: var(--quiet); box-shadow: 0 0 0 4px rgba(105,113,143,.08); }
.timeline li.pass i { background: var(--ok); box-shadow: 0 0 11px rgba(156,217,189,.6); }
.timeline li.fail i { background: var(--bad); box-shadow: 0 0 11px rgba(225,151,145,.55); }
.timeline strong { display: block; font-size: 13px; }
.timeline span { display: block; margin-top: 5px; color: var(--muted); font-size: 11px; line-height: 1.6; overflow-wrap: anywhere; }
.why { padding: 28px; text-align: center; }
.why p:last-child { max-width: 600px; margin: 14px auto 0; color: var(--muted); font-size: 13px; line-height: 1.9; }
dialog { width: min(430px, calc(100% - 28px)); padding: 0; color: var(--text); border: 1px solid var(--line-bright); border-radius: 20px; background: var(--panel-strong); box-shadow: 0 38px 100px rgba(0,0,0,.65); }
dialog::backdrop { background: rgba(3,5,15,.78); backdrop-filter: blur(8px); }
dialog form { position: relative; padding: 30px; }
dialog h2 { margin: 10px 0 0; font-size: 23px; }
dialog p:not(.label):not(.form-error) { margin: 12px 0 20px; color: var(--muted); font-size: 13px; line-height: 1.7; }
dialog label { display: block; margin: 15px 0 7px; color: var(--muted); font-size: 11px; }
dialog input { width: 100%; min-height: 45px; padding: 0 13px; border: 1px solid var(--line); border-radius: 10px; outline: 0; color: var(--text); background: rgba(255,255,255,.045); }
dialog input:focus { border-color: var(--line-bright); box-shadow: 0 0 0 3px rgba(166,165,255,.08); }
.dialog-close { position: absolute; top: 15px; right: 18px; border: 0; background: transparent; color: var(--muted); font-size: 24px; cursor: pointer; }
.form-error { min-height: 18px; margin: 10px 0 0; color: var(--bad); font-size: 12px; }
.toast { position: fixed; z-index: 10; left: 50%; bottom: 24px; transform: translate(-50%, 16px); max-width: calc(100% - 28px); padding: 11px 18px; opacity: 0; pointer-events: none; border: 1px solid var(--line-bright); border-radius: 999px; background: var(--panel-strong); box-shadow: var(--shadow); transition: .25s ease; font-size: 12px; text-align: center; }
.toast.show { opacity: 1; transform: translate(-50%, 0); }
footer { min-height: 90px; display: flex; justify-content: space-between; align-items: center; gap: 18px; border-top: 1px solid var(--line); color: var(--quiet); font-size: 10px; }
@media (max-width: 640px) {
.topbar, main, footer { width: min(100% - 24px, 760px); }
main { padding-top: 38px; }
.intro h1 { font-size: 31px; }
.intro > p:last-of-type { font-size: 13px; }
.topology { align-items: flex-start; flex-direction: column; text-align: left; }
.auth-strip { align-items: flex-start; flex-wrap: wrap; }
.auth-strip .button { width: 100%; }
.workspace, .timeline-section { padding: 19px; }
.section-head { align-items: center; }
.request-lead { flex-direction: column; }
.facts { grid-template-columns: 1fr; }
.facts div:nth-child(even) { padding-left: 0; border-left: 0; }
.binding-grid { grid-template-columns: 1fr; gap: 20px; }
.decision { align-items: stretch; flex-direction: column; }
select { width: 100%; }
.decision-buttons { display: grid; grid-template-columns: .8fr 1.4fr; }
.decision-buttons .button { padding-inline: 12px; }
footer { padding: 24px 0; align-items: flex-start; flex-direction: column; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { scroll-behavior: auto !important; transition: none !important; }
}