Compare commits

..
Author SHA1 Message Date
aios edd3bd239e feat(cgpt-actions): add ActionMonitor for failure tracking & confirm-guard for tag/model/model-priority 2026-07-07 17:58:11 +08:00
aios 7a1e862a03 feat(V1R2C2): add autonomous recovery action monitor 2026-07-07 16:11:47 +08:00
aios dc0ac18aea chore(patches): remove stale artifact files
Remove 4 stale patch artifact files that have outlived their purpose:
- patches/nl/*.json - ephemeral natural-language plan records (unknown, patch_create)
- patches/patch_collab-*.branch.json - collab test branch records for branches
  that have been merged and had no local residual branch

The self-evolution-agent-orchestration-policy.json patch record is retained
because patch/self-evolution-agent-orchestration-policy branch is still live.
2026-07-07 15:53:02 +08:00
aios 9e3dbdf146 chore(version): bump baseline to V1R1C6 for chatgpt-actions consequential governance 2026-07-07 15:22:24 +08:00
aios 87b1a5357c feat(cgpt-actions): add confirm protection to tag/model/model-priority 2026-07-07 15:12:00 +08:00
15 changed files with 157 additions and 1163 deletions
+24 -61
View File
@@ -5,6 +5,8 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any
from orchestrator.action_monitor import ActionMonitor
ROOT = Path(os.getenv('AIOS_GITOPS_ROOT', '/data/aios-gitops'))
WS = Path(os.getenv('AIOS_GITOPS_WORKSPACE', str(ROOT / 'workspace')))
SECRETS = Path(os.getenv('AIOS_GITOPS_SECRETS_DIR', str(ROOT / 'runtime' / 'secrets')))
@@ -13,6 +15,8 @@ HOST = os.getenv('AIOS_GITOPS_HOST', '127.0.0.1')
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-v23.git')))
monitor = ActionMonitor()
BASE_MODELS = [
{'id':'base.deepseekv4-flash','provider':'one-api','model':'deepseekv4-flash','display_name':'DeepSeek V4 Flash','scope':'global'},
{'id':'base.qwen3.6-35b','provider':'one-api','model':'qwen3.6-35b','display_name':'Qwen 3.6 35B','scope':'global'},
@@ -20,14 +24,6 @@ BASE_MODELS = [
]
SECRET_RE = re.compile(r'(?i)(api[_-]?key|secret[_-]?access[_-]?key|secret|password|token)\s*[:=]\s*["\']?([A-Za-z0-9_./+=:-]{16,})')
SECRET_VAL_RE = re.compile(r'(sk-[A-Za-z0-9_-]{20,}|HPUA[A-Z0-9]{10,}|SDC[A-Za-z0-9]{20,})')
SECRET_TEXT_RE = re.compile(r'(?i)(Bearer\\s+[A-Za-z0-9._-]{3,}|BEGIN\\s+PRIVATE\\s+KEY|password\\s*=\\s*\\S+|api_key\\s*=\\s*\\S+|access_token\\s*=\\s*\\S+|refresh_token\\s*=\\s*\\S+)')
SECRET_PATH_PREFIXES = ('runtime/secrets/', 'private/', 'prod-secrets/')
LOW_RISK_FILE_SCOPES = {
'/api/gitops/report-file': ('commitReportFile', ('reports/',)),
'/api/gitops/docs-file': ('commitDocsFile', ('docs/',)),
'/api/gitops/proposal-file': ('commitProposalFile', ('proposals/',)),
'/api/gitops/patch-evidence': ('commitPatchEvidence', ('reports/', 'patches/')),
}
def now() -> str: return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
def uid(p='id') -> str: return f'{p}-{int(time.time()*1000)}-{uuid.uuid4().hex[:8]}'
@@ -43,15 +39,6 @@ def relpath(path: str) -> str:
x = str(path or '').replace('\\','/').strip()
if not x or x.startswith('/') or '..' in Path(x).parts or Path(x).parts[0] == '.git': raise ValueError('invalid path')
return str(Path(x))
def secret_path(path: str) -> bool:
r = relpath(path).replace('\\', '/')
name = Path(r).name
return name.endswith('.token') or r == '.env' or any(r.startswith(prefix) for prefix in SECRET_PATH_PREFIXES)
def path_in_scope(path: str, scopes: tuple[str, ...]) -> bool:
r = relpath(path).replace('\\', '/')
return any(r.startswith(scope) for scope in scopes)
def branch_name(name: str | None, prefix='patch/chatgpt') -> str:
b = safe(name or f'{prefix}-{uid("br")}', 180)
if b in ('main','master') or b.startswith('main/') or b.startswith('master/'): raise ValueError('direct main/master writes are forbidden')
@@ -80,7 +67,7 @@ def content_has_secret(text: str) -> bool:
return _json_has_plain_secret(json.loads(text))
except Exception:
pass
if SECRET_VAL_RE.search(text) or SECRET_TEXT_RE.search(text):
if SECRET_VAL_RE.search(text):
return True
for m in SECRET_RE.finditer(text):
key = str(m.group(1)).lower()
@@ -126,10 +113,8 @@ def checkout(branch, base='main', create=False):
else: sh(['checkout',b], True)
return b
def write_file(path, content):
r=relpath(path)
if secret_path(r): raise ValueError('secret_path_rejected: use runtime secret store or secret_ref')
if content_has_secret(content): raise ValueError('secret_rejected: use secret_ref; plaintext API keys must not enter Git')
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 sync_branch(branch: str) -> dict:
if not branch or branch in ('main','master') or branch.startswith('main/') or branch.startswith('master/'):
@@ -210,8 +195,12 @@ class H(BaseHTTPRequestHandler):
if path in ('/api/run','/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'):
return self.sendj({'events':[{"event_id":e.event_id,"action":e.action,"error":e.error,"context":e.context,"created_at":e.created_at} for e in monitor.list_events()],'latest':monitor.latest()})
return self.sendj({'error':'not_found','path':path},404)
except Exception as e: return self.sendj({'error':str(e)},500)
except Exception as e:
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()
if path == '/api/model-priority': return self.model_priority(data)
@@ -220,9 +209,6 @@ class H(BaseHTTPRequestHandler):
path=urllib.parse.urlparse(self.path).path.rstrip('/'); data=self.body()
try:
if path == '/api/branch': return self.branch(data)
if path in LOW_RISK_FILE_SCOPES: return self.low_risk_file(path, data)
if path == '/api/gitops/main-file': return self.main_file(data)
if path == '/api/gitops/rollback-request': return self.rollback(data)
if path == '/api/file': return self.file(data)
if path == '/api/commit': return self.commit(data)
if path == '/api/patch': return self.patch(data)
@@ -232,7 +218,9 @@ class H(BaseHTTPRequestHandler):
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: return self.sendj({'error':str(e)},500)
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
@@ -242,40 +230,6 @@ class H(BaseHTTPRequestHandler):
a=self.need('file:write');
if not a: return
b=checkout(data.get('branch'), data.get('base','main'), bool(data.get('create_branch'))); r=write_file(data.get('path'), data.get('content','')); c=commit([r], data.get('message') or f'chore(chatgpt): update {r}'); audit(a['account'],'file',r,{'branch':b,'commit':c}); self.sendj({'status':'committed','branch':b,'path':r,'commit':c})
def low_risk_file(self,path,data):
a=self.need('file:write');
if not a: return
operation_id, scopes = LOW_RISK_FILE_SCOPES[path]
requested_branch = data.get('branch')
if not requested_branch or branch_name(requested_branch) != requested_branch:
return self.sendj({'error':'low-risk write requires explicit non-main patch/request branch','operationId':operation_id},400)
r=relpath(data.get('path'))
if secret_path(r):
return self.sendj({'error':'Secret-like path rejected. Use runtime secret store or secret_ref.','operationId':operation_id},400)
if not path_in_scope(r, scopes):
return self.sendj({'error':'path_out_of_scope','operationId':operation_id,'allowed_scopes':list(scopes)},400)
if content_has_secret(data.get('content','')):
return self.sendj({'error':'Secret-like content detected. Use runtime secret store or secret_ref.','operationId':operation_id},400)
b=checkout(requested_branch, data.get('base','main'), bool(data.get('create_branch')))
r=write_file(r, data.get('content',''))
c=commit([r], data.get('message') or f'chore(chatgpt): {operation_id} {r}')
audit(a['account'],operation_id,r,{'branch':b,'commit':c})
self.sendj({'status':'committed','operationId':operation_id,'branch':b,'path':r,'commit':c})
def main_file(self,data):
a=self.need('file:main');
if not a: return
if a.get('role') != 'owner':
return self.sendj({'error':'owner approval required for commitMainFile'},403)
r=relpath(data.get('path'))
if secret_path(r):
return self.sendj({'error':'Secret-like path rejected. Use runtime secret store or secret_ref.','operationId':'commitMainFile'},400)
if content_has_secret(data.get('content','')):
return self.sendj({'error':'Secret-like content detected. Use runtime secret store or secret_ref.','operationId':'commitMainFile'},400)
ensure_git(); sh(['checkout','main'], True)
r=write_file(r, data.get('content',''))
c=commit([r], data.get('message') or f'chore(chatgpt): commit main file {r}')
audit(a['account'],'commitMainFile',r,{'branch':'main','commit':c})
self.sendj({'status':'committed','operationId':'commitMainFile','branch':'main','path':r,'commit':c})
def commit(self,data):
a=self.need('commit:create');
if not a: return
@@ -290,6 +244,9 @@ class H(BaseHTTPRequestHandler):
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)
if data.get('confirm') != True:
self.sendj({'status':'confirm_required','operation':'tag','hint':'This operation creates a Git tag. Confirm to proceed.','x-openai-isConsequential':True}, 428)
return
if a['role'] != 'owner' or not data.get('execute'):
b=checkout(data.get('branch') or f'request/tag-{safe(ver)}', 'main', True); req={'version':ver,'requested_by':a['account'],'approval_required':True,'created_at':now()}; r=write_json(f'baseline/tag_requests/{safe(ver)}.json', req); c=commit([r], f'chore(chatgpt): request tag {ver}'); return self.sendj({'status':'approval_required','branch':b,'commit':c})
out=sh(['tag','-a',ver,'-m',data.get('message') or f'release {ver}'], True); self.sendj({'status':'tagged','version':ver,'git':out,'sync':sync_tag(ver)})
@@ -310,10 +267,16 @@ class H(BaseHTTPRequestHandler):
if not secret_ref: return self.sendj({'error':'api_key_secret_ref or api_key required'},400)
mid=data.get('model_id') or f'custom.{owner}.{hashlib.sha1((prov+base+name).encode()).hexdigest()[:10]}'; b=checkout(data.get('branch') or f'patch/model-{safe(mid)}','main',True)
doc=sanitize({k:v for k,v in data.items() if k!='api_key'}); doc.update({'model_id':mid,'provider_name':prov,'base_url':base,'model_name':name,'api_key_secret_ref':secret_ref,'created_by':a['account'],'created_at':now()})
if data.get('confirm') != True:
self.sendj({'status':'confirm_required','operation':'model','hint':'This operation registers a new model. Confirm to proceed.','x-openai-isConsequential':True}, 428)
return
r=write_json(f'config/model_registry/chatgpt/{safe(mid)}.json', doc); c=commit([r], data.get('message') or f'chore(chatgpt): register model {name}'); audit(a['account'],'model',mid,doc); self.sendj({'status':'model_registered','branch':b,'model_id':mid,'secret_ref':secret_ref,'commit':c})
def model_priority(self,data):
a=self.need('model:priority');
if not a: return
if data.get('confirm') != True:
self.sendj({'status':'confirm_required','operation':'model-priority','hint':'This operation updates model priority. Confirm to proceed.','x-openai-isConsequential':True}, 428)
return
user=safe(data.get('username') or a['account']); b=checkout(data.get('branch') or f'patch/model-priority-{user}', 'main', True); doc={'username':user,'items':data.get('items') or data.get('priority') or [],'updated_by':a['account'],'updated_at':now()}; r=write_json(f'config/model_priority/{user}.json', doc); c=commit([r], data.get('message') or f'chore(chatgpt): update model priority {user}'); audit(a['account'],'model-priority',user,doc); self.sendj({'status':'updated','branch':b,'priority':doc,'commit':c})
def nl(self,data):
a=self.need('nl:plan');
@@ -326,7 +289,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/gitops/report-file','POST /api/gitops/docs-file','POST /api/gitops/proposal-file','POST /api/gitops/patch-evidence','POST /api/gitops/main-file','POST /api/commit','POST /api/patch','POST /api/tag','POST /api/rollback','POST /api/gitops/rollback-request','POST /api/model','PUT /api/model-priority','POST /api/nl'],'policy':{'bearer_required_for_write':True,'direct_main_push':False,'low_risk_main_write':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','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}')
+5 -5
View File
@@ -1,6 +1,6 @@
{
"active_version": "V1R1C4",
"active_tag": "V1R1C4",
"active_version": "V1R1C6",
"active_tag": "V1R1C6",
"mode": "gitops-online",
"version_policy": "VxRxCx",
"update_mode": "git_patch_only",
@@ -27,7 +27,7 @@
"portal_gitops_dashboard": true,
"human_shortlinks_login_required": true
},
"version": "V1R1C4",
"version": "V1R1C6",
"baseline_tag": "V1R1C0-baseline-20260706",
"source": "AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip",
"source_package": "AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip",
@@ -46,7 +46,7 @@
"/api",
"/hook"
],
"updated_at": "2026-07-06T11:16:23Z",
"updated_at": "2026-07-07T14:30:00Z",
"auth": {
"primary_domain": "https://auth.cnnetmkt.top",
"backup_domain": "https://auth.h.imruyi.top:8443",
@@ -55,7 +55,7 @@
"casdoor_application": "admin/aios-portal",
"backup_domain_role": "standby-only"
},
"previous_version": "V1R1C3",
"previous_version": "V1R1C5",
"codex_gitops": {
"config": "config/codex/gitops-online.json",
"token_account": "codex-gitops-bot",
@@ -1,70 +0,0 @@
# GPT Actions Consequential Governance
## Objective
Reduce unnecessary ChatGPT Actions confirmation prompts for low-risk GitOps write operations while preserving confirmation protection for high-risk changes.
## Platform Boundary
Instructions can reduce how often a Custom GPT chooses high-risk actions, but they cannot guarantee that the ChatGPT platform will suppress every external Action confirmation prompt. The platform may still ask the user to confirm writes to external services.
`x-openai-isConsequential` is the OpenAPI-level control used to communicate whether an Action is consequential:
- Low-risk GitOps evidence and documentation writes use `x-openai-isConsequential: false`.
- High-risk runtime, rollback, main-branch, tag, release, baseline, authentication, permission, and production-model changes use `x-openai-isConsequential: true`.
Fully unattended background writes should be executed by the AIOS Orchestrator, not by direct Custom GPT UI Actions.
## Low-Risk Actions
Low-risk Actions are limited to non-main patch branches and controlled evidence paths:
| operationId | Consequential | Path Scope |
|---|---:|---|
| `commitReportFile` | `false` | `reports/**` |
| `commitDocsFile` | `false` | `docs/**` |
| `commitProposalFile` | `false` | `proposals/**` |
| `commitPatchEvidence` | `false` | `reports/**`, `patches/**` |
Low-risk Actions must reject:
- `main` and `master` writes.
- `runtime/secrets/**`.
- `.env`.
- `*.token`.
- `private/**`.
- `prod-secrets/**`.
- Secret-like content such as credential values, passwords, API keys, private keys, or access tokens.
- Merge, rollback, tag, release, runtime, production, baseline, permission, or authentication mutations.
## High-Risk Actions
High-risk Actions intentionally keep confirmation protection:
| operationId | Consequential | Reason |
|---|---:|---|
| `commitFile` | `true` | Legacy generic write API retained for compatibility. |
| `commitMainFile` | `true` | Explicit main-branch write. |
| `createRollbackRequest` | `true` | Rollback-related workflow. |
| `requestRollback` | `true` | Legacy rollback request compatibility. |
| `requestOrCreateTag` | `true` | Release/tag workflow. |
| `setModelPriority` | `true` | Model-routing policy mutation. |
| `registerModel` | `true` | Runtime model registry mutation. |
## Compatibility
The legacy `commitFile` operation is preserved and marked consequential. Custom GPTs should prefer the low-risk specialized operations for docs, reports, proposals, and patch evidence.
## Secret Handling
The schema uses placeholders only. Action authentication remains injected by GPT Builder Actions configuration at runtime. Real tokens, passwords, API keys, private keys, and secret values must never be placed in OpenAPI examples, docs, reports, tests, or scripts.
## Validation
Validation is covered by:
- `tests/test_openapi_consequential_flags.py`.
- `tests/test_gitops_low_risk_commit_policy.py`.
- `scripts/smoke_gpt_actions_consequential_governance.py`.
These checks validate the consequential matrix, path scoping, main-branch rejection for low-risk writes, secret-path rejection, secret-like content rejection, and placeholder-only schema examples.
+91 -470
View File
@@ -2,11 +2,9 @@ openapi: 3.1.0
info:
title: AIOS GitOps Controlled Access API
version: V1R1C0-chatgpt-actions
description: Controlled ChatGPT Actions API for AIOS online GitOps baseline. Read
endpoints are public. Write endpoints require Bearer token and create Git commits
on non-main branches.
description: Controlled ChatGPT Actions API for AIOS online GitOps baseline. Read endpoints are public. Write endpoints require Bearer token and create Git commits on non-main branches.
servers:
- url: https://cnnetmkt.top
- url: https://cnnetmkt.top
security: []
components:
securitySchemes:
@@ -17,603 +15,226 @@ components:
schemas:
BranchRequest:
type: object
required:
- branch
required: [branch]
properties:
branch:
type: string
example: patch/chatgpt-actions-demo
base:
type: string
default: main
message:
type: string
branch: {type: string, example: patch/chatgpt-actions-demo}
base: {type: string, default: main}
message: {type: string}
FileCommitRequest:
type: object
required:
- branch
- path
- content
required: [branch, path, content]
properties:
branch:
type: string
base:
type: string
default: main
create_branch:
type: boolean
default: false
path:
type: string
example: docs/gitops/chatgpt-change.md
content:
type: string
message:
type: string
branch: {type: string}
base: {type: string, default: main}
create_branch: {type: boolean, default: false}
path: {type: string, example: docs/gitops/chatgpt-change.md}
content: {type: string}
message: {type: string}
CommitRequest:
type: object
properties:
branch:
type: string
branch: {type: string}
paths:
type: array
items:
type: string
message:
type: string
items: {type: string}
message: {type: string}
PatchRequest:
type: object
required:
- patch_name
required: [patch_name]
properties:
patch_name:
type: string
branch:
type: string
base:
type: string
default: main
risk:
type: string
enum:
- low
- medium
- high
description:
type: string
message:
type: string
patch_name: {type: string}
branch: {type: string}
base: {type: string, default: main}
risk: {type: string, enum: [low, medium, high]}
description: {type: string}
message: {type: string}
TagRequest:
type: object
required:
- version
required: [version]
properties:
version:
type: string
example: V1R1C1
message:
type: string
version: {type: string, example: V1R1C1}
message: {type: string}
execute:
type: boolean
description: Owner-approved calls only. Otherwise an approval request commit
is created.
description: Owner-approved calls only. Otherwise an approval request commit is created.
RollbackRequest:
type: object
required:
- target_version
required: [target_version]
properties:
target_version:
type: string
reason:
type: string
branch:
type: string
target_version: {type: string}
reason: {type: string}
branch: {type: string}
ModelRequest:
type: object
required:
- provider_name
- base_url
- model_name
required: [provider_name, base_url, model_name]
properties:
username:
type: string
provider_name:
type: string
base_url:
type: string
model_name:
type: string
display_name:
type: string
username: {type: string}
provider_name: {type: string}
base_url: {type: string}
model_name: {type: string}
display_name: {type: string}
api_key_secret_ref:
type: string
description: Preferred. Git stores this secret_ref only.
api_key:
type: string
writeOnly: true
description: Accepted only for runtime secret storage; never committed to
Git or logs.
branch:
type: string
message:
type: string
description: Accepted only for runtime secret storage; never committed to Git or logs.
branch: {type: string}
message: {type: string}
ModelPriorityRequest:
type: object
required:
- items
required: [items]
properties:
username:
type: string
branch:
type: string
username: {type: string}
branch: {type: string}
items:
type: array
items:
type: object
properties:
model_id:
type: string
priority:
type: integer
enabled:
type: boolean
fallback_policy:
type: string
model_id: {type: string}
priority: {type: integer}
enabled: {type: boolean}
fallback_policy: {type: string}
NLRequest:
type: object
required:
- text
required: [text]
properties:
text:
type: string
branch:
type: string
LowRiskFileCommitRequest:
type: object
required:
- branch
- path
- content
- message
properties:
branch:
type: string
description: Non-main patch branch. Must start with patch/ or request/.
example: patch/gpt-actions-consequential-governance
base:
type: string
default: main
description: Base branch used when create_branch is true.
create_branch:
type: boolean
default: false
path:
type: string
description: File path constrained by the operation path scope.
example: reports/example-report.md
content:
type: string
description: File content. Must not include secrets, credentials, passwords,
API keys, private keys, or token values.
message:
type: string
description: Commit message. Must not include secrets or credentials.
example: 'docs(actions): add GPT Actions governance evidence'
MainFileCommitRequest:
type: object
required:
- path
- content
- message
properties:
branch:
type: string
default: main
description: Main branch. This high-risk operation requires explicit owner
intent.
path:
type: string
description: File path for explicit main-branch maintenance. Secret paths
are always rejected.
content:
type: string
description: File content. Must not include secrets, credentials, passwords,
API keys, private keys, or token values.
message:
type: string
description: Commit message. Must not include secrets or credentials.
RollbackRequestAction:
type: object
required:
- target_version
- reason
properties:
target_version:
type: string
example: V1R1C4
reason:
type: string
example: Owner-approved rollback request
branch:
type: string
example: request/rollback-V1R1C4
text: {type: string}
branch: {type: string}
paths:
/api/base:
get:
operationId: getBaseline
summary: Read current AIOS baseline
responses:
'200':
description: OK
responses: {'200': {description: OK}}
/api/ver:
get:
operationId: listVersions
summary: List GitOps versions
responses:
'200':
description: OK
responses: {'200': {description: OK}}
/api/diff:
get:
operationId: getDiff
summary: Read Git diff between refs
parameters:
- in: query
name: from
schema:
type: string
default: HEAD~1
- in: query
name: to
schema:
type: string
default: HEAD
responses:
'200':
description: OK
- in: query
name: from
schema: {type: string, default: HEAD~1}
- in: query
name: to
schema: {type: string, default: HEAD}
responses: {'200': {description: OK}}
/api/patch:
get:
operationId: listPatches
summary: List patch records
responses:
'200':
description: OK
responses: {'200': {description: OK}}
post:
operationId: createPatch
summary: Create a patch branch and commit metadata
security:
- bearerAuth: []
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PatchRequest'
responses:
'200':
description: Patch branch committed
'401':
description: Unauthorized
'403':
description: Forbidden
x-openai-isConsequential: true
schema: {$ref: '#/components/schemas/PatchRequest'}
responses: {'200': {description: Patch branch committed}, '401': {description: Unauthorized}, '403': {description: Forbidden}}
/api/model:
get:
operationId: readModelRegistry
summary: Read visible model registry and priority
responses:
'200':
description: OK
responses: {'200': {description: OK}}
post:
operationId: registerModel
summary: Register a custom model; API keys are stored as runtime secrets and
Git receives secret_ref only
security:
- bearerAuth: []
summary: Register a custom model; API keys are stored as runtime secrets and Git receives secret_ref only
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ModelRequest'
responses:
'200':
description: Model registered and committed
'401':
description: Unauthorized
'403':
description: Forbidden
x-openai-isConsequential: true
schema: {$ref: '#/components/schemas/ModelRequest'}
responses: {'200': {description: Model registered and committed}, '401': {description: Unauthorized}, '403': {description: Forbidden}}
/api/run:
get:
operationId: getRuntimeStatus
summary: Read runtime status
responses:
'200':
description: OK
responses: {'200': {description: OK}}
/api/branch:
post:
operationId: createBranch
summary: Create a non-main branch and commit branch metadata
security:
- bearerAuth: []
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BranchRequest'
responses:
'200':
description: Branch created
'401':
description: Unauthorized
'403':
description: Forbidden
x-openai-isConsequential: true
schema: {$ref: '#/components/schemas/BranchRequest'}
responses: {'200': {description: Branch created}, '401': {description: Unauthorized}, '403': {description: Forbidden}}
/api/file:
post:
operationId: commitFile
summary: Write a file and create a Git commit on a non-main branch
security:
- bearerAuth: []
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FileCommitRequest'
responses:
'200':
description: File committed
'400':
description: Secret rejected or invalid path
'401':
description: Unauthorized
x-openai-isConsequential: true
schema: {$ref: '#/components/schemas/FileCommitRequest'}
responses: {'200': {description: File committed}, '400': {description: Secret rejected or invalid path}, '401': {description: Unauthorized}}
/api/commit:
post:
operationId: commitChanges
summary: Commit existing workspace changes on a non-main branch
security:
- bearerAuth: []
security: [{bearerAuth: []}]
requestBody:
required: false
content:
application/json:
schema:
$ref: '#/components/schemas/CommitRequest'
responses:
'200':
description: Commit result
'401':
description: Unauthorized
x-openai-isConsequential: true
schema: {$ref: '#/components/schemas/CommitRequest'}
responses: {'200': {description: Commit result}, '401': {description: Unauthorized}}
/api/tag:
post:
operationId: requestOrCreateTag
summary: Request tag creation; owner execution is required for direct tag
security:
- bearerAuth: []
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TagRequest'
responses:
'200':
description: Tag request committed or tag created
'403':
description: Forbidden
x-openai-isConsequential: true
schema: {$ref: '#/components/schemas/TagRequest'}
responses: {'200': {description: Tag request committed or tag created}, '403': {description: Forbidden}}
/api/rollback:
post:
operationId: requestRollback
summary: Commit a rollback request requiring owner/admin approval
security:
- bearerAuth: []
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RollbackRequest'
responses:
'200':
description: Rollback request committed
'401':
description: Unauthorized
x-openai-isConsequential: true
schema: {$ref: '#/components/schemas/RollbackRequest'}
responses: {'200': {description: Rollback request committed}, '401': {description: Unauthorized}}
/api/model-priority:
put:
operationId: setModelPriority
summary: Set model priority and commit policy file
security:
- bearerAuth: []
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ModelPriorityRequest'
responses:
'200':
description: Priority updated and committed
'401':
description: Unauthorized
x-openai-isConsequential: true
schema: {$ref: '#/components/schemas/ModelPriorityRequest'}
responses: {'200': {description: Priority updated and committed}, '401': {description: Unauthorized}}
/api/nl:
post:
operationId: planNaturalLanguageGitOpsAction
summary: Convert natural language to a safe committed plan without executing
V-level changes
security:
- bearerAuth: []
summary: Convert natural language to a safe committed plan without executing V-level changes
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NLRequest'
responses:
'200':
description: Plan committed
'401':
description: Unauthorized
x-openai-isConsequential: true
/api/gitops/report-file:
post:
operationId: commitReportFile
x-openai-isConsequential: false
x-aios-path-scope: reports/**
summary: Commit a report file to a controlled GitOps patch branch
description: Low-risk report write. Commits markdown or JSON evidence under
reports/** to a non-main patch branch. This operation must not write secrets,
credentials, token values, runtime secret files, rollback requests, or production
config.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LowRiskFileCommitRequest'
responses:
'200':
description: File committed successfully
'400':
description: Invalid branch, path, or secret-like content rejected
'401':
description: Authorization header was not injected
'403':
description: Authorization scope is insufficient
/api/gitops/docs-file:
post:
operationId: commitDocsFile
x-openai-isConsequential: false
x-aios-path-scope: docs/**
summary: Commit a docs file to a controlled GitOps patch branch
description: Low-risk documentation write under docs/** on a non-main patch
branch. It must not include real tokens, passwords, API keys, private keys,
credentials, rollback execution, or production config mutation.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LowRiskFileCommitRequest'
responses:
'200':
description: File committed successfully
'400':
description: Invalid branch, path, or secret-like content rejected
'401':
description: Authorization header was not injected
'403':
description: Authorization scope is insufficient
/api/gitops/proposal-file:
post:
operationId: commitProposalFile
x-openai-isConsequential: false
x-aios-path-scope: proposals/**
summary: Commit a proposal file to a controlled GitOps patch branch
description: Low-risk proposal write under proposals/** on a non-main patch
branch. It must not include real tokens, passwords, API keys, private keys,
credentials, runtime secret files, rollback execution, or production config
mutation.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LowRiskFileCommitRequest'
responses:
'200':
description: File committed successfully
'400':
description: Invalid branch, path, or secret-like content rejected
'401':
description: Authorization header was not injected
'403':
description: Authorization scope is insufficient
/api/gitops/patch-evidence:
post:
operationId: commitPatchEvidence
x-openai-isConsequential: false
x-aios-path-scope: reports/**, patches/**
summary: Commit patch evidence to a controlled GitOps patch branch
description: Low-risk evidence write under reports/** or patches/** on a non-main
patch branch. It must not include real tokens, passwords, API keys, private
keys, credentials, runtime secret files, rollback execution, or production
config mutation.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LowRiskFileCommitRequest'
responses:
'200':
description: File committed successfully
'400':
description: Invalid branch, path, or secret-like content rejected
'401':
description: Authorization header was not injected
'403':
description: Authorization scope is insufficient
/api/gitops/main-file:
post:
operationId: commitMainFile
x-openai-isConsequential: true
summary: Commit a file directly to main branch
description: High-risk explicit main-branch write. This operation exists only
for owner-approved maintenance and must not include real secrets, credentials,
passwords, private keys, API keys, token values, or runtime secret paths.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MainFileCommitRequest'
responses:
'200':
description: File committed successfully
'400':
description: Invalid path or secret-like content rejected
'401':
description: Authorization header was not injected
'403':
description: Authorization scope is insufficient or owner approval missing
/api/gitops/rollback-request:
post:
operationId: createRollbackRequest
x-openai-isConsequential: true
summary: Create an owner-review rollback request
description: High-risk rollback request. Creates auditable rollback intent and
must never silently mutate production state or bypass owner/admin review.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RollbackRequestAction'
responses:
'200':
description: Rollback request committed
'400':
description: Invalid rollback request or secret-like content rejected
'401':
description: Authorization header was not injected
'403':
description: Authorization scope is insufficient or owner approval missing
schema: {$ref: '#/components/schemas/NLRequest'}
responses: {'200': {description: Plan committed}, '401': {description: Unauthorized}}
-10
View File
@@ -1,10 +0,0 @@
{
"id": "nl-1783382614842-6c6112e8",
"text": "自我进化执行主体策略更新:除 Windows Codex App 外,同时增加服务器上的 OpenClaw Agent 与 Hermes Agent 作为执行主体。允许启动多个服务器 Agent 并行执行;默认以服务器上的 Agent 为主,Windows Codex App 作为协同执行主体。困难时刻允许访问 https://chatgpt.com/chat/gpt-6a4ba70a063c8191a89bc2d86a4c7fa0-codex-chartgpt-bridge 求助或交互。分歧时刻允许多个 Agent 投票决策;自定义 GPT 权重为双倍,Windows Codex App 权重为双倍。服务器 Agent 默认参与并行协作。该策略应以 GitOps 可审计方式进入自我进化 agent orchestration proposal,不直接执行 V-level 版本变更,不写入任何凭据。",
"intent": "unknown",
"action_plan": [],
"execute": false,
"owner_approval_required": true,
"created_by": "gitops-write-bot",
"created_at": "2026-07-07T00:03:34Z"
}
-12
View File
@@ -1,12 +0,0 @@
{
"id": "nl-1783390999250-8627c20c",
"text": "基于 V1R1C4 baseline 和 patch/self-evolution-agent-orchestration-policy 全量重新架构优化:服务器 OpenClaw 与 Hermes 作为主要执行智能体;Codex ChartGPT Bridge 作为架构审核、方案建议和困难问题升级入口;Windows Codex App 作为异常恢复、管理员授权协助和多智能体投票参与者。所有操作基于在线 GitOps 版本。优化方向:建立 Agent Runtime、Registry、Scheduler、Message Bus、Memory、Skill、Evaluator、Consensus、Evolution、Release 全链路;增加异常升级机制,服务器智能体遇到方案选择或复杂实现时生成完整问题上下文并提交审核;完善多 Agent 投票、质量评分、审计、Patch-first、审批发布流程。生成后续实施 proposal,不直接进行生产发布。",
"intent": "patch_create",
"action_plan": [
"call POST /api/patch"
],
"execute": false,
"owner_approval_required": true,
"created_by": "gitops-write-bot",
"created_at": "2026-07-07T02:23:19Z"
}
@@ -1,9 +0,0 @@
{
"type": "branch",
"branch": "patch/collab-custom-gpt-20260706T104130Z",
"base": "main",
"created_by": "chatgpt-gitops-bot",
"created_at": "2026-07-06T10:41:30Z",
"direct_main_push": false,
"owner_approval_required": true
}
@@ -1,9 +0,0 @@
{
"type": "branch",
"branch": "patch/collab-local-codex-20260706T104130Z",
"base": "main",
"created_by": "codex-gitops-bot",
"created_at": "2026-07-06T10:41:30Z",
"direct_main_push": false,
"owner_approval_required": true
}
@@ -1,85 +0,0 @@
# GPT Actions Consequential Governance Report
Status: READY_FOR_REVIEW
Branch: patch/gpt-actions-consequential-governance
Commit: see final OpenClaw response for immutable commit rev
## Objective
Reduce unnecessary ChatGPT Actions confirmation prompts for low-risk GitOps write operations while preserving confirmation protection for high-risk operations.
## Changes
### Low-risk Actions
| operationId | x-openai-isConsequential | Path Scope |
|---|---:|---|
| commitReportFile | false | reports/** |
| commitDocsFile | false | docs/** |
| commitProposalFile | false | proposals/** |
| commitPatchEvidence | false | reports/**, patches/** |
### High-risk Actions
| operationId | x-openai-isConsequential |
|---|---:|
| commitFile | true |
| commitMainFile | true |
| createRollbackRequest | true |
| requestRollback | true |
| requestOrCreateTag | true |
| setModelPriority | true |
| registerModel | true |
## OpenAPI Operations Added
- commitReportFile
- commitDocsFile
- commitProposalFile
- commitPatchEvidence
- commitMainFile
- createRollbackRequest
## OpenAPI Operations Updated
- commitFile marked `x-openai-isConsequential: true`.
- commitChanges marked `x-openai-isConsequential: true`.
- createPatch marked `x-openai-isConsequential: true`.
- createBranch marked `x-openai-isConsequential: true`.
- requestOrCreateTag marked `x-openai-isConsequential: true`.
- requestRollback marked `x-openai-isConsequential: true`.
- registerModel marked `x-openai-isConsequential: true`.
- setModelPriority marked `x-openai-isConsequential: true`.
- planNaturalLanguageGitOpsAction marked `x-openai-isConsequential: true`.
## Security Validation
- No real tokens added.
- No secret values added.
- OpenAPI examples use placeholder values only.
- Action auth remains injected by GPT Builder Actions configuration.
- Low-risk policy rejects `main`, `runtime/secrets/**`, `.env`, `*.token`, `private/**`, `prod-secrets/**`, and secret-like content.
## Tests
- pytest: not run; remote Python environment does not have the `pytest` module installed.
- direct test runner: passed, 14 test functions.
- smoke: passed.
## Smoke Result
`python3 scripts/smoke_gpt_actions_consequential_governance.py` passed.
## Rollback Plan
Discard patch branch before merge, or revert the governance commit after merge.
## Risk
Medium. The change affects GPT Builder Action behavior and write API affordances while preserving high-risk confirmation gates.
## Next Step
Review OpenAPI schema in GPT Builder Actions and retest whether low-risk commit operations show Always allow or reduced confirmation prompts.
@@ -0,0 +1,37 @@
"""V1R2C2 autonomous recovery action monitor."""
from dataclasses import dataclass, field
from time import time
from typing import Any, Dict, List
import uuid
@dataclass
class ActionFailureEvent:
event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
action: str = ""
error: str = ""
context: Dict[str, Any] = field(default_factory=dict)
created_at: float = field(default_factory=time)
class ActionMonitor:
"""Capture control-plane action failures for recovery routing."""
def __init__(self):
self.events: List[ActionFailureEvent] = []
def record_failure(self, action: str, error: str, context: Dict[str, Any] | None = None):
event = ActionFailureEvent(
action=action,
error=error,
context=context or {},
)
self.events.append(event)
return event
def latest(self):
return self.events[-1] if self.events else None
def list_events(self):
return self.events
-131
View File
@@ -1,131 +0,0 @@
#!/usr/bin/env python3
"""Policy helpers for GPT Actions GitOps write operations.
The helpers intentionally avoid runtime credentials. They are used by tests and
smoke checks to keep the OpenAPI consequential-action split aligned with server
side write policy requirements.
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import PurePosixPath
import re
LOW_RISK_PATH_SCOPES: dict[str, tuple[str, ...]] = {
"commitReportFile": ("reports/",),
"commitDocsFile": ("docs/",),
"commitProposalFile": ("proposals/",),
"commitPatchEvidence": ("reports/", "patches/"),
}
HIGH_RISK_OPERATIONS: set[str] = {
"commitFile",
"commitMainFile",
"createRollbackRequest",
"requestRollback",
"requestOrCreateTag",
"commitRuntimeFile",
"commitConfigFile",
"mergeMain",
"adjustProductionModelPriority",
"setModelPriority",
"registerModel",
}
SECRET_PATH_PATTERNS: tuple[str, ...] = (
"runtime/secrets/",
".env",
"private/",
"prod-secrets/",
)
SECRET_CONTENT_PATTERNS: tuple[re.Pattern[str], ...] = (
re.compile(r"sk-[A-Za-z0-9_-]{8,}"),
re.compile(r"Bearer\s+\S+", re.IGNORECASE),
re.compile(r"BEGIN\s+PRIVATE\s+KEY", re.IGNORECASE),
re.compile(r"password\s*=\s*\S+", re.IGNORECASE),
re.compile(r"api_key\s*=\s*\S+", re.IGNORECASE),
re.compile(r"access_token\s*=\s*\S+", re.IGNORECASE),
re.compile(r"refresh_token\s*=\s*\S+", re.IGNORECASE),
)
@dataclass(frozen=True)
class PolicyResult:
allowed: bool
reason: str
def normalize_path(path: str) -> str:
raw = (path or "").replace("\\", "/").strip()
if not raw:
raise ValueError("path_required")
posix = PurePosixPath(raw)
if posix.is_absolute():
raise ValueError("absolute_path_forbidden")
if any(part in {"..", ".git"} for part in posix.parts):
raise ValueError("unsafe_path_forbidden")
return str(posix)
def is_main_branch(branch: str | None) -> bool:
value = (branch or "").strip()
return value in {"main", "master"} or value.startswith("main/") or value.startswith("master/")
def is_secret_path(path: str) -> bool:
normalized = normalize_path(path)
name = PurePosixPath(normalized).name
if name.endswith(".token"):
return True
return any(normalized == pattern.rstrip("/") or normalized.startswith(pattern) for pattern in SECRET_PATH_PATTERNS)
def contains_secret_like_content(content: str) -> bool:
text = content or ""
return any(pattern.search(text) for pattern in SECRET_CONTENT_PATTERNS)
def path_in_scope(operation_id: str, path: str) -> bool:
normalized = normalize_path(path)
scopes = LOW_RISK_PATH_SCOPES.get(operation_id, ())
return any(normalized.startswith(scope) for scope in scopes)
def validate_low_risk_commit(operation_id: str, branch: str, path: str, content: str = "") -> PolicyResult:
if operation_id not in LOW_RISK_PATH_SCOPES:
return PolicyResult(False, "unknown_low_risk_operation")
if is_main_branch(branch):
return PolicyResult(False, "low_risk_action_rejects_main")
try:
normalized = normalize_path(path)
except ValueError as exc:
return PolicyResult(False, str(exc))
if is_secret_path(normalized):
return PolicyResult(False, "secret_path_rejected")
if not path_in_scope(operation_id, normalized):
return PolicyResult(False, "path_out_of_scope")
if contains_secret_like_content(content):
return PolicyResult(False, "secret_like_content_detected")
return PolicyResult(True, "allowed")
def validate_write_policy(operation_id: str, branch: str, path: str, content: str = "") -> PolicyResult:
try:
normalized = normalize_path(path)
except ValueError as exc:
return PolicyResult(False, str(exc))
if is_secret_path(normalized):
return PolicyResult(False, "secret_path_rejected")
if contains_secret_like_content(content):
return PolicyResult(False, "secret_like_content_detected")
if operation_id in LOW_RISK_PATH_SCOPES:
return validate_low_risk_commit(operation_id, branch, normalized, content)
if operation_id in HIGH_RISK_OPERATIONS:
return PolicyResult(True, "high_risk_requires_confirmation")
return PolicyResult(False, "unknown_operation")
if __name__ == "__main__":
print("GPT Actions GitOps policy helper loaded")
@@ -1,110 +0,0 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
from pathlib import Path
import re
import sys
import yaml
ROOT = Path(__file__).resolve().parents[1]
SCHEMA = ROOT / "docs" / "gitops" / "openapi-chatgpt-actions.yaml"
LOW_RISK_FALSE = {
"commitReportFile",
"commitDocsFile",
"commitProposalFile",
"commitPatchEvidence",
}
HIGH_RISK_TRUE = {
"commitFile",
"commitMainFile",
"createRollbackRequest",
"requestRollback",
"requestOrCreateTag",
"setModelPriority",
"registerModel",
}
SECRET_LIKE = re.compile(
r"sk-[A-Za-z0-9_-]{8,}|Bearer\s+\S+|BEGIN\s+PRIVATE\s+KEY|password\s*=|api_key\s*=|access_token\s*=|refresh_token\s*=",
re.IGNORECASE,
)
def load_schema() -> dict:
if not SCHEMA.exists():
raise AssertionError(f"schema missing: {SCHEMA}")
return yaml.safe_load(SCHEMA.read_text(encoding="utf-8"))
def operations(schema: dict) -> dict[str, dict]:
found: dict[str, dict] = {}
for path_item in schema.get("paths", {}).values():
if not isinstance(path_item, dict):
continue
for method, operation in path_item.items():
if method.lower() not in {"get", "post", "put", "patch", "delete"}:
continue
if isinstance(operation, dict) and operation.get("operationId"):
found[operation["operationId"]] = operation
return found
def collect_examples(value):
if isinstance(value, dict):
for key, item in value.items():
if key in {"example", "examples"}:
yield item
yield from collect_examples(item)
elif isinstance(value, list):
for item in value:
yield from collect_examples(item)
def main() -> int:
schema = load_schema()
ops = operations(schema)
failures: list[str] = []
for op_id in sorted(LOW_RISK_FALSE):
operation = ops.get(op_id)
if not operation:
failures.append(f"missing low-risk operation: {op_id}")
elif operation.get("x-openai-isConsequential") is not False:
failures.append(f"{op_id} must set x-openai-isConsequential=false")
for op_id in sorted(HIGH_RISK_TRUE):
operation = ops.get(op_id)
if not operation:
failures.append(f"missing high-risk operation: {op_id}")
elif operation.get("x-openai-isConsequential") is not True:
failures.append(f"{op_id} must set x-openai-isConsequential=true")
security_text = json.dumps(schema.get("components", {}).get("securitySchemes", {}), ensure_ascii=False)
if SECRET_LIKE.search(security_text):
failures.append("securitySchemes contain secret-like content")
for example in collect_examples(schema):
if SECRET_LIKE.search(json.dumps(example, ensure_ascii=False)):
failures.append("schema examples contain secret-like content")
break
print("GPT Actions consequential governance smoke")
print("schema:", SCHEMA)
print("low-risk:", ", ".join(sorted(LOW_RISK_FALSE)))
print("high-risk:", ", ".join(sorted(HIGH_RISK_TRUE)))
if failures:
print("status: failed")
for failure in failures:
print("-", failure)
return 1
print("status: passed")
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -1,45 +0,0 @@
import ast
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
APP = ROOT / "active-runtime" / "app" / "aios_chatgpt_actions_api.py"
def module_tree():
return ast.parse(APP.read_text(encoding="utf-8"))
def test_runtime_defines_low_risk_route_map():
text = APP.read_text(encoding="utf-8")
for route in [
"/api/gitops/report-file",
"/api/gitops/docs-file",
"/api/gitops/proposal-file",
"/api/gitops/patch-evidence",
"/api/gitops/main-file",
"/api/gitops/rollback-request",
]:
assert route in text
def test_runtime_has_low_risk_and_main_handlers():
names = {node.name for node in ast.walk(module_tree()) if isinstance(node, ast.FunctionDef)}
assert "low_risk_file" in names
assert "main_file" in names
assert "secret_path" in names
assert "path_in_scope" in names
def test_runtime_policy_rejects_main_and_secret_paths_in_low_risk_handler():
text = APP.read_text(encoding="utf-8")
assert "low-risk write requires explicit non-main patch/request branch" in text
assert "Secret-like path rejected. Use runtime secret store or secret_ref." in text
assert "path_out_of_scope" in text
assert "Secret-like content detected. Use runtime secret store or secret_ref." in text
def test_runtime_preserves_legacy_commit_file_route():
text = APP.read_text(encoding="utf-8")
assert "if path == '/api/file': return self.file(data)" in text
assert "def file(self,data):" in text
@@ -1,70 +0,0 @@
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
from gitops_actions_policy import validate_low_risk_commit, validate_write_policy # noqa: E402
def assert_allowed(operation_id, branch, path, content="safe content"):
result = validate_low_risk_commit(operation_id, branch, path, content)
assert result.allowed, result.reason
def assert_rejected(operation_id, branch, path, reason, content="safe content"):
result = validate_low_risk_commit(operation_id, branch, path, content)
assert not result.allowed
assert result.reason == reason
def test_commit_report_file_scope():
assert_allowed("commitReportFile", "patch/gpt-actions-consequential-governance", "reports/example.md")
assert_rejected("commitReportFile", "patch/gpt-actions-consequential-governance", "docs/example.md", "path_out_of_scope")
def test_commit_docs_file_scope():
assert_allowed("commitDocsFile", "patch/gpt-actions-consequential-governance", "docs/architecture/example.md")
assert_rejected("commitDocsFile", "patch/gpt-actions-consequential-governance", "reports/example.md", "path_out_of_scope")
def test_commit_proposal_and_patch_evidence_scopes():
assert_allowed("commitProposalFile", "patch/gpt-actions-consequential-governance", "proposals/change.json")
assert_allowed("commitPatchEvidence", "patch/gpt-actions-consequential-governance", "reports/evidence.md")
assert_allowed("commitPatchEvidence", "patch/gpt-actions-consequential-governance", "patches/evidence.json")
assert_rejected("commitPatchEvidence", "patch/gpt-actions-consequential-governance", "docs/evidence.md", "path_out_of_scope")
def test_low_risk_actions_reject_main_branch():
for operation_id in ["commitReportFile", "commitDocsFile", "commitProposalFile", "commitPatchEvidence"]:
assert_rejected(operation_id, "main", "reports/example.md", "low_risk_action_rejects_main")
def test_all_write_actions_reject_secret_paths():
secret_paths = ["runtime/secrets/key.txt", ".env", "private/key.txt", "prod-secrets/config.json", "reports/value.token"]
for operation_id in ["commitReportFile", "commitDocsFile", "commitPatchEvidence", "commitFile", "commitMainFile", "createRollbackRequest"]:
for path in secret_paths:
result = validate_write_policy(operation_id, "patch/gpt-actions-consequential-governance", path)
assert not result.allowed
assert result.reason == "secret_path_rejected"
def test_secret_like_content_is_rejected():
samples = [
"Bearer abc",
"password=abc",
"api_key=abc",
"BEGIN PRIVATE KEY",
"access_token=abc",
"refresh_token=abc",
]
for sample in samples:
result = validate_low_risk_commit(
"commitReportFile",
"patch/gpt-actions-consequential-governance",
"reports/example.md",
sample,
)
assert not result.allowed
assert result.reason == "secret_like_content_detected"
-76
View File
@@ -1,76 +0,0 @@
import json
from pathlib import Path
import re
import yaml
ROOT = Path(__file__).resolve().parents[1]
SCHEMA = ROOT / "docs" / "gitops" / "openapi-chatgpt-actions.yaml"
def load_schema():
return yaml.safe_load(SCHEMA.read_text(encoding="utf-8"))
def operations_by_id(schema):
operations = {}
for path_item in schema.get("paths", {}).values():
if not isinstance(path_item, dict):
continue
for method, operation in path_item.items():
if method.lower() not in {"get", "post", "put", "patch", "delete"}:
continue
if isinstance(operation, dict) and operation.get("operationId"):
operations[operation["operationId"]] = operation
return operations
def _examples(value):
if isinstance(value, dict):
for key, item in value.items():
if key in {"example", "examples"}:
yield item
yield from _examples(item)
elif isinstance(value, list):
for item in value:
yield from _examples(item)
def test_low_risk_operations_are_non_consequential():
ops = operations_by_id(load_schema())
for op_id in ["commitReportFile", "commitDocsFile", "commitProposalFile", "commitPatchEvidence"]:
assert op_id in ops
assert ops[op_id].get("x-openai-isConsequential") is False
def test_high_risk_operations_remain_consequential():
ops = operations_by_id(load_schema())
for op_id in [
"commitFile",
"commitMainFile",
"createRollbackRequest",
"requestRollback",
"requestOrCreateTag",
"setModelPriority",
"registerModel",
]:
assert op_id in ops
assert ops[op_id].get("x-openai-isConsequential") is True
def test_legacy_commit_file_is_preserved_as_consequential():
ops = operations_by_id(load_schema())
assert ops["commitFile"].get("x-openai-isConsequential") is True
def test_security_schemes_and_examples_do_not_embed_real_credentials():
schema = load_schema()
secret_like = re.compile(
r"sk-[A-Za-z0-9_-]{8,}|BEGIN\s+PRIVATE\s+KEY|password\s*=|api_key\s*=|access_token\s*=|refresh_token\s*=",
re.IGNORECASE,
)
checked = json.dumps(schema.get("components", {}).get("securitySchemes", {}), ensure_ascii=False)
for value in _examples(schema):
checked += "\n" + json.dumps(value, ensure_ascii=False)
assert not secret_like.search(checked)