Guard followed route identity and expose current prepared playhead

This commit is contained in:
firestar5683
2026-09-20 10:58:08 -07:00
parent 0c3a94db23
commit 0f52f4c70e
5 changed files with 32 additions and 6 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ async function refresh(){
document.querySelector('#state').textContent=j.state+' · '+Math.max(0,j.audio_s||0).toFixed(1)+' s · '+(j.muted?'MUTED':'system audio output');
const follow=j.paired_controls?.following;
document.querySelector('#scope').textContent=follow?.enabled?'Galaxy is master · phone and comma controls are mirrored to this Mac':j.paired_controls?.enabled?'Mac + comma controls enabled · each button requests the same change on both':'Mac-only controls · comma is not paired in this session';
const followErrors={peer_session_changed:'Comma replay restarted · reconnect this Mac session',local_session_changed:'Mac replay changed · reconnect this session',peer_action_in_progress:'Waiting for the Mac request to reach Galaxy',local_replay_unready:'Waiting for the Mac replay',peer_not_ready_for_replay:'Waiting for a fresh comma replay',peer_status_stale:'Galaxy status is stale',peer_timeout:'Galaxy status timed out'};
const followErrors={peer_session_changed:'Comma replay restarted · reconnect this Mac session',local_session_changed:'Mac replay changed · reconnect this session',peer_route_mismatch:'Comma and Mac routes do not match',peer_route_changed:'Comma route changed · reconnect this Mac session',invalid_peer_route:'Galaxy route identity unavailable',peer_action_in_progress:'Waiting for the Mac request to reach Galaxy',local_replay_unready:'Waiting for the Mac replay',peer_not_ready_for_replay:'Waiting for a fresh comma replay',peer_status_stale:'Galaxy status is stale',peer_timeout:'Galaxy status timed out'};
document.querySelector('#following').textContent=!follow?.enabled?'':follow.status==='following'?'Mac controls match fresh Galaxy state':follow.status==='applying'?'Applying Galaxy controls on Mac':followErrors[follow.error]||'Galaxy control mirroring paused · status unavailable';
const local=j.demo.available?j.demo.mode+' / signals '+j.demo.signal_mode:'unready';
document.querySelector('#targets').textContent='Mac '+local+'; comma '+commaStatus(j.paired_controls);
+7 -1
View File
@@ -70,6 +70,8 @@ def control_server(project, out, shared, port, forwarder=None, *, follow_peer=Fa
following.update(status='pending',error='peer_action_in_progress',snapshot=None);return
demo=operator.demo_status()
if not demo['available']:raise ValueError('local_replay_unready')
local_route=demo.get('route') or module.read_json(out/'status.json').get('route')
if snapshot['route']!=local_route:raise ValueError('peer_route_mismatch')
if local_session is None:local_session=demo['session_id']
elif demo['session_id']!=local_session:raise ValueError('local_session_changed')
signature=(snapshot['session_id'],local_session,snapshot['mode'],snapshot['signal_mode'],sequence)
@@ -84,7 +86,8 @@ def control_server(project, out, shared, port, forwarder=None, *, follow_peer=Fa
following.update(status='following' if matching else 'applying',error=None,snapshot=snapshot)
except Exception as error:
known={'peer_action_in_progress','peer_session_changed','local_session_changed','local_replay_unready',
'peer_not_ready_for_replay','invalid_peer_session','forwarder_disabled'}
'peer_not_ready_for_replay','invalid_peer_session','forwarder_disabled','invalid_peer_route',
'peer_route_changed','peer_route_mismatch'}
reason=str(error) if str(error) in known else 'peer_timeout' if isinstance(error,TimeoutError) else 'peer_status_unavailable'
with control_lock:following.update(status='paused',error=reason,snapshot=None)
def follow():
@@ -235,6 +238,9 @@ def audio_worker(a):
if now-last_status>=.08:
shared.update(state='READY' if anchor else 'PREPARING',audio_s=max(0,(position or 0)/rate))
snapshot=dict(command_wall=now,input_mode='replay',compute='prepared-core',composer='ace',profile='prism',style='Prism',readiness=shared['state'],section='PREPARED PRISM',route=a.route,buffered=max(0,(len(audio)-(position or 0))/rate),presentation_session_id=session,demo_engagement_mode=controls.mode,demo_signal_mode=controls.signal_mode,engagement_presentation={'enabled':True},generation_invoked=False)
if anchor is not None and state['route_t'] is not None and np.isfinite(state['route_t']):
snapshot.update(route_t=float(state['route_t']),elapsed=max(0,(position or 0)/rate),
source_model_ns=int(sm.logMonoTime['modelV2']))
snapshot=delay.apply(snapshot,rendered)
# The command API must remain available during initial DAC lead-in.
snapshot.setdefault('engagement_presentation',{'enabled':True})
+6 -2
View File
@@ -141,6 +141,7 @@ class PairedDemoControls:
self._active = None
self._closed = False
self._bound_session = None
self._bound_route = None
self._write_revision = 0
def read_status(self):
@@ -162,12 +163,15 @@ class PairedDemoControls:
received = time.monotonic()
if received-started >= min(self.timeout,.75):raise TimeoutError()
session = _session(status)
route = status['demo'].get('route')
if not isinstance(route,str) or not route or route=='live':raise ValueError('invalid_peer_route')
with self._lock:
if self._closed:raise ValueError('forwarder_disabled')
if revision != self._write_revision or self._active is not None:raise ValueError('peer_action_in_progress')
if self._bound_session is None:self._bound_session=session
if self._bound_session is None:self._bound_session,self._bound_route=session,route
elif session != self._bound_session:raise ValueError('peer_session_changed')
return dict(session_id=session,mode=status['demo']['mode'],signal_mode=status['demo']['signal_mode'],
elif route != self._bound_route:raise ValueError('peer_route_changed')
return dict(session_id=session,route=route,mode=status['demo']['mode'],signal_mode=status['demo']['signal_mode'],
request_started_wall=started,received_wall=received,peer_status=status)
def _result(self, status, action, value, *, acknowledged=False, applied=False,
@@ -191,7 +191,7 @@ class ControlTests(unittest.TestCase):
def following_peer(self):
status=dict(available=True,offroad=True,state='READY',live={'enabled':False},
demo=dict(available=True,session_id='comma-session',mode='recorded',signal_mode='recorded'))
demo=dict(available=True,session_id='comma-session',route='fixture-route',mode='recorded',signal_mode='recorded'))
calls=[]
def transport(method,*args):
calls.append(method)
@@ -238,6 +238,13 @@ class ControlTests(unittest.TestCase):
self.wait_for(lambda:self.request()[1]['paired_controls']['following']['error']=='local_session_changed')
self.assertFalse((self.out/'demo_engagement.json').exists())
def test_different_local_route_cannot_receive_peer_controls(self):
peer,_=self.following_peer()
self.state['route']='other-route';self.write_state()
peer['demo']['mode']='engaged'
self.wait_for(lambda:self.request()[1]['paired_controls']['following']['error']=='peer_route_mismatch')
self.assertFalse((self.out/'demo_engagement.json').exists())
class RememberedConfigTests(unittest.TestCase):
def setUp(self):
@@ -16,7 +16,7 @@ from paired_demo_controls import GalaxyPeer, PairedDemoControls, _NoRedirect, _h
BASE = 'https://galaxy.firestar.link/ABCDEFGHIJKLMNOP'
COOKIE = 'ABCDEFGHIJKLMNOP%3A' + 'a' * 64
READY = {'available': True, 'offroad': True, 'state': 'READY', 'live': {'enabled': False},
'demo': {'available': True, 'session_id': 'independent-peer-session',
'demo': {'available': True, 'session_id': 'independent-peer-session', 'route':'fixture-route',
'mode': 'recorded', 'signal_mode': 'recorded'}}
@@ -153,6 +153,15 @@ class PairedTests(unittest.TestCase):
self.transport.status['demo']['readiness']='PREPARING'
with self.assertRaisesRegex(ValueError,'peer_not_ready_for_replay'):controls.read_status()
def test_follower_requires_and_pins_exact_route_identity(self):
controls=self.forwarder()
self.transport.status['demo'].pop('route')
with self.assertRaisesRegex(ValueError,'invalid_peer_route'):controls.read_status()
self.transport.status['demo']['route']='fixture-route'
self.assertEqual(controls.read_status()['route'],'fixture-route')
self.transport.status['demo']['route']='different-route'
with self.assertRaisesRegex(ValueError,'peer_route_changed'):controls.read_status()
def test_late_status_and_disabled_reader_never_return_applied_state(self):
controls = self.forwarder()
with patch('paired_demo_controls.time.monotonic',side_effect=[100.,101.]):