P08-03: 配置の通常操作受入

This commit is contained in:
Agent Control Runner
2026-10-15 00:12:44 +09:00
parent fd643f55a2
commit f5f85ae08c
6 changed files with 342 additions and 8 deletions
@@ -901,6 +901,18 @@ def check(project_root, evidence_dir):
require(os_proof['input_method'] == 'native_os' and os_proof['observer_mutations'] == [], 'fake OS provenance')
os_pids = [os_phase(n) for n in os_proof['phases']]
require(len(os_pids) == 2, 'OS separate PID pair')
role_actions = {}
for role, pid in zip(('writer', 'reader'), os_pids):
actions = [document(name) for name in index['artifacts']
if name.startswith('os-pair/') and name.endswith('/request.json')]
actions = sorted((a for a in actions if a.get('target_pid') == pid), key=lambda a: a['sequence'])
terminal = 'os-pair/'+role+'-terminal.json'
if terminal in index['artifacts']:
stopped = document(terminal)
require(not any(a['sequence'] > stopped['sequence'] for a in actions), 'terminal OS continuation')
require(False, 'terminal OS failure')
require([a['sequence'] for a in actions] == list(range(1, len(actions)+1)), 'OS request sequence gap')
role_actions[pid] = actions
os_rows = pair_gate(os_proof['directory'], os_pids, True)
lifecycle = []
for role, pid in zip(('writer','reader'),os_pids):
@@ -928,6 +940,30 @@ def check(project_root, evidence_dir):
require(ack['target_pid'] == request['target_pid'] == observed['pid'] == expected_pid
and ack['sequence'] == request['sequence'] == observed['sequence'], 'fake OS provenance')
require(ack['request_sha256'] == sha(artifact(folder+'request.json')), 'OS ack request mismatch')
if request['action'] == 'reveal_palette':
def finite(values, size):
return isinstance(values, list) and len(values) == size and all(
isinstance(v, (int, float)) and not isinstance(v, bool) and math.isfinite(v) for v in values)
def geometry(data):
bar = data.get('bar', {})
return (all(finite(data.get(k), 4) for k in ('target_rect', 'target_screen_rect', 'palette_rect', 'thumb_rect'))
and finite(data.get('viewport_to_screen'), 6) and isinstance(bar, dict)
and finite(bar.get('rect'), 4) and isinstance(bar.get('visible'), bool)
and finite([bar.get(k) for k in ('min_value', 'max_value', 'page', 'value')], 4))
require(geometry(request) and finite([request.get('scroll_before')], 1)
and request.get('direction') in ('left', 'right'), 'unverified palette reveal')
require(folder+'reveal.json' in index['artifacts'], 'unverified palette reveal')
revealed = document(folder+'reveal.json')
require(geometry(revealed) and revealed.get('visible_after') is True
and finite([revealed.get('scroll_after')], 1), 'unverified palette reveal')
x,y,w,h = revealed['target_rect']; px,py,pw,ph = revealed['palette_rect']
require(w >= 0 and h >= 0 and px <= x and py <= y and x+w <= px+pw and y+h <= py+ph,
'unverified palette reveal')
delta = revealed['scroll_after'] - request['scroll_before']
require(delta > 0 if request['direction'] == 'right' else delta < 0, 'palette reveal direction mismatch')
following = [a for a in role_actions[expected_pid] if a['sequence'] == request['sequence']+1]
require(len(following) == 1 and following[0]['action'] == 'button'
and following[0].get('node') == request.get('node') and request.get('node'), 'unverified palette reveal')
raw = document(ack['input_receipt'])
require(raw['method'] == 'native_os' and raw['target_pid'] == expected_pid and raw['timestamp_ns'] > 0
and raw['tool_name'] and raw['tool_call_id'], 'fake OS provenance')
@@ -98,6 +98,60 @@ class EvidenceTests(unittest.TestCase):
def test_positive_whole_checker(self):
self.assertEqual([], C.check(self.root, self.out))
def edit_os_request(self, folder, callback):
self.edit(folder+'/request.json', callback)
self.edit(folder+'/ack.json', lambda d: d.update(request_sha256=C.sha(self.out/(folder+'/request.json'))))
def test_reveal_missing_thumb(self):
self.edit_os_request('os-pair/supported_connected-1', lambda d: d.pop('thumb_rect'))
self.reject('unverified palette reveal')
def test_reveal_missing_bar(self):
self.edit_os_request('os-pair/supported_connected-1', lambda d: d.pop('bar'))
self.reject('unverified palette reveal')
def test_reveal_not_visible(self):
self.edit('os-pair/supported_connected-1/reveal.json', lambda d: d.update(visible_after=False))
self.reject('unverified palette reveal')
def test_reveal_target_outside(self):
self.edit('os-pair/supported_connected-1/reveal.json', lambda d: d.update(target_rect=[110,0,20,20]))
self.reject('unverified palette reveal')
def test_reveal_unchanged_scroll(self):
self.edit('os-pair/supported_connected-1/reveal.json', lambda d: d.update(scroll_after=0))
self.reject('palette reveal direction mismatch')
def test_reveal_opposite_scroll(self):
self.edit('os-pair/supported_connected-1/reveal.json', lambda d: d.update(scroll_after=-1))
self.reject('palette reveal direction mismatch')
def test_reveal_next_wrong_node(self):
self.edit_os_request('os-pair/supported_connected-2', lambda d: d.update(node='/other'))
self.reject('unverified palette reveal')
def test_reveal_next_not_button(self):
self.edit_os_request('os-pair/supported_connected-2', lambda d: d.update(action='framing'))
self.reject('unverified palette reveal')
def terminal(self, sequence):
F.put(self.out/'os-pair/writer-terminal.json', {'pid':303, 'role':'writer',
'sequence':sequence, 'stem':'writer-request', 'reason':'timeout'})
F.repin(self.out)
def test_terminal_failure(self):
sequences = [C.load(p)['sequence'] for p in (self.out/'os-pair').glob('*/request.json')]
self.terminal(max(sequences))
self.reject('terminal OS failure')
def test_terminal_continuation(self):
self.terminal(1)
self.reject('terminal OS continuation')
def test_os_sequence_gap(self):
self.edit_os_request('os-pair/supported_connected-2', lambda d: d.update(sequence=999))
self.reject('OS request sequence gap')
def action_row(self, label):
rows = C.load(self.out/'pair/writer-actions.json')['actions']
return copy.deepcopy(next(row for row in rows if row['id'] == label))