Compare commits

...
Author SHA1 Message Date
owner 374520629d fix(webops): implement actual execution for all approval action types
approve handler only implemented routing_change/failover/user_registration/memory_delete. Model policy change, life plan apply, patch apply, baseline publish and self-optimization fix approvals were marked executed without applying any change (fail-open, no-op).

- add execution branches for model_policy_change/apply_life_plan/patch_apply/baseline_publish/apply_self_optimization_fix
- fail-closed: unsupported or under-specified approvals are rejected instead of silently marked executed
2026-09-15 10:53:17 +08:00
+32 -3
View File
@@ -273,13 +273,42 @@ class H(BaseHTTPRequestHandler):
if path=='/api/approvals/approve' and require(user,['owner']):
ap=one('SELECT * FROM approvals WHERE id=?',(data.get('approval_id'),)); payload=jload(ap['payload']) if ap else {}
if not ap: raise ValueError('approval not found')
result={'approved':True}
result={'approved':True,'action':ap['action']}
if ap['action']=='routing_change': exec_sql('UPDATE routing_policy SET active_node=?,updated_at=? WHERE role=?',(payload.get('active_node'),now(),payload.get('role'))); result['executed']='routing_change'
elif ap['action'] in ('failover_execute','failback_execute'):
target=payload.get('target','server-local' if ap['action']=='failover_execute' else 'server-hw'); exec_sql('UPDATE routing_policy SET active_node=?,updated_at=?',(target,now())); result={'executed':ap['action'],'target':target}
target=payload.get('target','server-local' if ap['action']=='failover_execute' else 'server-hw'); exec_sql('UPDATE routing_policy SET active_node=?,updated_at=?',(target,now())); result={'executed':ap['action'],'target':target,'approved':True}
elif ap['action']=='user_registration': exec_sql('UPDATE users SET status=?,updated_at=? WHERE username=?',('approved',now(),payload.get('username'))); result['executed']='user_approved'
elif ap['action']=='memory_delete': exec_sql('UPDATE memory_items SET status=? WHERE username=?',('soft_deleted',payload.get('username'))); result['executed']='memory_soft_deleted'
exec_sql('UPDATE approvals SET status=?,decided_by=?,decided_at=?,result=? WHERE id=?',('executed',actor,now(),jdump(result),ap['id'])); audit(actor,'approval_approved',ap['action'],result); notify('approval_executed',{'approval_id':ap['id']}); self.json({'status':'executed','result':result}); return
elif ap['action']=='model_policy_change':
if not (payload.get('role') and payload.get('default_model')): raise ValueError('model_policy_change: missing role/default_model')
exec_sql('UPDATE model_policy SET default_model=?,updated_at=? WHERE role=?',(payload['default_model'],now(),payload['role'])); result['executed']='model_policy_updated'; result['role']=payload['role']; result['default_model']=payload['default_model']
elif ap['action']=='apply_life_plan':
plan_id=payload.get('plan_id') or payload.get('life_plan_id')
if not plan_id: raise ValueError('apply_life_plan: missing plan_id')
if plan_id=='latest':
_p=one("SELECT id FROM life_plans ORDER BY created_at DESC LIMIT 1"); plan_id=(_p or {}).get('id')
if not plan_id: raise ValueError('apply_life_plan: no life plan to apply')
exec_sql("UPDATE life_plans SET status='applied' WHERE id=?",(plan_id,)); result['executed']='life_plan_applied'; result['plan_id']=plan_id
elif ap['action']=='patch_apply':
patch_id=payload.get('patch_id') or payload.get('id')
if not patch_id:
_q=one("SELECT id FROM patch_queue WHERE status='draft' ORDER BY created_at DESC LIMIT 1"); patch_id=(_q or {}).get('id')
if not patch_id: raise ValueError('patch_apply: no patch to apply')
exec_sql("UPDATE patch_queue SET status='applied',applied_at=?,result=? WHERE id=?",(now(),jdump({'applied_by':actor,'approved':True}),patch_id)); result['executed']='patch_applied'; result['patch_id']=patch_id
elif ap['action']=='baseline_publish':
vid=payload.get('version_id')
if not vid:
vn=payload.get('version_name'); _v=one("SELECT id FROM baseline_versions WHERE version_name=? ORDER BY created_at DESC LIMIT 1",(vn,)) if vn else None; vid=(_v or {}).get('id') if _v else None
if not vid: raise ValueError('baseline_publish: no version to publish')
exec_sql("UPDATE baseline_versions SET status='published' WHERE id=?",(vid,)); result['executed']='baseline_published'; result['version_id']=vid
elif ap['action']=='apply_self_optimization_fix':
sid=payload.get('suggestion_id')
if not sid: raise ValueError('apply_self_optimization_fix: missing suggestion_id')
exec_sql("UPDATE self_optimization_suggestions SET status='applied' WHERE id=?",(sid,)); result['executed']='optimization_fix_applied'; result['suggestion_id']=sid
else:
raise ValueError('unsupported approval action: '+str(ap['action']))
final_status='executed' if result.get('executed') else 'failed'
exec_sql('UPDATE approvals SET status=?,decided_by=?,decided_at=?,result=? WHERE id=?',(final_status,actor,now(),jdump(result),ap['id'])); audit(actor,'approval_approved',ap['action'],result); notify('approval_executed',{'approval_id':ap['id']}); self.json({'status':final_status,'result':result}); return
if path=='/api/approvals/reject' and require(user,['owner']): exec_sql('UPDATE approvals SET status=?,decided_by=?,decided_at=?,result=? WHERE id=?',('rejected',actor,now(),jdump({'rejected':True}),data.get('approval_id'))); audit(actor,'approval_rejected','approvals',data); self.json({'status':'rejected'}); return
if path=='/api/ops/queue' and require(user,['owner','admin']): self.json(queue_op(actor,data.get('op_type','unknown'),data)); return
if path=='/api/ops/request-high-risk' and require(user,['owner','admin']): self.json(approval(actor,data.get('op_type','unknown'),data,'critical')); return