Compare commits

..
Author SHA1 Message Date
Codex 633c8308f5 chore(worktree): ignore registered child worktrees 2026-07-21 15:13:48 +08:00
OpenClaw Git Writer 6cd0f5aa0a acceptance: openclaw git writer TASK-CODEX-GITOPS-SECRET-ACL-AND-IDENTITY-HARDENING-20260715T004818UTC8-FINAL 2026-07-15 01:06:10 +08:00
OpenClaw Git Writer e6c4bef736 acceptance: openclaw git writer TASK-CODEX-GITOPS-SECRET-ACL-AND-IDENTITY-HARDENING-20260715T004818UTC8 2026-07-15 01:03:21 +08:00
AIOS OpenClaw Migration 6483e34892 test(OpenClaw): isolated worktree migration acceptance 20260715T001652UTC8 2026-07-15 00:32:53 +08:00
openclaw b05eb1babe task(codex): execute isolated worktree migration 20260715T001652UTC8 2026-07-15 00:17:28 +08:00
openclaw a8581699f2 task(openclaw): migrate GitOps to isolated worktrees 20260714T233742UTC8 2026-07-14 23:32:52 +08:00
openclaw cc9cada5df feat(gitops): add unified systemd runtime identity drop-in 2026-07-14 23:32:18 +08:00
openclaw dbed29c8c7 feat(gitops): add task environment preflight 2026-07-14 23:32:12 +08:00
openclaw 24f7e4ecc3 feat(gitops): add per-task worktree lifecycle script 2026-07-14 23:31:59 +08:00
openclaw b588bf8fb2 feat(gitops): add isolated worktree bootstrap script 2026-07-14 23:31:43 +08:00
openclaw f300033138 fix(gitops): correct default ACL runtime user 2026-07-14 23:31:32 +08:00
openclaw 7308c9c0c1 feat(gitops): add isolated worktree governance policy 2026-07-14 23:31:21 +08:00
openclaw 48290bd620 chore(gitops): start isolated worktree and unified identity governance 2026-07-14 23:30:07 +08:00
20 changed files with 587 additions and 658 deletions
@@ -1,88 +0,0 @@
{
"record_type": "central-baseline-governance-closure",
"version": "V2R1C1",
"semver": "2.1.0-rc.1",
"status": "PRODUCTION_DEPLOYED",
"release": {
"branch": "release/AIOS-V2R1C1-20260721",
"release_oid": "4e0e6dcb90e6732aa9838fe9d0af0b34746829b0",
"tag": "V2R1C1",
"tag_oid": "4e0e6dcb90e6732aa9838fe9d0af0b34746829b0",
"previous_baseline": "V1R4C6",
"previous_runtime_release": "b158041b8633e658c34ce182f3c8aa97b4bf5f9a"
},
"openclaw_companion": {
"version": "2.1.0-rc.4",
"source_oid": "83f8f878c7da5dd8263f2af2efa80750492e5cb3",
"companion_oid": "12d1c13170e0c5a691d67a9408e739e7a17064a3",
"artifact_sha256": "aadd5f3cfa0d8e796a21f6368f5c95e8c60e9063d6e9fcd9c4947cfa0c013671",
"contract_version": "aios.openclaw.command.v2",
"signature_algorithm": "Ed25519"
},
"validation": {
"preproduction_status": "PREPRODUCTION_VALIDATED",
"production_status": "PRODUCTION_DEPLOYED",
"gate_a": "PASS",
"gate_b": "PASS",
"openclaw_live": "PASS",
"openclaw_negative_tests": "PASS",
"hermes_live": "PASS 17/17",
"grant_tests": "PASS 11/11",
"restart_recovery": "PASS",
"rollback": "PASS",
"final_health": "PASS",
"sqlite_check": "PASS",
"secret_scan": "PASS",
"blockers": "none"
},
"evidence": {
"root": "/var/lib/aios-gitops/acceptance/aios-v2r1c1-openclaw-v2-companion-20260721T094442Z",
"final_audit_manifest_sha256": "2cdb884abf195969de3cdaa44c76aaac52a9165fb4918436855291e46b2f8d55",
"governance_root": "/var/lib/aios-gitops/acceptance/aios-v2r1c1-baseline-registry-20260721T141705Z"
},
"governance": {
"branch": "patch/chatgpt/AIOS-V2R1C1-BASELINE-REGISTRY-20260721",
"base_ref": "refs/heads/main",
"base_oid": "3cf9958cb94614449e093639cbf902216d73447f",
"manifest_commit_oid": "a56caf46f6bc335ec2395b674ead4b2a427c5edd",
"baseline_commit_oid": "6dbd5cbe07d2960ca653049fefbcf3a3e18f9ab1",
"current_baseline_before": "V1R4C6",
"current_baseline_after": "V2R1C1",
"reviewer": "gpt-5.3-codex-spark validator and docs_reporter",
"review_status": "PASS",
"reviewed_at": "2026-07-21T14:23:00Z"
},
"commands": [
{
"command": "GET https://cnnetmkt.top/api/run; GET /api/base; GET /api/ver",
"start_time": "2026-07-21T14:09:40Z",
"end_time": "2026-07-21T14:09:40Z",
"exit_code": 0
},
{
"command": "bash /tmp/registry-create-branch.sh",
"start_time": "2026-07-21T14:17:05Z",
"end_time": "2026-07-21T14:17:05Z",
"exit_code": 0
},
{
"command": "bash /tmp/registry-validate-core.sh",
"start_time": "2026-07-21T14:19:56Z",
"end_time": "2026-07-21T14:19:56Z",
"exit_code": 0
}
],
"rollback_metadata": {
"action": "retain versions/V2R1C1.json and restore only baseline/current.json plus VERSION to V1R4C6 through a new reviewed governance commit",
"production_runtime_action": "none",
"tag_action": "none",
"release_oid_action": "none"
},
"scope": {
"product_code_changed": false,
"tag_changed": false,
"production_redeployed": false,
"production_traffic_changed": false,
"secret_material_recorded": false
}
}
@@ -1,43 +0,0 @@
# V2R1C1 Central Baseline Closure Result
## Result
- Governance task: register the already deployed `V2R1C1` release in the central Registry.
- Baseline: `V1R4C6` -> `V2R1C1`.
- Release OID and tag OID: `4e0e6dcb90e6732aa9838fe9d0af0b34746829b0`.
- Companion OID: `12d1c13170e0c5a691d67a9408e739e7a17064a3`.
- Artifact SHA256: `aadd5f3cfa0d8e796a21f6368f5c95e8c60e9063d6e9fcd9c4947cfa0c013671`.
- Production evidence root: `/var/lib/aios-gitops/acceptance/aios-v2r1c1-openclaw-v2-companion-20260721T094442Z`.
- Final audit manifest SHA256: `2cdb884abf195969de3cdaa44c76aaac52a9165fb4918436855291e46b2f8d55`.
- Production status: `PRODUCTION_DEPLOYED`.
## Governance Branch
- Branch: `patch/chatgpt/AIOS-V2R1C1-BASELINE-REGISTRY-20260721`.
- Base ref: `refs/heads/main`.
- Base OID: `3cf9958cb94614449e093639cbf902216d73447f`.
- Manifest commit: `a56caf46f6bc335ec2395b674ead4b2a427c5edd`.
- Baseline commit: `6dbd5cbe07d2960ca653049fefbcf3a3e18f9ab1`.
- Review: `PASS` at `2026-07-21T14:23:00Z` by `gpt-5.3-codex-spark` validator and docs reporter.
## Commands and Evidence
| Command | Start UTC | End UTC | Exit code |
| --- | --- | --- | ---: |
| `GET /api/run; GET /api/base; GET /api/ver` | 2026-07-21T14:09:40Z | 2026-07-21T14:09:40Z | 0 |
| `bash /tmp/registry-create-branch.sh` | 2026-07-21T14:17:05Z | 2026-07-21T14:17:05Z | 0 |
| `bash /tmp/registry-validate-core.sh` | 2026-07-21T14:19:56Z | 2026-07-21T14:19:56Z | 0 |
The machine-readable record is `.agent/results/releases/V2R1C1-BASELINE-CLOSURE.json`. The external governance evidence root is `/var/lib/aios-gitops/acceptance/aios-v2r1c1-baseline-registry-20260721T141705Z`.
## Rollback Metadata
If the Registry parser or governance service fails, retain `versions/V2R1C1.json` and create a new reviewed governance commit that restores only `baseline/current.json` and `VERSION` to `V1R4C6`. Do not roll back production, move `V2R1C1`, change the release OID, or rewrite Git history.
## Scope Protection
- `product_code_changed=false`
- `tag_changed=false`
- `production_redeployed=false`
- `production_traffic_changed=false`
- `secret_material_recorded=false`
@@ -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和回滚能力,生产业务未变化。
```
+5
View File
@@ -11,3 +11,8 @@ runtime/tmp/
runtime/cache/
logs/
config/test_write_perms
# BEGIN AIOS REGISTERED CHILD WORKTREES (V2R1C1 2026-07-21)
/TASK-WORKTREE-CONCURRENCY-OPENCLAW-20260715T060254UTC8/
/TASK-WORKTREE-CONCURRENCY-OPENCLAW-20260715T061051UTC8/
# END AIOS REGISTERED CHILD WORKTREES (V2R1C1 2026-07-21)
+1 -1
View File
@@ -1 +1 @@
V2R1C1
V1R4C6
+36 -338
View File
@@ -7,7 +7,6 @@ from typing import Any
sys.path.insert(0, str(Path(os.getenv('AIOS_GITOPS_WORKSPACE', '/data/aios-gitops/workspace')) / 'runtime' / 'agent-os'))
from orchestrator.action_monitor import ActionMonitor
import threading
ROOT = Path(os.getenv('AIOS_GITOPS_ROOT', '/data/aios-gitops'))
WS = Path(os.getenv('AIOS_GITOPS_WORKSPACE', str(ROOT / 'workspace')))
@@ -18,7 +17,6 @@ PORT = int(os.getenv('AIOS_GITOPS_PORT', '18890'))
OPENAPI = WS / 'docs' / 'gitops' / 'openapi-chatgpt-actions.yaml'
BARE_REPO = Path(os.getenv('AIOS_GITOPS_BARE_REPO', str(ROOT / 'runtime' / 'gitea-bin' / 'data' / 'repositories' / 'aios' / 'aios.git')))
monitor = ActionMonitor()
WRITE_LOCK = threading.RLock()
BASE_MODELS = [
{'id':'base.deepseekv4-flash','provider':'one-api','model':'deepseekv4-flash','display_name':'DeepSeek V4 Flash','scope':'global'},
@@ -119,211 +117,28 @@ def write_file(path, content):
if content_has_secret(content): raise ValueError('secret_rejected: use secret_ref; plaintext API keys must not enter Git')
r=relpath(path); t=WS/r; t.parent.mkdir(parents=True, exist_ok=True); t.write_text(content, encoding='utf-8'); return r
def write_json(path, data): return write_file(path, json.dumps(data, ensure_ascii=False, indent=2) + '\n')
def controller_status() -> dict:
branch = sh(
['branch', '--show-current']
)['stdout'] or None
head = sh(
['rev-parse', 'HEAD'],
True,
)['stdout']
dirty = bool(
sh(
['status', '--porcelain']
)['stdout']
)
return {
'workspace_mode': (
'branch'
if branch
else 'detached'
),
'workspace_branch': branch,
'workspace_ref': head,
'workspace_dirty': dirty,
}
def detach_controller() -> dict:
state = controller_status()
if state['workspace_branch']:
sh(
[
'switch',
'--detach',
'HEAD',
],
True,
)
return controller_status()
def sync_branch(branch: str) -> dict:
if (
not branch
or branch in ('main', 'master')
or branch.startswith('main/')
or branch.startswith('master/')
):
return {
'status': 'skipped',
'reason': 'protected_branch',
'branch': branch,
}
state = detach_controller()
common_git_dir = Path(
sh(
[
'rev-parse',
'--path-format=absolute',
'--git-common-dir',
],
True,
)['stdout']
).resolve()
if not branch or branch in ('main','master') or branch.startswith('main/') or branch.startswith('master/'):
return {'status':'skipped','reason':'protected_branch','branch':branch}
if BARE_REPO.exists():
target = BARE_REPO.resolve()
if target == common_git_dir:
return {
'status': 'synced',
'target': 'common_git_dir',
'branch': branch,
**state,
}
proc = subprocess.run(
[
'git',
'push',
'--porcelain',
str(target),
f'HEAD:refs/heads/{branch}',
],
cwd=str(WS),
text=True,
capture_output=True,
)
else:
proc = subprocess.run(
[
'git',
'push',
'--porcelain',
'origin',
f'HEAD:refs/heads/{branch}',
],
cwd=str(WS),
text=True,
capture_output=True,
)
if proc.returncode != 0:
raise RuntimeError(
proc.stderr
or proc.stdout
or (
'git push failed for branch '
+ branch
)
)
return {
'status': 'synced',
'target': (
str(BARE_REPO.resolve())
if BARE_REPO.exists()
else 'origin'
),
'branch': branch,
'stdout': proc.stdout.strip(),
'stderr': proc.stderr.strip(),
**state,
}
ref=f'refs/heads/{branch}:refs/heads/{branch}'
p=subprocess.run(['git','--git-dir',str(BARE_REPO),'fetch',str(WS),ref], text=True, capture_output=True)
if p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or f'git sync failed for {branch}')
return {'status':'synced','target':'bare','branch':branch,'stdout':p.stdout.strip(),'stderr':p.stderr.strip()}
p=subprocess.run(['git','push','origin',f'HEAD:refs/heads/{branch}'], cwd=str(WS), text=True, capture_output=True)
if p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or f'git push failed for {branch}')
return {'status':'synced','target':'origin','branch':branch,'stdout':p.stdout.strip(),'stderr':p.stderr.strip()}
def sync_tag(tag: str) -> dict:
state = detach_controller()
common_git_dir = Path(
sh(
[
'rev-parse',
'--path-format=absolute',
'--git-common-dir',
],
True,
)['stdout']
).resolve()
tag_ref = f'refs/tags/{tag}'
if not tag: return {'status':'skipped','reason':'empty_tag'}
if BARE_REPO.exists():
target = BARE_REPO.resolve()
if target == common_git_dir:
return {
'status': 'synced',
'target': 'common_git_dir',
'tag': tag,
**state,
}
proc = subprocess.run(
[
'git',
'push',
'--porcelain',
str(target),
f'{tag_ref}:{tag_ref}',
],
cwd=str(WS),
text=True,
capture_output=True,
)
else:
proc = subprocess.run(
[
'git',
'push',
'--porcelain',
'origin',
f'{tag_ref}:{tag_ref}',
],
cwd=str(WS),
text=True,
capture_output=True,
)
if proc.returncode != 0:
raise RuntimeError(
proc.stderr
or proc.stdout
or (
'git push failed for tag '
+ tag
)
)
return {
'status': 'synced',
'target': (
str(BARE_REPO.resolve())
if BARE_REPO.exists()
else 'origin'
),
'tag': tag,
'stdout': proc.stdout.strip(),
'stderr': proc.stderr.strip(),
**state,
}
ref=f'refs/tags/{tag}:refs/tags/{tag}'
p=subprocess.run(['git','--git-dir',str(BARE_REPO),'fetch',str(WS),ref], text=True, capture_output=True)
if p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or f'git tag sync failed for {tag}')
return {'status':'synced','target':'bare','tag':tag,'stdout':p.stdout.strip(),'stderr':p.stderr.strip()}
p=subprocess.run(['git','push','origin',tag], cwd=str(WS), text=True, capture_output=True)
if p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or f'git tag push failed for {tag}')
return {'status':'synced','target':'origin','tag':tag,'stdout':p.stdout.strip(),'stderr':p.stderr.strip()}
def commit(paths, msg):
ensure_git(); paths = [relpath(p) for p in paths] if paths else ['.']; sh(['add'] + paths, True)
@@ -395,123 +210,25 @@ class H(BaseHTTPRequestHandler):
monitor.record_failure('do_GET', str(e), {'path':path})
return self.sendj({'error':str(e)},500)
def do_PUT(self):
path = urllib.parse.urlparse(
self.path
).path.rstrip('/')
data = self.body()
try:
with WRITE_LOCK:
if path == '/api/model-priority':
return self.model_priority(data)
return self.sendj(
{
'error': 'not_found',
'path': path,
},
404,
)
except Exception as exc:
monitor.record_failure(
'do_PUT',
str(exc),
{'path': path},
)
return self.sendj(
{'error': str(exc)},
500,
)
finally:
try:
detach_controller()
except Exception as detach_error:
monitor.record_failure(
'detach_controller',
str(detach_error),
{
'path': path,
'method': 'PUT',
},
)
path=urllib.parse.urlparse(self.path).path.rstrip('/'); data=self.body()
if path == '/api/model-priority': return self.model_priority(data)
return self.sendj({'error':'not_found','path':path},404)
def do_POST(self):
path = urllib.parse.urlparse(
self.path
).path.rstrip('/')
data = self.body()
path=urllib.parse.urlparse(self.path).path.rstrip('/'); data=self.body()
try:
with WRITE_LOCK:
if path in (
'/api/branch',
'/api/create-branch',
):
return self.branch(data)
if path in (
'/api/file',
'/api/commit-file',
):
return self.file(data)
if path == '/api/commit':
return self.commit(data)
if path in (
'/api/patch',
'/api/create-patch',
):
return self.patch(data)
if path in (
'/api/tag',
'/api/create-tag',
):
return self.tag(data)
if path == '/api/rollback':
return self.rollback(data)
if path == '/api/model':
return self.model(data)
if path == '/api/model-priority':
return self.model_priority(data)
if path == '/api/nl':
return self.nl(data)
return self.sendj(
{
'error': 'not_found',
'path': path,
},
404,
)
except Exception as exc:
monitor.record_failure(
'do_POST',
str(exc),
{'path': path},
)
return self.sendj(
{'error': str(exc)},
500,
)
finally:
try:
detach_controller()
except Exception as detach_error:
monitor.record_failure(
'detach_controller',
str(detach_error),
{
'path': path,
'method': 'POST',
},
)
if path in ('/api/branch','/api/create-branch'): return self.branch(data)
if path in ('/api/file','/api/commit-file'): return self.file(data)
if path == '/api/commit': return self.commit(data)
if path in ('/api/patch','/api/create-patch'): return self.patch(data)
if path in ('/api/tag','/api/create-tag'): return self.tag(data)
if path == '/api/rollback': return self.rollback(data)
if path == '/api/model': return self.model(data)
if path == '/api/model-priority': return self.model_priority(data)
if path == '/api/nl': return self.nl(data)
return self.sendj({'error':'not_found','path':path},404)
except Exception as e:
monitor.record_failure('do_POST', str(e), {'path':path})
return self.sendj({'error':str(e)},500)
def branch(self,data):
a=self.need('branch:create');
if not a: return
@@ -676,25 +393,6 @@ def api_spec():
return {'version':'V1R1C0-chatgpt-actions','openapi':'/api/openapi.yaml','read':['GET /api/base','GET /api/ver','GET /api/diff','GET /api/patch','GET /api/model','GET /api/run','GET /admin/ai-evolution'],'write':['POST /api/branch','POST /api/file','POST /api/commit','POST /api/patch','POST /api/tag','POST /api/rollback','POST /api/model','PUT /api/model-priority','POST /api/nl'],'policy':{'bearer_required_for_write':True,'direct_main_push':False,'delete_tag':False,'owner_approval_required_for_v_level':True,'api_keys_in_git':False,'git_secret_field':'secret_ref','online_git_sync':True}}
def main():
state = controller_status()
if state['workspace_dirty']:
raise RuntimeError(
'controller workspace is dirty; '
'refusing to start GitOps writes'
)
state = detach_controller()
print(
'AIOS ChatGPT GitOps adapter listening '
f'on {HOST}:{PORT}; '
f'workspace_mode={state["workspace_mode"]}; '
f'workspace_ref={state["workspace_ref"]}'
)
ThreadingHTTPServer(
(HOST, PORT),
H,
).serve_forever()
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
ThreadingHTTPServer((HOST,PORT), H).serve_forever()
if __name__ == '__main__': main()
@@ -1,58 +0,0 @@
#!/usr/bin/env bash
set -Eeuo pipefail
WT="${AIOS_GITOPS_WORKSPACE:-/data/aios-gitops/worktrees/controller}"
BRANCH="${AIOS_GITOPS_CONTROLLER_BRANCH:-main}"
git -C "$WT" rev-parse \
--is-inside-work-tree >/dev/null
DIRTY="$(
git -C "$WT" status --porcelain=v1
)"
if [[ -n "$DIRTY" ]]; then
echo "controller workspace is dirty" >&2
echo "$DIRTY" >&2
exit 73
fi
git -C "$WT" fetch --prune origin \
"+refs/heads/${BRANCH}:refs/remotes/origin/${BRANCH}"
git -C "$WT" switch --detach \
"refs/remotes/origin/${BRANCH}"
COMMON="$(
git -C "$WT" \
rev-parse \
--path-format=absolute \
--git-common-dir
)"
git --git-dir="$COMMON" \
worktree prune --expire now
CURRENT_BRANCH="$(
git -C "$WT" branch --show-current
)"
if [[ -n "$CURRENT_BRANCH" ]]; then
echo "controller remains attached: $CURRENT_BRANCH" >&2
exit 74
fi
DIRTY_AFTER="$(
git -C "$WT" status --porcelain=v1
)"
if [[ -n "$DIRTY_AFTER" ]]; then
echo "controller became dirty" >&2
echo "$DIRTY_AFTER" >&2
exit 75
fi
echo "workspace_mode=detached"
echo "workspace_branch=null"
echo "workspace_dirty=false"
echo "workspace_ref=$(git -C "$WT" rev-parse --short HEAD)"
+12 -19
View File
@@ -1,20 +1,13 @@
{
"current_baseline": "V2R1C1",
"previous_baseline": "V1R4C6",
"promoted_at": "2026-07-21T14:17:05Z",
"git": "4e0e6dcb90e6732aa9838fe9d0af0b34746829b0",
"promoted_by": "chatgpt/owner-approved-governance",
"promotion_reason": "Register the already validated and deployed V2R1C1 production release in the central GitOps baseline Registry.",
"version": "V2R1C1",
"release_oid": "4e0e6dcb90e6732aa9838fe9d0af0b34746829b0",
"tag_oid": "4e0e6dcb90e6732aa9838fe9d0af0b34746829b0",
"status": "PRODUCTION_DEPLOYED",
"constraints": {
"product_code_changed": false,
"tag_changed": false,
"production_redeployed": false,
"production_traffic_changed": false,
"force_push_allowed": false,
"gitea_db_mutation_requires_owner_approval": true
}
}
"current_baseline": "V1R4C6",
"previous_baseline": "V1R4C5",
"promoted_at": "2026-07-11T21:00:00+08:00",
"git": "e204a90398bf646d46245b3088226749fa056edd",
"promoted_by": "openclaw/admin",
"promotion_reason": "V1R4C6 admin search agent integration, search gateway, agent platform, VERSION bump, merge tests deploy pipeline — not yet deployed to production",
"constraints": {
"memory_search_enabled": false,
"force_push_allowed": false,
"gitea_db_mutation_requires_owner_approval": true
}
}
+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
-56
View File
@@ -1,56 +0,0 @@
# V2R1C1 中央 Baseline 治理收口
## 收口范围
本次变更只登记已经完成生产验证并上线的 `V2R1C1`。它新增中央版本 Manifest、把中央 Baseline Pointer 从 `V1R4C6` 切换为 `V2R1C1`,并建立 Release、Companion、Artifact 与最终生产 Evidence 的追溯关系。
本次不修改产品实现,不移动或重建 `V2R1C1` Tag,不改变 Release OID,不重新部署生产,不切换生产流量,也不重写历史证据。
## 权威标识
- Release:`release/AIOS-V2R1C1-20260721`
- Release OID:`4e0e6dcb90e6732aa9838fe9d0af0b34746829b0`
- Tag / Tag OID:`V2R1C1` / `4e0e6dcb90e6732aa9838fe9d0af0b34746829b0`
- SemVer:`2.1.0-rc.1`
- OpenClaw Companion:`2.1.0-rc.4`
- Companion Source OID:`83f8f878c7da5dd8263f2af2efa80750492e5cb3`
- Companion OID:`12d1c13170e0c5a691d67a9408e739e7a17064a3`
- Artifact SHA256:`aadd5f3cfa0d8e796a21f6368f5c95e8c60e9063d6e9fcd9c4947cfa0c013671`
- Contract:`aios.openclaw.command.v2` / `Ed25519`
- Evidence Root:`/var/lib/aios-gitops/acceptance/aios-v2r1c1-openclaw-v2-companion-20260721T094442Z`
- Final Audit Manifest SHA256:`2cdb884abf195969de3cdaa44c76aaac52a9165fb4918436855291e46b2f8d55`
- Production Status:`PRODUCTION_DEPLOYED`
## 治理路径
- Governance Branch:`patch/chatgpt/AIOS-V2R1C1-BASELINE-REGISTRY-20260721`
- Base Ref:`refs/heads/main`
- Base OID:`3cf9958cb94614449e093639cbf902216d73447f`
- Manifest Commit:`a56caf46f6bc335ec2395b674ead4b2a427c5edd`
- Baseline Commit:`6dbd5cbe07d2960ca653049fefbcf3a3e18f9ab1`
- Reviewer:`gpt-5.3-codex-spark validator and docs_reporter`
- Review:`PASS`(`2026-07-21T14:23:00Z`)
## 命令与退出码
| 阶段 | 命令 | 开始 UTC | 结束 UTC | Exit Code |
| --- | --- | --- | --- | ---: |
| 中央只读核对 | `GET /api/run; GET /api/base; GET /api/ver` | 2026-07-21T14:09:40Z | 2026-07-21T14:09:40Z | 0 |
| 创建治理分支 | `bash /tmp/registry-create-branch.sh` | 2026-07-21T14:17:05Z | 2026-07-21T14:17:05Z | 0 |
| 核心元数据验证 | `bash /tmp/registry-validate-core.sh` | 2026-07-21T14:19:56Z | 2026-07-21T14:19:56Z | 0 |
完整机读记录位于 `.agent/results/releases/V2R1C1-BASELINE-CLOSURE.json`,治理运行证据位于 `/var/lib/aios-gitops/acceptance/aios-v2r1c1-baseline-registry-20260721T141705Z`。
## 元数据回滚
若中央 Registry 解析或治理服务异常,保留 `versions/V2R1C1.json`,通过新的受审治理提交把 `baseline/current.json` 和 `VERSION` 临时恢复到 `V1R4C6`。该操作不得回滚生产系统、移动 Tag、修改 Release OID 或重写历史。
## 范围证明
```text
product_code_changed=false
tag_changed=false
production_redeployed=false
production_traffic_changed=false
secret_material_recorded=false
```
@@ -0,0 +1 @@
TASK-CODEX-GITOPS-SECRET-ACL-AND-IDENTITY-HARDENING-20260715T004818UTC8-FINAL
@@ -0,0 +1 @@
TASK-CODEX-GITOPS-SECRET-ACL-AND-IDENTITY-HARDENING-20260715T004818UTC8
@@ -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,5 @@
# OpenClaw isolated-worktree acceptance
branch: `agent/openclaw/gitops-isolated-worktree-migration-20260715T001652UTC8`
base: `b05eb1babe9c657e8776c09eec71302166902cc3`
@@ -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
-55
View File
@@ -1,55 +0,0 @@
{
"version": "V2R1C1",
"codename": "openclaw-command-v2-production-baseline",
"status": "PRODUCTION_DEPLOYED",
"tag": "V2R1C1",
"tag_ref": "refs/tags/V2R1C1",
"tag_object": "4e0e6dcb90e6732aa9838fe9d0af0b34746829b0",
"previous_version": "V1R4C6",
"rollback_version": "V1R4C6",
"baseline": "V1R4C6",
"release_reason": "Register the already deployed AIOS V2R1C1 release, immutable tag, OpenClaw Command v2 companion, and final production evidence in the central GitOps Registry.",
"purpose": "Close the central version-governance gap without changing product implementation, the V2R1C1 tag, production release, deployment, or traffic.",
"changes": [
"governance: add the canonical V2R1C1 version manifest",
"governance: advance the central baseline pointer from V1R4C6 to V2R1C1",
"governance: link release, companion, artifact, validation, and final audit evidence"
],
"artifacts": [
"docs/releases/V2R1C1-BASELINE-CLOSURE.md",
".agent/results/releases/V2R1C1-BASELINE-CLOSURE.json",
".agent/results/releases/V2R1C1-BASELINE-CLOSURE.md",
"/var/lib/aios-gitops/acceptance/aios-v2r1c1-openclaw-v2-companion-20260721T094442Z"
],
"validated_actions": [
"release and tag OID equality",
"local and Gitea ref synchronization",
"OpenClaw Command v2 positive and negative validation",
"Gate A and Gate B",
"Hermes lifecycle 17/17",
"grant isolation 11/11",
"restart recovery",
"rollback and forward recovery",
"SQLite quick_check",
"secret scan with zero violations",
"final production healthcheck"
],
"secret_plaintext_written": false,
"owner_approval_required": true,
"owner_notification_channels": [
"codex_owner_instruction",
"gitops_audit"
],
"constraints_original": {
"product_code_changed": false,
"tag_changed": false,
"production_redeployed": false,
"production_traffic_changed": false,
"force_push_allowed": false,
"incubator_in_release_graph": false
},
"promoted_at": "2026-07-21T14:17:05Z",
"promoted_by": "chatgpt/owner-approved-governance",
"created_at": "2026-07-21T14:17:05Z",
"author": "chatgpt"
}