Add reliable execution reporting and coordinated dashboard views

This commit is contained in:
aiagent
2026-10-19 04:29:33 -07:00
parent 547f62fa53
commit c2dfe311f7
41 changed files with 3867 additions and 107 deletions
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""Persist and deliver an execution report; never mark integration accepted."""
import argparse
import hashlib
import json
from pathlib import Path
import re
import sys
import time
from control import Client,APIError
from worker import atomic_json,session_lock
def deliver_file(client,path,now=None):
now=time.time() if now is None else now
with session_lock('task-report:'+str(path.resolve())):
record=json.loads(path.read_text(encoding='utf-8'))
if record['status']!='pending' or record.get('next_retry',0)>now:
return record
try:
receipt=client.call('/api/task-reports',record['payload'])
except Exception as exc:
record['attempts']=record.get('attempts',0)+1
terminal=isinstance(exc,APIError) and exc.status in (400,403,409)
record['status']='blocked' if terminal or record['attempts']>=10 else 'pending'
record['next_retry']=now+min(60,2**record['attempts'])
record['error']='Report delivery requires inspection' if record['status']=='blocked' else 'Temporary delivery interruption'
atomic_json(path,record)
return record
record.update(status='accepted',receipt=receipt,accepted_at=now)
record.pop('error',None)
atomic_json(path,record)
return record
def submit(cfg,payload):
root=Path(cfg['outbox_dir']).expanduser().resolve()
root.mkdir(parents=True,exist_ok=True,mode=0o700)
canonical=json.dumps(payload,sort_keys=True,ensure_ascii=True)
report_id=payload.get('report_id') or 'report-'+hashlib.sha256(canonical.encode()).hexdigest()[:32]
if not isinstance(report_id,str) or not re.fullmatch(r'[A-Za-z0-9_.:-]{1,128}',report_id):
raise ValueError('Invalid report id')
payload={**payload,'report_id':report_id}
path=root/(hashlib.sha256(report_id.encode()).hexdigest()+'.json')
if path.is_symlink():raise ValueError('Report file cannot be a symlink')
with session_lock('task-report:'+str(path)):
if path.exists():
record=json.loads(path.read_text(encoding='utf-8'))
if record['payload']!=payload:raise ValueError('Report id already used for different content')
else:
atomic_json(path,{'status':'pending','created':time.time(),'payload':payload,'attempts':0})
return deliver_file(Client(cfg['control']),path)
def flush(cfg):
root=Path(cfg['outbox_dir']).expanduser().resolve()
root.mkdir(parents=True,exist_ok=True,mode=0o700)
client=Client(cfg['control'])
counts={'pending':0,'accepted':0,'blocked':0,'superseded':0}
records=[]
for path in root.glob('*.json'):
if not re.fullmatch(r'[a-f0-9]{64}\.json',path.name):continue
if path.is_symlink():raise ValueError('Unexpected report symlink')
records.append((path,json.loads(path.read_text(encoding='utf-8'))))
completed={}
for _,record in records:
payload=record['payload']
if payload['stage']=='execution_completed' and record['status'] in ('pending','accepted'):
key=(payload['task_id'],payload['session_id'])
completed[key]=max(completed.get(key,0),record['created'])
for path,record in records:
payload=record['payload'];key=(payload['task_id'],payload['session_id'])
if record['status']=='pending' and payload['stage']!='execution_completed' and record['created']<completed.get(key,0):
with session_lock('task-report:'+str(path.resolve())):
current=json.loads(path.read_text(encoding='utf-8'))
if current['status']=='pending':
current.update(status='superseded',superseded_at=time.time())
atomic_json(path,current)
record.update(current)
pending=sorted(((path,r) for path,r in records if r['status']=='pending'),
key=lambda item:(item[1]['payload']['stage']!='execution_completed',item[1]['created']))
for path,record in pending[:100]:
try:record.update(deliver_file(client,path))
except (BlockingIOError,PermissionError):continue
for _,record in records:
counts[record['status']]=counts.get(record['status'],0)+1
return counts
def main():
p=argparse.ArgumentParser(description=__doc__)
p.add_argument('--config',required=True)
p.add_argument('--task-id',required=True)
p.add_argument('--session-id',required=True)
p.add_argument('--stage',choices=['execution_completed','blocked','progress','failed'],required=True)
p.add_argument('--progress',required=True)
p.add_argument('--summary',required=True)
p.add_argument('--evidence',action='append',default=[])
p.add_argument('--version')
p.add_argument('--report-id')
p.add_argument('--needs-attention',action='store_true')
a=p.parse_args()
cfg=json.loads(Path(a.config).read_text(encoding='utf-8-sig'))
payload={'task_id':a.task_id,'session_id':a.session_id,'stage':a.stage,
'current_progress':a.progress,'summary':a.summary,'evidence':a.evidence,
'needs_attention':a.needs_attention}
if a.version:payload['version']=a.version
if a.report_id:payload['report_id']=a.report_id
result=submit(cfg,payload)
print(json.dumps({'report_id':result['payload']['report_id'],'delivery':result['status'],
'execution_report_only':True,'integration_accepted':False},ensure_ascii=True))
return 0 if result['status']=='accepted' else 75
if __name__=='__main__':sys.exit(main())