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

This commit is contained in:
Agent Control Runner
2026-10-14 05:51:27 +09:00
parent 4a17d0e0f5
commit d01286360e
21 changed files with 7225 additions and 79 deletions
+89 -8
View File
@@ -149,7 +149,80 @@ def keyed(rows, key):
require(len(result) == len(rows), 'duplicate '+key)
return result
def state(node):
def infrastructure(r, facilities, root):
"""Runtime separates schema proxies from FacilityService; audit live topology.
FacilityInfrastructureProjector._projection_from_project_input,
_projection_live_matches and _expected_stair_connector are the authority.
"""
kinds = {'guest_corridor', 'service_corridor', 'staff_door', 'guest_elevator',
'service_elevator', 'guest_stairs', 'service_stairs'}
proxies = {k:v for k,v in facilities.items() if v['definition_id'] in kinds}
projections = keyed(r['facility_infrastructure'].get('projections', []), 'instance_id')
require(set(projections) == set(proxies), 'missing/extra infrastructure projection')
entities = keyed(r['spatial_subslots'].get('entities', []), 'entity_id')
connectors = keyed(r['path'].get('connectors', []), 'id')
routes, links, shafts = set(), set(), set()
for fid, f in proxies.items():
p = projections[fid]; kind = f['definition_id']
for field in ('definition_id', 'origin', 'rotation', 'layer', 'source_job_id', 'completed_at'):
require(p[field] == f[field], 'infrastructure construction drift '+field)
route = 'infra.route:'+fid
link = 'infra.connector:'+fid if kind in {'staff_door','guest_stairs','service_stairs'} else ''
require(p['kind'] == kind and p['cells'] == f['occupied_cells'] and
p['route_entity_id'] == route and p['connector_id'] == link,
'wrong infrastructure projection')
# Definition inputs are independently read from the unchanged registry resources.
definition = (root/'data/facilities/infrastructure'/(kind+'.tres')).read_text()
tags = re.findall(r'&"([^"]+)"', re.search(r'^tags = (.+)$', definition, re.M)[1])
footprint = [int(v) for v in re.search(r'footprint = Vector2i\((\d+), (\d+)\)', definition).groups()]
require(p['definition_tags'] == sorted(tags) and p['footprint'] == footprint and
p['pass_through'] == ('pass_through = true' in definition) and
p['staff_only'] == (kind in {'service_corridor','staff_door','service_elevator','service_stairs'}),
'infrastructure definition drift')
canonical = 'projection|'
for field in ('instance_id','definition_id','kind','route_entity_id','connector_id','source_job_id'):
text = p[field]; canonical += '%d:%s|' % (len(text),text)
canonical += 'layer=%d|staff=%d|pass=%d|footprint=%d,%d|rotation=%d|completed=%d|' % (
p['layer'],p['staff_only'],p['pass_through'],*p['footprint'],p['rotation'],p['completed_at'])
canonical += 'origin=%d,%d,%d|tags=' % tuple(p['origin'])
canonical += ''.join('%d:%s;' % (len(t),t) for t in p['definition_tags'])+'|cells='
canonical += ''.join('%d,%d,%d;' % tuple(c) for c in p['cells'])
require(p['record_fingerprint'] == hashlib.sha256(canonical.encode()).hexdigest(), 'infrastructure fingerprint drift')
require(entities.get(route) == {'entity_id':route,'category':'ROUTE','cells':p['cells']}, 'wrong infrastructure ROUTE topology')
routes.add(route)
if link:
upper = p['origin']; lower = [upper[0],upper[1]-1,upper[2]]
stair = kind.endswith('stairs')
expected = {'id':link,'from':upper if stair else [upper[0],upper[1],0],
'to':lower if stair else upper,'cost':150 if stair else 120,
'bidirectional':True,'access_id':'staff' if p['staff_only'] else '',
'enabled':True,'movement_type':'CONNECTOR','dynamic_cost':0,
'operational_state':'OPERATIONAL','cabin_capacity':8,'queue_capacity':256,
'cabin_state':'IDLE','current_floor':upper[1],'travel_minutes_per_floor':1,'request_queue':[]}
require(connectors.get(link) == expected, 'wrong infrastructure connector topology')
if stair:
require(lower in r['route_projection']['projected_walkable_cells'], 'missing stair landing')
links.add(link)
if kind.endswith('elevator'):
shafts.add('infra.shaft:'+fid)
require({k for k in entities if k.startswith('infra.route:')} == routes, 'extra infrastructure ROUTE topology')
require({k for k in connectors if k.startswith('infra.connector:')} == links, 'extra infrastructure connector topology')
blockers = keyed(r['path'].get('vertical_walk_blockers', []), 'owner_id')
require({k for k in blockers if k.startswith('infra.shaft:')} == shafts, 'wrong infrastructure shaft topology')
for fid, p in projections.items():
if 'infra.shaft:'+fid in shafts:
require(blockers['infra.shaft:'+fid]['cells'] == p['cells'], 'wrong infrastructure shaft cells')
if entities or proxies:
route_cells = {tuple(c) for e in entities.values() if e['category'] == 'ROUTE' for c in e['cells']}
projection = r['route_projection']
require(route_cells == {tuple(c) for c in projection['projected_route_cells']}, 'wrong projected ROUTE cells')
walkable = route_cells | {tuple(c) for c in projection['base_walkable_cells']}
require(walkable == {tuple(c) for c in projection['projected_walkable_cells']} ==
{tuple(c['cell']) for c in r['path']['cell_traffic']}, 'wrong projected walkable topology')
return proxies
def state(node, root=HERE.parent):
snap = decode(node)
require(set(snap) == {'schema_version', 'clock', 'game_state', 'systems'}, 'missing whole snapshot envelope')
r = snap['systems']['runtime']
@@ -170,7 +243,8 @@ def state(node):
require(not seen.intersection(actual), 'overlapping occupancy')
seen.update(actual)
operational = keyed(r['facilities']['instances'], 'instance_id')
require(set(operational) == set(facilities), 'partial demolition/completion')
proxies = infrastructure(r, facilities, root)
require(set(operational) == set(facilities)-set(proxies), 'partial demolition/completion')
for key, row in operational.items():
require(row['occupied_cells'] == facilities[key]['occupied_cells'], 'operational cells drift')
return snap
@@ -209,10 +283,10 @@ def preserve(a, b, results):
for key in ('pending', 'in_flight'):
require(nq[key] == oq[key], 'refusal queue loss')
def action_gate(row, typed):
def action_gate(row, typed, root=HERE.parent):
coordinates(row['position'])
before, after = typed(row['before']), typed(row['after'])
a, b = state(before), state(after)
a, b = state(before, root), state(after, root)
require(row['position']['bounds'] == a['systems']['runtime']['world']['bounds'], 'invented world bounds')
kind, label = row['kind'], row['id']
expected_kind = 'camera' if label == 'pan_zoom' else 'cancel' if label == 'cancel_demolition' else 'demolish' if label in ('demolition','refused_demolition') else 'build'
@@ -256,7 +330,7 @@ def action_gate(row, typed):
resources_a, resources_b = a['game_state']['resources'], b['game_state']['resources']
require(set(resources_a) == set(resources_b) and all(resources_b[k] == v + refund.get(k, 0) for k, v in resources_a.items()), 'demolition refund mismatch')
if kind == 'build' and not row['expected']:
completed = state(typed(row['completed']))
completed = state(typed(row['completed']), root)
facilities = completed['systems']['runtime']['construction']['facilities']
require(any(f['definition_id'] == row['definition'] and f['origin'] == row['position']['world_cell'] for f in facilities), 'construction completion missing')
require(row['report']['can_build'], 'positive support invalid')
@@ -426,7 +500,7 @@ def check(project_root, evidence_dir):
require(total == budget['needed'] and delta == budget['deltas'], 'diagnostic budget mismatch')
for row in doc['actions']:
require(row['input_method'] == ('native_os' if native_os else 'handler'), 'fake OS provenance')
action_gate(row, typed)
action_gate(row, typed, root)
if row['kind'] == 'build':
wanted_definition = 'standard_room' if row['id'] == 'unsupported' else 'guest_stairs' if row['id'] in ('missing_connection','lower_stair','upper_stair') else 'reception'
require(row['definition'] == wanted_definition, 'diagnostic definition substituted')
@@ -439,12 +513,12 @@ def check(project_root, evidence_dir):
width,height = map(int,re.search(r'^footprint = Vector2i\((\d+), (\d+)\)$',text,re.M).groups())
x,y,z = row['position']['world_cell']
cells = {(x+dx,y+dy,z) for dx in range(width) for dy in range(height)}
completed = state(typed(row['completed']))['systems']['runtime']['construction']['facilities']
completed = state(typed(row['completed']), root)['systems']['runtime']['construction']['facilities']
matches = [f for f in completed if f['origin'] == [x,y,z] and f['definition_id'] == wanted_definition]
require(len(matches) == 1 and {tuple(c) for c in matches[0]['occupied_cells']} == cells, 'production footprint mismatch')
rows += doc['actions']
checkpoint = typed('writer-checkpoint')
state(checkpoint)
state(checkpoint, root)
formal = typed('writer-formal-model')
require(not typed_differences(typed('writer-presave'),formal), 'wrong presave formal model')
require(sha(artifact(directory+'/writer-presave.var')) == sha(artifact(directory+'/writer-formal-model.var')), 'wrong raw presave formal model')
@@ -573,6 +647,13 @@ def check(project_root, evidence_dir):
require(request['world_cell'] == row['position']['world_cell']
and request['viewport'] == row['position']['viewport']
and request['screen'] == row['position']['screen'], 'OS transformed cell drift')
matrix = request['viewport_to_screen']; x,y = request['viewport']
require(len(matrix) == 6 and all(math.isfinite(v) for v in matrix), 'invalid OS canvas mapping')
require(all(abs(a-b) < 0.001 for a,b in zip(request['screen'],
[matrix[0]*x+matrix[2]*y+matrix[4],matrix[1]*x+matrix[3]*y+matrix[5]])), 'OS canvas mapping drift')
if row['id'] == 'unsupported' and request['guide_visible']:
gx,gy,gw,gh = request['guide_rect']
require(not (gx <= x < gx+gw and gy <= y < gy+gh), 'OS guide interception')
if request['action'] == 'wheel':
wheel_requests.append(request)
if row['id'] == 'pan_zoom':
@@ -13,6 +13,8 @@ import shutil
import contextlib
import io
import os
import gzip
import hashlib
sys.dont_write_bytecode = True
ROOT = Path(__file__).resolve().parents[1]
@@ -331,6 +333,25 @@ class EvidenceTests(unittest.TestCase):
self.edit('os-pair/unsupported-1/ack.json', lambda d: d.update(request_sha256='0'*64))
self.reject('OS ack request mismatch')
def test_os_canvas_mapping_drift(self):
self.edit('os-pair/unsupported-1/request.json', lambda d: d.update(viewport_to_screen=[1,0,0,1,32,0]))
# Repin acknowledgement too: test semantics, rather than checksum rejection.
self.edit('os-pair/unsupported-1/ack.json', lambda d: d.update(request_sha256=C.sha(self.out/'os-pair/unsupported-1/request.json')))
self.reject('OS canvas mapping drift')
def test_os_guide_interception(self):
self.edit('os-pair/unsupported-1/request.json', lambda d: d.update(guide_visible=True,guide_rect=[0,0,10000,10000]))
self.edit('os-pair/unsupported-1/ack.json', lambda d: d.update(request_sha256=C.sha(self.out/'os-pair/unsupported-1/request.json')))
self.reject('OS guide interception')
def test_whole_checker_missing_stair_projection(self):
self.edit('pair/writer-lower_stair-completed.json', lambda d: C.typed_member(d,'systems','runtime','facility_infrastructure','projections')['items'].pop())
self.reject('missing/extra infrastructure projection')
def test_whole_checker_wrong_stair_connector(self):
self.edit('pair/writer-lower_stair-completed.json', lambda d: C.typed_member(C.typed_member(d,'systems','runtime','path','connectors')['items'][0],'cost').update(text='999'))
self.reject('wrong infrastructure connector topology')
def test_refusal_cannot_be_relabelled(self):
self.edit('pair/writer-actions.json', lambda d: d['actions'][4].update(expected='OTHER_REASON'))
self.reject('required refusal reason changed')
@@ -411,5 +432,70 @@ class EvidenceTests(unittest.TestCase):
def test_mock_native_timeout_restores_project_and_cleans_owned_child(self):
self.collector_mock(fail_wait=True)
class RealR05InfrastructureTests(unittest.TestCase):
"""Actual native snapshot, never a fabricated acceptance specimen."""
def setUp(self):
folder = ROOT/'tests/fixtures/p08-03'
provenance = json.loads((folder/'r05-reader-checkpoint-provenance.json').read_bytes())
raw = gzip.decompress((folder/'r05-reader-checkpoint.json.gz').read_bytes())
self.assertEqual(provenance['raw_sha256'],hashlib.sha256(raw).hexdigest())
self.assertEqual(provenance['raw_bytes'],len(raw))
self.node = json.loads(raw)
self.model = C.decode(self.node)
def test_real_reader_checkpoint(self):
self.assertEqual(self.model,C.state(self.node))
def reject(self, mutate, message):
runtime = self.model['systems']['runtime']; mutate(runtime)
with self.assertRaisesRegex(ValueError,message): C.state(F.typed(self.model))
def test_missing_projection(self):
self.reject(lambda r:r['facility_infrastructure']['projections'].pop(),'missing/extra infrastructure projection')
def test_extra_projection(self):
def mutate(r):
p=copy.deepcopy(r['facility_infrastructure']['projections'][0]); p['instance_id']='ghost'
r['facility_infrastructure']['projections'].append(p)
self.reject(mutate,'missing/extra infrastructure projection')
def test_wrong_projected_cells(self):
self.reject(lambda r:r['facility_infrastructure']['projections'][0].update(cells=[[9,9,0]]),'wrong infrastructure projection')
def test_missing_route(self):
self.reject(lambda r:r['spatial_subslots']['entities'].pop(),'wrong infrastructure ROUTE topology')
def test_extra_route(self):
self.reject(lambda r:r['spatial_subslots']['entities'].append({'entity_id':'infra.route:ghost','category':'ROUTE','cells':[[8,4,0]]}),'extra infrastructure ROUTE topology')
def test_wrong_route(self):
self.reject(lambda r:r['spatial_subslots']['entities'][0].update(category='UTILITY'),'wrong infrastructure ROUTE topology')
def test_missing_connector(self):
self.reject(lambda r:r['path']['connectors'].pop(0),'wrong infrastructure connector topology')
def test_extra_connector(self):
def mutate(r):
c=copy.deepcopy(r['path']['connectors'][0]); c['id']='infra.connector:ghost'; r['path']['connectors'].append(c)
self.reject(mutate,'extra infrastructure connector topology')
def test_wrong_connector(self):
self.reject(lambda r:r['path']['connectors'][0].update(to=[8,0,0]),'wrong infrastructure connector topology')
def test_wrong_projected_walkable(self):
self.reject(lambda r:r['route_projection']['projected_walkable_cells'].pop(),'wrong projected walkable topology')
def test_wrong_path_cells(self):
self.reject(lambda r:r['path']['cell_traffic'].pop(),'wrong projected walkable topology')
def test_ordinary_facility_loss(self):
self.reject(lambda r:r['facilities']['instances'].clear(),'partial demolition/completion')
def test_proxy_occupancy_loss(self):
self.reject(lambda r:r['world']['occupancies'].pop(),'orphan/lost occupancy')
def test_proxy_operational_duplication(self):
self.reject(lambda r:r['facilities']['instances'].append(copy.deepcopy(r['construction']['facilities'][0])),'partial demolition/completion')
if __name__ == '__main__':
unittest.main()