Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-28 09:37:31

0001 """The MCP tool set served on the external face (docs/live-data-access.md).
0002 
0003 The relay forwards only the tools named here: tools/list results are
0004 filtered to the set and a tools/call for any other name is refused before
0005 it crosses the tunnel. The set is the read-only tools plus one write,
0006 ai_propose_ping, which creates a proposal that takes effect only when a
0007 person accepts it on the alarm dashboard; it is the way a named collaborator
0008 raises an obligation from outside. Not served: the testbed tools (swf_*),
0009 which control and inspect internal processes; the PCS intake and lifecycle
0010 mutations, driven by the production bot; ai_decide_proposal, whose approve
0011 executes the proposal; and epic_register_ai_assessment, written by the
0012 assessment services. A new monitor tool reaches the outside only when it is
0013 added here.
0014 """
0015 
0016 import json
0017 
0018 from django.http import HttpResponse
0019 
0020 _RUCIO = (
0021     'list_scopes', 'list_dids', 'list_files', 'list_content', 'get_did_metadata',
0022     'summarize_datasets', 'get_account_limits', 'get_account_usage', 'list_rses',
0023     'get_rse_usage', 'list_rules', 'get_rule_locks', 'list_file_replicas',
0024     'extract_scope',
0025 )
0026 
0027 EXTERNAL_TOOLS = frozenset(
0028     [
0029         'get_server_instructions',
0030         # PanDA production, read-only
0031         'panda_get_activity', 'panda_list_jobs', 'panda_diagnose_jobs',
0032         'panda_list_tasks', 'panda_error_summary', 'panda_study_job',
0033         'panda_list_queues', 'panda_get_queue', 'panda_resource_usage',
0034         'panda_harvester_workers',
0035         # Physics Configuration System, reads
0036         'pcs_list_tags', 'pcs_get_tag', 'pcs_search_tags',
0037         'pcs_dataset_list', 'pcs_dataset_get', 'pcs_data_provenance',
0038         'pcs_prodtask_list', 'pcs_prodtask_get', 'pcs_prodtask_artifact',
0039         # Campaigns and the action stream
0040         'epicprod_campaign_status', 'epicprod_list_actions',
0041         # Snapper operational history
0042         'snapper_latest', 'snapper_state_at', 'snapper_component_history',
0043         'snapper_changes_between', 'snapper_context_around', 'snapper_series',
0044         'snapper_cut_summary',
0045         # AI content and proposals: reads, and the one write, proposing a ping
0046         'epic_get_ai_content', 'ai_list_proposals', 'ai_propose_ping',
0047     ]
0048     + [f'jlab_rucio_{s}' for s in _RUCIO]
0049     + [f'bnl_rucio_{s}' for s in _RUCIO]
0050 )
0051 
0052 
0053 def _rpc_error(req_id, message):
0054     body = {'jsonrpc': '2.0', 'id': req_id,
0055             'error': {'code': -32602, 'message': message}}
0056     return HttpResponse(json.dumps(body), content_type='application/json')
0057 
0058 
0059 def refuse_call(body_bytes):
0060     """A JSON-RPC error response if the request calls a tool outside the
0061     external set, else None. Unparseable bodies pass through for the
0062     upstream to answer."""
0063     try:
0064         req = json.loads(body_bytes or b'')
0065     except (ValueError, UnicodeDecodeError):
0066         return None
0067     reqs = req if isinstance(req, list) else [req]
0068     for r in reqs:
0069         if not isinstance(r, dict) or r.get('method') != 'tools/call':
0070             continue
0071         name = (r.get('params') or {}).get('name')
0072         if name not in EXTERNAL_TOOLS:
0073             return _rpc_error(r.get('id'),
0074                               f'tool {name!r} is not served on the external face')
0075     return None
0076 
0077 
0078 def filter_tools_list(body_bytes, content_type):
0079     """Drop tools outside the external set from a tools/list result."""
0080     if 'json' not in (content_type or ''):
0081         return body_bytes
0082     try:
0083         resp = json.loads(body_bytes)
0084     except (ValueError, UnicodeDecodeError):
0085         return body_bytes
0086     resps = resp if isinstance(resp, list) else [resp]
0087     changed = False
0088     for r in resps:
0089         tools = (r.get('result') or {}).get('tools') if isinstance(r, dict) else None
0090         if isinstance(tools, list):
0091             r['result']['tools'] = [t for t in tools if t.get('name') in EXTERNAL_TOOLS]
0092             changed = True
0093     return json.dumps(resp).encode() if changed else body_bytes