Compare commits

...
Author SHA1 Message Date
aios b1fd9db1dd feat(actions): split tag approval request action 2026-07-08 00:58:24 +08:00
aios 2730098095 docs(release): add V1R2C3 promotion readiness report 2026-07-08 00:49:17 +08:00
aios eb48162898 chore(gitops): request V1R2C3 promotion after Actions recovery 2026-07-08 00:49:14 +08:00
aios b1c1e49cc5 fix: add READABLE aliases for all API endpoints (V1R2C3)
Add descriptive, user-friendly aliases for the existing API routes to
improve gitops-agent API discoverability:

  GET  /api/baseline     → /api/base
  GET  /api/versions     → /api/ver
  GET  /api/runtime-status → /api/run
  GET  /api/patches      → /api/patch
  POST /api/create-patch  → /api/patch
  GET  /api/branches     → /api/branch
  POST /api/create-branch → /api/branch
  POST /api/commit-file   → /api/file
  POST /api/create-tag    → /api/tag

All aliases wire through the same handler functions, tested live.
2026-07-07 22:04:47 +08:00
5 changed files with 141 additions and 8 deletions
+20 -8
View File
@@ -185,15 +185,15 @@ class H(BaseHTTPRequestHandler):
if path in ('/api/openapi.yaml','/api/openapi-chatgpt-actions.yaml','/api/schema'):
return self.sendt(OPENAPI.read_text(encoding='utf-8'), 'application/yaml; charset=utf-8') if OPENAPI.exists() else self.sendj({'error':'schema_missing'},404)
if path in ('/api','/api/status'): return self.sendj(api_spec())
if path in ('/api/base','/base'):
if path in ('/api/base','/api/baseline','/base'):
data=jload(WS/'baseline'/'current.json', {}); data.setdefault('git', sh(['rev-parse','--short','HEAD'])['stdout']); return self.sendj(data)
if path in ('/api/ver','/ver'):
if path in ('/api/ver','/api/versions','/ver'):
return self.sendj({'tags':sh(['tag','--list','V*'])['stdout'].splitlines(),'versions_dir':[p.name for p in (WS/'versions').glob('*')],'head':sh(['rev-parse','--short','HEAD'])['stdout']})
if path in ('/api/diff','/diff'):
frm=(q.get('from') or ['HEAD~1'])[0]; to=(q.get('to') or ['HEAD'])[0]; return self.sendj({'from':frm,'to':to,'diff':sh(['diff',frm,to])})
if path in ('/api/patch','/patch'): return self.sendj({'patches':patches(),'branches':sh(['branch','--format','%(refname:short)'])['stdout'].splitlines()})
if path in ('/api/patch','/api/patches','/api/branches','/patch'): return self.sendj({'patches':patches(),'branches':sh(['branch','--format','%(refname:short)'])['stdout'].splitlines()})
if path in ('/api/model','/model'): return self.sendj({'base':BASE_MODELS,'custom':model_docs(),'priority':[jload(p,{}) for p in sorted((WS/'config'/'model_priority').glob('*.json'))]})
if path in ('/api/run','/run'):
if path in ('/api/run','/api/runtime-status','/run'):
svc=subprocess.run(['systemctl','is-active','aios-gitops-model-api'], text=True, capture_output=True)
return self.sendj({'service':'aios-gitops-model-api','status':svc.stdout.strip() or svc.stderr.strip(),'workspace_branch':sh(['rev-parse','--abbrev-ref','HEAD'])['stdout']})
if path in ('/api/action-monitor','/action-monitor'):
@@ -211,11 +211,12 @@ class H(BaseHTTPRequestHandler):
def do_POST(self):
path=urllib.parse.urlparse(self.path).path.rstrip('/'); data=self.body()
try:
if path == '/api/branch': return self.branch(data)
if path == '/api/file': return self.file(data)
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 == '/api/patch': return self.patch(data)
if path == '/api/tag': return self.tag(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)
if path == '/api/model-priority': return self.model_priority(data)
@@ -242,6 +243,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
+23
View File
@@ -57,6 +57,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]
@@ -194,10 +203,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
@@ -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"
}
@@ -0,0 +1,57 @@
# 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`
## 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.