Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2dcc3b372 | ||
|
|
602cc9ee28 | ||
|
|
6dbd5cbe07 | ||
|
|
a56caf46f6 | ||
|
|
05025480ad |
@@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# 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`
|
||||||
@@ -7,6 +7,7 @@ from typing import Any
|
|||||||
|
|
||||||
sys.path.insert(0, str(Path(os.getenv('AIOS_GITOPS_WORKSPACE', '/data/aios-gitops/workspace')) / 'runtime' / 'agent-os'))
|
sys.path.insert(0, str(Path(os.getenv('AIOS_GITOPS_WORKSPACE', '/data/aios-gitops/workspace')) / 'runtime' / 'agent-os'))
|
||||||
from orchestrator.action_monitor import ActionMonitor
|
from orchestrator.action_monitor import ActionMonitor
|
||||||
|
import threading
|
||||||
|
|
||||||
ROOT = Path(os.getenv('AIOS_GITOPS_ROOT', '/data/aios-gitops'))
|
ROOT = Path(os.getenv('AIOS_GITOPS_ROOT', '/data/aios-gitops'))
|
||||||
WS = Path(os.getenv('AIOS_GITOPS_WORKSPACE', str(ROOT / 'workspace')))
|
WS = Path(os.getenv('AIOS_GITOPS_WORKSPACE', str(ROOT / 'workspace')))
|
||||||
@@ -17,6 +18,7 @@ PORT = int(os.getenv('AIOS_GITOPS_PORT', '18890'))
|
|||||||
OPENAPI = WS / 'docs' / 'gitops' / 'openapi-chatgpt-actions.yaml'
|
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')))
|
BARE_REPO = Path(os.getenv('AIOS_GITOPS_BARE_REPO', str(ROOT / 'runtime' / 'gitea-bin' / 'data' / 'repositories' / 'aios' / 'aios.git')))
|
||||||
monitor = ActionMonitor()
|
monitor = ActionMonitor()
|
||||||
|
WRITE_LOCK = threading.RLock()
|
||||||
|
|
||||||
BASE_MODELS = [
|
BASE_MODELS = [
|
||||||
{'id':'base.deepseekv4-flash','provider':'one-api','model':'deepseekv4-flash','display_name':'DeepSeek V4 Flash','scope':'global'},
|
{'id':'base.deepseekv4-flash','provider':'one-api','model':'deepseekv4-flash','display_name':'DeepSeek V4 Flash','scope':'global'},
|
||||||
@@ -117,28 +119,211 @@ def write_file(path, content):
|
|||||||
if content_has_secret(content): raise ValueError('secret_rejected: use secret_ref; plaintext API keys must not enter Git')
|
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
|
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 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:
|
def sync_branch(branch: str) -> dict:
|
||||||
if not branch or branch in ('main','master') or branch.startswith('main/') or branch.startswith('master/'):
|
if (
|
||||||
return {'status':'skipped','reason':'protected_branch','branch':branch}
|
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 BARE_REPO.exists():
|
if BARE_REPO.exists():
|
||||||
ref=f'refs/heads/{branch}:refs/heads/{branch}'
|
target = BARE_REPO.resolve()
|
||||||
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}')
|
if target == common_git_dir:
|
||||||
return {'status':'synced','target':'bare','branch':branch,'stdout':p.stdout.strip(),'stderr':p.stderr.strip()}
|
return {
|
||||||
p=subprocess.run(['git','push','origin',f'HEAD:refs/heads/{branch}'], cwd=str(WS), text=True, capture_output=True)
|
'status': 'synced',
|
||||||
if p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or f'git push failed for {branch}')
|
'target': 'common_git_dir',
|
||||||
return {'status':'synced','target':'origin','branch':branch,'stdout':p.stdout.strip(),'stderr':p.stderr.strip()}
|
'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,
|
||||||
|
}
|
||||||
|
|
||||||
def sync_tag(tag: str) -> dict:
|
def sync_tag(tag: str) -> dict:
|
||||||
if not tag: return {'status':'skipped','reason':'empty_tag'}
|
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 BARE_REPO.exists():
|
if BARE_REPO.exists():
|
||||||
ref=f'refs/tags/{tag}:refs/tags/{tag}'
|
target = BARE_REPO.resolve()
|
||||||
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}')
|
if target == common_git_dir:
|
||||||
return {'status':'synced','target':'bare','tag':tag,'stdout':p.stdout.strip(),'stderr':p.stderr.strip()}
|
return {
|
||||||
p=subprocess.run(['git','push','origin',tag], cwd=str(WS), text=True, capture_output=True)
|
'status': 'synced',
|
||||||
if p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or f'git tag push failed for {tag}')
|
'target': 'common_git_dir',
|
||||||
return {'status':'synced','target':'origin','tag':tag,'stdout':p.stdout.strip(),'stderr':p.stderr.strip()}
|
'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,
|
||||||
|
}
|
||||||
|
|
||||||
def commit(paths, msg):
|
def commit(paths, msg):
|
||||||
ensure_git(); paths = [relpath(p) for p in paths] if paths else ['.']; sh(['add'] + paths, True)
|
ensure_git(); paths = [relpath(p) for p in paths] if paths else ['.']; sh(['add'] + paths, True)
|
||||||
@@ -210,25 +395,123 @@ class H(BaseHTTPRequestHandler):
|
|||||||
monitor.record_failure('do_GET', str(e), {'path':path})
|
monitor.record_failure('do_GET', str(e), {'path':path})
|
||||||
return self.sendj({'error':str(e)},500)
|
return self.sendj({'error':str(e)},500)
|
||||||
def do_PUT(self):
|
def do_PUT(self):
|
||||||
path=urllib.parse.urlparse(self.path).path.rstrip('/'); data=self.body()
|
path = urllib.parse.urlparse(
|
||||||
if path == '/api/model-priority': return self.model_priority(data)
|
self.path
|
||||||
return self.sendj({'error':'not_found','path':path},404)
|
).path.rstrip('/')
|
||||||
def do_POST(self):
|
|
||||||
path=urllib.parse.urlparse(self.path).path.rstrip('/'); data=self.body()
|
data = self.body()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if path in ('/api/branch','/api/create-branch'): return self.branch(data)
|
with WRITE_LOCK:
|
||||||
if path in ('/api/file','/api/commit-file'): return self.file(data)
|
if path == '/api/model-priority':
|
||||||
if path == '/api/commit': return self.commit(data)
|
return self.model_priority(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)
|
return self.sendj(
|
||||||
if path == '/api/rollback': return self.rollback(data)
|
{
|
||||||
if path == '/api/model': return self.model(data)
|
'error': 'not_found',
|
||||||
if path == '/api/model-priority': return self.model_priority(data)
|
'path': path,
|
||||||
if path == '/api/nl': return self.nl(data)
|
},
|
||||||
return self.sendj({'error':'not_found','path':path},404)
|
404,
|
||||||
except Exception as e:
|
)
|
||||||
monitor.record_failure('do_POST', str(e), {'path':path})
|
except Exception as exc:
|
||||||
return self.sendj({'error':str(e)},500)
|
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',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def do_POST(self):
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
)
|
||||||
def branch(self,data):
|
def branch(self,data):
|
||||||
a=self.need('branch:create');
|
a=self.need('branch:create');
|
||||||
if not a: return
|
if not a: return
|
||||||
@@ -393,6 +676,25 @@ 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}}
|
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():
|
def main():
|
||||||
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
|
state = controller_status()
|
||||||
ThreadingHTTPServer((HOST,PORT), H).serve_forever()
|
|
||||||
|
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()
|
||||||
if __name__ == '__main__': main()
|
if __name__ == '__main__': main()
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
#!/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)"
|
||||||
+18
-11
@@ -1,13 +1,20 @@
|
|||||||
{
|
{
|
||||||
"current_baseline": "V1R4C6",
|
"current_baseline": "V2R1C1",
|
||||||
"previous_baseline": "V1R4C5",
|
"previous_baseline": "V1R4C6",
|
||||||
"promoted_at": "2026-07-11T21:00:00+08:00",
|
"promoted_at": "2026-07-21T14:17:05Z",
|
||||||
"git": "e204a90398bf646d46245b3088226749fa056edd",
|
"git": "4e0e6dcb90e6732aa9838fe9d0af0b34746829b0",
|
||||||
"promoted_by": "openclaw/admin",
|
"promoted_by": "chatgpt/owner-approved-governance",
|
||||||
"promotion_reason": "V1R4C6 admin search agent integration, search gateway, agent platform, VERSION bump, merge tests deploy pipeline — not yet deployed to production",
|
"promotion_reason": "Register the already validated and deployed V2R1C1 production release in the central GitOps baseline Registry.",
|
||||||
"constraints": {
|
"version": "V2R1C1",
|
||||||
"memory_search_enabled": false,
|
"release_oid": "4e0e6dcb90e6732aa9838fe9d0af0b34746829b0",
|
||||||
"force_push_allowed": false,
|
"tag_oid": "4e0e6dcb90e6732aa9838fe9d0af0b34746829b0",
|
||||||
"gitea_db_mutation_requires_owner_approval": true
|
"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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# 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,55 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user