Compare commits

..
12 changed files with 777 additions and 221 deletions
@@ -0,0 +1,95 @@
# GitOps 独立 Worktree 与统一运行身份迁移结果
```yaml
status: BLOCKED
technical_status: PASS
task_id: TASK-GITOPS-ISOLATED-WORKTREE-MIGRATION-20260714T233742UTC8
created_at: 2026-07-14T23:37:42+08:00
completed_at: 2026-07-15T06:24:00+08:00
timezone: Asia/Shanghai
user_action_required: false
runtime_identity:
user: aiman
group: gitops
umask: "0002"
bare_repo: /data/aios-gitops/repo.git
worktree_root: /data/aios-gitops/worktrees
controller_worktree: /data/aios-gitops/worktrees/controller
services:
aios-gitops-model-api:
state: active/running
pid: 1254370
user: aiman
group: gitops
umask: "0002"
working_directory: /data/aios-gitops/worktrees/controller
health_http_code: 200
aios-autonomous-controller: inactive/dead (pre-state preserved)
aios-autonomous-daemon: inactive/dead (pre-state preserved)
aios-agent-command-watcher: inactive/dead (pre-state preserved)
openclaw-gateway:
state: active/running
pid: 1333116
user: root
group: gitops
umask: "0002"
working_directory: /var/lib/aios/openclaw-home
resumed_at: 2026-07-15T06:21:19+08:00
listener: 127.0.0.1:18789
health_http_code: 200
weixin_accounts_running: 4
weixin_last_error: null
identity_changed: false
config_or_db_mutated: false
default_acl: PASS
preflight: PASS
concurrency_test:
status: PASS
run_id: 20260715T061051UTC8
openclaw_branch: task/openclaw/TASK-WORKTREE-CONCURRENCY-OPENCLAW-20260715T061051UTC8
openclaw_commit: dbe9284a49bccb664d042789b3deb3b21669ea0f
openclaw_interval_ns: "[1784067525001763595,1784067525404170029]"
chatgpt_branch: task/chatgpt/TASK-WORKTREE-CONCURRENCY-CHATGPT-20260715T061051UTC8
chatgpt_commit: fe85f051655afbf1e3ee69496f51f2ea64dc1100
chatgpt_interval_ns: "[1784067525005911896,1784067525404756753]"
overlap_ns: 398258133
push_mode: non-force
permission_denied_count: 0
untracked_overwrite_count: 0
git_lock_collision_count: 0
openclaw_commit_push: PASS
chatgpt_commit_push: PASS
deep_directory_acl_inheritance: PASS
shared_workspace_branch_switches: 0
owner_notifications:
received_message_id: null
terminal_message_id: null
received_status: NOT_SENT
terminal_status: NOT_SENT
gate_status: BLOCKED
reason: current operator directive prohibited WeChat; no safe verifiable target/message_id evidence exists
rollback:
triggered: false
source: /data/aios-gitops/workspace
source_head: b05eb1babe9c657e8776c09eec71302166902cc3
source_branch: task/codex/gitops-isolated-worktree-migration-20260715T001652UTC8
source_status_sha256_before: dd4c4d4e23faeda7f097bd9eadc24112be9da1c6b564a1682b4148061fe8d5f2
source_status_sha256_after: dd4c4d4e23faeda7f097bd9eadc24112be9da1c6b564a1682b4148061fe8d5f2
remaining_risks:
- Mandatory Owner notification evidence is absent; overall PASS is prohibited.
- Two invalid concurrency attempts are retained as audit evidence and are not acceptance runs.
next_step: Owner may explicitly waive the notification gate or authorize a verifiable notification; the technical migration does not require rerun.
```
## 技术执行证据
- 实际 Git 写入者为 `aios-gitops-model-api.service`。迁移前为 active/running、PID `621772`;安装 canonical identity 与 controller workdir drop-in 后,已恢复为 active/running、PID `1254370`,`aiman:gitops`、`UMask=0002`、controller worktree、`/health=200`。
- 四个写入服务的 `92-runtime-identity.conf` 均为 `root:root 0644`,SHA256 均为 `e37651f9e98869bcc0a295577ca240be0950214abbccfc9f94182bebebaf99c4`,与 task commit 中 canonical 文件一致。model-api 的 `93-controller-workdir.conf` SHA256 为 `d0a6ac1cbe48abf63a045eb2078c4582a3d05604e6821f765ad15587f5cc2613`。
- Gateway 未参与 Git writer identity 变更;迁移窗口内释放生产运行占用,验收后按原 identity 恢复。当前 active/running,18789 监听,`/health=200`,4 个 Weixin account running 且 `lastError=null`;未改 gateway config/DB。
- bare repo 与 worktree 根为 `aiman:gitops`、目录 `2775`、setgid/default ACL 继承有效。修复的 1 个 root-owned 目录与 2 个文件仅变更属主;两文件 SHA256 保持 `d83b843fd54eccfaf68a43fbe18e1c61542eeb17cc957979c04fa0e5c8dce126`、`7f58e4f7de5c72a5fdc88a56eee40aad6fa3047bc48e9b7e5a6d14f2692cfa63`,worktrees 内 root-owned remaining 为 0。
- 有效并发轮次 `20260715T061051UTC8` 由两个预启动 barrier worker 在同一 `execute_at_epoch` 执行。两条 commit+non-force push 区间真实重叠 `398258133 ns`;两侧 local OID 与 Gitea OID 一致,各自仅提交 3 个文件、worktree clean、深层 ACL PASS,权限拒绝/未跟踪覆盖/Git lock 均为 0。
- shared workspace 未切分支、未清理、未 stash、未提交;HEAD、branch 与 NUL-delimited `status --porcelain=v1 -z -uall` 哈希前后完全一致。
## 终态判定
技术迁移与并发硬门均为 PASS;但任务原文要求收到任务与终态均提供可验证 Owner 微信 `message_id`。当前操作指令明确禁止 WeChat,且不存在可安全验证的 target/message_id,因此不伪造通知,整体状态按强制门保守记为 BLOCKED。
@@ -1,86 +0,0 @@
---
task_id: TASK-AIOS-OPENCLAW-P6-MAIN-FASTFORWARD-20260714T014500+0800
executor: OPENCLAW
status: PASS
owner_approved: true
main_before: 6b70a3308031e3ef8e04a89ca02fd3bdcab1ebcc
integration_commit: 3cf9958cb94614449e093639cbf902216d73447f
integration_parent: 6b70a3308031e3ef8e04a89ca02fd3bdcab1ebcc
main_after: 3cf9958cb94614449e093639cbf902216d73447f
changed_files: 1
accepted_blob: ff31f7ab603d7a7cd07319e6f8422ae464916179
push_mode: fast-forward-only
force_push_used: false
merge_commit_created: false
tag_created: false
baseline_changed: false
deployment_performed: false
---
# P6 Main Fast-Forward Report
## Summary
| Field | Value |
|-------|-------|
| Task ID | `TASK-AIOS-OPENCLAW-P6-MAIN-FASTFORWARD-20260714T014500+0800` |
| Executor | OPENCLAW |
| Status | **PASS** |
| Owner approved | true |
## Git State
| Attribute | Before | After |
|-----------|--------|-------|
| `origin/main` | `6b70a3308031e3ef8e04a89ca02fd3bdcab1ebcc` | `3cf9958cb94614449e093639cbf902216d73447f` |
| Integration branch | — | `integration/v2r1-g1-evidence-main-20260714` |
| Integration commit | — | `3cf9958cb94614449e093639cbf902216d73447f` |
| Integration parent | — | `6b70a3308031e3ef8e04a89ca02fd3bdcab1ebcc` |
## Pre-push Verification
| Check | Command | Exit | Result |
|-------|---------|------|--------|
| Current main unchanged | `git rev-parse origin/main^{commit}` = `6b70a33...` | 0 | ✅ |
| Integration exists remotely | `git rev-parse origin/integration/v2r1-g1-evidence-main-20260714^{commit}` = `3cf9958...` | 0 | ✅ |
| Integration is direct child of main | `git rev-parse 3cf9958...^` = `6b70a33...` | 0 | ✅ |
| Accepted blob matches | `git rev-parse 3cf9958...:.agent/results/openclaw/RESULT-V2R1-G1-OPENCLAW-FINAL-EVIDENCE-20260714.md` = `ff31f7a...` | 0 | ✅ |
| Prefix `3cf9958` matches | `test "${TARGET#3cf9958}" != "$TARGET"` | 0 | ✅ |
| Exactly 1 commit in range | `git rev-list --count 6b70a33... 3cf9958...` = `1` | 0 | ✅ |
| Main is ancestor of integration | `git merge-base --is-ancestor 6b70a33... 3cf9958...` | 0 | ✅ |
| Diff is exactly 1 file | `git diff --name-only 6b70a33... 3cf9958...` → 1 file | 0 | ✅ |
| Changed file is accepted evidence | path = `.agent/results/openclaw/RESULT-V2R1-G1-OPENCLAW-FINAL-EVIDENCE-20260714.md` | 0 | ✅ |
## Push
```bash
git push --porcelain origin 3cf9958...:refs/heads/main
```
Output:
```
3cf9958...:refs/heads/main 6b70a33..3cf9958 Done
```
Exit code: **0**
## Post-push Verification
| Check | Command | Exit | Result |
|-------|---------|------|--------|
| origin/main updated | `git rev-parse origin/main^{commit}` = `3cf9958...` | 0 | ✅ |
| origin/main == integration commit | `test "$NEW_MAIN" = "$TARGET"` | 0 | ✅ |
| Post-push diff is 1 file | `git diff --name-only 6b70a33... 3cf9958...` → 1 file | 0 | ✅ |
| Diff file is accepted evidence | path = `.agent/results/openclaw/RESULT-V2R1-G1-OPENCLAW-FINAL-EVIDENCE-20260714.md` | 0 | ✅ |
## Constraints
| Operation | Performed |
|-----------|-----------|
| Force push | false |
| Merge commit | false |
| Tag created | false |
| Baseline changed | false |
| Deployment | false |
## Recommendation
**PASS** — `origin/main` fast-forwarded from `6b70a33...` to `3cf9958...` with a single evidence-only commit containing the accepted Gate 1 clean-room final evidence blob `ff31f7a...`. No force push, merge, baseline change, or deployment performed.
@@ -0,0 +1,156 @@
# V2R1 Gate 1 — Clean-Room Final Evidence
## Summary
| Field | Value |
|---|---|
| Date | 2026-07-14 |
| Source commit | `502ef30642597fb0b70b90ae2e8a0b6af217d80e` |
| Source branch | `origin/work/v2r1-integration-quality-2000` |
| Result branch | `fix/v2r1-g1-cleanroom-final-evidence-r2-20260714` |
| Result commit reference | `HEAD` |
| Gate | `G1` |
| Variant | `openclaw-cleanroom` |
| Status | `PASS` |
The immutable report uses `HEAD` for post-commit verification. The resolved commit object ID is recorded by the external acceptance result after this file is committed; a Git commit cannot reliably embed its own object ID because changing the file changes the tree and commit identity.
## Machine-Readable Result
```yaml
gate: G1
variant: openclaw-cleanroom
date: 2026-07-14
status: PASS
source_commit: 502ef30642597fb0b70b90ae2e8a0b6af217d80e
source_branch: origin/work/v2r1-integration-quality-2000
result_branch: fix/v2r1-g1-cleanroom-final-evidence-r2-20260714
result_commit_ref: HEAD
tests_total: 32
tests_passed: 32
tests_failed: 0
cleanup_scan: PASS
secret_scan: PASS
rollback_status: PASS
recovery_verified: true
workspace_clean_at_test_completion: true
merge_performed: false
tag_created: false
baseline_promoted: false
deployment_performed: false
```
## Evidence Provenance
The test, cleanup, secret-scan, rollback, and recovery results below are execution evidence supplied by OpenClaw for the Gate 1 run. This correction commit changes only the evidence report and does not claim that the GitOps API reran the suites.
## HMAC Test-Key Length Verification
| Test file | Variable | Test-only value | Length |
|---|---|---|---:|
| `tests/v2r1/test_runtime_wiring.py:24` | `internal_key` | `b"runtime-internal-context-key-is-32bytes"` | 39 bytes |
| `tests/v2r1/test_runtime_wiring.py:25` | `ingress_key` | `b"runtime-external-ingress-key-is-32bytes"` | 39 bytes |
Both values are test fixtures. The supplied evidence reports that no production secret was introduced.
## Test Results Supplied by OpenClaw
### V2R1 suite — 29 of 29 passed
```shell
PYTHONPATH=active-runtime/app python3 -m pytest \
tests/v2r1/test_hermes_fabric.py \
tests/v2r1/test_message_ledger_append.py \
tests/v2r1/test_router_isolation.py \
tests/v2r1/test_runtime_identity_binding.py \
tests/v2r1/test_runtime_wiring.py \
tests/v2r1/test_tenant_context.py -v
# Reported result: 29 passed, exit code 0
```
### Integration suite — 3 of 3 passed
```shell
PYTHONPATH=active-runtime/app python3 -m pytest tests/integration/test_runtime_ingress_flow.py -v
# Reported result: 3 passed, exit code 0
```
**Reported total: 32 of 32 passed.**
## Cleanup, Security, and Recovery Evidence
| Check | Supplied result |
|---|---|
| Conflict-marker scan | PASS |
| Cleanup scan | PASS |
| Secret scan | PASS |
| Legitimate hardcoded test keys | Two 39-byte fixtures in `test_runtime_wiring.py` |
| Rollback rehearsal | PASS; 13 of 13 core tests reported after rollback to `6077331` |
| Recovery verification | PASS |
## Deployment State
| Operation | Performed |
|---|---|
| Merge | No |
| Tag creation | No |
| Baseline promotion | No |
| Staging deployment | No |
| Production deployment | No |
## Post-Commit Verification
Run from the repository root after the commit is created:
```shell
set -eu
SOURCE=502ef30642597fb0b70b90ae2e8a0b6af217d80e
RESULT_FILE=.agent/results/openclaw/RESULT-V2R1-G1-OPENCLAW-FINAL-EVIDENCE-20260714.md
git rev-parse HEAD
test "$(git diff --name-only "$SOURCE" HEAD | wc -l | tr -d ' ')" = "1"
test "$(git diff --name-only "$SOURCE" HEAD)" = "$RESULT_FILE"
git ls-tree -r HEAD -- "$RESULT_FILE"
python3 - "$RESULT_FILE" <<'PY'
from pathlib import Path
import re
import sys
path = Path(sys.argv[1])
text = path.read_text(encoding="utf-8")
encoded_tokens = [
[84, 66, 68],
[80, 69, 78, 68, 73, 78, 71],
[84, 79, 68, 79],
[70, 73, 88, 77, 69],
[91, 123, 99, 111, 109, 109, 105, 116, 125, 93],
]
tokens = ["".join(chr(value) for value in item) for item in encoded_tokens]
angle_token = re.compile(chr(60) + r"[^>\n]+" + chr(62))
hits = [token for token in tokens if token in text]
if angle_token.search(text):
hits.append("angle-delimited-token")
if hits:
print("TOKEN_SCAN=FAIL", hits)
raise SystemExit(1)
print("TOKEN_SCAN=PASS")
PY
```
## Acceptance Conditions
Gate 1 is eligible to proceed only when the external acceptance record confirms all of the following against the resolved `HEAD` commit:
- the commit exists and has source commit `502ef30642597fb0b70b90ae2e8a0b6af217d80e` as its parent;
- exactly this one report file changed;
- the committed report contains no unresolved legacy token;
- the result tree is not identical to rejected commit `71239b4d187e683e05fd22fc9d5df8ae324e3390`;
- no merge, tag, baseline promotion, or deployment occurred.
@@ -1,135 +0,0 @@
---
task_id: TASK-AIOS-OPENCLAW-P6-MAIN-FASTFORWARD-20260714T014500+0800
created_at: 2026-07-14T01:45:00+08:00
timezone: Asia/Shanghai
project: AIOS
version: V2R1 Gate 1
executor: OPENCLAW
status: READY
objective: Update origin/main by fast-forward only to the reviewed evidence-only integration commit.
repository: /data/aios-gitops/workspace
owner_approved: true
owner_approval_context: The owner explicitly authorized proceeding to the next step after P6 integration review PASS.
expected_main_commit: 6b70a3308031e3ef8e04a89ca02fd3bdcab1ebcc
integration_branch: integration/v2r1-g1-evidence-main-20260714
integration_commit_expected_prefix: 3cf9958
accepted_file: .agent/results/openclaw/RESULT-V2R1-G1-OPENCLAW-FINAL-EVIDENCE-20260714.md
accepted_blob: ff31f7ab603d7a7cd07319e6f8422ae464916179
review_result_commit: 86a32e4
work_branch: task/openclaw/p6-main-fastforward-20260714-014500
scope:
- Re-read remote main and integration refs.
- Enforce direct-child and exact single-file conditions.
- Push integration commit to refs/heads/main without force.
- Verify remote main after push.
- Record the execution result on the task branch.
out_of_scope:
- Merge commit
- Squash or rebase
- Force push or force-with-lease
- Tag creation
- Baseline promotion
- Deployment
- Business-code or test changes
risk_level: MEDIUM
production_authorized: false
result_file: .agent/results/openclaw/RESULT-TASK-AIOS-OPENCLAW-P6-MAIN-FASTFORWARD-20260714T014500+0800.md
---
# Owner-authorized P6 main fast-forward
## Mandatory preconditions
Execute from the authoritative repository, without switching the shared workspace to `main`:
```bash
set -euo pipefail
REPO=/data/aios-gitops/workspace
EXPECTED_MAIN=6b70a3308031e3ef8e04a89ca02fd3bdcab1ebcc
INTEGRATION_BRANCH=integration/v2r1-g1-evidence-main-20260714
ACCEPTED_FILE=.agent/results/openclaw/RESULT-V2R1-G1-OPENCLAW-FINAL-EVIDENCE-20260714.md
ACCEPTED_BLOB=ff31f7ab603d7a7cd07319e6f8422ae464916179
cd "$REPO"
git fetch --prune origin
CURRENT_MAIN=$(git rev-parse origin/main^{commit})
TARGET=$(git rev-parse "origin/$INTEGRATION_BRANCH"^{commit})
TARGET_PARENT=$(git rev-parse "$TARGET^" )
TARGET_BLOB=$(git rev-parse "$TARGET:$ACCEPTED_FILE")
printf 'CURRENT_MAIN=%s\nTARGET=%s\nTARGET_PARENT=%s\nTARGET_BLOB=%s\n' \
"$CURRENT_MAIN" "$TARGET" "$TARGET_PARENT" "$TARGET_BLOB"
test "$CURRENT_MAIN" = "$EXPECTED_MAIN"
test "$TARGET_PARENT" = "$EXPECTED_MAIN"
test "$TARGET_BLOB" = "$ACCEPTED_BLOB"
test "${TARGET#3cf9958}" != "$TARGET"
test "$(git rev-list --count "$EXPECTED_MAIN..$TARGET")" = "1"
git merge-base --is-ancestor "$EXPECTED_MAIN" "$TARGET"
mapfile -t CHANGED < <(git diff --name-only "$EXPECTED_MAIN" "$TARGET")
test "${#CHANGED[@]}" = "1"
test "${CHANGED[0]}" = "$ACCEPTED_FILE"
# This is a normal non-force push. It must fail if remote main has moved incompatibly.
git push --porcelain origin "$TARGET:refs/heads/main"
git fetch origin main
NEW_MAIN=$(git rev-parse origin/main^{commit})
test "$NEW_MAIN" = "$TARGET"
mapfile -t POST_CHANGED < <(git diff --name-only "$EXPECTED_MAIN" "$NEW_MAIN")
test "${#POST_CHANGED[@]}" = "1"
test "${POST_CHANGED[0]}" = "$ACCEPTED_FILE"
echo MAIN_FAST_FORWARD=PASS
```
## Abort conditions
Return `BLOCKED` and do not push if any condition differs:
- `origin/main` is no longer `6b70a3308031e3ef8e04a89ca02fd3bdcab1ebcc`;
- the integration commit is not a direct child of that commit;
- the integration range contains more than one commit;
- the diff contains anything other than the accepted evidence file;
- the file blob differs from `ff31f7ab603d7a7cd07319e6f8422ae464916179`;
- the push would require force, merge, squash or rebase.
## Required result
Use an isolated worktree for the task branch. Commit and push the result file with:
```text
result(TASK-AIOS-OPENCLAW-P6-MAIN-FASTFORWARD-20260714T014500+0800): record main fast-forward
```
The result must contain:
```yaml
task_id: TASK-AIOS-OPENCLAW-P6-MAIN-FASTFORWARD-20260714T014500+0800
executor: OPENCLAW
status: PASS | BLOCKED | FAILED
owner_approved: true
main_before:
integration_commit:
integration_parent:
main_after:
changed_files:
accepted_blob:
push_mode: fast-forward-only
force_push_used: false
merge_commit_created: false
tag_created: false
baseline_changed: false
deployment_performed: false
commands:
exit_codes:
blockers:
remaining_risks:
recommendation:
```
Do not create a tag, change the baseline, or deploy after the push. Return the complete result commit ID.
@@ -0,0 +1,267 @@
# 执行 GitOps 独立 Worktree 与统一运行身份迁移
```yaml
task_id: TASK-GITOPS-ISOLATED-WORKTREE-MIGRATION-20260714T233742UTC8
created_at: 2026-07-14T23:37:42+08:00
timezone: Asia/Shanghai
owner_approved: true
user_action_required: false
risk: high
production_business_change: false
```
## 目标
根治共享 worktree 与多身份写入导致的 ACL、checkout 和未跟踪文件覆盖问题。
实施:
1. 统一 GitOps/OpenClaw 写入身份为 `aiman:gitops`;
2. 统一 `UMask=0002`;
3. 为 GitOps 根目录设置 setgid 与默认 ACL;
4. 建立 bare repo 和每任务独立 worktree;
5. 启用任务启动前自检;
6. 验证 OpenClaw 与 ChatGPT 并发任务互不干扰;
7. 保留原共享 workspace 作为只读回滚源,禁止智能体继续在其中切换分支。
## 强制通知
收到任务后立即微信通知 Owner:
```text
【任务已接收】GitOps独立worktree与统一运行身份迁移开始。将暂停写入服务、建立独立任务工作树并验证并发执行;生产业务版本不变。
```
任务结束后无论 PASS、BLOCKED 或 ROLLED_BACK,立即微信汇报结果并记录 message_id。
## 1. 变更前检查与封存
```bash
set -euo pipefail
cd /data/aios-gitops/workspace
pwd
id
umask
git status --short --branch
git rev-parse HEAD
systemctl list-units --type=service --all | grep -Ei 'openclaw|gitops|aios'
```
将工作区未提交内容完整提交到非 main 证据分支或 stash,禁止 `git clean -fd`。
记录当前运行服务、用户、组、umask、PID、工作区 HEAD 和未跟踪文件清单。
## 2. 暂停所有写入服务
识别并停止实际写入服务:
```bash
systemctl stop <gitops-writer-service>
systemctl stop <openclaw-controller-service>
systemctl stop <openclaw-watcher-service>
```
只停止写入者,不停止生产业务服务。记录每条命令和退出码。
## 3. 安装统一运行身份 drop-in
对每个 GitOps/OpenClaw 写入服务安装:
```text
config/systemd/aios-gitops-runtime-identity.conf
```
目标:
```yaml
User: aiman
Group: gitops
SupplementaryGroups: gitops
UMask: 0002
```
执行:
```bash
systemctl daemon-reload
systemctl cat <service>
```
不得让服务继续以 root 写 workspace。
## 4. 初始化 bare repo 与独立 worktree 根目录
执行治理补丁中的脚本:
```bash
bash scripts/gitops/bootstrap_isolated_worktrees.sh
```
验收:
```yaml
bare_repo: /data/aios-gitops/repo.git
worktree_root: /data/aios-gitops/worktrees
owner: aiman
group: gitops
directory_mode: 2775
default_acl: inherited
```
## 5. 创建 controller 独立 worktree
以 `aiman`、`umask 0002` 执行:
```bash
sudo -u aiman -g gitops bash -lc '
umask 0002
cd /data/aios-gitops
scripts/gitops/task_worktree.sh create controller controller-runtime origin/main
'
```
将 controller/watcher 的工作目录改为:
```text
/data/aios-gitops/worktrees/controller/controller-runtime
```
## 6. 启用任务启动前自检
每个任务在执行前必须运行:
```bash
scripts/gitops/preflight_task_environment.sh
```
任何一项失败时不得开始任务:
- 用户不是 aiman;
- 不在 gitops 组;
- umask 不是 0002;
- 使用共享 `/data/aios-gitops/workspace`;
- worktree 不干净;
- task/result/git dir 不可写;
- 存在 Git lock。
## 7. 启动服务
```bash
systemctl start <gitops-writer-service>
systemctl start <openclaw-controller-service>
systemctl start <openclaw-watcher-service>
```
验证:
```bash
systemctl show <service> -p User -p Group -p UMask -p WorkingDirectory
ps -eo user,group,pid,cmd | grep -Ei 'openclaw|gitops|aios'
```
## 8. 并发验收
创建两个独立任务:
```text
OpenClaw: TASK-WORKTREE-CONCURRENCY-OPENCLAW-<timestamp>
ChatGPT: TASK-WORKTREE-CONCURRENCY-CHATGPT-<timestamp>
```
分别使用:
```text
/data/aios-gitops/worktrees/openclaw/<task-id>
/data/aios-gitops/worktrees/chatgpt/<task-id>
```
两个任务同时执行:
1. 写各自 `.agent/tasks/<agent>/`;
2. 写各自 `.agent/results/<agent>/`;
3. 创建深层子目录,验证默认 ACL;
4. commit;
5. push;
6. 不切换共享 workspace 分支。
验收必须全部通过:
```yaml
permission_denied: 0
untracked_overwrite_errors: 0
git_lock_collisions: 0
openclaw_commit_push: PASS
chatgpt_commit_push: PASS
deep_directory_acl_inheritance: PASS
shared_workspace_branch_switches: 0
```
## 9. 回滚
若迁移失败:
1. 停止新服务;
2. 保留失败 worktree 供审计;
3. 恢复 systemd drop-in 备份;
4. `daemon-reload`;
5. 恢复原服务状态;
6. 不删除 `/data/aios-gitops/workspace`;
7. 生产业务保持不变。
## 10. 结果文件
提交:
```text
.agent/results/openclaw/RESULT-GITOPS-ISOLATED-WORKTREE-MIGRATION-20260714T233742UTC8.md
```
首段必须包含:
```yaml
status: PASS | BLOCKED | ROLLED_BACK
task_id: TASK-GITOPS-ISOLATED-WORKTREE-MIGRATION-20260714T233742UTC8
created_at: 2026-07-14T23:37:42+08:00
timezone: Asia/Shanghai
user_action_required: false
runtime_identity:
user:
group:
umask:
bare_repo:
worktree_root:
controller_worktree:
services:
default_acl:
preflight:
concurrency_test:
openclaw_branch:
openclaw_commit:
chatgpt_branch:
chatgpt_commit:
permission_denied_count:
untracked_overwrite_count:
git_lock_collision_count:
owner_notifications:
received_message_id:
terminal_message_id:
rollback:
remaining_risks:
next_step:
```
提交并推送到独立执行分支。禁止直接推 main。
## 完成微信模板
PASS:
```text
【任务完成】GitOps独立worktree迁移已通过。所有写入服务统一为aiman:gitops、umask=0002;OpenClaw与ChatGPT并发commit/push无ACL或checkout冲突。生产业务版本未变化。
```
阻塞:
```text
【任务阻塞】GitOps独立worktree迁移未通过。原因=<具体服务/权限/并发验收>;已保留原workspace和回滚能力,生产业务未变化。
```
+59
View File
@@ -0,0 +1,59 @@
version: 1
policy_id: gitops-isolated-worktree-governance-20260714T232948UTC8
runtime_identity:
user: aiman
group: gitops
umask: "0002"
prohibit_root_workspace_writes: true
repository:
source_worktree: /data/aios-gitops/workspace
bare_repo: /data/aios-gitops/repo.git
worktree_root: /data/aios-gitops/worktrees
remote: origin
worktrees:
controller: /data/aios-gitops/worktrees/controller
task_pattern: /data/aios-gitops/worktrees/{agent}/{task_id}
allowed_agents:
- openclaw
- chatgpt
- controller
require_clean_start: true
remove_after_successful_push: true
retain_on_failure: true
permissions:
owner: aiman
group: gitops
directory_mode: "2775"
file_mode: "0664"
default_acl:
user:aiman: rwx
group:gitops: rwx
apply_to:
- /data/aios-gitops
- /data/aios-gitops/worktrees
preflight:
required_user: aiman
required_group: gitops
required_umask: "0002"
require_writable_git_dir: true
require_writable_task_dir: true
require_writable_result_dir: true
reject_shared_source_worktree: true
reject_existing_git_lock: true
notifications:
owner_on_received: required
owner_on_terminal: required
preferred_channel: weixin
redact_secrets: true
rollback:
preserve_source_worktree: true
remove_created_worktrees: true
restore_service_dropins: true
restore_service_state: true
@@ -0,0 +1,14 @@
# Install as a systemd drop-in for GitOps/OpenClaw writer services.
# Example: /etc/systemd/system/<service>.service.d/10-runtime-identity.conf
[Service]
User=aiman
Group=gitops
SupplementaryGroups=gitops
UMask=0002
Environment=AIOS_GITOPS_ROOT=/data/aios-gitops
Environment=AIOS_BARE_REPO=/data/aios-gitops/repo.git
Environment=AIOS_WORKTREE_ROOT=/data/aios-gitops/worktrees
Environment=AIOS_RUNTIME_USER=aiman
Environment=AIOS_RUNTIME_GROUP=gitops
Environment=AIOS_RUNTIME_UMASK=0002
@@ -0,0 +1,9 @@
{
"id": "patch-1784043007770-e97206c3",
"patch_name": "gitops-isolated-worktree-governance-20260714T232948UTC8",
"branch": "patch/gitops-isolated-worktree-governance-20260714T232948UTC8",
"risk": "high",
"description": "Root-cause remediation for recurring ACL and shared-worktree failures: unified aiman:gitops runtime identity, UMask=0002, inherited default ACLs, per-task isolated Git worktrees, startup preflight, migration/rollback scripts, systemd drop-in templates, audit evidence, and an OpenClaw execution task. No production business deployment in this patch.",
"created_by": "chatgpt-gitops-bot",
"created_at": "2026-07-14T15:30:07Z"
}
@@ -0,0 +1,9 @@
{
"type": "branch",
"branch": "task/codex/gitops-isolated-worktree-migration-20260715T001652UTC8",
"base": "patch/gitops-isolated-worktree-governance-20260714T232948UTC8",
"created_by": "chatgpt-gitops-bot",
"created_at": "2026-07-14T16:17:28Z",
"direct_main_push": false,
"owner_approval_required": true
}
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="${AIOS_GITOPS_ROOT:-/data/aios-gitops}"
SOURCE="${AIOS_SOURCE_WORKTREE:-$ROOT/workspace}"
BARE="${AIOS_BARE_REPO:-$ROOT/repo.git}"
WORKTREES="${AIOS_WORKTREE_ROOT:-$ROOT/worktrees}"
RUNTIME_USER="${AIOS_RUNTIME_USER:-aiman}"
RUNTIME_GROUP="${AIOS_RUNTIME_GROUP:-gitops}"
if [[ "$(id -u)" -ne 0 ]]; then
echo "ERROR: bootstrap must run as root" >&2
exit 2
fi
getent passwd "$RUNTIME_USER" >/dev/null
getent group "$RUNTIME_GROUP" >/dev/null
[[ -d "$SOURCE/.git" || -f "$SOURCE/.git" ]] || {
echo "ERROR: source worktree not found: $SOURCE" >&2
exit 3
}
install -d -o "$RUNTIME_USER" -g "$RUNTIME_GROUP" -m 2775 "$ROOT" "$WORKTREES"
install -d -o "$RUNTIME_USER" -g "$RUNTIME_GROUP" -m 2775 \
"$WORKTREES/openclaw" "$WORKTREES/chatgpt" "$WORKTREES/controller"
if [[ ! -d "$BARE" ]]; then
sudo -u "$RUNTIME_USER" -g "$RUNTIME_GROUP" git clone --bare "$SOURCE" "$BARE"
fi
chown -R "$RUNTIME_USER:$RUNTIME_GROUP" "$BARE" "$WORKTREES"
find "$BARE" "$WORKTREES" -type d -exec chmod 2775 {} +
find "$BARE" "$WORKTREES" -type f -exec chmod ug+rw {} +
setfacl -R -m "u:${RUNTIME_USER}:rwX,g:${RUNTIME_GROUP}:rwX,m::rwX" "$BARE" "$WORKTREES"
find "$BARE" "$WORKTREES" -type d -exec \
setfacl -m "d:u:${RUNTIME_USER}:rwx,d:g:${RUNTIME_GROUP}:rwx,d:m::rwx" {} +
cat <<EOF
bootstrap_status: PASS
root: $ROOT
source_worktree: $SOURCE
bare_repo: $BARE
worktree_root: $WORKTREES
runtime_user: $RUNTIME_USER
runtime_group: $RUNTIME_GROUP
EOF
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail
EXPECTED_USER="${AIOS_RUNTIME_USER:-aiman}"
EXPECTED_GROUP="${AIOS_RUNTIME_GROUP:-gitops}"
EXPECTED_UMASK="${AIOS_RUNTIME_UMASK:-0002}"
TASK_DIR="${AIOS_TASK_DIR:-.agent/tasks}"
RESULT_DIR="${AIOS_RESULT_DIR:-.agent/results}"
fail() {
echo "preflight_status: FAIL"
echo "reason: $1"
exit 1
}
USER_NOW="$(id -un)"
GROUPS_NOW="$(id -Gn)"
UMASK_NOW="$(umask)"
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
GIT_DIR="$(git rev-parse --git-dir 2>/dev/null || true)"
BRANCH="$(git branch --show-current 2>/dev/null || true)"
[[ "$USER_NOW" == "$EXPECTED_USER" ]] || fail "runtime user is $USER_NOW, expected $EXPECTED_USER"
[[ " $GROUPS_NOW " == *" $EXPECTED_GROUP "* ]] || fail "runtime user is not in $EXPECTED_GROUP"
[[ "$UMASK_NOW" == "$EXPECTED_UMASK" || "$UMASK_NOW" == "002" ]] || fail "umask is $UMASK_NOW, expected $EXPECTED_UMASK"
[[ -n "$ROOT" ]] || fail "not inside a git worktree"
[[ "$ROOT" == /data/aios-gitops/worktrees/* ]] || fail "shared source worktree is prohibited: $ROOT"
[[ -w "$ROOT" ]] || fail "worktree root is not writable"
[[ -w "$GIT_DIR" ]] || fail "git dir is not writable: $GIT_DIR"
[[ ! -e "$GIT_DIR/index.lock" ]] || fail "git index lock exists"
install -d -m 2775 "$TASK_DIR" "$RESULT_DIR"
[[ -w "$TASK_DIR" ]] || fail "task directory is not writable: $TASK_DIR"
[[ -w "$RESULT_DIR" ]] || fail "result directory is not writable: $RESULT_DIR"
[[ -z "$(git status --porcelain)" ]] || fail "worktree is not clean at task start"
cat <<EOF
preflight_status: PASS
runtime_user: $USER_NOW
runtime_groups: $GROUPS_NOW
umask: $UMASK_NOW
worktree: $ROOT
git_dir: $GIT_DIR
branch: $BRANCH
workspace_clean: true
task_path_writable: true
result_path_writable: true
git_lock_present: false
EOF
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
set -euo pipefail
ACTION="${1:-}"
AGENT="${2:-}"
TASK_ID="${3:-}"
BASE_REF="${4:-origin/main}"
ROOT="${AIOS_GITOPS_ROOT:-/data/aios-gitops}"
BARE="${AIOS_BARE_REPO:-$ROOT/repo.git}"
WORKTREES="${AIOS_WORKTREE_ROOT:-$ROOT/worktrees}"
RUNTIME_USER="${AIOS_RUNTIME_USER:-aiman}"
RUNTIME_GROUP="${AIOS_RUNTIME_GROUP:-gitops}"
usage() {
echo "usage: $0 create|remove <openclaw|chatgpt|controller> <task-id> [base-ref]" >&2
exit 64
}
[[ "$ACTION" =~ ^(create|remove)$ ]] || usage
[[ "$AGENT" =~ ^(openclaw|chatgpt|controller)$ ]] || usage
[[ "$TASK_ID" =~ ^[A-Za-z0-9._-]+$ ]] || {
echo "ERROR: invalid task id" >&2
exit 65
}
[[ "$(id -un)" == "$RUNTIME_USER" ]] || {
echo "ERROR: must run as $RUNTIME_USER" >&2
exit 66
}
[[ "$(id -gn)" == "$RUNTIME_GROUP" || " $(id -Gn) " == *" $RUNTIME_GROUP "* ]] || {
echo "ERROR: user must be in $RUNTIME_GROUP" >&2
exit 67
}
[[ "$(umask)" == "0002" || "$(umask)" == "002" ]] || {
echo "ERROR: umask must be 0002" >&2
exit 68
}
[[ -d "$BARE" ]] || {
echo "ERROR: bare repository missing: $BARE" >&2
exit 69
}
PATHNAME="$WORKTREES/$AGENT/$TASK_ID"
BRANCH="task/$AGENT/$TASK_ID"
case "$ACTION" in
create)
[[ ! -e "$PATHNAME" ]] || {
echo "ERROR: worktree already exists: $PATHNAME" >&2
exit 70
}
git --git-dir="$BARE" fetch --all --prune
git --git-dir="$BARE" worktree add -b "$BRANCH" "$PATHNAME" "$BASE_REF"
install -d -m 2775 "$PATHNAME/.agent/tasks/$AGENT" "$PATHNAME/.agent/results/$AGENT"
setfacl -m "u:${RUNTIME_USER}:rwx,g:${RUNTIME_GROUP}:rwx,m::rwx" "$PATHNAME"
find "$PATHNAME" -type d -exec chmod g+s {} +
find "$PATHNAME" -type d -exec \
setfacl -m "d:u:${RUNTIME_USER}:rwx,d:g:${RUNTIME_GROUP}:rwx,d:m::rwx" {} +
printf 'worktree: %s\nbranch: %s\nbase: %s\n' "$PATHNAME" "$BRANCH" "$BASE_REF"
;;
remove)
[[ -d "$PATHNAME" ]] || {
echo "ERROR: worktree not found: $PATHNAME" >&2
exit 71
}
[[ -z "$(git -C "$PATHNAME" status --porcelain)" ]] || {
echo "ERROR: worktree is not clean; retain for investigation" >&2
exit 72
}
git --git-dir="$BARE" worktree remove "$PATHNAME"
printf 'removed: %s\n' "$PATHNAME"
;;
esac