Compare commits

..
9 changed files with 168 additions and 101 deletions
+28 -19
View File
@@ -110,8 +110,20 @@ def ensure_git():
def checkout(branch, base='main', create=False):
ensure_git(); b=branch_name(branch)
if create or sh(['rev-parse','--verify',b])['code'] != 0:
sh(['checkout',base], True); sh(['checkout','-B',b], True)
else: sh(['checkout',b], True)
remote_tip = None
if BARE_REPO.exists():
tip = subprocess.run(
['git', '--git-dir', str(BARE_REPO), 'rev-parse', '--verify', f'refs/heads/{b}'],
text=True,
capture_output=True,
)
if tip.returncode == 0:
remote_tip = tip.stdout.strip()
if remote_tip:
sh(['checkout', '-B', b, remote_tip], True)
else:
sh(['checkout', base], True); sh(['checkout', '-B', b], True)
else: sh(['checkout', b], True)
return b
def write_file(path, content):
if content_has_secret(content): raise ValueError('secret_rejected: use secret_ref; plaintext API keys must not enter Git')
@@ -160,36 +172,21 @@ def patches():
class H(BaseHTTPRequestHandler):
server_version='AIOSChatGPTGitOps/V1R1C0'
def log_request(self, format=''):
import sys
rid = getattr(self, '_request_id', uid('req'))
method = self.command
path = urllib.parse.urlparse(self.path).path
code = self.responses and list(self.responses.keys())[0] if hasattr(self, 'responses') and self.responses else 0
try:
act = self.actor()
a = ('auth_present=true' if act['ok'] else 'auth_present=false')
except:
a = 'auth_present=error'
print(f'[{now()}] {rid} {method} {path} -> {self._resp_code if hasattr(self,"_resp_code") else "?"} {a} pid={os.getpid()}', file=sys.stderr, flush=True)
def compat_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, HEAD, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Authorization, Content-Type, X-AIOS-Token')
self.send_header('Cache-Control', 'no-store')
def send_empty(self, code=204, ctype='application/json; charset=utf-8'):
self._resp_code = code
self.send_response(code)
self.send_header('Content-Type', ctype)
self.compat_headers()
self.send_header('Content-Length', '0')
self.end_headers()
def sendj(self, data, code=200, head=False):
self._resp_code = code
b=json.dumps(data, ensure_ascii=False, indent=2).encode(); self.send_response(code); self.send_header('Content-Type','application/json; charset=utf-8'); self.compat_headers(); self.send_header('Content-Length',str(len(b))); self.end_headers()
if not head: self.wfile.write(b)
def sendt(self, text, ctype='text/plain; charset=utf-8', code=200, head=False):
self._resp_code = code
b=text.encode(); self.send_response(code); self.send_header('Content-Type',ctype); self.compat_headers(); self.send_header('Content-Length',str(len(b))); self.end_headers()
if not head: self.wfile.write(b)
def body(self):
@@ -215,7 +212,7 @@ class H(BaseHTTPRequestHandler):
if OPENAPI.exists():
return self.sendt(OPENAPI.read_text(encoding='utf-8'), 'application/yaml; charset=utf-8', 200, head=True)
return self.send_empty(404)
known={'/health','/api/health','/api','/api/status','/api/base','/api/baseline','/base','/api/ver','/api/versions','/ver','/api/diff','/diff','/api/patch','/api/patches','/api/branches','/patch','/api/model','/model','/api/run','/api/runtime-status','/run','/api/action-monitor','/action-monitor'}
known={'/health','/api/health','/api','/api/status','/api/base','/api/baseline','/base','/api/ver','/api/versions','/ver','/api/diff','/diff','/api/patch','/api/patches','/api/branches','/patch','/api/model','/model','/api/run','/api/runtime-status','/run','/api/action-monitor','/action-monitor','/api/tag','/api/create-tag','/api/tag-request','/api/tag-approval-request','/api/stage-tag-request'}
if path in known: return self.send_empty(200)
return self.send_empty(404)
def do_GET(self):
@@ -255,6 +252,7 @@ class H(BaseHTTPRequestHandler):
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-request','/api/tag-approval-request','/api/stage-tag-request'): return self.tag_request(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)
@@ -282,6 +280,17 @@ class H(BaseHTTPRequestHandler):
if not a: return
name=data.get('patch_name') or data.get('name') or uid('patch'); b=checkout(data.get('branch') or f'patch/{safe(name)}', data.get('base','main'), True); meta={'id':uid('patch'),'patch_name':name,'branch':b,'risk':data.get('risk','medium'),'description':data.get('description',''),'created_by':a['account'],'created_at':now()}
r=write_json(f'patches/{safe(name)}.json', meta); c=commit([r], data.get('message') or f'chore(chatgpt): create patch {name}'); audit(a['account'],'patch',name,meta); self.sendj({'status':'patch_branch_created','patch':meta,'commit':c})
def tag_request(self,data):
a=self.need('tag:create');
if not a: return
ver=data.get('version') or data.get('tag')
if not re.match(r'^V\d+R\d+C\d+(-[A-Za-z0-9_.-]+)?$', ver or ''): return self.sendj({'error':'version must match VxRxCx'},400)
b=checkout(data.get('branch') or f'request/tag-{safe(ver)}', data.get('base','main'), True)
req={'version':ver,'message':data.get('message') or f'request tag {ver}','requested_by':a['account'],'approval_required':True,'execute':False,'tag_created':False,'created_at':now(),'operation':'create_tag_approval_request'}
r=write_json(f'baseline/tag_requests/{safe(ver)}.json', req)
c=commit([r], data.get('commit_message') or f'chore(chatgpt): request tag {ver}')
audit(a['account'],'tag-request',ver,{'branch':b,'request':req,'commit':c})
self.sendj({'status':'tag_request_committed','branch':b,'request':req,'commit':c,'tag_created':False})
def tag(self,data):
a=self.need('tag:create');
if not a: return
@@ -332,7 +341,7 @@ class H(BaseHTTPRequestHandler):
b=checkout(data.get('branch') or f'patch/nl-{uid("nl")}', 'main', True); doc={'id':uid('nl'),'text':text,'intent':intent,'action_plan':plan,'execute':False,'owner_approval_required':True,'created_by':a['account'],'created_at':now()}; r=write_json(f'patches/nl/{safe(doc["id"])}.json', doc); c=commit([r], f'chore(chatgpt): record nl plan {intent}'); self.sendj({'status':'planned','branch':b,'request':doc,'commit':c})
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'],'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'],'write':['POST /api/branch','POST /api/file','POST /api/commit','POST /api/patch','POST /api/tag-request','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():
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
+23
View File
@@ -83,6 +83,15 @@ components:
execute:
type: boolean
description: Owner-approved calls only. Otherwise an approval request commit is created.
TagApprovalRequest:
type: object
required: [version]
properties:
version: {type: string, example: V1R2C3}
message: {type: string}
branch: {type: string, description: Optional request branch. Defaults to request/tag-<version>.}
commit_message: {type: string}
execute: {type: boolean, default: false, description: Ignored; this action never creates a tag.}
RollbackRequest:
type: object
required: [target_version]
@@ -235,10 +244,24 @@ paths:
application/json:
schema: {$ref: '#/components/schemas/CommitRequest'}
responses: {'200': {description: Commit result}, '401': {description: Unauthorized}}
/api/tag-request:
post:
operationId: createTagApprovalRequest
summary: Stage a tag approval request without creating a Git tag
description: Low-risk preparation step. It commits an approval request on a non-main branch and never creates a tag.
x-openai-isConsequential: false
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/TagApprovalRequest'}
responses: {'200': {description: Tag approval request committed}, '401': {description: Unauthorized}, '403': {description: Forbidden}}
/api/tag:
post:
operationId: requestOrCreateTag
summary: Request tag creation; owner execution is required for direct tag
x-openai-isConsequential: true
security: [{bearerAuth: []}]
requestBody:
required: true
+13
View File
@@ -0,0 +1,13 @@
{
"id": "nl-1783443572161-fcbaed22",
"text": "Owner authorized granting gitops-write-bot the tag:create scope required for V1R2C3 release promotion. Create an auditable GitOps permission change plan only. Do not write or expose tokens, secrets, credentials, or private keys. After scope is actually applied by the permission controller, retry V1R2C3 tag creation and preserve V1R2C2 rollback path.",
"intent": "rollback_request",
"action_plan": [
"commit rollback request",
"wait owner/admin approval"
],
"execute": false,
"owner_approval_required": true,
"created_by": "gitops-write-bot",
"created_at": "2026-07-07T16:59:32Z"
}
@@ -1,9 +0,0 @@
{
"type": "branch",
"branch": "test/alias-write-verify",
"base": "main",
"created_by": "gitops-write-bot",
"created_at": "2026-07-07T14:43:14Z",
"direct_main_push": false,
"owner_approval_required": true
}
@@ -0,0 +1,9 @@
{
"id": "patch-1783442954588-5b091d8b",
"patch_name": "v1r2c3-promotion-request-20260708",
"branch": "patch/v1r2c3-promotion-request-20260708",
"risk": "high",
"description": "Promotion request for V1R2C3 after GPT Actions ClientResponseError compatibility recovery. Read/write GitOps Actions passed from codex-chartgpt-bridge. Baseline remains V1R2C2; request owner-reviewed V1R2C3 tag/baseline promotion without direct destructive writes.",
"created_by": "gitops-write-bot",
"created_at": "2026-07-07T16:49:14Z"
}
@@ -1,73 +0,0 @@
# V1R2C3 GPT Actions ClientResponseError Compatibility Fix Report
## Status
READY_FOR_OWNER_RETEST
## Scope
- Branch: `patch/V1R2C3-gpt-actions-clientresponse-compat`
- Service: `aios-gitops-model-api.service`
- Runtime entrypoint: `active-runtime/app/aios_chatgpt_actions_api.py`
- Schema: `docs/gitops/openapi-chatgpt-actions.yaml`
- Affected operations: `getBaseline`, `getRuntimeStatus`, `listVersions`
## Finding
The GPT Actions plugin was exposed in the GPT session, but read operations
returned `ClientResponseError`. Public `GET` probes returned `200`, while
`HEAD` and `OPTIONS` returned `501` for the schema and core read routes.
The OpenAPI document also described the core read responses without explicit
`application/json` response schemas.
## Changes
- Added compatibility response headers to the ChatGPT Actions adapter:
`Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`,
`Access-Control-Allow-Headers`, and `Cache-Control`.
- Added `HEAD` handling for schema and read endpoints.
- Added `OPTIONS` handling for schema and read endpoints.
- Added explicit OpenAPI response schemas for:
- `BaselineResponse`
- `VersionsResponse`
- `RuntimeStatusResponse`
- Preserved the existing runtime request logging changes already present in
the live workspace before this fix.
## Verification
- `python3 -m py_compile active-runtime/app/aios_chatgpt_actions_api.py`: PASS
- OpenAPI YAML parse with PyYAML: PASS
- Service restart: PASS
- Public `GET /api/openapi-chatgpt-actions.yaml`: `200`
- Public `HEAD /api/openapi-chatgpt-actions.yaml`: `200`
- Public `OPTIONS /api/openapi-chatgpt-actions.yaml`: `204`
- Public `GET /api/base`: `200`
- Public `HEAD /api/base`: `200`
- Public `OPTIONS /api/base`: `204`
- Public `GET /api/run`: `200`
- Public `HEAD /api/run`: `200`
- Public `OPTIONS /api/run`: `204`
- Public `GET /api/ver`: `200`
- Public `HEAD /api/ver`: `200`
- Public `OPTIONS /api/ver`: `204`
## Owner Retest
Re-import or refresh the GPT Builder Actions schema from:
`https://cnnetmkt.top/api/openapi-chatgpt-actions.yaml`
Then retry in a fresh GPT preview/session:
1. `getRuntimeStatus`
2. `getBaseline`
3. `listVersions`
## Security Notes
- No API key, token, password, bearer value, cookie, or secret value was added.
- No force push was used.
- No tag was created.
- No production switch was executed.
- Direct main writes remain forbidden.
@@ -0,0 +1,58 @@
# V1R2C3 Tag Approval Action Governance Report
## Status
READY_FOR_OWNER_REIMPORT
## Scope
- Branch: `patch/V1R2C3-tag-approval-action-governance`
- Runtime: `active-runtime/app/aios_chatgpt_actions_api.py`
- Schema: `docs/gitops/openapi-chatgpt-actions.yaml`
- Goal: reduce repeated GPT Actions confirmation by splitting tag staging from tag execution.
## Changes
- Added `POST /api/tag-request` with operationId `createTagApprovalRequest`.
- Marked `createTagApprovalRequest` as `x-openai-isConsequential: false`.
- `createTagApprovalRequest` only commits a tag approval request on a non-main branch.
- `createTagApprovalRequest` never creates a Git tag and returns `tag_created: false`.
- Kept `POST /api/tag` operationId `requestOrCreateTag` for actual tag execution.
- Marked `requestOrCreateTag` as `x-openai-isConsequential: true`.
## Safety Model
- Daily GPT use should call `createTagApprovalRequest` for low-risk staging.
- Actual tag creation remains behind `requestOrCreateTag` and still requires confirmation.
- Direct main writes remain forbidden.
- No force push is allowed.
- V-level, tag, baseline promotion, and production changes still require owner approval.
## Validation
- Python compile for the runtime adapter: PASS
- OpenAPI YAML parse: PASS
- Schema flag check:
- `/api/tag-request` -> `createTagApprovalRequest` -> `x-openai-isConsequential: false`
- `/api/tag` -> `requestOrCreateTag` -> `x-openai-isConsequential: true`
- Package contract check includes tag approval route assertions.
## Owner Retest
Re-import the schema from:
`https://cnnetmkt.top/api/openapi-chatgpt-actions.yaml`
Then verify:
1. `createTagApprovalRequest` appears as the low-risk tag staging action.
2. `requestOrCreateTag` still appears as the high-risk tag execution action.
3. Use `createTagApprovalRequest` for staging V1R2C3 approval.
4. Use `requestOrCreateTag` only for final owner-approved tag creation.
## Security Notes
- No API key, token, password, bearer value, cookie, or secret value was added.
- No force push was used.
- No tag was created by this deployment.
- No production switch was executed.
@@ -0,0 +1,32 @@
# V1R2C3 GPT Actions Promotion Readiness Report
Status: READY_FOR_OWNER_REVIEW
Verified:
- getRuntimeStatus: PASS
- getBaseline: PASS
- listVersions: PASS
- listPatches: PASS
- getDiff: PASS
- createBranch: PASS
- createPatch: PASS
Current baseline:
- active_version: V1R2C2
- active_tag: V1R2C2
Promotion target:
- V1R2C3
Reason:
GPT Actions ClientResponseError compatibility recovery completed. Changes include route compatibility, OpenAPI response schema compatibility, and Actions request handling improvements.
Risk:
High-risk release actions require owner approval before tag creation and baseline promotion.
No secrets, tokens, credentials, or private keys are included in this report.
+5
View File
@@ -23,4 +23,9 @@ python3 -m py_compile \
"$ROOT/adapters/openclaw/openclaw_gitops_commands.py" \
"$ROOT/adapters/hermes/hermes_gitops_nl_router.py"
grep -q "/api/branch" "$ROOT/docs/gitops/openapi-chatgpt-actions.yaml"
grep -q "/api/tag-request" "$ROOT/docs/gitops/openapi-chatgpt-actions.yaml"
grep -q "operationId: createTagApprovalRequest" "$ROOT/docs/gitops/openapi-chatgpt-actions.yaml"
grep -q "x-openai-isConsequential: false" "$ROOT/docs/gitops/openapi-chatgpt-actions.yaml"
grep -q "operationId: requestOrCreateTag" "$ROOT/docs/gitops/openapi-chatgpt-actions.yaml"
grep -q "x-openai-isConsequential: true" "$ROOT/docs/gitops/openapi-chatgpt-actions.yaml"
echo "PACKAGE_CONTRACT_OK $(cat "$ROOT/VERSION")"