Add event-driven reporting and durable failure recovery hooks

This commit is contained in:
aiagent
2026-10-19 03:13:33 -07:00
parent 579dece104
commit a4cf70d82b
18 changed files with 711 additions and 8 deletions
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Post bounded failure/recovery events, or wrap an explicitly authorized local check."""
import argparse
import hashlib
import json
from pathlib import Path
import subprocess
import sys
from control import Client
from worker import atomic_json
def main():
p=argparse.ArgumentParser(description=__doc__)
p.add_argument('--config',required=True)
p.add_argument('--source',required=True)
p.add_argument('--event-id',required=True)
p.add_argument('--severity',choices=['info','warning','critical'],default='warning')
p.add_argument('--summary',required=True)
p.add_argument('--evidence',default='')
p.add_argument('--task-id',default='')
p.add_argument('--status',choices=['firing','resolved'],default='firing')
p.add_argument('--timeout',type=int,default=300)
p.add_argument('command',nargs=argparse.REMAINDER)
a=p.parse_args()
cfg=json.loads(Path(a.config).read_text(encoding='utf-8-sig'))
client=Client(cfg.get('control',cfg))
command=a.command[1:] if a.command[:1]==['--'] else a.command
status=a.status
code=0
if command:
if not 1<=a.timeout<=86400:raise ValueError('Timeout must be bounded')
# Inherit output for the operator, never send raw logs or command arguments.
# No shell and no command is ever accepted by the HTTP service.
try:code=subprocess.run(command,timeout=a.timeout).returncode
except subprocess.TimeoutExpired:code=124
except OSError:code=127
status='resolved' if code==0 else 'firing'
payload={'source':a.source,'event_id':a.event_id,'severity':a.severity,
'status':status,'summary':a.summary,'evidence':[a.evidence] if a.evidence else []}
if a.task_id:payload['task_id']=a.task_id
state_path=Path(a.config).resolve().with_suffix('.hook-check-state.json')
state=json.loads(state_path.read_text(encoding='utf-8')) if state_path.exists() else {}
key=a.source+':'+a.event_id
previous=state.get(key)
if command and code==0 and not previous:
print(json.dumps({'status':'ok','recovery_required':False}))
return 0
if status=='firing':
state[key]={'payload':payload,'accepted':False}
atomic_json(state_path,state)
try:
if status=='resolved' and previous and not previous.get('accepted'):
client.call('/api/hooks',previous['payload'])
result=client.call('/api/hooks',payload)
except Exception:
print('Hook delivery failed; original command exit='+str(code)+'. Inspect event delivery before retry.',file=sys.stderr)
return code or 75
state[key]={'payload':payload,'accepted':True}
atomic_json(state_path,state)
print(json.dumps(result,ensure_ascii=True))
return code
if __name__=='__main__':sys.exit(main())