File indexing completed on 2026-09-28 09:37:30
0001
0002 """One bounded live check of devcloud TeamComms authentication and streaming.
0003
0004 Uses the dedicated host login file. Creates an AI token for native acceptance
0005 in a private output file and a temporary human token, revoked during the check.
0006 Session and message records remain as labeled acceptance evidence.
0007 """
0008
0009 import argparse
0010 from html.parser import HTMLParser
0011 import json
0012 import os
0013 from pathlib import Path
0014 import re
0015 import time
0016 import uuid
0017
0018 import httpx
0019
0020
0021 class Form(HTMLParser):
0022 def __init__(self, text):
0023 super().__init__()
0024 self.csrf = None
0025 self.feed(text)
0026
0027 def handle_starttag(self, tag, attrs):
0028 attrs = dict(attrs)
0029 if tag == 'input' and attrs.get('name') == 'csrfmiddlewaretoken':
0030 self.csrf = attrs.get('value')
0031
0032
0033 def require(condition, message):
0034 if not condition:
0035 raise RuntimeError(message)
0036
0037
0038 def login(client, base, credential_file):
0039 values = {}
0040 for line in credential_file.read_text().splitlines():
0041 if '=' in line and not line.lstrip().startswith('#'):
0042 key, value = line.removeprefix('export ').split('=', 1)
0043 values[key.strip()] = value.strip().strip('\"\'')
0044 url = base + '/accounts/login/'
0045 page = client.get(url)
0046 page.raise_for_status()
0047 csrf = Form(page.text).csrf
0048 require(csrf, 'Login form has no CSRF token')
0049 response = client.post(url, data={
0050 'username': values['SWF_REMOTE_CLAUDE_USER'],
0051 'password': values['SWF_REMOTE_CLAUDE_PASSWORD'],
0052 'csrfmiddlewaretoken': csrf,
0053 }, headers={'Referer': url})
0054 response.raise_for_status()
0055 page = client.get(base + '/account/tokens/')
0056 require(page.status_code == 200 and '/accounts/login/' not in str(page.url)
0057 and 'name="label"' in page.text, 'Login did not reach the account tokens page')
0058 return page
0059
0060
0061 def issue(client, url, *, ai, service_kind=''):
0062 page = client.get(url)
0063 csrf = Form(page.text).csrf
0064 require(csrf, 'Tokens form has no CSRF token')
0065 data = {'label': ('TeamComms ' + service_kind if service_kind else
0066 'TeamComms live acceptance ' + ('AI' if ai else 'human')),
0067 'csrfmiddlewaretoken': csrf}
0068 if ai:
0069 data['teamcomms_ai'] = 'on'
0070 if service_kind:
0071 data['teamcomms_service_kind'] = service_kind
0072 page = client.post(url, data=data, headers={'Referer': url})
0073 page.raise_for_status()
0074 found = re.search(r'swfr_[A-Za-z0-9_-]{43}', page.text)
0075 require(found is not None, 'Token issuance did not return a token')
0076 raw = found.group(0)
0077 prefix = raw[5:13]
0078 row = next((s for s in re.findall(r'<tr\b[^>]*>.*?</tr>', page.text, re.S)
0079 if 'swfr_' + prefix in s), '')
0080 token_id = re.search(r'name="revoke" value="(\d+)"', row)
0081 require(token_id is not None, 'Issued token row has no revocation control')
0082 return raw, token_id.group(1)
0083
0084
0085 def events(response):
0086 current = {}
0087 for line in response.iter_lines():
0088 if not line:
0089 if current:
0090 yield current
0091 current = {}
0092 elif line.startswith('event: '):
0093 current['event'] = line[7:]
0094 elif line.startswith('data: '):
0095 current['data'] = json.loads(line[6:])
0096 elif line.startswith('id: '):
0097 current['id'] = line[4:]
0098
0099
0100 def main():
0101 parser = argparse.ArgumentParser(description=__doc__)
0102 parser.add_argument('--base', default='https://epic-devcloud.org/prod')
0103 parser.add_argument('--credentials', type=Path, default=Path('/home/admin/.swf-remote-claude-ec2dev.env'))
0104 parser.add_argument('--ai-token-file', type=Path, required=True)
0105 args = parser.parse_args()
0106 require(not args.ai_token_file.exists(), 'AI token output file already exists')
0107 base = args.base.rstrip('/')
0108 tc = base + '/teamcomms'
0109 with httpx.Client(timeout=35, follow_redirects=True) as browser, httpx.Client(timeout=35) as machine:
0110 login(browser, base, args.credentials)
0111 response = browser.get(tc + '/api/whoami')
0112 require(response.status_code == 200, f'Browser identity HTTP {response.status_code}')
0113 human = response.json()
0114 require(machine.get(tc + '/api/whoami').status_code == 401, 'Anonymous request was not rejected')
0115 refused = browser.post(tc + '/api/comms/sessions', json={})
0116 require(refused.status_code == 403, 'Cookie mutation without CSRF was not rejected')
0117 print('PASS browser identity, anonymous denial, cookie CSRF denial', flush=True)
0118 tokens_url = base + '/account/tokens/'
0119 human_token, revoke_id = issue(browser, tokens_url, ai=False)
0120 ai_token, _ = issue(browser, tokens_url, ai=True)
0121 args.ai_token_file.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
0122 fd = os.open(args.ai_token_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
0123 with os.fdopen(fd, 'w') as output:
0124 output.write(ai_token + '\n')
0125 machine.headers['Authorization'] = 'Bearer ' + human_token
0126 response = machine.get(tc + '/api/whoami')
0127 require(response.status_code == 200 and response.json() == human, 'Human token identity differs from browser')
0128 ai_response = machine.get(tc + '/api/whoami', headers={'Authorization': 'Bearer ' + ai_token})
0129 require(ai_response.status_code == 200, f'AI identity HTTP {ai_response.status_code}')
0130 ai = ai_response.json()
0131 require(ai['kind'] == 'ai' and ai['operator_id'] == human['participant_id'],
0132 'AI identity was not bound to its human operator')
0133 print('PASS human token and bound AI identity; AI token saved privately', flush=True)
0134 run = str(uuid.uuid4())
0135 response = machine.post(tc + '/api/comms/sessions', json={
0136 'native_id': run, 'client': 'program', 'host': 'ec2dev',
0137 'name': 'TeamComms public integration acceptance',
0138 })
0139 require(response.status_code == 200, f'Session registration HTTP {response.status_code}: {response.text}')
0140 session = response.json()['session_id']
0141 message_id = str(uuid.uuid4())
0142 sent = machine.post(tc + '/api/comms/messages', json={
0143 'message_id': message_id, 'content': 'TeamComms public proxy acceptance',
0144 'audience': {'session_ids': [session]},
0145 })
0146 require(sent.status_code == 200, f'Message publication HTTP {sent.status_code}')
0147 with machine.stream('GET', tc + '/api/comms/stream', params={'session_id': session}) as response:
0148 require(response.status_code == 200, f'Stream HTTP {response.status_code}')
0149 for event in events(response):
0150 if event.get('event') == 'message':
0151 cursor = event['id']
0152 break
0153 else:
0154 raise RuntimeError('Published message did not arrive on stream')
0155 print('PASS public message publication and immediate stream delivery', flush=True)
0156 started = time.monotonic()
0157 with machine.stream('GET', tc + '/api/comms/stream', params={'session_id': session},
0158 headers={'Last-Event-ID': cursor}) as response:
0159 require(response.status_code == 200, f'Reconnect HTTP {response.status_code}')
0160 stream = events(response)
0161 first = next(stream)
0162 require(first.get('event') == 'ready' and str(first['data']['after']) == cursor,
0163 'Last-Event-ID cursor was not preserved')
0164 page = browser.get(tokens_url)
0165 revoke = browser.post(tokens_url, data={
0166 'revoke': revoke_id, 'csrfmiddlewaretoken': Form(page.text).csrf,
0167 }, headers={'Referer': tokens_url})
0168 revoke.raise_for_status()
0169 for event in stream:
0170 require(event.get('event') != 'message', 'Reconnect replayed acknowledged cursor')
0171 if event.get('event') == 'error':
0172 require(event['data'].get('status') == 401, 'Revocation returned wrong stream error')
0173 break
0174 else:
0175 raise RuntimeError('Revoked stream remained open')
0176 require(machine.get(tc + '/api/whoami').status_code == 401, 'Revoked token remains usable')
0177 print(f'PASS reconnect cursor and live revocation ({time.monotonic() - started:.1f}s)', flush=True)
0178 print('Live acceptance session:', session)
0179
0180
0181 if __name__ == '__main__':
0182 main()