d4b53bfb08
Docker / build (push) Has been cancelled
Test / pytest (ubuntu-latest, 3.12) (push) Has been cancelled
Test / pytest (windows-latest, 3.11) (push) Has been cancelled
Test / pytest (windows-latest, 3.12) (push) Has been cancelled
Lint / ruff (push) Has been cancelled
Test / pytest (ubuntu-latest, 3.11) (push) Has been cancelled
Build / build (push) Has been cancelled
97 lines
6.8 KiB
Python
97 lines
6.8 KiB
Python
"""Budgeted real public repository and route contracts; no business database."""
|
|
import asyncio
|
|
import copy
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
LOG = ROOT / '.hermes/test-runtime/unified-execution/b03-host'
|
|
|
|
def scenario():
|
|
from contextlib import asynccontextmanager
|
|
from unittest.mock import patch
|
|
from gateway.services import recoverable_runs as service
|
|
from gateway.routes import recoverable_runs as route
|
|
from gateway.models.thread import RecoverableRunRespond
|
|
from gateway.services.pending_display import approval_display
|
|
from gateway.services.projection_event_adapter import pending_input_projection_event
|
|
sentinel = 'private-canary'
|
|
value = {'action_requests': [{'name': 'execute', 'args': {'command': 'printf ' + sentinel}, 'description': sentinel}], 'review_configs': [{'action_name': 'execute', 'allowed_decisions': ['approve', 'edit', 'reject']}], 'hidden': sentinel}
|
|
pending = dict(kind='tool_approval', payload=value, safe_payload=value, interrupt_id='i', payload_hash='a' * 64, status='pending', run_id='r')
|
|
row = dict(run_id='r', turn_id='t', run_request_id='q', thread_id='t', command_kind='new', status='awaiting_input', message_id='m', pending_input=pending)
|
|
item = dict(type='approval_request', item_id='a', display_payload=value, interrupt_id='i', payload_hash='a' * 64)
|
|
message = dict(payload={'items': [item]}, message_id='m', projection_version=1, message_index=1)
|
|
frozen = copy.deepcopy([pending, row, message])
|
|
checks = {}
|
|
def safe(obj):
|
|
return sentinel not in json.dumps(obj)
|
|
checks['projection'] = safe(service.RecoverableRunRepository.projection(row))
|
|
checks['event'] = safe(pending_input_projection_event(pending, message_id='m', run_id='r', source_sequence=1).model_dump(mode='json'))
|
|
class DB:
|
|
async def execute_fetchone(self, *args): return row
|
|
async def execute_fetchall(self, query, *args): return [message] if 'SELECT payload,' in query else [row]
|
|
async def fetch(self, query, *args):
|
|
if 'FROM conversation_messages' in query: return [message]
|
|
if 'FROM conversation_run_pending_inputs' in query: return [pending]
|
|
return [dict(row, projection_sequence=0)]
|
|
async def fetchrow(self, *args): return {'snapshot_version': 1}
|
|
async def connection(): return DB()
|
|
@asynccontextmanager
|
|
async def transaction(**kwargs): yield DB()
|
|
async def reads():
|
|
repo = service.RecoverableRunRepository()
|
|
with patch('gateway.database.get_app_connection', connection), patch.object(service, '_transaction', transaction):
|
|
checks['get'] = safe(await repo.get('r', 't', 'u'))
|
|
checks['list'] = safe(await repo.list('t', 'u', active_only=False, limit=1))
|
|
checks['snapshot'] = safe(await repo.snapshot_thread('t', 'u'))
|
|
checks['history'] = safe(await repo.list_authoritative_messages('t', 'u'))
|
|
asyncio.run(reads())
|
|
for name, args, reviewable in [('execute', {'command': 'printf ' + sentinel}, False), ('mystery', {'path': '/workspace/a'}, False), ('read_file', {'file_path': '/workspace/a'}, True), ('read_file', {'file_path': '/workspace/a', 'secret': sentinel}, False)]:
|
|
payload = dict(action_requests=[dict(name=name, args=args)], review_configs=[dict(action_name=name, allowed_decisions=['approve', 'edit', 'reject'])])
|
|
for decision in ['approve', 'edit', 'reject']:
|
|
for scope in ['current', 'thread']:
|
|
body = RecoverableRunRespond.model_construct(decision_request_id='00000000-0000-0000-0000-000000000001', interrupt_id='i', payload_hash='a' * 64, decisions=None, decision=decision, approval_scope=scope)
|
|
try:
|
|
route._validated_resume_value(dict(kind='tool_approval', payload=payload), body)
|
|
accepted = True
|
|
except service.RecoverableRunError:
|
|
accepted = False
|
|
checks[f'{name}-{len(args)}-{decision}-{scope}'] = accepted == (decision == 'reject' or (reviewable and decision == 'approve'))
|
|
public = approval_display(payload)
|
|
checks[f'display-{name}-{len(args)}'] = public['review_configs'][0]['allowed_decisions'] == (['approve', 'reject'] if reviewable else ['reject'])
|
|
checks['unchanged'] = frozen == [pending, row, message]
|
|
body = RecoverableRunRespond(decision_request_id='00000000-0000-0000-0000-000000000001', interrupt_id='i', payload_hash='a' * 64, decision='approve')
|
|
checks['authorized_auto_independent'] = route._validated_resume_value(pending, body, authorized_auto=True) == {'decisions': [{'type': 'approve'}]}
|
|
for reviews in [[], [{'action_name': 'execute', 'allowed_decisions': ['reject']}]]:
|
|
try:
|
|
route._validated_resume_value(dict(pending, payload=dict(value, review_configs=reviews)), body, authorized_auto=True)
|
|
checks['auto-fail-closed-' + str(len(reviews))] = False
|
|
except service.RecoverableRunError:
|
|
checks['auto-fail-closed-' + str(len(reviews))] = True
|
|
from gateway.contracts.conversation_items import ApprovalRequestItem
|
|
from gateway.services.pending_display import public_items
|
|
typed = ApprovalRequestItem(item_id='item_' + 'a' * 32, item_sequence=1, revision=1, actor={'type': 'system', 'id': 'approval'}, status='in_progress', source={'protocol': 'run-v1', 'first_sequence': 1, 'last_sequence': 1, 'event_count': 1}, parent_run_id='r', interrupt_id='i', payload_hash='a' * 64, pending_input_ref='pending:r:i', display_payload=value)
|
|
cleaned = public_items([typed.model_dump(mode='json')])[0]
|
|
checks['history_schema_valid'] = ApprovalRequestItem.model_validate(cleaned).item_sequence == 1 and safe(cleaned)
|
|
print(json.dumps(checks), flush=True)
|
|
return all(checks.values())
|
|
|
|
if __name__ == '__main__':
|
|
if '--child' in sys.argv:
|
|
raise SystemExit(0 if scenario() else 1)
|
|
LOG.mkdir(parents=True, exist_ok=True)
|
|
ledger = LOG / 'pending-public-attempts.jsonl'
|
|
records = [json.loads(x) for x in ledger.read_text().splitlines()] if ledger.exists() else []
|
|
attempt = 1 + sum(r['phase'] == 'start' for r in records)
|
|
assert attempt <= 5
|
|
with ledger.open('a') as f: f.write(json.dumps(dict(phase='start', attempt=attempt, limit=5, node='public_reads_and_route_decisions')) + '\n')
|
|
env = dict(HOME=str(LOG), PATH='/usr/bin:/bin', PYTHONPATH=str(ROOT / 'Ai4Sci-Web'), PYTHON_DOTENV_DISABLED='1', PYTHONDONTWRITEBYTECODE='1')
|
|
result = subprocess.run([str(ROOT / 'Ai4Sci-Web/.venv/bin/python'), __file__, '--child'], env=env, capture_output=True, text=True, timeout=30)
|
|
output = result.stdout + result.stderr
|
|
(LOG / f'pending-public-{attempt}.log').write_text(output)
|
|
with ledger.open('a') as f: f.write(json.dumps(dict(phase='result', attempt=attempt, exit=result.returncode)) + '\n')
|
|
print(output)
|
|
raise SystemExit(result.returncode) |