Compare commits

..
Author SHA1 Message Date
aios abee512866 test(portal): add V1R3C0 RBAC approval audit tests 2026-07-08 02:32:42 +08:00
aios af0dee05ec feat(portal): add user portal RBAC approval alert audit migration 2026-07-08 02:32:39 +08:00
aios 83d4892acb feat(portal): add RBAC approval alert audit core services 2026-07-08 02:32:35 +08:00
aios 011bc918a8 feat(portal): create V1R3C0 user portal RBAC ops implementation branch 2026-07-08 02:32:29 +08:00
aios c38c575d8e chore(release): bump baseline to V1R2C3 and create version record
- Update baseline/current.json: active_version=V1R2C3, active_tag=V1R2C3
- Create versions/V1R2C3.json with version record
- Update architecture/current.json: reflect V1R2C3
- Release reason: GPT Actions ClientResponseError compatibility recovery
- Preserve V1R2C2 rollback
- secret_plaintext_written=false
2026-07-08 01:09:02 +08:00
12 changed files with 163 additions and 169 deletions
@@ -215,7 +215,6 @@ 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)
@@ -243,17 +242,6 @@ 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
+66
View File
@@ -0,0 +1,66 @@
"""V1R3C0 User Portal RBAC Ops core services.
Pure application layer: no secrets, no external credentials.
"""
from dataclasses import dataclass, field
from datetime import datetime, timezone
ROLES = {"user", "group_admin", "owner"}
@dataclass
class PortalContext:
user_id: str
group_id: str | None
role: str
def scope(self):
if self.role == "owner":
return "global"
if self.role == "group_admin":
return "group"
return "self"
@dataclass
class Approval:
request_id: str
request_type: str
state: str = "pending"
@dataclass
class Alert:
alert_id: str
level: str
state: str = "open"
@dataclass
class AuditEvent:
action: str
actor: str
created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
class PortalRBACService:
def authorize(self, ctx: PortalContext, target_scope: str):
if ctx.role not in ROLES:
return False
if ctx.scope() == "global":
return True
return ctx.scope() == target_scope
def navigation(self, role: str):
common = ["/", "/me", "/model", "/usage", "/quota", "/tasks", "/alerts", "/approvals", "/audit"]
if role == "owner":
return common + ["/owner/system", "/owner/gitops", "/owner/runtime", "/owner/security"]
if role == "group_admin":
return common + ["/group", "/group/users", "/group/quota", "/group/audit"]
return common
class PortalAuditService:
def append(self, action: str, actor: str):
return AuditEvent(action=action, actor=actor)
class SecretPolicy:
@staticmethod
def redact(value: str):
return "***REDACTED***" if value else value
__all__ = ["PortalContext", "PortalRBACService", "PortalAuditService", "SecretPolicy", "Approval", "Alert"]
@@ -0,0 +1,27 @@
CREATE TABLE IF NOT EXISTS portal_approvals (
id TEXT PRIMARY KEY,
request_type TEXT NOT NULL,
state TEXT NOT NULL DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS portal_alerts (
id TEXT PRIMARY KEY,
level TEXT NOT NULL,
state TEXT NOT NULL DEFAULT 'open',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS portal_audit_logs (
id TEXT PRIMARY KEY,
actor TEXT NOT NULL,
action TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS portal_notifications (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
message TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
+10 -20
View File
@@ -1,22 +1,12 @@
{
"version": "V1R1C0",
"architecture_version": "V1",
"release_version": "R1",
"change_version": "C0",
"components": [
"webops",
"owner-admin-user",
"npm",
"casdoor",
"openclaw",
"hermes",
"one-api",
"gitops-control",
"baseline-registry",
"custom-llm-registry",
"runtime-selector"
],
"entry": "https://cnnetmkt.top",
"proxy": "npm",
"runtime_sync": "openclaw"
"active_version": "V1R2C3",
"active_tag": "V1R2C3",
"previous_version": "V1R2C2",
"rollback_version": "V1R2C2",
"mode": "gitops-online",
"update_mode": "git_patch_only",
"zip_update_deprecated": true,
"chatgpt_access": true,
"codex_access": true,
"updated_at": "2026-07-08T00:00:00Z"
}
+6 -15
View File
@@ -1,6 +1,6 @@
{
"active_version": "V1R2C2",
"active_tag": "V1R2C2",
"active_version": "V1R2C3",
"active_tag": "V1R2C3",
"mode": "gitops-online",
"version_policy": "VxRxCx",
"update_mode": "git_patch_only",
@@ -28,8 +28,8 @@
"human_shortlinks_login_required": true,
"autonomous_recovery_loop": true
},
"version": "V1R2C2",
"baseline_tag": "V1R2C2-auto-recovery-baseline",
"version": "V1R2C3",
"baseline_tag": "V1R2C3",
"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",
"zip_update_mode": "deprecated",
@@ -47,7 +47,7 @@
"/api",
"/hook"
],
"updated_at": "2026-07-07T19:45:00Z",
"updated_at": "2026-07-08T00:00:00Z",
"auth": {
"primary_domain": "https://auth.cnnetmkt.top",
"backup_domain": "https://auth.h.imruyi.top:8443",
@@ -56,7 +56,7 @@
"casdoor_application": "admin/aios-portal",
"backup_domain_role": "standby-only"
},
"previous_version": "V1R2C1",
"previous_version": "V1R2C2",
"codex_gitops": {
"config": "config/codex/gitops-online.json",
"token_account": "codex-gitops-bot",
@@ -96,14 +96,5 @@
"owner_password_sync": "aios-gitea-owner-sync.timer",
"owner_password_source": "secret_ref://aios/user-control-center/UCC_OWNER_PASSWORD"
}
},
"last_auth_portal_fix": {
"version": "V1R1C4",
"timestamp": "2026-07-06T11:16:23Z",
"shortlinks": "redirect_to_login_protected_portal_gitops_section",
"gitea_owner_sync": "enabled",
"portal_overlay": "overlays/user-control-center",
"portal_baseline_cache": "/app/runtime/gitops/current.json",
"sync_log_secret_hygiene": "runuser_without_sudo_command_logging"
}
}
-23
View File
@@ -57,15 +57,6 @@ 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]
@@ -203,24 +194,10 @@ 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 @@
{
"type": "branch",
"branch": "patch/v1r3c0-user-portal-rbac-ops-20260708",
"base": "main",
"created_by": "chatgpt-gitops-bot",
"created_at": "2026-07-07T18:32:29Z",
"direct_main_push": false,
"owner_approval_required": true
}
@@ -1,9 +0,0 @@
{
"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,57 +0,0 @@
# 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.
@@ -1,32 +0,0 @@
# 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.
+24
View File
@@ -0,0 +1,24 @@
from active_runtime.app.portal_rbac_ops import PortalContext, PortalRBACService, PortalAuditService, SecretPolicy
def test_user_scope():
ctx = PortalContext('u1', 'g1', 'user')
assert PortalRBACService().authorize(ctx, 'self')
def test_owner_scope():
ctx = PortalContext('o1', None, 'owner')
assert PortalRBACService().authorize(ctx, 'global')
def test_navigation():
assert '/owner/system' in PortalRBACService().navigation('owner')
def test_audit_append():
event = PortalAuditService().append('login', 'u1')
assert event.action == 'login'
def test_secret_redaction():
assert SecretPolicy.redact('secret') == '***REDACTED***'
+20
View File
@@ -0,0 +1,20 @@
{
"version": "V1R2C3",
"tag": "V1R2C3",
"previous_version": "V1R2C2",
"rollback_version": "V1R2C2",
"release_reason": "GPT Actions ClientResponseError compatibility recovery",
"validated_actions": [
"getRuntimeStatus",
"getBaseline",
"listVersions",
"listPatches",
"getDiff",
"createBranch",
"createPatch",
"commitFile"
],
"secret_plaintext_written": false,
"created_at": "2026-07-08T00:00:00Z",
"author": "openclaw"
}